mesh files in: STL, OBJ, glTF and PLY
git clone https://git.lucas.co/cce-mesh-io.git
tests/designer_round_trip.rs (2.4K)
1 //! What cce-designer exports, this crate reads back as the same mesh.
2 //!
3 //! The fixtures are the designer's default project written by its own
4 //! exporter (`cce-designer --export default_project.json out.stl|out.obj`,
5 //! 2026-10-07): 362 points and 384 primitives, 48 triangles and 336 quads, so
6 //! 720 triangles. STL keeps only triangles and no shared points; OBJ keeps
7 //! both. Read and welded, the two must agree on every point and triangle.
8
9 use std::path::Path;
10
11 use cce_mesh_io::{load, Mesh, UpAxis};
12 use glam::Vec3;
13
14 fn fixture(name: &str) -> cce_mesh_io::Scene {
15 load(&Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures").join(name)).unwrap()
16 }
17
18 /// Every triangle as its three corners, starting from the smallest so the
19 /// same triangle written from another corner compares equal; sorted.
20 fn triangle_set(m: &Mesh) -> Vec<[[i64; 3]; 3]> {
21 // Exact-enough keys: the exporter writes shortest round-trip floats.
22 let key = |p: Vec3| [(p.x * 1e5).round() as i64, (p.y * 1e5).round() as i64, (p.z * 1e5).round() as i64];
23 let mut out: Vec<[[i64; 3]; 3]> = m
24 .triangles
25 .iter()
26 .map(|t| {
27 let k = t.map(|i| key(m.positions[i as usize]));
28 let first = (0..3).min_by_key(|&i| k[i]).unwrap();
29 [k[first], k[(first + 1) % 3], k[(first + 2) % 3]]
30 })
31 .collect();
32 out.sort();
33 out
34 }
35
36 #[test]
37 fn the_stl_export_reads_back_as_the_designer_wrote_it() {
38 let s = fixture("designer-default.stl");
39 assert_eq!(s.up, UpAxis::Z, "STL's convention, reported, not applied");
40 assert_eq!(s.parts.len(), 1);
41 assert_eq!(s.parts[0].name, "designer-default");
42 let m = &s.parts[0].mesh;
43 assert_eq!(m.triangles.len(), 720);
44 assert_eq!(m.positions.len(), 362, "welding recovers the designer's shared points");
45 }
46
47 #[test]
48 fn the_obj_export_reads_back_as_the_designer_wrote_it() {
49 let s = fixture("designer-default.obj");
50 assert_eq!(s.up, UpAxis::Y);
51 assert_eq!(s.parts[0].name, "default", "the exporter's `o` line");
52 let m = &s.parts[0].mesh;
53 assert_eq!(m.triangles.len(), 720, "48 triangles and 336 quads");
54 assert_eq!(m.positions.len(), 362);
55 }
56
57 #[test]
58 fn both_exports_are_the_same_mesh() {
59 let stl = fixture("designer-default.stl").merged();
60 let obj = fixture("designer-default.obj").merged();
61 assert_eq!(stl.bounds(), obj.bounds());
62 assert_eq!(triangle_set(&stl), triangle_set(&obj), "same triangles, same winding");
63 }