brepjs 8.3.0 → 8.7.4
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/lib/Curve2D.d.ts.map +1 -1
- package/dist/2d.cjs +2 -2
- package/dist/2d.js +13 -13
- package/dist/{Blueprint-a3ukJMG4.cjs → Blueprint-BcbOBF-9.cjs} +17 -102
- package/dist/{Blueprint-CdVaHDSx.js → Blueprint-Cmh8lKc4.js} +35 -120
- package/dist/{boolean2D-pvPIs21j.cjs → boolean2D-CqacqjME.cjs} +24 -25
- package/dist/{boolean2D-DzA0STqC.js → boolean2D-D94Axs3i.js} +23 -24
- package/dist/{booleanFns-BcQUqjUu.js → booleanFns-DdjtpcM6.js} +306 -12
- package/dist/{booleanFns-Cd414V3l.cjs → booleanFns-NtKxkiXn.cjs} +299 -5
- package/dist/brepjs.cjs +1782 -68
- package/dist/brepjs.js +2030 -317
- package/dist/core/errors.d.ts +25 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core.cjs +4 -4
- package/dist/core.js +4 -4
- package/dist/{cornerFinder-DvPiz-VR.js → cornerFinder-BBOYfsXl.js} +1 -1
- package/dist/{cornerFinder-BdKtobgb.cjs → cornerFinder-Bqy8Lw2p.cjs} +1 -1
- package/dist/{curveFns-CyHyk29c.js → curveFns-B85Glnfo.js} +19 -17
- package/dist/{curveFns-B5EQsSwv.cjs → curveFns-BXCbASW-.cjs} +6 -4
- package/dist/{drawFns-CAAE4Z88.js → drawFns-B-gJ2WUc.js} +52 -23
- package/dist/{drawFns-Mr2pghU8.cjs → drawFns-CAmFEqd1.cjs} +68 -39
- package/dist/{errors-wGhcJMpB.js → errors-Coh_5_19.js} +35 -1
- package/dist/{errors-DK1VAdP4.cjs → errors-eRQu29oc.cjs} +35 -1
- package/dist/{faceFns-ub3CugDN.js → faceFns-CltrEfOo.js} +109 -12
- package/dist/{faceFns-D1Sqnlu6.cjs → faceFns-DcndPHWm.cjs} +103 -6
- package/dist/{helpers-CP2KrBZl.cjs → helpers-CC21GeAr.cjs} +8 -9
- package/dist/{helpers-r_e-u1JM.js → helpers-SksQIreB.js} +16 -17
- package/dist/index.d.ts +20 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/io/dxfImportFns.d.ts +17 -0
- package/dist/io/dxfImportFns.d.ts.map +1 -0
- package/dist/io/objImportFns.d.ts +19 -0
- package/dist/io/objImportFns.d.ts.map +1 -0
- package/dist/io/threemfImportFns.d.ts +19 -0
- package/dist/io/threemfImportFns.d.ts.map +1 -0
- package/dist/io.cjs +5 -5
- package/dist/io.js +5 -5
- package/dist/kernel/hullOps.d.ts +23 -0
- package/dist/kernel/hullOps.d.ts.map +1 -0
- package/dist/kernel/occtAdapter.d.ts +11 -0
- package/dist/kernel/occtAdapter.d.ts.map +1 -1
- package/dist/kernel/solverAdapter.d.ts +39 -0
- package/dist/kernel/solverAdapter.d.ts.map +1 -0
- package/dist/kernel/types.d.ts +11 -0
- package/dist/kernel/types.d.ts.map +1 -1
- package/dist/{loft-PMRx9iMG.cjs → loft-BcyyvWCj.cjs} +28 -28
- package/dist/{loft-BHn7GKm8.js → loft-CJMPx1NQ.js} +16 -16
- package/dist/{measurement-BfhEneUl.js → measurement-ByOztLxb.js} +3 -3
- package/dist/{measurement-B06hNs89.cjs → measurement-DU3ry-0Q.cjs} +3 -3
- package/dist/measurement.cjs +1 -1
- package/dist/measurement.js +1 -1
- package/dist/{meshFns-BEvGVcym.js → meshFns-D2gLyLFt.js} +3 -3
- package/dist/{meshFns-CJV_k_EQ.cjs → meshFns-DawUwI3W.cjs} +3 -3
- package/dist/{occtBoundary-CoXB2xvx.js → occtBoundary-CWzWqBCm.js} +436 -7
- package/dist/{occtBoundary-BFAaUtA7.cjs → occtBoundary-DH2VO-rq.cjs} +431 -2
- package/dist/operations/assemblyFns.d.ts +1 -0
- package/dist/operations/assemblyFns.d.ts.map +1 -1
- package/dist/operations/guidedSweepFns.d.ts +25 -0
- package/dist/operations/guidedSweepFns.d.ts.map +1 -0
- package/dist/operations/mateFns.d.ts +50 -0
- package/dist/operations/mateFns.d.ts.map +1 -0
- package/dist/operations/multiSweepFns.d.ts +32 -0
- package/dist/operations/multiSweepFns.d.ts.map +1 -0
- package/dist/operations/roofFns.d.ts +16 -0
- package/dist/operations/roofFns.d.ts.map +1 -0
- package/dist/operations/straightSkeleton.d.ts +28 -0
- package/dist/operations/straightSkeleton.d.ts.map +1 -0
- package/dist/{operations-CYGNxn5D.cjs → operations-CdELWxgv.cjs} +7 -7
- package/dist/{operations-B314mytX.js → operations-DiXo_4t9.js} +15 -15
- package/dist/operations.cjs +2 -2
- package/dist/operations.js +13 -13
- package/dist/query.cjs +5 -5
- package/dist/query.js +7 -7
- package/dist/result.cjs +1 -1
- package/dist/result.js +1 -1
- package/dist/{shapeFns-Z_ScEjmn.cjs → shapeFns-3RYtsUVY.cjs} +54 -21
- package/dist/{shapeFns-CWd_ASDV.js → shapeFns-4ioRrhih.js} +52 -19
- package/dist/{shapeTypes-UqVCIO_T.cjs → shapeTypes-CMjrTv36.cjs} +1 -1
- package/dist/{shapeTypes-BU2LKv2S.js → shapeTypes-D0vfRxWb.js} +13 -13
- package/dist/sketching.cjs +2 -2
- package/dist/sketching.js +2 -2
- package/dist/{curveBuilders-CN72XaIQ.js → surfaceBuilders-B7Jxob8g.js} +106 -13
- package/dist/{curveBuilders-Du03_Yyf.cjs → surfaceBuilders-Xx9DRRxs.cjs} +96 -3
- package/dist/text/textBlueprints.d.ts +38 -0
- package/dist/text/textBlueprints.d.ts.map +1 -1
- package/dist/topology/api.d.ts +5 -0
- package/dist/topology/api.d.ts.map +1 -1
- package/dist/topology/booleanFns.d.ts +10 -1
- package/dist/topology/booleanFns.d.ts.map +1 -1
- package/dist/topology/colorFns.d.ts +38 -0
- package/dist/topology/colorFns.d.ts.map +1 -0
- package/dist/topology/curveFns.d.ts +1 -1
- package/dist/topology/curveFns.d.ts.map +1 -1
- package/dist/topology/faceTagFns.d.ts +44 -0
- package/dist/topology/faceTagFns.d.ts.map +1 -0
- package/dist/topology/hullFns.d.ts +16 -0
- package/dist/topology/hullFns.d.ts.map +1 -0
- package/dist/topology/minkowskiFns.d.ts +20 -0
- package/dist/topology/minkowskiFns.d.ts.map +1 -0
- package/dist/topology/modifierFns.d.ts.map +1 -1
- package/dist/topology/polyhedronFns.d.ts +8 -0
- package/dist/topology/polyhedronFns.d.ts.map +1 -0
- package/dist/topology/shapeFns.d.ts +4 -0
- package/dist/topology/shapeFns.d.ts.map +1 -1
- package/dist/topology/surfaceBuilders.d.ts +7 -0
- package/dist/topology/surfaceBuilders.d.ts.map +1 -1
- package/dist/topology/surfaceFns.d.ts +38 -0
- package/dist/topology/surfaceFns.d.ts.map +1 -0
- package/dist/{topology-A7-jUtHB.cjs → topology-D-nGjCzV.cjs} +19 -20
- package/dist/{topology-BupialMm.js → topology-DRP9zreU.js} +8 -9
- package/dist/topology.cjs +13 -14
- package/dist/topology.js +51 -52
- package/dist/{vectors-BhfKwL9J.js → vectors-CZV4ZrTz.js} +2 -2
- package/dist/{vectors-t1XG4LpL.cjs → vectors-DwFeX0Ja.cjs} +2 -2
- package/dist/vectors.cjs +2 -2
- package/dist/vectors.js +2 -2
- package/package.json +8 -7
- package/dist/cast-C107o5ow.cjs +0 -102
- package/dist/cast-D0OhP1nV.js +0 -103
package/dist/brepjs.cjs
CHANGED
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const occtBoundary = require("./occtBoundary-
|
|
4
|
-
const errors = require("./errors-
|
|
5
|
-
const shapeTypes = require("./shapeTypes-
|
|
3
|
+
const occtBoundary = require("./occtBoundary-DH2VO-rq.cjs");
|
|
4
|
+
const errors = require("./errors-eRQu29oc.cjs");
|
|
5
|
+
const shapeTypes = require("./shapeTypes-CMjrTv36.cjs");
|
|
6
6
|
const vecOps = require("./vecOps-CjRL1jau.cjs");
|
|
7
|
-
const Blueprint = require("./Blueprint-
|
|
8
|
-
const curveFns = require("./curveFns-
|
|
9
|
-
const loft$2 = require("./loft-
|
|
10
|
-
const operations = require("./operations-
|
|
11
|
-
const boolean2D = require("./boolean2D-
|
|
7
|
+
const Blueprint = require("./Blueprint-BcbOBF-9.cjs");
|
|
8
|
+
const curveFns = require("./curveFns-BXCbASW-.cjs");
|
|
9
|
+
const loft$2 = require("./loft-BcyyvWCj.cjs");
|
|
10
|
+
const operations = require("./operations-CdELWxgv.cjs");
|
|
11
|
+
const boolean2D = require("./boolean2D-CqacqjME.cjs");
|
|
12
12
|
const _2d = require("./2d.cjs");
|
|
13
|
-
const helpers = require("./helpers-
|
|
13
|
+
const helpers = require("./helpers-CC21GeAr.cjs");
|
|
14
14
|
const io = require("./io.cjs");
|
|
15
|
-
const drawFns = require("./drawFns-
|
|
16
|
-
const vectors = require("./vectors-
|
|
17
|
-
const shapeFns = require("./shapeFns-
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const measurement = require("./measurement-
|
|
23
|
-
const
|
|
24
|
-
const cast = require("./cast-C107o5ow.cjs");
|
|
15
|
+
const drawFns = require("./drawFns-CAmFEqd1.cjs");
|
|
16
|
+
const vectors = require("./vectors-DwFeX0Ja.cjs");
|
|
17
|
+
const shapeFns = require("./shapeFns-3RYtsUVY.cjs");
|
|
18
|
+
const booleanFns = require("./booleanFns-NtKxkiXn.cjs");
|
|
19
|
+
const topology = require("./topology-D-nGjCzV.cjs");
|
|
20
|
+
const faceFns = require("./faceFns-DcndPHWm.cjs");
|
|
21
|
+
const meshFns = require("./meshFns-DawUwI3W.cjs");
|
|
22
|
+
const measurement = require("./measurement-DU3ry-0Q.cjs");
|
|
23
|
+
const surfaceBuilders = require("./surfaceBuilders-Xx9DRRxs.cjs");
|
|
25
24
|
const query = require("./query.cjs");
|
|
26
25
|
const result = require("./result.cjs");
|
|
27
|
-
const cornerFinder = require("./cornerFinder-
|
|
26
|
+
const cornerFinder = require("./cornerFinder-Bqy8Lw2p.cjs");
|
|
28
27
|
const worker = require("./worker.cjs");
|
|
29
28
|
const errorFactories = {
|
|
30
29
|
OCCT_OPERATION: (code, message, cause) => ({ kind: "OCCT_OPERATION", code, message, cause }),
|
|
@@ -82,7 +81,7 @@ function buildWireFinder(filters) {
|
|
|
82
81
|
isOpen: () => withFilter((wire2) => !curveFns.curveIsClosed(wire2)),
|
|
83
82
|
ofEdgeCount: (count) => withFilter((wire2) => {
|
|
84
83
|
let edgeCount = 0;
|
|
85
|
-
for (const _raw of
|
|
84
|
+
for (const _raw of faceFns.iterTopo(wire2.wrapped, "edge")) {
|
|
86
85
|
edgeCount++;
|
|
87
86
|
}
|
|
88
87
|
return edgeCount === count;
|
|
@@ -145,6 +144,1228 @@ function buildVertexFinder(filters) {
|
|
|
145
144
|
function vertexFinder() {
|
|
146
145
|
return buildVertexFinder([]);
|
|
147
146
|
}
|
|
147
|
+
function surfaceFromGrid(heights, options = {}) {
|
|
148
|
+
if (heights.length < 2) {
|
|
149
|
+
return errors.err(
|
|
150
|
+
errors.validationError(
|
|
151
|
+
errors.BrepErrorCode.SURFACE_GRID_TOO_SMALL,
|
|
152
|
+
`surfaceFromGrid: need at least 2 rows, got ${heights.length}`
|
|
153
|
+
)
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const rows = heights.length;
|
|
157
|
+
const cols = heights[0]?.length ?? 0;
|
|
158
|
+
if (cols < 2) {
|
|
159
|
+
return errors.err(
|
|
160
|
+
errors.validationError(
|
|
161
|
+
errors.BrepErrorCode.SURFACE_GRID_TOO_SMALL,
|
|
162
|
+
`surfaceFromGrid: need at least 2 columns, got ${cols}`
|
|
163
|
+
)
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
for (let r = 0; r < rows; r++) {
|
|
167
|
+
const row = heights[r];
|
|
168
|
+
if (!row || row.length !== cols) {
|
|
169
|
+
return errors.err(
|
|
170
|
+
errors.validationError(
|
|
171
|
+
errors.BrepErrorCode.SURFACE_GRID_JAGGED,
|
|
172
|
+
`surfaceFromGrid: row ${r} has ${row?.length ?? 0} columns, expected ${cols}`
|
|
173
|
+
)
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const { width = cols - 1, depth = rows - 1, scaleZ = 1 } = options;
|
|
178
|
+
const dx = width / (cols - 1);
|
|
179
|
+
const dy = depth / (rows - 1);
|
|
180
|
+
try {
|
|
181
|
+
return buildBSplineSurface(heights, rows, cols, dx, dy, scaleZ);
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
return buildTriangulatedSurface(heights, rows, cols, dx, dy, scaleZ);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
188
|
+
return errors.err(errors.occtError(errors.BrepErrorCode.SURFACE_FAILED, `surfaceFromGrid failed: ${raw}`, e));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function buildBSplineSurface(heights, rows, cols, dx, dy, scaleZ) {
|
|
192
|
+
const oc = occtBoundary.getKernel().oc;
|
|
193
|
+
const OC = oc;
|
|
194
|
+
const pntArray = new OC.TColgp_Array2OfPnt_2(1, rows, 1, cols);
|
|
195
|
+
try {
|
|
196
|
+
for (let r = 0; r < rows; r++) {
|
|
197
|
+
for (let c = 0; c < cols; c++) {
|
|
198
|
+
const row = heights[r];
|
|
199
|
+
const z = (row ? row[c] ?? 0 : 0) * scaleZ;
|
|
200
|
+
const pnt = new oc.gp_Pnt_3(c * dx, r * dy, z);
|
|
201
|
+
pntArray.SetValue(r + 1, c + 1, pnt);
|
|
202
|
+
pnt.delete();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const fitter = new OC.GeomAPI_PointsToBSplineSurface_2(pntArray, 3, 8, 0, 1e-3);
|
|
206
|
+
const surface = fitter.Surface();
|
|
207
|
+
const faceMaker = new OC.BRepBuilderAPI_MakeFace_8(surface, 1e-6);
|
|
208
|
+
let result2;
|
|
209
|
+
if (faceMaker.IsDone()) {
|
|
210
|
+
const shape2 = shapeTypes.castShape(faceMaker.Face());
|
|
211
|
+
if (shapeTypes.isFace(shape2)) {
|
|
212
|
+
result2 = errors.ok(shape2);
|
|
213
|
+
} else {
|
|
214
|
+
shape2[Symbol.dispose]();
|
|
215
|
+
result2 = errors.err(
|
|
216
|
+
errors.occtError(errors.BrepErrorCode.SURFACE_FAILED, "B-spline surface did not produce a face")
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
result2 = errors.err(
|
|
221
|
+
errors.occtError(
|
|
222
|
+
errors.BrepErrorCode.SURFACE_FAILED,
|
|
223
|
+
"BRepBuilderAPI_MakeFace failed for B-spline surface"
|
|
224
|
+
)
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
faceMaker.delete();
|
|
228
|
+
fitter.delete();
|
|
229
|
+
return result2;
|
|
230
|
+
} finally {
|
|
231
|
+
pntArray.delete();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function buildTriangulatedSurface(heights, rows, cols, dx, dy, scaleZ) {
|
|
235
|
+
const oc = occtBoundary.getKernel().oc;
|
|
236
|
+
function pt(r, c) {
|
|
237
|
+
const row = heights[r];
|
|
238
|
+
const z = (row ? row[c] ?? 0 : 0) * scaleZ;
|
|
239
|
+
return { x: c * dx, y: r * dy, z };
|
|
240
|
+
}
|
|
241
|
+
function buildTriFace2(a, b, c) {
|
|
242
|
+
const gpA = new oc.gp_Pnt_3(a.x, a.y, a.z);
|
|
243
|
+
const gpB = new oc.gp_Pnt_3(b.x, b.y, b.z);
|
|
244
|
+
const gpC = new oc.gp_Pnt_3(c.x, c.y, c.z);
|
|
245
|
+
const e1 = new oc.BRepBuilderAPI_MakeEdge_3(gpA, gpB);
|
|
246
|
+
const e2 = new oc.BRepBuilderAPI_MakeEdge_3(gpB, gpC);
|
|
247
|
+
const e3 = new oc.BRepBuilderAPI_MakeEdge_3(gpC, gpA);
|
|
248
|
+
const wireBuilder = new oc.BRepBuilderAPI_MakeWire_1();
|
|
249
|
+
wireBuilder.Add_1(e1.Edge());
|
|
250
|
+
wireBuilder.Add_1(e2.Edge());
|
|
251
|
+
wireBuilder.Add_1(e3.Edge());
|
|
252
|
+
let face2 = null;
|
|
253
|
+
if (wireBuilder.IsDone()) {
|
|
254
|
+
const makeFace = new oc.BRepBuilderAPI_MakeFace_15(wireBuilder.Wire(), false);
|
|
255
|
+
if (makeFace.IsDone()) {
|
|
256
|
+
face2 = makeFace.Face();
|
|
257
|
+
}
|
|
258
|
+
makeFace.delete();
|
|
259
|
+
}
|
|
260
|
+
wireBuilder.delete();
|
|
261
|
+
e1.delete();
|
|
262
|
+
e2.delete();
|
|
263
|
+
e3.delete();
|
|
264
|
+
gpA.delete();
|
|
265
|
+
gpB.delete();
|
|
266
|
+
gpC.delete();
|
|
267
|
+
return face2;
|
|
268
|
+
}
|
|
269
|
+
const sewing = new oc.BRepBuilderAPI_Sewing(1e-6, true, true, true, false);
|
|
270
|
+
let faceCount = 0;
|
|
271
|
+
try {
|
|
272
|
+
for (let r = 0; r < rows - 1; r++) {
|
|
273
|
+
for (let c = 0; c < cols - 1; c++) {
|
|
274
|
+
const p00 = pt(r, c);
|
|
275
|
+
const p10 = pt(r + 1, c);
|
|
276
|
+
const p11 = pt(r + 1, c + 1);
|
|
277
|
+
const p01 = pt(r, c + 1);
|
|
278
|
+
const f1 = buildTriFace2(p00, p10, p11);
|
|
279
|
+
if (f1 !== null) {
|
|
280
|
+
sewing.Add(f1);
|
|
281
|
+
faceCount++;
|
|
282
|
+
}
|
|
283
|
+
const f2 = buildTriFace2(p00, p11, p01);
|
|
284
|
+
if (f2 !== null) {
|
|
285
|
+
sewing.Add(f2);
|
|
286
|
+
faceCount++;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (faceCount === 0) {
|
|
291
|
+
sewing.delete();
|
|
292
|
+
return errors.err(
|
|
293
|
+
errors.occtError(errors.BrepErrorCode.SURFACE_FAILED, "surfaceFromGrid: no valid triangular faces built")
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
const sewProgress = new oc.Message_ProgressRange_1();
|
|
297
|
+
sewing.Perform(sewProgress);
|
|
298
|
+
sewProgress.delete();
|
|
299
|
+
const sewn = sewing.SewedShape();
|
|
300
|
+
const shape2 = shapeTypes.castShape(sewn);
|
|
301
|
+
if (shapeTypes.isFace(shape2)) {
|
|
302
|
+
return errors.ok(shape2);
|
|
303
|
+
}
|
|
304
|
+
if (shapeTypes.isShell(shape2)) {
|
|
305
|
+
return errors.ok(shape2);
|
|
306
|
+
}
|
|
307
|
+
shape2[Symbol.dispose]();
|
|
308
|
+
return errors.err(
|
|
309
|
+
errors.occtError(errors.BrepErrorCode.SURFACE_FAILED, "surfaceFromGrid: unexpected shape type from sewing")
|
|
310
|
+
);
|
|
311
|
+
} finally {
|
|
312
|
+
sewing.delete();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async function surfaceFromImage(blob, options = {}) {
|
|
316
|
+
const channel = options.channel ?? "luminance";
|
|
317
|
+
const downsample = Math.max(1, Math.round(options.downsample ?? 1));
|
|
318
|
+
if (typeof createImageBitmap !== "function") {
|
|
319
|
+
return errors.err(
|
|
320
|
+
errors.ioError(
|
|
321
|
+
errors.BrepErrorCode.SURFACE_FAILED,
|
|
322
|
+
"surfaceFromImage requires createImageBitmap (not available in this environment)"
|
|
323
|
+
)
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
let bitmap;
|
|
327
|
+
try {
|
|
328
|
+
bitmap = await createImageBitmap(blob);
|
|
329
|
+
} catch (e) {
|
|
330
|
+
return errors.err(
|
|
331
|
+
errors.ioError(
|
|
332
|
+
errors.BrepErrorCode.SURFACE_FAILED,
|
|
333
|
+
`surfaceFromImage: failed to decode image — ${e instanceof Error ? e.message : String(e)}`
|
|
334
|
+
)
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
const w = bitmap.width;
|
|
338
|
+
const h = bitmap.height;
|
|
339
|
+
if (w < 2 || h < 2) {
|
|
340
|
+
bitmap.close();
|
|
341
|
+
return errors.err(
|
|
342
|
+
errors.validationError(
|
|
343
|
+
errors.BrepErrorCode.SURFACE_GRID_TOO_SMALL,
|
|
344
|
+
`surfaceFromImage: image too small (${w}x${h}), need at least 2x2`
|
|
345
|
+
)
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
if (typeof OffscreenCanvas !== "function") {
|
|
349
|
+
bitmap.close();
|
|
350
|
+
return errors.err(
|
|
351
|
+
errors.ioError(
|
|
352
|
+
errors.BrepErrorCode.SURFACE_FAILED,
|
|
353
|
+
"surfaceFromImage requires OffscreenCanvas (not available in this environment)"
|
|
354
|
+
)
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
const canvas = new OffscreenCanvas(w, h);
|
|
358
|
+
const ctx = canvas.getContext("2d");
|
|
359
|
+
if (!ctx) {
|
|
360
|
+
bitmap.close();
|
|
361
|
+
return errors.err(
|
|
362
|
+
errors.ioError(errors.BrepErrorCode.SURFACE_FAILED, "surfaceFromImage: could not get 2D canvas context")
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
ctx.drawImage(bitmap, 0, 0);
|
|
366
|
+
bitmap.close();
|
|
367
|
+
const imageData = ctx.getImageData(0, 0, w, h);
|
|
368
|
+
const data = imageData.data;
|
|
369
|
+
const rows = [];
|
|
370
|
+
for (let y = 0; y < h; y += downsample) {
|
|
371
|
+
const row = [];
|
|
372
|
+
for (let x = 0; x < w; x += downsample) {
|
|
373
|
+
const idx = (y * w + x) * 4;
|
|
374
|
+
const r = data[idx] ?? 0;
|
|
375
|
+
const g = data[idx + 1] ?? 0;
|
|
376
|
+
const b = data[idx + 2] ?? 0;
|
|
377
|
+
let value;
|
|
378
|
+
switch (channel) {
|
|
379
|
+
case "r":
|
|
380
|
+
value = r / 255;
|
|
381
|
+
break;
|
|
382
|
+
case "g":
|
|
383
|
+
value = g / 255;
|
|
384
|
+
break;
|
|
385
|
+
case "b":
|
|
386
|
+
value = b / 255;
|
|
387
|
+
break;
|
|
388
|
+
default:
|
|
389
|
+
value = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
row.push(value);
|
|
393
|
+
}
|
|
394
|
+
rows.push(row);
|
|
395
|
+
}
|
|
396
|
+
const gridOpts = {};
|
|
397
|
+
if (options.width !== void 0) gridOpts.width = options.width;
|
|
398
|
+
if (options.depth !== void 0) gridOpts.depth = options.depth;
|
|
399
|
+
if (options.scaleZ !== void 0) gridOpts.scaleZ = options.scaleZ;
|
|
400
|
+
return surfaceFromGrid(rows, gridOpts);
|
|
401
|
+
}
|
|
402
|
+
function validateNotNull$1(shape2, label) {
|
|
403
|
+
if (shape2.wrapped.IsNull()) {
|
|
404
|
+
return errors.err(errors.validationError(errors.BrepErrorCode.NULL_SHAPE_INPUT, `${label} is a null shape`));
|
|
405
|
+
}
|
|
406
|
+
return errors.ok(void 0);
|
|
407
|
+
}
|
|
408
|
+
function hull(shapes, options = {}) {
|
|
409
|
+
if (shapes.length === 0) {
|
|
410
|
+
return errors.err(
|
|
411
|
+
errors.validationError(
|
|
412
|
+
errors.BrepErrorCode.HULL_EMPTY_INPUT,
|
|
413
|
+
"hull: at least one shape is required",
|
|
414
|
+
void 0,
|
|
415
|
+
void 0,
|
|
416
|
+
"Provide one or more shapes to compute a convex hull"
|
|
417
|
+
)
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
for (const [i, shape2] of shapes.entries()) {
|
|
421
|
+
const check = validateNotNull$1(shape2, `hull: shapes[${i}]`);
|
|
422
|
+
if (errors.isErr(check)) return check;
|
|
423
|
+
}
|
|
424
|
+
const tolerance = options.tolerance ?? 0.1;
|
|
425
|
+
try {
|
|
426
|
+
const kernel = occtBoundary.getKernel();
|
|
427
|
+
const ocShapes = shapes.map((s) => s.wrapped);
|
|
428
|
+
const resultOc = kernel.hull(ocShapes, tolerance);
|
|
429
|
+
const cast = shapeTypes.castShape(resultOc);
|
|
430
|
+
if (!shapeTypes.isSolid(cast)) {
|
|
431
|
+
return errors.err(
|
|
432
|
+
errors.occtError(errors.BrepErrorCode.HULL_NOT_3D, "Hull result is not a solid; input may be degenerate")
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
return errors.ok(cast);
|
|
436
|
+
} catch (e) {
|
|
437
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
438
|
+
if (raw.includes("coplanar") || raw.includes("fewer than") || raw.includes("degenerate")) {
|
|
439
|
+
return errors.err(errors.occtError(errors.BrepErrorCode.HULL_DEGENERATE, `Hull degenerate: ${raw}`, e));
|
|
440
|
+
}
|
|
441
|
+
return errors.err(errors.occtError(errors.BrepErrorCode.HULL_FAILED, `Hull operation failed: ${raw}`, e));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function detectSphere(shape2) {
|
|
445
|
+
const oc = occtBoundary.getKernel().oc;
|
|
446
|
+
const faces = shapeFns.getFaces(shape2);
|
|
447
|
+
if (faces.length !== 1) return null;
|
|
448
|
+
const face2 = faces[0];
|
|
449
|
+
const r = shapeTypes.gcWithScope();
|
|
450
|
+
const adaptor = r(new oc.BRepAdaptor_Surface_2(face2.wrapped, true));
|
|
451
|
+
const surfType = adaptor.GetType();
|
|
452
|
+
if (surfType !== oc.GeomAbs_SurfaceType.GeomAbs_Sphere) return null;
|
|
453
|
+
const ocSphere = adaptor.Sphere();
|
|
454
|
+
const radius = ocSphere.Radius();
|
|
455
|
+
ocSphere.delete();
|
|
456
|
+
return radius;
|
|
457
|
+
}
|
|
458
|
+
function minkowskiSphere(shape2, radius, tolerance) {
|
|
459
|
+
const oc = occtBoundary.getKernel().oc;
|
|
460
|
+
const r = shapeTypes.gcWithScope();
|
|
461
|
+
try {
|
|
462
|
+
const offsetMaker = r(new oc.BRepOffsetAPI_MakeOffsetShape());
|
|
463
|
+
const progress = r(new oc.Message_ProgressRange_1());
|
|
464
|
+
offsetMaker.PerformByJoin(
|
|
465
|
+
shape2.wrapped,
|
|
466
|
+
radius,
|
|
467
|
+
tolerance,
|
|
468
|
+
oc.BRepOffset_Mode.BRepOffset_Skin,
|
|
469
|
+
false,
|
|
470
|
+
false,
|
|
471
|
+
oc.GeomAbs_JoinType.GeomAbs_Arc,
|
|
472
|
+
false,
|
|
473
|
+
progress
|
|
474
|
+
);
|
|
475
|
+
const resultShape = offsetMaker.Shape();
|
|
476
|
+
const wrapped = shapeTypes.castShape(resultShape);
|
|
477
|
+
if (!shapeTypes.isShape3D(wrapped)) {
|
|
478
|
+
wrapped[Symbol.dispose]();
|
|
479
|
+
return errors.err(
|
|
480
|
+
errors.typeCastError(
|
|
481
|
+
errors.BrepErrorCode.MINKOWSKI_NOT_3D,
|
|
482
|
+
"Minkowski sphere offset did not produce a 3D shape"
|
|
483
|
+
)
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
return errors.ok(wrapped);
|
|
487
|
+
} catch (e) {
|
|
488
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
489
|
+
return errors.err(
|
|
490
|
+
errors.occtError(errors.BrepErrorCode.MINKOWSKI_FAILED, `Minkowski sphere offset failed: ${raw}`, e, {
|
|
491
|
+
operation: "minkowski",
|
|
492
|
+
fastPath: "sphere"
|
|
493
|
+
})
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function minkowskiGeneral(shape2, tool, _tolerance) {
|
|
498
|
+
const oc = occtBoundary.getKernel().oc;
|
|
499
|
+
try {
|
|
500
|
+
const shapeVerts = shapeFns.getVertices(shape2);
|
|
501
|
+
const toolVerts = shapeFns.getVertices(tool);
|
|
502
|
+
if (shapeVerts.length === 0 || toolVerts.length === 0) {
|
|
503
|
+
return errors.err(
|
|
504
|
+
errors.occtError(
|
|
505
|
+
errors.BrepErrorCode.MINKOWSKI_FAILED,
|
|
506
|
+
"Minkowski sum: one or both shapes have no vertices",
|
|
507
|
+
void 0,
|
|
508
|
+
{
|
|
509
|
+
operation: "minkowski"
|
|
510
|
+
}
|
|
511
|
+
)
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
const sumPoints = [];
|
|
515
|
+
for (const sv of shapeVerts) {
|
|
516
|
+
const r1 = shapeTypes.gcWithScope();
|
|
517
|
+
const pa = r1(oc.BRep_Tool.Pnt(sv.wrapped));
|
|
518
|
+
const ax = pa.X(), ay = pa.Y(), az = pa.Z();
|
|
519
|
+
for (const tv of toolVerts) {
|
|
520
|
+
const r2 = shapeTypes.gcWithScope();
|
|
521
|
+
const pb = r2(oc.BRep_Tool.Pnt(tv.wrapped));
|
|
522
|
+
const bx = pb.X(), by = pb.Y(), bz = pb.Z();
|
|
523
|
+
sumPoints.push({ x: ax + bx, y: ay + by, z: az + bz });
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const kernel = occtBoundary.getKernel();
|
|
527
|
+
const hullShape = kernel.hullFromPoints(sumPoints, _tolerance);
|
|
528
|
+
const wrapped = shapeTypes.castShape(hullShape);
|
|
529
|
+
if (!shapeTypes.isShape3D(wrapped)) {
|
|
530
|
+
wrapped[Symbol.dispose]();
|
|
531
|
+
return errors.err(
|
|
532
|
+
errors.typeCastError(errors.BrepErrorCode.MINKOWSKI_NOT_3D, "Minkowski hull did not produce a 3D shape")
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
return errors.ok(wrapped);
|
|
536
|
+
} catch (e) {
|
|
537
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
538
|
+
return errors.err(
|
|
539
|
+
errors.occtError(errors.BrepErrorCode.MINKOWSKI_FAILED, `Minkowski general path failed: ${raw}`, e, {
|
|
540
|
+
operation: "minkowski"
|
|
541
|
+
})
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
function minkowski(shape2, tool, options = {}) {
|
|
546
|
+
const { tolerance = 1e-6 } = options;
|
|
547
|
+
if (shape2.wrapped.IsNull()) {
|
|
548
|
+
return errors.err(errors.validationError(errors.BrepErrorCode.NULL_SHAPE_INPUT, "minkowski: shape is a null shape"));
|
|
549
|
+
}
|
|
550
|
+
if (tool.wrapped.IsNull()) {
|
|
551
|
+
return errors.err(
|
|
552
|
+
errors.validationError(errors.BrepErrorCode.MINKOWSKI_NULL_TOOL, "minkowski: tool is a null shape")
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
if (!shapeTypes.isShape3D(shape2) || !shapeTypes.isShape3D(tool)) {
|
|
556
|
+
return errors.err(
|
|
557
|
+
errors.validationError(errors.BrepErrorCode.MINKOWSKI_NOT_3D, "minkowski: both shape and tool must be 3D")
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
const sphereRadius = detectSphere(tool);
|
|
561
|
+
if (sphereRadius !== null) {
|
|
562
|
+
return minkowskiSphere(shape2, sphereRadius, tolerance);
|
|
563
|
+
}
|
|
564
|
+
return minkowskiGeneral(shape2, tool, tolerance);
|
|
565
|
+
}
|
|
566
|
+
function polyhedron(points, faces, options = {}) {
|
|
567
|
+
const { tolerance = 1e-6 } = options;
|
|
568
|
+
if (points.length < 4) {
|
|
569
|
+
return errors.err(
|
|
570
|
+
errors.validationError(
|
|
571
|
+
errors.BrepErrorCode.POLYHEDRON_INSUFFICIENT_POINTS,
|
|
572
|
+
`polyhedron: need at least 4 points, got ${points.length}`
|
|
573
|
+
)
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
if (faces.length < 4) {
|
|
577
|
+
return errors.err(
|
|
578
|
+
errors.validationError(
|
|
579
|
+
errors.BrepErrorCode.POLYHEDRON_INSUFFICIENT_FACES,
|
|
580
|
+
`polyhedron: need at least 4 faces, got ${faces.length}`
|
|
581
|
+
)
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
const triangles = [];
|
|
585
|
+
for (const [fi, face2] of faces.entries()) {
|
|
586
|
+
for (const idx of face2) {
|
|
587
|
+
if (idx < 0 || idx >= points.length) {
|
|
588
|
+
return errors.err(
|
|
589
|
+
errors.validationError(
|
|
590
|
+
errors.BrepErrorCode.POLYHEDRON_INVALID_INDEX,
|
|
591
|
+
`polyhedron: face ${fi} has out-of-range index ${idx} (${points.length} points)`
|
|
592
|
+
)
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (face2.length < 3) continue;
|
|
597
|
+
const v0 = face2[0];
|
|
598
|
+
for (let i = 1; i < face2.length - 1; i++) {
|
|
599
|
+
triangles.push([v0, face2[i], face2[i + 1]]);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
try {
|
|
603
|
+
const kernel = occtBoundary.getKernel();
|
|
604
|
+
const ptObjs = points.map(([x, y, z]) => ({ x, y, z }));
|
|
605
|
+
const resultOc = kernel.buildSolidFromFaces(ptObjs, triangles, tolerance);
|
|
606
|
+
const cast = shapeTypes.castShape(resultOc);
|
|
607
|
+
if (!shapeTypes.isSolid(cast)) {
|
|
608
|
+
cast[Symbol.dispose]();
|
|
609
|
+
return errors.err(errors.occtError(errors.BrepErrorCode.POLYHEDRON_FAILED, "Polyhedron did not produce a solid"));
|
|
610
|
+
}
|
|
611
|
+
return errors.ok(cast);
|
|
612
|
+
} catch (e) {
|
|
613
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
614
|
+
return errors.err(errors.occtError(errors.BrepErrorCode.POLYHEDRON_FAILED, `Polyhedron failed: ${raw}`, e));
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
function multiSectionSweep(sections, spine, options) {
|
|
618
|
+
if (sections.length < 2) {
|
|
619
|
+
return errors.err(
|
|
620
|
+
errors.validationError(
|
|
621
|
+
errors.BrepErrorCode.MULTI_SWEEP_INSUFFICIENT_SECTIONS,
|
|
622
|
+
`Multi-section sweep requires at least 2 sections, got ${sections.length}`
|
|
623
|
+
)
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
const { solid: solid2 = true, ruled = false, tolerance = 1e-6 } = options ?? {};
|
|
627
|
+
try {
|
|
628
|
+
const oc = occtBoundary.getKernel().oc;
|
|
629
|
+
const r = shapeTypes.gcWithScope();
|
|
630
|
+
const adaptor = r(new oc.BRepAdaptor_CompCurve_2(spine.wrapped, false));
|
|
631
|
+
const uFirst = Number(adaptor.FirstParameter());
|
|
632
|
+
const uLast = Number(adaptor.LastParameter());
|
|
633
|
+
const uRange = uLast - uFirst;
|
|
634
|
+
const params = sections.map((s, i) => {
|
|
635
|
+
if (s.location !== void 0) {
|
|
636
|
+
return uFirst + s.location * uRange;
|
|
637
|
+
}
|
|
638
|
+
return uFirst + i / (sections.length - 1) * uRange;
|
|
639
|
+
});
|
|
640
|
+
const builder = r(new oc.BRepOffsetAPI_ThruSections(solid2, ruled, tolerance));
|
|
641
|
+
for (let i = 0; i < sections.length; i++) {
|
|
642
|
+
const param = params[i];
|
|
643
|
+
const section2 = sections[i];
|
|
644
|
+
if (param === void 0 || section2 === void 0) continue;
|
|
645
|
+
const pnt = r(new oc.gp_Pnt_1());
|
|
646
|
+
const tangent = r(new oc.gp_Vec_1());
|
|
647
|
+
adaptor.D1(param, pnt, tangent);
|
|
648
|
+
const tangentDir = r(new oc.gp_Dir_2(tangent));
|
|
649
|
+
const toAx3 = r(new oc.gp_Ax3_4(pnt, tangentDir));
|
|
650
|
+
const trsf = r(new oc.gp_Trsf_1());
|
|
651
|
+
trsf.SetTransformation_2(toAx3);
|
|
652
|
+
trsf.Invert();
|
|
653
|
+
const transformer = r(new oc.BRepBuilderAPI_Transform_2(section2.wire.wrapped, trsf, true));
|
|
654
|
+
const transformedShape = transformer.Shape();
|
|
655
|
+
const transformedWire = oc.TopoDS.Wire_1(transformedShape);
|
|
656
|
+
builder.AddWire(transformedWire);
|
|
657
|
+
}
|
|
658
|
+
const progress = r(new oc.Message_ProgressRange_1());
|
|
659
|
+
builder.Build(progress);
|
|
660
|
+
if (!builder.IsDone()) {
|
|
661
|
+
return errors.err(errors.occtError(errors.BrepErrorCode.MULTI_SWEEP_FAILED, "Multi-section sweep build failed"));
|
|
662
|
+
}
|
|
663
|
+
const result2 = shapeTypes.castShape(builder.Shape());
|
|
664
|
+
if (!shapeTypes.isShape3D(result2)) {
|
|
665
|
+
return errors.err(
|
|
666
|
+
errors.typeCastError("MULTI_SWEEP_NOT_3D", "Multi-section sweep did not produce a 3D shape")
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
return errors.ok(result2);
|
|
670
|
+
} catch (e) {
|
|
671
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
672
|
+
return errors.err(
|
|
673
|
+
errors.occtError(errors.BrepErrorCode.MULTI_SWEEP_FAILED, `Multi-section sweep failed: ${raw}`, e)
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
function guidedSweep(profile, spine, guides, options = {}) {
|
|
678
|
+
const { transition = "transformed", solid: solid2 = true, tolerance } = options;
|
|
679
|
+
try {
|
|
680
|
+
const oc = occtBoundary.getKernel().oc;
|
|
681
|
+
const r = shapeTypes.gcWithScope();
|
|
682
|
+
const builder = r(new oc.BRepOffsetAPI_MakePipeShell(spine.wrapped));
|
|
683
|
+
const modeMap = {
|
|
684
|
+
transformed: oc.BRepBuilderAPI_TransitionMode.BRepBuilderAPI_Transformed,
|
|
685
|
+
round: oc.BRepBuilderAPI_TransitionMode.BRepBuilderAPI_RoundCorner,
|
|
686
|
+
right: oc.BRepBuilderAPI_TransitionMode.BRepBuilderAPI_RightCorner
|
|
687
|
+
};
|
|
688
|
+
builder.SetTransitionMode(modeMap[transition]);
|
|
689
|
+
if (tolerance !== void 0) {
|
|
690
|
+
builder.SetTolerance(tolerance, tolerance, 1e-7);
|
|
691
|
+
}
|
|
692
|
+
if (guides.length > 0) {
|
|
693
|
+
const firstGuide = guides[0];
|
|
694
|
+
builder.SetMode_5(firstGuide.wrapped, false, oc.BRepFill_TypeOfContact.BRepFill_NoContact);
|
|
695
|
+
}
|
|
696
|
+
builder.Add_1(profile.wrapped, false, false);
|
|
697
|
+
const progress = r(new oc.Message_ProgressRange_1());
|
|
698
|
+
builder.Build(progress);
|
|
699
|
+
if (!builder.IsDone()) {
|
|
700
|
+
return errors.err(errors.occtError(errors.BrepErrorCode.GUIDED_SWEEP_FAILED, "Guided sweep build failed"));
|
|
701
|
+
}
|
|
702
|
+
if (solid2) {
|
|
703
|
+
builder.MakeSolid();
|
|
704
|
+
}
|
|
705
|
+
const result2 = shapeTypes.castShape(builder.Shape());
|
|
706
|
+
if (!shapeTypes.isShape3D(result2)) {
|
|
707
|
+
return errors.err(errors.typeCastError("GUIDED_SWEEP_NOT_3D", "Guided sweep did not produce a 3D shape"));
|
|
708
|
+
}
|
|
709
|
+
return errors.ok(result2);
|
|
710
|
+
} catch (e) {
|
|
711
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
712
|
+
return errors.err(errors.occtError(errors.BrepErrorCode.GUIDED_SWEEP_FAILED, `Guided sweep failed: ${raw}`, e));
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
const EPS = 1e-10;
|
|
716
|
+
function cross2(ax, ay, bx, by) {
|
|
717
|
+
return ax * by - ay * bx;
|
|
718
|
+
}
|
|
719
|
+
function dot2(ax, ay, bx, by) {
|
|
720
|
+
return ax * bx + ay * by;
|
|
721
|
+
}
|
|
722
|
+
function len2(x, y) {
|
|
723
|
+
return Math.sqrt(x * x + y * y);
|
|
724
|
+
}
|
|
725
|
+
function polyAt(poly, i) {
|
|
726
|
+
const p = poly[(i % poly.length + poly.length) % poly.length];
|
|
727
|
+
if (!p) throw new Error(`Invalid polygon index ${i} for length ${poly.length}`);
|
|
728
|
+
return p;
|
|
729
|
+
}
|
|
730
|
+
function ensureCCW(poly) {
|
|
731
|
+
let area = 0;
|
|
732
|
+
for (let i = 0; i < poly.length; i++) {
|
|
733
|
+
const cur = polyAt(poly, i);
|
|
734
|
+
const nxt = polyAt(poly, i + 1);
|
|
735
|
+
area += cur.x * nxt.y - nxt.x * cur.y;
|
|
736
|
+
}
|
|
737
|
+
if (area < 0) return [...poly].reverse();
|
|
738
|
+
return poly;
|
|
739
|
+
}
|
|
740
|
+
function bisector(poly, i) {
|
|
741
|
+
const prev = polyAt(poly, i - 1);
|
|
742
|
+
const cur = polyAt(poly, i);
|
|
743
|
+
const next = polyAt(poly, i + 1);
|
|
744
|
+
const e1x = cur.x - prev.x;
|
|
745
|
+
const e1y = cur.y - prev.y;
|
|
746
|
+
const e1l = len2(e1x, e1y);
|
|
747
|
+
const e2x = next.x - cur.x;
|
|
748
|
+
const e2y = next.y - cur.y;
|
|
749
|
+
const e2l = len2(e2x, e2y);
|
|
750
|
+
if (e1l < EPS || e2l < EPS) return { dx: 0, dy: 0 };
|
|
751
|
+
const n1x = -e1y / e1l;
|
|
752
|
+
const n1y = e1x / e1l;
|
|
753
|
+
const n2x = -e2y / e2l;
|
|
754
|
+
const n2y = e2x / e2l;
|
|
755
|
+
let bx = n1x + n2x;
|
|
756
|
+
let by = n1y + n2y;
|
|
757
|
+
const bl = len2(bx, by);
|
|
758
|
+
if (bl < EPS) {
|
|
759
|
+
return { dx: n1x, dy: n1y };
|
|
760
|
+
}
|
|
761
|
+
bx /= bl;
|
|
762
|
+
by /= bl;
|
|
763
|
+
const cosHalf = dot2(bx, by, n1x, n1y);
|
|
764
|
+
const speed = Math.abs(cosHalf) > EPS ? 1 / cosHalf : 1;
|
|
765
|
+
return { dx: bx * speed, dy: by * speed };
|
|
766
|
+
}
|
|
767
|
+
function isLavNodeReflex(node) {
|
|
768
|
+
const prev = node.prev;
|
|
769
|
+
const next = node.next;
|
|
770
|
+
return cross2(node.x - prev.x, node.y - prev.y, next.x - node.x, next.y - node.y) < -EPS;
|
|
771
|
+
}
|
|
772
|
+
function createLav(poly) {
|
|
773
|
+
const nodes = poly.map((p, i) => {
|
|
774
|
+
const b = bisector(poly, i);
|
|
775
|
+
return {
|
|
776
|
+
x: p.x,
|
|
777
|
+
y: p.y,
|
|
778
|
+
bx: b.dx,
|
|
779
|
+
by: b.dy,
|
|
780
|
+
origIdx: i,
|
|
781
|
+
prev: null,
|
|
782
|
+
next: null,
|
|
783
|
+
active: true
|
|
784
|
+
};
|
|
785
|
+
});
|
|
786
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
787
|
+
const node = nodes[i];
|
|
788
|
+
const prevNode = nodes[(i - 1 + nodes.length) % nodes.length];
|
|
789
|
+
const nextNode = nodes[(i + 1) % nodes.length];
|
|
790
|
+
if (node && prevNode && nextNode) {
|
|
791
|
+
node.prev = prevNode;
|
|
792
|
+
node.next = nextNode;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return nodes;
|
|
796
|
+
}
|
|
797
|
+
function lavSize(start) {
|
|
798
|
+
let count = 1;
|
|
799
|
+
let cur = start.next;
|
|
800
|
+
while (cur !== start) {
|
|
801
|
+
count++;
|
|
802
|
+
cur = cur.next;
|
|
803
|
+
if (count > 1e4) break;
|
|
804
|
+
}
|
|
805
|
+
return count;
|
|
806
|
+
}
|
|
807
|
+
function bisectorIntersectTime(a, b) {
|
|
808
|
+
const ddx = a.bx - b.bx;
|
|
809
|
+
const ddy = a.by - b.by;
|
|
810
|
+
const dxp = b.x - a.x;
|
|
811
|
+
const dyp = b.y - a.y;
|
|
812
|
+
if (Math.abs(ddx) < EPS && Math.abs(ddy) < EPS) return null;
|
|
813
|
+
let t;
|
|
814
|
+
if (Math.abs(ddx) > Math.abs(ddy)) {
|
|
815
|
+
t = dxp / ddx;
|
|
816
|
+
} else {
|
|
817
|
+
t = dyp / ddy;
|
|
818
|
+
}
|
|
819
|
+
if (t < EPS) return null;
|
|
820
|
+
const otherDd = Math.abs(ddx) > Math.abs(ddy) ? ddy : ddx;
|
|
821
|
+
const otherDp = Math.abs(ddx) > Math.abs(ddy) ? dyp : dxp;
|
|
822
|
+
if (Math.abs(otherDd) > EPS) {
|
|
823
|
+
const t2 = otherDp / otherDd;
|
|
824
|
+
if (Math.abs(t - t2) > 1e-4 * Math.max(1, Math.abs(t))) return null;
|
|
825
|
+
}
|
|
826
|
+
return t;
|
|
827
|
+
}
|
|
828
|
+
function raySplitTime(node, eA, eB) {
|
|
829
|
+
const edx = eB.x - eA.x;
|
|
830
|
+
const edy = eB.y - eA.y;
|
|
831
|
+
const el = len2(edx, edy);
|
|
832
|
+
if (el < EPS) return null;
|
|
833
|
+
const enx = -edy / el;
|
|
834
|
+
const eny = edx / el;
|
|
835
|
+
const d0 = (node.x - eA.x) * enx + (node.y - eA.y) * eny;
|
|
836
|
+
const relBx = node.bx - (eA.bx + eB.bx) / 2;
|
|
837
|
+
const relBy = node.by - (eA.by + eB.by) / 2;
|
|
838
|
+
const dRate = relBx * enx + relBy * eny;
|
|
839
|
+
if (Math.abs(dRate) < EPS) return null;
|
|
840
|
+
const t = -d0 / dRate;
|
|
841
|
+
if (t < EPS) return null;
|
|
842
|
+
const px = node.x + t * node.bx;
|
|
843
|
+
const py = node.y + t * node.by;
|
|
844
|
+
const ax = eA.x + t * eA.bx;
|
|
845
|
+
const ay = eA.y + t * eA.by;
|
|
846
|
+
const bxx = eB.x + t * eB.bx;
|
|
847
|
+
const byy = eB.y + t * eB.by;
|
|
848
|
+
const segDx = bxx - ax;
|
|
849
|
+
const segDy = byy - ay;
|
|
850
|
+
const segL = len2(segDx, segDy);
|
|
851
|
+
if (segL < EPS) return t;
|
|
852
|
+
const s = dot2(px - ax, py - ay, segDx, segDy) / (segL * segL);
|
|
853
|
+
if (s < -0.01 || s > 1.01) return null;
|
|
854
|
+
return t;
|
|
855
|
+
}
|
|
856
|
+
function computeEvents(lavNodes) {
|
|
857
|
+
const events = [];
|
|
858
|
+
for (const node of lavNodes) {
|
|
859
|
+
if (!node.active) continue;
|
|
860
|
+
const t = bisectorIntersectTime(node, node.next);
|
|
861
|
+
if (t !== null && t > EPS) {
|
|
862
|
+
const x = node.x + t * node.bx;
|
|
863
|
+
const y = node.y + t * node.by;
|
|
864
|
+
events.push({ time: t, x, y, nodeA: node, nodeB: node.next, type: "edge" });
|
|
865
|
+
}
|
|
866
|
+
if (isLavNodeReflex(node)) {
|
|
867
|
+
let cur = node.next.next;
|
|
868
|
+
let count = 0;
|
|
869
|
+
while (cur !== node.prev && cur !== node && count < 1e3) {
|
|
870
|
+
const st = raySplitTime(node, cur, cur.next);
|
|
871
|
+
if (st !== null && st > EPS) {
|
|
872
|
+
const x = node.x + st * node.bx;
|
|
873
|
+
const y = node.y + st * node.by;
|
|
874
|
+
events.push({ time: st, x, y, nodeA: node, nodeB: cur, type: "split" });
|
|
875
|
+
}
|
|
876
|
+
cur = cur.next;
|
|
877
|
+
count++;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
events.sort((a, b) => a.time - b.time);
|
|
882
|
+
return events;
|
|
883
|
+
}
|
|
884
|
+
function computeStraightSkeleton(polygon2) {
|
|
885
|
+
if (polygon2.length < 3) {
|
|
886
|
+
return { nodes: [], faces: [] };
|
|
887
|
+
}
|
|
888
|
+
const poly = ensureCCW(polygon2);
|
|
889
|
+
const n = poly.length;
|
|
890
|
+
const skeletonNodes = [];
|
|
891
|
+
const vertexToSkelNodes = Array.from({ length: n }, () => []);
|
|
892
|
+
const lavNodes = createLav(poly);
|
|
893
|
+
let iterations = 0;
|
|
894
|
+
const maxIter = n * n * 2;
|
|
895
|
+
while (iterations < maxIter) {
|
|
896
|
+
iterations++;
|
|
897
|
+
const activeStart = lavNodes.find((nd) => nd.active);
|
|
898
|
+
if (!activeStart) break;
|
|
899
|
+
const sz = lavSize(activeStart);
|
|
900
|
+
if (sz <= 3) {
|
|
901
|
+
if (sz === 3) {
|
|
902
|
+
const a = activeStart;
|
|
903
|
+
const b = a.next;
|
|
904
|
+
const c = b.next;
|
|
905
|
+
const t = bisectorIntersectTime(a, b);
|
|
906
|
+
const time = t !== null && t > EPS ? t : 0;
|
|
907
|
+
const mx = (a.x + b.x + c.x) / 3 + time * (a.bx + b.bx + c.bx) / 3;
|
|
908
|
+
const my = (a.y + b.y + c.y) / 3 + time * (a.by + b.by + c.by) / 3;
|
|
909
|
+
const nodeIdx = skeletonNodes.length;
|
|
910
|
+
skeletonNodes.push({ x: mx, y: my, height: time });
|
|
911
|
+
const aNodes = vertexToSkelNodes[a.origIdx];
|
|
912
|
+
const bNodes = vertexToSkelNodes[b.origIdx];
|
|
913
|
+
const cNodes = vertexToSkelNodes[c.origIdx];
|
|
914
|
+
if (aNodes) aNodes.push(nodeIdx);
|
|
915
|
+
if (bNodes) bNodes.push(nodeIdx);
|
|
916
|
+
if (cNodes) cNodes.push(nodeIdx);
|
|
917
|
+
a.active = false;
|
|
918
|
+
b.active = false;
|
|
919
|
+
c.active = false;
|
|
920
|
+
} else {
|
|
921
|
+
let cur = activeStart;
|
|
922
|
+
for (let i = 0; i < sz; i++) {
|
|
923
|
+
cur.active = false;
|
|
924
|
+
cur = cur.next;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
const activeNodes = lavNodes.filter((nd) => nd.active);
|
|
930
|
+
const events = computeEvents(activeNodes);
|
|
931
|
+
if (events.length === 0) {
|
|
932
|
+
for (const nd of activeNodes) {
|
|
933
|
+
nd.active = false;
|
|
934
|
+
}
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
const ev = events[0];
|
|
938
|
+
if (!ev) break;
|
|
939
|
+
if (ev.type === "edge") {
|
|
940
|
+
const a = ev.nodeA;
|
|
941
|
+
const b = ev.nodeB;
|
|
942
|
+
if (!a.active || !b.active) continue;
|
|
943
|
+
const nodeIdx = skeletonNodes.length;
|
|
944
|
+
skeletonNodes.push({ x: ev.x, y: ev.y, height: ev.time });
|
|
945
|
+
const aNodes = vertexToSkelNodes[a.origIdx];
|
|
946
|
+
const bNodes = vertexToSkelNodes[b.origIdx];
|
|
947
|
+
if (aNodes) aNodes.push(nodeIdx);
|
|
948
|
+
if (bNodes) bNodes.push(nodeIdx);
|
|
949
|
+
a.x = ev.x;
|
|
950
|
+
a.y = ev.y;
|
|
951
|
+
a.next = b.next;
|
|
952
|
+
b.next.prev = a;
|
|
953
|
+
b.active = false;
|
|
954
|
+
const lavPoly = [];
|
|
955
|
+
let cur = a;
|
|
956
|
+
do {
|
|
957
|
+
lavPoly.push({ x: cur.x, y: cur.y });
|
|
958
|
+
cur = cur.next;
|
|
959
|
+
} while (cur !== a);
|
|
960
|
+
const bDir = bisector(lavPoly, 0);
|
|
961
|
+
a.bx = bDir.dx;
|
|
962
|
+
a.by = bDir.dy;
|
|
963
|
+
} else {
|
|
964
|
+
const a = ev.nodeA;
|
|
965
|
+
const b = ev.nodeB;
|
|
966
|
+
if (!a.active || !b.active) continue;
|
|
967
|
+
const nodeIdx = skeletonNodes.length;
|
|
968
|
+
skeletonNodes.push({ x: ev.x, y: ev.y, height: ev.time });
|
|
969
|
+
const aNodes = vertexToSkelNodes[a.origIdx];
|
|
970
|
+
if (aNodes) aNodes.push(nodeIdx);
|
|
971
|
+
const bNodes = vertexToSkelNodes[b.origIdx];
|
|
972
|
+
if (bNodes) bNodes.push(nodeIdx);
|
|
973
|
+
const aCopy = {
|
|
974
|
+
x: ev.x,
|
|
975
|
+
y: ev.y,
|
|
976
|
+
bx: 0,
|
|
977
|
+
by: 0,
|
|
978
|
+
origIdx: a.origIdx,
|
|
979
|
+
prev: null,
|
|
980
|
+
next: null,
|
|
981
|
+
active: true
|
|
982
|
+
};
|
|
983
|
+
lavNodes.push(aCopy);
|
|
984
|
+
a.x = ev.x;
|
|
985
|
+
a.y = ev.y;
|
|
986
|
+
const aNext = a.next;
|
|
987
|
+
const bNext = b.next;
|
|
988
|
+
a.next = bNext;
|
|
989
|
+
bNext.prev = a;
|
|
990
|
+
aCopy.next = aNext;
|
|
991
|
+
aNext.prev = aCopy;
|
|
992
|
+
aCopy.prev = b;
|
|
993
|
+
b.next = aCopy;
|
|
994
|
+
const buildLavPoly = (start) => {
|
|
995
|
+
const poly2 = [];
|
|
996
|
+
let c = start;
|
|
997
|
+
do {
|
|
998
|
+
poly2.push({ x: c.x, y: c.y });
|
|
999
|
+
c = c.next;
|
|
1000
|
+
} while (c !== start);
|
|
1001
|
+
return poly2;
|
|
1002
|
+
};
|
|
1003
|
+
const lav1Poly = buildLavPoly(a);
|
|
1004
|
+
const bDir1 = bisector(lav1Poly, 0);
|
|
1005
|
+
a.bx = bDir1.dx;
|
|
1006
|
+
a.by = bDir1.dy;
|
|
1007
|
+
const lav2Poly = buildLavPoly(aCopy);
|
|
1008
|
+
const bDir2 = bisector(lav2Poly, 0);
|
|
1009
|
+
aCopy.bx = bDir2.dx;
|
|
1010
|
+
aCopy.by = bDir2.dy;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
const faces = [];
|
|
1014
|
+
for (let i = 0; i < n; i++) {
|
|
1015
|
+
const j = (i + 1) % n;
|
|
1016
|
+
const pi = polyAt(poly, i);
|
|
1017
|
+
const pj = polyAt(poly, j);
|
|
1018
|
+
const faceVerts = [pi, pj];
|
|
1019
|
+
const faceHeights = [0, 0];
|
|
1020
|
+
const jNodes = vertexToSkelNodes[j];
|
|
1021
|
+
const iNodes = vertexToSkelNodes[i];
|
|
1022
|
+
if (jNodes) {
|
|
1023
|
+
for (const ni of jNodes) {
|
|
1024
|
+
const sn = skeletonNodes[ni];
|
|
1025
|
+
if (sn) {
|
|
1026
|
+
faceVerts.push({ x: sn.x, y: sn.y });
|
|
1027
|
+
faceHeights.push(sn.height);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
if (iNodes) {
|
|
1032
|
+
for (let k = iNodes.length - 1; k >= 0; k--) {
|
|
1033
|
+
const idx = iNodes[k];
|
|
1034
|
+
if (idx === void 0) continue;
|
|
1035
|
+
const sn = skeletonNodes[idx];
|
|
1036
|
+
if (!sn) continue;
|
|
1037
|
+
const lastVert = faceVerts[faceVerts.length - 1];
|
|
1038
|
+
if (!lastVert) continue;
|
|
1039
|
+
const dist = len2(sn.x - lastVert.x, sn.y - lastVert.y);
|
|
1040
|
+
if (dist > EPS) {
|
|
1041
|
+
faceVerts.push({ x: sn.x, y: sn.y });
|
|
1042
|
+
faceHeights.push(sn.height);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
if (faceVerts.length >= 3) {
|
|
1047
|
+
faces.push({ vertices: faceVerts, heights: faceHeights });
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
const uniqueNodes = [];
|
|
1051
|
+
for (const sn of skeletonNodes) {
|
|
1052
|
+
const exists = uniqueNodes.some(
|
|
1053
|
+
(un) => Math.abs(un.x - sn.x) < 0.01 && Math.abs(un.y - sn.y) < 0.01
|
|
1054
|
+
);
|
|
1055
|
+
if (!exists) {
|
|
1056
|
+
uniqueNodes.push(sn);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return { nodes: uniqueNodes, faces };
|
|
1060
|
+
}
|
|
1061
|
+
function extractPolygon(w) {
|
|
1062
|
+
const edges = shapeFns.getEdges(w);
|
|
1063
|
+
const pts = edges.map((e) => {
|
|
1064
|
+
const pt = curveFns.curveStartPoint(e);
|
|
1065
|
+
return { x: pt[0], y: pt[1] };
|
|
1066
|
+
});
|
|
1067
|
+
const first = pts[0];
|
|
1068
|
+
const last = pts[pts.length - 1];
|
|
1069
|
+
if (pts.length > 1 && first && last && Math.abs(first.x - last.x) < 1e-10 && Math.abs(first.y - last.y) < 1e-10) {
|
|
1070
|
+
pts.pop();
|
|
1071
|
+
}
|
|
1072
|
+
return pts;
|
|
1073
|
+
}
|
|
1074
|
+
function fanTriangulate(count) {
|
|
1075
|
+
const tris = [];
|
|
1076
|
+
for (let i = 1; i < count - 1; i++) {
|
|
1077
|
+
tris.push([0, i, i + 1]);
|
|
1078
|
+
}
|
|
1079
|
+
return tris;
|
|
1080
|
+
}
|
|
1081
|
+
function buildTriFace$2(oc, a, b, c) {
|
|
1082
|
+
const gpA = new oc.gp_Pnt_3(a[0], a[1], a[2]);
|
|
1083
|
+
const gpB = new oc.gp_Pnt_3(b[0], b[1], b[2]);
|
|
1084
|
+
const gpC = new oc.gp_Pnt_3(c[0], c[1], c[2]);
|
|
1085
|
+
const e1 = new oc.BRepBuilderAPI_MakeEdge_3(gpA, gpB);
|
|
1086
|
+
const e2 = new oc.BRepBuilderAPI_MakeEdge_3(gpB, gpC);
|
|
1087
|
+
const e3 = new oc.BRepBuilderAPI_MakeEdge_3(gpC, gpA);
|
|
1088
|
+
const wireBuilder = new oc.BRepBuilderAPI_MakeWire_1();
|
|
1089
|
+
wireBuilder.Add_1(e1.Edge());
|
|
1090
|
+
wireBuilder.Add_1(e2.Edge());
|
|
1091
|
+
wireBuilder.Add_1(e3.Edge());
|
|
1092
|
+
let face2 = null;
|
|
1093
|
+
if (wireBuilder.IsDone()) {
|
|
1094
|
+
const makeFace = new oc.BRepBuilderAPI_MakeFace_15(wireBuilder.Wire(), false);
|
|
1095
|
+
if (makeFace.IsDone()) {
|
|
1096
|
+
face2 = makeFace.Face();
|
|
1097
|
+
}
|
|
1098
|
+
makeFace.delete();
|
|
1099
|
+
}
|
|
1100
|
+
wireBuilder.delete();
|
|
1101
|
+
e1.delete();
|
|
1102
|
+
e2.delete();
|
|
1103
|
+
e3.delete();
|
|
1104
|
+
gpA.delete();
|
|
1105
|
+
gpB.delete();
|
|
1106
|
+
gpC.delete();
|
|
1107
|
+
return face2;
|
|
1108
|
+
}
|
|
1109
|
+
function roof(w, options) {
|
|
1110
|
+
const angle = (options?.angle ?? 45) * (Math.PI / 180);
|
|
1111
|
+
const tanAngle = Math.tan(angle);
|
|
1112
|
+
try {
|
|
1113
|
+
const polygon2 = extractPolygon(w);
|
|
1114
|
+
if (polygon2.length < 3) {
|
|
1115
|
+
return errors.err(
|
|
1116
|
+
errors.occtError(errors.BrepErrorCode.ROOF_FAILED, "Wire must have at least 3 edges for roof generation")
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
const skeleton = computeStraightSkeleton(polygon2);
|
|
1120
|
+
if (skeleton.faces.length === 0) {
|
|
1121
|
+
return errors.err(
|
|
1122
|
+
errors.occtError(errors.BrepErrorCode.ROOF_FAILED, "Straight skeleton computation produced no faces")
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
const oc = occtBoundary.getKernel().oc;
|
|
1126
|
+
const sewing = new oc.BRepBuilderAPI_Sewing(1e-6, true, true, true, false);
|
|
1127
|
+
let faceCount = 0;
|
|
1128
|
+
try {
|
|
1129
|
+
for (const skFace of skeleton.faces) {
|
|
1130
|
+
const verts3d = skFace.vertices.map(
|
|
1131
|
+
(v, i) => [
|
|
1132
|
+
v.x,
|
|
1133
|
+
v.y,
|
|
1134
|
+
(skFace.heights[i] ?? 0) * tanAngle
|
|
1135
|
+
]
|
|
1136
|
+
);
|
|
1137
|
+
const tris = fanTriangulate(verts3d.length);
|
|
1138
|
+
for (const [ai, bi, ci] of tris) {
|
|
1139
|
+
const va = verts3d[ai];
|
|
1140
|
+
const vb = verts3d[bi];
|
|
1141
|
+
const vc = verts3d[ci];
|
|
1142
|
+
if (!va || !vb || !vc) continue;
|
|
1143
|
+
const abx = vb[0] - va[0];
|
|
1144
|
+
const aby = vb[1] - va[1];
|
|
1145
|
+
const abz = vb[2] - va[2];
|
|
1146
|
+
const acx = vc[0] - va[0];
|
|
1147
|
+
const acy = vc[1] - va[1];
|
|
1148
|
+
const acz = vc[2] - va[2];
|
|
1149
|
+
const nx = aby * acz - abz * acy;
|
|
1150
|
+
const ny = abz * acx - abx * acz;
|
|
1151
|
+
const nz = abx * acy - aby * acx;
|
|
1152
|
+
const areaSq = nx * nx + ny * ny + nz * nz;
|
|
1153
|
+
if (areaSq < 1e-20) continue;
|
|
1154
|
+
const triFace = buildTriFace$2(oc, va, vb, vc);
|
|
1155
|
+
if (triFace !== null) {
|
|
1156
|
+
sewing.Add(triFace);
|
|
1157
|
+
faceCount++;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
const p0 = polygon2[0];
|
|
1162
|
+
if (p0) {
|
|
1163
|
+
for (let i = 1; i < polygon2.length - 1; i++) {
|
|
1164
|
+
const pi = polygon2[i];
|
|
1165
|
+
const pi1 = polygon2[i + 1];
|
|
1166
|
+
if (!pi || !pi1) continue;
|
|
1167
|
+
const va = [p0.x, p0.y, 0];
|
|
1168
|
+
const vb = [pi.x, pi.y, 0];
|
|
1169
|
+
const vc = [pi1.x, pi1.y, 0];
|
|
1170
|
+
const triFace = buildTriFace$2(oc, va, vc, vb);
|
|
1171
|
+
if (triFace !== null) {
|
|
1172
|
+
sewing.Add(triFace);
|
|
1173
|
+
faceCount++;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
if (faceCount === 0) {
|
|
1178
|
+
return errors.err(
|
|
1179
|
+
errors.occtError(errors.BrepErrorCode.ROOF_FAILED, "No valid triangular faces could be built")
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
const progress = new oc.Message_ProgressRange_1();
|
|
1183
|
+
sewing.Perform(progress);
|
|
1184
|
+
progress.delete();
|
|
1185
|
+
const sewn = sewing.SewedShape();
|
|
1186
|
+
const fixer = new oc.ShapeFix_Solid_1();
|
|
1187
|
+
try {
|
|
1188
|
+
const shell2 = oc.TopoDS.Shell_1(sewn);
|
|
1189
|
+
const solid2 = fixer.SolidFromShell(shell2);
|
|
1190
|
+
const shapeFixer = new oc.ShapeFix_Shape_1(solid2);
|
|
1191
|
+
const shapeFixProgress = new oc.Message_ProgressRange_1();
|
|
1192
|
+
try {
|
|
1193
|
+
shapeFixer.Perform(shapeFixProgress);
|
|
1194
|
+
const fixed = shapeFixer.Shape();
|
|
1195
|
+
return errors.ok(shapeTypes.createSolid(fixed));
|
|
1196
|
+
} finally {
|
|
1197
|
+
shapeFixProgress.delete();
|
|
1198
|
+
shapeFixer.delete();
|
|
1199
|
+
}
|
|
1200
|
+
} catch {
|
|
1201
|
+
return errors.ok(shapeTypes.castShape(sewn));
|
|
1202
|
+
} finally {
|
|
1203
|
+
fixer.delete();
|
|
1204
|
+
}
|
|
1205
|
+
} finally {
|
|
1206
|
+
sewing.delete();
|
|
1207
|
+
}
|
|
1208
|
+
} catch (e) {
|
|
1209
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1210
|
+
return errors.err(errors.occtError(errors.BrepErrorCode.ROOF_FAILED, `Roof generation failed: ${msg}`, e));
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
function solveConstraints(nodes, constraints) {
|
|
1214
|
+
const transforms = /* @__PURE__ */ new Map();
|
|
1215
|
+
for (const node of nodes) {
|
|
1216
|
+
transforms.set(node, {
|
|
1217
|
+
position: [0, 0, 0],
|
|
1218
|
+
rotation: [1, 0, 0, 0]
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
for (const c of constraints) {
|
|
1222
|
+
if (c.type === "coincident" && c.entityA && c.entityB) {
|
|
1223
|
+
const a = c.entityA;
|
|
1224
|
+
const b = c.entityB;
|
|
1225
|
+
if (a.entity.type === "plane" && b.entity.type === "plane") {
|
|
1226
|
+
const aNormal = a.entity.normal ?? [0, 0, 1];
|
|
1227
|
+
const aOrigin = a.entity.origin;
|
|
1228
|
+
const bOrigin = b.entity.origin;
|
|
1229
|
+
const dot = aNormal[0] * (aOrigin[0] - bOrigin[0]) + aNormal[1] * (aOrigin[1] - bOrigin[1]) + aNormal[2] * (aOrigin[2] - bOrigin[2]);
|
|
1230
|
+
const pos = [dot * aNormal[0], dot * aNormal[1], dot * aNormal[2]];
|
|
1231
|
+
transforms.set(b.node, { position: pos, rotation: [1, 0, 0, 0] });
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
if (c.type === "distance" && c.entityA && c.entityB && c.value !== void 0) {
|
|
1235
|
+
const a = c.entityA;
|
|
1236
|
+
const b = c.entityB;
|
|
1237
|
+
if (a.entity.type === "plane" && b.entity.type === "plane") {
|
|
1238
|
+
const aNormal = a.entity.normal ?? [0, 0, 1];
|
|
1239
|
+
const aOrigin = a.entity.origin;
|
|
1240
|
+
const bOrigin = b.entity.origin;
|
|
1241
|
+
const currentDist = aNormal[0] * (aOrigin[0] - bOrigin[0]) + aNormal[1] * (aOrigin[1] - bOrigin[1]) + aNormal[2] * (aOrigin[2] - bOrigin[2]);
|
|
1242
|
+
const offset2 = currentDist + c.value;
|
|
1243
|
+
const pos = [offset2 * aNormal[0], offset2 * aNormal[1], offset2 * aNormal[2]];
|
|
1244
|
+
transforms.set(b.node, { position: pos, rotation: [1, 0, 0, 0] });
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
return { transforms, dof: 0, converged: true };
|
|
1249
|
+
}
|
|
1250
|
+
function extractEntity(mate) {
|
|
1251
|
+
if (mate.face) {
|
|
1252
|
+
const origin = faceFns.faceCenter(mate.face);
|
|
1253
|
+
const normal = faceFns.normalAt(mate.face);
|
|
1254
|
+
return { type: "plane", origin, normal };
|
|
1255
|
+
}
|
|
1256
|
+
if (mate.point) {
|
|
1257
|
+
return { type: "point", origin: mate.point };
|
|
1258
|
+
}
|
|
1259
|
+
return null;
|
|
1260
|
+
}
|
|
1261
|
+
function addMate(assembly, constraint) {
|
|
1262
|
+
const existing = assembly.mates ?? [];
|
|
1263
|
+
return { ...assembly, mates: [...existing, constraint] };
|
|
1264
|
+
}
|
|
1265
|
+
function solveAssembly(assembly) {
|
|
1266
|
+
const mates = assembly.mates;
|
|
1267
|
+
if (!mates || mates.length === 0) {
|
|
1268
|
+
return errors.err(
|
|
1269
|
+
errors.validationError(errors.BrepErrorCode.ASSEMBLY_MATE_INVALID, "solveAssembly: no mates defined")
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
try {
|
|
1273
|
+
const nodes = [];
|
|
1274
|
+
operations.walkAssembly(assembly, (node) => {
|
|
1275
|
+
nodes.push(node.name);
|
|
1276
|
+
});
|
|
1277
|
+
const solverConstraints = [];
|
|
1278
|
+
for (const mate of mates) {
|
|
1279
|
+
if (mate.type === "fixed") {
|
|
1280
|
+
solverConstraints.push({
|
|
1281
|
+
type: "fixed",
|
|
1282
|
+
entityA: { node: mate.entity.node, entity: { type: "point", origin: [0, 0, 0] } }
|
|
1283
|
+
});
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
if (mate.type === "coincident") {
|
|
1287
|
+
const entA = extractEntity(mate.entityA);
|
|
1288
|
+
const entB = extractEntity(mate.entityB);
|
|
1289
|
+
if (!entA || !entB) {
|
|
1290
|
+
return errors.err(
|
|
1291
|
+
errors.validationError(
|
|
1292
|
+
errors.BrepErrorCode.ASSEMBLY_MATE_INVALID,
|
|
1293
|
+
"solveAssembly: could not extract geometry from mate entities"
|
|
1294
|
+
)
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
solverConstraints.push({
|
|
1298
|
+
type: "coincident",
|
|
1299
|
+
entityA: { node: mate.entityA.node, entity: entA },
|
|
1300
|
+
entityB: { node: mate.entityB.node, entity: entB }
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
if (mate.type === "distance") {
|
|
1304
|
+
const entA = extractEntity(mate.entityA);
|
|
1305
|
+
const entB = extractEntity(mate.entityB);
|
|
1306
|
+
if (!entA || !entB) {
|
|
1307
|
+
return errors.err(
|
|
1308
|
+
errors.validationError(
|
|
1309
|
+
errors.BrepErrorCode.ASSEMBLY_MATE_INVALID,
|
|
1310
|
+
"solveAssembly: could not extract geometry from mate entities"
|
|
1311
|
+
)
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
solverConstraints.push({
|
|
1315
|
+
type: "distance",
|
|
1316
|
+
entityA: { node: mate.entityA.node, entity: entA },
|
|
1317
|
+
entityB: { node: mate.entityB.node, entity: entB },
|
|
1318
|
+
value: mate.distance
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
if (mate.type === "angle") {
|
|
1322
|
+
const entA = extractEntity(mate.entityA);
|
|
1323
|
+
const entB = extractEntity(mate.entityB);
|
|
1324
|
+
if (!entA || !entB) {
|
|
1325
|
+
return errors.err(
|
|
1326
|
+
errors.validationError(
|
|
1327
|
+
errors.BrepErrorCode.ASSEMBLY_MATE_INVALID,
|
|
1328
|
+
"solveAssembly: could not extract geometry from mate entities"
|
|
1329
|
+
)
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
solverConstraints.push({
|
|
1333
|
+
type: "angle",
|
|
1334
|
+
entityA: { node: mate.entityA.node, entity: entA },
|
|
1335
|
+
entityB: { node: mate.entityB.node, entity: entB },
|
|
1336
|
+
value: mate.angle
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
if (mate.type === "concentric") {
|
|
1340
|
+
const entA = extractEntity(mate.axisA);
|
|
1341
|
+
const entB = extractEntity(mate.axisB);
|
|
1342
|
+
if (!entA || !entB) {
|
|
1343
|
+
return errors.err(
|
|
1344
|
+
errors.validationError(
|
|
1345
|
+
errors.BrepErrorCode.ASSEMBLY_MATE_INVALID,
|
|
1346
|
+
"solveAssembly: could not extract geometry from mate entities"
|
|
1347
|
+
)
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
solverConstraints.push({
|
|
1351
|
+
type: "concentric",
|
|
1352
|
+
entityA: { node: mate.axisA.node, entity: entA },
|
|
1353
|
+
entityB: { node: mate.axisB.node, entity: entB }
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
const result2 = solveConstraints(nodes, solverConstraints);
|
|
1358
|
+
if (!result2.converged) ;
|
|
1359
|
+
return errors.ok({
|
|
1360
|
+
transforms: result2.transforms,
|
|
1361
|
+
dof: result2.dof,
|
|
1362
|
+
converged: result2.converged
|
|
1363
|
+
});
|
|
1364
|
+
} catch (e) {
|
|
1365
|
+
const raw = e instanceof Error ? e.message : String(e);
|
|
1366
|
+
return errors.err(errors.occtError(errors.BrepErrorCode.ASSEMBLY_SOLVE_FAILED, `Assembly solve failed: ${raw}`, e));
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
148
1369
|
function checkInterference(shape1, shape2, tolerance = 1e-6) {
|
|
149
1370
|
if (shape1.wrapped.IsNull()) {
|
|
150
1371
|
return errors.err(
|
|
@@ -182,6 +1403,458 @@ function checkAllInterferences(shapes, tolerance = 1e-6) {
|
|
|
182
1403
|
});
|
|
183
1404
|
return pairs;
|
|
184
1405
|
}
|
|
1406
|
+
function parseEntities(text) {
|
|
1407
|
+
const lines = text.split(/\r?\n/);
|
|
1408
|
+
const entities = [];
|
|
1409
|
+
let inEntities = false;
|
|
1410
|
+
let current;
|
|
1411
|
+
for (let i = 0; i < lines.length - 1; i += 2) {
|
|
1412
|
+
const codeLine = lines[i];
|
|
1413
|
+
const valueLine = lines[i + 1];
|
|
1414
|
+
if (codeLine === void 0 || valueLine === void 0) continue;
|
|
1415
|
+
const code = parseInt(codeLine.trim(), 10);
|
|
1416
|
+
const value = valueLine.trim();
|
|
1417
|
+
if (isNaN(code)) continue;
|
|
1418
|
+
if (code === 2 && value === "ENTITIES") {
|
|
1419
|
+
inEntities = true;
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
if (!inEntities) continue;
|
|
1423
|
+
if (code === 0) {
|
|
1424
|
+
if (value === "ENDSEC" || value === "EOF") {
|
|
1425
|
+
if (current) entities.push(current);
|
|
1426
|
+
break;
|
|
1427
|
+
}
|
|
1428
|
+
if (current) entities.push(current);
|
|
1429
|
+
current = { type: value, layer: "0", data: /* @__PURE__ */ new Map() };
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
if (current) {
|
|
1433
|
+
if (code === 8) {
|
|
1434
|
+
current.layer = value;
|
|
1435
|
+
} else {
|
|
1436
|
+
current.data.set(code, value);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return entities;
|
|
1441
|
+
}
|
|
1442
|
+
function getNum(data, code, fallback = 0) {
|
|
1443
|
+
const v = data.get(code);
|
|
1444
|
+
if (v === void 0) return fallback;
|
|
1445
|
+
const n = parseFloat(v);
|
|
1446
|
+
return isNaN(n) ? fallback : n;
|
|
1447
|
+
}
|
|
1448
|
+
function entityToEdge(entity, oc) {
|
|
1449
|
+
const { type, data } = entity;
|
|
1450
|
+
if (type === "LINE") {
|
|
1451
|
+
const p1 = new oc.gp_Pnt_3(getNum(data, 10), getNum(data, 20), getNum(data, 30));
|
|
1452
|
+
const p2 = new oc.gp_Pnt_3(getNum(data, 11), getNum(data, 21), getNum(data, 31));
|
|
1453
|
+
try {
|
|
1454
|
+
const builder = new oc.BRepBuilderAPI_MakeEdge_3(p1, p2);
|
|
1455
|
+
const edge = builder.Edge();
|
|
1456
|
+
builder.delete();
|
|
1457
|
+
return edge;
|
|
1458
|
+
} finally {
|
|
1459
|
+
p1.delete();
|
|
1460
|
+
p2.delete();
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
if (type === "CIRCLE") {
|
|
1464
|
+
const cx = getNum(data, 10);
|
|
1465
|
+
const cy = getNum(data, 20);
|
|
1466
|
+
const cz = getNum(data, 30);
|
|
1467
|
+
const radius = getNum(data, 40);
|
|
1468
|
+
const center = new oc.gp_Pnt_3(cx, cy, cz);
|
|
1469
|
+
const dir = new oc.gp_Dir_4(0, 0, 1);
|
|
1470
|
+
const ax2 = new oc.gp_Ax2_3(center, dir);
|
|
1471
|
+
const circ = new oc.gp_Circ_2(ax2, radius);
|
|
1472
|
+
try {
|
|
1473
|
+
const builder = new oc.BRepBuilderAPI_MakeEdge_8(circ);
|
|
1474
|
+
const edge = builder.Edge();
|
|
1475
|
+
builder.delete();
|
|
1476
|
+
return edge;
|
|
1477
|
+
} finally {
|
|
1478
|
+
center.delete();
|
|
1479
|
+
dir.delete();
|
|
1480
|
+
ax2.delete();
|
|
1481
|
+
circ.delete();
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
if (type === "ARC") {
|
|
1485
|
+
const cx = getNum(data, 10);
|
|
1486
|
+
const cy = getNum(data, 20);
|
|
1487
|
+
const cz = getNum(data, 30);
|
|
1488
|
+
const radius = getNum(data, 40);
|
|
1489
|
+
const startAngleDeg = getNum(data, 50);
|
|
1490
|
+
const endAngleDeg = getNum(data, 51);
|
|
1491
|
+
const startAngle = startAngleDeg * Math.PI / 180;
|
|
1492
|
+
const endAngle = endAngleDeg * Math.PI / 180;
|
|
1493
|
+
const center = new oc.gp_Pnt_3(cx, cy, cz);
|
|
1494
|
+
const dir = new oc.gp_Dir_4(0, 0, 1);
|
|
1495
|
+
const ax2 = new oc.gp_Ax2_3(center, dir);
|
|
1496
|
+
const circ = new oc.gp_Circ_2(ax2, radius);
|
|
1497
|
+
try {
|
|
1498
|
+
const builder = new oc.BRepBuilderAPI_MakeEdge_9(circ, startAngle, endAngle);
|
|
1499
|
+
const edge = builder.Edge();
|
|
1500
|
+
builder.delete();
|
|
1501
|
+
return edge;
|
|
1502
|
+
} finally {
|
|
1503
|
+
center.delete();
|
|
1504
|
+
dir.delete();
|
|
1505
|
+
ax2.delete();
|
|
1506
|
+
circ.delete();
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
return void 0;
|
|
1510
|
+
}
|
|
1511
|
+
async function importDXF(blob, options) {
|
|
1512
|
+
const oc = occtBoundary.getKernel().oc;
|
|
1513
|
+
let text;
|
|
1514
|
+
try {
|
|
1515
|
+
text = await blob.text();
|
|
1516
|
+
} catch (cause) {
|
|
1517
|
+
return errors.err(errors.ioError(errors.BrepErrorCode.DXF_IMPORT_FAILED, "Failed to read DXF blob", cause));
|
|
1518
|
+
}
|
|
1519
|
+
const allEntities = parseEntities(text);
|
|
1520
|
+
const entities = options?.layer !== void 0 ? allEntities.filter((e) => e.layer === options.layer) : allEntities;
|
|
1521
|
+
if (entities.length === 0) {
|
|
1522
|
+
return errors.ok([]);
|
|
1523
|
+
}
|
|
1524
|
+
const edges = [];
|
|
1525
|
+
try {
|
|
1526
|
+
for (const entity of entities) {
|
|
1527
|
+
const edge = entityToEdge(entity, oc);
|
|
1528
|
+
if (edge !== void 0) {
|
|
1529
|
+
edges.push(edge);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
if (edges.length === 0) {
|
|
1533
|
+
return errors.ok([]);
|
|
1534
|
+
}
|
|
1535
|
+
const wireBuilder = new oc.BRepBuilderAPI_MakeWire_1();
|
|
1536
|
+
try {
|
|
1537
|
+
for (const edge of edges) {
|
|
1538
|
+
wireBuilder.Add_1(edge);
|
|
1539
|
+
}
|
|
1540
|
+
if (wireBuilder.IsDone()) {
|
|
1541
|
+
const wire2 = wireBuilder.Wire();
|
|
1542
|
+
return errors.ok([shapeTypes.createWire(wire2)]);
|
|
1543
|
+
}
|
|
1544
|
+
return errors.err(
|
|
1545
|
+
errors.ioError(errors.BrepErrorCode.DXF_IMPORT_FAILED, "Failed to assemble DXF edges into a wire")
|
|
1546
|
+
);
|
|
1547
|
+
} finally {
|
|
1548
|
+
wireBuilder.delete();
|
|
1549
|
+
}
|
|
1550
|
+
} catch (cause) {
|
|
1551
|
+
return errors.err(
|
|
1552
|
+
errors.ioError(errors.BrepErrorCode.DXF_IMPORT_FAILED, "Failed to convert DXF entities to geometry", cause)
|
|
1553
|
+
);
|
|
1554
|
+
} finally {
|
|
1555
|
+
for (const edge of edges) {
|
|
1556
|
+
edge.delete();
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
async function importOBJ(blob) {
|
|
1561
|
+
const text = await blob.text();
|
|
1562
|
+
const lines = text.split("\n");
|
|
1563
|
+
const vertices = [];
|
|
1564
|
+
const faces = [];
|
|
1565
|
+
for (const raw of lines) {
|
|
1566
|
+
const line2 = raw.trim();
|
|
1567
|
+
if (line2.startsWith("v ")) {
|
|
1568
|
+
const parts = line2.split(/\s+/);
|
|
1569
|
+
const x = parseFloat(parts[1] ?? "");
|
|
1570
|
+
const y = parseFloat(parts[2] ?? "");
|
|
1571
|
+
const z = parseFloat(parts[3] ?? "");
|
|
1572
|
+
if (isNaN(x) || isNaN(y) || isNaN(z)) continue;
|
|
1573
|
+
vertices.push([x, y, z]);
|
|
1574
|
+
} else if (line2.startsWith("f ")) {
|
|
1575
|
+
const parts = line2.split(/\s+/).slice(1);
|
|
1576
|
+
const indices = [];
|
|
1577
|
+
for (const p of parts) {
|
|
1578
|
+
const idx = parseInt(p.split("/")[0] ?? "", 10);
|
|
1579
|
+
if (!isNaN(idx)) indices.push(idx);
|
|
1580
|
+
}
|
|
1581
|
+
if (indices.length >= 3) faces.push(indices);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
if (vertices.length === 0 || faces.length === 0) {
|
|
1585
|
+
return errors.err(errors.ioError(errors.BrepErrorCode.OBJ_IMPORT_FAILED, "OBJ file contains no valid geometry"));
|
|
1586
|
+
}
|
|
1587
|
+
try {
|
|
1588
|
+
return buildSolidFromMesh$1(vertices, faces);
|
|
1589
|
+
} catch (e) {
|
|
1590
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1591
|
+
return errors.err(errors.ioError(errors.BrepErrorCode.OBJ_IMPORT_FAILED, `OBJ import failed: ${msg}`, e));
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
function buildSolidFromMesh$1(vertices, faces) {
|
|
1595
|
+
const oc = occtBoundary.getKernel().oc;
|
|
1596
|
+
const sewing = new oc.BRepBuilderAPI_Sewing(1e-6, true, true, true, false);
|
|
1597
|
+
let faceCount = 0;
|
|
1598
|
+
try {
|
|
1599
|
+
for (const face2 of faces) {
|
|
1600
|
+
for (let i = 1; i < face2.length - 1; i++) {
|
|
1601
|
+
const rawA = face2[0] ?? 0;
|
|
1602
|
+
const rawB = face2[i] ?? 0;
|
|
1603
|
+
const rawC = face2[i + 1] ?? 0;
|
|
1604
|
+
const ai = rawA > 0 ? rawA - 1 : vertices.length + rawA;
|
|
1605
|
+
const bi = rawB > 0 ? rawB - 1 : vertices.length + rawB;
|
|
1606
|
+
const ci = rawC > 0 ? rawC - 1 : vertices.length + rawC;
|
|
1607
|
+
const va = vertices[ai];
|
|
1608
|
+
const vb = vertices[bi];
|
|
1609
|
+
const vc = vertices[ci];
|
|
1610
|
+
if (!va || !vb || !vc) continue;
|
|
1611
|
+
const triFace = buildTriFace$1(oc, va, vb, vc);
|
|
1612
|
+
if (triFace !== null) {
|
|
1613
|
+
sewing.Add(triFace);
|
|
1614
|
+
faceCount++;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
if (faceCount === 0) {
|
|
1619
|
+
return errors.err(
|
|
1620
|
+
errors.ioError(errors.BrepErrorCode.OBJ_IMPORT_FAILED, "No valid triangular faces could be built")
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
const progress = new oc.Message_ProgressRange_1();
|
|
1624
|
+
sewing.Perform(progress);
|
|
1625
|
+
progress.delete();
|
|
1626
|
+
const sewn = sewing.SewedShape();
|
|
1627
|
+
const fixer = new oc.ShapeFix_Solid_1();
|
|
1628
|
+
try {
|
|
1629
|
+
const shell2 = oc.TopoDS.Shell_1(sewn);
|
|
1630
|
+
const solid2 = fixer.SolidFromShell(shell2);
|
|
1631
|
+
return errors.ok(shapeTypes.castShape(solid2));
|
|
1632
|
+
} catch {
|
|
1633
|
+
return errors.ok(shapeTypes.castShape(sewn));
|
|
1634
|
+
} finally {
|
|
1635
|
+
fixer.delete();
|
|
1636
|
+
}
|
|
1637
|
+
} finally {
|
|
1638
|
+
sewing.delete();
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
function buildTriFace$1(oc, a, b, c) {
|
|
1642
|
+
const gpA = new oc.gp_Pnt_3(a[0], a[1], a[2]);
|
|
1643
|
+
const gpB = new oc.gp_Pnt_3(b[0], b[1], b[2]);
|
|
1644
|
+
const gpC = new oc.gp_Pnt_3(c[0], c[1], c[2]);
|
|
1645
|
+
const e1 = new oc.BRepBuilderAPI_MakeEdge_3(gpA, gpB);
|
|
1646
|
+
const e2 = new oc.BRepBuilderAPI_MakeEdge_3(gpB, gpC);
|
|
1647
|
+
const e3 = new oc.BRepBuilderAPI_MakeEdge_3(gpC, gpA);
|
|
1648
|
+
const wireBuilder = new oc.BRepBuilderAPI_MakeWire_1();
|
|
1649
|
+
wireBuilder.Add_1(e1.Edge());
|
|
1650
|
+
wireBuilder.Add_1(e2.Edge());
|
|
1651
|
+
wireBuilder.Add_1(e3.Edge());
|
|
1652
|
+
let face2 = null;
|
|
1653
|
+
if (wireBuilder.IsDone()) {
|
|
1654
|
+
const makeFace = new oc.BRepBuilderAPI_MakeFace_15(wireBuilder.Wire(), false);
|
|
1655
|
+
if (makeFace.IsDone()) {
|
|
1656
|
+
face2 = makeFace.Face();
|
|
1657
|
+
}
|
|
1658
|
+
makeFace.delete();
|
|
1659
|
+
}
|
|
1660
|
+
wireBuilder.delete();
|
|
1661
|
+
e1.delete();
|
|
1662
|
+
e2.delete();
|
|
1663
|
+
e3.delete();
|
|
1664
|
+
gpA.delete();
|
|
1665
|
+
gpB.delete();
|
|
1666
|
+
gpC.delete();
|
|
1667
|
+
return face2;
|
|
1668
|
+
}
|
|
1669
|
+
function extractFromZip(data, target) {
|
|
1670
|
+
let eocdOffset = -1;
|
|
1671
|
+
for (let i = data.length - 22; i >= 0; i--) {
|
|
1672
|
+
if (data[i] === 80 && data[i + 1] === 75 && data[i + 2] === 5 && data[i + 3] === 6) {
|
|
1673
|
+
eocdOffset = i;
|
|
1674
|
+
break;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
if (eocdOffset < 0) return null;
|
|
1678
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
1679
|
+
const cdOffset = view.getUint32(eocdOffset + 16, true);
|
|
1680
|
+
const cdSize = view.getUint32(eocdOffset + 12, true);
|
|
1681
|
+
const cdEnd = cdOffset + cdSize;
|
|
1682
|
+
let pos = cdOffset;
|
|
1683
|
+
const decoder = new TextDecoder();
|
|
1684
|
+
while (pos < cdEnd) {
|
|
1685
|
+
const sig = view.getUint32(pos, true);
|
|
1686
|
+
if (sig !== 33639248) break;
|
|
1687
|
+
const nameLen = view.getUint16(pos + 28, true);
|
|
1688
|
+
const extraLen = view.getUint16(pos + 30, true);
|
|
1689
|
+
const commentLen = view.getUint16(pos + 32, true);
|
|
1690
|
+
const localOffset = view.getUint32(pos + 42, true);
|
|
1691
|
+
const name = decoder.decode(data.subarray(pos + 46, pos + 46 + nameLen));
|
|
1692
|
+
if (name === target) {
|
|
1693
|
+
const compressionMethod = view.getUint16(localOffset + 8, true);
|
|
1694
|
+
if (compressionMethod !== 0) {
|
|
1695
|
+
return null;
|
|
1696
|
+
}
|
|
1697
|
+
const localNameLen = view.getUint16(localOffset + 26, true);
|
|
1698
|
+
const localExtraLen = view.getUint16(localOffset + 28, true);
|
|
1699
|
+
const compressedSize = view.getUint32(localOffset + 18, true);
|
|
1700
|
+
const dataStart = localOffset + 30 + localNameLen + localExtraLen;
|
|
1701
|
+
return data.subarray(dataStart, dataStart + compressedSize);
|
|
1702
|
+
}
|
|
1703
|
+
pos += 46 + nameLen + extraLen + commentLen;
|
|
1704
|
+
}
|
|
1705
|
+
return null;
|
|
1706
|
+
}
|
|
1707
|
+
function isAttrChar(code) {
|
|
1708
|
+
return code >= 97 && code <= 122 || // a-z
|
|
1709
|
+
code >= 65 && code <= 90 || // A-Z
|
|
1710
|
+
code >= 48 && code <= 57 || // 0-9
|
|
1711
|
+
code === 95;
|
|
1712
|
+
}
|
|
1713
|
+
function parseTagAttrs(tag) {
|
|
1714
|
+
const attrs = {};
|
|
1715
|
+
let pos = 0;
|
|
1716
|
+
while (pos < tag.length) {
|
|
1717
|
+
const eq = tag.indexOf('="', pos);
|
|
1718
|
+
if (eq < 0) break;
|
|
1719
|
+
let nameStart = eq;
|
|
1720
|
+
while (nameStart > 0 && isAttrChar(tag.charCodeAt(nameStart - 1))) nameStart--;
|
|
1721
|
+
if (nameStart === eq) {
|
|
1722
|
+
pos = eq + 2;
|
|
1723
|
+
continue;
|
|
1724
|
+
}
|
|
1725
|
+
const name = tag.slice(nameStart, eq);
|
|
1726
|
+
const valStart = eq + 2;
|
|
1727
|
+
const closeQuote = tag.indexOf('"', valStart);
|
|
1728
|
+
if (closeQuote < 0) break;
|
|
1729
|
+
attrs[name] = tag.slice(valStart, closeQuote);
|
|
1730
|
+
pos = closeQuote + 1;
|
|
1731
|
+
}
|
|
1732
|
+
return attrs;
|
|
1733
|
+
}
|
|
1734
|
+
function findTags(xml, tagName) {
|
|
1735
|
+
const tags = [];
|
|
1736
|
+
const needle = `<${tagName} `;
|
|
1737
|
+
let pos = 0;
|
|
1738
|
+
while (pos < xml.length) {
|
|
1739
|
+
const start = xml.indexOf(needle, pos);
|
|
1740
|
+
if (start < 0) break;
|
|
1741
|
+
const end = xml.indexOf(">", start);
|
|
1742
|
+
if (end < 0) break;
|
|
1743
|
+
tags.push(xml.slice(start, end + 1));
|
|
1744
|
+
pos = end + 1;
|
|
1745
|
+
}
|
|
1746
|
+
return tags;
|
|
1747
|
+
}
|
|
1748
|
+
function parseModelXml(xml) {
|
|
1749
|
+
const vertices = [];
|
|
1750
|
+
const triangles = [];
|
|
1751
|
+
for (const tag of findTags(xml, "vertex")) {
|
|
1752
|
+
const a = parseTagAttrs(tag);
|
|
1753
|
+
if (a["x"] !== void 0 && a["y"] !== void 0 && a["z"] !== void 0) {
|
|
1754
|
+
vertices.push([parseFloat(a["x"]), parseFloat(a["y"]), parseFloat(a["z"])]);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
for (const tag of findTags(xml, "triangle")) {
|
|
1758
|
+
const a = parseTagAttrs(tag);
|
|
1759
|
+
if (a["v1"] !== void 0 && a["v2"] !== void 0 && a["v3"] !== void 0) {
|
|
1760
|
+
triangles.push([parseInt(a["v1"], 10), parseInt(a["v2"], 10), parseInt(a["v3"], 10)]);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
return { vertices, triangles };
|
|
1764
|
+
}
|
|
1765
|
+
function buildTriFace(oc, a, b, c) {
|
|
1766
|
+
const gpA = new oc.gp_Pnt_3(a[0], a[1], a[2]);
|
|
1767
|
+
const gpB = new oc.gp_Pnt_3(b[0], b[1], b[2]);
|
|
1768
|
+
const gpC = new oc.gp_Pnt_3(c[0], c[1], c[2]);
|
|
1769
|
+
const e1 = new oc.BRepBuilderAPI_MakeEdge_3(gpA, gpB);
|
|
1770
|
+
const e2 = new oc.BRepBuilderAPI_MakeEdge_3(gpB, gpC);
|
|
1771
|
+
const e3 = new oc.BRepBuilderAPI_MakeEdge_3(gpC, gpA);
|
|
1772
|
+
const wireBuilder = new oc.BRepBuilderAPI_MakeWire_1();
|
|
1773
|
+
wireBuilder.Add_1(e1.Edge());
|
|
1774
|
+
wireBuilder.Add_1(e2.Edge());
|
|
1775
|
+
wireBuilder.Add_1(e3.Edge());
|
|
1776
|
+
let face2 = null;
|
|
1777
|
+
if (wireBuilder.IsDone()) {
|
|
1778
|
+
const makeFace = new oc.BRepBuilderAPI_MakeFace_15(wireBuilder.Wire(), false);
|
|
1779
|
+
if (makeFace.IsDone()) {
|
|
1780
|
+
face2 = makeFace.Face();
|
|
1781
|
+
}
|
|
1782
|
+
makeFace.delete();
|
|
1783
|
+
}
|
|
1784
|
+
wireBuilder.delete();
|
|
1785
|
+
e1.delete();
|
|
1786
|
+
e2.delete();
|
|
1787
|
+
e3.delete();
|
|
1788
|
+
gpA.delete();
|
|
1789
|
+
gpB.delete();
|
|
1790
|
+
gpC.delete();
|
|
1791
|
+
return face2;
|
|
1792
|
+
}
|
|
1793
|
+
function buildSolidFromMesh(mesh2) {
|
|
1794
|
+
const oc = occtBoundary.getKernel().oc;
|
|
1795
|
+
const sewing = new oc.BRepBuilderAPI_Sewing(1e-6, true, true, true, false);
|
|
1796
|
+
let faceCount = 0;
|
|
1797
|
+
try {
|
|
1798
|
+
for (const [v1, v2, v3] of mesh2.triangles) {
|
|
1799
|
+
const va = mesh2.vertices[v1];
|
|
1800
|
+
const vb = mesh2.vertices[v2];
|
|
1801
|
+
const vc = mesh2.vertices[v3];
|
|
1802
|
+
if (!va || !vb || !vc) continue;
|
|
1803
|
+
const triFace = buildTriFace(oc, va, vb, vc);
|
|
1804
|
+
if (triFace !== null) {
|
|
1805
|
+
sewing.Add(triFace);
|
|
1806
|
+
faceCount++;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
if (faceCount === 0) {
|
|
1810
|
+
return errors.err(
|
|
1811
|
+
errors.ioError(errors.BrepErrorCode.THREEMF_IMPORT_FAILED, "No valid triangular faces could be built")
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
const progress = new oc.Message_ProgressRange_1();
|
|
1815
|
+
sewing.Perform(progress);
|
|
1816
|
+
progress.delete();
|
|
1817
|
+
const sewn = sewing.SewedShape();
|
|
1818
|
+
const fixer = new oc.ShapeFix_Solid_1();
|
|
1819
|
+
try {
|
|
1820
|
+
const shell2 = oc.TopoDS.Shell_1(sewn);
|
|
1821
|
+
const solid2 = fixer.SolidFromShell(shell2);
|
|
1822
|
+
return errors.ok(shapeTypes.castShape(solid2));
|
|
1823
|
+
} catch {
|
|
1824
|
+
return errors.ok(shapeTypes.castShape(sewn));
|
|
1825
|
+
} finally {
|
|
1826
|
+
fixer.delete();
|
|
1827
|
+
}
|
|
1828
|
+
} finally {
|
|
1829
|
+
sewing.delete();
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
async function importThreeMF(blob) {
|
|
1833
|
+
try {
|
|
1834
|
+
const arrayBuf = await blob.arrayBuffer();
|
|
1835
|
+
const data = new Uint8Array(arrayBuf);
|
|
1836
|
+
const modelData = extractFromZip(data, "3D/3dmodel.model");
|
|
1837
|
+
if (!modelData) {
|
|
1838
|
+
return errors.err(
|
|
1839
|
+
errors.ioError(
|
|
1840
|
+
errors.BrepErrorCode.THREEMF_IMPORT_FAILED,
|
|
1841
|
+
"3MF archive does not contain 3D/3dmodel.model (or uses unsupported compression)"
|
|
1842
|
+
)
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
const xml = new TextDecoder().decode(modelData);
|
|
1846
|
+
const parsed = parseModelXml(xml);
|
|
1847
|
+
if (parsed.vertices.length === 0 || parsed.triangles.length === 0) {
|
|
1848
|
+
return errors.err(
|
|
1849
|
+
errors.ioError(errors.BrepErrorCode.THREEMF_IMPORT_FAILED, "3MF model contains no valid geometry")
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
return buildSolidFromMesh(parsed);
|
|
1853
|
+
} catch (e) {
|
|
1854
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1855
|
+
return errors.err(errors.ioError(errors.BrepErrorCode.THREEMF_IMPORT_FAILED, `3MF import failed: ${msg}`, e));
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
185
1858
|
function resolve(s) {
|
|
186
1859
|
if ("__wrapped" in s) {
|
|
187
1860
|
return s.val;
|
|
@@ -248,18 +1921,18 @@ function ellipsoid(rx, ry, rz, options) {
|
|
|
248
1921
|
return solid2;
|
|
249
1922
|
}
|
|
250
1923
|
function line(from, to) {
|
|
251
|
-
return
|
|
1924
|
+
return surfaceBuilders.makeLine(from, to);
|
|
252
1925
|
}
|
|
253
1926
|
function circle(radius, options) {
|
|
254
1927
|
const axisDir = options?.axis ?? [0, 0, 1];
|
|
255
|
-
return
|
|
1928
|
+
return surfaceBuilders.makeCircle(radius, options?.at ?? [0, 0, 0], axisDir);
|
|
256
1929
|
}
|
|
257
1930
|
function ellipse(majorRadius, minorRadius, options) {
|
|
258
1931
|
const axisDir = options?.axis ?? [0, 0, 1];
|
|
259
|
-
return
|
|
1932
|
+
return surfaceBuilders.makeEllipse(majorRadius, minorRadius, options?.at ?? [0, 0, 0], axisDir, options?.xDir);
|
|
260
1933
|
}
|
|
261
1934
|
function helix(pitch, height, radius, options) {
|
|
262
|
-
return
|
|
1935
|
+
return surfaceBuilders.makeHelix(
|
|
263
1936
|
pitch,
|
|
264
1937
|
height,
|
|
265
1938
|
radius,
|
|
@@ -269,11 +1942,11 @@ function helix(pitch, height, radius, options) {
|
|
|
269
1942
|
);
|
|
270
1943
|
}
|
|
271
1944
|
function threePointArc(p1, p2, p3) {
|
|
272
|
-
return
|
|
1945
|
+
return surfaceBuilders.makeThreePointArc(p1, p2, p3);
|
|
273
1946
|
}
|
|
274
1947
|
function ellipseArc(majorRadius, minorRadius, startAngle, endAngle, options) {
|
|
275
1948
|
const axisDir = options?.axis ?? [0, 0, 1];
|
|
276
|
-
return
|
|
1949
|
+
return surfaceBuilders.makeEllipseArc(
|
|
277
1950
|
majorRadius,
|
|
278
1951
|
minorRadius,
|
|
279
1952
|
startAngle * vecOps.DEG2RAD,
|
|
@@ -284,28 +1957,28 @@ function ellipseArc(majorRadius, minorRadius, startAngle, endAngle, options) {
|
|
|
284
1957
|
);
|
|
285
1958
|
}
|
|
286
1959
|
function bsplineApprox(points, config) {
|
|
287
|
-
return
|
|
1960
|
+
return surfaceBuilders.makeBSplineApproximation(points, config);
|
|
288
1961
|
}
|
|
289
1962
|
function bezier(points) {
|
|
290
|
-
return
|
|
1963
|
+
return surfaceBuilders.makeBezierCurve(points);
|
|
291
1964
|
}
|
|
292
1965
|
function tangentArc(startPoint, startTgt, endPoint) {
|
|
293
|
-
return
|
|
1966
|
+
return surfaceBuilders.makeTangentArc(startPoint, startTgt, endPoint);
|
|
294
1967
|
}
|
|
295
1968
|
function wire(listOfEdges) {
|
|
296
|
-
return
|
|
1969
|
+
return surfaceBuilders.assembleWire(listOfEdges);
|
|
297
1970
|
}
|
|
298
1971
|
function face(w, holes) {
|
|
299
|
-
return
|
|
1972
|
+
return surfaceBuilders.makeFace(w, holes);
|
|
300
1973
|
}
|
|
301
1974
|
function filledFace(w) {
|
|
302
|
-
return
|
|
1975
|
+
return surfaceBuilders.makeNonPlanarFace(w);
|
|
303
1976
|
}
|
|
304
1977
|
function subFace(originFace, w) {
|
|
305
|
-
return
|
|
1978
|
+
return surfaceBuilders.makeNewFaceWithinFace(originFace, w);
|
|
306
1979
|
}
|
|
307
1980
|
function polygon(points) {
|
|
308
|
-
return
|
|
1981
|
+
return surfaceBuilders.makePolygon(points);
|
|
309
1982
|
}
|
|
310
1983
|
function vertex(point) {
|
|
311
1984
|
return loft$2.makeVertex(point);
|
|
@@ -323,7 +1996,7 @@ function sewShells(facesOrShells, ignoreType) {
|
|
|
323
1996
|
return loft$2.weldShellsAndFaces(facesOrShells, ignoreType);
|
|
324
1997
|
}
|
|
325
1998
|
function addHoles(f, holes) {
|
|
326
|
-
return
|
|
1999
|
+
return surfaceBuilders.addHolesInFace(f, holes);
|
|
327
2000
|
}
|
|
328
2001
|
function validateNotNull(shape2, label) {
|
|
329
2002
|
if (shape2.wrapped.IsNull()) {
|
|
@@ -342,9 +2015,11 @@ function thicken$1(shape2, thickness) {
|
|
|
342
2015
|
const progress = r(new oc.Message_ProgressRange_1());
|
|
343
2016
|
builder.Build(progress);
|
|
344
2017
|
const resultOc = builder.Shape();
|
|
345
|
-
const
|
|
346
|
-
shapeFns.propagateOrigins(builder, [shape2],
|
|
347
|
-
|
|
2018
|
+
const cast = shapeTypes.castShape(resultOc);
|
|
2019
|
+
shapeFns.propagateOrigins(builder, [shape2], cast);
|
|
2020
|
+
booleanFns.propagateFaceTags(builder, [shape2], cast);
|
|
2021
|
+
booleanFns.propagateColors(builder, [shape2], cast);
|
|
2022
|
+
return errors.ok(cast);
|
|
348
2023
|
} catch (e) {
|
|
349
2024
|
const raw = e instanceof Error ? e.message : String(e);
|
|
350
2025
|
return errors.err(errors.occtError("THICKEN_FAILED", `Thicken operation failed: ${raw}`, e));
|
|
@@ -403,12 +2078,14 @@ function fillet$1(shape2, edges, radius) {
|
|
|
403
2078
|
}
|
|
404
2079
|
}
|
|
405
2080
|
const resultOc = builder.Shape();
|
|
406
|
-
const
|
|
407
|
-
if (!shapeTypes.isShape3D(
|
|
2081
|
+
const cast = shapeTypes.castShape(resultOc);
|
|
2082
|
+
if (!shapeTypes.isShape3D(cast)) {
|
|
408
2083
|
return errors.err(errors.occtError("FILLET_RESULT_NOT_3D", "Fillet result is not a 3D shape"));
|
|
409
2084
|
}
|
|
410
|
-
shapeFns.propagateOrigins(builder, [shape2],
|
|
411
|
-
|
|
2085
|
+
shapeFns.propagateOrigins(builder, [shape2], cast);
|
|
2086
|
+
booleanFns.propagateFaceTags(builder, [shape2], cast);
|
|
2087
|
+
booleanFns.propagateColors(builder, [shape2], cast);
|
|
2088
|
+
return errors.ok(cast);
|
|
412
2089
|
} catch (e) {
|
|
413
2090
|
const raw = e instanceof Error ? e.message : String(e);
|
|
414
2091
|
return errors.err(
|
|
@@ -497,12 +2174,14 @@ function chamfer$1(shape2, edges, distance) {
|
|
|
497
2174
|
}
|
|
498
2175
|
}
|
|
499
2176
|
const resultOc = builder.Shape();
|
|
500
|
-
const
|
|
501
|
-
if (!shapeTypes.isShape3D(
|
|
2177
|
+
const cast = shapeTypes.castShape(resultOc);
|
|
2178
|
+
if (!shapeTypes.isShape3D(cast)) {
|
|
502
2179
|
return errors.err(errors.occtError("CHAMFER_RESULT_NOT_3D", "Chamfer result is not a 3D shape"));
|
|
503
2180
|
}
|
|
504
|
-
shapeFns.propagateOrigins(builder, [shape2],
|
|
505
|
-
|
|
2181
|
+
shapeFns.propagateOrigins(builder, [shape2], cast);
|
|
2182
|
+
booleanFns.propagateFaceTags(builder, [shape2], cast);
|
|
2183
|
+
booleanFns.propagateColors(builder, [shape2], cast);
|
|
2184
|
+
return errors.ok(cast);
|
|
506
2185
|
} catch (e) {
|
|
507
2186
|
const raw = e instanceof Error ? e.message : String(e);
|
|
508
2187
|
return errors.err(
|
|
@@ -545,12 +2224,14 @@ function shell$1(shape2, faces, thickness, tolerance = 1e-3) {
|
|
|
545
2224
|
progress
|
|
546
2225
|
);
|
|
547
2226
|
const resultOc = builder.Shape();
|
|
548
|
-
const
|
|
549
|
-
if (!shapeTypes.isShape3D(
|
|
2227
|
+
const cast = shapeTypes.castShape(resultOc);
|
|
2228
|
+
if (!shapeTypes.isShape3D(cast)) {
|
|
550
2229
|
return errors.err(errors.occtError("SHELL_RESULT_NOT_3D", "Shell result is not a 3D shape"));
|
|
551
2230
|
}
|
|
552
|
-
shapeFns.propagateOrigins(builder, [shape2],
|
|
553
|
-
|
|
2231
|
+
shapeFns.propagateOrigins(builder, [shape2], cast);
|
|
2232
|
+
booleanFns.propagateFaceTags(builder, [shape2], cast);
|
|
2233
|
+
booleanFns.propagateColors(builder, [shape2], cast);
|
|
2234
|
+
return errors.ok(cast);
|
|
554
2235
|
} catch (e) {
|
|
555
2236
|
const raw = e instanceof Error ? e.message : String(e);
|
|
556
2237
|
return errors.err(
|
|
@@ -585,12 +2266,14 @@ function offset$1(shape2, distance, tolerance = 1e-6) {
|
|
|
585
2266
|
progress
|
|
586
2267
|
);
|
|
587
2268
|
const resultOc = builder.Shape();
|
|
588
|
-
const
|
|
589
|
-
if (!shapeTypes.isShape3D(
|
|
2269
|
+
const cast = shapeTypes.castShape(resultOc);
|
|
2270
|
+
if (!shapeTypes.isShape3D(cast)) {
|
|
590
2271
|
return errors.err(errors.occtError("OFFSET_RESULT_NOT_3D", "Offset result is not a 3D shape"));
|
|
591
2272
|
}
|
|
592
|
-
shapeFns.propagateOrigins(builder, [shape2],
|
|
593
|
-
|
|
2273
|
+
shapeFns.propagateOrigins(builder, [shape2], cast);
|
|
2274
|
+
booleanFns.propagateFaceTags(builder, [shape2], cast);
|
|
2275
|
+
booleanFns.propagateColors(builder, [shape2], cast);
|
|
2276
|
+
return errors.ok(cast);
|
|
594
2277
|
} catch (e) {
|
|
595
2278
|
const raw = e instanceof Error ? e.message : String(e);
|
|
596
2279
|
return errors.err(errors.occtError("OFFSET_FAILED", `Offset operation failed: ${raw}`, e));
|
|
@@ -631,6 +2314,9 @@ function intersect(a, b, options) {
|
|
|
631
2314
|
function section(shape2, plane, options) {
|
|
632
2315
|
return booleanFns.section(resolve(shape2), plane, options);
|
|
633
2316
|
}
|
|
2317
|
+
function sectionToFace(shape2, plane, options) {
|
|
2318
|
+
return booleanFns.sectionToFace(resolve(shape2), plane, options);
|
|
2319
|
+
}
|
|
634
2320
|
function split(shape2, tools) {
|
|
635
2321
|
return booleanFns.split(resolve(shape2), tools);
|
|
636
2322
|
}
|
|
@@ -740,7 +2426,7 @@ function toBREP(shape2) {
|
|
|
740
2426
|
return shapeFns.toBREP(resolve(shape2));
|
|
741
2427
|
}
|
|
742
2428
|
function fromBREP(data) {
|
|
743
|
-
return
|
|
2429
|
+
return faceFns.fromBREP(data);
|
|
744
2430
|
}
|
|
745
2431
|
function isValid(shape2) {
|
|
746
2432
|
return topology.isValid(resolve(shape2));
|
|
@@ -865,7 +2551,7 @@ function pocket(shape2, options) {
|
|
|
865
2551
|
const targetFace = resolveTargetFace(s, options.face);
|
|
866
2552
|
const normal = faceFns.normalAt(targetFace);
|
|
867
2553
|
const w = toWire(profile);
|
|
868
|
-
const faceResult =
|
|
2554
|
+
const faceResult = surfaceBuilders.makeFace(w);
|
|
869
2555
|
if (errors.isErr(faceResult)) return faceResult;
|
|
870
2556
|
const extDir = vecOps.vecScale(vecOps.vecNormalize(normal), -depth);
|
|
871
2557
|
const toolResult = operations.extrude(faceResult.value, extDir);
|
|
@@ -881,7 +2567,7 @@ function boss(shape2, options) {
|
|
|
881
2567
|
const targetFace = resolveTargetFace(s, options.face);
|
|
882
2568
|
const normal = faceFns.normalAt(targetFace);
|
|
883
2569
|
const w = toWire(profile);
|
|
884
|
-
const faceResult =
|
|
2570
|
+
const faceResult = surfaceBuilders.makeFace(w);
|
|
885
2571
|
if (errors.isErr(faceResult)) return faceResult;
|
|
886
2572
|
const extDir = vecOps.vecScale(vecOps.vecNormalize(normal), height);
|
|
887
2573
|
const toolResult = operations.extrude(faceResult.value, extDir);
|
|
@@ -1307,6 +2993,7 @@ exports.drawingFillet = drawFns.drawingFillet;
|
|
|
1307
2993
|
exports.drawingFuse = drawFns.drawingFuse;
|
|
1308
2994
|
exports.drawingIntersect = drawFns.drawingIntersect;
|
|
1309
2995
|
exports.drawingToSketchOnPlane = drawFns.drawingToSketchOnPlane;
|
|
2996
|
+
exports.fontMetrics = drawFns.fontMetrics;
|
|
1310
2997
|
exports.getFont = drawFns.getFont;
|
|
1311
2998
|
exports.isProjectionPlane = drawFns.isProjectionPlane;
|
|
1312
2999
|
exports.loadFont = drawFns.loadFont;
|
|
@@ -1333,6 +3020,7 @@ exports.sketchSweep = drawFns.sketchSweep;
|
|
|
1333
3020
|
exports.sketchText = drawFns.sketchText;
|
|
1334
3021
|
exports.sketchWires = drawFns.sketchWires;
|
|
1335
3022
|
exports.textBlueprints = drawFns.textBlueprints;
|
|
3023
|
+
exports.textMetrics = drawFns.textMetrics;
|
|
1336
3024
|
exports.translateDrawing = drawFns.translateDrawing;
|
|
1337
3025
|
exports.createNamedPlane = vectors.createNamedPlane;
|
|
1338
3026
|
exports.createPlane = vectors.createPlane;
|
|
@@ -1353,8 +3041,21 @@ exports.iterEdges = shapeFns.iterEdges;
|
|
|
1353
3041
|
exports.iterFaces = shapeFns.iterFaces;
|
|
1354
3042
|
exports.iterVertices = shapeFns.iterVertices;
|
|
1355
3043
|
exports.iterWires = shapeFns.iterWires;
|
|
3044
|
+
exports.resize = shapeFns.resize;
|
|
1356
3045
|
exports.setShapeOrigin = shapeFns.setShapeOrigin;
|
|
1357
3046
|
exports.vertexPosition = shapeFns.vertexPosition;
|
|
3047
|
+
exports.applyGlue = booleanFns.applyGlue;
|
|
3048
|
+
exports.colorFaces = booleanFns.colorFaces;
|
|
3049
|
+
exports.colorShape = booleanFns.colorShape;
|
|
3050
|
+
exports.cutAll = booleanFns.cutAll;
|
|
3051
|
+
exports.findFacesByTag = booleanFns.findFacesByTag;
|
|
3052
|
+
exports.fuseAll = booleanFns.fuseAll;
|
|
3053
|
+
exports.getFaceColor = booleanFns.getFaceColor;
|
|
3054
|
+
exports.getFaceTags = booleanFns.getFaceTags;
|
|
3055
|
+
exports.getShapeColor = booleanFns.getShapeColor;
|
|
3056
|
+
exports.getTagMetadata = booleanFns.getTagMetadata;
|
|
3057
|
+
exports.setTagMetadata = booleanFns.setTagMetadata;
|
|
3058
|
+
exports.tagFaces = booleanFns.tagFaces;
|
|
1358
3059
|
exports.adjacentFaces = topology.adjacentFaces;
|
|
1359
3060
|
exports.autoHeal = topology.autoHeal;
|
|
1360
3061
|
exports.chamferDistAngleShape = topology.chamferDistAngle;
|
|
@@ -1369,17 +3070,24 @@ exports.toGroupedBufferGeometryData = topology.toGroupedBufferGeometryData;
|
|
|
1369
3070
|
exports.toLineGeometryData = topology.toLineGeometryData;
|
|
1370
3071
|
exports.verticesOfEdge = topology.verticesOfEdge;
|
|
1371
3072
|
exports.wiresOfFace = topology.wiresOfFace;
|
|
3073
|
+
exports.asTopo = faceFns.asTopo;
|
|
3074
|
+
exports.cast = faceFns.cast;
|
|
1372
3075
|
exports.classifyPointOnFace = faceFns.classifyPointOnFace;
|
|
3076
|
+
exports.deserializeShape = faceFns.fromBREP;
|
|
3077
|
+
exports.downcast = faceFns.downcast;
|
|
1373
3078
|
exports.faceCenter = faceFns.faceCenter;
|
|
1374
3079
|
exports.faceGeomType = faceFns.faceGeomType;
|
|
1375
3080
|
exports.faceOrientation = faceFns.faceOrientation;
|
|
1376
3081
|
exports.flipFaceOrientation = faceFns.flipFaceOrientation;
|
|
1377
3082
|
exports.getSurfaceType = faceFns.getSurfaceType;
|
|
1378
3083
|
exports.innerWires = faceFns.innerWires;
|
|
3084
|
+
exports.isCompSolid = faceFns.isCompSolid;
|
|
3085
|
+
exports.iterTopo = faceFns.iterTopo;
|
|
1379
3086
|
exports.normalAt = faceFns.normalAt;
|
|
1380
3087
|
exports.outerWire = faceFns.outerWire;
|
|
1381
3088
|
exports.pointOnSurface = faceFns.pointOnSurface;
|
|
1382
3089
|
exports.projectPointOnFace = faceFns.projectPointOnFace;
|
|
3090
|
+
exports.shapeType = faceFns.shapeType;
|
|
1383
3091
|
exports.uvBounds = faceFns.uvBounds;
|
|
1384
3092
|
exports.uvCoordinates = faceFns.uvCoordinates;
|
|
1385
3093
|
exports.clearMeshCache = meshFns.clearMeshCache;
|
|
@@ -1387,9 +3095,6 @@ exports.createMeshCache = meshFns.createMeshCache;
|
|
|
1387
3095
|
exports.exportIGES = meshFns.exportIGES;
|
|
1388
3096
|
exports.exportSTEP = meshFns.exportSTEP;
|
|
1389
3097
|
exports.exportSTL = meshFns.exportSTL;
|
|
1390
|
-
exports.applyGlue = booleanFns.applyGlue;
|
|
1391
|
-
exports.cutAll = booleanFns.cutAll;
|
|
1392
|
-
exports.fuseAll = booleanFns.fuseAll;
|
|
1393
3098
|
exports.createDistanceQuery = measurement.createDistanceQuery;
|
|
1394
3099
|
exports.measureArea = measurement.measureArea;
|
|
1395
3100
|
exports.measureCurvatureAt = measurement.measureCurvatureAt;
|
|
@@ -1400,13 +3105,7 @@ exports.measureLinearProps = measurement.measureLinearProps;
|
|
|
1400
3105
|
exports.measureSurfaceProps = measurement.measureSurfaceProps;
|
|
1401
3106
|
exports.measureVolume = measurement.measureVolume;
|
|
1402
3107
|
exports.measureVolumeProps = measurement.measureVolumeProps;
|
|
1403
|
-
exports.
|
|
1404
|
-
exports.cast = cast.cast;
|
|
1405
|
-
exports.deserializeShape = cast.fromBREP;
|
|
1406
|
-
exports.downcast = cast.downcast;
|
|
1407
|
-
exports.isCompSolid = cast.isCompSolid;
|
|
1408
|
-
exports.iterTopo = cast.iterTopo;
|
|
1409
|
-
exports.shapeType = cast.shapeType;
|
|
3108
|
+
exports.fill = surfaceBuilders.fill;
|
|
1410
3109
|
exports.edgeFinder = query.edgeFinder;
|
|
1411
3110
|
exports.BrepBugError = result.BrepBugError;
|
|
1412
3111
|
exports.bug = result.bug;
|
|
@@ -1428,6 +3127,7 @@ exports.registerHandler = worker.registerHandler;
|
|
|
1428
3127
|
exports.rejectAll = worker.rejectAll;
|
|
1429
3128
|
exports.BrepWrapperError = BrepWrapperError;
|
|
1430
3129
|
exports.addHoles = addHoles;
|
|
3130
|
+
exports.addMate = addMate;
|
|
1431
3131
|
exports.applyMatrix = applyMatrix;
|
|
1432
3132
|
exports.bezier = bezier;
|
|
1433
3133
|
exports.boss = boss;
|
|
@@ -1439,6 +3139,7 @@ exports.checkInterference = checkInterference;
|
|
|
1439
3139
|
exports.circle = circle;
|
|
1440
3140
|
exports.clone = clone;
|
|
1441
3141
|
exports.compound = compound;
|
|
3142
|
+
exports.computeStraightSkeleton = computeStraightSkeleton;
|
|
1442
3143
|
exports.cone = cone;
|
|
1443
3144
|
exports.cut = cut;
|
|
1444
3145
|
exports.cylinder = cylinder;
|
|
@@ -1453,8 +3154,13 @@ exports.filledFace = filledFace;
|
|
|
1453
3154
|
exports.fillet = fillet;
|
|
1454
3155
|
exports.fromBREP = fromBREP;
|
|
1455
3156
|
exports.fuse = fuse;
|
|
3157
|
+
exports.guidedSweep = guidedSweep;
|
|
1456
3158
|
exports.heal = heal;
|
|
1457
3159
|
exports.helix = helix;
|
|
3160
|
+
exports.hull = hull;
|
|
3161
|
+
exports.importDXF = importDXF;
|
|
3162
|
+
exports.importOBJ = importOBJ;
|
|
3163
|
+
exports.importThreeMF = importThreeMF;
|
|
1458
3164
|
exports.intersect = intersect;
|
|
1459
3165
|
exports.isChamferRadius = isChamferRadius;
|
|
1460
3166
|
exports.isEmpty = isEmpty;
|
|
@@ -1467,28 +3173,36 @@ exports.line = line;
|
|
|
1467
3173
|
exports.loft = loft;
|
|
1468
3174
|
exports.mesh = mesh;
|
|
1469
3175
|
exports.meshEdges = meshEdges;
|
|
3176
|
+
exports.minkowski = minkowski;
|
|
1470
3177
|
exports.mirror = mirror;
|
|
1471
3178
|
exports.mirrorJoin = mirrorJoin;
|
|
3179
|
+
exports.multiSectionSweep = multiSectionSweep;
|
|
1472
3180
|
exports.offset = offset;
|
|
1473
3181
|
exports.offsetFace = offsetFace;
|
|
1474
3182
|
exports.pocket = pocket;
|
|
1475
3183
|
exports.polygon = polygon;
|
|
3184
|
+
exports.polyhedron = polyhedron;
|
|
1476
3185
|
exports.rectangularPattern = rectangularPattern;
|
|
1477
3186
|
exports.resolve = resolve;
|
|
1478
3187
|
exports.resolve3D = resolve3D;
|
|
1479
3188
|
exports.revolve = revolve;
|
|
3189
|
+
exports.roof = roof;
|
|
1480
3190
|
exports.rotate = rotate;
|
|
1481
3191
|
exports.scale = scale;
|
|
1482
3192
|
exports.section = section;
|
|
3193
|
+
exports.sectionToFace = sectionToFace;
|
|
1483
3194
|
exports.sewShells = sewShells;
|
|
1484
3195
|
exports.shape = shape;
|
|
1485
3196
|
exports.shell = shell;
|
|
1486
3197
|
exports.simplify = simplify;
|
|
1487
3198
|
exports.slice = slice;
|
|
1488
3199
|
exports.solid = solid;
|
|
3200
|
+
exports.solveAssembly = solveAssembly;
|
|
1489
3201
|
exports.sphere = sphere;
|
|
1490
3202
|
exports.split = split;
|
|
1491
3203
|
exports.subFace = subFace;
|
|
3204
|
+
exports.surfaceFromGrid = surfaceFromGrid;
|
|
3205
|
+
exports.surfaceFromImage = surfaceFromImage;
|
|
1492
3206
|
exports.tangentArc = tangentArc;
|
|
1493
3207
|
exports.thicken = thicken;
|
|
1494
3208
|
exports.threePointArc = threePointArc;
|