axiolid_mesh_section_contract/contract.rs
1//! Portable mesh plane-section provider contract.
2
3use axiolid_contracts::{
4 Backend, CancellationGranularity, ExecutionOptions, GeomError, GeomResult, Operation,
5 ScratchRequirement,
6};
7use axiolid_core::{Frame3, Point2};
8use axiolid_mesh::TriMesh;
9
10use crate::request::{PlaneSection, SectionRequest};
11
12/// Hard bounds for one mesh section request.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct SectionLimits {
15 /// Maximum source positions inspected.
16 pub max_source_vertices: usize,
17 /// Maximum source triangles inspected.
18 pub max_source_triangles: usize,
19 /// Maximum vertices across all output contours.
20 pub max_output_vertices: usize,
21 /// Maximum output contours.
22 pub max_contours: usize,
23}
24
25impl SectionLimits {
26 /// Construct explicit source and output work limits.
27 pub const fn new(
28 max_source_vertices: usize,
29 max_source_triangles: usize,
30 max_output_vertices: usize,
31 max_contours: usize,
32 ) -> Self {
33 Self {
34 max_source_vertices,
35 max_source_triangles,
36 max_output_vertices,
37 max_contours,
38 }
39 }
40}
41
42/// One closed plane-local polyline.
43///
44/// The terminal point is implicit and must not duplicate the first point.
45#[derive(Debug, Clone, PartialEq)]
46pub struct SectionContour {
47 /// Plane-local points in traversal order.
48 pub points: Vec<Point2>,
49 /// Private invariant marker: every constructible contour is closed.
50 closed: bool,
51}
52
53impl SectionContour {
54 /// Construct a closed contour. Registry validation checks cardinality and
55 /// finite, non-duplicated coordinates after provider dispatch.
56 pub fn new(points: Vec<Point2>) -> Self {
57 Self {
58 points,
59 closed: true,
60 }
61 }
62
63 /// Whether this polyline closes from its last point back to its first.
64 pub const fn is_closed(&self) -> bool {
65 self.closed
66 }
67}
68
69/// Provenance of a plane-section approximation.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71#[non_exhaustive]
72pub enum SectionSource {
73 /// Contours were intersected with the supplied triangle mesh.
74 InputMesh,
75}
76
77/// Auditable counts for one section operation.
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct SectionEvidence {
80 /// Approximation source.
81 pub source: SectionSource,
82 /// Source triangles inspected.
83 pub source_triangles: usize,
84 /// Output contour vertices.
85 pub output_vertices: usize,
86 /// Output contours.
87 pub output_contours: usize,
88}
89
90impl SectionEvidence {
91 /// Record evidence for a section derived from the input mesh.
92 pub const fn input_mesh(
93 source_triangles: usize,
94 output_vertices: usize,
95 output_contours: usize,
96 ) -> Self {
97 Self {
98 source: SectionSource::InputMesh,
99 source_triangles,
100 output_vertices,
101 output_contours,
102 }
103 }
104
105 /// Whether this result came from the discrete input mesh.
106 pub const fn is_derived_from_input_mesh(self) -> bool {
107 matches!(self.source, SectionSource::InputMesh)
108 }
109}
110
111/// Plane-local closed section contours plus approximation evidence.
112#[derive(Debug, Clone, PartialEq)]
113pub struct SectionOutcome {
114 /// Right-handed orthonormal mapping from local `(x,y,0)` to model space.
115 pub frame: Frame3,
116 /// Closed section contours. Empty means the plane misses the solid.
117 pub contours: Vec<SectionContour>,
118 /// Source and output counts.
119 pub evidence: SectionEvidence,
120}
121
122impl SectionOutcome {
123 /// Construct a provider result. Registries validate it before returning it.
124 pub fn new(frame: Frame3, contours: Vec<SectionContour>, evidence: SectionEvidence) -> Self {
125 Self {
126 frame,
127 contours,
128 evidence,
129 }
130 }
131}
132
133/// Provider for deterministic sections of closed oriented triangle solids.
134pub trait MeshPlaneSection: Backend {
135 /// Scratch needed beyond inputs and result.
136 fn scratch_requirement(&self) -> ScratchRequirement {
137 ScratchRequirement::Unbounded
138 }
139
140 /// How finely the provider polls cancellation.
141 fn cancellation_granularity(&self) -> CancellationGranularity {
142 CancellationGranularity::None
143 }
144
145 /// Intersect one validated closed oriented mesh with `frame`'s local XY
146 /// plane. Implementations must enforce `limits` before growing output.
147 fn section(
148 &self,
149 mesh: &TriMesh,
150 frame: Frame3,
151 limits: SectionLimits,
152 options: &ExecutionOptions,
153 ) -> GeomResult<SectionOutcome>;
154
155 /// Scratch needed beyond inputs and result by
156 /// [`section_request`](Self::section_request). Defaults to
157 /// [`scratch_requirement`](Self::scratch_requirement).
158 fn request_scratch_requirement(&self) -> ScratchRequirement {
159 self.scratch_requirement()
160 }
161
162 /// Intersect one validated mesh with the request frame's local XY plane,
163 /// reporting faces lying in the plane as regions (ADR 0091). Providers
164 /// that do not implement it refuse with `Unsupported`, which lets the
165 /// registry fall back to the next provider.
166 ///
167 /// A provider may implement `SectionRole::Solid` only: it then refuses a
168 /// `SectionRole::Sheet` request with `Unsupported`, and the region
169 /// conformance suite reports `sheets_claimed == false` instead of failing.
170 ///
171 /// A provider that does not trace cut regions refuses a request that has
172 /// them with `Unsupported`; the suite then reports
173 /// `cut_regions_claimed == false`. A provider written before cut regions
174 /// existed ignores the request and answers with `cut_regions == None`: the
175 /// registry treats that answer as `Unsupported` and tries the next
176 /// provider, and the suite treats it on the first solid case as not
177 /// claimed. A provider that returns cut regions on one case and drops them
178 /// on another fails the suite.
179 fn section_request(
180 &self,
181 mesh: &TriMesh,
182 request: &SectionRequest,
183 options: &ExecutionOptions,
184 ) -> GeomResult<PlaneSection> {
185 let _ = (mesh, request, options);
186 Err(GeomError::Unsupported {
187 backend: self.descriptor().id,
188 operation: Operation::MeshPlaneSection,
189 })
190 }
191}