File size: 12,917 Bytes
f187247
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
import argparse
import subprocess
import tempfile
from pathlib import Path
import sys
import os
import platform

root_dir = Path(__file__).parents[2]
sys.path.append(str(root_dir))

# 根据操作系统自动检测Blender路径
def _get_blender_path():
    """自动检测Blender路径(支持Mac/Linux)"""
    system = platform.system()

    # Mac系统常见的Blender安装路径
    if system == 'Darwin':
        possible_paths = [
            '/Applications/Blender.app/Contents/MacOS/Blender',
            '/Applications/Blender 3.6/Blender.app/Contents/MacOS/Blender',
            '/Applications/Blender 4.0/Blender.app/Contents/MacOS/Blender',
            os.path.expanduser('~/Applications/Blender.app/Contents/MacOS/Blender'),
        ]
        for path in possible_paths:
            if os.path.exists(path):
                print(f"✓ 找到Blender: {path}")
                return path

        # 如果没找到,提示用户安装
        print("⚠️  未找到Blender!")
        print("请从以下网址下载并安装Blender 3.6 LTS:")
        print("https://www.blender.org/download/lts/3-6/")
        print("安装到 /Applications/ 目录后重新运行。")
        return None

    # Linux系统(优先检测系统安装的Blender)
    else:
        # 首先检查通过apt/系统包管理器安装的Blender
        system_paths = [
            '/usr/bin/blender',
            '/usr/local/bin/blender',
            '/snap/bin/blender',
        ]

        for path in system_paths:
            if os.path.exists(path):
                print(f"✓ 找到系统Blender: {path}")
                return path

        # 检查手动下载的版本
        linux_path = '/tmp/blender-3.6.5-linux-x64/blender'
        if os.path.exists(linux_path):
            print(f"✓ 找到Blender: {linux_path}")
            return linux_path

        # 在Hugging Face Spaces等环境中,Blender应该通过packages.txt安装
        # 因此不需要自动下载安装,直接返回系统路径
        print("⚠️  未找到Blender,尝试使用系统默认路径...")
        return '/usr/bin/blender'  # 假设通过packages.txt安装到系统路径

BLENDER_PATH = _get_blender_path()

if BLENDER_PATH and os.path.exists(BLENDER_PATH):
    # 验证Blender版本
    try:
        version_check = subprocess.run(
            [BLENDER_PATH, '--version'],
            capture_output=True,
            text=True,
            timeout=10
        )
        if version_check.returncode == 0:
            version_info = version_check.stdout.strip().split('\n')[0]
            print(f"✓ Blender版本: {version_info}")
    except Exception as e:
        print(f"⚠️  Blender版本检查失败: {e}")

