brepjs 18.108.0 → 18.110.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/2d.cjs +6 -6
  2. package/dist/2d.js +6 -6
  3. package/dist/{blueprint-D5ArjKiA.cjs → blueprint-BwkD2qeC.cjs} +5 -5
  4. package/dist/{blueprint-CF12Nceg.js → blueprint-Dw0QqImu.js} +5 -5
  5. package/dist/{blueprintFns-CcCfkbyd.cjs → blueprintFns-B-aybpEo.cjs} +2 -2
  6. package/dist/{blueprintFns-BuPAK4OB.js → blueprintFns-BowWsZgJ.js} +2 -2
  7. package/dist/{blueprintSketcher-CEQ8s7TD.js → blueprintSketcher-7rtwqUHZ.js} +3 -3
  8. package/dist/{blueprintSketcher-DCttG3KS.cjs → blueprintSketcher-RK-cH6ps.cjs} +3 -3
  9. package/dist/{boolean2D-BCqDjg0y.cjs → boolean2D-CZUEOoAh.cjs} +4 -4
  10. package/dist/{boolean2D-8rB2sUzj.js → boolean2D-y0SSZfe2.js} +4 -4
  11. package/dist/brepjs.cjs +64 -27
  12. package/dist/brepjs.js +64 -27
  13. package/dist/{cameraFns-Bu6jgTKc.js → cameraFns-bk9e0F-T.js} +2 -2
  14. package/dist/{cameraFns-S7YbAxfg.cjs → cameraFns-uo1sZbBT.cjs} +2 -2
  15. package/dist/core.cjs +1 -1
  16. package/dist/core.js +1 -1
  17. package/dist/{cornerFinder-Ci2NbbAW.cjs → cornerFinder-B4q88TRb.cjs} +1 -1
  18. package/dist/{cornerFinder-COqoeY--.js → cornerFinder-DUGeBIvD.js} +1 -1
  19. package/dist/{curveFns-CG05Jygr.js → curveFns-CcSn4-Rf.js} +1 -1
  20. package/dist/{curveFns-Dx7_wMut.cjs → curveFns-DgUJpH4z.cjs} +1 -1
  21. package/dist/{drawFns-DYkjT_hx.cjs → drawFns-DNxs7PAT.cjs} +12 -12
  22. package/dist/{drawFns-CsXPFyDe.js → drawFns-SQIBls6p.js} +12 -12
  23. package/dist/{faceFns-mo4ThIHz.js → faceFns-B86WGqDg.js} +2 -2
  24. package/dist/{faceFns-DKg2St8b.cjs → faceFns-BV0GnTlt.cjs} +2 -2
  25. package/dist/{healingFns-OL0bBSSY.cjs → healingFns-C4UE1ZyM.cjs} +5 -5
  26. package/dist/{healingFns-C5AomW4j.js → healingFns-CPm_VFck.js} +5 -5
  27. package/dist/{helpers-8zxf2gkc.cjs → helpers-BSLPCflF.cjs} +6 -6
  28. package/dist/{helpers-DT44wBAD.js → helpers-DhJwFrkC.js} +6 -6
  29. package/dist/{importFns-B6hw9Nhx.cjs → importFns-CgMQIi0z.cjs} +2 -2
  30. package/dist/{importFns-CEczwPdN.js → importFns-nR9lEx22.js} +2 -2
  31. package/dist/io.cjs +2 -2
  32. package/dist/io.js +2 -2
  33. package/dist/kernel/brepkit/transformOps.d.ts +1 -0
  34. package/dist/kernel/interfaces/transformOps.d.ts +10 -0
  35. package/dist/kernel/occt/transformOps.d.ts +8 -0
  36. package/dist/kernel/occtWasm/occtWasmAdapter.cjs +1 -1
  37. package/dist/kernel/occtWasm/occtWasmAdapter.d.ts +1 -0
  38. package/dist/kernel/occtWasm/occtWasmAdapter.js +1 -1
  39. package/dist/kernel/occtWasm/occtWasmTypes.d.ts +7 -0
  40. package/dist/kernel/occtWasm/transformOps.d.ts +7 -0
  41. package/dist/{loftFns-DyNuD9XL.js → loftFns-CmShFQlv.js} +1 -1
  42. package/dist/{loftFns-DaRDYpfB.cjs → loftFns-fpTmrIdd.cjs} +1 -1
  43. package/dist/{measureFns-Q2dnokUU.cjs → measureFns-DblG0Yaz.cjs} +3 -3
  44. package/dist/{measureFns-Dkh_DmrE.js → measureFns-a5Vx25or.js} +3 -3
  45. package/dist/measurement.cjs +1 -1
  46. package/dist/measurement.js +1 -1
  47. package/dist/{meshFns-JwMEfMvC.cjs → meshFns-1hQSn1p2.cjs} +3 -3
  48. package/dist/{meshFns-DeGn7mQ4.js → meshFns-DbmidUzH.js} +3 -3
  49. package/dist/{occtWasmAdapter-DDZELjlB.cjs → occtWasmAdapter-B2TEQOjs.cjs} +38 -10
  50. package/dist/{occtWasmAdapter-BDEPveZs.js → occtWasmAdapter-CKwtxwNH.js} +38 -10
  51. package/dist/operations.cjs +2 -2
  52. package/dist/operations.js +2 -2
  53. package/dist/{primitiveFns-62SO4ti5.js → primitiveFns-CBrOyk5S.js} +4 -4
  54. package/dist/{primitiveFns-BAWtMd9H.cjs → primitiveFns-DLIWtQWA.cjs} +4 -4
  55. package/dist/projection.cjs +1 -1
  56. package/dist/projection.js +1 -1
  57. package/dist/query.cjs +2 -2
  58. package/dist/query.js +2 -2
  59. package/dist/{shapeFns-DyBbhU30.js → shapeFns-DrZOVwHX.js} +44 -4
  60. package/dist/{shapeFns-L4a8kH_A.cjs → shapeFns-ybo0INGL.cjs} +55 -3
  61. package/dist/shapeRef.cjs +1 -1
  62. package/dist/shapeRef.js +1 -1
  63. package/dist/{shapeRefFns-Cn4WNb3q.js → shapeRefFns-ChMnsOAF.js} +101 -49
  64. package/dist/{shapeRefFns-Dij41NuC.cjs → shapeRefFns-CuepnFOm.cjs} +101 -49
  65. package/dist/{shapeTypes-Bwgm5N6R.cjs → shapeTypes-BTTwjHy5.cjs} +18 -1
  66. package/dist/{shapeTypes-C4lPfrc2.js → shapeTypes-Dpu6mD3m.js} +18 -1
  67. package/dist/sketching.cjs +3 -3
  68. package/dist/sketching.js +3 -3
  69. package/dist/{solidBuilders-Biwv2cW3.js → solidBuilders-C32wXQWG.js} +5 -5
  70. package/dist/{solidBuilders-CgRmJEdn.cjs → solidBuilders-DiUv_mD0.cjs} +5 -5
  71. package/dist/{surfaceBuilders-FLnYYA70.cjs → surfaceBuilders-UyyfZy8w.cjs} +2 -2
  72. package/dist/{surfaceBuilders-Bww4jhBo.js → surfaceBuilders-rkE-jE90.js} +2 -2
  73. package/dist/text.cjs +2 -2
  74. package/dist/text.js +2 -2
  75. package/dist/{textBlueprints-CEzTC9vX.js → textBlueprints-BnkWAqwS.js} +7 -7
  76. package/dist/{textBlueprints-08NcMO9T.cjs → textBlueprints-D5lsZswr.cjs} +7 -7
  77. package/dist/{textMetrics-CBbDQ5QW.js → textMetrics-COGYk0LE.js} +1 -1
  78. package/dist/{textMetrics-W-H6jUDj.cjs → textMetrics-IC_IoI7l.cjs} +1 -1
  79. package/dist/{threadFns-DGYdomjN.cjs → threadFns-C8bErjVC.cjs} +6 -6
  80. package/dist/{threadFns-8rKpQXiR.js → threadFns-bCOhVQLb.js} +6 -6
  81. package/dist/topology/metadata/metadataPropagation.d.ts +21 -0
  82. package/dist/topology/shapeRef/shapeRefFns.d.ts +19 -13
  83. package/dist/topology/shapeRef/shapeRefTypes.d.ts +6 -2
  84. package/dist/topology.cjs +8 -8
  85. package/dist/topology.js +8 -8
  86. package/dist/{topologyQueryFns-Do8E4EXj.js → topologyQueryFns-BeKaWpNm.js} +1 -1
  87. package/dist/{topologyQueryFns-ygBBXxnP.cjs → topologyQueryFns-CoXVHuN7.cjs} +1 -1
  88. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
