Files
ViewDesignEngine/blender_addon/operators.py
T
茂之钳 5e2812a6f3
CI / Build & Test (push) Failing after 33s
CI / Release Build (push) Failing after 35s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled
feat(v6.2): Blender addon + .NET/C# bindings + developer docs + plugin system
v6.2.1 — Blender Integration Addon:
- 5 files, 1,732 lines: __init__, operators (9 ops), panels (4 panels)
- preferences, vde_bridge (subprocess CLI bridge)
- Import/Export STEP, primitives, boolean, SDF→Mesh

v6.2.2 — .NET/C# Bindings (VdeSharp):
- 8 files: NativeMethods (50+ P/Invoke), BrepModel, MeshData, SdfEngine, Assembly
- C API extended with 20 new functions (STEP I/O, Boolean, SDF, Assembly)
- vde_capi rebuilt as SHARED library
- Example: import STEP → boolean → export

v6.2.3 — Developer Docs + Plugin System:
- 7 docs: architecture, contributing, API overview, building, testing, plugin-system
- plugin_system.h/.cpp: PluginInterface, PluginManager, dlopen/LoadLibrary
- README.md updated with v6 features
- Syntax-check passed (GCC 10.2.1, -Wall -Wextra -Wpedantic)
2026-07-26 22:24:40 +08:00

571 lines
18 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.
"""
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)