Split Body by a face of a body: FusionSplitBodyCommand tool {body, point}

The Split feature takes an optional face tool (FaceAt, the face through a
point on a named body) and splits with kernel split_body, the tool's
surface extended; otherwise the plane as before. Engine test: a box split
by a cylinder's side into the core and the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-08 17:31:57 +00:00
1 parent 34be218639
commit feaaba156e
6 files changed
+129 -69

No files matched your search

+11 -1
View File
@@ -312,10 +312,13 @@ pub enum FeatureKind {
ConstructionPlane {
plane: PlaneRef,
},
/// Split a body with a plane into two bodies.
/// Split a body into the parts on either side of a plane, or of a face of a body (the face
/// through `point`, its surface extended) when `tool` is set.
Split {
body: String,
plane: PlaneRef,
#[serde(default, skip_serializing_if = "Option::is_none")]
tool: Option<FaceAt>,
},
/// Scale bodies about a point (uniform, or per axis).
Scale {
@@ -630,6 +633,13 @@ pub enum FeatureKind {
},
}
/// A face picked by a point on it, on a named body.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FaceAt {
pub body: String,
pub point: Vec3,
}
/// Triangles of one imported mesh body (millimetres).
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MeshData {
+15 -4
View File
@@ -2016,13 +2016,24 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
doc.resolve_plane(vals, plane, 0)?;
Ok(())
}
FeatureKind::Split { body, plane } => {
let pl = doc.resolve_plane(vals, plane, 0)?;
FeatureKind::Split { body, plane, tool } => {
let i = st.bodies.iter().position(|b| &b.name == body).ok_or_else(|| DocError::Unknown(format!("body `{body}`")))?;
let Some(mb) = st.bodies.get(i).cloned() else { return Err(DocError::Unknown(format!("body `{body}`"))) };
let parts = kernel::split_by_plane(&mb.body, &pl)?;
let parts = match tool {
// A face of a body (its surface extended).
Some(t) => {
let tb = st.body(&t.body).ok_or_else(|| DocError::Unknown(format!("body `{}`", t.body)))?;
let face = crate::appearance::face_index_at(tb, t.point)
.ok_or_else(|| DocError::Invalid(format!("no face of `{}` at the tool point", t.body)))?;
kernel::split_body(&mb.body, &kernel::SplitTool::Face { body: &tb.body, face })?
}
None => {
let pl = doc.resolve_plane(vals, plane, 0)?;
kernel::split_by_plane(&mb.body, &pl)?
}
};
if parts.len() < 2 {
return Err(DocError::Invalid("the plane does not split the body".into()));
return Err(DocError::Invalid("the tool does not split the body".into()));
}
let mut it = parts.into_iter();
if let (Some(first), Some(slot)) = (it.next(), st.bodies.get_mut(i)) {