@vcad/mcp 0.9.4-main.7 → 0.9.4-main.9

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 3f7779633a486aaa3e0e6d984834c539769923cc at 2026-07-23T23:44:37.186Z. Source: https://github.com/ecto/vcad
9
+ Built from c3ef1dae913d41fab4da6f029c6d6b347bb08395 at 2026-07-24T16:14:27.739Z. Source: https://github.com/ecto/vcad
package/index.mjs CHANGED
@@ -10077,120 +10077,22 @@ var init_diff = __esm({
10077
10077
  });
10078
10078
 
10079
10079
  // ../engine/dist/sequence.js
10080
- function sampleTrackValue(track, t2) {
10081
- const keys = track.keys;
10082
- if (keys.length === 0)
10083
- return 0;
10084
- const first = keys[0];
10085
- if (t2 <= first.t)
10086
- return first.value;
10087
- const last = keys[keys.length - 1];
10088
- if (t2 >= last.t)
10089
- return last.value;
10090
- const idx = keys.findIndex((k) => k.t > t2);
10091
- const a = keys[idx - 1];
10092
- const b = keys[idx];
10093
- const span = b.t - a.t;
10094
- let u = span <= 0 ? 1 : (t2 - a.t) / span;
10095
- switch (b.ease ?? "linear") {
10096
- case "step":
10097
- u = u >= 1 ? 1 : 0;
10098
- break;
10099
- case "ease-in-out":
10100
- u = u * u * (3 - 2 * u);
10101
- break;
10102
- default:
10103
- break;
10104
- }
10105
- return a.value + (b.value - a.value) * u;
10106
- }
10107
- function cameraPoseAt(shots, t2, prevPose) {
10108
- let active;
10109
- for (const shot of shots) {
10110
- if (t2 >= shot.startS && t2 < shot.endS)
10111
- active = shot;
10112
- }
10113
- if (!active)
10114
- return prevPose;
10115
- const span = active.endS - active.startS;
10116
- const u = span <= 0 ? 1 : (t2 - active.startS) / span;
10117
- const kind = active.kind;
10118
- switch (kind.type) {
10119
- case "Turntable":
10120
- return {
10121
- azimuthDeg: kind.degrees * u,
10122
- elevationDeg: kind.elevationDeg,
10123
- dolly: 1
10124
- };
10125
- case "Orbit":
10126
- return {
10127
- azimuthDeg: kind.from[0] + (kind.to[0] - kind.from[0]) * u,
10128
- elevationDeg: kind.from[1] + (kind.to[1] - kind.from[1]) * u,
10129
- dolly: 1
10130
- };
10131
- case "Focus":
10132
- return {
10133
- azimuthDeg: prevPose.azimuthDeg,
10134
- elevationDeg: prevPose.elevationDeg,
10135
- dolly: 1 + (kind.dolly - 1) * u,
10136
- target: kind.target
10137
- };
10138
- case "Static":
10139
- return { ...DEFAULT_POSE };
10080
+ function requireWasm() {
10081
+ const mod = getKernelWasmSync();
10082
+ if (!mod || typeof mod.sample_timeline_sequence !== "function" || typeof mod.sample_timeline_track !== "function") {
10083
+ throw new Error("kernel WASM not initialized (or too old for timeline sampling) \u2014 await getKernelWasm() before sampling sequences");
10140
10084
  }
10085
+ return mod;
10086
+ }
10087
+ function sampleTrackValue(track, t2) {
10088
+ return requireWasm().sample_timeline_track(JSON.stringify(track), t2);
10141
10089
  }
10142
10090
  function sampleSequence(doc, timelineOverride) {
10143
10091
  const timeline = timelineOverride ?? doc.timeline;
10144
10092
  if (!timeline)
10145
10093
  return [];
10146
- const fps = timeline.fps && timeline.fps > 0 ? timeline.fps : 24;
10147
- const frameCount = Math.max(2, Math.round(timeline.durationS * fps) + 1);
10148
- const tracks = timeline.tracks ?? [];
10149
- const shots = timeline.camera ?? [];
10150
- const hasParamTracks2 = tracks.some((tr) => tr.target.type === "Parameter");
10151
- const frames = [];
10152
- let prevParams;
10153
- let prevPose = { ...DEFAULT_POSE };
10154
- for (let index = 0; index < frameCount; index++) {
10155
- const t2 = index / fps;
10156
- const params = {};
10157
- const joints = {};
10158
- const visibility = {};
10159
- let explode = 0;
10160
- for (const track of tracks) {
10161
- const value = sampleTrackValue(track, t2);
10162
- const target = track.target;
10163
- switch (target.type) {
10164
- case "Parameter":
10165
- params[target.name] = value;
10166
- break;
10167
- case "Joint":
10168
- joints[target.jointId] = value;
10169
- break;
10170
- case "Visibility":
10171
- visibility[target.instanceId] = value > 0.5;
10172
- break;
10173
- case "Explode":
10174
- explode = value;
10175
- break;
10176
- }
10177
- }
10178
- const camera = cameraPoseAt(shots, t2, prevPose);
10179
- prevPose = camera;
10180
- const geometryDirty = prevParams === void 0 ? hasParamTracks2 : Object.keys(params).some((name) => params[name] !== prevParams[name]);
10181
- prevParams = params;
10182
- frames.push({
10183
- index,
10184
- t: t2,
10185
- params,
10186
- joints,
10187
- visibility,
10188
- explode,
10189
- camera,
10190
- geometryDirty
10191
- });
10192
- }
10193
- return frames;
10094
+ const json = requireWasm().sample_timeline_sequence(JSON.stringify(timeline));
10095
+ return JSON.parse(json);
10194
10096
  }
10195
10097
  function poseDocument(doc, frame) {
10196
10098
  const posed = structuredClone(doc);
@@ -10212,11 +10114,10 @@ function poseDocument(doc, frame) {
10212
10114
  }
10213
10115
  return posed;
10214
10116
  }
10215
- var DEFAULT_POSE;
10216
10117
  var init_sequence = __esm({
10217
10118
  "../engine/dist/sequence.js"() {
10218
10119
  "use strict";
10219
- DEFAULT_POSE = { azimuthDeg: 0, elevationDeg: 30, dolly: 1 };
10120
+ init_wasm_singleton();
10220
10121
  }
10221
10122
  });
10222
10123
 
@@ -41632,6 +41533,24 @@ var init_CHANGELOG = __esm({
41632
41533
  CHANGELOG_default = {
41633
41534
  $schema: "./changelog.schema.json",
41634
41535
  entries: [
41536
+ {
41537
+ id: "2026-07-24-router-keepout-edge-clearance",
41538
+ version: "0.9.4",
41539
+ date: "2026-07-24",
41540
+ category: "fix",
41541
+ title: "Autorouter respects keepouts and board-edge clearance",
41542
+ summary: "route_nets' legality oracle now indexes no-tracks keepouts and the board outline (incl. cutouts), so routes never cross keepouts or hug the edge inside edge_clearance; multi-round negotiation can no longer finish below the single-round baseline.",
41543
+ features: [
41544
+ "pcb",
41545
+ "autorouter",
41546
+ "drc"
41547
+ ],
41548
+ mcpTools: [
41549
+ "route_nets",
41550
+ "run_drc",
41551
+ "validate_for_fab"
41552
+ ]
41553
+ },
41635
41554
  {
41636
41555
  id: "2026-07-23-router-post-route-legalization",
41637
41556
  version: "0.9.4",
@@ -50968,83 +50887,26 @@ var init_store = __esm({
50968
50887
  });
50969
50888
 
50970
50889
  // src/tools/inspect.ts
50971
- function signedVolumeOfTriangle(p1, p2, p3) {
50972
- return (p1[0] * (p2[1] * p3[2] - p3[1] * p2[2]) - p2[0] * (p1[1] * p3[2] - p3[1] * p1[2]) + p3[0] * (p1[1] * p2[2] - p2[1] * p1[2])) / 6;
50973
- }
50974
- function triangleArea2(p1, p2, p3) {
50975
- const ax = p2[0] - p1[0];
50976
- const ay = p2[1] - p1[1];
50977
- const az = p2[2] - p1[2];
50978
- const bx = p3[0] - p1[0];
50979
- const by = p3[1] - p1[1];
50980
- const bz = p3[2] - p1[2];
50981
- const cx = ay * bz - az * by;
50982
- const cy = az * bx - ax * bz;
50983
- const cz = ax * by - ay * bx;
50984
- return Math.sqrt(cx * cx + cy * cy + cz * cz) / 2;
50985
- }
50986
- function getVertex(mesh, index) {
50987
- const i = index * 3;
50988
- return [mesh.positions[i], mesh.positions[i + 1], mesh.positions[i + 2]];
50989
- }
50990
50890
  function computeMeshProperties(mesh) {
50991
- const numTriangles = mesh.indices.length / 3;
50992
- let volume = 0;
50993
- let area = 0;
50994
- let cx = 0, cy = 0, cz = 0;
50995
- let acx = 0, acy = 0, acz = 0;
50996
- const bbox = {
50997
- min: { x: Infinity, y: Infinity, z: Infinity },
50998
- max: { x: -Infinity, y: -Infinity, z: -Infinity }
50999
- };
51000
- for (let t2 = 0; t2 < numTriangles; t2++) {
51001
- const i0 = mesh.indices[t2 * 3];
51002
- const i1 = mesh.indices[t2 * 3 + 1];
51003
- const i2 = mesh.indices[t2 * 3 + 2];
51004
- const p1 = getVertex(mesh, i0);
51005
- const p2 = getVertex(mesh, i1);
51006
- const p3 = getVertex(mesh, i2);
51007
- const v = signedVolumeOfTriangle(p1, p2, p3);
51008
- volume += v;
51009
- const a = triangleArea2(p1, p2, p3);
51010
- area += a;
51011
- cx += v * (p1[0] + p2[0] + p3[0]) / 4;
51012
- cy += v * (p1[1] + p2[1] + p3[1]) / 4;
51013
- cz += v * (p1[2] + p2[2] + p3[2]) / 4;
51014
- acx += a * (p1[0] + p2[0] + p3[0]) / 3;
51015
- acy += a * (p1[1] + p2[1] + p3[1]) / 3;
51016
- acz += a * (p1[2] + p2[2] + p3[2]) / 3;
51017
- for (const p of [p1, p2, p3]) {
51018
- bbox.min.x = Math.min(bbox.min.x, p[0]);
51019
- bbox.min.y = Math.min(bbox.min.y, p[1]);
51020
- bbox.min.z = Math.min(bbox.min.z, p[2]);
51021
- bbox.max.x = Math.max(bbox.max.x, p[0]);
51022
- bbox.max.y = Math.max(bbox.max.y, p[1]);
51023
- bbox.max.z = Math.max(bbox.max.z, p[2]);
51024
- }
51025
- }
51026
- const absVolume = Math.abs(volume);
51027
- let centroid = null;
51028
- if (absVolume > 1e-10) {
51029
- centroid = { x: cx / volume, y: cy / volume, z: cz / volume };
51030
- const eps = 1e-9 * Math.max(
51031
- bbox.max.x - bbox.min.x,
51032
- bbox.max.y - bbox.min.y,
51033
- bbox.max.z - bbox.min.z,
51034
- 1
51035
- );
51036
- const inBbox = centroid.x >= bbox.min.x - eps && centroid.x <= bbox.max.x + eps && centroid.y >= bbox.min.y - eps && centroid.y <= bbox.max.y + eps && centroid.z >= bbox.min.z - eps && centroid.z <= bbox.max.z + eps;
51037
- if (!inBbox) centroid = null;
50891
+ const wasm2 = getKernelWasmSync();
50892
+ if (!wasm2) {
50893
+ throw new Error("kernel WASM is not initialized \u2014 call Engine.init first");
51038
50894
  }
51039
- if (centroid === null) {
51040
- centroid = area > 0 ? { x: acx / area, y: acy / area, z: acz / area } : { x: 0, y: 0, z: 0 };
50895
+ if (typeof wasm2.computeMeshProperties !== "function") {
50896
+ throw new Error(
50897
+ "computeMeshProperties is not in this kernel build \u2014 rebuild vcad-kernel-wasm"
50898
+ );
51041
50899
  }
50900
+ const props = wasm2.computeMeshProperties(
50901
+ mesh.positions instanceof Float32Array ? mesh.positions : new Float32Array(mesh.positions),
50902
+ mesh.indices instanceof Uint32Array ? mesh.indices : new Uint32Array(mesh.indices)
50903
+ );
51042
50904
  return {
51043
- volume: absVolume,
51044
- area,
51045
- bbox,
51046
- centroid,
51047
- triangles: numTriangles
50905
+ volume: props.volume,
50906
+ area: props.area,
50907
+ bbox: props.bbox,
50908
+ centroid: props.centerOfMass,
50909
+ triangles: props.triangles
51048
50910
  };
51049
50911
  }
51050
50912
  function computeInspection(ir, engine) {
@@ -53665,21 +53527,6 @@ var init_continue_doc = __esm({
53665
53527
  });
53666
53528
 
53667
53529
  // src/fabricate/geometry.ts
53668
- function vertex(mesh, index) {
53669
- const i = index * 3;
53670
- return [mesh.positions[i], mesh.positions[i + 1], mesh.positions[i + 2]];
53671
- }
53672
- function signedTetVolume(p1, p2, p3) {
53673
- return (p1[0] * (p2[1] * p3[2] - p3[1] * p2[2]) - p2[0] * (p1[1] * p3[2] - p3[1] * p1[2]) + p3[0] * (p1[1] * p2[2] - p2[1] * p1[2])) / 6;
53674
- }
53675
- function triArea(p1, p2, p3) {
53676
- const ax = p2[0] - p1[0], ay = p2[1] - p1[1], az = p2[2] - p1[2];
53677
- const bx = p3[0] - p1[0], by = p3[1] - p1[1], bz = p3[2] - p1[2];
53678
- const cx = ay * bz - az * by;
53679
- const cy = az * bx - ax * bz;
53680
- const cz = ax * by - ay * bx;
53681
- return Math.sqrt(cx * cx + cy * cy + cz * cz) / 2;
53682
- }
53683
53530
  function measureDocument(ir, engine) {
53684
53531
  let scene;
53685
53532
  try {
@@ -53693,21 +53540,15 @@ function measureDocument(ir, engine) {
53693
53540
  const min = [Infinity, Infinity, Infinity];
53694
53541
  const max = [-Infinity, -Infinity, -Infinity];
53695
53542
  for (const part of scene.parts) {
53696
- const mesh = part.mesh;
53697
- const numTris = mesh.indices.length / 3;
53698
- for (let t2 = 0; t2 < numTris; t2++) {
53699
- const p1 = vertex(mesh, mesh.indices[t2 * 3]);
53700
- const p2 = vertex(mesh, mesh.indices[t2 * 3 + 1]);
53701
- const p3 = vertex(mesh, mesh.indices[t2 * 3 + 2]);
53702
- volume += signedTetVolume(p1, p2, p3);
53703
- area += triArea(p1, p2, p3);
53704
- for (const p of [p1, p2, p3]) {
53705
- for (let k = 0; k < 3; k++) {
53706
- if (p[k] < min[k]) min[k] = p[k];
53707
- if (p[k] > max[k]) max[k] = p[k];
53708
- }
53709
- }
53710
- }
53543
+ const props = computeMeshProperties(part.mesh);
53544
+ volume += props.volume;
53545
+ area += props.area;
53546
+ min[0] = Math.min(min[0], props.bbox.min.x);
53547
+ min[1] = Math.min(min[1], props.bbox.min.y);
53548
+ min[2] = Math.min(min[2], props.bbox.min.z);
53549
+ max[0] = Math.max(max[0], props.bbox.max.x);
53550
+ max[1] = Math.max(max[1], props.bbox.max.y);
53551
+ max[2] = Math.max(max[2], props.bbox.max.z);
53711
53552
  }
53712
53553
  if (!Number.isFinite(min[0])) return { ...EMPTY };
53713
53554
  const dx = max[0] - min[0];
@@ -53716,7 +53557,7 @@ function measureDocument(ir, engine) {
53716
53557
  return {
53717
53558
  ok: true,
53718
53559
  parts: scene.parts.length,
53719
- volume_mm3: Math.abs(volume),
53560
+ volume_mm3: volume,
53720
53561
  surface_area_mm2: area,
53721
53562
  footprint_mm2: dx * dy,
53722
53563
  max_dim_mm: Math.max(dx, dy, dz),
@@ -53727,6 +53568,7 @@ var EMPTY;
53727
53568
  var init_geometry2 = __esm({
53728
53569
  "src/fabricate/geometry.ts"() {
53729
53570
  "use strict";
53571
+ init_inspect();
53730
53572
  EMPTY = {
53731
53573
  ok: false,
53732
53574
  parts: 0,
@@ -77183,6 +77025,7 @@ ${issues.map((i) => i.track >= 0 ? `track[${i.track}]: ${i.problem}` : i.problem
77183
77025
  );
77184
77026
  }
77185
77027
  doc.timeline = timeline;
77028
+ await getKernelWasm();
77186
77029
  const frames = sampleSequence(doc);
77187
77030
  return ok({
77188
77031
  duration_s: timeline.durationS,
@@ -77453,6 +77296,7 @@ async function renderSequence(args, ctx) {
77453
77296
  ${issues.map((i) => i.problem).join("\n")}`
77454
77297
  );
77455
77298
  }
77299
+ await getKernelWasm();
77456
77300
  const frames = sampleSequence(doc, timeline);
77457
77301
  if (frames.length > MAX_FRAMES) {
77458
77302
  return err2(
@@ -77637,6 +77481,7 @@ async function exportVideo(args, ctx) {
77637
77481
  return err2(`timeline invalid:
77638
77482
  ${issues.map((i) => i.problem).join("\n")}`);
77639
77483
  }
77484
+ await getKernelWasm();
77640
77485
  const frames = sampleSequence(doc, timeline);
77641
77486
  if (frames.length > MAX_FRAMES) {
77642
77487
  return err2(
@@ -78903,8 +78748,8 @@ var init_server3 = __esm({
78903
78748
  init_order_feed();
78904
78749
  init_animate();
78905
78750
  PKG_VERSION = (() => {
78906
- if ("0.9.4-main.7") {
78907
- return "0.9.4-main.7";
78751
+ if ("0.9.4-main.9") {
78752
+ return "0.9.4-main.9";
78908
78753
  }
78909
78754
  try {
78910
78755
  const req = createRequire2(import.meta.url);
@@ -78913,8 +78758,8 @@ var init_server3 = __esm({
78913
78758
  return "0.0.0";
78914
78759
  }
78915
78760
  })();
78916
- BUILD_SHA = "3f7779633a486aaa3e0e6d984834c539769923cc";
78917
- BUILD_TIME = "2026-07-23T23:44:37.186Z";
78761
+ BUILD_SHA = "c3ef1dae913d41fab4da6f029c6d6b347bb08395";
78762
+ BUILD_TIME = "2026-07-24T16:14:27.739Z";
78918
78763
  SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
78919
78764
  VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
78920
78765
  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.7",
3
+ "version": "0.9.4-main.9",
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": "3f7779633a486aaa3e0e6d984834c539769923cc",
23
- "builtAt": "2026-07-23T23:44:37.186Z"
22
+ "sha": "c3ef1dae913d41fab4da6f029c6d6b347bb08395",
23
+ "builtAt": "2026-07-24T16:14:27.739Z"
24
24
  }
25
25
  }
Binary file