Skip to main content

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}