feat(v6.2): Blender addon + .NET/C# bindings + developer docs + plugin system
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

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)
This commit is contained in:
茂之钳
2026-07-26 22:24:40 +08:00
parent 66777e0839
commit 5e2812a6f3
27 changed files with 5748 additions and 2 deletions
+84
View File
@@ -588,6 +588,90 @@ VdeMeshHandle vde_body_to_mesh(VdeHandle ctx, VdeBodyHandle body, double deflect
*/
void vde_body_free(VdeBodyHandle body);
/** @brief 深拷贝 B-Rep 实体 */
VdeBodyHandle vde_body_clone(VdeHandle ctx, VdeBodyHandle body);
// ── B-Rep STEP/IGES I/O ──
/** @brief 从 STEP 文件导入 B-Rep 实体列表。返回数量,每个 body 写入 out_bodies */
int vde_body_load_step(VdeHandle ctx, const char* path, VdeBodyHandle** out_bodies);
/** @brief 从 IGES 文件导入 B-Rep 实体列表 */
int vde_body_load_iges(VdeHandle ctx, const char* path, VdeBodyHandle** out_bodies);
/** @brief 将 B-Rep 实体列表导出为 STEP 文件。成功返回 1 */
int vde_body_save_step(VdeHandle ctx, VdeBodyHandle* bodies, int count, const char* path);
/** @brief 将 B-Rep 实体列表导出为 IGES 文件 */
int vde_body_save_iges(VdeHandle ctx, VdeBodyHandle* bodies, int count, const char* path);
// ── B-Rep Boolean ──
/** @brief B-Rep 布尔运算。op: 0=Union, 1=Intersection, 2=Difference (A\B) */
VdeBodyHandle vde_body_boolean(VdeHandle ctx, VdeBodyHandle a, VdeBodyHandle b, int op);
// ── Mesh face data access ──
/** @brief 获取网格面的顶点索引。返回顶点数,out_indices 需调用 vde_free_buffer 释放 */
int vde_mesh_get_face(VdeMeshHandle mesh, size_t face_idx, int** out_indices);
// ═══════════════════════════════════════════════════════════
// SDF 建模
// ═══════════════════════════════════════════════════════════
/** @brief SDF 节点句柄(不透明) */
#ifndef VDE_SDF_HANDLE_DEFINED
typedef struct VdeSdfNode* VdeSdfHandle;
#endif
/** @brief 创建 SDF 球体节点 */
VdeSdfHandle vde_sdf_sphere(VdeHandle ctx, double radius);
/** @brief 创建 SDF 盒子节点 */
VdeSdfHandle vde_sdf_box(VdeHandle ctx, double hx, double hy, double hz);
/** @brief 创建 SDF 圆柱节点 */
VdeSdfHandle vde_sdf_cylinder(VdeHandle ctx, double radius, double height);
/** @brief SDF 布尔并集 */
VdeSdfHandle vde_sdf_union(VdeHandle ctx, VdeSdfHandle a, VdeSdfHandle b);
/** @brief SDF 布尔交集 */
VdeSdfHandle vde_sdf_intersection(VdeHandle ctx, VdeSdfHandle a, VdeSdfHandle b);
/** @brief SDF 布尔差集 */
VdeSdfHandle vde_sdf_difference(VdeHandle ctx, VdeSdfHandle a, VdeSdfHandle b);
/** @brief SDF 转网格 */
VdeMeshHandle vde_sdf_to_mesh(VdeHandle ctx, VdeSdfHandle sdf, int resolution);
/** @brief 释放 SDF 节点 */
void vde_sdf_free(VdeSdfHandle sdf);
// ═══════════════════════════════════════════════════════════
// 装配体
// ═══════════════════════════════════════════════════════════
/** @brief 装配体句柄 */
#ifndef VDE_ASSEMBLY_HANDLE_DEFINED
typedef struct VdeAssembly* VdeAssemblyHandle;
#endif
/** @brief 创建装配体 */
VdeAssemblyHandle vde_assembly_create(VdeHandle ctx, const char* name);
/** @brief 向装配体根节点添加零件 */
int vde_assembly_add_part(VdeAssemblyHandle assembly, const char* name, VdeBodyHandle body);
/** @brief 获取装配体零件数量 */
int vde_assembly_part_count(VdeAssemblyHandle assembly);
/** @brief 释放装配体(不释放其中的 body,调用方自行管理) */
void vde_assembly_free(VdeAssemblyHandle assembly);
/** @brief 释放 body 句柄数组(由 load_step/load_iges 返回) */
void vde_body_free_array(VdeBodyHandle* bodies, int count);
// ═══════════════════════════════════════════════════════════
// 序列化
// ═══════════════════════════════════════════════════════════
+396
View File
@@ -0,0 +1,396 @@
#pragma once
/**
* @file plugin_system.h
* @brief 插件系统 — 动态加载、生命周期管理、元数据定义
*
* ## 设计目标
* - 零侵入:插件独立编译,不修改核心代码
* - 动态加载:运行时 dlopen/LoadLibrary 加载
* - 版本兼容:Manifest 声明版本约束,自动检查
* - 隔离性:插件崩溃不影响主程序
*
* ## 核心组件
* - PluginInterface: 所有插件必须实现的抽象基类
* - PluginManifest: 插件元数据(name/version/author/type)
* - PluginManager: 加载/卸载/查询插件的管理器
*
* ## 使用示例
* @code
* vde::plugin::PluginManager mgr;
* mgr.load_plugin_dir("/usr/lib/vde/plugins");
* auto* p = mgr.get_plugin("io.export.gltf");
* p->execute("export", params, &mesh, &result);
* mgr.unload_all();
* @endcode
*
* @ingroup plugin
* @since v6.0
*/
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>
// ═══════════════════════════════════════════════════════════
// 平台宏 — 跨平台动态库导入/导出
// ═══════════════════════════════════════════════════════════
#if defined(_WIN32) || defined(_WIN64)
#ifdef VDE_PLUGIN_BUILD
#define VDE_PLUGIN_EXPORT __declspec(dllexport)
#else
#define VDE_PLUGIN_EXPORT __declspec(dllimport)
#endif
#define VDE_PLUGIN_LOCAL
#else
#if __GNUC__ >= 4
#define VDE_PLUGIN_EXPORT __attribute__((visibility("default")))
#define VDE_PLUGIN_LOCAL __attribute__((visibility("hidden")))
#else
#define VDE_PLUGIN_EXPORT
#define VDE_PLUGIN_LOCAL
#endif
#endif
namespace vde::plugin {
// ═══════════════════════════════════════════════════════════
// PluginManifest — 插件元数据
// ═══════════════════════════════════════════════════════════
/// 插件元数据,提供插件的身份信息和兼容性约束。
///
/// 每个插件在 initialize() 中填充此结构。PluginManager
/// 在加载时读取 manifest 检查版本兼容性。
///
/// @see plugin.json 清单文件格式
struct PluginManifest {
/// 唯一标识符,遵循反向域名格式
/// 示例: "io.export.usdz", "geometry.filter.laplacian"
std::string name;
/// 语义化版本号 "major.minor.patch"
/// 示例: "1.0.0", "2.1.3-beta"
std::string version;
/// 作者信息,格式 "Name <email>" 或 "Organization"
std::string author;
/// 插件功能描述(单行简介)
std::string description;
/// 许可证标识符 (SPDX)
/// 示例: "MIT", "Apache-2.0", "GPL-3.0-only"
std::string license;
/// 最低兼容的 VDE 版本 "6.0.0"
std::string vde_version_min;
/// 最高兼容的 VDE 版本(空字符串 = 无上限)
std::string vde_version_max;
// ══════════════════════════════════════════════
// 插件类型枚举
// ══════════════════════════════════════════════
enum class Type : uint8_t {
IO_IMPORT = 0, ///< 格式导入器 (STEP额外格式, USDZ, FBX...)
IO_EXPORT = 1, ///< 格式导出器 (USDZ, FBX, X3D...)
GEOMETRY_FILTER = 2, ///< 几何过滤器 (平滑, 简化, 特征提取)
CUSTOM_TOOL = 3, ///< 自定义工具 (应力分析, 拓扑优化...)
OTHER = 255 ///< 未分类
};
Type type = Type::OTHER;
/// 依赖的其他插件名称列表
/// 加载此插件前必须先加载依赖项
std::vector<std::string> dependencies;
/// 动态库文件名(用于清单文件加载模式)
/// 示例: "libvde_io_usdz.so"
std::string library;
/// 入口点符号名称
struct EntryPoints {
std::string create = "create_plugin"; ///< 工厂函数符号
std::string destroy = "destroy_plugin"; ///< 销毁函数符号
std::string get_manifest = "get_manifest"; ///< 清单查询(可选)
};
EntryPoints entry_points;
// ── 工厂方法 ──────────────────────────────────
/// 从 plugin.json 文件加载清单
/// @param path JSON 文件路径
/// @return 清单对象,解析失败返回 nullopt
static std::optional<PluginManifest> from_file(const std::string& path);
/// 从 JSON 字符串解析清单
/// @param json JSON 文本内容
/// @return 清单对象,解析失败返回 nullopt
static std::optional<PluginManifest> from_json(const std::string& json);
/// 获取类型对应的可读字符串
static const char* type_name(Type t);
/// 验证清单必要字段是否完整
/// @return true 如果 name/version/vde_version_min 均已设置
bool valid() const;
};
// ═══════════════════════════════════════════════════════════
// PluginInterface — 插件抽象基类
// ═══════════════════════════════════════════════════════════
/// 所有 VDE 插件必须实现的抽象基类。
///
/// 最小实现要求:
/// - initialize() — 设置 manifest 并初始化资源
/// - shutdown() — 清理所有资源
/// - manifest() — 返回 const 引用
/// - name() — 返回 manifest().name.c_str()
/// - version() — 返回 manifest().version.c_str()
/// - execute() — 实现插件核心功能
///
/// ## 生命周期
/// ```
/// create_plugin() → initialize() → execute(...)* → shutdown() → destroy_plugin()
/// ```
class PluginInterface {
public:
PluginInterface() = default;
virtual ~PluginInterface() = default;
// 禁止拷贝,允许移动
PluginInterface(const PluginInterface&) = delete;
PluginInterface& operator=(const PluginInterface&) = delete;
PluginInterface(PluginInterface&&) noexcept = default;
PluginInterface& operator=(PluginInterface&&) noexcept = default;
/// 初始化插件。在 dlopen + create_plugin 之后调用一次。
/// 应在此设置 manifest 并分配资源。
/// @return true 表示初始化成功
virtual bool initialize() = 0;
/// 关闭插件。在 dlclose 之前调用一次。
/// 应在此释放所有资源。
virtual void shutdown() = 0;
/// 获取插件清单的只读引用
virtual const PluginManifest& manifest() const = 0;
/// 插件唯一名称(与 manifest().name 一致)
virtual const char* name() const = 0;
/// 插件版本(与 manifest().version 一致)
virtual const char* version() const = 0;
/// 执行插件核心功能
///
/// @param action 操作名称,如 "export", "filter", "analyze"
/// @param params 参数键值对,如 {"path":"/tmp/out.glb", "binary":"true"}
/// @param input 输入数据指针(类型由 action 定义)
/// @param output 输出数据指针(类型由 action 定义,调用方负责生命周期)
/// @return true 表示执行成功
virtual bool execute(const std::string& action,
const std::map<std::string, std::string>& params,
void* input,
void* output) = 0;
/// 查询插件是否处于就绪状态
/// @return true 表示 initialize() 已完成且未 shutdown
virtual bool ready() const { return true; }
/// 返回最近一次错误描述
virtual std::string error() const { return {}; }
};
// ═══════════════════════════════════════════════════════════
// PluginManager — 插件加载/卸载/生命周期管理
// ═══════════════════════════════════════════════════════════
/// 插件管理器:扫描目录、加载动态库、维护注册表。
///
/// 线程安全:所有公开方法持有内部读写锁。
///
/// ## 搜索路径优先级
/// 1. load_plugin_dir() 指定的目录
/// 2. 环境变量 $VDE_PLUGIN_PATH(冒号分隔)
/// 3. 默认路径: /usr/lib/vde/plugins, ./vde_plugins
///
/// @code
/// PluginManager mgr;
/// mgr.load_plugin_dir("/usr/lib/vde/plugins");
/// mgr.load_plugin("/opt/custom/my_plugin.so");
///
/// if (auto* p = mgr.get_plugin("io.export.gltf")) {
/// p->execute("export", {}, &mesh, &buf);
/// }
/// @endcode
class PluginManager {
public:
PluginManager();
~PluginManager();
// 禁止拷贝
PluginManager(const PluginManager&) = delete;
PluginManager& operator=(const PluginManager&) = delete;
// ── 目录/批量加载 ───────────────────────────────
/// 扫描并加载指定目录下的所有插件
///
/// 识别 .so / .dylib / .dll 文件。
/// 如果同目录存在 plugin.json 清单,优先读取以验证兼容性。
///
/// @param dir_path 插件目录路径
/// @return 成功加载的插件数量
/// @note 单个插件加载失败不影响其他插件
int load_plugin_dir(const std::string& dir_path);
/// 通过环境变量和默认路径加载插件
/// 等价于 load_plugin_dir() 遍历 $VDE_PLUGIN_PATH
/// @return 成功加载的插件总数
int load_default_paths();
// ── 单个加载 ─────────────────────────────────────
/// 加载单个插件动态库
///
/// 流程: dlopen → dlsym(create_plugin) → initialize()
///
/// @param plugin_path 动态库完整路径(.so/.dylib/.dll
/// @return true 表示加载成功
bool load_plugin(const std::string& plugin_path);
/// 注册已创建的插件实例(通常由静态注册宏使用)
///
/// @param plugin 插件实例(所有权转移给 PluginManager
/// @return true 表示注册成功(名称不冲突)
bool register_plugin(std::unique_ptr<PluginInterface> plugin);
// ── 卸载 ─────────────────────────────────────────
/// 按名称卸载指定插件
///
/// @param name 插件名称(manifest().name
/// @return true 表示找到并卸载成功
bool unload_plugin(const std::string& name);
/// 卸载所有已加载插件
/// 按加载倒序依次 shutdown + destroy
void unload_all();
// ── 查询 ─────────────────────────────────────────
/// 按名称查找已加载插件
///
/// @param name 插件名称
/// @return 插件指针,未找到返回 nullptr
PluginInterface* get_plugin(const std::string& name);
/// 按名称查找(const 重载)
const PluginInterface* get_plugin(const std::string& name) const;
/// 列出所有已加载插件的名称
std::vector<std::string> list_plugins() const;
/// 检查指定插件是否已加载
bool is_loaded(const std::string& name) const;
/// 已加载插件数量
size_t plugin_count() const;
/// 最近一次错误的描述
std::string last_error() const;
/// 清空错误状态
void clear_error();
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
// ═══════════════════════════════════════════════════════════
// 插件导出符号 — 每个插件动态库必须导出
// ═══════════════════════════════════════════════════════════
/// @brief 创建插件实例(必须由每个 .so/.dll 导出)
///
/// 示例实现:
/// @code
/// extern "C" VDE_PLUGIN_EXPORT PluginInterface* create_plugin() {
/// return new MyPlugin();
/// }
/// @endcode
using CreatePluginFunc = PluginInterface* (*)();
/// @brief 销毁插件实例(必须由每个 .so/.dll 导出)
///
/// 示例实现:
/// @code
/// extern "C" VDE_PLUGIN_EXPORT void destroy_plugin(PluginInterface* p) {
/// delete p;
/// }
/// @endcode
using DestroyPluginFunc = void (*)(PluginInterface*);
/// @brief 获取插件清单(可选导出,优化加载速度)
///
/// 示例实现:
/// @code
/// extern "C" VDE_PLUGIN_EXPORT const PluginManifest* get_manifest() {
/// static PluginManifest m = { .name = "...", .version = "1.0.0", ... };
/// return &m;
/// }
/// @endcode
using GetManifestFunc = const PluginManifest* (*)();
// ═══════════════════════════════════════════════════════════
// 便捷注册宏
// ═══════════════════════════════════════════════════════════
/// 在插件 .cpp 文件中注册插件类
///
/// 自动生成 create_plugin / destroy_plugin 导出符号。
///
/// 用法:
/// @code
/// VDE_REGISTER_PLUGIN(MyExporter)
/// @endcode
///
/// 等价于手写:
/// @code
/// extern "C" VDE_PLUGIN_EXPORT void* create_plugin() {
/// return new MyExporter();
/// }
/// extern "C" VDE_PLUGIN_EXPORT void destroy_plugin(void* p) {
/// delete static_cast<MyExporter*>(p);
/// }
/// @endcode
#define VDE_REGISTER_PLUGIN(PluginClass) \
extern "C" VDE_PLUGIN_EXPORT vde::plugin::PluginInterface* \
create_plugin() { \
return new PluginClass(); \
} \
extern "C" VDE_PLUGIN_EXPORT void \
destroy_plugin(vde::plugin::PluginInterface* p) { \
delete p; \
}
/// 注册插件并同时导出 manifest 符号
///
/// @param PluginClass 插件类名
/// @param ManifestVar 静态 PluginManifest 变量名
#define VDE_REGISTER_PLUGIN_WITH_MANIFEST(PluginClass, ManifestVar) \
extern "C" VDE_PLUGIN_EXPORT const vde::plugin::PluginManifest* \
get_manifest() { \
return &ManifestVar; \
} \
VDE_REGISTER_PLUGIN(PluginClass)
} // namespace vde::plugin
+26
View File
@@ -26,6 +26,8 @@ typedef struct VdeSurface* VdeSurfaceHandle;
typedef struct VdeBVH* VdeBVHHandle;
typedef struct VdeSolver* VdeSolverHandle;
typedef struct VdeBody* VdeBodyHandle;
typedef struct VdeSdfNode* VdeSdfHandle;
typedef struct VdeAssembly* VdeAssemblyHandle;
// ── 上下文管理 ──
VDE_API VdeHandle vde_create(void);
@@ -84,6 +86,30 @@ VDE_API VdeBodyHandle vde_body_create_cylinder(VdeHandle ctx, double r, double h
VDE_API VdeBodyHandle vde_body_create_sphere(VdeHandle ctx, double r, int su, int sv);
VDE_API VdeMeshHandle vde_body_to_mesh(VdeHandle ctx, VdeBodyHandle body, double deflection);
VDE_API void vde_body_free(VdeBodyHandle body);
VDE_API VdeBodyHandle vde_body_clone(VdeHandle ctx, VdeBodyHandle body);
VDE_API int vde_body_load_step(VdeHandle ctx, const char* path, VdeBodyHandle** out_bodies);
VDE_API int vde_body_load_iges(VdeHandle ctx, const char* path, VdeBodyHandle** out_bodies);
VDE_API int vde_body_save_step(VdeHandle ctx, VdeBodyHandle* bodies, int count, const char* path);
VDE_API int vde_body_save_iges(VdeHandle ctx, VdeBodyHandle* bodies, int count, const char* path);
VDE_API VdeBodyHandle vde_body_boolean(VdeHandle ctx, VdeBodyHandle a, VdeBodyHandle b, int op);
VDE_API void vde_body_free_array(VdeBodyHandle* bodies, int count);
VDE_API int vde_mesh_get_face(VdeMeshHandle mesh, size_t face_idx, int** out_indices);
// ── SDF ──
VDE_API VdeSdfHandle vde_sdf_sphere(VdeHandle ctx, double r);
VDE_API VdeSdfHandle vde_sdf_box(VdeHandle ctx, double hx, double hy, double hz);
VDE_API VdeSdfHandle vde_sdf_cylinder(VdeHandle ctx, double r, double h);
VDE_API VdeSdfHandle vde_sdf_union(VdeHandle ctx, VdeSdfHandle a, VdeSdfHandle b);
VDE_API VdeSdfHandle vde_sdf_intersection(VdeHandle ctx, VdeSdfHandle a, VdeSdfHandle b);
VDE_API VdeSdfHandle vde_sdf_difference(VdeHandle ctx, VdeSdfHandle a, VdeSdfHandle b);
VDE_API VdeMeshHandle vde_sdf_to_mesh(VdeHandle ctx, VdeSdfHandle sdf, int resolution);
VDE_API void vde_sdf_free(VdeSdfHandle sdf);
// ── Assembly ──
VDE_API VdeAssemblyHandle vde_assembly_create(VdeHandle ctx, const char* name);
VDE_API int vde_assembly_add_part(VdeAssemblyHandle assembly, const char* name, VdeBodyHandle body);
VDE_API int vde_assembly_part_count(VdeAssemblyHandle assembly);
VDE_API void vde_assembly_free(VdeAssemblyHandle assembly);
// ── 序列化 ──
VDE_API size_t vde_serialize_mesh(VdeHandle ctx, VdeMeshHandle mesh, uint8_t** out_data);