Skip to main content

teamcraft/
mesh.rs

1//! Triangle meshes and point clouds for the 3D preview.
2//!
3//! Pure-Rust readers for the common mesh, 3D-printing, visualisation and
4//! point-cloud formats, plus STEP B-rep tessellation with the `truck` CAD
5//! kernel (Apache-2.0). Every reader produces one [`Mesh`] in model
6//! coordinates; [`Mesh::encode`] writes the compact binary form that
7//! `shared/js/viewer.js` draws (`TCMESH01`, little endian).
8//!
9//! Simulation results (VTK, VTU/VTP, CalculiX FRD, see [`crate::simmesh`])
10//! additionally carry per-vertex scalar [`Field`]s that the viewer colours
11//! with a colour map; the section is optional and flagged, so older viewers
12//! and cached meshes keep working.
13//!
14//! The readers are defensive: sizes are limited, indices are checked and a
15//! malformed file is an error, never a panic or an endless loop. The server
16//! still runs them in a separate process with a time limit (see `preview`).
17#![deny(
18    clippy::unwrap_used,
19    clippy::expect_used,
20    clippy::indexing_slicing,
21    clippy::panic,
22    clippy::unreachable
23)]
24
25use anyhow::{bail, Result};
26
27mod catia;
28pub mod creo;
29mod earcut;
30mod f3d;
31mod gltf;
32mod helpers;
33mod inventor;
34mod jt;
35mod jt10;
36mod model;
37mod obj;
38mod off;
39mod p21;
40mod ply;
41mod pn;
42mod points;
43pub mod render;
44pub mod rhino;
45mod sketchup;
46mod step;
47mod stl;
48mod sw;
49mod tessellated;
50#[cfg(test)]
51mod test_support;
52mod three_ds;
53mod three_mf;
54
55pub(crate) use helpers::*;
56pub use model::{color_map, Field, Mesh, MAX_FIELDS, MAX_POINTS, MAX_TRIANGLES};
57pub use step::MAX_STEP_BYTES;
58
59/// Mesh formats the readers understand (ids as in `cadformat`).
60pub fn read(format: &str, data: &[u8]) -> Result<Mesh> {
61    let mesh = match format {
62        "stl" => stl::read(data)?,
63        "obj" => obj::read(data)?,
64        "ply" => ply::read(data)?,
65        "off" => off::read(data)?,
66        "3mf" => three_mf::read_3mf(data)?,
67        "amf" => three_mf::read_amf(data)?,
68        "gltf" | "glb" => gltf::read(data)?,
69        "3ds" => three_ds::read(data)?,
70        "pts" | "xyz" => points::read_text(data)?,
71        "las" => points::read_las(data)?,
72        "step" => step::read(data)?,
73        "nx-part" => jt::read_nx(data)?,
74        "jt" => jt10::read(data)?,
75        "solidworks-part" => sw::read_part(data)?,
76        "creo-part" => creo::read_part(data)?,
77        "solidworks-assembly" => bail!(
78            "A SOLIDWORKS assembly is shown with the previews of its parts (found by file name)."
79        ),
80        "rhino" => rhino::read(data)?,
81        "inventor-part" => inventor::read_part(data)?,
82        "inventor-assembly" => bail!(
83            "An Inventor assembly is shown with the previews of its parts (found by file name)."
84        ),
85        "catia-part" => catia::read_part(data)?,
86        "creo-assembly" => bail!(
87            "A Creo assembly is shown with the previews of its parts (found by file name)."
88        ),
89        "catia-product" => bail!(
90            "A CATIA product is shown with the previews of its parts (found by document or part number)."
91        ),
92        "sketchup" => sketchup::read(data)?,
93        "fusion-design" => f3d::read_f3d(data)?,
94        "vtk-legacy" => crate::simmesh::vtk_legacy(data)?,
95        "vtk-unstructured" | "vtk-polydata" => crate::simmesh::vtk_xml(data)?,
96        "calculix-frd" => crate::simmesh::frd(data)?,
97        other => bail!("No 3D preview for the format '{other}'."),
98    };
99    mesh.check()?;
100    Ok(mesh)
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn step_box_is_tessellated() {
109        let mesh = read("step", include_bytes!("../tests/fixtures/cube.step")).unwrap();
110        assert!(
111            mesh.triangle_count() >= 12,
112            "{} triangles",
113            mesh.triangle_count()
114        );
115        let b = mesh.bounds();
116        assert!((b[3] - b[0] - 10.0).abs() < 1e-3, "{b:?}");
117        assert!(read(
118            "step",
119            b"ISO-10303-21;\nHEADER;\nENDSEC;\nDATA;\nENDSEC;\nEND-ISO-10303-21;\n"
120        )
121        .is_err());
122        assert!(read("step", b"not a step file").is_err());
123    }
124
125    #[test]
126    fn read_dispatches_by_format_and_checks_the_result() {
127        let err = read("dwg", b"").unwrap_err().to_string();
128        assert!(err.contains("No 3D preview for the format 'dwg'"), "{err}");
129        let err = read("solidworks-assembly", b"").unwrap_err().to_string();
130        assert!(err.contains("previews of its parts"), "{err}");
131        // glTF and GLB share a reader
132        assert_eq!(
133            read("glb", b"{ not json").unwrap_err().to_string(),
134            read("gltf", b"{ not json").unwrap_err().to_string()
135        );
136        // every reader's mesh goes through `check`: a header-only PLY has no
137        // geometry
138        let ply = b"ply\nformat ascii 1.0\nelement vertex 0\nproperty float x\nproperty float y\nproperty float z\nend_header\n";
139        let err = read("ply", ply).unwrap_err().to_string();
140        assert!(err.contains("no geometry"), "{err}");
141    }
142}