Files
BL/toModel.py
2025-08-23 09:27:42 +08:00

368 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import bpy
import json
import mathutils
import os
import bmesh
import sys
# 获取命令行参数
def get_json_path():
"""从命令行参数获取JSON文件路径"""
if len(sys.argv) > 5: # Blender传递的参数格式: blender --background --python script.py -- json_path
# 查找 -- 分隔符后的参数
for i, arg in enumerate(sys.argv):
if arg == "--" and i + 1 < len(sys.argv):
return sys.argv[i + 1]
# 如果没有找到参数,使用默认路径
return "./e8caffb4622e03b1495bbc1ed13fce13.json"
# 清空场景
bpy.ops.wm.read_factory_settings(use_empty=True)
# 设置渲染引擎为Cycles更好的材质渲染
bpy.context.scene.render.engine = 'CYCLES'
# 加载 JSON 文件
json_path = get_json_path()
print(f"尝试加载JSON文件{json_path}")
try:
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
print(f"✅ 成功加载JSON文件{json_path}")
print(f"包含 {len(data['children'])} 个对象")
except FileNotFoundError:
print(f"❌ 错误找不到JSON文件 {json_path}")
sys.exit(1)
except json.JSONDecodeError as e:
print(f"❌ 错误JSON文件格式错误 {e}")
sys.exit(1)
except Exception as e:
print(f"❌ 错误加载JSON文件时出错 {e}")
sys.exit(1)
# 创建金属材质
def create_metal_material():
mat = bpy.data.materials.new(name="metal")
mat.use_nodes = True
bsdf = mat.node_tree.nodes["Principled BSDF"]
bsdf.inputs["Metallic"].default_value = 1.0
bsdf.inputs["Roughness"].default_value = 0.2
bsdf.inputs["Base Color"].default_value = (0.8, 0.8, 0.9, 1.0) # 淡蓝色金属
return mat
# 创建透明材质(用于 nothing
def create_nothing_material():
mat = bpy.data.materials.new(name="nothing")
mat.use_nodes = True
bsdf = mat.node_tree.nodes["Principled BSDF"]
bsdf.inputs["Alpha"].default_value = 0.1
bsdf.inputs["Base Color"].default_value = (1.0, 1.0, 1.0, 0.1)
mat.blend_method = 'BLEND'
mat.show_transparent_back = False
return mat
# 创建发光材质(用于特殊对象)
def create_emissive_material():
mat = bpy.data.materials.new(name="emissive")
mat.use_nodes = True
bsdf = mat.node_tree.nodes["Principled BSDF"]
# 在Blender 4.0+中,发光属性可能位置不同
try:
if "Emission" in bsdf.inputs:
bsdf.inputs["Emission"].default_value = (1.0, 0.8, 0.2, 1.0)
elif "Emission Color" in bsdf.inputs:
bsdf.inputs["Emission Color"].default_value = (1.0, 0.8, 0.2, 1.0)
if "Emission Strength" in bsdf.inputs:
bsdf.inputs["Emission Strength"].default_value = 2.0
else:
# 备用方案:使用较亮的基础颜色
bsdf.inputs["Base Color"].default_value = (1.0, 0.8, 0.2, 1.0)
except Exception as e:
print(f"设置发光材质时出错: {e}")
# 备用方案:创建简单的明亮材质
bsdf.inputs["Base Color"].default_value = (1.0, 0.8, 0.2, 1.0)
bsdf.inputs["Roughness"].default_value = 0.1
return mat
# 创建材质
metal_mat = create_metal_material()
nothing_mat = create_nothing_material()
emissive_mat = create_emissive_material()
# 创建更精确的抛物面
def create_parabolic_surface(face_f, size=1.0, resolution=32):
bm = bmesh.new()
# 创建抛物面网格
for i in range(resolution):
for j in range(resolution):
u = (i / (resolution - 1) - 0.5) * size
v = (j / (resolution - 1) - 0.5) * size
# 抛物面方程: z = (u^2 + v^2) / (4*f)
if face_f > 0 and face_f < 1e9: # 避免除零和无限大
z = (u*u + v*v) / (4 * face_f)
else:
z = 0
bm.verts.new((u, v, z))
bm.verts.ensure_lookup_table()
# 创建面
for i in range(resolution - 1):
for j in range(resolution - 1):
v1 = i * resolution + j
v2 = i * resolution + j + 1
v3 = (i + 1) * resolution + j + 1
v4 = (i + 1) * resolution + j
bm.faces.new([bm.verts[v1], bm.verts[v2], bm.verts[v3], bm.verts[v4]])
# 创建网格对象
mesh = bpy.data.meshes.new("parabolic_surface")
bm.to_mesh(mesh)
bm.free()
obj = bpy.data.objects.new("parabolic_surface", mesh)
bpy.context.collection.objects.link(obj)
return obj
# 创建双曲面
def create_hyperbolic_surface(face_f, face_g, size=1.0, resolution=32):
bm = bmesh.new()
# 创建双曲面网格
for i in range(resolution):
for j in range(resolution):
u = (i / (resolution - 1) - 0.5) * size
v = (j / (resolution - 1) - 0.5) * size
# 双曲面方程: z^2/f^2 - (u^2 + v^2)/g^2 = 1
if abs(face_g) > 0.1 and abs(face_f) > 0.1:
try:
z_squared = face_f * face_f * (1 + (u*u + v*v) / (face_g * face_g))
if z_squared >= 0:
z = (face_f if face_f > 0 else -face_f) * (z_squared ** 0.5)
else:
z = 0
except:
z = 0
else:
z = 0
bm.verts.new((u, v, z))
bm.verts.ensure_lookup_table()
# 创建面
for i in range(resolution - 1):
for j in range(resolution - 1):
v1 = i * resolution + j
v2 = i * resolution + j + 1
v3 = (i + 1) * resolution + j + 1
v4 = (i + 1) * resolution + j
bm.faces.new([bm.verts[v1], bm.verts[v2], bm.verts[v3], bm.verts[v4]])
mesh = bpy.data.meshes.new("hyperbolic_surface")
bm.to_mesh(mesh)
bm.free()
obj = bpy.data.objects.new("hyperbolic_surface", mesh)
bpy.context.collection.objects.link(obj)
return obj
# 根据面类型创建物体
def create_geometry(obj_data):
face_geom = obj_data.get("face_geometry", "plane")
face_type = obj_data.get("face_type", "")
name = obj_data.get("name", "noname")
pos = obj_data["p"]
quat = obj_data["q"]
material_name = obj_data.get("draw_material", "metal")
face_f = obj_data.get("face_f", 1.0)
face_g = obj_data.get("face_g", 1.0)
print(f"创建对象: {name} ({face_geom})")
mesh = None
try:
if face_geom == "plane":
# 创建圆形平面而不是方形
bpy.ops.mesh.primitive_circle_add(vertices=32, radius=0.5)
mesh = bpy.context.active_object
# 填充圆形
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.edge_face_add()
bpy.ops.object.mode_set(mode='OBJECT')
elif face_geom == "circle":
# 创建圆形平面
bpy.ops.mesh.primitive_circle_add(vertices=32, radius=0.5)
mesh = bpy.context.active_object
# 填充圆形
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.edge_face_add()
bpy.ops.object.mode_set(mode='OBJECT')
elif face_geom == "parabola":
if face_f < 1e9 and face_f > 0.1: # 使用精确抛物面
mesh = create_parabolic_surface(face_f)
else: # 使用简化圆锥
bpy.ops.mesh.primitive_cone_add(vertices=32, radius1=0.75, radius2=0.05, depth=1.0)
mesh = bpy.context.active_object
elif face_geom == "hyperbola":
if abs(face_f) > 0.1 and abs(face_g) > 0.1: # 使用精确双曲面
mesh = create_hyperbolic_surface(face_f, face_g)
else: # 使用简化环面
bpy.ops.mesh.primitive_torus_add(major_radius=0.5, minor_radius=0.15)
mesh = bpy.context.active_object
else:
# 默认立方体
bpy.ops.mesh.primitive_cube_add(size=0.5)
mesh = bpy.context.active_object
except Exception as e:
print(f"创建几何体时出错: {e}")
# 备用:创建简单立方体
bpy.ops.mesh.primitive_cube_add(size=0.25)
mesh = bpy.context.active_object
# 设置对象属性
mesh.name = name
mesh.location = pos
mesh.rotation_mode = 'QUATERNION'
mesh.rotation_quaternion = mathutils.Quaternion(quat)
# 设置材质
if material_name == "metal":
mesh.data.materials.append(metal_mat)
elif material_name == "nothing":
mesh.data.materials.append(nothing_mat)
else:
mesh.data.materials.append(emissive_mat)
return mesh
# 创建集合来组织对象
collection = bpy.data.collections.new("OpticalSystem")
bpy.context.scene.collection.children.link(collection)
# 遍历并创建所有子对象
created_objects = []
for i, child in enumerate(data["children"]):
try:
obj = create_geometry(child)
created_objects.append(obj)
# 将对象移动到专用集合
bpy.context.collection.objects.unlink(obj)
collection.objects.link(obj)
except Exception as e:
print(f"创建对象 {i} 时出错: {e}")
print(f"成功创建了 {len(created_objects)} 个对象")
# 添加相机和灯光
def setup_scene():
# 添加相机
bpy.ops.object.camera_add(location=(15, -15, 10))
camera = bpy.context.active_object
camera.rotation_euler = (1.1, 0, 0.785)
bpy.context.scene.camera = camera
# 添加主灯光
bpy.ops.object.light_add(type='SUN', location=(5, 5, 10))
sun = bpy.context.active_object
sun.data.energy = 3.0
# 添加环境光
bpy.ops.object.light_add(type='AREA', location=(-5, -5, 8))
area = bpy.context.active_object
area.data.energy = 50.0
area.data.size = 10.0
setup_scene()
# 设置渲染参数
scene = bpy.context.scene
scene.render.resolution_x = 1920
scene.render.resolution_y = 1080
scene.render.image_settings.file_format = 'PNG'
# 导出函数
def export_model():
print(f"准备导出 {len(created_objects)} 个对象...")
if not created_objects:
print("警告:没有创建任何对象,跳过导出")
return
# 选择所有创建的对象
bpy.ops.object.select_all(action='DESELECT')
for obj in created_objects:
if obj and obj.name in bpy.data.objects:
obj.select_set(True)
print(f"已选择对象: {obj.name}")
try:
# 导出为OBJ格式
obj_path = os.path.abspath("./optical_system.obj")
bpy.ops.export_scene.obj(filepath=obj_path, use_selection=True, use_materials=True)
print(f"模型已导出为: {obj_path}")
except Exception as e:
print(f"OBJ导出失败: {e}")
try:
# 导出为GLB格式适合web展示
glb_path = os.path.abspath("./optical_system.glb")
bpy.ops.export_scene.gltf(filepath=glb_path, use_selection=True, export_materials='EXPORT')
print(f"模型已导出为: {glb_path}")
except Exception as e:
print(f"GLB导出失败: {e}")
try:
# 保存Blender文件
blend_path = os.path.abspath("./optical_system.blend")
bpy.ops.wm.save_as_mainfile(filepath=blend_path)
print(f"Blender文件已保存为: {blend_path}")
except Exception as e:
print(f"Blender文件保存失败: {e}")
# 渲染图像
def render_image():
try:
render_path = os.path.abspath("./optical_system_render.png")
scene.render.filepath = render_path
print(f"开始渲染到: {render_path}")
bpy.ops.render.render(write_still=True)
print(f"渲染图像已保存为: {render_path}")
except Exception as e:
print(f"渲染失败: {e}")
import traceback
traceback.print_exc()
# 执行导出和渲染
try:
print("开始导出模型...")
export_model()
print("开始渲染图像...")
render_image()
print("脚本执行完成!")
print("生成的文件:")
print("- optical_system.obj (3D模型)")
print("- optical_system.glb (Web友好格式)")
print("- optical_system.blend (Blender工程文件)")
print("- optical_system_render.png (渲染图像)")
except Exception as e:
print(f"执行导出/渲染时出错: {e}")
import traceback
traceback.print_exc()