step_io/lib.rs
1//! Lean STEP (ISO 10303) I/O for 3D CAD kernels — reads every mainstream AP,
2//! writes only modern AP242.
3//!
4//! > ⚠️ **Experimental** — early stage; expect breaking API changes at any time.
5//!
6//! - **Reads everything, writes AP242** — any mainstream AP comes in, legacy
7//! included; only AP242 edition 2 (IS) goes out.
8//! - **Lean** — one universal schema model instead of a per-schema
9//! implementation; entities that never appear in real STEP files are pruned
10//! out.
11//! - **Heals non-standard input** — what can be fixed is normalized; what
12//! cannot is dropped with a reason, never silently.
13//! - **Ergonomic API** — [`scene()`](StepModel::scene) navigates what you read
14//! with lightweight handles; [`StepBuilder`] writes new files the way
15//! kernels hold data.
16//!
17//! # Design
18//!
19//! step-io exists to sit between a 3D CAD kernel and the STEP files it
20//! exchanges.
21//!
22//! Most STEP libraries carry per-schema entity and read/write code, so each
23//! supported schema adds source and binary size. step-io instead merges the
24//! mainstream APs into one *universal* schema and reads them all through a
25//! single pipeline. Entities that never appear in practice are left out,
26//! judged against 50,000+ real-world files — the [entity coverage
27//! matrix](https://github.com/elgar328/step-io/blob/main/docs/entities.md)
28//! lists exactly what is read and written.
29//!
30//! Output is AP242 edition 2 (IS) only — AP203 and AP214 were merged into
31//! it in 2014, and every modern tool reads it. When edition 3 reaches IS
32//! and takes over, the output moves up with it: one output schema,
33//! always the current one.
34//!
35//! Most of the pipeline is generated from the schemas rather than written
36//! by hand — no drift, little to maintain.
37//!
38//! # Reading STEP into a kernel or viewer
39//!
40//! Reading gives the result and the report together: [`read`] returns the
41//! [`StepModel`] plus a [`Report`] of what was kept, normalized, or dropped
42//! and why. Messy files are common in the wild — non-standard content is
43//! healed or dropped rather than failing the import, and the report is the
44//! record of what happened on the way in.
45//!
46//! The model is raw and complete: one public arena per entity type,
47//! schema-faithful. Everything the reader kept is directly accessible.
48//! The Part 21 header is read too, as [`StepModel::header`] — file name,
49//! timestamp, authors, the originating CAD system, and the identified
50//! schema.
51//!
52//! [`Scene`](scene::Scene) — `model.scene()` — is the navigation layer on
53//! top: lightweight `Copy` handles covering what imports touch most, not
54//! every entity. In reach are b-rep geometry (solid → face → surface, edge →
55//! curve → point), the assembly tree with placements, display meshes, PMI
56//! (dimensions, tolerances, datum features), product metadata (versions,
57//! approvals, documents), presentation (colour / layer / visibility), and
58//! units — see the [`scene`] module docs. Anything beyond the handles is
59//! reached through the raw model via [`Scene::model`](scene::Scene::model).
60//!
61//! ```no_run
62//! use step_io::scene::geometry::SurfaceKind;
63//!
64//! let source = std::fs::read("part.step").unwrap();
65//! let (model, _report) = step_io::read(&source).unwrap(); // report: kept / normalized / dropped
66//! let scene = model.scene();
67//!
68//! // Geometry comes in its stored analytic form first; NURBS only on demand.
69//! for solid in scene.all_solids() {
70//! for face in solid.faces() {
71//! match face.surface().kind() {
72//! SurfaceKind::Plane(_p) => { /* the kernel's own plane */ }
73//! SurfaceKind::Cylindrical(_c) => { /* the kernel's own cylinder */ }
74//! // … other analytic kinds …
75//! _ => {
76//! let _patch = face.to_nurbs(); // opt-in NURBS fallback
77//! }
78//! }
79//! }
80//! }
81//!
82//! // Assemblies, meshes, PMI, metadata, presentation: the same handles —
83//! // the full tour is in the `scene` module docs.
84//!
85//! // Navigation anomalies collected during the walk above — not the read-time
86//! // `Report`, a second channel. Log and continue, do not fail the import.
87//! for w in scene.warnings() {
88//! eprintln!("step-io: {w}");
89//! }
90//! ```
91//!
92//! # Writing STEP from a kernel
93//!
94//! The write side is strict by construction. Its foundation is [`Ap242Author`],
95//! a generated low-level layer with one constructor per AP242 entity (exposed
96//! as [`StepBuilder::author`]); every argument is validated at insertion, so
97//! the output always conforms to the AP242 edition 2 schema — there is no loss
98//! report to check afterwards.
99//!
100//! [`StepBuilder`] is the convenience layer on top, covering what exports
101//! touch most: parts and b-rep topology (vertex → edge → face → solid, over
102//! analytic surfaces or NURBS), assembly instances with placements, display
103//! meshes, presentation (colour / transparency / layer / visibility), PLM
104//! metadata (contributors, approvals, documents, security), units, and the
105//! Part 21 header — see the [`build`] module docs.
106//!
107//! ```no_run
108//! # use step_io::build::{CurveInput, FaceBoundInput, Frame, SurfaceInput};
109//! # struct KernelEdge { start: usize, end: usize }
110//! # struct KernelFace { same_sense: bool }
111//! # struct Kernel { points: Vec<[f64; 3]>, edges: Vec<KernelEdge>, faces: Vec<KernelFace> }
112//! # let kernel = Kernel { points: vec![], edges: vec![], faces: vec![] };
113//! # fn curve_of(_: &KernelEdge) -> CurveInput { CurveInput::Line }
114//! # fn surface_of(_: &KernelFace) -> SurfaceInput {
115//! # SurfaceInput::Plane(Frame { origin: [0.0; 3], axis: [0.0, 0.0, 1.0], ref_dir: [1.0, 0.0, 0.0] })
116//! # }
117//! # fn loops_of(_: &KernelFace, _es: &[step_io::generated::model::EdgeCurveId]) -> Vec<FaceBoundInput> {
118//! # Vec::new()
119//! # }
120//! use step_io::StepBuilder;
121//! use step_io::build::PartOptions;
122//!
123//! let mut b = StepBuilder::new().unwrap(); // millimetre; new_with() to choose
124//! let wheel = b.part_with("wheel", &PartOptions {
125//! id: Some("W-100".into()),
126//! version: Some("B".into()),
127//! ..Default::default()
128//! }).unwrap();
129//!
130//! // Mirror the kernel's own arrays — vertex → edge → face → solid, one
131//! // handle per element.
132//! let vs: Vec<_> = kernel.points.iter()
133//! .map(|p| b.vertex(*p).unwrap())
134//! .collect();
135//! let es: Vec<_> = kernel.edges.iter()
136//! .map(|e| b.edge(vs[e.start], vs[e.end], curve_of(e)).unwrap())
137//! .collect();
138//! let fs: Vec<_> = kernel.faces.iter()
139//! .map(|f| b.face(surface_of(f), f.same_sense, loops_of(f, &es)).unwrap())
140//! .collect();
141//! b.solid(wheel, "wheel body", fs).unwrap();
142//!
143//! // Assemblies, display meshes, colours, layers, and PLM metadata are one
144//! // call each.
145//! std::fs::write("wheel.step", b.finish().unwrap()).unwrap();
146//! ```
147
148#[cfg(test)]
149mod author_tests;
150pub mod build;
151pub mod emit;
152pub mod generated;
153pub mod header;
154pub mod parser;
155pub mod reader;
156pub mod refgraph;
157pub mod scene;
158
159pub use build::StepBuilder;
160pub use emit::write;
161pub use generated::author::{Ap242Author, AuthorError};
162pub use generated::model::{EntityKey, StepModel};
163pub use header::FileHeader;
164pub use reader::{DropKind, DropReason, Report, read};
165
166// Internal round-trip oracle — kept callable for the external verification
167// harness, but not part of the supported API surface.
168#[doc(hidden)]
169pub use emit::dump_universal;
170pub use parser::{ApFamily, Error, LexError, LexErrorKind, SchemaId, Stage};
171pub use refgraph::RefGraph;
172
173/// Crate-wide result alias: the only fallible public entry point ([`read`])
174/// fails with [`Error`].
175pub type Result<T> = std::result::Result<T, Error>;