Skip to main content

brep_kernel/brep/
topology.rs

1use crate::{
2    make_cylinder_surface, make_line, make_plane, KernelTolerances, NurbsCurve, NurbsSurface, Vec2,
3    Vec3, Vec4,
4};
5use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
6use serde::{Deserialize, Serialize};
7
8#[derive(Clone, Debug, Deserialize, Serialize)]
9pub struct VertexRecord {
10    pub id: u64,
11    pub point: Vec3,
12}
13
14#[derive(Clone, Debug, Deserialize, Serialize)]
15pub struct EdgeRecord {
16    pub id: u64,
17    pub curve: NurbsCurve,
18    pub t0: f64,
19    pub t1: f64,
20    pub start_vertex_id: u64,
21    pub end_vertex_id: u64,
22    #[serde(default)]
23    pub degenerate: bool,
24    /// Persistent name carried through splits, welds, and booleans so the
25    /// application never has to re-derive edge identity geometrically.
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub name: Option<String>,
28}
29
30#[derive(Clone, Debug, Deserialize, Serialize)]
31pub struct CoedgeRecord {
32    pub id: u64,
33    pub edge_id: u64,
34    pub forward: bool,
35    pub pcurve: NurbsCurve,
36}
37
38#[derive(Clone, Debug, Deserialize, Serialize)]
39pub struct LoopRecord {
40    pub id: u64,
41    pub coedges: Vec<CoedgeRecord>,
42}
43
44#[derive(Clone, Debug, Deserialize, Serialize)]
45pub struct FaceRecord {
46    pub id: u64,
47    pub surface: NurbsSurface,
48    pub same_sense: bool,
49    pub loops: Vec<LoopRecord>,
50    /// Persistent name propagated through booleans (split fragments get
51    /// deterministic `_1`, `_2` suffixes; merges keep the first name).
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub name: Option<String>,
54}
55
56#[derive(Clone, Debug, Deserialize, Serialize)]
57pub struct ShellRecord {
58    pub id: u64,
59    pub faces: Vec<FaceRecord>,
60}
61
62/// A declared closed region of a Cellular body. Face senses are relative
63/// to the body's face records; a shared wall has opposite senses in its
64/// two cells. `void` distinguishes a void region from material.
65#[derive(Clone, Debug, Deserialize, Serialize)]
66pub struct CellRecord {
67    pub id: u64,
68    pub faces: Vec<(u64, bool)>,
69    pub void: bool,
70}
71
72#[derive(Clone, Debug, Deserialize, Serialize)]
73pub struct BrepSolid {
74    /// Content-checked measurements; cloned with this solid, never serialized.
75    /// Initialize with `Default::default()` when constructing a solid.
76    #[serde(skip)]
77    pub mass_properties_cache: crate::MassPropertiesCache,
78    pub id: u64,
79    pub vertices: Vec<VertexRecord>,
80    pub edges: Vec<EdgeRecord>,
81    pub shells: Vec<ShellRecord>,
82    #[serde(default)]
83    pub genus: i64,
84    /// The kind the body declares (`Solid` unless a producer says
85    /// otherwise). Skipped in JSON when `Solid`, so every body written
86    /// before the field existed reads and writes byte-identically; the flat
87    /// codec keeps its 1.0 header for a Solid for the same reason.
88    #[serde(default, skip_serializing_if = "BodyKind::is_solid")]
89    pub kind: BodyKind,
90    /// Explicit regions, empty on all historical bodies. Empty storage is
91    /// omitted so every existing JSON body keeps its bytes and digest.
92    #[serde(default, skip_serializing_if = "Vec::is_empty")]
93    pub cells: Vec<CellRecord>,
94}
95
96/// The closed kind of a `ValidationIssue`. Only the kinds a dispatcher
97/// needs are minted (the boolean's endpoint-gap repair lane keys on
98/// `CurveVertexGap`); everything else is `Other` and carries its message as
99/// the payload. A dispatcher matches the kind, never the message text.
100///
101/// On the wire it is a nested object, `"kind":{"name":"curve_vertex_gap",
102/// "at_start":true}`, NOT flattened into the issue: serde's `default` does not
103/// apply to a flattened internally-tagged enum, and a report written before
104/// kinds existed must still read back (as `Other`).
105#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
106#[serde(tag = "name", rename_all = "snake_case")]
107pub enum IssueKind {
108    /// An edge curve's start (`at_start`) or end does not land on its vertex.
109    CurveVertexGap { at_start: bool },
110    /// An edge used fewer than twice (a boundary the shell leaves open).
111    OpenEdge,
112    /// An edge used more than twice.
113    OverUsedEdge,
114    /// The Euler formula does not close.
115    GenusMismatch,
116    #[default]
117    Other,
118}
119
120#[derive(Clone, Debug, Deserialize, Serialize)]
121pub struct ValidationIssue {
122    pub severity: &'static str,
123    #[serde(default)]
124    pub kind: IssueKind,
125    pub message: String,
126}
127
128impl ValidationIssue {
129    fn error(message: impl Into<String>) -> Self {
130        Self::error_kind(IssueKind::Other, message)
131    }
132
133    fn error_kind(kind: IssueKind, message: impl Into<String>) -> Self {
134        Self {
135            severity: "error",
136            kind,
137            message: message.into(),
138        }
139    }
140
141    fn warning(message: impl Into<String>) -> Self {
142        Self {
143            severity: "warning",
144            kind: IssueKind::Other,
145            message: message.into(),
146        }
147    }
148}
149
150#[derive(Clone, Debug, Serialize)]
151pub struct ValidationReport {
152    pub issues: Vec<ValidationIssue>,
153    pub wire_warnings: Vec<ValidationIssue>,
154    pub max_pcurve_error: f64,
155}
156
157#[path = "topology/queries.rs"]
158mod queries;
159pub(crate) use queries::{nearest_edge, edge_endpoint_alignment, coedge_neighbor_endpoints, edge_use_counts};
160
161#[path = "topology/seam.rs"]
162mod seam;
163#[path = "topology/validate.rs"]
164mod validate;
165#[path = "topology/primitives.rs"]
166mod primitives;
167#[path = "topology/kind.rs"]
168mod kind;
169
170pub use seam::{
171    analyze_doubly_periodic_seam_band, loop_seam_offsets, seam_band_uv_polygon, seam_band_uv_polygon_with,
172};
173pub(crate) use seam::doubly_periodic_has_only_collapsed_loops;
174pub(crate) use validate::{adaptive_coedge_error, clear_coedge_readings, coedge_error_floored, coedge_memo_enabled, exact_iso_image_error, exact_line_on_plane_error, CoedgeMemoScope};
175pub use primitives::{make_box_brep, make_cylinder_brep, make_pyramid_brep};
176pub use kind::{topology_kind, BodyKind, KindReport, TopologyKind};
177