Files
BL/fused_optical_system.py
nicomacbookpro 565bb02a28 案例
2025-09-02 15:58:31 +08:00

559 lines
19 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.
#!/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()