brepjs 18.118.2 → 18.118.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/2d.cjs +6 -6
  2. package/dist/2d.js +6 -6
  3. package/dist/{adjacencyFns-COWMJ1OX.cjs → adjacencyFns-CiDCunAU.cjs} +2 -2
  4. package/dist/{adjacencyFns-D026zKM3.js → adjacencyFns-CxepMm7H.js} +2 -2
  5. package/dist/{blueprint-BeCL-AAl.js → blueprint-BIf01dZl.js} +15 -10
  6. package/dist/{blueprint-xcDMSkia.cjs → blueprint-Bp_sSxx2.cjs} +17 -11
  7. package/dist/{blueprintFns-DIMiD67q.cjs → blueprintFns-Dc8amB5a.cjs} +2 -2
  8. package/dist/{blueprintFns-BkSFPipn.js → blueprintFns-Dl_drDhd.js} +2 -2
  9. package/dist/{blueprintSketcher-DKfkNg71.js → blueprintSketcher-D-xSC6Mp.js} +5 -4
  10. package/dist/{blueprintSketcher-DGcCDQq2.cjs → blueprintSketcher-XiLc6fJ4.cjs} +5 -4
  11. package/dist/{boolean2D-BCTOcf19.js → boolean2D-BNjpYyZl.js} +5 -5
  12. package/dist/{boolean2D-Dh4IPW_X.cjs → boolean2D-BfKG5Wio.cjs} +7 -6
  13. package/dist/brepjs.cjs +29 -29
  14. package/dist/brepjs.js +29 -29
  15. package/dist/{cameraFns-BAm-2Clq.cjs → cameraFns-ClE0QcuX.cjs} +2 -2
  16. package/dist/{cameraFns-CRln-7RT.js → cameraFns-DyMKpFHq.js} +2 -2
  17. package/dist/core/shapeTypes.d.ts +32 -0
  18. package/dist/core.cjs +1 -1
  19. package/dist/core.js +1 -1
  20. package/dist/{cornerFinder-DMJBP4I2.cjs → cornerFinder-CINnfCPc.cjs} +1 -1
  21. package/dist/{cornerFinder-Bu482P3S.js → cornerFinder-DFswCdwf.js} +1 -1
  22. package/dist/{curveFns-pGQFlJG-.js → curveFns-BYcTyyb7.js} +1 -1
  23. package/dist/{curveFns-CpaxYpa4.cjs → curveFns-YvH970hp.cjs} +1 -1
  24. package/dist/{drawFns-D-PqWuBQ.cjs → drawFns-CG3ksr6q.cjs} +14 -13
  25. package/dist/{drawFns-BAETgRWM.js → drawFns-DkhqDgS5.js} +14 -13
  26. package/dist/{faceFns-Bc6sBrBV.js → faceFns-CpOAl1K5.js} +2 -2
  27. package/dist/{faceFns-rbWcdpvj.cjs → faceFns-DnLXDWq2.cjs} +4 -3
  28. package/dist/{healingFns-DfshoSeQ.cjs → healingFns-BIcPeCM9.cjs} +16 -15
  29. package/dist/{healingFns-DHxzmeCt.js → healingFns-BP0xjoIs.js} +16 -15
  30. package/dist/{helpers-YD71h41H.js → helpers-BVkEnH5n.js} +6 -6
  31. package/dist/{helpers-CKbgoPDr.cjs → helpers-Beq_ugyN.cjs} +6 -6
  32. package/dist/{importFns-CQ16LqPQ.cjs → importFns-BZYgEOu-.cjs} +4 -4
  33. package/dist/{importFns-A437Sgj6.js → importFns-DPQmk2TS.js} +4 -4
  34. package/dist/io.cjs +2 -2
  35. package/dist/io.js +2 -2
  36. package/dist/kernel/occtWasm/booleanOps.d.ts +9 -1
  37. package/dist/kernel/occtWasm/occtWasmAdapter.cjs +1 -1
  38. package/dist/kernel/occtWasm/occtWasmAdapter.js +1 -1
  39. package/dist/kernel/types.d.ts +2 -4
  40. package/dist/{loftFns-BQktV4u4.cjs → loftFns-CZC_NH_h.cjs} +1 -1
  41. package/dist/{loftFns-CXc9tRv6.js → loftFns-NauDWzek.js} +1 -1
  42. package/dist/{measureFns-B_vJqwDc.js → measureFns-BLmLZr0K.js} +3 -3
  43. package/dist/{measureFns-I5KcAgSb.cjs → measureFns-C4BxD4kC.cjs} +3 -3
  44. package/dist/measurement.cjs +1 -1
  45. package/dist/measurement.js +1 -1
  46. package/dist/{meshFns-COdN1kVO.js → meshFns-BK5O_pJ9.js} +3 -3
  47. package/dist/{meshFns-fPnPGEPl.cjs → meshFns-BoF6g1go.cjs} +3 -3
  48. package/dist/{occtWasmAdapter-B2TEQOjs.cjs → occtWasmAdapter-Bj9dX3Lb.cjs} +45 -10
  49. package/dist/{occtWasmAdapter-CKwtxwNH.js → occtWasmAdapter-wBLknH8f.js} +45 -10
  50. package/dist/operations.cjs +2 -2
  51. package/dist/operations.js +2 -2
  52. package/dist/{primitiveFns-DxP7GVQh.js → primitiveFns-DJUXtN6W.js} +4 -4
  53. package/dist/{primitiveFns-Cohp602Q.cjs → primitiveFns-DOyyz54-.cjs} +4 -4
  54. package/dist/projection.cjs +1 -1
  55. package/dist/projection.js +1 -1
  56. package/dist/query.cjs +2 -2
  57. package/dist/query.js +2 -2
  58. package/dist/{refResolveFns-NGXXyTBa.js → refResolveFns-DL1zXF8u.js} +18 -11
  59. package/dist/{refResolveFns-Du0v4e3Y.cjs → refResolveFns-DbTYd_Nj.cjs} +18 -11
  60. package/dist/{shapeFns-DqGDtCsE.js → shapeFns-DED4C70_.js} +84 -23
  61. package/dist/{shapeFns-DvmBIZaX.cjs → shapeFns-DxAhtxFP.cjs} +84 -23
  62. package/dist/shapeRef.cjs +1 -1
  63. package/dist/shapeRef.js +1 -1
  64. package/dist/{shapeTypes-EI4Uxlsb.js → shapeTypes-BguzpBK0.js} +70 -22
  65. package/dist/{shapeTypes-BLREi6xB.cjs → shapeTypes-Ca76Bklg.cjs} +87 -21
  66. package/dist/sketching.cjs +3 -3
  67. package/dist/sketching.js +3 -3
  68. package/dist/{solidBuilders-Oq-hxTvJ.js → solidBuilders-_8mqPO7I.js} +6 -5
  69. package/dist/{solidBuilders-B6DBRdRc.cjs → solidBuilders-yyfZ2Q-7.cjs} +6 -5
  70. package/dist/{surfaceBuilders-C0pJwtCO.js → surfaceBuilders-CT__o6Be.js} +2 -2
  71. package/dist/{surfaceBuilders-C-UhFlpS.cjs → surfaceBuilders-De2XKidK.cjs} +2 -2
  72. package/dist/text.cjs +2 -2
  73. package/dist/text.js +2 -2
  74. package/dist/{textBlueprints-lSKzw-nT.cjs → textBlueprints-C17PPSzH.cjs} +9 -8
  75. package/dist/{textBlueprints-fBpuvT3m.js → textBlueprints-D9l3v-f3.js} +9 -8
  76. package/dist/{textMetrics-BkTdFBwC.cjs → textMetrics-BwQ7TD47.cjs} +1 -1
  77. package/dist/{textMetrics-CnrAQwLI.js → textMetrics-GSAxEtaJ.js} +1 -1
  78. package/dist/{threadFns-DJt3EStH.cjs → threadFns-Bu2c-lu_.cjs} +8 -8
  79. package/dist/{threadFns-Dlts4xFy.js → threadFns-DOqhI5OK.js} +8 -8
  80. package/dist/topology.cjs +9 -9
  81. package/dist/topology.js +9 -9
  82. package/dist/{topologyQueryFns-1PqfeHrV.cjs → topologyQueryFns-B1QAiZVI.cjs} +1 -1
  83. package/dist/{topologyQueryFns-BjgBf2t-.js → topologyQueryFns-Dl2F-2d9.js} +1 -1
  84. package/package.json +16 -15
