#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use brepkit_operations::boolean::{BooleanOp, boolean};
use brepkit_topology::Topology;
use brepkit_topology::explorer::solid_faces;
use brepkit_topology::solid::SolidId;
fn fixture(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/data")
.join(name)
}
fn read_one(name: &str, topo: &mut Topology) -> SolidId {
let text = std::fs::read_to_string(fixture(name)).unwrap();
let solids = brepkit_io::step::reader::read_step(&text, topo).unwrap();
assert_eq!(solids.len(), 1, "expected exactly one solid in {name}");
solids[0]
}
fn edge_use(topo: &Topology, solid: SolidId) -> (usize, usize) {
type QPoint = (i64, i64, i64);
let scale = 1.0e6;
let q = |p: brepkit_math::vec::Point3| -> QPoint {
(
(p.x() * scale).round() as i64,
(p.y() * scale).round() as i64,
(p.z() * scale).round() as i64,
)
};
let mut counts: HashMap<(QPoint, QPoint), usize> = HashMap::new();
for fid in solid_faces(topo, solid).unwrap() {
let face = topo.face(fid).unwrap();
for wid in std::iter::once(face.outer_wire()).chain(face.inner_wires().iter().copied()) {
let wire = topo.wire(wid).unwrap();
for oe in wire.edges() {
let e = topo.edge(oe.edge()).unwrap();
let a = q(topo.vertex(e.start()).unwrap().point());
let b = q(topo.vertex(e.end()).unwrap().point());
let key = if a <= b { (a, b) } else { (b, a) };
*counts.entry(key).or_insert(0) += 1;
}
}
}
let free = counts.values().filter(|&&c| c == 1).count();
let over = counts.values().filter(|&&c| c > 2).count();
(free, over)
}
fn curved_face_count(topo: &Topology, solid: SolidId) -> usize {
solid_faces(topo, solid)
.unwrap()
.iter()
.filter(|&&fid| topo.face(fid).unwrap().surface().type_tag() != "plane")
.count()
}
fn assert_lip_fuse_clean(body_step: &str, lip_step: &str) {
let mut topo = Topology::new();
let body = read_one(body_step, &mut topo);
let lip = read_one(lip_step, &mut topo);
let result = boolean(&mut topo, BooleanOp::Fuse, body, lip).unwrap();
let (free, over) = edge_use(&topo, result);
assert_eq!(
free, 0,
"stacking-lip fuse must be watertight (no free edges)"
);
assert_eq!(
over, 0,
"stacking-lip fuse must be manifold (no over-shared edges)"
);
let faces = solid_faces(&topo, result).unwrap().len();
assert!(
faces < 120,
"expected a compact analytic result, got {faces} faces (mesh fallback?)"
);
assert!(
curved_face_count(&topo, result) >= 24,
"stacking-lip fuse must stay analytic (corner cylinders + lip cones preserved)"
);
}
#[test]
fn gridfinity_stacking_lip_fuse_is_watertight() {
assert_lip_fuse_clean("lipfuse_body.step", "lipfuse_lip.step");
}
#[test]
fn gridfinity_stacking_lip_fuse_2x1_is_watertight() {
assert_lip_fuse_clean("lipfuse_2x1_body.step", "lipfuse_2x1_lip.step");
}
#[test]
fn gridfinity_stacking_lip_fuse_3x3_is_watertight() {
assert_lip_fuse_clean("lipfuse_3x3_body.step", "lipfuse_3x3_lip.step");
}