mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-05 11:35:55 +00:00
Sheet machining: a trimmed To Object tool is measured as the prism its record proves
A To Object extrude to a curved face runs its tool through the target and cuts the target out of it. The SMLib boolean carries no extrusion face sources (they stood for the untrimmed profile), so sheet_tool_span, which read the tool's depth and footprint only off its extrusion caps, gave up and the intersect was refused as unrepresentable. The tool's current geometry says what it is: the kernel's prism certificate (prism_measure, the one component copies are compared by) reads off the exact record whether a body is a uniform prism along a direction, its two cap levels and its section of lines and arcs. It is now public as certified_prism_section, and sheet_tool_span uses it when the caps don't certify the tool: the levels are the depth, the section the footprint the To Object cut is bounded by. A tool trimmed to something that is not a prism (a curved roof over it) is still refused. partial_depth_intersect_curved_to_object_preserves_current_trimmed_cap_footprint passes: the tool is the square 20 ... 30 less the pin's circle (radius 2), z 0 ... 1, 100 - 4π, as the test's own witness extrude measures it. A kernel test reads that section off the trimmed lump. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -843,9 +843,21 @@ pub(super) fn sheet_eval(
|
||||
}
|
||||
}
|
||||
|
||||
/// Current unmodified extrusion caps, including placement, certify the tool's depth.
|
||||
/// Tessellation can drift in thickness at distant coordinates and cannot certify a slab.
|
||||
/// The tool's depth along the sketch normal (from `source`), the roundoff of that depth and the
|
||||
/// tool's current footprint where it differs from the profile: from its current unmodified
|
||||
/// extrusion caps, including placement, else from its exact record proving it a uniform prism
|
||||
/// along the normal (a To Object tool trimmed by the body it runs to, whose boolean leaves no
|
||||
/// extrusion faces: its section is its footprint). Tessellation can drift in thickness at
|
||||
/// distant coordinates and cannot certify a slab.
|
||||
fn sheet_tool_span(body: &Body, source: &Plane) -> Option<(f64, f64, f64, Option<(Plane, Region2)>)> {
|
||||
extrusion_tool_span(body, source).or_else(|| {
|
||||
let prism = kernel::certified_prism_section(body, source).ok().flatten()?;
|
||||
(prism.tolerance <= 1e-7).then_some((prism.lo, prism.hi, prism.tolerance, Some((*source, prism.region))))
|
||||
})
|
||||
}
|
||||
|
||||
/// [`sheet_tool_span`] from the tool's extrusion caps.
|
||||
fn extrusion_tool_span(body: &Body, source: &Plane) -> Option<(f64, f64, f64, Option<(Plane, Region2)>)> {
|
||||
use kernel::FaceSource;
|
||||
#[derive(PartialEq)]
|
||||
enum Signature<'a> {
|
||||
|
||||
@@ -207,7 +207,7 @@ pub use point_path::{
|
||||
pub use polybool::faceted_boolean;
|
||||
pub use polyhedron::{HalfSpace, convex_polyhedron, draft, offset_faces, shell};
|
||||
pub use prism::{PLANAR_FIT_TOL, extrude_planar_regions, planar_regions_boolean};
|
||||
pub use prism_measure::certified_prism_equivalent;
|
||||
pub use prism_measure::{PrismSection, certified_prism_equivalent, certified_prism_section};
|
||||
pub use repair::{Hole, RebuiltFace, RefusedFace, RepairOptions, RepairReport, SewnGap, repair};
|
||||
pub use sew::{EdgeSpec, FaceSpec, SurfSpec, sew, sew_touching};
|
||||
pub use splitface::{SplitTool, split_body, split_faces, split_faces_with_map};
|
||||
|
||||
@@ -637,6 +637,54 @@ pub fn certified_prism_equivalent(a: &crate::Body, b: &crate::Body) -> crate::Re
|
||||
})
|
||||
}
|
||||
|
||||
/// A uniform prism along a plane's normal, read off a body's record: its levels above the plane
|
||||
/// and its section in the plane's coordinates.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PrismSection {
|
||||
/// The levels of its bottom and its top cap above the plane (`lo < hi`).
|
||||
pub lo: f64,
|
||||
pub hi: f64,
|
||||
/// Its section, exact: lines and arcs, the outer loop counter-clockwise, the holes clockwise.
|
||||
pub region: solidcraft_geom::Region2,
|
||||
/// How far the levels may be off the body's caps (the caps' vertices lie within it of them).
|
||||
pub tolerance: f64,
|
||||
}
|
||||
|
||||
/// The uniform prism `body` is along `plane`'s normal, when its record proves it one: two caps
|
||||
/// square to the normal, every other face a wall running straight between them along the
|
||||
/// normal and standing on both caps' edges, the two caps one section (see [`certified_prism_equivalent`]
|
||||
/// for the profiles it proves: a convex straight outline or a circle, with separate round
|
||||
/// holes). The body's current geometry, not how it was made: a tool trimmed by a boolean that
|
||||
/// left it a prism is one. `None` when that is not proved.
|
||||
pub fn certified_prism_section(body: &crate::Body, plane: &Plane) -> crate::Result<Option<PrismSection>> {
|
||||
crate::guard("certified prism section", || {
|
||||
if body.is_mesh() || !body.size().is_finite() {
|
||||
return Ok(None);
|
||||
}
|
||||
let record = crate::native::brep_record(body)?;
|
||||
let tol = body.size().max(1.) * 1e-10;
|
||||
let Some(c) = certificate(&record, body.size(), plane, tol) else { return Ok(None) };
|
||||
let mut outer = None;
|
||||
let mut holes = Vec::new();
|
||||
for lp in c.loops {
|
||||
let positive = lp.area.lo > 0.;
|
||||
let lp = solidcraft_geom::Loop2 { segs: lp.segments.into_vec() };
|
||||
if positive {
|
||||
if outer.replace(lp).is_some() {
|
||||
return Ok(None);
|
||||
}
|
||||
} else {
|
||||
holes.push(lp);
|
||||
}
|
||||
}
|
||||
let Some(outer) = outer else { return Ok(None) };
|
||||
// The caps' vertices are within twice the tolerance of their levels (`certificate`), and
|
||||
// the levels are worked out from the plane's origin.
|
||||
let tolerance = 2. * tol + 128. * f64::EPSILON * (plane.origin.len() + c.lo.abs() + c.hi.abs()).max(1.);
|
||||
Ok(Some(PrismSection { lo: c.lo, hi: c.hi, region: solidcraft_geom::Region2 { outer, holes }, tolerance }))
|
||||
})
|
||||
}
|
||||
|
||||
/// [`certified_prism_equivalent`] of the records `a` and `b` of bodies of sizes `size_a` and
|
||||
/// `size_b`.
|
||||
fn equivalent(a: &Brep, size_a: f64, b: &Brep, size_b: f64) -> Option<bool> {
|
||||
@@ -823,6 +871,31 @@ mod tests {
|
||||
r.faces.iter().position(|f| plane_of(&f.surface).is_none()).unwrap()
|
||||
}
|
||||
|
||||
/// A 10 x 10 tool from z = 0 cut by a pin of radius 2 standing through it, joined to a plate
|
||||
/// at z 1 ... 2 (a To Object tool trimmed by its curved target): the lump under the plate is
|
||||
/// a prism from 0 to 1 whose section is the square less the pin's circle, 100 - 4π; the lump
|
||||
/// above it steps (the pin stands up through it) and is no prism.
|
||||
#[test]
|
||||
fn a_trimmed_tool_reads_as_the_prism_of_its_current_section() {
|
||||
use solidcraft_geom::{Loop2, Region2};
|
||||
let pin = crate::cylinder(Vec3::new(25., 25., 0.), Vec3::Z, 2., 20.).unwrap();
|
||||
let stop = crate::box_solid(Vec3::new(19., 19., 1.), Vec3::new(31., 31., 2.)).unwrap();
|
||||
let roof = crate::boolean(&pin, &stop, crate::BoolOp::Union).unwrap().unwrap();
|
||||
let square =
|
||||
Region2 { outer: Loop2::polygon(&[Vec2::new(20., 20.), Vec2::new(30., 20.), Vec2::new(30., 30.), Vec2::new(20., 30.)]), holes: vec![] };
|
||||
let tool = crate::extrude(&Plane::XY, &[square], 0., 25.).unwrap().remove(0);
|
||||
let lumps = crate::boolean(&tool, &roof, crate::BoolOp::Cut).unwrap().unwrap().lumps().unwrap();
|
||||
assert_eq!(lumps.len(), 2);
|
||||
let sections: Vec<Option<PrismSection>> = lumps.iter().map(|l| certified_prism_section(l, &Plane::XY).unwrap()).collect();
|
||||
let lower = sections.iter().flatten().collect::<Vec<_>>();
|
||||
assert_eq!(lower.len(), 1, "only the lump under the plate is a prism");
|
||||
let lower = lower[0];
|
||||
assert!(lower.lo.abs() <= lower.tolerance && (lower.hi - 1.).abs() <= lower.tolerance, "{} … {}", lower.lo, lower.hi);
|
||||
assert_eq!(lower.region.holes.len(), 1);
|
||||
assert!(lower.region.outer.signed_area() > 0. && lower.region.holes[0].signed_area() < 0.);
|
||||
assert!((lower.region.area() - (100. - 4. * std::f64::consts::PI)).abs() < 1e-8, "{}", lower.region.area());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn actual_drilled_prism_and_step_reopen_are_certified_without_history() {
|
||||
let source = drilled(6., 0.);
|
||||
|
||||
Reference in New Issue
Block a user