#![allow(clippy::unwrap_used, clippy::expect_used)]
use brepkit_math::mat::Mat4;
use brepkit_operations::boolean::{self, BooleanOp};
use brepkit_topology::Topology;
use brepkit_topology::solid::SolidId;
fn plate_and_block(topo: &mut Topology) -> (SolidId, SolidId) {
let plate = brepkit_operations::primitives::make_box(topo, 80.0, 80.0, 5.0).unwrap();
brepkit_operations::transform::transform_solid(topo, plate, &Mat4::translation(0.0, 0.0, 5.0))
.unwrap();
let block = brepkit_operations::primitives::make_box(topo, 80.0, 80.0, 5.0).unwrap();
(plate, block)
}
fn assert_strictly_valid(topo: &Topology, sid: SolidId, label: &str) {
let report = brepkit_operations::validate::validate_solid(topo, sid).unwrap();
assert!(report.is_valid(), "{label} must validate: {report:?}");
}
#[test]
fn rect_through_hole_cut_and_interface_fuse_have_valid_winding() {
let mut topo = Topology::new();
let (plate, block) = plate_and_block(&mut topo);
let hole = brepkit_operations::primitives::make_box(&mut topo, 20.0, 20.0, 7.0).unwrap();
brepkit_operations::transform::transform_solid(
&mut topo,
hole,
&Mat4::translation(30.0, 30.0, 4.0),
)
.unwrap();
let holed = boolean::boolean(&mut topo, BooleanOp::Cut, plate, hole).unwrap();
assert_strictly_valid(&topo, holed, "rect through-hole cut");
let fused = boolean::boolean(&mut topo, BooleanOp::Fuse, holed, block).unwrap();
assert_strictly_valid(&topo, fused, "rect interface fuse");
let vol = brepkit_operations::measure::oriented_solid_volume(&topo, fused, 0.05).unwrap();
assert!((vol - 62000.0).abs() < 0.5, "fuse volume {vol:.3}");
}
#[test]
fn circle_through_hole_cut_and_interface_fuse_have_valid_winding() {
let mut topo = Topology::new();
let (plate, block) = plate_and_block(&mut topo);
let hole = brepkit_operations::primitives::make_cylinder(&mut topo, 10.0, 7.0).unwrap();
brepkit_operations::transform::transform_solid(
&mut topo,
hole,
&Mat4::translation(40.0, 40.0, 4.0),
)
.unwrap();
let holed = boolean::boolean(&mut topo, BooleanOp::Cut, plate, hole).unwrap();
assert_strictly_valid(&topo, holed, "circle through-hole cut");
let fused = boolean::boolean(&mut topo, BooleanOp::Fuse, holed, block).unwrap();
assert_strictly_valid(&topo, fused, "circle interface fuse");
}
#[test]
fn coincident_cap_pocket_cut_has_valid_winding() {
let mut topo = Topology::new();
let (plate, _block) = plate_and_block(&mut topo);
let hole = brepkit_operations::primitives::make_cylinder(&mut topo, 10.0, 6.0).unwrap();
brepkit_operations::transform::transform_solid(
&mut topo,
hole,
&Mat4::translation(40.0, 40.0, 5.0),
)
.unwrap();
let holed = boolean::boolean(&mut topo, BooleanOp::Cut, plate, hole).unwrap();
assert_strictly_valid(&topo, holed, "coincident-cap pocket cut");
}
#[test]
fn cw_wound_extruded_profile_cut_has_valid_winding() {
use brepkit_math::curves::Circle3D;
use brepkit_math::vec::{Point3, Vec3};
use brepkit_topology::edge::{Edge, EdgeCurve};
use brepkit_topology::face::{Face, FaceSurface};
use brepkit_topology::vertex::Vertex;
use brepkit_topology::wire::{OrientedEdge, Wire};
let mut topo = Topology::new();
let (plate, block) = plate_and_block(&mut topo);
let (cx, cy, r, z0) = (40.0, 40.0, 10.0, 5.0);
let z = Vec3::new(0.0, 0.0, 1.0);
let v = |topo: &mut Topology, x: f64, y: f64| {
topo.add_vertex(Vertex::new(Point3::new(x, y, z0), 1e-7))
};
let v0 = v(&mut topo, cx + r, cy);
let v1 = v(&mut topo, cx, cy + r);
let v2 = v(&mut topo, cx - r, cy);
let v3 = v(&mut topo, cx, cy - r);
let arc = |topo: &mut Topology, a, b| {
let circle = Circle3D::new(Point3::new(cx, cy, z0), z, r).unwrap();
topo.add_edge(Edge::new(a, b, EdgeCurve::Circle(circle)))
};
let edges = [
arc(&mut topo, v0, v1),
arc(&mut topo, v1, v2),
arc(&mut topo, v2, v3),
arc(&mut topo, v3, v0),
];
let oes: Vec<OrientedEdge> = edges
.iter()
.rev()
.map(|&e| OrientedEdge::new(e, false))
.collect();
let wid = topo.add_wire(Wire::new(oes, true).unwrap());
let fid = topo.add_face(Face::new(
wid,
vec![],
FaceSurface::Plane {
normal: Vec3::new(0.0, 0.0, 1.0),
d: z0,
},
));
let hole = brepkit_operations::extrude::extrude(&mut topo, fid, z, 6.0).unwrap();
assert_strictly_valid(&topo, hole, "cw-profile extruded tool");
let holed = boolean::boolean(&mut topo, BooleanOp::Cut, plate, hole).unwrap();
assert_strictly_valid(&topo, holed, "cw-profile coincident-cap cut");
let fused = boolean::boolean(&mut topo, BooleanOp::Fuse, holed, block).unwrap();
assert_strictly_valid(&topo, fused, "cw-profile interface fuse");
}
#[test]
fn partial_overlap_corner_hole_interface_fuse_is_exact() {
use brepkit_math::curves::Circle3D;
use brepkit_math::vec::{Point3, Vec3};
use brepkit_topology::edge::{Edge, EdgeCurve};
use brepkit_topology::face::{Face, FaceSurface};
use brepkit_topology::vertex::Vertex;
use brepkit_topology::wire::{OrientedEdge, Wire};
let mut topo = Topology::new();
let z = Vec3::new(0.0, 0.0, 1.0);
let z0 = 5.0;
let (hw, r) = (40.0, 3.75);
let c = hw - r;
let v = |topo: &mut Topology, x: f64, y: f64| {
topo.add_vertex(Vertex::new(Point3::new(x, y, z0), 1e-7))
};
let corners = [
(hw, -c),
(hw, c),
(c, hw),
(-c, hw),
(-hw, c),
(-hw, -c),
(-c, -hw),
(c, -hw),
];
let vs: Vec<_> = corners.iter().map(|&(x, y)| v(&mut topo, x, y)).collect();
let arc_centers = [(c, c), (-c, c), (-c, -c), (c, -c)];
let mut edges = Vec::new();
for i in 0..4 {
edges.push(topo.add_edge(Edge::new(vs[2 * i], vs[2 * i + 1], EdgeCurve::Line)));
let (ax, ay) = arc_centers[i];
let circle = Circle3D::new(Point3::new(ax, ay, z0), z, r).unwrap();
edges.push(topo.add_edge(Edge::new(
vs[2 * i + 1],
vs[(2 * i + 2) % 8],
EdgeCurve::Circle(circle),
)));
}
let wid = topo.add_wire(
Wire::new(
edges.iter().map(|&e| OrientedEdge::new(e, true)).collect(),
true,
)
.unwrap(),
);
let fid = topo.add_face(Face::new(
wid,
vec![],
FaceSurface::Plane { normal: z, d: z0 },
));
let plate = brepkit_operations::extrude::extrude(&mut topo, fid, z, 5.0).unwrap();
let hr = 10.0;
let hv = [
v(&mut topo, hr, 0.0),
v(&mut topo, 0.0, hr),
v(&mut topo, -hr, 0.0),
v(&mut topo, 0.0, -hr),
];
let harc = |topo: &mut Topology, a, b| {
let circle = Circle3D::new(Point3::new(0.0, 0.0, z0), z, hr).unwrap();
topo.add_edge(Edge::new(a, b, EdgeCurve::Circle(circle)))
};
let hedges = [
harc(&mut topo, hv[0], hv[1]),
harc(&mut topo, hv[1], hv[2]),
harc(&mut topo, hv[2], hv[3]),
harc(&mut topo, hv[3], hv[0]),
];
let hwid = topo.add_wire(
Wire::new(
hedges.iter().map(|&e| OrientedEdge::new(e, true)).collect(),
true,
)
.unwrap(),
);
let hfid = topo.add_face(Face::new(
hwid,
vec![],
FaceSurface::Plane { normal: z, d: z0 },
));
let hole = brepkit_operations::extrude::extrude(&mut topo, hfid, z, 6.0).unwrap();
let holed = boolean::boolean(&mut topo, BooleanOp::Cut, plate, hole).unwrap();
assert_strictly_valid(&topo, holed, "corner-hole cut");
let block = brepkit_operations::primitives::make_box(&mut topo, 80.0, 80.0, 5.0).unwrap();
let deflection = 0.05;
let holed_vol =
brepkit_operations::measure::oriented_solid_volume(&topo, holed, deflection).unwrap();
let block_vol =
brepkit_operations::measure::oriented_solid_volume(&topo, block, deflection).unwrap();
let fallbacks_before = boolean::mesh_fallback_count();
let fused = boolean::boolean(&mut topo, BooleanOp::Fuse, holed, block).unwrap();
assert_eq!(
boolean::mesh_fallback_count(),
fallbacks_before,
"partial-overlap corner-hole fuse must not mesh-fallback"
);
assert_strictly_valid(&topo, fused, "partial-overlap corner-hole fuse");
let radii: Vec<f64> = brepkit_topology::explorer::solid_faces(&topo, fused)
.unwrap()
.iter()
.filter_map(|&f| match topo.face(f).unwrap().surface() {
FaceSurface::Cylinder(c) => Some(c.radius()),
_ => None,
})
.collect();
let hole_walls = radii.iter().filter(|&&cr| (cr - hr).abs() < 1e-9).count();
let corner_faces = radii.iter().filter(|&&cr| (cr - r).abs() < 1e-9).count();
assert_eq!(
hole_walls, 4,
"all quarter-cylinder hole walls must survive"
);
assert_eq!(
corner_faces, 4,
"all rounded-corner side faces must survive"
);
assert_eq!(radii.len(), 8, "no extra cylinder fragments: {radii:?}");
let vol = brepkit_operations::measure::oriented_solid_volume(&topo, fused, deflection).unwrap();
assert!(
(vol - (holed_vol + block_vol)).abs() < 0.5,
"fuse volume {vol:.3} vs operand sum {:.3}",
holed_vol + block_vol
);
}