| |
| """ |
| 获取.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]) |
| |
| |
| 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) |
| |
| |
| bpy.ops.wm.open_mainfile(filepath=args.blend) |
| |
| |
| 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) |
| |
| |
| 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) |
| |
| |
| 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) |
| |
| |
| 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" |
| } |
| |
| |
| print(f"[BOUNDS] {json.dumps(result)}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|