Files
ViewDesignEngine/blender_addon/panels.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

421 lines
16 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.
"""
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)