@@ -1,6 +1,6 @@
1
- import { Z as getKernel, t as castShape } from "./shapeTypes-EI4Uxlsb.js";
1
+ import { et as getKernel, t as castResultShape } from "./shapeTypes-BguzpBK0.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-BjgBf2t-.js";
3
+ import { C as kernelCall, c as getFaces, l as getOrCreateCache, r as getCacheEntry, w as kernelCallRaw } from "./topologyQueryFns-Dl2F-2d9.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
  /**
@@ -13,8 +13,12 @@ import { n as HASH_CODE_MAX, t as DEG2RAD } from "./constants-ITRzCnCp.js";
13
13
  */
14
14
  function setShapeOrigin(shape, origin) {
15
15
  const cache = getOrCreateCache(shape);
16
+ const kernel = getKernel();
16
17
  const map = /* @__PURE__ */ new Map();
17
- for (const f of getFaces(shape)) map.set(getKernel().hashCode(f.wrapped, HASH_CODE_MAX), origin);
18
+ for (const face of kernel.iterShapes(shape.wrapped, "face")) {
19
+ map.set(kernel.hashCode(face, HASH_CODE_MAX), origin);
20
+ kernel.dispose(face);
21
+ }
18
22
  cache.faceOrigins = map;
19
23
  }
20
24
  /**
@@ -51,13 +55,44 @@ function propagateOriginsFromEvolution(evolution, inputs, result) {
51
55
  cache.faceOrigins = resultMap;
52
56
  }
53
57
  }
58
+ /** Max 3D centroid separation (mm²) for a "near" match — the general case. */
59
+ var MAX_MATCH_DIST_SQ = 100;
60
+ /** Min |outNormal·inNormal| for two faces to count as sharing a plane's orientation. */
61
+ var COPLANAR_NORMAL_MIN = .985;
62
+ /**
63
+ * Max squared perpendicular offset (mm²) from an input face's plane for the
64
+ * output face to count as *coplanar* with it — i.e. a piece the boolean sliced
65
+ * off that same planar surface. 0.1mm² tolerates kernel/mesh float noise while
66
+ * still separating floors that sit even a fraction of a mm apart in depth.
67
+ */
68
+ var COPLANAR_OFFSET_SQ = .1;
54
69
  /**
55
70
  * Find the best origin match for a result face by comparing normals and centroids
56
71
  * against input face signatures. Returns `undefined` if no good match exists.
72
+ *
73
+ * Two passes, near strictly preferred:
74
+ * 1. Near match — same-facing normal (signed dot ≥ 0.707) and centroids within
75
+ * {@link MAX_MATCH_DIST_SQ}. The general case for regenerated seam faces
76
+ * (feature tops, flush walls). Unchanged legacy behavior. If any near match
77
+ * exists it wins outright — the coplanar pass never runs — so a distant
78
+ * coplanar candidate can't outscore a valid near one.
79
+ * 2. Coplanar match (only when no near match, and only between PLANAR faces) —
80
+ * the output face lies on an input face's plane (parallel *or* antiparallel
81
+ * normal, tiny perpendicular offset) regardless of how far its centroid
82
+ * drifted in-plane. Two cases pass 1 misses:
83
+ * • A boolean that splits a large planar face — a wide cutout floor sliced
84
+ * by an overlapping deeper cutout — leaves pieces >10mm from the parent
85
+ * centroid (past pass 1's cutoff).
86
+ * • A cut flips the cavity face's normal versus the tool face it came from,
87
+ * so the floor is antiparallel to its origin's input face.
88
+ * Both left the surface tagless → body color in multi-color consumers
89
+ * (gridfinity GH #2443). The planar gate keeps a curved output face from
90
+ * inheriting an origin off an unrelated face that merely shares a sampled
91
+ * tangent plane; among coplanar candidates the nearest in-plane wins.
57
92
  */
