1#![forbid(unsafe_code)]
2
3use 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 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
41pub type ExactTopology = BRep<Curve3Id, Curve2Id, SurfaceId>;
43
44#[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 pub fn topology(&self) -> &ExactTopology {
64 &self.topology
65 }
66
67 pub fn curves3(&self) -> &[Curve3] {
69 &self.curves3
70 }
71
72 pub fn curves2(&self) -> &[Curve2] {
74 &self.curves2
75 }
76
77 pub fn surfaces(&self) -> &[Surface] {
79 &self.surfaces
80 }
81
82 pub fn edge_interval(&self, edge: EdgeId) -> Option<Interval> {
84 self.edge_intervals.get(&edge).copied()
85 }
86
87 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#[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 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 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 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 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 pub fn topology_mut(&mut self) -> &mut ExactTopology {
145 &mut self.topology
146 }
147
148 pub fn set_edge_interval(&mut self, edge: EdgeId, interval: Interval) {
150 self.edge_intervals.insert(edge, interval);
151 }
152
153 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 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#[non_exhaustive]
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub enum ExactBRepError {
176 Empty,
178 Topology(BRepHealth),
180 MissingEdgeCurve { edge_index: usize },
182 UnknownCurve3 { edge_index: usize },
184 MissingEdgeInterval { edge_index: usize },
186 InvalidEdgeInterval { edge_index: usize },
188 MissingPcurve { loop_index: usize, use_index: usize },
190 UnknownCurve2 { loop_index: usize, use_index: usize },
192 MissingPcurveInterval { loop_index: usize, use_index: usize },
194 InvalidPcurveInterval { loop_index: usize, use_index: usize },
196 MissingFaceSurface { face_index: usize },
198 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}