STEP export: AP242 file of our own, names, colours, components as assemblies

Our writer assembles the file: AP242 header and contexts, a product per part
(or per component, with NEXT_ASSEMBLY_USAGE_OCCURRENCE and item-defined
placements per occurrence), named solids, surface colours as styled items,
ADVANCED_FACE for advanced breps. truck now writes only each body's B-rep,
which we copy in; identical geometric entities are written once and shared.
Designs with components export as assemblies (bodies in component frames).

step_validate also rejects unreferenced non-root entities. Tests: the sample
plate and a component assembly export, validate and read back with no
warnings and the same volume, area and face counts; the oracle replay checks
area and faces too (39 rebuildable cases pass). An independent Part 21
parser (step-p21, run outside the repo) accepts our exports.

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 00:16:52 +00:00
1 parent dd7485503a
commit 9330ee5698
10 files changed
+682 -14

No files matched your search

+34 -3
View File
@@ -256,8 +256,10 @@ pub fn export(state: &ModelState, bodies: &[String], format: Format, name: &str)
}
}
Format::Step => {
let bs: Vec<&solvecraft_kernel::Body> = sel.iter().map(|b| &b.body).collect();
solvecraft_kernel::step_export(&bs, "SolveCraft")?.into_bytes()
let bs: Vec<solvecraft_kernel::ExportBody> =
sel.iter().map(|b| solvecraft_kernel::ExportBody { name: b.name.clone(), body: &b.body, color: b.body.color() }).collect();
let header = solvecraft_kernel::StepHeader { file_name: format!("{name}.step"), ..Default::default() };
solvecraft_kernel::step_export_bodies(&bs, &header)?.into_bytes()
}
Format::ThreeMf => {
let objs: Vec<MeshObject> = sel
@@ -275,6 +277,33 @@ pub fn export(state: &ModelState, bodies: &[String], format: Format, name: &str)
})
}
/// STEP assembly of a design with components: a product per component holding its bodies in
/// the component's own frame (`local` is the model before placing occurrences), and an
/// assembly occurrence per component occurrence.
pub fn step_assembly(doc: &Document, local: &ModelState, name: &str) -> Result<Vec<u8>> {
use solvecraft_kernel::{ExportBody, ExportProduct};
let mut index: std::collections::HashMap<u64, usize> = std::collections::HashMap::new();
index.insert(0, 0);
let mut products = vec![ExportProduct { name: doc.name.clone(), bodies: Vec::new(), children: Vec::new() }];
for c in &doc.components {
index.insert(c.id, products.len());
products.push(ExportProduct { name: c.name.clone(), bodies: Vec::new(), children: Vec::new() });
}
for b in &local.bodies {
let comp = doc.body_component(&b.name, b.feature);
let Some(p) = index.get(&comp).and_then(|i| products.get_mut(*i)) else { continue };
p.bodies.push(ExportBody { name: b.name.clone(), body: &b.body, color: b.body.color() });
}
for o in &doc.occurrences {
let (Some(&parent), Some(&child)) = (index.get(&o.parent), index.get(&o.component)) else { continue };
if let Some(p) = products.get_mut(parent) {
p.children.push((child, o.transform, o.name.clone()));
}
}
let header = solvecraft_kernel::StepHeader { file_name: format!("{name}.step"), ..Default::default() };
Ok(solvecraft_kernel::step_export_products(&products, 0, &header)?.into_bytes())
}
/// Read a design file's bytes.
pub fn read_design(bytes: &[u8]) -> Result<Document> {
if bytes.len() > MAX_DESIGN_BYTES {
@@ -373,7 +402,9 @@ mod tests {
assert_eq!(Format::from_name("a.STP").unwrap(), Format::Step);
assert!(is_step_path("/x/Part.STEP") && is_step_path("a.stp") && !is_step_path("a.step.json") && !is_step_path("step"));
let f = step_import_feature(s.as_bytes(), "/tmp/My Part.step").unwrap();
assert_eq!(f.name, "My Part");
// Named after the exported product (the export was called `t`).
assert_eq!(f.name, "t");
assert_eq!(f.body_names, ["Body1"]);
assert_eq!(f.body_names.len(), 1);
assert!(matches!(&f.kind, FeatureKind::Import { file, .. } if file == "My Part.step"));
assert!(step_import_feature(b"ISO-10303-21;", "x.step").is_err());