Skip to main content

brep_kernel/brep/
analytic_topology.rs

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
10// A certificate owns the just-built body; it is consumed at this module's
11// acceptance boundary. It is never attached to mutable BrepSolid data or
12// serialized, and no caller can certify an imported or subsequently edited body.
13// Primitive certificate type: begin
14struct CertifiedPrimitive { solid: BrepSolid }
15// Primitive certificate type: end
16impl 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    // Promoted after the 98/144-case byte census. Explicit =0 restores the
25    // full constructor validator; eligibility never changes geometry or errors.
26    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
33// This is an eligibility bound, never a new refusal: every input outside it
34// takes the original validator. Keep floating point conditioning far from the
35// model/edge coincidence bands: dimensions 1e-3..1e3, origin at most 1000
36// times the smallest dimension, and axes of order unity. Surface/curve builders
37// still check finite homogeneous control points, positive weights and knots.
38// The trusted builders supply incidence and exact shared boundary construction:
39// box affine faces; cylinder two iso-curves and their plane images; sphere
40// one periodic chart with two pole edges; ring torus two periodic seams.
41// Neither a certificate nor this eligibility predicate accepts arbitrary bodies.
42pub(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
73/// A sphere solid whose SEAM direction is chosen as well as its polar axis — see
74/// [`make_sphere_surface_framed`]. The topology is identical either way (one
75/// face, one pole-to-pole seam edge, two degenerate pole edges); only WHERE on
76/// the ball the seam and the poles sit changes. A caller that will trim the ball
77/// uses it to hide both inside the removed region, so the surviving face has no
78/// edge bordering itself.
79pub 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    // The pole/seam topology above is fixed. A caller-selected seam keeps the
179    // full validator until that separate construction is certified as well.
180    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    // Positive clearance makes the periodic tube a ring torus, not a horn or
271    // spindle. Near-horn numerics keep the original validation path.
272    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