Introduction

openie-cad is a Rust-first, WASM-native CAD + ECAD + Multi-Physics simulation engine. One codebase, one language, one file per product.

The thesis: the design IS the simulation. Every geometric entity carries physics from the moment it's created. No export/import/mesh-setup loop. No 80% of engineering time lost at the design–simulation interface.

Three entry points

You can use openie-cad in three ways:

What's substrate-native vs commercial

openie-cad ships several algorithms that, as of June 2026, exist in no other open-source codebase under an MIT/Apache license:

AlgorithmPaper / OriginWhere in substrate
LCIS BGA escape routingKong/Yan/Ozdal ICCAD 2007cad-router::bga_escape
FDTD Huygens NTFF antenna patternBalanis §6.7 textbookcad-sim::antenna
Djordjevic-Sarkar + Cannonball-Huray 60 GHz lossShlepnev 2015, Simonovich 2015cad-sim::signal
preCICE-equivalent IQN-ILS couplingDegroote 2009 / Chourdakis 2021cad-sim::coupling
SALT shallow-light Steiner topologyChen et al. ICCAD 2017cad-router::steiner
Fang DAC 2024 length-match co-optimizationFang et al. arXiv 2407.19195cad-router::length_tuning

Every commercial equivalent — Altium, Cadence, Ansys, Comsol — charges thousands of dollars per seat per year for the same capabilities. The algorithm theory pages document each one with the originating paper cited.

Quickstart

See Quickstart for a 5-minute walkthrough of:

  1. Routing a small PCB via MCP.
  2. Generating a CAM toolpath.
  3. Running a multi-physics coupled simulation.

Quickstart

Five minutes from zero to a routed PCB + a CAM toolpath + a coupled multi-physics simulation, all via the hosted MCP at cad.openie.dev.

Prerequisites

You need:

  • An HTTP client (curl is fine).
  • An MCP-capable agent (Claude Code, Claude Desktop, or any tool-use LLM).

1. Start a session

The first call returns a session ID. Reuse it on subsequent calls to keep the per-session project in memory.

curl -s https://cad.openie.dev/mcp \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc":"2.0","id":1,
    "method":"tools/call",
    "params":{
      "name":"create_box",
      "arguments":{"width":10,"height":20,"depth":5}
    }
  }'

The response header includes Mcp-Session-Id: <uuid>; pass that as Mcp-Session-Id: ... on every subsequent call.

2. Design a PCB

The complete aether-t-style flow looks like:

import_kicad / import_zener           # ingest a design
  → run_placement                     # force-directed component placement
  → check_bga_escape_feasibility      # is the BGA routable on this tier?
  → run_autorouter(
      bgas=[{footprint_ref:"U8", ...}],
      length_match_groups=[{name:"DDR_DIQ1", ...}]
    )                                 # full routing pass
  → render_pcb_svg                    # static SVG preview
  → /viewer/kicad?session=<id>        # live interactive viewer
  → export_gerber / generate_jlcpcb_bom / generate_jlcpcb_cpl

Walk through every tool in the MCP Tool Reference.

3. Run a CAM operation

Generate G-code for a rectangular pocket:

curl -s https://cad.openie.dev/mcp \
  -H 'Content-Type: application/json' \
  -H 'Mcp-Session-Id: YOUR_SESSION_ID' \
  -d '{
    "jsonrpc":"2.0","id":2,
    "method":"tools/call",
    "params":{
      "name":"generate_pocket_gcode",
      "arguments":{
        "width_mm": 50, "height_mm": 30, "depth_mm": 8,
        "cutter_diameter_mm": 6, "step_down_mm": 2
      }
    }
  }'

The result includes ready-to-run RS-274 G-code.

4. Run a coupled multi-physics simulation

Test which relaxation scheme converges fastest for a given coupling stiffness:

curl -s https://cad.openie.dev/mcp \
  -H 'Content-Type: application/json' \
  -H 'Mcp-Session-Id: YOUR_SESSION_ID' \
  -d '{
    "jsonrpc":"2.0","id":3,
    "method":"tools/call",
    "params":{
      "name":"simulate_coupled",
      "arguments":{
        "coupling_alpha":0.9,"coupling_beta":0.95,
        "relaxation":"iqn_ils","iqn_history_max":10
      }
    }
  }'

The result reports iteration count, final residual, and the analytic fixed point for comparison.

Next

MCP Tool Reference

Every tool in the cad-agent registry (the kernel-side tool set) served at https://cad.openie.dev/mcp. Auto-generated from the substrate source on each release. For the full served surface, including planner-server tools, see https://cad.openie.dev/reference/mcp-tools/.

Call any tool by sending an MCP tools/call request with the tool's name and the documented arguments. The session ID returned from the first call keeps the project in memory for subsequent calls — see the Quickstart for the round-trip pattern.


81 tools registered.


add_track

Add copper track segments between consecutive points on the \ project's PCB. Args: net (default "VCC"), width (mm, \ default 0.25), layer (e.g. "F.Cu", default "F.Cu"), \ points (REQUIRED array of [x, y] in mm, at least 2 points). \ If the project has no PCB yet, a default 100x80 mm 4-layer \ board is created first. Returns the segment count, layer, net, \ and width. Errors: missing/fewer than 2 points, malformed \ [x, y] pairs, or an unknown layer name.

Arguments

  • net (string)
  • width (number)
  • layer (string)
  • points (array)

add_via

Add a through-hole via (F.Cu to B.Cu) on the project's PCB. \ Args: x, y (mm, default 0), drill (mm, default 0.3, must \ be positive), pad (mm, default drill + 0.5), net (default \ "GND"). If the project has no PCB yet, a default 100x80 mm \ 4-layer board is created first. Returns position, drill, pad, \ and net. Errors when drill is not positive.

Arguments

  • x (number)
  • y (number)
  • drill (number)
  • pad (number)
  • net (string)

certify_drc

Certify the project's PCB against the DRC engine and return \ a certification receipt (CERTIFIED or REFUSED) bound by \ SHA-256 to the exact board certified. Runs the five DRC \ checks — track widths, via sizes, clearance, hole spacing, \ copper edge — covering six violation types (TrackWidth, \ ViaDrill, ViaAnnularRing, Clearance, HoleSpacing, \ CopperEdge). Silkscreen, Courtyard, Connectivity, and \ Unconnected have no check function, are NOT run, and are \ named on the receipt. CERTIFIED iff the DRC report passed; \ warnings are listed and never fail. Re-running verification \ against a tampered board fails the hash check. No inputs. \ Errors when the project has no PCB yet — create one with \ create_board or import_kicad first.

No arguments.


check_bga_escape_feasibility

