1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
//! # brepkit-operations
//!
//! CAD modeling operations for B-Rep solids, and the entry point for Rust
//! consumers of brepkit. Layer L3, depending on `brepkit-math`,
//! `brepkit-topology`, `brepkit-geometry`, `brepkit-algo`, `brepkit-blend`,
//! `brepkit-heal`, `brepkit-check`, `brepkit-offset`, and `brepkit-sketch`.
//!
//! # Getting started
//!
//! ```
//! use brepkit_operations::boolean::{boolean, BooleanOp};
//! use brepkit_operations::measure::solid_volume;
//! use brepkit_operations::primitives::{make_box, make_cylinder};
//! use brepkit_topology::Topology;
//!
//! let mut topo = Topology::new();
//!
//! // Primitives are anchored at the origin, so this cylinder rounds off the
//! // block's corner. Use `transform_solid` to place it somewhere else.
//! let block = make_box(&mut topo, 30.0, 20.0, 10.0)?;
//! let cutter = make_cylinder(&mut topo, 5.0, 15.0)?;
//! let notched = boolean(&mut topo, BooleanOp::Cut, block, cutter)?;
//!
//! // A quarter-cylinder of radius 5 and height 10 is gone from the corner.
//! let expected = 30.0 * 20.0 * 10.0 - 0.25 * std::f64::consts::PI * 25.0 * 10.0;
//! let volume = solid_volume(&topo, notched, 0.01)?;
//! assert!((volume - expected).abs() / expected < 1e-3);
//! # Ok::<(), brepkit_operations::OperationsError>(())
//! ```
//!
//! # Conventions
//!
//! Modeling operations take the [`Topology`](brepkit_topology::Topology) arena
//! as `&mut` and return a typed handle into it, so results compose without
//! copying geometry. Interrogation does not: measurement, classification,
//! validation, distance, and query borrow the arena as `&` and return a value,
//! whether a number, a report, or a collection.
//!
//! Fallible work returns a [`Result`] rather than panicking. `unwrap`,
//! `expect`, and `panic!` are denied by lint across the workspace.
//!
//! Primitives are anchored at the origin. Place them with
//! [`transform`] rather than expecting a position argument.
//!
//! # Exact geometry, and when it degrades
//!
//! Booleans run on an exact path that preserves analytic and NURBS surfaces.
//! A cylinder cut by a plane stays a cylinder, so face counts stay flat across
//! chained operations instead of compounding: a nine-step compound boolean
//! settles around 72 faces where a mesh-based approach would reach several
//! thousand.
//!
//! Some configurations defeat that path and fall back to a mesh-based boolean
//! built on co-refinement. The usual causes are coincident-face contact,
//! coaxial analytic surfaces, razor-thin geometry, and very high face counts.
//! The fallback returns a usable, non-degenerate solid, but the curved faces
//! come back tessellated and the result is not guaranteed watertight.
//!
//! The fallback does not announce itself in the return value, which matters
//! most for export pipelines: a STEP file written from a fallback result
//! carries triangles where it should carry a cylinder. Snapshot
//! [`boolean::mesh_fallback_count`] around the chain and refuse the output
//! when it grew.
//!
//! ```
//! use brepkit_operations::boolean::{boolean, mesh_fallback_count, BooleanOp};
//! use brepkit_operations::primitives::{make_box, make_cylinder};
//! use brepkit_operations::validate::validate_solid;
//! use brepkit_topology::Topology;
//!
//! let mut topo = Topology::new();
//! let block = make_box(&mut topo, 30.0, 20.0, 10.0)?;
//! let cutter = make_cylinder(&mut topo, 5.0, 15.0)?;
//!
//! let before = mesh_fallback_count();
//! let notched = boolean(&mut topo, BooleanOp::Cut, block, cutter)?;
//!
//! // This cut takes the exact path, so the counter is unmoved and the
//! // rounded wall is still a real cylinder.
//! assert_eq!(mesh_fallback_count(), before);
//!
//! // Topological checks: wire closure, manifold and boundary edges,
//! // Euler characteristic, degenerate faces, and duplicate faces.
//! assert!(validate_solid(&topo, notched)?.is_valid());
//! # Ok::<(), brepkit_operations::OperationsError>(())
//! ```
//!
//! # Verifying a result
//!
//! Three checks, in increasing cost, and they catch different things:
//!
//! 1. [`validate::validate_solid`] reports topological defects: an unclosed
//! wire, a shell with a free edge, a non-manifold edge, a wrong Euler
//! characteristic, a degenerate face. Cheap, and the right default.
//! 2. [`measure::solid_volume`] against a closed-form expectation catches
//! geometric errors that leave the topology intact, which is the failure
//! mode a boolean is most likely to produce. Pass a tight deflection:
//! a coarse one under-counts curved faces and will disagree with itself
//! across values.
//! 3. [`heal::heal_solid`] repairs what the first two find, merging
//! coincident vertices, dropping degenerate edges, closing wire gaps, and
//! fixing face orientation.
//!
//! A solid that passes `validate_solid` is well-formed, not necessarily
//! correct. Volume is what distinguishes the two.
//!
//! # Module families
//!
//! | Family | Modules | Purpose |
//! |--------|---------|---------|
//! | **Core** | [`primitives`], [`extrude`], [`revolve`], [`sweep`], [`loft`], [`pipe`], [`helix`] | Shape creation |
//! | **Transform** | [`transform`], [`copy`], [`mirror`], [`pattern`] | Spatial operations |
//! | **Boolean** | [`boolean`], [`mesh_boolean`] | Set operations |
//! | **Blend** | [`fillet`], [`chamfer`], [`blend_ops`] | Edge smoothing |
//! | **Offset** | [`offset_face`], [`offset_trim`], [`offset_v2`], [`offset_wire`] | Wall thickness |
//! | **Surface** | [`fill_face`], [`thicken`], [`shell_op`], [`draft`], [`section`], [`split`] | Surface/solid modification |
//! | **Repair** | [`heal`], [`defeature`], [`sew`], [`untrim`] | Shape fixing |
//! | **Analysis** | [`measure`], [`distance`], [`classify`], [`validate`], [`query`], [`feature_recognition`] | Interrogation |
//! | **Tessellation** | [`tessellate`] | Mesh generation |
//! | **Infrastructure** | [`assembly`], [`compound_ops`], [`evolution`], [`sketch`] | Utilities |
//!
//! # See also
//!
//! - [`brepkit_io`](https://docs.rs/brepkit-io): reading and writing STEP and
//! the mesh formats.
//! - [`brepkit_topology`](https://docs.rs/brepkit-topology): the arena every
//! operation here takes, and the surface enums it stores.
//! - [brepjs.dev](https://brepjs.dev): concepts, task recipes, and the
//! TypeScript API built on this kernel.
use ;
pub
pub
pub
/// Compute `n · p` treating a `Point3` as a direction vector.
///
/// Equivalent to the dot product `n.x*p.x + n.y*p.y + n.z*p.z`, used
/// for the plane equation `n · point = d`.
/// Errors from modeling operations.