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

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/README.md CHANGED
@@ -6,4 +6,4 @@ vcad's MCP server as a self-contained bundle (server + BRep kernel WASM).
6
6
  { "mcpServers": { "vcad": { "command": "npx", "args": ["-y", "@vcad/mcp"] } } }
7
7
  ```
8
8
 
9
- Built from 292cf10c5c65aec57d1c40f8bd5bdb0a1222acaf at 2026-07-24T16:37:44.101Z. Source: https://github.com/ecto/vcad
9
+ Built from 63c1a040cf3352ca640144ec217f3f96271180c9 at 2026-07-24T17:21:48.535Z. Source: https://github.com/ecto/vcad
package/index.mjs CHANGED
@@ -69,7 +69,9 @@ __export(vcad_kernel_wasm_exports, {
69
69
  atoms_parse_xyz: () => atoms_parse_xyz,
70
70
  atoms_write_xyz: () => atoms_write_xyz,
71
71
  buildCalibrationReportJson: () => buildCalibrationReportJson,
72
+ buildGlbBytes: () => buildGlbBytes,
72
73
  buildPart: () => buildPart,
74
+ buildStlBytes: () => buildStlBytes,
73
75
  build_chat_system_prompt: () => build_chat_system_prompt,
74
76
  calibrationDefaultTolerance: () => calibrationDefaultTolerance,
75
77
  calibrationFingerprintDocument: () => calibrationFingerprintDocument,
@@ -148,8 +150,10 @@ __export(vcad_kernel_wasm_exports, {
148
150
  ecadVerifyReceipt: () => ecadVerifyReceipt,
149
151
  ecadVerifySubstitution: () => ecadVerifySubstitution,
150
152
  emSimulate: () => emSimulate,
153
+ embroideryDesignToMesh: () => embroideryDesignToMesh,
151
154
  estimatePrintCost: () => estimatePrintCost,
152
155
  estimate_cost_for_process: () => estimate_cost_for_process,
156
+ eulerXyzDegToQuat: () => eulerXyzDegToQuat,
153
157
  evalVcadSource: () => evalVcadSource,
154
158
  evaluateDocument: () => evaluateDocument,
155
159
  evaluateSheetMetalChain: () => evaluateSheetMetalChain,
@@ -254,6 +258,7 @@ __export(vcad_kernel_wasm_exports, {
254
258
  toleranceAnalyze: () => toleranceAnalyze,
255
259
  topologyOptimizeBox: () => topologyOptimizeBox,
256
260
  topologyOptimizeMesh: () => topologyOptimizeMesh,
261
+ transformMeshBuffers: () => transformMeshBuffers,
257
262
  writeEmbroideryDst: () => writeEmbroideryDst,
258
263
  writeEmbroideryPes: () => writeEmbroideryPes
259
264
  });
@@ -463,6 +468,21 @@ function buildCalibrationReportJson(prediction_json, measurements_json, options_
463
468
  wasm.__wbindgen_free(deferred5_0, deferred5_1, 1);
464
469
  }
465
470
  }
471
+ function buildGlbBytes(spec_json, f32_data, u32_data) {
472
+ const ptr0 = passStringToWasm0(spec_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
473
+ const len0 = WASM_VECTOR_LEN;
474
+ const ptr1 = passArrayF32ToWasm0(f32_data, wasm.__wbindgen_malloc);
475
+ const len1 = WASM_VECTOR_LEN;
476
+ const ptr2 = passArray32ToWasm0(u32_data, wasm.__wbindgen_malloc);
477
+ const len2 = WASM_VECTOR_LEN;
478
+ const ret = wasm.buildGlbBytes(ptr0, len0, ptr1, len1, ptr2, len2);
479
+ if (ret[3]) {
480
+ throw takeFromExternrefTable0(ret[2]);
481
+ }
482
+ var v4 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
483
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
484
+ return v4;
485
+ }
466
486
  function buildPart(path, params_json) {
467
487
  let deferred4_0;
468
488
  let deferred4_1;
@@ -486,6 +506,21 @@ function buildPart(path, params_json) {
486
506
  wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
487
507
  }
488
508
  }
509
+ function buildStlBytes(spec_json, f32_data, u32_data) {
510
+ const ptr0 = passStringToWasm0(spec_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
511
+ const len0 = WASM_VECTOR_LEN;
512
+ const ptr1 = passArrayF32ToWasm0(f32_data, wasm.__wbindgen_malloc);
513
+ const len1 = WASM_VECTOR_LEN;
514
+ const ptr2 = passArray32ToWasm0(u32_data, wasm.__wbindgen_malloc);
515
+ const len2 = WASM_VECTOR_LEN;
516
+ const ret = wasm.buildStlBytes(ptr0, len0, ptr1, len1, ptr2, len2);
517
+ if (ret[3]) {
518
+ throw takeFromExternrefTable0(ret[2]);
519
+ }
520
+ var v4 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
521
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
522
+ return v4;
523
+ }
489
524
  function build_chat_system_prompt(parts_json, selection_json) {
490
525
  let deferred3_0;
491
526
  let deferred3_1;
@@ -1486,6 +1521,15 @@ function emSimulate(spec_json, params_json, options_json) {
1486
1521
  }
1487
1522
  return takeFromExternrefTable0(ret[0]);
1488
1523
  }
1524
+ function embroideryDesignToMesh(design_json) {
1525
+ const ptr0 = passStringToWasm0(design_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1526
+ const len0 = WASM_VECTOR_LEN;
1527
+ const ret = wasm.embroideryDesignToMesh(ptr0, len0);
1528
+ if (ret[2]) {
1529
+ throw takeFromExternrefTable0(ret[1]);
1530
+ }
1531
+ return takeFromExternrefTable0(ret[0]);
1532
+ }
1489
1533
  function estimatePrintCost(volume_mm3, infill_density, wall_count, line_width, material_name) {
1490
1534
  const ptr0 = passStringToWasm0(material_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1491
1535
  const len0 = WASM_VECTOR_LEN;
@@ -1506,6 +1550,12 @@ function estimate_cost_for_process(process3, material_name, part_volume_mm3, sto
1506
1550
  }
1507
1551
  return takeFromExternrefTable0(ret[0]);
1508
1552
  }
1553
+ function eulerXyzDegToQuat(x_deg, y_deg, z_deg) {
1554
+ const ret = wasm.eulerXyzDegToQuat(x_deg, y_deg, z_deg);
1555
+ var v1 = getArrayF64FromWasm0(ret[0], ret[1]).slice();
1556
+ wasm.__wbindgen_free(ret[0], ret[1] * 8, 8);
1557
+ return v1;
1558
+ }
1509
1559
  function evalVcadSource(source) {
1510
1560
  const ptr0 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
1511
1561
  const len0 = WASM_VECTOR_LEN;
@@ -2907,6 +2957,19 @@ function topologyOptimizeMesh(spec_json, positions, indices) {
2907
2957
  }
2908
2958
  return takeFromExternrefTable0(ret[0]);
2909
2959
  }
2960
+ function transformMeshBuffers(positions, normals, transform_json) {
2961
+ const ptr0 = passArrayF32ToWasm0(positions, wasm.__wbindgen_malloc);
2962
+ const len0 = WASM_VECTOR_LEN;
2963
+ var ptr1 = isLikeNone(normals) ? 0 : passArrayF32ToWasm0(normals, wasm.__wbindgen_malloc);
2964
+ var len1 = WASM_VECTOR_LEN;
2965
+ const ptr2 = passStringToWasm0(transform_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
2966
+ const len2 = WASM_VECTOR_LEN;
2967
+ const ret = wasm.transformMeshBuffers(ptr0, len0, ptr1, len1, ptr2, len2);
2968
+ if (ret[2]) {
2969
+ throw takeFromExternrefTable0(ret[1]);
2970
+ }
2971
+ return takeFromExternrefTable0(ret[0]);
2972
+ }
2910
2973
  function writeEmbroideryDst(json) {
2911
2974
  const ptr0 = passStringToWasm0(json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
2912
2975
  const len0 = WASM_VECTOR_LEN;
@@ -8727,8 +8790,8 @@ function evaluateDocument2(doc, kernel2, options = {}) {
8727
8790
  if (p.mesh.positions.length === 0 && i < visibleRoots.length) {
8728
8791
  const emb = findEmbroideryPattern(visibleRoots[i].root, doc.nodes);
8729
8792
  if (emb) {
8730
- const baseMesh = embroideryPatternToMesh(emb.pattern);
8731
- const mesh = transformMesh(baseMesh, emb.transform);
8793
+ const baseMesh = embroideryPatternToMeshWithKernel(emb.pattern, kernel2);
8794
+ const mesh = transformMeshWithKernel(baseMesh, emb.transform, kernel2);
8732
8795
  return { mesh, material: p.material };
8733
8796
  }
8734
8797
  try {
@@ -8806,35 +8869,9 @@ function convertSketchToProfile(op) {
8806
8869
  }
8807
8870
  function solidToMesh(solid) {
8808
8871
  const meshData = solid.getMesh();
8809
- const positions = new Float32Array(meshData.positions);
8810
- const indices = new Uint32Array(meshData.indices);
8811
- const numVertices = positions.length / 3;
8812
- let hasInvalidIndices = false;
8813
- for (let i = 0; i < indices.length; i++) {
8814
- if (indices[i] >= numVertices) {
8815
- hasInvalidIndices = true;
8816
- break;
8817
- }
8818
- }
8819
- if (hasInvalidIndices) {
8820
- const validIndices = [];
8821
- for (let i = 0; i < indices.length; i += 3) {
8822
- const i0 = indices[i];
8823
- const i1 = indices[i + 1];
8824
- const i2 = indices[i + 2];
8825
- if (i0 < numVertices && i1 < numVertices && i2 < numVertices) {
8826
- validIndices.push(i0, i1, i2);
8827
- }
8828
- }
8829
- return {
8830
- positions,
8831
- indices: new Uint32Array(validIndices),
8832
- normals: meshData.normals ? new Float32Array(meshData.normals) : void 0
8833
- };
8834
- }
8835
8872
  return {
8836
- positions,
8837
- indices,
8873
+ positions: new Float32Array(meshData.positions),
8874
+ indices: new Uint32Array(meshData.indices),
8838
8875
  normals: meshData.normals ? new Float32Array(meshData.normals) : void 0
8839
8876
  };
8840
8877
  }
@@ -8855,7 +8892,7 @@ function resolveSheetMetalPart(rootId, nodes, kernel2) {
8855
8892
  return null;
8856
8893
  const { mesh, sheetMetal } = evaluateSheetMetalChain2(chain, kernel2);
8857
8894
  return {
8858
- mesh: isIdentityTransform(sm.transform) ? mesh : transformMesh(mesh, sm.transform),
8895
+ mesh: isIdentityTransform(sm.transform) ? mesh : transformMeshWithKernel(mesh, sm.transform, kernel2),
8859
8896
  sheetMetal
8860
8897
  };
8861
8898
  }
@@ -8898,6 +8935,37 @@ function embroideryPatternToMesh(op) {
8898
8935
  colors: new Float32Array(allColors)
8899
8936
  };
8900
8937
  }
8938
+ function embroideryPatternToMeshWithKernel(op, kernel2) {
8939
+ if (kernel2?.embroideryDesignToMesh) {
8940
+ try {
8941
+ const m = kernel2.embroideryDesignToMesh(JSON.stringify(op.design));
8942
+ return {
8943
+ positions: new Float32Array(m.positions),
8944
+ indices: new Uint32Array(m.indices),
8945
+ colors: new Float32Array(m.colors)
8946
+ };
8947
+ } catch (e) {
8948
+ console.warn("[ENGINE] kernel embroideryDesignToMesh failed, falling back to TS:", e);
8949
+ }
8950
+ }
8951
+ return embroideryPatternToMesh(op);
8952
+ }
8953
+ function transformMeshWithKernel(mesh, transform2, kernel2) {
8954
+ if (kernel2?.transformMeshBuffers) {
8955
+ try {
8956
+ const r = kernel2.transformMeshBuffers(mesh.positions, mesh.normals, JSON.stringify(transform2));
8957
+ return {
8958
+ positions: new Float32Array(r.positions),
8959
+ indices: mesh.indices,
8960
+ normals: r.normals ? new Float32Array(r.normals) : void 0,
8961
+ colors: mesh.colors
8962
+ };
8963
+ } catch (e) {
8964
+ console.warn("[ENGINE] kernel transformMeshBuffers failed, falling back to TS:", e);
8965
+ }
8966
+ }
8967
+ return transformMesh(mesh, transform2);
8968
+ }
8901
8969
  function transformMesh(mesh, transform2) {
8902
8970
  const { translate, rotate, scale } = transform2;
8903
8971
  const positions = new Float32Array(mesh.positions.length);
@@ -8954,8 +9022,8 @@ function evaluateDocumentTS(doc, kernel2, options = {}) {
8954
9022
  const parts = visibleRoots.map((entry, idx) => {
8955
9023
  const embPattern = findEmbroideryPattern(entry.root, doc.nodes);
8956
9024
  if (embPattern) {
8957
- const baseMesh = embroideryPatternToMesh(embPattern.pattern);
8958
- const mesh = transformMesh(baseMesh, embPattern.transform);
9025
+ const baseMesh = embroideryPatternToMeshWithKernel(embPattern.pattern, kernel2);
9026
+ const mesh = transformMeshWithKernel(baseMesh, embPattern.transform, kernel2);
8959
9027
  solids.push(Solid2.empty());
8960
9028
  return { mesh, material: entry.material };
8961
9029
  }
@@ -8966,7 +9034,7 @@ function evaluateDocumentTS(doc, kernel2, options = {}) {
8966
9034
  indices: new Uint32Array(imported.mesh.indices),
8967
9035
  normals: imported.mesh.normals ? new Float32Array(imported.mesh.normals) : void 0
8968
9036
  };
8969
- const mesh = transformMesh(baseMesh, imported.transform);
9037
+ const mesh = transformMeshWithKernel(baseMesh, imported.transform, kernel2);
8970
9038
  solids.push(Solid2.empty());
8971
9039
  return { mesh, material: entry.material };
8972
9040
  }
@@ -10851,753 +10919,57 @@ var init_atoms = __esm({
10851
10919
  });
10852
10920
 
10853
10921
  // ../engine/dist/enclosure-fit.js
10854
- function outlineAabb(outline) {
10855
- let minX = Infinity;
10856
- let maxX = -Infinity;
10857
- let minY = Infinity;
10858
- let maxY = -Infinity;
10859
- for (const v of outline.vertices) {
10860
- if (v.x < minX)
10861
- minX = v.x;
10862
- if (v.x > maxX)
10863
- maxX = v.x;
10864
- if (v.y < minY)
10865
- minY = v.y;
10866
- if (v.y > maxY)
10867
- maxY = v.y;
10868
- }
10869
- return { minX, maxX, minY, maxY };
10922
+ function enclosureWasm() {
10923
+ const wasm3 = getKernelWasmSync();
10924
+ if (!wasm3?.enclosure_fit) {
10925
+ throw new Error("enclosure checks require the kernel WASM to be initialized \u2014 await getKernelWasm() (or Engine.init()) first");
10926
+ }
10927
+ return wasm3;
10870
10928
  }
10871
- function toWorld(p, placement) {
10872
- const t2 = placement.rotationDeg * Math.PI / 180;
10873
- const cos = Math.cos(t2);
10874
- const sin = Math.sin(t2);
10875
- return {
10876
- x: placement.offset.x + p.x * cos - p.y * sin,
10877
- y: placement.offset.y + p.x * sin + p.y * cos,
10878
- z: placement.offset.z + (p.z ?? 0)
10879
- };
10929
+ function compact(obj) {
10930
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
10880
10931
  }
10881
- function nearestEdge(x, y, b) {
10882
- const d = [
10883
- ["minX", Math.abs(x - b.minX)],
10884
- ["maxX", Math.abs(x - b.maxX)],
10885
- ["minY", Math.abs(y - b.minY)],
10886
- ["maxY", Math.abs(y - b.maxY)]
10887
- ];
10888
- d.sort((a, c) => a[1] - c[1]);
10889
- return d[0][0];
10932
+ function outlineAabb(outline) {
10933
+ return JSON.parse(enclosureWasm().enclosure_outline_aabb(JSON.stringify(outline)));
10890
10934
  }
10891
- function padWorld(fp, pad) {
10892
- const t2 = (fp.rotation ?? 0) * Math.PI / 180;
10893
- const cos = Math.cos(t2);
10894
- const sin = Math.sin(t2);
10895
- return {
10896
- x: fp.position.x + pad.position.x * cos - pad.position.y * sin,
10897
- y: fp.position.y + pad.position.x * sin + pad.position.y * cos
10898
- };
10935
+ function toWorld(p, placement) {
10936
+ return JSON.parse(enclosureWasm().enclosure_to_world(p.x, p.y, p.z ?? 0, JSON.stringify(placement)));
10899
10937
  }
10900
10938
  function mountingHolesFromPcb(pcb) {
10901
- const holes = [];
10902
- for (const fp of pcb.footprints) {
10903
- const isMount = MOUNT_RE.test(fp.footprintName) || MOUNT_RE.test(fp.ref);
10904
- if (isMount) {
10905
- const pad = fp.pads[0];
10906
- let dia = 3.2;
10907
- if (pad) {
10908
- if (pad.drill && typeof pad.drill === "object") {
10909
- const dd = pad.drill;
10910
- if (typeof dd.diameter === "number")
10911
- dia = dd.diameter;
10912
- } else if (pad.shape.type === "Circle") {
10913
- dia = pad.shape.diameter;
10914
- }
10915
- }
10916
- holes.push({ x: round2(fp.position.x), y: round2(fp.position.y), diameter: round2(dia), ref: fp.ref });
10917
- continue;
10918
- }
10919
- for (const pad of fp.pads) {
10920
- if (pad.padType !== "NPTH")
10921
- continue;
10922
- const w = padWorld(fp, pad);
10923
- let dia = 3.2;
10924
- const dd = pad.drill;
10925
- if (dd && typeof dd.diameter === "number")
10926
- dia = dd.diameter;
10927
- else if (pad.shape.type === "Circle")
10928
- dia = pad.shape.diameter;
10929
- holes.push({ x: round2(w.x), y: round2(w.y), diameter: round2(dia), ref: fp.ref });
10930
- }
10931
- }
10932
- return holes;
10939
+ return JSON.parse(enclosureWasm().enclosure_mounting_holes(JSON.stringify(pcb)));
10933
10940
  }
10934
10941
  function componentExtentsFromMeshes(meshes, pcb) {
10935
- const frontByRef = new Map(pcb.footprints.map((fp) => [fp.ref, fp.front ?? true]));
10936
- const out = [];
10937
- for (const m of meshes) {
10938
- let minZ = Infinity;
10939
- let maxZ = -Infinity;
10940
- for (let i = 2; i < m.positions.length; i += 3) {
10941
- const z = m.positions[i];
10942
- if (z < minZ)
10943
- minZ = z;
10944
- if (z > maxZ)
10945
- maxZ = z;
10946
- }
10947
- if (!Number.isFinite(minZ))
10948
- continue;
10949
- out.push({
10950
- ref: m.footprint_ref,
10951
- front: frontByRef.get(m.footprint_ref) ?? true,
10952
- topZ: round2(maxZ),
10953
- bottomZ: round2(minZ)
10954
- });
10955
- }
10956
- return out;
10942
+ const payload = meshes.map((m) => ({
10943
+ footprint_ref: m.footprint_ref,
10944
+ positions: Array.from(m.positions)
10945
+ }));
10946
+ return JSON.parse(enclosureWasm().enclosure_component_extents(JSON.stringify(payload), JSON.stringify(pcb)));
10957
10947
  }
10958
10948
  function connectorsFromPcb(pcb, outline) {
10959
- const aabb = outlineAabb(outline);
10960
- const out = [];
10961
- for (const fp of pcb.footprints) {
10962
- const isConn = CONNECTOR_REF_RE.test(fp.ref) || CONNECTOR_NAME_RE.test(fp.footprintName) || CONNECTOR_NAME_RE.test(fp.value ?? "");
10963
- if (!isConn)
10964
- continue;
10965
- out.push({
10966
- ref: fp.ref,
10967
- x: round2(fp.position.x),
10968
- y: round2(fp.position.y),
10969
- edge: nearestEdge(fp.position.x, fp.position.y, aabb),
10970
- height: 0
10971
- });
10972
- }
10973
- return out;
10974
- }
10975
- function autoPlacement(input, clearance) {
10976
- const { cavity } = input;
10977
- const a = outlineAabb(input.outline);
10978
- const boardW = a.maxX - a.minX;
10979
- const boardH = a.maxY - a.minY;
10980
- const cavW = cavity.maxX - cavity.minX;
10981
- const cavH = cavity.maxY - cavity.minY;
10982
- const offX = cavity.minX + (cavW - boardW) / 2 - a.minX;
10983
- const offY = cavity.minY + (cavH - boardH) / 2 - a.minY;
10984
- const standoffTop = input.standoffs && input.standoffs.length > 0 ? Math.max(...input.standoffs.map((s) => s.topZ)) : cavity.floorZ + (input.standoffHeight ?? 0);
10985
- void clearance;
10986
- return { offset: { x: round2(offX), y: round2(offY), z: round2(standoffTop) }, rotationDeg: 0 };
10987
- }
10988
- function checkBoardFit(input, placement, clearance) {
10989
- const { cavity } = input;
10990
- let minX = Infinity;
10991
- let maxX = -Infinity;
10992
- let minY = Infinity;
10993
- let maxY = -Infinity;
10994
- for (const v of input.outline.vertices) {
10995
- const w = toWorld(v, placement);
10996
- if (w.x < minX)
10997
- minX = w.x;
10998
- if (w.x > maxX)
10999
- maxX = w.x;
11000
- if (w.y < minY)
11001
- minY = w.y;
11002
- if (w.y > maxY)
11003
- maxY = w.y;
11004
- }
11005
- const marginMinX = minX - cavity.minX;
11006
- const marginMaxX = cavity.maxX - maxX;
11007
- const marginMinY = minY - cavity.minY;
11008
- const marginMaxY = cavity.maxY - maxY;
11009
- const worst2 = Math.min(marginMinX, marginMaxX, marginMinY, marginMaxY);
11010
- const sides = [
11011
- ["-X", marginMinX],
11012
- ["+X", marginMaxX],
11013
- ["-Y", marginMinY],
11014
- ["+Y", marginMaxY]
11015
- ];
11016
- const tight = sides.filter(([, m]) => m < clearance).map(([s]) => s);
11017
- const ok4 = worst2 >= clearance - 1e-6;
11018
- return {
11019
- id: "board_fit",
11020
- label: "Board fits cavity with clearance",
11021
- status: ok4 ? "pass" : "fail",
11022
- detail: ok4 ? `Board fits with ${round2(worst2)}mm worst-case clearance (need ${clearance}mm)` : worst2 < 0 ? `Board overhangs the cavity by ${round2(-worst2)}mm on ${tight.join(", ")}` : `Clearance on ${tight.join(", ")} is ${round2(worst2)}mm < required ${clearance}mm`,
11023
- measurements: {
11024
- worst_clearance_mm: round2(worst2),
11025
- margin_minus_x: round2(marginMinX),
11026
- margin_plus_x: round2(marginMaxX),
11027
- margin_minus_y: round2(marginMinY),
11028
- margin_plus_y: round2(marginMaxY),
11029
- board_w: round2(maxX - minX),
11030
- board_h: round2(maxY - minY),
11031
- cavity_w: round2(cavity.maxX - cavity.minX),
11032
- cavity_h: round2(cavity.maxY - cavity.minY)
11033
- }
11034
- };
11035
- }
11036
- function checkLidClearance(input, placement, clearance) {
11037
- const { cavity } = input;
11038
- const extents = input.componentExtents ?? [];
11039
- if (extents.length === 0) {
11040
- return {
11041
- id: "lid_clearance",
11042
- label: "Components clear the lid",
11043
- status: "skip",
11044
- detail: "No component heights available (kernel component meshes unavailable)"
11045
- };
11046
- }
11047
- const cavityDepth = cavity.ceilZ - cavity.floorZ;
11048
- const front = extents.filter((e) => e.front);
11049
- const back = extents.filter((e) => !e.front);
11050
- let tallest = { ref: "", top: -Infinity };
11051
- for (const e of front) {
11052
- const top = placement.offset.z + e.topZ;
11053
- if (top > tallest.top)
11054
- tallest = { ref: e.ref, top };
11055
- }
11056
- const lidGap = cavity.ceilZ - tallest.top;
11057
- const topOk = front.length === 0 || lidGap >= clearance - 1e-6;
11058
- let lowest = { ref: "", bottom: Infinity };
11059
- for (const e of back) {
11060
- const bot = placement.offset.z + e.bottomZ;
11061
- if (bot < lowest.bottom)
11062
- lowest = { ref: e.ref, bottom: bot };
11063
- }
11064
- const floorGap = back.length > 0 ? lowest.bottom - cavity.floorZ : Infinity;
11065
- const botOk = back.length === 0 || floorGap >= -1e-6;
11066
- const ok4 = topOk && botOk;
11067
- let detail;
11068
- if (ok4) {
11069
- detail = `Tallest part ${tallest.ref || "\u2014"} leaves ${round2(lidGap)}mm under the lid (cavity depth ${round2(cavityDepth)}mm)`;
11070
- if (back.length > 0 && Number.isFinite(floorGap)) {
11071
- detail += `; back-side ${lowest.ref} clears floor by ${round2(floorGap)}mm`;
11072
- }
11073
- } else if (!topOk) {
11074
- detail = `${tallest.ref} is ${round2(-lidGap + clearance)}mm too tall \u2014 it ${lidGap < 0 ? "punches through" : "is within clearance of"} the lid (cavity depth ${round2(cavityDepth)}mm)`;
11075
- } else {
11076
- detail = `Back-side ${lowest.ref} collides with the floor by ${round2(-floorGap)}mm \u2014 raise the standoffs`;
11077
- }
11078
- return {
11079
- id: "lid_clearance",
11080
- label: "Components clear the lid",
11081
- status: ok4 ? "pass" : "fail",
11082
- detail,
11083
- measurements: {
11084
- cavity_depth_mm: round2(cavityDepth),
11085
- tallest_ref: tallest.ref || "none",
11086
- lid_gap_mm: Number.isFinite(lidGap) ? round2(lidGap) : "n/a",
11087
- stack_top_z: Number.isFinite(tallest.top) ? round2(tallest.top) : "n/a",
11088
- floor_gap_mm: Number.isFinite(floorGap) ? round2(floorGap) : "n/a"
11089
- }
11090
- };
11091
- }
11092
- function checkMountingHoles(input, placement) {
11093
- const holes = input.mountingHoles ?? [];
11094
- const standoffs = input.standoffs ?? [];
11095
- const tol = input.holeTolerance ?? DEFAULT_HOLE_TOL;
11096
- if (holes.length === 0) {
11097
- return {
11098
- id: "mounting_holes",
11099
- label: "Mounting holes land on standoffs",
11100
- status: "skip",
11101
- detail: "Board declares no mounting holes"
11102
- };
11103
- }
11104
- if (standoffs.length === 0) {
11105
- return {
11106
- id: "mounting_holes",
11107
- label: "Mounting holes land on standoffs",
11108
- status: "skip",
11109
- detail: `Board has ${holes.length} mounting hole(s) but no standoffs were detected in the enclosure`
11110
- };
11111
- }
11112
- let matched = 0;
11113
- let worst2 = 0;
11114
- const misses = [];
11115
- for (const h of holes) {
11116
- const w = toWorld(h, placement);
11117
- let best = Infinity;
11118
- for (const s of standoffs) {
11119
- const d = Math.hypot(w.x - s.x, w.y - s.y);
11120
- if (d < best)
11121
- best = d;
11122
- }
11123
- if (best <= tol) {
11124
- matched++;
11125
- if (best > worst2)
11126
- worst2 = best;
11127
- } else {
11128
- misses.push(`${h.ref ?? "hole"}@(${round2(w.x)},${round2(w.y)}) is ${round2(best)}mm off`);
11129
- }
11130
- }
11131
- const ok4 = matched === holes.length;
11132
- return {
11133
- id: "mounting_holes",
11134
- label: "Mounting holes land on standoffs",
11135
- status: ok4 ? "pass" : "fail",
11136
- detail: ok4 ? `All ${holes.length} mounting holes align to standoffs (worst offset ${round2(worst2)}mm, tol ${tol}mm)` : `${matched}/${holes.length} holes align \u2014 ${misses.join("; ")}`,
11137
- measurements: {
11138
- holes_total: holes.length,
11139
- holes_matched: matched,
11140
- standoffs: standoffs.length,
11141
- tolerance_mm: tol,
11142
- worst_offset_mm: round2(worst2)
11143
- }
11144
- };
11145
- }
11146
- function checkConnectors(input, placement, clearance) {
11147
- const conns = input.connectors ?? [];
11148
- const openings = input.openings ?? [];
11149
- if (conns.length === 0) {
11150
- return {
11151
- id: "connector_cutouts",
11152
- label: "Connectors align to wall cutouts",
11153
- status: "skip",
11154
- detail: "Board declares no edge connectors"
11155
- };
11156
- }
11157
- const cav = input.cavity;
11158
- let aligned = 0;
11159
- const problems = [];
11160
- for (const c of conns) {
11161
- const w = toWorld(c, placement);
11162
- const wallEdge = nearestEdge(w.x, w.y, cav);
11163
- const along = wallEdge === "minX" || wallEdge === "maxX" ? w.y : w.x;
11164
- const onWall = openings.filter((o) => o.edge === wallEdge);
11165
- if (onWall.length === 0) {
11166
- problems.push(`${c.ref} faces the ${wallEdge} wall but it has no cutout`);
11167
- continue;
11168
- }
11169
- const hit = onWall.find((o) => {
11170
- const oc = o.edge === "minX" || o.edge === "maxX" ? o.center.y : o.center.x;
11171
- return Math.abs(along - oc) <= o.width / 2 + clearance;
11172
- });
11173
- if (hit) {
11174
- aligned++;
11175
- } else {
11176
- const nearest = onWall.reduce((best, o) => {
11177
- const oc = o.edge === "minX" || o.edge === "maxX" ? o.center.y : o.center.x;
11178
- const off = Math.abs(along - oc) - o.width / 2;
11179
- return off < best ? off : best;
11180
- }, Infinity);
11181
- problems.push(`${c.ref} on ${wallEdge} misses its cutout by ${round2(nearest)}mm`);
11182
- }
11183
- }
11184
- const ok4 = aligned === conns.length;
11185
- const status = ok4 ? "pass" : openings.length === 0 ? "warn" : "fail";
11186
- return {
11187
- id: "connector_cutouts",
11188
- label: "Connectors align to wall cutouts",
11189
- status,
11190
- detail: ok4 ? `All ${conns.length} connector(s) line up with wall cutouts` : openings.length === 0 ? `No wall cutouts detected; ${conns.length} connector(s) would be enclosed: ${problems.join("; ")}` : `${aligned}/${conns.length} connectors aligned \u2014 ${problems.join("; ")}`,
11191
- measurements: {
11192
- connectors_total: conns.length,
11193
- connectors_aligned: aligned,
11194
- wall_openings: openings.length
11195
- }
11196
- };
10949
+ return JSON.parse(enclosureWasm().enclosure_connectors(JSON.stringify(pcb), JSON.stringify(outline)));
11197
10950
  }
11198
10951
  function checkEnclosureFit(input) {
11199
- const clearance = input.clearance ?? DEFAULT_CLEARANCE;
11200
- const placement = input.placement ?? autoPlacement(input, clearance);
11201
- const checks = [
11202
- checkBoardFit(input, placement, clearance),
11203
- checkLidClearance(input, placement, clearance),
11204
- checkMountingHoles(input, placement),
11205
- checkConnectors(input, placement, clearance)
11206
- ];
11207
- const failed = checks.filter((c) => c.status === "fail");
11208
- const warned = checks.filter((c) => c.status === "warn");
11209
- const passed = checks.filter((c) => c.status === "pass");
11210
- const ok4 = failed.length === 0;
11211
- const verified = ok4 && warned.length === 0;
11212
- let summary;
11213
- if (failed.length > 0) {
11214
- summary = `Enclosure fit: FAIL \u2014 ${failed.map((c) => c.label.toLowerCase()).join("; ")}`;
11215
- } else if (warned.length > 0) {
11216
- summary = `Enclosure fit: UNVERIFIED \u2014 ${passed.length} passed, ${warned.length} warning(s): ${warned.map((c) => c.detail).join("; ")}`;
11217
- } else {
11218
- summary = `Enclosure fit: PASS \u2014 ${passed.length}/${checks.length} checks (${checks.filter((c) => c.status === "pass").map((c) => c.label.toLowerCase()).join(", ")})`;
11219
- }
11220
- return { ok: ok4, verified, summary, clearance, placement, checks };
10952
+ return JSON.parse(enclosureWasm().enclosure_fit(JSON.stringify(compact(input))));
11221
10953
  }
11222
10954
  function deriveBoardFromCavity(cavity, standoffs, opts = {}) {
11223
- const clearance = opts.clearance ?? DEFAULT_CLEARANCE;
11224
- const thickness = opts.thickness ?? 1.6;
11225
- const holeDia = opts.holeDiameter ?? 3.2;
11226
- const w = round2(cavity.maxX - cavity.minX - 2 * clearance);
11227
- const h = round2(cavity.maxY - cavity.minY - 2 * clearance);
11228
- const outline = {
11229
- vertices: [
11230
- { x: 0, y: 0 },
11231
- { x: w, y: 0 },
11232
- { x: w, y: h },
11233
- { x: 0, y: h }
11234
- ],
11235
- thickness
11236
- };
11237
- const standoffTop = standoffs.length > 0 ? Math.max(...standoffs.map((s) => s.topZ)) : cavity.floorZ + (opts.standoffHeight ?? 0);
11238
- const offX = cavity.minX + clearance;
11239
- const offY = cavity.minY + clearance;
11240
- const placement = {
11241
- offset: { x: round2(offX), y: round2(offY), z: round2(standoffTop) },
11242
- rotationDeg: 0
11243
- };
11244
- const mountingHoles = [];
11245
- for (const s of standoffs) {
11246
- const lx = round2(s.x - offX);
11247
- const ly = round2(s.y - offY);
11248
- if (lx >= 0 && lx <= w && ly >= 0 && ly <= h) {
11249
- mountingHoles.push({ x: lx, y: ly, diameter: holeDia });
11250
- }
11251
- }
11252
- return { outline, mountingHoles, placement };
10955
+ return JSON.parse(enclosureWasm().enclosure_derive_board(JSON.stringify(cavity), JSON.stringify(standoffs), JSON.stringify(compact(opts))));
11253
10956
  }
11254
- var DEFAULT_CLEARANCE, DEFAULT_HOLE_TOL, round2, MOUNT_RE, CONNECTOR_REF_RE, CONNECTOR_NAME_RE;
11255
10957
  var init_enclosure_fit = __esm({
11256
10958
  "../engine/dist/enclosure-fit.js"() {
11257
10959
  "use strict";
11258
- DEFAULT_CLEARANCE = 0.5;
11259
- DEFAULT_HOLE_TOL = 0.6;
11260
- round2 = (n) => Math.round(n * 100) / 100;
11261
- MOUNT_RE = /mount(ing)?[_-]?hole|mountingpad|mounthole/i;
11262
- CONNECTOR_REF_RE = /^(J|CN|CON|USB|P)\d/i;
11263
- CONNECTOR_NAME_RE = /usb|type[-_]?c|micro|mini|conn|header|jst|molex|rj45|hdr|terminal|socket|receptacle|barrel|dcjack/i;
10960
+ init_wasm_singleton();
11264
10961
  }
11265
10962
  });
11266
10963
 
11267
10964
  // ../engine/dist/enclosure-mesh.js
11268
- function gwn(positions, indices, qx, qy, qz) {
11269
- let w = 0;
11270
- const tris = indices.length;
11271
- for (let t2 = 0; t2 < tris; t2 += 3) {
11272
- const i0 = indices[t2] * 3;
11273
- const i1 = indices[t2 + 1] * 3;
11274
- const i2 = indices[t2 + 2] * 3;
11275
- const ax = positions[i0] - qx;
11276
- const ay = positions[i0 + 1] - qy;
11277
- const az = positions[i0 + 2] - qz;
11278
- const bx = positions[i1] - qx;
11279
- const by = positions[i1 + 1] - qy;
11280
- const bz = positions[i1 + 2] - qz;
11281
- const cx = positions[i2] - qx;
11282
- const cy = positions[i2 + 1] - qy;
11283
- const cz = positions[i2 + 2] - qz;
11284
- const la = Math.sqrt(ax * ax + ay * ay + az * az);
11285
- const lb = Math.sqrt(bx * bx + by * by + bz * bz);
11286
- const lc = Math.sqrt(cx * cx + cy * cy + cz * cz);
11287
- const cbx = by * cz - bz * cy;
11288
- const cby = bz * cx - bx * cz;
11289
- const cbz = bx * cy - by * cx;
11290
- const num2 = ax * cbx + ay * cby + az * cbz;
11291
- const den = la * lb * lc + (ax * bx + ay * by + az * bz) * lc + (bx * cx + by * cy + bz * cz) * la + (cx * ax + cy * ay + cz * az) * lb;
11292
- w += Math.atan2(num2, den);
11293
- }
11294
- return w / (2 * Math.PI);
11295
- }
11296
- function buildOccupancy(positions, indices) {
11297
- let minX = Infinity;
11298
- let maxX = -Infinity;
11299
- let minY = Infinity;
11300
- let maxY = -Infinity;
11301
- let minZ = Infinity;
11302
- let maxZ = -Infinity;
11303
- for (let i = 0; i < positions.length; i += 3) {
11304
- const x = positions[i];
11305
- const y = positions[i + 1];
11306
- const z = positions[i + 2];
11307
- if (x < minX)
11308
- minX = x;
11309
- if (x > maxX)
11310
- maxX = x;
11311
- if (y < minY)
11312
- minY = y;
11313
- if (y > maxY)
11314
- maxY = y;
11315
- if (z < minZ)
11316
- minZ = z;
11317
- if (z > maxZ)
11318
- maxZ = z;
11319
- }
11320
- if (!(maxX > minX) || !(maxY > minY) || !(maxZ > minZ))
11321
- return null;
11322
- const gw = GRID_XY;
11323
- const gh = GRID_XY;
11324
- const gz = GRID_Z;
11325
- const dx = (maxX - minX) / gw;
11326
- const dy = (maxY - minY) / gh;
11327
- const dz = (maxZ - minZ) / gz;
11328
- const occ = new Uint8Array(gw * gh * gz);
11329
- for (let k = 0; k < gz; k++) {
11330
- const qz = minZ + (k + JZ) * dz;
11331
- for (let j = 0; j < gh; j++) {
11332
- const qy = minY + (j + JY) * dy;
11333
- for (let i = 0; i < gw; i++) {
11334
- const qx = minX + (i + JX) * dx;
11335
- if (Math.abs(gwn(positions, indices, qx, qy, qz)) > INSIDE) {
11336
- occ[i + gw * (j + gh * k)] = 1;
11337
- }
11338
- }
11339
- }
11340
- }
11341
- return { gw, gh, gz, ox: minX, oy: minY, oz: minZ, dx, dy, dz, minZ, maxZ, occ };
11342
- }
11343
- function bottomRunTopZ(o, i, j) {
11344
- const base = i + o.gw * j;
11345
- if (o.occ[base] !== 1)
11346
- return null;
11347
- let k = 1;
11348
- while (k < o.gz && o.occ[base + o.gw * o.gh * k] === 1)
11349
- k++;
11350
- return o.oz + k * o.dz;
11351
- }
11352
- function openAtTop(o, i, j) {
11353
- return o.occ[i + o.gw * (j + o.gh * (o.gz - 1))] !== 1;
11354
- }
11355
- function hasGap(o, i, j, kLo, kHi) {
11356
- for (let k = kLo; k <= kHi; k++) {
11357
- if (o.occ[i + o.gw * (j + o.gh * k)] !== 1)
11358
- return true;
11359
- }
11360
- return false;
11361
- }
11362
10965
  function extractEnclosureFeatures(positions, indices) {
11363
- const o = buildOccupancy(positions, indices);
11364
- if (!o) {
11365
- return {
11366
- outer: { minX: 0, maxX: 0, minY: 0, maxY: 0, minZ: 0, maxZ: 0 },
11367
- cavity: null,
11368
- standoffs: [],
11369
- openings: []
11370
- };
11371
- }
11372
- const outer = {
11373
- minX: o.ox,
11374
- maxX: o.ox + o.gw * o.dx,
11375
- minY: o.oy,
11376
- maxY: o.oy + o.gh * o.dy,
11377
- minZ: o.minZ,
11378
- maxZ: o.maxZ
11379
- };
11380
- const isPocket = new Uint8Array(o.gw * o.gh);
11381
- const firstTops = [];
11382
- const countX = new Int32Array(o.gw);
11383
- const countY = new Int32Array(o.gh);
11384
- let pocketCount = 0;
11385
- for (let j = 0; j < o.gh; j++) {
11386
- for (let i = 0; i < o.gw; i++) {
11387
- const top = bottomRunTopZ(o, i, j);
11388
- if (top != null && openAtTop(o, i, j)) {
11389
- isPocket[i + o.gw * j] = 1;
11390
- pocketCount++;
11391
- firstTops.push(top);
11392
- countX[i]++;
11393
- countY[j]++;
11394
- }
11395
- }
11396
- }
11397
- if (pocketCount === 0) {
11398
- return { outer, cavity: null, standoffs: [], openings: [] };
11399
- }
11400
- const core = (counts) => {
11401
- let peak = 0;
11402
- for (const c of counts)
11403
- if (c > peak)
11404
- peak = c;
11405
- const thr = peak * 0.5;
11406
- let lo = 0;
11407
- let hi = counts.length - 1;
11408
- while (lo < counts.length && counts[lo] < thr)
11409
- lo++;
11410
- while (hi >= 0 && counts[hi] < thr)
11411
- hi--;
11412
- return [lo, hi];
11413
- };
11414
- const [pminI, pmaxI] = core(countX);
11415
- const [pminJ, pmaxJ] = core(countY);
11416
- const sortedTops = [...firstTops].sort((a, b) => a - b);
11417
- const floorZ = sortedTops[Math.floor(sortedTops.length / 2)] ?? o.minZ;
11418
- const ceilZ = o.maxZ;
11419
- const cavity = {
11420
- minX: o.ox + pminI * o.dx,
11421
- maxX: o.ox + (pmaxI + 1) * o.dx,
11422
- minY: o.oy + pminJ * o.dy,
11423
- maxY: o.oy + (pmaxJ + 1) * o.dy,
11424
- floorZ: round22(floorZ),
11425
- ceilZ: round22(ceilZ),
11426
- hasLid: false
11427
- };
11428
- const standoffs = extractStandoffs(o, isPocket, floorZ);
11429
- const openings = extractOpenings(o, cavity, floorZ, ceilZ);
11430
- return { outer, cavity, standoffs, openings };
11431
- }
11432
- function extractStandoffs(o, isPocket, floorZ) {
11433
- const isPost = new Uint8Array(o.gw * o.gh);
11434
- for (let j = 0; j < o.gh; j++) {
11435
- for (let i = 0; i < o.gw; i++) {
11436
- if (!isPocket[i + o.gw * j])
11437
- continue;
11438
- const top = bottomRunTopZ(o, i, j);
11439
- if (top != null && top > floorZ + MIN_POST_HEIGHT)
11440
- isPost[i + o.gw * j] = 1;
11441
- }
11442
- }
11443
- const seen = new Uint8Array(o.gw * o.gh);
11444
- const standoffs = [];
11445
- for (let j = 0; j < o.gh; j++) {
11446
- for (let i = 0; i < o.gw; i++) {
11447
- const k0 = i + o.gw * j;
11448
- if (!isPost[k0] || seen[k0])
11449
- continue;
11450
- const stack = [[i, j]];
11451
- seen[k0] = 1;
11452
- let sumX = 0;
11453
- let sumY = 0;
11454
- let n = 0;
11455
- let topMax = -Infinity;
11456
- let minI = i;
11457
- let maxI = i;
11458
- let minJ = j;
11459
- let maxJ = j;
11460
- while (stack.length) {
11461
- const [ci, cj] = stack.pop();
11462
- sumX += o.ox + (ci + 0.5) * o.dx;
11463
- sumY += o.oy + (cj + 0.5) * o.dy;
11464
- n++;
11465
- const t2 = bottomRunTopZ(o, ci, cj);
11466
- if (t2 != null && t2 > topMax)
11467
- topMax = t2;
11468
- if (ci < minI)
11469
- minI = ci;
11470
- if (ci > maxI)
11471
- maxI = ci;
11472
- if (cj < minJ)
11473
- minJ = cj;
11474
- if (cj > maxJ)
11475
- maxJ = cj;
11476
- for (const [ni, nj] of [
11477
- [ci - 1, cj],
11478
- [ci + 1, cj],
11479
- [ci, cj - 1],
11480
- [ci, cj + 1]
11481
- ]) {
11482
- if (ni < 0 || nj < 0 || ni >= o.gw || nj >= o.gh)
11483
- continue;
11484
- const nk = ni + o.gw * nj;
11485
- if (isPost[nk] && !seen[nk]) {
11486
- seen[nk] = 1;
11487
- stack.push([ni, nj]);
11488
- }
11489
- }
11490
- }
11491
- if (n < 2)
11492
- continue;
11493
- const radius = Math.max(((maxI - minI + 1) * o.dx + (maxJ - minJ + 1) * o.dy) / 4, o.dx);
11494
- standoffs.push({
11495
- x: round22(sumX / n),
11496
- y: round22(sumY / n),
11497
- topZ: round22(topMax),
11498
- radius: round22(radius)
11499
- });
11500
- }
11501
- }
11502
- return standoffs;
11503
- }
11504
- function extractOpenings(o, cavity, floorZ, ceilZ) {
11505
- const pIminI = Math.round((cavity.minX - o.ox) / o.dx);
11506
- const pImaxI = Math.round((cavity.maxX - o.ox) / o.dx) - 1;
11507
- const pIminJ = Math.round((cavity.minY - o.oy) / o.dy);
11508
- const pImaxJ = Math.round((cavity.maxY - o.oy) / o.dy) - 1;
11509
- const kLo = Math.max(0, Math.floor((floorZ - o.oz) / o.dz) + 1);
11510
- const kHi = Math.min(o.gz - 1, Math.ceil((ceilZ - o.oz) / o.dz) - 1);
11511
- const openings = [];
11512
- const wallOpen = (i, j) => {
11513
- if (i < 0 || j < 0 || i >= o.gw || j >= o.gh)
11514
- return true;
11515
- return hasGap(o, i, j, kLo, kHi);
11516
- };
11517
- const scans = [];
11518
- for (const [edge, wi] of [
11519
- ["minX", pIminI - 1],
11520
- ["maxX", pImaxI + 1]
11521
- ]) {
11522
- const cells = [];
11523
- for (let j = pIminJ; j <= pImaxJ; j++) {
11524
- cells.push({ open: wallOpen(wi, j), x: o.ox + (wi + 0.5) * o.dx, y: o.oy + (j + 0.5) * o.dy, i: wi, j });
11525
- }
11526
- scans.push({ edge, cells });
11527
- }
11528
- for (const [edge, wj] of [
11529
- ["minY", pIminJ - 1],
11530
- ["maxY", pImaxJ + 1]
11531
- ]) {
11532
- const cells = [];
11533
- for (let i = pIminI; i <= pImaxI; i++) {
11534
- cells.push({ open: wallOpen(i, wj), x: o.ox + (i + 0.5) * o.dx, y: o.oy + (wj + 0.5) * o.dy, i, j: wj });
11535
- }
11536
- scans.push({ edge, cells });
11537
- }
11538
- for (const scan of scans) {
11539
- let run = [];
11540
- const flush = () => {
11541
- if (run.length >= MIN_OPENING_CELLS) {
11542
- const xs = run.map((p) => p.x);
11543
- const ys = run.map((p) => p.y);
11544
- const horiz = scan.edge === "minY" || scan.edge === "maxY";
11545
- const center = {
11546
- x: (Math.min(...xs) + Math.max(...xs)) / 2,
11547
- y: (Math.min(...ys) + Math.max(...ys)) / 2
11548
- };
11549
- const width = horiz ? Math.max(...xs) - Math.min(...xs) + o.dx : Math.max(...ys) - Math.min(...ys) + o.dy;
11550
- const mid = run[Math.floor(run.length / 2)];
11551
- const { zMin, zMax } = openingZSpan(o, mid.i, mid.j, kLo, kHi);
11552
- openings.push({
11553
- edge: scan.edge,
11554
- center: { x: round22(center.x), y: round22(center.y) },
11555
- width: round22(width),
11556
- zMin: round22(zMin),
11557
- zMax: round22(zMax)
11558
- });
11559
- }
11560
- run = [];
11561
- };
11562
- for (const c of scan.cells) {
11563
- if (c.open)
11564
- run.push({ x: c.x, y: c.y, i: c.i, j: c.j });
11565
- else
11566
- flush();
11567
- }
11568
- flush();
11569
- }
11570
- return openings;
11571
- }
11572
- function openingZSpan(o, i, j, kLo, kHi) {
11573
- let zMin = Infinity;
11574
- let zMax = -Infinity;
11575
- for (let k = kLo; k <= kHi; k++) {
11576
- if (o.occ[i + o.gw * (j + o.gh * k)] !== 1) {
11577
- const z = o.oz + (k + 0.5) * o.dz;
11578
- if (z < zMin)
11579
- zMin = z;
11580
- if (z > zMax)
11581
- zMax = z;
11582
- }
11583
- }
11584
- if (!Number.isFinite(zMin))
11585
- return { zMin: o.oz, zMax: o.maxZ };
11586
- return { zMin, zMax };
10966
+ const json = enclosureWasm().enclosure_features(Float64Array.from(positions), Uint32Array.from(indices));
10967
+ return JSON.parse(json);
11587
10968
  }
11588
- var GRID_XY, GRID_Z, INSIDE, MIN_POST_HEIGHT, MIN_OPENING_CELLS, JX, JY, JZ, round22;
11589
10969
  var init_enclosure_mesh = __esm({
11590
10970
  "../engine/dist/enclosure-mesh.js"() {
11591
10971
  "use strict";
11592
- GRID_XY = 48;
11593
- GRID_Z = 28;
11594
- INSIDE = 0.5;
11595
- MIN_POST_HEIGHT = 0.8;
11596
- MIN_OPENING_CELLS = 2;
11597
- JX = 0.5 + 0.137;
11598
- JY = 0.5 - 0.077;
11599
- JZ = 0.5 + 0.041;
11600
- round22 = (n) => Math.round(n * 100) / 100;
10972
+ init_enclosure_fit();
11601
10973
  }
11602
10974
  });
11603
10975
 
@@ -11640,6 +11012,7 @@ __export(dist_exports, {
11640
11012
  decimateMeshGpu: () => decimateMeshGpu2,
11641
11013
  defaultParamsFor: () => defaultParamsFor,
11642
11014
  deriveBoardFromCavity: () => deriveBoardFromCavity,
11015
+ embroideryPatternToMeshWithKernel: () => embroideryPatternToMeshWithKernel,
11643
11016
  estimateCost: () => estimateCost,
11644
11017
  evalAst: () => evalAst,
11645
11018
  evalExprSafe: () => evalExprSafe,
@@ -11717,6 +11090,7 @@ __export(dist_exports, {
11717
11090
  threeWayMerge: () => threeWayMerge,
11718
11091
  toWorld: () => toWorld,
11719
11092
  transformMesh: () => transformMesh,
11093
+ transformMeshWithKernel: () => transformMeshWithKernel,
11720
11094
  tryExportFabFiles: () => tryExportFabFiles,
11721
11095
  tryPcbPreviewMeshes: () => tryPcbPreviewMeshes,
11722
11096
  tryRunDrc: () => tryRunDrc,
@@ -11742,6 +11116,7 @@ var init_dist = __esm({
11742
11116
  init_mesh_cache();
11743
11117
  init_dependency_graph();
11744
11118
  init_evaluate();
11119
+ init_evaluate();
11745
11120
  init_sequence();
11746
11121
  init_sheet_metal();
11747
11122
  init_expressions();
@@ -32217,13 +31592,13 @@ var init_document_store = __esm({
32217
31592
  const ang = (fp.rotation ?? 0) * Math.PI / 180;
32218
31593
  const cos = Math.cos(ang);
32219
31594
  const sin = Math.sin(ang);
32220
- const padWorld3 = fp.pads.map((p) => ({
31595
+ const padWorld2 = fp.pads.map((p) => ({
32221
31596
  x: fp.position.x + (p.position.x * cos - p.position.y * sin),
32222
31597
  y: fp.position.y + (p.position.x * sin + p.position.y * cos)
32223
31598
  }));
32224
31599
  for (const tr of pcb.traces) {
32225
31600
  for (const end of [tr.start, tr.end]) {
32226
- if (padWorld3.some((pw) => Math.abs(end.x - pw.x) < TOL && Math.abs(end.y - pw.y) < TOL)) {
31601
+ if (padWorld2.some((pw) => Math.abs(end.x - pw.x) < TOL && Math.abs(end.y - pw.y) < TOL)) {
32227
31602
  end.x += dx;
32228
31603
  end.y += dy;
32229
31604
  }
@@ -49384,7 +48759,7 @@ function buildPartLabels(doc) {
49384
48759
  return `${entry.root}:${name ?? ""}`;
49385
48760
  });
49386
48761
  }
49387
- function eulerXyzDegToQuat(rotation) {
48762
+ function eulerXyzDegToQuat2(rotation) {
49388
48763
  const q = exportKernel().eulerXyzDegToQuat(rotation.x, rotation.y, rotation.z);
49389
48764
  return [q[0], q[1], q[2], q[3]];
49390
48765
  }
@@ -49597,7 +48972,7 @@ function generateInstancesGlbPreview(doc, engine) {
49597
48972
  inst.transform.translation.y,
49598
48973
  inst.transform.translation.z
49599
48974
  ],
49600
- rotationQuat: eulerXyzDegToQuat(inst.transform.rotation),
48975
+ rotationQuat: eulerXyzDegToQuat2(inst.transform.rotation),
49601
48976
  scale: [
49602
48977
  inst.transform.scale.x,
49603
48978
  inst.transform.scale.y,
@@ -58293,7 +57668,7 @@ function computeUtilization(footprints, vertices, cutouts, shape, innerRadius, e
58293
57668
  }
58294
57669
  const boardArea = Math.abs(loopSignedArea(vertices)) - (cutouts ?? []).reduce((s, c) => s + Math.abs(loopSignedArea(c)), 0);
58295
57670
  if (!Number.isFinite(occMinX) || boardArea <= 0) return void 0;
58296
- const round25 = (v) => Math.round(v * 100) / 100;
57671
+ const round23 = (v) => Math.round(v * 100) / 100;
58297
57672
  const ceilHalf = (v) => Math.ceil(v * 2) / 2;
58298
57673
  const bbW = occMaxX - occMinX;
58299
57674
  const bbH = occMaxY - occMinY;
@@ -58306,8 +57681,8 @@ function computeUtilization(footprints, vertices, cutouts, shape, innerRadius, e
58306
57681
  suggested = {
58307
57682
  type: "circle",
58308
57683
  outer_diameter: od,
58309
- center: { x: round25(cx), y: round25(cy) },
58310
- ...innerRadius > 0 ? { inner_diameter: round25(2 * innerRadius) } : {},
57684
+ center: { x: round23(cx), y: round23(cy) },
57685
+ ...innerRadius > 0 ? { inner_diameter: round23(2 * innerRadius) } : {},
58311
57686
  note: `Minimum enclosing circle with ${margin}mm edge clearance`
58312
57687
  };
58313
57688
  } else {
@@ -58315,19 +57690,19 @@ function computeUtilization(footprints, vertices, cutouts, shape, innerRadius, e
58315
57690
  type: "rect",
58316
57691
  width: ceilHalf(bbW + 2 * margin),
58317
57692
  height: ceilHalf(bbH + 2 * margin),
58318
- origin: { x: round25(occMinX - margin), y: round25(occMinY - margin) },
57693
+ origin: { x: round23(occMinX - margin), y: round23(occMinY - margin) },
58319
57694
  note: `Minimum enclosing rectangle with ${margin}mm edge clearance`
58320
57695
  };
58321
57696
  }
58322
57697
  return {
58323
- board_area_mm2: round25(boardArea),
58324
- component_area_mm2: round25(componentArea),
57698
+ board_area_mm2: round23(boardArea),
57699
+ component_area_mm2: round23(componentArea),
58325
57700
  utilization_pct: Math.round(componentArea / boardArea * 1e3) / 10,
58326
- bounding_box: { x: round25(occMinX), y: round25(occMinY), w: round25(bbW), h: round25(bbH) },
57701
+ bounding_box: { x: round23(occMinX), y: round23(occMinY), w: round23(bbW), h: round23(bbH) },
58327
57702
  suggested_outline: suggested
58328
57703
  };
58329
57704
  }
58330
- function padWorld2(fp, pad) {
57705
+ function padWorld(fp, pad) {
58331
57706
  const ang = (fp.rotation ?? 0) * Math.PI / 180;
58332
57707
  const cos = Math.cos(ang);
58333
57708
  const sin = Math.sin(ang);
@@ -58346,7 +57721,7 @@ function detectStaleNets(pcb) {
58346
57721
  const arr = padsByNet.get(pad.net) ?? [];
58347
57722
  const sh = pad.shape;
58348
57723
  const halfExtent = sh.diameter ? sh.diameter / 2 : Math.hypot(sh.width ?? 0, sh.height ?? 0) / 2;
58349
- arr.push({ pos: padWorld2(fp, pad), layers: pad.layers, halfExtent });
57724
+ arr.push({ pos: padWorld(fp, pad), layers: pad.layers, halfExtent });
58350
57725
  padsByNet.set(pad.net, arr);
58351
57726
  }
58352
57727
  }
@@ -59230,7 +58605,7 @@ function layoutLint(pcb) {
59230
58605
  for (const fp of fps) {
59231
58606
  const prefix = refPrefix(fp.ref);
59232
58607
  for (const pad of fp.pads) {
59233
- const w = { ref: fp.ref, prefix, pad, pos: padWorld2(fp, pad) };
58608
+ const w = { ref: fp.ref, prefix, pad, pos: padWorld(fp, pad) };
59234
58609
  allPads.push(w);
59235
58610
  if (pad.net) {
59236
58611
  const arr = byNet.get(pad.net) ?? [];
@@ -59246,7 +58621,7 @@ function layoutLint(pcb) {
59246
58621
  for (const pad of fp.pads) {
59247
58622
  const net = pad.net;
59248
58623
  if (!net || LINT_GROUND_NET_RE.test(net) || LINT_SUPPLY_NET_RE.test(net)) continue;
59249
- const from = padWorld2(fp, pad);
58624
+ const from = padWorld(fp, pad);
59250
58625
  let nearest = null;
59251
58626
  for (const other of byNet.get(net) ?? []) {
59252
58627
  if (other.ref === fp.ref || other.prefix !== "U") continue;
@@ -59261,9 +58636,9 @@ function layoutLint(pcb) {
59261
58636
  warnings.push({
59262
58637
  kind: "crystal_far_from_ic",
59263
58638
  refs: [fp.ref, worst2.icRef],
59264
- distance_mm: round23(worst2.dist),
58639
+ distance_mm: round2(worst2.dist),
59265
58640
  threshold_mm: LINT_CRYSTAL_MAX_MM,
59266
- message: `Crystal ${fp.ref} is ${round23(worst2.dist)}mm from ${worst2.icRef} pad ${worst2.icPad} (net '${worst2.net}') \u2014 oscillator loops want <${LINT_CRYSTAL_MAX_MM}mm; move ${fp.ref} next to its XIN/XOUT pins.`
58641
+ message: `Crystal ${fp.ref} is ${round2(worst2.dist)}mm from ${worst2.icRef} pad ${worst2.icPad} (net '${worst2.net}') \u2014 oscillator loops want <${LINT_CRYSTAL_MAX_MM}mm; move ${fp.ref} next to its XIN/XOUT pins.`
59267
58642
  });
59268
58643
  }
59269
58644
  }
@@ -59274,7 +58649,7 @@ function layoutLint(pcb) {
59274
58649
  const pwrIdx = nets.findIndex((n) => n && LINT_SUPPLY_NET_RE.test(n) && !LINT_GROUND_NET_RE.test(n));
59275
58650
  if (gndIdx < 0 || pwrIdx < 0) continue;
59276
58651
  const pwrPad = fp.pads[pwrIdx];
59277
- const from = padWorld2(fp, pwrPad);
58652
+ const from = padWorld(fp, pwrPad);
59278
58653
  let nearest = null;
59279
58654
  for (const other of byNet.get(pwrPad.net) ?? []) {
59280
58655
  if (other.ref === fp.ref || other.prefix !== "U") continue;
@@ -59287,9 +58662,9 @@ function layoutLint(pcb) {
59287
58662
  warnings.push({
59288
58663
  kind: "decoupling_cap_far_from_pin",
59289
58664
  refs: [fp.ref, nearest.icRef],
59290
- distance_mm: round23(nearest.dist),
58665
+ distance_mm: round2(nearest.dist),
59291
58666
  threshold_mm: LINT_DECAP_MAX_MM,
59292
- message: `Decoupling cap ${fp.ref} is ${round23(nearest.dist)}mm from ${nearest.icRef} pad ${nearest.icPad} (net '${pwrPad.net}') \u2014 decouplers want <${LINT_DECAP_MAX_MM}mm to the pin they decouple.`
58667
+ message: `Decoupling cap ${fp.ref} is ${round2(nearest.dist)}mm from ${nearest.icRef} pad ${nearest.icPad} (net '${pwrPad.net}') \u2014 decouplers want <${LINT_DECAP_MAX_MM}mm to the pin they decouple.`
59293
58668
  });
59294
58669
  }
59295
58670
  }
@@ -59307,15 +58682,15 @@ function layoutLint(pcb) {
59307
58682
  for (const fp of fps) {
59308
58683
  const prefix = refPrefix(fp.ref);
59309
58684
  if (prefix !== "J" && prefix !== "P" && prefix !== "CN" && prefix !== "USB") continue;
59310
- const pts = fp.pads.length > 0 ? fp.pads.map((p) => padWorld2(fp, p)) : [fp.position];
58685
+ const pts = fp.pads.length > 0 ? fp.pads.map((p) => padWorld(fp, p)) : [fp.position];
59311
58686
  const d = Math.min(...pts.map(edgeDist));
59312
58687
  if (d > LINT_CONNECTOR_EDGE_MAX_MM) {
59313
58688
  warnings.push({
59314
58689
  kind: "connector_not_on_edge",
59315
58690
  refs: [fp.ref],
59316
- distance_mm: round23(d),
58691
+ distance_mm: round2(d),
59317
58692
  threshold_mm: LINT_CONNECTOR_EDGE_MAX_MM,
59318
- message: `Connector ${fp.ref} sits ${round23(d)}mm from the board edge \u2014 connectors want edge access (<${LINT_CONNECTOR_EDGE_MAX_MM}mm); move it to the outline.`
58693
+ message: `Connector ${fp.ref} sits ${round2(d)}mm from the board edge \u2014 connectors want edge access (<${LINT_CONNECTOR_EDGE_MAX_MM}mm); move it to the outline.`
59319
58694
  });
59320
58695
  }
59321
58696
  }
@@ -59351,9 +58726,9 @@ function layoutLint(pcb) {
59351
58726
  warnings.push({
59352
58727
  kind: "high_current_near_sensitive",
59353
58728
  refs: [c.hi.ref, c.lo.ref],
59354
- distance_mm: round23(c.dist),
58729
+ distance_mm: round2(c.dist),
59355
58730
  threshold_mm: LINT_HIGH_CURRENT_SEP_MIN_MM,
59356
- message: `High-current net '${c.hiNet}' (${c.hi.ref} pad ${c.hi.pad.number}) is ${round23(c.dist)}mm from '${c.loNet}' (${c.lo.ref} pad ${c.lo.pad.number}) \u2014 keep \u2265${LINT_HIGH_CURRENT_SEP_MIN_MM}mm between high-current and USB/analog pads.`
58731
+ message: `High-current net '${c.hiNet}' (${c.hi.ref} pad ${c.hi.pad.number}) is ${round2(c.dist)}mm from '${c.loNet}' (${c.lo.ref} pad ${c.lo.pad.number}) \u2014 keep \u2265${LINT_HIGH_CURRENT_SEP_MIN_MM}mm between high-current and USB/analog pads.`
59357
58732
  });
59358
58733
  }
59359
58734
  }
@@ -62962,7 +62337,7 @@ async function addMotorWinding(args) {
62962
62337
  if (!pad.net) continue;
62963
62338
  const arr = padsByNet.get(pad.net) ?? [];
62964
62339
  arr.push({
62965
- pos: padWorld2(fp, pad),
62340
+ pos: padWorld(fp, pad),
62966
62341
  onReturnLayer: pad.layers.includes(returnLayer),
62967
62342
  size: padApproxRadius(pad)
62968
62343
  });
@@ -63305,7 +62680,7 @@ async function addMotorWinding(args) {
63305
62680
  for (const fp of pcb.footprints) {
63306
62681
  for (const pad of fp.pads) {
63307
62682
  if (pad.net === net) continue;
63308
- const pw = padWorld2(fp, pad);
62683
+ const pw = padWorld(fp, pad);
63309
62684
  if (pointSegDist(pw, t2.a, t2.b) < padApproxRadius(pad) + halfW + clearance) return false;
63310
62685
  }
63311
62686
  }
@@ -64998,7 +64373,7 @@ async function fixDrc(args) {
64998
64373
  let corridorFail = null;
64999
64374
  for (const pt of movePoints) {
65000
64375
  const onPad = pcb.footprints.some(
65001
- (fp) => fp.pads.some((pad) => samePoint(padWorld2(fp, pad), pt))
64376
+ (fp) => fp.pads.some((pad) => samePoint(padWorld(fp, pad), pt))
65002
64377
  );
65003
64378
  if (onPad) {
65004
64379
  corridorFail = "endpoint lands on a component pad \u2014 fixing requires moving the component";
@@ -65148,7 +64523,7 @@ async function fixDrc(args) {
65148
64523
  };
65149
64524
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
65150
64525
  }
65151
- var PCB_LAYER_SET, COPPER_LAYERS, COPPER_LAYER_SET, POSITION_TOLERANCE, PIN_SEP, pinKey, createSchematicSchema, docInputProperties, placeComponentsSchema, routeNetsSchema, runDrcSchema, runErcSchema, critiqueRouteSchema, routeDiffPairSchema, lengthMatchTracesSchema, exportGerberSchema, validateForFabSchema, exportKicadSchema, addCoilSchema, boardFromSolidSchema, addCoilArraySchema, windingLayoutSchema, calcImpedanceSchema, sizeImpedanceSchema, sizePdnSchema, calcCoilSchema, sizeCoilSchema, calcRfSchema, COIL_GEOMETRY, MU0, RAIL_PIN_NAMES, PIN_TYPES, CHIP_SIZE_CODES, CONNECTIVITY_GUARD_MAX_NETS, POWER_NET_RE, DRC_CATEGORY, DRC_DELTA_SAMPLE_CAP, DRC_DELTA_FULL_BOARD_MAX, LINT_CRYSTAL_MAX_MM, LINT_DECAP_MAX_MM, LINT_CONNECTOR_EDGE_MAX_MM, LINT_HIGH_CURRENT_SEP_MIN_MM, LINT_GROUND_NET_RE, LINT_SUPPLY_NET_RE, SENSITIVE_NET_RE, HIGH_CURRENT_CLASS_RE, refPrefix, dist2d, round23, sumSq, round33, vec2Schema, setBoardOutlineSchema, FOOTPRINT_FAMILIES, listFootprintsSchema, searchFootprintsSchema, getPadPositionsSchema, getFootprintSchema, ROTATION_CONVENTION, describePcbSchema, DESCRIBE_NET_NAME_CAP, addTraceSchema, addViaSchema, OZ_TO_MM, setStackupSchema, setPlacementSchema, addZoneSchema, deleteElementProps, deleteZoneSchema, deleteTraceSchema, deleteViaSchema, COPPER_KINDS, GET_COPPER_CAP, getCopperSchema, TIE_POSITION_TOL, addNetTieSchema, deleteNetTieSchema, undoSchema, setDesignRulesSchema, sizeTraceForCurrentSchema, addViaArraySchema, MAX_VIA_ARRAY, addMotorWindingSchema, calcMotorSchema, sig6, BEARING_FRICTION_MNM, checkSelfStartSchema, solidFromBoardSchema, FR4_DENSITY_KG_M3, COPPER_DENSITY_KG_M3, DEFAULT_COPPER_THICKNESS_MM, loopArea, MOUNTING_FP_RE, KEEPOUT_ECHO_CAP, INLINE_DOC_ECHO_CAP, searchElectronicPartsSchema, resolvePartSchema, findAlternativesSchema, verifySubstitutionSchema, buildReceiptSchema, verifyReceiptSchema, FIX_EPS, FIX_DRC_NEVER, fixDrcSchema, toolDefs20;
64526
+ var PCB_LAYER_SET, COPPER_LAYERS, COPPER_LAYER_SET, POSITION_TOLERANCE, PIN_SEP, pinKey, createSchematicSchema, docInputProperties, placeComponentsSchema, routeNetsSchema, runDrcSchema, runErcSchema, critiqueRouteSchema, routeDiffPairSchema, lengthMatchTracesSchema, exportGerberSchema, validateForFabSchema, exportKicadSchema, addCoilSchema, boardFromSolidSchema, addCoilArraySchema, windingLayoutSchema, calcImpedanceSchema, sizeImpedanceSchema, sizePdnSchema, calcCoilSchema, sizeCoilSchema, calcRfSchema, COIL_GEOMETRY, MU0, RAIL_PIN_NAMES, PIN_TYPES, CHIP_SIZE_CODES, CONNECTIVITY_GUARD_MAX_NETS, POWER_NET_RE, DRC_CATEGORY, DRC_DELTA_SAMPLE_CAP, DRC_DELTA_FULL_BOARD_MAX, LINT_CRYSTAL_MAX_MM, LINT_DECAP_MAX_MM, LINT_CONNECTOR_EDGE_MAX_MM, LINT_HIGH_CURRENT_SEP_MIN_MM, LINT_GROUND_NET_RE, LINT_SUPPLY_NET_RE, SENSITIVE_NET_RE, HIGH_CURRENT_CLASS_RE, refPrefix, dist2d, round2, sumSq, round33, vec2Schema, setBoardOutlineSchema, FOOTPRINT_FAMILIES, listFootprintsSchema, searchFootprintsSchema, getPadPositionsSchema, getFootprintSchema, ROTATION_CONVENTION, describePcbSchema, DESCRIBE_NET_NAME_CAP, addTraceSchema, addViaSchema, OZ_TO_MM, setStackupSchema, setPlacementSchema, addZoneSchema, deleteElementProps, deleteZoneSchema, deleteTraceSchema, deleteViaSchema, COPPER_KINDS, GET_COPPER_CAP, getCopperSchema, TIE_POSITION_TOL, addNetTieSchema, deleteNetTieSchema, undoSchema, setDesignRulesSchema, sizeTraceForCurrentSchema, addViaArraySchema, MAX_VIA_ARRAY, addMotorWindingSchema, calcMotorSchema, sig6, BEARING_FRICTION_MNM, checkSelfStartSchema, solidFromBoardSchema, FR4_DENSITY_KG_M3, COPPER_DENSITY_KG_M3, DEFAULT_COPPER_THICKNESS_MM, loopArea, MOUNTING_FP_RE, KEEPOUT_ECHO_CAP, INLINE_DOC_ECHO_CAP, searchElectronicPartsSchema, resolvePartSchema, findAlternativesSchema, verifySubstitutionSchema, buildReceiptSchema, verifyReceiptSchema, FIX_EPS, FIX_DRC_NEVER, fixDrcSchema, toolDefs20;
65152
64527
  var init_ecad2 = __esm({
65153
64528
  "src/tools/ecad.ts"() {
65154
64529
  "use strict";
@@ -65950,7 +65325,7 @@ var init_ecad2 = __esm({
65950
65325
  HIGH_CURRENT_CLASS_RE = /pwr|power|high[-_ ]?current|motor|\bhv\b|hi[-_ ]?amp/i;
65951
65326
  refPrefix = (ref) => /^[A-Za-z]+/.exec(ref)?.[0]?.toUpperCase() ?? "";
65952
65327
  dist2d = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
65953
- round23 = (n) => Math.round(n * 100) / 100;
65328
+ round2 = (n) => Math.round(n * 100) / 100;
65954
65329
  sumSq = (v) => v.reduce((s, x) => s + x * x, 0);
65955
65330
  round33 = (v) => Math.round(v * 1e3) / 1e3;
65956
65331
  vec2Schema = {
@@ -72571,12 +71946,12 @@ function simulateStrike(input, engine) {
72571
71946
  },
72572
71947
  modes: result.modes.map((m) => ({
72573
71948
  n: m.n,
72574
- hz: round24(m.hz),
71949
+ hz: round22(m.hz),
72575
71950
  gain: round4(m.gain),
72576
71951
  q: Math.round(m.q),
72577
- t60_s: round24(m.t60_s)
71952
+ t60_s: round22(m.t60_s)
72578
71953
  })),
72579
- spectrum_peaks: result.spectrum_peaks.map((p) => ({ hz: round24(p.hz), db: round24(p.db) })),
71954
+ spectrum_peaks: result.spectrum_peaks.map((p) => ({ hz: round22(p.hz), db: round22(p.db) })),
72580
71955
  ...result.verdict ? { verdict: result.verdict } : {},
72581
71956
  limits: "1-D transverse bending only (no torsional/lateral modes); decay Q is a heuristic (material + cord-at-holes), frequencies are not"
72582
71957
  };
@@ -72605,7 +71980,7 @@ function simulateStrike(input, engine) {
72605
71980
  }
72606
71981
  return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
72607
71982
  }
72608
- var cents, MATERIAL_ALIASES, simulateStrikeSchema, round24, round4, toolDefs39;
71983
+ var cents, MATERIAL_ALIASES, simulateStrikeSchema, round22, round4, toolDefs39;
72609
71984
  var init_acoustics = __esm({
72610
71985
  "src/tools/acoustics.ts"() {
72611
71986
  "use strict";
@@ -72677,7 +72052,7 @@ var init_acoustics = __esm({
72677
72052
  }
72678
72053
  }
72679
72054
  };
72680
- round24 = (x) => Math.round(x * 100) / 100;
72055
+ round22 = (x) => Math.round(x * 100) / 100;
72681
72056
  round4 = (x) => Math.round(x * 1e4) / 1e4;
72682
72057
  toolDefs39 = [
72683
72058
  {
@@ -73081,8 +72456,8 @@ function parseDocument(input) {
73081
72456
  }
73082
72457
  throw new Error("Document must be JSON (starting with '{') or VCode (starting with '#')");
73083
72458
  }
73084
- function compressForUrl(compact) {
73085
- const compressed = gzipSync(Buffer.from(compact, "utf-8"), { level: 9 });
72459
+ function compressForUrl(compact2) {
72460
+ const compressed = gzipSync(Buffer.from(compact2, "utf-8"), { level: 9 });
73086
72461
  return base64urlEncode(compressed);
73087
72462
  }
73088
72463
  function openInBrowser(input) {
@@ -76157,7 +75532,7 @@ function buildSequenceGlb(doc, timeline, frames, engine) {
76157
75532
  rec.times.push(frame.t);
76158
75533
  rec.trans.push(...translation);
76159
75534
  rec.rot.push(
76160
- ...eulerXyzDegToQuat(
75535
+ ...eulerXyzDegToQuat2(
76161
75536
  t2?.rotation ?? { x: 0, y: 0, z: 0 }
76162
75537
  )
76163
75538
  );
@@ -77758,8 +77133,8 @@ var init_server3 = __esm({
77758
77133
  init_order_feed();
77759
77134
  init_animate();
77760
77135
  PKG_VERSION = (() => {
77761
- if ("0.9.4-main.14") {
77762
- return "0.9.4-main.14";
77136
+ if ("0.9.4-main.16") {
77137
+ return "0.9.4-main.16";
77763
77138
  }
77764
77139
  try {
77765
77140
  const req = createRequire2(import.meta.url);
@@ -77768,8 +77143,8 @@ var init_server3 = __esm({
77768
77143
  return "0.0.0";
77769
77144
  }
77770
77145
  })();
77771
- BUILD_SHA = "292cf10c5c65aec57d1c40f8bd5bdb0a1222acaf";
77772
- BUILD_TIME = "2026-07-24T16:37:44.101Z";
77146
+ BUILD_SHA = "63c1a040cf3352ca640144ec217f3f96271180c9";
77147
+ BUILD_TIME = "2026-07-24T17:21:48.535Z";
77773
77148
  SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
77774
77149
  VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
77775
77150
  INSTANCE_ID = randomUUID6().slice(0, 8);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcad/mcp",
3
- "version": "0.9.4-main.14",
3
+ "version": "0.9.4-main.16",
4
4
  "description": "vcad MCP server — parametric CAD + PCB design tools for AI agents (self-contained: bundled server + kernel WASM)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "homepage": "https://vcad.io",
21
21
  "vcadBuild": {
22
- "sha": "292cf10c5c65aec57d1c40f8bd5bdb0a1222acaf",
23
- "builtAt": "2026-07-24T16:37:44.101Z"
22
+ "sha": "63c1a040cf3352ca640144ec217f3f96271180c9",
23
+ "builtAt": "2026-07-24T17:21:48.535Z"
24
24
  }
25
25
  }
Binary file