partforge 0.117.0 → 0.117.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "partforge",
3
- "version": "0.117.0",
3
+ "version": "0.117.1",
4
4
  "description": "Turn a declarative part definition into a parametric-CAD web app (three.js + Manifold/Replicad). Requires a Vite-based consumer.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -147,9 +147,22 @@ export function createManifoldKernel(wasm, { quality = "preview" } = {}) {
147
147
  const images = new Map();
148
148
  // Boundary ops route through cache.lookup; on a miss `make` runs the WASM op,
149
149
  // tracks the result, and returns the triple the cache needs to pin/dispose it.
150
+ // Delete an embind handle once. Every WASM object a build makes is T()-tracked
151
+ // for cleanup(), and the cached ones are ALSO owned by the solid cache, which
152
+ // disposes them when a later round evicts them. Those two owners only stay
153
+ // apart because cleanup() normally runs at the end of every build round and
154
+ // empties `tracked` — a build that THROWS on a path without a finally-cleanup
155
+ // (the oracle's measure(), a host's own build loop) leaves that round's new
156
+ // cached solids on `tracked`; the next round evicts them, the cache deletes
157
+ // them, and the cleanup after that deletes them again: "Manifold instance
158
+ // already deleted", on every build for the rest of the kernel's life. Two
159
+ // failing builds in a row was the reproduction (2026-09-18: plate → boss →
160
+ // bad cut → hole → bad cut → anything). embind knows whether a handle is
161
+ // gone, so both owners ask before deleting instead of trusting the other.
162
+ const del = (o) => { if (!o?.isDeleted?.()) o?.delete?.(); };
150
163
  const cached = (hash, computeM) => cache.lookup(hash, () => {
151
164
  const m = computeM(); // already T()-tracked by the op
152
- return { value: wrap(m, hash), pin: m, dispose: () => m.delete?.() };
165
+ return { value: wrap(m, hash), pin: m, dispose: () => del(m) };
153
166
  });
154
167
 
155
168
  // Booleans commute with any invertible affine map, so a transform EVERY operand
@@ -188,7 +201,7 @@ export function createManifoldKernel(wasm, { quality = "preview" } = {}) {
188
201
  // the entry (cleanup() skips pinned objects).
189
202
  const csFor = (shape) => cache.lookup(h("cs2d", shape._hash, segs), () => {
190
203
  const cs = T(CrossSection.ofPolygons(regionPolys(shape._regions, segsAt), "EvenOdd"));
191
- return { value: cs, pin: cs, dispose: () => cs.delete?.() };
204
+ return { value: cs, pin: cs, dispose: () => del(cs) };
192
205
  });
193
206
  // The same shape as a LATHE profile: its arcs sampled at the double-curvature count
194
207
  // (every sample becomes a full ring of the sweep — the quadratic cost circle-segs.js
@@ -196,7 +209,7 @@ export function createManifoldKernel(wasm, { quality = "preview" } = {}) {
196
209
  // own key. Keyed on the tier, not a count: the count varies per arc radius.
197
210
  const csForLathe = (shape) => cache.lookup(h("cs2d-lathe", shape._hash, quality), () => {
198
211
  const cs = T(CrossSection.ofPolygons(regionPolys(shape._regions, dcAt), "EvenOdd"));
199
- return { value: cs, pin: cs, dispose: () => cs.delete?.() };
212
+ return { value: cs, pin: cs, dispose: () => del(cs) };
200
213
  });
201
214
 
202
215
  // Copy the mesh out into JS-owned arrays (so it survives cleanup) and free the
@@ -885,7 +898,7 @@ export function createManifoldKernel(wasm, { quality = "preview" } = {}) {
885
898
  _warnProfile: profileWarner.warn,
886
899
  // Free every WASM object created since the last cleanup EXCEPT solids the cache
887
900
  // still pins (they must survive for the next build to resume from them).
888
- cleanup: () => { for (const o of tracked) if (!cache.isPinned(o)) o.delete?.(); tracked.length = 0; },
901
+ cleanup: () => { for (const o of tracked) if (!cache.isPinned(o)) del(o); tracked.length = 0; },
889
902
  });
890
903
  return kernel;
891
904
  }