Thanks @SychPL.
This commit is contained in:
1 parent
2f24878bb0
commit
d1b96100d3
3 files changed
+162
-1
No files matched your search
@@ -1528,7 +1528,87 @@ pub fn split_by_plane(body: &Body, plane: &solidcraft_geom::Plane) -> Result<Vec
|
||||
pub fn half_space(body: &Body, plane: &solidcraft_geom::Plane, positive: bool) -> Result<(Option<Body>, Body)> {
|
||||
// The box's own construction roles describe no face of the design: the cut face has none.
|
||||
let tool = half_box(body, plane, positive)?.with_face_sources(None);
|
||||
Ok((boolean(body, &tool, BoolOp::Intersect)?, tool))
|
||||
let (part, report) = capture_boolean_diagnostics(|| boolean(body, &tool, BoolOp::Intersect));
|
||||
// Faces of the body lying in the plane coincide with the box's cap, which the boolean may
|
||||
// only take by nudging the box, by a share of its own size: the cut face then moves off the
|
||||
// plane, a different way for each part, and the parts lose the slab between (#1510). Moved
|
||||
// off the plane to the side the box leaves out, those faces change nothing of the part, and
|
||||
// the cap meets no face. Asked only when the boolean failed or nudged the box: a part made
|
||||
// without, its faces' names included, stays as it is.
|
||||
let nudged = report.events.iter().any(|e| e.strategy == BooleanStrategy::ToolNudge);
|
||||
if (part.is_err() || nudged)
|
||||
&& let Some(moved) = off_plane(body, plane, positive)
|
||||
{
|
||||
let (exact, moved_report) = capture_boolean_diagnostics(|| boolean(&moved, &tool, BoolOp::Intersect));
|
||||
if let Ok(exact) = exact {
|
||||
merge(moved_report);
|
||||
// The part's history over [body, box], through the moved body (its faces are the body's).
|
||||
let hb = recorded::placed(recorded::through(&moved, 0, recorded::kept(body, 0, 1).as_ref()), 0, 2);
|
||||
let ht = recorded::placed(recorded::kept(&tool, 0, 1), 1, 2);
|
||||
let counts = record::counts(&[body, &tool]);
|
||||
let exact = exact.map(|r| {
|
||||
let h = recorded::of_pair(&r, &hb, &ht);
|
||||
r.with_checked_history(h, &counts)
|
||||
});
|
||||
return Ok((exact, tool));
|
||||
}
|
||||
}
|
||||
merge(report);
|
||||
Ok((part?, tool))
|
||||
}
|
||||
|
||||
/// `body` with its planar faces lying in `plane` moved off it, a thousandth of the body's size,
|
||||
/// to the side a split's part on the `positive` side leaves out. `None` when no face lies there
|
||||
/// or moving them doesn't give a sound body.
|
||||
pub(crate) fn off_plane(body: &Body, plane: &solidcraft_geom::Plane, positive: bool) -> Option<Body> {
|
||||
let m = plane.normal().normalized()?;
|
||||
let keep = if positive { m } else { -m };
|
||||
let size = body.size();
|
||||
let tol = (size * 1e-7).max(1e-9);
|
||||
// Lying in the plane means in it, not within the boolean's tolerance of it: a face that close
|
||||
// but off it bounds a real sliver, which moving the face would take or add.
|
||||
let exact = (size * 1e-9).max(1e-12);
|
||||
let delta = size * 1e-3;
|
||||
let mut out: Option<Body> = None;
|
||||
for n in [keep, -keep] {
|
||||
let d = n.dot(plane.origin);
|
||||
// The faces `push_faces` takes for this plane (by their surface), which must all lie in
|
||||
// it: one only nearly parallel, its origin in the plane, would be moved where it isn't.
|
||||
let taken: Vec<&mt::Face> = body
|
||||
.solid
|
||||
.face_iter()
|
||||
.filter(|f| match f.oriented_surface() {
|
||||
mt::Surface::Plane(p) => {
|
||||
let q = p.normal();
|
||||
Vec3::new(q.x, q.y, q.z).normalized().is_some_and(|q| q.dot(n) > 1.0 - 1e-9)
|
||||
&& (n.dot(from_p3(p.origin())) - d).abs() < tol * 10.0
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
.collect();
|
||||
if taken.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// Its vertices and points along its edges (a disc has one vertex).
|
||||
let lies = |f: &mt::Face| {
|
||||
use mt::{BoundedCurve, ParametricCurve};
|
||||
let on = |p: mt::Point3| (n.dot(from_p3(p)) - d).abs() < exact;
|
||||
f.vertex_iter().all(|v| on(v.point()))
|
||||
&& f.edge_iter().all(|e| {
|
||||
let c = e.curve();
|
||||
let (t0, t1) = c.range_tuple();
|
||||
[0.25, 0.5, 0.75].iter().all(|k| on(c.subs(t0 + (t1 - t0) * k)))
|
||||
})
|
||||
};
|
||||
if !taken.iter().all(|f| lies(f)) {
|
||||
return None;
|
||||
}
|
||||
// Along -keep: inwards for a face facing the kept side, outwards for one facing away.
|
||||
let shift = if n.dot(keep) > 0.0 { -delta } else { delta };
|
||||
let cur = out.as_ref().unwrap_or(body);
|
||||
out = Some(crate::coplanar::push_faces(cur, n, d, shift).ok().filter(|b| b.validity().is_empty())?);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// A box covering everything of `body` on one side of a plane, its start cap in the plane.
|
||||
|
||||
Reference in new issue
Block a user