@vcad/mcp 0.9.4-main.11 → 0.9.4-main.13
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 +1 -1
- package/index.mjs +82 -504
- package/package.json +3 -3
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
|
|
9
|
+
Built from 70552964e3760fb5b103235917b00364f6d45763 at 2026-07-24T16:29:07.170Z. Source: https://github.com/ecto/vcad
|
package/index.mjs
CHANGED
|
@@ -8022,14 +8022,22 @@ async function loadAndInit() {
|
|
|
8022
8022
|
}
|
|
8023
8023
|
await mod.default(hint ? { module_or_path: hint } : void 0);
|
|
8024
8024
|
}
|
|
8025
|
+
assertKernelBaseline(mod);
|
|
8025
8026
|
wasmModule2 = mod;
|
|
8026
8027
|
generation++;
|
|
8027
8028
|
return mod;
|
|
8028
8029
|
}
|
|
8030
|
+
function assertKernelBaseline(mod) {
|
|
8031
|
+
const missing = REQUIRED_BINDINGS.filter((name) => typeof mod[name] !== "function");
|
|
8032
|
+
if (missing.length > 0) {
|
|
8033
|
+
const version2 = typeof mod.get_kernel_version === "function" ? mod.get_kernel_version() : "unknown";
|
|
8034
|
+
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`);
|
|
8035
|
+
}
|
|
8036
|
+
}
|
|
8029
8037
|
function getKernelWasmSync() {
|
|
8030
8038
|
return wasmModule2;
|
|
8031
8039
|
}
|
|
8032
|
-
var wasmPromise, wasmModule2, wasmInputHint, bindgenStarted, lastModuleBuffer, lastTrap, generation;
|
|
8040
|
+
var wasmPromise, wasmModule2, wasmInputHint, bindgenStarted, lastModuleBuffer, lastTrap, generation, REQUIRED_BINDINGS;
|
|
8033
8041
|
var init_wasm_singleton = __esm({
|
|
8034
8042
|
"../engine/dist/wasm-singleton.js"() {
|
|
8035
8043
|
"use strict";
|
|
@@ -8038,6 +8046,13 @@ var init_wasm_singleton = __esm({
|
|
|
8038
8046
|
bindgenStarted = false;
|
|
8039
8047
|
lastTrap = null;
|
|
8040
8048
|
generation = 0;
|
|
8049
|
+
REQUIRED_BINDINGS = [
|
|
8050
|
+
"evaluateDocument",
|
|
8051
|
+
"documentDiff",
|
|
8052
|
+
"documentMerge",
|
|
8053
|
+
"documentToLoon",
|
|
8054
|
+
"documentToLoonChecked"
|
|
8055
|
+
];
|
|
8041
8056
|
}
|
|
8042
8057
|
});
|
|
8043
8058
|
|
|
@@ -8509,7 +8524,10 @@ function evaluateDocument2(doc, kernel2, options = {}) {
|
|
|
8509
8524
|
const resolved = resolveDocument(doc);
|
|
8510
8525
|
doc = resolved.doc;
|
|
8511
8526
|
doc = expandPartInstances(doc, kernel2);
|
|
8512
|
-
if (kernel2.evaluateDocument) {
|
|
8527
|
+
if (!kernel2.evaluateDocument) {
|
|
8528
|
+
throw new Error("kernel WASM bundle is missing the evaluateDocument binding \u2014 rebuild @vcad/kernel-wasm (stale bundle); old bundles are unsupported");
|
|
8529
|
+
}
|
|
8530
|
+
{
|
|
8513
8531
|
try {
|
|
8514
8532
|
const docJson = JSON.stringify(doc);
|
|
8515
8533
|
const result = kernel2.evaluateDocument(docJson, options.skipClashDetection ?? false);
|
|
@@ -9637,114 +9655,11 @@ var init_gpu = __esm({
|
|
|
9637
9655
|
});
|
|
9638
9656
|
|
|
9639
9657
|
// ../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
9658
|
function semanticDiff(oldDoc, newDoc, kernel2) {
|
|
9740
|
-
if (kernel2?.documentDiff) {
|
|
9741
|
-
|
|
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
|
-
}
|
|
9659
|
+
if (!kernel2?.documentDiff) {
|
|
9660
|
+
throw new Error("semantic diff needs a kernel build with the documentDiff binding \u2014 ensure the kernel WASM is initialized (getKernelWasm) and current");
|
|
9746
9661
|
}
|
|
9747
|
-
return
|
|
9662
|
+
return kernel2.documentDiff(JSON.stringify(oldDoc), JSON.stringify(newDoc));
|
|
9748
9663
|
}
|
|
9749
9664
|
function mergeAvailable(kernel2) {
|
|
9750
9665
|
return typeof kernel2?.documentMerge === "function";
|
|
@@ -9755,33 +9670,9 @@ function threeWayMerge(base, ours, theirs, resolutions, kernel2) {
|
|
|
9755
9670
|
}
|
|
9756
9671
|
return kernel2.documentMerge(JSON.stringify(base), JSON.stringify(ours), JSON.stringify(theirs), resolutions.length > 0 ? JSON.stringify(resolutions) : null);
|
|
9757
9672
|
}
|
|
9758
|
-
var MAP_TABLES, VEC_TABLES, DOC_FIELDS;
|
|
9759
9673
|
var init_diff = __esm({
|
|
9760
9674
|
"../engine/dist/diff.js"() {
|
|
9761
9675
|
"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
9676
|
}
|
|
9786
9677
|
});
|
|
9787
9678
|
|
|
@@ -11631,7 +11522,6 @@ __export(dist_exports, {
|
|
|
11631
11522
|
searchEcadParts: () => searchEcadParts,
|
|
11632
11523
|
searchParts: () => searchParts,
|
|
11633
11524
|
semanticDiff: () => semanticDiff,
|
|
11634
|
-
semanticDiffFallback: () => semanticDiffFallback,
|
|
11635
11525
|
solveDesignConstraints: () => solveDesignConstraints2,
|
|
11636
11526
|
solveForwardKinematics: () => solveForwardKinematics2,
|
|
11637
11527
|
threeWayMerge: () => threeWayMerge,
|
|
@@ -11801,6 +11691,8 @@ var init_dist = __esm({
|
|
|
11801
11691
|
sheetMetalSequence: wasmModule7.sheetMetalSequence,
|
|
11802
11692
|
nestSheetMetalParts: wasmModule7.nestSheetMetalParts,
|
|
11803
11693
|
getSheetMetalMaterials: wasmModule7.getSheetMetalMaterials,
|
|
11694
|
+
simulateStrikeKernel: wasmModule7.simulateStrikeKernel,
|
|
11695
|
+
noteToHz: wasmModule7.noteToHz,
|
|
11804
11696
|
getSheetMetalBendTable: wasmModule7.getSheetMetalBendTable,
|
|
11805
11697
|
getSheetMetalShopCatalog: wasmModule7.getSheetMetalShopCatalog,
|
|
11806
11698
|
sheetMetalFoldedStep: wasmModule7.sheetMetalFoldedStep,
|
|
@@ -12342,6 +12234,27 @@ var init_dist = __esm({
|
|
|
12342
12234
|
return null;
|
|
12343
12235
|
return sheetMetalSequence2(chain, this.kernel);
|
|
12344
12236
|
}
|
|
12237
|
+
/**
|
|
12238
|
+
* Run the kernel's mallet-strike pipeline (free-free bar modal analysis +
|
|
12239
|
+
* synthesis + FFT verdict). Input/output shapes are JSON mirrors of
|
|
12240
|
+
* `vcad_kernel_acoustics::strike::{StrikeInput, StrikeResult}` with the
|
|
12241
|
+
* WAV base64-encoded as `wav_base64`.
|
|
12242
|
+
*/
|
|
12243
|
+
simulateStrike(input) {
|
|
12244
|
+
const kernel2 = this.kernel;
|
|
12245
|
+
if (!kernel2.simulateStrikeKernel) {
|
|
12246
|
+
throw new Error("kernel WASM build lacks simulateStrikeKernel \u2014 rebuild @vcad/kernel-wasm");
|
|
12247
|
+
}
|
|
12248
|
+
return JSON.parse(kernel2.simulateStrikeKernel(JSON.stringify(input)));
|
|
12249
|
+
}
|
|
12250
|
+
/** Parse a note name ("C6", "F#4") to Hz via the kernel. */
|
|
12251
|
+
noteToHz(note) {
|
|
12252
|
+
const kernel2 = this.kernel;
|
|
12253
|
+
if (!kernel2.noteToHz) {
|
|
12254
|
+
throw new Error("kernel WASM build lacks noteToHz \u2014 rebuild @vcad/kernel-wasm");
|
|
12255
|
+
}
|
|
12256
|
+
return kernel2.noteToHz(note);
|
|
12257
|
+
}
|
|
12345
12258
|
/** Return the kernel's curated sheet-metal materials registry. */
|
|
12346
12259
|
getSheetMetalMaterials() {
|
|
12347
12260
|
return getSheetMetalMaterials2(this.kernel);
|
|
@@ -26615,33 +26528,33 @@ var init_server = __esm({
|
|
|
26615
26528
|
});
|
|
26616
26529
|
|
|
26617
26530
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/helpers.js
|
|
26618
|
-
function assertToolsCallTaskCapability(requests, method,
|
|
26531
|
+
function assertToolsCallTaskCapability(requests, method, entityName) {
|
|
26619
26532
|
if (!requests) {
|
|
26620
|
-
throw new Error(`${
|
|
26533
|
+
throw new Error(`${entityName} does not support task creation (required for ${method})`);
|
|
26621
26534
|
}
|
|
26622
26535
|
switch (method) {
|
|
26623
26536
|
case "tools/call":
|
|
26624
26537
|
if (!requests.tools?.call) {
|
|
26625
|
-
throw new Error(`${
|
|
26538
|
+
throw new Error(`${entityName} does not support task creation for tools/call (required for ${method})`);
|
|
26626
26539
|
}
|
|
26627
26540
|
break;
|
|
26628
26541
|
default:
|
|
26629
26542
|
break;
|
|
26630
26543
|
}
|
|
26631
26544
|
}
|
|
26632
|
-
function assertClientRequestTaskCapability(requests, method,
|
|
26545
|
+
function assertClientRequestTaskCapability(requests, method, entityName) {
|
|
26633
26546
|
if (!requests) {
|
|
26634
|
-
throw new Error(`${
|
|
26547
|
+
throw new Error(`${entityName} does not support task creation (required for ${method})`);
|
|
26635
26548
|
}
|
|
26636
26549
|
switch (method) {
|
|
26637
26550
|
case "sampling/createMessage":
|
|
26638
26551
|
if (!requests.sampling?.createMessage) {
|
|
26639
|
-
throw new Error(`${
|
|
26552
|
+
throw new Error(`${entityName} does not support task creation for sampling/createMessage (required for ${method})`);
|
|
26640
26553
|
}
|
|
26641
26554
|
break;
|
|
26642
26555
|
case "elicitation/create":
|
|
26643
26556
|
if (!requests.elicitation?.create) {
|
|
26644
|
-
throw new Error(`${
|
|
26557
|
+
throw new Error(`${entityName} does not support task creation for elicitation/create (required for ${method})`);
|
|
26645
26558
|
}
|
|
26646
26559
|
break;
|
|
26647
26560
|
default:
|
|
@@ -40930,6 +40843,7 @@ var init_save_load = __esm({
|
|
|
40930
40843
|
var init_document_to_loon = __esm({
|
|
40931
40844
|
"../core/dist/utils/document-to-loon.js"() {
|
|
40932
40845
|
"use strict";
|
|
40846
|
+
init_wasm_singleton2();
|
|
40933
40847
|
}
|
|
40934
40848
|
});
|
|
40935
40849
|
|
|
@@ -72670,335 +72584,6 @@ var init_sheet_metal2 = __esm({
|
|
|
72670
72584
|
|
|
72671
72585
|
// src/tools/acoustics.ts
|
|
72672
72586
|
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
72587
|
function lookupMaterial(engine, name) {
|
|
73003
72588
|
const key = name.trim().toLowerCase().replace(/[_ ]/g, "-");
|
|
73004
72589
|
const registry4 = engine.getSheetMetalMaterials();
|
|
@@ -73045,10 +72630,8 @@ function barFromDocument(engine, documentId) {
|
|
|
73045
72630
|
let holeDia = 0;
|
|
73046
72631
|
for (const loop of holeLoops) {
|
|
73047
72632
|
const cs = loop.map((p) => axis === "x" ? p.x : p.y);
|
|
73048
|
-
const os = loop.map((p) => axis === "x" ? p.y : p.x);
|
|
73049
72633
|
holesMm.push((Math.min(...cs) + Math.max(...cs)) / 2);
|
|
73050
72634
|
holeDia = Math.max(holeDia, Math.max(...cs) - Math.min(...cs));
|
|
73051
|
-
void os;
|
|
73052
72635
|
}
|
|
73053
72636
|
return {
|
|
73054
72637
|
bar: {
|
|
@@ -73095,28 +72678,26 @@ function simulateStrike(input, engine) {
|
|
|
73095
72678
|
const durationS = Math.min(Math.max(Number(a.duration_s ?? 2.5), 0.5), 10);
|
|
73096
72679
|
const strikeFrac = Math.min(Math.max(Number(a.strike_position ?? 0.5), 0.02), 0.98);
|
|
73097
72680
|
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
72681
|
let expected;
|
|
73106
72682
|
if (typeof a.expect_hz === "number") expected = a.expect_hz;
|
|
73107
|
-
else if (typeof a.note === "string") expected = noteToHz(a.note);
|
|
73108
|
-
const
|
|
73109
|
-
|
|
73110
|
-
(
|
|
73111
|
-
|
|
73112
|
-
|
|
73113
|
-
|
|
73114
|
-
|
|
73115
|
-
|
|
73116
|
-
|
|
73117
|
-
|
|
73118
|
-
|
|
73119
|
-
|
|
72683
|
+
else if (typeof a.note === "string") expected = engine.noteToHz(a.note);
|
|
72684
|
+
const wantWav = typeof a.wav_filename === "string" && a.wav_filename.length > 0;
|
|
72685
|
+
if (wantWav && !/\.wav$/i.test(a.wav_filename)) {
|
|
72686
|
+
throw new Error("wav_filename must end in .wav");
|
|
72687
|
+
}
|
|
72688
|
+
const result = engine.simulateStrike({
|
|
72689
|
+
bar,
|
|
72690
|
+
strike_position: strikeFrac,
|
|
72691
|
+
mallet_contact_ms: contactMs,
|
|
72692
|
+
duration_s: durationS,
|
|
72693
|
+
sample_rate: sampleRate,
|
|
72694
|
+
n_modes: 6,
|
|
72695
|
+
expected_hz: expected ?? null,
|
|
72696
|
+
tolerance_cents: Number(a.tolerance_cents ?? 10),
|
|
72697
|
+
include_wav: wantWav
|
|
72698
|
+
});
|
|
72699
|
+
const closed = result.closed_form_hz;
|
|
72700
|
+
const fem = result.fem_hz;
|
|
73120
72701
|
const payload = {
|
|
73121
72702
|
bar: { ...bar, material: mat.name, material_display: mat.display_name },
|
|
73122
72703
|
physics: {
|
|
@@ -73133,22 +72714,19 @@ function simulateStrike(input, engine) {
|
|
|
73133
72714
|
duration_s: durationS,
|
|
73134
72715
|
sample_rate: sampleRate
|
|
73135
72716
|
},
|
|
73136
|
-
modes:
|
|
72717
|
+
modes: result.modes.map((m) => ({
|
|
73137
72718
|
n: m.n,
|
|
73138
72719
|
hz: round24(m.hz),
|
|
73139
72720
|
gain: round4(m.gain),
|
|
73140
72721
|
q: Math.round(m.q),
|
|
73141
72722
|
t60_s: round24(m.t60_s)
|
|
73142
72723
|
})),
|
|
73143
|
-
spectrum_peaks:
|
|
73144
|
-
...verdict ? { verdict } : {},
|
|
72724
|
+
spectrum_peaks: result.spectrum_peaks.map((p) => ({ hz: round24(p.hz), db: round24(p.db) })),
|
|
72725
|
+
...result.verdict ? { verdict: result.verdict } : {},
|
|
73145
72726
|
limits: "1-D transverse bending only (no torsional/lateral modes); decay Q is a heuristic (material + cord-at-holes), frequencies are not"
|
|
73146
72727
|
};
|
|
73147
|
-
if (
|
|
73148
|
-
|
|
73149
|
-
throw new Error("wav_filename must end in .wav");
|
|
73150
|
-
}
|
|
73151
|
-
const bytes = encodeWav(samples, sampleRate);
|
|
72728
|
+
if (wantWav && result.wav_base64) {
|
|
72729
|
+
const bytes = Buffer.from(result.wav_base64, "base64");
|
|
73152
72730
|
if (isRemoteDeployment()) {
|
|
73153
72731
|
const cap = maxInlineExportBytes();
|
|
73154
72732
|
if (bytes.length > cap) {
|
|
@@ -73159,7 +72737,7 @@ function simulateStrike(input, engine) {
|
|
|
73159
72737
|
payload.wav = {
|
|
73160
72738
|
filename: a.wav_filename,
|
|
73161
72739
|
bytes: bytes.length,
|
|
73162
|
-
data_base64:
|
|
72740
|
+
data_base64: result.wav_base64
|
|
73163
72741
|
};
|
|
73164
72742
|
} else {
|
|
73165
72743
|
const path = resolveWithinRoot(
|
|
@@ -78325,8 +77903,8 @@ var init_server3 = __esm({
|
|
|
78325
77903
|
init_order_feed();
|
|
78326
77904
|
init_animate();
|
|
78327
77905
|
PKG_VERSION = (() => {
|
|
78328
|
-
if ("0.9.4-main.
|
|
78329
|
-
return "0.9.4-main.
|
|
77906
|
+
if ("0.9.4-main.13") {
|
|
77907
|
+
return "0.9.4-main.13";
|
|
78330
77908
|
}
|
|
78331
77909
|
try {
|
|
78332
77910
|
const req = createRequire2(import.meta.url);
|
|
@@ -78335,8 +77913,8 @@ var init_server3 = __esm({
|
|
|
78335
77913
|
return "0.0.0";
|
|
78336
77914
|
}
|
|
78337
77915
|
})();
|
|
78338
|
-
BUILD_SHA = "
|
|
78339
|
-
BUILD_TIME = "2026-07-24T16:
|
|
77916
|
+
BUILD_SHA = "70552964e3760fb5b103235917b00364f6d45763";
|
|
77917
|
+
BUILD_TIME = "2026-07-24T16:29:07.170Z";
|
|
78340
77918
|
SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
|
|
78341
77919
|
VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
|
|
78342
77920
|
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.
|
|
3
|
+
"version": "0.9.4-main.13",
|
|
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": "
|
|
23
|
-
"builtAt": "2026-07-24T16:
|
|
22
|
+
"sha": "70552964e3760fb5b103235917b00364f6d45763",
|
|
23
|
+
"builtAt": "2026-07-24T16:29:07.170Z"
|
|
24
24
|
}
|
|
25
25
|
}
|