@vcad/mcp 0.9.4-main.11 → 0.9.4-main.14

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/index.mjs CHANGED
@@ -68,8 +68,11 @@ __export(vcad_kernel_wasm_exports, {
68
68
  atoms_minimize: () => atoms_minimize,
69
69
  atoms_parse_xyz: () => atoms_parse_xyz,
70
70
  atoms_write_xyz: () => atoms_write_xyz,
71
+ buildCalibrationReportJson: () => buildCalibrationReportJson,
71
72
  buildPart: () => buildPart,
72
73
  build_chat_system_prompt: () => build_chat_system_prompt,
74
+ calibrationDefaultTolerance: () => calibrationDefaultTolerance,
75
+ calibrationFingerprintDocument: () => calibrationFingerprintDocument,
73
76
  camDropCutter: () => camDropCutter,
74
77
  camExportGcode: () => camExportGcode,
75
78
  camExportLinuxCnc: () => camExportLinuxCnc,
@@ -155,6 +158,9 @@ __export(vcad_kernel_wasm_exports, {
155
158
  exportKicadProject: () => exportKicadProject,
156
159
  exportKicadSch: () => exportKicadSch,
157
160
  exportProjectedViewToDxf: () => exportProjectedViewToDxf,
161
+ exprEvalAst: () => exprEvalAst,
162
+ exprEvaluate: () => exprEvaluate,
163
+ exprParse: () => exprParse,
158
164
  feaAnalyzeMesh: () => feaAnalyzeMesh,
159
165
  generate3mf: () => generate3mf,
160
166
  generate3mfWithGcode: () => generate3mfWithGcode,
@@ -184,6 +190,7 @@ __export(vcad_kernel_wasm_exports, {
184
190
  nestSheetMetalParts: () => nestSheetMetalParts,
185
191
  nestedSheetMetalDxf: () => nestedSheetMetalDxf,
186
192
  neutronicsSimulate: () => neutronicsSimulate,
193
+ noteToHz: () => noteToHz,
187
194
  offsetSectionMesh: () => offsetSectionMesh,
188
195
  op_chamfer: () => op_chamfer,
189
196
  op_circular_pattern: () => op_circular_pattern,
@@ -218,11 +225,14 @@ __export(vcad_kernel_wasm_exports, {
218
225
  render_svg_view: () => render_svg_view,
219
226
  render_svg_view_highlight: () => render_svg_view_highlight,
220
227
  render_svg_view_section: () => render_svg_view_section,
228
+ resolveDocumentJson: () => resolveDocumentJson,
229
+ resolveParametersJson: () => resolveParametersJson,
221
230
  sample_timeline_sequence: () => sample_timeline_sequence,
222
231
  sample_timeline_track: () => sample_timeline_track,
223
232
  sectionMesh: () => sectionMesh,
224
233
  sheetMetalFoldedStep: () => sheetMetalFoldedStep,
225
234
  sheetMetalSequence: () => sheetMetalSequence,
235
+ simulateStrikeKernel: () => simulateStrikeKernel,
226
236
  sketchCircleSegments: () => sketchCircleSegments,
227
237
  sketchHitTest: () => sketchHitTest,
228
238
  sketchPlaneBasis: () => sketchPlaneBasis,
@@ -428,6 +438,31 @@ function atoms_write_xyz(molecule_json) {
428
438
  wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
429
439
  }
430
440
  }
441
+ function buildCalibrationReportJson(prediction_json, measurements_json, options_json) {
442
+ let deferred5_0;
443
+ let deferred5_1;
444
+ try {
445
+ const ptr0 = passStringToWasm0(prediction_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
446
+ const len0 = WASM_VECTOR_LEN;
447
+ const ptr1 = passStringToWasm0(measurements_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
448
+ const len1 = WASM_VECTOR_LEN;
449
+ const ptr2 = passStringToWasm0(options_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
450
+ const len2 = WASM_VECTOR_LEN;
451
+ const ret = wasm.buildCalibrationReportJson(ptr0, len0, ptr1, len1, ptr2, len2);
452
+ var ptr4 = ret[0];
453
+ var len4 = ret[1];
454
+ if (ret[3]) {
455
+ ptr4 = 0;
456
+ len4 = 0;
457
+ throw takeFromExternrefTable0(ret[2]);
458
+ }
459
+ deferred5_0 = ptr4;
460
+ deferred5_1 = len4;
461
+ return getStringFromWasm0(ptr4, len4);
462
+ } finally {
463
+ wasm.__wbindgen_free(deferred5_0, deferred5_1, 1);
464
+ }
465
+ }
431
466
  function buildPart(path, params_json) {
432
467
  let deferred4_0;
433
468
  let deferred4_1;
@@ -467,6 +502,36 @@ function build_chat_system_prompt(parts_json, selection_json) {
467
502
  wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
468
503
  }
469
504
  }
505
+ function calibrationDefaultTolerance(kind, predicted) {
506
+ const ptr0 = passStringToWasm0(kind, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
507
+ const len0 = WASM_VECTOR_LEN;
508
+ const ret = wasm.calibrationDefaultTolerance(ptr0, len0, predicted);
509
+ if (ret[2]) {
510
+ throw takeFromExternrefTable0(ret[1]);
511
+ }
512
+ return ret[0];
513
+ }
514
+ function calibrationFingerprintDocument(doc_json) {
515
+ let deferred3_0;
516
+ let deferred3_1;
517
+ try {
518
+ const ptr0 = passStringToWasm0(doc_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
519
+ const len0 = WASM_VECTOR_LEN;
520
+ const ret = wasm.calibrationFingerprintDocument(ptr0, len0);
521
+ var ptr2 = ret[0];
522
+ var len2 = ret[1];
523
+ if (ret[3]) {
524
+ ptr2 = 0;
525
+ len2 = 0;
526
+ throw takeFromExternrefTable0(ret[2]);
527
+ }
528
+ deferred3_0 = ptr2;
529
+ deferred3_1 = len2;
530
+ return getStringFromWasm0(ptr2, len2);
531
+ } finally {
532
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
533
+ }
534
+ }
470
535
  function camDropCutter(vertices_json, indices_json, tool_json, bounds_json, resolution) {
471
536
  let deferred6_0;
472
537
  let deferred6_1;
@@ -1548,6 +1613,31 @@ function exportProjectedViewToDxf(view_json) {
1548
1613
  wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
1549
1614
  return v2;
1550
1615
  }
1616
+ function exprEvalAst(ast, env) {
1617
+ const ret = wasm.exprEvalAst(ast, env);
1618
+ if (ret[2]) {
1619
+ throw takeFromExternrefTable0(ret[1]);
1620
+ }
1621
+ return ret[0];
1622
+ }
1623
+ function exprEvaluate(src, env) {
1624
+ const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1625
+ const len0 = WASM_VECTOR_LEN;
1626
+ const ret = wasm.exprEvaluate(ptr0, len0, env);
1627
+ if (ret[2]) {
1628
+ throw takeFromExternrefTable0(ret[1]);
1629
+ }
1630
+ return ret[0];
1631
+ }
1632
+ function exprParse(src) {
1633
+ const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1634
+ const len0 = WASM_VECTOR_LEN;
1635
+ const ret = wasm.exprParse(ptr0, len0);
1636
+ if (ret[2]) {
1637
+ throw takeFromExternrefTable0(ret[1]);
1638
+ }
1639
+ return takeFromExternrefTable0(ret[0]);
1640
+ }
1551
1641
  function feaAnalyzeMesh(spec_json, options_json, positions, indices) {
1552
1642
  const ptr0 = passStringToWasm0(spec_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1553
1643
  const len0 = WASM_VECTOR_LEN;
@@ -1856,6 +1946,15 @@ function neutronicsSimulate(spec_json, params_json) {
1856
1946
  }
1857
1947
  return takeFromExternrefTable0(ret[0]);
1858
1948
  }
1949
+ function noteToHz(note) {
1950
+ const ptr0 = passStringToWasm0(note, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1951
+ const len0 = WASM_VECTOR_LEN;
1952
+ const ret = wasm.noteToHz(ptr0, len0);
1953
+ if (ret[2]) {
1954
+ throw takeFromExternrefTable0(ret[1]);
1955
+ }
1956
+ return ret[0];
1957
+ }
1859
1958
  function offsetSectionMesh(mesh_js, plane_json, hatch_json) {
1860
1959
  const ptr0 = passStringToWasm0(plane_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1861
1960
  const len0 = WASM_VECTOR_LEN;
@@ -2344,6 +2443,48 @@ function render_svg_view_section(vcad_json, scale, view, section) {
2344
2443
  wasm.__wbindgen_free(deferred5_0, deferred5_1, 1);
2345
2444
  }
2346
2445
  }
2446
+ function resolveDocumentJson(doc_json) {
2447
+ let deferred3_0;
2448
+ let deferred3_1;
2449
+ try {
2450
+ const ptr0 = passStringToWasm0(doc_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
2451
+ const len0 = WASM_VECTOR_LEN;
2452
+ const ret = wasm.resolveDocumentJson(ptr0, len0);
2453
+ var ptr2 = ret[0];
2454
+ var len2 = ret[1];
2455
+ if (ret[3]) {
2456
+ ptr2 = 0;
2457
+ len2 = 0;
2458
+ throw takeFromExternrefTable0(ret[2]);
2459
+ }
2460
+ deferred3_0 = ptr2;
2461
+ deferred3_1 = len2;
2462
+ return getStringFromWasm0(ptr2, len2);
2463
+ } finally {
2464
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
2465
+ }
2466
+ }
2467
+ function resolveParametersJson(params_json) {
2468
+ let deferred3_0;
2469
+ let deferred3_1;
2470
+ try {
2471
+ const ptr0 = passStringToWasm0(params_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
2472
+ const len0 = WASM_VECTOR_LEN;
2473
+ const ret = wasm.resolveParametersJson(ptr0, len0);
2474
+ var ptr2 = ret[0];
2475
+ var len2 = ret[1];
2476
+ if (ret[3]) {
2477
+ ptr2 = 0;
2478
+ len2 = 0;
2479
+ throw takeFromExternrefTable0(ret[2]);
2480
+ }
2481
+ deferred3_0 = ptr2;
2482
+ deferred3_1 = len2;
2483
+ return getStringFromWasm0(ptr2, len2);
2484
+ } finally {
2485
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
2486
+ }
2487
+ }
2347
2488
  function sample_timeline_sequence(timeline_json) {
2348
2489
  let deferred3_0;
2349
2490
  let deferred3_1;
@@ -2410,6 +2551,27 @@ function sheetMetalSequence(chain_json) {
2410
2551
  wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
2411
2552
  }
2412
2553
  }
2554
+ function simulateStrikeKernel(input_json) {
2555
+ let deferred3_0;
2556
+ let deferred3_1;
2557
+ try {
2558
+ const ptr0 = passStringToWasm0(input_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
2559
+ const len0 = WASM_VECTOR_LEN;
2560
+ const ret = wasm.simulateStrikeKernel(ptr0, len0);
2561
+ var ptr2 = ret[0];
2562
+ var len2 = ret[1];
2563
+ if (ret[3]) {
2564
+ ptr2 = 0;
2565
+ len2 = 0;
2566
+ throw takeFromExternrefTable0(ret[2]);
2567
+ }
2568
+ deferred3_0 = ptr2;
2569
+ deferred3_1 = len2;
2570
+ return getStringFromWasm0(ptr2, len2);
2571
+ } finally {
2572
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
2573
+ }
2574
+ }
2413
2575
  function sketchCircleSegments(cx, cy, radius, segments) {
2414
2576
  let deferred2_0;
2415
2577
  let deferred2_1;
@@ -2793,6 +2955,12 @@ function __wbg_get_imports() {
2793
2955
  const ret = arg0.WorkerGlobalScope;
2794
2956
  return ret;
2795
2957
  },
2958
+ __wbg___wbindgen_bigint_get_as_i64_8fcf4ce7f1ca72a2: function(arg0, arg1) {
2959
+ const v = arg1;
2960
+ const ret = typeof v === "bigint" ? v : void 0;
2961
+ getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true);
2962
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
2963
+ },
2796
2964
  __wbg___wbindgen_boolean_get_bbbb1c18aa2f5e25: function(arg0) {
2797
2965
  const v = arg0;
2798
2966
  const ret = typeof v === "boolean" ? v : void 0;
@@ -2809,6 +2977,10 @@ function __wbg_get_imports() {
2809
2977
  const ret = arg0 in arg1;
2810
2978
  return ret;
2811
2979
  },
2980
+ __wbg___wbindgen_is_bigint_31b12575b56f32fc: function(arg0) {
2981
+ const ret = typeof arg0 === "bigint";
2982
+ return ret;
2983
+ },
2812
2984
  __wbg___wbindgen_is_function_0095a73b8b156f76: function(arg0) {
2813
2985
  const ret = typeof arg0 === "function";
2814
2986
  return ret;
@@ -2830,6 +3002,10 @@ function __wbg_get_imports() {
2830
3002
  const ret = arg0 === void 0;
2831
3003
  return ret;
2832
3004
  },
3005
+ __wbg___wbindgen_jsval_eq_11888390b0186270: function(arg0, arg1) {
3006
+ const ret = arg0 === arg1;
3007
+ return ret;
3008
+ },
2833
3009
  __wbg___wbindgen_jsval_loose_eq_9dd77d8cd6671811: function(arg0, arg1) {
2834
3010
  const ret = arg0 == arg1;
2835
3011
  return ret;
@@ -3385,6 +3561,10 @@ function __wbg_get_imports() {
3385
3561
  __wbg_end_6f776519f1faa582: function(arg0) {
3386
3562
  arg0.end();
3387
3563
  },
3564
+ __wbg_entries_58c7934c745daac7: function(arg0) {
3565
+ const ret = Object.entries(arg0);
3566
+ return ret;
3567
+ },
3388
3568
  __wbg_error_7534b8e9a36f1ab4: function(arg0, arg1) {
3389
3569
  let deferred0_0;
3390
3570
  let deferred0_1;
@@ -3730,6 +3910,16 @@ function __wbg_get_imports() {
3730
3910
  const ret = result;
3731
3911
  return ret;
3732
3912
  },
3913
+ __wbg_instanceof_Map_53af74335dec57f4: function(arg0) {
3914
+ let result;
3915
+ try {
3916
+ result = arg0 instanceof Map;
3917
+ } catch (_) {
3918
+ result = false;
3919
+ }
3920
+ const ret = result;
3921
+ return ret;
3922
+ },
3733
3923
  __wbg_instanceof_Object_1c6af87502b733ed: function(arg0) {
3734
3924
  let result;
3735
3925
  try {
@@ -8022,14 +8212,22 @@ async function loadAndInit() {
8022
8212
  }
8023
8213
  await mod.default(hint ? { module_or_path: hint } : void 0);
8024
8214
  }
8215
+ assertKernelBaseline(mod);
8025
8216
  wasmModule2 = mod;
8026
8217
  generation++;
8027
8218
  return mod;
8028
8219
  }
8220
+ function assertKernelBaseline(mod) {
8221
+ const missing = REQUIRED_BINDINGS.filter((name) => typeof mod[name] !== "function");
8222
+ if (missing.length > 0) {
8223
+ const version2 = typeof mod.get_kernel_version === "function" ? mod.get_kernel_version() : "unknown";
8224
+ throw new Error(`stale @vcad/kernel-wasm bundle (kernel version ${version2}): missing required bindings ${missing.join(", ")} \u2014 rebuild the workspace (VCAD_WASM_SKIP unset) or update to a current bundle`);
8225
+ }
8226
+ }
8029
8227
  function getKernelWasmSync() {
8030
8228
  return wasmModule2;
8031
8229
  }
8032
- var wasmPromise, wasmModule2, wasmInputHint, bindgenStarted, lastModuleBuffer, lastTrap, generation;
8230
+ var wasmPromise, wasmModule2, wasmInputHint, bindgenStarted, lastModuleBuffer, lastTrap, generation, REQUIRED_BINDINGS;
8033
8231
  var init_wasm_singleton = __esm({
8034
8232
  "../engine/dist/wasm-singleton.js"() {
8035
8233
  "use strict";
@@ -8038,6 +8236,13 @@ var init_wasm_singleton = __esm({
8038
8236
  bindgenStarted = false;
8039
8237
  lastTrap = null;
8040
8238
  generation = 0;
8239
+ REQUIRED_BINDINGS = [
8240
+ "evaluateDocument",
8241
+ "documentDiff",
8242
+ "documentMerge",
8243
+ "documentToLoon",
8244
+ "documentToLoonChecked"
8245
+ ];
8041
8246
  }
8042
8247
  });
8043
8248
 
@@ -8509,7 +8714,10 @@ function evaluateDocument2(doc, kernel2, options = {}) {
8509
8714
  const resolved = resolveDocument(doc);
8510
8715
  doc = resolved.doc;
8511
8716
  doc = expandPartInstances(doc, kernel2);
8512
- if (kernel2.evaluateDocument) {
8717
+ if (!kernel2.evaluateDocument) {
8718
+ throw new Error("kernel WASM bundle is missing the evaluateDocument binding \u2014 rebuild @vcad/kernel-wasm (stale bundle); old bundles are unsupported");
8719
+ }
8720
+ {
8513
8721
  try {
8514
8722
  const docJson = JSON.stringify(doc);
8515
8723
  const result = kernel2.evaluateDocument(docJson, options.skipClashDetection ?? false);
@@ -9637,114 +9845,11 @@ var init_gpu = __esm({
9637
9845
  });
9638
9846
 
9639
9847
  // ../engine/dist/diff.js
9640
- function decompose(doc) {
9641
- const obj = doc;
9642
- const entities = /* @__PURE__ */ new Map();
9643
- for (const [field, kind] of MAP_TABLES) {
9644
- const table = obj[field];
9645
- if (table && typeof table === "object" && !Array.isArray(table)) {
9646
- for (const [id, v] of Object.entries(table)) {
9647
- entities.set(`${kind}\0${id}`, v);
9648
- }
9649
- }
9650
- }
9651
- for (const [field, kind, idField] of VEC_TABLES) {
9652
- const items = obj[field];
9653
- if (Array.isArray(items)) {
9654
- for (const item of items) {
9655
- const raw = item?.[idField];
9656
- const id = typeof raw === "string" ? raw : JSON.stringify(raw);
9657
- entities.set(`${kind}\0${id}`, item);
9658
- }
9659
- }
9660
- }
9661
- for (const field of DOC_FIELDS) {
9662
- const v = obj[field];
9663
- if (v !== void 0 && v !== null)
9664
- entities.set(`doc\0${field}`, v);
9665
- }
9666
- return entities;
9667
- }
9668
- function deepEqual(a, b) {
9669
- if (a === b)
9670
- return true;
9671
- if (typeof a !== typeof b || a === null || b === null)
9672
- return false;
9673
- if (typeof a !== "object")
9674
- return false;
9675
- if (Array.isArray(a) !== Array.isArray(b))
9676
- return false;
9677
- if (Array.isArray(a) && Array.isArray(b)) {
9678
- if (a.length !== b.length)
9679
- return false;
9680
- return a.every((v, i) => deepEqual(v, b[i]));
9681
- }
9682
- const ka = Object.keys(a).filter((k) => a[k] !== void 0);
9683
- const kb = Object.keys(b).filter((k) => b[k] !== void 0);
9684
- if (ka.length !== kb.length)
9685
- return false;
9686
- return ka.every((k) => deepEqual(a[k], b[k]));
9687
- }
9688
- function valueDiff(prefix, oldV, newV, out) {
9689
- if (deepEqual(oldV, newV))
9690
- return;
9691
- const join4 = (k) => prefix ? `${prefix}.${k}` : k;
9692
- const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
9693
- if (isObj(oldV) && isObj(newV)) {
9694
- const keys = /* @__PURE__ */ new Set([...Object.keys(oldV), ...Object.keys(newV)]);
9695
- for (const k of keys) {
9696
- valueDiff(join4(k), oldV[k] ?? null, newV[k] ?? null, out);
9697
- }
9698
- return;
9699
- }
9700
- if (Array.isArray(oldV) && Array.isArray(newV) && oldV.length === newV.length) {
9701
- oldV.forEach((v, i) => valueDiff(join4(String(i)), v, newV[i], out));
9702
- return;
9703
- }
9704
- out.push({ path: prefix, old: oldV, new: newV });
9705
- }
9706
- function entityName(v) {
9707
- const n = v?.name;
9708
- return typeof n === "string" ? n : void 0;
9709
- }
9710
- function semanticDiffFallback(oldDoc, newDoc) {
9711
- const a = decompose(oldDoc);
9712
- const b = decompose(newDoc);
9713
- const keys = [.../* @__PURE__ */ new Set([...a.keys(), ...b.keys()])].sort();
9714
- const changes = [];
9715
- for (const key of keys) {
9716
- const [kind, id] = key.split("\0");
9717
- const va = a.get(key);
9718
- const vb = b.get(key);
9719
- if (va === void 0 && vb !== void 0) {
9720
- changes.push({ kind, id, name: entityName(vb), type: "added", value: vb });
9721
- } else if (va !== void 0 && vb === void 0) {
9722
- changes.push({ kind, id, name: entityName(va), type: "removed", value: va });
9723
- } else if (!deepEqual(va, vb)) {
9724
- const fields = [];
9725
- valueDiff("", va, vb, fields);
9726
- if (fields.length > 0) {
9727
- changes.push({
9728
- kind,
9729
- id,
9730
- name: entityName(vb) ?? entityName(va),
9731
- type: "modified",
9732
- fields
9733
- });
9734
- }
9735
- }
9736
- }
9737
- return { changes };
9738
- }
9739
9848
  function semanticDiff(oldDoc, newDoc, kernel2) {
9740
- if (kernel2?.documentDiff) {
9741
- try {
9742
- return kernel2.documentDiff(JSON.stringify(oldDoc), JSON.stringify(newDoc));
9743
- } catch (e) {
9744
- console.warn("[diff] kernel documentDiff failed, using TS fallback:", e);
9745
- }
9849
+ if (!kernel2?.documentDiff) {
9850
+ throw new Error("semantic diff needs a kernel build with the documentDiff binding \u2014 ensure the kernel WASM is initialized (getKernelWasm) and current");
9746
9851
  }
9747
- return semanticDiffFallback(oldDoc, newDoc);
9852
+ return kernel2.documentDiff(JSON.stringify(oldDoc), JSON.stringify(newDoc));
9748
9853
  }
9749
9854
  function mergeAvailable(kernel2) {
9750
9855
  return typeof kernel2?.documentMerge === "function";
@@ -9755,33 +9860,9 @@ function threeWayMerge(base, ours, theirs, resolutions, kernel2) {
9755
9860
  }
9756
9861
  return kernel2.documentMerge(JSON.stringify(base), JSON.stringify(ours), JSON.stringify(theirs), resolutions.length > 0 ? JSON.stringify(resolutions) : null);
9757
9862
  }
9758
- var MAP_TABLES, VEC_TABLES, DOC_FIELDS;
9759
9863
  var init_diff = __esm({
9760
9864
  "../engine/dist/diff.js"() {
9761
9865
  "use strict";
9762
- MAP_TABLES = [
9763
- ["nodes", "node"],
9764
- ["materials", "material"],
9765
- ["part_materials", "part-material"],
9766
- ["partDefs", "part-def"],
9767
- ["parameters", "parameter"],
9768
- ["bindings", "binding"]
9769
- ];
9770
- VEC_TABLES = [
9771
- ["roots", "root", "root"],
9772
- ["instances", "instance", "id"],
9773
- ["joints", "joint", "id"],
9774
- ["clearance_specs", "clearance", "label"]
9775
- ];
9776
- DOC_FIELDS = [
9777
- "version",
9778
- "scene",
9779
- "groundInstanceId",
9780
- "schematic",
9781
- "pcb",
9782
- "molecule",
9783
- "timeline"
9784
- ];
9785
9866
  }
9786
9867
  });
9787
9868
 
@@ -11631,7 +11712,6 @@ __export(dist_exports, {
11631
11712
  searchEcadParts: () => searchEcadParts,
11632
11713
  searchParts: () => searchParts,
11633
11714
  semanticDiff: () => semanticDiff,
11634
- semanticDiffFallback: () => semanticDiffFallback,
11635
11715
  solveDesignConstraints: () => solveDesignConstraints2,
11636
11716
  solveForwardKinematics: () => solveForwardKinematics2,
11637
11717
  threeWayMerge: () => threeWayMerge,
@@ -11801,6 +11881,8 @@ var init_dist = __esm({
11801
11881
  sheetMetalSequence: wasmModule7.sheetMetalSequence,
11802
11882
  nestSheetMetalParts: wasmModule7.nestSheetMetalParts,
11803
11883
  getSheetMetalMaterials: wasmModule7.getSheetMetalMaterials,
11884
+ simulateStrikeKernel: wasmModule7.simulateStrikeKernel,
11885
+ noteToHz: wasmModule7.noteToHz,
11804
11886
  getSheetMetalBendTable: wasmModule7.getSheetMetalBendTable,
11805
11887
  getSheetMetalShopCatalog: wasmModule7.getSheetMetalShopCatalog,
11806
11888
  sheetMetalFoldedStep: wasmModule7.sheetMetalFoldedStep,
@@ -12342,6 +12424,27 @@ var init_dist = __esm({
12342
12424
  return null;
12343
12425
  return sheetMetalSequence2(chain, this.kernel);
12344
12426
  }
12427
+ /**
12428
+ * Run the kernel's mallet-strike pipeline (free-free bar modal analysis +
12429
+ * synthesis + FFT verdict). Input/output shapes are JSON mirrors of
12430
+ * `vcad_kernel_acoustics::strike::{StrikeInput, StrikeResult}` with the
12431
+ * WAV base64-encoded as `wav_base64`.
12432
+ */
12433
+ simulateStrike(input) {
12434
+ const kernel2 = this.kernel;
12435
+ if (!kernel2.simulateStrikeKernel) {
12436
+ throw new Error("kernel WASM build lacks simulateStrikeKernel \u2014 rebuild @vcad/kernel-wasm");
12437
+ }
12438
+ return JSON.parse(kernel2.simulateStrikeKernel(JSON.stringify(input)));
12439
+ }
12440
+ /** Parse a note name ("C6", "F#4") to Hz via the kernel. */
12441
+ noteToHz(note) {
12442
+ const kernel2 = this.kernel;
12443
+ if (!kernel2.noteToHz) {
12444
+ throw new Error("kernel WASM build lacks noteToHz \u2014 rebuild @vcad/kernel-wasm");
12445
+ }
12446
+ return kernel2.noteToHz(note);
12447
+ }
12345
12448
  /** Return the kernel's curated sheet-metal materials registry. */
12346
12449
  getSheetMetalMaterials() {
12347
12450
  return getSheetMetalMaterials2(this.kernel);
@@ -26615,33 +26718,33 @@ var init_server = __esm({
26615
26718
  });
26616
26719
 
26617
26720
  // node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
26618
- function assertToolsCallTaskCapability(requests, method, entityName2) {
26721
+ function assertToolsCallTaskCapability(requests, method, entityName) {
26619
26722
  if (!requests) {
26620
- throw new Error(`${entityName2} does not support task creation (required for ${method})`);
26723
+ throw new Error(`${entityName} does not support task creation (required for ${method})`);
26621
26724
  }
26622
26725
  switch (method) {
26623
26726
  case "tools/call":
26624
26727
  if (!requests.tools?.call) {
26625
- throw new Error(`${entityName2} does not support task creation for tools/call (required for ${method})`);
26728
+ throw new Error(`${entityName} does not support task creation for tools/call (required for ${method})`);
26626
26729
  }
26627
26730
  break;
26628
26731
  default:
26629
26732
  break;
26630
26733
  }
26631
26734
  }
26632
- function assertClientRequestTaskCapability(requests, method, entityName2) {
26735
+ function assertClientRequestTaskCapability(requests, method, entityName) {
26633
26736
  if (!requests) {
26634
- throw new Error(`${entityName2} does not support task creation (required for ${method})`);
26737
+ throw new Error(`${entityName} does not support task creation (required for ${method})`);
26635
26738
  }
26636
26739
  switch (method) {
26637
26740
  case "sampling/createMessage":
26638
26741
  if (!requests.sampling?.createMessage) {
26639
- throw new Error(`${entityName2} does not support task creation for sampling/createMessage (required for ${method})`);
26742
+ throw new Error(`${entityName} does not support task creation for sampling/createMessage (required for ${method})`);
26640
26743
  }
26641
26744
  break;
26642
26745
  case "elicitation/create":
26643
26746
  if (!requests.elicitation?.create) {
26644
- throw new Error(`${entityName2} does not support task creation for elicitation/create (required for ${method})`);
26747
+ throw new Error(`${entityName} does not support task creation for elicitation/create (required for ${method})`);
26645
26748
  }
26646
26749
  break;
26647
26750
  default:
@@ -40887,6 +40990,7 @@ var init_part_labels = __esm({
40887
40990
  var init_export_stl = __esm({
40888
40991
  "../core/dist/utils/export-stl.js"() {
40889
40992
  "use strict";
40993
+ init_dist();
40890
40994
  }
40891
40995
  });
40892
40996
 
@@ -40894,6 +40998,7 @@ var init_export_stl = __esm({
40894
40998
  var init_export_gltf = __esm({
40895
40999
  "../core/dist/utils/export-gltf.js"() {
40896
41000
  "use strict";
41001
+ init_dist();
40897
41002
  }
40898
41003
  });
40899
41004
 
@@ -40930,6 +41035,7 @@ var init_save_load = __esm({
40930
41035
  var init_document_to_loon = __esm({
40931
41036
  "../core/dist/utils/document-to-loon.js"() {
40932
41037
  "use strict";
41038
+ init_wasm_singleton2();
40933
41039
  }
40934
41040
  });
40935
41041
 
@@ -49263,6 +49369,15 @@ var init_loon = __esm({
49263
49369
  });
49264
49370
 
49265
49371
  // src/export/glb.ts
49372
+ function exportKernel() {
49373
+ const mod = getKernelWasmSync();
49374
+ if (!mod) {
49375
+ throw new Error(
49376
+ "kernel WASM not initialized \u2014 await getKernelWasm() before exporting"
49377
+ );
49378
+ }
49379
+ return mod;
49380
+ }
49266
49381
  function buildPartLabels(doc) {
49267
49382
  return doc.roots.filter((entry) => entry.visible !== false).map((entry) => {
49268
49383
  const name = doc.nodes[String(entry.root)]?.name;
@@ -49270,363 +49385,71 @@ function buildPartLabels(doc) {
49270
49385
  });
49271
49386
  }
49272
49387
  function eulerXyzDegToQuat(rotation) {
49273
- const rad = Math.PI / 180;
49274
- const hx = rotation.x * rad / 2;
49275
- const hy = rotation.y * rad / 2;
49276
- const hz = rotation.z * rad / 2;
49277
- const c1 = Math.cos(hx);
49278
- const s1 = Math.sin(hx);
49279
- const c2 = Math.cos(hy);
49280
- const s2 = Math.sin(hy);
49281
- const c3 = Math.cos(hz);
49282
- const s3 = Math.sin(hz);
49283
- return [
49284
- s1 * c2 * c3 - c1 * s2 * s3,
49285
- c1 * s2 * c3 + s1 * c2 * s3,
49286
- c1 * c2 * s3 - s1 * s2 * c3,
49287
- c1 * c2 * c3 + s1 * s2 * s3
49288
- ];
49388
+ const q = exportKernel().eulerXyzDegToQuat(rotation.x, rotation.y, rotation.z);
49389
+ return [q[0], q[1], q[2], q[3]];
49289
49390
  }
49290
49391
  function buildGlb(inputMeshes, name, animation) {
49291
- const materialMap = /* @__PURE__ */ new Map();
49292
- const materials = [];
49293
- const materialIndexFor = (m) => {
49294
- const emissive = m.emissive ?? [0, 0, 0];
49295
- const emissiveStrength = m.emissiveStrength ?? 1;
49296
- const clearcoat = m.clearcoat ?? 0;
49297
- const clearcoatRoughness = m.clearcoatRoughness ?? 0;
49298
- const alpha = m.alpha ?? 1;
49299
- const key = `${m.color[0]},${m.color[1]},${m.color[2]},${m.metallic},${m.roughness},${emissive[0]},${emissive[1]},${emissive[2]},${emissiveStrength},${clearcoat},${clearcoatRoughness},${alpha}`;
49300
- const existing = materialMap.get(key);
49301
- if (existing !== void 0) return existing;
49302
- const idx = materials.length;
49303
- materialMap.set(key, idx);
49304
- materials.push({
49305
- name: m.name.includes(":") ? m.name.split(":")[0] : m.name,
49306
- color: m.color,
49307
- metallic: m.metallic,
49308
- roughness: m.roughness,
49309
- emissive,
49310
- emissiveStrength,
49311
- clearcoat,
49312
- clearcoatRoughness,
49313
- alpha
49314
- });
49315
- return idx;
49392
+ let f32Len = 0;
49393
+ let u32Len = 0;
49394
+ for (const m of inputMeshes) {
49395
+ f32Len += m.positions.length;
49396
+ u32Len += m.indices.length;
49397
+ if (m.normals && m.normals.length === m.positions.length) {
49398
+ f32Len += m.normals.length;
49399
+ }
49400
+ }
49401
+ for (const ch of animation?.channels ?? []) {
49402
+ f32Len += ch.times.length + ch.values.length;
49403
+ }
49404
+ const f32Data = new Float32Array(f32Len);
49405
+ const u32Data = new Uint32Array(u32Len);
49406
+ let f32Off = 0;
49407
+ let u32Off = 0;
49408
+ const pushF32 = (data) => {
49409
+ f32Data.set(data, f32Off);
49410
+ const span = [f32Off, data.length];
49411
+ f32Off += data.length;
49412
+ return span;
49316
49413
  };
49317
- if (inputMeshes.length === 0) {
49318
- materials.push({
49319
- ...DEFAULT_MATERIAL,
49320
- emissive: [0, 0, 0],
49321
- emissiveStrength: 1,
49322
- clearcoat: 0,
49323
- clearcoatRoughness: 0,
49324
- alpha: 1
49325
- });
49326
- }
49327
- const bufferChunks = [];
49328
- const bufferViews = [];
49329
- const accessors = [];
49330
- const meshes = [];
49331
- const nodes = [];
49332
- let bufferOffset = 0;
49333
- const meshKeyToIdx = /* @__PURE__ */ new Map();
49334
- for (let inputIdx = 0; inputIdx < inputMeshes.length; inputIdx++) {
49335
- const input = inputMeshes[inputIdx];
49336
- const dedupIdx = input.meshKey !== void 0 ? meshKeyToIdx.get(input.meshKey) : void 0;
49337
- if (dedupIdx !== void 0) {
49338
- nodes.push(makeNode(input, dedupIdx));
49339
- continue;
49340
- }
49341
- const positions = f32(input.positions);
49342
- const normals = input.normals && input.normals.length === positions.length ? f32(input.normals) : void 0;
49343
- const vertexCount = positions.length / 3;
49344
- const indexCount = input.indices.length;
49345
- let minX = Infinity, minY = Infinity, minZ = Infinity;
49346
- let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
49347
- for (let i = 0; i < vertexCount; i++) {
49348
- const x = positions[i * 3];
49349
- const y = positions[i * 3 + 1];
49350
- const z = positions[i * 3 + 2];
49351
- minX = Math.min(minX, x);
49352
- minY = Math.min(minY, y);
49353
- minZ = Math.min(minZ, z);
49354
- maxX = Math.max(maxX, x);
49355
- maxY = Math.max(maxY, y);
49356
- maxZ = Math.max(maxZ, z);
49357
- }
49358
- const indicesBytes = new Uint8Array(indexCount * 4);
49359
- const indicesView = new DataView(indicesBytes.buffer);
49360
- for (let i = 0; i < indexCount; i++) {
49361
- indicesView.setUint32(i * 4, input.indices[i], true);
49362
- }
49363
- const indicesPadded = padTo4(indicesBytes);
49364
- bufferChunks.push(indicesPadded);
49365
- const indicesBvIdx = bufferViews.length;
49366
- bufferViews.push({
49367
- buffer: 0,
49368
- byteOffset: bufferOffset,
49369
- byteLength: indexCount * 4,
49370
- target: 34963
49371
- // ELEMENT_ARRAY_BUFFER
49372
- });
49373
- bufferOffset += indicesPadded.length;
49374
- const indicesAccIdx = accessors.length;
49375
- accessors.push({
49376
- bufferView: indicesBvIdx,
49377
- componentType: 5125,
49378
- // UNSIGNED_INT
49379
- count: indexCount,
49380
- type: "SCALAR"
49381
- });
49382
- const positionsBytes = new Uint8Array(
49383
- positions.buffer.slice(
49384
- positions.byteOffset,
49385
- positions.byteOffset + vertexCount * 12
49386
- )
49387
- );
49388
- const positionsPadded = padTo4(positionsBytes);
49389
- bufferChunks.push(positionsPadded);
49390
- const positionsBvIdx = bufferViews.length;
49391
- bufferViews.push({
49392
- buffer: 0,
49393
- byteOffset: bufferOffset,
49394
- byteLength: vertexCount * 12,
49395
- target: 34962
49396
- // ARRAY_BUFFER
49397
- });
49398
- bufferOffset += positionsPadded.length;
49399
- const positionsAccIdx = accessors.length;
49400
- accessors.push({
49401
- bufferView: positionsBvIdx,
49402
- componentType: 5126,
49403
- // FLOAT
49404
- count: vertexCount,
49405
- type: "VEC3",
49406
- min: [minX, minY, minZ],
49407
- max: [maxX, maxY, maxZ]
49408
- });
49409
- const attributes = {
49410
- POSITION: positionsAccIdx
49411
- };
49412
- if (normals) {
49413
- const normalsBytes = new Uint8Array(
49414
- normals.buffer.slice(
49415
- normals.byteOffset,
49416
- normals.byteOffset + vertexCount * 12
49417
- )
49418
- );
49419
- const normalsPadded = padTo4(normalsBytes);
49420
- bufferChunks.push(normalsPadded);
49421
- const normalsBvIdx = bufferViews.length;
49422
- bufferViews.push({
49423
- buffer: 0,
49424
- byteOffset: bufferOffset,
49425
- byteLength: vertexCount * 12,
49426
- target: 34962
49427
- // ARRAY_BUFFER
49428
- });
49429
- bufferOffset += normalsPadded.length;
49430
- const normalsAccIdx = accessors.length;
49431
- accessors.push({
49432
- bufferView: normalsBvIdx,
49433
- componentType: 5126,
49434
- // FLOAT
49435
- count: vertexCount,
49436
- type: "VEC3"
49437
- });
49438
- attributes.NORMAL = normalsAccIdx;
49439
- }
49440
- const meshIdx = meshes.length;
49441
- meshes.push({
49442
- name: `mesh_${meshIdx}`,
49443
- primitives: [
49444
- {
49445
- attributes,
49446
- indices: indicesAccIdx,
49447
- material: materialIndexFor(input)
49448
- }
49449
- ]
49450
- });
49451
- if (input.meshKey !== void 0) meshKeyToIdx.set(input.meshKey, meshIdx);
49452
- nodes.push(makeNode(input, meshIdx));
49453
- }
49454
- let sceneNodeIndices = nodes.map((_, i) => i);
49455
- if (animation?.rootNodeName !== void 0) {
49456
- const rootIdx = nodes.length;
49457
- nodes.push({
49458
- name: animation.rootNodeName,
49459
- children: sceneNodeIndices
49460
- });
49461
- sceneNodeIndices = [rootIdx];
49462
- }
49463
- for (const extra of animation?.extraNodes ?? []) {
49464
- const idx = nodes.length;
49465
- nodes.push({ name: extra });
49466
- sceneNodeIndices = [...sceneNodeIndices, idx];
49467
- }
49468
- let animationsJson;
49469
- if (animation && animation.channels.length > 0) {
49470
- const nodeIndexByName = /* @__PURE__ */ new Map();
49471
- for (let i = 0; i < nodes.length; i++) {
49472
- nodeIndexByName.set(nodes[i].name, i);
49473
- }
49474
- const writeF32Accessor = (data, type, withMinMax) => {
49475
- const arr = new Float32Array(data);
49476
- const bytes = new Uint8Array(arr.buffer.slice(0));
49477
- const padded = padTo4(bytes);
49478
- bufferChunks.push(padded);
49479
- const bvIdx = bufferViews.length;
49480
- bufferViews.push({
49481
- buffer: 0,
49482
- byteOffset: bufferOffset,
49483
- byteLength: bytes.length
49484
- });
49485
- bufferOffset += padded.length;
49486
- const components = type === "SCALAR" ? 1 : type === "VEC3" ? 3 : 4;
49487
- const accIdx = accessors.length;
49488
- const acc = {
49489
- bufferView: bvIdx,
49490
- componentType: 5126,
49491
- // FLOAT
49492
- count: data.length / components,
49493
- type
49494
- };
49495
- if (withMinMax) {
49496
- acc.min = [Math.min(...data)];
49497
- acc.max = [Math.max(...data)];
49498
- }
49499
- accessors.push(acc);
49500
- return accIdx;
49501
- };
49502
- const samplers = [];
49503
- const channels = [];
49504
- for (const ch of animation.channels) {
49505
- const nodeIdx = nodeIndexByName.get(ch.nodeName);
49506
- if (nodeIdx === void 0) {
49507
- console.warn(
49508
- `buildGlb: animation channel targets unknown node "${ch.nodeName}" \u2014 skipped`
49509
- );
49510
- continue;
49511
- }
49512
- const inputAcc = writeF32Accessor(ch.times, "SCALAR", true);
49513
- const outputAcc = writeF32Accessor(
49514
- ch.values,
49515
- ch.path === "rotation" ? "VEC4" : "VEC3",
49516
- false
49517
- );
49518
- const samplerIdx = samplers.length;
49519
- samplers.push({
49520
- input: inputAcc,
49521
- output: outputAcc,
49522
- interpolation: ch.interpolation ?? "LINEAR"
49523
- });
49524
- channels.push({
49525
- sampler: samplerIdx,
49526
- target: { node: nodeIdx, path: ch.path }
49527
- });
49528
- }
49529
- if (channels.length > 0) {
49530
- animationsJson = [
49531
- { name: animation.name ?? "timeline", samplers, channels }
49532
- ];
49533
- }
49534
- }
49535
- let usesClearcoat = false;
49536
- let usesEmissiveStrength = false;
49537
- const materialJson = materials.map((m) => {
49538
- const mat = {
49539
- name: m.name,
49540
- pbrMetallicRoughness: {
49541
- baseColorFactor: [...m.color, m.alpha],
49542
- metallicFactor: m.metallic,
49543
- roughnessFactor: m.roughness
49544
- }
49545
- };
49546
- if (m.alpha < 1) {
49547
- mat.alphaMode = "BLEND";
49548
- mat.doubleSided = true;
49549
- }
49550
- if (isEmissive(m.emissive)) {
49551
- mat.emissiveFactor = m.emissive;
49552
- }
49553
- const extensions = {};
49554
- if (m.clearcoat > 0) {
49555
- usesClearcoat = true;
49556
- extensions.KHR_materials_clearcoat = {
49557
- clearcoatFactor: m.clearcoat,
49558
- clearcoatRoughnessFactor: m.clearcoatRoughness
49559
- };
49560
- }
49561
- if (isEmissive(m.emissive) && m.emissiveStrength !== 1) {
49562
- usesEmissiveStrength = true;
49563
- extensions.KHR_materials_emissive_strength = {
49564
- emissiveStrength: m.emissiveStrength
49565
- };
49566
- }
49567
- if (Object.keys(extensions).length > 0) mat.extensions = extensions;
49568
- return mat;
49569
- });
49570
- const extensionsUsed = [];
49571
- if (usesClearcoat) extensionsUsed.push("KHR_materials_clearcoat");
49572
- if (usesEmissiveStrength)
49573
- extensionsUsed.push("KHR_materials_emissive_strength");
49574
- const json = {
49575
- asset: { version: "2.0", generator: "vcad-mcp" },
49576
- scene: 0,
49577
- scenes: [{ name, nodes: sceneNodeIndices }],
49578
- nodes,
49414
+ const pushU32 = (data) => {
49415
+ u32Data.set(data, u32Off);
49416
+ const span = [u32Off, data.length];
49417
+ u32Off += data.length;
49418
+ return span;
49419
+ };
49420
+ const meshes = inputMeshes.map((m) => ({
49421
+ name: m.name,
49422
+ positions: pushF32(m.positions),
49423
+ indices: pushU32(m.indices),
49424
+ normals: m.normals && m.normals.length === m.positions.length ? pushF32(m.normals) : void 0,
49425
+ color: m.color,
49426
+ metallic: m.metallic,
49427
+ roughness: m.roughness,
49428
+ emissive: m.emissive,
49429
+ emissiveStrength: m.emissiveStrength,
49430
+ clearcoat: m.clearcoat,
49431
+ clearcoatRoughness: m.clearcoatRoughness,
49432
+ alpha: m.alpha,
49433
+ transform: m.transform,
49434
+ meshKey: m.meshKey
49435
+ }));
49436
+ const spec = {
49437
+ name,
49579
49438
  meshes,
49580
- materials: materialJson,
49581
- accessors,
49582
- bufferViews,
49583
- buffers: [{ byteLength: bufferOffset }]
49439
+ animation: animation ? {
49440
+ name: animation.name,
49441
+ channels: animation.channels.map((ch) => ({
49442
+ nodeName: ch.nodeName,
49443
+ path: ch.path,
49444
+ times: pushF32(ch.times),
49445
+ values: pushF32(ch.values),
49446
+ interpolation: ch.interpolation
49447
+ })),
49448
+ rootNodeName: animation.rootNodeName,
49449
+ extraNodes: animation.extraNodes
49450
+ } : void 0
49584
49451
  };
49585
- if (extensionsUsed.length > 0) json.extensionsUsed = extensionsUsed;
49586
- if (animationsJson) json.animations = animationsJson;
49587
- const jsonStr = JSON.stringify(json);
49588
- const jsonBytes = new TextEncoder().encode(jsonStr);
49589
- const jsonPadded = padTo4(jsonBytes, 32);
49590
- const binBuffer = new Uint8Array(bufferOffset);
49591
- let binOffset = 0;
49592
- for (const chunk of bufferChunks) {
49593
- binBuffer.set(chunk, binOffset);
49594
- binOffset += chunk.length;
49595
- }
49596
- const totalLength = 12 + 8 + jsonPadded.length + 8 + binBuffer.length;
49597
- const glb = new Uint8Array(totalLength);
49598
- const glbView = new DataView(glb.buffer);
49599
- let offset = 0;
49600
- glb.set(new TextEncoder().encode("glTF"), offset);
49601
- offset += 4;
49602
- glbView.setUint32(offset, 2, true);
49603
- offset += 4;
49604
- glbView.setUint32(offset, totalLength, true);
49605
- offset += 4;
49606
- glbView.setUint32(offset, jsonPadded.length, true);
49607
- offset += 4;
49608
- glbView.setUint32(offset, 1313821514, true);
49609
- offset += 4;
49610
- glb.set(jsonPadded, offset);
49611
- offset += jsonPadded.length;
49612
- glbView.setUint32(offset, binBuffer.length, true);
49613
- offset += 4;
49614
- glbView.setUint32(offset, 5130562, true);
49615
- offset += 4;
49616
- glb.set(binBuffer, offset);
49617
- return glb;
49618
- }
49619
- function makeNode(input, meshIdx) {
49620
- const node = { mesh: meshIdx, name: input.name };
49621
- const t2 = input.transform;
49622
- if (!t2) return node;
49623
- const [tx, ty, tz] = t2.translation;
49624
- if (tx !== 0 || ty !== 0 || tz !== 0) node.translation = t2.translation;
49625
- const [qx, qy, qz, qw] = t2.rotationQuat;
49626
- if (qx !== 0 || qy !== 0 || qz !== 0 || qw !== 1) node.rotation = t2.rotationQuat;
49627
- const [sx, sy, sz] = t2.scale;
49628
- if (sx !== 1 || sy !== 1 || sz !== 1) node.scale = t2.scale;
49629
- return node;
49452
+ return exportKernel().buildGlbBytes(JSON.stringify(spec), f32Data, u32Data);
49630
49453
  }
49631
49454
  function toGlbBytes(scene, name, partLabels) {
49632
49455
  const meshes = scene.parts.map((part, i) => ({
@@ -49640,28 +49463,17 @@ function toGlbBytes(scene, name, partLabels) {
49640
49463
  }));
49641
49464
  return buildGlb(meshes, name);
49642
49465
  }
49643
- function padTo4(bytes, padByte = 0) {
49644
- const paddedLength = bytes.length + 3 & ~3;
49645
- if (paddedLength === bytes.length) return bytes;
49646
- const padded = new Uint8Array(paddedLength);
49647
- padded.set(bytes);
49648
- for (let i = bytes.length; i < paddedLength; i++) {
49649
- padded[i] = padByte;
49650
- }
49651
- return padded;
49652
- }
49653
- var DEFAULT_MATERIAL, isEmissive, f32;
49466
+ var DEFAULT_MATERIAL;
49654
49467
  var init_glb = __esm({
49655
49468
  "src/export/glb.ts"() {
49656
49469
  "use strict";
49470
+ init_dist();
49657
49471
  DEFAULT_MATERIAL = {
49658
49472
  name: "default",
49659
49473
  color: [0.8, 0.8, 0.8],
49660
49474
  metallic: 0.1,
49661
49475
  roughness: 0.5
49662
49476
  };
49663
- isEmissive = (e) => e[0] > 0 || e[1] > 0 || e[2] > 0;
49664
- f32 = (a) => a instanceof Float32Array ? a : new Float32Array(a);
49665
49477
  }
49666
49478
  });
49667
49479
 
@@ -56448,80 +56260,37 @@ var init_parts2 = __esm({
56448
56260
 
56449
56261
  // src/export/stl.ts
56450
56262
  function toStlBytes(scene, name) {
56451
- let totalTriangles = 0;
56263
+ let f32Len = 0;
56264
+ let u32Len = 0;
56452
56265
  for (const part of scene.parts) {
56453
- totalTriangles += part.mesh.indices.length / 3;
56454
- }
56455
- const buffer = new ArrayBuffer(84 + totalTriangles * 50);
56456
- const view = new DataView(buffer);
56457
- const uint8 = new Uint8Array(buffer);
56458
- const header = name.slice(0, 80).padEnd(80, " ");
56459
- for (let i = 0; i < 80; i++) {
56460
- uint8[i] = header.charCodeAt(i);
56461
- }
56462
- view.setUint32(80, totalTriangles, true);
56463
- let offset = 84;
56464
- for (const part of scene.parts) {
56465
- offset = writeMeshTriangles(part.mesh, view, offset);
56466
- }
56467
- return uint8;
56468
- }
56469
- function writeMeshTriangles(mesh, view, offset) {
56470
- const positions = mesh.positions;
56471
- const indices = mesh.indices;
56472
- const numTriangles = indices.length / 3;
56473
- for (let t2 = 0; t2 < numTriangles; t2++) {
56474
- const i0 = indices[t2 * 3];
56475
- const i1 = indices[t2 * 3 + 1];
56476
- const i2 = indices[t2 * 3 + 2];
56477
- const v0x = positions[i0 * 3];
56478
- const v0y = positions[i0 * 3 + 1];
56479
- const v0z = positions[i0 * 3 + 2];
56480
- const v1x = positions[i1 * 3];
56481
- const v1y = positions[i1 * 3 + 1];
56482
- const v1z = positions[i1 * 3 + 2];
56483
- const v2x = positions[i2 * 3];
56484
- const v2y = positions[i2 * 3 + 1];
56485
- const v2z = positions[i2 * 3 + 2];
56486
- const e1x = v1x - v0x;
56487
- const e1y = v1y - v0y;
56488
- const e1z = v1z - v0z;
56489
- const e2x = v2x - v0x;
56490
- const e2y = v2y - v0y;
56491
- const e2z = v2z - v0z;
56492
- let nx = e1y * e2z - e1z * e2y;
56493
- let ny = e1z * e2x - e1x * e2z;
56494
- let nz = e1x * e2y - e1y * e2x;
56495
- const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
56496
- if (len > 1e-10) {
56497
- nx /= len;
56498
- ny /= len;
56499
- nz /= len;
56500
- }
56501
- view.setFloat32(offset, nx, true);
56502
- view.setFloat32(offset + 4, ny, true);
56503
- view.setFloat32(offset + 8, nz, true);
56504
- offset += 12;
56505
- view.setFloat32(offset, v0x, true);
56506
- view.setFloat32(offset + 4, v0y, true);
56507
- view.setFloat32(offset + 8, v0z, true);
56508
- offset += 12;
56509
- view.setFloat32(offset, v1x, true);
56510
- view.setFloat32(offset + 4, v1y, true);
56511
- view.setFloat32(offset + 8, v1z, true);
56512
- offset += 12;
56513
- view.setFloat32(offset, v2x, true);
56514
- view.setFloat32(offset + 4, v2y, true);
56515
- view.setFloat32(offset + 8, v2z, true);
56516
- offset += 12;
56517
- view.setUint16(offset, 0, true);
56518
- offset += 2;
56519
- }
56520
- return offset;
56266
+ f32Len += part.mesh.positions.length;
56267
+ u32Len += part.mesh.indices.length;
56268
+ }
56269
+ const f32Data = new Float32Array(f32Len);
56270
+ const u32Data = new Uint32Array(u32Len);
56271
+ let f32Off = 0;
56272
+ let u32Off = 0;
56273
+ const meshes = scene.parts.map((part) => {
56274
+ f32Data.set(part.mesh.positions, f32Off);
56275
+ u32Data.set(part.mesh.indices, u32Off);
56276
+ const spec = {
56277
+ positions: [f32Off, part.mesh.positions.length],
56278
+ indices: [u32Off, part.mesh.indices.length]
56279
+ };
56280
+ f32Off += part.mesh.positions.length;
56281
+ u32Off += part.mesh.indices.length;
56282
+ return spec;
56283
+ });
56284
+ return exportKernel().buildStlBytes(
56285
+ JSON.stringify({ name, meshes }),
56286
+ f32Data,
56287
+ u32Data
56288
+ );
56521
56289
  }
56522
56290
  var init_stl = __esm({
56523
56291
  "src/export/stl.ts"() {
56524
56292
  "use strict";
56293
+ init_glb();
56525
56294
  }
56526
56295
  });
56527
56296
 
@@ -72670,335 +72439,6 @@ var init_sheet_metal2 = __esm({
72670
72439
 
72671
72440
  // src/tools/acoustics.ts
72672
72441
  import { writeFileSync as writeFileSync5 } from "node:fs";
72673
- function freeFreeBetaL(count) {
72674
- const roots = [];
72675
- for (let n = 1; n <= count; n++) {
72676
- let x = (2 * n + 1) * Math.PI / 2;
72677
- for (let i = 0; i < 50; i++) {
72678
- const f = Math.cosh(x) * Math.cos(x) - 1;
72679
- const df = Math.sinh(x) * Math.cos(x) - Math.cosh(x) * Math.sin(x);
72680
- const step = f / df;
72681
- x -= step;
72682
- if (Math.abs(step) < 1e-13) break;
72683
- }
72684
- roots.push(x);
72685
- }
72686
- return roots;
72687
- }
72688
- function modeShape(betaL, xi) {
72689
- const sigma = (Math.cosh(betaL) - Math.cos(betaL)) / (Math.sinh(betaL) - Math.sin(betaL));
72690
- const raw = (t2) => Math.cosh(betaL * t2) + Math.cos(betaL * t2) - sigma * (Math.sinh(betaL * t2) + Math.sin(betaL * t2));
72691
- return raw(xi) / Math.abs(raw(0));
72692
- }
72693
- function closedFormHz(bar, count) {
72694
- const L = bar.length_mm / 1e3;
72695
- const t2 = bar.thickness_mm / 1e3;
72696
- const E = bar.modulus_gpa * 1e9;
72697
- const c = t2 * Math.sqrt(E / (12 * bar.density_kg_m3));
72698
- return freeFreeBetaL(count).map((bl) => bl * bl * c / (2 * Math.PI * L * L));
72699
- }
72700
- function effectiveWidthMm(bar, xMm) {
72701
- let w = bar.width_mm;
72702
- const r = bar.hole_dia_mm / 2;
72703
- for (const h of bar.holes_mm) {
72704
- const d = Math.abs(xMm - h);
72705
- if (d < r) w -= 2 * Math.sqrt(r * r - d * d);
72706
- }
72707
- return Math.max(w, 1e-6);
72708
- }
72709
- function femHz(bar, count, nel = 96) {
72710
- const L = bar.length_mm / 1e3;
72711
- const t2 = bar.thickness_mm / 1e3;
72712
- const E = bar.modulus_gpa * 1e9;
72713
- const rho = bar.density_kg_m3;
72714
- const nDof = 2 * (nel + 1);
72715
- const K = zeros(nDof, nDof);
72716
- const M = zeros(nDof, nDof);
72717
- const le = L / nel;
72718
- const gp = [-0.8611363115940526, -0.3399810435848563, 0.3399810435848563, 0.8611363115940526];
72719
- const gw = [0.3478548451374538, 0.6521451548625461, 0.6521451548625461, 0.3478548451374538];
72720
- for (let e = 0; e < nel; e++) {
72721
- const x0 = e * le;
72722
- const ke = zeros(4, 4);
72723
- const me = zeros(4, 4);
72724
- for (let g = 0; g < 4; g++) {
72725
- const xi = gp[g];
72726
- const x = x0 + (xi + 1) / 2 * le;
72727
- const wEff = effectiveWidthMm(bar, x * 1e3) / 1e3;
72728
- const A = wEff * t2;
72729
- const I = wEff * t2 * t2 * t2 / 12;
72730
- const s = le / 2;
72731
- const N = [
72732
- 0.25 * (1 - xi) * (1 - xi) * (2 + xi),
72733
- s * 0.25 * (1 - xi) * (1 - xi) * (1 + xi),
72734
- 0.25 * (1 + xi) * (1 + xi) * (2 - xi),
72735
- s * 0.25 * (1 + xi) * (1 + xi) * (xi - 1)
72736
- ];
72737
- const c2 = 2 / le * (2 / le);
72738
- const B = [
72739
- 1.5 * xi * c2,
72740
- s * (1.5 * xi - 0.5) * c2,
72741
- -1.5 * xi * c2,
72742
- s * (1.5 * xi + 0.5) * c2
72743
- ];
72744
- const wJ = gw[g] * s;
72745
- for (let i = 0; i < 4; i++) {
72746
- for (let j = 0; j < 4; j++) {
72747
- ke[i][j] += E * I * B[i] * B[j] * wJ;
72748
- me[i][j] += rho * A * N[i] * N[j] * wJ;
72749
- }
72750
- }
72751
- }
72752
- const dof = [2 * e, 2 * e + 1, 2 * e + 2, 2 * e + 3];
72753
- for (let i = 0; i < 4; i++) {
72754
- for (let j = 0; j < 4; j++) {
72755
- K[dof[i]][dof[j]] += ke[i][j];
72756
- M[dof[i]][dof[j]] += me[i][j];
72757
- }
72758
- }
72759
- }
72760
- const Lc = cholesky(M);
72761
- const C = congruence(K, Lc);
72762
- const lambda = jacobiEigenvalues(C);
72763
- lambda.sort((a, b) => a - b);
72764
- const elastic = lambda.filter((l) => l > (2 * Math.PI) ** 2);
72765
- return elastic.slice(0, count).map((l) => Math.sqrt(l) / (2 * Math.PI));
72766
- }
72767
- function zeros(r, c) {
72768
- return Array.from({ length: r }, () => new Array(c).fill(0));
72769
- }
72770
- function cholesky(A) {
72771
- const n = A.length;
72772
- const L = zeros(n, n);
72773
- for (let i = 0; i < n; i++) {
72774
- for (let j = 0; j <= i; j++) {
72775
- let s = A[i][j];
72776
- for (let k = 0; k < j; k++) s -= L[i][k] * L[j][k];
72777
- if (i === j) {
72778
- if (s <= 0) throw new Error("mass matrix not positive definite");
72779
- L[i][i] = Math.sqrt(s);
72780
- } else {
72781
- L[i][j] = s / L[j][j];
72782
- }
72783
- }
72784
- }
72785
- return L;
72786
- }
72787
- function congruence(K, L) {
72788
- const n = K.length;
72789
- const Y = zeros(n, n);
72790
- for (let c = 0; c < n; c++) {
72791
- for (let i = 0; i < n; i++) {
72792
- let s = K[i][c];
72793
- for (let k = 0; k < i; k++) s -= L[i][k] * Y[k][c];
72794
- Y[i][c] = s / L[i][i];
72795
- }
72796
- }
72797
- const C = zeros(n, n);
72798
- for (let r = 0; r < n; r++) {
72799
- for (let i = 0; i < n; i++) {
72800
- let s = Y[r][i];
72801
- for (let k = 0; k < i; k++) s -= L[i][k] * C[r][k];
72802
- C[r][i] = s / L[i][i];
72803
- }
72804
- }
72805
- for (let i = 0; i < n; i++)
72806
- for (let j = 0; j < i; j++) {
72807
- const v = 0.5 * (C[i][j] + C[j][i]);
72808
- C[i][j] = v;
72809
- C[j][i] = v;
72810
- }
72811
- return C;
72812
- }
72813
- function jacobiEigenvalues(A) {
72814
- const n = A.length;
72815
- const a = A.map((row) => row.slice());
72816
- for (let sweep = 0; sweep < 30; sweep++) {
72817
- let off = 0;
72818
- for (let i = 0; i < n; i++)
72819
- for (let j = i + 1; j < n; j++) off += a[i][j] * a[i][j];
72820
- if (off < 1e-18 * n * n) break;
72821
- for (let p = 0; p < n - 1; p++) {
72822
- for (let q = p + 1; q < n; q++) {
72823
- if (Math.abs(a[p][q]) < 1e-30) continue;
72824
- const theta = (a[q][q] - a[p][p]) / (2 * a[p][q]);
72825
- const t2 = Math.sign(theta) / (Math.abs(theta) + Math.sqrt(theta * theta + 1));
72826
- const c = 1 / Math.sqrt(t2 * t2 + 1);
72827
- const s = t2 * c;
72828
- for (let k = 0; k < n; k++) {
72829
- const akp = a[k][p];
72830
- const akq = a[k][q];
72831
- a[k][p] = c * akp - s * akq;
72832
- a[k][q] = s * akp + c * akq;
72833
- }
72834
- for (let k = 0; k < n; k++) {
72835
- const apk = a[p][k];
72836
- const aqk = a[q][k];
72837
- a[p][k] = c * apk - s * aqk;
72838
- a[q][k] = s * apk + c * aqk;
72839
- }
72840
- }
72841
- }
72842
- }
72843
- return a.map((row, i) => row[i]);
72844
- }
72845
- function malletSpectrum(hz, contactMs) {
72846
- const tc = contactMs / 1e3;
72847
- const u = 2 * hz * tc;
72848
- const denom = Math.abs(1 - u * u);
72849
- if (denom < 1e-9) return Math.PI / 4;
72850
- return Math.abs(Math.cos(Math.PI * hz * tc)) / denom;
72851
- }
72852
- function strikeModes(bar, femFreqs, strikeFrac, contactMs, materialQ = 2500, suspensionQ0 = 150) {
72853
- const betas = freeFreeBetaL(femFreqs.length);
72854
- const modes = femFreqs.map((hz, i) => {
72855
- const phiStrike = modeShape(betas[i], strikeFrac);
72856
- const gain = Math.abs(phiStrike) * malletSpectrum(hz, contactMs);
72857
- let phiSq = 0;
72858
- for (const h of bar.holes_mm) {
72859
- const phi = modeShape(betas[i], h / bar.length_mm);
72860
- phiSq += phi * phi;
72861
- }
72862
- const qSusp = suspensionQ0 / Math.max(phiSq, 1e-9);
72863
- const q = 1 / (1 / materialQ + 1 / qSusp);
72864
- return {
72865
- n: i + 1,
72866
- hz,
72867
- gain,
72868
- q,
72869
- t60_s: Math.log(1e3) * q / (Math.PI * hz)
72870
- };
72871
- });
72872
- const peak = Math.max(...modes.map((m) => m.gain), 1e-12);
72873
- for (const m of modes) m.gain /= peak;
72874
- return modes;
72875
- }
72876
- function synthesize(modes, durationS, sampleRate) {
72877
- const n = Math.floor(durationS * sampleRate);
72878
- const out = new Float64Array(n);
72879
- for (const m of modes) {
72880
- if (m.hz >= 0.45 * sampleRate) continue;
72881
- const w = 2 * Math.PI * m.hz / sampleRate;
72882
- const tau = m.q / (Math.PI * m.hz);
72883
- const decay = Math.exp(-1 / (tau * sampleRate));
72884
- let amp = m.gain;
72885
- for (let k = 0; k < n; k++) {
72886
- out[k] += amp * Math.sin(w * k);
72887
- amp *= decay;
72888
- }
72889
- }
72890
- let peak = 1e-12;
72891
- for (let k = 0; k < n; k++) peak = Math.max(peak, Math.abs(out[k]));
72892
- const norm = 0.891 / peak;
72893
- for (let k = 0; k < n; k++) out[k] *= norm;
72894
- return out;
72895
- }
72896
- function encodeWav(samples, sampleRate) {
72897
- const n = samples.length;
72898
- const buf = new ArrayBuffer(44 + n * 2);
72899
- const v = new DataView(buf);
72900
- const str2 = (off, s) => {
72901
- for (let i = 0; i < s.length; i++) v.setUint8(off + i, s.charCodeAt(i));
72902
- };
72903
- str2(0, "RIFF");
72904
- v.setUint32(4, 36 + n * 2, true);
72905
- str2(8, "WAVE");
72906
- str2(12, "fmt ");
72907
- v.setUint32(16, 16, true);
72908
- v.setUint16(20, 1, true);
72909
- v.setUint16(22, 1, true);
72910
- v.setUint32(24, sampleRate, true);
72911
- v.setUint32(28, sampleRate * 2, true);
72912
- v.setUint16(32, 2, true);
72913
- v.setUint16(34, 16, true);
72914
- str2(36, "data");
72915
- v.setUint32(40, n * 2, true);
72916
- for (let i = 0; i < n; i++) {
72917
- const s = Math.max(-1, Math.min(1, samples[i]));
72918
- v.setInt16(44 + i * 2, Math.round(s * 32767), true);
72919
- }
72920
- return new Uint8Array(buf);
72921
- }
72922
- function fft(re, im) {
72923
- const n = re.length;
72924
- for (let i = 1, j = 0; i < n; i++) {
72925
- let bit = n >> 1;
72926
- for (; j & bit; bit >>= 1) j ^= bit;
72927
- j ^= bit;
72928
- if (i < j) {
72929
- [re[i], re[j]] = [re[j], re[i]];
72930
- [im[i], im[j]] = [im[j], im[i]];
72931
- }
72932
- }
72933
- for (let len = 2; len <= n; len <<= 1) {
72934
- const ang = -2 * Math.PI / len;
72935
- const wr = Math.cos(ang);
72936
- const wi = Math.sin(ang);
72937
- for (let i = 0; i < n; i += len) {
72938
- let cr = 1;
72939
- let ci = 0;
72940
- for (let k = 0; k < len / 2; k++) {
72941
- const ur = re[i + k];
72942
- const ui = im[i + k];
72943
- const vr = re[i + k + len / 2] * cr - im[i + k + len / 2] * ci;
72944
- const vi = re[i + k + len / 2] * ci + im[i + k + len / 2] * cr;
72945
- re[i + k] = ur + vr;
72946
- im[i + k] = ui + vi;
72947
- re[i + k + len / 2] = ur - vr;
72948
- im[i + k + len / 2] = ui - vi;
72949
- const ncr = cr * wr - ci * wi;
72950
- ci = cr * wi + ci * wr;
72951
- cr = ncr;
72952
- }
72953
- }
72954
- }
72955
- }
72956
- function spectrumPeaks(samples, sampleRate, maxPeaks = 8) {
72957
- const n = Math.min(65536, 1 << Math.floor(Math.log2(samples.length)));
72958
- const re = new Float64Array(n);
72959
- const im = new Float64Array(n);
72960
- for (let i = 0; i < n; i++) {
72961
- const w = 0.5 * (1 - Math.cos(2 * Math.PI * i / (n - 1)));
72962
- re[i] = samples[i] * w;
72963
- }
72964
- fft(re, im);
72965
- const half = n / 2;
72966
- const mag = new Float64Array(half);
72967
- let maxMag = 1e-30;
72968
- for (let i = 0; i < half; i++) {
72969
- mag[i] = Math.hypot(re[i], im[i]);
72970
- maxMag = Math.max(maxMag, mag[i]);
72971
- }
72972
- const peaks = [];
72973
- const floorDb = -60;
72974
- for (let i = 2; i < half - 2; i++) {
72975
- if (mag[i] <= mag[i - 1] || mag[i] < mag[i + 1]) continue;
72976
- const db = 20 * Math.log10(mag[i] / maxMag);
72977
- if (db < floorDb) continue;
72978
- const a = Math.log(mag[i - 1] + 1e-30);
72979
- const b = Math.log(mag[i] + 1e-30);
72980
- const c = Math.log(mag[i + 1] + 1e-30);
72981
- const delta = 0.5 * (a - c) / (a - 2 * b + c || 1e-30);
72982
- peaks.push({ hz: (i + delta) * sampleRate / n, db });
72983
- }
72984
- peaks.sort((a, b) => b.db - a.db);
72985
- const kept = [];
72986
- for (const p of peaks) {
72987
- if (kept.every((k) => Math.abs(p.hz - k.hz) / k.hz > 0.03)) kept.push(p);
72988
- if (kept.length >= maxPeaks) break;
72989
- }
72990
- return kept.sort((a, b) => a.hz - b.hz);
72991
- }
72992
- function noteToHz(note) {
72993
- const m = /^([A-Ga-g])([#b]?)(-?\d)$/.exec(note.trim());
72994
- if (!m) throw new Error(`unparseable note ${JSON.stringify(note)} \u2014 use e.g. "C6", "F#4"`);
72995
- const base = { C: 0, D: 2, E: 4, F: 5, G: 7, A: 9, B: 11 };
72996
- let semis = base[m[1].toUpperCase()];
72997
- if (m[2] === "#") semis += 1;
72998
- if (m[2] === "b") semis -= 1;
72999
- const midi = (Number(m[3]) + 1) * 12 + semis;
73000
- return 440 * 2 ** ((midi - 69) / 12);
73001
- }
73002
72442
  function lookupMaterial(engine, name) {
73003
72443
  const key = name.trim().toLowerCase().replace(/[_ ]/g, "-");
73004
72444
  const registry4 = engine.getSheetMetalMaterials();
@@ -73045,10 +72485,8 @@ function barFromDocument(engine, documentId) {
73045
72485
  let holeDia = 0;
73046
72486
  for (const loop of holeLoops) {
73047
72487
  const cs = loop.map((p) => axis === "x" ? p.x : p.y);
73048
- const os = loop.map((p) => axis === "x" ? p.y : p.x);
73049
72488
  holesMm.push((Math.min(...cs) + Math.max(...cs)) / 2);
73050
72489
  holeDia = Math.max(holeDia, Math.max(...cs) - Math.min(...cs));
73051
- void os;
73052
72490
  }
73053
72491
  return {
73054
72492
  bar: {
@@ -73095,28 +72533,26 @@ function simulateStrike(input, engine) {
73095
72533
  const durationS = Math.min(Math.max(Number(a.duration_s ?? 2.5), 0.5), 10);
73096
72534
  const strikeFrac = Math.min(Math.max(Number(a.strike_position ?? 0.5), 0.02), 0.98);
73097
72535
  const contactMs = Math.min(Math.max(Number(a.mallet_contact_ms ?? 0.5), 0.05), 10);
73098
- const nModes = 6;
73099
- const closed = closedFormHz(bar, nModes);
73100
- const fem = femHz(bar, nModes);
73101
- const modes = strikeModes(bar, fem, strikeFrac, contactMs);
73102
- const audible = modes.filter((m) => m.hz < 0.45 * sampleRate && m.gain > 1e-4);
73103
- const samples = synthesize(audible, durationS, sampleRate);
73104
- const peaks = spectrumPeaks(samples, sampleRate);
73105
72536
  let expected;
73106
72537
  if (typeof a.expect_hz === "number") expected = a.expect_hz;
73107
- else if (typeof a.note === "string") expected = noteToHz(a.note);
73108
- const toleranceCents = Number(a.tolerance_cents ?? 10);
73109
- const dominant = peaks.reduce(
73110
- (best, p) => p.db > best.db ? p : best,
73111
- { hz: 0, db: -Infinity }
73112
- );
73113
- const verdict = expected !== void 0 ? {
73114
- expected_hz: expected,
73115
- measured_hz: dominant.hz,
73116
- cents_error: cents(dominant.hz, expected),
73117
- tolerance_cents: toleranceCents,
73118
- pass: Math.abs(cents(dominant.hz, expected)) <= toleranceCents
73119
- } : void 0;
72538
+ else if (typeof a.note === "string") expected = engine.noteToHz(a.note);
72539
+ const wantWav = typeof a.wav_filename === "string" && a.wav_filename.length > 0;
72540
+ if (wantWav && !/\.wav$/i.test(a.wav_filename)) {
72541
+ throw new Error("wav_filename must end in .wav");
72542
+ }
72543
+ const result = engine.simulateStrike({
72544
+ bar,
72545
+ strike_position: strikeFrac,
72546
+ mallet_contact_ms: contactMs,
72547
+ duration_s: durationS,
72548
+ sample_rate: sampleRate,
72549
+ n_modes: 6,
72550
+ expected_hz: expected ?? null,
72551
+ tolerance_cents: Number(a.tolerance_cents ?? 10),
72552
+ include_wav: wantWav
72553
+ });
72554
+ const closed = result.closed_form_hz;
72555
+ const fem = result.fem_hz;
73120
72556
  const payload = {
73121
72557
  bar: { ...bar, material: mat.name, material_display: mat.display_name },
73122
72558
  physics: {
@@ -73133,22 +72569,19 @@ function simulateStrike(input, engine) {
73133
72569
  duration_s: durationS,
73134
72570
  sample_rate: sampleRate
73135
72571
  },
73136
- modes: audible.map((m) => ({
72572
+ modes: result.modes.map((m) => ({
73137
72573
  n: m.n,
73138
72574
  hz: round24(m.hz),
73139
72575
  gain: round4(m.gain),
73140
72576
  q: Math.round(m.q),
73141
72577
  t60_s: round24(m.t60_s)
73142
72578
  })),
73143
- spectrum_peaks: peaks.map((p) => ({ hz: round24(p.hz), db: round24(p.db) })),
73144
- ...verdict ? { verdict } : {},
72579
+ spectrum_peaks: result.spectrum_peaks.map((p) => ({ hz: round24(p.hz), db: round24(p.db) })),
72580
+ ...result.verdict ? { verdict: result.verdict } : {},
73145
72581
  limits: "1-D transverse bending only (no torsional/lateral modes); decay Q is a heuristic (material + cord-at-holes), frequencies are not"
73146
72582
  };
73147
- if (typeof a.wav_filename === "string" && a.wav_filename.length > 0) {
73148
- if (!/\.wav$/i.test(a.wav_filename)) {
73149
- throw new Error("wav_filename must end in .wav");
73150
- }
73151
- const bytes = encodeWav(samples, sampleRate);
72583
+ if (wantWav && result.wav_base64) {
72584
+ const bytes = Buffer.from(result.wav_base64, "base64");
73152
72585
  if (isRemoteDeployment()) {
73153
72586
  const cap = maxInlineExportBytes();
73154
72587
  if (bytes.length > cap) {
@@ -73159,7 +72592,7 @@ function simulateStrike(input, engine) {
73159
72592
  payload.wav = {
73160
72593
  filename: a.wav_filename,
73161
72594
  bytes: bytes.length,
73162
- data_base64: Buffer.from(bytes).toString("base64")
72595
+ data_base64: result.wav_base64
73163
72596
  };
73164
72597
  } else {
73165
72598
  const path = resolveWithinRoot(
@@ -78325,8 +77758,8 @@ var init_server3 = __esm({
78325
77758
  init_order_feed();
78326
77759
  init_animate();
78327
77760
  PKG_VERSION = (() => {
78328
- if ("0.9.4-main.11") {
78329
- return "0.9.4-main.11";
77761
+ if ("0.9.4-main.14") {
77762
+ return "0.9.4-main.14";
78330
77763
  }
78331
77764
  try {
78332
77765
  const req = createRequire2(import.meta.url);
@@ -78335,8 +77768,8 @@ var init_server3 = __esm({
78335
77768
  return "0.0.0";
78336
77769
  }
78337
77770
  })();
78338
- BUILD_SHA = "681857ca2f62f173237de79ff2621cd1609480a7";
78339
- BUILD_TIME = "2026-07-24T16:25:54.297Z";
77771
+ BUILD_SHA = "292cf10c5c65aec57d1c40f8bd5bdb0a1222acaf";
77772
+ BUILD_TIME = "2026-07-24T16:37:44.101Z";
78340
77773
  SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
78341
77774
  VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
78342
77775
  INSTANCE_ID = randomUUID6().slice(0, 8);