@vcad/mcp 0.9.4-main.27 → 0.9.4-main.28

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 cfda229f9a317556e3c9f64a43700549cd5265ef at 2026-07-25T23:24:45.817Z. Source: https://github.com/ecto/vcad
9
+ Built from 301abfbddc2a05c29fabc9f655031e554eab1d04 at 2026-07-26T00:32:29.039Z. Source: https://github.com/ecto/vcad
package/index.mjs CHANGED
@@ -10367,6 +10367,20 @@ async function airgapFluxDensity(spec) {
10367
10367
  return null;
10368
10368
  }
10369
10369
  }
10370
+ async function airgapSolve(spec) {
10371
+ const wasm3 = await loadEcadWasm();
10372
+ if (!wasm3)
10373
+ return null;
10374
+ try {
10375
+ const fn = wasm3.ecadAirgapSolve;
10376
+ if (typeof fn !== "function")
10377
+ return null;
10378
+ return fn(JSON.stringify(spec));
10379
+ } catch (e) {
10380
+ console.warn("[ECAD] airgapSolve failed:", e);
10381
+ return null;
10382
+ }
10383
+ }
10370
10384
  async function generateNetlist(sheet) {
10371
10385
  const wasm3 = await loadEcadWasm();
10372
10386
  if (!wasm3)
@@ -11229,6 +11243,7 @@ __export(dist_exports, {
11229
11243
  PhysicsEnv: () => PhysicsEnv,
11230
11244
  SolidCache: () => SolidCache,
11231
11245
  airgapFluxDensity: () => airgapFluxDensity,
11246
+ airgapSolve: () => airgapSolve,
11232
11247
  applyForwardKinematics: () => applyForwardKinematics,
11233
11248
  buildMoleculeReceipt: () => buildReceipt2,
11234
11249
  buildPartDocument: () => buildPartDocument,
@@ -40898,6 +40913,26 @@ var init_CHANGELOG = __esm({
40898
40913
  "sheet_metal_create"
40899
40914
  ]
40900
40915
  },
40916
+ {
40917
+ id: "2026-07-25-shell-and-booleans-on-filleted-solids",
40918
+ version: "0.9.4",
40919
+ date: "2026-07-25",
40920
+ category: "fix",
40921
+ title: "Shelling and cutting a filleted solid no longer leaks",
40922
+ summary: "Hollowing a filleted part scattered its cavity and a pocket cut collapsed the neighboring blends flat; both are now exact and watertight, and the kernel is reproducible run to run.",
40923
+ features: [
40924
+ "kernel",
40925
+ "fillet",
40926
+ "shell",
40927
+ "booleans",
40928
+ "tessellation"
40929
+ ],
40930
+ mcpTools: [
40931
+ "create_cad_loon",
40932
+ "inspect_cad",
40933
+ "export_cad"
40934
+ ]
40935
+ },
40901
40936
  {
40902
40937
  id: "2026-07-25-router-window-repair-commit-parity",
40903
40938
  version: "0.9.4",
@@ -41169,6 +41204,22 @@ var init_CHANGELOG = __esm({
41169
41204
  "quote_manufacturing"
41170
41205
  ]
41171
41206
  },
41207
+ {
41208
+ id: "2026-07-25-calc-motor-tooth-saturation",
41209
+ version: "0.9.4",
41210
+ date: "2026-07-25",
41211
+ category: "fix",
41212
+ title: "calc_motor sees saturating stator teeth",
41213
+ summary: "calc_motor's MEC model treated iron as linear and silently over-predicted Kt on machines whose teeth saturate; it now reports tooth flux, warns past the knee, and can solve the saturating network.",
41214
+ features: [
41215
+ "motor",
41216
+ "magnetics",
41217
+ "simulation"
41218
+ ],
41219
+ mcpTools: [
41220
+ "calc_motor"
41221
+ ]
41222
+ },
41172
41223
  {
41173
41224
  id: "2026-07-24-visionos-app",
41174
41225
  version: "0.9.4",
@@ -64125,6 +64176,7 @@ async function calcMotor(args) {
64125
64176
  let bGap = num2(args.airgap_flux_tesla);
64126
64177
  let bGapSource = "supplied";
64127
64178
  let magnetSpec;
64179
+ let solution = null;
64128
64180
  let fringing;
64129
64181
  if (!Number.isFinite(bGap)) {
64130
64182
  const m = args.magnet ?? {};
@@ -64138,15 +64190,55 @@ async function calcMotor(args) {
64138
64190
  gapAreaMm2: mnum(m.gap_area_mm2, 1),
64139
64191
  ironMuRel: typeof m.iron_mu_rel === "number" ? m.iron_mu_rel : null,
64140
64192
  ironPathMm: mnum(m.iron_path_mm, 0),
64141
- ironAreaMm2: mnum(m.iron_area_mm2, 1)
64142
- };
64143
- const computed = await airgapFluxDensity(magnetSpec);
64144
- if (computed == null) {
64145
- return fail2(
64146
- "air-gap flux is required: pass airgap_flux_tesla, or `magnet` params (ECAD WASM must be available to compute B_gap)."
64147
- );
64193
+ ironAreaMm2: mnum(m.iron_area_mm2, 1),
64194
+ ironJsT: typeof m.iron_js_t === "number" ? m.iron_js_t : null
64195
+ };
64196
+ if (m.slots !== void 0) {
64197
+ const slots = num2(m.slots);
64198
+ if (!(slots > 0)) return fail2("magnet.slots must be > 0");
64199
+ const meanR = Number.isFinite(num2(m.mean_radius_mm)) ? num2(m.mean_radius_mm) : (innerR + outerR) / 2;
64200
+ const pitch = 2 * Math.PI * meanR / slots;
64201
+ let toothW = num2(m.tooth_width_mm);
64202
+ if (!Number.isFinite(toothW)) {
64203
+ const frac = num2(m.tooth_fraction);
64204
+ if (!Number.isFinite(frac)) {
64205
+ return fail2("magnet.slots requires magnet.tooth_width_mm or magnet.tooth_fraction");
64206
+ }
64207
+ if (!(frac > 0 && frac <= 1)) return fail2("magnet.tooth_fraction must be in (0, 1]");
64208
+ toothW = frac * pitch;
64209
+ }
64210
+ if (!(toothW > 0)) return fail2("magnet.tooth_width_mm must be > 0");
64211
+ magnetSpec.teeth = {
64212
+ slots,
64213
+ toothWidthMm: toothW,
64214
+ meanRadiusMm: meanR,
64215
+ toothPathMm: mnum(m.tooth_path_mm, 0)
64216
+ };
64217
+ }
64218
+ solution = await airgapSolve(magnetSpec);
64219
+ if (solution == null) {
64220
+ const computed = await airgapFluxDensity(magnetSpec);
64221
+ if (computed == null) {
64222
+ return fail2(
64223
+ "air-gap flux is required: pass airgap_flux_tesla, or `magnet` params (ECAD WASM must be available to compute B_gap)."
64224
+ );
64225
+ }
64226
+ const t2 = magnetSpec.teeth;
64227
+ const k = t2 ? Math.max(1, 2 * Math.PI * t2.meanRadiusMm / t2.slots / t2.toothWidthMm) : null;
64228
+ solution = {
64229
+ bGapTesla: computed,
64230
+ bToothTesla: k == null ? null : computed * k,
64231
+ bIronTesla: null,
64232
+ toothConcentration: k,
64233
+ nonlinear: false,
64234
+ iterations: 0,
64235
+ converged: true,
64236
+ warnings: k != null && computed * k > SILICON_STEEL_KNEE_T ? [
64237
+ `tooth flux density ${(computed * k).toFixed(2)} T exceeds the ${SILICON_STEEL_KNEE_T.toFixed(2)} T knee \u2014 the LINEAR iron model over-predicts B_gap and Kt here; pass magnet.iron_js_t to solve the saturating network`
64238
+ ] : []
64239
+ };
64148
64240
  }
64149
- bGap = computed;
64241
+ bGap = solution.bGapTesla;
64150
64242
  bGapSource = "computed";
64151
64243
  if (m.pole_width_mm !== void 0) {
64152
64244
  const poleW = num2(m.pole_width_mm);
@@ -64218,6 +64310,24 @@ async function calcMotor(args) {
64218
64310
  })
64219
64311
  );
64220
64312
  }
64313
+ if (solution?.bToothTesla != null && magnetSpec.teeth) {
64314
+ claims.push(
64315
+ emClaim(
64316
+ "tooth_flux_density",
64317
+ r4(solution.bToothTesla),
64318
+ "T",
64319
+ solution.nonlinear ? "mec-saturating-iron" : "mec-tooth-concentration",
64320
+ {
64321
+ ...mecInputs,
64322
+ slots: magnetSpec.teeth.slots,
64323
+ tooth_width_mm: r4(magnetSpec.teeth.toothWidthMm),
64324
+ mean_radius_mm: r4(magnetSpec.teeth.meanRadiusMm),
64325
+ tooth_concentration: r4(solution.toothConcentration ?? 1),
64326
+ ...magnetSpec.ironJsT != null ? { iron_js_t: magnetSpec.ironJsT } : {}
64327
+ }
64328
+ )
64329
+ );
64330
+ }
64221
64331
  }
64222
64332
  return {
64223
64333
  content: [
@@ -64233,6 +64343,16 @@ async function calcMotor(args) {
64233
64343
  fringing_derate: r4(fringing.derate),
64234
64344
  fringing_note: "Carter-like first-order derate: B under the pole drops by w/(w+2g) as flux fringes ~one gap length past each pole edge. Kt/Ke and the curve use the DERATED flux. Honest for pole width \u2273 2\xD7 gap; below that treat it as a lower bound."
64235
64345
  } : {},
64346
+ ...solution?.bToothTesla != null ? {
64347
+ tooth_flux_tesla: r4(solution.bToothTesla),
64348
+ tooth_concentration: r4(solution.toothConcentration ?? 1),
64349
+ // Fringing redistributes flux under the pole; it does not remove
64350
+ // it, so the tooth carries the RAW gap flux either way.
64351
+ tooth_note: "B_tooth = raw B_gap \xB7 (tooth pitch / tooth width) \u2014 the gap flux of a whole tooth pitch funnels into one tooth body. Compare against your steel's knee (~1.5-1.7 T for M19-class silicon steel)."
64352
+ } : {},
64353
+ ...solution?.bIronTesla != null ? { yoke_flux_tesla: r4(solution.bIronTesla) } : {},
64354
+ ...solution?.nonlinear ? { iron_model: "saturating (arctangent B-H)" } : {},
64355
+ ...solution && solution.warnings.length > 0 ? { warnings: solution.warnings } : {},
64236
64356
  winding_factor: windingFactor,
64237
64357
  kt_nm_per_a: r4(perf.ktNmPerA),
64238
64358
  ke_v_s_per_rad: r4(perf.keVSPerRad),
@@ -64243,7 +64363,7 @@ async function calcMotor(args) {
64243
64363
  speed_rad_s: r4(p.speedRadS),
64244
64364
  torque_nm: r4(p.torqueNm)
64245
64365
  })),
64246
- note: fringing ? "First-order steady-state estimate (no slotting/saturation/losses; pole-edge fringing derated via magnet.pole_width_mm)." : "First-order steady-state estimate (no slotting/fringing/saturation/losses; pass magnet.pole_width_mm to derate for fringing).",
64366
+ note: "First-order steady-state estimate (no slotting, no losses)" + (fringing ? "; pole-edge fringing derated via magnet.pole_width_mm" : "; pass magnet.pole_width_mm to derate for fringing") + (solution?.nonlinear ? "; iron solved with its saturating B-H law" : "; iron is LINEAR, so Kt is OPTIMISTIC on a machine whose teeth saturate \u2014 " + (solution?.bToothTesla != null ? "see tooth_flux_tesla, and pass magnet.iron_js_t to solve the saturating network" : "pass magnet.slots + tooth_width_mm to see the tooth field")) + ".",
64247
64367
  claims
64248
64368
  })
64249
64369
  }
@@ -65715,7 +65835,7 @@ async function fixDrc(args) {
65715
65835
  };
65716
65836
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
65717
65837
  }
