#!/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()