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.
Files changed (106) hide show
  1. package/dist/2d/blueprints/blueprintFns.d.ts +2 -0
  2. package/dist/2d.cjs +6 -6
  3. package/dist/2d.js +6 -6
  4. package/dist/{adjacencyFns-Djv832MP.cjs → adjacencyFns-BHdJW5pd.cjs} +3 -3
  5. package/dist/{adjacencyFns-DBN26xm_.js → adjacencyFns-C4yjxKu4.js} +3 -3
  6. package/dist/{blueprint-nVQsEilp.cjs → blueprint-D4juQWXs.cjs} +5 -5
  7. package/dist/{blueprint-BTccTqFy.js → blueprint-ku_wYIZl.js} +5 -5
  8. package/dist/{blueprintFns-DFDOgqqe.cjs → blueprintFns-8obtvMEn.cjs} +4 -5
  9. package/dist/{blueprintFns-DX7yLZpR.js → blueprintFns-B0cqtoFB.js} +4 -5
  10. package/dist/{blueprintSketcher-CoGp1dM9.cjs → blueprintSketcher-CRFpnZAS.cjs} +3 -3
  11. package/dist/{blueprintSketcher-rk64lh8e.js → blueprintSketcher-Dpdgf5aR.js} +3 -3
  12. package/dist/{boolean2D-Dv-57m9d.js → boolean2D-CnCDsRJ4.js} +4 -4
  13. package/dist/{boolean2D-DP2DiMfT.cjs → boolean2D-D3jxohLZ.cjs} +4 -4
  14. package/dist/{booleanFns-CjIZaNBh.js → booleanFns-CItcObM_.js} +5 -5
  15. package/dist/{booleanFns-ewBwy7V_.cjs → booleanFns-GBPAGQ5g.cjs} +5 -5
  16. package/dist/brepjs.cjs +59 -61
  17. package/dist/brepjs.js +59 -61
  18. package/dist/{cameraFns-D0JDXxwO.js → cameraFns-B6ujpyKy.js} +3 -3
  19. package/dist/{cameraFns-DmIQsPIr.cjs → cameraFns-C2XhSqrr.cjs} +3 -3
  20. package/dist/core/shapeTypes.d.ts +8 -4
  21. package/dist/core.cjs +2 -2
  22. package/dist/core.js +2 -2
  23. package/dist/{cornerFinder-CZ1XbVx7.cjs → cornerFinder-CqHx_Itj.cjs} +1 -1
  24. package/dist/{cornerFinder-Df7ajnlz.js → cornerFinder-D3nBhdfi.js} +1 -1
  25. package/dist/{curveFns-DK5eG57I.cjs → curveFns-BQjw0VBB.cjs} +1 -1
  26. package/dist/{curveFns-CUrIF9LG.js → curveFns-nAwtilbz.js} +1 -1
  27. package/dist/{drawFns-BKKWQ4JW.js → drawFns-D9q4Hxc_.js} +12 -12
  28. package/dist/{drawFns-B2CPxqd1.cjs → drawFns-hFjTvdw4.cjs} +12 -12
  29. package/dist/{faceFns-C1Ii1MJ-.cjs → faceFns-DdMpocg9.cjs} +2 -2
  30. package/dist/{faceFns-B3vblI_s.js → faceFns-Dodcduw8.js} +2 -2
  31. package/dist/{healingFns-sfguIlrW.cjs → healingFns-Cdpu44d0.cjs} +5 -5
  32. package/dist/{healingFns-CqiHrJcx.js → healingFns-GNF1rFuw.js} +5 -5
  33. package/dist/{helpers-Dy1moMMd.js → helpers-CoVtL5c9.js} +6 -6
  34. package/dist/{helpers-CTxyRIoE.cjs → helpers-TWIfnyto.cjs} +6 -6
  35. package/dist/{importFns-BXiAHbyn.js → importFns-C9TUFscx.js} +479 -57
  36. package/dist/{importFns-C3ILnGTv.cjs → importFns-P-TcOJ1G.cjs} +479 -57
  37. package/dist/io/importFns.d.ts +6 -4
  38. package/dist/io/ioUtils.d.ts +23 -5
  39. package/dist/io/meshNesting.d.ts +25 -4
  40. package/dist/io/meshTopology.d.ts +33 -0
  41. package/dist/io/stlTriangles.d.ts +10 -0
  42. package/dist/io/threemfImportFns.d.ts +9 -6
  43. package/dist/io.cjs +2 -2
  44. package/dist/io.js +2 -2
  45. package/dist/kernel/capabilities.d.ts +9 -1
  46. package/dist/kernel/interfaces/evolutionOps.d.ts +1 -0
  47. package/dist/kernel/interfaces/transformOps.d.ts +7 -2
  48. package/dist/kernel/occt/transformOps.d.ts +1 -1
  49. package/dist/kernel/occtWasm/occtWasmAdapter.cjs +1 -1
  50. package/dist/kernel/occtWasm/occtWasmAdapter.js +1 -1
  51. package/dist/{loftFns-BJqF1p_f.js → loftFns-DVsWk-9v.js} +19 -52
  52. package/dist/{loftFns-CzERU_TY.cjs → loftFns-Uq6BhPVL.cjs} +19 -52
  53. package/dist/{measureFns-DN5YMfKZ.cjs → measureFns-B0Cy7GcQ.cjs} +3 -3
  54. package/dist/{measureFns-CT2TRojo.js → measureFns-DoUWDfAP.js} +3 -3
  55. package/dist/measurement.cjs +2 -2
  56. package/dist/measurement.js +2 -2
  57. package/dist/{meshFns-Cp8uEpVt.js → meshFns-BpBgKIIE.js} +4 -4
  58. package/dist/{meshFns-BDn_nIVv.cjs → meshFns-Bz9PPZWP.cjs} +4 -4
  59. package/dist/{occtWasmAdapter-C-Qi-jxS.js → occtWasmAdapter-DeVQrL5n.js} +4 -3
  60. package/dist/{occtWasmAdapter-BK--kx4R.cjs → occtWasmAdapter-DzxyXpLG.cjs} +4 -3
  61. package/dist/operations/instanceFns.d.ts +3 -2
  62. package/dist/operations/sweepFns.d.ts +15 -3
  63. package/dist/operations.cjs +2 -2
  64. package/dist/operations.js +2 -2
  65. package/dist/{primitiveFns-BzJi89Y6.js → primitiveFns-Crqsdby_.js} +5 -5
  66. package/dist/{primitiveFns-1FcmvZ6Y.cjs → primitiveFns-DE1RROq-.cjs} +5 -5
  67. package/dist/{principalBoxFns-DT4ngNuy.cjs → principalBoxFns-CzEBI6tC.cjs} +3 -3
  68. package/dist/{principalBoxFns-farY7tvh.js → principalBoxFns-DwitCsif.js} +3 -3
  69. package/dist/projection.cjs +1 -1
  70. package/dist/projection.js +1 -1
  71. package/dist/query.cjs +2 -2
  72. package/dist/query.js +2 -2
  73. package/dist/{refResolveFns-dNAt7W9t.js → refResolveFns-BJz9UvXM.js} +6 -6
  74. package/dist/{refResolveFns-By31fyEv.cjs → refResolveFns-DgM_B9WU.cjs} +6 -6
  75. package/dist/{shapeFns-DhTfkxbN.cjs → shapeFns-B9LB54_t.cjs} +2 -2
  76. package/dist/{shapeFns-381QqZ8n.js → shapeFns-Ddh0M4Iz.js} +2 -2
  77. package/dist/shapeRef.cjs +1 -1
  78. package/dist/shapeRef.js +1 -1
  79. package/dist/{shapeTypes-C0NcoN5g.cjs → shapeTypes-C1Ljs72p.cjs} +20 -18
  80. package/dist/{shapeTypes-DfBx__EF.js → shapeTypes-DiCMRU6Z.js} +20 -18
  81. package/dist/sketching/compoundSketch.d.ts +3 -2
  82. package/dist/sketching/sketch.d.ts +5 -4
  83. package/dist/sketching/sketches.d.ts +3 -2
  84. package/dist/sketching.cjs +3 -3
  85. package/dist/sketching.js +3 -3
  86. package/dist/{solidBuilders-VPkj5ChQ.cjs → solidBuilders-C6_6tZXG.cjs} +3 -3
  87. package/dist/{solidBuilders-BZ1qXlFZ.js → solidBuilders-Dzhk5L6w.js} +3 -3
  88. package/dist/{surfaceBuilders-DQre19g8.cjs → surfaceBuilders-CU_vd71e.cjs} +3 -3
  89. package/dist/{surfaceBuilders-sBre5aYy.js → surfaceBuilders-Idshk6C3.js} +3 -3
  90. package/dist/text.cjs +2 -2
  91. package/dist/text.js +2 -2
  92. package/dist/{textBlueprints-ChDNr0L4.cjs → textBlueprints-BmIxy1fi.cjs} +8 -8
  93. package/dist/{textBlueprints-DCorV4gM.js → textBlueprints-DyT-kfxc.js} +8 -8
  94. package/dist/{textMetrics-C0qTX3sH.cjs → textMetrics-CbDASt0r.cjs} +1 -1
  95. package/dist/{textMetrics-ByzyMC1I.js → textMetrics-DuzBLY9z.js} +1 -1
  96. package/dist/{threadFns-AJP69FU6.js → threadFns-CN1R3YKa.js} +7 -7
  97. package/dist/{threadFns-CzWqzDN_.cjs → threadFns-YPn7KF9u.cjs} +7 -7
  98. package/dist/topology/apiTypes.d.ts +3 -3
  99. package/dist/topology/wrapperFns.d.ts +11 -2
  100. package/dist/topology.cjs +9 -9
  101. package/dist/topology.js +9 -9
  102. package/dist/{topologyQueryFns-svM8Yfsk.cjs → topologyQueryFns-0Ycw298Y.cjs} +1 -1
  103. package/dist/{topologyQueryFns-CTCsQIH_.js → topologyQueryFns-CNOqc1rB.js} +1 -1
  104. package/dist/{validityTypes-BCUWXQNA.cjs → validityTypes-CXnc46WG.cjs} +1 -1
  105. package/dist/{validityTypes-DrWg6_qM.js → validityTypes-rUpnumpL.js} +1 -1
  106. package/package.json +1 -1
