Skip to main content

axiolid_brep/
lib.rs

1#![forbid(unsafe_code)]
2
3//! Strict exact B-rep results over neutral analytic curve and surface values.
4//!
5//! [`axiolid_topology::BRep`] remains generic so graph/import clients can link
6//! their own handles. [`ExactBRep`] binds that graph to owned `Curve3`, `Curve2`,
7//! and `Surface` catalogs, then requires every support and trim span explicitly.
8//! It does not evaluate, intersect, or tessellate geometry.
9
10use std::collections::HashMap;
11use std::fmt;
12
13use axiolid_core::Interval;
14use axiolid_curve::{Curve2, Curve3};
15use axiolid_surface::Surface;
16use axiolid_topology::{audit_brep, BRep, BRepHealth, EdgeId, LoopId};
17
18macro_rules! geometry_id {
19    ($name:ident, $label:literal) => {
20        #[doc = concat!("Typed handle into the exact B-rep ", $label, " catalog.")]
21        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
22        pub struct $name(u32);
23
24        impl $name {
25            fn from_index(index: usize) -> Self {
26                Self(u32::try_from(index).expect("exact B-rep catalog exceeds u32 capacity"))
27            }
28
29            /// Zero-based catalog index.
30            pub const fn index(self) -> usize {
31                self.0 as usize
32            }
33        }
34    };
35}
36
37geometry_id!(Curve3Id, "3D curve");
38geometry_id!(Curve2Id, "2D pcurve");
39geometry_id!(SurfaceId, "surface");
40
41/// Topology whose supports are typed references into [`ExactBRep`] catalogs.
42pub type ExactTopology = BRep<Curve3Id, Curve2Id, SurfaceId>;
43
44/// Owned, analytic boundary representation.
45///
46/// Every edge and pcurve use has a finite, non-zero native parameter span. An
47/// interval is deliberately owned here rather than inferred from endpoints:
48/// periodic supports, reversed pcurves, and parameter re-mapping are not
49/// recoverable from coordinates. The result may be an open sheet; an eventual
50/// exact-solid contract will additionally require a selected closed shell.
51#[derive(Debug, Clone, PartialEq)]
52pub struct ExactBRep {
53    topology: ExactTopology,
54    curves3: Vec<Curve3>,
55    curves2: Vec<Curve2>,
56    surfaces: Vec<Surface>,
57    edge_intervals: HashMap<EdgeId, Interval>,
58    pcurve_intervals: HashMap<(LoopId, usize), Interval>,
59}
60
61impl ExactBRep {
62    /// Structural topology with typed support handles.
63    pub fn topology(&self) -> &ExactTopology {
64        &self.topology
65    }
66
67    /// Owned exact 3D curve supports.
68    pub fn curves3(&self) -> &[Curve3] {
69        &self.curves3
70    }
71
72    /// Owned exact 2D trim-curve supports.
73    pub fn curves2(&self) -> &[Curve2] {
74        &self.curves2
75    }
76
77    /// Owned exact support surfaces.
78    pub fn surfaces(&self) -> &[Surface] {
79        &self.surfaces
80    }
81
82    /// Native parameter span oriented from an edge's start vertex to its end.
83    pub fn edge_interval(&self, edge: EdgeId) -> Option<Interval> {
84        self.edge_intervals.get(&edge).copied()
85    }
86
87    /// Native parameter span for an edge use's pcurve in loop traversal order.
88    pub fn pcurve_interval(&self, loop_id: LoopId, use_index: usize) -> Option<Interval> {
89        self.pcurve_intervals.get(&(loop_id, use_index)).copied()
90    }
91}
92
93/// Mutable assembly state that can only yield an [`ExactBRep`] after validation.
94#[derive(Debug, Clone, Default, PartialEq)]
95pub struct ExactBRepBuilder {
96    topology: ExactTopology,
97    curves3: Vec<Curve3>,
98    curves2: Vec<Curve2>,
99    surfaces: Vec<Surface>,
100    edge_intervals: HashMap<EdgeId, Interval>,
101    pcurve_intervals: HashMap<(LoopId, usize), Interval>,
102}
103
104impl ExactBRepBuilder {
105    /// Fallibly reserve owned support and interval catalogs for bounded assembly.
106    pub fn try_reserve(
107        &mut self,
108        curves3: usize,
109        curves2: usize,
110        surfaces: usize,
111        edge_intervals: usize,
112        pcurve_intervals: usize,
113    ) -> Result<(), std::collections::TryReserveError> {
114        self.curves3.try_reserve_exact(curves3)?;
115        self.curves2.try_reserve_exact(curves2)?;
116        self.surfaces.try_reserve_exact(surfaces)?;
117        self.edge_intervals.try_reserve(edge_intervals)?;
118        self.pcurve_intervals.try_reserve(pcurve_intervals)?;
119        Ok(())
120    }
121
122    /// Store an exact 3D curve support.
123    pub fn add_curve3(&mut self, curve: Curve3) -> Curve3Id {
124        let id = Curve3Id::from_index(self.curves3.len());
125        self.curves3.push(curve);
126        id
127    }
128
129    /// Store an exact 2D pcurve support.
130    pub fn add_curve2(&mut self, curve: Curve2) -> Curve2Id {
131        let id = Curve2Id::from_index(self.curves2.len());
132        self.curves2.push(curve);
133        id
134    }
135
136    /// Store an exact surface support.
137    pub fn add_surface(&mut self, surface: Surface) -> SurfaceId {
138        let id = SurfaceId::from_index(self.surfaces.len());
139        self.surfaces.push(surface);
140        id
141    }
142
143    /// Mutable typed topology under construction. [`Self::finish`] validates it.
144    pub fn topology_mut(&mut self) -> &mut ExactTopology {
145        &mut self.topology
146    }
147
148    /// State an edge's finite native curve span.
149    pub fn set_edge_interval(&mut self, edge: EdgeId, interval: Interval) {
150        self.edge_intervals.insert(edge, interval);
151    }
152
153    /// State an edge use's finite native pcurve span in its loop traversal order.
154    pub fn set_pcurve_interval(&mut self, loop_id: LoopId, use_index: usize, interval: Interval) {
155        self.pcurve_intervals.insert((loop_id, use_index), interval);
156    }
157
158    /// Validate and freeze the exact B-rep result.
159    pub fn finish(self) -> Result<ExactBRep, ExactBRepError> {
160        validate(&self)?;
161        Ok(ExactBRep {
162            topology: self.topology,
163            curves3: self.curves3,
164            curves2: self.curves2,
165            surfaces: self.surfaces,
166            edge_intervals: self.edge_intervals,
167            pcurve_intervals: self.pcurve_intervals,
168        })
169    }
170}
171
172/// Why exact B-rep assembly was refused.
173#[non_exhaustive]
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub enum ExactBRepError {
176    /// A result without faces is not a boundary representation.
177    Empty,
178    /// Generic topology has unresolved handles or invalid loop/face structure.
179    Topology(BRepHealth),
180    /// An edge did not state its exact three-dimensional support.
181    MissingEdgeCurve { edge_index: usize },
182    /// An edge support handle does not resolve in the 3D curve catalog.
183    UnknownCurve3 { edge_index: usize },
184    /// An edge did not state its native support-curve interval.
185    MissingEdgeInterval { edge_index: usize },
186    /// An edge support interval was non-finite or zero-length.
187    InvalidEdgeInterval { edge_index: usize },
188    /// An edge use did not state its trim curve in the owning face's parameters.
189    MissingPcurve { loop_index: usize, use_index: usize },
190    /// A pcurve support handle does not resolve in the 2D curve catalog.
191    UnknownCurve2 { loop_index: usize, use_index: usize },
192    /// A pcurve did not state its native interval.
193    MissingPcurveInterval { loop_index: usize, use_index: usize },
194    /// A pcurve interval was non-finite or zero-length.
195    InvalidPcurveInterval { loop_index: usize, use_index: usize },
196    /// A face did not state its exact support surface.
197    MissingFaceSurface { face_index: usize },
198    /// A face support handle does not resolve in the surface catalog.
199    UnknownSurface { face_index: usize },
200}
201
202impl fmt::Display for ExactBRepError {
203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204        match self {
205            Self::Empty => f.write_str("exact B-rep requires at least one face"),
206            Self::Topology(_) => f.write_str("exact B-rep topology is structurally invalid"),
207            Self::MissingEdgeCurve { edge_index } => {
208                write!(f, "edge {edge_index} lacks a 3D curve")
209            }
210            Self::UnknownCurve3 { edge_index } => {
211                write!(f, "edge {edge_index} has an unknown 3D curve")
212            }
213            Self::MissingEdgeInterval { edge_index } => {
214                write!(f, "edge {edge_index} lacks a curve interval")
215            }
216            Self::InvalidEdgeInterval { edge_index } => {
217                write!(f, "edge {edge_index} has an invalid curve interval")
218            }
219            Self::MissingPcurve {
220                loop_index,
221                use_index,
222            } => write!(f, "loop {loop_index} use {use_index} lacks a pcurve"),
223            Self::UnknownCurve2 {
224                loop_index,
225                use_index,
226            } => write!(f, "loop {loop_index} use {use_index} has an unknown pcurve"),
227            Self::MissingPcurveInterval {
228                loop_index,
229                use_index,
230            } => write!(
231                f,
232                "loop {loop_index} use {use_index} lacks a pcurve interval"
233            ),
234            Self::InvalidPcurveInterval {
235                loop_index,
236                use_index,
237            } => write!(
238                f,
239                "loop {loop_index} use {use_index} has an invalid pcurve interval"
240            ),
241            Self::MissingFaceSurface { face_index } => {
242                write!(f, "face {face_index} lacks a support surface")
243            }
244            Self::UnknownSurface { face_index } => {
245                write!(f, "face {face_index} has an unknown support surface")
246            }
247        }
248    }
249}
250
251impl std::error::Error for ExactBRepError {}
252
253fn validate(value: &ExactBRepBuilder) -> Result<(), ExactBRepError> {
254    if value.topology.faces().is_empty() {
255        return Err(ExactBRepError::Empty);
256    }
257    let health = audit_brep(&value.topology);
258    if !health.is_tessellable() {
259        return Err(ExactBRepError::Topology(health));
260    }
261    for (edge_index, edge) in value.topology.edges().iter().enumerate() {
262        let Some(curve) = edge.curve else {
263            return Err(ExactBRepError::MissingEdgeCurve { edge_index });
264        };
265        if curve.index() >= value.curves3.len() {
266            return Err(ExactBRepError::UnknownCurve3 { edge_index });
267        }
268        let Some(edge_id) = value.topology.edge_id_at(edge_index) else {
269            return Err(ExactBRepError::MissingEdgeInterval { edge_index });
270        };
271        let Some(interval) = value.edge_intervals.get(&edge_id) else {
272            return Err(ExactBRepError::MissingEdgeInterval { edge_index });
273        };
274        if !valid_interval(*interval) {
275            return Err(ExactBRepError::InvalidEdgeInterval { edge_index });
276        }
277    }
278    for (loop_index, loop_) in value.topology.loops().iter().enumerate() {
279        let Some(loop_id) = value.topology.loop_id_at(loop_index) else {
280            return Err(ExactBRepError::Topology(health));
281        };
282        for (use_index, use_) in loop_.edges.iter().enumerate() {
283            let Some(curve) = use_.pcurve else {
284                return Err(ExactBRepError::MissingPcurve {
285                    loop_index,
286                    use_index,
287                });
288            };
289            if curve.index() >= value.curves2.len() {
290                return Err(ExactBRepError::UnknownCurve2 {
291                    loop_index,
292                    use_index,
293                });
294            }
295            let Some(interval) = value.pcurve_intervals.get(&(loop_id, use_index)) else {
296                return Err(ExactBRepError::MissingPcurveInterval {
297                    loop_index,
298                    use_index,
299                });
300            };
301            if !valid_interval(*interval) {
302                return Err(ExactBRepError::InvalidPcurveInterval {
303                    loop_index,
304                    use_index,
305                });
306            }
307        }
308    }
309    for (face_index, face) in value.topology.faces().iter().enumerate() {
310        let Some(surface) = face.surface else {
311            return Err(ExactBRepError::MissingFaceSurface { face_index });
312        };
313        if surface.index() >= value.surfaces.len() {
314            return Err(ExactBRepError::UnknownSurface { face_index });
315        }
316    }
317    Ok(())
318}
319
320fn valid_interval(interval: Interval) -> bool {
321    interval.start.is_finite() && interval.end.is_finite() && interval.length() > 0.0
322}