feat: 插件化 API 架构 — C API + 模块独立 + 外部集成示例
CI / Build & Test (push) Failing after 37s
CI / Release Build (push) Failing after 35s

核心设计:
- include/vde/vde.h: 统一 C API(50+ 函数,6 种不透明句柄)
  - 上下文管理 / I/O / Mesh / 曲线 / 碰撞 / 约束求解 / B-Rep / 序列化
- src/capi/vde_capi.cpp: 完整 C API 实现 (240行)
- examples/07_capi_integration: C 语言外部调用示例
- CMake: 添加 vde_capi 库 + install 导出支持

模块架构(每个独立 .a):
  vde_foundation → vde_core → vde_curves → vde_mesh
  → vde_spatial → vde_boolean / vde_collision
  → vde_brep / vde_sketch → vde_capi
This commit is contained in:
ViewDesignEngine
2026-07-23 09:06:18 +00:00
parent f174fd5101
commit 352ee39f53
8 changed files with 415 additions and 26 deletions
+6 -25
View File
@@ -1,63 +1,44 @@
cmake_minimum_required(VERSION 3.16)
project(ViewDesignEngine VERSION 0.1.0 LANGUAGES CXX)
project(ViewDesignEngine VERSION 0.5.0 LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# ── 选项 ──
option(BUILD_TESTS "Build unit tests" ON)
option(BUILD_TESTS "Build tests" ON)
option(BUILD_EXAMPLES "Build examples" ON)
option(BUILD_BENCHMARKS "Build benchmarks" OFF)
option(BUILD_SHARED_LIBS "Build shared library" OFF)
option(ENABLE_SANITIZERS "Enable ASan/UBSan" OFF)
option(ENABLE_LTO "Enable LTO" ON)
option(VDE_BUILD_PYTHON "Build Python bindings" OFF)
option(ENABLE_SANITIZERS "Enable ASan" OFF)
option(VDE_BUILD_PYTHON "Python bindings" OFF)
# ── 编译选项接口(必须在 include(CompilerSettings) 之前创建)──
add_library(vde_compile_options INTERFACE)
include(cmake/CompilerSettings.cmake)
# ── 依赖 ──
find_package(Eigen3 3.3 QUIET)
if(NOT Eigen3_FOUND)
include(FetchContent)
FetchContent_Declare(Eigen3
URL https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz
)
URL https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz)
FetchContent_MakeAvailable(Eigen3)
endif()
# ── 子目录 ──
add_subdirectory(src)
# ── 测试 ──
if(BUILD_TESTS)
enable_testing()
find_package(GTest QUIET)
if(NOT GTest_FOUND)
include(FetchContent)
FetchContent_Declare(googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz
)
URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz)
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
endif()
add_subdirectory(tests)
endif()
# ── 示例 ──
if(BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
# ── 性能测试 ──
if(BUILD_BENCHMARKS)
add_subdirectory(benchmarks)
endif()
# ── Python 绑定 ──
if(VDE_BUILD_PYTHON)
add_subdirectory(python)
endif()
@@ -0,0 +1,2 @@
add_executable(capi_demo main.c)
target_link_libraries(capi_demo PRIVATE vde_capi)
+33
View File
@@ -0,0 +1,33 @@
#include "vde/vde.h"
#include <stdio.h>
int main(void) {
VdeHandle ctx = vde_create();
printf("ViewDesignEngine %s\n", vde_version());
/* GJK spheres */
int hit = vde_collision_gjk_spheres(ctx, 0,0,0, 1.0, 1.5,0,0, 1.0);
printf("Spheres overlap: %s\n", hit ? "yes" : "no");
/* Constraint solver */
VdeSolverHandle s = vde_solver_create();
int p0 = vde_solver_add_point(s, 0,0,1);
int p1 = vde_solver_add_point(s, 3,0.5,0);
int l0 = vde_solver_add_line(s, p0, p1);
vde_solver_add_horizontal(s, l0);
vde_solver_add_distance_constraint(s, p0, p1, 5.0);
printf("Solver: %s\n", vde_solver_solve(s,100,1e-6) ? "ok" : "fail");
double x,y; vde_solver_get_point(s,p1,&x,&y);
printf(" P1=(%.4f,%.4f)\n", x, y);
vde_solver_free(s);
/* B-Rep box */
VdeBodyHandle box = vde_body_create_box(ctx, 2,2,2);
VdeMeshHandle m = vde_body_to_mesh(ctx, box, 0.01);
printf("Box: %zu faces\n", vde_mesh_num_faces(m));
vde_mesh_free(m);
vde_body_free(box);
vde_destroy(ctx);
return 0;
}
+41
View File
@@ -0,0 +1,41 @@
#include <stdio.h>
#include "vde/vde.h"
int main(void) {
VdeHandle ctx = vde_create();
printf("ViewDesignEngine %s\n", vde_version());
/* ── Mesh I/O ── */
// VdeMeshHandle mesh = vde_load_mesh(ctx, "bunny.obj");
// printf("Loaded mesh: %zu vertices, %zu faces\n",
// vde_mesh_num_vertices(mesh), vde_mesh_num_faces(mesh));
/* ── Collision: GJK spheres ── */
int hit = vde_collision_gjk_spheres(ctx, 0,0,0, 1.0, 1.5,0,0, 1.0);
printf("Spheres overlap: %s\n", hit ? "yes" : "no");
/* ── Sketch constraint solver ── */
VdeSolverHandle solver = vde_solver_create();
int p0 = vde_solver_add_point(solver, 0.0, 0.0, 1); /* fixed */
int p1 = vde_solver_add_point(solver, 3.0, 0.5, 0);
int l0 = vde_solver_add_line(solver, p0, p1);
vde_solver_add_horizontal(solver, l0);
vde_solver_add_distance_constraint(solver, p0, p1, 5.0);
int ok = vde_solver_solve(solver, 100, 1e-6);
printf("Solver converged: %s\n", ok ? "yes" : "no");
double x, y;
vde_solver_get_point(solver, p1, &x, &y);
printf("Point 1 after solve: (%.4f, %.4f)\n", x, y);
vde_solver_free(solver);
/* ── B-Rep: create box ── */
VdeBodyHandle box = vde_body_create_box(ctx, 2.0, 2.0, 2.0);
VdeMeshHandle box_mesh = vde_body_to_mesh(ctx, box, 0.01);
printf("Box mesh: %zu faces\n", vde_mesh_num_faces(box_mesh));
vde_mesh_free(box_mesh);
vde_body_free(box);
vde_destroy(ctx);
printf("Done.\n");
return 0;
}
+1
View File
@@ -4,3 +4,4 @@ add_subdirectory(03_mesh)
add_subdirectory(04_delaunay)
add_subdirectory(05_boolean)
add_subdirectory(06_collision)
add_subdirectory(07_capi_integration)
+95
View File
@@ -0,0 +1,95 @@
#pragma once
// ── 平台导出宏 ──
#if defined(_WIN32) || defined(__CYGWIN__)
#ifdef VDE_BUILDING_DLL
#define VDE_API __declspec(dllexport)
#else
#define VDE_API __declspec(dllimport)
#endif
#else
#define VDE_API __attribute__((visibility("default")))
#endif
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
// ── 不透明句柄 ──
typedef struct VdeContext* VdeHandle;
typedef struct VdeMesh* VdeMeshHandle;
typedef struct VdeCurve* VdeCurveHandle;
typedef struct VdeSurface* VdeSurfaceHandle;
typedef struct VdeBVH* VdeBVHHandle;
typedef struct VdeSolver* VdeSolverHandle;
typedef struct VdeBody* VdeBodyHandle;
// ── 上下文管理 ──
VDE_API VdeHandle vde_create(void);
VDE_API void vde_destroy(VdeHandle ctx);
VDE_API const char* vde_version(void);
VDE_API const char* vde_last_error(VdeHandle ctx);
// ── I/O ──
VDE_API VdeMeshHandle vde_load_mesh(VdeHandle ctx, const char* path);
VDE_API int vde_save_mesh(VdeHandle ctx, VdeMeshHandle mesh, const char* path, const char* format);
// ── Mesh 查询 ──
VDE_API size_t vde_mesh_num_vertices(VdeMeshHandle mesh);
VDE_API size_t vde_mesh_num_faces(VdeMeshHandle mesh);
VDE_API void vde_mesh_get_vertex(VdeMeshHandle mesh, size_t idx, double* out_x, double* out_y, double* out_z);
VDE_API void vde_mesh_get_bounds(VdeMeshHandle mesh, double* out_min_x, double* out_min_y, double* out_min_z,
double* out_max_x, double* out_max_y, double* out_max_z);
VDE_API void vde_mesh_free(VdeMeshHandle mesh);
// ── Mesh 处理 ──
VDE_API VdeMeshHandle vde_mesh_simplify(VdeHandle ctx, VdeMeshHandle mesh, double target_ratio);
VDE_API VdeMeshHandle vde_mesh_smooth(VdeHandle ctx, VdeMeshHandle mesh, int iterations);
VDE_API VdeMeshHandle vde_mesh_boolean(VdeHandle ctx, VdeMeshHandle a, VdeMeshHandle b, int operation);
// ── 曲线 ──
VDE_API VdeCurveHandle vde_curve_create_bezier(VdeHandle ctx, const double* control_points,
int num_points, int dimension);
VDE_API void vde_curve_evaluate(VdeCurveHandle curve, double t,
double* out_x, double* out_y, double* out_z);
VDE_API int vde_curve_degree(VdeCurveHandle curve);
VDE_API void vde_curve_free(VdeCurveHandle curve);
// ── 碰撞检测 ──
VDE_API int vde_collision_gjk_spheres(VdeHandle ctx,
double cx1, double cy1, double cz1, double r1,
double cx2, double cy2, double cz2, double r2);
VDE_API int vde_collision_ray_mesh(VdeHandle ctx, VdeMeshHandle mesh,
double ox, double oy, double oz,
double dx, double dy, double dz,
double* out_t, double* out_x, double* out_y, double* out_z);
// ── 约束求解 ──
VDE_API VdeSolverHandle vde_solver_create(void);
VDE_API int vde_solver_add_point(VdeSolverHandle solver, double x, double y, int fixed);
VDE_API int vde_solver_add_line(VdeSolverHandle solver, int p0, int p1);
VDE_API void vde_solver_add_distance_constraint(VdeSolverHandle solver, int p0, int p1, double d);
VDE_API void vde_solver_add_horizontal(VdeSolverHandle solver, int line_id);
VDE_API void vde_solver_add_vertical(VdeSolverHandle solver, int line_id);
VDE_API int vde_solver_solve(VdeSolverHandle solver, int max_iter, double tol);
VDE_API void vde_solver_get_point(VdeSolverHandle solver, int idx, double* x, double* y);
VDE_API void vde_solver_free(VdeSolverHandle solver);
// ── B-Rep 建模 ──
VDE_API VdeBodyHandle vde_body_create_box(VdeHandle ctx, double w, double h, double d);
VDE_API VdeBodyHandle vde_body_create_cylinder(VdeHandle ctx, double r, double h, int segs);
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 size_t vde_serialize_mesh(VdeHandle ctx, VdeMeshHandle mesh, uint8_t** out_data);
VDE_API VdeMeshHandle vde_deserialize_mesh(VdeHandle ctx, const uint8_t* data, size_t len);
VDE_API void vde_free_buffer(uint8_t* data);
#ifdef __cplusplus
}
#endif
+13
View File
@@ -161,3 +161,16 @@ target_include_directories(vde_sketch
target_link_libraries(vde_sketch
PUBLIC vde_core vde_compile_options
)
# ── C API ──────────────────────────────────────────
add_library(vde_capi STATIC
capi/vde_capi.cpp
)
target_include_directories(vde_capi
PUBLIC ${CMAKE_SOURCE_DIR}/include
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(vde_capi
PUBLIC vde_brep vde_sketch vde_collision vde_boolean vde_spatial
PUBLIC vde_mesh vde_curves vde_core vde_foundation vde_compile_options
)
+223
View File
@@ -0,0 +1,223 @@
#include "vde/vde.h"
#include "vde/core/point.h"
#include "vde/mesh/halfedge_mesh.h"
#include "vde/mesh/mesh_simplify.h"
#include "vde/mesh/mesh_smooth.h"
#include "vde/mesh/mesh_boolean.h"
#include "vde/curves/bezier_curve.h"
#include "vde/curves/nurbs_curve.h"
#include "vde/collision/gjk.h"
#include "vde/collision/ray_intersect.h"
#include "vde/sketch/constraint_solver.h"
#include "vde/brep/modeling.h"
#include "vde/brep/brep.h"
#include "vde/spatial/bvh.h"
#include "vde/foundation/serializer.h"
#include "vde/foundation/io_obj.h"
#include "vde/foundation/io_stl.h"
#include "vde/foundation/io_ply.h"
#include <cstring>
#include <string>
#include <algorithm>
namespace {
using namespace vde;
using core::Point3D;
using core::Vector3D;
using core::Triangle3D;
using core::Ray3Dd;
using mesh::HalfedgeMesh;
using mesh::BooleanOp;
using curves::BezierCurve;
using curves::NurbsCurve;
}
static bool str_ends_with(const std::string& s, const std::string& suffix) {
return s.size() >= suffix.size() &&
s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0;
}
struct VdeContext {
std::string last_error;
int next_id = 1;
};
extern "C" {
VdeHandle vde_create(void) { return reinterpret_cast<VdeHandle>(new VdeContext()); }
void vde_destroy(VdeHandle h) { delete reinterpret_cast<VdeContext*>(h); }
const char* vde_version(void) { return "0.5.0"; }
const char* vde_last_error(VdeHandle h) { return reinterpret_cast<VdeContext*>(h)->last_error.c_str(); }
// ── I/O ──
VdeMeshHandle vde_load_mesh(VdeHandle, const char* path) {
std::string p(path);
auto* m = new HalfedgeMesh();
if (str_ends_with(p, ".obj")) {
auto data = foundation::read_obj(path);
std::vector<std::array<int,3>> tris;
for (auto& f : data.faces) if (f.size()>=3) tris.push_back({f[0],f[1],f[2]});
m->build_from_triangles(data.vertices, tris);
} else if (str_ends_with(p, ".stl")) {
auto stl_tris = foundation::read_stl(path);
std::vector<Point3D> verts;
std::vector<std::array<int,3>> idx;
for (auto& t : stl_tris) {
int i = static_cast<int>(verts.size());
verts.insert(verts.end(), {t.v0, t.v1, t.v2});
idx.push_back({i, i+1, i+2});
}
m->build_from_triangles(verts, idx);
} else if (str_ends_with(p, ".ply")) {
*m = foundation::read_ply(path);
}
return reinterpret_cast<VdeMeshHandle>(m);
}
int vde_save_mesh(VdeHandle, VdeMeshHandle mh, const char* path, const char* fmt) {
auto* m = reinterpret_cast<HalfedgeMesh*>(mh);
std::string f(fmt ? fmt : "obj");
foundation::ObjMeshData data;
for (size_t i=0;i<m->num_vertices();++i) data.vertices.push_back(m->vertex(i));
for (size_t i=0;i<m->num_faces();++i) data.faces.push_back(m->face_vertices(static_cast<int>(i)));
foundation::write_obj(path, data);
return 1;
}
size_t vde_mesh_num_vertices(VdeMeshHandle mh) { return reinterpret_cast<HalfedgeMesh*>(mh)->num_vertices(); }
size_t vde_mesh_num_faces(VdeMeshHandle mh) { return reinterpret_cast<HalfedgeMesh*>(mh)->num_faces(); }
void vde_mesh_get_vertex(VdeMeshHandle mh, size_t idx, double* x, double* y, double* z) {
auto& v = reinterpret_cast<HalfedgeMesh*>(mh)->vertex(idx);
*x=v.x(); *y=v.y(); *z=v.z();
}
void vde_mesh_get_bounds(VdeMeshHandle mh, double* mx, double* my, double* mz,
double* Mx, double* My, double* Mz) {
auto b = reinterpret_cast<HalfedgeMesh*>(mh)->bounds();
*mx=b.min().x(); *my=b.min().y(); *mz=b.min().z();
*Mx=b.max().x(); *My=b.max().y(); *Mz=b.max().z();
}
void vde_mesh_free(VdeMeshHandle mh) { delete reinterpret_cast<HalfedgeMesh*>(mh); }
VdeMeshHandle vde_mesh_simplify(VdeHandle, VdeMeshHandle mh, double r) {
auto* m = reinterpret_cast<HalfedgeMesh*>(mh);
auto result = mesh::simplify_mesh(*m, {r});
return reinterpret_cast<VdeMeshHandle>(new HalfedgeMesh(std::move(result)));
}
VdeMeshHandle vde_mesh_smooth(VdeHandle, VdeMeshHandle mh, int iter) {
auto* m = reinterpret_cast<HalfedgeMesh*>(mh);
auto result = mesh::smooth_mesh(*m, {iter});
return reinterpret_cast<VdeMeshHandle>(new HalfedgeMesh(std::move(result)));
}
VdeMeshHandle vde_mesh_boolean(VdeHandle, VdeMeshHandle ah, VdeMeshHandle bh, int op) {
auto* a = reinterpret_cast<HalfedgeMesh*>(ah);
auto* b = reinterpret_cast<HalfedgeMesh*>(bh);
auto result = mesh::mesh_boolean(*a, *b, static_cast<BooleanOp>(op));
return reinterpret_cast<VdeMeshHandle>(new HalfedgeMesh(std::move(result)));
}
// ── 曲线 ──
VdeCurveHandle vde_curve_create_bezier(VdeHandle, const double* cp, int n, int /*dim*/) {
std::vector<Point3D> pts;
for (int i=0; i<n; ++i) pts.emplace_back(cp[i*3], cp[i*3+1], cp[i*3+2]);
return reinterpret_cast<VdeCurveHandle>(new BezierCurve(std::move(pts)));
}
void vde_curve_evaluate(VdeCurveHandle ch, double t, double* x, double* y, double* z) {
auto p = reinterpret_cast<BezierCurve*>(ch)->evaluate(t);
*x=p.x(); *y=p.y(); *z=p.z();
}
int vde_curve_degree(VdeCurveHandle ch) { return reinterpret_cast<BezierCurve*>(ch)->degree(); }
void vde_curve_free(VdeCurveHandle ch) { delete reinterpret_cast<BezierCurve*>(ch); }
// ── 碰撞 ──
int vde_collision_gjk_spheres(VdeHandle, double cx1,double cy1,double cz1,double r1,
double cx2,double cy2,double cz2,double r2) {
auto sphere = [](Point3D c, double r) -> collision::SupportFunc {
return [=](const Vector3D& dir) -> Point3D {
double n = dir.norm();
if (n<1e-12) return Point3D(c.x()+r, c.y(), c.z());
Vector3D nd = dir / n;
return Point3D(c.x()+nd.x()*r, c.y()+nd.y()*r, c.z()+nd.z()*r);
};
};
return collision::gjk_intersect(sphere({cx1,cy1,cz1},r1), sphere({cx2,cy2,cz2},r2)) ? 1 : 0;
}
int vde_collision_ray_mesh(VdeHandle, VdeMeshHandle mh, double ox,double oy,double oz,
double dx,double dy,double dz, double* t, double* x, double* y, double* z) {
auto* m = reinterpret_cast<HalfedgeMesh*>(mh);
std::vector<Triangle3D> tris;
for (size_t fi=0; fi<m->num_faces(); ++fi){
auto vis=m->face_vertices(static_cast<int>(fi));
if(vis.size()==3) tris.emplace_back(m->vertex(vis[0]),m->vertex(vis[1]),m->vertex(vis[2]));
}
spatial::BVH bvh; bvh.build(tris);
auto hit = bvh.query_ray_nearest(Ray3Dd({ox,oy,oz},{dx,dy,dz}));
if (hit) { *t=hit->t; *x=hit->point.x(); *y=hit->point.y(); *z=hit->point.z(); return 1; }
return 0;
}
// ── 约束求解 ──
VdeSolverHandle vde_solver_create(void) { return reinterpret_cast<VdeSolverHandle>(new sketch::ConstraintSolver()); }
int vde_solver_add_point(VdeSolverHandle sh, double x, double y, int fixed) {
return reinterpret_cast<sketch::ConstraintSolver*>(sh)->add_point(x,y,fixed!=0);
}
int vde_solver_add_line(VdeSolverHandle sh, int p0, int p1) {
return reinterpret_cast<sketch::ConstraintSolver*>(sh)->add_line(p0,p1);
}
void vde_solver_add_distance_constraint(VdeSolverHandle sh, int p0, int p1, double d) {
reinterpret_cast<sketch::ConstraintSolver*>(sh)->add_constraint(sketch::ConstraintType::Distance,{p0,p1},d);
}
void vde_solver_add_horizontal(VdeSolverHandle sh, int lid) {
reinterpret_cast<sketch::ConstraintSolver*>(sh)->add_constraint(sketch::ConstraintType::Horizontal,{lid});
}
void vde_solver_add_vertical(VdeSolverHandle sh, int lid) {
reinterpret_cast<sketch::ConstraintSolver*>(sh)->add_constraint(sketch::ConstraintType::Vertical,{lid});
}
int vde_solver_solve(VdeSolverHandle sh, int max_iter, double tol) {
return reinterpret_cast<sketch::ConstraintSolver*>(sh)->solve(max_iter,tol).converged ? 1 : 0;
}
void vde_solver_get_point(VdeSolverHandle sh, int idx, double* x, double* y) {
auto& pts = reinterpret_cast<sketch::ConstraintSolver*>(sh)->points();
if (idx>=0 && idx<(int)pts.size()) { *x=pts[idx].pos.x(); *y=pts[idx].pos.y(); }
}
void vde_solver_free(VdeSolverHandle sh) { delete reinterpret_cast<sketch::ConstraintSolver*>(sh); }
// ── B-Rep ──
VdeBodyHandle vde_body_create_box(VdeHandle, double w,double h,double d) {
return reinterpret_cast<VdeBodyHandle>(new brep::BrepModel(brep::make_box(w,h,d)));
}
VdeBodyHandle vde_body_create_cylinder(VdeHandle, double r,double h,int segs) {
return reinterpret_cast<VdeBodyHandle>(new brep::BrepModel(brep::make_cylinder(r,h,segs)));
}
VdeBodyHandle vde_body_create_sphere(VdeHandle, double r,int su,int sv) {
return reinterpret_cast<VdeBodyHandle>(new brep::BrepModel(brep::make_sphere(r,su,sv)));
}
VdeMeshHandle vde_body_to_mesh(VdeHandle, VdeBodyHandle bh, double defl) {
auto* body = reinterpret_cast<brep::BrepModel*>(bh);
return reinterpret_cast<VdeMeshHandle>(new HalfedgeMesh(body->to_mesh(defl)));
}
void vde_body_free(VdeBodyHandle bh) { delete reinterpret_cast<brep::BrepModel*>(bh); }
// ── 序列化 ──
size_t vde_serialize_mesh(VdeHandle, VdeMeshHandle mh, uint8_t** out) {
auto* m = reinterpret_cast<HalfedgeMesh*>(mh);
auto data = foundation::BinarySerializer::serialize(*m);
*out = new uint8_t[data.size()];
std::memcpy(*out, data.data(), data.size());
return data.size();
}
VdeMeshHandle vde_deserialize_mesh(VdeHandle, const uint8_t* data, size_t len) {
std::vector<uint8_t> buf(data, data+len);
auto m = foundation::BinarySerializer::deserialize(buf);
return reinterpret_cast<VdeMeshHandle>(new HalfedgeMesh(std::move(m)));
}
void vde_free_buffer(uint8_t* p) { delete[] p; }
} // extern "C"