@@ -1,12 +1,12 @@
1
- const require_shapeTypes = require("./shapeTypes-C0NcoN5g.cjs");
1
+ const require_shapeTypes = require("./shapeTypes-C1Ljs72p.cjs");
2
2
  const require_bug = require("./bug-CBcpIPuf.cjs");
3
3
  const require_errors = require("./errors-O_blYTWT.cjs");
4
- const require_topologyQueryFns = require("./topologyQueryFns-svM8Yfsk.cjs");
4
+ const require_topologyQueryFns = require("./topologyQueryFns-0Ycw298Y.cjs");
5
5
  const require_constants = require("./constants-oee0S_x6.cjs");
6
- const require_solidBuilders = require("./solidBuilders-VPkj5ChQ.cjs");
7
- const require_meshFns = require("./meshFns-BDn_nIVv.cjs");
6
+ const require_solidBuilders = require("./solidBuilders-C6_6tZXG.cjs");
7
+ const require_meshFns = require("./meshFns-Bz9PPZWP.cjs");
8
8
  const require_arrayAccess = require("./arrayAccess-DpJYARwv.cjs");
9
- const require_blueprint = require("./blueprint-nVQsEilp.cjs");
9
+ const require_blueprint = require("./blueprint-D4juQWXs.cjs");
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 tessellation, because no kernel here can classify a
1318
- * point against a solid.
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 ??= require_shapeTypes.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 built from the same-index shell of `shells`)
1328
- * with every cavity cut from the body around it and left out. Cut results are
1329
- * pushed to `owned` for the caller to dispose; no input is disposed here.
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, shells, owned) {
1334
- const solids = bodies.flatMap((body, i) => require_shapeTypes.isSolid(body) ? [{
1335
- body,
1336
- shell: shells[i]
1337
- }] : []);
1345
+ function carveCavities(bodies, outlines, owned) {
1346
+ const solids = bodies.flatMap((body, i) => {
1347
+ const outline = outlines[i];
1348
+ return require_shapeTypes.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 kernel = require_shapeTypes.getKernel();
1352
- const boxes = solids.map(({ body }) => kernel.boundingBox(body.wrapped));
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 = require_shapeTypes._usingCtx();
1701
+ if (mesh) {
1702
+ const sewn = sewKnownMesh(faces, tolerance, mesh);
1703
+ if (sewn !== null) return sewn;
1704
+ }
1481
1705
  const kernel = require_shapeTypes.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 parts = carveCavities(shells.map((shell) => {
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
- return body;
1495
- }), shells, owned);
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 = require_shapeTypes.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 = require_shapeTypes._usingCtx();
1747
+ if (faces.length !== mesh.corners.length / 3) return null;
1748
+ const sewn = require_shapeTypes.castResultShape(require_shapeTypes.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) || require_topologyQueryFns.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 = require_shapeTypes._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 require_shapeTypes.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 = require_shapeTypes.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 = require_shapeTypes.castResultShape(kernel.copyShape(face));
1787
+ owned.push(copy);
1788
+ looseFaces.push(copy);
1789
+ continue;
1790
+ }
1791
+ const shell = require_shapeTypes.castResultShape(kernel.sew(own, tolerance));
1792
+ owned.push(shell);
1793
+ if (!require_shapeTypes.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 require_solidBuilders.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 require_topologyQueryFns.subShapeCount(shape, "face") === faces && require_topologyQueryFns.subShapeCount(shape, "edge") === edges && require_topologyQueryFns.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 = require_shapeTypes._usingCtx();
1819
+ const kernel = require_shapeTypes.getKernel();
1820
+ if (volume > 0) return require_shapeTypes.castResultShape(kernel.solidFromShell(shell.wrapped));
1821
+ const reversed = _usingCtx5.u(require_shapeTypes.castResultShape(kernel.reverseShape(shell.wrapped)));
1822
+ return require_shapeTypes.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 = require_shapeTypes.getKernel();
1513
1831
  const inShells = /* @__PURE__ */ new Map();
@@ -1548,17 +1866,78 @@ function solidifyShell(shell, tolerance) {
1548
1866
  }
1549
1867
  }
1550
1868
  /**
1551
- * Build triangular B-Rep faces from vertex data and sew them into solids.
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 = require_shapeTypes.castResultShape(require_shapeTypes.getKernel().importSTL(lines.join("\n")));
1882
+ } catch {
1883
+ return null;
1884
+ }
1885
+ if (require_shapeTypes.isCompound(facets) && require_topologyQueryFns.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 || require_shapeTypes.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 (require_shapeTypes.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
- * For each triple of vertex coordinates, calls `kernel.buildTriFace` to create
1554
- * a planar triangular face, then sews them with {@link sewFacesIntoSolids}.
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 = require_shapeTypes._usingCtx();
1930
+ _usingCtx6.u(facets);
1931
+ try {
1932
+ return require_errors.ok(sewFacesIntoSolids(require_topologyQueryFns.getFaces(facets).map((face) => face.wrapped), tolerance, mesh));
1933
+ } catch (e) {
1934
+ return require_errors.err(require_errors.ioError(errorCode, "Failed to sew triangular faces", e));
1935
+ }
1936
+ } catch (_) {
1937
+ _usingCtx6.e = _;
1938
+ } finally {
1939
+ _usingCtx6.d();
1940
+ }
1562
1941
  const kernel = require_shapeTypes.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 require_errors.err(require_errors.ioError(errorCode, "No valid triangular faces could be built"));
1569
1948
  try {
1570
- return require_errors.ok(sewFacesIntoSolids(triFaces, tolerance));
1949
+ return require_errors.ok(sewFacesIntoSolids(triFaces, tolerance, mesh));
1571
1950
  } catch (e) {
1572
1951
  return require_errors.err(require_errors.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: on the default occt-wasm kernel, roughly 2.5 seconds per
1629
- * 10,000 triangles (about 15 seconds for 50,000), and up to about twice that
1630
- * when the file holds several bodies. The temporary file on the WASM FS is
1631
- * cleaned up automatically.
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 (require_shapeTypes.isSolid(shape)) return require_errors.ok(shape);
1647
2068
  const facets = _usingCtx$1.u(shape);
1648
2069
  const faces = require_topologyQueryFns.getFaces(facets).map((f) => f.wrapped);
1649
- return require_errors.ok(sewFacesIntoSolids(faces, STL_SEW_TOLERANCE));
2070
+ const mesh = sewableTopology(stlTrianglePositions(data));
2071
+ return require_errors.ok(sewFacesIntoSolids(faces, STL_SEW_TOLERANCE, mesh));
1650
2072
  } catch (_) {
1651
2073
  _usingCtx$1.e = _;
1652
2074
  } finally {
@@ -32,10 +32,12 @@ export declare function importSTEP(blob: Blob): Promise<Result<UnknownDimShape>>
32
32
  * compound for several bodies), or an error if parsing fails.
33
33
  *
34
34
  * @remarks Every triangle becomes its own B-Rep face, so import time grows with
35
- * the triangle count: on the default occt-wasm kernel, roughly 2.5 seconds per
36
- * 10,000 triangles (about 15 seconds for 50,000), and up to about twice that
37
- * when the file holds several bodies. The temporary file on the WASM FS is
38
- * cleaned up automatically.
35
+ * the triangle count. On the default occt-wasm kernel a mesh whose shared edges
36
+ * each join two consistently wound triangles takes about 0.3 seconds per 10,000
37
+ * triangles (about 1.7 seconds for 50,000). One that needs its winding repaired
38
+ * takes about 2 seconds per 10,000 (about 13 seconds for 50,000), and up to
39
+ * about twice that when the file holds several bodies. The temporary file on
40
+ * the WASM FS is cleaned up automatically.
39
41
  *
40
42
  * @example
41
43
  * ```ts