Skip to main content

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 target 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. Real-world files are rarely clean — messy content is healed or
43//! dropped rather than failing the import, and the report is the record of
44//! 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//!
49//! [`Scene`](scene::Scene) — `model.scene()` — is the navigation layer on
50//! top: lightweight `Copy` handles covering what imports touch most, not
51//! every entity. In reach are b-rep geometry (solid → face → surface, edge →
52//! curve → point), the assembly tree with placements, display meshes, PMI
53//! (dimensions, tolerances, datum features), product metadata (versions,
54//! approvals, documents), presentation (colour / layer / visibility), and
55//! units — see the [`scene`] module docs. Anything beyond the handles is
56//! reached through the raw model via [`Scene::model`](scene::Scene::model).
57//!
58//! ```no_run
59//! use step_io::scene::geometry::SurfaceKind;
60//!
61//! let source = std::fs::read("part.step").unwrap();
62//! let (model, _report) = step_io::read(&source).unwrap(); // report: kept / normalized / dropped
63//! let scene = model.scene();
64//!
65//! // Geometry comes in its stored analytic form first; NURBS only on demand.
66//! for solid in scene.all_solids() {
67//!     for face in solid.faces() {
68//!         match face.surface().kind() {
69//!             SurfaceKind::Plane(_p) => { /* the kernel's own plane */ }
70//!             SurfaceKind::Cylindrical(_c) => { /* the kernel's own cylinder */ }
71//!             // … other analytic kinds …
72//!             _ => {
73//!                 let _patch = face.to_nurbs(); // opt-in NURBS fallback
74//!             }
75//!         }
76//!     }
77//! }
78//!
79//! // Assemblies, meshes, PMI, metadata, presentation: the same handles —
80//! // the full tour is in the `scene` module docs.
81//!
82//! // Navigation anomalies collected during the walk above — not the read-time
83//! // `Report`, a second channel. Log and continue, do not fail the import.
84//! for w in scene.warnings() {
85//!     eprintln!("step-io: {w}");
86//! }
87//! ```
88//!
89//! # Writing STEP from a kernel
90//!
91//! The write side is strict by construction. Its foundation is [`Ap242Author`],
92//! a generated low-level layer with one constructor per AP242 entity (exposed
93//! as [`StepBuilder::author`]); every argument is validated at insertion, so
94//! the output always conforms to the AP242 edition 2 schema — there is no loss
95//! report to check afterwards.
96//!
97//! [`StepBuilder`] is the convenience layer on top, covering what exports
98//! touch most: parts and b-rep topology (vertex → edge → face → solid, over
99//! analytic surfaces or NURBS), assembly instances with placements, display
100//! meshes, presentation (colour / transparency / layer / visibility), PLM
101//! metadata (contributors, approvals, documents, security), units, and the
102//! Part 21 header — see the [`build`] module docs.
103//!
104//! ```no_run
105//! # use step_io::build::{CurveInput, FaceBoundInput, Frame, SurfaceInput};
106//! # struct KernelEdge { start: usize, end: usize }
107//! # struct KernelFace { same_sense: bool }
108//! # struct Kernel { points: Vec<[f64; 3]>, edges: Vec<KernelEdge>, faces: Vec<KernelFace> }
109//! # let kernel = Kernel { points: vec![], edges: vec![], faces: vec![] };
110//! # fn curve_of(_: &KernelEdge) -> CurveInput { CurveInput::Line }
111//! # fn surface_of(_: &KernelFace) -> SurfaceInput {
112//! #     SurfaceInput::Plane(Frame { origin: [0.0; 3], axis: [0.0, 0.0, 1.0], ref_dir: [1.0, 0.0, 0.0] })
113//! # }
114//! # fn loops_of(_: &KernelFace, _es: &[step_io::generated::model::EdgeCurveId]) -> Vec<FaceBoundInput> {
115//! #     Vec::new()
116//! # }
117//! use step_io::StepBuilder;
118//! use step_io::build::PartOptions;
119//!
120//! let mut b = StepBuilder::new().unwrap(); // millimetre; new_with() to choose
121//! let wheel = b.part_with("wheel", &PartOptions {
122//!     id: Some("W-100".into()),
123//!     version: Some("B".into()),
124//!     ..Default::default()
125//! }).unwrap();
126//!
127//! // Mirror the kernel's own arrays — vertex → edge → face → solid, one
128//! // handle per element.
129//! let vs: Vec<_> = kernel.points.iter()
130//!     .map(|p| b.vertex(*p).unwrap())
131//!     .collect();
132//! let es: Vec<_> = kernel.edges.iter()
133//!     .map(|e| b.edge(vs[e.start], vs[e.end], curve_of(e)).unwrap())
134//!     .collect();
135//! let fs: Vec<_> = kernel.faces.iter()
136//!     .map(|f| b.face(surface_of(f), f.same_sense, loops_of(f, &es)).unwrap())
137//!     .collect();
138//! b.solid(wheel, "wheel body", fs).unwrap();
139//!
140//! // Assemblies, display meshes, colours, layers, and PLM metadata are one
141//! // call each.
142//! std::fs::write("wheel.step", b.finish().unwrap()).unwrap();
143//! ```
144
145#[cfg(test)]
146mod author_tests;
147pub mod build;
148pub mod emit;
149pub mod generated;
150pub mod parser;
151pub mod reader;
152pub mod refgraph;
153pub mod scene;
154
155pub use build::StepBuilder;
156pub use emit::FileHeader;
157pub use generated::author::{Ap242Author, AuthorError};
158pub use generated::model::{EntityKey, StepModel};
159pub use reader::{DropKind, DropReason, Report, read};
160
161// Internal round-trip oracle — kept callable for the external verification
162// harness, but not part of the supported API surface (the supported write
163// path is the authoring API).
164#[doc(hidden)]
165pub use emit::{LossReport, write_target, write_target_with_header, write_universal};
166#[doc(hidden)]
167pub use generated::profile::SchemaTarget;
168pub use parser::{ApFamily, Error, LexError, LexErrorKind, SchemaId, Stage};
169pub use refgraph::RefGraph;
170
171/// Crate-wide result alias: the only fallible public entry point ([`read`])
172/// fails with [`Error`].
173pub type Result<T> = std::result::Result<T, Error>;