Skip to main content

brep_kernel/io/step_import/
trim_readings.rs

1//! Three readings of every imported edge: the file's 3D curve as written, the
2//! file's own pcurve mapped through the file's surface, and the edge the
3//! importer built — the instrument that says whether a defect on an imported
4//! solid is the FILE's geometry or OUR reconstruction of it.
5//!
6//! A diagnostic lane, like [`crate::import_step_report`]: it builds the same
7//! bodies through the same stages, and carries what each stage did to each edge
8//! beside the solid instead of discarding it. Nothing in the import path reads
9//! it.
10
11use super::*;
12
13/// Every flat body of one STEP document, with the per-edge provenance.
14#[derive(Clone, Debug)]
15pub struct StepTrimReadings {
16    /// Every precision the file states, in millimetres, ascending (the same
17    /// reading [`StepImportReport::stated_precisions_mm`] carries).
18    pub stated_precisions_mm: Vec<f64>,
19    /// The file carries a product structure. Its bodies are listed here in the
20    /// FLAT order, once per representation — [`crate::import_step_report`]
21    /// places one copy per occurrence instead, so the two indices only agree on
22    /// a single-part file.
23    pub has_structure: bool,
24    pub bodies: Vec<StepBodyTrimReadings>,
25}
26
27#[derive(Clone, Debug)]
28pub struct StepBodyTrimReadings {
29    /// The STEP body entity (`MANIFOLD_SOLID_BREP`, `BREP_WITH_VOIDS`, or the
30    /// `SHELL_BASED_SURFACE_MODEL`'s shell).
31    pub body_ref: usize,
32    /// The body as the importer built it, or why it refused.
33    pub solid: Result<BrepSolid, String>,
34    /// The shell-closure refusal the ordinary import lane draws on this body
35    /// (`builder/closure.rs`), which THIS lane holds so the body can be read:
36    /// a `Some` here is a body `import_step` refuses.
37    pub closure_refusal: Option<String>,
38    pub edges: Vec<StepEdgeReading>,
39    pub faces: Vec<StepFaceReading>,
40}
41
42#[derive(Clone, Debug)]
43pub struct StepEdgeReading {
44    /// The built edge's id in [`StepBodyTrimReadings::solid`].
45    pub edge_id: u64,
46    /// The `EDGE_CURVE.edge_geometry` entity, when the file wrote one.
47    pub curve_ref: Option<usize>,
48    /// The file's own curve over this edge's span, `(curve, [t0, t1])`, before
49    /// any importer stage — including the endpoint heal. `None` for an edge
50    /// the importer minted with no file statement behind it.
51    pub written: Option<(NurbsCurve, [f64; 2])>,
52    /// Every stage that changed the edge's geometry, in order. Empty means the
53    /// built edge is the written one (up to the endpoint heal, which is listed
54    /// when it moved anything).
55    pub stages: Vec<String>,
56}
57
58#[derive(Clone, Debug)]
59pub struct StepFaceReading {
60    /// The built face's id in [`StepBodyTrimReadings::solid`].
61    pub face_id: u64,
62    pub face_ref: usize,
63    /// `usize::MAX` for a face the planar split minted.
64    pub surface_ref: usize,
65    /// The file's own trims on this face, one per coedge whose edge's
66    /// `SURFACE_CURVE`/`SEAM_CURVE` bundle names this face's surface.
67    pub supplied: Vec<StepSuppliedTrim>,
68}
69
70/// A vendor pcurve evaluated through the vendor's own surface.
71#[derive(Clone, Debug)]
72pub struct StepSuppliedTrim {
73    pub edge_id: u64,
74    pub forward: bool,
75    /// `STATIONS + 1` points of the stated trim, each the trim point nearest
76    /// the written curve's station at the same coedge fraction.
77    pub stations: Vec<Vec3>,
78}
79
80/// Coedge fractions the supplied trims are sampled at.
81const STATIONS: usize = 64;
82
83/// Build every flat body of `text` with the per-edge provenance captured.
84pub fn import_step_trim_readings(text: &str) -> Result<StepTrimReadings, String> {
85    if !text.contains("ISO-10303-21") {
86        return Err("step_import: not an ISO-10303-21 Part 21 file".into());
87    }
88    let entities = parse_data_section(text)?;
89    let resolver = Resolver::new(&entities, derive_length_scale_mm(&entities));
90    let stated_precisions_mm = stated_precisions_mm(&entities);
91    let has_structure = !structure_edges(&entities, &resolver).is_empty();
92    let mut bodies = Vec::new();
93    for body in flat_step_bodies(&resolver, &entities) {
94        let body_ref = match body {
95            StepBody::SolidBrep(entity_ref) | StepBody::BrepWithVoids(entity_ref) => entity_ref,
96            StepBody::SurfaceModelShell { shell_ref, .. } => shell_ref,
97        };
98        // The capture lane HOLDS a closure refusal (`builder/closure.rs`): the
99        // body is built and read so the residual can be attributed edge by
100        // edge, and the refusal it would have drawn rides beside it.
101        let (solid, capture, closure_refusal) = match build_step_body_captured(&resolver, body) {
102            Ok((solid, capture, readings)) => (
103                Ok(solid),
104                capture.unwrap_or_default(),
105                readings.closure.overridden().map(|refusal| refusal.message.clone()),
106            ),
107            Err(error) => (Err(error.to_string()), Default::default(), None),
108        };
109        let mut edges: Vec<StepEdgeReading> = Vec::new();
110        let mut faces: Vec<StepFaceReading> = Vec::new();
111        if let Ok(solid) = &solid {
112            for edge in &solid.edges {
113                let written = capture.written.get(&edge.id);
114                edges.push(StepEdgeReading {
115                    edge_id: edge.id,
116                    curve_ref: written.and_then(|written| written.curve_ref),
117                    written: written.map(|written| (written.curve.clone(), [written.t0, written.t1])),
118                    stages: capture.stages.get(&edge.id).cloned().unwrap_or_default(),
119                });
120            }
121            for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
122                let Some(&(_, face_ref, surface_ref)) =
123                    capture.faces.iter().find(|(id, _, _)| *id == face.id)
124                else {
125                    continue;
126                };
127                let mut supplied = Vec::new();
128                for coedge in face.loops.iter().flat_map(|loop_record| &loop_record.coedges) {
129                    let Some(written) = capture.written.get(&coedge.edge_id) else {
130                        continue;
131                    };
132                    let Some(curve_ref) = written.curve_ref else {
133                        continue;
134                    };
135                    let bundle = resolver.supplied_pcurves(curve_ref);
136                    let Some(pcurve) = bundle.iter().find(|pcurve| pcurve.basis_ref == surface_ref)
137                    else {
138                        continue;
139                    };
140                    let mut stations = Vec::with_capacity(STATIONS + 1);
141                    let mut cursor = None;
142                    let mut readable = true;
143                    for index in 0..=STATIONS {
144                        let fraction = index as f64 / STATIONS as f64;
145                        let along = if coedge.forward { fraction } else { 1.0 - fraction };
146                        let Ok(point) =
147                            written.curve.evaluate(written.t0 + (written.t1 - written.t0) * along)
148                        else {
149                            readable = false;
150                            break;
151                        };
152                        let Ok((uv, on_trim)) = pcurve.snap(point, cursor) else {
153                            readable = false;
154                            break;
155                        };
156                        cursor = Some(uv);
157                        stations.push(on_trim);
158                    }
159                    if readable {
160                        supplied.push(StepSuppliedTrim {
161                            edge_id: coedge.edge_id,
162                            forward: coedge.forward,
163                            stations,
164                        });
165                    }
166                }
167                faces.push(StepFaceReading {
168                    face_id: face.id,
169                    face_ref,
170                    surface_ref,
171                    supplied,
172                });
173            }
174        }
175        bodies.push(StepBodyTrimReadings {
176            body_ref,
177            solid,
178            closure_refusal,
179            edges,
180            faces,
181        });
182    }
183    Ok(StepTrimReadings {
184        stated_precisions_mm,
185        has_structure,
186        bodies,
187    })
188}