cmevs-code / pipelines /get_blend_bounds.py
anon-cmevs-2026's picture
Initial code release for NeurIPS 2026 D&B reviewer reference
5c1bb37 verified
#!/usr/bin/env python3
"""
获取.blend文件的场景信息(相机位置、边界框)
此脚本在Blender内部执行,用于获取.blend文件的场景信息。
输出JSON格式的数据供batch_render.py使用。
智能检测策略:
1. 如果场景中有相机,使用相机位置作为第一帧参考
2. 如果没有相机,使用室内对象(Floor、Wall、Room等)的边界框计算中心
使用方法:
blender --background --python get_blend_bounds.py -- \
--blend "path/to/scene.blend"
输出格式:
[BOUNDS] {"bounds_min": [...], "bounds_max": [...], "center": [...], "camera": {...}}
"""
import bpy
import sys
import json
import argparse
from mathutils import Vector
# 室内对象关键词
INDOOR_KEYWORDS = [
'room', 'interior', 'floor', 'wall', 'ceiling', 'indoor',
'kitchen', 'bathroom', 'bedroom', 'living', 'dining',
'door', 'window', 'lamp', 'light', 'sofa', 'table', 'chair',
'bed', 'desk', 'cabinet', 'shelf', 'carpet', 'curtain',
'stair', 'corridor', 'hallway', 'closet', 'wardrobe'
]
def parse_args():
"""解析命令行参数"""
argv = sys.argv
if "--" in argv:
argv = argv[argv.index("--") + 1:]
else:
argv = []
parser = argparse.ArgumentParser(description="获取.blend文件场景信息")
parser.add_argument("--blend", type=str, required=True,
help=".blend文件路径")
return parser.parse_args(argv)
def is_indoor_object(obj_name):
"""判断对象是否是室内对象"""
name_lower = obj_name.lower()
return any(kw in name_lower for kw in INDOOR_KEYWORDS)
def get_scene_cameras():
"""
获取场景中的所有相机
Returns:
cameras: 相机信息列表 [{"name": str, "position": [x,y,z], "rotation": [x,y,z]}, ...]
"""
cameras = []
for obj in bpy.context.scene.objects:
if obj.type == 'CAMERA':
loc = obj.location
rot = obj.rotation_euler
cameras.append({
"name": obj.name,
"position": [loc.x, loc.y, loc.z],
"rotation": [rot.x, rot.y, rot.z]
})
return cameras
def get_scene_bounds(indoor_only=True):
"""
获取场景中mesh物体的边界框(Blender坐标系:X右, Y前, Z上)
Args:
indoor_only: 是否仅考虑室内对象(默认True)
Returns:
bounds_min: 边界框最小坐标 [x, y, z]
bounds_max: 边界框最大坐标 [x, y, z]
center: 几何中心 [x, y, z]
indoor_count: 室内对象数量
"""
min_coords = [float('inf'), float('inf'), float('inf')]
max_coords = [float('-inf'), float('-inf'), float('-inf')]
mesh_count = 0
indoor_count = 0
for obj in bpy.context.scene.objects:
if obj.type == 'MESH':
mesh_count += 1
# 如果启用室内过滤,检查对象名称
if indoor_only and not is_indoor_object(obj.name):
continue
indoor_count += 1
# 获取世界坐标下的边界框
for corner in obj.bound_box:
world_corner = obj.matrix_world @ Vector(corner)
for i in range(3):
min_coords[i] = min(min_coords[i], world_corner[i])
max_coords[i] = max(max_coords[i], world_corner[i])
# 如果没有找到任何符合条件的mesh
if min_coords[0] == float('inf'):
if indoor_only:
print(f"[WARN] 未找到室内对象,回退到全场景边界框", file=sys.stderr)
return get_scene_bounds(indoor_only=False)
else:
print(f"[WARN] 未找到任何mesh对象,使用默认边界框", file=sys.stderr)
min_coords = [-5, -5, 0]
max_coords = [5, 5, 3]
indoor_count = 0
# 计算几何中心
center = [
(min_coords[0] + max_coords[0]) / 2,
(min_coords[1] + max_coords[1]) / 2,
(min_coords[2] + max_coords[2]) / 2
]
mode_str = "室内对象" if indoor_only else "全场景"
print(f"[INFO] 边界框模式: {mode_str}", file=sys.stderr)
print(f"[INFO] 找到 {indoor_count}/{mesh_count} 个对象", file=sys.stderr)
print(f"[INFO] 边界框: min={[f'{x:.2f}' for x in min_coords]}, max={[f'{x:.2f}' for x in max_coords]}", file=sys.stderr)
print(f"[INFO] 几何中心: [{center[0]:.2f}, {center[1]:.2f}, {center[2]:.2f}]", file=sys.stderr)
return min_coords, max_coords, center, indoor_count
def main():
args = parse_args()
print(f"[INFO] 打开.blend文件: {args.blend}", file=sys.stderr)
# 打开.blend文件
bpy.ops.wm.open_mainfile(filepath=args.blend)
# 1. 检测场景中的相机
cameras = get_scene_cameras()
print(f"[INFO] 检测到 {len(cameras)} 个相机", file=sys.stderr)
camera_info = None
first_frame_position = None
position_source = "none"
if cameras:
# 使用第一个相机的位置作为参考
cam = cameras[0]
camera_info = cam
first_frame_position = cam["position"]
position_source = "camera"
print(f"[INFO] 使用相机 '{cam['name']}' 的位置作为第一帧参考", file=sys.stderr)
print(f"[INFO] 相机位置: [{cam['position'][0]:.2f}, {cam['position'][1]:.2f}, {cam['position'][2]:.2f}]", file=sys.stderr)
# 2. 获取室内对象边界框
bounds_min, bounds_max, center, indoor_count = get_scene_bounds(indoor_only=True)
# 如果没有相机,使用室内边界框中心
if first_frame_position is None:
first_frame_position = center
position_source = "indoor_bounds_center"
print(f"[INFO] 无相机,使用室内边界框中心作为第一帧位置", file=sys.stderr)
# 3. 获取场景单位比例(用于将米转换为场景单位)
unit_scale = bpy.context.scene.unit_settings.scale_length
unit_system = bpy.context.scene.unit_settings.system
print(f"[INFO] 场景单位: scale={unit_scale}, system={unit_system}", file=sys.stderr)
# 输出JSON格式(供batch_render.py解析)
result = {
"bounds_min": bounds_min,
"bounds_max": bounds_max,
"center": center,
"first_frame_position": first_frame_position,
"position_source": position_source,
"camera": camera_info,
"cameras_count": len(cameras),
"indoor_objects_count": indoor_count,
"unit_scale": unit_scale, # 用于米->场景单位转换
"coordinate_system": "blender_z_up" # X右, Y前, Z上
}
# 使用特殊前缀,便于batch_render.py解析
print(f"[BOUNDS] {json.dumps(result)}")
if __name__ == "__main__":
main()