step_io/scene/mod.rs
1//! [`Scene`] — the convenience navigation layer over the raw [`StepModel`].
2//!
3//! The generated model is faithful but tedious to walk by hand (arenas and
4//! reference enums); [`model.scene()`](StepModel::scene) wraps it in
5//! lightweight `Copy` handles where forward *and* backward moves are plain
6//! `handle.method()` calls. The backward index ([`RefGraph`]) is built
7//! lazily on the first backward move — a forward-only import never pays
8//! for it.
9//!
10//! The core is enumeration plus scoped navigation. Model-level
11//! `all_<type>()` iterators ([`all_solids`](Scene::all_solids),
12//! [`all_products`](Scene::all_products), …) enumerate one type across the
13//! whole file; each handle then navigates its own neighbourhood
14//! (`solid.faces()`, `face.surface()`, `occurrence.definition()`, …).
15//! Coverage spans b-rep geometry ([`geometry`]), the product/assembly tree
16//! and PLM metadata ([`product`]), display meshes ([`mesh`]), PMI ([`pmi`]),
17//! presentation ([`presentation`]), units ([`units`]), and the NURBS views
18//! ([`nurbs`]).
19//!
20//! Kinds outside the handled set come back as `*Kind::Other`; what a handle
21//! cannot resolve into the shape it exposes lands in [`Scene::warnings`],
22//! never silently skipped.
23//!
24//! Anything beyond the handles is reached through the raw model via
25//! [`Scene::model`].
26//!
27//! ```no_run
28//! let source = std::fs::read("part.step").unwrap();
29//! let (model, _report) = step_io::read(&source).unwrap();
30//! let scene = model.scene();
31//!
32//! // Assembly: definitions and their placed occurrences, top-down. Keep the
33//! // placement on the instance — do not bake it into shared geometry.
34//! for def in scene.root_definitions() {
35//! for occ in def.occurrences() {
36//! let _child = occ.definition(); // recurse from here
37//! let _matrix = occ.transform().and_then(|t| t.matrix());
38//! }
39//! }
40//!
41//! // Geometry: solid → face → surface, bound → edge → curve → vertex.
42//! // Presentation (colour / layer / visibility) rides on the same handles.
43//! for solid in scene.all_solids() {
44//! let _colour = solid.color();
45//! for face in solid.faces() {
46//! let _surface = face.surface().kind(); // analytic form; `Other` beyond the set
47//! let _patch = face.to_nurbs(); // the NURBS view, on demand
48//! for bound in face.bounds() {
49//! for (edge, _forward) in bound.oriented_edges() {
50//! let _curve = edge.curve().kind();
51//! let _ends = (edge.start(), edge.end());
52//! }
53//! }
54//! }
55//! }
56//!
57//! // Display meshes, linked back to the b-rep solid they render.
58//! for group in scene.all_mesh_groups() {
59//! let _brep = group.solid();
60//! for mesh in group.meshes() {
61//! let (_pts, _tris) = (mesh.points(), mesh.triangles());
62//! }
63//! }
64//!
65//! // PMI: dimensions, tolerances, and the annotated features.
66//! for dim in scene.dimensions() {
67//! let (_kind, _value) = (dim.kind(), dim.value());
68//! }
69//! for tol in scene.tolerances() {
70//! let (_kind, _datums) = (tol.kind(), tol.datums());
71//! }
72//! for feat in scene.features() {
73//! let _geometry = feat.geometry(); // the faces/edges it annotates
74//! }
75//!
76//! // Product metadata: versions, approvals, documents, people.
77//! for product in scene.all_products() {
78//! let _versions = product.versions();
79//! let _approvals = product.approvals();
80//! let _documents = product.documents();
81//! let _people = product.contributors();
82//! }
83//!
84//! // Coordinates come in the file's units — scaling is the consumer's job.
85//! let _to_si = scene.units().length.map(|u| u.to_si);
86//! for w in scene.warnings() {
87//! eprintln!("step-io: {w}");
88//! }
89//! ```
90
91pub mod geometry;
92pub mod mesh;
93pub mod nurbs;
94pub mod pmi;
95pub mod presentation;
96pub mod product;
97pub mod units;
98
99use std::sync::{Mutex, OnceLock};
100
101use crate::RefGraph;
102use crate::StepModel;
103
104pub use mesh::{Mesh, MeshGroup, MeshNormals};
105pub use nurbs::{NurbsCurve, NurbsSurface};
106pub use pmi::{
107 Datum, Dimension, DimensionKind, Feature, FeatureGeometry, FeatureKind, Tolerance,
108 ToleranceKind,
109};
110pub use presentation::Rgb;
111pub use product::{
112 Approval, ApprovalDate, Approver, Contributor, Document, MappedInstance, Occurrence, Person,
113 Placement, Scope, SecurityClassification, Target, Transform, Version,
114};
115pub use units::{Unit, Units};
116
117/// A `Copy` reference bundle handed to every handle: the model, the lazy reverse
118/// index, and the scene's warning log. All borrows share one lifetime (the scene
119/// borrow), so handles stay single-lifetime.
120#[derive(Clone, Copy)]
121pub(crate) struct Ctx<'a> {
122 pub(crate) model: &'a StepModel,
123 pub(crate) rg: &'a OnceLock<RefGraph>,
124 pub(crate) warnings: &'a Mutex<Vec<String>>,
125}
126
127impl<'a> Ctx<'a> {
128 /// The reverse-reference index, built on first use.
129 pub(crate) fn ref_graph(&self) -> &'a RefGraph {
130 self.rg.get_or_init(|| self.model.ref_graph())
131 }
132
133 /// Record a navigation anomaly (something a handle could not resolve into
134 /// the shape it exposes) so it is surfaced rather than silently dropped.
135 pub(crate) fn warn(&self, msg: String) {
136 self.warnings
137 .lock()
138 .unwrap_or_else(std::sync::PoisonError::into_inner)
139 .push(msg);
140 }
141}
142
143/// The read API entry point: a [`StepModel`] paired with its (lazy) [`RefGraph`]
144/// and a log of navigation warnings.
145pub struct Scene<'m> {
146 model: &'m StepModel,
147 rg: OnceLock<RefGraph>,
148 warnings: Mutex<Vec<String>>,
149}
150
151impl StepModel {
152 /// Build a [`Scene`] over this model — the entry point for the navigation
153 /// handles. The reverse-reference index is built lazily on first use; reuse
154 /// the returned scene for all navigation.
155 #[must_use]
156 pub fn scene(&self) -> Scene<'_> {
157 Scene {
158 model: self,
159 rg: OnceLock::new(),
160 warnings: Mutex::new(Vec::new()),
161 }
162 }
163}
164
165impl<'m> Scene<'m> {
166 /// The reference bundle to hand to a handle created from this scene.
167 pub(crate) fn ctx(&self) -> Ctx<'_> {
168 Ctx {
169 model: self.model,
170 rg: &self.rg,
171 warnings: &self.warnings,
172 }
173 }
174
175 /// The underlying raw model (escape hatch for uncovered long-tail entities).
176 #[must_use]
177 pub fn model(&self) -> &'m StepModel {
178 self.model
179 }
180
181 /// Navigation anomalies recorded so far — cases a handle could not resolve
182 /// into the shape it exposes (e.g. an occurrence transform whose item is not
183 /// an `AXIS2_PLACEMENT_3D`). Surfaced here rather than silently dropped.
184 #[must_use]
185 pub fn warnings(&self) -> Vec<String> {
186 self.warnings
187 .lock()
188 .unwrap_or_else(std::sync::PoisonError::into_inner)
189 .clone()
190 }
191}