Spaces:
Paused
Paused
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() |