Thanks @SychPL.
This commit is contained in:
1 parent
8907da5e86
commit
920ea42fe1
6 files changed
+118
-34
No files matched your search
@@ -305,6 +305,18 @@ pub fn canonical_revolve_axis(plane: &Plane, region: &Region2, axis_origin: Vec2
|
||||
/// Revolve each region about the sketch axis by `angle` radians.
|
||||
/// This legacy API preserves its historical sweep sign behavior.
|
||||
pub fn revolve(plane: &Plane, regions: &[Region2], axis_origin: Vec2, axis_dir: Vec2, angle: f64) -> Result<Vec<Body>> {
|
||||
revolved(plane, regions, axis_origin, axis_dir, angle, false)
|
||||
}
|
||||
|
||||
/// [`revolve`] for bodies that are final (new bodies, not a boolean's tools): a partial turn's
|
||||
/// planar faces come out whole too, as a full turn's always do (#726). A tool is left as swept:
|
||||
/// healed, its planar faces meet a target exactly where they are tangent to it, and the
|
||||
/// boolean leaves pieces there that touch. The boolean heals its own result anyway.
|
||||
pub fn revolve_whole(plane: &Plane, regions: &[Region2], axis_origin: Vec2, axis_dir: Vec2, angle: f64) -> Result<Vec<Body>> {
|
||||
revolved(plane, regions, axis_origin, axis_dir, angle, true)
|
||||
}
|
||||
|
||||
fn revolved(plane: &Plane, regions: &[Region2], axis_origin: Vec2, axis_dir: Vec2, angle: f64, whole: bool) -> Result<Vec<Body>> {
|
||||
let angle = finite(angle, "angle")?;
|
||||
if angle.abs() < 1e-6 {
|
||||
return Err(KernelError::Invalid("revolve angle is zero".into()));
|
||||
@@ -447,11 +459,46 @@ pub fn revolve(plane: &Plane, regions: &[Region2], axis_origin: Vec2, axis_dir:
|
||||
let closed = angle.abs() >= std::f64::consts::TAU;
|
||||
let history = guard("revolve history", || Ok(record::swept_face(&solid, &f, &seeds(&profile), closed)))?;
|
||||
let solid = if shift.len() > 0.0 { builder::translated(&solid, v3(shift)) } else { solid };
|
||||
let (solid, history) = if closed || whole { turned_whole(solid, history) } else { (solid, history) };
|
||||
out.push(record::attach(Body::new(solid)?, &[], history, Default::default())?);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// A revolved solid with its planar faces whole (#726): the sweep turns each profile edge into
|
||||
/// two or three patches, the planar ones on revolved surfaces, which stayed apart until a later
|
||||
/// feature healed the body, so a pick on one took part of the face. Healed as booleans heal
|
||||
/// their results, each face is still generated, from the profile edges of the patches merged
|
||||
/// into it. The solid as it was when healing fails or can't be followed.
|
||||
fn turned_whole(solid: Solid, faces: Option<Vec<FaceProv>>) -> (Solid, Option<Vec<FaceProv>>) {
|
||||
let Some(given) = faces.as_ref() else { return (solid, faces) };
|
||||
let Ok(size) = Body::new(solid.clone()).map(|b| b.size()) else { return (solid, faces) };
|
||||
let healed = guard("heal revolve", || {
|
||||
let (healed, map) = crate::heal::heal_traced(solid.clone(), size, &[]);
|
||||
let failed = || KernelError::Failed("heal revolve".into());
|
||||
if !crate::boolean_record::edges_evaluate(&healed) {
|
||||
return Err(failed());
|
||||
}
|
||||
let carried = map
|
||||
.ok_or_else(failed)?
|
||||
.faces
|
||||
.iter()
|
||||
.map(|under| {
|
||||
let mut origins: Vec<_> = under.iter().map(|i| given.get(*i).map(|f| f.origins.clone())).collect::<Option<Vec<_>>>()?.concat();
|
||||
origins.sort();
|
||||
origins.dedup();
|
||||
Some(FaceProv { origins, change: crate::history::Change::Generated })
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()
|
||||
.ok_or_else(failed)?;
|
||||
Ok((healed, carried))
|
||||
});
|
||||
match healed {
|
||||
Ok((healed, carried)) => (healed, Some(carried)),
|
||||
Err(_) => (solid, faces),
|
||||
}
|
||||
}
|
||||
|
||||
/// Axis-aligned box between two corners.
|
||||
pub fn box_solid(a: Vec3, b: Vec3) -> Result<Body> {
|
||||
let (lo, hi) = (a.min(b), a.max(b));
|
||||
|
||||
@@ -134,8 +134,8 @@ pub use boolean_diagnostics::{BooleanDiagnostics, BooleanFallback, BooleanStrate
|
||||
pub use boundary::{Boundary, boundary_patch};
|
||||
pub use bridge::{Continuity, Frame, blend_between, bridge, chain, chain_closes, frames, frames_across, sample_grid};
|
||||
pub use build::{
|
||||
PathSeg, box_solid, canonical_revolve_axis, cylinder, extrude, extrude_tapered, extrude_tapered_between, loft, revolve, sphere, sweep,
|
||||
sweep_with, torus,
|
||||
PathSeg, box_solid, canonical_revolve_axis, cylinder, extrude, extrude_tapered, extrude_tapered_between, loft, revolve, revolve_whole, sphere,
|
||||
sweep, sweep_with, torus,
|
||||
};
|
||||
pub use continuity::{
|
||||
ContinuityReport, ContinuityTolerances, EdgePick, MAX_SAMPLES, MIN_SAMPLES, SeamReport, SeamVerdict, SeamWorst, pick_edges, seam_diagnostics,
|
||||
|
||||
@@ -208,15 +208,23 @@ fn expected_revolution(b: &Body, r: &Region2) -> Vec<Vec<FaceOrigin>> {
|
||||
#[test]
|
||||
fn revolve_partial_and_full() {
|
||||
let r = Region2 { outer: rect(10.0, 0.0, 20.0, 5.0), holes: vec![rect(12.0, 1.0, 14.0, 2.0).reversed()] };
|
||||
// Under half a turn truck sweeps in two parts, beyond it in three; a full turn has no caps.
|
||||
for (angle, parts, caps) in [(FRAC_PI_2, 2, 2), (1.5 * PI, 3, 2), (TAU, 3, 0)] {
|
||||
let b = crate::revolve(&SIDE, std::slice::from_ref(&r), Vec2::ZERO, Vec2::Y, angle).unwrap().pop().unwrap();
|
||||
let got = origins(&b);
|
||||
assert_eq!(got, expected_revolution(&b, &r), "angle {angle}");
|
||||
for k in 0..8 {
|
||||
assert_eq!(got.iter().filter(|o| **o == [P(k)]).count(), parts, "segment {k} at angle {angle}");
|
||||
// Under half a turn truck sweeps in two parts, beyond it in three; a full turn has no caps,
|
||||
// and its planar faces (from the segments square to the axis) are whole (#726), as a partial
|
||||
// turn's are when it is final (`revolve_whole`).
|
||||
let segs: Vec<Seg2> = r.outer.segs.iter().chain(r.holes.iter().flat_map(|h| &h.segs)).cloned().collect();
|
||||
for whole in [false, true] {
|
||||
for (angle, parts, caps) in [(FRAC_PI_2, 2, 2), (1.5 * PI, 3, 2), (TAU, 3, 0)] {
|
||||
let revolve = if whole { crate::revolve_whole } else { crate::revolve };
|
||||
let b = revolve(&SIDE, std::slice::from_ref(&r), Vec2::ZERO, Vec2::Y, angle).unwrap().pop().unwrap();
|
||||
let got = origins(&b);
|
||||
assert_eq!(got, expected_revolution(&b, &r), "angle {angle}, whole {whole}");
|
||||
for (k, s) in segs.iter().enumerate() {
|
||||
let square = matches!(s, Seg2::Line { a, b } if (a.y - b.y).abs() < 1e-12);
|
||||
let want = if (angle == TAU || whole) && square { 1 } else { parts };
|
||||
assert_eq!(got.iter().filter(|o| **o == [P(k)]).count(), want, "segment {k} at angle {angle}, whole {whole}");
|
||||
}
|
||||
assert_eq!(got.iter().filter(|o| **o == [START] || **o == [END]).count(), caps);
|
||||
}
|
||||
assert_eq!(got.iter().filter(|o| **o == [START] || **o == [END]).count(), caps);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user