@vcad/mcp 0.9.4-main.17 → 0.9.4-main.19

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 cfb504c93d13b7e0339f79b4b8863ffeb1ca83b1 at 2026-07-24T20:18:23.601Z. Source: https://github.com/ecto/vcad
9
+ Built from 7f18700be295061f294b748d3d7aa957f34ddd1c at 2026-07-25T13:19:21.672Z. Source: https://github.com/ecto/vcad
package/index.mjs CHANGED
@@ -198,6 +198,7 @@ __export(vcad_kernel_wasm_exports, {
198
198
  isGpuAvailable: () => isGpuAvailable,
199
199
  isPhysicsAvailable: () => isPhysicsAvailable,
200
200
  isSlicerAvailable: () => isSlicerAvailable,
201
+ latticeGaugeSimulate: () => latticeGaugeSimulate,
201
202
  mesh_clearance: () => mesh_clearance,
202
203
  nestSheetMetalParts: () => nestSheetMetalParts,
203
204
  nestedSheetMetalDxf: () => nestedSheetMetalDxf,
@@ -244,6 +245,7 @@ __export(vcad_kernel_wasm_exports, {
244
245
  sectionMesh: () => sectionMesh,
245
246
  sheetMetalFoldedStep: () => sheetMetalFoldedStep,
246
247
  sheetMetalSequence: () => sheetMetalSequence,
248
+ simulateFlow: () => simulateFlow,
247
249
  simulateStrikeKernel: () => simulateStrikeKernel,
248
250
  sketchCircleSegments: () => sketchCircleSegments,
249
251
  sketchHitTest: () => sketchHitTest,
@@ -2126,6 +2128,15 @@ function isSlicerAvailable() {
2126
2128
  const ret = wasm.isSlicerAvailable();
2127
2129
  return ret !== 0;
2128
2130
  }
2131
+ function latticeGaugeSimulate(spec_json) {
2132
+ const ptr0 = passStringToWasm0(spec_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
2133
+ const len0 = WASM_VECTOR_LEN;
2134
+ const ret = wasm.latticeGaugeSimulate(ptr0, len0);
2135
+ if (ret[2]) {
2136
+ throw takeFromExternrefTable0(ret[1]);
2137
+ }
2138
+ return takeFromExternrefTable0(ret[0]);
2139
+ }
2129
2140
  function mesh_clearance(positions_a, indices_a, positions_b, indices_b) {
2130
2141
  const ptr0 = passArrayF32ToWasm0(positions_a, wasm.__wbindgen_malloc);
2131
2142
  const len0 = WASM_VECTOR_LEN;
@@ -2787,6 +2798,17 @@ function sheetMetalSequence(chain_json) {
2787
2798
  wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
2788
2799
  }
2789
2800
  }
2801
+ function simulateFlow(spec_json, options_json, include_fields) {
2802
+ const ptr0 = passStringToWasm0(spec_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
2803
+ const len0 = WASM_VECTOR_LEN;
2804
+ const ptr1 = passStringToWasm0(options_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
2805
+ const len1 = WASM_VECTOR_LEN;
2806
+ const ret = wasm.simulateFlow(ptr0, len0, ptr1, len1, include_fields);
2807
+ if (ret[2]) {
2808
+ throw takeFromExternrefTable0(ret[1]);
2809
+ }
2810
+ return takeFromExternrefTable0(ret[0]);
2811
+ }
2790
2812
  function simulateStrikeKernel(input_json) {
2791
2813
  let deferred3_0;
2792
2814
  let deferred3_1;
@@ -10239,6 +10261,25 @@ async function tryRunDrc(pcb) {
10239
10261
  return { ok: false, reason: "error", message: e instanceof Error ? e.message : String(e) };
10240
10262
  }
10241
10263
  }
10264
+ async function runFabPrep(pcb, options) {
10265
+ const wasm3 = await loadEcadWasm();
10266
+ if (!wasm3) {
10267
+ return { ok: false, reason: "unavailable", message: "ECAD kernel WASM not loaded" };
10268
+ }
10269
+ if (typeof wasm3.ecadFabPrep !== "function") {
10270
+ return {
10271
+ ok: false,
10272
+ reason: "unavailable",
10273
+ message: "kernel WASM predates ecadFabPrep"
10274
+ };
10275
+ }
10276
+ try {
10277
+ const out = wasm3.ecadFabPrep(JSON.stringify(pcb), options ? JSON.stringify(options) : void 0);
10278
+ return { ok: true, value: out };
10279
+ } catch (e) {
10280
+ return { ok: false, reason: "error", message: e instanceof Error ? e.message : String(e) };
10281
+ }
10282
+ }
10242
10283
  async function runPcbDfm(pcb, profile, rulePackToml) {
10243
10284
  const wasm3 = await loadEcadWasm();
10244
10285
  if (!wasm3 || typeof wasm3.ecadDfmCheck !== "function")
@@ -11265,6 +11306,7 @@ __export(dist_exports, {
11265
11306
  runDrc: () => runDrc,
11266
11307
  runDrcInRegion: () => runDrcInRegion,
11267
11308
  runErc: () => runErc,
11309
+ runFabPrep: () => runFabPrep,
11268
11310
  runPcbDfm: () => runPcbDfm,
11269
11311
  sampleSequence: () => sampleSequence,
11270
11312
  sampleTrackValue: () => sampleTrackValue,
@@ -11455,6 +11497,7 @@ var init_dist = __esm({
11455
11497
  toleranceAnalyze: wasmModule7.toleranceAnalyze,
11456
11498
  thermalSolve: wasmModule7.thermalSolve,
11457
11499
  thermalSolveTransient: wasmModule7.thermalSolveTransient,
11500
+ simulateFlow: wasmModule7.simulateFlow,
11458
11501
  documentDiff: wasmModule7.documentDiff,
11459
11502
  documentDiffApply: wasmModule7.documentDiffApply,
11460
11503
  documentMerge: wasmModule7.documentMerge,
@@ -11733,6 +11776,20 @@ var init_dist = __esm({
11733
11776
  }
11734
11777
  return fn(specJson, transientJson, paramsJson, optionsJson);
11735
11778
  }
11779
+ /**
11780
+ * Steady laminar flow solve (D3Q19 BGK lattice Boltzmann): pressure
11781
+ * drop, flow rates, mass audit, optional thermal pickup, and predicted
11782
+ * receipt claims. Per-voxel fields are only returned when
11783
+ * `includeFields` is true. Inputs are JSON strings (FlowSpec, options);
11784
+ * see `vcad-kernel-flow`.
11785
+ */
11786
+ simulateFlow(specJson, optionsJson, includeFields) {
11787
+ const fn = this.kernel.simulateFlow;
11788
+ if (typeof fn !== "function") {
11789
+ throw new Error("simulateFlow is not exported by this kernel WASM build \u2014 rebuild packages/kernel-wasm");
11790
+ }
11791
+ return fn(specJson, optionsJson, includeFields);
11792
+ }
11736
11793
  circuitFn(name) {
11737
11794
  const fn = this.kernel[name];
11738
11795
  if (typeof fn !== "function") {
@@ -40791,6 +40848,20 @@ var init_CHANGELOG = __esm({
40791
40848
  CHANGELOG_default = {
40792
40849
  $schema: "./changelog.schema.json",
40793
40850
  entries: [
40851
+ {
40852
+ id: "2026-07-24-visionos-app",
40853
+ version: "0.9.4",
40854
+ date: "2026-07-24",
40855
+ category: "feat",
40856
+ title: "vcad for Apple Vision Pro",
40857
+ summary: "The full native editor ported to visionOS: documents float in a volume with the feature tree, inspector, tool palette, and AI composer around them.",
40858
+ features: [
40859
+ "native",
40860
+ "visionos",
40861
+ "assembly",
40862
+ "patterns"
40863
+ ]
40864
+ },
40794
40865
  {
40795
40866
  id: "2026-07-24-router-keepout-edge-clearance",
40796
40867
  version: "0.9.4",
@@ -40809,6 +40880,24 @@ var init_CHANGELOG = __esm({
40809
40880
  "validate_for_fab"
40810
40881
  ]
40811
40882
  },
40883
+ {
40884
+ id: "2026-07-24-router-drill-aware-vias-pad-layer-stubs",
40885
+ version: "0.9.4",
40886
+ date: "2026-07-24",
40887
+ category: "fix",
40888
+ title: "Router vias clear drills; stubs leave pads on the pad's layer",
40889
+ summary: "The routing oracle now checks hole-to-hole spacing across layer spans (two vias on disjoint spans no longer collide in the drill file), probes a via barrel on every layer it spans, and can pin a connection's terminals so stubs leave a pad on a layer the pad is actually on instead of floating on an inner layer.",
40890
+ features: [
40891
+ "pcb",
40892
+ "autorouter",
40893
+ "drc"
40894
+ ],
40895
+ mcpTools: [
40896
+ "route_nets",
40897
+ "run_drc",
40898
+ "validate_for_fab"
40899
+ ]
40900
+ },
40812
40901
  {
40813
40902
  id: "2026-07-24-lattice-gauge-mcp",
40814
40903
  version: "0.9.4",
@@ -40824,6 +40913,42 @@ var init_CHANGELOG = __esm({
40824
40913
  "simulate_lattice_gauge"
40825
40914
  ]
40826
40915
  },
40916
+ {
40917
+ id: "2026-07-24-flow-cfd-lbm",
40918
+ version: "0.9.4",
40919
+ date: "2026-07-24",
40920
+ category: "feat",
40921
+ title: "Laminar CFD: lattice-Boltzmann flow solver",
40922
+ summary: "New simulate_flow tool: steady laminar D3Q19 LBM on a voxel grid with pressure drop, flow rates, mass audit, thermal pickup, and predicted vcad.flow-claims/1 receipts.",
40923
+ features: [
40924
+ "flow",
40925
+ "cfd",
40926
+ "simulation"
40927
+ ],
40928
+ mcpTools: [
40929
+ "simulate_flow"
40930
+ ]
40931
+ },
40932
+ {
40933
+ id: "2026-07-24-fab-prep-drc-delta-receipt",
40934
+ version: "0.9.4",
40935
+ date: "2026-07-24",
40936
+ category: "feat",
40937
+ title: "One-command fab prep with a DRC-delta receipt",
40938
+ summary: "vcad fab-prep (and the fab_prep MCP tool) takes a routed board to a complete Gerber package in one call: opt-in logged rule calibration, verdict ladder, strip-and-re-route fix loop, dangling-copper prune, and a receipt that separates route-attributable violations from the board's own baseline.",
40939
+ features: [
40940
+ "pcb",
40941
+ "drc",
40942
+ "autorouter",
40943
+ "fabrication",
40944
+ "cli"
40945
+ ],
40946
+ mcpTools: [
40947
+ "fab_prep",
40948
+ "validate_for_fab",
40949
+ "export_gerber"
40950
+ ]
40951
+ },
40827
40952
  {
40828
40953
  id: "2026-07-23-router-post-route-legalization",
40829
40954
  version: "0.9.4",
@@ -51847,6 +51972,7 @@ var init_tool_metadata = __esm({
51847
51972
  simulate_photonics: { title: "Simulate Photonics", annotations: RO },
51848
51973
  analyze_antenna: { title: "Analyze Antenna", annotations: RO },
51849
51974
  solve_thermal: { title: "Solve Thermal", annotations: RO },
51975
+ simulate_flow: { title: "Simulate Flow", annotations: RO },
51850
51976
  analyze_structure: { title: "Analyze Structure", annotations: RO },
51851
51977
  simulate_neutron_shield: {
51852
51978
  title: "Simulate Neutron Shield",
@@ -51926,6 +52052,7 @@ var init_tool_metadata = __esm({
51926
52052
  annotations: RW,
51927
52053
  outputSchema: objectOut({ export_kicad: { type: "object" } })
51928
52054
  },
52055
+ fab_prep: { title: "Fab Prep", annotations: RW },
51929
52056
  validate_for_fab: { title: "Validate for Fab", annotations: RO },
51930
52057
  calc_impedance: { title: "Calc Impedance", annotations: RO },
51931
52058
  size_impedance: { title: "Size Impedance", annotations: RO },
@@ -59492,6 +59619,85 @@ function detectUnsupportedFeatures(pcb) {
59492
59619
  }
59493
59620
  return out;
59494
59621
  }
59622
+ async function fabPrep(args) {
59623
+ const ctx = resolveDocInput(args);
59624
+ const pcb = getDocPcb2(ctx.doc);
59625
+ if (!pcb) {
59626
+ return ecadError(
59627
+ "Document has no PCB \u2014 run place_components first (or open a document that has a board)"
59628
+ );
59629
+ }
59630
+ if (!await isEcadAvailable2()) {
59631
+ return ecadError(
59632
+ "fab_prep requires the kernel DRC/routing engine (ECAD WASM unavailable) \u2014 refusing to report an unverifiable fab-readiness verdict"
59633
+ );
59634
+ }
59635
+ const dryRun = Boolean(args.dry_run);
59636
+ const options = {
59637
+ calibrate_rules: Boolean(args.calibrate_rules),
59638
+ route_remaining: args.route_remaining === void 0 ? true : Boolean(args.route_remaining),
59639
+ prune_dangling: args.prune_dangling === void 0 ? true : Boolean(args.prune_dangling),
59640
+ max_rounds: Math.max(0, Math.round(Number(args.max_rounds ?? 8))),
59641
+ accept_rules: Array.isArray(args.accept_rules) ? args.accept_rules : [],
59642
+ verdict: {
59643
+ budget: Math.max(1, Math.round(Number(args.budget ?? 5e6))),
59644
+ max_cluster: Math.max(1, Math.round(Number(args.max_cluster ?? 6)))
59645
+ }
59646
+ };
59647
+ const out = await runFabPrep(pcb, options);
59648
+ if (!out.ok) {
59649
+ return ecadError(`fab_prep could not run: ${out.message}`);
59650
+ }
59651
+ const { report, pcb: fixed } = out.value;
59652
+ if (!dryRun) {
59653
+ pcb.traces = fixed.traces;
59654
+ pcb.traceArcs = fixed.traceArcs;
59655
+ pcb.vias = fixed.vias;
59656
+ pcb.rules = fixed.rules;
59657
+ }
59658
+ const payload = {
59659
+ success: true,
59660
+ ...dryRun ? { dry_run: true } : {},
59661
+ converged: report.converged,
59662
+ ...report.blocker ? { blocker: report.blocker } : {},
59663
+ headline: fabPrepHeadline(report),
59664
+ // Both numbers, always. A caller that sees only one of them is being told
59665
+ // something other than what this run established.
59666
+ drc_delta: {
59667
+ baseline_total: report.delta.baseline_total,
59668
+ final_total: report.delta.final_total,
59669
+ route_attributable_total: report.delta.route_attributable_total,
59670
+ route_attributable_blocking: report.delta.route_attributable_fixable,
59671
+ route_attributable_accepted: report.delta.route_attributable_accepted,
59672
+ by_rule: report.delta.rules,
59673
+ baseline_note: "baseline = this same board with every trace and via stripped, checked under the same rules. It is not zero on an imported fixture and is not supposed to be; the router is answerable for the difference, not the total."
59674
+ },
59675
+ connectivity: report.connectivity,
59676
+ ...report.accepted_rules.length > 0 ? { accepted_rules: report.accepted_rules } : {},
59677
+ calibration: {
59678
+ requested: report.calibration_requested,
59679
+ applied: report.calibration.applied,
59680
+ refused: report.calibration.refused
59681
+ },
59682
+ initial_verdict: report.initial_verdict,
59683
+ rounds: report.rounds,
59684
+ pruned: { traces: report.pruned_traces, vias: report.pruned_vias },
59685
+ // Cap the offender list: a non-converging run on a dense board can name
59686
+ // thousands, and the full list lives in the board's own DRC.
59687
+ offenders: report.delta.offenders.slice(0, 25),
59688
+ offender_count: report.delta.offenders.length,
59689
+ next_action: report.converged ? "export_gerber (the clean-DRC gate will now pass)" : "resolve the offenders above (fix_drc / route_nets / set_placement), then re-run fab_prep",
59690
+ ...docResultPayload(ctx)
59691
+ };
59692
+ return { content: [{ type: "text", text: JSON.stringify(payload) }] };
59693
+ }
59694
+ function fabPrepHeadline(report) {
59695
+ if (!report.converged) {
59696
+ return `NOT FAB-READY \u2014 ${report.delta.route_attributable_fixable} route-attributable violation(s) remain (${report.blocker ?? "loop did not converge"})`;
59697
+ }
59698
+ const waived = report.delta.route_attributable_accepted > 0 ? `, ${report.delta.route_attributable_accepted} waived under ${report.accepted_rules.join("+")}` : "";
59699
+ return `zero route-attributable violations${waived} \u2014 ${report.delta.final_total} on the finished board, ${report.delta.baseline_total} on the same board stripped of all routing`;
59700
+ }
59495
59701
  async function validateForFab(args) {
59496
59702
  const ctx = resolveDocInput(args);
59497
59703
  const pcb = getDocPcb2(ctx.doc);
@@ -64742,7 +64948,7 @@ async function fixDrc(args) {
64742
64948
  };
64743
64949
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
64744
64950
  }
64745
- 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;
64951
+ 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;
64746
64952
  var init_ecad2 = __esm({
64747
64953
  "src/tools/ecad.ts"() {
64748
64954
  "use strict";
@@ -65108,6 +65314,46 @@ var init_ecad2 = __esm({
65108
65314
  },
65109
65315
  required: []
65110
65316
  };
65317
+ fabPrepSchema = {
65318
+ type: "object",
65319
+ properties: {
65320
+ ...docInputProperties,
65321
+ calibrate_rules: {
65322
+ type: "boolean",
65323
+ description: "Derive and apply design-rule calibration from the board's OWN declared via classes and pre-existing footprint holes (default FALSE). Imported boards routinely carry global minima that forbid the via class they themselves declare \u2014 e.g. a 0.21/0.12mm class under a 0.2mm minDrill, which flags every via on the board. Calibration only ever relaxes a rule to the point where the board's own GIVEN geometry stops being illegal, is floored at laser-microvia limits, and records every change with its derivation in the receipt. Off by default because silently relaxing DRC rules to make a board pass is how an unbuildable board ships."
65324
+ },
65325
+ route_remaining: {
65326
+ type: "boolean",
65327
+ description: "Route or certify the connections the board arrived without, before the fix loop (default TRUE). Each unrouted connection ends as Routed, ProvedInfeasible (with a bottleneck-cut certificate), or an honest unknown \u2014 never silently dropped."
65328
+ },
65329
+ max_rounds: {
65330
+ type: "number",
65331
+ description: "Maximum strip-and-re-route rounds (default 8). Each round censuses the violations the ROUTING is answerable for, strips those nets, and hands them back to the session-probed router."
65332
+ },
65333
+ budget: {
65334
+ type: "number",
65335
+ description: "Per-cluster search budget in node expansions for the complete router (default 5,000,000). Lower is faster and yields more honest unknowns."
65336
+ },
65337
+ max_cluster: {
65338
+ type: "number",
65339
+ description: "Maximum connections coalesced into one joint search window (default 6)."
65340
+ },
65341
+ prune_dangling: {
65342
+ type: "boolean",
65343
+ description: "Remove copper that reaches no pad or pour of its net before the final DRC (default TRUE)."
65344
+ },
65345
+ accept_rules: {
65346
+ type: "array",
65347
+ items: { type: "string" },
65348
+ description: 'DRC rule names whose route-attributable violations are ACCEPTED rather than fixed (e.g. ["MinTraceWidth"]). Real fab packages ship with real, named exceptions; the difference between that and a footgun is whether the exception is written down. Waived violations are still counted, still listed, and the waiver is named in the receipt \u2014 it stops blocking the verdict, it does not hide anything. An unrecognised rule name refuses the run rather than silently accepting nothing.'
65349
+ },
65350
+ dry_run: {
65351
+ type: "boolean",
65352
+ description: "Compute the receipt without writing the fixed board back to the session."
65353
+ }
65354
+ },
65355
+ required: []
65356
+ };
65111
65357
  exportKicadSchema = {
65112
65358
  type: "object",
65113
65359
  properties: {
@@ -66724,6 +66970,14 @@ var init_ecad2 = __esm({
66724
66970
  handler: (a) => exportKicad(a),
66725
66971
  behavior: behavior({})
66726
66972
  },
66973
+ {
66974
+ name: "fab_prep",
66975
+ pack: "ecad",
66976
+ description: "Take a routed board to fab-ready in one call, and return the DRC-delta receipt that says what was actually achieved. Runs the whole pipeline: optional rule calibration (opt-in, every change logged with its derivation), the verdict ladder over unrouted connections (each ends Routed / ProvedInfeasible / honest-unknown), then a strip-and-re-route fix loop until the violations the ROUTING is answerable for reach zero, then a dangling-copper prune. Mutates the session document. THE RECEIPT IS THE POINT: on an imported fixture absolute zero is not achievable \u2014 the same board stripped of all routing already violates its own rules \u2014 so the report always gives BOTH numbers (stripped-board baseline and finished board) and charges only the difference to the router. Fails closed: a loop that does not converge returns `converged:false` with the remaining offenders and does not pretend otherwise, and `export_gerber`'s clean-DRC gate still stands \u2014 this is the supported way to GET clean, not a way around it.",
66977
+ inputSchema: fabPrepSchema,
66978
+ handler: (a) => fabPrep(a),
66979
+ behavior: behavior({ writesDoc: true, geometry: true, mount: true })
66980
+ },
66727
66981
  {
66728
66982
  name: "validate_for_fab",
66729
66983
  pack: "ecad",
@@ -69571,7 +69825,7 @@ var init_thermal = __esm({
69571
69825
  }
69572
69826
  });
69573
69827
 
69574
- // src/tools/circuit.ts
69828
+ // src/tools/flow.ts
69575
69829
  function textResult5(payload) {
69576
69830
  return {
69577
69831
  content: [
@@ -69582,6 +69836,207 @@ function textResult5(payload) {
69582
69836
  ]
69583
69837
  };
69584
69838
  }
69839
+ var vec32, shapeSchema2, flowSpecSchema, toolDefs30;
69840
+ var init_flow = __esm({
69841
+ "src/tools/flow.ts"() {
69842
+ "use strict";
69843
+ init_tool_def();
69844
+ vec32 = {
69845
+ type: "array",
69846
+ minItems: 3,
69847
+ maxItems: 3,
69848
+ items: { type: "number" }
69849
+ };
69850
+ shapeSchema2 = {
69851
+ type: "object",
69852
+ required: ["type"],
69853
+ description: 'Region shape: `{"type":"Box","min_mm":[..],"size_mm":[..]}` or `{"type":"Tube","axis":"Z","center_mm":[..2],"span_mm":[..2],"outer_radius_mm":..,"inner_radius_mm":..}`.',
69854
+ properties: {
69855
+ type: { type: "string", enum: ["Box", "Tube"] },
69856
+ min_mm: vec32,
69857
+ size_mm: vec32,
69858
+ axis: { type: "string", enum: ["X", "Y", "Z"] },
69859
+ center_mm: {
69860
+ type: "array",
69861
+ minItems: 2,
69862
+ maxItems: 2,
69863
+ items: { type: "number" }
69864
+ },
69865
+ span_mm: {
69866
+ type: "array",
69867
+ minItems: 2,
69868
+ maxItems: 2,
69869
+ items: { type: "number" }
69870
+ },
69871
+ outer_radius_mm: { type: "number" },
69872
+ inner_radius_mm: { type: "number" }
69873
+ }
69874
+ };
69875
+ flowSpecSchema = {
69876
+ type: "object",
69877
+ required: ["origin_mm", "size_mm", "divisions"],
69878
+ description: "Voxelized flow domain: an axis-aligned box discretized into cubic voxels (`size_mm[a]/divisions[a]` must agree across axes), painted with regions in order (`background` first, then `regions`). Units mm / kg / m / s / Pa / degC.",
69879
+ properties: {
69880
+ origin_mm: vec32,
69881
+ size_mm: vec32,
69882
+ divisions: {
69883
+ type: "array",
69884
+ minItems: 3,
69885
+ maxItems: 3,
69886
+ items: { type: "number" },
69887
+ description: "Voxel counts per axis (voxels must come out cubic; the cost knob)."
69888
+ },
69889
+ background: {
69890
+ type: "string",
69891
+ enum: ["solid", "fluid"],
69892
+ description: "What unpainted voxels are. Default solid."
69893
+ },
69894
+ regions: {
69895
+ type: "array",
69896
+ items: {
69897
+ type: "object",
69898
+ required: ["shape", "role"],
69899
+ properties: {
69900
+ shape: shapeSchema2,
69901
+ role: {
69902
+ type: "string",
69903
+ enum: ["solid", "fluid", "inlet", "outlet"]
69904
+ }
69905
+ }
69906
+ },
69907
+ description: "Painted in order; later regions override earlier ones. Inlet and outlet patches must sit on the domain boundary."
69908
+ },
69909
+ fluid: {
69910
+ type: "object",
69911
+ properties: {
69912
+ density_kg_m3: { type: "number" },
69913
+ viscosity_pa_s: { type: "number" }
69914
+ },
69915
+ description: "Fluid properties. Default water (998, 1.002e-3)."
69916
+ },
69917
+ inlet_velocity_m_s: {
69918
+ ...vec32,
69919
+ description: "Plug-flow inlet velocity vector, m/s."
69920
+ },
69921
+ outlet_gauge_pa: {
69922
+ type: "number",
69923
+ description: "Outlet gauge pressure, Pa. Default 0."
69924
+ },
69925
+ body_force_n_m3: {
69926
+ ...vec32,
69927
+ description: "Volumetric body force, N/m^3 (periodic channel drive; needs `options.u_ref_m_s`)."
69928
+ },
69929
+ periodic: {
69930
+ type: "array",
69931
+ minItems: 3,
69932
+ maxItems: 3,
69933
+ items: { type: "boolean" },
69934
+ description: "Per-axis periodic boundaries."
69935
+ },
69936
+ re_envelope: {
69937
+ type: "number",
69938
+ description: "Reynolds gate: the solve refuses Re above this (laminar only). Default 2300."
69939
+ },
69940
+ thermal: {
69941
+ type: "object",
69942
+ properties: {
69943
+ inlet_temp_c: { type: "number" },
69944
+ initial_temp_c: { type: "number" },
69945
+ wall_temp_c: { type: "number" },
69946
+ diffusivity_m2_s: { type: "number" },
69947
+ heat_capacity_j_kg_k: { type: "number" },
69948
+ buoyancy: {
69949
+ type: "object",
69950
+ properties: {
69951
+ beta_per_k: { type: "number" },
69952
+ t_ref_c: { type: "number" },
69953
+ gravity_m_s2: { type: "number" }
69954
+ }
69955
+ }
69956
+ },
69957
+ description: "Enable the advected temperature field (film-averaged conjugate seam; optional Boussinesq buoyancy, gated at Ra <= 1e8)."
69958
+ },
69959
+ hot_walls: {
69960
+ type: "array",
69961
+ items: {
69962
+ type: "object",
69963
+ required: ["shape", "temp_c"],
69964
+ properties: {
69965
+ shape: shapeSchema2,
69966
+ temp_c: { type: "number" }
69967
+ }
69968
+ },
69969
+ description: "Isothermal wall patches (Dirichlet) for thermal runs, painted over solid voxels adjacent to fluid."
69970
+ }
69971
+ }
69972
+ };
69973
+ toolDefs30 = [
69974
+ {
69975
+ name: "simulate_flow",
69976
+ pack: null,
69977
+ description: 'Steady laminar CFD on a voxel grid: D3Q19 BGK lattice Boltzmann with half-way bounce-back walls, velocity inlets, pressure outlets, and an optional advected temperature field (isothermal walls, Boussinesq buoyancy). Returns pressure drop, inlet/outlet flow rates, the mass-balance audit, max speed, and thermal pickup \u2014 as data plus `vcad.flow-claims/1` and unified-receipt claims. Predictions carry basis "predicted" and roll up Provisional until hardware is measured. Laminar only: fail-closed gates at Re <= 2300, the stable tau window, and Ra <= 1e8; weakly compressible (pressure noise O(Ma^2)); walls are voxel staircases. Per-voxel velocity/pressure/temperature fields are only returned with `include_fields: true` \u2014 they are grid-sized (voxel cap 2M). Steadiness is detected, not assumed: a run that never goes steady is an error, not a result.',
69978
+ inputSchema: {
69979
+ type: "object",
69980
+ required: ["spec"],
69981
+ properties: {
69982
+ spec: flowSpecSchema,
69983
+ options: {
69984
+ type: "object",
69985
+ properties: {
69986
+ u_ref_m_s: {
69987
+ type: "number",
69988
+ description: "Reference speed for unit scaling, m/s. Defaults to the inlet speed; body-force-driven cases must supply it."
69989
+ },
69990
+ max_steps: {
69991
+ type: "number",
69992
+ description: "Step budget before failing. Default 400000."
69993
+ },
69994
+ check_every: {
69995
+ type: "number",
69996
+ description: "Steadiness check interval, steps. Default 200."
69997
+ },
69998
+ steady_tol: {
69999
+ type: "number",
70000
+ description: "Relative L-inf velocity change per check. Default 1e-6."
70001
+ },
70002
+ ramp_steps: {
70003
+ type: "number",
70004
+ description: "Inlet velocity ramp length, steps. Default 1000."
70005
+ }
70006
+ }
70007
+ },
70008
+ include_fields: {
70009
+ type: "boolean",
70010
+ description: "Return the per-voxel velocity/pressure/temperature fields (grid-sized). Default false."
70011
+ }
70012
+ }
70013
+ },
70014
+ handler: (args, ctx) => {
70015
+ const a = args;
70016
+ const out = ctx.engine.simulateFlow(
70017
+ JSON.stringify(a.spec),
70018
+ JSON.stringify(a.options ?? {}),
70019
+ !!a.include_fields
70020
+ );
70021
+ return textResult5(out);
70022
+ },
70023
+ behavior: behavior({})
70024
+ }
70025
+ ];
70026
+ }
70027
+ });
70028
+
70029
+ // src/tools/circuit.ts
70030
+ function textResult6(payload) {
70031
+ return {
70032
+ content: [
70033
+ {
70034
+ type: "text",
70035
+ text: JSON.stringify(payload, null, 2)
70036
+ }
70037
+ ]
70038
+ };
70039
+ }
69585
70040
  function frequencyGrid(ac) {
69586
70041
  const list = ac.frequenciesHz;
69587
70042
  if (Array.isArray(list) && list.length > 0) return list;
@@ -69597,7 +70052,7 @@ function frequencyGrid(ac) {
69597
70052
  }
69598
70053
  return out;
69599
70054
  }
69600
- var deviceSchema, devicesSchema, toolDefs30;
70055
+ var deviceSchema, devicesSchema, toolDefs31;
69601
70056
  var init_circuit = __esm({
69602
70057
  "src/tools/circuit.ts"() {
69603
70058
  "use strict";
@@ -69634,7 +70089,7 @@ var init_circuit = __esm({
69634
70089
  items: deviceSchema,
69635
70090
  description: "Netlist as data. Node ids are dense integers with 0 = ground; the device's index in this array is its device id (used by sourceId / freeDevices / outNode references)."
69636
70091
  };
69637
- toolDefs30 = [
70092
+ toolDefs31 = [
69638
70093
  {
69639
70094
  name: "simulate_circuit",
69640
70095
  pack: null,
@@ -69743,7 +70198,7 @@ var init_circuit = __esm({
69743
70198
  tr.sampleEvery ?? 1
69744
70199
  );
69745
70200
  }
69746
- return textResult5(out);
70201
+ return textResult6(out);
69747
70202
  },
69748
70203
  behavior: behavior({})
69749
70204
  },
@@ -69806,7 +70261,7 @@ var init_circuit = __esm({
69806
70261
  ...typeof a.maxIters === "number" ? { maxIters: a.maxIters } : {}
69807
70262
  };
69808
70263
  const specJson = JSON.stringify({ devices: a.devices });
69809
- return textResult5(ctx.engine.circuitTune(specJson, JSON.stringify(tune)));
70264
+ return textResult6(ctx.engine.circuitTune(specJson, JSON.stringify(tune)));
69810
70265
  },
69811
70266
  behavior: behavior({})
69812
70267
  }
@@ -69815,7 +70270,7 @@ var init_circuit = __esm({
69815
70270
  });
69816
70271
 
69817
70272
  // src/tools/structure.ts
69818
- function textResult6(payload) {
70273
+ function textResult7(payload) {
69819
70274
  return {
69820
70275
  content: [
69821
70276
  {
@@ -69825,7 +70280,7 @@ function textResult6(payload) {
69825
70280
  ]
69826
70281
  };
69827
70282
  }
69828
- var regionSchema2, toolDefs31;
70283
+ var regionSchema2, toolDefs32;
69829
70284
  var init_structure = __esm({
69830
70285
  "src/tools/structure.ts"() {
69831
70286
  "use strict";
@@ -69852,7 +70307,7 @@ var init_structure = __esm({
69852
70307
  }
69853
70308
  }
69854
70309
  };
69855
- toolDefs31 = [
70310
+ toolDefs32 = [
69856
70311
  {
69857
70312
  name: "analyze_structure",
69858
70313
  pack: null,
@@ -69964,7 +70419,7 @@ var init_structure = __esm({
69964
70419
  resolved.mesh.indices
69965
70420
  );
69966
70421
  const solveMs = Math.round(performance.now() - started);
69967
- return textResult6({
70422
+ return textResult7({
69968
70423
  document_id: a.document_id,
69969
70424
  part: resolved.name ?? a.part,
69970
70425
  solve_ms: solveMs,
@@ -69978,7 +70433,7 @@ var init_structure = __esm({
69978
70433
  });
69979
70434
 
69980
70435
  // src/tools/em.ts
69981
- function textResult7(payload) {
70436
+ function textResult8(payload) {
69982
70437
  return {
69983
70438
  content: [
69984
70439
  {
@@ -69988,7 +70443,7 @@ function textResult7(payload) {
69988
70443
  ]
69989
70444
  };
69990
70445
  }
69991
- var paramValue4, rectRegion, bcSchema, electroShape, emSpecSchema, toolDefs32;
70446
+ var paramValue4, rectRegion, bcSchema, electroShape, emSpecSchema, toolDefs33;
69992
70447
  var init_em = __esm({
69993
70448
  "src/tools/em.ts"() {
69994
70449
  "use strict";
@@ -70144,7 +70599,7 @@ var init_em = __esm({
70144
70599
  }
70145
70600
  }
70146
70601
  };
70147
- toolDefs32 = [
70602
+ toolDefs33 = [
70148
70603
  {
70149
70604
  name: "simulate_em",
70150
70605
  pack: null,
@@ -70222,7 +70677,7 @@ var init_em = __esm({
70222
70677
  JSON.stringify(a.parameters ?? {}),
70223
70678
  JSON.stringify(a.options ?? {})
70224
70679
  );
70225
- return textResult7(out);
70680
+ return textResult8(out);
70226
70681
  },
70227
70682
  behavior: behavior({})
70228
70683
  }
@@ -70231,7 +70686,7 @@ var init_em = __esm({
70231
70686
  });
70232
70687
 
70233
70688
  // src/tools/antenna.ts
70234
- function textResult8(payload) {
70689
+ function textResult9(payload) {
70235
70690
  return {
70236
70691
  content: [
70237
70692
  {
@@ -70241,7 +70696,7 @@ function textResult8(payload) {
70241
70696
  ]
70242
70697
  };
70243
70698
  }
70244
- var paramValue5, pointSpec, antennaSpecSchema, toolDefs33;
70699
+ var paramValue5, pointSpec, antennaSpecSchema, toolDefs34;
70245
70700
  var init_antenna = __esm({
70246
70701
  "src/tools/antenna.ts"() {
70247
70702
  "use strict";
@@ -70290,7 +70745,7 @@ var init_antenna = __esm({
70290
70745
  feed_mm: pointSpec
70291
70746
  }
70292
70747
  };
70293
- toolDefs33 = [
70748
+ toolDefs34 = [
70294
70749
  {
70295
70750
  name: "analyze_antenna",
70296
70751
  pack: null,
@@ -70348,7 +70803,7 @@ var init_antenna = __esm({
70348
70803
  JSON.stringify(a.parameters ?? {}),
70349
70804
  JSON.stringify({ band: a.band, ...a.options })
70350
70805
  );
70351
- return textResult8(out);
70806
+ return textResult9(out);
70352
70807
  },
70353
70808
  behavior: behavior({})
70354
70809
  }
@@ -70357,7 +70812,7 @@ var init_antenna = __esm({
70357
70812
  });
70358
70813
 
70359
70814
  // src/tools/photonics.ts
70360
- function textResult9(payload) {
70815
+ function textResult10(payload) {
70361
70816
  return {
70362
70817
  content: [
70363
70818
  {
@@ -70367,7 +70822,7 @@ function textResult9(payload) {
70367
70822
  ]
70368
70823
  };
70369
70824
  }
70370
- var photonicsSpecSchema, toolDefs34;
70825
+ var photonicsSpecSchema, toolDefs35;
70371
70826
  var init_photonics = __esm({
70372
70827
  "src/tools/photonics.ts"() {
70373
70828
  "use strict";
@@ -70453,7 +70908,7 @@ var init_photonics = __esm({
70453
70908
  }
70454
70909
  }
70455
70910
  };
70456
- toolDefs34 = [
70911
+ toolDefs35 = [
70457
70912
  {
70458
70913
  name: "simulate_photonics",
70459
70914
  pack: null,
@@ -70500,7 +70955,7 @@ var init_photonics = __esm({
70500
70955
  JSON.stringify(a.spec),
70501
70956
  JSON.stringify(a.options ?? {})
70502
70957
  );
70503
- return textResult9(out);
70958
+ return textResult10(out);
70504
70959
  },
70505
70960
  behavior: behavior({})
70506
70961
  }
@@ -70509,7 +70964,7 @@ var init_photonics = __esm({
70509
70964
  });
70510
70965
 
70511
70966
  // src/tools/neutronics.ts
70512
- function textResult10(payload) {
70967
+ function textResult11(payload) {
70513
70968
  return {
70514
70969
  content: [
70515
70970
  {
@@ -70519,7 +70974,7 @@ function textResult10(payload) {
70519
70974
  ]
70520
70975
  };
70521
70976
  }
70522
- var paramValue6, shieldSpecSchema, toolDefs35;
70977
+ var paramValue6, shieldSpecSchema, toolDefs36;
70523
70978
  var init_neutronics = __esm({
70524
70979
  "src/tools/neutronics.ts"() {
70525
70980
  "use strict";
@@ -70590,7 +71045,7 @@ var init_neutronics = __esm({
70590
71045
  }
70591
71046
  }
70592
71047
  };
70593
- toolDefs35 = [
71048
+ toolDefs36 = [
70594
71049
  {
70595
71050
  name: "simulate_neutron_shield",
70596
71051
  pack: null,
@@ -70613,7 +71068,7 @@ var init_neutronics = __esm({
70613
71068
  JSON.stringify(a.spec),
70614
71069
  JSON.stringify(a.parameters ?? {})
70615
71070
  );
70616
- return textResult10(out);
71071
+ return textResult11(out);
70617
71072
  },
70618
71073
  behavior: behavior({})
70619
71074
  }
@@ -70622,7 +71077,7 @@ var init_neutronics = __esm({
70622
71077
  });
70623
71078
 
70624
71079
  // src/tools/qcd.ts
70625
- function textResult11(payload) {
71080
+ function textResult12(payload) {
70626
71081
  return {
70627
71082
  content: [
70628
71083
  {
@@ -70632,7 +71087,7 @@ function textResult11(payload) {
70632
71087
  ]
70633
71088
  };
70634
71089
  }
70635
- var simSpecSchema, toolDefs36;
71090
+ var simSpecSchema, toolDefs37;
70636
71091
  var init_qcd = __esm({
70637
71092
  "src/tools/qcd.ts"() {
70638
71093
  "use strict";
@@ -70722,7 +71177,7 @@ var init_qcd = __esm({
70722
71177
  }
70723
71178
  }
70724
71179
  };
70725
- toolDefs36 = [
71180
+ toolDefs37 = [
70726
71181
  {
70727
71182
  name: "simulate_lattice_gauge",
70728
71183
  pack: null,
@@ -70735,7 +71190,7 @@ var init_qcd = __esm({
70735
71190
  handler: (args, ctx) => {
70736
71191
  const a = args;
70737
71192
  const out = ctx.engine.latticeGaugeSimulate(JSON.stringify(a.spec));
70738
- return textResult11(out);
71193
+ return textResult12(out);
70739
71194
  },
70740
71195
  behavior: behavior({})
70741
71196
  }
@@ -70864,7 +71319,7 @@ function predictPhysicsTool(args, engine) {
70864
71319
  ]
70865
71320
  };
70866
71321
  }
70867
- var PHYSICS_DOMAIN, ORACLE, TIER_RESOLUTION, regionSchema3, predictPhysicsSchema, round52, toolDefs37;
71322
+ var PHYSICS_DOMAIN, ORACLE, TIER_RESOLUTION, regionSchema3, predictPhysicsSchema, round52, toolDefs38;
70868
71323
  var init_physics2 = __esm({
70869
71324
  "src/tools/physics.ts"() {
70870
71325
  "use strict";
@@ -70977,7 +71432,7 @@ var init_physics2 = __esm({
70977
71432
  }
70978
71433
  };
70979
71434
  round52 = (v) => Number(v.toPrecision(5));
70980
- toolDefs37 = [
71435
+ toolDefs38 = [
70981
71436
  {
70982
71437
  name: "predict_physics",
70983
71438
  pack: null,
@@ -71219,7 +71674,7 @@ function applySetParam(doc, node, path, value) {
71219
71674
  }
71220
71675
  cursor[parts[parts.length - 1]] = value;
71221
71676
  }
71222
- var lastReports, mechanicalProcessEnum, pcbProcessEnum, processEnum, dfmCheckSchema, dfmExplainSchema, dfmSuggestFixSchema, dfmApplyFixSchema, toolDefs38;
71677
+ var lastReports, mechanicalProcessEnum, pcbProcessEnum, processEnum, dfmCheckSchema, dfmExplainSchema, dfmSuggestFixSchema, dfmApplyFixSchema, toolDefs39;
71223
71678
  var init_dfm2 = __esm({
71224
71679
  "src/tools/dfm.ts"() {
71225
71680
  "use strict";
@@ -71316,7 +71771,7 @@ var init_dfm2 = __esm({
71316
71771
  },
71317
71772
  required: ["issue_id"]
71318
71773
  };
71319
- toolDefs38 = [
71774
+ toolDefs39 = [
71320
71775
  {
71321
71776
  name: "dfm_check",
71322
71777
  pack: "dfm",
@@ -71515,7 +71970,7 @@ function renderedOf(engine, doc) {
71515
71970
  "document has no sheet-metal part (evaluation produced no sheetMetal bundle)"
71516
71971
  );
71517
71972
  }
71518
- function textResult12(payload) {
71973
+ function textResult13(payload) {
71519
71974
  return {
71520
71975
  content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
71521
71976
  };
@@ -71557,7 +72012,7 @@ function sheetMetalCreate(input, engine) {
71557
72012
  const errors = rendered.violations.filter(
71558
72013
  (v) => v.severity === "Error"
71559
72014
  ).length;
71560
- return textResult12({
72015
+ return textResult13({
71561
72016
  document_id: documentId,
71562
72017
  model: rendered.model,
71563
72018
  flat: {
@@ -71573,7 +72028,7 @@ function sheetMetalUnfold(input, engine) {
71573
72028
  const doc = getSession(String(a.document_id ?? ""));
71574
72029
  const rendered = renderedOf(engine, doc);
71575
72030
  const includeDxf = a.include_dxf !== false;
71576
- return textResult12({
72031
+ return textResult13({
71577
72032
  flat_pattern: rendered.flatPattern,
71578
72033
  ...includeDxf ? { dxf: rendered.dxf } : {},
71579
72034
  note: "The DXF is a fab-ready merged silhouette: one closed exterior LWPOLYLINE plus hole loops on CUT, DASHED bend centerlines (on the allowance midline) on BEND_UP/BEND_DOWN, and any surface markings as open polylines on ENGRAVE (select the laser-marking/engraving service for that layer in the fab's UI). DXF carries no bend angles \u2014 you enter those in the fab's UI. For zero data entry, export the folded body as STEP instead (export_cad with a .step filename); bends are auto-detected by the shop, but radii/K must match their tooling at model time (use shop_profile in sheet_metal_create)."
@@ -71581,7 +72036,7 @@ function sheetMetalUnfold(input, engine) {
71581
72036
  }
71582
72037
  function sheetMetalMaterials(_input, engine) {
71583
72038
  const materials = engine.getSheetMetalMaterials();
71584
- return textResult12({
72039
+ return textResult13({
71585
72040
  materials,
71586
72041
  note: 'Pass any `name` (e.g. `"al-soft"`, `"steel-mild"`) as the `material` field of `sheet_metal_create`. Aliases like `"aluminum"`, `"stainless"`, `"6061-T6"` also resolve.'
71587
72042
  });
@@ -71590,13 +72045,13 @@ function sheetMetalBendTable(input, engine) {
71590
72045
  const a = input ?? {};
71591
72046
  if (typeof a.shop_profile === "string" && a.shop_profile.length > 0) {
71592
72047
  const catalog = engine.getSheetMetalShopCatalog(a.shop_profile);
71593
- return textResult12({
72048
+ return textResult13({
71594
72049
  catalog,
71595
72050
  note: `Published catalog for ${catalog.name} (retrieved ${catalog.retrieved}). inside_radius_mm is FIXED per material/thickness \u2014 pass shop_profile: "${catalog.id}" to sheet_metal_create and omit per-bend radius to use it.`
71596
72051
  });
71597
72052
  }
71598
72053
  const table = engine.getSheetMetalBendTable();
71599
- return textResult12({
72054
+ return textResult13({
71600
72055
  table,
71601
72056
  note: "K-factors are interpolated by closest R/t for the chosen material. To override K for a specific bend, pass `manual_k` on that flange in sheet_metal_create."
71602
72057
  });
@@ -71618,7 +72073,7 @@ function sheetMetalCheck(input, engine) {
71618
72073
  const errors = result.violations.filter(
71619
72074
  (v) => v.severity === "Error"
71620
72075
  ).length;
71621
- return textResult12({
72076
+ return textResult13({
71622
72077
  shop: result.shop,
71623
72078
  violations: result.violations,
71624
72079
  shop_ready: result.violations.length === 0,
@@ -71670,7 +72125,7 @@ function sheetMetalNest(input, engine) {
71670
72125
  footprints,
71671
72126
  params
71672
72127
  );
71673
- return textResult12({
72128
+ return textResult13({
71674
72129
  parts: footprints,
71675
72130
  result,
71676
72131
  summary: `${result.placements.length} placements across ${result.sheets_used} sheet(s); ${result.utilization_pct.toFixed(1)}% utilization${result.unplaceable.length > 0 ? `; ${result.unplaceable.length} unplaceable (oversize)` : ""}.`
@@ -71683,7 +72138,7 @@ function sheetMetalSequence3(input, engine) {
71683
72138
  if (!steps) {
71684
72139
  throw new Error("document has no sheet-metal part");
71685
72140
  }
71686
- return textResult12({
72141
+ return textResult13({
71687
72142
  count: steps.length,
71688
72143
  steps,
71689
72144
  note: "Form each bend in order. compensated_angle_rad is the angle to dial in on the brake \u2014 springs back to angle_rad once released."
@@ -71706,7 +72161,7 @@ function sheetMetalSuggestFix(input, engine) {
71706
72161
  violation: v,
71707
72162
  fix: suggestFix(v)
71708
72163
  }));
71709
- return textResult12({
72164
+ return textResult13({
71710
72165
  shop: result.shop,
71711
72166
  count: suggestions.length,
71712
72167
  suggestions,
@@ -71809,13 +72264,13 @@ function sheetMetalCost(input, engine) {
71809
72264
  if (!result) {
71810
72265
  throw new Error("document has no sheet-metal part");
71811
72266
  }
71812
- return textResult12({
72267
+ return textResult13({
71813
72268
  breakdown: result.breakdown,
71814
72269
  rates: result.rates,
71815
72270
  summary: `${result.breakdown.currency} ${result.breakdown.total_each.toFixed(2)} each @ qty ${result.breakdown.quantity} (mass ${result.breakdown.mass_kg_each.toFixed(3)} kg, ${result.breakdown.cut_length_m.toFixed(2)} m cut, ${result.breakdown.bends} bend(s)).`
71816
72271
  });
71817
72272
  }
71818
- var sheetMetalCreateSchema, sheetMetalUnfoldSchema, sheetMetalMaterialsSchema, sheetMetalBendTableSchema, sheetMetalCheckSchema, sheetMetalNestSchema, sheetMetalSequenceSchema, sheetMetalSuggestFixSchema, sheetMetalCostSchema, toolDefs39;
72273
+ var sheetMetalCreateSchema, sheetMetalUnfoldSchema, sheetMetalMaterialsSchema, sheetMetalBendTableSchema, sheetMetalCheckSchema, sheetMetalNestSchema, sheetMetalSequenceSchema, sheetMetalSuggestFixSchema, sheetMetalCostSchema, toolDefs40;
71819
72274
  var init_sheet_metal2 = __esm({
71820
72275
  "src/tools/sheet-metal.ts"() {
71821
72276
  "use strict";
@@ -72076,7 +72531,7 @@ var init_sheet_metal2 = __esm({
72076
72531
  },
72077
72532
  required: ["document_id"]
72078
72533
  };
72079
- toolDefs39 = [
72534
+ toolDefs40 = [
72080
72535
  {
72081
72536
  name: "sheet_metal_create",
72082
72537
  pack: "sheet_metal",
@@ -72321,7 +72776,7 @@ function simulateStrike(input, engine) {
72321
72776
  }
72322
72777
  return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
72323
72778
  }
72324
- var cents, MATERIAL_ALIASES, simulateStrikeSchema, round22, round4, toolDefs40;
72779
+ var cents, MATERIAL_ALIASES, simulateStrikeSchema, round22, round4, toolDefs41;
72325
72780
  var init_acoustics = __esm({
72326
72781
  "src/tools/acoustics.ts"() {
72327
72782
  "use strict";
@@ -72395,7 +72850,7 @@ var init_acoustics = __esm({
72395
72850
  };
72396
72851
  round22 = (x) => Math.round(x * 100) / 100;
72397
72852
  round4 = (x) => Math.round(x * 1e4) / 1e4;
72398
- toolDefs40 = [
72853
+ toolDefs41 = [
72399
72854
  {
72400
72855
  name: "simulate_strike",
72401
72856
  pack: "sheet_metal",
@@ -72546,7 +73001,7 @@ function importStep(input, engine) {
72546
73001
  ]
72547
73002
  };
72548
73003
  }
72549
- var MAX_STEP_BYTES, importStepSchema, toolDefs41;
73004
+ var MAX_STEP_BYTES, importStepSchema, toolDefs42;
72550
73005
  var init_import = __esm({
72551
73006
  "src/tools/import.ts"() {
72552
73007
  "use strict";
@@ -72578,7 +73033,7 @@ var init_import = __esm({
72578
73033
  }
72579
73034
  }
72580
73035
  };
72581
- toolDefs41 = [
73036
+ toolDefs42 = [
72582
73037
  {
72583
73038
  name: "import_step",
72584
73039
  pack: null,
@@ -72706,7 +73161,7 @@ function importEagle(_input) {
72706
73161
  "Eagle (.brd) import is not yet supported. Export your board from Eagle as KiCad (File > Export > KiCad .kicad_pcb) and use import_kicad instead."
72707
73162
  );
72708
73163
  }
72709
- var MAX_PCB_BYTES, importKicadSchema, importEagleSchema, toolDefs42;
73164
+ var MAX_PCB_BYTES, importKicadSchema, importEagleSchema, toolDefs43;
72710
73165
  var init_import_pcb = __esm({
72711
73166
  "src/tools/import-pcb.ts"() {
72712
73167
  "use strict";
@@ -72747,7 +73202,7 @@ var init_import_pcb = __esm({
72747
73202
  }
72748
73203
  }
72749
73204
  };
72750
- toolDefs42 = [
73205
+ toolDefs43 = [
72751
73206
  {
72752
73207
  name: "import_kicad",
72753
73208
  pack: "ecad",
@@ -72843,7 +73298,7 @@ Compressed URL param: ${encoded.length} bytes`
72843
73298
  ]
72844
73299
  };
72845
73300
  }
72846
- var openInBrowserSchema, toolDefs43;
73301
+ var openInBrowserSchema, toolDefs44;
72847
73302
  var init_share = __esm({
72848
73303
  "src/tools/share.ts"() {
72849
73304
  "use strict";
@@ -72867,7 +73322,7 @@ var init_share = __esm({
72867
73322
  }
72868
73323
  }
72869
73324
  };
72870
- toolDefs43 = [
73325
+ toolDefs44 = [
72871
73326
  {
72872
73327
  name: "open_in_browser",
72873
73328
  pack: null,
@@ -73192,7 +73647,7 @@ function batchReset(input) {
73192
73647
  };
73193
73648
  }
73194
73649
  }
73195
- var simulations, MAX_TRAJECTORY, envRecords, batchGroups, createRobotEnvSchema, gymStepSchema, gymResetSchema, gymObserveSchema, gymCloseSchema, batchCreateEnvsSchema, batchStepSchema, batchResetSchema, toolDefs44;
73650
+ var simulations, MAX_TRAJECTORY, envRecords, batchGroups, createRobotEnvSchema, gymStepSchema, gymResetSchema, gymObserveSchema, gymCloseSchema, batchCreateEnvsSchema, batchStepSchema, batchResetSchema, toolDefs45;
73196
73651
  var init_gym = __esm({
73197
73652
  "src/tools/gym.ts"() {
73198
73653
  "use strict";
@@ -73344,7 +73799,7 @@ var init_gym = __esm({
73344
73799
  },
73345
73800
  required: ["batch_id"]
73346
73801
  };
73347
- toolDefs44 = [
73802
+ toolDefs45 = [
73348
73803
  {
73349
73804
  name: "create_robot_env",
73350
73805
  pack: "physics",
@@ -73702,7 +74157,7 @@ async function renderMolecule(input) {
73702
74157
  return fail(err7);
73703
74158
  }
73704
74159
  }
73705
- var loadStructureSchema, inspectMoleculeSchema, configSchema, minimizeEnergySchema, mdRunSchema, designMaterialSchema, homogenizeMaterialSchema, renderMoleculeSchema, CPK, DEFAULT_RADIUS, toolDefs45;
74160
+ var loadStructureSchema, inspectMoleculeSchema, configSchema, minimizeEnergySchema, mdRunSchema, designMaterialSchema, homogenizeMaterialSchema, renderMoleculeSchema, CPK, DEFAULT_RADIUS, toolDefs46;
73706
74161
  var init_atoms2 = __esm({
73707
74162
  "src/tools/atoms.ts"() {
73708
74163
  "use strict";
@@ -73837,7 +74292,7 @@ var init_atoms2 = __esm({
73837
74292
  S: 1.05,
73838
74293
  P: 1.07
73839
74294
  };
73840
- toolDefs45 = [
74295
+ toolDefs46 = [
73841
74296
  {
73842
74297
  name: "load_structure",
73843
74298
  pack: "atoms",
@@ -75045,7 +75500,7 @@ function errorResult(text) {
75045
75500
  isError: true
75046
75501
  };
75047
75502
  }
75048
- var recordSimulationSchema, SVG_SCALE2, MAX_STEPS, MIN_STEPS, DEFAULT_WIDTH_PX2, MIN_WIDTH_PX, MAX_WIDTH_PX, DEFAULT_FPS, MIN_FPS, MAX_FPS, toolDefs46;
75503
+ var recordSimulationSchema, SVG_SCALE2, MAX_STEPS, MIN_STEPS, DEFAULT_WIDTH_PX2, MIN_WIDTH_PX, MAX_WIDTH_PX, DEFAULT_FPS, MIN_FPS, MAX_FPS, toolDefs47;
75049
75504
  var init_record2 = __esm({
75050
75505
  "src/tools/record.ts"() {
75051
75506
  "use strict";
@@ -75109,7 +75564,7 @@ var init_record2 = __esm({
75109
75564
  DEFAULT_FPS = 30;
75110
75565
  MIN_FPS = 1;
75111
75566
  MAX_FPS = 60;
75112
- toolDefs46 = [
75567
+ toolDefs47 = [
75113
75568
  {
75114
75569
  name: "record_simulation",
75115
75570
  pack: "physics",
@@ -75202,7 +75657,7 @@ ${text}`
75202
75657
  ]
75203
75658
  };
75204
75659
  }
75205
- var getChangelogSchema, toolDefs47;
75660
+ var getChangelogSchema, toolDefs48;
75206
75661
  var init_changelog2 = __esm({
75207
75662
  "src/tools/changelog.ts"() {
75208
75663
  "use strict";
@@ -75242,7 +75697,7 @@ var init_changelog2 = __esm({
75242
75697
  }
75243
75698
  }
75244
75699
  };
75245
- toolDefs47 = [
75700
+ toolDefs48 = [
75246
75701
  {
75247
75702
  name: "get_changelog",
75248
75703
  pack: null,
@@ -75391,7 +75846,7 @@ function getSimVersion(input) {
75391
75846
  ]
75392
75847
  };
75393
75848
  }
75394
- var getSimReplaySchema, getSimVersionSchema, toolDefs48;
75849
+ var getSimReplaySchema, getSimVersionSchema, toolDefs49;
75395
75850
  var init_sim_replay = __esm({
75396
75851
  "src/tools/sim-replay.ts"() {
75397
75852
  "use strict";
@@ -75419,7 +75874,7 @@ var init_sim_replay = __esm({
75419
75874
  },
75420
75875
  required: ["env_id"]
75421
75876
  };
75422
- toolDefs48 = [
75877
+ toolDefs49 = [
75423
75878
  {
75424
75879
  name: "get_sim_replay",
75425
75880
  pack: null,
@@ -75538,7 +75993,7 @@ async function getOrderFeed(input, store, user) {
75538
75993
  version: version2
75539
75994
  });
75540
75995
  }
75541
- var toUsd4, getOrderFeedSchema, toolDefs49;
75996
+ var toUsd4, getOrderFeedSchema, toolDefs50;
75542
75997
  var init_order_feed = __esm({
75543
75998
  "src/tools/order-feed.ts"() {
75544
75999
  "use strict";
@@ -75557,7 +76012,7 @@ var init_order_feed = __esm({
75557
76012
  },
75558
76013
  required: ["document_id"]
75559
76014
  };
75560
- toolDefs49 = [
76015
+ toolDefs50 = [
75561
76016
  {
75562
76017
  name: "get_order_feed",
75563
76018
  pack: "fabricate",
@@ -75762,7 +76217,7 @@ ${issues.map((i) => i.track >= 0 ? `track[${i.track}]: ${i.problem}` : i.problem
75762
76217
  note: "Timeline set. Preview with render_sequence, ship with export_video."
75763
76218
  });
75764
76219
  }
75765
- function vec32(v) {
76220
+ function vec33(v) {
75766
76221
  return v ? [v.x, v.y, v.z] : [0, 0, 0];
75767
76222
  }
75768
76223
  function hasParamTracks(timeline) {
@@ -75805,7 +76260,7 @@ function cameraChannels(frames) {
75805
76260
  }
75806
76261
  function explodeDirections(doc, transforms) {
75807
76262
  const ids = [...transforms.keys()];
75808
- const pts = ids.map((id) => vec32(transforms.get(id).translation));
76263
+ const pts = ids.map((id) => vec33(transforms.get(id).translation));
75809
76264
  const c = pts.reduce((acc, p) => [acc[0] + p[0], acc[1] + p[1], acc[2] + p[2]], [0, 0, 0]).map((v) => v / Math.max(1, pts.length));
75810
76265
  let extent = 0;
75811
76266
  for (const p of pts) {
@@ -75863,7 +76318,7 @@ function buildSequenceGlb(doc, timeline, frames, engine) {
75863
76318
  for (const inst of instances) {
75864
76319
  const rec = perInstance.get(inst.instanceId);
75865
76320
  const t2 = world.get(inst.instanceId) ?? inst.transform;
75866
- const translation = vec32(t2?.translation);
76321
+ const translation = vec33(t2?.translation);
75867
76322
  if (frame.explode > 0) {
75868
76323
  const d = dirs.get(inst.instanceId) ?? [0, 0, 0];
75869
76324
  translation[0] += d[0] * frame.explode;
@@ -76459,7 +76914,7 @@ ${issues.map((i) => i.problem).join("\n")}`
76459
76914
  note: "Rollout compiled to the document timeline. Preview with render_sequence, ship with export_video."
76460
76915
  });
76461
76916
  }
76462
- var MAX_FRAMES, MAX_GEO_SAMPLES, MAX_VERIFY_FRAMES, INLINE_GLB_MAX_BASE64, DEFAULT_WIDTH_PX3, MIN_WIDTH_PX2, MAX_WIDTH_PX2, SVG_SCALE3, KEYFRAMES_DESCRIPTION, animateSchema, renderSequenceSchema, CAMERA_NODE, exportVideoSchema, ffmpegAvailable, timelineFromSimulationSchema, toolDefs50;
76917
+ var MAX_FRAMES, MAX_GEO_SAMPLES, MAX_VERIFY_FRAMES, INLINE_GLB_MAX_BASE64, DEFAULT_WIDTH_PX3, MIN_WIDTH_PX2, MAX_WIDTH_PX2, SVG_SCALE3, KEYFRAMES_DESCRIPTION, animateSchema, renderSequenceSchema, CAMERA_NODE, exportVideoSchema, ffmpegAvailable, timelineFromSimulationSchema, toolDefs51;
76463
76918
  var init_animate = __esm({
76464
76919
  "src/tools/animate.ts"() {
76465
76920
  "use strict";
@@ -76611,7 +77066,7 @@ var init_animate = __esm({
76611
77066
  },
76612
77067
  required: ["env_id", "document_id"]
76613
77068
  };
76614
- toolDefs50 = [
77069
+ toolDefs51 = [
76615
77070
  {
76616
77071
  name: "animate",
76617
77072
  pack: null,
@@ -77450,6 +77905,7 @@ var init_server3 = __esm({
77450
77905
  init_particle();
77451
77906
  init_tolerance();
77452
77907
  init_thermal();
77908
+ init_flow();
77453
77909
  init_circuit();
77454
77910
  init_structure();
77455
77911
  init_em();
@@ -77475,8 +77931,8 @@ var init_server3 = __esm({
77475
77931
  init_order_feed();
77476
77932
  init_animate();
77477
77933
  PKG_VERSION = (() => {
77478
- if ("0.9.4-main.17") {
77479
- return "0.9.4-main.17";
77934
+ if ("0.9.4-main.19") {
77935
+ return "0.9.4-main.19";
77480
77936
  }
77481
77937
  try {
77482
77938
  const req = createRequire2(import.meta.url);
@@ -77485,8 +77941,8 @@ var init_server3 = __esm({
77485
77941
  return "0.0.0";
77486
77942
  }
77487
77943
  })();
77488
- BUILD_SHA = "cfb504c93d13b7e0339f79b4b8863ffeb1ca83b1";
77489
- BUILD_TIME = "2026-07-24T20:18:23.601Z";
77944
+ BUILD_SHA = "7f18700be295061f294b748d3d7aa957f34ddd1c";
77945
+ BUILD_TIME = "2026-07-25T13:19:21.672Z";
77490
77946
  SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
77491
77947
  VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
77492
77948
  INSTANCE_ID = randomUUID6().slice(0, 8);
@@ -77547,8 +78003,8 @@ var init_server3 = __esm({
77547
78003
  ...toolDefs35,
77548
78004
  ...toolDefs36,
77549
78005
  ...toolDefs37,
77550
- ...toolDefs2,
77551
78006
  ...toolDefs38,
78007
+ ...toolDefs2,
77552
78008
  ...toolDefs39,
77553
78009
  ...toolDefs40,
77554
78010
  ...toolDefs41,
@@ -77558,11 +78014,12 @@ var init_server3 = __esm({
77558
78014
  ...toolDefs45,
77559
78015
  ...toolDefs46,
77560
78016
  ...toolDefs47,
78017
+ ...toolDefs48,
77561
78018
  ...toolDefs20,
77562
78019
  ...toolDefs19,
77563
- ...toolDefs48,
77564
78020
  ...toolDefs49,
77565
- ...toolDefs50
78021
+ ...toolDefs50,
78022
+ ...toolDefs51
77566
78023
  ];
77567
78024
  LIST_TOOL_ORDER = [
77568
78025
  // ── Session lifecycle ──────────────────────────────────────
@@ -77631,6 +78088,7 @@ var init_server3 = __esm({
77631
78088
  "simulate_photonics",
77632
78089
  "analyze_antenna",
77633
78090
  "solve_thermal",
78091
+ "simulate_flow",
77634
78092
  "analyze_structure",
77635
78093
  "simulate_neutron_shield",
77636
78094
  "simulate_lattice_gauge",
@@ -77744,6 +78202,7 @@ var init_server3 = __esm({
77744
78202
  "run_erc",
77745
78203
  "export_gerber",
77746
78204
  "export_kicad",
78205
+ "fab_prep",
77747
78206
  "validate_for_fab",
77748
78207
  "calc_impedance",
77749
78208
  "size_impedance",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcad/mcp",
3
- "version": "0.9.4-main.17",
3
+ "version": "0.9.4-main.19",
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": "cfb504c93d13b7e0339f79b4b8863ffeb1ca83b1",
23
- "builtAt": "2026-07-24T20:18:23.601Z"
22
+ "sha": "7f18700be295061f294b748d3d7aa957f34ddd1c",
23
+ "builtAt": "2026-07-25T13:19:21.672Z"
24
24
  }
25
25
  }
Binary file