Split Face (FusionSplitFaceCommand): a timeline feature dividing the picked faces where a plane, another body's face (its surface extended) or a sketch's curves meet them, through kernel::split_faces; the solid is unchanged and the pieces take the face's name with #k. Rebase fixes: duplicate Chamfer and Combine specs; the Delete selection's face test expects Delete Face's refusal
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
3db6119cc6
commit
68271137a5
9 files changed
+168
-7
No files matched your search
@@ -162,6 +162,13 @@ impl FeatureKind {
|
||||
pt(&mut t.point);
|
||||
}
|
||||
}
|
||||
FeatureKind::SplitFace { faces, plane, tool, .. } => {
|
||||
faces.iter_mut().for_each(pt);
|
||||
map_plane_ref(m, plane);
|
||||
if let Some(t) = tool {
|
||||
pt(&mut t.point);
|
||||
}
|
||||
}
|
||||
FeatureKind::Revolve { axis: AxisRef::Line { origin, dir: d }, .. } => {
|
||||
pt(origin);
|
||||
dir(d);
|
||||
|
||||
@@ -40,6 +40,12 @@ impl FeatureKind {
|
||||
FeatureKind::Sketch { plane, .. } | FeatureKind::ConstructionPlane { plane } | FeatureKind::Split { plane, .. } => {
|
||||
plane_names(plane, names)
|
||||
}
|
||||
FeatureKind::SplitFace { plane, sketch, .. } => {
|
||||
plane_names(plane, names);
|
||||
if let Some(s) = sketch {
|
||||
sk(s);
|
||||
}
|
||||
}
|
||||
FeatureKind::Extrude { sketch, .. }
|
||||
| FeatureKind::Revolve { sketch, .. }
|
||||
| FeatureKind::Emboss { sketch, .. }
|
||||
|
||||
@@ -355,6 +355,19 @@ pub enum FeatureKind {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
tool: Option<FaceAt>,
|
||||
},
|
||||
/// Split faces (at the given points) where a tool meets them, the solid unchanged: a plane,
|
||||
/// a face of a body (`tool`, its surface extended) or a sketch's curves (`sketch`, swept
|
||||
/// along its normal). The pieces are named after the face (`#k`).
|
||||
SplitFace {
|
||||
faces: Vec<Vec3>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
body: Option<String>,
|
||||
plane: PlaneRef,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
tool: Option<FaceAt>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
sketch: Option<u64>,
|
||||
},
|
||||
/// Scale bodies about a point (uniform, or per axis).
|
||||
Scale {
|
||||
bodies: Vec<String>,
|
||||
@@ -936,6 +949,7 @@ impl FeatureKind {
|
||||
FeatureKind::Shell { .. } => "ShellFeature",
|
||||
FeatureKind::Draft { .. } => "DraftFeature",
|
||||
FeatureKind::Split { .. } => "SplitBodyFeature",
|
||||
FeatureKind::SplitFace { .. } => "SplitFaceFeature",
|
||||
FeatureKind::Move { .. } => "MoveFeature",
|
||||
FeatureKind::Scale { .. } => "ScaleFeature",
|
||||
FeatureKind::OffsetFace { .. } => "OffsetFacesFeature",
|
||||
@@ -997,6 +1011,7 @@ impl FeatureKind {
|
||||
FeatureKind::Shell { .. } => "Shell",
|
||||
FeatureKind::Draft { .. } => "Draft",
|
||||
FeatureKind::Split { .. } => "Split",
|
||||
FeatureKind::SplitFace { .. } => "SplitFace",
|
||||
FeatureKind::Move { .. } => "Move",
|
||||
FeatureKind::Scale { .. } => "Scale",
|
||||
FeatureKind::OffsetFace { .. } => "OffsetFace",
|
||||
@@ -1042,6 +1057,7 @@ impl FeatureKind {
|
||||
| FeatureKind::Draft { faces, .. }
|
||||
| FeatureKind::OffsetFace { faces, .. }
|
||||
| FeatureKind::DeleteFace { faces, .. }
|
||||
| FeatureKind::SplitFace { faces, .. }
|
||||
| FeatureKind::ReplaceFace { faces, .. } => faces.iter_mut().collect(),
|
||||
FeatureKind::Hole { position, points: None, to, .. } => std::iter::once(position).chain(to.iter_mut()).collect(),
|
||||
FeatureKind::Hole { to: Some(t), .. } => vec![t],
|
||||
@@ -1060,9 +1076,10 @@ impl FeatureKind {
|
||||
pub fn expressions(&self) -> Vec<&str> {
|
||||
let mut v: Vec<&str> = Vec::new();
|
||||
match self {
|
||||
FeatureKind::Sketch { plane, .. } | FeatureKind::ConstructionPlane { plane } | FeatureKind::Split { plane, .. } => {
|
||||
plane_exprs(plane, &mut v)
|
||||
}
|
||||
FeatureKind::Sketch { plane, .. }
|
||||
| FeatureKind::ConstructionPlane { plane }
|
||||
| FeatureKind::Split { plane, .. }
|
||||
| FeatureKind::SplitFace { plane, .. } => plane_exprs(plane, &mut v),
|
||||
FeatureKind::Extrude { extent, .. } => {
|
||||
v.push(&extent.distance);
|
||||
if let Some(d) = &extent.distance2 {
|
||||
@@ -1551,7 +1568,7 @@ impl Document {
|
||||
FeatureKind::Pattern { pattern: PatternKind::Path { path_sketch, .. }, .. } if *path_sketch == id => gone.push(f.id),
|
||||
FeatureKind::Emboss { sketch, .. } | FeatureKind::Rib { sketch, .. } if *sketch == id => gone.push(f.id),
|
||||
FeatureKind::SheetBase { sketch, .. } | FeatureKind::SheetContour { sketch, .. } if *sketch == id => gone.push(f.id),
|
||||
FeatureKind::SheetFold { sketch: Some(s), .. } if *s == id => gone.push(f.id),
|
||||
FeatureKind::SheetFold { sketch: Some(s), .. } | FeatureKind::SplitFace { sketch: Some(s), .. } if *s == id => gone.push(f.id),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2414,6 +2414,46 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
}
|
||||
apply_op(st, f, made, *operation, &[])
|
||||
}
|
||||
FeatureKind::SplitFace { faces, body, plane, tool, sketch } => {
|
||||
let i = body_at(st, body, faces)?;
|
||||
let Some(mb) = st.bodies.get(i).cloned() else { return Err(DocError::Invalid("body".into())) };
|
||||
let mut picked: Vec<usize> = Vec::new();
|
||||
for p in faces {
|
||||
let fi = crate::appearance::face_index_at(&mb, *p)
|
||||
.ok_or_else(|| DocError::Invalid(format!("Split Face: no face of {} at {p:?}", mb.name)))?;
|
||||
if !picked.contains(&fi) {
|
||||
picked.push(fi);
|
||||
}
|
||||
}
|
||||
let nb = match (tool, sketch) {
|
||||
(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_faces(&mb.body, &picked, &kernel::SplitTool::Face { body: &tb.body, face })?
|
||||
}
|
||||
(None, Some(sid)) => {
|
||||
let ss = st.sketch(*sid).ok_or_else(|| DocError::Unknown(format!("sketch {sid}")))?;
|
||||
let curves: Vec<Vec<Vec2>> = (0..ss.sketch.curves.len())
|
||||
.filter(|ci| ss.sketch.curves.get(*ci).is_some_and(|c| !c.construction))
|
||||
.map(|ci| ss.sketch.polyline(ci))
|
||||
.filter(|pl| pl.len() >= 2)
|
||||
.collect();
|
||||
if curves.is_empty() {
|
||||
return Err(DocError::Invalid(format!("sketch `{}` has no curves to split with", ss.name)));
|
||||
}
|
||||
kernel::split_faces(&mb.body, &picked, &kernel::SplitTool::Curves { plane: ss.plane, curves })?
|
||||
}
|
||||
(None, None) => {
|
||||
let pl = doc.resolve_plane(vals, plane, 0)?;
|
||||
kernel::split_faces(&mb.body, &picked, &kernel::SplitTool::Plane(pl))?
|
||||
}
|
||||
};
|
||||
if let Some(slot) = st.bodies.get_mut(i) {
|
||||
*slot = ModelBody::new(mb.name, nb, mb.feature);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
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}`"))) };
|
||||
|
||||
@@ -34,6 +34,7 @@ impl FeatureKind {
|
||||
FeatureKind::Sketch { plane, .. }
|
||||
| FeatureKind::ConstructionPlane { plane }
|
||||
| FeatureKind::Split { plane, .. }
|
||||
| FeatureKind::SplitFace { plane, .. }
|
||||
| FeatureKind::Mirror { plane, .. } => plane_inputs_mut(plane, &mut v),
|
||||
FeatureKind::Extrude { extent, .. } => {
|
||||
v.push(("Distance", &mut extent.distance, L));
|
||||
|
||||
Reference in new issue
Block a user