Files
ViewDesignEngine/blender_addon/operators.py
T

571 lines
18 KiB
Python
Raw Normal View History

"""
operators.py — VDE Blender 操作符
=================================
所有 VDE 操作符定义,可在 Blender 中搜索和调用。
操作符列表:
- VDE_OT_import_step — 导入 STEP 文件
- VDE_OT_export_step — 导出选中对象为 STEP
- VDE_OT_create_box — 创建 Box 图元
- VDE_OT_create_cylinder — 创建 Cylinder 图元
- VDE_OT_create_sphere — 创建 Sphere 图元
- VDE_OT_boolean_union — 布尔并集
- VDE_OT_boolean_intersect— 布尔交集
- VDE_OT_boolean_diff — 布尔差集
- VDE_OT_sdf_to_mesh — SDF→Mesh 转换
"""
import os
import bpy
from bpy.props import (
StringProperty, FloatProperty, IntProperty,
BoolProperty, EnumProperty, PointerProperty,
)
from bpy_extras.io_utils import ImportHelper, ExportHelper
from . import vde_bridge
# ═══════════════════════════════════════════════════════════════
# 辅助函数
# ═══════════════════════════════════════════════════════════════
def get_prefs():
"""获取插件偏好设置。"""
return bpy.context.preferences.addons[__package__].preferences
def get_bridge():
"""获取 VDE 桥接实例。"""
prefs = get_prefs()
return vde_bridge.VDEBridge(
vde_lib_path=prefs.vde_lib_path,
python_executable=prefs.python_path or None,
)
def report_and_raise(self, error_msg):
"""报告错误并设置操作符状态。"""
self.report({'ERROR'}, error_msg)
return {'CANCELLED'}
# ═══════════════════════════════════════════════════════════════
# 导入/导出操作符
# ═══════════════════════════════════════════════════════════════
class VDE_OT_import_step(bpy.types.Operator, ImportHelper):
"""从 STEP 文件导入几何体为 Blender Mesh。"""
bl_idname = "vde.import_step"
bl_label = "导入 STEP"
bl_description = "使用 VDE 引擎从 STEP/STP 文件导入 B-Rep 几何体"
bl_options = {'REGISTER', 'UNDO'}
filename_ext = ".stp;.step"
filter_glob: StringProperty(
default="*.stp;*.step",
options={'HIDDEN'},
)
heal_import: BoolProperty(
name="自动修复",
description="导入后自动修复拓扑缺陷(合并顶点、闭合缝隙)",
default=True,
)
deflection: FloatProperty(
name="网格精度",
description="B-Rep 离散化为网格的弦高偏差 (越小越精细)",
default=0.01,
min=0.001,
max=1.0,
precision=4,
subtype='FACTOR',
)
def execute(self, context):
bridge = get_bridge()
self.report({'INFO'}, f"正在导入: {self.filepath}")
try:
result = bridge.execute("import_step", path=self.filepath)
except RuntimeError as e:
return report_and_raise(self, str(e))
name = os.path.basename(self.filepath).rsplit(".", 1)[0]
try:
obj = vde_bridge.bridge_result_to_blender(result, name=name)
except RuntimeError as e:
return report_and_raise(self, str(e))
self.report({'INFO'}, f"导入成功: {name} "
f"({result.get('num_vertices', 0)} 顶点, "
f"{result.get('num_faces', 0)} 面)")
return {'FINISHED'}
class VDE_OT_export_step(bpy.types.Operator, ExportHelper):
"""将选中对象导出为 STEP 文件。"""
bl_idname = "vde.export_step"
bl_label = "导出 STEP"
bl_description = "使用 VDE 引擎将选中网格导出为 STEP AP242 格式"
bl_options = {'REGISTER', 'UNDO'}
filename_ext = ".stp"
filter_glob: StringProperty(
default="*.stp;*.step",
options={'HIDDEN'},
)
include_pmi: BoolProperty(
name="包含 PMI",
description="在 STEP 文件中包含产品和制造信息",
default=False,
)
def execute(self, context):
bridge = get_bridge()
selected = [o for o in context.selected_objects if o.type == 'MESH']
if not selected:
return report_and_raise(self, "请先选中至少一个 Mesh 对象。")
meshes = []
for obj in selected:
try:
mesh_data = vde_bridge.blender_mesh_to_bridge_data(obj)
meshes.append(mesh_data)
except RuntimeError as e:
return report_and_raise(self, f"对象 '{obj.name}': {e}")
self.report({'INFO'}, f"正在导出 {len(meshes)} 个对象到: {self.filepath}")
try:
result = bridge.execute(
"export_step",
path=self.filepath,
meshes=meshes,
)
except RuntimeError as e:
return report_and_raise(self, str(e))
self.report({'INFO'}, f"导出成功: {self.filepath}")
return {'FINISHED'}
# ═══════════════════════════════════════════════════════════════
# 图元创建操作符
# ═══════════════════════════════════════════════════════════════
class VDE_OT_create_box(bpy.types.Operator):
"""创建 Box B-Rep 图元。"""
bl_idname = "vde.create_box"
bl_label = "Box"
bl_description = "使用 VDE 创建 Box 图元"
bl_options = {'REGISTER', 'UNDO'}
width: FloatProperty(
name="宽度 (X)",
default=2.0,
min=0.01,
max=100.0,
)
height: FloatProperty(
name="高度 (Y)",
default=2.0,
min=0.01,
max=100.0,
)
depth: FloatProperty(
name="深度 (Z)",
default=2.0,
min=0.01,
max=100.0,
)
def execute(self, context):
bridge = get_bridge()
try:
result = bridge.execute("create_primitive", type="box", params={
"width": self.width,
"height": self.height,
"depth": self.depth,
})
except RuntimeError as e:
return report_and_raise(self, str(e))
try:
vde_bridge.bridge_result_to_blender(
result,
name=f"VDE_Box_{self.width:.1f}x{self.height:.1f}x{self.depth:.1f}"
)
except RuntimeError as e:
return report_and_raise(self, str(e))
self.report({'INFO'}, f"创建 Box ({self.width}×{self.height}×{self.depth})")
return {'FINISHED'}
class VDE_OT_create_cylinder(bpy.types.Operator):
"""创建 Cylinder B-Rep 图元。"""
bl_idname = "vde.create_cylinder"
bl_label = "Cylinder"
bl_description = "使用 VDE 创建圆柱体图元"
bl_options = {'REGISTER', 'UNDO'}
radius: FloatProperty(
name="半径",
default=1.0,
min=0.01,
max=50.0,
)
height: FloatProperty(
name="高度",
default=2.0,
min=0.01,
max=100.0,
)
def execute(self, context):
bridge = get_bridge()
try:
result = bridge.execute("create_primitive", type="cylinder", params={
"radius": self.radius,
"height": self.height,
})
except RuntimeError as e:
return report_and_raise(self, str(e))
try:
vde_bridge.bridge_result_to_blender(
result,
name=f"VDE_Cylinder_r{self.radius:.1f}_h{self.height:.1f}"
)
except RuntimeError as e:
return report_and_raise(self, str(e))
self.report({'INFO'}, f"创建 Cylinder (r={self.radius}, h={self.height})")
return {'FINISHED'}
class VDE_OT_create_sphere(bpy.types.Operator):
"""创建 Sphere B-Rep 图元。"""
bl_idname = "vde.create_sphere"
bl_label = "Sphere"
bl_description = "使用 VDE 创建球体图元"
bl_options = {'REGISTER', 'UNDO'}
radius: FloatProperty(
name="半径",
default=1.0,
min=0.01,
max=50.0,
)
def execute(self, context):
bridge = get_bridge()
try:
result = bridge.execute("create_primitive", type="sphere", params={
"radius": self.radius,
})
except RuntimeError as e:
return report_and_raise(self, str(e))
try:
vde_bridge.bridge_result_to_blender(
result,
name=f"VDE_Sphere_r{self.radius:.1f}"
)
except RuntimeError as e:
return report_and_raise(self, str(e))
self.report({'INFO'}, f"创建 Sphere (r={self.radius})")
return {'FINISHED'}
# ═══════════════════════════════════════════════════════════════
# 布尔运算操作符
# ═══════════════════════════════════════════════════════════════
class VDE_OT_boolean_union(bpy.types.Operator):
"""布尔并集 — 合并两个选中对象。"""
bl_idname = "vde.boolean_union"
bl_label = "布尔并集 (Union)"
bl_description = "使用 VDE 引擎计算两个网格的布尔并集"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
return _execute_boolean(self, context, "union", "Union")
class VDE_OT_boolean_intersect(bpy.types.Operator):
"""布尔交集 — 保留两对象重叠部分。"""
bl_idname = "vde.boolean_intersect"
bl_label = "布尔交集 (Intersect)"
bl_description = "使用 VDE 引擎计算两个网格的布尔交集"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
return _execute_boolean(self, context, "intersect", "Intersect")
class VDE_OT_boolean_diff(bpy.types.Operator):
"""布尔差集 — 从第一个对象减去第二个对象。"""
bl_idname = "vde.boolean_diff"
bl_label = "布尔差集 (Difference)"
bl_description = "使用 VDE 引擎计算两个网格的布尔差集 (A - B)"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
return _execute_boolean(self, context, "diff", "Diff")
def _execute_boolean(operator, context, op_type, label):
"""内部布尔运算执行函数。"""
selected = [o for o in context.selected_objects if o.type == 'MESH']
if len(selected) < 2:
return report_and_raise(operator, "请选中恰好两个 Mesh 对象。")
obj_a = context.active_object
obj_b = selected[0] if selected[0] != obj_a else selected[1]
if obj_a is None or obj_b is None:
return report_and_raise(operator, "请设置活动对象并选中另一个对象。")
operator.report(
{'INFO'},
f"正在计算布尔{label}: {obj_a.name} vs {obj_b.name}"
)
bridge = get_bridge()
try:
mesh_a = vde_bridge.blender_mesh_to_bridge_data(obj_a)
mesh_b = vde_bridge.blender_mesh_to_bridge_data(obj_b)
except RuntimeError as e:
return report_and_raise(operator, str(e))
try:
result = bridge.execute(
"boolean",
boolean_type=op_type,
mesh_a=mesh_a,
mesh_b=mesh_b,
)
except RuntimeError as e:
return report_and_raise(operator, str(e))
try:
vde_bridge.bridge_result_to_blender(
result,
name=f"VDE_Boolean_{label}_{obj_a.name}"
)
except RuntimeError as e:
return report_and_raise(operator, str(e))
operator.report(
{'INFO'},
f"布尔{label}完成: "
f"{result.get('num_vertices', 0)} 顶点, "
f"{result.get('num_faces', 0)}"
)
return {'FINISHED'}
# ═══════════════════════════════════════════════════════════════
# SDF → Mesh 操作符
# ═══════════════════════════════════════════════════════════════
class VDE_OT_sdf_to_mesh(bpy.types.Operator):
"""将 SDF 隐式曲面转换为三角网格。"""
bl_idname = "vde.sdf_to_mesh"
bl_label = "SDF → Mesh"
bl_description = "使用 VDE 引擎将 SDF 隐式几何体转为三角网格"
bl_options = {'REGISTER', 'UNDO'}
sdf_type: EnumProperty(
name="SDF 类型",
description="要生成的隐式曲面类型",
items=[
('sphere', "球体", "球体 SDF"),
('box', "Box", "Box SDF"),
('cylinder', "圆柱体", "圆柱体 SDF"),
('torus', "环面", "环面 SDF"),
('custom', "自定义 CSG", "自定义 SDF CSG 组合树"),
],
default='sphere',
)
resolution: IntProperty(
name="分辨率",
description="Marching Cubes 网格分辨率 (越高越精细)",
default=64,
min=16,
max=512,
)
# ══ 球体参数 ══
sphere_radius: FloatProperty(
name="半径",
default=1.0,
min=0.01,
max=50.0,
)
# ══ Box 参数 ══
box_hx: FloatProperty(
name="半长 (X)",
default=1.0,
min=0.01,
max=50.0,
)
box_hy: FloatProperty(
name="半长 (Y)",
default=1.0,
min=0.01,
max=50.0,
)
box_hz: FloatProperty(
name="半长 (Z)",
default=1.0,
min=0.01,
max=50.0,
)
# ══ 圆柱体参数 ══
cyl_radius: FloatProperty(
name="半径",
default=1.0,
min=0.01,
max=50.0,
)
cyl_height: FloatProperty(
name="高度",
default=2.0,
min=0.01,
max=100.0,
)
# ══ 环面参数 ══
torus_major: FloatProperty(
name="主半径",
default=1.0,
min=0.1,
max=50.0,
)
torus_minor: FloatProperty(
name="管半径",
default=0.3,
min=0.01,
max=25.0,
)
def draw(self, context):
layout = self.layout
layout.prop(self, "sdf_type")
layout.prop(self, "resolution")
box = layout.box()
if self.sdf_type == 'sphere':
box.prop(self, "sphere_radius")
elif self.sdf_type == 'box':
box.prop(self, "box_hx")
box.prop(self, "box_hy")
box.prop(self, "box_hz")
elif self.sdf_type == 'cylinder':
box.prop(self, "cyl_radius")
box.prop(self, "cyl_height")
elif self.sdf_type == 'torus':
box.prop(self, "torus_major")
box.prop(self, "torus_minor")
elif self.sdf_type == 'custom':
box.label(text="自定义 CSG 通过面板配置")
def execute(self, context):
params = {}
node_name = "SDF"
if self.sdf_type == 'sphere':
params = {"radius": self.sphere_radius}
node_name = f"VDE_SDF_Sphere_r{self.sphere_radius:.1f}"
elif self.sdf_type == 'box':
params = {"half_extents": [self.box_hx, self.box_hy, self.box_hz]}
node_name = (f"VDE_SDF_Box_"
f"{self.box_hx:.1f}x{self.box_hy:.1f}x{self.box_hz:.1f}")
elif self.sdf_type == 'cylinder':
params = {"radius": self.cyl_radius, "height": self.cyl_height}
node_name = f"VDE_SDF_Cyl_r{self.cyl_radius:.1f}_h{self.cyl_height:.1f}"
elif self.sdf_type == 'torus':
params = {"major_radius": self.torus_major,
"minor_radius": self.torus_minor}
node_name = (f"VDE_SDF_Torus_"
f"R{self.torus_major:.1f}_r{self.torus_minor:.1f}")
elif self.sdf_type == 'custom':
# 从场景属性读取自定义 CSG 配置
scene = context.scene
params = {
"children": list(scene.vde_sdf_children),
"operations": list(scene.vde_sdf_operations),
}
if scene.vde_sdf_blend_radius > 0:
params["blend_radius"] = scene.vde_sdf_blend_radius
if scene.vde_sdf_round > 0:
params["round"] = scene.vde_sdf_round
node_name = "VDE_SDF_CSG"
bridge = get_bridge()
self.report(
{'INFO'},
f"SDF→Mesh: {self.sdf_type}, res={self.resolution}"
)
try:
result = bridge.execute(
"sdf_to_mesh",
sdf_type=self.sdf_type,
params=params,
resolution=self.resolution,
)
except RuntimeError as e:
return report_and_raise(self, str(e))
try:
vde_bridge.bridge_result_to_blender(result, name=node_name)
except RuntimeError as e:
return report_and_raise(self, str(e))
self.report(
{'INFO'},
f"SDF→Mesh 完成: "
f"{result.get('num_vertices', 0)} 顶点, "
f"{result.get('num_faces', 0)}"
)
return {'FINISHED'}
# ═══════════════════════════════════════════════════════════════
# 注册/注销
# ═══════════════════════════════════════════════════════════════
CLASSES = [
VDE_OT_import_step,
VDE_OT_export_step,
VDE_OT_create_box,
VDE_OT_create_cylinder,
VDE_OT_create_sphere,
VDE_OT_boolean_union,
VDE_OT_boolean_intersect,
VDE_OT_boolean_diff,
VDE_OT_sdf_to_mesh,
]
def register():
for cls in CLASSES:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(CLASSES):
bpy.utils.unregister_class(cls)