Repository navigation
feat(exchange): export_step_in for bodies in a unit other than metres #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,13 +15,22 @@ 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, | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Medium: the body's tolerances change meaning too.
On a millimetre body, the exchange check becomes 1000× stricter and display meshes become 1000× finer. At building scale in millimetres, with coordinates around 1e5 to 1e6, the 1e-9 intersection budget is only a few ulps of a double. Could the new test build its plate with accuracy scaled to millimetres, so it covers the case a real user would hit? |
||||||||||||||||||
| unit: &str, | ||||||||||||||||||
| ) -> Result<(String, StepReport), GeometryError> { | ||||||||||||||||||
| let (text, report) = export_bodies( | ||||||||||||||||||
| &[StepBodyInput { | ||||||||||||||||||
| og_id: &brep.id, | ||||||||||||||||||
| shape_id: &brep.id, | ||||||||||||||||||
| 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<StepSkipped>, | ||||||||||||||||||
| ) -> 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<f64, GeometryError> { | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: a per-unit factor would be simpler. Each unit name appears three times: in the guard, in the pair table and in the catch-all. A small helper that returns metres per unit (and errors on an unknown name), called once for each side, does the same job. It also turns adding inches into one line instead of a growing pair table. Inches are common in US STEP files, where they're written as a conversion-based unit of 25.4 mm. This isn't needed for this PR, but it's worth doing if more units are coming. |
||||||||||||||||||
| 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), | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Low: dividing gives cleaner numbers.
Dividing by 1000 for this direction fixes it. |
||||||||||||||||||
| ("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<'_>], | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Low: the name covers only one of the five checks. Besides the millimetre case, this test checks that the default path still scales, that millimetre to metre scales the bounds, and that unknown file and model units are refused. If one of the rejection checks breaks, the failure still reports "exports unscaled in millimetres". Could you split it into one test per behaviour? For example |
||
| 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Low: these checks are weak.
A check that the metre file contains |
||
| 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", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -145,6 +145,7 @@ impl WorldGraph { | |
| .collect::<Vec<_>>(); | ||
| export_bodies( | ||
| &inputs, | ||
| "metre", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. High: JavaScript users can't reach this yet. The only STEP export in the wasm bindings goes through |
||
| &options.unit, | ||
| &options.up_axis, | ||
| &options.name, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
High: the unit exists only for STEP.
After this change a body can be in millimetres, but only
export_step_inknows that. The IFC body export still writeslength_unit: "metre"(exchange/ifc_body/mod.rs), and the world graph assumes metres too. So the same millimetre body comes out right in STEP and 1000× too large in IFC.