diff --git a/crates/kernel/src/offset.rs b/crates/kernel/src/offset.rs
index 8e5113a5..ae15a750 100644
--- a/crates/kernel/src/offset.rs
+++ b/crates/kernel/src/offset.rs
@@ -1120,9 +1120,10 @@ pub(crate) fn offset_body(b: &Body, shift: impl Fn(usize, Vec3) -> f64) -> Resul
/// Draft walls parallel to `pull` (planes, and cylinders about axes along `pull`) by `angle`
/// about their line on the neutral plane: at height h along `pull` a wall moves along its
-/// outward normal by −h·tan(angle), so planes tilt and cylinders become cones. The other
-/// faces must be caps square to `pull`; the topology is kept, and the result's history is over
-/// `[b]` (each face and edge the one it was moved from).
+/// outward normal by −h·tan(angle), so planes tilt and cylinders become cones. The faces that
+/// share a corner with a drafted wall must be caps square to `pull` or walls; the rest of the
+/// body (a rounded edge elsewhere, say) is left as it is. The topology is kept, and the
+/// result's history is over `[b]` (each face and edge the one it was moved from).
pub(crate) fn draft_walls(b: &Body, chosen: &[usize], neutral: &solidcraft_geom::Plane, pull: Vec3, angle: f64) -> Result
{
let faces: Vec = b.solid.face_iter().cloned().collect();
let size = b.size();
@@ -1134,37 +1135,6 @@ pub(crate) fn draft_walls(b: &Body, chosen: &[usize], neutral: &solidcraft_geom:
}
let height = |p: Vec3| (p - neutral.origin).dot(nn) / den;
let tan = angle.tan();
- let mut old = Vec::with_capacity(faces.len());
- for (i, f) in faces.iter().enumerate() {
- let sf = surf_of(f, tol * 100.0).ok_or_else(|| fail("draft: only bodies with planar and cylindrical faces"))?;
- let wall = match sf {
- Surf::Plane { n, .. } => n.dot(pull).abs() < 1e-9,
- Surf::Cylinder { a, .. } => a.dot(pull).abs() > 1.0 - 1e-9,
- Surf::Sphere { .. } | Surf::Cone { .. } | Surf::Torus { .. } => false,
- };
- let cap = matches!(sf, Surf::Plane { n, .. } if n.dot(pull).abs() > 1.0 - 1e-9);
- if chosen.contains(&i) && !wall {
- return Err(fail("draft: a chosen face isn't parallel to the pull direction"));
- }
- if !chosen.contains(&i) && !cap && !wall {
- return Err(fail("draft: faces next to the drafted walls must be square to the pull direction"));
- }
- old.push(sf);
- }
- // Shifts and moved surfaces at a height.
- let at = |ids: &[usize], h: f64| -> Option<(Vec, Vec, Vec)> {
- let mut o = Vec::new();
- let mut s = Vec::new();
- let mut n = Vec::new();
- for i in ids {
- let sf = *old.get(*i)?;
- let sh = if chosen.contains(i) { -h * tan } else { 0.0 };
- o.push(sf);
- s.push(sh);
- n.push(sf.moved(sh)?);
- }
- Some((o, s, n))
- };
let mut vfaces: HashMap> = HashMap::new();
let mut efaces: HashMap> = HashMap::new();
for (i, f) in faces.iter().enumerate() {
@@ -1181,9 +1151,53 @@ pub(crate) fn draft_walls(b: &Body, chosen: &[usize], neutral: &solidcraft_geom:
}
}
}
+ // A corner moves when one of its faces is drafted; the faces round a moving corner must be
+ // walls or caps for the corner to follow them. Faces away from the drafted walls may be
+ // anything: they keep their surface and their edges.
+ let moves = |vid: &mt::VertexID| vfaces.get(vid).is_some_and(|fs| fs.iter().any(|i| chosen.contains(i)));
+ let touched: Vec = faces.iter().enumerate().map(|(i, f)| chosen.contains(&i) || f.vertex_iter().any(|v| moves(&v.id()))).collect();
+ let mut old: Vec