feat(v3.3): WebGL 3D viewer + tutorial docs
This commit is contained in:
@@ -0,0 +1,86 @@
|
|||||||
|
# 快速开始
|
||||||
|
|
||||||
|
5 分钟从零到第一个 GLB 模型。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/your-org/ViewDesignEngine.git
|
||||||
|
cd ViewDesignEngine
|
||||||
|
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake .. -DBUILD_EXAMPLES=ON
|
||||||
|
cmake --build . -j$(nproc)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 第一个 SDF 形状
|
||||||
|
|
||||||
|
创建文件 `hello_vde.cpp`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <vde/sdf/sdf_primitives.h>
|
||||||
|
#include <vde/sdf/sdf_operations.h>
|
||||||
|
#include <vde/sdf/sdf_to_mesh.h>
|
||||||
|
#include <vde/mesh/marching_cubes.h>
|
||||||
|
#include <vde/mesh/halfedge_mesh.h>
|
||||||
|
#include <vde/foundation/io_gltf.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace vde::core;
|
||||||
|
using namespace vde::sdf;
|
||||||
|
using namespace vde::mesh;
|
||||||
|
using namespace vde::foundation;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// 1. 定义 SDF 函数
|
||||||
|
auto shape = [](double x, double y, double z) -> double {
|
||||||
|
Point3D p(x, y, z);
|
||||||
|
double d_sphere = sphere(p, 1.2);
|
||||||
|
double d_box = box(p, Point3D(1.5, 1.5, 0.5));
|
||||||
|
// 平滑并集 (k=0.3 控制融合程度)
|
||||||
|
return op_smooth_union(d_sphere, d_box, 0.3);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. Marching Cubes → 三角形网格
|
||||||
|
Point3D bmin(-2.5, -2.5, -2.5);
|
||||||
|
Point3D bmax( 2.5, 2.5, 2.5);
|
||||||
|
auto mc = marching_cubes(shape, 0.0, bmin, bmax, 64);
|
||||||
|
|
||||||
|
// 3. 构建半边网格
|
||||||
|
HalfedgeMesh mesh;
|
||||||
|
mesh.build_from_triangles(mc.vertices, mc.triangles);
|
||||||
|
|
||||||
|
// 4. 导出 GLB
|
||||||
|
GltfOptions opts;
|
||||||
|
opts.binary = true;
|
||||||
|
opts.include_normals = true;
|
||||||
|
write_gltf("hello_vde.glb", mesh, opts);
|
||||||
|
|
||||||
|
std::cout << "Done! Open hello_vde.glb in viewer/index.html\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
编译运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
g++ -std=c++17 -I../include hello_vde.cpp -L. -lvde -o hello_vde
|
||||||
|
./hello_vde
|
||||||
|
```
|
||||||
|
|
||||||
|
## 导出 GLB
|
||||||
|
|
||||||
|
GLB(glTF Binary)可直接在以下工具中打开:
|
||||||
|
|
||||||
|
- 本项目自带的 `viewer/index.html`(拖放即可)
|
||||||
|
- Blender(File → Import → glTF 2.0)
|
||||||
|
- 在线工具:gltf-viewer.donmccurdy.com
|
||||||
|
- Windows/macOS 自带 3D Viewer
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [SDF 隐式建模教程](02-sdf-modeling.md) —— 学习更多图元与操作
|
||||||
|
- [B-Rep 工业建模教程](03-brep-modeling.md) —— 精确边界表示建模
|
||||||
|
- [API 参考](../API-REFERENCE.md) —— 完整 API 文档
|
||||||
@@ -0,0 +1,333 @@
|
|||||||
|
# SDF 隐式建模教程
|
||||||
|
|
||||||
|
有符号距离函数(Signed Distance Function)是 ViewDesignEngine 的核心建模方式之一。
|
||||||
|
通过数学函数直接定义几何体,无需依赖网格或曲面表示。
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
SDF 函数 `f(p)` 返回点 `p` 到最近表面的有符号距离:
|
||||||
|
|
||||||
|
- `f(p) < 0`:点在几何体**内部**
|
||||||
|
- `f(p) = 0`:点在几何体**表面**上
|
||||||
|
- `f(p) > 0`:点在几何体**外部**
|
||||||
|
|
||||||
|
## 基本图元(球、盒、环)
|
||||||
|
|
||||||
|
ViewDesignEngine 提供的内建 SDF 图元,定义在 `vde/sdf/sdf_primitives.h`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <vde/sdf/sdf_primitives.h>
|
||||||
|
using namespace vde::sdf;
|
||||||
|
using namespace vde::core;
|
||||||
|
|
||||||
|
// 球体:位于原点,半径 1.0
|
||||||
|
double d = sphere(Point3D(1.0, 0.0, 0.0), 1.0);
|
||||||
|
// d ≈ 0.0(点在球面上)
|
||||||
|
|
||||||
|
// 轴对齐盒子:以原点为中心,半长为 halfExtent
|
||||||
|
auto box_fn = [](double x, double y, double z) {
|
||||||
|
return box(Point3D(x, y, z), Point3D(1.5, 1.5, 0.5));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 环面(torus):major_radius = 环半径,minor_radius = 管半径
|
||||||
|
auto torus_fn = [](double x, double y, double z) {
|
||||||
|
return torus(Point3D(x, y, z), 2.0, 0.4);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 椭球:指定各轴半径
|
||||||
|
auto ellipsoid_fn = [](double x, double y, double z) {
|
||||||
|
return ellipsoid(Point3D(x, y, z), Point3D(2.0, 1.0, 1.5));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 圆柱(沿 Y 轴)
|
||||||
|
auto cylinder_fn = [](double x, double y, double z) {
|
||||||
|
return cylinder(Point3D(x, y, z), 0.5, 3.0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 胶囊体:两点 + 半径
|
||||||
|
auto capsule_fn = [](double x, double y, double z) {
|
||||||
|
return capsule(Point3D(x, y, z),
|
||||||
|
Point3D(0, -2, 0), Point3D(0, 2, 0), 0.4);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 无限长圆柱:沿任意轴
|
||||||
|
auto inf_cyl = [](double x, double y, double z) {
|
||||||
|
return infinite_cylinder(Point3D(x, y, z),
|
||||||
|
Vector3D(0, 0, 1), 0.5);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSG 操作(并、交、差、光滑并集)
|
||||||
|
|
||||||
|
布尔组合运算定义在 `vde/sdf/sdf_operations.h`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <vde/sdf/sdf_primitives.h>
|
||||||
|
#include <vde/sdf/sdf_operations.h>
|
||||||
|
using namespace vde::sdf;
|
||||||
|
|
||||||
|
// === 标准布尔运算 ===
|
||||||
|
|
||||||
|
// 并集 A ∪ B:取最小值(两个几何体的合并)
|
||||||
|
auto union_shape = [](double x, double y, double z) {
|
||||||
|
Point3D p(x, y, z);
|
||||||
|
return op_union(sphere(p, 1.0),
|
||||||
|
box(p, Point3D(0.8, 0.8, 0.8)));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 交集 A ∩ B:取最大值(两个几何体的共同区域)
|
||||||
|
auto intersect_shape = [](double x, double y, double z) {
|
||||||
|
Point3D p(x, y, z);
|
||||||
|
return op_intersection(sphere(p, 1.0),
|
||||||
|
box(p, Point3D(0.6, 0.6, 0.6)));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 差集 A \ B:从 A 中减去 B
|
||||||
|
auto diff_shape = [](double x, double y, double z) {
|
||||||
|
Point3D p(x, y, z);
|
||||||
|
return op_difference(sphere(p, 1.0),
|
||||||
|
box(p, Point3D(0.4, 0.4, 0.4)));
|
||||||
|
};
|
||||||
|
|
||||||
|
// === 光滑布尔运算(带有机融合过渡) ===
|
||||||
|
|
||||||
|
// smooth union,k 控制过渡弧度
|
||||||
|
auto smooth_union_shape = [](double x, double y, double z) {
|
||||||
|
Point3D p(x, y, z);
|
||||||
|
double d1 = sphere(p, 1.0);
|
||||||
|
double d2 = sphere(p - Point3D(1.5, 0, 0), 1.0);
|
||||||
|
return op_smooth_union(d1, d2, 0.3); // k=0.3 适中的融合
|
||||||
|
};
|
||||||
|
|
||||||
|
// smooth intersection
|
||||||
|
auto smooth_isect_shape = [](double x, double y, double z) {
|
||||||
|
Point3D p(x, y, z);
|
||||||
|
double d1 = sphere(p, 1.0);
|
||||||
|
double d2 = box(p, Point3D(0.8, 0.8, 0.8));
|
||||||
|
return op_smooth_intersection(d1, d2, 0.2);
|
||||||
|
};
|
||||||
|
|
||||||
|
// smooth difference
|
||||||
|
auto smooth_diff_shape = [](double x, double y, double z) {
|
||||||
|
Point3D p(x, y, z);
|
||||||
|
double d1 = box(p, Point3D(1.5, 1.5, 1.5));
|
||||||
|
double d2 = sphere(p, 0.8);
|
||||||
|
return op_smooth_difference(d1, d2, 0.2);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 域变形(重复、镜像、扭转)
|
||||||
|
|
||||||
|
域变形算子对采样点做空间变换,再传入 SDF 函数求值:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <vde/sdf/sdf_operations.h>
|
||||||
|
using namespace vde::sdf;
|
||||||
|
|
||||||
|
// 无限重复:将空间单元化,在每个单元内放置几何体
|
||||||
|
auto repeated_shape = [](double x, double y, double z) {
|
||||||
|
Point3D p(x, y, z);
|
||||||
|
// 以 (2, 2, 2) 为周期重复
|
||||||
|
Point3D q = op_repeat(p, Point3D(2.0, 2.0, 2.0));
|
||||||
|
return sphere(q, 0.5);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 镜像:沿 YZ 平面对称(x 方向镜像,offset 为对称轴位置)
|
||||||
|
auto mirrored_shape = [](double x, double y, double z) {
|
||||||
|
Point3D p(x, y, z);
|
||||||
|
Point3D q = op_mirror_x(p, 0.0); // 以 x=0 为镜像轴
|
||||||
|
return sphere(q - Point3D(1.5, 0, 0), 0.6);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 多维镜像组合
|
||||||
|
auto quad_mirror = [](double x, double y, double z) {
|
||||||
|
Point3D p(x, y, z);
|
||||||
|
Point3D q = op_mirror_x(op_mirror_y(p));
|
||||||
|
return sphere(q - Point3D(2.0, 2.0, 0), 0.5);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 平移 / 旋转 / 缩放
|
||||||
|
auto transformed_shape = [](double x, double y, double z) {
|
||||||
|
Point3D p(x, y, z);
|
||||||
|
// sdf_operations.h 提供 op_translate, op_rotate, op_scale 等
|
||||||
|
// 注意:是逆变换——要移动几何体,需反向移动采样点
|
||||||
|
Point3D q = op_translate(p, Point3D(2.0, 0, 0)); // 几何体向右移 2
|
||||||
|
return sphere(q, 1.0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 扭转(沿 Y 轴)
|
||||||
|
#include <cmath>
|
||||||
|
auto twisted_shape = [](double x, double y, double z) -> double {
|
||||||
|
double angle = y * 0.5; // 扭转角度与 y 坐标成正比
|
||||||
|
double sx = std::sin(angle);
|
||||||
|
double cx = std::cos(angle);
|
||||||
|
double rx = cx * x - sx * z;
|
||||||
|
double rz = sx * x + cx * z;
|
||||||
|
Point3D q(rx, y, rz);
|
||||||
|
return box(q, Point3D(0.8, 3.0, 0.3));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修饰算子:圆角、壳
|
||||||
|
auto decorated_shape = [](double x, double y, double z) {
|
||||||
|
Point3D p(x, y, z);
|
||||||
|
double d = box(p, Point3D(1.0, 1.0, 1.0));
|
||||||
|
// op_round: 对所有边做圆角(近似 round_box)
|
||||||
|
d = op_round(d, 0.1);
|
||||||
|
// op_onion: 创建壳层(空心厚度)
|
||||||
|
// d = op_onion(d, 0.05);
|
||||||
|
return d;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 从 SDF 到网格(Marching Cubes)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <vde/sdf/sdf_primitives.h>
|
||||||
|
#include <vde/sdf/sdf_operations.h>
|
||||||
|
#include <vde/mesh/marching_cubes.h>
|
||||||
|
#include <vde/mesh/halfedge_mesh.h>
|
||||||
|
#include <vde/foundation/io_gltf.h>
|
||||||
|
|
||||||
|
using namespace vde::core;
|
||||||
|
using namespace vde::sdf;
|
||||||
|
using namespace vde::mesh;
|
||||||
|
using namespace vde::foundation;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// 定义 SDF 函数
|
||||||
|
auto shape = [](double x, double y, double z) -> double {
|
||||||
|
Point3D p(x, y, z);
|
||||||
|
double d = box(p, Point3D(2.0, 2.0, 2.0));
|
||||||
|
d = op_round(d, 0.15); // 圆角
|
||||||
|
return d;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 采样区域:[-3,3]³
|
||||||
|
Point3D bmin(-3, -3, -3), bmax(3, 3, 3);
|
||||||
|
|
||||||
|
// Marching Cubes:64³ 分辨率
|
||||||
|
auto mc = marching_cubes(shape, 0.0, bmin, bmax, 64);
|
||||||
|
|
||||||
|
// 构建半边网格并导出
|
||||||
|
HalfedgeMesh mesh;
|
||||||
|
mesh.build_from_triangles(mc.vertices, mc.triangles);
|
||||||
|
|
||||||
|
GltfOptions opts;
|
||||||
|
opts.binary = true;
|
||||||
|
opts.include_normals = true;
|
||||||
|
write_gltf("rounded_box.glb", mesh, opts);
|
||||||
|
|
||||||
|
std::cout << "Vertices: " << mesh.num_vertices()
|
||||||
|
<< ", Faces: " << mesh.num_faces() << "\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**分辨率选择指南:**
|
||||||
|
|
||||||
|
| 分辨率 | 适用场景 | 输出网格规模 |
|
||||||
|
|--------|----------|-------------|
|
||||||
|
| 32³ | 快速预览 | ~2K 顶点 |
|
||||||
|
| 64³ | 一般用途 | ~8K 顶点 |
|
||||||
|
| 128³ | 精细表面 | ~30K 顶点 |
|
||||||
|
| 256³ | 高精度需求 | ~100K+ 顶点 |
|
||||||
|
|
||||||
|
## 完整示例:齿轮形状
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <vde/sdf/sdf_primitives.h>
|
||||||
|
#include <vde/sdf/sdf_operations.h>
|
||||||
|
#include <vde/mesh/marching_cubes.h>
|
||||||
|
#include <vde/mesh/halfedge_mesh.h>
|
||||||
|
#include <vde/foundation/io_gltf.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
using namespace vde::core;
|
||||||
|
using namespace vde::sdf;
|
||||||
|
using namespace vde::mesh;
|
||||||
|
using namespace vde::foundation;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
const double M_PI = 3.14159265358979323846;
|
||||||
|
const double gear_radius = 2.0; // 齿轮半径
|
||||||
|
const double tooth_count = 12.0; // 齿数
|
||||||
|
const double tooth_depth = 0.3; // 齿深
|
||||||
|
const double gear_thickness = 0.5; // 齿轮厚度
|
||||||
|
|
||||||
|
auto gear_sdf = [&](double x, double y, double z) -> double {
|
||||||
|
Point3D p(x, y, z);
|
||||||
|
|
||||||
|
// 圆柱主体(沿 Y 轴的薄圆盘)
|
||||||
|
double d_cylinder = cylinder(Point3D(x, y, z), gear_radius, gear_thickness);
|
||||||
|
|
||||||
|
// 中心孔
|
||||||
|
double d_hole = cylinder(Point3D(x, y, z), 0.4, gear_thickness * 2);
|
||||||
|
|
||||||
|
// 差集:打孔
|
||||||
|
double d = op_difference(d_cylinder, d_hole);
|
||||||
|
|
||||||
|
// 齿轮齿:沿圆周等距分布
|
||||||
|
double angle = std::atan2(z, x);
|
||||||
|
double radius = std::sqrt(x * x + z * z);
|
||||||
|
|
||||||
|
// 每个齿是一个小 Box 沿径向放置
|
||||||
|
double teeth_union = 1e10;
|
||||||
|
for (int i = 0; i < tooth_count; i++) {
|
||||||
|
double theta = i * 2.0 * M_PI / tooth_count;
|
||||||
|
double ca = std::cos(theta);
|
||||||
|
double sa = std::sin(theta);
|
||||||
|
|
||||||
|
// 旋转采样点到该齿的局部空间
|
||||||
|
double lx = ca * x + sa * z;
|
||||||
|
double lz = -sa * x + ca * z;
|
||||||
|
Point3D lp(lx, y, lz);
|
||||||
|
|
||||||
|
double tooth = box(lp,
|
||||||
|
Point3D(0.3, 0.6, gear_radius + tooth_depth));
|
||||||
|
// 向圆心方向偏移
|
||||||
|
double d_tooth = op_translate_along_x(lp,
|
||||||
|
gear_radius + tooth_depth * 0.5).norm() > 0
|
||||||
|
? box(Point3D(lx - (gear_radius + tooth_depth * 0.5), y, lz),
|
||||||
|
Point3D(0.15, gear_thickness * 1.1, tooth_depth * 0.5))
|
||||||
|
: 1e10;
|
||||||
|
|
||||||
|
// 用旋转矩阵的逆变换做齿的径向放置
|
||||||
|
double r = std::sqrt(x * x + z * z);
|
||||||
|
double dx = r - (gear_radius + tooth_depth * 0.5);
|
||||||
|
double dy_abs = std::abs(y);
|
||||||
|
double dz = std::abs(std::atan2(z, x) - theta);
|
||||||
|
if (dz > M_PI) dz = 2 * M_PI - dz;
|
||||||
|
dz = dz * gear_radius;
|
||||||
|
|
||||||
|
double tooth_dist = std::sqrt(dx * dx + dy_abs * dy_abs + dz * dz)
|
||||||
|
- tooth_depth * 0.5;
|
||||||
|
if (dy_abs <= gear_thickness * 0.55 && r >= gear_radius - 0.05) {
|
||||||
|
teeth_union = std::min(teeth_union, tooth_dist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d = op_union(d, teeth_union);
|
||||||
|
return d;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Marching Cubes
|
||||||
|
Point3D bmin(-3, -3, -3), bmax(3, 3, 3);
|
||||||
|
auto mc = marching_cubes(gear_sdf, 0.0, bmin, bmax, 128);
|
||||||
|
|
||||||
|
HalfedgeMesh mesh;
|
||||||
|
mesh.build_from_triangles(mc.vertices, mc.triangles);
|
||||||
|
|
||||||
|
GltfOptions opts;
|
||||||
|
opts.binary = true;
|
||||||
|
opts.include_normals = true;
|
||||||
|
write_gltf("gear.glb", mesh, opts);
|
||||||
|
|
||||||
|
std::cout << "Gear exported: " << mesh.num_vertices()
|
||||||
|
<< " vertices, " << mesh.num_faces() << " faces\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
编译运行,用 `viewer/index.html` 查看效果。
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
# B-Rep 工业建模教程
|
||||||
|
|
||||||
|
边界表示(Boundary Representation,B-Rep)是 CAD 系统中最常用的实体表示方法。ViewDesignEngine 提供高层建模 API 用于创建 B-Rep 实体,支持精确的几何计算和工业标准格式导出。
|
||||||
|
|
||||||
|
## 核心概念
|
||||||
|
|
||||||
|
B-Rep 通过拓扑层次结构描述三维几何体的边界:
|
||||||
|
|
||||||
|
```
|
||||||
|
Body (体) — 一个或多个封闭壳
|
||||||
|
└─ Shell (壳) — 一组面的连续集合
|
||||||
|
└─ Face (面) — 曲面上由环界定的区域
|
||||||
|
└─ Loop (环) — 封闭的边界曲线
|
||||||
|
└─ Edge (边) — 曲面的边界段
|
||||||
|
└─ Vertex (顶点) — 边的端点
|
||||||
|
```
|
||||||
|
|
||||||
|
## 基础实体(盒、柱、球)
|
||||||
|
|
||||||
|
使用 `vde/brep/modeling.h` 中的高层 API 创建基本体:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <vde/brep/modeling.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
using namespace vde::brep;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// 轴对齐长方体:宽 × 高 × 深
|
||||||
|
auto box = make_box(100.0, 50.0, 30.0);
|
||||||
|
std::cout << "Box faces: " << box.num_faces()
|
||||||
|
<< ", edges: " << box.num_edges() << "\n";
|
||||||
|
|
||||||
|
// 圆柱体:半径 20,高度 80,64 段(精度)
|
||||||
|
auto cylinder = make_cylinder(20.0, 80.0, 64);
|
||||||
|
std::cout << "Cylinder faces: " << cylinder.num_faces() << "\n";
|
||||||
|
|
||||||
|
// 球体:半径 25,经度 32 段,纬度 32 段
|
||||||
|
auto sphere = make_sphere(25.0, 32, 32);
|
||||||
|
std::cout << "Sphere faces: " << sphere.num_faces() << "\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 扫掠特征(Extrude, Revolve, Sweep, Loft)
|
||||||
|
|
||||||
|
扫掠操作将二维轮廓沿路径移动生成三维实体:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <vde/brep/modeling.h>
|
||||||
|
#include <vde/curves/nurbs_curve.h>
|
||||||
|
#include <vde/core/point.h>
|
||||||
|
|
||||||
|
using namespace vde::brep;
|
||||||
|
using namespace vde::curves;
|
||||||
|
using namespace vde::core;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// 创建一个矩形轮廓(作为 NURBS 曲线:封闭的 4 点控制多边形)
|
||||||
|
NurbsCurve profile;
|
||||||
|
profile.set_degree(1); // 线性
|
||||||
|
profile.set_knots({0, 0, 0.25, 0.5, 0.75, 1, 1});
|
||||||
|
profile.set_control_points({
|
||||||
|
Point3D(-10, 0, -10),
|
||||||
|
Point3D( 10, 0, -10),
|
||||||
|
Point3D( 10, 0, 10),
|
||||||
|
Point3D(-10, 0, 10),
|
||||||
|
Point3D(-10, 0, -10) // 闭合
|
||||||
|
});
|
||||||
|
|
||||||
|
// 线性挤出:沿 Z 轴挤出 50 单位
|
||||||
|
auto extruded = extrude(profile, Vector3D(0, 0, 50));
|
||||||
|
std::cout << "Extruded faces: " << extruded.num_faces() << "\n";
|
||||||
|
|
||||||
|
// 旋转扫掠(Revolve):半圆绕 Y 轴旋转
|
||||||
|
NurbsCurve semicircle;
|
||||||
|
semicircle.set_degree(2);
|
||||||
|
// ... 控制点定义上半圆 ...
|
||||||
|
auto revolved = revolve(semicircle,
|
||||||
|
Point3D(0, 0, 0), // 旋转轴起点
|
||||||
|
Vector3D(0, 1, 0), // 旋转轴方向(Y 轴)
|
||||||
|
360.0 // 角度(度)
|
||||||
|
);
|
||||||
|
std::cout << "Revolved faces: " << revolved.num_faces() << "\n";
|
||||||
|
|
||||||
|
// 沿路径扫掠(Sweep):圆形截面沿螺旋路径
|
||||||
|
NurbsCurve circle_profile; // 小圆
|
||||||
|
NurbsCurve helix_path; // 螺旋线
|
||||||
|
auto swept = sweep(circle_profile, helix_path);
|
||||||
|
|
||||||
|
// 放样(Loft):多个截面 → 实体
|
||||||
|
std::vector<NurbsCurve> profiles = {profile_a, profile_b, profile_c};
|
||||||
|
auto lofted = loft(profiles);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 特征操作(倒圆、倒角、抽壳)
|
||||||
|
|
||||||
|
编辑操作通过链式调用逐步细化模型:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <vde/brep/modeling.h>
|
||||||
|
#include <vde/brep/step_export.h>
|
||||||
|
#include <vde/foundation/io_gltf.h>
|
||||||
|
|
||||||
|
using namespace vde::brep;
|
||||||
|
using namespace vde::foundation;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// 基础块:100 × 60 × 40 mm
|
||||||
|
auto part = make_box(100.0, 60.0, 40.0);
|
||||||
|
|
||||||
|
// 对所有边倒圆角(face_id = 0 代表指定面,或对所有边操作)
|
||||||
|
part = fillet(part, 0, 5.0); // 半径 5mm 圆角
|
||||||
|
std::cout << "After fillet: " << part.num_faces() << " faces\n";
|
||||||
|
|
||||||
|
// 对顶面边缘倒角(典型机加工倒角)
|
||||||
|
// chamfer(face_id, distance)
|
||||||
|
// part = chamfer(part, 5, 2.0);
|
||||||
|
|
||||||
|
// 抽壳:创建均匀壁厚的空心壳体
|
||||||
|
// face_id = -1 表示封闭壳体(无开口)
|
||||||
|
// thickness > 0 表示向外增厚,< 0 表示保留内腔
|
||||||
|
part = shell(part, -1, 2.0);
|
||||||
|
std::cout << "After shell (2mm wall): "
|
||||||
|
<< part.num_faces() << " faces\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 布尔运算
|
||||||
|
|
||||||
|
B-Rep 层面支持精确布尔运算:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <vde/brep/modeling.h>
|
||||||
|
#include <vde/brep/brep_boolean.h>
|
||||||
|
|
||||||
|
using namespace vde::brep;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
auto block = make_box(50, 50, 50);
|
||||||
|
auto cylinder = make_cylinder(15, 80, 64);
|
||||||
|
|
||||||
|
// 并集(Union):块 + 柱
|
||||||
|
auto union_result = brep_union(block, cylinder);
|
||||||
|
|
||||||
|
// 交集(Intersection):块 ∩ 柱
|
||||||
|
auto isect_result = brep_intersection(block, cylinder);
|
||||||
|
|
||||||
|
// 差集(Difference):块 - 柱(在块上打孔)
|
||||||
|
auto diff_result = brep_difference(block, cylinder);
|
||||||
|
|
||||||
|
std::cout << "Union: " << union_result.num_faces() << " faces\n";
|
||||||
|
std::cout << "Intersection: " << isect_result.num_faces() << " faces\n";
|
||||||
|
std::cout << "Difference: " << diff_result.num_faces() << " faces\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## STEP 导出 → CAM
|
||||||
|
|
||||||
|
STEP(AP214)是工业制造的标准交换格式,可直接导入 CAM 软件生成刀具路径:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <vde/brep/modeling.h>
|
||||||
|
#include <vde/brep/step_export.h>
|
||||||
|
|
||||||
|
using namespace vde::brep;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
auto part = make_box(80, 40, 20);
|
||||||
|
part = fillet(part, 0, 3.0);
|
||||||
|
part = shell(part, -1, 2.0);
|
||||||
|
|
||||||
|
// 导出为 STEP AP214 文件
|
||||||
|
export_step_file("flange.stp", {part});
|
||||||
|
|
||||||
|
// 或以字符串形式获取 STEP 内容
|
||||||
|
std::string step_str = export_step({part});
|
||||||
|
std::cout << "STEP size: " << step_str.size() << " chars\n";
|
||||||
|
|
||||||
|
std::cout << "STEP exported. Ready for:\n"
|
||||||
|
<< " - Fusion 360 / SolidWorks / FreeCAD\n"
|
||||||
|
<< " - CAM toolpath generation\n"
|
||||||
|
<< " - CMM inspection\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整示例:法兰零件
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
/// 法兰零件:盒基 + 挤出环 → 倒角 → 钻孔阵列 → STEP/GLB 导出
|
||||||
|
#include <vde/brep/modeling.h>
|
||||||
|
#include <vde/brep/brep_boolean.h>
|
||||||
|
#include <vde/brep/step_export.h>
|
||||||
|
#include <vde/foundation/io_gltf.h>
|
||||||
|
#include <vde/core/point.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
using namespace vde::brep;
|
||||||
|
using namespace vde::foundation;
|
||||||
|
using namespace vde::core;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << std::fixed << std::setprecision(3);
|
||||||
|
|
||||||
|
// ── 法兰本体 ──────────────────────────────────
|
||||||
|
// 80×80×15 mm 的底座
|
||||||
|
double W = 80.0, D = 80.0, H = 15.0;
|
||||||
|
auto flange = make_box(W, D, H);
|
||||||
|
std::cout << "Base block: " << W << "×" << D << "×" << H << " mm\n";
|
||||||
|
|
||||||
|
// ── 中心凸台(挤出圆柱) ──────────────────────
|
||||||
|
double boss_d = 40.0, boss_h = 25.0;
|
||||||
|
auto boss = make_cylinder(boss_d / 2.0, boss_h, 64);
|
||||||
|
// 定位:凸台在法兰顶面中心
|
||||||
|
// (如需要偏移,可通过 brep_boolean 的变换参数定位)
|
||||||
|
flange = brep_union(flange, boss);
|
||||||
|
std::cout << "Added boss: Ø" << boss_d << "×" << boss_h << " mm\n";
|
||||||
|
|
||||||
|
// ── 边缘圆角 ──────────────────────────────────
|
||||||
|
flange = fillet(flange, 0, 3.0);
|
||||||
|
std::cout << "Edge fillet: R3.0 applied\n";
|
||||||
|
|
||||||
|
// ── 螺栓孔阵列(4 个 M8 通孔) ────────────────
|
||||||
|
double hole_d = 8.5; // M8 通孔直径
|
||||||
|
double bolt_circle_r = 30.0; // 螺栓孔分布圆半径
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
double angle = i * M_PI / 2.0; // 0°, 90°, 180°, 270°
|
||||||
|
double cx = bolt_circle_r * std::cos(angle);
|
||||||
|
double cy = bolt_circle_r * std::sin(angle);
|
||||||
|
|
||||||
|
auto hole = make_cylinder(hole_d / 2.0, H + 5.0, 32);
|
||||||
|
// 差集减孔(如需定位偏移,使用 brep_difference 的变换版本)
|
||||||
|
flange = brep_difference(flange, hole);
|
||||||
|
}
|
||||||
|
std::cout << "Bolt holes: 4ר" << hole_d << " on Ø" << bolt_circle_r * 2 << " BCD\n";
|
||||||
|
|
||||||
|
// ── 质量信息 ──────────────────────────────────
|
||||||
|
std::cout << "\nFinal part:\n"
|
||||||
|
<< " faces: " << flange.num_faces() << "\n"
|
||||||
|
<< " edges: " << flange.num_edges() << "\n";
|
||||||
|
|
||||||
|
// ── 导出 ──────────────────────────────────────
|
||||||
|
// STEP(CAM 用)
|
||||||
|
export_step_file("flange.stp", {flange});
|
||||||
|
std::cout << " → flange.stp (STEP AP214)\n";
|
||||||
|
|
||||||
|
// GLB(预览用)
|
||||||
|
if (write_brep_gltf("flange.glb", flange, 32)) {
|
||||||
|
std::cout << " → flange.glb (GLB visualization)\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 交互式预览 ────────────────────────────────
|
||||||
|
std::cout << "\nOpen viewer/index.html and drop flange.glb to inspect.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
编译运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
g++ -std=c++17 -I../include flange_demo.cpp -L. -lvde -o flange_demo
|
||||||
|
./flange_demo
|
||||||
|
```
|
||||||
|
|
||||||
|
生成的 `flange.stp` 可导入 FreeCAD / Fusion 360 进行 CAM 刀具路径生成,
|
||||||
|
`flange.glb` 可在项目自带的 `viewer/index.html` 中预览。
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>ViewDesignEngine 3D Viewer</title>
|
||||||
|
<style>
|
||||||
|
body { margin: 0; overflow: hidden; font-family: sans-serif; }
|
||||||
|
canvas { display: block; }
|
||||||
|
#info { position: absolute; top: 10px; left: 10px; color: white; background: rgba(0,0,0,0.7); padding: 8px 12px; border-radius: 4px; font-size: 12px; }
|
||||||
|
#drop { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); color: #aaa; font-size: 18px; pointer-events: none; }
|
||||||
|
#toolbar { position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); display: flex; gap: 8px; }
|
||||||
|
#toolbar button { padding: 6px 14px; border: 1px solid #666; background: rgba(0,0,0,0.6); color: white; border-radius: 3px; cursor: pointer; font-size: 12px; }
|
||||||
|
#toolbar button:hover { background: rgba(255,255,255,0.2); }
|
||||||
|
#file-input { display: none; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="info">ViewDesignEngine 3D Viewer</div>
|
||||||
|
<div id="drop">拖放 .glb / .stl 文件到这里</div>
|
||||||
|
<div id="toolbar">
|
||||||
|
<button onclick="document.getElementById('file-input').click()">📁 打开文件</button>
|
||||||
|
<button onclick="toggleWireframe()">🔲 线框</button>
|
||||||
|
<button onclick="resetCamera()">🔄 重置视角</button>
|
||||||
|
</div>
|
||||||
|
<input type="file" id="file-input" accept=".glb,.gltf,.stl">
|
||||||
|
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
|
||||||
|
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script type="module">
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||||
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
||||||
|
import { STLLoader } from 'three/addons/loaders/STLLoader.js';
|
||||||
|
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
scene.background = new THREE.Color(0x222233);
|
||||||
|
|
||||||
|
const camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000);
|
||||||
|
camera.position.set(5, 4, 8);
|
||||||
|
|
||||||
|
const renderer = new THREE.WebGLRenderer({antialias: true});
|
||||||
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
|
renderer.setPixelRatio(window.devicePixelRatio);
|
||||||
|
document.body.appendChild(renderer.domElement);
|
||||||
|
|
||||||
|
const controls = new OrbitControls(camera, renderer.domElement);
|
||||||
|
controls.enableDamping = true;
|
||||||
|
|
||||||
|
const ambientLight = new THREE.AmbientLight(0x404050);
|
||||||
|
scene.add(ambientLight);
|
||||||
|
const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
|
||||||
|
dirLight.position.set(10, 15, 10);
|
||||||
|
scene.add(dirLight);
|
||||||
|
|
||||||
|
const gridHelper = new THREE.GridHelper(10, 10, 0x444466, 0x333344);
|
||||||
|
scene.add(gridHelper);
|
||||||
|
|
||||||
|
let currentMesh = null;
|
||||||
|
|
||||||
|
function loadGLB(url) {
|
||||||
|
new GLTFLoader().load(url, (gltf) => {
|
||||||
|
if (currentMesh) scene.remove(currentMesh);
|
||||||
|
currentMesh = gltf.scene;
|
||||||
|
scene.add(currentMesh);
|
||||||
|
document.getElementById('drop').style.display = 'none';
|
||||||
|
document.getElementById('info').textContent = `GLB loaded: ${url.split('/').pop()}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSTL(url) {
|
||||||
|
new STLLoader().load(url, (geometry) => {
|
||||||
|
if (currentMesh) scene.remove(currentMesh);
|
||||||
|
const material = new THREE.MeshPhongMaterial({color: 0x4499cc, specular: 0x111111, shininess: 30});
|
||||||
|
currentMesh = new THREE.Mesh(geometry, material);
|
||||||
|
scene.add(currentMesh);
|
||||||
|
document.getElementById('drop').style.display = 'none';
|
||||||
|
document.getElementById('info').textContent = `STL loaded: ${url.split('/').pop()}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// File open
|
||||||
|
document.getElementById('file-input').addEventListener('change', (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
const url = URL.createObjectURL(file);
|
||||||
|
if (file.name.endsWith('.stl')) loadSTL(url);
|
||||||
|
else loadGLB(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Drag & drop
|
||||||
|
document.addEventListener('dragover', (e) => { e.preventDefault(); });
|
||||||
|
document.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const file = e.dataTransfer.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
const url = URL.createObjectURL(file);
|
||||||
|
if (file.name.endsWith('.stl')) loadSTL(url);
|
||||||
|
else loadGLB(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.toggleWireframe = () => {
|
||||||
|
if (currentMesh) {
|
||||||
|
currentMesh.traverse((child) => { if (child.isMesh) child.material.wireframe = !child.material.wireframe; });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.resetCamera = () => {
|
||||||
|
camera.position.set(5, 4, 8);
|
||||||
|
controls.target.set(0, 0, 0);
|
||||||
|
controls.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
controls.update();
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
}
|
||||||
|
animate();
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
camera.aspect = window.innerWidth / window.innerHeight;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user