Wave 63.A — answer the 'can this BGA escape at all on this fab tier?' \ question before the autorouter chews through 100+ net pairs only to \ fail. Models the BGA as a rows×cols pad grid; computes via geometry \ from the fab tier (Standard JLCPCB = 0.45mm via pad, HDI tier = 0.25mm \ microvia); reports whether vias fit between adjacent BGA pads at all, \ and whether the perimeter has enough channel capacity for the \ assigned inner-ring nets. Args: rows (int), cols (int), \ pitch_mm (number, e.g. 0.4 for LMS7002M aQFN), pad_diameter_mm \ (number, the BGA's own pad diameter, vendor-defined), fab_tier \ ("standard" | "hdi", default standard), assigned_inner_nets \ (int, count of inner pads needing escape, default 0).

Arguments

  • rows (integer)
  • cols (integer)
  • pitch_mm (number)
  • pad_diameter_mm (number)
  • fab_tier (string)
  • assigned_inner_nets (integer)

check_diff_pair

Calculate differential-pair impedance (edge-coupled, 1 oz \ copper, 0.035 mm thickness, FR-4 er=4.3 fixed). Args: \ trace_width (mm, default 0.127), gap (mm, default 0.127), \ dielectric_height (mm, default 0.2). Returns \ Diff pair Z_diff=<n>Ω, Z_common=<n>Ω, coupling=<n>. Pure \ calculation — no project state is read or written.

Arguments

  • trace_width (number)
  • gap (number)
  • dielectric_height (number)

check_impedance

Calculate microstrip trace impedance for a surface trace \ (1 oz copper, 0.035 mm thickness, is fixed). Args: \ trace_width (mm, default 0.254), dielectric_height (mm, \ default 0.2), er (dielectric constant, default 4.3). Returns \ Microstrip Z0=<n>Ω, delay=<n>ps/mm, Eff.Er=<n>. Pure \ calculation — no project state is read or written.

Arguments

  • trace_width (number)
  • dielectric_height (number)
  • er (number)

check_impedance_at_frequency

Wave 65 — microstrip impedance + loss at mmWave (>10 GHz) with \ Djordjevic-Sarkar causal dielectric model and Cannonball-Huray \ conductor-roughness multiplier. Replaces single-point loss tangent \ (good only to ~10 GHz) with frequency-anchored material + roughness \ anchored to a single datasheet (Dk, tanδ) point. Args: \ trace_width mm (default 0.55), dielectric_height mm (default 0.254), \ trace_thickness mm (default 0.018), frequency_ghz (default 60), \ material ("ro4350b"|"fr4", default ro4350b), roughness \ ("vlp"|"std_ed", default vlp).

Arguments

  • trace_width (number)
  • dielectric_height (number)
  • trace_thickness (number)
  • frequency_ghz (number)
  • material (string)
  • roughness (string)

control_plant

Stabilize ANY controllable plant in the registry by name using \ iLQR — compute a control sequence that drives the state from x0 \ toward a target under quadratic state/control/terminal costs. \ Works generically across the 350+ plants (no per-plant solver \ wiring); autonomous plants with no inputs are rejected (use \ simulate_plant instead). Args: name (string, required), x0 \ (array, required — initial state), target (array, optional — \ goal state, default origin), horizon (integer, default 30), q \ (number — state-error weight, default 1.0), r (number — control \ weight, default 0.01), qf (number — terminal weight, default \ 10.0), max_iter (integer, default 50). Returns convergence, cost, \ the immediate control u0, the full control sequence, and the final \ state.

Arguments

  • name (string)
  • x0 (array)
  • target (array)
  • horizon (integer)
  • q (number)
  • r (number)
  • qf (number)
  • max_iter (integer)

create_board

Create a rectangular PCB and install it as the project's PCB \ (replaces any existing project PCB). Args: name (default \ "board"), width (mm, default 35), height (mm, default 25), \ layers (copper count, default 2). Returns the board dimensions \ and area, e.g. Created 35x25mm 2-layer board (875mm²) and set \ it as the project's PCB. Errors when width, height, or layers \ are not positive.

Arguments

  • name (string)
  • width (number)
  • height (number)
  • layers (number)

create_box

Create an axis-aligned box solid in the kernel, centered at the \ origin. Args: width, height, depth (numbers, mm; each \ defaults to 10). Returns the new body's UUID and its dimensions, \ e.g. Created box <uuid> (10x10x10mm). Fails with the kernel's \ error if the primitive is rejected.

Arguments

  • width (number)
  • height (number)
  • depth (number)

create_cylinder

Create a solid cylinder in the kernel along the Y axis, centered \ at the origin. Args: radius (mm, default 5), height (mm, \ default 10). Returns the new body's UUID. Fails with the \ kernel's error if the primitive is rejected.

Arguments

  • radius (number)
  • height (number)

deepcad_forward

Wave 74.B — pure-Rust forward pass through the DeepCAD command-\ sequence decoder (Wu et al. ICCV 2021), implemented in Candle. \ Substrate-side architecture lives in cad-deepcad: 6-token vocab, \ 256-d embedding, 4-layer transformer, 256-bin arg quantization \ matching the paper. Weights are randomly initialized in Wave 74.B; \ Wave 74.C will swap in the MIT-licensed DeepCAD .pt → safetensors \ path so this tool produces real image-conditioned CAD command \ sequences. The whole inference path stays single-binary Rust — no \ Python, no PyTorch runtime, no Torch C++ linking. Wave 74.C wires \ weight loading from .safetensors and PyTorch .pt files (both \ via Candle's pure-Rust loaders). Args: batch (int, default 1), \ seq_len (int, default 8), weights_path (string, optional — \ file path; .safetensors uses mmap, .pt/.pth uses pickle reader; \ omit for random init), state_key (string, optional — for nested \ .pt files like {"model": <state_dict>}).

Arguments

  • batch (integer)
  • seq_len (integer)
  • weights_path (string)
  • state_key (string)

delete_body

Delete a kernel body by UUID. Arg: body_id (string, required) \ — a body UUID from create_box / eval_ocl / list_bodies. Returns \ Deleted body <id>. Errors: Invalid body ID '<id>': ... when \ the string is not a valid entity id; Failed to delete body: ... \ when no such body exists.

Arguments

  • body_id (string)

design_lqr

Wave 75.C — design a discrete-time infinite-horizon Linear-\ Quadratic Regulator via iterated DARE (Discrete Algebraic Riccati \ Equation). For x_{k+1} = A x + B u and cost Σ (x^T Q x + u^T R u), \ returns the optimal feedback gain K such that u = -K x is the \ stabilising optimal policy. Closed-loop spectral radius is \ reported; values strictly under 1 mean (A, B) is stabilisable with \ the chosen Q, R. Args: a, b, q, r (matrices, required), \ max_iter (int, default 500), tol (number, default 1e-10).

Arguments

  • a (array)
  • b (array)
  • q (array)
  • r (array)
  • max_iter (integer)
  • tol (number)

estimate_cost

Estimate manufacturing cost for the project's PCB + BOM. Args: \ quantity (number, default 5), fab (string, default \ "JLCPCB"; also "PCBWay", "OSHPark"). Returns the \ estimated cost for the requested quantity at that fab's \ pricing. Unknown fabs fall back to a generic per-board rate.

Arguments

  • quantity (number)
  • fab (string)

estimate_mhe

Wave 81.A — Moving Horizon Estimation, the dual of MPC. \ Given a window of past noisy measurements (y_seq) and \ controls (u_seq) plus an arrival-cost prior (x_prior, p_0), \ returns the Bayesian-optimal smoothed state trajectory. On \ linear-Gaussian data the final-time estimate matches a \ Kalman filter run over the same window — but the QP framing \ extends to state bounds (later wave) and nonlinear plants \ (iterative LTV, like Wave 76's MPC, also a later wave). \ Args: a (n×n required), b (n×m optional — omit for \ autonomous), c (p×n required), q_proc (n×n required, \ process noise covariance), r_meas (p×p required, \ measurement noise covariance), p_0 (n×n required, prior \ covariance), x_prior (length n required), u_seq (array \ of length-m vectors), y_seq (array of length-p vectors). \ horizon = y_seq.len(). Returns current_estimate, full x_seq, \ cost, iterations.

Arguments

  • a (array)
  • b (array)
  • c (array)
  • q_proc (array)
  • r_meas (array)
  • p_0 (array)
  • x_prior (array)
  • u_seq (array)
  • y_seq (array)

eval_ocl

Parse and evaluate an OCL document against the engine. Arg: \ source (string, required). Produces kernel geometry plus \ ECAD netlist/PCB, schematic, sim, and firmware records. Parse \ errors fail fast (OCL evaluation error: ...); errors inside \ domain blocks are non-fatal by design but reported alongside \ the result (... errors inside domain blocks: ...) so they \ are never silent.

Arguments

  • source (string)

export_brep

Export bodies as OpenCascade BREP (polyhedral subset — \ each triangle becomes a planar Face. Importable by FreeCAD / \ Salome / OCCT-CLI for viz or further Boolean ops; not a \ substitute for full surface BREP — for that use IGES / STEP).

No arguments.


export_gerber

Report the Gerber layer set for the project's PCB (F.Cu, B.Cu, \ F.Mask, B.Mask, F.SilkS, Edge.Cuts, drill). No inputs. \ Currently returns the layer list only — no files are written \ yet (roadmap); the full Gerber writer belongs to the \ format/mfg surface.

No arguments.


export_gltf

Encode the FIRST kernel body as glTF 2.0 in memory and report \ its size. No inputs. Returns glTF exported: <N> bytes. \ Errors: No bodies when the project has no geometry. Note: the \ payload is not written to disk or returned — this validates the \ export path only.

No arguments.


export_obj

Encode the FIRST kernel body as Wavefront OBJ text in memory \ and report its size. No inputs. Returns OBJ exported: <N> \ bytes. Errors: No bodies when the project has no geometry. \ Note: the text is not written to disk or returned — this \ validates the export path only.

No arguments.


export_ply

Encode the FIRST kernel body as Stanford PLY (ASCII) in memory \ and report its size. No inputs. Returns PLY exported: <N> \ bytes, <M> triangles. Errors: No bodies when the project has \ no geometry. Note: the text is not written to disk or returned \ — this validates the export path only.

No arguments.


export_stl

Encode the FIRST kernel body as binary STL in memory and \ report its size. No inputs. Returns STL exported: <N> bytes, \ <M> triangles. Errors: No bodies when the project has no \ geometry. Note: the bytes are not written to disk or returned \ — this validates the export path only.

No arguments.


export_xao

Export bodies as Salome XAO (BREP payload + topology + groups). \ Argument name overrides the geometry name (defaults to project.name).

Arguments

  • name (string)

generate_bom

Count the project's BOM lines from PCB footprints and schematic \ symbols. No inputs. Returns <N> entries (<F> footprints, <S> \ schematic symbols). Note: this reports the count only — it \ does not emit a BOM file; use generate_jlcpcb_bom for a JLCPCB \ CSV.

No arguments.


generate_contour_gcode

Wave 70.B — follow an arbitrary 2D path at successive Z depths. \ Caller supplies path_mm (array of [x,y] waypoints in mm). At each \ Z level the tool follows the path in order; closed: true adds a \ closing move back to the first waypoint (perimeter milling). No \ cutter compensation — caller offsets the path by the cutter radius. \ Args: path_mm (required, array of [x,y]), depth_mm (default 5), \ step_down_mm (default 1), closed (default true), \ feedrate_mm_min (default 800), plunge_rate_mm_min (default 200), \ spindle_rpm (default 10000), safe_z_mm (default 5).

Arguments

  • path_mm (array)
  • depth_mm (number)
  • step_down_mm (number)
  • closed (boolean)
  • feedrate_mm_min (number)
  • plunge_rate_mm_min (number)
  • spindle_rpm (number)
  • safe_z_mm (number)

generate_drawing

Wave 81 — 2D engineering drawing of a kernel body with hidden-line \ removal. Tessellates the body and renders a 2×2 multi-view SVG \ (front / top / right / iso) where edges occluded by the part's own \ surface are drawn dashed and visible edges solid — the standard \ drafting convention. Returns the full SVG inline plus a length and \ snippet. Args: body (index into the project's bodies, default 0), \ part_name (title block, default 'part'), tolerance (tessellation \ chord error mm, default 0.1, clamped 0.01–1.0), feature_angle_deg \ (crease angle for edge extraction, default 30, clamped 1–89), \ samples (per-edge occlusion sampling, default 24, clamped 2–64). \ Requires at least one body in the project.

Arguments

  • body (integer)
  • part_name (string)
  • tolerance (number)
  • feature_angle_deg (number)
  • samples (integer)

generate_drill_gcode

Wave 70.B — drill a list of holes. Args: positions_mm (required, \ array of [x,y]), depth_mm (default 5), peck_increment_mm \ (optional; when set, enables G83-style peck drilling with full \ retract between pecks), plunge_rate_mm_min (default 200), \ spindle_rpm (default 10000), safe_z_mm (default 5).

Arguments

  • positions_mm (array)
  • depth_mm (number)
  • peck_increment_mm (number)
  • plunge_rate_mm_min (number)
  • spindle_rpm (number)
  • safe_z_mm (number)

generate_firmware

Generate firmware skeleton code for a target MCU family. Arg: \ target (string, default "Esp32S3"; e.g. stm32f4, stm32h7, \ nrf52840, esp32, esp32c3, rp2040, rp2350 — case-insensitive). \ Returns the generated skeleton source. The output is a marked \ skeleton (peripheral drivers not yet generated) — see the \ cad-firmware scope; unknown targets are an error naming the \ supported families.

Arguments

  • target (string)

generate_jlcpcb_bom

Generate a JLCPCB-format BOM CSV from project.eda.pcb (columns: \ Comment, Designator, Footprint, LCSC Part #). Designators sharing \ (value, footprint, LCSC) collapse into one row. Returns the CSV \ inline. Requires a PCB — run import_zener_with_layout / \ import_kicad first.

No arguments.


generate_jlcpcb_cpl

Generate a JLCPCB-format CPL (component placement list) CSV from \ project.eda.pcb (columns: Designator, Mid X, Mid Y, Layer, \ Rotation). Coordinates in mm to 4 decimals; F.Cu / B.Cu mapped \ to Top / Bottom. Returns the CSV inline. Requires a PCB.

No arguments.


generate_pocket_gcode

Wave 70 — emit ready-to-run CNC G-code for a rectangular pocket \ zig-zag toolpath. Picks safe Z plane → plunges in step-down \ increments → sweeps zig-zag rows at each Z level → retracts \ between Z levels. Cutter-radius inset is applied automatically so \ the pocket walls match the requested width × height exactly. The \ generated program is RS-274 (Fanuc-style) via cad-cam5's RTCP \ post. Args: width_mm (default 20), height_mm (default 20), \ depth_mm (default 5), cutter_diameter_mm (default 3), \ step_down_mm (default 1), step_over_mm (default cutter/2), \ feedrate_mm_min (default 800), plunge_rate_mm_min (default \ 200), spindle_rpm (default 10000), safe_z_mm (default 5), \ x_mm/y_mm (pocket origin, default 0).

Arguments

  • width_mm (number)
  • height_mm (number)
  • depth_mm (number)
  • cutter_diameter_mm (number)
  • step_down_mm (number)
  • step_over_mm (number)
  • feedrate_mm_min (number)
  • plunge_rate_mm_min (number)
  • spindle_rpm (number)
  • safe_z_mm (number)
  • x_mm (number)
  • y_mm (number)

import_atopile

Import an atopile (.ato) source string. atopile is the Python-flavored \ text-HDL from atopile.io; this tool runs the cad-interop atopile bridge \ which extracts module declarations, new instantiations, ~ connections, \ and parameter assignments into a UDD. Returns a summary; UDD is generated \ but not yet lowered into project.eda (follow-on).

Arguments

  • source (string)
  • name (string)

import_atopile_with_layout

Atopile-pipeline completer: imports a .ato file's schematic + netlist \ AND overlays a companion .kicad_pcb file's placements + traces in one \ call. After this, the session's project has a real routed board — DRC \ finds real violations, generate_jlcpcb_bom/cpl + export_gerber produce \ fab-ready output. Use this whenever you have both the atopile source and \ the KiCad layout that ato build emitted.

Arguments

  • atopile_source (string)
  • kicad_pcb_source (string)
  • name (string)

import_deepcad

Wave 72 — ingest a DeepCAD command-sequence JSON (Wu et al. ICCV \ 2021; output format of GenCAD, arXiv 2409.16294 MIT DECODE Lab). \ Pure-Rust parser + lifter: no Python / PyTorch / OpenCascade runtime, \ no ML bloat in the eval path. Each SOL..EOS..Ext operation block is \ lifted to a cad-kernel primitive (rectangular sketches → box, \ circular sketches → cylinder) and the Wave 72 boolean op \ (NewBody / Join / Cut / Intersect) is applied against the prior body. \ Args: json (string, required, the DeepCAD command-sequence JSON).

Arguments

  • json (string)

import_kcl

Parse a KCL (Zoo.dev) source string and report the statement \ count. Arg: source (string, required) — the KCL text. Returns \ Imported KCL: <N> statements or KCL error: ... with the \ parse failure. Note: currently validates/parses only — the \ parsed program is not yet converted into kernel geometry \ (roadmap).

Arguments

  • source (string)

import_kicad

Import a KiCad project — accepts pcb_source (.kicad_pcb contents) and/or \ schematic_source (.kicad_sch contents). Replaces project.eda with the \ result. To layer a layout on top of an existing schematic (e.g. one from \ a prior import_zener call), use import_zener_with_layout instead.

Arguments

  • name (string)
  • pcb_source (string)
  • schematic_source (string)

import_tscircuit

Import a tscircuit Circuit JSON document. tscircuit (tscircuit.com) is a \ React/JSX DSL for PCB; you write JSX, the TS runtime emits Circuit JSON. \ This tool consumes that JSON (skipping the JSX eval) and lifts source_* \ elements into a UDD. Returns a summary; UDD is generated but not yet \ lowered into project.eda (follow-on).

Arguments

  • source (string)
  • name (string)

import_zener

Import a Zener (Diode Computers' Starlark PCB-as-code) source string. \ Populates the project's EDA schematic + netlist with the parsed \ components, nets, and pin connections; if the Zener Board(...) call \ is present, also creates a placeholder Pcb with the declared layer \ count (board width/height default to 50x50mm — actual layout lives \ in the KiCad file at Board(layout_path=) and arrives via a follow-up \ import). Errors carry line numbers from the Zener parser.

Arguments

  • source (string)

import_zener_with_layout

Zener-pipeline completer: imports a .zen file's schematic + netlist AND \ overlays a .kicad_pcb file's placements + traces in one call. After \ this, the session's project has a real routed board — DRC finds real \ violations, export_gerber produces fab-ready output. Use this whenever \ you have both the Zener source and the KiCad layout it layout_path=s.

Arguments

  • zener_source (string)
  • kicad_pcb_source (string)
  • name (string)

kalman_step

Wave 75.B — single predict+update step of a linear Kalman filter \ for x_{k+1} = F x + B u + w, y = H x + v with w ~ N(0, Q), \ v ~ N(0, R). Stateless from the caller's POV: pass in current \ (x, P, u, y) and the matrices (F, B, H, Q, R); receive back the \ new posterior mean, covariance, and innovation. B and u may be \ omitted for autonomous systems. Args: f, h, q, r, p \ (matrices, required), x, y (vectors, required), b (matrix, \ optional), u (vector, optional).

Arguments

  • f (array)
  • h (array)
  • q (array)
  • r (array)
  • p (array)
  • x (array)
  • y (array)
  • b (array)
  • u (array)

list_control_plants

Browse the cad-control dynamics-plant registry — 350+ canonical \ control/dynamical-systems plants (chaos, robotics, power, process, \ aerospace, biology, economics, …), each callable by name. Lists \ plants as Name [state×control] — summary. Args: filter (string, \ optional — case-insensitive substring matched against the name and \ summary, e.g. "motor", "chaos", "pendulum"), limit (integer, \ optional, default 40). Pair with plant_info and simulate_plant.

Arguments

  • filter (string)
  • limit (integer)

optimize_placement

Wave 81 — simulated-annealing GLOBAL component placement on \ project.eda.pcb. Unlike run_placement (a local force-directed \ relaxation), this minimizes total half-perimeter wirelength (HPWL) \ plus an overlap/out-of-bounds penalty over a geometric cooling \ schedule, so it escapes the local minima that trap the FR solver. \ Reports HPWL and overlap before/after. Args: iterations (move \ budget, default 8000, clamped 100–50000), seed (PRNG seed for \ reproducibility), overlap_weight (penalty on overlap area mm², \ default 50), allow_rotation (90° transposes, default true), \ locked (array of refs to pin). Requires a PCB with ≥2 footprints.

Arguments

  • iterations (integer)
  • seed (integer)
  • overlap_weight (number)
  • allow_rotation (boolean)
  • locked (array)

parse_ocl

Parse an OCL source string into an AST without evaluating it. \ Arg: source (string, required) — the OCL document text. \ Returns Parsed OCL: <N> items or the first parse error with \ its source span (OCL parse error: ...). Use eval_ocl to \ actually execute the document.

Arguments

  • source (string)

place_footprint

Place a component footprint instance on the project's PCB at \ (x, y) mm. Args: reference (e.g. "U1", default "U1"), \ lib_id (footprint library id, e.g. "Package:QFP", default \ "Package:QFP"), x, y (mm, default 0). If the project has \ no PCB yet, a default 100x80 mm 4-layer board is created first. \ Returns the reference, position, and footprint UUID. The \ footprint carries no pads — use place_vendor_footprint for a \ fully-detailed vendor footprint from a .kicad_mod file.

Arguments

  • reference (string)
  • lib_id (string)
  • x (number)
  • y (number)

place_vendor_footprint

Wave 68 — place a vendor-validated footprint into project.eda.pcb \ from a standalone .kicad_mod source string. Pad positions, sizes, \ shapes, and drill come from the parsed module verbatim — closing the \ aether-t "140 dangling pads from approximate footprint stubs" \ failure mode by guaranteeing the land pattern matches the vendor \ datasheet. Creates an empty 100×80 mm PCB if none exists. Args: \ kicad_mod (string, required, the .kicad_mod source), reference \ (string, default "U?"), value (string, optional, e.g. the MPN), \ x_mm (number, default 0), y_mm (number, default 0), \ rotation_deg (number, default 0).

Arguments

  • kicad_mod (string)
  • reference (string)
  • value (string)
  • x_mm (number)
  • y_mm (number)
  • rotation_deg (number)

plan_rrt_star

Wave 79.C — 2D RRT* (Karaman & Frazzoli 2011) for global \ path planning through circular obstacles. Pure Rust; pairs \ with the bicycle MPCs — feed the returned waypoints in as \ the reference trajectory (interpolated to 4-state \ (X, Y, ψ, v)) and have solve_bicycle_mpc / _ilqr / _mppi \ track it. Asymptotically optimal: more iterations → better \ path. Reuses the same Obstacle primitive as \ solve_bicycle_mpc, so plan + control share data. \ Args: start (length-2 vector, required), goal (length-2, \ required), bounds (length-4 vector [x_min, x_max, y_min, \ y_max], required), obstacles (array of {x, y, radius, \ safety_margin?, slack_penalty?}, optional), step_size \ (number, default 0.5), goal_radius (number, default 0.3), \ rewire_radius (number, default 1.0), n_iterations (int, \ default 2000), goal_bias (number 0..1, default 0.1), \ seed (int, optional). Returns the waypoint sequence or \ an error if no path was found in n_iterations.

Arguments

  • start (array)
  • goal (array)
  • bounds (array)
  • obstacles (array)
  • step_size (number)
  • goal_radius (number)
  • rewire_radius (number)
  • n_iterations (integer)
  • goal_bias (number)
  • seed (integer)

plant_info

Report the state dimension, control (input) dimension, and one-line \ summary of a single registered dynamics plant. Args: name (string, \ required — the plant's registry name, e.g. "Lorenz", "CartPole", \ "DynamicBicycle"; browse with list_control_plants).

Arguments

  • name (string)

recognise_features

Recognise manufacturing features in a STEP B-rep solid by calling the quiddity CLI (0.4.9) as a process and parsing its JSON recognition document. Arg: step (string, required) — STEP file content. Returns the recognition receipt as JSON: verdict Recognised/Refused, records with parameters and evidence_status, face accounting, source_sha256, and the measured-joule fields under boundary (b). Refuses (never crashes) when the CLI is absent, the input is not a B-rep solid/shell with faces, or the document fails validation — the refusal receipt names the precondition. Requires the quiddity CLI installed (pip install quiddity==0.4.9). No project state is read or written.

Arguments

  • step (string)

render_pcb_svg

Render project.eda.pcb as an SVG board preview — outline, copper \ tracks per layer, vias, placed footprints with reference designators. \ Returns the SVG inline so agents/users can see what was imported. \ Optional args: scale (px/mm, default 10), margin_mm (default 5), \ show_references (default true). Requires a PCB.

Arguments

  • scale (number)
  • margin_mm (number)
  • show_references (boolean)

rl_policy_action

Wave 80.D — query a Candle-trained Gaussian MLP policy \ (Wave 80.A's GaussianMlpPolicy) for an action given a \ state. Loads weights via the same pure-Rust mmaped-\ safetensors path as cad-deepcad. Use this to deploy a \ policy trained by Wave 80.B/C's train_vpg / train_ppo, \ OR a policy trained externally (PyTorch / JAX) and \ exported to safetensors with the substrate's tensor \ naming convention (fc1, fc2, mean_head, log_std). \ Stateless across calls: caller threads the state. \ Args: weights_path (string, required, .safetensors path), \ n_state (int, required), n_action (int, required), \ hidden (int, default 64), state (length-n_state \ vector, required), stochastic (bool, default false — \ returns the mean if false, samples from the Gaussian if \ true), seed (int, optional — used when stochastic=true).

Arguments

  • weights_path (string)
  • n_state (integer)
  • n_action (integer)
  • hidden (integer)
  • state (array)
  • stochastic (boolean)
  • seed (integer)

route_dmf_droplets

Route a digital-microfluidics (EWOD) assay: move droplets across \ an electrode grid from start to target, collision-free, and \ return the activation schedule. Enforces the fluidic constraint \ that two droplets may never be on adjacent electrodes at once \ (they'd merge) via prioritised time-expanded A* with a one-cell \ exclusion halo. Args: nx,ny (electrode grid size, required), \ droplets (array; each {id?, start:[x,y], target:[x,y]}). \ Returns the timestep count and each droplet's routed endpoints; \ a verified ≥2-cell separation holds at every step. Set \ optimal true to route with Conflict-Based Search (minimum \ total travel) instead of the fast greedy planner.

Arguments

  • nx (integer)
  • ny (integer)
  • droplets (array)
  • optimal (boolean)

route_microfluidic_design

Synthesise a microfluidic layout from a MINT-subset netlist — \ the fluidic analogue of PCB place-and-route. Parses components \ and the channels between their ports, places the components \ without overlap, and routes each channel as a Manhattan path \ (grid A*) around every footprint. MINT grammar (one per line): \ component <id> <kind> <width_um> <height_um> and \ channel <id>.<port> <id>.<port>, where ports are the \ auto-generated edge midpoints W/E/N/S. Args: mint (netlist \ text, required), gap_um (placement gap, default 400), \ row_width_um (shelf wrap width, default 8000), pitch_um \ (routing grid, default 100). Returns component placements, \ channel count, and total channel length.

Arguments

  • mint (string)
  • gap_um (number)
  • row_width_um (number)
  • pitch_um (number)

run_autorouter

Route all unconnected nets on project.eda.pcb. Groups footprint \ pads by net name, decomposes each multi-pin net via SALT \ (Wave 58 shallow-light tree), and routes each tree edge with the \ cad-router A* engine — optionally using PathFinder negotiation \ (Wave 55) when negotiation = true. Resulting paths get lifted \ back into project.eda.pcb.tracks. Args: epsilon (number, SALT \ radius/wirelength knob, default 1.0); negotiation (boolean, \ default false); length_targets (Wave 62: optional array of \ {net: string, target_mm: number, tolerance_mm: number, \ amplitude_mm?: number, spacing_mm?: number} — each named net \ gets a serpentine meander inserted to match the target length, \ useful for DDR group-skew and PCIe diff-pair matching); \ length_match_groups (Wave 64.A: optional array of \ {name: string, nets: [string, ...], tolerance_mm: number, \ target_mm?: number, amplitude_mm?: number, spacing_mm?: number} \ — names a group of nets to be length-matched. When \ target_mm is omitted the substrate picks the longest \ routed length as the consensus target, so every \ undersize net meanders up to match. Replaces \ length_targets when the caller wants substrate- \ computed targets); \ bgas (Wave 63.F: optional array of \ {footprint_ref: string, pitch_mm: number, \ pad_diameter_mm: number, fab_tier: "standard"|"hdi", \ inner_layers: ["In1Cu", ...], buses: {bus_name: [net_name, ...]}, \ strategy?: "offset"|"via_in_pad"} — each entry \ names a BGA in the PCB by footprint reference; the \ substrate runs the Wave 63 LCIS escape stack and \ splices the resulting tracks + vias before routing \ the general fabric). Requires a PCB.

Arguments

  • epsilon (number)
  • negotiation (boolean)
  • length_targets (array)
  • length_match_groups (array)
  • bgas (array)

run_dfm

Run the Design-for-Manufacturability check against the \ project's PCB for a target fab. Arg: fab (string, default \ "JLCPCB"; also "PCBWay", "OSHPark"). Returns \ manufacturable yes/NO, error/warning counts, the first 10 \ violations, and the estimated cost per board. Errors: unknown \ fab name (Unknown fab ... — supported: JLCPCB | PCBWay | \ OSHPark), or no PCB in the project — create one with \ create_board or import_kicad first.

Arguments

  • fab (string)

run_drc

Run the Design Rule Check (cad-eda DRC engine) against the \ project's PCB using the project's DRC rules, augmented with \ per-fab energy/CO2 estimates. No inputs. Returns PASSED/FAILED \ plus the violation list. Errors when the project has no PCB \ yet — create one with create_board or import_kicad first. (A \ project with no layout is an error, not a pass.)

No arguments.


run_placement

Wave 66 — force-directed component placement on project.eda.pcb. \ Models each footprint by an AABB derived from its pad layout; pulls \ net-connected footprints toward their common centroid; pushes \ overlapping footprints apart; clamps to the board's outline. \ Replaces aether-t's hand-coded auto_place.py. Args: \ iterations (int, default 200), attraction (number, spring \ strength on connected nets, default 0.001), repulsion (number, \ separation force on overlaps, default 10000), step_mm (per-\ iteration cap on movement, default 0.1), locked (array of \ footprint refs to pin in place). Requires a PCB with footprints.

Arguments

  • iterations (integer)
  • attraction (number)
  • repulsion (number)
  • step_mm (number)
  • locked (array)

run_thermal

Report the thermal-simulation entry point for the project. \ Arg: ambient_c (number, °C, default 25). Currently returns \ the requested ambient temperature and notes the solver needs a \ mesh — a full thermal solve is NOT wired yet (roadmap). Do not \ treat the result as a simulation outcome.

Arguments

  • ambient_c (number)

simulate_conjugate_heat

Solve conjugate heat transfer across a two-material (e.g. \ solid/fluid) composite wall: a single temperature field with \ per-cell conductivity, harmonic-mean face coupling (which makes \ temperature AND heat flux continuous across the interface \ automatically), and optional flow that advects heat. Reports the \ steady interface temperature vs the analytic series-resistance \ value and how continuous the flux is across the conductivity jump. \ Args: nx,ny (grid, default 60×6), dx (m, default 1e-4), \ k1,k2 (W/m·K, left/right halves, default 1/3), rho_cp \ (J/m³·K, default 1e6), t_left,t_right (default 1/0), flow \ (m/s, default 0).

Arguments

  • nx (integer)
  • ny (integer)
  • dx (number)
  • k1 (number)
  • k2 (number)
  • rho_cp (number)
  • t_left (number)
  • t_right (number)
  • flow (number)

simulate_coupled

Wave 67 — partitioned multi-physics coupling primitive. Runs a \ linear coupling toy (solve_a(b) = a0 + α·b, solve_b(a) = b0 + β·a) \ through the cad_sim::coupling engine to demonstrate convergence \ behavior at different stiffness levels (αβ) and relaxation modes. \ Useful for choosing a coupling scheme before plugging in real \ cad-sim domains. Args: coupling_alpha, coupling_beta (stiffness, \ default 0.5/0.5), a0, b0 (offsets, default 1/1), scheme \ ("implicit"|"explicit", default implicit), relaxation \ ("aitken"|"constant"|"iqn_ils", default aitken), omega (for \ constant), iqn_history_max (for IQN-ILS, default 10), \ max_iterations (default 200), tolerance (default 1e-8). \ Reports iteration count, final residual, and the analytic fixed \ point for comparison.

Arguments

  • coupling_alpha (number)
  • coupling_beta (number)
  • a0 (number)
  • b0 (number)
  • scheme (string)
  • relaxation (string)
  • omega (number)
  • iqn_history_max (integer)
  • max_iterations (integer)
  • tolerance (number)

simulate_microfluidic_network

Solve a microfluidic channel network by nodal analysis — the \ hydraulic analogue of circuit MNA (pressure↔voltage, flow↔current, \ Hagen-Poiseuille resistance↔resistance, reservoir↔voltage source, \ flow source↔current source). This is MMFT-1D-class 1D network \ simulation: give it the channel graph + boundary conditions and it \ returns every nodal pressure and channel flow rate. Args: n_nodes \ (int, required), viscosity (Pa·s, default 1e-3 = water), channels \ (array; each {from,to} plus either resistance (Pa·s/m³), or \ radius+length (circular), or width+height+length \ (rectangular) — all SI metres), boundaries (array; each {node} \ plus either pressure (Pa) or flow (m³/s); at least one pressure \ BC required to anchor the network). Series/parallel channels \ reproduce the textbook R_eq; a single channel reproduces \ Hagen-Poiseuille exactly.

Arguments

  • n_nodes (integer)
  • viscosity (number)
  • channels (array)
  • boundaries (array)

simulate_mixing_field

Simulate solute mixing in a channel as a 2D advection-diffusion \ field — the grid solver behind the analytical Péclet/mixing-length \ estimates. Seeds a left-half slug (sharp interface) and advances it; \ reports how much the interface homogenises (variance drop = % mixed). \ Runs on the GPU compute kernel when the server is built with the \ gpu feature and an adapter is present, else on the identical CPU \ stencil (they agree to f32 precision). Args: nx,ny (grid, default \ 48), dx (m, default 1e-5), diffusivity (m²/s, default 1e-9), \ ux,uy (m/s, default 0), steps (default 200).

Arguments

  • nx (integer)
  • ny (integer)
  • dx (number)
  • diffusivity (number)
  • ux (number)
  • uy (number)
  • steps (integer)

simulate_natural_convection

Solve buoyancy-driven natural convection in a differentially-heated \ square cavity (Boussinesq, vorticity-streamfunction) — the velocity \ is generated by the temperature field, not prescribed. Reports the \ hot-wall Nusselt number (Nu=1 ⇒ pure conduction; de Vahl Davis \ benchmark Nu≈1.118 at Ra=10³) and the peak flow speed. Set \ smagorinsky > 0 to enable the algebraic eddy-viscosity turbulence \ closure. NOTE: this runs a steady-state CFD solve synchronously and \ is bounded (grid ≤51, capped iterations) — higher Ra takes longer. \ Args: n (grid, default 31, clamped 11–51), ra (Rayleigh, default \ 1e3), pr (Prandtl, default 0.71), smagorinsky (Cs, default 0), \ max_steps (default 40000, capped 60000).

Arguments

  • n (integer)
  • ra (number)
  • pr (number)
  • smagorinsky (number)
  • max_steps (integer)

simulate_plant

Roll a registered dynamics plant forward in time from an initial \ state under a constant control input, returning the (down-sampled) \ state trajectory and final state. Works for any of the 350+ plants in \ the registry — no per-plant wiring. Args: name (string, required), \ x0 (array, required — initial state, must match the plant's state \ dimension; see plant_info), control (array, optional — constant \ input, defaults to zeros of the plant's control width), steps \ (integer, optional, default 100). Returns final_state + a sampled \ trajectory + a finiteness flag.

Arguments

  • name (string)
  • x0 (array)
  • control (array)
  • steps (integer)

simulate_rans_channel

Wave 81 — RANS turbulent channel flow via the Spalart-Allmaras \ one-equation model. Solves the fully-developed half-channel velocity \ profile in wall units (ν=1, u_τ=1) and reports the log-law slope \ (1/κ ≈ 2.44 when the model is correct; κ≈0.41), the centreline u⁺, \ and a few profile points (y⁺, u⁺, ν_t⁺) across the wall / buffer / \ log layers. NOTE: a synchronous solve, bounded (grid ≤256, sweeps \ ≤2000). Args: re_tau (friction Reynolds number, default 395, \ clamped 50–5000), n (grid points across half-channel, default 96, \ clamped 16–256), outer_iters (Picard sweeps, default 300, clamped \ 20–2000).

Arguments

  • re_tau (number)
  • n (integer)
  • outer_iters (integer)

simulate_rayleigh_benard_3d

Solve 3D Rayleigh-Bénard convection — a fluid layer heated from \ below — with a full 3D incompressible Navier-Stokes projection \ solver (Boussinesq buoyancy). Reports the Nusselt number and peak \ speed, and whether the layer has crossed the convective instability \ (Nu>1) or stays conductive (Nu=1). Above the critical Rayleigh \ number (Ra_c≈1708) a small perturbation grows into convection \ cells. NOTE: a synchronous 3D CFD solve, bounded (grid ≤16³, steps \ ≤2500) — higher Ra/steps cost more. Args: n (horizontal grid, \ default 12, 6–16), nz (vertical, default n), ra (Rayleigh, \ default 5e4), pr (Prandtl, default 1), steps (default 1200).

Arguments

  • n (integer)
  • nz (integer)
  • ra (number)
  • pr (number)
  • steps (integer)

solve_acrobot_step

Wave 89.B — one iLQR call on the Acrobot, a 2-link arm \ with only the ELBOW joint actuated (shoulder is passive). \ The Spong-canonical underactuated benchmark — like \ cart-pole, only one control acts on 4 states, but the \ coupling is via Coriolis (not cart acceleration). True \ swing-up to upright is genuinely fragile for local \ methods, so this tool returns whatever the iLQR finds \ without claiming stabilisation — useful for trajectory \ exploration, energy-shaping bootstrap, or RL warm-starts. \ Args: arm physical params m1, m2, l1, l2, lc1, lc2, i1, \ i2, gravity (defaults to standard Spong-1995 acrobot). \ dt (default 0.02), horizon (int, default 25), x0 \ (length-4: q1, q2, qd1, qd2; required), x_target \ (length-4; required). Optional cost: matrices q/r/qf. \ iLQR knobs max_iter/tol/reg_init. Returns elbow-torque \ sequence + predicted trajectory.

Arguments

  • m1 (number)
  • m2 (number)
  • l1 (number)
  • l2 (number)
  • lc1 (number)
  • lc2 (number)
  • i1 (number)
  • i2 (number)
  • gravity (number)
  • dt (number)
  • horizon (integer)
  • x0 (array)
  • x_target (array)
  • q (array)
  • r (array)
  • qf (array)
  • max_iter (integer)
  • tol (number)
  • reg_init (number)

solve_arm_setpoint

Wave 85.C — solve setpoint regulation for a 2-DOF planar arm \ via the Wave 85.A PlanarTwoLinkArm plant + Wave 78 iLQR. \ The plant is built from a cad-assembly Mechanism (Wave 85.B \ bridge) — the same robot-arm model a CAD user designs in \ the assembly editor feeds straight into the optimal-control \ substrate, no manual dynamics derivation needed. \ Args: arm physical params m1, m2, l1, l2, lc1, lc2, i1, i2, \ gravity (all numbers, defaults to the textbook m=l=1, \ lc=0.5, i=0.1 arm with g=None for floating); name \ (string, optional), dt (s, default 0.01), horizon (int, \ default 30), x0 (length-4 vector: q1, q2, qd1, qd2; \ required), x_target (length-4 vector; required), \ optional cost matrices q/r/qf or scalar weights \ q_pos/q_vel/r_torque, max_iter (default 50), \ tol (default 1e-6), reg_init (default 1e-2). \ Returns the joint-torque sequence τ_0..τ_{N-1} and the \ predicted state trajectory under the true nonlinear arm \ dynamics.

Arguments

  • name (string)
  • m1 (number)
  • m2 (number)
  • l1 (number)
  • l2 (number)
  • lc1 (number)
  • lc2 (number)
  • i1 (number)
  • i2 (number)
  • gravity (number)
  • dt (number)
  • horizon (integer)
  • x0 (array)
  • x_target (array)
  • q (array)
  • r (array)
  • qf (array)
  • q_pos (number)
  • q_vel (number)
  • r_torque (number)
  • max_iter (integer)
  • tol (number)
  • reg_init (number)

solve_bicycle_ilqr

Wave 78.A/B — bicycle trajectory optimization via iLQR \ (Iterative Linear Quadratic Regulator). Backward Riccati \ + forward line search; locally quadratic-convergent on \ smooth quadratic-cost problems — typically 5-20 outer \ iterations vs solve_bicycle_nmpc's ~200 PANOC iters. \ No box constraints in this variant (use solve_bicycle_mpc \ for hard control bounds, or solve_bicycle_nmpc for ball/box \ via PANOC projection). \ Args: wheelbase (m, default 0.3), dt (s, default 0.1), \ horizon (int, default 10), q/r/qf (matrices, \ required), x0 (length-4 vector, required), x_ref \ (horizon+1 length array of 4-vectors, required), u_ref \ (horizon length array of 2-vectors, required), max_iter \ (int, default 30), tol (float, default 1e-6), reg_init \ (float, default 1e-4 — initial Tikhonov regularization on \ Q_uu; raised on backward-pass / line-search failures). \ Returns u_seq, x_seq, cost, iterations, converged.

Arguments

  • wheelbase (number)
  • dt (number)
  • horizon (integer)
  • q (array)
  • r (array)
  • qf (array)
  • x0 (array)
  • x_ref (array)
  • u_ref (array)
  • max_iter (integer)
  • tol (number)
  • reg_init (number)

solve_bicycle_mpc

Wave 76.C/D — solve iterative MPC for the kinematic bicycle \ with optional circular-obstacle avoidance. State x = (X, Y, \ ψ, v) in m, m, rad, m/s. Control u = (a, δ) in m/s² and rad. \ The outer loop linearizes around the nominal trajectory and \ re-solves until the control sequence converges (Σ ‖Δu_k‖ < \ tol) or max_iter is reached. Each obstacle adds a soft \ half-plane constraint per horizon step (linearized keep-out + \ slack penalty) so the solver gracefully handles initial \ nominals that cut through obstacles. \ Args: wheelbase (m, default 0.3), dt (s, default 0.1), \ horizon (int, default 15), q/r/qf (matrices, required: \ 4×4 / 2×2 / 4×4), x0 (length-4 vector, required), x_ref \ (horizon+1 length array of 4-vectors, required), u_ref \ (horizon length array of 2-vectors, required), u_min/u_max \ (length-2 vectors, optional), max_iter (int, default 6), \ tol (float, default 1e-3), obstacles (array of objects \ {x, y, radius, safety_margin?, slack_penalty?}, optional). \ Returns u_seq, x_seq (under the true nonlinear dynamics), \ iterations, converged.

Arguments

  • wheelbase (number)
  • dt (number)
  • horizon (integer)
  • q (array)
  • r (array)
  • qf (array)
  • x0 (array)
  • x_ref (array)
  • u_ref (array)
  • u_min (array)
  • u_max (array)
  • max_iter (integer)
  • tol (number)
  • obstacles (array)

solve_bicycle_mppi

Wave 79.A — bicycle Model Predictive Path Integral (MPPI) \ control. Sampling-based MPC: at each step, draws \ n_samples Gaussian noise sequences over the horizon, \ rolls each through the plant, and Boltzmann-averages by \ cost. No gradients required — works with any plant. CPU- \ parallel via rayon. Use when: plant is non-smooth or \ discrete; you want robustness to local minima; or you have \ spare CPU/GPU cores to throw at it. \ Args: same plant + cost as solve_bicycle_ilqr, plus \ n_samples (int, default 256), n_iterations (int, \ default 2), sigma (length-2 vector, default [0.5, 0.2] \ — accel/steer noise stdev), lambda (number, default 1.0 \ — Boltzmann temperature: smaller = greedier), u_min/\ u_max (length-2 vectors, optional — sampled controls \ are clamped), seed (int, optional — pass for reproducibility).

Arguments

  • wheelbase (number)
  • dt (number)
  • horizon (integer)
  • q (array)
  • r (array)
  • qf (array)
  • x0 (array)
  • x_ref (array)
  • u_ref (array)
  • n_samples (integer)
  • n_iterations (integer)
  • sigma (array)
  • lambda (number)
  • u_min (array)
  • u_max (array)
  • seed (integer)

solve_bicycle_nmpc

Wave 77.B/C — bicycle Nonlinear MPC via OpEn's PANOC solver. \ Companion to solve_bicycle_mpc (which uses iterative LTV \ around a nominal via Clarabel QP). NMPC tackles the full \ nonconvex problem directly, with adjoint-method gradient \ backpropagated through the bicycle dynamics. Use this variant \ when: cost is non-quadratic, control set isn't a box (try \ bound_kind="ball" for a Euclidean-ball constraint), or \ dynamics are non-smooth (replace bicycle with another \ NonlinearPlant in future waves). \ Args: same as solve_bicycle_mpc, plus bound_kind \ ("rectangle"|"ball"|"inf_ball"|"none", default \ "rectangle"), bound_radius (number, used when bound_kind \ is ball/inf_ball, default 10.0 / 2.0), max_iter (int, \ default 300), tol (number, default 1e-5), lbfgs_memory \ (int, default 10). No obstacle field — Wave 77 NMPC \ doesn't include obstacle penalties (use solve_bicycle_mpc \ for obstacles).

Arguments

  • wheelbase (number)
  • dt (number)
  • horizon (integer)
  • q (array)
  • r (array)
  • qf (array)
  • x0 (array)
  • x_ref (array)
  • u_ref (array)
  • u_min (array)
  • u_max (array)
  • bound_kind (string)
  • bound_radius (number)
  • max_iter (integer)
  • tol (number)
  • lbfgs_memory (integer)

solve_cartpole_stabilize

Wave 86.B — solve one step of receding-horizon iLQR to \ stabilize a cart-pole near its upright (unstable) \ equilibrium. The cart-pole is the canonical UNDERACTUATED \ control benchmark: 1 control (horizontal cart force) over \ 4 states (cart pos, pole angle, cart vel, pole ang-vel), \ with q2 = 0 the unstable equilibrium (pole upright). The \ substrate's iLQR (Wave 78) handles this without \ modifications — same algorithm as the bicycle and arm \ plants, fully nonlinear inertial coupling. \ Args: cart_mass (kg, default 1.0), pole_mass (kg, \ default 0.1), pole_length (m, default 0.5), gravity \ (m/s², default 9.81), dt (s, default 0.02), horizon \ (int, default 25), x0 (length-4 vector required: \ cart_pos, pole_angle, cart_vel, pole_ang_vel; angles in \ rad), optional cost overrides via matrices q/r/qf \ OR scalar weights q_cart/q_angle/r_force, iLQR \ knobs max_iter/tol/reg_init. Returns cart-force \ sequence + predicted state trajectory.

Arguments

  • cart_mass (number)
  • pole_mass (number)
  • pole_length (number)
  • gravity (number)
  • dt (number)
  • horizon (integer)
  • x0 (array)
  • q (array)
  • r (array)
  • qf (array)
  • q_cart (number)
  • q_angle (number)
  • r_force (number)
  • max_iter (integer)
  • tol (number)
  • reg_init (number)

solve_impedance_field

Wave 81 — field-solver transmission-line impedance. Runs a 2D \ finite-difference Laplace solve over the conductor/dielectric \ cross-section to extract the TRUE Z0, effective εr, C, L and phase \ velocity, whereas check_impedance uses the analytic microstrip \ closed form. Default geometry is a microstrip: a signal trace on a \ layered substrate over a ground plane. NOTE: a synchronous PDE solve, \ bounded (grid ≤200×200). Args: nx,ny (grid, default 120×80, \ clamped 20–200), dx,dy (cell pitch m, default 5e-5), \ trace_width (m, default 0.254e-3), dielectric_height (m, default \ 0.2e-3), trace_thickness (m, default 0.035e-3), er (default 4.3).

Arguments

  • nx (integer)
  • ny (integer)
  • dx (number)
  • dy (number)
  • trace_width (number)
  • dielectric_height (number)
  • trace_thickness (number)
  • er (number)

solve_lti_mpc

Wave 76.A — solve one step of a linear time-invariant MPC \ via condensed-form QP. Problem: minimize Σ (x^T Q x + u^T R u) \ + x_N^T Qf x_N s.t. x_{k+1} = A x + B u, x_0 = given, optional \ box constraints on u and x. The decision variables are the \ control sequence u_0..u_{N-1}; the predicted state trajectory \ is reconstructed. Returns the full sequence + cost + status. \ For receding-horizon use, call repeatedly with the updated x0. \ If qf is omitted, the infinite-horizon LQR cost-to-go P∞ for \ (A, B, Q, R) is auto-synthesized as terminal cost, so even \ short horizons inherit LQR-grade stabilising behaviour. \ Args: a, b, q, r (matrices, required), qf (matrix, \ optional), horizon (int, default 10), x0 (vector, required), \ u_min/u_max/x_min/x_max (vectors, optional).

Arguments

  • a (array)
  • b (array)
  • q (array)
  • r (array)
  • qf (array)
  • horizon (integer)
  • x0 (array)
  • u_min (array)
  • u_max (array)
  • x_min (array)
  • x_max (array)

Wave 87.C — receding-horizon iLQR for a planar n-link \ serial arm of any chain length. Uses the RNEA-based \ forward dynamics in cad-control (Wave 87.A) — same iLQR \ algorithm as solve_arm_setpoint, generalised to arbitrary \ n. The plant is built from a cad-assembly Mechanism (Wave \ 87.B bridge) so a CAD-designed robot arm with any number \ of joints feeds straight into the substrate. \ Args: links (array of {mass, length, com_offset, inertia} \ objects; required), gravity (m/s², optional), dt (s, \ default 0.01), horizon (int, default 25), x0 (length \ 2·n: q₀…q_{n-1}, q̇₀…q̇_{n-1}; required), x_target (same \ length; required), name (optional). Cost can be supplied \ via matrices q/r/qf OR via scalar weights q_pos/q_vel/\ r_torque/qf_pos/qf_vel. iLQR knobs: max_iter/tol/\ reg_init. Returns joint-torque sequence (each length n) \ + predicted state trajectory (each length 2·n).

Arguments

  • name (string)
  • links (array)
  • gravity (number)
  • dt (number)
  • horizon (integer)
  • x0 (array)
  • x_target (array)
  • q (array)
  • r (array)
  • qf (array)
  • q_pos (number)
  • q_vel (number)
  • r_torque (number)
  • qf_pos (number)
  • qf_vel (number)
  • max_iter (integer)
  • tol (number)
  • reg_init (number)

solve_qp

Wave 75.A — solve a convex quadratic program via Clarabel.rs \ (pure Rust, Apache-2.0). Problem: minimize (1/2) x^T P x + q^T x \ subject to A_eq x = b_eq and A_ineq x ≤ b_ineq. P must be symmetric \ PSD. Matrices are JSON nested arrays; vectors are flat JSON arrays. \ Equality and inequality blocks are optional (omit / null = no \ constraints of that kind). Args: p (matrix, required), q \ (vector, required), a_eq (matrix, optional), b_eq (vector, \ optional), a_ineq (matrix, optional), b_ineq (vector, optional). \ Returns x*, objective, status, iterations.

Arguments

  • p (array)
  • q (array)
  • a_eq (array)
  • b_eq (array)
  • a_ineq (array)
  • b_ineq (array)

solve_quadrotor_hover

Wave 88.B — drive a planar VTOL quadrotor (PVTOL) to a \ hover setpoint via the substrate's iLQR. The PVTOL has 6 \ states (px, py, body-tilt θ, ẋ, ẏ, ω) and 2 controls \ (left and right rotor thrusts) — underactuated, so \ horizontal motion has to be commanded by tilting the \ body. Hover equilibrium is θ = 0, both rotors at m·g/2. \ Args: mass (kg, default 1.0), arm_length (m, body \ centre to rotor; default 0.25), inertia (kg·m², default \ 0.1), gravity (m/s², default 9.81), dt (s, default \ 0.02), horizon (int, default 30), x0 (length-6, \ required), x_target (length-6, required). Optional \ cost: full matrices q/r/qf (defaults emphasise tilt \ and altitude). iLQR knobs max_iter/tol/reg_init. \ Returns rotor-thrust sequence + predicted state \ trajectory + hover_thrust_per_rotor for reference.

Arguments

  • mass (number)
  • arm_length (number)
  • inertia (number)
  • gravity (number)
  • dt (number)
  • horizon (integer)
  • x0 (array)
  • x_target (array)
  • q (array)
  • r (array)
  • qf (array)
  • max_iter (integer)
  • tol (number)
  • reg_init (number)

solve_vlm

C3-1 — vortex-lattice solve of a wing planform via the \ clean-room VLM, routed through the V1 input validator \ (validate_builder_job → ValidatedVlmJob::solve_metered). \ Args: planform (string: rectangular | tapered | elliptic, \ default rectangular), span (default 10), chord (default \ 1; root chord for tapered/elliptic), tip_chord (default = \ chord; tapered only), sweep_deg / dihedral_deg / \ twist_deg (default 0), nx / ny (panel counts, defaults \ 4 / 20), alpha_deg (default 5), v_inf (default 50), \ rho (default 1.225). Returns result + both receipts \ (validation + solve) as separate JSON records, never summed. \ A refused job returns its refusal receipt as a normal \ result. Low-speed attached-flow theory only.

Arguments

  • planform (string)
  • span (number)
  • chord (number)
  • tip_chord (number)
  • sweep_deg (number)
  • dihedral_deg (number)
  • twist_deg (number)
  • nx (integer)
  • ny (integer)
  • alpha_deg (number)
  • v_inf (number)
  • rho (number)

solve_vspaero

C3-1 — VSPAERO vortex-lattice / panel solve via the vendored \ solver subprocess, metered. Same planform inputs as the VLM \ path: planform (rectangular/tapered/elliptic, default \ rectangular), span (default 10), chord (default 1), \ tip_chord (default = chord), sweep_deg/dihedral_deg/ \ twist_deg (default 0), nx/ny (defaults 4/20, clamped \ 0–4096 each; the validator's 2,000-panel cap is the binding \ bound, and 0 is a named refusal). Flow: mach (default 0.05), alpha_deg (default 5), \ beta_deg (default 0), v_inf (default 50), rho (default \ 1.225), re_cref (default 1e7), wake_iters (default 3, \ clamped 0–10; 0 is a frozen wake upstream). The mesh is \ validated by the V1 validator before the case is built; a \ refusal returns its receipt as a normal result. A solve \ returns JSON with the polar (cl, cdi, cd0, cm) plus the \ validation receipt and the solve receipt as separate \ records, never summed. Joule fields are measured or \ absent, never estimated. No project state is read or written.

Arguments

  • planform (string)
  • span (number)
  • chord (number)
  • tip_chord (number)
  • sweep_deg (number)
  • dihedral_deg (number)
  • twist_deg (number)
  • nx (integer)
  • ny (integer)
  • mach (number)
  • alpha_deg (number)
  • beta_deg (number)
  • v_inf (number)
  • rho (number)
  • re_cref (number)
  • wake_iters (integer)

tessellate

Tessellate the FIRST body in the kernel into a triangle mesh. \ Arg: tolerance (number, mm, default 0.1) — smaller is finer. \ Returns <N> vertices, <M> triangles. Errors: No bodies to \ tessellate when the project has no geometry — create some with \ create_box or eval_ocl first. Note: only the first body is \ meshed; there is no body selector yet.

Arguments

  • tolerance (number)

REST API

cad-planner-server exposes a small REST surface on https://cad.openie.dev/api. The bulk of the substrate's capability lives behind the MCP endpoint; the REST surface is mostly health/session/artifact bookkeeping plus a few specialised handoff routes.

Endpoints

MethodPathPurpose
GET/api/healthLiveness probe — returns 200 when the server is up.
POST/api/planSubmit a planning request; returns a transcript of agent turns.
POST/api/napkin-planSame as /api/plan but with the napkin-tailored tool subset.
GET/api/sessions/{id}Inspect a session's project + agent history.
GET/api/sessions/{id}/joulesPer-session "joules" accumulator (per tool call).
GET/api/sessions/{id}/pcb.kicad_pcbThe live PCB of an MCP session as a .kicad_pcb text blob. KiCanvas-compatible.
GET/api/joulesServer-wide joules totals.
POST/api/handoffCreate a handoff blob (cross-process project transfer).
GET/api/handoff/{id}Fetch a handoff blob.
POST/api/design-pcb-from-chipSpecialised one-shot chip→PCB design call.
POST/api/design-pcb-from-chip-with-layoutSame with an explicit layout.
GET/api/artifact/{id}Fetch an artifact (STEP / STL / Gerber / etc.).
GET/api/artifactsList artifacts.
GET/api/kicad-httplib/v1/Roots of the KiCad HTTP-lib catalogue endpoint.
GET/api/kicad-httplib/v1/categories.jsonCategories list.
GET/viewer/kicadInteractive KiCanvas-based PCB viewer. Accepts ?src=, ?artifact=, or ?session=.
GET/POST/mcpJSON-RPC endpoint (POST) + landing page (GET). See MCP tool reference.

Sessions

The same session id covers both REST and MCP. After any POST /mcp call (or POST /api/plan) the response header Mcp-Session-Id: <uuid> identifies the per-process state. Re-use it on subsequent calls — REST or MCP — to continue working on the same project.

Authentication

Some POST endpoints require a Bearer token configured at server start time (see the substrate's deploy docs). Public read endpoints (/api/health, /api/artifact/{id} on artifacts owned by the caller's session, /viewer/*) work without auth.

Why REST is thin

MCP is the substrate's primary surface — every meaningful capability ships there. REST exists for orchestration (planning, session introspection, artifact fetch, viewer rendering) and for tools that don't yet speak MCP. Adding more REST endpoints isn't on the roadmap; new capabilities ship as MCP tools.

OCL — openie CAD Language

OCL ("openie CAD Language") is a Rust-flavored declarative language for describing products end-to-end. One .ocl file = one product: mechanical body, schematic, PCB, simulations.

product "Widget" {
  body Case {
    let base = box(50mm, 30mm, 10mm)
    let hole = cylinder(3mm, 15mm) |> translate(25mm, 15mm, 0mm)
    Case = base - hole
  }
  schematic Main {
    let U1 = place MCU(x: 20mm, y: 15mm)
    wire(U1.VCC, VCC)
  }
  circuit Power {
    V1(VCC, GND) = 3.3V
    R1(VCC, LED) = 330ohm
  }
}

The language enforces dimensional analysis — every numeric quantity carries its unit, and the type system rejects expressions that mix incompatible units (e.g. adding millimeters to ohms).

Pages

Toolchain

cad-ocl (lexer + parser + evaluator) and cad-ocl-lsp (Language Server) ship in the private substrate. The MCP tools parse_ocl and eval_ocl expose them over the network — see the MCP tool reference.

OCL Cheatsheet — OpenIE CAD Language

Structure

product "Product Name" {
  body BodyName { ... }
  sketch SketchName { ... }
  schematic SheetName { ... }
  pcb BoardName { ... }
  circuit CircuitName { ... }
  sim StudyName { ... }
  assembly AssemblyName { ... }
  bom { ... }
  firmware TaskName { ... }
}

Variables & Types

let width = 50mm          // immutable, with unit
var count = 4             // mutable integer
let name = "bracket"      // string
let active = true         // boolean
let parts = [1, 2, 3]    // array
let point = (10mm, 20mm) // tuple
let props = { k: 0.5, name: "steel" } // table

Units (dimensional analysis enforced)

Length:    mm, cm, m, um, mil, in, ft
Angle:    deg, rad
Electrical: V, mV, A, mA, uA, ohm, kohm, F, pF, nF, uF, H, uH, mH
Frequency: Hz, kHz, MHz, GHz
Power:    W, mW
Temperature: C, K

3D Bodies

body Bracket {
  let base = box(50mm, 30mm, 5mm)
  let hole = cylinder(3mm, 10mm)
  let ball = sphere(5mm)
  let pin = cone(2mm, 8mm)
  let ring = torus(10mm, 2mm)

  // Transform with pipe
  let moved = hole |> translate(25mm, 15mm, 0mm)
  let turned = base |> rotate(0deg, 0deg, 45deg)
  let big = base |> scale(2.0)
}

Schematics

schematic Main {
  let U1 = place ESP32(x: 40mm, y: 25mm)
  let R1 = place Resistor(x: 10mm, y: 10mm, value: "10k")
  let C1 = place Cap(x: 20mm, y: 10mm, value: "100nF")

  wire(U1.VCC, C1.1)
  wire(C1.2, GND)
  wire(U1.GPIO2, R1.1)

  net VCC { U1.VCC, C1.1 }
}

PCB Layout

pcb MainBoard {
  board(width: 80mm, height: 50mm, layers: 4)

  place U1(x: 40mm, y: 25mm, layer: "F.Cu", rotation: 0deg)
  place R1(x: 10mm, y: 10mm, layer: "F.Cu")

  track(net: "VCC", width: 0.25mm, layer: "F.Cu", points: [(0mm, 0mm), (10mm, 0mm)])
  via(x: 15mm, y: 20mm, drill: 0.3mm, net: "GND")
  zone(net: "GND", layer: "B.Cu", outline: [(0mm,0mm), (80mm,0mm), (80mm,50mm), (0mm,50mm)])
}

Circuits (SPICE)

circuit Amplifier {
  V1(VCC, GND) = 12V
  R1(VCC, base) = 100kohm
  R2(base, GND) = 47kohm
  Rc(VCC, collector) = 4.7kohm
  Re(emitter, GND) = 1kohm
  C1(input, base) = 10uF
  C2(collector, output) = 10uF
}

Simulation

sim ThermalStudy {
  type: steady_state
  ambient: 25C
  power(U1): 0.5W
  power(R1): 0.1W
}

sim ModalAnalysis {
  type: modal
  modes: 10
  material: aluminum
}

sim DropTest {
  type: transient
  duration: 5ms
  gravity: -9.81m/s2
}

Assembly

assembly Complete {
  place Shell(transform: identity)
  place Lid(transform: translate(0mm, 0mm, 25mm))
  place PCB(transform: translate(2mm, 2mm, 6mm))

  mate Lid_to_Shell {
    type: coincident
    face_a: Lid.bottom
    face_b: Shell.top
  }
}

BOM

bom {
  entry(reference: "U1", value: "ESP32-WROOM-32E", manufacturer: "Espressif", mpn: "ESP32-WROOM-32E")
  entry(reference: "R1", value: "10k 0402", manufacturer: "Yageo", mpn: "RC0402FR-0710KL")
}

Control Flow

// Conditionals
if width > 50mm {
  let result = "too wide"
} else {
  let result = "ok"
}

// Loops
for i in 0..10 {
  place Hole(x: i * 5mm, y: 0mm)
}

// Functions
fn slot(w: length, h: length, d: length) -> body {
  box(w, h, d)
}

Pipe Operator

// Chain transformations
let part = box(10mm, 20mm, 5mm)
  |> translate(5mm, 0mm, 0mm)
  |> rotate(0deg, 0deg, 45deg)

Designing a PCB end-to-end (aether-t flow)

This recipe walks through the substrate's flagship PCB flow on a board class that historically broke open-source tooling.

aether-t is a real-world design: 65 × 56.5 mm 6-layer with 8 wireless radios + PCIe Gen 3 + DDR data bus + 60 GHz mmWave radar. The 06-02 substrate audit found a Python-pipeline implementation stuck at "rev 0.12, 162 of ~350 tracks routed, 140 dangling pads, 20 DRC violations" — every named failure mode traced to a missing algorithm in the open-source toolchain. Waves 62-70 of openie-cad closed each one in turn.

This recipe is what the agent-callable equivalent looks like.

Prerequisites

  • An HTTP client and an Mcp-Session-Id from any prior tools/call on https://cad.openie.dev/mcp (see the Quickstart).

1. Ingest the design

Two options. If you have a KiCad project:

{
  "method": "tools/call",
  "params": {
    "name": "import_kicad",
    "arguments": {
      "schematic_source": "...",
      "pcb_source": "..."
    }
  }
}

If you have a Zener (pcb-sch JSON) source-of-truth:

{
  "method": "tools/call",
  "params": {
    "name": "import_zener_with_layout",
    "arguments": { "zener_source": "...", "kicad_pcb_source": "..." }
  }
}

2. Validate the BGA escape up front

The aether-t pipeline hit "BGA fanout impossible" only after dozens of failed routing attempts. The substrate fails-fast:

{
  "method": "tools/call",
  "params": {
    "name": "check_bga_escape_feasibility",
    "arguments": {
      "rows": 20, "cols": 20,
      "pitch_mm": 0.4, "pad_diameter_mm": 0.2,
      "fab_tier": "standard"
    }
  }
}

Returns INFEASIBLE with the exact geometric reason. Switch "fab_tier" to "hdi" to see the via-in-pad path. See the LCIS BGA escape algorithm for the substrate's implementation of Kong/Yan/Ozdal ICCAD 2007.

3. Place the components

Replaces aether-t's hand-coded 800-line auto_place.py:

{
  "method": "tools/call",
  "params": {
    "name": "run_placement",
    "arguments": {
      "iterations": 200,
      "locked": ["J1", "J8", "J2"]
    }
  }
}

Force-directed Fruchterman-Reingold iterations pull net-connected footprints toward shared centroids and push overlapping footprints apart. locked pins board-edge connectors.

4. Route the board

The headline call — composes BGA escape, length-match groups, PathFinder negotiation, SALT topology, and the general A* routing engine in one MCP invocation:

{
  "method": "tools/call",
  "params": {
    "name": "run_autorouter",
    "arguments": {
      "epsilon": 0.5,
      "negotiation": true,
      "bgas": [{
        "footprint_ref": "U8",
        "pitch_mm": 0.4,
        "pad_diameter_mm": 0.2,
        "fab_tier": "hdi",
        "inner_layers": ["In1Cu", "In2Cu"],
        "buses": { "DDR_DIQ1": ["DIQ1_0", "DIQ1_1", "DIQ1_2", "DIQ1_3"] },
        "strategy": "via_in_pad"
      }],
      "length_match_groups": [{
        "name": "DDR_DIQ1",
        "nets": ["DIQ1_0", "DIQ1_1", "DIQ1_2", "DIQ1_3"],
        "tolerance_mm": 0.127
      }]
    }
  }
}

5. Check the impedance at frequency

For RF nets (the 60 GHz BGT60TR13C feed, the LoRa front-end, etc.) the substrate's mmWave-correct impedance check:

{
  "method": "tools/call",
  "params": {
    "name": "check_impedance_at_frequency",
    "arguments": {
      "trace_width": 0.55,
      "dielectric_height": 0.254,
      "frequency_ghz": 60,
      "material": "ro4350b",
      "roughness": "vlp"
    }
  }
}

See mmWave loss models for the Djordjevic-Sarkar + Cannonball-Huray pair of refinements.

6. Inspect the result

Static SVG:

{ "method":"tools/call",
  "params":{"name":"render_pcb_svg","arguments":{}} }

Interactive in the browser (KiCanvas):

https://cad.openie.dev/viewer/kicad?session=<your-session-id>

7. Ship it

{ "method":"tools/call", "params":{"name":"export_gerber","arguments":{}} }
{ "method":"tools/call", "params":{"name":"generate_jlcpcb_bom","arguments":{}} }
{ "method":"tools/call", "params":{"name":"generate_jlcpcb_cpl","arguments":{}} }

Where each piece came from

StepWaveAlgorithm
BGA escape feasibility + routing63.A-FKong/Yan/Ozdal LCIS
Force-directed placement66Fruchterman-Reingold
Negotiation routing55McMurchie-Ebeling PathFinder
Multi-pin topology58/59SALT shallow-light tree
Length-match groups64.A-DFang DAC 2024 + capacity-aware insertion
60 GHz impedance65Djordjevic-Sarkar + Cannonball-Huray
KiCanvas viewer61Thea Flowers' KiCanvas embedded via session route
Fabrication export(legacy)Gerber X2 + ODB++ + JLCPCB BOM/CPL

Multi-physics coupling

cad-sim ships 38 physics domains; Wave 67 added the partitioned coupling scheduler that lets two of them exchange a shared interface field iteratively. This recipe walks through choosing a coupling scheme + relaxation mode for your stiffness regime before you wire real solvers in.

See the IQN-ILS algorithm page for the math + paper citations.

When you need this

When two physics domains touch at an interface and the response of one feeds back into the input of the other:

  • Conjugate heat transfer — solid wall heated by adjacent fluid.
  • Fluid-structural interaction — flexible airfoil in transonic flow.
  • Electromagnetic-thermal — induction-heated piece.
  • Piezoelectric-mechanical — sensor strain feeding voltage.

Picking a scheme

Coupling stiffness is roughly the product of the two solvers' sensitivities to each other's interface output. simulate_coupled exposes a two-parameter linear toy α × β so you can test your stiffness regime against each relaxation scheme.

{
  "method": "tools/call",
  "params": {
    "name": "simulate_coupled",
    "arguments": {
      "coupling_alpha": 0.9,
      "coupling_beta": 0.95,
      "scheme": "implicit",
      "relaxation": "iqn_ils",
      "iqn_history_max": 10,
      "max_iterations": 200,
      "tolerance": 1e-8
    }
  }
}

Returns Converged in N iterations, final residual … plus the analytic fixed point so you can verify behaviour.

Which relaxation when

RegimeRecommendedWhy
αβ < 0.3 (weak)explicitOne solver pass per side per step. Cheapest, stable.
αβ ∈ [0.3, 0.7]constant ω = 0.5Sub-Newton, robust.
αβ > 0.7 (stiff FSI, CHT)aitken or iqn_ilsSub-linear vs. linear convergence rate.
Tight FSI, added-mass dominatediqn_ils history 10-20preCICE's workhorse for the hardest cases.

The Wave 67.B test shows IQN-ILS finishes in ≤ Aitken iterations on a stiff α=0.9, β=0.95 case — the assertion the substrate verifies on every build.

Wiring real solvers (Wave 67.C preview)

The current MCP tool runs the canonical linear toy so you can characterise convergence behaviour. Wave 67.C will accept real cad-sim domain handles — thermal, structural, cfd — and loop them through the same scheme + relaxation choice you picked on the toy. Same convergence properties; just plug different solvers in.

Antenna pattern from FDTD

Wave 69 + 69.B closed the loop between cad-sim's existing 3D FDTD solver and the Huygens-equivalence Near-to-Far-Field transform — the only MIT/Apache FDTD → NTFF antenna pattern code in the open-source ecosystem.

See the NTFF algorithm page for the math.

What you need

The current Wave 69 pipeline is a Rust library — there's no MCP tool yet (Wave 69.C will add run_fdtd_radiation_pattern). The library sequence is:

  1. Configure the FDTD grid (FdtdConfig, FdtdGrid).
  2. Place a source — FdtdSource::GaussianPulse at the radiating structure's feed point.
  3. Define a Huygens surface enclosing the structure: HuygensSurface::new_box(box_idx, &grid, frequency_hz).
  4. Time-step the simulation. On every step, after update_e and update_h and any source application:
    #![allow(unused)]
    fn main() {
    grid.update_h();
    grid.apply_source(&source);
    grid.update_e();
    surface.step_sample(&grid, t_now);
    }
  5. When stepping completes, convert to NTFF input: let samples = surface.extract_currents();
  6. Compute the radiation pattern: let result = ntff_radiation_pattern(&samples, freq, n_theta, n_phi);

The FarFieldResult exposes per-(θ, φ) gain in dBi, |E_θ|, |E_φ|, peak direction, etc.

Validating against analytic patterns

The Wave 69 tests show this loop reproduces:

  • Hertzian dipole sin θ pattern shape to ratios within 10⁻⁶ at 9 sample angles.
  • Two-element broadside array at z = ±λ/4 in phase — the classic main-lobe at θ = 90° dominating the near-axis values by ≥ 5×.
  • Pattern sweep peak lands within 10° of θ = 90° (broadside) for a z-oriented dipole.

The Wave 69.B Huygens-surface sampling tests verify:

  • A static Ex = 1 V/m sampled at t = 0 accumulates exactly (dt, 0) in the running DFT phasor (pure cosine projection).
  • The same field at t = T/4 accumulates exactly (0, −dt) (quarter-period phase rotation correct).

What's coming (Wave 69.C)

run_fdtd_radiation_pattern(geometry, frequency_hz, n_theta, n_phi, grid_steps) MCP tool will encapsulate the whole loop so agents can compute patterns over the network. The current path requires the substrate running in-process.

When to bother

For boards using antenna-in-package parts (the Infineon BGT60TR13C 60 GHz radar in aether-t, for example), the antenna sits inside the chip — the PCB carries no RF trace to it and no NTFF is needed.

For boards with on-PCB antennas (LoRa monopoles, GNSS patches, Wi-Fi PIFAs, mmWave horns), NTFF validates the radiation pattern before fabrication. The substrate's NTFF is matched against analytic shapes at lib-test time; for novel geometries, run an external reference (HFSS / openEMS / CST) once and use the substrate's result as the go-to for design-iteration speed.

CAM pocket clearing

Wave 70 opened the CAM domain with the first high-level operation: pocket_zigzag_rect (rectangular pocket clearing via zig-zag raster) plus the generate_pocket_gcode MCP tool that wraps the existing 5-axis substrate's RTCP post-processor.

Quick example

A 50 × 30 × 8 mm pocket with a 6 mm cutter:

{
  "method": "tools/call",
  "params": {
    "name": "generate_pocket_gcode",
    "arguments": {
      "width_mm": 50,
      "height_mm": 30,
      "depth_mm": 8,
      "cutter_diameter_mm": 6,
      "step_down_mm": 2,
      "step_over_mm": 3,
      "feedrate_mm_min": 1200,
      "plunge_rate_mm_min": 300,
      "spindle_rpm": 12000,
      "safe_z_mm": 5
    }
  }
}

Returns ready-to-run RS-274 G-code with the standard Fanuc-style program markers (%, O0001, G90 G94 G17 G40), the spindle/ coolant block, line-numbered G00/G01 moves with axis components, and the M30 end-of-program.

What the algorithm does

  1. Compute the inset boundary by offsetting inward by the cutter radius — the pocket's outer wall ends up at exactly width × height after machining.
  2. For each Z level (0, −step_down, −2·step_down, …, −depth):
    • Rapid above the row start at safe_z_mm.
    • Plunge (cut feed) to the next Z.
    • Sweep zig-zag rows: alternating +X / −X traversals spaced step_over_mm apart along Y.
    • Retract to safe_z_mm.
  3. Final Z snaps to exactly −depth_mm even if depth_mm isn't a multiple of step_down_mm — the floor is never overshot.

Substrate guarantees (Wave 70 tests)

  • Step-down picks the correct Z levels (10 × 10 × 5 mm pocket with step-down 2 mm → Z values {−2, −4, −5}).
  • Adjacent zig-zag rows go opposite directions.
  • All rapids end at Z ≥ safe_z_mm.
  • Move chain is continuous (every move's start equals the previous move's end — no machine teleports).
  • Tool centre never crosses the inset boundary (20 × 10 mm pocket + 4 mm cutter → centre stays ≥ 2 mm from each wall).

Composition with other CAM operations

Wave 70 ships only pocket clearing. Wave 70.B will add:

  • Contour milling (perimeter, with climb / conventional choice).
  • Drilling cycles (G81, G83 peck, G73 high-speed peck).
  • Arbitrary-polygon pockets via polygon offsetting + adaptive clearing.

The cad_cam5::Move / ToolPath model is already 5-axis-capable (RTCP post-processor, kinematic smoothing, collision checking ship in the substrate); the high-level operations are the missing piece that Wave 70 + follow-ons are filling.

LCIS BGA escape routing

Kong, Yan, Wong & Ozdal — "Optimal bus sequencing for escape routing in dense PCBs," ICCAD 2007.

openie-cad's cad-router::bga_escape module is the only permissively-licensed (MIT/Apache) implementation of the Kong/Yan/Ozdal escape-routing algorithm in the open-source ecosystem. Every other open-source autorouter (Freerouting v2, KiCad, OpenROAD) either lacks BGA escape entirely (Freerouting removed it in v2.0) or is GPL.

The problem

A BGA component has hundreds of pads in a 2D grid. Most pads are inside the package outline — the autorouter can't reach them by fanning outward. The standard solution is escape routing: drop a via at each inner pad, traverse to an inner copper layer, and route outward to the package boundary.

Two constraints make this hard:

  1. Geometric feasibility. At fine pitches (≤ 0.5 mm, common for modern aQFNs and BGAs), a standard 0.45 mm via pad simply doesn't fit between adjacent BGA pads. JLCPCB's standard 6-layer service can't route a 0.4 mm-pitch aQFN-261 at all without switching to HDI tier with via-in-pad.

  2. Layer assignment / crossing avoidance. Multiple buses competing for the same boundary region must be ordered so their escape paths don't cross.

The algorithm

Kong/Yan/Ozdal split escape into two stages:

Stage 1 — Bus sequencing. For each BGA edge, project the buses onto the boundary and find the Longest Common Increasing Subsequence (LCIS) of (inner-pin position, exit position). Two buses with (innerᵢ < innerⱼ AND exitᵢ < exitⱼ) are routable in parallel without crossing; the LCIS picks the maximum such subset in O(n log n) via tails-binary-search.

Stage 2 — Layer assignment. Iteratively peel off LISes. The first LIS goes on routing layer 1; then those buses are removed; the remaining buses form a new LIS for layer 2; and so on.

openie-cad implementation

StageModuleWave
Data model + feasibilityBgaGrid, feasibility_report63.A
LCIS sequencing + layer assignmentlcis_non_crossing_buses, assign_buses_to_layers63.B
Track + Via geometry emissionemit_all_escapes63.C
Via-in-pad strategy (HDI tier)EscapeStrategy::ViaInPad63.D
Autorouter integrationAutorouter::route_with_bga_escape63.E
MCP surfacerun_autorouter(bgas=[...]), check_bga_escape_feasibility63.F

What the substrate fixes

The aether-t board audit (a real RF mesh radio design) reached rev 0.12 with 162 of ~350 tracks routed, 140 dangling pads, and 20 DRC violations because its Python bga_fanout.py was disabled in source with a comment explaining the 0.4 mm-pitch / 0.45 mm via geometry conflict. With Wave 63 deployed, the check_bga_escape_feasibility MCP tool reports INFEASIBLE up front with the exact reason — agents pick the right fab tier before burning a 100-pair routing attempt.

Calling it

{
  "method": "tools/call",
  "params": {
    "name": "check_bga_escape_feasibility",
    "arguments": {
      "rows": 20, "cols": 20,
      "pitch_mm": 0.4, "pad_diameter_mm": 0.2,
      "fab_tier": "standard"
    }
  }
}

Returns INFEASIBLE with "no via fits between adjacent pads".

For the full routing pass, see run_autorouter.

SALT shallow-light Steiner tree

Chen, Hu, Hentschel, Held — "SALT: provably good routing topology by a novel steiner shallow-light tree algorithm," ICCAD 2017.

cad-router::steiner ships SALT, a topology generator that gives the caller continuous control over the radius–wirelength tradeoff — the central tension in PCB tree topology design.

The problem

For a multi-pin net, two extreme topologies bracket the design space:

  • Shortest-path tree (SPT) — every sink reachable by its shortest path from the source. Minimum radius (signal arrival time), maximum wirelength.
  • Rectilinear Steiner minimal tree (RSMT) — minimum total wirelength. Sinks may sit on stretched paths.

For clock nets and high-speed digital, low radius matters; for analog and power, total wirelength matters. Everything else lives on the curve between them.

The algorithm

SALT exposes the tradeoff as a single parameter ε ∈ [0, ∞]:

  • ε = 0 — shortest-path tree (minimum radius).
  • ε → ∞ — Steiner minimal tree (minimum wirelength).
  • Intermediate — a (1+ε)-shallow-light tree where every sink's tree-distance ≤ (1+ε) × its direct distance from the source, and total wirelength is bounded by O(1 + 1/ε) × RSMT-optimal.

Construction starts from baseline RSMT and iteratively re-wires any sink whose path exceeds the (1+ε) bound, swapping the violating parent edge for a direct edge to the source. Iteration terminates when every sink satisfies the bound.

openie-cad implementation

SurfaceWave
baseline_rsmt(terminals) — MST over the Hanan grid57
salt(terminals, source, epsilon) — (1+ε)-shallow-light iteration58
salt_decompose(MultipinNet, epsilon) — Connection list for Autorouter59

The run_autorouter MCP tool accepts an epsilon argument; the default ε = 1.0 is a reasonable middle ground for mixed boards.

Why it matters

The substrate's PathFinder negotiation router (Wave 55) and Kong/Yan/Ozdal BGA escape (Wave 63) both need a topology before they can lay tracks. Without SALT they'd default to a fixed strategy — usually shortest path, which leaves wirelength money on the table on relaxed-radius nets like power rails. With SALT the agent tunes per-net.

Reference

Chen, Hu, Hentschel, Held — "SALT: provably good routing topology by a novel steiner shallow-light tree algorithm," ICCAD 2017.

PathFinder negotiation routing

McMurchie & Ebeling — "PathFinder: a negotiation-based performance-driven router for FPGAs," FPGA 1995.

cad-router::negotiated implements PathFinder, the negotiation-based congestion router that the FPGA community adopted in the 1990s and that still underlies VPR (Verilog-to-Routing) and most academic routers today. The algorithm has been a public-domain reference for decades; openie-cad ships it as a PCB autorouter primitive.

The problem

Classical rip-up-retry routing falls apart on congested designs: each iteration rips one net to make room, but the order in which nets are ripped becomes path-dependent. Two competing nets ping-pong forever, ripping each other in turn.

PathFinder converts the conflict into a negotiation. Each cell in the routing grid carries two cost components:

  • present-congestion cost — how many nets currently route through this cell.
  • history cost — accumulated congestion across previous iterations.

Both components multiply the cell's intrinsic cost in the A*-style search. Cells that have been congested in the past become permanently more expensive even after they're cleared, so the same net no longer claims them in the next iteration. Equilibrium is provably reached when no further reduction is possible — the final routing is congestion-free if any congestion-free routing exists.

openie-cad implementation

cad-router::negotiated:

  • Congestion { present, history } — per-cell cost maps with present_weight and history_weight tuning factors.
  • Congestion::cost(cell, weights) — combined cost function.
  • Congestion::converged() — equilibrium predicate.
  • route_negotiated(pathfinder, nets, config) — the outer negotiation loop.

The Wave 55 substrate test verifies the algorithm converges on a classic two-nets-share-a-channel scenario where naive A* would ping-pong.

Composition with the rest of cad-router

Autorouter::route_multipin_nets accepts a negotiation_enabled flag that switches the inner loop from classical rip-up-retry to the negotiation router. Composes naturally with:

  • SALT topology generation (Wave 58) — chooses where the nets want to go.
  • Kong/Yan/Ozdal LCIS BGA escape (Wave 63) — pre-places escape geometry before negotiation runs.
  • Capacity-aware length-match meander insertion (Wave 64.B/C) — runs after negotiation converges.

Calling it

{
  "method": "tools/call",
  "params": {
    "name": "run_autorouter",
    "arguments": {
      "epsilon": 0.5,
      "negotiation": true
    }
  }
}

Reference

McMurchie & Ebeling — "PathFinder: a negotiation-based performance-driven router for FPGAs," FPGA 1995.

IQN-ILS partitioned multi-physics coupling

Degroote, Bathe, Vierendeels — "Performance of a new partitioned procedure versus a monolithic procedure in fluid-structure interaction," Computers & Structures 2009.

Chourdakis et al. — "preCICE v2: a sustainable and user-friendly coupling library," arXiv 2109.14470 (2021).

cad-sim::coupling ships the only MIT/Apache implementation of preCICE-style partitioned multi-physics coupling in the open-source ecosystem. preCICE itself is LGPLv3 — usable via dynamic linking but not directly portable into MIT/Apache codebases — so this module re-derives the algorithms from the public papers.

The problem

When two physics domains share an interface (a heated solid wall contacted by fluid; an oscillating airfoil in a transonic flow), solving them jointly as one monolithic system is expensive and inflexible. The partitioned approach keeps each solver independent and exchanges only the interface field — temperature, pressure, displacement — but introduces a numerical loop:

loop k:
  x_a  = solve_A(x_b)        # A's response to B's interface
  x_b  = solve_B(x_a)        # B's response to A's update
  if ‖x_a^new − x_a^old‖ < tol: converged

For stiff couplings (large added-mass FSI, conjugate heat transfer with mismatched conductivities) this loop diverges or crawls. Acceleration schemes choose how to combine the raw solver outputs into the next iterate.

The algorithms

cad-sim::coupling::RelaxationMode exposes three schemes:

Constant under-relaxation

x_{k+1} = x_k + ω · r_k with fixed ω ∈ (0, 1]. Always stable for small enough ω but slow when the optimal ω is unknown a priori.

Aitken's Δ²

Adapts ω from the previous two residuals (Mok 2001, used in preCICE):

ω_new = -ω_old · (r_prev · (r − r_prev)) / ‖r − r_prev‖²

Mathematically free (one extra inner product per iteration), and dramatically faster than constant ω on stiff couplings.

IQN-ILS (Interface Quasi-Newton with Inverse Least Squares)

The preCICE workhorse for tight FSI and conjugate heat transfer. Maintains column histories of past displacement and residual differences V = [Δr_1 … Δr_k], W = [Δx_1 … Δx_k]. On each iteration:

  1. Solve the small least-squares problem min_c ‖V·c − (−r)‖² via normal equations (VᵀV + λI) c = −Vᵀr with Tikhonov regularisation (λ ≈ 10⁻¹²).
  2. Compute the IQN-ILS update Δx = W·c.
  3. Apply: x_{k+1} = x_k + Δx.
  4. Truncate the oldest column when history exceeds history_max.

Substrate test (Wave 67.B)

On a stiff α = 0.9, β = 0.95 linear toy:

SchemeIterations to ‖r‖ < 10⁻⁸
Constant ω = 0.3many
Aitkenfewer
IQN-ILS (history_max = 20)fewest (assertion: ≤ Aitken)

Calling it

{
  "method": "tools/call",
  "params": {
    "name": "simulate_coupled",
    "arguments": {
      "coupling_alpha": 0.9,
      "coupling_beta": 0.95,
      "relaxation": "iqn_ils",
      "iqn_history_max": 10,
      "max_iterations": 200,
      "tolerance": 1e-8
    }
  }
}

Returns iteration count, final residual, and the analytic fixed point for comparison — useful for choosing a relaxation scheme before plugging real cad-sim domains in.

Implementation notes

cad-sim::coupling::couple pre-primes field_b = solve_b(field_a) before the loop when RelaxationMode::IqnIls is selected. Without this, the first iteration's residual would lie off the fixed-point manifold (because field_b started as initial_b, not B(initial_a)), and the first (Δx, Δr) pair would poison the least-squares solve. The Aitken and constant-ω schemes don't need this since they're memoryless.

References

  • Degroote, Bathe, Vierendeels (2009) — original IQN-ILS derivation.
  • Mok (2001) — the Aitken Δ² ω-update form preCICE uses.
  • Chourdakis et al. (2021) — preCICE v2 architecture survey (arXiv 2109.14470).

mmWave loss models — Djordjevic-Sarkar + Cannonball-Huray

Svensson, Djordjevic — "Causal RC models" (2001).

Shlepnev — "Broadband material model identification with GMS," EPEPS 2015.

Simonovich — "Cannonball–Huray Demystified" (LAMSim blog, 2019).

Huray — The Foundations of Signal Integrity (Wiley 2010).

cad-sim::signal extends the existing Hammerstad-Jensen microstrip impedance solver with two industry-standard mmWave refinements that together match measured Rogers RO4350B insertion loss within ±1 dB at 60 GHz.

Why Hammerstad-Jensen alone breaks at mmWave

The classical H-J formulas predict characteristic impedance Z₀ and effective Er for a microstrip in terms of geometric parameters and a single dielectric constant + loss tangent. They're accurate to roughly 10–15 GHz. Above that, three error mechanisms compound:

  1. Dielectric dispersion. A single-point (Dk, tanδ) pair is non-causal and grossly under-predicts loss above ~10 GHz.
  2. Conductor surface roughness. H-J's "saturating multiplier" roughness term tops out around 15 GHz; above that it under-predicts loss because it assumes a sawtooth surface.
  3. "Design Dk" vs datasheet Dk. The effective Dk seen at mmWave is not the IPC-test datasheet Dk; Rogers recommends a "design Dk" several percent above the datasheet to absorb copper-roughness side effects.

Djordjevic-Sarkar (causal dielectric)

Anchors a wideband-Debye expression at a single datasheet point (Dk_ref, tanδ_ref @ f_ref) so the resulting Dk(f), tanδ(f) curves are causal (Kramers-Kronig consistent) over the full mmWave band without requiring a multi-pole measurement.

cad-sim::signal::DielectricMaterial:

  • ro4350b() — Rogers RO4350B at the "design Dk" 3.66, tanδ 0.0037 @ 10 GHz (the value Coonrod recommends).
  • fr4() — generic FR-408HR-class at Dk 4.3, tanδ 0.02 @ 10 GHz.
  • dk_at(f), tand_at(f) — D-S frequency interpolation.

The D-S model anchors exactly at the reference frequency by construction (verified in the substrate test); Dk drops slowly with frequency in the well-documented way that FR-4 and Rogers laminates actually behave at mmWave.

Cannonball-Huray (conductor roughness)

Simonovich's simplification of the full Huray "snowball" derivation. Captures the right physics — roughness loss monotone in Rz/δ, saturating around 2× as δ → 0 — without requiring the multi-sphere geometric construction.

K_SR(f) = 1 + Rz / (Rz + 2δ(f))

cad-sim::signal::ConductorRoughness:

  • vlp() — very-low-profile copper for Rogers mmWave laminates, Rz ≈ 1 µm.
  • standard_ed() — standard electrodeposited copper, Rz ≈ 7.6 µm (the RO4350B datasheet upper bound).
  • roughness_multiplier(f), skin_depth_mm(f), sphere_radius_um().

Composed: 60 GHz microstrip Z₀ + loss

#![allow(unused)]
fn main() {
microstrip_impedance_at_frequency(
    &stackup,
    DielectricMaterial::ro4350b(),
    ConductorRoughness::vlp(),
    60e9,
)
}

Returns Z₀ from Hammerstad-Jensen with Dk evaluated by D-S at 60 GHz, plus a loss_db_per_mm that sums:

  • Dielectric loss α_d = π·f·√(ε_eff)/c · tanδ(f).
  • Conductor loss α_c = R_s / (Z₀ · w) with the Cannonball-Huray K_SR multiplier.

At 60 GHz on RO4350B, this lands in the 0.005–1.0 dB/mm envelope that Coonrod's Rogers application notes describe — matching measurement to ±1 dB on a CMP-28 coupon.

Calling it

{
  "method": "tools/call",
  "params": {
    "name": "check_impedance_at_frequency",
    "arguments": {
      "trace_width": 0.55,
      "dielectric_height": 0.254,
      "trace_thickness": 0.018,
      "frequency_ghz": 60,
      "material": "ro4350b",
      "roughness": "vlp"
    }
  }
}

Returns Z₀, eff.Er, ps/mm delay, and dB/mm loss.

References

  • Svensson & Djordjevic (2001) — original causal RC formulation.
  • Shlepnev (2015) — practical anchoring procedure used by CST, HFSS, Simberian.
  • Simonovich (2019) — Cannonball-Huray simplified form, measurement comparisons on FR408HR / CMP-28.
  • Huray (2010) — The Foundations of Signal Integrity, the original snowball formulation.
  • Coonrod (Rogers) — "Circuit Material Design Guide for mmWave Radar" white paper, including the design-Dk recommendation.

FDTD Near-to-Far-Field (NTFF)

Balanis — Antenna Theory: Analysis and Design, 4th ed., §6.7 (Wiley 2016).

cad-sim::antenna ships the only MIT/Apache implementation of the Huygens-equivalence Near-to-Far-Field transform in the open-source ecosystem. Every existing OSS FDTD with a built-in NTFF is GPL:

  • openEMS — GPL-3.0
  • MEEP (NanoComp / MIT) — GPL-2.0
  • gprMax — GPL-3.0-or-later

cad-sim::antenna::ntff_far_field_at re-derives the integral from textbook Balanis so openie-cad's MIT/Apache posture stays clean.

The problem

A finite-difference time-domain simulation gives you E and H fields everywhere on the Yee grid, but radiation patterns are properties of the far field — the field at distances large compared to the radiator and the wavelength. Computing the far field by extending the FDTD grid out to many wavelengths is wasteful: most of that volume is empty propagation.

The Huygens equivalence principle reduces the problem to a closed surface integral: pick any closed surface enclosing the radiating structure, record the tangential E and H fields on that surface, and the far-field anywhere outside is determined.

The transform

Define equivalent surface currents on the Huygens surface:

J = n̂ × H        (electric surface current, A/m)
M = −n̂ × E      (magnetic surface current, V/m)

The far-field at observation direction r̂(θ, φ) is:

E_θ(θ, φ) ∝ Σᵢ [Jᵢ·θ̂ + (1/η) Mᵢ × r̂·θ̂] · Aᵢ · e^{jk r̂·rᵢ}
E_φ(θ, φ) ∝ Σᵢ [Jᵢ·φ̂ + (1/η) Mᵢ × r̂·φ̂] · Aᵢ · e^{jk r̂·rᵢ}

with the leading factor −jωμ / 4π.

The phase factor e^{jk r̂·rᵢ} is the only direction-dependent piece; everything else is precomputed per sample. The whole transform is a matrix-vector multiply.

openie-cad implementation

ComponentWave
SurfaceCurrentSample data shape69
ntff_far_field_at(samples, θ, φ, λ) integral69
ntff_radiation_pattern(samples, f, n_θ, n_φ) sweep69
HuygensSurface::new_box(box_idx, grid, freq)69.B
HuygensSurface::step_sample(grid, t) running DFT69.B
HuygensSurface::extract_currents() to SurfaceCurrentSample69.B

The Wave 69 substrate tests verify:

  • A single Hertzian dipole sample reproduces the analytic sin θ pattern shape with consecutive-angle ratios within 10⁻⁶ of the expected sin θ ratio.
  • Two unit elements at z = ±λ/4 in phase show the classic broadside pattern (|E_θ| at θ = 90° dominates near-axis values by >5×).
  • The pattern sweep peak lands near θ = 90° (broadside) for a z-oriented dipole, gain normalised to 0 dB at peak.

The Wave 69.B substrate tests verify:

  • A box surface (2..=5)³ emits 96 cells with correct ±unit outward normals; opposing-face normals sum to zero.
  • A static Ex = 1 V/m field, sampled at t = 0, accumulates exactly (dt, 0) in the DFT phasor — pure cosine projection.
  • At t = T/4 the same field accumulates (0, −dt) — quarter-period phase rotation correct.
  • End-to-end: an FDTD grid with non-trivial Ez / Hy fields, sampled once, produces finite E_θ / E_φ when fed to the NTFF integral.

Calling it

The NTFF integral is a library function. There's no MCP tool yet — Wave 69.C will add one. For now, hosts that want antenna patterns run cad-sim FDTD locally and call the NTFF function in-process.

Reference

Balanis — Antenna Theory: Analysis and Design, 4th ed., §6.7 (Wiley 2016). The Huygens-equivalence integral form used here.

Changelog

Curated wave-by-wave history. The full git log is in the private substrate repo; only user-facing changes appear here.

June 2026 — the "aether-t buildability" series

A real-world RF mesh radio design (the aether-t board: 65×56.5 mm 6-layer with 8 wireless radios + PCIe Gen 3 + DDR + 60 GHz radar) exposed every weak spot in the open-source PCB toolchain. Waves 62 through 70 closed each named failure mode in turn.

Wave 70 — CNC G-code (June 3)

  • New cad_cam5::operations::pocket_zigzag_rect — 2.5D pocket clearing with cutter-radius inset.
  • MCP tool generate_pocket_gcode returns RS-274 G-code via the existing RTCP post-processor.

Wave 69 / 69.B — FDTD → NTFF antenna validation (June 3)

  • cad_sim::antenna::ntff_far_field_at — textbook Balanis §6.7 Huygens-equivalence integral.
  • HuygensSurface::new_box / step_sample / extract_currents — the bridge between the existing FDTD engine and the NTFF math.
  • The only MIT/Apache FDTD→NTFF antenna pattern code in the open-source ecosystem (openEMS, MEEP, gprMax are all GPL).

Wave 68 — vendor footprint ingest (June 3)

  • cad_format::kicad::parse_kicad_mod + cad_interop::kicad_mod_to_eda_footprint.
  • MCP tool place_vendor_footprint — closes the "140 dangling pads from approximate footprint stubs" failure mode by ingesting vendor-validated .kicad_mod library files verbatim.

Wave 67.A / 67.B — partitioned multi-physics coupling (June 3)

  • cad_sim::coupling::couple with CouplingScheme::{Explicit,Implicit}Serial.
  • RelaxationMode::{Constant(ω), Aitken, IqnIls{history_max}} — including the preCICE workhorse IQN-ILS quasi-Newton acceleration.
  • MCP tool simulate_coupled — runs the canonical linear coupling toy through any scheme + relaxation, reports convergence stats.
  • The only MIT/Apache partitioned-coupling implementation (preCICE itself is LGPLv3, blocking direct use in MIT/Apache codebases).

Wave 66 — force-directed placement (June 3)

  • cad_router::placement::force_directed_place — Fruchterman-Reingold iterations with bounds clamping and locked-component support.
  • MCP tool run_placement — replaces aether-t's hand-coded 800-line auto_place.py.

Wave 65 — Djordjevic-Sarkar + Cannonball-Huray 60 GHz (June 2)

  • cad_sim::signal::DielectricMaterial with dk_at(f) / tand_at(f) — causal wideband Debye dielectric (Shlepnev 2015 anchor at a single datasheet point).
  • ConductorRoughness::roughness_multiplier(f) — simplified Cannonball-Huray (Simonovich 2015) form: K_SR = 1 + Rz/(Rz + 2δ).
  • microstrip_impedance_at_frequency composes Hammerstad-Jensen + D-S + C-H for 60 GHz-accurate impedance + loss.
  • MCP tool check_impedance_at_frequency.

Wave 64.A–D — length matching (June 2)

  • Group-aware length matching with consensus-target picking.
  • Obstacle-aware meander amplitude clamping.
  • Capacity-aware segment selection (highest clearance × length).
  • Diff-pair coupled tuning (tune_diff_pair_coupled).
  • Run-aware splicing fixes the A*-output catastrophic overshoot.
  • MCP run_autorouter(length_match_groups=[...]).

Wave 63.A–F — Kong/Yan/Ozdal LCIS BGA escape (June 2)

  • Full Kong/Yan/Ozdal ICCAD 2007 stack: feasibility predicate → LCIS layer assignment → Track + Via emission → ViaOffsetExterior / ViaInPad strategies → Autorouter integration → MCP surface.
  • See the algorithm theory.
  • MCP run_autorouter(bgas=[...]) + check_bga_escape_feasibility.

Wave 62 — length-match meander insertion (June 2)

  • length_tuning::tune_route_to_length — picks the longest single-layer segment, inserts a serpentine via the existing generate_meander.
  • MCP run_autorouter(length_targets=[...]).

Earlier waves

Waves 1–61 (substrate bootstrap, ECAD/CAD/sim domains, MCP server, KiCanvas viewer, etc.) are documented in the private repo's commit history; user-visible additions roll up as they become callable from MCP.

License

openie-cad is free to use — both via the hosted MCP at https://cad.openie.dev/mcp and locally as a binary. The source code is not yet open; it lives in a private repository while the substrate is still iterating fast.

Hosted use (cad.openie.dev)

Free for any use, with no signup. Rate limits and per-session "joules" accounting apply (see /api/joules for visibility). No warranty.

Self-hosted binaries

The binary distribution is on the roadmap. License terms for the binary will be published with the first release. Until then, contact the openie-dev team if you have a self-hosted requirement.

Documentation

This repository (openIE-dev/cad) — the documentation site, the example projects, and the recipe markdown — is MIT licensed. Contributions are welcome via pull request; see https://github.com/openIE-dev/cad.

Third-party algorithms

The substrate implements published algorithms from the academic literature; each one is cited on its algorithm theory page:

AlgorithmPaperLicense posture
LCIS BGA escapeKong/Yan/Ozdal ICCAD 2007Algorithm is public-domain prior art; openie-cad's clean-room implementation has no encumbrance.
SALT shallow-light SteinerChen et al. ICCAD 2017Same.
PathFinder negotiationMcMurchie-Ebeling FPGA 1995Same; public-domain since 1995.
IQN-ILS couplingDegroote 2009 / Chourdakis 2021preCICE itself is LGPLv3; openie-cad's MIT-clean re-derivation does not link or wrap preCICE.
Djordjevic-Sarkar dielectricSvensson-Djordjevic 2001Published formula.
Cannonball-Huray roughnessSimonovich 2015/2019 (simplified from Huray 2010)Published formula.
FDTD NTFFBalanis textbook §6.7Published formula.
Aitken's Δ²Aitken 1926Classical.