From 8ea38bc51826e10bf875381731160430b36883b0 Mon Sep 17 00:00:00 2001 From: bherbruck Date: Thu, 8 Oct 2026 08:31:54 +0000 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL --- crates/kernel/src/step_out.rs | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/crates/kernel/src/step_out.rs b/crates/kernel/src/step_out.rs index d7a0e694..f47ca956 100644 --- a/crates/kernel/src/step_out.rs +++ b/crates/kernel/src/step_out.rs @@ -937,10 +937,51 @@ fn brep_exchange(b: &Body) -> Result { 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 { + let map: HashMap = ex.entities.keys().map(|k| (*k, *k)).collect(); + let mut ids: Vec = 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]];