Taper any profile, sketch text included (#1014)
A tapered extrude now offsets spline and conic curves as well as lines and arcs, so sketch text and any other profile can take a taper, and the join of tapered letters onto a face finds its boolean result.
This commit is contained in:
1 parent
d114c58540
commit
4dbc0efff9
6 files changed
+619
-136
No files matched your search
+61
-3
@@ -1789,6 +1789,26 @@ fn extend_for_coplanar(st: &ModelState, plane: &Plane, r: &Region2, lo: f64, hi:
|
||||
(lo2, hi2)
|
||||
}
|
||||
|
||||
/// How high (mm) a tapered tool's sides stand straight where it starts on a face it can't run
|
||||
/// on into.
|
||||
const TAPER_FOOT: f64 = 0.01;
|
||||
|
||||
/// Whether a face of the targets (all bodies when none are named) lies in the plane at height
|
||||
/// `h` along `plane`'s normal.
|
||||
fn face_level_at(st: &ModelState, plane: &Plane, h: f64, targets: &[String]) -> bool {
|
||||
let n = plane.normal();
|
||||
st.bodies.iter().filter(|b| targets.is_empty() || targets.contains(&b.name)).any(|b| {
|
||||
let m = b.mesh();
|
||||
let tol = (b.body.size() * 1e-7).max(1e-9);
|
||||
m.triangles.iter().any(|t| {
|
||||
m.tri(t).is_some_and(|[a, b, c]| {
|
||||
(b - a).cross(c - a).normalized().is_some_and(|normal| normal.dot(n).abs() > 1.0 - 1e-9)
|
||||
&& ((a - plane.origin).dot(n) - h).abs() < tol
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// The body a face-based feature applies to: by name, or the one whose surface is nearest the
|
||||
/// first point.
|
||||
fn body_at(st: &ModelState, body: &Option<String>, points: &[Vec3]) -> Result<usize> {
|
||||
@@ -2582,13 +2602,51 @@ fn feature_tools_with_constructor(
|
||||
Some(crate::lineage::extrude_cap_role(from)),
|
||||
Some(crate::lineage::extrude_cap_role(if sign < 0.0 { from - len } else { from + len })),
|
||||
];
|
||||
return regions
|
||||
// How far a tool grows past its profile at either end, at most.
|
||||
let mut growth: f64 = 0.0;
|
||||
let tools: Vec<Traced> = regions
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let body = kernel::extrude_tapered(&start_plane, r, len, sign, taper)?;
|
||||
// An end on a face of the target runs on into it, as a prism's does: tapered
|
||||
// sides standing on a face are curved where they meet it (along text, say),
|
||||
// and the boolean finds no result there. The sides keep their slope, so
|
||||
// the profile stays the section at the start.
|
||||
let (lo, hi) = if sign < 0.0 { (from - len, from) } else { (from, from + len) };
|
||||
let (l2, h2) = extend_for_coplanar(st, &ss.plane, r, lo, hi, *operation, targets);
|
||||
let (a, b) = if sign < 0.0 { (from - h2, from - l2) } else { (l2 - from, h2 - from) };
|
||||
// A start that can't run on (the profile hangs over the face's edge):
|
||||
// the sides stand straight for a hair first, so the boolean can push the
|
||||
// face the tool stands on into the body.
|
||||
let foot = if a >= 0.0 && matches!(operation, Operation::Join | Operation::Cut) && face_level_at(st, &ss.plane, from, targets)
|
||||
{
|
||||
TAPER_FOOT.min((b - a) * 0.01)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let body = kernel::extrude_tapered_between(&start_plane, r, a, b, foot, sign, taper)?;
|
||||
growth = growth.max(a.abs().max(b.abs()) * taper.tan().abs());
|
||||
Ok(crate::lineage::ProfileRoles { sketch: Some(&ss), region: Some(r), caps: caps.clone() }.tool(body, None))
|
||||
})
|
||||
.collect();
|
||||
.collect::<Result<_>>()?;
|
||||
// Profiles apart by more than the tools grow (the letters of a text): the tools
|
||||
// are apart too, and go into the body as one, in one boolean rather than one per
|
||||
// profile, each on a body grown or cut by the ones before. (A join meeting several
|
||||
// bodies keeps joining them tool by tool; sheet metal keeps cutting tool by tool.)
|
||||
let sheet =
|
||||
if targets.is_empty() { !st.sheets.is_empty() } else { targets.iter().any(|t| st.sheets.iter().any(|sheet| &sheet.body == t)) };
|
||||
let one_target = if targets.is_empty() { st.bodies.len() == 1 } else { targets.len() == 1 };
|
||||
let at_once = match operation {
|
||||
Operation::Cut => !sheet,
|
||||
Operation::Join => one_target && !sheet,
|
||||
_ => false,
|
||||
};
|
||||
if at_once && tools.len() > 1 && regions_apart(®ions, APART_GAP + 2.0 * growth) {
|
||||
let bodies: Vec<Body> = tools.iter().map(|t| t.body.clone()).collect();
|
||||
if let Ok(Some(one)) = kernel::join_apart(&bodies) {
|
||||
return Ok(vec![Traced::step(one, &tools.iter().collect::<Vec<_>>())]);
|
||||
}
|
||||
}
|
||||
return Ok(tools);
|
||||
}
|
||||
let off = from
|
||||
+ match &extent.start_offset {
|
||||
|
||||
Reference in new issue
Block a user