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#[derive(Clone, Debug, Deserialize, Serialize)]
63pub struct BrepSolid {
64    /// Content-checked measurements; cloned with this solid, never serialized.
65    /// Initialize with `Default::default()` when constructing a solid.
66    #[serde(skip)]
67    pub mass_properties_cache: crate::MassPropertiesCache,
68    pub id: u64,
69    pub vertices: Vec<VertexRecord>,
70    pub edges: Vec<EdgeRecord>,
71    pub shells: Vec<ShellRecord>,
72    #[serde(default)]
73    pub genus: i64,
74    /// The kind the body declares (`Solid` unless a producer says
75    /// otherwise). Skipped in JSON when `Solid`, so every body written
76    /// before the field existed reads and writes byte-identically; the flat
77    /// codec keeps its 1.0 header for a Solid for the same reason.
78    #[serde(default, skip_serializing_if = "BodyKind::is_solid")]
79    pub kind: BodyKind,
80}
81
82/// The closed kind of a [`ValidationIssue`]. Only the kinds a dispatcher
83/// needs are minted (the boolean's endpoint-gap repair lane keys on
84/// `CurveVertexGap`); everything else is `Other` and carries its message as
85/// the payload. A dispatcher matches the kind, never the message text.
86///
87/// On the wire it is a nested object, `"kind":{"name":"curve_vertex_gap",
88/// "at_start":true}`, NOT flattened into the issue: serde's `default` does not
89/// apply to a flattened internally-tagged enum, and a report written before
90/// kinds existed must still read back (as `Other`).
91#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
92#[serde(tag = "name", rename_all = "snake_case")]
93pub enum IssueKind {
94    /// An edge curve's start (`at_start`) or end does not land on its vertex.
95    CurveVertexGap { at_start: bool },
96    /// An edge used fewer than twice (a boundary the shell leaves open).
97    OpenEdge,
98    /// An edge used more than twice.
99    OverUsedEdge,
100    /// The Euler formula does not close.
101    GenusMismatch,
102    #[default]
103    Other,
104}
105
106#[derive(Clone, Debug, Deserialize, Serialize)]
107pub struct ValidationIssue {
108    pub severity: &'static str,
109    #[serde(default)]
110    pub kind: IssueKind,
111    pub message: String,
112}
113
114impl ValidationIssue {
115    fn error(message: impl Into<String>) -> Self {
116        Self::error_kind(IssueKind::Other, message)
117    }
118
119    fn error_kind(kind: IssueKind, message: impl Into<String>) -> Self {
120        Self {
121            severity: "error",
122            kind,
123            message: message.into(),
124        }
125    }
126
127    fn warning(message: impl Into<String>) -> Self {
128        Self {
129            severity: "warning",
130            kind: IssueKind::Other,
131            message: message.into(),
132        }
133    }
134}
135
136#[derive(Clone, Debug, Serialize)]
137pub struct ValidationReport {
138    pub issues: Vec<ValidationIssue>,
139    pub wire_warnings: Vec<ValidationIssue>,
140    pub max_pcurve_error: f64,
141}
142
143#[path = "topology/queries.rs"]
144mod queries;
145pub(crate) use queries::{nearest_edge, edge_endpoint_alignment, coedge_neighbor_endpoints, edge_use_counts};
146
147#[path = "topology/seam.rs"]
148mod seam;
149#[path = "topology/validate.rs"]
150mod validate;
151#[path = "topology/primitives.rs"]
152mod primitives;
153#[path = "topology/kind.rs"]
154mod kind;
155
156pub use seam::{
157    analyze_doubly_periodic_seam_band, loop_seam_offsets, seam_band_uv_polygon, seam_band_uv_polygon_with,
158};
159pub(crate) use seam::doubly_periodic_has_only_collapsed_loops;
160pub(crate) use validate::{adaptive_coedge_error, coedge_error_floored};
161pub use primitives::{make_box_brep, make_cylinder_brep, make_pyramid_brep};
162pub use kind::{topology_kind, BodyKind, KindReport, TopologyKind};
163