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}