Faces split on purpose stay split: the body remembers its Split Face lines, export and healing keep the pieces apart

Split Face records the new edges as polylines on the body (Body::split_lines); the document carries them through later features. STEP export no longer joins pieces of one surface across such a line, and healing (fillets, chamfers, booleans, shell, draft) no longer merges coplanar pieces there, while incidental pieces (boolean leftovers, seam halves) still merge as before. Tests: plane and cylinder splits keep their face count through a STEP round trip (and join without the record), a second split keeps both lines, and an engine test exports a split box after a fillet with 8 faces.

Fixes #11

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-09 21:31:16 +00:00
1 parent 0a1ab40ea0
commit 4c8f080691
12 files changed
+144 -19

No files matched your search

+20 -1
View File
@@ -19,6 +19,9 @@ pub struct Body {
pub(crate) color: Option<[f32; 3]>,
/// Opacity and face colours for export (set by the document; operations drop it).
pub(crate) paint: Option<std::sync::Arc<Paint>>,
/// Lines the user split faces along (Split Face), as polylines: export keeps the faces on
/// either side apart where it would otherwise join pieces of one surface.
pub(crate) splits: Option<std::sync::Arc<Vec<Vec<Vec3>>>>,
}
/// How a body looks beyond its colour, for export: its opacity and its faces' own colours.
@@ -278,6 +281,7 @@ impl Body {
}
&& self.color == other.color
&& self.paint == other.paint
&& self.splits == other.splits
}
pub(crate) fn new(solid: Solid) -> Result<Body> {
@@ -302,7 +306,7 @@ impl Body {
if v < 0.0 {
solid.not();
}
Ok(Body { solid: std::sync::Arc::new(solid), mesh: None, color: None, paint: None })
Ok(Body { solid: std::sync::Arc::new(solid), mesh: None, color: None, paint: None, splits: None })
}
/// A deep copy of the solid that can be mutated without affecting other bodies.
@@ -335,6 +339,21 @@ impl Body {
self
}
/// Lines faces were split along on purpose (Split Face), as polylines.
pub fn split_lines(&self) -> Option<&std::sync::Arc<Vec<Vec<Vec3>>>> {
self.splits.as_ref()
}
pub(crate) fn split_keep(&self) -> &[Vec<Vec3>] {
self.splits.as_ref().map(|l| l.as_slice()).unwrap_or(&[])
}
/// The same body with the lines its faces were split along on purpose.
pub fn with_split_lines(mut self, s: Option<std::sync::Arc<Vec<Vec<Vec3>>>>) -> Body {
self.splits = s.filter(|l| !l.is_empty());
self
}
/// Opacity and face colours for export, if any.
pub fn paint(&self) -> Option<&Paint> {
self.paint.as_deref()