58
- function findBestOriginMatch(outNormal, outCentroid, inputSigs) {
59
- let bestScore = -Infinity;
60
- let bestOrigin;
93
+ function findBestOriginMatch(outNormal, outCentroid, outPlanar, inputSigs) {
94
+ let bestNearScore = -Infinity;
95
+ let bestNear;
61
96
  for (const inp of inputSigs) {
62
97
  const dot = outNormal[0] * inp.normal[0] + outNormal[1] * inp.normal[1] + outNormal[2] * inp.normal[2];
63
98
  if (dot < .707) continue;
@@ -65,14 +100,33 @@ function findBestOriginMatch(outNormal, outCentroid, inputSigs) {
65
100
  const dy = outCentroid[1] - inp.centroid[1];
66
101
  const dz = outCentroid[2] - inp.centroid[2];
67
102
  const distSq = dx * dx + dy * dy + dz * dz;
68
- if (distSq > 100) continue;
69
- const score = dot - distSq / 100;
70
- if (score > bestScore) {
71
- bestScore = score;
72
- bestOrigin = inp.origin;
103
+ if (distSq > MAX_MATCH_DIST_SQ) continue;
104
+ const score = dot - distSq / MAX_MATCH_DIST_SQ;
105
+ if (score > bestNearScore) {
106
+ bestNearScore = score;
107
+ bestNear = inp.origin;
73
108
  }
74
109
  }
75
- return bestOrigin;
110
+ if (bestNear !== void 0) return bestNear;
111
+ if (!outPlanar) return void 0;
112
+ let bestInPlaneSq = Infinity;
113
+ let bestCoplanar;
114
+ for (const inp of inputSigs) {
115
+ if (!inp.planar) continue;
116
+ const dot = outNormal[0] * inp.normal[0] + outNormal[1] * inp.normal[1] + outNormal[2] * inp.normal[2];
117
+ if (Math.abs(dot) < COPLANAR_NORMAL_MIN) continue;
118
+ const dx = outCentroid[0] - inp.centroid[0];
119
+ const dy = outCentroid[1] - inp.centroid[1];
120
+ const dz = outCentroid[2] - inp.centroid[2];
121
+ const perp = dx * inp.normal[0] + dy * inp.normal[1] + dz * inp.normal[2];
122
+ if (perp * perp > COPLANAR_OFFSET_SQ) continue;
123
+ const inPlaneSq = Math.max(0, dx * dx + dy * dy + dz * dz - perp * perp);
124
+ if (inPlaneSq < bestInPlaneSq) {
125
+ bestInPlaneSq = inPlaneSq;
126
+ bestCoplanar = inp.origin;
127
+ }
128
+ }
129
+ return bestCoplanar;
76
130
  }
77
131
  /**
78
132
  * Fallback origin propagation when no kernel op object is available
@@ -126,10 +180,12 @@ function propagateOriginsByHash(inputs, result) {
126
180
  const bounds = kernel.uvBounds(f.wrapped);
127
181
  const normal = kernel.surfaceNormal(f.wrapped, .5 * (bounds.uMin + bounds.uMax), .5 * (bounds.vMin + bounds.vMax));
128
182
  const centroid = kernel.surfaceCenterOfMass(f.wrapped);
183
+ const planar = kernel.surfaceType(f.wrapped) === "plane";
129
184
  inputSigs.push({
130
185
  origin,
131
186
  normal,
132
- centroid
187
+ centroid,
188
+ planar
133
189
  });
134
190
  } catch {}
135
191
  }
@@ -138,7 +194,7 @@ function propagateOriginsByHash(inputs, result) {
138
194
  const hash = kernel.hashCode(f.wrapped, HASH_CODE_MAX);
139
195
  try {
140
196
  const outBounds = kernel.uvBounds(f.wrapped);
141
- const bestOrigin = findBestOriginMatch(kernel.surfaceNormal(f.wrapped, .5 * (outBounds.uMin + outBounds.uMax), .5 * (outBounds.vMin + outBounds.vMax)), kernel.surfaceCenterOfMass(f.wrapped), inputSigs);
197
+ const bestOrigin = findBestOriginMatch(kernel.surfaceNormal(f.wrapped, .5 * (outBounds.uMin + outBounds.uMax), .5 * (outBounds.vMin + outBounds.vMax)), kernel.surfaceCenterOfMass(f.wrapped), kernel.surfaceType(f.wrapped) === "plane", inputSigs);
142
198
  if (bestOrigin !== void 0) resultMap.set(hash, bestOrigin);
143
199
  } catch {}
144
200
  }
@@ -390,7 +446,10 @@ function collectInputFaceHashes(inputs) {
390
446
  const hashes = [];
391
447
  for (const input of inputs) {
392
448
  const faces = kernel.iterShapes(input.wrapped, "face");
393
- for (const face of faces) hashes.push(kernel.hashCode(face, HASH_CODE_MAX));
449
+ for (const face of faces) {
450
+ hashes.push(kernel.hashCode(face, HASH_CODE_MAX));
451
+ kernel.dispose(face);
452
+ }
394
453
  }
395
454
  return hashes;
396
455
  }
@@ -446,6 +505,8 @@ function propagateMetadataThroughRelocation(source, moved) {
446
505
  if (sf === void 0 || mf === void 0) continue;
447
506
  modified.set(kernel.hashCode(sf, HASH_CODE_MAX), [kernel.hashCode(mf, HASH_CODE_MAX)]);
448
507
  }
508
+ for (const f of srcFaces) kernel.dispose(f);
509
+ for (const f of movedFaces) kernel.dispose(f);
449
510
  propagateAllMetadata({
450
511
  modified,
451
512
  generated: /* @__PURE__ */ new Map(),
@@ -462,7 +523,7 @@ function propagateMetadataThroughRelocation(source, moved) {
462
523
  function translate(shape, v) {
463
524
  const inputFaceHashes = collectInputFaceHashes([shape]);
464
525
  const { shape: resultShape, evolution } = getKernel().translateWithHistory(shape.wrapped, v[0], v[1], v[2], inputFaceHashes, HASH_CODE_MAX);
465
- const result = castShape(resultShape);
526
+ const result = castResultShape(resultShape);
466
527
  propagateAllMetadata(evolution, [shape], result);
467
528
  return result;
468
529
  }
@@ -478,7 +539,7 @@ function rotate(shape, angle, position = [
478
539
  ]) {
479
540
  const inputFaceHashes = collectInputFaceHashes([shape]);
480
541
  const { shape: resultShape, evolution } = getKernel().rotateWithHistory(shape.wrapped, angle * DEG2RAD, inputFaceHashes, HASH_CODE_MAX, direction, position);
481
- const result = castShape(resultShape);
542
+ const result = castResultShape(resultShape);
482
543
  propagateAllMetadata(evolution, [shape], result);
483
544
  return result;
484
545
  }
@@ -494,7 +555,7 @@ function mirror(shape, planeNormal = [
494
555
  ]) {
495
556
  const inputFaceHashes = collectInputFaceHashes([shape]);
496
557
  const { shape: resultShape, evolution } = getKernel().mirrorWithHistory(shape.wrapped, planeOrigin, planeNormal, inputFaceHashes, HASH_CODE_MAX);
497
- const result = castShape(resultShape);
558
+ const result = castResultShape(resultShape);
498
559
  propagateAllMetadata(evolution, [shape], result);
499
560
  return result;
500
561
  }
@@ -506,7 +567,7 @@ function scale(shape, factor, center = [
506
567
  ]) {
507
568
  const inputFaceHashes = collectInputFaceHashes([shape]);
508
569
  const { shape: resultShape, evolution } = getKernel().scaleWithHistory(shape.wrapped, center, factor, inputFaceHashes, HASH_CODE_MAX);
509
- const result = castShape(resultShape);
570
+ const result = castResultShape(resultShape);
510
571
  propagateAllMetadata(evolution, [shape], result);
511
572
  return result;
512
573
  }
@@ -608,11 +669,11 @@ function applyMatrix(shape, matrix) {
608
669
  if (isOrthogonalMatrix(linear)) {
609
670
  const inputFaceHashes = collectInputFaceHashes([shape]);
610
671
  const { shape: resultShape, evolution } = getKernel().generalTransformWithHistory(shape.wrapped, linear, translation, true, inputFaceHashes, HASH_CODE_MAX);
611
- const result = castShape(resultShape);
672
+ const result = castResultShape(resultShape);
612
673
  propagateAllMetadata(evolution, [shape], result);
613
674
  return ok(result);
614
675
  }
615
- const result = castShape(getKernel().generalTransformNonOrthogonal(shape.wrapped, linear, translation));
676
+ const result = castResultShape(getKernel().generalTransformNonOrthogonal(shape.wrapped, linear, translation));
616
677
  propagateMetadataByHash([shape], result);
617
678
  return ok(result);
618
679
  /* v8 ignore stop */
@@ -661,7 +722,7 @@ function locate(shape, placement) {
661
722
  const { trsf, cleanup } = composeTransforms("type" in placement ? [placement] : placement);
662
723
  let moved;
663
724
  try {
664
- moved = castShape(getKernel().locate(shape.wrapped, trsf));
725
+ moved = castResultShape(getKernel().locate(shape.wrapped, trsf));
665
726
  } finally {
666
727
  cleanup();
667
728
  }
@@ -675,7 +736,7 @@ function locate(shape, placement) {
675
736
  function transformCopy(shape, composed) {
676
737
  const inputFaceHashes = collectInputFaceHashes([shape]);
677
738
  const { shape: resultShape, evolution } = getKernel().applyComposedTransformWithHistory(shape.wrapped, composed.trsf, inputFaceHashes, HASH_CODE_MAX);
678
- const result = castShape(resultShape);
739
+ const result = castResultShape(resultShape);
679
740
  propagateAllMetadata(evolution, [shape], result);
680
741
  return result;
681
742
  }
@@ -1,6 +1,6 @@
1
- const require_shapeTypes = require("./shapeTypes-BLREi6xB.cjs");
1
+ const require_shapeTypes = require("./shapeTypes-Ca76Bklg.cjs");
2
2
  const require_errors = require("./errors-CXJtc4I7.cjs");
3
- const require_topologyQueryFns = require("./topologyQueryFns-1PqfeHrV.cjs");
3
+ const require_topologyQueryFns = require("./topologyQueryFns-B1QAiZVI.cjs");
4
4
  const require_constants = require("./constants-BOVyEYGH.cjs");
5
5
  //#region src/topology/metadata/originTrackingFns.ts
6
6
  /**
@@ -13,8 +13,12 @@ const require_constants = require("./constants-BOVyEYGH.cjs");
13
13
  */
14
14
  function setShapeOrigin(shape, origin) {
15
15
  const cache = require_topologyQueryFns.getOrCreateCache(shape);
16
+ const kernel = require_shapeTypes.getKernel();
16
17
  const map = /* @__PURE__ */ new Map();
17
- for (const f of require_topologyQueryFns.getFaces(shape)) map.set(require_shapeTypes.getKernel().hashCode(f.wrapped, require_constants.HASH_CODE_MAX), origin);
18
+ for (const face of kernel.iterShapes(shape.wrapped, "face")) {
19
+ map.set(kernel.hashCode(face, require_constants.HASH_CODE_MAX), origin);
20
+ kernel.dispose(face);
21
+ }
18
22
  cache.faceOrigins = map;
19
23
  }
20
24
  /**
@@ -51,13 +55,44 @@ function propagateOriginsFromEvolution(evolution, inputs, result) {
51
55
  cache.faceOrigins = resultMap;
52
56
  }
53
57
  }
58
+ /** Max 3D centroid separation (mm²) for a "near" match — the general case. */
59
+ var MAX_MATCH_DIST_SQ = 100;
60
+ /** Min |outNormal·inNormal| for two faces to count as sharing a plane's orientation. */
61
+ var COPLANAR_NORMAL_MIN = .985;
62
+ /**
63
+ * Max squared perpendicular offset (mm²) from an input face's plane for the
64
+ * output face to count as *coplanar* with it — i.e. a piece the boolean sliced
65
+ * off that same planar surface. 0.1mm² tolerates kernel/mesh float noise while
66
+ * still separating floors that sit even a fraction of a mm apart in depth.
67
+ */
68
+ var COPLANAR_OFFSET_SQ = .1;
54
69
  /**
55
70
  * Find the best origin match for a result face by comparing normals and centroids
56
71
  * against input face signatures. Returns `undefined` if no good match exists.
72
+ *
73
+ * Two passes, near strictly preferred:
74
+ * 1. Near match — same-facing normal (signed dot ≥ 0.707) and centroids within
75
+ * {@link MAX_MATCH_DIST_SQ}. The general case for regenerated seam faces
76
+ * (feature tops, flush walls). Unchanged legacy behavior. If any near match
77
+ * exists it wins outright — the coplanar pass never runs — so a distant
78
+ * coplanar candidate can't outscore a valid near one.
79
+ * 2. Coplanar match (only when no near match, and only between PLANAR faces) —
80
+ * the output face lies on an input face's plane (parallel *or* antiparallel
81
+ * normal, tiny perpendicular offset) regardless of how far its centroid
82
+ * drifted in-plane. Two cases pass 1 misses:
83
+ * • A boolean that splits a large planar face — a wide cutout floor sliced
84
+ * by an overlapping deeper cutout — leaves pieces >10mm from the parent
85
+ * centroid (past pass 1's cutoff).
86
+ * • A cut flips the cavity face's normal versus the tool face it came from,
87
+ * so the floor is antiparallel to its origin's input face.
88
+ * Both left the surface tagless → body color in multi-color consumers
89
+ * (gridfinity GH #2443). The planar gate keeps a curved output face from
90
+ * inheriting an origin off an unrelated face that merely shares a sampled
91
+ * tangent plane; among coplanar candidates the nearest in-plane wins.
57
92
  */
58
- function findBestOriginMatch(outNormal, outCentroid, inputSigs) {
59
- let bestScore = -Infinity;
60
- let bestOrigin;
93
+ function findBestOriginMatch(outNormal, outCentroid, outPlanar, inputSigs) {
94
+ let bestNearScore = -Infinity;
95
+ let bestNear;
61
96
  for (const inp of inputSigs) {
62
97
  const dot = outNormal[0] * inp.normal[0] + outNormal[1] * inp.normal[1] + outNormal[2] * inp.normal[2];
63
98
  if (dot < .707) continue;
@@ -65,14 +100,33 @@ function findBestOriginMatch(outNormal, outCentroid, inputSigs) {
65
100
  const dy = outCentroid[1] - inp.centroid[1];
66
101
  const dz = outCentroid[2] - inp.centroid[2];
67
102
  const distSq = dx * dx + dy * dy + dz * dz;
68
- if (distSq > 100) continue;
69
- const score = dot - distSq / 100;
70
- if (score > bestScore) {
71
- bestScore = score;
72
- bestOrigin = inp.origin;
103
+ if (distSq > MAX_MATCH_DIST_SQ) continue;
104
+ const score = dot - distSq / MAX_MATCH_DIST_SQ;
105
+ if (score > bestNearScore) {
106
+ bestNearScore = score;
107
+ bestNear = inp.origin;
73
108
  }
74
109
  }
75
- return bestOrigin;
110
+ if (bestNear !== void 0) return bestNear;
111
+ if (!outPlanar) return void 0;
112
+ let bestInPlaneSq = Infinity;
113
+ let bestCoplanar;
114
+ for (const inp of inputSigs) {
115
+ if (!inp.planar) continue;
116
+ const dot = outNormal[0] * inp.normal[0] + outNormal[1] * inp.normal[1] + outNormal[2] * inp.normal[2];
117
+ if (Math.abs(dot) < COPLANAR_NORMAL_MIN) continue;
118
+ const dx = outCentroid[0] - inp.centroid[0];
119
+ const dy = outCentroid[1] - inp.centroid[1];
120
+ const dz = outCentroid[2] - inp.centroid[2];
121
+ const perp = dx * inp.normal[0] + dy * inp.normal[1] + dz * inp.normal[2];
122
+ if (perp * perp > COPLANAR_OFFSET_SQ) continue;
123
+ const inPlaneSq = Math.max(0, dx * dx + dy * dy + dz * dz - perp * perp);
124
+ if (inPlaneSq < bestInPlaneSq) {
125
+ bestInPlaneSq = inPlaneSq;
126
+ bestCoplanar = inp.origin;
127
+ }
128
+ }
129
+ return bestCoplanar;
76
130
  }
77
131
  /**
78
132
  * Fallback origin propagation when no kernel op object is available
@@ -126,10 +180,12 @@ function propagateOriginsByHash(inputs, result) {
126
180
  const bounds = kernel.uvBounds(f.wrapped);
127
181
  const normal = kernel.surfaceNormal(f.wrapped, .5 * (bounds.uMin + bounds.uMax), .5 * (bounds.vMin + bounds.vMax));
128
182
  const centroid = kernel.surfaceCenterOfMass(f.wrapped);
183
+ const planar = kernel.surfaceType(f.wrapped) === "plane";
129
184
  inputSigs.push({
130
185
  origin,
131
186
  normal,
132
- centroid
187
+ centroid,
188
+ planar
133
189
  });
134
190
  } catch {}
135
191
  }
@@ -138,7 +194,7 @@ function propagateOriginsByHash(inputs, result) {
138
194
  const hash = kernel.hashCode(f.wrapped, require_constants.HASH_CODE_MAX);
139
195
  try {
140
196
  const outBounds = kernel.uvBounds(f.wrapped);
141
- const bestOrigin = findBestOriginMatch(kernel.surfaceNormal(f.wrapped, .5 * (outBounds.uMin + outBounds.uMax), .5 * (outBounds.vMin + outBounds.vMax)), kernel.surfaceCenterOfMass(f.wrapped), inputSigs);
197
+ const bestOrigin = findBestOriginMatch(kernel.surfaceNormal(f.wrapped, .5 * (outBounds.uMin + outBounds.uMax), .5 * (outBounds.vMin + outBounds.vMax)), kernel.surfaceCenterOfMass(f.wrapped), kernel.surfaceType(f.wrapped) === "plane", inputSigs);
142
198
  if (bestOrigin !== void 0) resultMap.set(hash, bestOrigin);
143
199
  } catch {}
144
200
  }
@@ -390,7 +446,10 @@ function collectInputFaceHashes(inputs) {
390
446
  const hashes = [];
391
447
  for (const input of inputs) {
392
448
  const faces = kernel.iterShapes(input.wrapped, "face");
393
- for (const face of faces) hashes.push(kernel.hashCode(face, require_constants.HASH_CODE_MAX));
449
+ for (const face of faces) {
450
+ hashes.push(kernel.hashCode(face, require_constants.HASH_CODE_MAX));
451
+ kernel.dispose(face);
452
+ }
394
453
  }
395
454
  return hashes;
396
455
  }
@@ -446,6 +505,8 @@ function propagateMetadataThroughRelocation(source, moved) {
446
505
  if (sf === void 0 || mf === void 0) continue;
447
506
  modified.set(kernel.hashCode(sf, require_constants.HASH_CODE_MAX), [kernel.hashCode(mf, require_constants.HASH_CODE_MAX)]);
448
507
  }
508
+ for (const f of srcFaces) kernel.dispose(f);
509
+ for (const f of movedFaces) kernel.dispose(f);
449
510
  propagateAllMetadata({
450
511
  modified,
451
512
  generated: /* @__PURE__ */ new Map(),
@@ -462,7 +523,7 @@ function propagateMetadataThroughRelocation(source, moved) {
462
523
  function translate(shape, v) {
463
524
  const inputFaceHashes = collectInputFaceHashes([shape]);
464
525
  const { shape: resultShape, evolution } = require_shapeTypes.getKernel().translateWithHistory(shape.wrapped, v[0], v[1], v[2], inputFaceHashes, require_constants.HASH_CODE_MAX);
465
- const result = require_shapeTypes.castShape(resultShape);
526
+ const result = require_shapeTypes.castResultShape(resultShape);
466
527
  propagateAllMetadata(evolution, [shape], result);
467
528
  return result;
468
529
  }
@@ -478,7 +539,7 @@ function rotate(shape, angle, position = [
478
539
  ]) {
479
540
  const inputFaceHashes = collectInputFaceHashes([shape]);
480
541
  const { shape: resultShape, evolution } = require_shapeTypes.getKernel().rotateWithHistory(shape.wrapped, angle * require_constants.DEG2RAD, inputFaceHashes, require_constants.HASH_CODE_MAX, direction, position);
481
- const result = require_shapeTypes.castShape(resultShape);
542
+ const result = require_shapeTypes.castResultShape(resultShape);
482
543
  propagateAllMetadata(evolution, [shape], result);
483
544
  return result;
484
545
  }
@@ -494,7 +555,7 @@ function mirror(shape, planeNormal = [
494
555
  ]) {
495
556
  const inputFaceHashes = collectInputFaceHashes([shape]);
496
557
  const { shape: resultShape, evolution } = require_shapeTypes.getKernel().mirrorWithHistory(shape.wrapped, planeOrigin, planeNormal, inputFaceHashes, require_constants.HASH_CODE_MAX);
497
- const result = require_shapeTypes.castShape(resultShape);
558
+ const result = require_shapeTypes.castResultShape(resultShape);
498
559
  propagateAllMetadata(evolution, [shape], result);
499
560
  return result;
500
561
  }
@@ -506,7 +567,7 @@ function scale(shape, factor, center = [
506
567
  ]) {
507
568
  const inputFaceHashes = collectInputFaceHashes([shape]);
508
569
  const { shape: resultShape, evolution } = require_shapeTypes.getKernel().scaleWithHistory(shape.wrapped, center, factor, inputFaceHashes, require_constants.HASH_CODE_MAX);
509
- const result = require_shapeTypes.castShape(resultShape);
570
+ const result = require_shapeTypes.castResultShape(resultShape);
510
571
  propagateAllMetadata(evolution, [shape], result);
511
572
  return result;
512
573
  }
@@ -608,11 +669,11 @@ function applyMatrix(shape, matrix) {
608
669
  if (isOrthogonalMatrix(linear)) {
609
670
  const inputFaceHashes = collectInputFaceHashes([shape]);
610
671
  const { shape: resultShape, evolution } = require_shapeTypes.getKernel().generalTransformWithHistory(shape.wrapped, linear, translation, true, inputFaceHashes, require_constants.HASH_CODE_MAX);
611
- const result = require_shapeTypes.castShape(resultShape);
672
+ const result = require_shapeTypes.castResultShape(resultShape);
612
673
  propagateAllMetadata(evolution, [shape], result);
613
674
  return require_errors.ok(result);
614
675
  }
615
- const result = require_shapeTypes.castShape(require_shapeTypes.getKernel().generalTransformNonOrthogonal(shape.wrapped, linear, translation));
676
+ const result = require_shapeTypes.castResultShape(require_shapeTypes.getKernel().generalTransformNonOrthogonal(shape.wrapped, linear, translation));
616
677
  propagateMetadataByHash([shape], result);
617
678
  return require_errors.ok(result);
618
679
  /* v8 ignore stop */
@@ -661,7 +722,7 @@ function locate(shape, placement) {
661
722
  const { trsf, cleanup } = composeTransforms("type" in placement ? [placement] : placement);
662
723
  let moved;
663
724
  try {
664
- moved = require_shapeTypes.castShape(require_shapeTypes.getKernel().locate(shape.wrapped, trsf));
725
+ moved = require_shapeTypes.castResultShape(require_shapeTypes.getKernel().locate(shape.wrapped, trsf));
665
726
  } finally {
666
727
  cleanup();
667
728
  }
@@ -675,7 +736,7 @@ function locate(shape, placement) {
675
736
  function transformCopy(shape, composed) {
676
737
  const inputFaceHashes = collectInputFaceHashes([shape]);
677
738
  const { shape: resultShape, evolution } = require_shapeTypes.getKernel().applyComposedTransformWithHistory(shape.wrapped, composed.trsf, inputFaceHashes, require_constants.HASH_CODE_MAX);
678
- const result = require_shapeTypes.castShape(resultShape);
739
+ const result = require_shapeTypes.castResultShape(resultShape);
679
740
  propagateAllMetadata(evolution, [shape], result);
680
741
  return result;
681
742
  }
package/dist/shapeRef.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_refResolveFns = require("./refResolveFns-Du0v4e3Y.cjs");
2
+ const require_refResolveFns = require("./refResolveFns-DbTYd_Nj.cjs");
3
3
  exports.assignRoles = require_refResolveFns.assignRoles;
4
4
  exports.captureHint = require_refResolveFns.captureHint;
5
5
  exports.createDerivedFaceRef = require_refResolveFns.createDerivedFaceRef;
package/dist/shapeRef.js CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as createRef, a as isVertexRef, b as defaultScorer, c as resolveRefParams, d as createVertexRef, f as resolveVertexRef, g as captureHint, h as assignRoles, i as isLineageRef, l as createDerivedFaceRef, m as resolveEdgeRef, n as isEdgeRef, o as resolveLineageRef, p as createEdgeRef, r as isFaceRef, s as resolveRefIn, t as isDerivedFaceRef, u as resolveDerivedFaceRef, v as resolveRef, y as updateRoles } from "./refResolveFns-NGXXyTBa.js";
1
+ import { _ as createRef, a as isVertexRef, b as defaultScorer, c as resolveRefParams, d as createVertexRef, f as resolveVertexRef, g as captureHint, h as assignRoles, i as isLineageRef, l as createDerivedFaceRef, m as resolveEdgeRef, n as isEdgeRef, o as resolveLineageRef, p as createEdgeRef, r as isFaceRef, s as resolveRefIn, t as isDerivedFaceRef, u as resolveDerivedFaceRef, v as resolveRef, y as updateRoles } from "./refResolveFns-DL1zXF8u.js";
2
2
  export { assignRoles, captureHint, createDerivedFaceRef, createEdgeRef, createRef, createVertexRef, defaultScorer, isDerivedFaceRef, isEdgeRef, isFaceRef, isLineageRef, isVertexRef, resolveDerivedFaceRef, resolveEdgeRef, resolveLineageRef, resolveRef, resolveRefIn, resolveRefParams, resolveVertexRef, updateRoles };