This commit is contained in:
nicomacbookpro
2025-09-02 15:58:31 +08:00
parent 5478efef6c
commit 565bb02a28
7 changed files with 847 additions and 0 deletions

558
fused_optical_system.py Normal file
View File

@@ -0,0 +1,558 @@
#!/usr/bin/env python3
"""
融合光学系统脚本
将光学系统几何体和光路可视化合并到一个场景中
使用方法:
blender --background --python fused_optical_system.py -- [optical_json_path] [light_path_json_path]
输出文件:
- fused_optical_system.blend - Blender工程文件
- fused_optical_system.glb - Web友好格式
- fused_optical_system_render.png - 渲染图像
"""
import bpy
import json
import mathutils
import os
import bmesh
import sys
import numpy as np
# 获取命令行参数
def get_json_paths():
"""从命令行参数获取JSON文件路径"""
optical_json = "./e8caffb4622e03b1495bbc1ed13fce13.json"
light_path_json = "./miao_light_path_tsingtao.json"
if len(sys.argv) > 5: # Blender传递的参数格式: blender --background --python script.py -- optical_json light_path_json
# 查找 -- 分隔符后的参数
for i, arg in enumerate(sys.argv):
if arg == "--" and i + 1 < len(sys.argv):
if i + 2 < len(sys.argv):
optical_json = sys.argv[i + 1]
light_path_json = sys.argv[i + 2]
else:
optical_json = sys.argv[i + 1]
break
return optical_json, light_path_json
# 清空场景
bpy.ops.wm.read_factory_settings(use_empty=True)
# 设置渲染引擎为Cycles更好的材质渲染
bpy.context.scene.render.engine = 'CYCLES'
# 获取JSON文件路径
optical_json_path, light_path_json_path = get_json_paths()
print(f"光学系统JSON文件{optical_json_path}")
print(f"光路JSON文件{light_path_json_path}")
# ==================== 光学系统几何体部分 ====================
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
def create_nothing_material():
"""创建透明材质(用于 nothing"""
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"]
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
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
# ==================== 光路可视化部分 ====================
def create_light_path_material(color=(1.0, 0.2, 0.2, 1.0)):
"""创建光路材质"""
mat = bpy.data.materials.new(name="light_path")
mat.use_nodes = True
nodes = mat.node_tree.nodes
links = mat.node_tree.links
# 清除默认节点
nodes.clear()
# 创建发光节点
emission = nodes.new(type='ShaderNodeEmission')
emission.inputs['Color'].default_value = color
emission.inputs['Strength'].default_value = 2.0 # 增强发光强度
# 创建输出节点
output = nodes.new(type='ShaderNodeOutputMaterial')
# 连接节点
links.new(emission.outputs['Emission'], output.inputs['Surface'])
return mat
def create_light_path_curve(points, name="light_path"):
"""创建光路曲线"""
# 创建曲线数据
curve_data = bpy.data.curves.new(name, type='CURVE')
curve_data.dimensions = '3D'
curve_data.resolution_u = 12
curve_data.bevel_depth = 0.03 # 稍微增加厚度
curve_data.bevel_resolution = 4
# 创建样条线
spline = curve_data.splines.new('POLY')
spline.points.add(len(points) - 1)
# 设置点坐标
for i, point in enumerate(points):
spline.points[i].co = (point[0], point[1], point[2], 1.0)
# 创建曲线对象
curve_obj = bpy.data.objects.new(name, curve_data)
bpy.context.collection.objects.link(curve_obj)
return curve_obj
def create_light_paths(light_paths):
"""创建所有光路"""
objects = []
# 颜色方案
colors = [
(1.0, 0.2, 0.2, 1.0), # 红色
(0.2, 1.0, 0.2, 1.0), # 绿色
(0.2, 0.2, 1.0, 1.0), # 蓝色
(1.0, 1.0, 0.2, 1.0), # 黄色
(1.0, 0.2, 1.0, 1.0), # 紫色
(0.2, 1.0, 1.0, 1.0), # 青色
(1.0, 0.6, 0.2, 1.0), # 橙色
(0.8, 0.2, 0.8, 1.0), # 粉色
]
# 限制光路数量以避免场景过于复杂
max_paths = min(20, len(light_paths))
print(f"创建前 {max_paths} 条光路(总共 {len(light_paths)} 条)")
for i, path in enumerate(light_paths[:max_paths]):
if len(path) < 2:
continue
# 选择颜色
color = colors[i % len(colors)]
# 创建材质
material = create_light_path_material(color)
# 创建光路对象
obj = create_light_path_curve(path, f"light_path_{i}")
# 应用材质
obj.data.materials.append(material)
objects.append(obj)
print(f"创建光路 {i+1}: {len(path)} 个点")
return objects
# ==================== 主程序 ====================
def load_optical_system_data(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'])} 个光学对象")
return data
except FileNotFoundError:
print(f"❌ 错误找不到光学系统JSON文件 {json_path}")
return None
except json.JSONDecodeError as e:
print(f"❌ 错误光学系统JSON文件格式错误 {e}")
return None
except Exception as e:
print(f"❌ 错误加载光学系统JSON文件时出错 {e}")
return None
def load_light_path_data(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)} 条光路")
return data
except FileNotFoundError:
print(f"❌ 错误找不到光路JSON文件 {json_path}")
return None
except json.JSONDecodeError as e:
print(f"❌ 错误光路JSON文件格式错误 {e}")
return None
except Exception as e:
print(f"❌ 错误加载光路JSON文件时出错 {e}")
return None
def setup_scene():
"""设置场景"""
# 添加相机
bpy.ops.object.camera_add(location=(20, -20, 15))
camera = bpy.context.active_object
camera.rotation_euler = (1.0, 0, 0.785)
bpy.context.scene.camera = camera
# 添加主灯光
bpy.ops.object.light_add(type='SUN', location=(10, 10, 20))
sun = bpy.context.active_object
sun.data.energy = 3.0
# 添加环境光
bpy.ops.object.light_add(type='AREA', location=(-10, -10, 15))
area = bpy.context.active_object
area.data.energy = 100.0
area.data.size = 15.0
# 设置世界背景
world = bpy.context.scene.world
if world is None:
world = bpy.data.worlds.new("World")
bpy.context.scene.world = world
world.use_nodes = True
bg_node = world.node_tree.nodes['Background']
bg_node.inputs['Color'].default_value = (0.02, 0.02, 0.05, 1.0) # 深蓝色背景
bg_node.inputs['Strength'].default_value = 0.3
def export_model(optical_objects, light_path_objects):
"""导出模型"""
print(f"准备导出 {len(optical_objects)} 个光学对象和 {len(light_path_objects)} 条光路...")
all_objects = optical_objects + light_path_objects
if not all_objects:
print("警告:没有创建任何对象,跳过导出")
return
# 选择所有创建的对象
bpy.ops.object.select_all(action='DESELECT')
for obj in all_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("./fused_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("./fused_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("./fused_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("./fused_optical_system_render.png")
scene = bpy.context.scene
scene.render.filepath = render_path
scene.render.resolution_x = 1920
scene.render.resolution_y = 1080
scene.render.image_settings.file_format = 'PNG'
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()
def main():
"""主函数"""
print("🎨 融合光学系统脚本 - 几何体 + 光路可视化")
print("=" * 50)
# 创建材质
global metal_mat, nothing_mat, emissive_mat
metal_mat = create_metal_material()
nothing_mat = create_nothing_material()
emissive_mat = create_emissive_material()
# 加载光学系统数据
optical_data = load_optical_system_data(optical_json_path)
if not optical_data:
print("❌ 无法加载光学系统数据,退出")
return
# 加载光路数据
light_path_data = load_light_path_data(light_path_json_path)
if not light_path_data:
print("❌ 无法加载光路数据,退出")
return
# 创建集合来组织对象
optical_collection = bpy.data.collections.new("OpticalSystem")
light_path_collection = bpy.data.collections.new("LightPaths")
bpy.context.scene.collection.children.link(optical_collection)
bpy.context.scene.collection.children.link(light_path_collection)
# 创建光学系统几何体
print("\n🔧 创建光学系统几何体...")
optical_objects = []
for i, child in enumerate(optical_data["children"]):
try:
obj = create_geometry(child)
optical_objects.append(obj)
# 将对象移动到专用集合
bpy.context.collection.objects.unlink(obj)
optical_collection.objects.link(obj)
except Exception as e:
print(f"创建光学对象 {i} 时出错: {e}")
print(f"成功创建了 {len(optical_objects)} 个光学对象")
# 创建光路可视化
print("\n💡 创建光路可视化...")
light_path_objects = create_light_paths(light_path_data)
# 将光路对象移动到专用集合
for obj in light_path_objects:
bpy.context.collection.objects.unlink(obj)
light_path_collection.objects.link(obj)
print(f"成功创建了 {len(light_path_objects)} 条光路")
# 设置场景
print("\n🎬 设置场景...")
setup_scene()
# 执行导出和渲染
try:
print("\n📤 开始导出模型...")
export_model(optical_objects, light_path_objects)
print("\n🎨 开始渲染图像...")
render_image()
print("\n🎉 脚本执行完成!")
print("生成的文件:")
print("- fused_optical_system.obj (3D模型)")
print("- fused_optical_system.glb (Web友好格式)")
print("- fused_optical_system.blend (Blender工程文件)")
print("- fused_optical_system_render.png (渲染图像)")
print(f"\n场景统计:")
print(f"- 光学对象: {len(optical_objects)}")
print(f"- 光路: {len(light_path_objects)}")
except Exception as e:
print(f"执行导出/渲染时出错: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()