File size: 6,850 Bytes
5c1bb37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#!/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()