#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use brepkit_io::arena_io::deserialize_solid;
use brepkit_operations::boolean::{BooleanOp, boolean_with_evolution};
use brepkit_operations::tessellate::{
boundary_edge_count, non_manifold_edge_count, tessellate_solid_with_tolerance,
};
use brepkit_topology::Topology;
use brepkit_topology::explorer::solid_faces;
use brepkit_topology::face::FaceId;
use brepkit_topology::solid::SolidId;
fn fixture(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/data")
.join(name)
}
fn load(name: &str, topo: &mut Topology) -> SolidId {
deserialize_solid(&std::fs::read(fixture(name)).unwrap(), topo).unwrap()
}
fn curved_count(topo: &Topology, solid: SolidId) -> usize {
solid_faces(topo, solid)
.unwrap()
.iter()
.filter(|&&f| topo.face(f).unwrap().surface().type_tag() != "plane")
.count()
}
fn free_edge_count(topo: &Topology, solid: SolidId) -> 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 faces_per_edge: HashMap<(QPoint, QPoint), HashSet<FaceId>> = 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()) {
for oe in topo.wire(wid).unwrap().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) };
faces_per_edge.entry(key).or_default().insert(fid);
}
}
}
faces_per_edge.values().filter(|f| f.len() == 1).count()
}
fn assert_fuse_health(topo: &Topology, r: SolidId, min_curved: usize, max_faces: usize) {
let n_faces = solid_faces(topo, r).unwrap().len();
assert!(
n_faces < max_faces,
"fuse returned {n_faces} faces — mesh fallback fired?"
);
let curved = curved_count(topo, r);
assert!(
curved >= min_curved,
"only {curved} curved faces — analytic surfaces lost?"
);
assert_eq!(free_edge_count(topo, r), 0, "free B-Rep edges in result");
let mesh = tessellate_solid_with_tolerance(topo, r, 0.01, 5.0_f64.to_radians()).unwrap();
assert_eq!(
boundary_edge_count(&mesh),
0,
"boundary edges in export mesh"
);
assert_eq!(
non_manifold_edge_count(&mesh),
0,
"non-manifold edges in export mesh"
);
}
#[test]
fn hs2x2_bin_socket_fuse_is_watertight_and_analytic() {
let mut topo = Topology::new();
let bin = load("hs2x2_socketfuse_body.bin", &mut topo);
let sockets = load("hs2x2_socketfuse_sockets.bin", &mut topo);
let (r, _evo) = boolean_with_evolution(&mut topo, BooleanOp::Fuse, bin, sockets).unwrap();
assert_fuse_health(&topo, r, 200, 700);
}