1use crate::curve::{curve_to_plane_parameters, parameter_line};
2use crate::topology::{
3 BrepSolid, CoedgeRecord, EdgeRecord, FaceRecord, LoopRecord, ShellRecord, VertexRecord,
4};
5use crate::{
6 make_cone_surface, make_line, make_plane, make_sphere_surface_framed, make_torus_surface,
7 Vec3,
8};
9
10struct CertifiedPrimitive { solid: BrepSolid }
15impl CertifiedPrimitive {
17 fn accept(self) -> BrepSolid {
18 primitive_check_count(true);
19 self.solid
20 }
21}
22
23pub(crate) fn primitive_certificate_enabled() -> bool {
24 switch!(live "BREP_CERTIFIED_PRIMITIVES", true)
27}
28
29pub(crate) fn primitive_domain(origin: Vec3, dimensions: &[f64], axis: Option<Vec3>) -> bool {
39 if !dimensions.iter().all(|v| v.is_finite() && (1e-3..=1e3).contains(v)) { return false; }
40 let minimum = dimensions.iter().copied().fold(f64::INFINITY, f64::min);
41 if ![origin.x, origin.y, origin.z].iter().all(|v| v.is_finite() && v.abs() <= minimum * 1000.) { return false; }
42 axis.is_none_or(|a| [a.x, a.y, a.z].iter().all(|v| v.is_finite())
43 && (0.25..=4.).contains(&a.length_squared()))
44}
45
46pub(crate) fn primitive_check_count(certified: bool) {
47 let _ = certified;
48}
49
50
51fn validated(solid: BrepSolid, label: &'static str) -> Result<BrepSolid, String> {
52 let mut profile = crate::loft_topology::ConstructionProfile::new(label);
53 primitive_check_count(false);
54 let issues = solid.validate();
55 profile.lap("validation");
56 if issues.is_empty() {
57 Ok(solid)
58 } else {
59 Err(format!(
60 "Rust {label} builder produced invalid topology: {issues:?}"
61 ))
62 }
63}
64
65pub fn make_sphere_brep(center: Vec3, radius: f64, polar_axis: Vec3) -> Result<BrepSolid, String> {
66 make_sphere_brep_framed(center, radius, polar_axis, None)
67}
68
69pub fn make_sphere_brep_framed(
76 center: Vec3,
77 radius: f64,
78 polar_axis: Vec3,
79 seam_direction: Option<Vec3>,
80) -> Result<BrepSolid, String> {
81 let mut profile = crate::loft_topology::ConstructionProfile::new("sphere");
82 let surface = make_sphere_surface_framed(center, radius, polar_axis, seam_direction)?;
83 let south = surface.evaluate(0.0, 0.0)?;
84 let north = surface.evaluate(0.0, 1.0)?;
85 let seam = surface.iso_curve_u(0.0)?;
86 let solid = BrepSolid {
87 mass_properties_cache: Default::default(),
88 cells: Default::default(),
89 id: 120,
90 vertices: vec![
91 VertexRecord {
92 id: 1,
93 point: south,
94 },
95 VertexRecord {
96 id: 2,
97 point: north,
98 },
99 ],
100 edges: vec![
101 EdgeRecord {
102 id: 10,
103 curve: seam,
104 t0: 0.0,
105 t1: 1.0,
106 start_vertex_id: 1,
107 end_vertex_id: 2,
108 degenerate: false,
109 name: None,
110 },
111 EdgeRecord {
112 id: 11,
113 curve: make_line(south, south)?,
114 t0: 0.0,
115 t1: 1.0,
116 start_vertex_id: 1,
117 end_vertex_id: 1,
118 degenerate: true,
119 name: None,
120 },
121 EdgeRecord {
122 id: 12,
123 curve: make_line(north, north)?,
124 t0: 0.0,
125 t1: 1.0,
126 start_vertex_id: 2,
127 end_vertex_id: 2,
128 degenerate: true,
129 name: None,
130 },
131 ],
132 shells: vec![ShellRecord {
133 id: 119,
134 faces: vec![FaceRecord {
135 id: 118,
136 surface,
137 same_sense: true,
138 loops: vec![LoopRecord {
139 id: 117,
140 coedges: vec![
141 CoedgeRecord {
142 id: 101,
143 edge_id: 11,
144 forward: true,
145 pcurve: parameter_line(0.0, 0.0, 1.0, 0.0)?,
146 },
147 CoedgeRecord {
148 id: 102,
149 edge_id: 10,
150 forward: true,
151 pcurve: parameter_line(1.0, 0.0, 1.0, 1.0)?,
152 },
153 CoedgeRecord {
154 id: 103,
155 edge_id: 12,
156 forward: true,
157 pcurve: parameter_line(1.0, 1.0, 0.0, 1.0)?,
158 },
159 CoedgeRecord {
160 id: 104,
161 edge_id: 10,
162 forward: false,
163 pcurve: parameter_line(0.0, 1.0, 0.0, 0.0)?,
164 },
165 ],
166 }],
167 name: None,
168 }],
169 }],
170 genus: 0,
171 kind: crate::BodyKind::Solid,
172 };
173 profile.lap("construct");
174 if primitive_certificate_enabled() && seam_direction.is_none()
177 && primitive_domain(center, &[radius], Some(polar_axis)) {
178 return Ok(CertifiedPrimitive { solid }.accept());
179 }
180 validated(solid, "sphere")
181}
182
183pub fn make_torus_brep(
184 center: Vec3,
185 axis_direction: Vec3,
186 major_radius: f64,
187 minor_radius: f64,
188) -> Result<BrepSolid, String> {
189 let mut profile = crate::loft_topology::ConstructionProfile::new("torus");
190 let surface = make_torus_surface(center, axis_direction, major_radius, minor_radius)?;
191 let corner = surface.evaluate(0.0, 0.0)?;
192 let major_curve = surface.iso_curve_v(0.0)?;
193 let tube_curve = surface.iso_curve_u(0.0)?;
194 let solid = BrepSolid {
195 mass_properties_cache: Default::default(),
196 cells: Default::default(),
197 id: 130,
198 vertices: vec![VertexRecord {
199 id: 1,
200 point: corner,
201 }],
202 edges: vec![
203 EdgeRecord {
204 id: 10,
205 curve: major_curve,
206 t0: 0.0,
207 t1: 1.0,
208 start_vertex_id: 1,
209 end_vertex_id: 1,
210 degenerate: false,
211 name: None,
212 },
213 EdgeRecord {
214 id: 11,
215 curve: tube_curve,
216 t0: 0.0,
217 t1: 1.0,
218 start_vertex_id: 1,
219 end_vertex_id: 1,
220 degenerate: false,
221 name: None,
222 },
223 ],
224 shells: vec![ShellRecord {
225 id: 129,
226 faces: vec![FaceRecord {
227 id: 128,
228 surface,
229 same_sense: true,
230 loops: vec![LoopRecord {
231 id: 127,
232 coedges: vec![
233 CoedgeRecord {
234 id: 101,
235 edge_id: 10,
236 forward: true,
237 pcurve: parameter_line(0.0, 0.0, 1.0, 0.0)?,
238 },
239 CoedgeRecord {
240 id: 102,
241 edge_id: 11,
242 forward: true,
243 pcurve: parameter_line(1.0, 0.0, 1.0, 1.0)?,
244 },
245 CoedgeRecord {
246 id: 103,
247 edge_id: 10,
248 forward: false,
249 pcurve: parameter_line(1.0, 1.0, 0.0, 1.0)?,
250 },
251 CoedgeRecord {
252 id: 104,
253 edge_id: 11,
254 forward: false,
255 pcurve: parameter_line(0.0, 1.0, 0.0, 0.0)?,
256 },
257 ],
258 }],
259 name: None,
260 }],
261 }],
262 genus: 1,
263 kind: crate::BodyKind::Solid,
264 };
265 profile.lap("construct");
266 if primitive_certificate_enabled()
269 && major_radius - minor_radius >= minor_radius * 1e-3
270 && primitive_domain(center, &[major_radius, minor_radius], Some(axis_direction)) {
271 return Ok(CertifiedPrimitive { solid }.accept());
272 }
273 validated(solid, "torus")
274}
275
276pub fn make_cone_brep(
277 base: Vec3,
278 axis_direction: Vec3,
279 radius_bottom: f64,
280 radius_top: f64,
281 height: f64,
282) -> Result<BrepSolid, String> {
283 if radius_bottom <= 0.0 || radius_top < 0.0 || height <= 0.0 {
284 return Err("makeConeSolid: invalid radius or height".into());
285 }
286 let axis = axis_direction.normalized()?;
287 let side = make_cone_surface(base, axis, radius_bottom, radius_top, height)?;
288 let bottom_point = side.evaluate(0.0, 0.0)?;
289 let bottom_circle = side.iso_curve_v(0.0)?;
290 let seam = side.iso_curve_u(0.0)?;
291 let x_axis = axis.perpendicular()?;
292 let y_axis = axis.cross(x_axis).normalized()?;
293 let bottom_origin = base
294 .add(x_axis.scale(-radius_bottom))
295 .add(y_axis.scale(-radius_bottom));
296 let bottom_plane = make_plane(
297 bottom_origin,
298 x_axis,
299 y_axis,
300 2.0 * radius_bottom,
301 2.0 * radius_bottom,
302 )?;
303 let bottom_pcurve =
304 curve_to_plane_parameters(&bottom_circle, bottom_origin, x_axis, y_axis)?.reversed()?;
305 let bottom_face = FaceRecord {
306 id: 108,
307 surface: bottom_plane,
308 same_sense: false,
309 loops: vec![LoopRecord {
310 id: 107,
311 coedges: vec![CoedgeRecord {
312 id: 106,
313 edge_id: 10,
314 forward: false,
315 pcurve: bottom_pcurve,
316 }],
317 }],
318 name: None,
319 };
320
321 if radius_top <= 1e-7 {
322 let apex = side.evaluate(0.0, 1.0)?;
323 return validated(
324 BrepSolid {
325 mass_properties_cache: Default::default(),
326 cells: Default::default(),
327 id: 140,
328 vertices: vec![
329 VertexRecord {
330 id: 1,
331 point: bottom_point,
332 },
333 VertexRecord { id: 2, point: apex },
334 ],
335 edges: vec![
336 EdgeRecord {
337 id: 10,
338 curve: bottom_circle,
339 t0: 0.0,
340 t1: 1.0,
341 start_vertex_id: 1,
342 end_vertex_id: 1,
343 degenerate: false,
344 name: None,
345 },
346 EdgeRecord {
347 id: 11,
348 curve: seam,
349 t0: 0.0,
350 t1: 1.0,
351 start_vertex_id: 1,
352 end_vertex_id: 2,
353 degenerate: false,
354 name: None,
355 },
356 EdgeRecord {
357 id: 12,
358 curve: make_line(apex, apex)?,
359 t0: 0.0,
360 t1: 1.0,
361 start_vertex_id: 2,
362 end_vertex_id: 2,
363 degenerate: true,
364 name: None,
365 },
366 ],
367 shells: vec![ShellRecord {
368 id: 139,
369 faces: vec![
370 FaceRecord {
371 id: 105,
372 surface: side,
373 same_sense: true,
374 loops: vec![LoopRecord {
375 id: 100,
376 coedges: vec![
377 CoedgeRecord {
378 id: 101,
379 edge_id: 10,
380 forward: true,
381 pcurve: parameter_line(0.0, 0.0, 1.0, 0.0)?,
382 },
383 CoedgeRecord {
384 id: 102,
385 edge_id: 11,
386 forward: true,
387 pcurve: parameter_line(1.0, 0.0, 1.0, 1.0)?,
388 },
389 CoedgeRecord {
390 id: 103,
391 edge_id: 12,
392 forward: true,
393 pcurve: parameter_line(1.0, 1.0, 0.0, 1.0)?,
394 },
395 CoedgeRecord {
396 id: 104,
397 edge_id: 11,
398 forward: false,
399 pcurve: parameter_line(0.0, 1.0, 0.0, 0.0)?,
400 },
401 ],
402 }],
403 name: None,
404 },
405 bottom_face,
406 ],
407 }],
408 genus: 0,
409 kind: crate::BodyKind::Solid,
410 },
411 "cone",
412 );
413 }
414
415 let top_point = side.evaluate(0.0, 1.0)?;
416 let top_circle = side.iso_curve_v(1.0)?;
417 let top_origin = base
418 .add(axis.scale(height))
419 .add(x_axis.scale(-radius_top))
420 .add(y_axis.scale(-radius_top));
421 let top_plane = make_plane(
422 top_origin,
423 x_axis,
424 y_axis,
425 2.0 * radius_top,
426 2.0 * radius_top,
427 )?;
428 let top_pcurve = curve_to_plane_parameters(&top_circle, top_origin, x_axis, y_axis)?;
429 validated(
430 BrepSolid {
431 mass_properties_cache: Default::default(),
432 cells: Default::default(),
433 id: 150,
434 vertices: vec![
435 VertexRecord {
436 id: 1,
437 point: bottom_point,
438 },
439 VertexRecord {
440 id: 2,
441 point: top_point,
442 },
443 ],
444 edges: vec![
445 EdgeRecord {
446 id: 10,
447 curve: bottom_circle,
448 t0: 0.0,
449 t1: 1.0,
450 start_vertex_id: 1,
451 end_vertex_id: 1,
452 degenerate: false,
453 name: None,
454 },
455 EdgeRecord {
456 id: 11,
457 curve: top_circle,
458 t0: 0.0,
459 t1: 1.0,
460 start_vertex_id: 2,
461 end_vertex_id: 2,
462 degenerate: false,
463 name: None,
464 },
465 EdgeRecord {
466 id: 12,
467 curve: seam,
468 t0: 0.0,
469 t1: 1.0,
470 start_vertex_id: 1,
471 end_vertex_id: 2,
472 degenerate: false,
473 name: None,
474 },
475 ],
476 shells: vec![ShellRecord {
477 id: 149,
478 faces: vec![
479 FaceRecord {
480 id: 105,
481 surface: side,
482 same_sense: true,
483 loops: vec![LoopRecord {
484 id: 100,
485 coedges: vec![
486 CoedgeRecord {
487 id: 101,
488 edge_id: 10,
489 forward: true,
490 pcurve: parameter_line(0.0, 0.0, 1.0, 0.0)?,
491 },
492 CoedgeRecord {
493 id: 102,
494 edge_id: 12,
495 forward: true,
496 pcurve: parameter_line(1.0, 0.0, 1.0, 1.0)?,
497 },
498 CoedgeRecord {
499 id: 103,
500 edge_id: 11,
501 forward: false,
502 pcurve: parameter_line(1.0, 1.0, 0.0, 1.0)?,
503 },
504 CoedgeRecord {
505 id: 104,
506 edge_id: 12,
507 forward: false,
508 pcurve: parameter_line(0.0, 1.0, 0.0, 0.0)?,
509 },
510 ],
511 }],
512 name: None,
513 },
514 bottom_face,
515 FaceRecord {
516 id: 111,
517 surface: top_plane,
518 same_sense: true,
519 loops: vec![LoopRecord {
520 id: 110,
521 coedges: vec![CoedgeRecord {
522 id: 109,
523 edge_id: 11,
524 forward: true,
525 pcurve: top_pcurve,
526 }],
527 }],
528 name: None,
529 },
530 ],
531 }],
532 genus: 0,
533 kind: crate::BodyKind::Solid,
534 },
535 "cone frustum",
536 )
537}
538
539