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    pub edges: Vec<StepEdgeReading>,
35    pub faces: Vec<StepFaceReading>,
36}
37
38#[derive(Clone, Debug)]
39pub struct StepEdgeReading {
40    /// The built edge's id in [`StepBodyTrimReadings::solid`].
41    pub edge_id: u64,
42    /// The `EDGE_CURVE.edge_geometry` entity, when the file wrote one.
43    pub curve_ref: Option<usize>,
44    /// The file's own curve over this edge's span, `(curve, [t0, t1])`, before
45    /// any importer stage — including the endpoint heal. `None` for an edge
46    /// the importer minted with no file statement behind it.
47    pub written: Option<(NurbsCurve, [f64; 2])>,
48    /// Every stage that changed the edge's geometry, in order. Empty means the
49    /// built edge is the written one (up to the endpoint heal, which is listed
50    /// when it moved anything).
51    pub stages: Vec<String>,
52}
53
54#[derive(Clone, Debug)]
55pub struct StepFaceReading {
56    /// The built face's id in [`StepBodyTrimReadings::solid`].
57    pub face_id: u64,
58    pub face_ref: usize,
59    /// `usize::MAX` for a face the planar split minted.
60    pub surface_ref: usize,
61    /// The file's own trims on this face, one per coedge whose edge's
62    /// `SURFACE_CURVE`/`SEAM_CURVE` bundle names this face's surface.
63    pub supplied: Vec<StepSuppliedTrim>,
64}
65
66/// A vendor pcurve evaluated through the vendor's own surface.
67#[derive(Clone, Debug)]
68pub struct StepSuppliedTrim {
69    pub edge_id: u64,
70    pub forward: bool,
71    /// `STATIONS + 1` points of the stated trim, each the trim point nearest
72    /// the written curve's station at the same coedge fraction.
73    pub stations: Vec<Vec3>,
74}
75
76/// Coedge fractions the supplied trims are sampled at.
77const STATIONS: usize = 64;
78
79/// Build every flat body of `text` with the per-edge provenance captured.
80pub fn import_step_trim_readings(text: &str) -> Result<StepTrimReadings, String> {
81    if !text.contains("ISO-10303-21") {
82        return Err("step_import: not an ISO-10303-21 Part 21 file".into());
83    }
84    let entities = parse_data_section(text)?;
85    let resolver = Resolver::new(&entities, derive_length_scale_mm(&entities));
86    let stated_precisions_mm = stated_precisions_mm(&entities);
87    let has_structure = !structure_edges(&entities, &resolver).is_empty();
88    let mut bodies = Vec::new();
89    for body in flat_step_bodies(&resolver, &entities) {
90        let body_ref = match body {
91            StepBody::SolidBrep(entity_ref) | StepBody::BrepWithVoids(entity_ref) => entity_ref,
92            StepBody::SurfaceModelShell { shell_ref, .. } => shell_ref,
93        };
94        let (solid, capture) = match build_step_body_captured(&resolver, body) {
95            Ok((solid, capture)) => (Ok(solid), capture.unwrap_or_default()),
96            Err(error) => (Err(error), Default::default()),
97        };
98        let mut edges: Vec<StepEdgeReading> = Vec::new();
99        let mut faces: Vec<StepFaceReading> = Vec::new();
100        if let Ok(solid) = &solid {
101            for edge in &solid.edges {
102                let written = capture.written.get(&edge.id);
103                edges.push(StepEdgeReading {
104                    edge_id: edge.id,
105                    curve_ref: written.and_then(|written| written.curve_ref),
106                    written: written.map(|written| (written.curve.clone(), [written.t0, written.t1])),
107                    stages: capture.stages.get(&edge.id).cloned().unwrap_or_default(),
108                });
109            }
110            for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
111                let Some(&(_, face_ref, surface_ref)) =
112                    capture.faces.iter().find(|(id, _, _)| *id == face.id)
113                else {
114                    continue;
115                };
116                let mut supplied = Vec::new();
117                for coedge in face.loops.iter().flat_map(|loop_record| &loop_record.coedges) {
118                    let Some(written) = capture.written.get(&coedge.edge_id) else {
119                        continue;
120                    };
121                    let Some(curve_ref) = written.curve_ref else {
122                        continue;
123                    };
124                    let bundle = resolver.supplied_pcurves(curve_ref);
125                    let Some(pcurve) = bundle.iter().find(|pcurve| pcurve.basis_ref == surface_ref)
126                    else {
127                        continue;
128                    };
129                    let mut stations = Vec::with_capacity(STATIONS + 1);
130                    let mut cursor = None;
131                    let mut readable = true;
132                    for index in 0..=STATIONS {
133                        let fraction = index as f64 / STATIONS as f64;
134                        let along = if coedge.forward { fraction } else { 1.0 - fraction };
135                        let Ok(point) =
136                            written.curve.evaluate(written.t0 + (written.t1 - written.t0) * along)
137                        else {
138                            readable = false;
139                            break;
140                        };
141                        let Ok((uv, on_trim)) = pcurve.snap(point, cursor) else {
142                            readable = false;
143                            break;
144                        };
145                        cursor = Some(uv);
146                        stations.push(on_trim);
147                    }
148                    if readable {
149                        supplied.push(StepSuppliedTrim {
150                            edge_id: coedge.edge_id,
151                            forward: coedge.forward,
152                            stations,
153                        });
154                    }
155                }
156                faces.push(StepFaceReading {
157                    face_id: face.id,
158                    face_ref,
159                    surface_ref,
160                    supplied,
161                });
162            }
163        }
164        bodies.push(StepBodyTrimReadings {
165            body_ref,
166            solid,
167            edges,
168            faces,
169        });
170    }
171    Ok(StepTrimReadings {
172        stated_precisions_mm,
173        has_structure,
174        bodies,
175    })
176}