Thanks @DuBearte.
This commit is contained in:
1 parent
132b63d24d
commit
e70bd52409
6 files changed
+137
-8
No files matched your search
@@ -190,6 +190,19 @@ pub(crate) fn frame_ok(plane: &Plane) -> bool {
|
||||
|
||||
/// Extrude each region from `start` to `end` along the plane normal (offsets in mm, `end > start`).
|
||||
pub fn extrude(plane: &Plane, regions: &[Region2], start: f64, end: f64) -> Result<Vec<Body>> {
|
||||
extrude_with(plane, regions, start, end, false)
|
||||
}
|
||||
|
||||
/// [`extrude`] with each run of profile lines that continue one another in a straight line (a side
|
||||
/// drawn in two pieces, a mirrored half meeting its mirror image) swept into one side face over
|
||||
/// all its pieces, rather than coplanar faces with an edge between them (#1788). The side's
|
||||
/// history lists every piece under it. For the Extrude feature; other builders (sheet metal's
|
||||
/// flat pattern, tools) keep a face per segment.
|
||||
pub fn extrude_whole_sides(plane: &Plane, regions: &[Region2], start: f64, end: f64) -> Result<Vec<Body>> {
|
||||
extrude_with(plane, regions, start, end, true)
|
||||
}
|
||||
|
||||
fn extrude_with(plane: &Plane, regions: &[Region2], start: f64, end: f64, whole_sides: bool) -> Result<Vec<Body>> {
|
||||
if !frame_ok(plane) {
|
||||
return Err(KernelError::Invalid("extrude frame is not finite and orthonormal".into()));
|
||||
}
|
||||
@@ -206,7 +219,19 @@ pub fn extrude(plane: &Plane, regions: &[Region2], start: f64, end: f64) -> Resu
|
||||
for r in regions {
|
||||
region_ok(r)?;
|
||||
let base = plane.offset(lo);
|
||||
let profile = region_wires(&base, &oriented(r)?)?;
|
||||
let pieces = oriented(r)?;
|
||||
let numbered = if whole_sides { pieces.map_loops(|l| l.merged_lines(continues_straight)) } else { pieces.clone() };
|
||||
// A joined side's source is its first piece, a segment of the profile as drawn (the side
|
||||
// lies along it): the sketch curve it is certified and named by, as when it stood alone.
|
||||
let first_piece = |side: &ProfileEdge| {
|
||||
pieces
|
||||
.loops()
|
||||
.flat_map(|l| l.lp.segs.iter())
|
||||
.copied()
|
||||
.find(|piece| side.numbers.len() > 1 && piece.start() == side.segment.start())
|
||||
.unwrap_or(side.segment)
|
||||
};
|
||||
let profile = region_wires(&base, &numbered)?;
|
||||
let wires = wires_of(&profile);
|
||||
let f = guard("extrude profile", || builder::try_attach_plane(&wires).map_err(|e| KernelError::Invalid(format!("profile: {e}"))))?;
|
||||
let solid: Solid = guard("extrude", || Ok(builder::tsweep(&f, v3(n * (hi - lo)))))?;
|
||||
@@ -263,7 +288,7 @@ pub fn extrude(plane: &Plane, regions: &[Region2], start: f64, end: f64) -> Resu
|
||||
_ => None,
|
||||
};
|
||||
(
|
||||
Some(crate::FaceSource::ExtrudeSide { segment: side.segment, plane: basis, coalesced: false }),
|
||||
Some(crate::FaceSource::ExtrudeSide { segment: first_piece(side), plane: basis, coalesced: false }),
|
||||
Some(record::profile(&side.numbers)),
|
||||
)
|
||||
} else if Some(*id) == end {
|
||||
@@ -285,6 +310,14 @@ pub fn extrude(plane: &Plane, regions: &[Region2], start: f64, end: f64) -> Resu
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// `t` carries on from `s` in the same straight line: the turn at their meeting point is within
|
||||
/// rounding of none, measured against both lengths.
|
||||
fn continues_straight(s: &Seg2, t: &Seg2) -> bool {
|
||||
let (u, v) = (s.end() - s.start(), t.end() - t.start());
|
||||
let (lu, lv) = (u.len(), v.len());
|
||||
lu > 0.0 && lv > 0.0 && s.end().dist(t.start()) <= 1e-9 * (lu + lv) && u.dot(v) > 0.0 && u.cross(v).abs() <= 1e-9 * lu * lv
|
||||
}
|
||||
|
||||
/// Choose the per-profile axis sign whose positive rotation follows the sketch normal.
|
||||
pub fn canonical_revolve_axis(plane: &Plane, region: &Region2, axis_origin: Vec2, axis_dir: Vec2) -> Result<Vec3> {
|
||||
if !axis_origin.is_finite() || !axis_dir.is_finite() || !plane.origin.is_finite() || !plane.x.is_finite() || !plane.y.is_finite() {
|
||||
|
||||
@@ -244,3 +244,36 @@ fn face_sources_follow_kept_split_and_agreeing_merged_faces() {
|
||||
let s = h.face_sources(&[&a]);
|
||||
assert_eq!(s, vec![src(0), src(1), None, src(2), None]);
|
||||
}
|
||||
|
||||
/// A side drawn in two collinear pieces (here the bottom edge, split at x = 10) sweeps into one
|
||||
/// side face over both pieces, not two coplanar faces with an edge between them (#1788).
|
||||
#[test]
|
||||
fn collinear_profile_lines_extrude_into_one_side_face() {
|
||||
use solidcraft_geom::{Loop2, Plane, Region2, Vec2};
|
||||
let pts = [(0.0, 0.0), (10.0, 0.0), (20.0, 0.0), (20.0, 10.0), (0.0, 10.0)].map(|(x, y)| Vec2::new(x, y));
|
||||
let region = Region2 { outer: Loop2::polygon(&pts), holes: vec![] };
|
||||
// Plain `extrude` keeps a face per segment (sheet metal's flat pattern relies on it).
|
||||
assert_eq!(crate::extrude(&Plane::XY, std::slice::from_ref(®ion), 0.0, 5.0).unwrap()[0].face_count(), 7);
|
||||
let body = crate::extrude_whole_sides(&Plane::XY, &[region], 0.0, 5.0).unwrap().pop().unwrap();
|
||||
assert_eq!(body.face_count(), 6, "a box has six faces");
|
||||
assert!((crate::measure(&body).unwrap().volume - 1000.0).abs() < 1e-9);
|
||||
let h = body.history().unwrap();
|
||||
h.check(&body, &[]).unwrap();
|
||||
let mut sides: Vec<Vec<FaceOrigin>> =
|
||||
h.faces.iter().map(|f| f.origins.clone()).filter(|o| matches!(o.first(), Some(FaceOrigin::Profile { .. }))).collect();
|
||||
sides.sort();
|
||||
assert_eq!(
|
||||
sides,
|
||||
vec![
|
||||
vec![FaceOrigin::Profile { segment: 0 }, FaceOrigin::Profile { segment: 1 }],
|
||||
vec![FaceOrigin::Profile { segment: 2 }],
|
||||
vec![FaceOrigin::Profile { segment: 3 }],
|
||||
vec![FaceOrigin::Profile { segment: 4 }],
|
||||
],
|
||||
"the bottom side is generated by both its pieces"
|
||||
);
|
||||
// A corner is not a straight run: an L-shaped step keeps its faces.
|
||||
let step = [(0.0, 0.0), (10.0, 0.0), (10.0, 5.0), (20.0, 5.0), (20.0, 10.0), (0.0, 10.0)].map(|(x, y)| Vec2::new(x, y));
|
||||
let body = crate::extrude_whole_sides(&Plane::XY, &[Region2 { outer: Loop2::polygon(&step), holes: vec![] }], 0.0, 5.0).unwrap().pop().unwrap();
|
||||
assert_eq!(body.face_count(), 8);
|
||||
}
|
||||
@@ -151,8 +151,8 @@ pub use boundary::{Boundary, boundary_patch};
|
||||
pub use boundary_network::{BoundaryCurve, CurveSource, MAX_CHAIN_CURVES, MAX_EDGE_PICKS, boundary_join, boundary_surface};
|
||||
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, path_polyline, revolve,
|
||||
revolve_whole, sphere, sweep, sweep_with, torus,
|
||||
PathSeg, box_solid, canonical_revolve_axis, cylinder, extrude, extrude_tapered, extrude_tapered_between, extrude_whole_sides, loft,
|
||||
path_polyline, revolve, revolve_whole, sphere, sweep, sweep_with, torus,
|
||||
};
|
||||
pub use clearance::{Clearance, Occupancy};
|
||||
pub use coaxial_overlap::{CoaxialOverlap, coaxial_overlap};
|
||||
|
||||
Reference in new issue
Block a user