Kernel: revolved faces turned away from the angle wrap (booleans with revolved bodies work), cuts that leave pieces make separate bodies, trivial booleans (clear of / inside) by sampling, body mirrors with join; recipes: pattern source names, thread, holes at sketch points, mirror of bodies

Oracle: 43/66.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 00:00:51 +00:00
1 parent cb01314d35
commit 57d01f8405
9 files changed
+377 -11

No files matched your search

+6
View File
@@ -215,6 +215,12 @@ pub enum FeatureKind {
Mirror {
features: Vec<String>,
plane: PlaneRef,
/// Mirror these bodies instead of replaying features.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
bodies: Vec<String>,
/// Join each mirrored body with its original.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
combine: bool,
},
/// A drilled hole (simple, counterbore or countersink) at a point, into the material.
Hole {
+49 -3
View File
@@ -458,6 +458,20 @@ fn apply_op(state: &mut ModelState, f: &Feature, tools: Vec<Body>, op: Operation
i += 1;
keep
});
// A cut can leave a body in pieces: each piece becomes its own body ("Bar", "Bar (1)").
let mut split: Vec<ModelBody> = Vec::new();
for mb in std::mem::take(&mut state.bodies) {
let pieces = mb.body.lumps().unwrap_or_else(|_| vec![mb.body.clone()]);
if pieces.len() < 2 {
split.push(mb);
continue;
}
for (k, p) in pieces.into_iter().enumerate() {
let name = if k == 0 { mb.name.clone() } else { format!("{} ({k})", mb.name) };
split.push(ModelBody::new(name, p, mb.feature));
}
}
state.bodies = split;
}
Operation::NewBody => {}
}
@@ -1157,7 +1171,13 @@ fn replay(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st: &mut
return Err(DocError::Invalid("no features selected".into()));
}
for name in features {
let src = doc.find_feature(name).ok_or_else(|| DocError::Unknown(format!("feature `{name}`")))?;
// A sketch and its extrude often share a name; the feature that makes geometry wins.
let src = doc
.features
.iter()
.find(|x| &x.name == name && !matches!(x.kind, FeatureKind::Sketch { .. } | FeatureKind::ConstructionPlane { .. }))
.or_else(|| doc.find_feature(name))
.ok_or_else(|| DocError::Unknown(format!("feature `{name}`")))?;
let tools = feature_tools(vals, src, st)?;
let (op, targets) = feature_op(src);
let mut copies = Vec::new();
@@ -1273,9 +1293,35 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
let mats = pattern_transforms(vals, pattern)?;
replay(doc, vals, f, st, features, &mats)
}
FeatureKind::Mirror { features, plane } => {
FeatureKind::Mirror { features, plane, bodies, combine } => {
let pl = doc.resolve_plane(vals, plane, 0)?;
replay(doc, vals, f, st, features, &[mirror_matrix(&pl)])
if bodies.is_empty() {
return replay(doc, vals, f, st, features, &[mirror_matrix(&pl)]);
}
let m = mirror_matrix(&pl);
for n in bodies {
let i = st.bodies.iter().position(|b| &b.name == n).ok_or_else(|| DocError::Unknown(format!("body `{n}`")))?;
let Some(mb) = st.bodies.get(i).cloned() else { continue };
let mirrored = kernel::transform_matrix(&mb.body, m)?;
if *combine {
let joined = kernel::boolean(&mb.body, &mirrored, BoolOp::Union)?
.ok_or_else(|| DocError::Invalid("the mirror join produced nothing".into()))?;
for (k, p) in joined.lumps()?.into_iter().enumerate() {
if k == 0 {
if let Some(slot) = st.bodies.get_mut(i) {
*slot = ModelBody::new(mb.name.clone(), p, mb.feature);
}
} else {
let name = unique_body_name(st, &mb.name);
st.bodies.push(ModelBody::new(name, p, f.id));
}
}
} else {
let name = unique_body_name(st, &mb.name);
st.bodies.push(ModelBody::new(name, mirrored, f.id));
}
}
Ok(())
}
FeatureKind::Fillet { edges, radius, body } | FeatureKind::Chamfer { edges, distance: radius, body } => {
let r = val(vals, radius, Kind::Length)?;