331 lines
11 KiB
Python
331 lines
11 KiB
Python
import bpy
|
||
import json
|
||
import mathutils
|
||
import os
|
||
import bmesh
|
||
|
||
# 清空场景
|
||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||
|
||
# 设置渲染引擎为Cycles(更好的材质渲染)
|
||
bpy.context.scene.render.engine = 'CYCLES'
|
||
|
||
# 加载 JSON 文件
|
||
json_path = "./e8caffb4622e03b1495bbc1ed13fce13.json"
|
||
with open(json_path, 'r', encoding='utf-8') as f:
|
||
data = json.load(f)
|
||
|
||
print(f"已加载JSON文件:{json_path}")
|
||
print(f"包含 {len(data['children'])} 个对象")
|
||
|
||
# 创建金属材质
|
||
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=2.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=2.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_plane_add(size=2)
|
||
mesh = bpy.context.active_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=1.5, radius2=0.1, depth=2.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=1.0, minor_radius=0.3)
|
||
mesh = bpy.context.active_object
|
||
else:
|
||
# 默认立方体
|
||
bpy.ops.mesh.primitive_cube_add(size=1.0)
|
||
mesh = bpy.context.active_object
|
||
except Exception as e:
|
||
print(f"创建几何体时出错: {e}")
|
||
# 备用:创建简单立方体
|
||
bpy.ops.mesh.primitive_cube_add(size=0.5)
|
||
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()
|