MoonBit 实现的 STL/OBJ/3MF 三维网格工具链:解析、校验、修复、分析、流式扫描

| 网格的问题 | 打印时会发生什么 |
|---|---|
| 不水密(有破洞) | 切片软件分不清哪是实心哪是空的,算出来的填充是错的 |
| 有非流形边(一条边被三个以上的面共用) | 切片软件不知道该往哪一侧填材料 |
| 有退化面(三个点共线) | 算出零面积的层,产生碎片 |
| 法线朝向反了 | 有的工具会把内外面画颠倒 |

解析 STL(ASCII + 二进制)、OBJ(含多边形面与负数编号)、
3MF(含装配体与单位换算)、PLY(ASCII + 两种字节序的二进制)
校验 水密性、非流形边、退化面、法线朝向、绕向一致性
分析 体积、表面积、包围盒、定向包围盒、尺寸、三角形数、去重顶点数、
壁厚(从表面往里打射线量对面有多远)、
零件数(几块互不相连的几何)、欧拉示性数、
重心与转动惯量(密度取 1)
预览 等轴测 SVG 图(自带背面剔除与明暗)
修复 删退化面、去重复面、补洞、重算法线;可导出成 STL / OBJ / PLY
流式 分块扫描超大模型,内存不随模型大小增长
形态 库 + 命令行 + 网页# 出一份体检报告
moon run cmd/main examples/cube.stl
# 顺便导出预览图
moon run cmd/main examples/cube.stl --svg cube.svg
# 跑基准测试,对比三种解析方式的耗时与内存
moon run cmd/main big.stl --bench
# 不带参数打印帮助
moon run cmd/mainexamples/broken.stl
格式 ASCII STL
三角形 11 个
顶点 33 个(去重后 8 个)
包围盒 1 × 1 × 1 mm
体积 1 mm³
表面积 5.5 mm²
✗ 不是水密网格 —— 有 3 条边只被一个面共用
网格上有破洞,切片软件分不清哪里是实心哪里是空的,直接打印会失败
结论:直接打印会失败,需要先修复let data = @fs.read_file_to_bytes("model.stl")
let mesh = match @stlkit.parse_mesh(data) {
Ok(mesh) => mesh
Err(msg) => { println("读不了:\{msg}"); return }
}
let check = @stlkit.validate_default(mesh)
if check.is_watertight() {
println("可以打印,体积 \{mesh.volume()} mm³")
} else {
println("有 \{check.boundary_edge_count} 条边界边,需要先修复")
}| 文件 | 三角形 F | 边 E = 3F/2 | 顶点 V | V − E + F | 应该是 |
|---|---|---|---|---|---|
| box.3mf | 12 | 18 | 8 | 2 | 2(球面) |
| torus.3mf | 2200 | 3300 | 1100 | 0 | 0(圆环面) |
| multiple_cylinders.3mf | 528 | 792 | 276 | 12 | 12(6 个闭合体 × 2) |
<object id="2" type="model">
<components>
<component objectid="1" transform="1 0 0 0 1 0 0 0 1 10 0 0"/>
</components>
</object>m00 m01 m02
m10 m11 m12
m20 m21 m22
m30 m31 m32 ← 平移在这里,不是前三个 ✗ 有 53 个面的绕向和其他面相反(顶点顺序写反了)
✗ 有 24 条边被三个以上的面共用(非流形边)
✗ 有 24 个退化三角形(三个点共线,没有面积)| 组成 | 大小 | |
|---|---|---|
| 文件里(二进制 STL) | 3 个坐标 + 1 个法线 = 12 个 float32 | 50 字节 |
| 内存里(Triangle) | 4 个 Vec3 = 12 个 Double | 96 字节 |
$ moon run cmd/main huge.stl --bench
全量解析(保留网格,能校验能修复)
耗时 18087.6 ms
内存 文件 95.37 MB + 网格 198.36 MB = 293.73 MB
流式扫描(不保留网格,文件整块读)
耗时 2048.4 ms
内存 文件 95.37 MB + 顶点边表 45.85 MB = 141.22 MB
流式扫描(不保留网格,文件分块读)
耗时 3334.3 ms
内存 缓冲 256 KB + 顶点边表 45.85 MB = 46.1 MB
省内存 247.63 MB(84.3%)
提速 5.4 倍| 全量解析 | 流式扫描 | |
|---|---|---|
| 体积 / 表面积 / 包围盒 | ✓ | ✓ |
| 水密性 / 非流形边 | ✓ | ✓ |
| 绕向一致性 | ✓ | ✗ |
| 修复、导出、预览 | ✓ | ✗ |
| 内存 | 随模型线性增长 | 固定(分块模式下) |
moon test # 跑测试
moon run cmd/main # 不带参数会打印帮助
bash web/build.sh # 重新编译网页用的 JSTotal tests: 239, passed: 239, failed: 0.moon run cmd/main examples/broken.stl --fix fixed.stl 修复前 边界边 3 非流形边 0 退化面 0
修复后 边界边 0 非流形边 0 退化面 0
已存到 fixed.stl(684 字节),现在可以切片打印了
$ moon run cmd/main examples/flipped.stl
体积 0.667 mm³ ← 错!应该是 1
✗ 有 1 个面的绕向和其他面相反(顶点顺序写反了)
这些面朝里了:水密性检查看不出问题,但体积会算错,切片软件也可能把实心区域填反
⚠ 有 1 个三角形的法线朝向和顶点顺序不一致$ moon run cmd/main examples/flipped.stl --fix fixed.stl
修复:
翻转 1 个绕向反了的面(体积从 0.667 修正为 1)
已存到 fixed.stl(684 字节),现在可以切片打印了[0] [1] [2] Ixx Ixy Ixz
[3] [4] [5] = Iyx Iyy Iyz
[6] [7] [8] Izx Izy Izzpub(all) enum MeshFormat {
Ascii
Binary
ThreeMF
Ply
Unknown
}pub(all) struct PreviewOptions {
width : Int
height : Int
yaw : Double
pitch : Double
stroke : String
stroke_width : Double
}pub(all) struct RepairOptions {
tolerance : Double
drop_degenerate : Bool
drop_duplicates : Bool
fill_holes : Bool
recompute_normals : Bool
unify_winding : Bool
}pub(all) struct RepairResult {
mesh : Mesh
removed_degenerate : Int
removed_duplicates : Int
filled_holes : Int
added_triangles : Int
recomputed_normals : Int
flipped_faces : Int
}pub(all) struct StlScanner {
// private fields
}pub(all) struct StreamReport {
triangle_count : Int
raw_vertex_count : Int
unique_vertex_count : Int
boundary_edge_count : Int
non_manifold_edge_count : Int
volume : Double
surface_area : Double
bbox : Aabb?
}pub(all) struct ThicknessReport {
samples : Int
hits : Int
min_thickness : Double
min_at : Vec3?
mean_thickness : Double
}pub(all) struct ValidationReport {
triangle_count : Int
raw_vertex_count : Int
unique_vertex_count : Int
boundary_edge_count : Int
non_manifold_edge_count : Int
degenerate_count : Int
wrong_normal_count : Int
unique_edge_count : Int
}pub(all) struct Vec3 {
x : Double
y : Double
z : Double
}pub(all) struct WindingReport {
flipped_faces : Int
consistent : Bool
signed_volume : Double
}fn default_weld_tolerance() -> Doublefn is_3mf(data : Bytes) -> Boolfn is_ply(data : Bytes) -> Boolfn symmetric_eigenvalues(m : FixedArray[Double]) -> FixedArray[Double]Install
Download zipMoonBit 实现的 STL/OBJ/3MF 三维网格工具链:解析、校验、修复、分析、流式扫描