Skip to main content

axiolid_construct/
trimmed_intersection_types.rs

1use axiolid_brep::{Curve2Id, ExactBRep};
2use axiolid_core::{Interval, Scalar};
3use axiolid_nurbs::{
4    CertifiedSurfaceSurfaceIntersection3, CertifiedSurfaceSurfaceIntersectionOptions,
5    TransverseSurfaceSurfaceTrace3,
6};
7use axiolid_topology::{EdgeId, FaceId};
8
9/// Explicit resource and residual policy for certified topology integration.
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct CertifiedSurfacePairSplitOptions {
12    /// Policy for the underlying certified surface/surface query.
13    intersection: CertifiedSurfaceSurfaceIntersectionOptions,
14    /// Largest certified surface/carrier residual accepted by the B-rep handoff.
15    max_surface_residual: Scalar,
16}
17
18impl CertifiedSurfacePairSplitOptions {
19    /// Validate and construct a split policy.
20    pub fn new(
21        intersection: CertifiedSurfaceSurfaceIntersectionOptions,
22        max_surface_residual: Scalar,
23    ) -> Result<Self, axiolid_contracts::GeomError> {
24        if !max_surface_residual.is_finite() || max_surface_residual <= 0.0 {
25            return Err(axiolid_contracts::GeomError::InvalidInput(
26                "surface-pair split residual must be finite and positive".into(),
27            ));
28        }
29        Ok(Self {
30            intersection,
31            max_surface_residual,
32        })
33    }
34
35    pub(super) fn intersection_options(self) -> CertifiedSurfaceSurfaceIntersectionOptions {
36        self.intersection
37    }
38
39    pub(super) fn max_surface_residual(self) -> Scalar {
40        self.max_surface_residual
41    }
42}
43
44impl Default for CertifiedSurfacePairSplitOptions {
45    fn default() -> Self {
46        Self {
47            intersection: CertifiedSurfaceSurfaceIntersectionOptions::default(),
48            max_surface_residual: 1.0e-7,
49        }
50    }
51}
52
53/// Which input surface is partitioned by the certified chord.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum SurfacePairMember {
56    /// The first function argument.
57    First,
58    /// The second function argument.
59    Second,
60}
61
62/// A certified edge embedded in a face without pretending it is a closed trim.
63#[derive(Debug, Clone, Copy, PartialEq)]
64pub struct EmbeddedFaceCurve {
65    /// Unsplit face that contains the intersection chord.
66    pub face: FaceId,
67    /// Shared model-space intersection edge.
68    pub edge: EdgeId,
69    /// Surface-parameter image from edge start to edge end.
70    pub pcurve: Curve2Id,
71    /// Native pcurve interval, oriented from edge start to edge end.
72    pub interval: Interval,
73}
74
75/// Validated analytic B-rep arrangement for one certified finite trace.
76#[derive(Debug, Clone, PartialEq)]
77pub struct CertifiedTrimmedSurfacePair3 {
78    /// Strict analytic B-rep containing three closed faces.
79    pub brep: ExactBRep,
80    /// The same edge used by the two split-face loops and embedded in the other face.
81    pub intersection_edge: EdgeId,
82    /// Input member whose rectangular face was split.
83    pub split_surface: SurfacePairMember,
84    /// Two deterministic trimmed faces on `split_surface`.
85    pub split_faces: [FaceId; 2],
86    /// Rectangular face that the finite chord does not partition.
87    pub unsplit_face: FaceId,
88    /// Explicit interior attachment on `unsplit_face`.
89    pub embedded_curve: EmbeddedFaceCurve,
90    /// Original certified bounded trace; no endpoint is widened by construction.
91    pub trace: TransverseSurfaceSurfaceTrace3,
92    /// Conservative global carrier-to-surface residual bound.
93    pub max_surface_residual_upper_bound: Scalar,
94    /// Certified patch pairs processed by the intersection query.
95    pub visited_patch_pairs: u32,
96    /// Bounded boundary queries used by the intersection query.
97    pub boundary_queries: u32,
98}
99
100/// Validated arrangement for a chord that partitions BOTH patches.
101///
102/// Four closed trimmed faces share one intersection edge: two on each
103/// input surface. Unlike [`CertifiedTrimmedSurfacePair3`] there is no
104/// asymmetry to record -- neither patch merely contains the chord.
105#[derive(Debug, Clone, PartialEq)]
106pub struct CertifiedDualTrimmedSurfacePair3 {
107    /// Strict analytic B-rep containing four closed faces.
108    pub brep: ExactBRep,
109    /// The edge shared by all four trimmed loops.
110    pub intersection_edge: EdgeId,
111    /// Two deterministic trimmed faces on the first input surface.
112    pub first_faces: [FaceId; 2],
113    /// Two deterministic trimmed faces on the second input surface.
114    pub second_faces: [FaceId; 2],
115    /// Original certified bounded trace; no endpoint is widened.
116    pub trace: TransverseSurfaceSurfaceTrace3,
117    /// Conservative global carrier-to-surface residual bound.
118    pub max_surface_residual_upper_bound: Scalar,
119    /// Certified patch pairs processed by the intersection query.
120    pub visited_patch_pairs: u32,
121    /// Bounded boundary queries used by the intersection query.
122    pub boundary_queries: u32,
123}
124
125/// Why valid geometry could not be promoted to a closed trimmed arrangement.
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub enum SurfacePairSplitUnresolvedReason {
128    /// The underlying certified query retained uncertainty.
129    IntersectionUnresolved,
130    /// The query did not produce exactly one finite trace.
131    UnsupportedTraceCount,
132    /// Endpoint ownership did not identify exactly one partitioned rectangle.
133    ///
134    /// The arrangement may still be constructible; this crate has not
135    /// implemented it. Distinct from [`Self::NoPartitionExists`], which is a
136    /// proof that no arrangement exists at all.
137    UnsupportedEndpointOwnership,
138    /// Proven: this trace cannot partition either patch, so no split exists.
139    ///
140    /// A trace that stops strictly inside a patch is a slit, not a partition:
141    /// the face stays simply connected, exactly as a slot cut halfway into a
142    /// sheet leaves one piece. When that holds on *both* patches, neither can
143    /// become two closed trimmed faces and retrying with different options or
144    /// tighter tolerances cannot change the answer.
145    ///
146    /// This is terminal. Callers should stop rather than escalate.
147    NoPartitionExists,
148    /// The certified carrier residual exceeded explicit policy.
149    ResidualExceedsPolicy,
150    /// Representative parameters or carrier were finite but degenerate for topology.
151    DegenerateRepresentative,
152}
153
154/// Certified topology-integration outcome.
155///
156/// The success payload stays inline deliberately: boxing it would add an
157/// infallible allocation after all certified construction allocations have
158/// already been made fallible and bounded.
159#[allow(clippy::large_enum_variant)]
160#[derive(Debug, Clone, PartialEq)]
161#[non_exhaustive]
162pub enum CertifiedSurfacePairSplit3 {
163    /// Conservative proof that the bounded patches do not intersect.
164    Empty {
165        /// Certified patch pairs processed.
166        visited_patch_pairs: u32,
167        /// Bounded boundary queries used.
168        boundary_queries: u32,
169    },
170    /// One finite trace integrated into a strict trimmed arrangement.
171    Split(CertifiedTrimmedSurfacePair3),
172    /// A chord that partitions BOTH patches, so each becomes two faces.
173    ///
174    /// Distinct from [`Self::Split`] because there is no unsplit face and
175    /// no embedded curve: the chord is a real trim boundary on both sides,
176    /// not an interior annotation on one. Representing it as `Split` would
177    /// force naming one patch the 'owner' and lying about the other.
178    DualSplit(CertifiedDualTrimmedSurfacePair3),
179    /// Geometry remains usable, but no B-rep split was invented.
180    Unresolved {
181        /// Original intersection evidence retained without widening.
182        intersection: CertifiedSurfaceSurfaceIntersection3,
183        /// Topology-specific refusal reason.
184        reason: SurfacePairSplitUnresolvedReason,
185    },
186}