- import { Z as getKernel, t as castShape } from "./shapeTypes-C4lPfrc2.js";
1
+ import { Z as getKernel, t as castShape } from "./shapeTypes-Dpu6mD3m.js";
2
2
  import { A as ok, b as err, d as validationError, t as BrepErrorCode } from "./errors-DNWJsfVU.js";
3
- import { C as kernelCall, c as getFaces, l as getOrCreateCache, r as getCacheEntry, w as kernelCallRaw } from "./topologyQueryFns-Do8E4EXj.js";
3
+ import { C as kernelCall, c as getFaces, l as getOrCreateCache, r as getCacheEntry, w as kernelCallRaw } from "./topologyQueryFns-BeKaWpNm.js";
4
4
  import { n as HASH_CODE_MAX, t as DEG2RAD } from "./constants-ITRzCnCp.js";
5
5
  //#region src/topology/metadata/originTrackingFns.ts
6
6
  /**
@@ -376,8 +376,16 @@ function propagateColorsFromEvolution(evolution, inputs, result) {
376
376
  * Fast-path: returns empty array when no inputs have any metadata (origins,
377
377
  * tags, or colors), avoiding expensive WASM topology exploration.
378
378
  */
379
+ /**
380
+ * O(1) check: does a shape carry any propagatable metadata (face origins,
381
+ * tags, or colors)? Lets callers skip both expensive face iteration and
382
+ * metadata-preserving slow paths when there's nothing to preserve.
383
+ */
384
+ function hasAnyMetadata(shape) {
385
+ return getFaceOrigins(shape) !== void 0 || hasFaceTags(shape) || hasColorMetadata(shape);
386
+ }
379
387
  function collectInputFaceHashes(inputs) {
380
- if (!inputs.some((s) => getFaceOrigins(s) !== void 0 || hasFaceTags(s) || hasColorMetadata(s))) return [];
388
+ if (!inputs.some(hasAnyMetadata)) return [];
381
389
  const kernel = getKernel();
382
390
  const hashes = [];
383
391
  for (const input of inputs) {
@@ -412,6 +420,38 @@ function propagateAllMetadata(evolution, inputs, result) {
412
420
  function propagateMetadataByHash(inputs, result) {
413
421
  propagateOriginsByHash(inputs, result);
414
422
  }
423
+ /**
424
+ * Propagate all metadata through a *rigid relocation* (a `locate` re-tag).
425
+ *
426
+ * A relocated shape shares the source's TShape, so its faces correspond 1:1 to
427
+ * the source faces in iteration order — but carry new, location-dependent
428
+ * hashes, so the hash-keyed metadata must be re-keyed rather than left as-is.
429
+ * This synthesizes a 1:1 `modified` evolution (source face hash → moved face
430
+ * hash) and runs the standard propagation pipeline, so origins, tags, and
431
+ * colors all survive a move at `locate` cost (O(faces)), not a full copy.
432
+ *
433
+ * Callers should gate on {@link hasAnyMetadata} first: with no metadata this
434
+ * does pointless face iteration. The face counts always match for a rigid
435
+ * relocation; the length guard is purely defensive.
436
+ */
437
+ function propagateMetadataThroughRelocation(source, moved) {
438
+ const kernel = getKernel();
439
+ const srcFaces = [...kernel.iterShapes(source.wrapped, "face")];
440
+ const movedFaces = [...kernel.iterShapes(moved.wrapped, "face")];
441
+ if (srcFaces.length !== movedFaces.length) return;
442
+ const modified = /* @__PURE__ */ new Map();
443
+ for (let i = 0; i < srcFaces.length; i++) {
444
+ const sf = srcFaces[i];
445
+ const mf = movedFaces[i];
446
+ if (sf === void 0 || mf === void 0) continue;
447
+ modified.set(kernel.hashCode(sf, HASH_CODE_MAX), [kernel.hashCode(mf, HASH_CODE_MAX)]);
448
+ }
449
+ propagateAllMetadata({
450
+ modified,
451
+ generated: /* @__PURE__ */ new Map(),
452
+ deleted: /* @__PURE__ */ new Set()
453
+ }, [source], moved);
454
+ }
415
455
  //#endregion
416
456
  //#region src/topology/transformFns.ts
417
457
  /**
@@ -653,4 +693,4 @@ function simplify(shape) {
653
693
  return kernelCall(() => getKernel().simplify(shape.wrapped), BrepErrorCode.SIMPLIFY_FAILED, "Failed to simplify shape");
654
694
  }
655
695
  //#endregion
656
- export { findFacesByTag as C, tagFaces as D, setTagMetadata as E, getFaceOrigins as O, getShapeColor as S, getTagMetadata as T, propagateAllMetadata as _, isSameShape as a, colorShape as b, applyMatrix as c, resize as d, rotate as f, collectInputFaceHashes as g, translate as h, isEqualShape as i, setShapeOrigin as k, composeTransforms as l, transformCopy as m, getHashCode as n, simplify as o, scale as p, isEmpty as r, toBREP as s, clone as t, mirror as u, propagateMetadataByHash as v, getFaceTags as w, getFaceColor as x, colorFaces as y };
696
+ export { getFaceOrigins as A, getFaceColor as C, getTagMetadata as D, getFaceTags as E, setTagMetadata as O, colorShape as S, findFacesByTag as T, hasAnyMetadata as _, isSameShape as a, propagateMetadataThroughRelocation as b, applyMatrix as c, resize as d, rotate as f, collectInputFaceHashes as g, translate as h, isEqualShape as i, setShapeOrigin as j, tagFaces as k, composeTransforms as l, transformCopy as m, getHashCode as n, simplify as o, scale as p, isEmpty as r, toBREP as s, clone as t, mirror as u, propagateAllMetadata as v, getShapeColor as w, colorFaces as x, propagateMetadataByHash as y };
@@ -1,6 +1,6 @@
1
- const require_shapeTypes = require("./shapeTypes-Bwgm5N6R.cjs");
1
+ const require_shapeTypes = require("./shapeTypes-BTTwjHy5.cjs");
2
2
  const require_errors = require("./errors-CXJtc4I7.cjs");
3
- const require_topologyQueryFns = require("./topologyQueryFns-ygBBXxnP.cjs");
3
+ const require_topologyQueryFns = require("./topologyQueryFns-CoXVHuN7.cjs");
4
4
  const require_constants = require("./constants-BOVyEYGH.cjs");
5
5
  //#region src/topology/metadata/originTrackingFns.ts
6
6
  /**
@@ -376,8 +376,16 @@ function propagateColorsFromEvolution(evolution, inputs, result) {
376
376
  * Fast-path: returns empty array when no inputs have any metadata (origins,
377
377
  * tags, or colors), avoiding expensive WASM topology exploration.
378
378
  */
379
+ /**
380
+ * O(1) check: does a shape carry any propagatable metadata (face origins,
381
+ * tags, or colors)? Lets callers skip both expensive face iteration and
382
+ * metadata-preserving slow paths when there's nothing to preserve.
383
+ */
384
+ function hasAnyMetadata(shape) {
385
+ return getFaceOrigins(shape) !== void 0 || hasFaceTags(shape) || hasColorMetadata(shape);
386
+ }
379
387
  function collectInputFaceHashes(inputs) {
380
- if (!inputs.some((s) => getFaceOrigins(s) !== void 0 || hasFaceTags(s) || hasColorMetadata(s))) return [];
388
+ if (!inputs.some(hasAnyMetadata)) return [];
381
389
  const kernel = require_shapeTypes.getKernel();
382
390
  const hashes = [];
383
391
  for (const input of inputs) {
@@ -412,6 +420,38 @@ function propagateAllMetadata(evolution, inputs, result) {
412
420
  function propagateMetadataByHash(inputs, result) {
413
421
  propagateOriginsByHash(inputs, result);
414
422
  }
423
+ /**
424
+ * Propagate all metadata through a *rigid relocation* (a `locate` re-tag).
425
+ *
426
+ * A relocated shape shares the source's TShape, so its faces correspond 1:1 to
427
+ * the source faces in iteration order — but carry new, location-dependent
428
+ * hashes, so the hash-keyed metadata must be re-keyed rather than left as-is.
429
+ * This synthesizes a 1:1 `modified` evolution (source face hash → moved face
430
+ * hash) and runs the standard propagation pipeline, so origins, tags, and
431
+ * colors all survive a move at `locate` cost (O(faces)), not a full copy.
432
+ *
433
+ * Callers should gate on {@link hasAnyMetadata} first: with no metadata this
434
+ * does pointless face iteration. The face counts always match for a rigid
435
+ * relocation; the length guard is purely defensive.
436
+ */
437
+ function propagateMetadataThroughRelocation(source, moved) {
438
+ const kernel = require_shapeTypes.getKernel();
439
+ const srcFaces = [...kernel.iterShapes(source.wrapped, "face")];
440
+ const movedFaces = [...kernel.iterShapes(moved.wrapped, "face")];
441
+ if (srcFaces.length !== movedFaces.length) return;
442
+ const modified = /* @__PURE__ */ new Map();
443
+ for (let i = 0; i < srcFaces.length; i++) {
444
+ const sf = srcFaces[i];
445
+ const mf = movedFaces[i];
446
+ if (sf === void 0 || mf === void 0) continue;
447
+ modified.set(kernel.hashCode(sf, require_constants.HASH_CODE_MAX), [kernel.hashCode(mf, require_constants.HASH_CODE_MAX)]);
448
+ }
449
+ propagateAllMetadata({
450
+ modified,
451
+ generated: /* @__PURE__ */ new Map(),
452
+ deleted: /* @__PURE__ */ new Set()
453
+ }, [source], moved);
454
+ }
415
455
  //#endregion
416
456
  //#region src/topology/transformFns.ts
417
457
  /**
@@ -731,6 +771,12 @@ Object.defineProperty(exports, "getTagMetadata", {
731
771
  return getTagMetadata;
732
772
  }
733
773
  });
774
+ Object.defineProperty(exports, "hasAnyMetadata", {
775
+ enumerable: true,
776
+ get: function() {
777
+ return hasAnyMetadata;
778
+ }
779
+ });
734
780
  Object.defineProperty(exports, "isEmpty", {
735
781
  enumerable: true,
736
782
  get: function() {
@@ -767,6 +813,12 @@ Object.defineProperty(exports, "propagateMetadataByHash", {
767
813
  return propagateMetadataByHash;
768
814
  }
769
815
  });
816
+ Object.defineProperty(exports, "propagateMetadataThroughRelocation", {
817
+ enumerable: true,
818
+ get: function() {
819
+ return propagateMetadataThroughRelocation;
820
+ }
821
+ });
770
822
  Object.defineProperty(exports, "resize", {
771
823
  enumerable: true,
772
824
  get: function() {
package/dist/shapeRef.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_shapeRefFns = require("./shapeRefFns-Dij41NuC.cjs");
2
+ const require_shapeRefFns = require("./shapeRefFns-CuepnFOm.cjs");
3
3
  exports.assignRoles = require_shapeRefFns.assignRoles;
4
4
  exports.captureHint = require_shapeRefFns.captureHint;
5
5
  exports.createRef = require_shapeRefFns.createRef;
package/dist/shapeRef.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as updateRoles, i as resolveRef, n as captureHint, o as defaultScorer, r as createRef, t as assignRoles } from "./shapeRefFns-Cn4WNb3q.js";
1
+ import { a as updateRoles, i as resolveRef, n as captureHint, o as defaultScorer, r as createRef, t as assignRoles } from "./shapeRefFns-ChMnsOAF.js";
2
2
  export { assignRoles, captureHint, createRef, defaultScorer, resolveRef, updateRoles };
@@ -1,8 +1,7 @@
1
- import { n as wasmIndex } from "./vec3-Dpha8d5k.js";
2
- import { c as getFaces } from "./topologyQueryFns-Do8E4EXj.js";
3
- import { i as faceGeomType, l as normalAt, r as faceCenter } from "./faceFns-mo4ThIHz.js";
4
- import { n as getHashCode } from "./shapeFns-DyBbhU30.js";
5
- import { n as measureArea } from "./measureFns-Dkh_DmrE.js";
1
+ import { c as getFaces } from "./topologyQueryFns-BeKaWpNm.js";
2
+ import { i as faceGeomType, l as normalAt, r as faceCenter } from "./faceFns-B86WGqDg.js";
3
+ import { n as getHashCode } from "./shapeFns-DrZOVwHX.js";
4
+ import { n as measureArea } from "./measureFns-a5Vx25or.js";
6
5
  //#region src/topology/shapeRef/scoring.ts
7
6
  /**
8
7
  * Default face scorer combining surface type, normal alignment, centroid proximity,
@@ -81,7 +80,8 @@ function boxRoleFromNormal(n) {
81
80
  *
82
81
  * For other types: sequential naming ('opType:face_0', 'opType:face_1', ...).
83
82
  *
84
- * @returns Map from role name to face hash code
83
+ * @returns Map from role name to its face hash codes (one at assignment time;
84
+ * a role accrues more hashes only later, when `updateRoles` tracks a split).
85
85
  */
86
86
  function assignRoles(shape, operationType) {
87
87
  const faces = getFaces(shape);
@@ -89,13 +89,13 @@ function assignRoles(shape, operationType) {
89
89
  if (operationType === "box") {
90
90
  for (const face of faces) {
91
91
  const role = boxRoleFromNormal(normalAt(face));
92
- if (role !== void 0 && !roles.has(role)) roles.set(role, getHashCode(face));
92
+ if (role !== void 0 && !roles.has(role)) roles.set(role, [getHashCode(face)]);
93
93
  }
94
94
  return roles;
95
95
  }
96
96
  let index = 0;
97
97
  for (const face of faces) {
98
- roles.set(`${operationType}:face_${index}`, getHashCode(face));
98
+ roles.set(`${operationType}:face_${index}`, [getHashCode(face)]);
99
99
  index++;
100
100
  }
101
101
  return roles;
@@ -109,31 +109,47 @@ function createRef(origin, role, face) {
109
109
  };
110
110
  }
111
111
  /**
112
+ * Advance a role's face hashes through one evolution: drop deleted faces,
113
+ * replace a modified face with *all* its successors (a 1→many split keeps every
114
+ * fragment), and keep unchanged faces. Deduped so a shared successor isn't
115
+ * doubled.
116
+ */
117
+ function nextHashes(hashes, evolution) {
118
+ const successors = [];
119
+ for (const hash of hashes) {
120
+ if (evolution.deleted.has(hash)) continue;
121
+ const modified = evolution.modified.get(hash);
122
+ const targets = modified && modified.length > 0 ? modified : [hash];
123
+ for (const h of targets) if (!successors.includes(h)) successors.push(h);
124
+ }
125
+ return successors;
126
+ }
127
+ /**
112
128
  * Propagate a role table through a ShapeEvolution record.
113
129
  * Returns a new RoleTable with hashes updated according to the evolution.
114
130
  *
115
- * - Deleted faces: role removed
116
- * - Modified faces: hash updated to first result hash
117
- * - Unchanged faces: hash preserved
131
+ * - Deleted faces: hash dropped (role removed once all its hashes are gone).
132
+ * - Modified faces: hash replaced by **all** successor hashes — so a 1→many
133
+ * split keeps every fragment, and `resolveRef` disambiguates among them.
134
+ * - Unchanged faces: hash preserved.
118
135
  *
119
- * **Limitation:** When a face splits (1→many in `evolution.modified`), only the
120
- * first successor hash is tracked. The geometric fallback in `resolveRef` handles
121
- * cases where this picks the "wrong" successor. A future version may return
122
- * multi-hash mappings for split-aware resolution.
136
+ * Note: `evolution.generated` is intentionally not consumed here on the OCCT
137
+ * kernels its hashes refer to an intermediate shape, not the final result, so
138
+ * naming generated faces produces roles that never resolve (verified: 0 live
139
+ * generated hashes across cut/fuse on occt-wasm). Stable names for generated
140
+ * geometry (fillet rounds, boolean seams) need history-fidelity work tracked
141
+ * separately.
123
142
  */
124
143
  function updateRoles(roles, origin, evolution) {
125
144
  const originRoles = roles.get(origin);
126
145
  if (!originRoles) return roles;
127
146
  const updatedOriginRoles = /* @__PURE__ */ new Map();
128
- for (const [role, hash] of originRoles) {
129
- if (evolution.deleted.has(hash)) continue;
130
- const modifiedHashes = evolution.modified.get(hash);
131
- if (modifiedHashes && modifiedHashes.length > 0) updatedOriginRoles.set(role, wasmIndex(modifiedHashes, 0));
132
- else updatedOriginRoles.set(role, hash);
147
+ for (const [role, hashes] of originRoles) {
148
+ const successors = nextHashes(hashes, evolution);
149
+ if (successors.length > 0) updatedOriginRoles.set(role, successors);
133
150
  }
134
151
  const newRoles = /* @__PURE__ */ new Map();
135
- for (const [key, value] of roles) if (key === origin) newRoles.set(key, updatedOriginRoles);
136
- else newRoles.set(key, value);
152
+ for (const [key, value] of roles) newRoles.set(key, key === origin ? updatedOriginRoles : value);
137
153
  return newRoles;
138
154
  }
139
155
  /** Ambiguity threshold: if two scores are within this range, it's ambiguous. */
@@ -141,34 +157,19 @@ var AMBIGUITY_THRESHOLD = .1;
141
157
  /** Minimum score for geometric fallback to accept a match. */
142
158
  var MIN_SCORE = .5;
143
159
  /**
144
- * Resolve a ShapeRef to a face in the current shape.
145
- *
146
- * Resolution strategy:
147
- * 1. Exact lookup via role table hash match
148
- * 2. Geometric fallback using scorer against all faces
149
- * 3. Ambiguous if multiple faces score within threshold
150
- * 4. Not-found if no match above minimum score
160
+ * Score `candidates` against `hint`, returning the best match, a tie within
161
+ * {@link AMBIGUITY_THRESHOLD}, or nothing above {@link MIN_SCORE}. Scoping the
162
+ * candidates to a role's tracked successors (rather than every face) is what
163
+ * makes split-face disambiguation reliable the fragments compete only with
164
+ * each other, not with unrelated geometry.
151
165
  */
152
- function resolveRef(ref, roles, currentShape, scorer) {
153
- const faces = getFaces(currentShape);
154
- const scoreFn = scorer ?? defaultScorer;
155
- const targetHash = roles.get(ref.origin)?.get(ref.role);
156
- if (targetHash !== void 0) {
157
- for (const face of faces) if (getHashCode(face) === targetHash) return {
158
- face,
159
- confidence: "exact"
160
- };
161
- return {
162
- ref,
163
- reason: "deleted"
164
- };
165
- }
166
+ function scoreFaces(hint, candidates, scoreFn) {
166
167
  let bestScore = -Infinity;
167
168
  let bestFace;
168
169
  let secondBestScore = -Infinity;
169
170
  const scored = [];
170
- for (const face of faces) {
171
- const score = scoreFn(ref.hint, face);
171
+ for (const face of candidates) {
172
+ const score = scoreFn(hint, face);
172
173
  if (score > MIN_SCORE) scored.push([face, score]);
173
174
  if (score > bestScore) {
174
175
  secondBestScore = bestScore;
@@ -178,15 +179,66 @@ function resolveRef(ref, roles, currentShape, scorer) {
178
179
  }
179
180
  if (bestFace !== void 0 && bestScore > MIN_SCORE) {
180
181
  if (bestScore - secondBestScore < AMBIGUITY_THRESHOLD && scored.length > 1) return {
181
- ref,
182
- reason: "ambiguous",
182
+ kind: "ambiguous",
183
183
  candidates: scored.filter(([, s]) => s >= bestScore - AMBIGUITY_THRESHOLD).map(([f]) => f)
184
184
  };
185
185
  return {
186
- face: bestFace,
187
- confidence: "geometric-fallback"
186
+ kind: "match",
187
+ face: bestFace
188
+ };
189
+ }
190
+ return { kind: "none" };
191
+ }
192
+ /**
193
+ * Resolve a ShapeRef to a face in the current shape.
194
+ *
195
+ * Resolution strategy:
196
+ * 1. Exact: the role's tracked successor hashes. One survivor → exact match;
197
+ * several survivors (a face that split) → disambiguate among *only those*
198
+ * fragments; none survive → deleted.
199
+ * 2. Geometric fallback over the whole shape when the role isn't tracked (or a
200
+ * scoped score turned up nothing): best-scoring face, else ambiguous /
201
+ * not-found.
202
+ */
203
+ function resolveRef(ref, roles, currentShape, scorer) {
204
+ const faces = getFaces(currentShape);
205
+ const scoreFn = scorer ?? defaultScorer;
206
+ const targetHashes = roles.get(ref.origin)?.get(ref.role);
207
+ if (targetHashes !== void 0 && targetHashes.length > 0) {
208
+ const survivors = faces.filter((f) => targetHashes.includes(getHashCode(f)));
209
+ if (survivors.length === 1) {
210
+ const [only] = survivors;
211
+ if (only !== void 0) return {
212
+ face: only,
213
+ confidence: "exact"
214
+ };
215
+ } else if (survivors.length === 0) return {
216
+ ref,
217
+ reason: "deleted"
188
218
  };
219
+ else {
220
+ const outcome = scoreFaces(ref.hint, survivors, scoreFn);
221
+ if (outcome.kind === "match") return {
222
+ face: outcome.face,
223
+ confidence: "geometric-fallback"
224
+ };
225
+ if (outcome.kind === "ambiguous") return {
226
+ ref,
227
+ reason: "ambiguous",
228
+ candidates: outcome.candidates
229
+ };
230
+ }
189
231
  }
232
+ const outcome = scoreFaces(ref.hint, faces, scoreFn);
233
+ if (outcome.kind === "match") return {
234
+ face: outcome.face,
235
+ confidence: "geometric-fallback"
236
+ };
237
+ if (outcome.kind === "ambiguous") return {
238
+ ref,
239
+ reason: "ambiguous",
240
+ candidates: outcome.candidates
241
+ };
190
242
  return {
191
243
  ref,
192
244
  reason: "not-found"
@@ -1,8 +1,7 @@
1
- const require_vec3 = require("./vec3-CFwOI0ZI.cjs");
2
- const require_topologyQueryFns = require("./topologyQueryFns-ygBBXxnP.cjs");
3
- const require_faceFns = require("./faceFns-DKg2St8b.cjs");
4
- const require_shapeFns = require("./shapeFns-L4a8kH_A.cjs");
5
- const require_measureFns = require("./measureFns-Q2dnokUU.cjs");
1
+ const require_topologyQueryFns = require("./topologyQueryFns-CoXVHuN7.cjs");
2
+ const require_faceFns = require("./faceFns-BV0GnTlt.cjs");
3
+ const require_shapeFns = require("./shapeFns-ybo0INGL.cjs");
4
+ const require_measureFns = require("./measureFns-DblG0Yaz.cjs");
6
5
  //#region src/topology/shapeRef/scoring.ts
7
6
  /**
8
7
  * Default face scorer combining surface type, normal alignment, centroid proximity,
@@ -81,7 +80,8 @@ function boxRoleFromNormal(n) {
81
80
  *
82
81
  * For other types: sequential naming ('opType:face_0', 'opType:face_1', ...).
83
82
  *
84
- * @returns Map from role name to face hash code
83
+ * @returns Map from role name to its face hash codes (one at assignment time;
84
+ * a role accrues more hashes only later, when `updateRoles` tracks a split).
85
85
  */
86
86
  function assignRoles(shape, operationType) {
87
87
  const faces = require_topologyQueryFns.getFaces(shape);
@@ -89,13 +89,13 @@ function assignRoles(shape, operationType) {
89
89
  if (operationType === "box") {
90
90
  for (const face of faces) {
91
91
  const role = boxRoleFromNormal(require_faceFns.normalAt(face));
92
- if (role !== void 0 && !roles.has(role)) roles.set(role, require_shapeFns.getHashCode(face));
92
+ if (role !== void 0 && !roles.has(role)) roles.set(role, [require_shapeFns.getHashCode(face)]);
93
93
  }
94
94
  return roles;
95
95
  }
96
96
  let index = 0;
97
97
  for (const face of faces) {
98
- roles.set(`${operationType}:face_${index}`, require_shapeFns.getHashCode(face));
98
+ roles.set(`${operationType}:face_${index}`, [require_shapeFns.getHashCode(face)]);
99
99
  index++;
100
100
  }
101
101
  return roles;
@@ -109,31 +109,47 @@ function createRef(origin, role, face) {
109
109
  };
110
110
  }
111
111
  /**
112
+ * Advance a role's face hashes through one evolution: drop deleted faces,
113
+ * replace a modified face with *all* its successors (a 1→many split keeps every
114
+ * fragment), and keep unchanged faces. Deduped so a shared successor isn't
115
+ * doubled.
116
+ */
117
+ function nextHashes(hashes, evolution) {
118
+ const successors = [];
119
+ for (const hash of hashes) {
120
+ if (evolution.deleted.has(hash)) continue;
121
+ const modified = evolution.modified.get(hash);
122
+ const targets = modified && modified.length > 0 ? modified : [hash];
123
+ for (const h of targets) if (!successors.includes(h)) successors.push(h);
124
+ }
125
+ return successors;
126
+ }
127
+ /**
112
128
  * Propagate a role table through a ShapeEvolution record.
113
129
  * Returns a new RoleTable with hashes updated according to the evolution.
114
130
  *
115
- * - Deleted faces: role removed
116
- * - Modified faces: hash updated to first result hash
117
- * - Unchanged faces: hash preserved
131
+ * - Deleted faces: hash dropped (role removed once all its hashes are gone).
132
+ * - Modified faces: hash replaced by **all** successor hashes — so a 1→many
133
+ * split keeps every fragment, and `resolveRef` disambiguates among them.
134
+ * - Unchanged faces: hash preserved.
118
135
  *
119
- * **Limitation:** When a face splits (1→many in `evolution.modified`), only the
120
- * first successor hash is tracked. The geometric fallback in `resolveRef` handles
121
- * cases where this picks the "wrong" successor. A future version may return
122
- * multi-hash mappings for split-aware resolution.
136
+ * Note: `evolution.generated` is intentionally not consumed here on the OCCT
137
+ * kernels its hashes refer to an intermediate shape, not the final result, so
138
+ * naming generated faces produces roles that never resolve (verified: 0 live
139
+ * generated hashes across cut/fuse on occt-wasm). Stable names for generated
140
+ * geometry (fillet rounds, boolean seams) need history-fidelity work tracked
141
+ * separately.
123
142
  */
124
143
  function updateRoles(roles, origin, evolution) {
125
144
  const originRoles = roles.get(origin);
126
145
  if (!originRoles) return roles;
127
146
  const updatedOriginRoles = /* @__PURE__ */ new Map();
128
- for (const [role, hash] of originRoles) {
129
- if (evolution.deleted.has(hash)) continue;
130
- const modifiedHashes = evolution.modified.get(hash);
131
- if (modifiedHashes && modifiedHashes.length > 0) updatedOriginRoles.set(role, require_vec3.wasmIndex(modifiedHashes, 0));
132
- else updatedOriginRoles.set(role, hash);
147
+ for (const [role, hashes] of originRoles) {
148
+ const successors = nextHashes(hashes, evolution);
149
+ if (successors.length > 0) updatedOriginRoles.set(role, successors);
133
150
  }
134
151
  const newRoles = /* @__PURE__ */ new Map();
135
- for (const [key, value] of roles) if (key === origin) newRoles.set(key, updatedOriginRoles);
136
- else newRoles.set(key, value);
152
+ for (const [key, value] of roles) newRoles.set(key, key === origin ? updatedOriginRoles : value);
137
153
  return newRoles;
138
154
  }
139
155
  /** Ambiguity threshold: if two scores are within this range, it's ambiguous. */
@@ -141,34 +157,19 @@ var AMBIGUITY_THRESHOLD = .1;
141
157
  /** Minimum score for geometric fallback to accept a match. */
142
158
  var MIN_SCORE = .5;
143
159
  /**
144
- * Resolve a ShapeRef to a face in the current shape.
145
- *
146
- * Resolution strategy:
147
- * 1. Exact lookup via role table hash match
148
- * 2. Geometric fallback using scorer against all faces
149
- * 3. Ambiguous if multiple faces score within threshold
150
- * 4. Not-found if no match above minimum score
160
+ * Score `candidates` against `hint`, returning the best match, a tie within
161
+ * {@link AMBIGUITY_THRESHOLD}, or nothing above {@link MIN_SCORE}. Scoping the
162
+ * candidates to a role's tracked successors (rather than every face) is what
163
+ * makes split-face disambiguation reliable the fragments compete only with
164
+ * each other, not with unrelated geometry.
151
165
  */
152
- function resolveRef(ref, roles, currentShape, scorer) {
153
- const faces = require_topologyQueryFns.getFaces(currentShape);
154
- const scoreFn = scorer ?? defaultScorer;
155
- const targetHash = roles.get(ref.origin)?.get(ref.role);
156
- if (targetHash !== void 0) {
157
- for (const face of faces) if (require_shapeFns.getHashCode(face) === targetHash) return {
158
- face,
159
- confidence: "exact"
160
- };
161
- return {
162
- ref,
163
- reason: "deleted"
164
- };
165
- }
166
+ function scoreFaces(hint, candidates, scoreFn) {
166
167
  let bestScore = -Infinity;
167
168
  let bestFace;
168
169
  let secondBestScore = -Infinity;
169
170
  const scored = [];
170
- for (const face of faces) {
171
- const score = scoreFn(ref.hint, face);
171
+ for (const face of candidates) {
172
+ const score = scoreFn(hint, face);
172
173
  if (score > MIN_SCORE) scored.push([face, score]);
173
174
  if (score > bestScore) {
174
175
  secondBestScore = bestScore;
@@ -178,15 +179,66 @@ function resolveRef(ref, roles, currentShape, scorer) {
178
179
  }
179
180
  if (bestFace !== void 0 && bestScore > MIN_SCORE) {
180
181
  if (bestScore - secondBestScore < AMBIGUITY_THRESHOLD && scored.length > 1) return {
181
- ref,
182
- reason: "ambiguous",
182
+ kind: "ambiguous",
183
183
  candidates: scored.filter(([, s]) => s >= bestScore - AMBIGUITY_THRESHOLD).map(([f]) => f)
184
184
  };
185
185
  return {
186
- face: bestFace,
187
- confidence: "geometric-fallback"
186
+ kind: "match",
187
+ face: bestFace
188
+ };
189
+ }
190
+ return { kind: "none" };
191
+ }
192
+ /**
193
+ * Resolve a ShapeRef to a face in the current shape.
194
+ *
195
+ * Resolution strategy:
196
+ * 1. Exact: the role's tracked successor hashes. One survivor → exact match;
197
+ * several survivors (a face that split) → disambiguate among *only those*
198
+ * fragments; none survive → deleted.
199
+ * 2. Geometric fallback over the whole shape when the role isn't tracked (or a
200
+ * scoped score turned up nothing): best-scoring face, else ambiguous /
201
+ * not-found.
202
+ */
203
+ function resolveRef(ref, roles, currentShape, scorer) {
204
+ const faces = require_topologyQueryFns.getFaces(currentShape);
205
+ const scoreFn = scorer ?? defaultScorer;
206
+ const targetHashes = roles.get(ref.origin)?.get(ref.role);
207
+ if (targetHashes !== void 0 && targetHashes.length > 0) {
208
+ const survivors = faces.filter((f) => targetHashes.includes(require_shapeFns.getHashCode(f)));
209
+ if (survivors.length === 1) {
210
+ const [only] = survivors;
211
+ if (only !== void 0) return {
212
+ face: only,
213
+ confidence: "exact"
214
+ };
215
+ } else if (survivors.length === 0) return {
216
+ ref,
217
+ reason: "deleted"
188
218
  };
219
+ else {
220
+ const outcome = scoreFaces(ref.hint, survivors, scoreFn);
221
+ if (outcome.kind === "match") return {
222
+ face: outcome.face,
223
+ confidence: "geometric-fallback"
224
+ };
225
+ if (outcome.kind === "ambiguous") return {
226
+ ref,
227
+ reason: "ambiguous",
228
+ candidates: outcome.candidates
229
+ };
230
+ }
189
231
  }
232
+ const outcome = scoreFaces(ref.hint, faces, scoreFn);
233
+ if (outcome.kind === "match") return {
234
+ face: outcome.face,
235
+ confidence: "geometric-fallback"
236
+ };
237
+ if (outcome.kind === "ambiguous") return {
238
+ ref,
239
+ reason: "ambiguous",
240
+ candidates: outcome.candidates
241
+ };
190
242
  return {
191
243
  ref,
192
244
  reason: "not-found"
@@ -1,4 +1,4 @@
1
- const require_occtWasmAdapter = require("./occtWasmAdapter-DDZELjlB.cjs");
1
+ const require_occtWasmAdapter = require("./occtWasmAdapter-B2TEQOjs.cjs");
2
2
  const require_vec3 = require("./vec3-CFwOI0ZI.cjs");
3
3
  const require_errors = require("./errors-CXJtc4I7.cjs");
4
4
  //#region src/kernel/quality.ts
@@ -2215,6 +2215,20 @@ function transform$3(oc, shape, trsf) {
2215
2215
  return result;
2216
2216
  }
2217
2217
  /**
2218
+ * Apply a rigid transform as a location re-tag. Passing copy=false makes
2219
+ * BRepBuilderAPI_Transform share the source TShape under a new location for a
2220
+ * rigid motion, instead of the deep topology copy {@link transform} (copy=true)
2221
+ * performs. Use only for rigid `trsf` (rotation + translation).
2222
+ */
2223
+ function locate(oc, shape, trsf) {
2224
+ const transformer = new oc.BRepBuilderAPI_Transform_2(shape, trsf, false, false);
2225
+ try {
2226
+ return transformer.ModifiedShape(shape);
2227
+ } finally {
2228
+ transformer.delete();
2229
+ }
2230
+ }
2231
+ /**
2218
2232
  * Translates a shape by the given offset.
2219
2233
  */
2220
2234
  function translate$2(oc, shape, x, y, z) {
@@ -2322,6 +2336,7 @@ function simplify$2(oc, shape) {
2322
2336
  function makeTransformOps$2(oc) {
2323
2337
  return {
2324
2338
  transform: (shape, trsf) => transform$3(oc, shape, trsf),
2339
+ locate: (shape, trsf) => locate(oc, shape, trsf),
2325
2340
  translate: (shape, x, y, z) => translate$2(oc, shape, x, y, z),
2326
2341
  rotate: (shape, angle, axis, center) => rotate$2(oc, shape, angle, axis, center),
2327
2342
  mirror: (shape, origin, normal) => mirror$2(oc, shape, origin, normal),
@@ -6250,6 +6265,7 @@ function transformBatch$1(bk, entries) {
6250
6265
  function makeTransformOps$1(bk) {
6251
6266
  return {
6252
6267
  transform: (shape, trsf) => transform$2(bk, shape, trsf),
6268
+ locate: (shape, trsf) => transform$2(bk, shape, trsf),
6253
6269
  translate: (shape, x, y, z) => translate$1(bk, shape, x, y, z),
6254
6270
  rotate: (shape, angle, axis, center) => rotate$1(bk, shape, angle, axis, center),
6255
6271
  mirror: (shape, origin, normal) => mirror$1(bk, shape, origin, normal),
@@ -10492,6 +10508,7 @@ function makeTransformOps(module) {
10492
10508
  return {
10493
10509
  composeTransform,
10494
10510
  transform: transform$1,
10511
+ locate: transform$1,
10495
10512
  translate,
10496
10513
  rotate,
10497
10514
  mirror,