From 5e2812a6f3346f8744a31dd1fb0cc09e9fd30c79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E4=B9=8B=E9=92=B3?= Date: Sun, 26 Jul 2026 22:24:40 +0800 Subject: [PATCH] feat(v6.2): Blender addon + .NET/C# bindings + developer docs + plugin system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- README.md | 41 ++ blender_addon/__init__.py | 54 +++ blender_addon/operators.py | 570 +++++++++++++++++++++++++ blender_addon/panels.py | 420 ++++++++++++++++++ blender_addon/preferences.py | 187 +++++++++ blender_addon/vde_bridge.py | 501 ++++++++++++++++++++++ docs/dev-guide/README.md | 106 +++++ docs/dev-guide/api-overview.md | 222 ++++++++++ docs/dev-guide/architecture.md | 240 +++++++++++ docs/dev-guide/building.md | 292 +++++++++++++ docs/dev-guide/contributing.md | 249 +++++++++++ docs/dev-guide/plugin-system.md | 501 ++++++++++++++++++++++ docs/dev-guide/testing.md | 361 ++++++++++++++++ dotnet/VdeSharp.csproj | 16 + dotnet/VdeSharp/Assembly.cs | 46 ++ dotnet/VdeSharp/BrepModel.cs | 191 +++++++++ dotnet/VdeSharp/MeshData.cs | 104 +++++ dotnet/VdeSharp/NativeMethods.cs | 190 +++++++++ dotnet/VdeSharp/SdfEngine.cs | 82 ++++ dotnet/VdeSharp/VdeSharp.csproj | 11 + dotnet/examples/Program.cs | 63 +++ include/vde/capi/vde_capi.h | 84 ++++ include/vde/plugin/plugin_system.h | 396 +++++++++++++++++ include/vde/vde.h | 26 ++ src/CMakeLists.txt | 4 +- src/capi/vde_capi.cpp | 139 ++++++ src/plugin/plugin_system.cpp | 654 +++++++++++++++++++++++++++++ 27 files changed, 5748 insertions(+), 2 deletions(-) create mode 100644 blender_addon/__init__.py create mode 100644 blender_addon/operators.py create mode 100644 blender_addon/panels.py create mode 100644 blender_addon/preferences.py create mode 100644 blender_addon/vde_bridge.py create mode 100644 docs/dev-guide/README.md create mode 100644 docs/dev-guide/api-overview.md create mode 100644 docs/dev-guide/architecture.md create mode 100644 docs/dev-guide/building.md create mode 100644 docs/dev-guide/contributing.md create mode 100644 docs/dev-guide/plugin-system.md create mode 100644 docs/dev-guide/testing.md create mode 100644 dotnet/VdeSharp.csproj create mode 100644 dotnet/VdeSharp/Assembly.cs create mode 100644 dotnet/VdeSharp/BrepModel.cs create mode 100644 dotnet/VdeSharp/MeshData.cs create mode 100644 dotnet/VdeSharp/NativeMethods.cs create mode 100644 dotnet/VdeSharp/SdfEngine.cs create mode 100644 dotnet/VdeSharp/VdeSharp.csproj create mode 100644 dotnet/examples/Program.cs create mode 100644 include/vde/plugin/plugin_system.h create mode 100644 src/plugin/plugin_system.cpp diff --git a/README.md b/README.md index b37dd43..0009fce 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ PYTHONPATH=build_py/python python3 -c "import vde; print(vde.__version__)" | `sdf` | `vde::sdf` | 15+ 隐式图元、CSG 树、自动微分、梯度优化、SDF → Mesh | | `sketch` | `vde::sketch` | 2D 草图约束求解器 | | `capi` | `vde::capi` | C API,跨语言互操作 | +| `plugin` | `vde::plugin` | 插件系统:动态加载、第三方扩展(v6.0+) | ## 格式支持 @@ -148,11 +149,51 @@ auto result = brep_union(a, b); // 布尔并 ## 文档 +- [开发者指南](docs/dev-guide/) — 架构/API/构建/测试/贡献/插件系统(v6.0+) - [开发计划](docs/00-开发计划.md) — 版本规划 + Sprint 划分 - [v3.1 计划](docs/10-v3.1-开发计划.md) — 下一阶段路线图 - [CHANGELOG](CHANGELOG.md) — 版本历史 - [构建指南](docs/06-部署维护/01-构建指南.md) +## v6.0 新功能(计划中) 🔌 + +### 插件系统 (`vde::plugin`) + +```cpp +#include + +// 插件管理器:动态加载第三方扩展 +vde::plugin::PluginManager mgr; +mgr.load_plugin_dir("/usr/lib/vde/plugins"); + +// 查找并使用插件 +auto* exporter = mgr.get_plugin("io.export.gltf"); +exporter->execute("export", {{"path", "output.glb"}}, &mesh, nullptr); +``` + +**核心能力**: +- **动态加载**: 运行时 dlopen/LoadLibrary,无需重新编译 VDE +- **版本兼容**: PluginManifest 声明约束,自动检查兼容性 +- **插件隔离**: 崩溃不影响主程序,独立 .so/.dylib/.dll +- **注册宏**: `VDE_REGISTER_PLUGIN(MyPlugin)` 一行注册 +- **清单文件**: `plugin.json` 加速发现和验证 + +**插件类型**: +| 类型 | 说明 | 示例 | +|------|------|------| +| IO_IMPORT | 格式导入器 | USDZ, FBX, 3DS | +| IO_EXPORT | 格式导出器 | USDZ, X3D, AMF | +| GEOMETRY_FILTER | 几何过滤器 | 平滑、简化、重网格 | +| CUSTOM_TOOL | 自定义工具 | 应力分析、拓扑优化 | + +详见: [插件系统设计](docs/dev-guide/plugin-system.md) | [开发者指南](docs/dev-guide/) + +### 开发者生态 + +- 📘 **[开发者指南](docs/dev-guide/)** — 7 篇完整文档覆盖架构/API/构建/测试/贡献/插件 +- 🏗️ **插件 SDK** — 头文件 + CMake 模板 + 注册宏,5 分钟上手 +- 🔒 **安全分级** — L0 内置 → L1 签名 → L2 社区 → L3 沙箱 + ## 许可证 Apache License 2.0 — 详见 [LICENSE](LICENSE) diff --git a/blender_addon/__init__.py b/blender_addon/__init__.py new file mode 100644 index 0000000..3a5ffd6 --- /dev/null +++ b/blender_addon/__init__.py @@ -0,0 +1,54 @@ +""" +ViewDesignEngine (VDE) Blender Integration Addon +================================================ +将 VDE 高性能计算几何引擎集成到 Blender 中,提供: + - STEP/IGES 文件导入/导出 + - B-Rep 图元创建 (Box, Cylinder, Sphere) + - 布尔运算 (并集, 交集, 差集) + - SDF 隐式建模与网格转换 + +Author: VDE Team +Version: 1.0.0 +Blender: (3, 0, 0) 及以上 +""" + +bl_info = { + "name": "ViewDesignEngine (VDE)", + "description": "高性能 CAD 几何引擎集成 — B-Rep/SDF 建模、STEP 导入导出、布尔运算", + "author": "VDE Team", + "version": (1, 0, 0), + "blender": (3, 0, 0), + "location": "3D Viewport > Sidebar (N) > VDE", + "category": "Import-Export", + "support": "COMMUNITY", + "doc_url": "https://github.com/ViewDesignEngine/ViewDesignEngine", + "tracker_url": "https://github.com/ViewDesignEngine/ViewDesignEngine/issues", +} + +import bpy + +# 延迟导入 — Blender 注册时需要模块可用 +from . import operators +from . import panels +from . import preferences +from . import vde_bridge + + +def register(): + """注册所有操作符、面板和偏好设置。""" + preferences.register() + operators.register() + panels.register() + print("[VDE] ViewDesignEngine addon registered.") + + +def unregister(): + """注销所有操作符、面板和偏好设置。""" + panels.unregister() + operators.unregister() + preferences.unregister() + print("[VDE] ViewDesignEngine addon unregistered.") + + +if __name__ == "__main__": + register() diff --git a/blender_addon/operators.py b/blender_addon/operators.py new file mode 100644 index 0000000..e39efdd --- /dev/null +++ b/blender_addon/operators.py @@ -0,0 +1,570 @@ +""" +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) diff --git a/blender_addon/panels.py b/blender_addon/panels.py new file mode 100644 index 0000000..33e4675 --- /dev/null +++ b/blender_addon/panels.py @@ -0,0 +1,420 @@ +""" +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) diff --git a/blender_addon/preferences.py b/blender_addon/preferences.py new file mode 100644 index 0000000..7a38962 --- /dev/null +++ b/blender_addon/preferences.py @@ -0,0 +1,187 @@ +""" +preferences.py — VDE 插件偏好设置 +================================== +在 Blender 偏好设置中添加 VDE 配置面板: + - VDE Python 库路径 + - Python 解释器路径 + - 状态检测与缓存 +""" + +import bpy +from bpy.props import StringProperty, BoolProperty, FloatProperty + +from . import vde_bridge + + +class VDEAddonPreferences(bpy.types.AddonPreferences): + """VDE 插件偏好设置。""" + bl_idname = __package__ + + # ══ 路径设置 ══ + vde_lib_path: StringProperty( + name="VDE 库路径", + description="VDE Python 包的安装目录(包含 vde/__init__.py 的父目录)", + default="", + subtype='DIR_PATH', + ) + + python_path: StringProperty( + name="Python 解释器", + description="用于运行 VDE 子进程的 Python 解释器路径(留空使用当前 Blender Python)", + default="", + subtype='FILE_PATH', + ) + + # ══ 网格化参数 ══ + default_deflection: FloatProperty( + name="默认网格精度", + description="B-Rep → Mesh 转换的默认弦高偏差", + default=0.01, + min=0.001, + max=1.0, + precision=4, + ) + + default_sdf_resolution: bpy.props.IntProperty( + name="默认 SDF 分辨率", + description="Marching Cubes 的默认网格分辨率", + default=64, + min=16, + max=512, + ) + + # ══ 缓存状态(不持久化) ══ + vde_available: BoolProperty( + name="VDE 可用", + default=False, + options={'SKIP_SAVE'}, + ) + vde_status_message: StringProperty( + name="状态消息", + default="未检测", + options={'SKIP_SAVE'}, + ) + + def draw(self, context): + layout = self.layout + + # 标题 + box = layout.box() + box.label(text="ViewDesignEngine (VDE) 设置", icon='SETTINGS') + + # 路径设置 + col = box.column(align=True) + col.prop(self, "vde_lib_path") + col.prop(self, "python_path") + + # 检测按钮 + row = box.row(align=True) + row.operator("vde.check_installation", + text="检测 VDE 安装", icon='CHECKMARK') + row.operator("vde.open_docs", + text="在线文档", icon='URL') + + # 状态 + if self.vde_available: + box.label(text=f"✓ {self.vde_status_message}", icon='CHECKMARK') + else: + row = box.row() + row.label(text=f"✗ {self.vde_status_message}", icon='ERROR') + row = box.row() + row.label( + text="VDE 库未连接。请确保 VDE Python 包已安装,", + icon='INFO' + ) + row = box.row() + row.label( + text="并将「VDE 库路径」设为包含 vde/ 的目录。", + icon='BLANK1' + ) + + # 默认参数 + box = layout.box() + box.label(text="默认参数", icon='PREFERENCES') + box.prop(self, "default_deflection") + box.prop(self, "default_sdf_resolution") + + # 关于 + box = layout.box() + box.label(text="关于", icon='INFO') + col = box.column() + col.label(text="ViewDesignEngine v1.0.0") + col.label(text="高性能 C++ CAD 计算几何引擎") + col.label(text="B-Rep | SDF | 布尔 | 网格") + col.label( + text="文档: github.com/ViewDesignEngine/ViewDesignEngine" + ) + + +# ═══════════════════════════════════════════════════════════════ +# 辅助操作符 +# ═══════════════════════════════════════════════════════════════ + +class VDE_OT_check_installation(bpy.types.Operator): + """检测 VDE Python 库是否可用。""" + bl_idname = "vde.check_installation" + bl_label = "检测 VDE 安装" + bl_description = "检测 VDE Python 库是否配置正确" + + def execute(self, context): + prefs = context.preferences.addons[__package__].preferences + + self.report({'INFO'}, "正在检测 VDE...") + + try: + available, message = vde_bridge.check_vde_available(prefs) + except Exception as e: + available, message = False, str(e) + + prefs.vde_available = available + prefs.vde_status_message = message + + if available: + self.report({'INFO'}, f"VDE 检测成功: {message}") + else: + self.report({'ERROR'}, f"VDE 检测失败: {message}") + + # 刷新 UI + for area in context.screen.areas: + if area.type == 'PREFERENCES': + area.tag_redraw() + return {'FINISHED'} + + +class VDE_OT_open_docs(bpy.types.Operator): + """在浏览器中打开 VDE 文档。""" + bl_idname = "vde.open_docs" + bl_label = "打开文档" + bl_description = "在浏览器中打开 ViewDesignEngine 在线文档" + + def execute(self, context): + import webbrowser + webbrowser.open( + "https://github.com/ViewDesignEngine/ViewDesignEngine" + ) + self.report({'INFO'}, "已在浏览器中打开文档页面。") + return {'FINISHED'} + + +# ═══════════════════════════════════════════════════════════════ +# 注册/注销 +# ═══════════════════════════════════════════════════════════════ + +CLASSES = [ + VDEAddonPreferences, + VDE_OT_check_installation, + VDE_OT_open_docs, +] + + +def register(): + for cls in CLASSES: + bpy.utils.register_class(cls) + + +def unregister(): + for cls in reversed(CLASSES): + bpy.utils.unregister_class(cls) diff --git a/blender_addon/vde_bridge.py b/blender_addon/vde_bridge.py new file mode 100644 index 0000000..3d0e939 --- /dev/null +++ b/blender_addon/vde_bridge.py @@ -0,0 +1,501 @@ +""" +vde_bridge.py — VDE 子进程桥接模块 +=================================== +通过独立的 Python 子进程调用 VDE Python 绑定,避免 Blender 与 VDE 的环境冲突。 + +工作原理: + 1. 将操作参数序列化为 JSON + 2. 启动 Python 子进程,传入操作类型和参数 + 3. 子进程导入 VDE,执行操作,输出 JSON 结果 + 4. 解析结果,将几何数据转换为 Blender Mesh + +对于不支持的操作(VDE 库未安装时),返回错误信息并提示安装。 +""" + +import os +import sys +import json +import subprocess + + +# ═══════════════════════════════════════════════════════════════ +# VDE 内联执行脚本(使用 %s 占位符避免与 JSON 大括号冲突) +# ═══════════════════════════════════════════════════════════════ + +_VDE_SCRIPT = r'''"""VDE bridge worker — 由 blender_addon 子进程调用。""" +import sys +import json + +def error(msg): + print(json.dumps({"error": str(msg)})) + sys.exit(1) + +# ── 定位 VDE 库 ── +vde_path = %s + +if vde_path: + sys.path.insert(0, vde_path) + +try: + from vde import brep, mesh, sdf as vde_sdf +except ImportError as e: + error("VDE 库未安装或路径不正确: " + str(e) + + "\n请检查 Blender 插件偏好设置中的 VDE 库路径。") + +# ── 执行操作 ── +operation = %s +args = %s + +try: + if operation == "import_step": + result = run_import_step(args) + elif operation == "export_step": + result = run_export_step(args) + elif operation == "create_primitive": + result = run_create_primitive(args) + elif operation == "boolean": + result = run_boolean(args) + elif operation == "sdf_to_mesh": + result = run_sdf_to_mesh(args) + else: + error("未知操作: " + operation) + print(json.dumps(result)) +except Exception as e: + import traceback + print(json.dumps({ + "error": str(e), + "traceback": traceback.format_exc() + })) + +# ── 操作实现 ── + +def brep_to_mesh_data(body): + """将 BrepModel 转换为网格数据字典。""" + try: + he_mesh = body.to_mesh(0.01) + except Exception as e: + raise RuntimeError("B-Rep 转网格失败: " + str(e)) + + verts = [] + for i in range(he_mesh.num_vertices()): + v = he_mesh.vertex(i) + verts.append([v.x(), v.y(), v.z()]) + + faces = [] + for i in range(he_mesh.num_faces()): + fv = he_mesh.face_vertices(i) + faces.append(list(fv)) + + return { + "vertices": verts, + "faces": faces, + "num_vertices": len(verts), + "num_faces": len(faces), + } + + +def run_import_step(args): + """导入 STEP 文件。""" + path = args.get("path", "") + if not path: + error("STEP 文件路径未指定") + + # 使用 industrial_formats 或 brep 的 import_step + try: + body = brep.import_step(path) + except AttributeError: + try: + from vde._vde.foundation import import_step as vde_import_step + body = vde_import_step(path) + except (ImportError, AttributeError): + error("VDE 未编译 STEP 导入支持。请确保 industrial_formats 模块已编译。") + + return brep_to_mesh_data(body) + + +def run_export_step(args): + """导出选中对象为 STEP 文件。""" + path = args.get("path", "") + if not path: + error("导出路径未指定") + + meshes = args.get("meshes", []) + bodies = [] + + for m in meshes: + verts = m.get("vertices", []) + faces = m.get("faces", []) + + if not verts or not faces: + continue + + # 从网格数据创建 HalfedgeMesh → BrepModel + hm = mesh.HalfedgeMesh() + hm.build_from_triangles(verts, faces) + body = brep.BrepModel.from_mesh(hm) + bodies.append(body) + + if not bodies: + error("没有有效的网格数据可导出") + + try: + brep.export_step(path, bodies) + except AttributeError: + try: + from vde._vde import io + io.export_step_ap242_file(path, bodies) + except (ImportError, AttributeError) as e: + error("VDE 未编译 STEP 导出支持: " + str(e)) + + return {"status": "ok", "path": path, "num_bodies": len(bodies)} + + +def run_create_primitive(args): + """创建 B-Rep 图元。""" + prim_type = args.get("type", "box") + params = args.get("params", {}) + + if prim_type == "box": + w = params.get("width", 2.0) + h = params.get("height", 2.0) + d = params.get("depth", 2.0) + body = brep.make_box(w, h, d) + elif prim_type == "cylinder": + r = params.get("radius", 1.0) + h = params.get("height", 2.0) + body = brep.make_cylinder(r, h) + elif prim_type == "sphere": + r = params.get("radius", 1.0) + body = brep.make_sphere(r) + else: + error("未知图元类型: " + prim_type) + + return brep_to_mesh_data(body) + + +def run_boolean(args): + """布尔运算。""" + op_type = args.get("boolean_type", "union") + mesh_a = args.get("mesh_a", {}) + mesh_b = args.get("mesh_b", {}) + + def mesh_to_brep(m): + hm = mesh.HalfedgeMesh() + hm.build_from_triangles(m.get("vertices", []), m.get("faces", [])) + return brep.BrepModel.from_mesh(hm) + + body_a = mesh_to_brep(mesh_a) + body_b = mesh_to_brep(mesh_b) + + if op_type == "union": + result_body = brep.boolean_union(body_a, body_b) + elif op_type == "intersect": + result_body = brep.boolean_intersection(body_a, body_b) + elif op_type == "diff": + result_body = brep.boolean_difference(body_a, body_b) + else: + error("未知布尔操作: " + op_type) + + return brep_to_mesh_data(result_body) + + +def run_sdf_to_mesh(args): + """SDF → Mesh 转换。""" + sdf_type = args.get("sdf_type", "sphere") + params = args.get("params", {}) + resolution = args.get("resolution", 64) + + if sdf_type == "sphere": + sdf_node = vde_sdf.SdfNode.sphere(params.get("radius", 1.0)) + elif sdf_type == "box": + sdf_node = vde_sdf.SdfNode.box(params.get("half_extents", [1.0, 1.0, 1.0])) + elif sdf_type == "cylinder": + sdf_node = vde_sdf.SdfNode.cylinder( + params.get("radius", 1.0), params.get("height", 2.0) + ) + elif sdf_type == "torus": + sdf_node = vde_sdf.SdfNode.torus( + params.get("major_radius", 1.0), params.get("minor_radius", 0.3) + ) + elif sdf_type == "custom": + sdf_node = build_custom_sdf(params) + else: + error("未知 SDF 类型: " + sdf_type) + + mc_mesh = vde_sdf.sdf_to_mesh(sdf_node, resolution) + + verts = [[v[0], v[1], v[2]] for v in mc_mesh.vertices] + faces = [[t[0], t[1], t[2]] for t in mc_mesh.triangles] + + return { + "vertices": verts, + "faces": faces, + "num_vertices": len(verts), + "num_faces": len(faces), + } + + +def build_custom_sdf(params): + """构建自定义 SDF CSG 树。""" + children = params.get("children", []) + operations = params.get("operations", []) + + if not children: + return vde_sdf.SdfNode.sphere(1.0) + + nodes = [] + for child in children: + ct = child.get("type", "sphere") + cp = child.get("params", {}) + if ct == "sphere": + nodes.append(vde_sdf.SdfNode.sphere(cp.get("radius", 1.0))) + elif ct == "box": + nodes.append(vde_sdf.SdfNode.box( + cp.get("half_extents", [1.0, 1.0, 1.0])) + ) + elif ct == "cylinder": + nodes.append(vde_sdf.SdfNode.cylinder( + cp.get("radius", 1.0), cp.get("height", 2.0)) + ) + elif ct == "torus": + nodes.append(vde_sdf.SdfNode.torus( + cp.get("major_radius", 1.0), cp.get("minor_radius", 0.3)) + ) + + result = nodes[0] + for i, op in enumerate(operations): + next_node = nodes[i + 1] + if op == "union": + result = vde_sdf.SdfNode.op_union(result, next_node) + elif op == "intersection": + result = vde_sdf.SdfNode.op_intersection(result, next_node) + elif op == "difference": + result = vde_sdf.SdfNode.op_difference(result, next_node) + elif op == "smooth_union": + result = vde_sdf.SdfNode.smooth_union( + result, next_node, params.get("blend_radius", 0.1)) + elif op == "smooth_intersection": + result = vde_sdf.SdfNode.smooth_intersection( + result, next_node, params.get("blend_radius", 0.1)) + elif op == "smooth_difference": + result = vde_sdf.SdfNode.smooth_difference( + result, next_node, params.get("blend_radius", 0.1)) + + # 应用变换 + if params.get("translate"): + t = params["translate"] + result = vde_sdf.SdfNode.translate(result, t) + if params.get("scale"): + s = params["scale"] + result = vde_sdf.SdfNode.scale(result, s) + if params.get("round"): + result = vde_sdf.SdfNode.round(result, params["round"]) + if params.get("onion"): + result = vde_sdf.SdfNode.onion(result, params["onion"]) + + return result +''' + + +# ═══════════════════════════════════════════════════════════════ +# 桥接客户端 +# ═══════════════════════════════════════════════════════════════ + +class VDEBridge: + """VDE 桥接客户端 — 通过子进程调用 VDE Python 绑定。""" + + def __init__(self, vde_lib_path=None, python_executable=None): + self.vde_lib_path = vde_lib_path or "" + self.python_executable = python_executable or sys.executable + + def execute(self, operation, **kwargs): + """ + 通过子进程执行 VDE 操作。 + + Args: + operation: 操作类型 ("import_step", "export_step", + "create_primitive", "boolean", "sdf_to_mesh") + **kwargs: 操作参数 + + Returns: + dict: 操作结果,可能包含 "vertices" 和 "faces" 字段 + + Raises: + RuntimeError: VDE 操作失败 + """ + vde_path_repr = repr(self.vde_lib_path) + op_repr = repr(operation) + args_json = json.dumps(kwargs, indent=2) + + script = _VDE_SCRIPT % (vde_path_repr, op_repr, args_json) + + try: + proc = subprocess.run( + [self.python_executable, "-c", script], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + timeout=120, + env={**os.environ, "PYTHONUNBUFFERED": "1"}, + ) + except subprocess.TimeoutExpired: + raise RuntimeError("VDE 操作超时 (120s)。请减小分辨率或检查模型复杂度。") + except FileNotFoundError: + raise RuntimeError( + f"Python 解释器未找到: {self.python_executable}。" + "请检查 VDE 库路径设置。" + ) + + stdout = proc.stdout.strip() + stderr = proc.stderr.strip() + + if proc.returncode != 0: + error_msg = stdout or stderr or f"进程退出码: {proc.returncode}" + raise RuntimeError(f"VDE 子进程错误:\n{error_msg}") + + try: + result = json.loads(stdout) + except json.JSONDecodeError: + raise RuntimeError( + f"VDE 子进程返回了无效 JSON。\n" + f"stdout: {stdout[:500]}\n" + f"stderr: {stderr[:500]}" + ) + + if "error" in result: + raise RuntimeError(f"VDE 操作错误: {result['error']}") + + return result + + +# ═══════════════════════════════════════════════════════════════ +# Blender Mesh 构建工具 +# ═══════════════════════════════════════════════════════════════ + +def bridge_result_to_blender(result, name="VDE_Mesh", collection=None): + """ + 将 VDE 桥接结果转换为 Blender Mesh 对象。 + + Args: + result: VDE 操作返回的 dict,含 "vertices" 和 "faces" + name: 对象名称 + collection: 目标集合(None 则使用活动集合) + + Returns: + bpy.types.Object: 新创建的 Blender 对象 + """ + import bpy + + vertices = result.get("vertices", []) + faces = result.get("faces", []) + + if not vertices or not faces: + raise RuntimeError("VDE 返回了空的网格数据。") + + # 创建 Blender Mesh + mesh = bpy.data.meshes.new(name=name) + mesh.from_pydata(vertices, [], faces) + mesh.update(calc_edges=True) + mesh.validate() + + # 创建 Object 并链接到场景 + obj = bpy.data.objects.new(name=name, object_data=mesh) + if collection is None: + collection = bpy.context.collection + collection.objects.link(obj) + + # 设为活动选中对象 + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + + print(f"[VDE] 创建了 '{name}': " + f"{len(vertices)} 顶点, {len(faces)} 面") + + return obj + + +def blender_mesh_to_bridge_data(obj): + """ + 将 Blender 对象网格数据转换为桥接 JSON 格式。 + + Args: + obj: Blender Object (须为 MESH 类型) + + Returns: + dict: {"vertices": [...], "faces": [...]} + """ + import bpy + + if obj.type != 'MESH': + raise RuntimeError(f"对象 '{obj.name}' 不是 MESH 类型 (是 {obj.type})") + + # 获取世界空间坐标 + depsgraph = bpy.context.evaluated_depsgraph_get() + eval_obj = obj.evaluated_get(depsgraph) + mesh = eval_obj.to_mesh() + + vertices = [] + for v in mesh.vertices: + co = v.co + vertices.append([co.x, co.y, co.z]) + + # 三角剖分(布尔运算需要三角形网格) + faces = [] + for poly in mesh.polygons: + if len(poly.vertices) == 3: + faces.append(list(poly.vertices)) + elif len(poly.vertices) == 4: + faces.append([poly.vertices[0], poly.vertices[1], poly.vertices[2]]) + faces.append([poly.vertices[0], poly.vertices[2], poly.vertices[3]]) + else: + for i in range(1, len(poly.vertices) - 1): + faces.append([ + poly.vertices[0], + poly.vertices[i], + poly.vertices[i + 1] + ]) + + eval_obj.to_mesh_clear() + + return {"vertices": vertices, "faces": faces} + + +# ═══════════════════════════════════════════════════════════════ +# VDE 可用性检测 +# ═══════════════════════════════════════════════════════════════ + +def check_vde_available(prefs): + """ + 检测 VDE Python 库是否可用。 + + Args: + prefs: 插件偏好设置(含 vde_lib_path) + + Returns: + tuple: (available: bool, message: str) + """ + vde_path = prefs.vde_lib_path if prefs else "" + test_script = """import sys +vde_path = %r +if vde_path: + sys.path.insert(0, vde_path) +try: + from vde import brep, mesh, sdf + print("OK") +except ImportError as e: + print("ERROR: " + str(e)) +""" + try: + proc = subprocess.run( + [sys.executable, "-c", test_script % vde_path], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + universal_newlines=True, timeout=10, + env={**os.environ, "PYTHONUNBUFFERED": "1"}, + ) + output = proc.stdout.strip() + if output == "OK": + return True, "VDE 库可用" + else: + return False, output + except Exception as e: + return False, str(e) diff --git a/docs/dev-guide/README.md b/docs/dev-guide/README.md new file mode 100644 index 0000000..ae32b9e --- /dev/null +++ b/docs/dev-guide/README.md @@ -0,0 +1,106 @@ +# ViewDesignEngine 开发者指南 + +欢迎来到 ViewDesignEngine (VDE) 开发者文档。本指南面向希望理解 VDE 内部架构、贡献代码或基于 VDE 构建应用的开发者。 + +## 文档导航 + +| 文档 | 说明 | +|------|------| +| [架构概览](architecture.md) | 模块依赖关系图、数据流、分层架构设计 | +| [API 总览](api-overview.md) | 公开 API 接口一览、命名空间规范、核心类型 | +| [构建指南](building.md) | 从源码构建、Docker 环境、CMake 选项、依赖管理 | +| [测试指南](testing.md) | 测试框架、编写测试、运行测试、覆盖率 | +| [贡献指南](contributing.md) | 代码规范、提交流程、Code Review、分支策略 | +| [插件系统设计](plugin-system.md) | 插件架构、生命周期、动态加载、清单文件 | + +## 快速开始 + +```bash +# 克隆仓库 +git clone ssh://git@localhost:22/hm/ViewDesignEngine.git +cd ViewDesignEngine + +# Docker 构建(推荐) +docker run --rm -v $PWD:/ws vde-builder:latest bash -c \ + "cd /ws && cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j\$(nproc)" + +# 本地构建 +cmake -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build -j$(nproc) + +# 运行测试 +cd build && ctest --output-on-failure +``` + +## 项目结构 + +``` +ViewDesignEngine/ +├── CMakeLists.txt # 顶层 CMake +├── cmake/ # CMake 模块 (CompilerSettings 等) +├── include/vde/ # 公开头文件 +│ ├── foundation/ # 基础数学、I/O、公差 +│ ├── core/ # 几何核心、CAM +│ ├── curves/ # 曲线曲面 +│ ├── mesh/ # 网格处理 +│ ├── spatial/ # 空间索引 +│ ├── boolean/ # 布尔运算 +│ ├── collision/ # 碰撞检测 +│ ├── brep/ # B-Rep 建模 +│ ├── sdf/ # SDF 隐式建模 +│ ├── sketch/ # 草图约束 +│ └── plugin/ # 插件系统 (v6.0+) +├── src/ # 实现文件(按模块分目录) +├── tests/ # 单元测试 +├── bench/ # 性能基准 +├── examples/ # 示例程序 +├── python/ # Python 绑定 +└── docs/ # 文档 +``` + +## 关键概念 + +### 命名空间 + +所有 VDE 代码位于 `vde::` 命名空间下,按模块分子命名空间: + +```cpp +namespace vde::foundation { /* 基础设施 */ } +namespace vde::core { /* 几何核心 */ } +namespace vde::curves { /* 曲线曲面 */ } +namespace vde::mesh { /* 网格处理 */ } +namespace vde::spatial { /* 空间索引 */ } +namespace vde::boolean { /* 布尔运算 */ } +namespace vde::collision { /* 碰撞检测 */ } +namespace vde::brep { /* B-Rep 建模 */ } +namespace vde::sdf { /* SDF 隐式建模 */ } +namespace vde::sketch { /* 草图约束 */ } +namespace vde::plugin { /* 插件系统 */ } +``` + +### 依赖关系 + +- **L0 foundation**: 零上游依赖(仅 Eigen) +- **L1 core**: 依赖 foundation +- **L2 curves**: 依赖 core +- **L3 mesh**: 依赖 core + brep +- **L4 spatial**: 依赖 core +- **L5 boolean/collision**: 依赖 L1-L4 +- **brep**: 依赖 curves + mesh + collision +- **sdf**: 依赖 core + mesh +- **plugin**: 依赖 foundation(独立于其他模块) + +### 设计原则 + +1. **零开销抽象** — 模板与编译期多态,避免不必要的虚函数 +2. **数据驱动** — SoA 布局优化缓存命中率 +3. **不可变优先** — 几何实体默认不可变,变换返回新实体 +4. **RAII 资源管理** — 智能指针管理所有资源 +5. **单一依赖** — 仅依赖 Eigen,不引入 Boost + +## 相关文档 + +- [项目开发计划](../00-开发计划.md) +- [系统架构设计](../02-概要设计/01-系统架构设计.md) +- [CHANGELOG](../../CHANGELOG.md) +- [API 参考手册](../API-REFERENCE.md) diff --git a/docs/dev-guide/api-overview.md b/docs/dev-guide/api-overview.md new file mode 100644 index 0000000..f6dcde7 --- /dev/null +++ b/docs/dev-guide/api-overview.md @@ -0,0 +1,222 @@ +# API 总览 + +ViewDesignEngine 提供 C++17 头文件 API,所有公开接口位于 `include/vde/` 目录下。 + +## 模块 API 概览 + +### foundation — 基础设施 (`vde::foundation`) + +```cpp +// 数学类型 +#include // Point2D, Point3D, Vector2D, Vector3D +#include // Matrix3x3, Matrix4x4, Transform3D +#include // AABB2D, AABB3D + +// 公差与精确谓词 +#include // Tolerance, is_zero(), is_equal() +#include // orient2d, orient3d, incircle + +// 格式 I/O +#include // load_obj(), export_obj() +#include // load_stl(), export_stl() +#include // load_ply(), export_ply() +#include // export_gltf(), export_glb() +#include // load_3mf(), export_3mf() +#include // STEP/IGES 导入导出 +``` + +### core — 几何核心 (`vde::core`) + +```cpp +#include // Polygon2D, triangulate(), simplify() +#include // VoronoiDiagram2D +#include // convex_hull_2d(), convex_hull_3d() +#include // point_to_line(), point_to_triangle() +#include // icp_align() +#include // ToolPath, GCode, CLData +#include // contour_toolpath, pocket_toolpath +#include // 5轴联动: swarf, multi_axis_roughing +#include // ObjectPool 内存池 +``` + +### curves — 曲线曲面 (`vde::curves`) + +```cpp +#include // BezierCurve +#include // BSplineCurve +#include // NurbsCurve +#include // BezierSurface +#include // BSplineSurface +#include // NurbsSurface +#include // offset, trim, blend, ruled, coons, extrude +#include // adaptive_tessellate() +#include // surface_intersect() +#include // curvature_analysis() +#include // G0/G1/G2 continuity check +#include // surface_fairing() +#include // exact_offset_curve() +#include // extend_surface() +#include // parallel_intersect() +``` + +### mesh — 网格处理 (`vde::mesh`) + +```cpp +#include // HalfedgeMesh +#include // delaunay_2d(), cdt_2d() +#include // delaunay_3d() +#include // qem_simplify() +#include // laplacian_smooth(), hc_smooth(), bilateral_smooth() +#include // mesh_union(), mesh_intersection(), mesh_difference() +#include // mesh_quality_report() +#include // repair_non_manifold() +#include // alpha_shape() +#include // parameterize() +#include // geodesic_distance() +#include // curvature() +#include // marching_cubes() +#include // parallel_marching_cubes() +#include // generate_lod() +#include // reverse_engineering pipeline +#include // PointCloud processing +#include // FEA mesh generation +``` + +### spatial — 空间索引 (`vde::spatial`) + +```cpp +#include // BVH +#include // Octree +#include // KDTree +#include // RTree +#include // IncrementalBVH +``` + +### boolean — 布尔运算 (`vde::boolean`) + +```cpp +#include // clip_polygon(), boolean_2d() +#include // mesh_union(), mesh_difference(), mesh_intersection() +#include // offset_polygon() +``` + +### collision — 碰撞检测 (`vde::collision`) + +```cpp +#include // gjk_distance(), gjk_intersect() +#include // sat_intersect() +#include // ray_triangle(), ray_aabb(), ray_mesh() +#include // tri_tri_intersection() +``` + +### brep — B-Rep 建模 (`vde::brep`) + +```cpp +#include // BrepModel, TopoVertex, TopoEdge, TopoFace +#include // make_box, make_sphere, fillet, chamfer, shell +#include // brep_union, brep_intersection, brep_difference +#include // validate() +#include // heal() +#include // Tolerance settings +#include // TrimmedSurface +#include // Surface-surface intersection boolean +#include // STEP/IGES import/export +#include // distance, surface_area, volume, centroid +#include // Drawing generation +#include // Automatic dimensioning +#include // PMI/MBD annotations +#include // GD&T annotations +#include // Euler operators +#include // Draft angle analysis +#include // Feature recognition +#include // Defeaturing +#include // Advanced blending +#include // Free-form deformation +#include // Assembly constraint solving +#include // Direct modeling +#include // Kinematic chains +#include // Motion simulation +#include // Assembly LOD +#include // Fastener library +``` + +### sdf — 隐式建模 (`vde::sdf`) + +```cpp +#include // sphere, box, cylinder, torus, cone, etc. +#include // union, intersection, difference, smooth_union +#include // SdfNode, evaluate() +#include // sdf_to_mesh() +#include // gradient() +#include // fit_to_point_cloud(), optimize() +``` + +### sketch — 草图约束 (`vde::sketch`) + +```cpp +#include // ConstraintSolver +``` + +### plugin — 插件系统 (`vde::plugin`, v6.0+) + +```cpp +#include // PluginInterface, PluginManager, PluginManifest +``` + +详见 [插件系统设计](plugin-system.md)。 + +## 核心类型速查 + +| 类型 | 命名空间 | 说明 | +|------|---------|------| +| `Point2D` / `Point3D` | `vde::core` | 2D/3D 点 | +| `Vector2D` / `Vector3D` | `vde::core` | 2D/3D 向量 | +| `AABB2D` / `AABB3D` | `vde::core` | 轴对齐包围盒 | +| `Matrix3x3` / `Matrix4x4` | `vde::core` | 3x3 / 4x4 变换矩阵 | +| `Triangle` | `vde::core` | 三角形 | +| `HalfedgeMesh` | `vde::mesh` | 半边网格数据结构 | +| `BrepModel` | `vde::brep` | B-Rep 实体模型 | +| `SdfNode` | `vde::sdf` | SDF 场景图节点 | +| `BezierCurve` | `vde::curves` | Bézier 曲线 | +| `NurbsSurface` | `vde::curves` | NURBS 曲面 | + +## 使用约定 + +### 头文件包含 + +```cpp +// 推荐:只包含需要的头文件 +#include +#include + +// 不推荐:包含聚合头(编译慢) +// #include // 不存在此文件 +``` + +### 命名空间使用 + +```cpp +// .cpp 文件中可以使用 using +using namespace vde::core; +using namespace vde::mesh; + +// .h 文件中禁止 using namespace +// 使用完整限定名或在作用域内声明 +namespace vde::my_module { + using vde::core::Point3D; +} +``` + +### 错误处理 + +VDE 采用以下错误处理策略: +- **返回值**: 大部分函数返回 `std::optional` 或错误码 +- **断言**: 内部不变量使用 `assert()` +- **异常**: 仅在构造函数/资源分配失败时使用(RAII 保证) +- **日志**: 通过 `VDE_LOG_WARNING` / `VDE_LOG_ERROR` 宏 + +### 线程安全 + +- 所有 `const` 方法可并发调用 +- 非 `const` 方法需要外部同步 +- PluginManager 提供内部读写锁 diff --git a/docs/dev-guide/architecture.md b/docs/dev-guide/architecture.md new file mode 100644 index 0000000..2595734 --- /dev/null +++ b/docs/dev-guide/architecture.md @@ -0,0 +1,240 @@ +# 架构概览 + +## 1. 整体架构 + +ViewDesignEngine 采用分层架构,从底层数学基础设施到高层领域建模,层层递进。v6.0 引入了插件系统,支持第三方扩展。 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 应用层 (Application) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │ +│ │ C++ API │ │ Python绑定│ │ C API │ │ 插件系统 (v6) │ │ +│ │ (头文件) │ │(pybind11) │ │ (vde_capi)│ │ PluginManager │ │ +│ └─────┬─────┘ └─────┬────┘ └─────┬────┘ └───────┬────────┘ │ +│ │ │ │ │ │ +├────────┴──────────────┴─────────────┴───────────────┴────────────┤ +│ 领域建模层 (Domain) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ +│ │ B-Rep │ │ SDF │ │ Sketch │ │ CAM │ │ +│ │ 实体建模 │ │ 隐式建模 │ │ 草图约束 │ │ 加工策略 │ │ +│ └─────┬─────┘ └─────┬────┘ └─────┬────┘ └────────┬─────────┘ │ +│ │ │ │ │ │ +├────────┴──────────────┴─────────────┴───────────────┴────────────┤ +│ 几何处理层 (Geometry) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ +│ │ Curves │ │ Mesh │ │ Boolean │ │ Collision │ │ +│ │ 曲线曲面 │ │ 网格处理 │ │ 布尔运算 │ │ 碰撞检测 │ │ +│ └─────┬─────┘ └─────┬────┘ └─────┬────┘ └────────┬─────────┘ │ +│ │ │ │ │ │ +│ └──────────────┴──────┬──────┴───────────────┘ │ +│ │ │ +├──────────────────────────────┴────────────────────────────────────┤ +│ 空间索引层 (Spatial) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ +│ │ BVH │ │ Octree │ │ KD-Tree │ │ R-Tree │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────────────┘ │ +├───────────────────────────────────────────────────────────────────┤ +│ 基础设施层 (Foundation) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ +│ │ Eigen封装│ │ 公差系统 │ │ I/O │ │ 精确谓词 │ │ +│ │ 数学库 │ │Tolerance │ │格式读写 │ │ exact_predicates│ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────────────┘ │ +└───────────────────────────────────────────────────────────────────┘ +``` + +## 2. 模块依赖图 + +``` + ┌─────────────────────┐ + │ vde (聚合) │ + └──────────┬──────────┘ + ┌───────────────────┼───────────────────┐ + │ │ │ + ┌──────┴──────┐ ┌──────┴──────┐ ┌──────┴──────┐ + │ vde_capi │ │ vde_brep │ │ vde_plugin │ + │ (C 接口) │ │ (B-Rep) │ │ (插件系统) │ + └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ + │ │ │ + ┌──────┼──────────┬──────┼──────────┐ │ + │ │ │ │ │ │ + ▼ ▼ ▼ ▼ ▼ │ +┌──────┐┌──────┐┌──────┐┌──────┐┌─────────┐ │ +│vde_ ││vde_ ││vde_ ││vde_ ││vde_ │ │ +│sdf ││sketch││mesh ││curves││collision│ │ +└──┬───┘└──┬───┘└──┬───┘└──┬───┘└────┬────┘ │ + │ │ │ │ │ │ + └───────┴───────┼───────┴─────────┘ │ + │ │ + ┌────┴────┐ ┌─────┴──────┐ + │vde_core │ │vde_spatial │ + └────┬────┘ └─────┬──────┘ + │ │ + ┌────┴──────────────────────────┘ + │ + ┌────┴──────────┐ + │vde_foundation │ + └───────┬───────┘ + │ + ┌─────┴─────┐ + │Eigen3 (外部)│ + └───────────┘ + +vde_boolean 依赖: vde_mesh + vde_spatial + vde_core +vde_collision 依赖: vde_core + vde_spatial +vde_gpu (可选): vde_mesh + CUDA::cudart +vde_plugin 依赖: vde_foundation (独立于其他模块) +``` + +## 3. 数据流 + +### 3.1 典型几何处理管线 + +``` +输入 (STEP/IGES/OBJ/STL) + │ + ▼ +┌──────────────┐ +│ Format I/O │ foundation::import_step / import_iges / load_obj / load_stl +│ 格式解析 │ +└──────┬───────┘ + │ + ▼ +┌──────────────┐ +│ 几何核心 │ core::Point3D, core::AABB3D, core::transform +│ 类型系统 │ +└──────┬───────┘ + │ + ├─────────────────────────────────────┐ + ▼ ▼ +┌──────────────┐ ┌──────────────┐ +│ 曲线曲面建模 │ │ 网格处理 │ +│ curves │ │ mesh │ +│ Bezier/NURBS │ │ Delaunay/MC │ +└──────┬───────┘ └──────┬───────┘ + │ │ + ├──────────────┬──────────────────────┘ + ▼ ▼ +┌──────────────┐┌──────────────┐ +│ 空间索引 ││ 布尔运算 │ +│ spatial ││ boolean │ +│ BVH/Octree ││ CSG │ +└──────┬───────┘└──────┬───────┘ + │ │ + ▼ ▼ +┌──────────────┐┌──────────────┐ +│ 碰撞检测 ││ B-Rep 建模 │ +│ collision ││ brep │ +│ GJK/SAT ││ 抽壳/倒圆 │ +└──────┬───────┘└──────┬───────┘ + │ │ + └───────┬───────┘ + ▼ +┌──────────────┐ +│ Format I/O │ foundation::export_step / export_gltf / export_stl +│ 格式导出 │ +└──────┬───────┘ + ▼ +输出 (STEP/glTF/STL/OBJ) +``` + +### 3.2 插件系统数据流 (v6.0) + +``` +┌─────────────────────────────────────────────────────┐ +│ PluginManager │ +│ │ +│ load_plugin_dir("/usr/lib/vde/plugins") │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────┐ │ +│ │ 扫描 .so/.dylib/.dll 文件 │ │ +│ │ dlopen → dlsym(create_plugin) │ │ +│ │ → PluginInterface* │ │ +│ └──────────────┬──────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────┐ │ +│ │ PluginRegistry (name → Plugin*) │ │ +│ │ io_exporters / io_importers │ │ +│ │ geometry_filters │ │ +│ │ custom_tools │ │ +│ └─────────────────────────────────────┘ │ +│ │ +│ 调用: manager.get_plugin("io.export.gltf") │ +│ → PluginInterface::execute(...) │ +└─────────────────────────────────────────────────────┘ +``` + +### 3.3 可微分几何管线 (SDF) + +``` +参数化形状 (SdfNode) + │ + ▼ +┌──────────────────┐ +│ SDF 求值 │ sdf::evaluate(node, point) +│ (CSG 树遍历) │ +└────────┬─────────┘ + │ + ▼ +┌──────────────────┐ +│ 自动微分 │ sdf::gradient(node, point) +│ (正向/反向模式) │ +└────────┬─────────┘ + │ + ▼ +┌──────────────────┐ +│ 梯度优化 │ sdf::optimize::fit_to_point_cloud +│ (Adam/SGD) │ +└────────┬─────────┘ + │ + ▼ +┌──────────────────┐ +│ SDF → Mesh │ sdf::marching_cubes → HalfedgeMesh +│ (Marching Cubes) │ +└────────┬─────────┘ + │ + ▼ +┌──────────────────┐ +│ 导出 GLB │ foundation::export_gltf_binary +└──────────────────┘ +``` + +## 4. 线程模型 + +- **单线程默认**: 所有 API 默认线程安全,内部无全局可变状态 +- **OpenMP 并行**: 在 B-Rep 布尔、网格处理等热点路径启用 (`VDE_USE_OPENMP=ON`) +- **CUDA 加速**: 独立 GPU 库 `vde_gpu`,不阻塞 CPU 路径 +- **插件线程安全**: 插件需自行保证线程安全,PluginManager 提供读写锁 + +## 5. 内存模型 + +``` +┌─────────────────────────────────────────┐ +│ 内存分配策略 │ +│ │ +│ ┌──────────────┐ ┌─────────────────┐ │ +│ │ ObjectPool │ │ std::unique_ptr │ │ +│ │ (mesh顶点/面) │ │ (B-Rep 实体) │ │ +│ │ 连续内存块 │ │ RAII 所有权 │ │ +│ └──────────────┘ └─────────────────┘ │ +│ │ +│ ┌──────────────┐ ┌─────────────────┐ │ +│ │ SoA 布局 │ │ 小对象栈分配 │ │ +│ │ (AoS→SoA) │ │ (Point3D/AABB) │ │ +│ │ 缓存友好 │ │ 零堆开销 │ │ +│ └──────────────┘ └─────────────────┘ │ +└─────────────────────────────────────────┘ +``` + +## 6. 版本演进 + +| 版本 | 关键里程碑 | 日期 | +|------|-----------|------| +| v0.1 - v0.9 | 核心模块建立 | 2026-07-23 | +| v1.0 | 首个稳定版本 | 2026-07-24 | +| v2.0 | B-Rep 完善 + STEP | 2026-07-24 | +| v3.0 - v3.5 | SDF/IGES/装配/CAM | 2026-07-24 | +| v4.x | 工业强化 (GDT/PMI/特征识别) | 计划中 | +| v5.x | 性能优化 + 反向工程 | 计划中 | +| v6.0 | 插件系统 + 开发者生态 | 计划中 | diff --git a/docs/dev-guide/building.md b/docs/dev-guide/building.md new file mode 100644 index 0000000..37fcc86 --- /dev/null +++ b/docs/dev-guide/building.md @@ -0,0 +1,292 @@ +# 构建指南 + +本文档说明如何从源码构建 ViewDesignEngine。 + +## 目录 + +1. [环境要求](#1-环境要求) +2. [快速构建](#2-快速构建) +3. [Docker 构建](#3-docker-构建) +4. [CMake 选项](#4-cmake-选项) +5. [依赖管理](#5-依赖管理) +6. [跨平台构建](#6-跨平台构建) +7. [常见问题](#7-常见问题) + +--- + +## 1. 环境要求 + +| 组件 | 最低版本 | 推荐版本 | +|------|---------|---------| +| C++ 编译器 | GCC 11 / Clang 16 | GCC 13 / Clang 18 | +| CMake | 3.16 | 3.28+ | +| Eigen 3 | 3.3 | 3.4.0 (自动下载) | +| Python (可选) | 3.8 | 3.11+ | +| pybind11 (可选) | 2.10 | 2.12+ | +| CUDA (可选) | 10.0 | 12.0+ | + +### 安装编译工具 + +**Ubuntu/Debian**: +```bash +sudo apt update +sudo apt install build-essential cmake g++-11 +``` + +**CentOS/RHEL 8**: +```bash +sudo yum install gcc-toolset-11 cmake3 +scl enable gcc-toolset-11 bash +``` + +**macOS**: +```bash +brew install cmake gcc@13 +``` + +**Windows (MSYS2)**: +```bash +pacman -S mingw-w64-x86_64-cmake mingw-w64-x86_64-gcc +``` + +## 2. 快速构建 + +```bash +# 克隆仓库 +git clone ssh://git@localhost:22/hm/ViewDesignEngine.git +cd ViewDesignEngine + +# 创建构建目录 +cmake -B build -DCMAKE_BUILD_TYPE=Release + +# 构建(使用所有 CPU 核心) +cmake --build build -j$(nproc) + +# 运行测试 +cd build && ctest --output-on-failure +``` + +### 构建类型 + +| 类型 | 说明 | +|------|------| +| `Release` | 优化构建,生产环境使用 | +| `Debug` | 调试符号,无优化 | +| `RelWithDebInfo` | 优化 + 调试符号 | +| `MinSizeRel` | 最小体积 | + +```bash +# Debug 构建(包含 Address Sanitizer) +cmake -B build -DCMAKE_BUILD_TYPE=Debug -DENABLE_SANITIZERS=ON +cmake --build build -j$(nproc) +``` + +## 3. Docker 构建 + +推荐使用 Docker 确保构建环境一致。 + +### 3.1 预构建镜像 + +```bash +# 拉取或构建镜像 +docker build -t vde-builder -f docker/Dockerfile.dev . + +# Release 构建 +docker run --rm --cpus=4 --memory=8g \ + -v $(pwd):/ws vde-builder bash -c \ + "cd /ws && cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j4" + +# 运行测试 +docker run --rm --cpus=4 --memory=8g \ + -v $(pwd):/ws vde-builder bash -c \ + "cd /ws/build && ctest --output-on-failure" +``` + +### 3.2 资源限制 + +```bash +# 模拟低配环境(2 核 / 4GB) +docker run --rm --cpus=2 --memory=4g --memory-swap=4g \ + -v $(pwd):/ws vde-builder bash -c \ + "cd /ws && cmake -B build_ci -DCMAKE_BUILD_TYPE=Release && cmake --build build_ci -j2" +``` + +## 4. CMake 选项 + +| 选项 | 默认值 | 说明 | +|------|--------|------| +| `BUILD_TESTS` | ON | 构建测试 | +| `BUILD_BENCHMARKS` | OFF | 构建性能基准 | +| `BUILD_EXAMPLES` | ON | 构建示例程序 | +| `ENABLE_SANITIZERS` | OFF | 启用 Address Sanitizer | +| `VDE_USE_GMP` | OFF | GMP 精确运算 | +| `VDE_USE_OPENMP` | ON | OpenMP 并行 | +| `VDE_USE_CUDA` | OFF | CUDA GPU 加速 | +| `VDE_BUILD_PYTHON` | OFF | Python 绑定 | + +### 常用组合 + +```bash +# 开发调试(ASan + Debug) +cmake -B build_asan \ + -DCMAKE_BUILD_TYPE=Debug \ + -DENABLE_SANITIZERS=ON \ + -DVDE_USE_OPENMP=OFF + +# 生产构建(Release + OpenMP) +cmake -B build_release \ + -DCMAKE_BUILD_TYPE=Release \ + -DVDE_USE_OPENMP=ON + +# GPU 加速 +cmake -B build_gpu \ + -DCMAKE_BUILD_TYPE=Release \ + -DVDE_USE_CUDA=ON + +# Python 绑定 +cmake -B build_py \ + -DCMAKE_BUILD_TYPE=Release \ + -DVDE_BUILD_PYTHON=ON + +# 基准测试 +cmake -B build_bench \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_BENCHMARKS=ON +``` + +## 5. 依赖管理 + +VDE 使用 CMake FetchContent 自动下载依赖,无需手动安装。 + +### 自动下载的依赖 + +| 依赖 | 版本 | 用途 | +|------|------|------| +| Eigen 3 | 3.4.0 | 线性代数 | +| Google Test | 1.14.0 | 单元测试 | +| Google Benchmark | (可选) | 性能基准 | +| pybind11 | (可选) | Python 绑定 | + +### 系统依赖 + +| 依赖 | 检测方式 | 回退 | +|------|---------|------| +| Eigen 3 | `find_package(Eigen3)` | FetchContent 自动下载 | +| GMP | `find_package(GMP)` | `VDE_USE_GMP=OFF` 时跳过 | +| OpenMP | `find_package(OpenMP)` | `VDE_USE_OPENMP=OFF` 时跳过 | +| CUDA | `find_package(CUDAToolkit)` | `VDE_USE_CUDA=OFF` 时跳过 | + +```bash +# 使用系统 Eigen(加速首次构建) +sudo apt install libeigen3-dev # Ubuntu +sudo yum install eigen3-devel # CentOS + +# 使用系统 GTest +sudo apt install libgtest-dev # Ubuntu +``` + +## 6. 跨平台构建 + +### Linux + +```bash +cmake -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build -j$(nproc) +``` + +### macOS + +```bash +# 使用 Homebrew GCC +export CC=/usr/local/bin/gcc-13 +export CXX=/usr/local/bin/g++-13 +cmake -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build -j$(sysctl -n hw.ncpu) +``` + +### Windows (MSYS2 MinGW64) + +```bash +cmake -B build -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release +cmake --build build -j$(nproc) +``` + +### Windows (Visual Studio) + +```bash +cmake -B build -G "Visual Studio 17 2022" +cmake --build build --config Release +``` + +## 7. 常见问题 + +### Q: CMake 找不到编译器 + +```bash +export CC=/usr/bin/gcc-11 +export CXX=/usr/bin/g++-11 +cmake -B build ... +``` + +### Q: FetchContent 下载失败(网络问题) + +手动下载并放到 `build/_deps/`: +```bash +mkdir -p build/_deps +# 下载 eigen-3.4.0.tar.gz 到 build/_deps/ +# 下载 googletest-1.14.0.tar.gz 到 build/_deps/ +``` + +### Q: 编译内存不足 + +```bash +# 限制并行数 +cmake --build build -j2 + +# 或在 Docker 中限制内存 +docker run --memory=4g ... +``` + +### Q: ASan 报告内存泄漏 + +```bash +# Debug + ASan 构建 +cmake -B build_asan -DCMAKE_BUILD_TYPE=Debug -DENABLE_SANITIZERS=ON +cmake --build build_asan -j$(nproc) + +# 运行测试并检查泄漏 +cd build_asan +ASAN_OPTIONS=detect_leaks=1 ctest --output-on-failure +``` + +### Q: 如何加速增量构建 + +```bash +# 安装 ccache +sudo apt install ccache +export CMAKE_C_COMPILER_LAUNCHER=ccache +export CMAKE_CXX_COMPILER_LAUNCHER=ccache +cmake -B build ... +``` + +### Q: 构建产物位置 + +``` +build/ +├── src/ # 静态库 (.a) +│ ├── libvde_foundation.a +│ ├── libvde_core.a +│ ├── libvde_curves.a +│ ├── libvde_mesh.a +│ ├── libvde_spatial.a +│ ├── libvde_boolean.a +│ ├── libvde_collision.a +│ ├── libvde_brep.a +│ ├── libvde_sdf.a +│ ├── libvde_sketch.a +│ └── libvde_capi.a +├── tests/ # 测试可执行文件 +├── bench/ # 基准测试 (BUILD_BENCHMARKS=ON) +├── examples/ # 示例程序 +└── python/ # Python 绑定 (VDE_BUILD_PYTHON=ON) +``` diff --git a/docs/dev-guide/contributing.md b/docs/dev-guide/contributing.md new file mode 100644 index 0000000..184b100 --- /dev/null +++ b/docs/dev-guide/contributing.md @@ -0,0 +1,249 @@ +# 贡献指南 + +感谢你对 ViewDesignEngine 的关注!本文档说明如何参与 VDE 开发。 + +## 目录 + +1. [行为准则](#1-行为准则) +2. [如何贡献](#2-如何贡献) +3. [开发环境搭建](#3-开发环境搭建) +4. [代码规范](#4-代码规范) +5. [提交规范](#5-提交规范) +6. [Code Review 流程](#6-code-review-流程) +7. [测试要求](#7-测试要求) +8. [文档要求](#8-文档要求) + +--- + +## 1. 行为准则 + +- 尊重所有贡献者,建设性沟通 +- 关注代码质量而非个人 +- 接受建设性批评,乐于改进 +- 帮助新人融入项目 + +## 2. 如何贡献 + +| 贡献方式 | 说明 | +|---------|------| +| Bug 报告 | 通过 Issue 提交,附重现步骤 | +| 功能请求 | 先开 Issue 讨论,获得认同后再实现 | +| 代码贡献 | Fork → Branch → PR → Review → Merge | +| 文档改进 | 直接提 PR 修正文档错误 | +| 测试补充 | 新增测试用例,提高覆盖率 | +| 插件开发 | 按 [插件系统设计](plugin-system.md) 开发第三方插件 | + +### 贡献流程 + +```bash +# 1. 创建分支 +git checkout -b feat/my-feature + +# 2. 开发(遵循代码规范) +# ... 编写代码 + 测试 ... + +# 3. 本地验证 +cmake -B build -DBUILD_TESTS=ON +cmake --build build -j$(nproc) +cd build && ctest --output-on-failure + +# 4. 提交 +git add -A +git commit -m "feat(module): description" + +# 5. 推送并创建 PR +git push origin feat/my-feature +``` + +## 3. 开发环境搭建 + +### 前提条件 + +- **编译器**: GCC 11+ / Clang 16+ +- **CMake**: ≥ 3.16 +- **Eigen 3**: 自动下载(FetchContent) +- **Google Test**: 自动下载(FetchContent) + +### Docker 环境(推荐) + +```bash +# 构建 Docker 镜像 +docker build -t vde-builder -f docker/Dockerfile.dev . + +# 运行开发容器 +docker run -it --rm -v $PWD:/ws vde-builder bash +cd /ws +cmake -B build -DCMAKE_BUILD_TYPE=Debug +cmake --build build -j$(nproc) +``` + +### 本地环境 + +```bash +# Ubuntu/Debian +sudo apt install build-essential cmake g++-11 + +# CentOS/RHEL +sudo yum install gcc-toolset-11 cmake3 + +# 构建 +cmake -B build -DCMAKE_BUILD_TYPE=Debug +cmake --build build -j$(nproc) +``` + +## 4. 代码规范 + +### 命名规范 + +| 元素 | 规范 | 示例 | +|------|------|------| +| 命名空间 | 小写,`vde::` 前缀 | `vde::brep`, `vde::mesh` | +| 类/结构体 | PascalCase | `HalfedgeMesh`, `BrepModel` | +| 函数/方法 | snake_case | `add_vertex()`, `to_mesh()` | +| 成员变量 | snake_case,尾部 `_` | `vertices_`, `tolerance_` | +| 常量 | kPascalCase 或 UPPER_SNAKE | `kDefaultTolerance`, `VDE_PI` | +| 头文件 | snake_case.h | `halfedge_mesh.h` | +| 源文件 | snake_case.cpp | `halfedge_mesh.cpp` | +| 模板参数 | PascalCase | `typename T`, `typename Scalar` | + +### 文件组织 + +```cpp +// 头文件示例 +#pragma once + +#include // 公开依赖 +#include // 标准库 + +namespace vde::mesh { + +/// 简要描述 +class HalfedgeMesh { +public: + // 构造/析构 + HalfedgeMesh(); + ~HalfedgeMesh(); + + // 禁止拷贝,允许移动 + HalfedgeMesh(const HalfedgeMesh&) = delete; + HalfedgeMesh& operator=(const HalfedgeMesh&) = delete; + HalfedgeMesh(HalfedgeMesh&&) noexcept = default; + HalfedgeMesh& operator=(HalfedgeMesh&&) noexcept = default; + + // 公开接口 + int add_vertex(const Point3D& p); + int add_face(const std::vector& vertex_ids); + +private: + // 成员变量 + std::vector vertices_; +}; + +} // namespace vde::mesh +``` + +### 编码风格 + +- 缩进: 4 空格,不用 Tab +- 行宽: 100 字符 +- 大括号: K&R 风格(开括号不换行) +- 注释: Doxygen `///` 风格 +- `#include` 顺序: 本模块头 → 项目头 → 标准库 +- 避免 `using namespace` 在头文件中 +- 优先使用 `std::unique_ptr` 而非裸指针 + +### 禁止事项 + +- ❌ 全局可变状态 +- ❌ 裸 `new`/`delete`(用智能指针) +- ❌ C 风格类型转换(用 `static_cast` 等) +- ❌ 可变参数 `...` +- ❌ 异常规范声明(`throw()`) +- ❌ 头文件 `using namespace` + +## 5. 提交规范 + +### 提交消息格式 + +``` +(): + + + +