brepjs 20.12.13 → 20.12.15
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/blueprints/blueprintFns.d.ts +2 -0
- package/dist/2d.cjs +6 -6
- package/dist/2d.js +6 -6
- package/dist/{adjacencyFns-Djv832MP.cjs → adjacencyFns-BHdJW5pd.cjs} +3 -3
- package/dist/{adjacencyFns-DBN26xm_.js → adjacencyFns-C4yjxKu4.js} +3 -3
- package/dist/{blueprint-nVQsEilp.cjs → blueprint-D4juQWXs.cjs} +5 -5
- package/dist/{blueprint-BTccTqFy.js → blueprint-ku_wYIZl.js} +5 -5
- package/dist/{blueprintFns-DFDOgqqe.cjs → blueprintFns-8obtvMEn.cjs} +4 -5
- package/dist/{blueprintFns-DX7yLZpR.js → blueprintFns-B0cqtoFB.js} +4 -5
- package/dist/{blueprintSketcher-CoGp1dM9.cjs → blueprintSketcher-CRFpnZAS.cjs} +3 -3
- package/dist/{blueprintSketcher-rk64lh8e.js → blueprintSketcher-Dpdgf5aR.js} +3 -3
- package/dist/{boolean2D-Dv-57m9d.js → boolean2D-CnCDsRJ4.js} +4 -4
- package/dist/{boolean2D-DP2DiMfT.cjs → boolean2D-D3jxohLZ.cjs} +4 -4
- package/dist/{booleanFns-CjIZaNBh.js → booleanFns-CItcObM_.js} +5 -5
- package/dist/{booleanFns-ewBwy7V_.cjs → booleanFns-GBPAGQ5g.cjs} +5 -5
- package/dist/brepjs.cjs +59 -61
- package/dist/brepjs.js +59 -61
- package/dist/{cameraFns-D0JDXxwO.js → cameraFns-B6ujpyKy.js} +3 -3
- package/dist/{cameraFns-DmIQsPIr.cjs → cameraFns-C2XhSqrr.cjs} +3 -3
- package/dist/core/shapeTypes.d.ts +8 -4
- package/dist/core.cjs +2 -2
- package/dist/core.js +2 -2
- package/dist/{cornerFinder-CZ1XbVx7.cjs → cornerFinder-CqHx_Itj.cjs} +1 -1
- package/dist/{cornerFinder-Df7ajnlz.js → cornerFinder-D3nBhdfi.js} +1 -1
- package/dist/{curveFns-DK5eG57I.cjs → curveFns-BQjw0VBB.cjs} +1 -1
- package/dist/{curveFns-CUrIF9LG.js → curveFns-nAwtilbz.js} +1 -1
- package/dist/{drawFns-BKKWQ4JW.js → drawFns-D9q4Hxc_.js} +12 -12
- package/dist/{drawFns-B2CPxqd1.cjs → drawFns-hFjTvdw4.cjs} +12 -12
- package/dist/{faceFns-C1Ii1MJ-.cjs → faceFns-DdMpocg9.cjs} +2 -2
- package/dist/{faceFns-B3vblI_s.js → faceFns-Dodcduw8.js} +2 -2
- package/dist/{healingFns-sfguIlrW.cjs → healingFns-Cdpu44d0.cjs} +5 -5
- package/dist/{healingFns-CqiHrJcx.js → healingFns-GNF1rFuw.js} +5 -5
- package/dist/{helpers-Dy1moMMd.js → helpers-CoVtL5c9.js} +6 -6
- package/dist/{helpers-CTxyRIoE.cjs → helpers-TWIfnyto.cjs} +6 -6
- package/dist/{importFns-BXiAHbyn.js → importFns-C9TUFscx.js} +479 -57
- package/dist/{importFns-C3ILnGTv.cjs → importFns-P-TcOJ1G.cjs} +479 -57
- package/dist/io/importFns.d.ts +6 -4
- package/dist/io/ioUtils.d.ts +23 -5
- package/dist/io/meshNesting.d.ts +25 -4
- package/dist/io/meshTopology.d.ts +33 -0
- package/dist/io/stlTriangles.d.ts +10 -0
- package/dist/io/threemfImportFns.d.ts +9 -6
- package/dist/io.cjs +2 -2
- package/dist/io.js +2 -2
- package/dist/kernel/capabilities.d.ts +9 -1
- package/dist/kernel/interfaces/evolutionOps.d.ts +1 -0
- package/dist/kernel/interfaces/transformOps.d.ts +7 -2
- package/dist/kernel/occt/transformOps.d.ts +1 -1
- package/dist/kernel/occtWasm/occtWasmAdapter.cjs +1 -1
- package/dist/kernel/occtWasm/occtWasmAdapter.js +1 -1
- package/dist/{loftFns-BJqF1p_f.js → loftFns-DVsWk-9v.js} +19 -52
- package/dist/{loftFns-CzERU_TY.cjs → loftFns-Uq6BhPVL.cjs} +19 -52
- package/dist/{measureFns-DN5YMfKZ.cjs → measureFns-B0Cy7GcQ.cjs} +3 -3
- package/dist/{measureFns-CT2TRojo.js → measureFns-DoUWDfAP.js} +3 -3
- package/dist/measurement.cjs +2 -2
- package/dist/measurement.js +2 -2
- package/dist/{meshFns-Cp8uEpVt.js → meshFns-BpBgKIIE.js} +4 -4
- package/dist/{meshFns-BDn_nIVv.cjs → meshFns-Bz9PPZWP.cjs} +4 -4
- package/dist/{occtWasmAdapter-C-Qi-jxS.js → occtWasmAdapter-DeVQrL5n.js} +4 -3
- package/dist/{occtWasmAdapter-BK--kx4R.cjs → occtWasmAdapter-DzxyXpLG.cjs} +4 -3
- package/dist/operations/instanceFns.d.ts +3 -2
- package/dist/operations/sweepFns.d.ts +15 -3
- package/dist/operations.cjs +2 -2
- package/dist/operations.js +2 -2
- package/dist/{primitiveFns-BzJi89Y6.js → primitiveFns-Crqsdby_.js} +5 -5
- package/dist/{primitiveFns-1FcmvZ6Y.cjs → primitiveFns-DE1RROq-.cjs} +5 -5
- package/dist/{principalBoxFns-DT4ngNuy.cjs → principalBoxFns-CzEBI6tC.cjs} +3 -3
- package/dist/{principalBoxFns-farY7tvh.js → principalBoxFns-DwitCsif.js} +3 -3
- package/dist/projection.cjs +1 -1
- package/dist/projection.js +1 -1
- package/dist/query.cjs +2 -2
- package/dist/query.js +2 -2
- package/dist/{refResolveFns-dNAt7W9t.js → refResolveFns-BJz9UvXM.js} +6 -6
- package/dist/{refResolveFns-By31fyEv.cjs → refResolveFns-DgM_B9WU.cjs} +6 -6
- package/dist/{shapeFns-DhTfkxbN.cjs → shapeFns-B9LB54_t.cjs} +2 -2
- package/dist/{shapeFns-381QqZ8n.js → shapeFns-Ddh0M4Iz.js} +2 -2
- package/dist/shapeRef.cjs +1 -1
- package/dist/shapeRef.js +1 -1
- package/dist/{shapeTypes-C0NcoN5g.cjs → shapeTypes-C1Ljs72p.cjs} +20 -18
- package/dist/{shapeTypes-DfBx__EF.js → shapeTypes-DiCMRU6Z.js} +20 -18
- package/dist/sketching/compoundSketch.d.ts +3 -2
- package/dist/sketching/sketch.d.ts +5 -4
- package/dist/sketching/sketches.d.ts +3 -2
- package/dist/sketching.cjs +3 -3
- package/dist/sketching.js +3 -3
- package/dist/{solidBuilders-VPkj5ChQ.cjs → solidBuilders-C6_6tZXG.cjs} +3 -3
- package/dist/{solidBuilders-BZ1qXlFZ.js → solidBuilders-Dzhk5L6w.js} +3 -3
- package/dist/{surfaceBuilders-DQre19g8.cjs → surfaceBuilders-CU_vd71e.cjs} +3 -3
- package/dist/{surfaceBuilders-sBre5aYy.js → surfaceBuilders-Idshk6C3.js} +3 -3
- package/dist/text.cjs +2 -2
- package/dist/text.js +2 -2
- package/dist/{textBlueprints-ChDNr0L4.cjs → textBlueprints-BmIxy1fi.cjs} +8 -8
- package/dist/{textBlueprints-DCorV4gM.js → textBlueprints-DyT-kfxc.js} +8 -8
- package/dist/{textMetrics-C0qTX3sH.cjs → textMetrics-CbDASt0r.cjs} +1 -1
- package/dist/{textMetrics-ByzyMC1I.js → textMetrics-DuzBLY9z.js} +1 -1
- package/dist/{threadFns-AJP69FU6.js → threadFns-CN1R3YKa.js} +7 -7
- package/dist/{threadFns-CzWqzDN_.cjs → threadFns-YPn7KF9u.cjs} +7 -7
- package/dist/topology/apiTypes.d.ts +3 -3
- package/dist/topology/wrapperFns.d.ts +11 -2
- package/dist/topology.cjs +9 -9
- package/dist/topology.js +9 -9
- package/dist/{topologyQueryFns-svM8Yfsk.cjs → topologyQueryFns-0Ycw298Y.cjs} +1 -1
- package/dist/{topologyQueryFns-CTCsQIH_.js → topologyQueryFns-CNOqc1rB.js} +1 -1
- package/dist/{validityTypes-BCUWXQNA.cjs → validityTypes-CXnc46WG.cjs} +1 -1
- package/dist/{validityTypes-DrWg6_qM.js → validityTypes-rUpnumpL.js} +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { C as isSolid, G as _usingCtx, n as castResultShape, q as getKernel } from "./shapeTypes-
|
|
1
|
+
import { C as isSolid, G as _usingCtx, S as isShell, _ as isCompound, n as castResultShape, q as getKernel } from "./shapeTypes-DiCMRU6Z.js";
|
|
2
2
|
import { i as wasmIndex, r as vec3At$1 } from "./bug-ClMTAJhe.js";
|
|
3
3
|
import { E as ok, g as err, r as ioError, t as BrepErrorCode } from "./errors-BTccWbtE.js";
|
|
4
|
-
import { S as subShapeCount, c as getFaces, u as getShells } from "./topologyQueryFns-
|
|
4
|
+
import { S as subShapeCount, c as getFaces, u as getShells } from "./topologyQueryFns-CNOqc1rB.js";
|
|
5
5
|
import { n as HASH_CODE_MAX } from "./constants-iraOnb2p.js";
|
|
6
|
-
import { t as makeCompound } from "./solidBuilders-
|
|
7
|
-
import { u as igesUnsupportedError } from "./meshFns-
|
|
6
|
+
import { t as makeCompound } from "./solidBuilders-Dzhk5L6w.js";
|
|
7
|
+
import { u as igesUnsupportedError } from "./meshFns-BpBgKIIE.js";
|
|
8
8
|
import { n as getAtOrThrow } from "./arrayAccess-BpgouNir.js";
|
|
9
|
-
import { c as make2dBezierCurve, h as make2dThreePointArc, p as make2dSegmentCurve, t as Blueprint } from "./blueprint-
|
|
9
|
+
import { c as make2dBezierCurve, h as make2dThreePointArc, p as make2dSegmentCurve, t as Blueprint } from "./blueprint-ku_wYIZl.js";
|
|
10
10
|
// src/io/objExportFns.ts
|
|
11
11
|
/** Read a vec3 from a typed array at the given vertex index. */
|
|
12
12
|
function vec3At(arr, i) {
|
|
@@ -1314,9 +1314,20 @@ function exportSTEPConfigured(parts, options) {
|
|
|
1314
1314
|
* inward (negative signed volume, read before solidifying reorients it) that lies
|
|
1315
1315
|
* inside another body; it is cut from the innermost body around it. An outward-facing
|
|
1316
1316
|
* shell inside another is a separate part and stays one. Containment comes from ray
|
|
1317
|
-
* casts against each body's
|
|
1318
|
-
*
|
|
1317
|
+
* casts against each body's triangles, because no kernel here can classify a point
|
|
1318
|
+
* against a solid.
|
|
1319
1319
|
*/
|
|
1320
|
+
/** The outline of `body`, read from the kernel; `shell` is the sewn shell it was built from. */
|
|
1321
|
+
function shellOutline(body, shell) {
|
|
1322
|
+
let box;
|
|
1323
|
+
let mesh;
|
|
1324
|
+
const boxOf = () => box ??= getKernel().boundingBox(body.wrapped);
|
|
1325
|
+
return {
|
|
1326
|
+
box: boxOf,
|
|
1327
|
+
mesh: () => mesh ??= meshShape(body, boxOf()),
|
|
1328
|
+
facesInward: () => facesInward(shell, boxOf())
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1320
1331
|
var RAY_LENGTH = Math.hypot(.5389, .6213, .5689);
|
|
1321
1332
|
var RAY = [
|
|
1322
1333
|
.5389 / RAY_LENGTH,
|
|
@@ -1324,17 +1335,21 @@ var RAY = [
|
|
|
1324
1335
|
.5689 / RAY_LENGTH
|
|
1325
1336
|
];
|
|
1326
1337
|
/**
|
|
1327
|
-
* The parts to keep: `bodies` (each
|
|
1328
|
-
* with every cavity cut from the body around it and left out. Cut
|
|
1329
|
-
* pushed to `owned` for the caller to dispose; no input is disposed
|
|
1338
|
+
* The parts to keep: `bodies` (each described by the same-index entry of
|
|
1339
|
+
* `outlines`) with every cavity cut from the body around it and left out. Cut
|
|
1340
|
+
* results are pushed to `owned` for the caller to dispose; no input is disposed
|
|
1341
|
+
* here.
|
|
1330
1342
|
*
|
|
1331
1343
|
* @throws When meshing or a cut fails, rather than leave a cavity filled.
|
|
1332
1344
|
*/
|
|
1333
|
-
function carveCavities(bodies,
|
|
1334
|
-
const solids = bodies.flatMap((body, i) =>
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1345
|
+
function carveCavities(bodies, outlines, owned) {
|
|
1346
|
+
const solids = bodies.flatMap((body, i) => {
|
|
1347
|
+
const outline = outlines[i];
|
|
1348
|
+
return isSolid(body) && outline !== void 0 ? [{
|
|
1349
|
+
body,
|
|
1350
|
+
outline
|
|
1351
|
+
}] : [];
|
|
1352
|
+
});
|
|
1338
1353
|
if (solids.length < 2) return [...bodies];
|
|
1339
1354
|
const voids = findVoids(solids);
|
|
1340
1355
|
const carved = /* @__PURE__ */ new Map();
|
|
@@ -1348,22 +1363,10 @@ function carveCavities(bodies, shells, owned) {
|
|
|
1348
1363
|
}
|
|
1349
1364
|
/** The cavities of each body that has any, keyed by that body. */
|
|
1350
1365
|
function findVoids(solids) {
|
|
1351
|
-
const
|
|
1352
|
-
const
|
|
1353
|
-
const meshes = /* @__PURE__ */ new Map();
|
|
1354
|
-
const meshOf = (i) => {
|
|
1355
|
-
let mesh = meshes.get(i);
|
|
1356
|
-
if (mesh === void 0) {
|
|
1357
|
-
mesh = meshShape(solids[i].body, boxes[i]);
|
|
1358
|
-
meshes.set(i, mesh);
|
|
1359
|
-
}
|
|
1360
|
-
return mesh;
|
|
1361
|
-
};
|
|
1366
|
+
const boxes = solids.map(({ outline }) => outline.box());
|
|
1367
|
+
const meshOf = (i) => solids[i].outline.mesh();
|
|
1362
1368
|
const containers = solids.map((_inner, j) => solids.map((_outer, i) => i).filter((i) => i !== j && boxInside(boxes[j], boxes[i]) && insideMesh(firstVertex(meshOf(j)), meshOf(i))));
|
|
1363
|
-
const isVoid = containers.map((around, j) =>
|
|
1364
|
-
const shell = solids[j]?.shell;
|
|
1365
|
-
return around.length > 0 && shell !== void 0 && facesInward(shell, boxes[j]);
|
|
1366
|
-
});
|
|
1369
|
+
const isVoid = containers.map((around, j) => around.length > 0 && solids[j]?.outline.facesInward() === true);
|
|
1367
1370
|
const voids = /* @__PURE__ */ new Map();
|
|
1368
1371
|
containers.forEach((around, j) => {
|
|
1369
1372
|
if (!isVoid[j]) return;
|
|
@@ -1381,7 +1384,7 @@ function facesInward(shell, box) {
|
|
|
1381
1384
|
let volume = 0;
|
|
1382
1385
|
for (let i = 0; i + 2 < t.length; i += 3) {
|
|
1383
1386
|
const a = vertexAt(mesh, t[i]);
|
|
1384
|
-
volume += dot(a, cross(vertexAt(mesh, t[i + 1]), vertexAt(mesh, t[i + 2])));
|
|
1387
|
+
volume += dot(a, cross$1(vertexAt(mesh, t[i + 1]), vertexAt(mesh, t[i + 2])));
|
|
1385
1388
|
}
|
|
1386
1389
|
return volume < 0;
|
|
1387
1390
|
}
|
|
@@ -1410,11 +1413,7 @@ function boxInside(inner, outer) {
|
|
|
1410
1413
|
return inner.min.every((v, i) => v >= (outer.min[i] ?? 0) && (inner.max[i] ?? 0) <= (outer.max[i] ?? 0));
|
|
1411
1414
|
}
|
|
1412
1415
|
function firstVertex(mesh) {
|
|
1413
|
-
return [
|
|
1414
|
-
mesh.vertices[0] ?? 0,
|
|
1415
|
-
mesh.vertices[1] ?? 0,
|
|
1416
|
-
mesh.vertices[2] ?? 0
|
|
1417
|
-
];
|
|
1416
|
+
return vertexAt(mesh, mesh.triangles[0]);
|
|
1418
1417
|
}
|
|
1419
1418
|
function vertexAt(mesh, index) {
|
|
1420
1419
|
const i = (index ?? 0) * 3;
|
|
@@ -1431,32 +1430,247 @@ function insideMesh(p, mesh) {
|
|
|
1431
1430
|
for (let i = 0; i + 2 < t.length; i += 3) if (rayHits(p, vertexAt(mesh, t[i]), vertexAt(mesh, t[i + 1]), vertexAt(mesh, t[i + 2]))) crossings++;
|
|
1432
1431
|
return crossings % 2 === 1;
|
|
1433
1432
|
}
|
|
1434
|
-
var sub = (a, b) => [
|
|
1433
|
+
var sub$1 = (a, b) => [
|
|
1435
1434
|
a[0] - b[0],
|
|
1436
1435
|
a[1] - b[1],
|
|
1437
1436
|
a[2] - b[2]
|
|
1438
1437
|
];
|
|
1439
1438
|
var dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
1440
|
-
var cross = (a, b) => [
|
|
1439
|
+
var cross$1 = (a, b) => [
|
|
1441
1440
|
a[1] * b[2] - a[2] * b[1],
|
|
1442
1441
|
a[2] * b[0] - a[0] * b[2],
|
|
1443
1442
|
a[0] * b[1] - a[1] * b[0]
|
|
1444
1443
|
];
|
|
1445
1444
|
/** Moller-Trumbore: does the ray from `p` along RAY cross triangle abc ahead of `p`? */
|
|
1446
1445
|
function rayHits(p, a, b, c) {
|
|
1447
|
-
const e1 = sub(b, a);
|
|
1448
|
-
const e2 = sub(c, a);
|
|
1449
|
-
const h = cross(RAY, e2);
|
|
1446
|
+
const e1 = sub$1(b, a);
|
|
1447
|
+
const e2 = sub$1(c, a);
|
|
1448
|
+
const h = cross$1(RAY, e2);
|
|
1450
1449
|
const det = dot(e1, h);
|
|
1451
1450
|
if (det === 0) return false;
|
|
1452
|
-
const s = sub(p, a);
|
|
1451
|
+
const s = sub$1(p, a);
|
|
1453
1452
|
const u = dot(s, h) / det;
|
|
1454
1453
|
if (u < 0 || u > 1) return false;
|
|
1455
|
-
const q = cross(s, e1);
|
|
1454
|
+
const q = cross$1(s, e1);
|
|
1456
1455
|
const v = dot(RAY, q) / det;
|
|
1457
1456
|
if (v < 0 || u + v > 1) return false;
|
|
1458
1457
|
return dot(e2, q) / det > 0;
|
|
1459
1458
|
}
|
|
1459
|
+
// src/io/meshTopology.ts
|
|
1460
|
+
/**
|
|
1461
|
+
* The topology of the triangles in `positions` (nine coordinates per triangle),
|
|
1462
|
+
* welding vertices whose coordinates are identical.
|
|
1463
|
+
*
|
|
1464
|
+
* Returns `null` for a mesh that sewing might change rather than reproduce: a
|
|
1465
|
+
* non-finite coordinate, a triangle with a repeated vertex or no area, an edge
|
|
1466
|
+
* shared by more than two triangles, two triangles running the same direction
|
|
1467
|
+
* along their shared edge, or a closed body that encloses no volume.
|
|
1468
|
+
*/
|
|
1469
|
+
function analyzeMesh(positions) {
|
|
1470
|
+
const welded = weld(positions);
|
|
1471
|
+
if (welded === null) return null;
|
|
1472
|
+
const { vertices, corners } = welded;
|
|
1473
|
+
const triangleCount = corners.length / 3;
|
|
1474
|
+
if (triangleCount === 0) return null;
|
|
1475
|
+
for (let t = 0; t < triangleCount; t++) if (!hasArea(vertices, corners, t)) return null;
|
|
1476
|
+
const edges = pairEdges(corners, vertices.length / 3);
|
|
1477
|
+
if (edges === null) return null;
|
|
1478
|
+
const bodies = [];
|
|
1479
|
+
const stamp = new Int32Array(vertices.length / 3).fill(-1);
|
|
1480
|
+
for (const triangles of edges.groups) {
|
|
1481
|
+
const body = describeBody(triangles, vertices, corners, edges.boundary, stamp, bodies.length);
|
|
1482
|
+
if (body === null) return null;
|
|
1483
|
+
bodies.push(body);
|
|
1484
|
+
}
|
|
1485
|
+
return {
|
|
1486
|
+
vertexCount: vertices.length / 3,
|
|
1487
|
+
edgeCount: edges.count,
|
|
1488
|
+
bodies,
|
|
1489
|
+
vertices,
|
|
1490
|
+
corners
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
/** What nesting needs to know about a closed body of `mesh`, read from its triangles. */
|
|
1494
|
+
function meshBodyOutline(mesh, body) {
|
|
1495
|
+
const triangles = new Uint32Array(body.triangles.length * 3);
|
|
1496
|
+
body.triangles.forEach((t, i) => {
|
|
1497
|
+
triangles.set(mesh.corners.subarray(t * 3, t * 3 + 3), i * 3);
|
|
1498
|
+
});
|
|
1499
|
+
const box = {
|
|
1500
|
+
min: [
|
|
1501
|
+
Infinity,
|
|
1502
|
+
Infinity,
|
|
1503
|
+
Infinity
|
|
1504
|
+
],
|
|
1505
|
+
max: [
|
|
1506
|
+
-Infinity,
|
|
1507
|
+
-Infinity,
|
|
1508
|
+
-Infinity
|
|
1509
|
+
]
|
|
1510
|
+
};
|
|
1511
|
+
for (const v of triangles) for (let axis = 0; axis < 3; axis++) {
|
|
1512
|
+
const c = mesh.vertices[v * 3 + axis] ?? 0;
|
|
1513
|
+
if (c < (box.min[axis] ?? 0)) box.min[axis] = c;
|
|
1514
|
+
if (c > (box.max[axis] ?? 0)) box.max[axis] = c;
|
|
1515
|
+
}
|
|
1516
|
+
return {
|
|
1517
|
+
box: () => box,
|
|
1518
|
+
mesh: () => ({
|
|
1519
|
+
vertices: mesh.vertices,
|
|
1520
|
+
triangles
|
|
1521
|
+
}),
|
|
1522
|
+
facesInward: () => body.volume < 0
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
function weld(positions) {
|
|
1526
|
+
if (positions.length % 9 !== 0) return null;
|
|
1527
|
+
const corners = new Uint32Array(positions.length / 3);
|
|
1528
|
+
const index = /* @__PURE__ */ new Map();
|
|
1529
|
+
const coords = [];
|
|
1530
|
+
for (let i = 0; i < corners.length; i++) {
|
|
1531
|
+
const x = positions[i * 3] ?? NaN;
|
|
1532
|
+
const y = positions[i * 3 + 1] ?? NaN;
|
|
1533
|
+
const z = positions[i * 3 + 2] ?? NaN;
|
|
1534
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) return null;
|
|
1535
|
+
const key = `${x},${y},${z}`;
|
|
1536
|
+
let v = index.get(key);
|
|
1537
|
+
if (v === void 0) {
|
|
1538
|
+
v = index.size;
|
|
1539
|
+
index.set(key, v);
|
|
1540
|
+
coords.push(x, y, z);
|
|
1541
|
+
}
|
|
1542
|
+
corners[i] = v;
|
|
1543
|
+
}
|
|
1544
|
+
return {
|
|
1545
|
+
vertices: Float64Array.from(coords),
|
|
1546
|
+
corners
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
function hasArea(vertices, corners, t) {
|
|
1550
|
+
const a = corners[t * 3] ?? 0;
|
|
1551
|
+
const b = corners[t * 3 + 1] ?? 0;
|
|
1552
|
+
const c = corners[t * 3 + 2] ?? 0;
|
|
1553
|
+
if (a === b || b === c || c === a) return false;
|
|
1554
|
+
const n = cross(sub(point(vertices, b), point(vertices, a)), sub(point(vertices, c), point(vertices, a)));
|
|
1555
|
+
return n[0] !== 0 || n[1] !== 0 || n[2] !== 0;
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Match every edge with the triangle on its other side. Groups triangles joined
|
|
1559
|
+
* by shared edges, and counts each triangle's unshared (boundary) edges.
|
|
1560
|
+
*/
|
|
1561
|
+
function pairEdges(corners, vertexCount) {
|
|
1562
|
+
const triangleCount = corners.length / 3;
|
|
1563
|
+
const parent = new Int32Array(triangleCount).map((_, i) => i);
|
|
1564
|
+
const find = (t) => {
|
|
1565
|
+
let root = t;
|
|
1566
|
+
while (parent[root] !== root) root = parent[root] ?? root;
|
|
1567
|
+
while (parent[t] !== root) {
|
|
1568
|
+
const next = parent[t] ?? root;
|
|
1569
|
+
parent[t] = root;
|
|
1570
|
+
t = next;
|
|
1571
|
+
}
|
|
1572
|
+
return root;
|
|
1573
|
+
};
|
|
1574
|
+
const open = /* @__PURE__ */ new Map();
|
|
1575
|
+
for (let t = 0; t < triangleCount; t++) for (let k = 0; k < 3; k++) {
|
|
1576
|
+
const a = corners[t * 3 + k] ?? 0;
|
|
1577
|
+
const b = corners[t * 3 + (k + 1) % 3] ?? 0;
|
|
1578
|
+
const forward = a < b;
|
|
1579
|
+
const key = forward ? a * vertexCount + b : b * vertexCount + a;
|
|
1580
|
+
const first = open.get(key);
|
|
1581
|
+
if (first === void 0) {
|
|
1582
|
+
open.set(key, forward ? t + 1 : -(t + 1));
|
|
1583
|
+
continue;
|
|
1584
|
+
}
|
|
1585
|
+
if (first === 0 || first > 0 === forward) return null;
|
|
1586
|
+
parent[find(t)] = find(Math.abs(first) - 1);
|
|
1587
|
+
open.set(key, 0);
|
|
1588
|
+
}
|
|
1589
|
+
const boundary = new Uint8Array(triangleCount);
|
|
1590
|
+
for (const first of open.values()) {
|
|
1591
|
+
const t = Math.abs(first) - 1;
|
|
1592
|
+
if (first !== 0) boundary[t] = (boundary[t] ?? 0) + 1;
|
|
1593
|
+
}
|
|
1594
|
+
const groupOf = /* @__PURE__ */ new Map();
|
|
1595
|
+
for (let t = 0; t < triangleCount; t++) {
|
|
1596
|
+
const root = find(t);
|
|
1597
|
+
const group = groupOf.get(root);
|
|
1598
|
+
if (group) group.push(t);
|
|
1599
|
+
else groupOf.set(root, [t]);
|
|
1600
|
+
}
|
|
1601
|
+
return {
|
|
1602
|
+
count: open.size,
|
|
1603
|
+
groups: [...groupOf.values()],
|
|
1604
|
+
boundary
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
function describeBody(triangles, vertices, corners, boundary, stamp, id) {
|
|
1608
|
+
let vertexCount = 0;
|
|
1609
|
+
let boundaryEdges = 0;
|
|
1610
|
+
for (const t of triangles) {
|
|
1611
|
+
boundaryEdges += boundary[t] ?? 0;
|
|
1612
|
+
for (let k = 0; k < 3; k++) {
|
|
1613
|
+
const v = corners[t * 3 + k] ?? 0;
|
|
1614
|
+
if (stamp[v] !== id) {
|
|
1615
|
+
stamp[v] = id;
|
|
1616
|
+
vertexCount++;
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
const closed = boundaryEdges === 0;
|
|
1621
|
+
let volume = 0;
|
|
1622
|
+
if (closed) {
|
|
1623
|
+
const enclosed = enclosedVolume(triangles, vertices, corners);
|
|
1624
|
+
if (Math.abs(enclosed.value) <= FLAT_VOLUME * enclosed.size ** 3) return null;
|
|
1625
|
+
volume = enclosed.value;
|
|
1626
|
+
}
|
|
1627
|
+
return {
|
|
1628
|
+
triangles,
|
|
1629
|
+
vertexCount,
|
|
1630
|
+
edgeCount: (triangles.length * 3 + boundaryEdges) / 2,
|
|
1631
|
+
closed,
|
|
1632
|
+
volume
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
/** Below this fraction of its size cubed, a closed body is a flat sheet with no inside. */
|
|
1636
|
+
var FLAT_VOLUME = 1e-12;
|
|
1637
|
+
/**
|
|
1638
|
+
* Signed volume by the divergence theorem, measured from the body's first vertex
|
|
1639
|
+
* so a body far from the origin keeps its precision, and the body's size: its
|
|
1640
|
+
* farthest coordinate offset from that vertex.
|
|
1641
|
+
*/
|
|
1642
|
+
function enclosedVolume(triangles, vertices, corners) {
|
|
1643
|
+
const origin = point(vertices, corners[(triangles[0] ?? 0) * 3] ?? 0);
|
|
1644
|
+
let sum = 0;
|
|
1645
|
+
let size = 0;
|
|
1646
|
+
for (const t of triangles) {
|
|
1647
|
+
const a = sub(point(vertices, corners[t * 3] ?? 0), origin);
|
|
1648
|
+
const n = cross(sub(point(vertices, corners[t * 3 + 1] ?? 0), origin), sub(point(vertices, corners[t * 3 + 2] ?? 0), origin));
|
|
1649
|
+
sum += a[0] * n[0] + a[1] * n[1] + a[2] * n[2];
|
|
1650
|
+
size = Math.max(size, Math.abs(a[0]), Math.abs(a[1]), Math.abs(a[2]));
|
|
1651
|
+
}
|
|
1652
|
+
return {
|
|
1653
|
+
value: sum / 6,
|
|
1654
|
+
size
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
function point(vertices, v) {
|
|
1658
|
+
return [
|
|
1659
|
+
vertices[v * 3] ?? 0,
|
|
1660
|
+
vertices[v * 3 + 1] ?? 0,
|
|
1661
|
+
vertices[v * 3 + 2] ?? 0
|
|
1662
|
+
];
|
|
1663
|
+
}
|
|
1664
|
+
var sub = (a, b) => [
|
|
1665
|
+
a[0] - b[0],
|
|
1666
|
+
a[1] - b[1],
|
|
1667
|
+
a[2] - b[2]
|
|
1668
|
+
];
|
|
1669
|
+
var cross = (a, b) => [
|
|
1670
|
+
a[1] * b[2] - a[2] * b[1],
|
|
1671
|
+
a[2] * b[0] - a[0] * b[2],
|
|
1672
|
+
a[0] * b[1] - a[1] * b[0]
|
|
1673
|
+
];
|
|
1460
1674
|
// src/io/ioUtils.ts
|
|
1461
1675
|
/**
|
|
1462
1676
|
* Shared mesh-to-solid sewing utilities for mesh importers.
|
|
@@ -1473,11 +1687,21 @@ function rayHits(p, a, b, c) {
|
|
|
1473
1687
|
* inward-facing closed shell inside another body is a cavity, cut from that
|
|
1474
1688
|
* body. The caller keeps ownership of `faces`.
|
|
1475
1689
|
*
|
|
1690
|
+
* Pass `mesh`, the {@link sewableTopology} of the triangles `faces` were built
|
|
1691
|
+
* from (face `i` from triangle `i`), to skip the kernel's orientation
|
|
1692
|
+
* repair, which is nearly all of the time: when the sewn faces have exactly the
|
|
1693
|
+
* vertices, edges, and shells it predicts, each closed body becomes a solid
|
|
1694
|
+
* straight from its shell.
|
|
1695
|
+
*
|
|
1476
1696
|
* @throws When the kernel cannot sew the faces at all.
|
|
1477
1697
|
*/
|
|
1478
|
-
function sewFacesIntoSolids(faces, tolerance) {
|
|
1698
|
+
function sewFacesIntoSolids(faces, tolerance, mesh) {
|
|
1479
1699
|
try {
|
|
1480
1700
|
var _usingCtx$2 = _usingCtx();
|
|
1701
|
+
if (mesh) {
|
|
1702
|
+
const sewn = sewKnownMesh(faces, tolerance, mesh);
|
|
1703
|
+
if (sewn !== null) return sewn;
|
|
1704
|
+
}
|
|
1481
1705
|
const kernel = getKernel();
|
|
1482
1706
|
const solid = solidify(faces, tolerance);
|
|
1483
1707
|
if (solid !== null) return solid;
|
|
@@ -1488,11 +1712,15 @@ function sewFacesIntoSolids(faces, tolerance) {
|
|
|
1488
1712
|
_usingCtx$2.u(sewn);
|
|
1489
1713
|
const owned = [];
|
|
1490
1714
|
try {
|
|
1491
|
-
const
|
|
1715
|
+
const bodies = [];
|
|
1716
|
+
const outlines = [];
|
|
1717
|
+
for (const shell of shells) {
|
|
1492
1718
|
const body = solidifyShell(shell.wrapped, tolerance);
|
|
1493
1719
|
owned.push(body);
|
|
1494
|
-
|
|
1495
|
-
|
|
1720
|
+
bodies.push(body);
|
|
1721
|
+
outlines.push(shellOutline(body, shell));
|
|
1722
|
+
}
|
|
1723
|
+
const parts = carveCavities(bodies, outlines, owned);
|
|
1496
1724
|
if (hasLooseFaces) for (const face of facesOutsideShells(sewn, shells)) {
|
|
1497
1725
|
const copy = castResultShape(kernel.copyShape(face.wrapped));
|
|
1498
1726
|
owned.push(copy);
|
|
@@ -1508,6 +1736,96 @@ function sewFacesIntoSolids(faces, tolerance) {
|
|
|
1508
1736
|
_usingCtx$2.d();
|
|
1509
1737
|
}
|
|
1510
1738
|
}
|
|
1739
|
+
/**
|
|
1740
|
+
* {@link sewFacesIntoSolids} for a mesh whose topology is known, or `null` when
|
|
1741
|
+
* the kernel's sewing doesn't reproduce it exactly (say, it merged two vertices
|
|
1742
|
+
* within tolerance, or a triangle built no face), leaving the caller to repair.
|
|
1743
|
+
*/
|
|
1744
|
+
function sewKnownMesh(faces, tolerance, mesh) {
|
|
1745
|
+
try {
|
|
1746
|
+
var _usingCtx3 = _usingCtx();
|
|
1747
|
+
if (faces.length !== mesh.corners.length / 3) return null;
|
|
1748
|
+
const sewn = castResultShape(getKernel().sew(faces, tolerance));
|
|
1749
|
+
const shellBodies = mesh.bodies.filter((body) => body.triangles.length > 1);
|
|
1750
|
+
if (!hasTopology(sewn, mesh.vertexCount, mesh.edgeCount, faces.length) || getShells(sewn).length !== shellBodies.length) {
|
|
1751
|
+
sewn[Symbol.dispose]();
|
|
1752
|
+
return null;
|
|
1753
|
+
}
|
|
1754
|
+
const [single, ...more] = mesh.bodies;
|
|
1755
|
+
if (single === void 0 || shellBodies.length === 0) return sewn;
|
|
1756
|
+
if (more.length > 0) try {
|
|
1757
|
+
var _usingCtx4 = _usingCtx();
|
|
1758
|
+
_usingCtx4.u(sewn);
|
|
1759
|
+
return sewBodies(faces, tolerance, mesh);
|
|
1760
|
+
} catch (_) {
|
|
1761
|
+
_usingCtx4.e = _;
|
|
1762
|
+
} finally {
|
|
1763
|
+
_usingCtx4.d();
|
|
1764
|
+
}
|
|
1765
|
+
if (!single.closed) return sewn;
|
|
1766
|
+
const shell = _usingCtx3.u(sewn);
|
|
1767
|
+
return isShell(shell) ? outwardSolid(shell, single.volume) : null;
|
|
1768
|
+
} catch (_) {
|
|
1769
|
+
_usingCtx3.e = _;
|
|
1770
|
+
} finally {
|
|
1771
|
+
_usingCtx3.d();
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
/** Each body of `mesh` sewn on its own, so its triangles say which way it faces. */
|
|
1775
|
+
function sewBodies(faces, tolerance, mesh) {
|
|
1776
|
+
const kernel = getKernel();
|
|
1777
|
+
const owned = [];
|
|
1778
|
+
try {
|
|
1779
|
+
const bodies = [];
|
|
1780
|
+
const outlines = [];
|
|
1781
|
+
const looseFaces = [];
|
|
1782
|
+
for (const body of mesh.bodies) {
|
|
1783
|
+
const own = body.triangles.flatMap((t) => faces[t] ?? []);
|
|
1784
|
+
const [face] = own;
|
|
1785
|
+
if (own.length === 1 && face !== void 0) {
|
|
1786
|
+
const copy = castResultShape(kernel.copyShape(face));
|
|
1787
|
+
owned.push(copy);
|
|
1788
|
+
looseFaces.push(copy);
|
|
1789
|
+
continue;
|
|
1790
|
+
}
|
|
1791
|
+
const shell = castResultShape(kernel.sew(own, tolerance));
|
|
1792
|
+
owned.push(shell);
|
|
1793
|
+
if (!isShell(shell) || !hasTopology(shell, body.vertexCount, body.edgeCount, own.length)) return null;
|
|
1794
|
+
if (!body.closed) {
|
|
1795
|
+
bodies.push(shell);
|
|
1796
|
+
outlines.push(void 0);
|
|
1797
|
+
continue;
|
|
1798
|
+
}
|
|
1799
|
+
const solid = outwardSolid(shell, body.volume);
|
|
1800
|
+
owned.push(solid);
|
|
1801
|
+
bodies.push(solid);
|
|
1802
|
+
outlines.push(meshBodyOutline(mesh, body));
|
|
1803
|
+
}
|
|
1804
|
+
return makeCompound([...carveCavities(bodies, outlines, owned), ...looseFaces]);
|
|
1805
|
+
} finally {
|
|
1806
|
+
for (const shape of owned) shape[Symbol.dispose]();
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
function hasTopology(shape, vertices, edges, faces) {
|
|
1810
|
+
return subShapeCount(shape, "face") === faces && subShapeCount(shape, "edge") === edges && subShapeCount(shape, "vertex") === vertices;
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* The solid bounded by a consistently oriented closed shell, turned outward when
|
|
1814
|
+
* the shell's triangles face in (`volume` negative).
|
|
1815
|
+
*/
|
|
1816
|
+
function outwardSolid(shell, volume) {
|
|
1817
|
+
try {
|
|
1818
|
+
var _usingCtx5 = _usingCtx();
|
|
1819
|
+
const kernel = getKernel();
|
|
1820
|
+
if (volume > 0) return castResultShape(kernel.solidFromShell(shell.wrapped));
|
|
1821
|
+
const reversed = _usingCtx5.u(castResultShape(kernel.reverseShape(shell.wrapped)));
|
|
1822
|
+
return castResultShape(kernel.solidFromShell(reversed.wrapped));
|
|
1823
|
+
} catch (_) {
|
|
1824
|
+
_usingCtx5.e = _;
|
|
1825
|
+
} finally {
|
|
1826
|
+
_usingCtx5.d();
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1511
1829
|
function facesOutsideShells(sewn, shells) {
|
|
1512
1830
|
const kernel = getKernel();
|
|
1513
1831
|
const inShells = /* @__PURE__ */ new Map();
|
|
@@ -1548,17 +1866,78 @@ function solidifyShell(shell, tolerance) {
|
|
|
1548
1866
|
}
|
|
1549
1867
|
}
|
|
1550
1868
|
/**
|
|
1551
|
-
*
|
|
1869
|
+
* A compound of one face per triangle, in order, sharing vertices and edges so
|
|
1870
|
+
* that sewing it is cheap. The kernel's STL reader is the only way to build
|
|
1871
|
+
* faces that share them; a text STL carries every coordinate exactly, because a
|
|
1872
|
+
* number prints as the shortest decimal that parses back to it. `null` when the
|
|
1873
|
+
* kernel returns anything else (some read an STL straight into a solid).
|
|
1874
|
+
*/
|
|
1875
|
+
function readerFacets(triangles) {
|
|
1876
|
+
const lines = ["solid mesh"];
|
|
1877
|
+
for (const [a, b, c] of triangles) lines.push("facet normal 0 0 0", "outer loop", `vertex ${a[0]} ${a[1]} ${a[2]}`, `vertex ${b[0]} ${b[1]} ${b[2]}`, `vertex ${c[0]} ${c[1]} ${c[2]}`, "endloop", "endfacet");
|
|
1878
|
+
lines.push("endsolid mesh", "");
|
|
1879
|
+
let facets;
|
|
1880
|
+
try {
|
|
1881
|
+
facets = castResultShape(getKernel().importSTL(lines.join("\n")));
|
|
1882
|
+
} catch {
|
|
1883
|
+
return null;
|
|
1884
|
+
}
|
|
1885
|
+
if (isCompound(facets) && subShapeCount(facets, "face") === triangles.length) return facets;
|
|
1886
|
+
facets[Symbol.dispose]();
|
|
1887
|
+
return null;
|
|
1888
|
+
}
|
|
1889
|
+
/**
|
|
1890
|
+
* The {@link analyzeMesh} topology of the triangles in `positions` (nine
|
|
1891
|
+
* coordinates per triangle) for {@link sewFacesIntoSolids}, or `null` when the
|
|
1892
|
+
* mesh fails the analysis or the kernel's sewing may reorient faces.
|
|
1893
|
+
*/
|
|
1894
|
+
function sewableTopology(positions) {
|
|
1895
|
+
if (positions === null || getKernel().capabilities.sewKeepsOrientation !== true) return null;
|
|
1896
|
+
return analyzeMesh(positions);
|
|
1897
|
+
}
|
|
1898
|
+
/** {@link sewableTopology} of `triangles`, without copying them on a kernel that can't use it. */
|
|
1899
|
+
function sewableTriangles(triangles) {
|
|
1900
|
+
if (getKernel().capabilities.sewKeepsOrientation !== true) return null;
|
|
1901
|
+
return sewableTopology(trianglePositions(triangles));
|
|
1902
|
+
}
|
|
1903
|
+
function trianglePositions(triangles) {
|
|
1904
|
+
const positions = new Float64Array(triangles.length * 9);
|
|
1905
|
+
triangles.forEach((triangle, t) => {
|
|
1906
|
+
triangle.forEach((vertex, k) => {
|
|
1907
|
+
positions.set(vertex, t * 9 + k * 3);
|
|
1908
|
+
});
|
|
1909
|
+
});
|
|
1910
|
+
return positions;
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* Build triangular B-Rep faces from vertex data and sew them into solids with
|
|
1914
|
+
* {@link sewFacesIntoSolids}.
|
|
1552
1915
|
*
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1916
|
+
* A mesh with a {@link sewableTopology} gets its faces from the kernel's STL
|
|
1917
|
+
* reader, already sharing edges, when the kernel reads an STL into faces.
|
|
1918
|
+
* Otherwise `kernel.buildTriFace` builds a planar face per triangle.
|
|
1555
1919
|
*
|
|
1556
1920
|
* @param triangles - Array of vertex triples, each triple being three [x,y,z] tuples.
|
|
1557
1921
|
* @param errorCode - The error code to use in any returned errors.
|
|
1558
1922
|
* @param tolerance - Sewing tolerance (default 1e-6).
|
|
1923
|
+
* @param mesh - The triangles' {@link sewableTopology}, when the caller already has it.
|
|
1559
1924
|
* @returns A `Result` wrapping the sewn shape, or an error.
|
|
1560
1925
|
*/
|
|
1561
|
-
function sewMeshToSolid(triangles, errorCode, tolerance = 1e-6) {
|
|
1926
|
+
function sewMeshToSolid(triangles, errorCode, tolerance = 1e-6, mesh = sewableTriangles(triangles)) {
|
|
1927
|
+
const facets = mesh ? readerFacets(triangles) : null;
|
|
1928
|
+
if (facets !== null) try {
|
|
1929
|
+
var _usingCtx6 = _usingCtx();
|
|
1930
|
+
_usingCtx6.u(facets);
|
|
1931
|
+
try {
|
|
1932
|
+
return ok(sewFacesIntoSolids(getFaces(facets).map((face) => face.wrapped), tolerance, mesh));
|
|
1933
|
+
} catch (e) {
|
|
1934
|
+
return err(ioError(errorCode, "Failed to sew triangular faces", e));
|
|
1935
|
+
}
|
|
1936
|
+
} catch (_) {
|
|
1937
|
+
_usingCtx6.e = _;
|
|
1938
|
+
} finally {
|
|
1939
|
+
_usingCtx6.d();
|
|
1940
|
+
}
|
|
1562
1941
|
const kernel = getKernel();
|
|
1563
1942
|
const triFaces = [];
|
|
1564
1943
|
for (const [va, vb, vc] of triangles) {
|
|
@@ -1567,13 +1946,53 @@ function sewMeshToSolid(triangles, errorCode, tolerance = 1e-6) {
|
|
|
1567
1946
|
}
|
|
1568
1947
|
if (triFaces.length === 0) return err(ioError(errorCode, "No valid triangular faces could be built"));
|
|
1569
1948
|
try {
|
|
1570
|
-
return ok(sewFacesIntoSolids(triFaces, tolerance));
|
|
1949
|
+
return ok(sewFacesIntoSolids(triFaces, tolerance, mesh));
|
|
1571
1950
|
} catch (e) {
|
|
1572
1951
|
return err(ioError(errorCode, "Failed to sew triangular faces", e));
|
|
1573
1952
|
} finally {
|
|
1574
1953
|
for (const triFace of triFaces) kernel.dispose(triFace);
|
|
1575
1954
|
}
|
|
1576
1955
|
}
|
|
1956
|
+
// src/io/stlTriangles.ts
|
|
1957
|
+
/**
|
|
1958
|
+
* The triangles of an STL file, read in JavaScript so the importer can check the
|
|
1959
|
+
* mesh's topology before the kernel sews it.
|
|
1960
|
+
*/
|
|
1961
|
+
var HEADER_BYTES = 80;
|
|
1962
|
+
var FACET_BYTES = 50;
|
|
1963
|
+
/** OCCT's reader takes anything shorter than one binary facet as text. */
|
|
1964
|
+
var MIN_BINARY_BYTES = 134;
|
|
1965
|
+
var VERTEX_LINE = /^[ \t]*vertex[ \t]+(\S+)[ \t]+(\S+)[ \t]+(\S+)/gim;
|
|
1966
|
+
/**
|
|
1967
|
+
* The corner coordinates of every facet, nine per triangle in file order, or
|
|
1968
|
+
* `null` when the file is malformed. Text versus binary is decided as OCCT's
|
|
1969
|
+
* reader decides it: binary when any of the first 134 bytes is above `~`.
|
|
1970
|
+
*/
|
|
1971
|
+
function stlTrianglePositions(data) {
|
|
1972
|
+
const bytes = new Uint8Array(data);
|
|
1973
|
+
return isText(bytes) ? textPositions(bytes) : binaryPositions(data);
|
|
1974
|
+
}
|
|
1975
|
+
function isText(bytes) {
|
|
1976
|
+
if (bytes.length < MIN_BINARY_BYTES) return true;
|
|
1977
|
+
for (let i = 0; i < MIN_BINARY_BYTES; i++) if ((bytes[i] ?? 0) > 126) return false;
|
|
1978
|
+
return true;
|
|
1979
|
+
}
|
|
1980
|
+
function textPositions(bytes) {
|
|
1981
|
+
const coords = [];
|
|
1982
|
+
for (const [, x, y, z] of new TextDecoder().decode(bytes).matchAll(VERTEX_LINE)) coords.push(Number(x), Number(y), Number(z));
|
|
1983
|
+
return coords.length % 9 === 0 ? Float64Array.from(coords) : null;
|
|
1984
|
+
}
|
|
1985
|
+
function binaryPositions(data) {
|
|
1986
|
+
const view = new DataView(data);
|
|
1987
|
+
const count = view.getUint32(HEADER_BYTES, true);
|
|
1988
|
+
if (84 + count * FACET_BYTES > data.byteLength) return null;
|
|
1989
|
+
const positions = new Float64Array(count * 9);
|
|
1990
|
+
for (let t = 0; t < count; t++) {
|
|
1991
|
+
const corners = 84 + t * FACET_BYTES + 12;
|
|
1992
|
+
for (let k = 0; k < 9; k++) positions[t * 9 + k] = view.getFloat32(corners + k * 4, true);
|
|
1993
|
+
}
|
|
1994
|
+
return positions;
|
|
1995
|
+
}
|
|
1577
1996
|
// src/io/importFns.ts
|
|
1578
1997
|
/**
|
|
1579
1998
|
* Functional file import operations using branded shape types.
|
|
@@ -1625,10 +2044,12 @@ var STL_SEW_TOLERANCE = 1e-6;
|
|
|
1625
2044
|
* compound for several bodies), or an error if parsing fails.
|
|
1626
2045
|
*
|
|
1627
2046
|
* @remarks Every triangle becomes its own B-Rep face, so import time grows with
|
|
1628
|
-
* the triangle count
|
|
1629
|
-
*
|
|
1630
|
-
*
|
|
1631
|
-
*
|
|
2047
|
+
* the triangle count. On the default occt-wasm kernel a mesh whose shared edges
|
|
2048
|
+
* each join two consistently wound triangles takes about 0.3 seconds per 10,000
|
|
2049
|
+
* triangles (about 1.7 seconds for 50,000). One that needs its winding repaired
|
|
2050
|
+
* takes about 2 seconds per 10,000 (about 13 seconds for 50,000), and up to
|
|
2051
|
+
* about twice that when the file holds several bodies. The temporary file on
|
|
2052
|
+
* the WASM FS is cleaned up automatically.
|
|
1632
2053
|
*
|
|
1633
2054
|
* @example
|
|
1634
2055
|
* ```ts
|
|
@@ -1646,7 +2067,8 @@ async function importSTL(blob) {
|
|
|
1646
2067
|
if (isSolid(shape)) return ok(shape);
|
|
1647
2068
|
const facets = _usingCtx$1.u(shape);
|
|
1648
2069
|
const faces = getFaces(facets).map((f) => f.wrapped);
|
|
1649
|
-
|
|
2070
|
+
const mesh = sewableTopology(stlTrianglePositions(data));
|
|
2071
|
+
return ok(sewFacesIntoSolids(faces, STL_SEW_TOLERANCE, mesh));
|
|
1650
2072
|
} catch (_) {
|
|
1651
2073
|
_usingCtx$1.e = _;
|
|
1652
2074
|
} finally {
|