brepjs 13.0.0 → 13.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/2d.cjs +3 -3
- package/dist/2d.js +3 -3
- package/dist/{arrayAccess-CccV7jov.js → arrayAccess-Dps31ERU.js} +3 -3
- package/dist/{arrayAccess-BF8Hm4-H.cjs → arrayAccess-peFKE9Ob.cjs} +3 -3
- package/dist/{blueprint-mSGgCL3V.js → blueprint-DYCdRlW5.js} +8 -8
- package/dist/{blueprint-BP3P8Ado.cjs → blueprint-PLJan-W5.cjs} +8 -8
- package/dist/{blueprintFns-BQ-MP_Vy.cjs → blueprintFns-Bsx25BG7.cjs} +3 -3
- package/dist/{blueprintFns-DWFkjbDT.js → blueprintFns-eWh7NpZx.js} +3 -3
- package/dist/{boolean2D-Cqfwz60G.js → boolean2D-52qVCooY.js} +10 -10
- package/dist/{boolean2D-D76Hc7Wx.cjs → boolean2D-CtB21ajK.cjs} +10 -10
- package/dist/{booleanFns-CYPXeNVN.cjs → booleanFns-BrptUFkP.cjs} +25 -13
- package/dist/{booleanFns-B79ALtKn.js → booleanFns-iM6UPb8e.js} +25 -13
- package/dist/brepjs.cjs +43 -311
- package/dist/brepjs.js +23 -306
- package/dist/core/errors.d.ts +7 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core.cjs +3 -3
- package/dist/core.js +3 -3
- package/dist/{cornerFinder-BSwshSGB.js → cornerFinder-C7aDyYLJ.js} +2 -2
- package/dist/{cornerFinder-sg0JNgux.cjs → cornerFinder-SF-xmMO1.cjs} +2 -2
- package/dist/{curveFns-D_s3LdNT.js → curveFns-C-jU1_Y_.js} +2 -2
- package/dist/{curveFns-CJjkUGyW.cjs → curveFns-ywh7Ctyk.cjs} +2 -2
- package/dist/{drawFns-oyqai_HD.js → drawFns-D-0p86Lf.js} +13 -13
- package/dist/{drawFns-0CYuQn0J.cjs → drawFns-DknEB-Qs.cjs} +13 -13
- package/dist/{errors-B1fl3mAU.js → errors-B_T0aMQF.js} +7 -0
- package/dist/{errors-C85KVJr-.cjs → errors-DupKEMqI.cjs} +7 -0
- package/dist/{extrudeFns-0kBZvqJz.cjs → extrudeFns-CGCIbydL.cjs} +2 -2
- package/dist/{extrudeFns-lDvV4ir2.js → extrudeFns-LsH1rDMa.js} +2 -2
- package/dist/{faceFns-8BurpAGN.cjs → faceFns-8dGb8q3J.cjs} +2 -2
- package/dist/{faceFns-Bne5RIvn.js → faceFns-EnGcKFAr.js} +2 -2
- package/dist/{helpers-CxexSe1n.js → helpers-Rf0vhX6I.js} +6 -6
- package/dist/{helpers-D8DIMw2U.cjs → helpers-pQpV9Mwh.cjs} +6 -6
- package/dist/{historyFns-BTeasREV.js → historyFns-XkjLAQyu.js} +5 -5
- package/dist/{historyFns-BAzQr6EP.cjs → historyFns-lNalnOdR.cjs} +5 -5
- package/dist/{importFns-D__wN_Pq.cjs → importFns-BSH9cGIp.cjs} +36 -4
- package/dist/{importFns-clldr3EF.js → importFns-Bgs-FYAP.js} +31 -5
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/io/stepConfigFns.d.ts +27 -0
- package/dist/io/stepConfigFns.d.ts.map +1 -0
- package/dist/io.cjs +3 -2
- package/dist/io.d.ts +1 -0
- package/dist/io.d.ts.map +1 -1
- package/dist/io.js +3 -3
- package/dist/kernel/brepkit/booleanOps.d.ts +7 -1
- package/dist/kernel/brepkit/booleanOps.d.ts.map +1 -1
- package/dist/kernel/brepkit/brepkitAdapter.d.ts +5 -4
- package/dist/kernel/brepkit/brepkitAdapter.d.ts.map +1 -1
- package/dist/kernel/brepkit/evolutionOps.d.ts +4 -4
- package/dist/kernel/brepkit/evolutionOps.d.ts.map +1 -1
- package/dist/kernel/interfaces/booleanOps.d.ts +3 -1
- package/dist/kernel/interfaces/booleanOps.d.ts.map +1 -1
- package/dist/kernel/interfaces/evolutionOps.d.ts +4 -4
- package/dist/kernel/interfaces/evolutionOps.d.ts.map +1 -1
- package/dist/kernel/occt/booleanOps.d.ts +7 -1
- package/dist/kernel/occt/booleanOps.d.ts.map +1 -1
- package/dist/kernel/occt/defaultAdapter.d.ts +5 -4
- package/dist/kernel/occt/defaultAdapter.d.ts.map +1 -1
- package/dist/kernel/occt/evolutionOps.d.ts +2 -2
- package/dist/kernel/occt/evolutionOps.d.ts.map +1 -1
- package/dist/kernel/occt/historyOps.d.ts +4 -4
- package/dist/kernel/occt/historyOps.d.ts.map +1 -1
- package/dist/kernel/occt/wasmTypes/occtBuilders.d.ts +4 -0
- package/dist/kernel/occt/wasmTypes/occtBuilders.d.ts.map +1 -1
- package/dist/kernel/types.d.ts +30 -0
- package/dist/kernel/types.d.ts.map +1 -1
- package/dist/{measureFns-DrMZGJ6r.cjs → measureFns-CFdHa_fj.cjs} +3 -3
- package/dist/{measureFns-C4WqH4OT.js → measureFns-D7J6qUY_.js} +3 -3
- package/dist/measurement.cjs +1 -1
- package/dist/measurement.js +1 -1
- package/dist/{meshFns-DvOM43vV.cjs → meshFns-2XnDXgIh.cjs} +3 -3
- package/dist/{meshFns--M5PTyHG.js → meshFns-B7uklc4M.js} +3 -3
- package/dist/operations.cjs +2 -2
- package/dist/operations.js +2 -2
- package/dist/{planeOps-DPintPbl.js → planeOps-BuBXTLBr.js} +1 -1
- package/dist/{planeOps-DdkIuVjk.cjs → planeOps-cTxDywpG.cjs} +1 -1
- package/dist/primitiveFns-CASk8g16.js +1452 -0
- package/dist/primitiveFns-DKtvEA0i.cjs +1817 -0
- package/dist/query.cjs +2 -2
- package/dist/query.js +2 -2
- package/dist/result.cjs +1 -1
- package/dist/result.js +1 -1
- package/dist/{shapeTypes-GmE4D5Q_.cjs → shapeTypes-CElaawp7.cjs} +114 -9
- package/dist/{shapeTypes-D38b_BKF.js → shapeTypes-CYb8Byqj.js} +114 -9
- package/dist/sketching.cjs +2 -2
- package/dist/sketching.js +2 -2
- package/dist/{solidBuilders-FaTmd_PS.js → solidBuilders-ClJxiUa3.js} +3 -3
- package/dist/{solidBuilders-BqU0oT2q.cjs → solidBuilders-Cs4XyL58.cjs} +3 -3
- package/dist/{surfaceBuilders-DiCVk_Un.js → surfaceBuilders-DnGdDW8i.js} +3 -3
- package/dist/{surfaceBuilders-BzDQQ4EG.cjs → surfaceBuilders-ZUTb3z6i.cjs} +3 -3
- package/dist/topology/booleanDiagnosticFns.d.ts +18 -0
- package/dist/topology/booleanDiagnosticFns.d.ts.map +1 -0
- package/dist/topology/booleanFns.d.ts.map +1 -1
- package/dist/topology/evolutionFns.d.ts +71 -0
- package/dist/topology/evolutionFns.d.ts.map +1 -0
- package/dist/topology/healingFns.d.ts +17 -0
- package/dist/topology/healingFns.d.ts.map +1 -1
- package/dist/topology/modifierFns.d.ts +15 -0
- package/dist/topology/modifierFns.d.ts.map +1 -1
- package/dist/topology/positionFns.d.ts +15 -0
- package/dist/topology/positionFns.d.ts.map +1 -0
- package/dist/topology.cjs +18 -6
- package/dist/topology.d.ts +6 -1
- package/dist/topology.d.ts.map +1 -1
- package/dist/topology.js +7 -7
- package/dist/vectors.cjs +1 -1
- package/dist/vectors.js +1 -1
- package/package.json +1 -1
- package/dist/primitiveFns-BXufrcii.cjs +0 -1063
- package/dist/primitiveFns-u3Bbdvlw.js +0 -806
|
@@ -0,0 +1,1817 @@
|
|
|
1
|
+
const require_shapeTypes = require("./shapeTypes-CElaawp7.cjs");
|
|
2
|
+
const require_errors = require("./errors-DupKEMqI.cjs");
|
|
3
|
+
const require_vecOps = require("./vecOps-4iBMiet9.cjs");
|
|
4
|
+
const require_faceFns = require("./faceFns-8dGb8q3J.cjs");
|
|
5
|
+
const require_arrayAccess = require("./arrayAccess-peFKE9Ob.cjs");
|
|
6
|
+
const require_surfaceBuilders = require("./surfaceBuilders-ZUTb3z6i.cjs");
|
|
7
|
+
const require_solidBuilders = require("./solidBuilders-Cs4XyL58.cjs");
|
|
8
|
+
//#region src/topology/threeHelpers.ts
|
|
9
|
+
/**
|
|
10
|
+
* Convert a ShapeMesh into BufferGeometry-compatible typed arrays.
|
|
11
|
+
*
|
|
12
|
+
* The returned arrays can be used directly with Three.js:
|
|
13
|
+
* ```ts
|
|
14
|
+
* const geo = new THREE.BufferGeometry();
|
|
15
|
+
* geo.setAttribute('position', new THREE.BufferAttribute(data.position, 3));
|
|
16
|
+
* geo.setAttribute('normal', new THREE.BufferAttribute(data.normal, 3));
|
|
17
|
+
* geo.setIndex(new THREE.BufferAttribute(data.index, 1));
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function toBufferGeometryData(mesh) {
|
|
21
|
+
return {
|
|
22
|
+
position: mesh.vertices,
|
|
23
|
+
normal: mesh.normals,
|
|
24
|
+
index: mesh.triangles
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Convert a ShapeMesh into grouped BufferGeometry data with face material groups.
|
|
29
|
+
*
|
|
30
|
+
* Each face becomes a separate group, allowing per-face materials in Three.js:
|
|
31
|
+
* ```ts
|
|
32
|
+
* const data = toGroupedBufferGeometryData(mesh);
|
|
33
|
+
* const geo = new THREE.BufferGeometry();
|
|
34
|
+
* geo.setAttribute('position', new THREE.BufferAttribute(data.position, 3));
|
|
35
|
+
* geo.setAttribute('normal', new THREE.BufferAttribute(data.normal, 3));
|
|
36
|
+
* geo.setIndex(new THREE.BufferAttribute(data.index, 1));
|
|
37
|
+
* for (const g of data.groups) {
|
|
38
|
+
* geo.addGroup(g.start, g.count, g.materialIndex);
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
function toGroupedBufferGeometryData(mesh) {
|
|
43
|
+
return {
|
|
44
|
+
position: mesh.vertices,
|
|
45
|
+
normal: mesh.normals,
|
|
46
|
+
index: mesh.triangles,
|
|
47
|
+
groups: mesh.faceGroups.map((g, i) => ({
|
|
48
|
+
start: g.start,
|
|
49
|
+
count: g.count,
|
|
50
|
+
materialIndex: i,
|
|
51
|
+
faceId: g.faceId
|
|
52
|
+
}))
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Convert an EdgeMesh into position data for THREE.LineSegments.
|
|
57
|
+
*
|
|
58
|
+
* ```ts
|
|
59
|
+
* const geo = new THREE.BufferGeometry();
|
|
60
|
+
* geo.setAttribute('position', new THREE.BufferAttribute(data.position, 3));
|
|
61
|
+
* const lines = new THREE.LineSegments(geo, material);
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
function toLineGeometryData(mesh) {
|
|
65
|
+
return { position: mesh.lines };
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/topology/chamferAngleFns.ts
|
|
69
|
+
/**
|
|
70
|
+
* Chamfer with distance + angle — functional API.
|
|
71
|
+
*
|
|
72
|
+
* Provides chamferDistAngle() which chamfers edges using a distance
|
|
73
|
+
* measured along one face and an angle to determine the chamfer on the other.
|
|
74
|
+
*/
|
|
75
|
+
/**
|
|
76
|
+
* Chamfer edges of a shape using distance + angle.
|
|
77
|
+
*
|
|
78
|
+
* The distance is measured along the face that contains the edge, and the
|
|
79
|
+
* angle (in degrees) determines how the chamfer cuts into the adjacent face.
|
|
80
|
+
*
|
|
81
|
+
* @param shape - The 3D shape to chamfer.
|
|
82
|
+
* @param edges - Edges to chamfer (must not be empty).
|
|
83
|
+
* @param distance - Chamfer distance along the face (must be positive).
|
|
84
|
+
* @param angleDeg - Chamfer angle in degrees (must be in range (0, 90)).
|
|
85
|
+
* @returns Ok with the chamfered shape, or Err on invalid input or kernel failure.
|
|
86
|
+
*
|
|
87
|
+
* @remarks Uses `BRepFilletAPI_MakeChamfer.AddDA(dist, angle, edge, face)` internally.
|
|
88
|
+
*/
|
|
89
|
+
function chamferDistAngle(shape, edges, distance, angleDeg) {
|
|
90
|
+
if (edges.length === 0) return require_errors.err(require_errors.validationError("CHAMFER_ANGLE_NO_EDGES", "chamferDistAngle requires at least one edge", void 0, { edgeCount: 0 }));
|
|
91
|
+
if (distance <= 0) return require_errors.err(require_errors.validationError("CHAMFER_ANGLE_BAD_DISTANCE", `distance must be positive, got ${distance}`, void 0, { distance }));
|
|
92
|
+
if (angleDeg <= 0 || angleDeg >= 90) return require_errors.err(require_errors.validationError("CHAMFER_ANGLE_BAD_ANGLE", `angleDeg must be in range (0, 90), got ${angleDeg}`, void 0, { angleDeg }));
|
|
93
|
+
let raw;
|
|
94
|
+
try {
|
|
95
|
+
const kernel = require_shapeTypes.getKernel();
|
|
96
|
+
const rawEdges = edges.map((e) => e.wrapped);
|
|
97
|
+
raw = kernel.chamferDistAngle(shape.wrapped, rawEdges, distance, angleDeg);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
return require_errors.err(require_errors.kernelError("CHAMFER_ANGLE_FAILED", `chamferDistAngle kernel call failed: ${e instanceof Error ? e.message : String(e)}`, e, {
|
|
100
|
+
distance,
|
|
101
|
+
angleDeg,
|
|
102
|
+
edgeCount: edges.length
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
const downcastResult = require_faceFns.downcast(raw);
|
|
106
|
+
if (require_errors.isErr(downcastResult)) return downcastResult;
|
|
107
|
+
const wrapped = require_shapeTypes.castShape(downcastResult.value);
|
|
108
|
+
if (!require_shapeTypes.isShape3D(wrapped)) {
|
|
109
|
+
wrapped[Symbol.dispose]();
|
|
110
|
+
return require_errors.err(require_errors.typeCastError("CHAMFER_ANGLE_NOT_3D", "chamferDistAngle did not produce a 3D shape"));
|
|
111
|
+
}
|
|
112
|
+
return require_errors.ok(wrapped);
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/topology/adjacencyFns.ts
|
|
116
|
+
/**
|
|
117
|
+
* Topology adjacency queries — find related sub-shapes within a parent shape.
|
|
118
|
+
*
|
|
119
|
+
* Uses cached topology extraction and an edge→faces adjacency map
|
|
120
|
+
* (built once per parent shape and cached) to avoid redundant WASM calls.
|
|
121
|
+
*/
|
|
122
|
+
function wrapAll(shapes, type) {
|
|
123
|
+
return shapes.map((s) => require_shapeTypes.castShapeWithKnownType(s, type));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Iterate sub-shapes of `parentKernel` of the given `type`, deduplicate by
|
|
127
|
+
* hash+isSame, and return branded handles of type `T`.
|
|
128
|
+
*
|
|
129
|
+
* Used by edgesOfFace, wiresOfFace, and verticesOfEdge — all of which need
|
|
130
|
+
* the same deduplicated-children pattern on a raw KernelShape.
|
|
131
|
+
*/
|
|
132
|
+
function deduplicatedSubShapes(parentKernel, type) {
|
|
133
|
+
const kernel = require_shapeTypes.getKernel();
|
|
134
|
+
const items = kernel.iterShapes(parentKernel, type);
|
|
135
|
+
const results = [];
|
|
136
|
+
const seen = /* @__PURE__ */ new Map();
|
|
137
|
+
for (const item of items) {
|
|
138
|
+
const hash = kernel.hashCode(item, require_vecOps.HASH_CODE_MAX);
|
|
139
|
+
const bucket = seen.get(hash);
|
|
140
|
+
if (!bucket) {
|
|
141
|
+
seen.set(hash, [item]);
|
|
142
|
+
results.push(item);
|
|
143
|
+
} else if (!bucket.some((r) => kernel.isSame(r, item))) {
|
|
144
|
+
bucket.push(item);
|
|
145
|
+
results.push(item);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return wrapAll(results, type);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Build or retrieve the cached edge→faces adjacency map for a parent shape.
|
|
152
|
+
* Maps edge hash codes to edge-face pairs, storing the edge alongside each
|
|
153
|
+
* face so facesOfEdge can verify via isSame without re-extracting face edges.
|
|
154
|
+
*/
|
|
155
|
+
function getEdgeToFacesMap(parent) {
|
|
156
|
+
const cache = require_arrayAccess.getOrCreateCache(parent);
|
|
157
|
+
if (cache.edgeToFaces) return cache.edgeToFaces;
|
|
158
|
+
const kernel = require_shapeTypes.getKernel();
|
|
159
|
+
const edgeToFaces = /* @__PURE__ */ new Map();
|
|
160
|
+
const allFaces = kernel.iterShapes(parent.wrapped, "face");
|
|
161
|
+
for (const f of allFaces) {
|
|
162
|
+
const edges = kernel.iterShapes(f, "edge");
|
|
163
|
+
for (const e of edges) {
|
|
164
|
+
const hash = kernel.hashCode(e, require_vecOps.HASH_CODE_MAX);
|
|
165
|
+
let bucket = edgeToFaces.get(hash);
|
|
166
|
+
if (!bucket) {
|
|
167
|
+
bucket = [];
|
|
168
|
+
edgeToFaces.set(hash, bucket);
|
|
169
|
+
}
|
|
170
|
+
if (!bucket.some((entry) => kernel.isSame(entry.edge, e) && kernel.isSame(entry.face, f))) bucket.push({
|
|
171
|
+
edge: e,
|
|
172
|
+
face: f
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
cache.edgeToFaces = edgeToFaces;
|
|
177
|
+
return edgeToFaces;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get all faces adjacent to a given edge within a parent shape.
|
|
181
|
+
*
|
|
182
|
+
* An edge typically borders exactly two faces in a solid, or one face
|
|
183
|
+
* if the edge is on a boundary.
|
|
184
|
+
*
|
|
185
|
+
* @param parent - The parent shape to search within.
|
|
186
|
+
* @param edge - The edge whose adjacent faces to find.
|
|
187
|
+
* @returns Array of unique faces containing the given edge.
|
|
188
|
+
*/
|
|
189
|
+
function facesOfEdge(parent, edge) {
|
|
190
|
+
const kernel = require_shapeTypes.getKernel();
|
|
191
|
+
const edgeToFaces = getEdgeToFacesMap(parent);
|
|
192
|
+
const hash = kernel.hashCode(edge.wrapped, require_vecOps.HASH_CODE_MAX);
|
|
193
|
+
const bucket = edgeToFaces.get(hash) ?? [];
|
|
194
|
+
const results = [];
|
|
195
|
+
const seen = /* @__PURE__ */ new Map();
|
|
196
|
+
for (const entry of bucket) {
|
|
197
|
+
if (!kernel.isSame(entry.edge, edge.wrapped)) continue;
|
|
198
|
+
const fHash = kernel.hashCode(entry.face, require_vecOps.HASH_CODE_MAX);
|
|
199
|
+
const fBucket = seen.get(fHash);
|
|
200
|
+
if (!fBucket) {
|
|
201
|
+
seen.set(fHash, [entry.face]);
|
|
202
|
+
results.push(entry.face);
|
|
203
|
+
} else if (!fBucket.some((r) => kernel.isSame(r, entry.face))) {
|
|
204
|
+
fBucket.push(entry.face);
|
|
205
|
+
results.push(entry.face);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return wrapAll(results, "face");
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get all edges bounding a face.
|
|
212
|
+
*
|
|
213
|
+
* @param face - The face whose edges to enumerate.
|
|
214
|
+
* @returns Array of unique edges forming the face boundary.
|
|
215
|
+
*/
|
|
216
|
+
function edgesOfFace(face) {
|
|
217
|
+
return deduplicatedSubShapes(face.wrapped, "edge");
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get all wires of a face (outer wire + inner hole wires).
|
|
221
|
+
* All wires bounding a face are closed by definition.
|
|
222
|
+
*
|
|
223
|
+
* @param face - The face whose wires to enumerate.
|
|
224
|
+
*/
|
|
225
|
+
function wiresOfFace(face) {
|
|
226
|
+
return deduplicatedSubShapes(face.wrapped, "wire");
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get the start and end vertices of an edge.
|
|
230
|
+
*
|
|
231
|
+
* @param edge - The edge whose vertices to retrieve.
|
|
232
|
+
* @returns Array of 1-2 vertices (1 if degenerate/closed, 2 otherwise).
|
|
233
|
+
*/
|
|
234
|
+
function verticesOfEdge(edge) {
|
|
235
|
+
return deduplicatedSubShapes(edge.wrapped, "vertex");
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get all faces that share at least one edge with the given face.
|
|
239
|
+
*
|
|
240
|
+
* The returned list does not include the input face itself.
|
|
241
|
+
* Uses the cached edge→faces adjacency map for the parent shape.
|
|
242
|
+
*
|
|
243
|
+
* @param parent - The parent shape to search within.
|
|
244
|
+
* @param face - The face whose neighbors to find.
|
|
245
|
+
* @returns Array of unique adjacent faces (excluding the input face).
|
|
246
|
+
*/
|
|
247
|
+
function adjacentFaces(parent, face) {
|
|
248
|
+
const kernel = require_shapeTypes.getKernel();
|
|
249
|
+
const edgeToFaces = getEdgeToFacesMap(parent);
|
|
250
|
+
const faceEdgeHandles = deduplicatedSubShapes(face.wrapped, "edge");
|
|
251
|
+
const neighborRaw = [];
|
|
252
|
+
const seen = /* @__PURE__ */ new Map();
|
|
253
|
+
for (const edgeHandle of faceEdgeHandles) {
|
|
254
|
+
const hash = kernel.hashCode(edgeHandle.wrapped, require_vecOps.HASH_CODE_MAX);
|
|
255
|
+
const entries = edgeToFaces.get(hash) ?? [];
|
|
256
|
+
for (const entry of entries) {
|
|
257
|
+
if (kernel.isSame(entry.face, face.wrapped)) continue;
|
|
258
|
+
const fHash = kernel.hashCode(entry.face, require_vecOps.HASH_CODE_MAX);
|
|
259
|
+
const bucket = seen.get(fHash);
|
|
260
|
+
if (!bucket) {
|
|
261
|
+
seen.set(fHash, [entry.face]);
|
|
262
|
+
neighborRaw.push(entry.face);
|
|
263
|
+
} else if (!bucket.some((r) => kernel.isSame(r, entry.face))) {
|
|
264
|
+
bucket.push(entry.face);
|
|
265
|
+
neighborRaw.push(entry.face);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return wrapAll(neighborRaw, "face");
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Get all edges shared between two faces.
|
|
273
|
+
*
|
|
274
|
+
* @param face1 - The first face.
|
|
275
|
+
* @param face2 - The second face.
|
|
276
|
+
* @returns Array of edges present in both faces (via isSame comparison).
|
|
277
|
+
*/
|
|
278
|
+
function sharedEdges(face1, face2) {
|
|
279
|
+
const kernel = require_shapeTypes.getKernel();
|
|
280
|
+
const edges1 = kernel.iterShapes(face1.wrapped, "edge");
|
|
281
|
+
const edges2 = kernel.iterShapes(face2.wrapped, "edge");
|
|
282
|
+
const edge2Map = /* @__PURE__ */ new Map();
|
|
283
|
+
for (const e2 of edges2) {
|
|
284
|
+
const hash = kernel.hashCode(e2, require_vecOps.HASH_CODE_MAX);
|
|
285
|
+
let bucket = edge2Map.get(hash);
|
|
286
|
+
if (!bucket) {
|
|
287
|
+
bucket = [];
|
|
288
|
+
edge2Map.set(hash, bucket);
|
|
289
|
+
}
|
|
290
|
+
bucket.push(e2);
|
|
291
|
+
}
|
|
292
|
+
const shared = [];
|
|
293
|
+
for (const e1 of edges1) if (edge2Map.get(kernel.hashCode(e1, 2147483647))?.some((e2) => kernel.isSame(e1, e2))) shared.push(e1);
|
|
294
|
+
return wrapAll(shared, "edge");
|
|
295
|
+
}
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region src/topology/booleanDiagnosticFns.ts
|
|
298
|
+
/**
|
|
299
|
+
* Boolean pre-validation diagnostics.
|
|
300
|
+
*/
|
|
301
|
+
/**
|
|
302
|
+
* Pre-validate operands before a boolean operation.
|
|
303
|
+
*
|
|
304
|
+
* Checks that both shapes are non-null and topologically valid.
|
|
305
|
+
* Returns a structured report of any issues found.
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```typescript
|
|
309
|
+
* const check = checkBoolean(base, tool, 'fuse');
|
|
310
|
+
* if (!check.valid) {
|
|
311
|
+
* console.warn('Boolean will likely fail:', check.issues);
|
|
312
|
+
* }
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
function checkBoolean(base, tool, op) {
|
|
316
|
+
return require_shapeTypes.getKernel().checkBoolean(base.wrapped, tool.wrapped, op);
|
|
317
|
+
}
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region src/topology/evolutionFns.ts
|
|
320
|
+
/**
|
|
321
|
+
* Evolution-tracking variants of boolean and modifier operations.
|
|
322
|
+
*
|
|
323
|
+
* These functions mirror the standard fuse/cut/intersect/fillet/chamfer/shell
|
|
324
|
+
* operations but additionally return the ShapeEvolution data, enabling
|
|
325
|
+
* persistent face selections, constraint tracking, and custom face-level logic.
|
|
326
|
+
*/
|
|
327
|
+
function validateShape3D(shape, label) {
|
|
328
|
+
if (require_shapeTypes.getKernel().isNull(shape.wrapped)) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.NULL_SHAPE_INPUT, `${label} is a null shape`));
|
|
329
|
+
return require_errors.ok(void 0);
|
|
330
|
+
}
|
|
331
|
+
function validateNotNull$1(shape, label) {
|
|
332
|
+
if (require_shapeTypes.getKernel().isNull(shape.wrapped)) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.NULL_SHAPE_INPUT, `${label} is a null shape`));
|
|
333
|
+
return require_errors.ok(void 0);
|
|
334
|
+
}
|
|
335
|
+
function castToShape3D(shape, errorCode, errorMsg, suggestion) {
|
|
336
|
+
const wrapped = require_shapeTypes.castShape(shape);
|
|
337
|
+
if (!require_shapeTypes.isShape3D(wrapped)) {
|
|
338
|
+
const shapeType = shape.ShapeType();
|
|
339
|
+
const typeName = [
|
|
340
|
+
"COMPOUND",
|
|
341
|
+
"COMPSOLID",
|
|
342
|
+
"SOLID",
|
|
343
|
+
"SHELL",
|
|
344
|
+
"FACE",
|
|
345
|
+
"WIRE",
|
|
346
|
+
"EDGE",
|
|
347
|
+
"VERTEX",
|
|
348
|
+
"SHAPE"
|
|
349
|
+
][shapeType] ?? `UNKNOWN(${shapeType})`;
|
|
350
|
+
wrapped[Symbol.dispose]();
|
|
351
|
+
return require_errors.err(require_errors.typeCastError(errorCode, `${errorMsg}. Got ${typeName} instead.`, void 0, void 0, suggestion));
|
|
352
|
+
}
|
|
353
|
+
return require_errors.ok(wrapped);
|
|
354
|
+
}
|
|
355
|
+
function resolveEdgeCallback$1(selectedEdges, callbackFn) {
|
|
356
|
+
const filteredEdges = [];
|
|
357
|
+
const hashToValue = /* @__PURE__ */ new Map();
|
|
358
|
+
for (const edge of selectedEdges) {
|
|
359
|
+
const val = callbackFn(edge) ?? 0;
|
|
360
|
+
if (typeof val === "number" && val <= 0) continue;
|
|
361
|
+
if (Array.isArray(val) && (val[0] <= 0 || val[1] <= 0)) continue;
|
|
362
|
+
filteredEdges.push(edge);
|
|
363
|
+
hashToValue.set(require_shapeTypes.getKernel().hashCode(edge.wrapped, require_vecOps.HASH_CODE_MAX), val);
|
|
364
|
+
}
|
|
365
|
+
if (filteredEdges.length === 0) return null;
|
|
366
|
+
const kernelParam = (ocEdge) => {
|
|
367
|
+
return hashToValue.get(ocEdge.HashCode(2147483647)) ?? 1;
|
|
368
|
+
};
|
|
369
|
+
return {
|
|
370
|
+
edges: filteredEdges,
|
|
371
|
+
kernelParam
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function fuseWithEvolution(a, b, { optimisation = "none", simplify = false, signal, fuzzyValue } = {}) {
|
|
375
|
+
if (signal?.aborted) throw signal.reason;
|
|
376
|
+
const checkA = validateShape3D(a, "fuseWithEvolution: first operand");
|
|
377
|
+
if (require_errors.isErr(checkA)) return checkA;
|
|
378
|
+
const checkB = validateShape3D(b, "fuseWithEvolution: second operand");
|
|
379
|
+
if (require_errors.isErr(checkB)) return checkB;
|
|
380
|
+
const inputFaceHashes = require_arrayAccess.collectInputFaceHashes([a, b]);
|
|
381
|
+
const { shape: resultShape, evolution } = require_shapeTypes.getKernel().fuseWithHistory(a.wrapped, b.wrapped, inputFaceHashes, require_vecOps.HASH_CODE_MAX, {
|
|
382
|
+
optimisation,
|
|
383
|
+
simplify,
|
|
384
|
+
fuzzyValue
|
|
385
|
+
});
|
|
386
|
+
const fuseResult = castToShape3D(resultShape, "FUSE_NOT_3D", "Fuse did not produce a 3D shape", "Common causes: overlapping coplanar faces, zero-thickness geometry, or non-manifold input. Try autoHeal() on inputs first.");
|
|
387
|
+
if (fuseResult.ok) {
|
|
388
|
+
require_arrayAccess.propagateAllMetadata(evolution, [a, b], fuseResult.value);
|
|
389
|
+
return require_errors.ok({
|
|
390
|
+
shape: fuseResult.value,
|
|
391
|
+
evolution
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
return fuseResult;
|
|
395
|
+
}
|
|
396
|
+
function cutWithEvolution(base, tool, { optimisation = "none", simplify = false, signal, fuzzyValue } = {}) {
|
|
397
|
+
if (signal?.aborted) throw signal.reason;
|
|
398
|
+
const checkBase = validateShape3D(base, "cutWithEvolution: base");
|
|
399
|
+
if (require_errors.isErr(checkBase)) return checkBase;
|
|
400
|
+
const checkTool = validateShape3D(tool, "cutWithEvolution: tool");
|
|
401
|
+
if (require_errors.isErr(checkTool)) return checkTool;
|
|
402
|
+
const inputFaceHashes = require_arrayAccess.collectInputFaceHashes([base, tool]);
|
|
403
|
+
const { shape: resultShape, evolution } = require_shapeTypes.getKernel().cutWithHistory(base.wrapped, tool.wrapped, inputFaceHashes, require_vecOps.HASH_CODE_MAX, {
|
|
404
|
+
optimisation,
|
|
405
|
+
simplify,
|
|
406
|
+
fuzzyValue
|
|
407
|
+
});
|
|
408
|
+
const cutResult = castToShape3D(resultShape, "CUT_NOT_3D", "Cut did not produce a 3D shape", "Common causes: tool does not fully intersect the base, or produces a zero-thickness sliver. Ensure the tool extends through the shape.");
|
|
409
|
+
if (cutResult.ok) {
|
|
410
|
+
require_arrayAccess.propagateAllMetadata(evolution, [base, tool], cutResult.value);
|
|
411
|
+
return require_errors.ok({
|
|
412
|
+
shape: cutResult.value,
|
|
413
|
+
evolution
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return cutResult;
|
|
417
|
+
}
|
|
418
|
+
function intersectWithEvolution(a, b, { simplify = false, signal, fuzzyValue } = {}) {
|
|
419
|
+
if (signal?.aborted) throw signal.reason;
|
|
420
|
+
const checkA = validateShape3D(a, "intersectWithEvolution: first operand");
|
|
421
|
+
if (require_errors.isErr(checkA)) return checkA;
|
|
422
|
+
const checkB = validateShape3D(b, "intersectWithEvolution: second operand");
|
|
423
|
+
if (require_errors.isErr(checkB)) return checkB;
|
|
424
|
+
const inputFaceHashes = require_arrayAccess.collectInputFaceHashes([a, b]);
|
|
425
|
+
const { shape: resultShape, evolution } = require_shapeTypes.getKernel().intersectWithHistory(a.wrapped, b.wrapped, inputFaceHashes, require_vecOps.HASH_CODE_MAX, {
|
|
426
|
+
simplify,
|
|
427
|
+
fuzzyValue
|
|
428
|
+
});
|
|
429
|
+
const intResult = castToShape3D(resultShape, "INTERSECT_NOT_3D", "Intersect did not produce a 3D shape", "Shapes may not overlap. Verify they share a common volume before intersecting.");
|
|
430
|
+
if (intResult.ok) {
|
|
431
|
+
require_arrayAccess.propagateAllMetadata(evolution, [a, b], intResult.value);
|
|
432
|
+
return require_errors.ok({
|
|
433
|
+
shape: intResult.value,
|
|
434
|
+
evolution
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
return intResult;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Apply a fillet (rounded edge) to selected edges, returning both
|
|
441
|
+
* the result shape and the face evolution data.
|
|
442
|
+
*
|
|
443
|
+
* @param shape - The shape to modify.
|
|
444
|
+
* @param edges - Edges to fillet. Pass `undefined` to fillet all edges.
|
|
445
|
+
* @param radius - Constant radius, variable radius `[r1, r2]`, or per-edge callback.
|
|
446
|
+
*/
|
|
447
|
+
function filletWithEvolution(shape, edges, radius) {
|
|
448
|
+
const check = validateNotNull$1(shape, "filletWithEvolution: shape");
|
|
449
|
+
if (require_errors.isErr(check)) return check;
|
|
450
|
+
if (typeof radius === "number" && radius <= 0) return require_errors.err(require_errors.validationError("INVALID_FILLET_RADIUS", "Fillet radius must be positive", void 0, void 0, "Provide a positive radius value greater than 0"));
|
|
451
|
+
if (Array.isArray(radius) && (radius[0] <= 0 || radius[1] <= 0)) return require_errors.err(require_errors.validationError("INVALID_FILLET_RADIUS", "Fillet radii must both be positive", void 0, void 0, "Both radius values must be greater than 0"));
|
|
452
|
+
const selectedEdges = edges ?? require_arrayAccess.getEdges(shape);
|
|
453
|
+
if (selectedEdges.length === 0) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.FILLET_NO_EDGES, "No edges found for fillet", void 0, void 0, "Check that the shape has edges, or adjust your edge finder criteria"));
|
|
454
|
+
try {
|
|
455
|
+
let filteredEdges;
|
|
456
|
+
let kernelRadius;
|
|
457
|
+
if (typeof radius === "function") {
|
|
458
|
+
const resolved = resolveEdgeCallback$1(selectedEdges, radius);
|
|
459
|
+
if (!resolved) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.FILLET_NO_EDGES, "No edges with positive radius for fillet", void 0, void 0, "Check that the radius callback returns positive values"));
|
|
460
|
+
filteredEdges = resolved.edges;
|
|
461
|
+
kernelRadius = resolved.kernelParam;
|
|
462
|
+
} else {
|
|
463
|
+
filteredEdges = [...selectedEdges];
|
|
464
|
+
kernelRadius = radius;
|
|
465
|
+
}
|
|
466
|
+
const inputFaceHashes = require_arrayAccess.collectInputFaceHashes([shape]);
|
|
467
|
+
const { shape: resultShape, evolution } = require_shapeTypes.getKernel().filletWithHistory(shape.wrapped, filteredEdges.map((e) => e.wrapped), kernelRadius, inputFaceHashes, require_vecOps.HASH_CODE_MAX);
|
|
468
|
+
const cast = require_shapeTypes.castShape(resultShape);
|
|
469
|
+
if (!require_shapeTypes.isShape3D(cast)) return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.FILLET_NOT_3D, "Fillet result is not a 3D shape"));
|
|
470
|
+
require_arrayAccess.propagateAllMetadata(evolution, [shape], cast);
|
|
471
|
+
return require_errors.ok({
|
|
472
|
+
shape: cast,
|
|
473
|
+
evolution
|
|
474
|
+
});
|
|
475
|
+
} catch (e) {
|
|
476
|
+
return require_errors.err(require_errors.kernelError("FILLET_FAILED", `Fillet operation failed: ${e instanceof Error ? e.message : String(e)}`, e, {
|
|
477
|
+
operation: "fillet",
|
|
478
|
+
edgeCount: selectedEdges.length,
|
|
479
|
+
radius
|
|
480
|
+
}));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Apply a chamfer (beveled edge) to selected edges, returning both
|
|
485
|
+
* the result shape and the face evolution data.
|
|
486
|
+
*
|
|
487
|
+
* @param shape - The shape to modify.
|
|
488
|
+
* @param edges - Edges to chamfer. Pass `undefined` to chamfer all edges.
|
|
489
|
+
* @param distance - Symmetric distance, asymmetric `[d1, d2]`, or per-edge callback.
|
|
490
|
+
*/
|
|
491
|
+
function chamferWithEvolution(shape, edges, distance) {
|
|
492
|
+
const check = validateNotNull$1(shape, "chamferWithEvolution: shape");
|
|
493
|
+
if (require_errors.isErr(check)) return check;
|
|
494
|
+
if (typeof distance === "number" && distance <= 0) return require_errors.err(require_errors.validationError("INVALID_CHAMFER_DISTANCE", "Chamfer distance must be positive", void 0, void 0, "Provide a positive distance value greater than 0"));
|
|
495
|
+
if (Array.isArray(distance) && (distance[0] <= 0 || distance[1] <= 0)) return require_errors.err(require_errors.validationError("INVALID_CHAMFER_DISTANCE", "Chamfer distances must both be positive", void 0, void 0, "Both distance values must be greater than 0"));
|
|
496
|
+
const selectedEdges = edges ?? require_arrayAccess.getEdges(shape);
|
|
497
|
+
if (selectedEdges.length === 0) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.CHAMFER_NO_EDGES, "No edges found for chamfer"));
|
|
498
|
+
try {
|
|
499
|
+
let filteredEdges;
|
|
500
|
+
let kernelDistance;
|
|
501
|
+
if (typeof distance === "function") {
|
|
502
|
+
const resolved = resolveEdgeCallback$1(selectedEdges, distance);
|
|
503
|
+
if (!resolved) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.CHAMFER_NO_EDGES, "No edges with positive distance for chamfer"));
|
|
504
|
+
filteredEdges = resolved.edges;
|
|
505
|
+
kernelDistance = resolved.kernelParam;
|
|
506
|
+
} else {
|
|
507
|
+
filteredEdges = [...selectedEdges];
|
|
508
|
+
kernelDistance = distance;
|
|
509
|
+
}
|
|
510
|
+
const inputFaceHashes = require_arrayAccess.collectInputFaceHashes([shape]);
|
|
511
|
+
const { shape: resultShape, evolution } = require_shapeTypes.getKernel().chamferWithHistory(shape.wrapped, filteredEdges.map((e) => e.wrapped), kernelDistance, inputFaceHashes, require_vecOps.HASH_CODE_MAX);
|
|
512
|
+
const cast = require_shapeTypes.castShape(resultShape);
|
|
513
|
+
if (!require_shapeTypes.isShape3D(cast)) return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.CHAMFER_NOT_3D, "Chamfer result is not a 3D shape"));
|
|
514
|
+
require_arrayAccess.propagateAllMetadata(evolution, [shape], cast);
|
|
515
|
+
return require_errors.ok({
|
|
516
|
+
shape: cast,
|
|
517
|
+
evolution
|
|
518
|
+
});
|
|
519
|
+
} catch (e) {
|
|
520
|
+
return require_errors.err(require_errors.kernelError("CHAMFER_FAILED", `Chamfer operation failed: ${e instanceof Error ? e.message : String(e)}`, e, {
|
|
521
|
+
operation: "chamfer",
|
|
522
|
+
edgeCount: selectedEdges.length,
|
|
523
|
+
distance
|
|
524
|
+
}));
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Create a hollow shell by removing faces and offsetting remaining walls,
|
|
529
|
+
* returning both the result shape and the face evolution data.
|
|
530
|
+
*
|
|
531
|
+
* @param shape - The solid to hollow out.
|
|
532
|
+
* @param faces - Faces to remove.
|
|
533
|
+
* @param thickness - Wall thickness.
|
|
534
|
+
* @param tolerance - Shell operation tolerance (default 1e-3).
|
|
535
|
+
*/
|
|
536
|
+
function shellWithEvolution(shape, faces, thickness, tolerance = .001) {
|
|
537
|
+
const check = validateNotNull$1(shape, "shellWithEvolution: shape");
|
|
538
|
+
if (require_errors.isErr(check)) return check;
|
|
539
|
+
if (thickness <= 0) return require_errors.err(require_errors.validationError("INVALID_THICKNESS", "Shell thickness must be positive"));
|
|
540
|
+
if (faces.length === 0) return require_errors.err(require_errors.validationError("NO_FACES", "At least one face must be specified for shell"));
|
|
541
|
+
try {
|
|
542
|
+
const inputFaceHashes = require_arrayAccess.collectInputFaceHashes([shape]);
|
|
543
|
+
const { shape: resultShape, evolution } = require_shapeTypes.getKernel().shellWithHistory(shape.wrapped, faces.map((f) => f.wrapped), thickness, inputFaceHashes, require_vecOps.HASH_CODE_MAX, tolerance);
|
|
544
|
+
const cast = require_shapeTypes.castShape(resultShape);
|
|
545
|
+
if (!require_shapeTypes.isShape3D(cast)) return require_errors.err(require_errors.kernelError("SHELL_RESULT_NOT_3D", "Shell result is not a 3D shape"));
|
|
546
|
+
require_arrayAccess.propagateAllMetadata(evolution, [shape], cast);
|
|
547
|
+
return require_errors.ok({
|
|
548
|
+
shape: cast,
|
|
549
|
+
evolution
|
|
550
|
+
});
|
|
551
|
+
} catch (e) {
|
|
552
|
+
return require_errors.err(require_errors.kernelError("SHELL_FAILED", `Shell operation failed: ${e instanceof Error ? e.message : String(e)}`, e, {
|
|
553
|
+
operation: "shell",
|
|
554
|
+
faceCount: faces.length,
|
|
555
|
+
thickness
|
|
556
|
+
}));
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
//#endregion
|
|
560
|
+
//#region src/topology/positionFns.ts
|
|
561
|
+
/**
|
|
562
|
+
* Curve-based positioning operations.
|
|
563
|
+
*/
|
|
564
|
+
/**
|
|
565
|
+
* Position a shape at a point along a spine curve with Frenet frame orientation.
|
|
566
|
+
*
|
|
567
|
+
* The shape is translated and rotated so its origin aligns with the curve point
|
|
568
|
+
* and its Z axis aligns with the curve tangent at the given parameter.
|
|
569
|
+
*
|
|
570
|
+
* @param shape - The shape to position.
|
|
571
|
+
* @param spine - The spine curve (Edge or Wire) to position along.
|
|
572
|
+
* @param param - Normalized parameter (0 = start, 1 = end).
|
|
573
|
+
* @returns The repositioned shape.
|
|
574
|
+
*/
|
|
575
|
+
function positionOnCurve(shape, spine, param) {
|
|
576
|
+
try {
|
|
577
|
+
const wrapped = require_shapeTypes.castShape(require_shapeTypes.getKernel().positionOnCurve(shape.wrapped, spine.wrapped, param));
|
|
578
|
+
if (!require_shapeTypes.isShape3D(wrapped)) return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.POSITION_ON_CURVE_FAILED, "positionOnCurve did not produce a 3D shape"));
|
|
579
|
+
return require_errors.ok(wrapped);
|
|
580
|
+
} catch (e) {
|
|
581
|
+
return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.POSITION_ON_CURVE_FAILED, `Failed to position shape on curve at param ${param}`, e));
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
//#endregion
|
|
585
|
+
//#region src/topology/modifierFns.ts
|
|
586
|
+
/**
|
|
587
|
+
* Functional modifier operations — fillet, chamfer, shell, thicken, offset, draft.
|
|
588
|
+
*
|
|
589
|
+
* These are standalone functions that operate on branded shape types
|
|
590
|
+
* and return Result values.
|
|
591
|
+
*/
|
|
592
|
+
function validateNotNull(shape, label) {
|
|
593
|
+
if (require_shapeTypes.getKernel().isNull(shape.wrapped)) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.NULL_SHAPE_INPUT, `${label} is a null shape`));
|
|
594
|
+
return require_errors.ok(void 0);
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Validate that a scalar or `[a, b]` pair is positive.
|
|
598
|
+
* Returns an Err Result on failure, `undefined` on success.
|
|
599
|
+
*
|
|
600
|
+
* Function-type values (per-edge callbacks) are intentionally skipped here --
|
|
601
|
+
* they are validated lazily in {@link resolveEdgeCallback} when each edge is processed.
|
|
602
|
+
*/
|
|
603
|
+
function validatePositiveParam(value, msgs) {
|
|
604
|
+
if (typeof value === "number" && value <= 0) return require_errors.err(require_errors.validationError(msgs.code, msgs.scalar, void 0, void 0, msgs.scalarHint));
|
|
605
|
+
if (Array.isArray(value) && (value[0] <= 0 || value[1] <= 0)) return require_errors.err(require_errors.validationError(msgs.code, msgs.pair, void 0, void 0, msgs.pairHint));
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* When the user supplies a per-edge callback, pre-filter edges and build a
|
|
609
|
+
* hash-indexed lookup for the kernel. Returns `null` if no edges survive.
|
|
610
|
+
*/
|
|
611
|
+
function resolveEdgeCallback(selectedEdges, callbackFn) {
|
|
612
|
+
const filteredEdges = [];
|
|
613
|
+
const hashToValue = /* @__PURE__ */ new Map();
|
|
614
|
+
for (const edge of selectedEdges) {
|
|
615
|
+
const val = callbackFn(edge) ?? 0;
|
|
616
|
+
if (typeof val === "number" && val <= 0) continue;
|
|
617
|
+
if (Array.isArray(val) && (val[0] <= 0 || val[1] <= 0)) continue;
|
|
618
|
+
filteredEdges.push(edge);
|
|
619
|
+
hashToValue.set(require_shapeTypes.getKernel().hashCode(edge.wrapped, require_vecOps.HASH_CODE_MAX), val);
|
|
620
|
+
}
|
|
621
|
+
if (filteredEdges.length === 0) return null;
|
|
622
|
+
const kernelParam = (ocEdge) => {
|
|
623
|
+
return hashToValue.get(ocEdge.HashCode(2147483647)) ?? 1;
|
|
624
|
+
};
|
|
625
|
+
return {
|
|
626
|
+
edges: filteredEdges,
|
|
627
|
+
kernelParam
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Cast a kernel result to a Shape3D, propagate metadata, and wrap in `ok()`.
|
|
632
|
+
* Returns an error if the result is not a 3D shape.
|
|
633
|
+
*/
|
|
634
|
+
function finalizeShape3D(evolution, resultShape, inputs, not3dCode, not3dMessage) {
|
|
635
|
+
const cast = require_shapeTypes.castShape(resultShape);
|
|
636
|
+
if (!require_shapeTypes.isShape3D(cast)) return require_errors.err(require_errors.kernelError(not3dCode, not3dMessage));
|
|
637
|
+
require_arrayAccess.propagateAllMetadata(evolution, inputs, cast);
|
|
638
|
+
return require_errors.ok(cast);
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* When the user supplies a per-face callback for draft angle, pre-filter
|
|
642
|
+
* faces and build a hash-indexed lookup for the kernel.
|
|
643
|
+
*/
|
|
644
|
+
function resolveDraftCallback(faces, angle) {
|
|
645
|
+
if (typeof angle !== "function") return {
|
|
646
|
+
filteredFaces: [...faces],
|
|
647
|
+
kernelAngle: angle
|
|
648
|
+
};
|
|
649
|
+
const filteredFaces = [];
|
|
650
|
+
const hashToAngle = /* @__PURE__ */ new Map();
|
|
651
|
+
for (const face of faces) {
|
|
652
|
+
const a = angle(face);
|
|
653
|
+
if (a === null || a === 0 || Math.abs(a) >= 90) continue;
|
|
654
|
+
filteredFaces.push(face);
|
|
655
|
+
hashToAngle.set(require_shapeTypes.getKernel().hashCode(face.wrapped, require_vecOps.HASH_CODE_MAX), a);
|
|
656
|
+
}
|
|
657
|
+
const kernelAngle = (ocFace) => {
|
|
658
|
+
const a = hashToAngle.get(ocFace.HashCode(require_vecOps.HASH_CODE_MAX));
|
|
659
|
+
if (a === void 0) throw new Error("draft: face hash not found — possible hash collision");
|
|
660
|
+
return a;
|
|
661
|
+
};
|
|
662
|
+
return {
|
|
663
|
+
filteredFaces,
|
|
664
|
+
kernelAngle
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Thickens a surface (face or shell) into a solid by offsetting it.
|
|
669
|
+
*
|
|
670
|
+
* Takes a planar or non-planar surface shape and creates a solid
|
|
671
|
+
* by offsetting it by the given thickness. Positive thickness offsets
|
|
672
|
+
* along the surface normal; negative thickness offsets against it.
|
|
673
|
+
*/
|
|
674
|
+
function thicken(shape, thickness) {
|
|
675
|
+
const check = validateNotNull(shape, "thicken: shape");
|
|
676
|
+
if (require_errors.isErr(check)) return check;
|
|
677
|
+
try {
|
|
678
|
+
const inputFaceHashes = require_arrayAccess.collectInputFaceHashes([shape]);
|
|
679
|
+
const { shape: resultShape, evolution } = require_shapeTypes.getKernel().thickenWithHistory(shape.wrapped, thickness, inputFaceHashes, require_vecOps.HASH_CODE_MAX);
|
|
680
|
+
const cast = require_shapeTypes.castShape(resultShape);
|
|
681
|
+
require_arrayAccess.propagateAllMetadata(evolution, [shape], cast);
|
|
682
|
+
return require_errors.ok(cast);
|
|
683
|
+
} catch (e) {
|
|
684
|
+
return require_errors.err(require_errors.kernelError("THICKEN_FAILED", `Thicken operation failed: ${e instanceof Error ? e.message : String(e)}`, e));
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Apply a fillet (rounded edge) to selected edges of a 3D shape.
|
|
689
|
+
*
|
|
690
|
+
* @param shape - The shape to modify.
|
|
691
|
+
* @param edges - Edges to fillet. Pass `undefined` to fillet all edges.
|
|
692
|
+
* @param radius - Constant radius, variable radius `[r1, r2]`, or per-edge callback.
|
|
693
|
+
*/
|
|
694
|
+
function fillet(shape, edges, radius) {
|
|
695
|
+
const check = validateNotNull(shape, "fillet: shape");
|
|
696
|
+
if (require_errors.isErr(check)) return check;
|
|
697
|
+
const paramErr = validatePositiveParam(radius, {
|
|
698
|
+
code: "INVALID_FILLET_RADIUS",
|
|
699
|
+
scalar: "Fillet radius must be positive",
|
|
700
|
+
pair: "Fillet radii must both be positive",
|
|
701
|
+
scalarHint: "Provide a positive radius value greater than 0",
|
|
702
|
+
pairHint: "Both radius values must be greater than 0"
|
|
703
|
+
});
|
|
704
|
+
if (paramErr) return paramErr;
|
|
705
|
+
const selectedEdges = edges ?? require_arrayAccess.getEdges(shape);
|
|
706
|
+
if (selectedEdges.length === 0) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.FILLET_NO_EDGES, "No edges found for fillet", void 0, void 0, "Check that the shape has edges, or adjust your edge finder criteria"));
|
|
707
|
+
try {
|
|
708
|
+
let filteredEdges;
|
|
709
|
+
let kernelRadius;
|
|
710
|
+
if (typeof radius === "function") {
|
|
711
|
+
const resolved = resolveEdgeCallback(selectedEdges, radius);
|
|
712
|
+
if (!resolved) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.FILLET_NO_EDGES, "No edges with positive radius for fillet", void 0, void 0, "Check that the radius callback returns positive values"));
|
|
713
|
+
filteredEdges = resolved.edges;
|
|
714
|
+
kernelRadius = resolved.kernelParam;
|
|
715
|
+
} else {
|
|
716
|
+
filteredEdges = [...selectedEdges];
|
|
717
|
+
kernelRadius = radius;
|
|
718
|
+
}
|
|
719
|
+
const inputFaceHashes = require_arrayAccess.collectInputFaceHashes([shape]);
|
|
720
|
+
const { shape: resultShape, evolution } = require_shapeTypes.getKernel().filletWithHistory(shape.wrapped, filteredEdges.map((e) => e.wrapped), kernelRadius, inputFaceHashes, require_vecOps.HASH_CODE_MAX);
|
|
721
|
+
return finalizeShape3D(evolution, resultShape, [shape], require_errors.BrepErrorCode.FILLET_NOT_3D, "Fillet result is not a 3D shape");
|
|
722
|
+
} catch (e) {
|
|
723
|
+
return require_errors.err(require_errors.kernelError("FILLET_FAILED", `Fillet operation failed: ${e instanceof Error ? e.message : String(e)}`, e, {
|
|
724
|
+
operation: "fillet",
|
|
725
|
+
edgeCount: selectedEdges.length,
|
|
726
|
+
radius
|
|
727
|
+
}));
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Apply a chamfer (beveled edge) to selected edges of a 3D shape.
|
|
732
|
+
*
|
|
733
|
+
* @param shape - The shape to modify.
|
|
734
|
+
* @param edges - Edges to chamfer. Pass `undefined` to chamfer all edges.
|
|
735
|
+
* @param distance - Symmetric distance, asymmetric `[d1, d2]`, or per-edge callback.
|
|
736
|
+
*/
|
|
737
|
+
function chamfer(shape, edges, distance) {
|
|
738
|
+
const check = validateNotNull(shape, "chamfer: shape");
|
|
739
|
+
if (require_errors.isErr(check)) return check;
|
|
740
|
+
const paramErr = validatePositiveParam(distance, {
|
|
741
|
+
code: "INVALID_CHAMFER_DISTANCE",
|
|
742
|
+
scalar: "Chamfer distance must be positive",
|
|
743
|
+
pair: "Chamfer distances must both be positive",
|
|
744
|
+
scalarHint: "Provide a positive distance value greater than 0",
|
|
745
|
+
pairHint: "Both distance values must be greater than 0"
|
|
746
|
+
});
|
|
747
|
+
if (paramErr) return paramErr;
|
|
748
|
+
const selectedEdges = edges ?? require_arrayAccess.getEdges(shape);
|
|
749
|
+
if (selectedEdges.length === 0) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.CHAMFER_NO_EDGES, "No edges found for chamfer"));
|
|
750
|
+
try {
|
|
751
|
+
let filteredEdges;
|
|
752
|
+
let kernelDistance;
|
|
753
|
+
if (typeof distance === "function") {
|
|
754
|
+
const resolved = resolveEdgeCallback(selectedEdges, distance);
|
|
755
|
+
if (!resolved) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.CHAMFER_NO_EDGES, "No edges with positive distance for chamfer"));
|
|
756
|
+
filteredEdges = resolved.edges;
|
|
757
|
+
kernelDistance = resolved.kernelParam;
|
|
758
|
+
} else {
|
|
759
|
+
filteredEdges = [...selectedEdges];
|
|
760
|
+
kernelDistance = distance;
|
|
761
|
+
}
|
|
762
|
+
const inputFaceHashes = require_arrayAccess.collectInputFaceHashes([shape]);
|
|
763
|
+
const { shape: resultShape, evolution } = require_shapeTypes.getKernel().chamferWithHistory(shape.wrapped, filteredEdges.map((e) => e.wrapped), kernelDistance, inputFaceHashes, require_vecOps.HASH_CODE_MAX);
|
|
764
|
+
return finalizeShape3D(evolution, resultShape, [shape], require_errors.BrepErrorCode.CHAMFER_NOT_3D, "Chamfer result is not a 3D shape");
|
|
765
|
+
} catch (e) {
|
|
766
|
+
return require_errors.err(require_errors.kernelError("CHAMFER_FAILED", `Chamfer operation failed: ${e instanceof Error ? e.message : String(e)}`, e, {
|
|
767
|
+
operation: "chamfer",
|
|
768
|
+
edgeCount: selectedEdges.length,
|
|
769
|
+
distance
|
|
770
|
+
}));
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Create a hollow shell by removing faces and offsetting remaining walls.
|
|
775
|
+
*
|
|
776
|
+
* @param shape - The solid to hollow out.
|
|
777
|
+
* @param faces - Faces to remove.
|
|
778
|
+
* @param thickness - Wall thickness.
|
|
779
|
+
* @param tolerance - Shell operation tolerance (default 1e-3).
|
|
780
|
+
*/
|
|
781
|
+
function shell(shape, faces, thickness, tolerance = .001) {
|
|
782
|
+
const check = validateNotNull(shape, "shell: shape");
|
|
783
|
+
if (require_errors.isErr(check)) return check;
|
|
784
|
+
if (thickness <= 0) return require_errors.err(require_errors.validationError("INVALID_THICKNESS", "Shell thickness must be positive"));
|
|
785
|
+
if (faces.length === 0) return require_errors.err(require_errors.validationError("NO_FACES", "At least one face must be specified for shell"));
|
|
786
|
+
try {
|
|
787
|
+
const inputFaceHashes = require_arrayAccess.collectInputFaceHashes([shape]);
|
|
788
|
+
const { shape: resultShape, evolution } = require_shapeTypes.getKernel().shellWithHistory(shape.wrapped, faces.map((f) => f.wrapped), thickness, inputFaceHashes, require_vecOps.HASH_CODE_MAX, tolerance);
|
|
789
|
+
const cast = require_shapeTypes.castShape(resultShape);
|
|
790
|
+
if (!require_shapeTypes.isShape3D(cast)) return require_errors.err(require_errors.kernelError("SHELL_RESULT_NOT_3D", "Shell result is not a 3D shape"));
|
|
791
|
+
require_arrayAccess.propagateAllMetadata(evolution, [shape], cast);
|
|
792
|
+
return require_errors.ok(cast);
|
|
793
|
+
} catch (e) {
|
|
794
|
+
return require_errors.err(require_errors.kernelError("SHELL_FAILED", `Shell operation failed: ${e instanceof Error ? e.message : String(e)}`, e, {
|
|
795
|
+
operation: "shell",
|
|
796
|
+
faceCount: faces.length,
|
|
797
|
+
thickness
|
|
798
|
+
}));
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Offset all faces of a shape by a given distance.
|
|
803
|
+
*
|
|
804
|
+
* @param shape - The shape to offset (must be a 3D shape with faces).
|
|
805
|
+
* @param distance - Offset distance (positive = outward, negative = inward).
|
|
806
|
+
* @param tolerance - Offset tolerance (default 1e-6).
|
|
807
|
+
*/
|
|
808
|
+
function offset(shape, distance, tolerance = 1e-6) {
|
|
809
|
+
const check = validateNotNull(shape, "offset: shape");
|
|
810
|
+
if (require_errors.isErr(check)) return check;
|
|
811
|
+
if (distance === 0) return require_errors.err(require_errors.validationError("ZERO_OFFSET", "Offset distance cannot be zero"));
|
|
812
|
+
try {
|
|
813
|
+
const inputFaceHashes = require_arrayAccess.collectInputFaceHashes([shape]);
|
|
814
|
+
const { shape: resultShape, evolution } = require_shapeTypes.getKernel().offsetWithHistory(shape.wrapped, distance, inputFaceHashes, require_vecOps.HASH_CODE_MAX, tolerance);
|
|
815
|
+
const cast = require_shapeTypes.castShape(resultShape);
|
|
816
|
+
if (!require_shapeTypes.isShape3D(cast)) return require_errors.err(require_errors.kernelError("OFFSET_RESULT_NOT_3D", "Offset result is not a 3D shape"));
|
|
817
|
+
require_arrayAccess.propagateAllMetadata(evolution, [shape], cast);
|
|
818
|
+
return require_errors.ok(cast);
|
|
819
|
+
} catch (e) {
|
|
820
|
+
return require_errors.err(require_errors.kernelError("OFFSET_FAILED", `Offset operation failed: ${e instanceof Error ? e.message : String(e)}`, e));
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Apply a draft (taper) to selected faces of a 3D shape.
|
|
825
|
+
*
|
|
826
|
+
* Draft tilts faces by a specified angle relative to a pull direction,
|
|
827
|
+
* pivoting about a neutral plane. This is essential for injection molding
|
|
828
|
+
* and casting workflows where parts must release from a mold.
|
|
829
|
+
*
|
|
830
|
+
* @param shape - The solid to modify.
|
|
831
|
+
* @param faces - Faces to draft.
|
|
832
|
+
* @param pullDirection - Mold opening direction vector.
|
|
833
|
+
* @param neutralPlane - A point on the plane where faces are not displaced.
|
|
834
|
+
* @param angle - Constant angle in degrees, or per-face callback returning degrees (null to skip).
|
|
835
|
+
*/
|
|
836
|
+
function draft(shape, faces, pullDirection, neutralPlane, angle) {
|
|
837
|
+
const check = validateNotNull(shape, "draft: shape");
|
|
838
|
+
if (require_errors.isErr(check)) return check;
|
|
839
|
+
if (typeof angle === "number") {
|
|
840
|
+
if (angle === 0) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.DRAFT_INVALID_ANGLE, "Draft angle cannot be zero", void 0, void 0, "Provide a non-zero angle in degrees"));
|
|
841
|
+
if (Math.abs(angle) >= 90) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.DRAFT_INVALID_ANGLE, "Draft angle must be between -90 and 90 degrees (exclusive)", void 0, void 0, "Typical draft angles are 1-5 degrees for injection molding"));
|
|
842
|
+
}
|
|
843
|
+
if (faces.length === 0) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.DRAFT_NO_FACES, "No faces specified for draft", void 0, void 0, "Select at least one face to apply the draft angle to"));
|
|
844
|
+
try {
|
|
845
|
+
const { filteredFaces, kernelAngle } = resolveDraftCallback(faces, angle);
|
|
846
|
+
if (filteredFaces.length === 0) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.DRAFT_NO_FACES, "No faces with valid draft angle", void 0, void 0, "Check that the angle callback returns non-zero values between -90 and 90 degrees"));
|
|
847
|
+
const inputFaceHashes = require_arrayAccess.collectInputFaceHashes([shape]);
|
|
848
|
+
const { shape: resultShape, evolution } = require_shapeTypes.getKernel().draftWithHistory(shape.wrapped, filteredFaces.map((f) => f.wrapped), [
|
|
849
|
+
pullDirection[0],
|
|
850
|
+
pullDirection[1],
|
|
851
|
+
pullDirection[2]
|
|
852
|
+
], [
|
|
853
|
+
neutralPlane[0],
|
|
854
|
+
neutralPlane[1],
|
|
855
|
+
neutralPlane[2]
|
|
856
|
+
], kernelAngle, inputFaceHashes, require_vecOps.HASH_CODE_MAX);
|
|
857
|
+
return finalizeShape3D(evolution, resultShape, [shape], require_errors.BrepErrorCode.DRAFT_NOT_3D, "Draft result is not a 3D shape");
|
|
858
|
+
} catch (e) {
|
|
859
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
860
|
+
return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.DRAFT_FAILED, `Draft operation failed: ${raw}`, e, {
|
|
861
|
+
operation: "draft",
|
|
862
|
+
faceCount: faces.length,
|
|
863
|
+
angle
|
|
864
|
+
}));
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Apply a variable-radius fillet to an edge.
|
|
869
|
+
*
|
|
870
|
+
* The radius varies along the edge according to the provided spec points.
|
|
871
|
+
* Each point specifies a normalized parameter (0 = start, 1 = end) and radius.
|
|
872
|
+
*
|
|
873
|
+
* **Cross-kernel note:** Only brepkit supports variable-radius fillet.
|
|
874
|
+
* Returns UNSUPPORTED_CAPABILITY error on OCCT.
|
|
875
|
+
*/
|
|
876
|
+
function variableFillet(shape, edge, radii) {
|
|
877
|
+
if (radii.length === 0) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.VARIABLE_FILLET_FAILED, "radii must contain at least one radius spec"));
|
|
878
|
+
for (const r of radii) if (r.radius <= 0) return require_errors.err(require_errors.validationError(require_errors.BrepErrorCode.VARIABLE_FILLET_FAILED, "All radius values must be positive"));
|
|
879
|
+
const kernel = require_shapeTypes.getKernel();
|
|
880
|
+
try {
|
|
881
|
+
const spec = JSON.stringify({
|
|
882
|
+
edge: kernel.hashCode(edge.wrapped, require_vecOps.HASH_CODE_MAX),
|
|
883
|
+
radii: radii.map((r) => ({
|
|
884
|
+
param: r.param,
|
|
885
|
+
radius: r.radius
|
|
886
|
+
}))
|
|
887
|
+
});
|
|
888
|
+
const wrapped = require_shapeTypes.castShape(kernel.filletVariable(shape.wrapped, spec));
|
|
889
|
+
if (!require_shapeTypes.isShape3D(wrapped)) {
|
|
890
|
+
wrapped[Symbol.dispose]();
|
|
891
|
+
return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.VARIABLE_FILLET_FAILED, "Variable-radius fillet did not produce a 3D shape"));
|
|
892
|
+
}
|
|
893
|
+
if (!require_shapeTypes.isSolid(wrapped)) {
|
|
894
|
+
wrapped[Symbol.dispose]();
|
|
895
|
+
return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.VARIABLE_FILLET_FAILED, "Variable-radius fillet did not produce a solid"));
|
|
896
|
+
}
|
|
897
|
+
return require_errors.ok(wrapped);
|
|
898
|
+
} catch (e) {
|
|
899
|
+
return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.VARIABLE_FILLET_FAILED, "Variable-radius fillet failed", e));
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
//#endregion
|
|
903
|
+
//#region src/topology/healingFns.ts
|
|
904
|
+
/**
|
|
905
|
+
* Shape healing and validation functions.
|
|
906
|
+
*
|
|
907
|
+
* Uses ShapeFix_Solid, ShapeFix_Face, ShapeFix_Wire, and BRepCheck_Analyzer
|
|
908
|
+
* to validate and repair shapes.
|
|
909
|
+
*/
|
|
910
|
+
/**
|
|
911
|
+
* Check if a shape is valid according to kernel geometry and topology checks.
|
|
912
|
+
*/
|
|
913
|
+
function isValid(shape) {
|
|
914
|
+
return require_shapeTypes.getKernel().isValid(shape.wrapped);
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Attempt to heal/fix a solid shape.
|
|
918
|
+
*
|
|
919
|
+
* Uses ShapeFix_Solid to repair topology issues like gaps, wrong orientation, etc.
|
|
920
|
+
*/
|
|
921
|
+
function healSolid(solid) {
|
|
922
|
+
if (!require_shapeTypes.isSolid(solid)) return require_errors.err(require_errors.validationError("NOT_A_SOLID", "Input shape is not a solid"));
|
|
923
|
+
const alreadyValid = isValid(solid);
|
|
924
|
+
try {
|
|
925
|
+
const result = require_shapeTypes.getKernel().healSolid(solid.wrapped);
|
|
926
|
+
if (!result) {
|
|
927
|
+
if (alreadyValid) return require_errors.ok(solid);
|
|
928
|
+
return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.HEAL_NO_EFFECT, "Solid healing had no effect — shape is still invalid"));
|
|
929
|
+
}
|
|
930
|
+
const cast = require_shapeTypes.castShape(result);
|
|
931
|
+
if (!require_shapeTypes.isSolid(cast)) return require_errors.err(require_errors.kernelError("HEAL_RESULT_NOT_SOLID", "Healed result is not a solid"));
|
|
932
|
+
if (!isValid(cast)) return require_errors.err(require_errors.kernelError("HEAL_SOLID_INCOMPLETE", "Healed result is still invalid after ShapeFix_Solid"));
|
|
933
|
+
return require_errors.ok(cast);
|
|
934
|
+
} catch (e) {
|
|
935
|
+
return require_errors.err(require_errors.kernelError("HEAL_SOLID_FAILED", "Solid healing failed", e));
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Attempt to heal/fix a face.
|
|
940
|
+
*
|
|
941
|
+
* Uses ShapeFix_Face to repair wire ordering, orientation, and geometry issues.
|
|
942
|
+
*/
|
|
943
|
+
function healFace(face) {
|
|
944
|
+
if (!require_shapeTypes.isFace(face)) return require_errors.err(require_errors.validationError("NOT_A_FACE", "Input shape is not a face"));
|
|
945
|
+
try {
|
|
946
|
+
const cast = require_shapeTypes.castShape(require_shapeTypes.getKernel().healFace(face.wrapped));
|
|
947
|
+
if (!require_shapeTypes.isFace(cast)) return require_errors.err(require_errors.kernelError("HEAL_RESULT_NOT_FACE", "Healed result is not a face"));
|
|
948
|
+
return require_errors.ok(cast);
|
|
949
|
+
} catch (e) {
|
|
950
|
+
return require_errors.err(require_errors.kernelError("HEAL_FACE_FAILED", "Face healing failed", e));
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Attempt to heal/fix a wire.
|
|
955
|
+
*
|
|
956
|
+
* Uses ShapeFix_Wire to repair edge connectivity, gaps, and self-intersections.
|
|
957
|
+
* Requires a face for surface context; pass `undefined` to use a default planar context.
|
|
958
|
+
*/
|
|
959
|
+
function healWire(wire, face) {
|
|
960
|
+
if (!require_shapeTypes.isWire(wire)) return require_errors.err(require_errors.validationError("NOT_A_WIRE", "Input shape is not a wire"));
|
|
961
|
+
try {
|
|
962
|
+
const cast = require_shapeTypes.castShape(require_shapeTypes.getKernel().healWire(wire.wrapped, face?.wrapped));
|
|
963
|
+
if (!require_shapeTypes.isWire(cast)) return require_errors.err(require_errors.kernelError("HEAL_RESULT_NOT_WIRE", "Healed result is not a wire"));
|
|
964
|
+
return require_errors.ok(cast);
|
|
965
|
+
} catch (e) {
|
|
966
|
+
return require_errors.err(require_errors.kernelError("HEAL_WIRE_FAILED", "Wire healing failed", e));
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Attempt to heal any shape by dispatching to the appropriate fixer.
|
|
971
|
+
*
|
|
972
|
+
* Supports solids, faces, and wires. For other shape types, returns the
|
|
973
|
+
* input unchanged.
|
|
974
|
+
*/
|
|
975
|
+
function heal(shape) {
|
|
976
|
+
if (require_shapeTypes.isSolid(shape)) return healSolid(shape);
|
|
977
|
+
if (require_shapeTypes.isFace(shape)) return healFace(shape);
|
|
978
|
+
if (require_shapeTypes.isWire(shape)) return healWire(shape);
|
|
979
|
+
return require_errors.ok(shape);
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Automatically heal a shape using the appropriate shape-level fixer.
|
|
983
|
+
*
|
|
984
|
+
* If the shape is already valid, returns it unchanged with a no-op report.
|
|
985
|
+
* Uses ShapeFix_Solid/Face/Wire depending on shape type, which internally
|
|
986
|
+
* handles sub-shape healing and reconstruction.
|
|
987
|
+
*/
|
|
988
|
+
function autoHeal(shape, options) {
|
|
989
|
+
const fixWires = options?.fixWires !== false;
|
|
990
|
+
const fixFaces = options?.fixFaces !== false;
|
|
991
|
+
const fixSolids = options?.fixSolids !== false;
|
|
992
|
+
const fixSelfIntersection = options?.fixSelfIntersection === true;
|
|
993
|
+
const sewTolerance = options?.sewTolerance;
|
|
994
|
+
const steps = [];
|
|
995
|
+
const diagnostics = [];
|
|
996
|
+
if (isValid(shape)) return require_errors.ok({
|
|
997
|
+
shape,
|
|
998
|
+
report: {
|
|
999
|
+
isValid: true,
|
|
1000
|
+
alreadyValid: true,
|
|
1001
|
+
wiresHealed: 0,
|
|
1002
|
+
facesHealed: 0,
|
|
1003
|
+
solidHealed: false,
|
|
1004
|
+
steps: ["Shape already valid"],
|
|
1005
|
+
diagnostics: [{
|
|
1006
|
+
name: "validation",
|
|
1007
|
+
attempted: true,
|
|
1008
|
+
succeeded: true
|
|
1009
|
+
}]
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
steps.push("Shape invalid — applying shape-level healing");
|
|
1013
|
+
const wiresBefore = require_arrayAccess.getWires(shape).length;
|
|
1014
|
+
const facesBefore = require_arrayAccess.getFaces(shape).length;
|
|
1015
|
+
let current = shape;
|
|
1016
|
+
let solidHealed = false;
|
|
1017
|
+
if (sewTolerance !== void 0) try {
|
|
1018
|
+
current = require_shapeTypes.castShape(require_shapeTypes.getKernel().sew([current.wrapped], sewTolerance));
|
|
1019
|
+
steps.push(`Applied sewing with tolerance ${sewTolerance}`);
|
|
1020
|
+
diagnostics.push({
|
|
1021
|
+
name: "sew",
|
|
1022
|
+
attempted: true,
|
|
1023
|
+
succeeded: true,
|
|
1024
|
+
detail: `tolerance=${sewTolerance}`
|
|
1025
|
+
});
|
|
1026
|
+
} catch (e) {
|
|
1027
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
1028
|
+
steps.push(`Sewing failed: ${detail}`);
|
|
1029
|
+
diagnostics.push({
|
|
1030
|
+
name: "sew",
|
|
1031
|
+
attempted: true,
|
|
1032
|
+
succeeded: false,
|
|
1033
|
+
detail
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
if (fixSelfIntersection && fixWires) {
|
|
1037
|
+
const wires = require_arrayAccess.getWires(current);
|
|
1038
|
+
let fixCount = 0;
|
|
1039
|
+
for (const wire of wires) try {
|
|
1040
|
+
require_shapeTypes.getKernel().fixSelfIntersection(wire.wrapped);
|
|
1041
|
+
fixCount++;
|
|
1042
|
+
} catch {}
|
|
1043
|
+
steps.push(`Self-intersection fix: ${fixCount}/${wires.length} wires`);
|
|
1044
|
+
diagnostics.push({
|
|
1045
|
+
name: "fixSelfIntersection",
|
|
1046
|
+
attempted: true,
|
|
1047
|
+
succeeded: fixCount > 0,
|
|
1048
|
+
detail: `${fixCount}/${wires.length} wires fixed`
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
if (require_shapeTypes.isSolid(current) && fixSolids || require_shapeTypes.isFace(current) && fixFaces || require_shapeTypes.isWire(current) && fixWires) {
|
|
1052
|
+
const healResult = heal(current);
|
|
1053
|
+
if (require_errors.isOk(healResult)) {
|
|
1054
|
+
current = healResult.value;
|
|
1055
|
+
if (require_shapeTypes.isSolid(shape)) {
|
|
1056
|
+
solidHealed = true;
|
|
1057
|
+
steps.push("Applied ShapeFix_Solid");
|
|
1058
|
+
diagnostics.push({
|
|
1059
|
+
name: "healSolid",
|
|
1060
|
+
attempted: true,
|
|
1061
|
+
succeeded: true
|
|
1062
|
+
});
|
|
1063
|
+
} else if (require_shapeTypes.isFace(shape)) {
|
|
1064
|
+
steps.push("Applied ShapeFix_Face");
|
|
1065
|
+
diagnostics.push({
|
|
1066
|
+
name: "healFace",
|
|
1067
|
+
attempted: true,
|
|
1068
|
+
succeeded: true
|
|
1069
|
+
});
|
|
1070
|
+
} else {
|
|
1071
|
+
steps.push("Applied ShapeFix_Wire");
|
|
1072
|
+
diagnostics.push({
|
|
1073
|
+
name: "healWire",
|
|
1074
|
+
attempted: true,
|
|
1075
|
+
succeeded: true
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
} else {
|
|
1079
|
+
steps.push("Shape-level healing failed");
|
|
1080
|
+
diagnostics.push({
|
|
1081
|
+
name: "healShape",
|
|
1082
|
+
attempted: true,
|
|
1083
|
+
succeeded: false
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
} else diagnostics.push({
|
|
1087
|
+
name: "healShape",
|
|
1088
|
+
attempted: false,
|
|
1089
|
+
succeeded: false,
|
|
1090
|
+
detail: "skipped by options"
|
|
1091
|
+
});
|
|
1092
|
+
const wiresAfter = require_arrayAccess.getWires(current).length;
|
|
1093
|
+
const facesAfter = require_arrayAccess.getFaces(current).length;
|
|
1094
|
+
const wiresHealed = Math.abs(wiresAfter - wiresBefore);
|
|
1095
|
+
const facesHealed = Math.abs(facesAfter - facesBefore);
|
|
1096
|
+
if (wiresHealed > 0) steps.push(`Wire count changed by ${wiresHealed}`);
|
|
1097
|
+
if (facesHealed > 0) steps.push(`Face count changed by ${facesHealed}`);
|
|
1098
|
+
const valid = isValid(current);
|
|
1099
|
+
steps.push(valid ? "Final validation: valid" : "Final validation: still invalid");
|
|
1100
|
+
diagnostics.push({
|
|
1101
|
+
name: "finalValidation",
|
|
1102
|
+
attempted: true,
|
|
1103
|
+
succeeded: valid
|
|
1104
|
+
});
|
|
1105
|
+
return require_errors.ok({
|
|
1106
|
+
shape: current,
|
|
1107
|
+
report: {
|
|
1108
|
+
isValid: valid,
|
|
1109
|
+
alreadyValid: false,
|
|
1110
|
+
wiresHealed,
|
|
1111
|
+
facesHealed,
|
|
1112
|
+
solidHealed,
|
|
1113
|
+
steps,
|
|
1114
|
+
diagnostics
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* General-purpose shape repair using ShapeFix_Shape.
|
|
1120
|
+
* Fixes orientations, missing curves, and other common issues.
|
|
1121
|
+
*/
|
|
1122
|
+
function fixShape(shape) {
|
|
1123
|
+
try {
|
|
1124
|
+
return require_errors.ok(require_shapeTypes.castShape(require_shapeTypes.getKernel().fixShape(shape.wrapped)));
|
|
1125
|
+
} catch (e) {
|
|
1126
|
+
return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.FIX_SHAPE_FAILED, "ShapeFix_Shape failed", e));
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Convert a closed shell into a solid.
|
|
1131
|
+
*
|
|
1132
|
+
* The shell must be closed (all faces share edges) for the conversion to succeed.
|
|
1133
|
+
*/
|
|
1134
|
+
function solidFromShell(shell) {
|
|
1135
|
+
try {
|
|
1136
|
+
const wrapped = require_shapeTypes.castShape(require_shapeTypes.getKernel().solidFromShell(shell.wrapped));
|
|
1137
|
+
if (!require_shapeTypes.isSolid(wrapped)) return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.SOLID_FROM_SHELL_FAILED, "solidFromShell did not produce a solid"));
|
|
1138
|
+
if (!isValid(wrapped)) return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.SOLID_FROM_SHELL_FAILED, "solidFromShell produced an invalid solid"));
|
|
1139
|
+
return require_errors.ok(wrapped);
|
|
1140
|
+
} catch (e) {
|
|
1141
|
+
return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.SOLID_FROM_SHELL_FAILED, "Failed to create solid from shell", e));
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Fix self-intersections in a wire.
|
|
1146
|
+
*
|
|
1147
|
+
* Uses ShapeFix_Wire to detect and repair self-intersecting edges.
|
|
1148
|
+
*/
|
|
1149
|
+
function fixSelfIntersection(wire) {
|
|
1150
|
+
try {
|
|
1151
|
+
const wrapped = require_shapeTypes.castShape(require_shapeTypes.getKernel().fixSelfIntersection(wire.wrapped));
|
|
1152
|
+
if (!require_shapeTypes.isWire(wrapped)) return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.FIX_SELF_INTERSECTION_FAILED, "Result is not a wire"));
|
|
1153
|
+
return require_errors.ok(wrapped);
|
|
1154
|
+
} catch (e) {
|
|
1155
|
+
return require_errors.err(require_errors.kernelError(require_errors.BrepErrorCode.FIX_SELF_INTERSECTION_FAILED, "Failed to fix wire self-intersection", e));
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
//#endregion
|
|
1159
|
+
//#region src/topology/primitiveFns.ts
|
|
1160
|
+
/**
|
|
1161
|
+
* Create a box with the given dimensions.
|
|
1162
|
+
*
|
|
1163
|
+
* @param width - Size along X.
|
|
1164
|
+
* @param depth - Size along Y.
|
|
1165
|
+
* @param height - Size along Z.
|
|
1166
|
+
*/
|
|
1167
|
+
function box(width, depth, height, options) {
|
|
1168
|
+
const solid = require_shapeTypes.createSolid(require_shapeTypes.getKernel().makeBox(width, depth, height));
|
|
1169
|
+
const center = options?.at ?? (options?.centered ? [
|
|
1170
|
+
0,
|
|
1171
|
+
0,
|
|
1172
|
+
0
|
|
1173
|
+
] : void 0);
|
|
1174
|
+
if (center) return require_arrayAccess.translate(solid, [
|
|
1175
|
+
center[0] - width / 2,
|
|
1176
|
+
center[1] - depth / 2,
|
|
1177
|
+
center[2] - height / 2
|
|
1178
|
+
]);
|
|
1179
|
+
return solid;
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Create a cylinder with the given radius and height.
|
|
1183
|
+
*/
|
|
1184
|
+
function cylinder(radius, height, options) {
|
|
1185
|
+
const at = options?.at ?? [
|
|
1186
|
+
0,
|
|
1187
|
+
0,
|
|
1188
|
+
0
|
|
1189
|
+
];
|
|
1190
|
+
const axis = options?.axis ?? [
|
|
1191
|
+
0,
|
|
1192
|
+
0,
|
|
1193
|
+
1
|
|
1194
|
+
];
|
|
1195
|
+
let solid = require_solidBuilders.makeCylinder(radius, height, at, axis);
|
|
1196
|
+
if (options?.centered) {
|
|
1197
|
+
const halfShift = [
|
|
1198
|
+
-axis[0] * height * .5,
|
|
1199
|
+
-axis[1] * height * .5,
|
|
1200
|
+
-axis[2] * height * .5
|
|
1201
|
+
];
|
|
1202
|
+
solid = require_arrayAccess.translate(solid, halfShift);
|
|
1203
|
+
}
|
|
1204
|
+
return solid;
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Create a sphere with the given radius.
|
|
1208
|
+
*/
|
|
1209
|
+
function sphere(radius, options) {
|
|
1210
|
+
let solid = require_solidBuilders.makeSphere(radius);
|
|
1211
|
+
if (options?.at) solid = require_arrayAccess.translate(solid, options.at);
|
|
1212
|
+
return solid;
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Create a cone (or frustum) with the given radii and height.
|
|
1216
|
+
*
|
|
1217
|
+
* @param bottomRadius - Radius at the base.
|
|
1218
|
+
* @param topRadius - Radius at the top (0 for a full cone).
|
|
1219
|
+
* @param height - Height of the cone.
|
|
1220
|
+
*/
|
|
1221
|
+
function cone(bottomRadius, topRadius, height, options) {
|
|
1222
|
+
const at = options?.at ?? [
|
|
1223
|
+
0,
|
|
1224
|
+
0,
|
|
1225
|
+
0
|
|
1226
|
+
];
|
|
1227
|
+
const axis = options?.axis ?? [
|
|
1228
|
+
0,
|
|
1229
|
+
0,
|
|
1230
|
+
1
|
|
1231
|
+
];
|
|
1232
|
+
let solid = require_solidBuilders.makeCone(bottomRadius, topRadius, height, at, axis);
|
|
1233
|
+
if (options?.centered) {
|
|
1234
|
+
const halfShift = [
|
|
1235
|
+
-axis[0] * height * .5,
|
|
1236
|
+
-axis[1] * height * .5,
|
|
1237
|
+
-axis[2] * height * .5
|
|
1238
|
+
];
|
|
1239
|
+
solid = require_arrayAccess.translate(solid, halfShift);
|
|
1240
|
+
}
|
|
1241
|
+
return solid;
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Create a torus with the given major and minor radii.
|
|
1245
|
+
*/
|
|
1246
|
+
function torus(majorRadius, minorRadius, options) {
|
|
1247
|
+
return require_solidBuilders.makeTorus(majorRadius, minorRadius, options?.at ?? [
|
|
1248
|
+
0,
|
|
1249
|
+
0,
|
|
1250
|
+
0
|
|
1251
|
+
], options?.axis ?? [
|
|
1252
|
+
0,
|
|
1253
|
+
0,
|
|
1254
|
+
1
|
|
1255
|
+
]);
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Create an ellipsoid with the given axis half-lengths.
|
|
1259
|
+
*
|
|
1260
|
+
* @param rx - Half-length along X.
|
|
1261
|
+
* @param ry - Half-length along Y.
|
|
1262
|
+
* @param rz - Half-length along Z.
|
|
1263
|
+
*/
|
|
1264
|
+
function ellipsoid(rx, ry, rz, options) {
|
|
1265
|
+
let solid = require_solidBuilders.makeEllipsoid(rx, ry, rz);
|
|
1266
|
+
if (options?.at) solid = require_arrayAccess.translate(solid, options.at);
|
|
1267
|
+
return solid;
|
|
1268
|
+
}
|
|
1269
|
+
/** Create a straight edge between two 3D points. */
|
|
1270
|
+
function line(from, to) {
|
|
1271
|
+
return require_surfaceBuilders.makeLine(from, to);
|
|
1272
|
+
}
|
|
1273
|
+
/** Create a circular edge with the given radius. */
|
|
1274
|
+
function circle(radius, options) {
|
|
1275
|
+
const axisDir = options?.axis ?? [
|
|
1276
|
+
0,
|
|
1277
|
+
0,
|
|
1278
|
+
1
|
|
1279
|
+
];
|
|
1280
|
+
return require_surfaceBuilders.makeCircle(radius, options?.at ?? [
|
|
1281
|
+
0,
|
|
1282
|
+
0,
|
|
1283
|
+
0
|
|
1284
|
+
], axisDir);
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Create an elliptical edge.
|
|
1288
|
+
*
|
|
1289
|
+
* @returns An error if `minorRadius` exceeds `majorRadius`.
|
|
1290
|
+
*/
|
|
1291
|
+
function ellipse(majorRadius, minorRadius, options) {
|
|
1292
|
+
const axisDir = options?.axis ?? [
|
|
1293
|
+
0,
|
|
1294
|
+
0,
|
|
1295
|
+
1
|
|
1296
|
+
];
|
|
1297
|
+
return require_surfaceBuilders.makeEllipse(majorRadius, minorRadius, options?.at ?? [
|
|
1298
|
+
0,
|
|
1299
|
+
0,
|
|
1300
|
+
0
|
|
1301
|
+
], axisDir, options?.xDir);
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Create a helical wire.
|
|
1305
|
+
*
|
|
1306
|
+
* @param pitch - Vertical distance per full turn.
|
|
1307
|
+
* @param height - Total height.
|
|
1308
|
+
* @param radius - Helix radius.
|
|
1309
|
+
*/
|
|
1310
|
+
function helix(pitch, height, radius, options) {
|
|
1311
|
+
return require_surfaceBuilders.makeHelix(pitch, height, radius, options?.at ?? [
|
|
1312
|
+
0,
|
|
1313
|
+
0,
|
|
1314
|
+
0
|
|
1315
|
+
], options?.axis ?? [
|
|
1316
|
+
0,
|
|
1317
|
+
0,
|
|
1318
|
+
1
|
|
1319
|
+
], options?.lefthand ?? false);
|
|
1320
|
+
}
|
|
1321
|
+
/** Create a circular arc edge passing through three points. */
|
|
1322
|
+
function threePointArc(p1, p2, p3) {
|
|
1323
|
+
return require_surfaceBuilders.makeThreePointArc(p1, p2, p3);
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Create an elliptical arc edge between two angles.
|
|
1327
|
+
*
|
|
1328
|
+
* All angles are in **degrees** (unlike the legacy `makeEllipseArc` which used radians).
|
|
1329
|
+
*
|
|
1330
|
+
* @param startAngle - Start angle in degrees.
|
|
1331
|
+
* @param endAngle - End angle in degrees.
|
|
1332
|
+
*/
|
|
1333
|
+
function ellipseArc(majorRadius, minorRadius, startAngle, endAngle, options) {
|
|
1334
|
+
const axisDir = options?.axis ?? [
|
|
1335
|
+
0,
|
|
1336
|
+
0,
|
|
1337
|
+
1
|
|
1338
|
+
];
|
|
1339
|
+
return require_surfaceBuilders.makeEllipseArc(majorRadius, minorRadius, startAngle * require_vecOps.DEG2RAD, endAngle * require_vecOps.DEG2RAD, options?.at ?? [
|
|
1340
|
+
0,
|
|
1341
|
+
0,
|
|
1342
|
+
0
|
|
1343
|
+
], axisDir, options?.xDir);
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Create a B-spline edge that approximates a set of 3D points.
|
|
1347
|
+
*
|
|
1348
|
+
* @returns An error if the approximation algorithm fails.
|
|
1349
|
+
*/
|
|
1350
|
+
function bsplineApprox(points, config) {
|
|
1351
|
+
return require_surfaceBuilders.makeBSplineApproximation(points, config);
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Create a Bezier curve edge from control points.
|
|
1355
|
+
*
|
|
1356
|
+
* @param points - Two or more control points.
|
|
1357
|
+
*/
|
|
1358
|
+
function bezier(points) {
|
|
1359
|
+
return require_surfaceBuilders.makeBezierCurve(points);
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Create a circular arc edge tangent to a direction at the start point.
|
|
1363
|
+
*/
|
|
1364
|
+
function tangentArc(startPoint, startTgt, endPoint) {
|
|
1365
|
+
return require_surfaceBuilders.makeTangentArc(startPoint, startTgt, endPoint);
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Assemble edges and/or wires into a single connected wire.
|
|
1369
|
+
*/
|
|
1370
|
+
function wire(listOfEdges) {
|
|
1371
|
+
return require_surfaceBuilders.assembleWire(listOfEdges);
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Assemble edges into a wire and verify it forms a closed loop.
|
|
1375
|
+
*
|
|
1376
|
+
* Combines {@link wire} + the `closedWire` smart constructor in a single step.
|
|
1377
|
+
* Returns an error if the edges cannot be assembled or the wire is not closed.
|
|
1378
|
+
*
|
|
1379
|
+
* @example
|
|
1380
|
+
* ```ts
|
|
1381
|
+
* const cw = unwrap(wireLoop([e1, e2, e3, e4]));
|
|
1382
|
+
* const f = unwrap(face(cw)); // ClosedWire accepted directly
|
|
1383
|
+
* ```
|
|
1384
|
+
*/
|
|
1385
|
+
function wireLoop(listOfEdges) {
|
|
1386
|
+
return require_errors.andThen(require_surfaceBuilders.assembleWire(listOfEdges), (w) => {
|
|
1387
|
+
if (require_shapeTypes.isClosedWire(w)) return require_errors.ok(w);
|
|
1388
|
+
return require_errors.err(require_errors.validationError("WIRE_NOT_CLOSED", "Assembled wire is not closed: start and end points do not coincide"));
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Create a planar face from a closed wire, optionally with holes.
|
|
1393
|
+
* The resulting face is always oriented (consistent normal direction).
|
|
1394
|
+
*/
|
|
1395
|
+
function face(w, holes) {
|
|
1396
|
+
return require_surfaceBuilders.makeFace(w, holes);
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Create a non-planar face from a wire using surface filling.
|
|
1400
|
+
* The resulting face is always oriented.
|
|
1401
|
+
*/
|
|
1402
|
+
function filledFace(w) {
|
|
1403
|
+
return require_surfaceBuilders.makeNonPlanarFace(w);
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Create a face bounded by a wire on an existing face's surface.
|
|
1407
|
+
* The resulting face inherits orientation from the origin face.
|
|
1408
|
+
*/
|
|
1409
|
+
function subFace(originFace, w) {
|
|
1410
|
+
return require_surfaceBuilders.makeNewFaceWithinFace(originFace, w);
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Create a polygonal face from three or more coplanar points.
|
|
1414
|
+
* The resulting face is always oriented.
|
|
1415
|
+
*/
|
|
1416
|
+
function polygon(points) {
|
|
1417
|
+
return require_surfaceBuilders.makePolygon(points);
|
|
1418
|
+
}
|
|
1419
|
+
/** Create a vertex at a 3D point. */
|
|
1420
|
+
function vertex(point) {
|
|
1421
|
+
return require_solidBuilders.makeVertex(point);
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Build a compound from multiple shapes.
|
|
1425
|
+
*/
|
|
1426
|
+
function compound(shapeArray) {
|
|
1427
|
+
return require_solidBuilders.makeCompound(shapeArray);
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* Weld faces and shells into a single solid.
|
|
1431
|
+
* The resulting solid is always validated.
|
|
1432
|
+
*/
|
|
1433
|
+
function solid(facesOrShells) {
|
|
1434
|
+
return require_solidBuilders.makeSolid(facesOrShells);
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Create an offset shape from a face.
|
|
1438
|
+
*/
|
|
1439
|
+
function offsetFace(f, distance, tolerance) {
|
|
1440
|
+
return require_solidBuilders.makeOffset(f, distance, tolerance);
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Weld faces and shells into a single shell.
|
|
1444
|
+
*/
|
|
1445
|
+
function sewShells(facesOrShells, ignoreType) {
|
|
1446
|
+
return require_solidBuilders.weldShellsAndFaces(facesOrShells, ignoreType);
|
|
1447
|
+
}
|
|
1448
|
+
function addHoles(f, holes) {
|
|
1449
|
+
return require_surfaceBuilders.addHolesInFace(f, holes);
|
|
1450
|
+
}
|
|
1451
|
+
//#endregion
|
|
1452
|
+
Object.defineProperty(exports, "addHoles", {
|
|
1453
|
+
enumerable: true,
|
|
1454
|
+
get: function() {
|
|
1455
|
+
return addHoles;
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
Object.defineProperty(exports, "adjacentFaces", {
|
|
1459
|
+
enumerable: true,
|
|
1460
|
+
get: function() {
|
|
1461
|
+
return adjacentFaces;
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
Object.defineProperty(exports, "autoHeal", {
|
|
1465
|
+
enumerable: true,
|
|
1466
|
+
get: function() {
|
|
1467
|
+
return autoHeal;
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
Object.defineProperty(exports, "bezier", {
|
|
1471
|
+
enumerable: true,
|
|
1472
|
+
get: function() {
|
|
1473
|
+
return bezier;
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
Object.defineProperty(exports, "box", {
|
|
1477
|
+
enumerable: true,
|
|
1478
|
+
get: function() {
|
|
1479
|
+
return box;
|
|
1480
|
+
}
|
|
1481
|
+
});
|
|
1482
|
+
Object.defineProperty(exports, "bsplineApprox", {
|
|
1483
|
+
enumerable: true,
|
|
1484
|
+
get: function() {
|
|
1485
|
+
return bsplineApprox;
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
Object.defineProperty(exports, "chamfer", {
|
|
1489
|
+
enumerable: true,
|
|
1490
|
+
get: function() {
|
|
1491
|
+
return chamfer;
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
Object.defineProperty(exports, "chamferDistAngle", {
|
|
1495
|
+
enumerable: true,
|
|
1496
|
+
get: function() {
|
|
1497
|
+
return chamferDistAngle;
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
Object.defineProperty(exports, "chamferWithEvolution", {
|
|
1501
|
+
enumerable: true,
|
|
1502
|
+
get: function() {
|
|
1503
|
+
return chamferWithEvolution;
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
Object.defineProperty(exports, "checkBoolean", {
|
|
1507
|
+
enumerable: true,
|
|
1508
|
+
get: function() {
|
|
1509
|
+
return checkBoolean;
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1512
|
+
Object.defineProperty(exports, "circle", {
|
|
1513
|
+
enumerable: true,
|
|
1514
|
+
get: function() {
|
|
1515
|
+
return circle;
|
|
1516
|
+
}
|
|
1517
|
+
});
|
|
1518
|
+
Object.defineProperty(exports, "compound", {
|
|
1519
|
+
enumerable: true,
|
|
1520
|
+
get: function() {
|
|
1521
|
+
return compound;
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1524
|
+
Object.defineProperty(exports, "cone", {
|
|
1525
|
+
enumerable: true,
|
|
1526
|
+
get: function() {
|
|
1527
|
+
return cone;
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1530
|
+
Object.defineProperty(exports, "cutWithEvolution", {
|
|
1531
|
+
enumerable: true,
|
|
1532
|
+
get: function() {
|
|
1533
|
+
return cutWithEvolution;
|
|
1534
|
+
}
|
|
1535
|
+
});
|
|
1536
|
+
Object.defineProperty(exports, "cylinder", {
|
|
1537
|
+
enumerable: true,
|
|
1538
|
+
get: function() {
|
|
1539
|
+
return cylinder;
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
Object.defineProperty(exports, "draft", {
|
|
1543
|
+
enumerable: true,
|
|
1544
|
+
get: function() {
|
|
1545
|
+
return draft;
|
|
1546
|
+
}
|
|
1547
|
+
});
|
|
1548
|
+
Object.defineProperty(exports, "edgesOfFace", {
|
|
1549
|
+
enumerable: true,
|
|
1550
|
+
get: function() {
|
|
1551
|
+
return edgesOfFace;
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
Object.defineProperty(exports, "ellipse", {
|
|
1555
|
+
enumerable: true,
|
|
1556
|
+
get: function() {
|
|
1557
|
+
return ellipse;
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
Object.defineProperty(exports, "ellipseArc", {
|
|
1561
|
+
enumerable: true,
|
|
1562
|
+
get: function() {
|
|
1563
|
+
return ellipseArc;
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
Object.defineProperty(exports, "ellipsoid", {
|
|
1567
|
+
enumerable: true,
|
|
1568
|
+
get: function() {
|
|
1569
|
+
return ellipsoid;
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
Object.defineProperty(exports, "face", {
|
|
1573
|
+
enumerable: true,
|
|
1574
|
+
get: function() {
|
|
1575
|
+
return face;
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
Object.defineProperty(exports, "facesOfEdge", {
|
|
1579
|
+
enumerable: true,
|
|
1580
|
+
get: function() {
|
|
1581
|
+
return facesOfEdge;
|
|
1582
|
+
}
|
|
1583
|
+
});
|
|
1584
|
+
Object.defineProperty(exports, "filledFace", {
|
|
1585
|
+
enumerable: true,
|
|
1586
|
+
get: function() {
|
|
1587
|
+
return filledFace;
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1590
|
+
Object.defineProperty(exports, "fillet", {
|
|
1591
|
+
enumerable: true,
|
|
1592
|
+
get: function() {
|
|
1593
|
+
return fillet;
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
Object.defineProperty(exports, "filletWithEvolution", {
|
|
1597
|
+
enumerable: true,
|
|
1598
|
+
get: function() {
|
|
1599
|
+
return filletWithEvolution;
|
|
1600
|
+
}
|
|
1601
|
+
});
|
|
1602
|
+
Object.defineProperty(exports, "fixSelfIntersection", {
|
|
1603
|
+
enumerable: true,
|
|
1604
|
+
get: function() {
|
|
1605
|
+
return fixSelfIntersection;
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
Object.defineProperty(exports, "fixShape", {
|
|
1609
|
+
enumerable: true,
|
|
1610
|
+
get: function() {
|
|
1611
|
+
return fixShape;
|
|
1612
|
+
}
|
|
1613
|
+
});
|
|
1614
|
+
Object.defineProperty(exports, "fuseWithEvolution", {
|
|
1615
|
+
enumerable: true,
|
|
1616
|
+
get: function() {
|
|
1617
|
+
return fuseWithEvolution;
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
Object.defineProperty(exports, "heal", {
|
|
1621
|
+
enumerable: true,
|
|
1622
|
+
get: function() {
|
|
1623
|
+
return heal;
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1626
|
+
Object.defineProperty(exports, "healFace", {
|
|
1627
|
+
enumerable: true,
|
|
1628
|
+
get: function() {
|
|
1629
|
+
return healFace;
|
|
1630
|
+
}
|
|
1631
|
+
});
|
|
1632
|
+
Object.defineProperty(exports, "healSolid", {
|
|
1633
|
+
enumerable: true,
|
|
1634
|
+
get: function() {
|
|
1635
|
+
return healSolid;
|
|
1636
|
+
}
|
|
1637
|
+
});
|
|
1638
|
+
Object.defineProperty(exports, "healWire", {
|
|
1639
|
+
enumerable: true,
|
|
1640
|
+
get: function() {
|
|
1641
|
+
return healWire;
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
Object.defineProperty(exports, "helix", {
|
|
1645
|
+
enumerable: true,
|
|
1646
|
+
get: function() {
|
|
1647
|
+
return helix;
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
Object.defineProperty(exports, "intersectWithEvolution", {
|
|
1651
|
+
enumerable: true,
|
|
1652
|
+
get: function() {
|
|
1653
|
+
return intersectWithEvolution;
|
|
1654
|
+
}
|
|
1655
|
+
});
|
|
1656
|
+
Object.defineProperty(exports, "isValid", {
|
|
1657
|
+
enumerable: true,
|
|
1658
|
+
get: function() {
|
|
1659
|
+
return isValid;
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
Object.defineProperty(exports, "line", {
|
|
1663
|
+
enumerable: true,
|
|
1664
|
+
get: function() {
|
|
1665
|
+
return line;
|
|
1666
|
+
}
|
|
1667
|
+
});
|
|
1668
|
+
Object.defineProperty(exports, "offset", {
|
|
1669
|
+
enumerable: true,
|
|
1670
|
+
get: function() {
|
|
1671
|
+
return offset;
|
|
1672
|
+
}
|
|
1673
|
+
});
|
|
1674
|
+
Object.defineProperty(exports, "offsetFace", {
|
|
1675
|
+
enumerable: true,
|
|
1676
|
+
get: function() {
|
|
1677
|
+
return offsetFace;
|
|
1678
|
+
}
|
|
1679
|
+
});
|
|
1680
|
+
Object.defineProperty(exports, "polygon", {
|
|
1681
|
+
enumerable: true,
|
|
1682
|
+
get: function() {
|
|
1683
|
+
return polygon;
|
|
1684
|
+
}
|
|
1685
|
+
});
|
|
1686
|
+
Object.defineProperty(exports, "positionOnCurve", {
|
|
1687
|
+
enumerable: true,
|
|
1688
|
+
get: function() {
|
|
1689
|
+
return positionOnCurve;
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1692
|
+
Object.defineProperty(exports, "sewShells", {
|
|
1693
|
+
enumerable: true,
|
|
1694
|
+
get: function() {
|
|
1695
|
+
return sewShells;
|
|
1696
|
+
}
|
|
1697
|
+
});
|
|
1698
|
+
Object.defineProperty(exports, "sharedEdges", {
|
|
1699
|
+
enumerable: true,
|
|
1700
|
+
get: function() {
|
|
1701
|
+
return sharedEdges;
|
|
1702
|
+
}
|
|
1703
|
+
});
|
|
1704
|
+
Object.defineProperty(exports, "shell", {
|
|
1705
|
+
enumerable: true,
|
|
1706
|
+
get: function() {
|
|
1707
|
+
return shell;
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
Object.defineProperty(exports, "shellWithEvolution", {
|
|
1711
|
+
enumerable: true,
|
|
1712
|
+
get: function() {
|
|
1713
|
+
return shellWithEvolution;
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
Object.defineProperty(exports, "solid", {
|
|
1717
|
+
enumerable: true,
|
|
1718
|
+
get: function() {
|
|
1719
|
+
return solid;
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
Object.defineProperty(exports, "solidFromShell", {
|
|
1723
|
+
enumerable: true,
|
|
1724
|
+
get: function() {
|
|
1725
|
+
return solidFromShell;
|
|
1726
|
+
}
|
|
1727
|
+
});
|
|
1728
|
+
Object.defineProperty(exports, "sphere", {
|
|
1729
|
+
enumerable: true,
|
|
1730
|
+
get: function() {
|
|
1731
|
+
return sphere;
|
|
1732
|
+
}
|
|
1733
|
+
});
|
|
1734
|
+
Object.defineProperty(exports, "subFace", {
|
|
1735
|
+
enumerable: true,
|
|
1736
|
+
get: function() {
|
|
1737
|
+
return subFace;
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
Object.defineProperty(exports, "tangentArc", {
|
|
1741
|
+
enumerable: true,
|
|
1742
|
+
get: function() {
|
|
1743
|
+
return tangentArc;
|
|
1744
|
+
}
|
|
1745
|
+
});
|
|
1746
|
+
Object.defineProperty(exports, "thicken", {
|
|
1747
|
+
enumerable: true,
|
|
1748
|
+
get: function() {
|
|
1749
|
+
return thicken;
|
|
1750
|
+
}
|
|
1751
|
+
});
|
|
1752
|
+
Object.defineProperty(exports, "threePointArc", {
|
|
1753
|
+
enumerable: true,
|
|
1754
|
+
get: function() {
|
|
1755
|
+
return threePointArc;
|
|
1756
|
+
}
|
|
1757
|
+
});
|
|
1758
|
+
Object.defineProperty(exports, "toBufferGeometryData", {
|
|
1759
|
+
enumerable: true,
|
|
1760
|
+
get: function() {
|
|
1761
|
+
return toBufferGeometryData;
|
|
1762
|
+
}
|
|
1763
|
+
});
|
|
1764
|
+
Object.defineProperty(exports, "toGroupedBufferGeometryData", {
|
|
1765
|
+
enumerable: true,
|
|
1766
|
+
get: function() {
|
|
1767
|
+
return toGroupedBufferGeometryData;
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1770
|
+
Object.defineProperty(exports, "toLineGeometryData", {
|
|
1771
|
+
enumerable: true,
|
|
1772
|
+
get: function() {
|
|
1773
|
+
return toLineGeometryData;
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1776
|
+
Object.defineProperty(exports, "torus", {
|
|
1777
|
+
enumerable: true,
|
|
1778
|
+
get: function() {
|
|
1779
|
+
return torus;
|
|
1780
|
+
}
|
|
1781
|
+
});
|
|
1782
|
+
Object.defineProperty(exports, "variableFillet", {
|
|
1783
|
+
enumerable: true,
|
|
1784
|
+
get: function() {
|
|
1785
|
+
return variableFillet;
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
Object.defineProperty(exports, "vertex", {
|
|
1789
|
+
enumerable: true,
|
|
1790
|
+
get: function() {
|
|
1791
|
+
return vertex;
|
|
1792
|
+
}
|
|
1793
|
+
});
|
|
1794
|
+
Object.defineProperty(exports, "verticesOfEdge", {
|
|
1795
|
+
enumerable: true,
|
|
1796
|
+
get: function() {
|
|
1797
|
+
return verticesOfEdge;
|
|
1798
|
+
}
|
|
1799
|
+
});
|
|
1800
|
+
Object.defineProperty(exports, "wire", {
|
|
1801
|
+
enumerable: true,
|
|
1802
|
+
get: function() {
|
|
1803
|
+
return wire;
|
|
1804
|
+
}
|
|
1805
|
+
});
|
|
1806
|
+
Object.defineProperty(exports, "wireLoop", {
|
|
1807
|
+
enumerable: true,
|
|
1808
|
+
get: function() {
|
|
1809
|
+
return wireLoop;
|
|
1810
|
+
}
|
|
1811
|
+
});
|
|
1812
|
+
Object.defineProperty(exports, "wiresOfFace", {
|
|
1813
|
+
enumerable: true,
|
|
1814
|
+
get: function() {
|
|
1815
|
+
return wiresOfFace;
|
|
1816
|
+
}
|
|
1817
|
+
});
|