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}
75
76/// The closed kind of a [`ValidationIssue`]. Only the kinds a dispatcher
77/// needs are minted (the boolean's endpoint-gap repair lane keys on
78/// `CurveVertexGap`); everything else is `Other` and carries its message as
79/// the payload. A dispatcher matches the kind, never the message text.
80///
81/// On the wire it is a nested object, `"kind":{"name":"curve_vertex_gap",
82/// "at_start":true}`, NOT flattened into the issue: serde's `default` does not
83/// apply to a flattened internally-tagged enum, and a report written before
84/// kinds existed must still read back (as `Other`).
85#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
86#[serde(tag = "name", rename_all = "snake_case")]
87pub enum IssueKind {
88    /// An edge curve's start (`at_start`) or end does not land on its vertex.
89    CurveVertexGap { at_start: bool },
90    /// An edge used fewer than twice (a boundary the shell leaves open).
91    OpenEdge,
92    /// An edge used more than twice.
93    OverUsedEdge,
94    /// The Euler formula does not close.
95    GenusMismatch,
96    #[default]
97    Other,
98}
99
100#[derive(Clone, Debug, Deserialize, Serialize)]
101pub struct ValidationIssue {
102    pub severity: &'static str,
103    #[serde(default)]
104    pub kind: IssueKind,
105    pub message: String,
106}
107
108impl ValidationIssue {
109    fn error(message: impl Into<String>) -> Self {
110        Self::error_kind(IssueKind::Other, message)
111    }
112
113    fn error_kind(kind: IssueKind, message: impl Into<String>) -> Self {
114        Self {
115            severity: "error",
116            kind,
117            message: message.into(),
118        }
119    }
120
121    fn warning(message: impl Into<String>) -> Self {
122        Self {
123            severity: "warning",
124            kind: IssueKind::Other,
125            message: message.into(),
126        }
127    }
128}
129
130#[derive(Clone, Debug, Serialize)]
131pub struct ValidationReport {
132    pub issues: Vec<ValidationIssue>,
133    pub wire_warnings: Vec<ValidationIssue>,
134    pub max_pcurve_error: f64,
135}
136
137#[path = "topology/queries.rs"]
138mod queries;
139pub(crate) use queries::{nearest_edge, edge_endpoint_alignment, coedge_neighbor_endpoints, edge_use_counts};
140
141#[path = "topology/seam.rs"]
142mod seam;
143#[path = "topology/validate.rs"]
144mod validate;
145#[path = "topology/primitives.rs"]
146mod primitives;
147
148pub use seam::{
149    analyze_doubly_periodic_seam_band, loop_seam_offsets, seam_band_uv_polygon, seam_band_uv_polygon_with,
150};
151pub(crate) use seam::doubly_periodic_has_only_collapsed_loops;
152pub(crate) use validate::{adaptive_coedge_error, coedge_error_floored};
153pub use primitives::{make_box_brep, make_cylinder_brep, make_pyramid_brep};
154