def render_bricks_safe(
        in_file: str,
        out_file: str,
        reposition_camera: bool = True,
        square_image: bool = True,
        instructions_look: bool = False,
        fov: float = 45,
        img_resolution: int = 512,
) -> None:
    in_file = os.path.abspath(in_file)
    out_file = os.path.abspath(out_file)

    # 确保输出目录存在
    out_dir = os.path.dirname(out_file)
    Path(out_dir).mkdir(parents=True, exist_ok=True)
    os.chmod(out_dir, 0o755)

    ldraw_lib_path = os.environ.get('LDRAW_LIBRARY_PATH')
    if not ldraw_lib_path or not os.path.exists(ldraw_lib_path):
        ldraw_lib_path = Path.home() / 'ldraw'
    ldraw_lib_path = os.path.abspath(ldraw_lib_path)
    
    print(f"LDraw库路径: {ldraw_lib_path}")
    print(f"LDraw库是否存在: {os.path.exists(ldraw_lib_path)}")

    # 修复布尔值格式化问题
    blender_script = '''
import bpy
import math
import os
import sys
from pathlib import Path
import traceback

print("=== Blender脚本开始执行 ===")

# 添加ImportLDraw路径
sys.path.append("{import_ldraw_path}")
print(f"添加的路径: {{sys.path[-1]}}")

try:
    print("尝试导入ImportLDraw...")
    import ImportLDraw
    from ImportLDraw.loadldraw.loadldraw import Options, Configure, loadFromFile, FileSystem
    print("ImportLDraw导入成功")
    print(f"ImportLDraw文件位置: {{ImportLDraw.__file__}}")
except ImportError as import_error:
    print(f"ImportLDraw导入失败: {{import_error}}")
    traceback.print_exc()
    sys.exit(1)

try:
    plugin_path = Path(ImportLDraw.__file__).parent
    print(f"插件路径: {{plugin_path}}")
    
    print("Blender内部输出路径: " + "{output_file}")
    print("输出目录是否存在: " + str(os.path.exists(os.path.dirname("{output_file}"))))
    
    # 检查输入文件
    print(f"输入文件路径: {{'{input_file}'}}")
    print(f"输入文件是否存在: {{os.path.exists('{input_file}')}}")
    
    if os.path.exists("{input_file}"):
        with open("{input_file}", "r") as f:
            content = f.read()
            print(f"LDR文件内容长度: {{len(content)}}")
            print("文件前几行:")
            for i, line in enumerate(content.split('\\n')[:5]):
                print(f"  {{i+1}}: {{line}}")

    # 设置选项 - 修复布尔值
    Options.ldrawDirectory = "{ldraw_path}"
    Options.instructionsLook = {instructions_look_bool}  # 直接使用布尔值
    Options.useLogoStuds = True
    Options.useUnofficialParts = True
    Options.gaps = True
    Options.studLogoDirectory = os.path.join(str(plugin_path), 'studs')
    Options.LSynthDirectory = os.path.join(str(plugin_path), 'lsynth')
    Options.verbose = 1
    Options.overwriteExistingMaterials = True
    Options.overwriteExistingMeshes = True
    Options.scale = 0.01
    Options.createInstances = True
    Options.removeDoubles = True
    Options.positionObjectOnGroundAtOrigin = True
    Options.flattenHierarchy = False
    Options.edgeSplit = True
    Options.addBevelModifier = True
    Options.bevelWidth = 0.5
    Options.addEnvironmentTexture = True
    Options.scriptDirectory = os.path.join(str(plugin_path), 'loadldraw')
    Options.addWorldEnvironmentTexture = True
    Options.addGroundPlane = True
    Options.setRenderSettings = True
    Options.removeDefaultObjects = True
    Options.positionCamera = {reposition_camera_bool}  # 直接使用布尔值
    Options.cameraBorderPercent = 0.05

    print("开始Configure...")
    Configure()
    print("Configure执行成功")

    print("清理场景...")
    bpy.ops.object.select_all(action='DESELECT')
    bpy.ops.object.select_by_type(type='MESH')
    bpy.ops.object.delete()
    print("场景清理完成")

    print("尝试加载LDR文件...")
    ldr_file = FileSystem.locate("{input_file}")
    print(f"找到LDR文件: {{ldr_file}}")
    loadFromFile(None, ldr_file)
    print("LDR文件加载成功")
    
    # 检查加载的对象
    mesh_objects = [obj for obj in bpy.data.objects if obj.type == 'MESH']
    print(f"加载的网格对象数量: {{len(mesh_objects)}}")
    
    if len(mesh_objects) == 0:
        print("警告: 没有加载任何网格对象!")
    else:
        for obj in mesh_objects[:3]:  # 只显示前3个对象
            print(f"  对象: {{obj.name}}, 顶点数: {{len(obj.data.vertices) if obj.data else 0}}")

    scene = bpy.context.scene
    if {square_image_bool}:  # 直接使用布尔值
        scene.render.resolution_x = {img_resolution}
        scene.render.resolution_y = {img_resolution}
        if scene.camera:
            scene.camera.data.angle = math.radians({fov})
        else:
            print("警告:场景中没有相机,创建默认相机")
            bpy.ops.object.camera_add(location=(5, -5, 3))
            scene.camera = bpy.context.active_object

    scene.render.engine = 'CYCLES'
    scene.cycles.samples = 512
    scene.cycles.device = 'GPU'
    scene.render.image_settings.file_format = 'PNG'
    scene.render.filepath = "{output_file}"
    scene.render.use_overwrite = True

    if sys.platform == 'darwin':
        compute_device_type = 'METAL'
    else:
        compute_device_type = 'CUDA'

    print("配置GPU...")
    bpy.context.preferences.addons['cycles'].preferences.compute_device_type = compute_device_type
    bpy.context.preferences.addons['cycles'].preferences.get_devices()
    gpu_used = False
    for device in bpy.context.preferences.addons['cycles'].preferences.devices:
        if device['name'].startswith(('NVIDIA', 'Apple')):
            device['use'] = 1
            gpu_used = True
            print("启用GPU: " + device['name'])
        else:
            device['use'] = 0
    if not gpu_used:
        print("警告:未找到可用GPU,使用CPU渲染")

    print("开始渲染...")
    result = bpy.ops.render.render(write_still=True)
    print("渲染执行结果: " + str(result))
    print("渲染后文件是否存在: " + str(os.path.exists("{output_file}")))
    
    if os.path.exists("{output_file}"):
        file_size = os.path.getsize("{output_file}")
        print("文件生成成功: " + str(file_size) + " 字节")
    else:
        print("严重错误:渲染完成但未生成文件")
        
    print("=== Blender脚本执行完成 ===")

except Exception as e:
    print(f"脚本执行过程中发生错误: {{e}}")
    traceback.print_exc()
    sys.exit(1)
'''

    # 写入临时脚本 - 修复布尔值格式化
    with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
        script_content = blender_script.format(
            import_ldraw_path=str(root_dir),
            ldraw_path=ldraw_lib_path,
            instructions_look_bool=str(instructions_look),  # 直接使用Python布尔值
            reposition_camera_bool=str(reposition_camera),   # 直接使用Python布尔值
            square_image_bool=str(square_image),             # 直接使用Python布尔值
            fov=fov,
            img_resolution=img_resolution,
            input_file=in_file,
            output_file=out_file
        )
        f.write(script_content)
        script_path = f.name
    
    try:
        # 检查Blender路径是否存在
        if not BLENDER_PATH or not os.path.exists(BLENDER_PATH):
            print("⚠️  跳过渲染:未找到Blender")
            print("如需3D渲染功能,请安装Blender 3.6+")
            # 创建一个占位图片,避免程序崩溃
            from PIL import Image, ImageDraw, ImageFont
            img = Image.new('RGB', (512, 512), color=(240, 240, 240))
            draw = ImageDraw.Draw(img)
            draw.text((150, 250), "Blender未安装\n渲染功能不可用", fill=(100, 100, 100))
            img.save(out_file)
            print(f"已生成占位图片: {out_file}")
            return
        
        cmd = [
            BLENDER_PATH, '--background', #'--factory-startup',
            '--python', script_path, #'--enable-autoexec'
        ]
        
        print(f"执行命令: {' '.join(cmd)}")
        
        result = subprocess.run(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            timeout=300
        )
        
        # 输出Blender日志
        if result.stdout:
            print("=== Blender标准输出 ===")
            print(result.stdout)
        
        if result.stderr:
            print("=== Blender错误输出 ===")
            print(result.stderr)
        
        if result.returncode != 0:
            raise RuntimeError(f"Blender返回错误码: {result.returncode}")
        
        if not os.path.exists(out_file):
            raise FileNotFoundError(f"渲染失败:未生成输出文件 {out_file}")
            
        # 验证文件大小
        file_size = os.path.getsize(out_file)
        if file_size < 100:
            raise RuntimeError(f"生成的文件无效(大小:{file_size}字节)")
        
        print(f"渲染成功!文件大小: {file_size} 字节")
            
    except subprocess.TimeoutExpired:
        raise TimeoutError("渲染超时(超过5分钟)")
    except Exception as e:
        raise e
    finally:
        if os.path.exists(script_path):
            os.unlink(script_path)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--in_file', type=str, required=True, help='Path to LDR file')
    parser.add_argument('--out_file', type=str, required=True, help='Path to output image file')
    args = parser.parse_args()

    # 检查输入文件是否存在
    if not os.path.exists(args.in_file):
        print(f"错误: 输入文件不存在: {args.in_file}")
        return
    
    # 获取输入文件的绝对路径
    in_file = os.path.abspath(args.in_file)
    out_file = os.path.abspath(args.out_file)
    
    print(f"输入文件: {in_file}")
    print(f"输出文件: {out_file}")
    
    try:
        render_bricks_safe(in_file, out_file, square_image=True, instructions_look=False)
        print(f'成功渲染图像到: {out_file}')
    except Exception as e:
        print(f'渲染失败: {e}')

if __name__ == '__main__':
    main()