STEP export: undo face merging for a body when the merged faces do not read back cleanly

With solvecraft-repo's curved-edge fillets (d72ec38), oracle 40's torus
groove comes out of truck in three pieces. Merging them made a band round the
torus whose single loop crosses itself, and the reader could not lay it out
("unsupported seam loop layout (2 runs)"). Each body's merged B-rep is now
read back. If that gives warnings and the unmerged pieces read cleanly, the
pieces are written instead.

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 08:45:27 +00:00
1 parent 2ac226cf0a
commit 8ea38bc518
1 file changed
+41
+41
View File
@@ -937,10 +937,51 @@ fn brep_exchange(b: &Body) -> Result<p21::Exchange> {
let text = crate::step::truck_step(&[b], "SolveCraft")?;
let mut ex = p21::parse(&text).map_err(|e| KernelError::Failed(format!("STEP export: {e}")))?;
collapse_short_edges(&mut ex);
let plain = ex.clone();
merge_split_faces(&mut ex);
// Keep the merged faces only when they read back as cleanly as the pieces (a merge that
// leaves a face the reader cannot lay out, such as a band round a closed surface whose
// loop crosses itself, is undone).
let clean = |ex: &p21::Exchange| {
let header = text.split("DATA;").next().unwrap_or_default();
exchange_text(header, ex).and_then(|t| crate::step_in::step_import(&t).ok()).is_some_and(|imp| imp.warnings.is_empty())
};
if ex.entities.len() != plain.entities.len() && !clean(&ex) && clean(&plain) {
return Ok(plain);
}
Ok(ex)
}
/// A parsed exchange written back as STEP text (with the given header).
fn exchange_text(header: &str, ex: &p21::Exchange) -> Option<String> {
let map: HashMap<u64, u64> = ex.entities.keys().map(|k| (*k, *k)).collect();
let mut ids: Vec<u64> = ex.entities.keys().copied().collect();
ids.sort();
let mut o = format!("{header}DATA;\n");
for id in ids {
let e = ex.get(id)?;
let _ = write!(o, "#{id}=");
if e.is_complex() {
o.push('(');
for r in &e.records {
o.push_str(&r.name);
o.push('(');
params(&r.params, &map, &mut o).ok()?;
o.push(')');
}
o.push(')');
} else {
o.push_str(e.name());
o.push('(');
params(e.params(), &map, &mut o).ok()?;
o.push(')');
}
o.push_str(";\n");
}
o.push_str("ENDSEC;\nEND-ISO-10303-21;\n");
Some(o)
}
fn rigid_frame(m: &[[f64; 4]; 4]) -> Result<([f64; 3], [f64; 3], [f64; 3])> {
let x = [m[0][0], m[0][1], m[0][2]];
let y = [m[1][0], m[1][1], m[1][2]];