Extrude: a taper composes with a start offset (#2312) (#2313)

Thanks @acbrazzl.
This commit is contained in:
Brazz authored and GitHub committed 2026-10-18 06:18:43 -04:00
1 parent a4c4b411e5
commit 07708a4e2a
3 files changed
+181 -9

No files matched your search

+17 -8
View File
@@ -3195,21 +3195,30 @@ fn feature_tools_with_constructor(
};
if let Some(tp) = &extent.taper {
let taper = val(vals, tp, Kind::Angle)?;
if extent.distance2.is_some() || extent.start_offset.is_some() || extent.direction == crate::Direction::Symmetric {
return Err(DocError::Invalid("not supported yet: taper with two-sided, symmetric or offset extents".into()));
if extent.distance2.is_some() || extent.direction == crate::Direction::Symmetric {
return Err(DocError::Invalid("not supported yet: taper with two-sided or symmetric extents".into()));
}
let sign = if extent.to.is_none() && extent.direction == crate::Direction::Negative { -1.0 } else { 1.0 };
let (len, sign) = if d < 0.0 { (-d, -sign) } else { (d, sign) };
let start_plane = ss.plane.offset(from);
// Where the extrude starts: the From Object's level and a start offset along the
// normal from it. The offset moves the profile that far and the sides are drafted
// from there, so the profile stays the section at the start whatever the offset
// (`d` is measured from the start already, To Object included).
let base = from
+ match &extent.start_offset {
Some(e) => val(vals, e, Kind::Length)?,
None => 0.0,
};
let start_plane = ss.plane.offset(base);
let caps = [
Some(crate::lineage::extrude_cap_role(from)),
Some(crate::lineage::extrude_cap_role(if sign < 0.0 { from - len } else { from + len })),
Some(crate::lineage::extrude_cap_role(base)),
Some(crate::lineage::extrude_cap_role(if sign < 0.0 { base - len } else { base + len })),
];
// How far a tool grows past its profile at either end, at most.
let mut growth: f64 = 0.0;
// A face of the targets in the profile's plane (the same for every profile).
let plain_targets: Vec<String> = crate::plain(targets);
let on_face = matches!(operation, Operation::Join | Operation::Cut) && face_level_at(st, &ss.plane, from, &plain_targets);
let on_face = matches!(operation, Operation::Join | Operation::Cut) && face_level_at(st, &ss.plane, base, &plain_targets);
let surroundings = Surroundings::of(st, *operation, &plain_targets);
let tools: Vec<Traced> = regions
.iter()
@@ -3233,9 +3242,9 @@ fn feature_tools_with_constructor(
growth = growth.max(len * taper.tan().abs());
return Ok(roles.tool(exact, None));
}
let (lo, hi) = if sign < 0.0 { (from - len, from) } else { (from, from + len) };
let (lo, hi) = if sign < 0.0 { (base - len, base) } else { (base, base + len) };
let (l2, h2) = surroundings.as_ref().map_or((lo, hi), |sur| extend_for_coplanar(sur, &ss.plane, r, lo, hi));
let (mut a, b) = if sign < 0.0 { (from - h2, from - l2) } else { (l2 - from, h2 - from) };
let (mut a, b) = if sign < 0.0 { (base - h2, base - l2) } else { (l2 - base, h2 - base) };
// A taper that narrows the tool away from the face widens it as it runs on into it, past
// the profile (where the profile is the whole face, out through the body's sides, #1534):
// then it starts on the face instead.