Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

feat(interop): step writer emit ap214

Lewis: May this revision serve well! <lu5a@proton.me>

author
Lewis
date (Jun 7, 2026, 12:49 PM +0300) commit e8cb983d parent 5dfb25dc change-id ntnkxxkw
+271 -129
+3 -1
crates/bone-document/src/evaluator.rs
··· 21 21 } 22 22 } 23 23 24 - #[derive(Clone, Debug)] 24 + #[derive(Clone, Debug, thiserror::Error)] 25 25 pub enum ExtrudeError { 26 + #[error(transparent)] 26 27 UnsolvedSketch(SolverError), 28 + #[error(transparent)] 27 29 Kernel(BrepError), 28 30 } 29 31
+1 -1
crates/bone-interop/src/lib.rs
··· 1 1 pub mod step; 2 2 3 - pub use step::{ImportOutcome, StepError, read, write}; 3 + pub use step::{ImportOutcome, StepError, body_of, read, write};
+46 -11
crates/bone-interop/src/step.rs
··· 2 2 use std::io; 3 3 use std::path::{Path, PathBuf}; 4 4 5 - use bone_document::LabelSidecar; 5 + use bone_document::{ 6 + Document, ExtrudeError, FeatureNode, LabelSidecar, evaluate_extrude, evaluate_sketch, 7 + }; 6 8 use bone_kernel::{BrepError, BrepSolid}; 7 9 use bone_types::{ 8 10 FaceRole, FeatureId, StepFileHeader, StepFileName, StepOrganization, StepOriginatingSystem, ··· 25 27 pub enum StepError { 26 28 #[error("schema {0} is unsupported for export; only AP214 is emitted")] 27 29 SchemaUnsupported(StepSchema), 30 + #[error("phase-2 step export writes a single body per document")] 31 + BodyCount { count: usize }, 32 + #[error("extrude feature {feature:?} has no evaluable sketch")] 33 + DanglingExtrude { feature: FeatureId }, 34 + #[error("feature {feature:?} failed to evaluate")] 35 + Evaluation { 36 + feature: FeatureId, 37 + #[source] 38 + source: ExtrudeError, 39 + }, 28 40 #[error("io at {path}: {source}")] 29 41 Io { 30 42 path: PathBuf, ··· 67 79 } 68 80 } 69 81 70 - pub fn write(solid: &BrepSolid, path: &Path, schema: StepSchema) -> Result<(), StepError> { 82 + pub fn write(document: &Document, path: &Path, schema: StepSchema) -> Result<(), StepError> { 71 83 if schema != StepSchema::Ap214 { 72 84 return Err(StepError::SchemaUnsupported(schema)); 73 85 } 86 + let solid = body_of(document)?; 74 87 let body = solid.to_step_body()?; 75 - let file_name = path 76 - .file_name() 77 - .and_then(|name| name.to_str()) 78 - .unwrap_or(""); 79 - let document = envelope(&render_header(&export_header(file_name, schema)), &body); 80 - write_file(path, document.as_bytes())?; 81 - 88 + let contents = envelope( 89 + &render_header(&export_header(document.name(), schema)), 90 + &body, 91 + ); 82 92 let labels_path = sidecar_path(path); 83 - let sidecar = LabelSidecar::capture(solid) 93 + let sidecar = LabelSidecar::capture(&solid) 84 94 .to_ron() 85 95 .map_err(|source| StepError::Sidecar { 86 96 path: labels_path.clone(), 87 97 source: Box::new(source), 88 98 })?; 89 - write_file(&labels_path, sidecar.as_bytes()) 99 + write_file(&labels_path, sidecar.as_bytes())?; 100 + write_file(path, contents.as_bytes()) 101 + } 102 + 103 + pub fn body_of(document: &Document) -> Result<BrepSolid, StepError> { 104 + let extrudes: Vec<FeatureId> = document 105 + .feature_tree() 106 + .iter() 107 + .filter_map(|(feature, node)| matches!(node, FeatureNode::Extrude(_)).then_some(feature)) 108 + .collect(); 109 + let [feature] = extrudes.as_slice() else { 110 + return Err(StepError::BodyCount { 111 + count: extrudes.len(), 112 + }); 113 + }; 114 + let feature = *feature; 115 + let extrude = document 116 + .extrude_of_feature(feature) 117 + .ok_or(StepError::DanglingExtrude { feature })?; 118 + let sketch = document 119 + .sketch(extrude.sketch) 120 + .ok_or(StepError::DanglingExtrude { feature })?; 121 + evaluate_extrude(feature, &evaluate_sketch(sketch), extrude) 122 + .result() 123 + .clone() 124 + .map_err(|source| StepError::Evaluation { feature, source }) 90 125 } 91 126 92 127 pub fn read(path: &Path, feature: FeatureId) -> Result<ImportOutcome, StepError> {
+221 -116
crates/bone-interop/tests/step.rs
··· 1 - use bone_interop::{StepError, read, write}; 1 + use bone_document::{Document, EditOutcome, Sketch, SketchEdit, SketchEntity}; 2 + use bone_interop::{StepError, body_of, read, write}; 2 3 use bone_kernel::{ 3 - BrepFace, BrepSolid, Circle2, Curve2Kind, ExtrudeDirection, ExtrudeEndCondition, 4 - ExtrudeFeature, ExtrudeProfile, ExtrudeSense, Line2, MergeResult, ProfileEdge, ProfileLoop, 5 - evaluate_extrude, 4 + BrepFace, BrepSolid, ExtrudeDirection, ExtrudeEndCondition, ExtrudeFeature, ExtrudeSense, 5 + MergeResult, 6 6 }; 7 7 use bone_types::{ 8 - FaceLabel, FeatureId, Length, Plane3, Point2, PositiveLength, SketchEntityId, SketchId, 9 - StepSchema, Tolerance, UnitVec3, millimeter, 8 + DocumentId, ExtrudeId, FaceLabel, FeatureId, Length, Point2, Point3, PositiveLength, 9 + SketchEntityId, SketchId, SketchPlaneBasis, StepSchema, Tolerance, UnitVec3, millimeter, 10 10 }; 11 - use slotmap::{Key, SlotMap}; 11 + use slotmap::KeyData; 12 12 13 13 const TOL: Tolerance = Tolerance::new(1.0e-9); 14 14 15 - fn xy_plane() -> Plane3 { 16 - let Ok(plane) = Plane3::new( 17 - bone_types::Point3::origin(), 15 + fn xy_basis() -> SketchPlaneBasis { 16 + let Ok(basis) = SketchPlaneBasis::new( 17 + Point3::origin(), 18 18 UnitVec3::x_axis(), 19 19 UnitVec3::y_axis(), 20 20 TOL, 21 21 ) else { 22 22 panic!("orthonormal axes"); 23 23 }; 24 - plane 24 + basis 25 + } 26 + 27 + fn ffi_key(n: u64) -> KeyData { 28 + KeyData::from_ffi((1u64 << 32) | n) 29 + } 30 + 31 + fn sketch_id(n: u64) -> SketchId { 32 + SketchId::from(ffi_key(n)) 33 + } 34 + 35 + fn extrude_id(n: u64) -> ExtrudeId { 36 + ExtrudeId::from(ffi_key(n)) 37 + } 38 + 39 + fn import_feature() -> FeatureId { 40 + FeatureId::from(ffi_key(99)) 41 + } 42 + 43 + fn add_point(sketch: Sketch, x: f64, y: f64) -> (Sketch, SketchEntityId) { 44 + let Ok((next, EditOutcome::Entity(id))) = sketch.apply(SketchEdit::AddEntity( 45 + SketchEntity::point(Point2::from_mm(x, y)), 46 + )) else { 47 + panic!("add point"); 48 + }; 49 + (next, id) 50 + } 51 + 52 + fn add_line(sketch: Sketch, a: SketchEntityId, b: SketchEntityId) -> Sketch { 53 + let Ok((next, _)) = sketch.apply(SketchEdit::AddEntity(SketchEntity::line(a, b, false))) else { 54 + panic!("add line"); 55 + }; 56 + next 57 + } 58 + 59 + fn add_circle(sketch: Sketch, center: SketchEntityId, radius_mm: f64) -> Sketch { 60 + let Ok((next, _)) = sketch.apply(SketchEdit::AddEntity(SketchEntity::circle( 61 + center, 62 + Length::new::<millimeter>(radius_mm), 63 + false, 64 + ))) else { 65 + panic!("add circle"); 66 + }; 67 + next 68 + } 69 + 70 + fn rectangle_sketch() -> Sketch { 71 + let (sketch, p0) = add_point(Sketch::new(xy_basis()), 0.0, 0.0); 72 + let (sketch, p1) = add_point(sketch, 4.0, 0.0); 73 + let (sketch, p2) = add_point(sketch, 4.0, 2.0); 74 + let (sketch, p3) = add_point(sketch, 0.0, 2.0); 75 + let sketch = add_line(sketch, p0, p1); 76 + let sketch = add_line(sketch, p1, p2); 77 + let sketch = add_line(sketch, p2, p3); 78 + add_line(sketch, p3, p0) 79 + } 80 + 81 + fn circle_sketch(radius_mm: f64) -> Sketch { 82 + let (sketch, center) = add_point(Sketch::new(xy_basis()), 0.0, 0.0); 83 + add_circle(sketch, center, radius_mm) 84 + } 85 + 86 + fn donut_sketch() -> Sketch { 87 + let (sketch, center) = add_point(Sketch::new(xy_basis()), 0.0, 0.0); 88 + let sketch = add_circle(sketch, center, 10.0); 89 + add_circle(sketch, center, 4.0) 25 90 } 26 91 27 - fn blind(depth_mm: f64) -> ExtrudeFeature { 92 + fn blind(sketch: SketchId, depth_mm: f64) -> ExtrudeFeature { 28 93 let Ok(depth) = PositiveLength::new(Length::new::<millimeter>(depth_mm)) else { 29 94 panic!("positive depth"); 30 95 }; 31 96 ExtrudeFeature { 32 - sketch: SketchId::null(), 97 + sketch, 33 98 direction: ExtrudeDirection::Normal { 34 99 sense: ExtrudeSense::Forward, 35 100 }, ··· 40 105 } 41 106 } 42 107 43 - fn rectangle(entities: &mut SlotMap<SketchEntityId, ()>) -> ProfileLoop { 44 - let point = |x: f64, y: f64| Point2::from_mm(x, y); 45 - let line = |a: Point2, b: Point2| { 46 - let Ok(segment) = Line2::new(a, b, TOL) else { 47 - panic!("distinct endpoints"); 48 - }; 49 - Curve2Kind::Line(segment) 50 - }; 51 - let corners = [ 52 - point(0.0, 0.0), 53 - point(4.0, 0.0), 54 - point(4.0, 2.0), 55 - point(0.0, 2.0), 56 - ]; 57 - let edges = (0..4) 58 - .map(|index| { 59 - ProfileEdge::new( 60 - line(corners[index], corners[(index + 1) % 4]), 61 - entities.insert(()), 62 - entities.insert(()), 63 - ) 64 - }) 65 - .collect(); 66 - ProfileLoop::Open(edges) 67 - } 68 - 69 - fn cylinder(entities: &mut SlotMap<SketchEntityId, ()>) -> ProfileLoop { 70 - let Ok(disk) = Circle2::new( 71 - Point2::from_mm(0.0, 0.0), 72 - Length::new::<millimeter>(5.0), 73 - TOL, 74 - ) else { 75 - panic!("positive radius"); 76 - }; 77 - ProfileLoop::Closed { 78 - curve: Curve2Kind::Circle(disk), 79 - curve_entity: entities.insert(()), 80 - } 81 - } 82 - 83 - fn build(loops: Vec<ProfileLoop>, depth_mm: f64) -> BrepSolid { 84 - let mut features: SlotMap<FeatureId, ()> = SlotMap::with_key(); 85 - let profile = ExtrudeProfile::new(xy_plane(), loops); 86 - let Ok(solid) = evaluate_extrude(features.insert(()), &profile, &blind(depth_mm)) else { 87 - panic!("profile extrudes"); 88 - }; 89 - solid 90 - } 91 - 92 - fn cube() -> BrepSolid { 93 - let mut entities: SlotMap<SketchEntityId, ()> = SlotMap::with_key(); 94 - build(vec![rectangle(&mut entities)], 5.0) 108 + fn document(name: &str, sketch: Sketch, depth_mm: f64) -> Document { 109 + let mut document = Document::new(DocumentId::default(), name.to_owned()); 110 + let sketch_key = sketch_id(1); 111 + document.insert_sketch(sketch_key, "Sketch1".to_owned(), sketch); 112 + document.insert_extrude(extrude_id(1), blind(sketch_key, depth_mm)); 113 + document 95 114 } 96 115 97 116 fn face_labels(solid: &BrepSolid) -> Vec<FaceLabel> { 98 117 solid.iter_faces().map(BrepFace::label).collect() 99 118 } 100 119 101 - fn import_feature() -> FeatureId { 102 - let mut features: SlotMap<FeatureId, ()> = SlotMap::with_key(); 103 - features.insert(()) 104 - } 105 - 106 - fn donut(entities: &mut SlotMap<SketchEntityId, ()>) -> Vec<ProfileLoop> { 107 - let ring = |radius_mm: f64, entities: &mut SlotMap<SketchEntityId, ()>| { 108 - let Ok(disk) = Circle2::new( 109 - Point2::from_mm(0.0, 0.0), 110 - Length::new::<millimeter>(radius_mm), 111 - TOL, 112 - ) else { 113 - panic!("positive radius"); 114 - }; 115 - ProfileLoop::Closed { 116 - curve: Curve2Kind::Circle(disk), 117 - curve_entity: entities.insert(()), 118 - } 120 + fn step_round_trip_keeps_labels(document: &Document) { 121 + let Ok(expected) = body_of(document) else { 122 + panic!("document evaluates to one body"); 119 123 }; 120 - vec![ring(10.0, entities), ring(4.0, entities)] 121 - } 122 - 123 - fn step_round_trip_keeps_labels(solid: &BrepSolid) { 124 124 let Ok(dir) = tempfile::tempdir() else { 125 125 panic!("temp dir"); 126 126 }; 127 127 let path = dir.path().join("part.step"); 128 - let Ok(()) = write(solid, &path, StepSchema::Ap214) else { 128 + let Ok(()) = write(document, &path, StepSchema::Ap214) else { 129 129 panic!("write step"); 130 130 }; 131 131 let Ok(outcome) = read(&path, import_feature()) else { 132 132 panic!("read step"); 133 133 }; 134 134 assert!(outcome.is_labeled(), "matching sidecar restores labels"); 135 - assert_eq!(face_labels(solid), face_labels(outcome.solid())); 135 + assert_eq!(face_labels(&expected), face_labels(outcome.solid())); 136 136 assert!(outcome.solid().validate(TOL).is_ok()); 137 137 } 138 138 139 139 #[test] 140 140 fn cube_step_round_trip_keeps_labels() { 141 - step_round_trip_keeps_labels(&cube()); 141 + step_round_trip_keeps_labels(&document("cube", rectangle_sketch(), 4.0)); 142 142 } 143 143 144 144 #[test] 145 145 fn cylinder_step_round_trip_keeps_labels() { 146 - let mut entities: SlotMap<SketchEntityId, ()> = SlotMap::with_key(); 147 - let solid = build(vec![cylinder(&mut entities)], 8.0); 148 - step_round_trip_keeps_labels(&solid); 146 + step_round_trip_keeps_labels(&document("cylinder", circle_sketch(5.0), 8.0)); 149 147 } 150 148 151 149 #[test] 152 150 fn donut_step_round_trip_keeps_labels() { 153 - let mut entities: SlotMap<SketchEntityId, ()> = SlotMap::with_key(); 154 - let solid = build(donut(&mut entities), 6.0); 155 - step_round_trip_keeps_labels(&solid); 151 + step_round_trip_keeps_labels(&document("donut", donut_sketch(), 6.0)); 156 152 } 157 153 158 154 #[test] 159 - fn apostrophe_in_file_name_round_trips() { 160 - let solid = cube(); 155 + fn header_carries_document_name() { 156 + let document = document("bracket", rectangle_sketch(), 4.0); 161 157 let Ok(dir) = tempfile::tempdir() else { 162 158 panic!("temp dir"); 163 159 }; 164 - let path = dir.path().join("nel's bracket.step"); 165 - let Ok(()) = write(&solid, &path, StepSchema::Ap214) else { 160 + let path = dir.path().join("export.step"); 161 + let Ok(()) = write(&document, &path, StepSchema::Ap214) else { 166 162 panic!("write step"); 167 163 }; 164 + let Ok(text) = std::fs::read_to_string(&path) else { 165 + panic!("read back"); 166 + }; 167 + assert!( 168 + text.contains("FILE_NAME('bracket'"), 169 + "header file_name carries the document name, not the step path" 170 + ); 171 + } 172 + 173 + #[test] 174 + fn apostrophe_in_document_name_round_trips() { 175 + let document = document("nel's bracket", rectangle_sketch(), 4.0); 176 + let Ok(dir) = tempfile::tempdir() else { 177 + panic!("temp dir"); 178 + }; 179 + let path = dir.path().join("part.step"); 180 + let Ok(()) = write(&document, &path, StepSchema::Ap214) else { 181 + panic!("an apostrophe in the document name must not break the header"); 182 + }; 183 + let Ok(text) = std::fs::read_to_string(&path) else { 184 + panic!("read back"); 185 + }; 186 + assert!( 187 + text.contains("FILE_NAME('nel''s bracket'"), 188 + "an apostrophe is doubled per ISO 10303-21" 189 + ); 168 190 let Ok(outcome) = read(&path, import_feature()) else { 169 - panic!("an apostrophe in the file name must not break the header"); 191 + panic!("read step"); 170 192 }; 171 - assert!(outcome.is_labeled(), "matching sidecar restores labels"); 172 - assert_eq!(face_labels(&solid), face_labels(outcome.solid())); 193 + assert!(outcome.is_labeled()); 173 194 } 174 195 175 196 #[test] 176 197 fn step_export_is_byte_deterministic() { 177 - let solid = cube(); 198 + let document = document("cube", rectangle_sketch(), 4.0); 178 199 let Ok(dir) = tempfile::tempdir() else { 179 200 panic!("temp dir"); 180 201 }; 181 202 let path = dir.path().join("part.step"); 182 - let Ok(()) = write(&solid, &path, StepSchema::Ap214) else { 203 + let Ok(()) = write(&document, &path, StepSchema::Ap214) else { 183 204 panic!("write first"); 184 205 }; 185 206 let Ok(a) = std::fs::read(&path) else { 186 207 panic!("read first"); 187 208 }; 188 - let Ok(()) = write(&solid, &path, StepSchema::Ap214) else { 209 + let Ok(()) = write(&document, &path, StepSchema::Ap214) else { 189 210 panic!("write second"); 190 211 }; 191 212 let Ok(b) = std::fs::read(&path) else { 192 213 panic!("read second"); 193 214 }; 194 - assert_eq!(a, b, "same solid and path write byte-identical step"); 215 + assert_eq!(a, b, "same document and path write byte-identical step"); 195 216 } 196 217 197 218 #[test] 198 219 fn missing_sidecar_imports_dumb_body() { 199 - let solid = cube(); 220 + let document = document("cube", rectangle_sketch(), 4.0); 221 + let Ok(expected) = body_of(&document) else { 222 + panic!("one body"); 223 + }; 200 224 let Ok(dir) = tempfile::tempdir() else { 201 225 panic!("temp dir"); 202 226 }; 203 227 let path = dir.path().join("part.step"); 204 - let Ok(()) = write(&solid, &path, StepSchema::Ap214) else { 228 + let Ok(()) = write(&document, &path, StepSchema::Ap214) else { 205 229 panic!("write step"); 206 230 }; 207 - let mut labels = dir.path().join("part.step").into_os_string(); 231 + let mut labels = path.clone().into_os_string(); 208 232 labels.push(".labels"); 209 233 let Ok(()) = std::fs::remove_file(std::path::PathBuf::from(labels)) else { 210 234 panic!("remove sidecar"); ··· 215 239 assert!(!outcome.is_labeled(), "no sidecar yields a dumb body"); 216 240 assert_eq!( 217 241 outcome.solid().iter_faces().count(), 218 - solid.iter_faces().count() 242 + expected.iter_faces().count() 219 243 ); 220 244 } 221 245 222 246 #[test] 223 247 fn ap242_export_is_unsupported() { 224 - let solid = cube(); 248 + let document = document("cube", rectangle_sketch(), 4.0); 225 249 let Ok(dir) = tempfile::tempdir() else { 226 250 panic!("temp dir"); 227 251 }; 228 252 let path = dir.path().join("part.step"); 229 253 assert!(matches!( 230 - write(&solid, &path, StepSchema::Ap242E2), 254 + write(&document, &path, StepSchema::Ap242E2), 231 255 Err(StepError::SchemaUnsupported(StepSchema::Ap242E2)) 232 256 )); 233 257 } 234 258 235 259 #[test] 260 + fn empty_document_has_no_body() { 261 + let document = Document::new(DocumentId::default(), "empty".to_owned()); 262 + let Ok(dir) = tempfile::tempdir() else { 263 + panic!("temp dir"); 264 + }; 265 + let path = dir.path().join("part.step"); 266 + assert!(matches!( 267 + write(&document, &path, StepSchema::Ap214), 268 + Err(StepError::BodyCount { count: 0 }) 269 + )); 270 + } 271 + 272 + #[test] 273 + fn multiple_bodies_are_rejected() { 274 + let mut document = Document::new(DocumentId::default(), "two".to_owned()); 275 + let first = sketch_id(1); 276 + let second = sketch_id(2); 277 + document.insert_sketch(first, "A".to_owned(), rectangle_sketch()); 278 + document.insert_sketch(second, "B".to_owned(), circle_sketch(5.0)); 279 + document.insert_extrude(extrude_id(1), blind(first, 4.0)); 280 + document.insert_extrude(extrude_id(2), blind(second, 6.0)); 281 + let Ok(dir) = tempfile::tempdir() else { 282 + panic!("temp dir"); 283 + }; 284 + let path = dir.path().join("part.step"); 285 + assert!(matches!( 286 + write(&document, &path, StepSchema::Ap214), 287 + Err(StepError::BodyCount { count: 2 }) 288 + )); 289 + } 290 + 291 + #[test] 292 + fn lone_unresolved_extrude_reports_dangling() { 293 + let mut document = Document::new(DocumentId::default(), "pending".to_owned()); 294 + document.insert_extrude(extrude_id(1), blind(sketch_id(1), 4.0)); 295 + let Ok(dir) = tempfile::tempdir() else { 296 + panic!("temp dir"); 297 + }; 298 + let path = dir.path().join("part.step"); 299 + assert!(matches!( 300 + write(&document, &path, StepSchema::Ap214), 301 + Err(StepError::DanglingExtrude { .. }) 302 + )); 303 + } 304 + 305 + #[test] 306 + fn unresolved_body_is_not_silently_dropped() { 307 + let mut document = Document::new(DocumentId::default(), "mixed".to_owned()); 308 + document.insert_sketch(sketch_id(1), "A".to_owned(), rectangle_sketch()); 309 + document.insert_extrude(extrude_id(1), blind(sketch_id(1), 4.0)); 310 + document.insert_extrude(extrude_id(2), blind(sketch_id(2), 6.0)); 311 + let Ok(dir) = tempfile::tempdir() else { 312 + panic!("temp dir"); 313 + }; 314 + let path = dir.path().join("part.step"); 315 + assert!(matches!( 316 + write(&document, &path, StepSchema::Ap214), 317 + Err(StepError::BodyCount { count: 2 }) 318 + )); 319 + assert!(!path.exists(), "a rejected export leaves no file behind"); 320 + } 321 + 322 + #[test] 323 + fn sidecar_failure_leaves_no_orphan_step() { 324 + let document = document("cube", rectangle_sketch(), 4.0); 325 + let Ok(dir) = tempfile::tempdir() else { 326 + panic!("temp dir"); 327 + }; 328 + let path = dir.path().join("part.step"); 329 + let mut blocker = path.clone().into_os_string(); 330 + blocker.push(".labels"); 331 + let Ok(()) = std::fs::create_dir(std::path::PathBuf::from(blocker)) else { 332 + panic!("block the sidecar path with a directory"); 333 + }; 334 + assert!(write(&document, &path, StepSchema::Ap214).is_err()); 335 + assert!( 336 + !path.exists(), 337 + "no labelless step is written when the sidecar cannot be" 338 + ); 339 + } 340 + 341 + #[test] 236 342 fn cylinder_export_writes_step_and_sidecar() { 237 - let mut entities: SlotMap<SketchEntityId, ()> = SlotMap::with_key(); 238 - let solid = build(vec![cylinder(&mut entities)], 8.0); 343 + let document = document("cyl", circle_sketch(5.0), 8.0); 239 344 let Ok(dir) = tempfile::tempdir() else { 240 345 panic!("temp dir"); 241 346 }; 242 347 let path = dir.path().join("cyl.step"); 243 - let Ok(()) = write(&solid, &path, StepSchema::Ap214) else { 348 + let Ok(()) = write(&document, &path, StepSchema::Ap214) else { 244 349 panic!("write step"); 245 350 }; 246 351 assert!(path.exists());