From e007470e16ce223451cea782e9c34ca2fdbaa40a Mon Sep 17 00:00:00 2001 From: Bas <16050771+Basnederveen@users.noreply.github.com> Date: Sun, 1 Nov 2026 10:56:00 +0100 Subject: [PATCH] feat(exchange): export_step_in for bodies in a unit other than metres export_step takes a body's coordinates as metres and scales them to the file unit. A body built in millimetres had no way to say so: exporting it as millimetres multiplied every coordinate by 1000 (a 100 mm plate came out 100 m wide). export_step_in(brep, model_unit, unit) takes the unit the body is built in ("metre" or "millimetre") and scales from it to the file unit once. export_step keeps its behaviour and delegates with model_unit = "metre". The scale is a fixed table, so metre bodies export the same digits as before. export_bodies takes the model unit; the world graph passes "metre". Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VFZvqx1RmXzPtbL9VXP4H7 --- main/opengeometry/src/exchange/mod.rs | 4 ++- main/opengeometry/src/exchange/step/mod.rs | 37 ++++++++++++++------ main/opengeometry/src/exchange/step/tests.rs | 36 ++++++++++++++++++- main/opengeometry/src/world_graph/export.rs | 1 + 4 files changed, 66 insertions(+), 12 deletions(-) diff --git a/main/opengeometry/src/exchange/mod.rs b/main/opengeometry/src/exchange/mod.rs index 346095bb..4793eddb 100644 --- a/main/opengeometry/src/exchange/mod.rs +++ b/main/opengeometry/src/exchange/mod.rs @@ -5,4 +5,6 @@ mod preflight; mod step; pub(super) use step::{export_bodies, StepBodyInput, StepExportFailure}; -pub use step::{export_step, StepBodyReport, StepExportReport, StepReport, StepSkipped}; +pub use step::{ + export_step, export_step_in, StepBodyReport, StepExportReport, StepReport, StepSkipped, +}; diff --git a/main/opengeometry/src/exchange/step/mod.rs b/main/opengeometry/src/exchange/step/mod.rs index 1d5fbeeb..3a03e54e 100644 --- a/main/opengeometry/src/exchange/step/mod.rs +++ b/main/opengeometry/src/exchange/step/mod.rs @@ -15,6 +15,14 @@ use body::{emit_body, BodyEmission}; use product::{emit_context, emit_products, emit_representations}; pub fn export_step(brep: &BrepEnvelope, unit: &str) -> Result<(String, StepReport), GeometryError> { + export_step_in(brep, "metre", unit) +} + +pub fn export_step_in( + brep: &BrepEnvelope, + model_unit: &str, + unit: &str, +) -> Result<(String, StepReport), GeometryError> { let (text, report) = export_bodies( &[StepBodyInput { og_id: &brep.id, @@ -22,6 +30,7 @@ pub fn export_step(brep: &BrepEnvelope, unit: &str) -> Result<(String, StepRepor shape_revision: brep.revision, brep, }], + model_unit, unit, "Y", &brep.id, @@ -50,22 +59,14 @@ pub fn export_step(brep: &BrepEnvelope, unit: &str) -> Result<(String, StepRepor pub(crate) fn export_bodies( bodies: &[StepBodyInput<'_>], + model_unit: &str, unit: &str, up_axis: &str, name: &str, timestamp: &str, skipped: Vec, ) -> Result<(String, StepExportReport), StepExportFailure> { - let scale = match unit { - "metre" => 1.0, - "millimetre" => 1000.0, - _ => { - return Err(GeometryError::InvalidGeometry( - "STEP length unit must be metre or millimetre".into(), - ) - .into()) - } - }; + let scale = unit_scale(model_unit, unit)?; if bodies.is_empty() { return Err( GeometryError::InvalidGeometry("STEP export has no solid bodies".into()).into(), @@ -121,6 +122,22 @@ pub(crate) fn export_bodies( Ok((text, report)) } +fn unit_scale(model_unit: &str, unit: &str) -> Result { + if !matches!(unit, "metre" | "millimetre") { + return Err(GeometryError::InvalidGeometry( + "STEP length unit must be metre or millimetre".into(), + )); + } + match (model_unit, unit) { + ("metre", "millimetre") => Ok(1000.0), + ("millimetre", "metre") => Ok(1e-3), + ("metre" | "millimetre", _) => Ok(1.0), + _ => Err(GeometryError::InvalidGeometry( + "STEP model length unit must be metre or millimetre".into(), + )), + } +} + fn add_bodies( writer: &mut Part21Writer, bodies: &[StepBodyInput<'_>], diff --git a/main/opengeometry/src/exchange/step/tests.rs b/main/opengeometry/src/exchange/step/tests.rs index de60a67d..e00b41f1 100644 --- a/main/opengeometry/src/exchange/step/tests.rs +++ b/main/opengeometry/src/exchange/step/tests.rs @@ -1,4 +1,4 @@ -use super::{export_bodies, export_step, StepBodyInput}; +use super::{export_bodies, export_step, export_step_in, StepBodyInput}; use crate::brep::{ coarse_accuracy, BrepEnvelope, Curve, CurveGeometry, EdgeGeometry, Frame3, GeometryError, IntersectionDefinition, IntersectionSide, PcurveGeometry, Surface, TraceAnchor, @@ -194,6 +194,39 @@ fn rejects_unrepresentable_exchange_and_preserves_large_revisions() { )); } +#[test] +fn a_body_in_millimetres_exports_unscaled_in_millimetres() { + let brep = primitives::cuboid( + "plate".into(), + Frame3::IDENTITY, + [100.0, 50.0, 2.0], + coarse_accuracy(1e-3), + ) + .unwrap(); + let (metres, _) = export_step(&brep, "metre").unwrap(); + let (mm, report) = export_step_in(&brep, "millimetre", "millimetre").unwrap(); + assert_eq!(report.length_unit, "millimetre"); + assert_eq!( + mm.replace("SI_UNIT(.MILLI.,.METRE.)", "SI_UNIT($,.METRE.)"), + metres + ); + let (scaled, _) = export_step(&brep, "millimetre").unwrap(); + assert_ne!(scaled, mm); + let (_, to_metres) = export_step_in(&brep, "millimetre", "metre").unwrap(); + assert_eq!( + to_metres.exchange_error_bound, + report.exchange_error_bound * 1e-3 + ); + assert!(matches!( + export_step_in(&brep, "millimetre", "inch"), + Err(GeometryError::InvalidGeometry(_)) + )); + assert!(matches!( + export_step_in(&brep, "inch", "millimetre"), + Err(GeometryError::InvalidGeometry(_)) + )); +} + #[test] fn two_million_entity_export_is_limit_exceeded() { let brep = primitives::cuboid( @@ -214,6 +247,7 @@ fn two_million_entity_export_is_limit_exceeded() { let failure = export_bodies( &inputs, "metre", + "metre", "Y", "cap", "1970-01-01T00:00:00", diff --git a/main/opengeometry/src/world_graph/export.rs b/main/opengeometry/src/world_graph/export.rs index dda17854..6f8cf915 100644 --- a/main/opengeometry/src/world_graph/export.rs +++ b/main/opengeometry/src/world_graph/export.rs @@ -145,6 +145,7 @@ impl WorldGraph { .collect::>(); export_bodies( &inputs, + "metre", &options.unit, &options.up_axis, &options.name,