5e2812a6f3
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)
421 lines
16 KiB
Python
421 lines
16 KiB
Python
"""
|
||
panels.py — VDE Blender 侧边栏面板
|
||
===================================
|
||
在 3D Viewport 的 N 面板(右侧工具栏)添加 VDE 选项卡,
|
||
包含 Create、Modify、SDF 三个子面板。
|
||
"""
|
||
|
||
import bpy
|
||
from bpy.props import (
|
||
FloatProperty, IntProperty, EnumProperty,
|
||
StringProperty, CollectionProperty, BoolProperty,
|
||
)
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# SDF CSG 配置属性(存储在场景级别)
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
SDF_PRIMITIVE_ITEMS = [
|
||
('sphere', "球体", "球体 SDF"),
|
||
('box', "Box", "Box SDF"),
|
||
('cylinder', "圆柱体", "圆柱体 SDF"),
|
||
('torus', "环面", "环面 SDF"),
|
||
]
|
||
|
||
SDF_OPERATION_ITEMS = [
|
||
('union', "Union (并集)", ""),
|
||
('intersection', "Intersection (交集)", ""),
|
||
('difference', "Difference (差集)", ""),
|
||
('smooth_union', "Smooth Union (光滑并集)", ""),
|
||
('smooth_intersection', "Smooth Intersection (光滑交集)", ""),
|
||
('smooth_difference', "Smooth Difference (光滑差集)", ""),
|
||
]
|
||
|
||
|
||
class VDE_SdfChildItem(bpy.types.PropertyGroup):
|
||
"""SDF CSG 子节点定义。"""
|
||
sdf_type: EnumProperty(
|
||
name="类型",
|
||
items=SDF_PRIMITIVE_ITEMS,
|
||
default='sphere',
|
||
)
|
||
radius: FloatProperty(name="半径", default=1.0, min=0.01, max=50.0)
|
||
half_x: FloatProperty(name="半长 X", default=1.0, min=0.01, max=50.0)
|
||
half_y: FloatProperty(name="半长 Y", default=1.0, min=0.01, max=50.0)
|
||
half_z: 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)
|
||
|
||
|
||
class VDE_SdfOpItem(bpy.types.PropertyGroup):
|
||
"""SDF CSG 操作定义。"""
|
||
operation: EnumProperty(
|
||
name="操作",
|
||
items=SDF_OPERATION_ITEMS,
|
||
default='union',
|
||
)
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 面板: VDE 主面板
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
class VIEW3D_PT_vde_main(bpy.types.Panel):
|
||
"""VDE 主面板容器。"""
|
||
bl_label = "VDE"
|
||
bl_idname = "VIEW3D_PT_vde_main"
|
||
bl_space_type = 'VIEW_3D'
|
||
bl_region_type = 'UI'
|
||
bl_category = "VDE"
|
||
bl_description = "ViewDesignEngine 计算几何引擎"
|
||
|
||
def draw(self, context):
|
||
layout = self.layout
|
||
prefs = context.preferences.addons[__package__].preferences
|
||
|
||
# 状态指示器
|
||
row = layout.row(align=True)
|
||
available, msg = self._check_available(prefs)
|
||
if available:
|
||
row.label(text="● VDE 就绪", icon='CHECKMARK')
|
||
else:
|
||
row.label(text="○ VDE 未连接", icon='ERROR')
|
||
row = layout.row()
|
||
row.label(text=msg, icon='INFO')
|
||
|
||
# 快捷设置
|
||
box = layout.box()
|
||
box.label(text="快捷操作:", icon='TOOL_SETTINGS')
|
||
col = box.column(align=True)
|
||
col.operator("vde.import_step", text="导入 STEP...", icon='IMPORT')
|
||
col.operator("vde.export_step", text="导出 STEP...", icon='EXPORT')
|
||
col.operator("vde.sdf_to_mesh", text="SDF → Mesh", icon='MESH_DATA')
|
||
|
||
def _check_available(self, prefs):
|
||
"""缓存检查以避免每次重绘都调用子进程。"""
|
||
try:
|
||
return (prefs.vde_available, prefs.vde_status_message)
|
||
except Exception:
|
||
return (False, "检查失败")
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 子面板: Create — 图元创建
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
class VIEW3D_PT_vde_create(bpy.types.Panel):
|
||
"""创建 B-Rep 图元子面板。"""
|
||
bl_label = "创建"
|
||
bl_parent_id = "VIEW3D_PT_vde_main"
|
||
bl_space_type = 'VIEW_3D'
|
||
bl_region_type = 'UI'
|
||
bl_category = "VDE"
|
||
bl_options = {'DEFAULT_CLOSED'}
|
||
|
||
def draw(self, context):
|
||
layout = self.layout
|
||
|
||
# Box
|
||
box = layout.box()
|
||
box.label(text="Box", icon='MESH_CUBE')
|
||
col = box.column(align=True)
|
||
op = col.operator("vde.create_box", text="创建 Box")
|
||
col.prop(
|
||
bpy.types.VDE_OT_create_box.bl_rna.properties['width'],
|
||
"default", text="宽"
|
||
)
|
||
col.prop(
|
||
bpy.types.VDE_OT_create_box.bl_rna.properties['height'],
|
||
"default", text="高"
|
||
)
|
||
col.prop(
|
||
bpy.types.VDE_OT_create_box.bl_rna.properties['depth'],
|
||
"default", text="深"
|
||
)
|
||
|
||
# Cylinder
|
||
box = layout.box()
|
||
box.label(text="圆柱体", icon='MESH_CYLINDER')
|
||
col = box.column(align=True)
|
||
op = col.operator("vde.create_cylinder", text="创建圆柱体")
|
||
col.prop(
|
||
bpy.types.VDE_OT_create_cylinder.bl_rna.properties['radius'],
|
||
"default", text="半径"
|
||
)
|
||
col.prop(
|
||
bpy.types.VDE_OT_create_cylinder.bl_rna.properties['height'],
|
||
"default", text="高"
|
||
)
|
||
|
||
# Sphere
|
||
box = layout.box()
|
||
box.label(text="球体", icon='MESH_UVSPHERE')
|
||
col = box.column(align=True)
|
||
op = col.operator("vde.create_sphere", text="创建球体")
|
||
col.prop(
|
||
bpy.types.VDE_OT_create_sphere.bl_rna.properties['radius'],
|
||
"default", text="半径"
|
||
)
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 子面板: Modify — 布尔/变形
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
class VIEW3D_PT_vde_modify(bpy.types.Panel):
|
||
"""布尔运算和修改子面板。"""
|
||
bl_label = "修改"
|
||
bl_parent_id = "VIEW3D_PT_vde_main"
|
||
bl_space_type = 'VIEW_3D'
|
||
bl_region_type = 'UI'
|
||
bl_category = "VDE"
|
||
bl_options = {'DEFAULT_CLOSED'}
|
||
|
||
def draw(self, context):
|
||
layout = self.layout
|
||
|
||
# 布尔运算
|
||
box = layout.box()
|
||
box.label(text="布尔运算", icon='MOD_BOOLEAN')
|
||
box.label(text="选中两个 Mesh 对象后执行:", icon='INFO')
|
||
|
||
col = box.column(align=True)
|
||
col.operator("vde.boolean_union", text="并集 (Union)", icon='ADD')
|
||
col.operator("vde.boolean_intersect", text="交集 (Intersect)",
|
||
icon='SELECT_INTERSECT')
|
||
col.operator("vde.boolean_diff", text="差集 (A − B)", icon='REMOVE')
|
||
|
||
# 使用提示
|
||
box = layout.box()
|
||
box.label(text="使用说明", icon='HELP')
|
||
col = box.column(align=True)
|
||
col.label(text="1. Shift+点击选中两个对象")
|
||
col.label(text="2. 最后选中的为「活动对象」")
|
||
col.label(text="3. 点击布尔运算按钮")
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 子面板: SDF — 隐式建模
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
class VIEW3D_PT_vde_sdf(bpy.types.Panel):
|
||
"""SDF 隐式建模子面板。"""
|
||
bl_label = "SDF 建模"
|
||
bl_parent_id = "VIEW3D_PT_vde_main"
|
||
bl_space_type = 'VIEW_3D'
|
||
bl_region_type = 'UI'
|
||
bl_category = "VDE"
|
||
bl_options = {'DEFAULT_CLOSED'}
|
||
|
||
def draw(self, context):
|
||
layout = self.layout
|
||
scene = context.scene
|
||
|
||
# SDF 类型选择
|
||
box = layout.box()
|
||
box.label(text="基础 SDF", icon='MESH_DATA')
|
||
col = box.column(align=True)
|
||
|
||
# 直接用操作符属性绘制
|
||
op_props = bpy.types.VDE_OT_sdf_to_mesh.bl_rna.properties
|
||
col.prop(op_props['sdf_type'], "default", text="类型")
|
||
|
||
# 根据类型显示参数
|
||
sdf_type = op_props['sdf_type'].default
|
||
if sdf_type == 'sphere':
|
||
col.prop(op_props['sphere_radius'], "default", text="半径")
|
||
elif sdf_type == 'box':
|
||
col.prop(op_props['box_hx'], "default", text="半长 X")
|
||
col.prop(op_props['box_hy'], "default", text="半长 Y")
|
||
col.prop(op_props['box_hz'], "default", text="半长 Z")
|
||
elif sdf_type == 'cylinder':
|
||
col.prop(op_props['cyl_radius'], "default", text="半径")
|
||
col.prop(op_props['cyl_height'], "default", text="高度")
|
||
elif sdf_type == 'torus':
|
||
col.prop(op_props['torus_major'], "default", text="主半径")
|
||
col.prop(op_props['torus_minor'], "default", text="管半径")
|
||
|
||
col.prop(op_props['resolution'], "default", text="分辨率")
|
||
|
||
row = col.row()
|
||
row.operator("vde.sdf_to_mesh", text="生成网格", icon='PLAY')
|
||
|
||
# 自定义 CSG
|
||
box = layout.box()
|
||
box.label(text="自定义 CSG 组合", icon='NODETREE')
|
||
|
||
# 子节点列表
|
||
row = box.row()
|
||
row.label(text="子节点:")
|
||
row.operator("vde.sdf_add_child", text="", icon='ADD')
|
||
row.operator("vde.sdf_remove_child", text="", icon='REMOVE')
|
||
|
||
for i, child in enumerate(scene.vde_sdf_children):
|
||
sub_box = box.box()
|
||
sub_box.label(text=f"节点 {i + 1}:")
|
||
sub_box.prop(child, "sdf_type", text="类型")
|
||
if child.sdf_type == 'sphere':
|
||
sub_box.prop(child, "radius", text="半径")
|
||
elif child.sdf_type == 'box':
|
||
sub_box.prop(child, "half_x", text="半长 X")
|
||
sub_box.prop(child, "half_y", text="半长 Y")
|
||
sub_box.prop(child, "half_z", text="半长 Z")
|
||
elif child.sdf_type == 'cylinder':
|
||
sub_box.prop(child, "cyl_radius", text="半径")
|
||
sub_box.prop(child, "cyl_height", text="高度")
|
||
elif child.sdf_type == 'torus':
|
||
sub_box.prop(child, "torus_major", text="主半径")
|
||
sub_box.prop(child, "torus_minor", text="管半径")
|
||
|
||
# CSG 操作列表
|
||
if len(scene.vde_sdf_children) > 1:
|
||
row = box.row()
|
||
row.label(text="操作:")
|
||
row.operator("vde.sdf_add_operation", text="", icon='ADD')
|
||
row.operator("vde.sdf_remove_operation", text="", icon='REMOVE')
|
||
|
||
for i, op_item in enumerate(scene.vde_sdf_operations):
|
||
sub_box = box.box()
|
||
sub_box.prop(op_item, "operation",
|
||
text=f"节点{i}→节点{i + 1}")
|
||
|
||
# 额外变换
|
||
col = box.column(align=True)
|
||
col.prop(scene, "vde_sdf_blend_radius", text="光滑混合半径")
|
||
col.prop(scene, "vde_sdf_round", text="圆角半径")
|
||
|
||
# 生成按钮
|
||
row = box.row()
|
||
row.scale_y = 1.5
|
||
row.operator("vde.sdf_to_mesh", text="▶ CSG → Mesh",
|
||
icon='OUTLINER_OB_MESH').sdf_type = 'custom'
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# SDF CSG 管理操作符
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
class VDE_OT_sdf_add_child(bpy.types.Operator):
|
||
"""添加 SDF CSG 子节点。"""
|
||
bl_idname = "vde.sdf_add_child"
|
||
bl_label = "添加节点"
|
||
bl_options = {'REGISTER', 'UNDO'}
|
||
|
||
def execute(self, context):
|
||
item = context.scene.vde_sdf_children.add()
|
||
item.name = f"Node_{len(context.scene.vde_sdf_children)}"
|
||
return {'FINISHED'}
|
||
|
||
|
||
class VDE_OT_sdf_remove_child(bpy.types.Operator):
|
||
"""移除最后一个 SDF CSG 子节点。"""
|
||
bl_idname = "vde.sdf_remove_child"
|
||
bl_label = "移除节点"
|
||
bl_options = {'REGISTER', 'UNDO'}
|
||
|
||
def execute(self, context):
|
||
children = context.scene.vde_sdf_children
|
||
ops = context.scene.vde_sdf_operations
|
||
if len(children) > 0:
|
||
children.remove(len(children) - 1)
|
||
# 保持操作列表与节点数一致
|
||
while len(ops) >= len(children):
|
||
ops.remove(len(ops) - 1)
|
||
return {'FINISHED'}
|
||
|
||
|
||
class VDE_OT_sdf_add_operation(bpy.types.Operator):
|
||
"""添加 SDF CSG 操作。"""
|
||
bl_idname = "vde.sdf_add_operation"
|
||
bl_label = "添加操作"
|
||
bl_options = {'REGISTER', 'UNDO'}
|
||
|
||
def execute(self, context):
|
||
children = context.scene.vde_sdf_children
|
||
ops = context.scene.vde_sdf_operations
|
||
if len(ops) < len(children) - 1:
|
||
ops.add()
|
||
return {'FINISHED'}
|
||
|
||
|
||
class VDE_OT_sdf_remove_operation(bpy.types.Operator):
|
||
"""移除最后一个 SDF CSG 操作。"""
|
||
bl_idname = "vde.sdf_remove_operation"
|
||
bl_label = "移除操作"
|
||
bl_options = {'REGISTER', 'UNDO'}
|
||
|
||
def execute(self, context):
|
||
ops = context.scene.vde_sdf_operations
|
||
if len(ops) > 0:
|
||
ops.remove(len(ops) - 1)
|
||
return {'FINISHED'}
|
||
|
||
|
||
# ═══════════════════════════════════════════════════════════════
|
||
# 注册/注销
|
||
# ═══════════════════════════════════════════════════════════════
|
||
|
||
PANEL_CLASSES = [
|
||
VIEW3D_PT_vde_main,
|
||
VIEW3D_PT_vde_create,
|
||
VIEW3D_PT_vde_modify,
|
||
VIEW3D_PT_vde_sdf,
|
||
]
|
||
|
||
SDF_CLASSES = [
|
||
VDE_SdfChildItem,
|
||
VDE_SdfOpItem,
|
||
VDE_OT_sdf_add_child,
|
||
VDE_OT_sdf_remove_child,
|
||
VDE_OT_sdf_add_operation,
|
||
VDE_OT_sdf_remove_operation,
|
||
]
|
||
|
||
SCENE_PROPS = [
|
||
# SDF CSG 配置
|
||
("vde_sdf_children", CollectionProperty(type=VDE_SdfChildItem)),
|
||
("vde_sdf_operations", CollectionProperty(type=VDE_SdfOpItem)),
|
||
("vde_sdf_blend_radius", FloatProperty(
|
||
name="光滑混合半径",
|
||
default=0.1,
|
||
min=0.0,
|
||
max=10.0,
|
||
precision=3,
|
||
)),
|
||
("vde_sdf_round", FloatProperty(
|
||
name="圆角半径",
|
||
default=0.0,
|
||
min=0.0,
|
||
max=10.0,
|
||
precision=3,
|
||
)),
|
||
]
|
||
|
||
|
||
def register():
|
||
for cls in SDF_CLASSES:
|
||
bpy.utils.register_class(cls)
|
||
|
||
for cls in PANEL_CLASSES:
|
||
bpy.utils.register_class(cls)
|
||
|
||
# 注册场景属性
|
||
for name, prop in SCENE_PROPS:
|
||
setattr(bpy.types.Scene, name, prop)
|
||
|
||
|
||
def unregister():
|
||
for cls in reversed(PANEL_CLASSES):
|
||
bpy.utils.unregister_class(cls)
|
||
|
||
for cls in reversed(SDF_CLASSES):
|
||
bpy.utils.unregister_class(cls)
|
||
|
||
# 清理场景属性
|
||
for name, _ in SCENE_PROPS:
|
||
if hasattr(bpy.types.Scene, name):
|
||
delattr(bpy.types.Scene, name)
|