65718
- 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, fabPrepSchema, 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;
65838
+ 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, fabPrepSchema, 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, SILICON_STEEL_KNEE_T, 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;
65719
65839
  var init_ecad2 = __esm({
65720
65840
  "src/tools/ecad.ts"() {
65721
65841
  "use strict";
@@ -67127,7 +67247,7 @@ var init_ecad2 = __esm({
67127
67247
  },
67128
67248
  magnet: {
67129
67249
  type: "object",
67130
- description: "PM mode: optional magnet/geometry to compute B_gap when airgap_flux_tesla is omitted (NdFeB defaults). Fields: remanence_tesla, magnet_thickness_mm, airgap_mm, recoil_mu_rel, magnet_area_mm2, gap_area_mm2, iron_mu_rel. Add pole_width_mm (magnet pole face width across the fringing direction) to also apply the first-order Carter-like fringing derate w/(w+2g) \u2014 the tool then reports raw AND derated B and uses the derated value for Kt."
67250
+ description: "PM mode: optional magnet/geometry to compute B_gap when airgap_flux_tesla is omitted (NdFeB defaults). Fields: remanence_tesla, magnet_thickness_mm, airgap_mm, recoil_mu_rel, magnet_area_mm2, gap_area_mm2, iron_mu_rel. Add pole_width_mm (magnet pole face width across the fringing direction) to also apply the first-order Carter-like fringing derate w/(w+2g) \u2014 the tool then reports raw AND derated B and uses the derated value for Kt. SATURATION: the MEC iron is LINEAR unless you say otherwise, so it over-predicts Kt on a machine whose teeth saturate. Pass slots + (tooth_width_mm or tooth_fraction) to make the teeth visible \u2014 the tool then reports tooth_flux_density (B_gap \xB7 pitch/width, routinely 2x B_gap) and warns when it passes the ~1.5 T silicon-steel knee. Add iron_js_t (saturation polarization, silicon steel \u2248 2.0 T) with iron_mu_rel to actually SOLVE the saturating network instead of merely warning; tooth_path_mm puts the teeth in the reluctance loop."
67131
67251
  },
67132
67252
  phase_current_a: { type: "number", description: "Induction mode (required): phase current, A RMS (balanced 3-phase drive)." },
67133
67253
  electrical_freq_hz: { type: "number", description: "Induction mode (required): electrical drive frequency, Hz." },
@@ -67146,6 +67266,7 @@ var init_ecad2 = __esm({
67146
67266
  },
67147
67267
  required: ["pole_pairs", "turns_per_phase"]
67148
67268
  };
67269
+ SILICON_STEEL_KNEE_T = 1.5;
67149
67270
  sig6 = (v) => v === 0 || !Number.isFinite(v) ? v : Number(v.toPrecision(6));
67150
67271
  BEARING_FRICTION_MNM = {
67151
67272
  "608-2RS": {
@@ -78869,8 +78990,8 @@ var init_server3 = __esm({
78869
78990
  init_order_feed();
78870
78991
  init_animate();
78871
78992
  PKG_VERSION = (() => {
78872
- if ("0.9.4-main.27") {
78873
- return "0.9.4-main.27";
78993
+ if ("0.9.4-main.28") {
78994
+ return "0.9.4-main.28";
78874
78995
  }
78875
78996
  try {
78876
78997
  const req = createRequire2(import.meta.url);
@@ -78879,8 +79000,8 @@ var init_server3 = __esm({
78879
79000
  return "0.0.0";
78880
79001
  }
78881
79002
  })();
78882
- BUILD_SHA = "cfda229f9a317556e3c9f64a43700549cd5265ef";
78883
- BUILD_TIME = "2026-07-25T23:24:45.817Z";
79003
+ BUILD_SHA = "301abfbddc2a05c29fabc9f655031e554eab1d04";
79004
+ BUILD_TIME = "2026-07-26T00:32:29.039Z";
78884
79005
  SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
78885
79006
  VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
78886
79007
  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.27",
3
+ "version": "0.9.4-main.28",
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": "cfda229f9a317556e3c9f64a43700549cd5265ef",
23
- "builtAt": "2026-07-25T23:24:45.817Z"
22
+ "sha": "301abfbddc2a05c29fabc9f655031e554eab1d04",
23
+ "builtAt": "2026-07-26T00:32:29.039Z"
24
24
  }
25
25
  }
Binary file