use axiolid_brep::{FaceName, Operand, SweptFace};
use axiolid_construct::boolean_exact::{boolean_prisms_exact, Prism};
use axiolid_core::{BooleanOperator, Point2, Scalar, Tolerance};
fn rect_ring(cx: Scalar, cy: Scalar, x: Scalar, y: Scalar) -> Vec<Point2> {
let (hx, hy) = (x / 2.0, y / 2.0);
vec![
Point2::new(cx - hx, cy - hy),
Point2::new(cx + hx, cy - hy),
Point2::new(cx + hx, cy + hy),
Point2::new(cx - hx, cy + hy),
]
}
fn prism(rings: Vec<Vec<Point2>>, bottom: Scalar, top: Scalar) -> Prism {
Prism { rings, bottom, top }
}
#[test]
fn difference_walls_name_their_source_wall() {
let wall = prism(vec![rect_ring(0.0, 0.0, 10.0, 4.0)], 0.0, 3.0);
let opening = prism(vec![rect_ring(0.0, 0.0, 2.0, 2.0)], 0.0, 3.0);
let result = boolean_prisms_exact(
&wall,
&opening,
BooleanOperator::Difference,
Tolerance::METRE,
)
.expect("a wall with an interior opening is exactly constructible");
let topology = result.topology();
let mut subject_walls = 0;
let mut tool_walls = 0;
let mut unnamed = 0;
for index in 0..topology.faces().len() {
let Some(id) = topology.face_id_at(index) else {
continue;
};
match result.face_name(id) {
Some(FaceName::Fragment { operand, source }) => {
if matches!(**source, FaceName::Swept(SweptFace::Side(_))) {
match operand {
Operand::Subject => subject_walls += 1,
Operand::Tool => tool_walls += 1,
}
}
}
None => unnamed += 1,
_ => {}
}
}
assert_eq!(
subject_walls, 4,
"the four outer wall faces must name the subject walls they came from"
);
assert_eq!(
tool_walls, 4,
"the four opening faces must name the tool walls that cut them"
);
assert_eq!(unnamed, 0, "no face of an exact boolean should be unnamed");
}
#[test]
fn origin_sees_through_the_boolean_layer() {
let subject = prism(vec![rect_ring(0.0, 0.0, 4.0, 4.0)], 0.0, 3.0);
let tool = prism(vec![rect_ring(2.0, 0.0, 4.0, 4.0)], 0.0, 3.0);
let result = boolean_prisms_exact(
&subject,
&tool,
BooleanOperator::Intersection,
Tolerance::METRE,
)
.expect("coaxial prism intersection is exactly constructible");
let topology = result.topology();
let mut checked = 0;
for index in 0..topology.faces().len() {
let Some(id) = topology.face_id_at(index) else {
continue;
};
if let Some(name) = result.face_name(id) {
assert!(
!matches!(name.origin(), FaceName::Fragment { .. }),
"origin must strip all boolean layers, got {}",
name.origin()
);
assert!(
!name.is_anonymous(),
"an exactly-constructed face must not be anonymous, got {name}"
);
checked += 1;
}
}
assert!(checked > 0, "the result must carry at least one name");
}
#[test]
fn a_shared_wall_plane_stays_deterministic() {
let left = prism(vec![rect_ring(0.0, 0.0, 4.0, 4.0)], 0.0, 3.0);
let right = prism(vec![rect_ring(4.0, 0.0, 4.0, 4.0)], 0.0, 3.0);
let first = boolean_prisms_exact(&left, &right, BooleanOperator::Union, Tolerance::METRE);
let second = boolean_prisms_exact(&left, &right, BooleanOperator::Union, Tolerance::METRE);
let (Ok(first), Ok(second)) = (first, second) else {
return;
};
let names = |solid: &axiolid_brep::ExactBRep| -> Vec<String> {
let topology = solid.topology();
(0..topology.faces().len())
.filter_map(|index| topology.face_id_at(index))
.map(|id| match solid.face_name(id) {
Some(name) => name.to_string(),
None => "<unnamed>".to_owned(),
})
.collect()
};
assert_eq!(
names(&first),
names(&second),
"the same inputs must produce the same names on every run"
);
}
#[test]
fn a_touching_corner_does_not_borrow_the_wrong_wall() {
let subject = prism(vec![rect_ring(0.0, 0.0, 4.0, 2.0)], 0.0, 3.0);
let tool = prism(vec![rect_ring(0.0, 2.0, 2.0, 2.0)], 0.0, 3.0);
let Ok(result) =
boolean_prisms_exact(&subject, &tool, BooleanOperator::Union, Tolerance::METRE)
else {
return;
};
let topology = result.topology();
let mut seen: Vec<String> = (0..topology.faces().len())
.filter_map(|index| topology.face_id_at(index))
.filter_map(|id| result.face_name(id))
.map(|name| name.to_string())
.collect();
seen.sort();
for wall in ["tool/side[1]", "tool/side[2]", "tool/side[3]"] {
assert!(
seen.iter().any(|name| name == wall),
"{wall} must appear in the mapping, got {seen:?}"
);
}
assert!(
seen.iter().any(|name| name == "subject/side[3]"),
"subject/side[3] must survive as its own fragment, got {seen:?}"
);
}