brep_kernel/io/step_import/mod.rs
1//! ISO-10303-21 (STEP Part 21) importer — the read side of `step.rs`.
2//!
3//! Strategy is ROUND-TRIP-FIRST: this reads back exactly what `export_step`
4//! writes (an all-NURBS ADVANCED_BREP: B_SPLINE_CURVE/SURFACE_WITH_KNOTS and
5//! their rational complex forms, VERTEX_POINT / EDGE_CURVE / ORIENTED_EDGE /
6//! EDGE_LOOP / FACE_(OUTER_)BOUND / ADVANCED_FACE / CLOSED_SHELL /
7//! MANIFOLD_SOLID_BREP), reconstructs a `BrepSolid`, and validates it.
8//!
9//! This module DERIVES pcurves rather than reading them: every 3D edge curve is
10//! projected onto its carrier surface (`build_pcurve_on_surface_range`), seam
11//! edges are placed on opposite domain boundaries, and any pole boundary that
12//! is not written as a `VERTEX_LOOP` is re-synthesised as a degenerate edge so
13//! faces close their parameter domain and integrate to the correct mass
14//! properties.
15//!
16//! That is now a CHOICE, not a necessity: since `step.rs` grew curve-on-surface
17//! emission, our own files carry `SURFACE_CURVE`/`SEAM_CURVE` bundles with
18//! verified `PCURVE`s, and `VERTEX_LOOP` for collapsed bounds — and this reader
19//! still unwraps the bundle to its 3D curve and discards the pcurve list
20//! (`parse.rs`). Consuming them instead (a trust-but-verify read path) is a
21//! documented follow-up in kernel-plans/step-export-pcurves.md; deriving is
22//! correct meanwhile, and is what vendor files without pcurves need anyway.
23//!
24//! Analytic-surface recognition on import (PLANE/CYLINDRICAL/…) is a documented
25//! follow-up: the exporter emits only NURBS, so faithful NURBS round-trip is the
26//! milestone. Unsupported entities produce a clear `Err`, never a panic.
27
28use crate::topology::{
29 BrepSolid, CoedgeRecord, EdgeRecord, FaceRecord, LoopRecord, ShellRecord, VertexRecord,
30};
31use crate::{
32 build_pcurve_on_surface_range, make_arc, make_extrusion, make_hyperbola, make_line,
33 make_parabola, make_plane, make_revolution, offset_surface, transform_brep, AffineTransform,
34 NurbsCurve, NurbsSurface, Vec3, Vec4,
35};
36use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
37use std::collections::VecDeque;
38
39const TAU: f64 = std::f64::consts::TAU;
40
41mod parse;
42mod bodies;
43mod assembly;
44mod builder;
45mod geometry;
46mod styles;
47#[cfg(test)]
48mod tests;
49
50use crate::appearance::{BodyAppearance, ImportedColor};
51use parse::*;
52use bodies::*;
53use builder::*;
54use geometry::*;
55use styles::*;
56pub use assembly::{read_step_assembly, StepAssembly, StepOccurrence, StepProduct};
57
58
59// ---------------------------------------------------------------------------
60// Topology assembly
61// ---------------------------------------------------------------------------
62
63/// Public entry: parse a STEP Part 21 document and return one `BrepSolid` per
64/// MANIFOLD_SOLID_BREP, FACETED_BREP, or certified BREP_WITH_VOIDS. Every
65/// returned solid passes `validate()`.
66pub fn import_step(text: &str) -> Result<Vec<BrepSolid>, String> {
67 Ok(import_step_with_appearance(text)?.0)
68}
69
70/// [`import_step`] plus the file's PRESENTATION colours: one
71/// [`BodyAppearance`] per returned solid, in the same order.
72///
73/// This is the lane the IMPORT3D feature uses, because colour is stamped as
74/// scene metadata on the names IT chooses — see
75/// `feature_pipeline::features::import3d` and `io/appearance.rs` for the record
76/// shape. A file with no presentation entities returns default (empty)
77/// appearances, never a short vector, so callers can `zip` unconditionally.
78pub fn import_step_with_appearance(
79 text: &str,
80) -> Result<(Vec<BrepSolid>, Vec<BodyAppearance>), String> {
81 let imported = collect_step_solids(text)?;
82 if imported.solids.is_empty() {
83 return Err(imported.first_error.unwrap_or_else(|| {
84 "step_import: no MANIFOLD_SOLID_BREP/FACETED_BREP/BREP_WITH_VOIDS body imported (unsupported or invalid representation)"
85 .into()
86 }));
87 }
88 Ok((imported.solids, imported.appearances))
89}
90
91/// Like [`import_step`] but reports how many bodies failed and the first error,
92/// so callers can distinguish a fully- from a partially-imported assembly.
93pub fn import_step_report(text: &str) -> Result<(Vec<BrepSolid>, usize, Option<String>), String> {
94 let imported = collect_step_solids(text)?;
95 Ok((imported.solids, imported.failed, imported.first_error))
96}