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