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:
- MCP (agent-callable) at https://cad.openie.dev/mcp — 60+ tools that an AI agent (Claude, GPT, etc.) can call directly. The MCP tool reference lists every tool with its arguments.
- REST API at https://cad.openie.dev/api — see REST API docs.
- Interactive viewer at https://cad.openie.dev/viewer/kicad?session=ID — live KiCad PCB viewer for any MCP session.
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:
| Algorithm | Paper / Origin | Where in substrate |
|---|---|---|
| LCIS BGA escape routing | Kong/Yan/Ozdal ICCAD 2007 | cad-router::bga_escape |
| FDTD Huygens NTFF antenna pattern | Balanis §6.7 textbook | cad-sim::antenna |
| Djordjevic-Sarkar + Cannonball-Huray 60 GHz loss | Shlepnev 2015, Simonovich 2015 | cad-sim::signal |
| preCICE-equivalent IQN-ILS coupling | Degroote 2009 / Chourdakis 2021 | cad-sim::coupling |
| SALT shallow-light Steiner topology | Chen et al. ICCAD 2017 | cad-router::steiner |
| Fang DAC 2024 length-match co-optimization | Fang et al. arXiv 2407.19195 | cad-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:
- Routing a small PCB via MCP.
- Generating a CAM toolpath.
- 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 (
curlis 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 with its arguments.
- Designing a PCB end-to-end — the long walkthrough.
- Algorithm theory — the substantive algorithms the substrate ships, with citations.
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)
solve_n_link_arm_setpoint
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
| Method | Path | Purpose |
|---|---|---|
GET | /api/health | Liveness probe — returns 200 when the server is up. |
POST | /api/plan | Submit a planning request; returns a transcript of agent turns. |
POST | /api/napkin-plan | Same 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}/joules | Per-session "joules" accumulator (per tool call). |
GET | /api/sessions/{id}/pcb.kicad_pcb | The live PCB of an MCP session as a .kicad_pcb text blob. KiCanvas-compatible. |
GET | /api/joules | Server-wide joules totals. |
POST | /api/handoff | Create a handoff blob (cross-process project transfer). |
GET | /api/handoff/{id} | Fetch a handoff blob. |
POST | /api/design-pcb-from-chip | Specialised one-shot chip→PCB design call. |
POST | /api/design-pcb-from-chip-with-layout | Same with an explicit layout. |
GET | /api/artifact/{id} | Fetch an artifact (STEP / STL / Gerber / etc.). |
GET | /api/artifacts | List artifacts. |
GET | /api/kicad-httplib/v1/ | Roots of the KiCad HTTP-lib catalogue endpoint. |
GET | /api/kicad-httplib/v1/categories.json | Categories list. |
GET | /viewer/kicad | Interactive KiCanvas-based PCB viewer. Accepts ?src=, ?artifact=, or ?session=. |
GET/POST | /mcp | JSON-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
- Cheatsheet — quick syntax reference.
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-Idfrom any priortools/callonhttps://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
| Step | Wave | Algorithm |
|---|---|---|
| BGA escape feasibility + routing | 63.A-F | Kong/Yan/Ozdal LCIS |
| Force-directed placement | 66 | Fruchterman-Reingold |
| Negotiation routing | 55 | McMurchie-Ebeling PathFinder |
| Multi-pin topology | 58/59 | SALT shallow-light tree |
| Length-match groups | 64.A-D | Fang DAC 2024 + capacity-aware insertion |
| 60 GHz impedance | 65 | Djordjevic-Sarkar + Cannonball-Huray |
| KiCanvas viewer | 61 | Thea 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
| Regime | Recommended | Why |
|---|---|---|
| αβ < 0.3 (weak) | explicit | One solver pass per side per step. Cheapest, stable. |
| αβ ∈ [0.3, 0.7] | constant ω = 0.5 | Sub-Newton, robust. |
| αβ > 0.7 (stiff FSI, CHT) | aitken or iqn_ils | Sub-linear vs. linear convergence rate. |
| Tight FSI, added-mass dominated | iqn_ils history 10-20 | preCICE'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:
- Configure the FDTD grid (
FdtdConfig,FdtdGrid). - Place a source —
FdtdSource::GaussianPulseat the radiating structure's feed point. - Define a Huygens surface enclosing the structure:
HuygensSurface::new_box(box_idx, &grid, frequency_hz). - Time-step the simulation. On every step, after
update_eandupdate_hand any source application:#![allow(unused)] fn main() { grid.update_h(); grid.apply_source(&source); grid.update_e(); surface.step_sample(&grid, t_now); } - When stepping completes, convert to NTFF input:
let samples = surface.extract_currents(); - 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
- Compute the inset boundary by offsetting inward by the cutter
radius — the pocket's outer wall ends up at exactly
width×heightafter machining. - 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_mmapart along Y. - Retract to
safe_z_mm.
- Rapid above the row start at
- Final Z snaps to exactly
−depth_mmeven ifdepth_mmisn't a multiple ofstep_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:
-
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.
-
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
| Stage | Module | Wave |
|---|---|---|
| Data model + feasibility | BgaGrid, feasibility_report | 63.A |
| LCIS sequencing + layer assignment | lcis_non_crossing_buses, assign_buses_to_layers | 63.B |
| Track + Via geometry emission | emit_all_escapes | 63.C |
| Via-in-pad strategy (HDI tier) | EscapeStrategy::ViaInPad | 63.D |
| Autorouter integration | Autorouter::route_with_bga_escape | 63.E |
| MCP surface | run_autorouter(bgas=[...]), check_bga_escape_feasibility | 63.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
| Surface | Wave |
|---|---|
baseline_rsmt(terminals) — MST over the Hanan grid | 57 |
salt(terminals, source, epsilon) — (1+ε)-shallow-light iteration | 58 |
salt_decompose(MultipinNet, epsilon) — Connection list for Autorouter | 59 |
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 withpresent_weightandhistory_weighttuning 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:
- Solve the small least-squares problem
min_c ‖V·c − (−r)‖²via normal equations(VᵀV + λI) c = −Vᵀrwith Tikhonov regularisation (λ ≈ 10⁻¹²). - Compute the IQN-ILS update
Δx = W·c. - Apply:
x_{k+1} = x_k + Δx. - Truncate the oldest column when history exceeds
history_max.
Substrate test (Wave 67.B)
On a stiff α = 0.9, β = 0.95 linear toy:
| Scheme | Iterations to ‖r‖ < 10⁻⁸ |
|---|---|
| Constant ω = 0.3 | many |
| Aitken | fewer |
| 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:
- Dielectric dispersion. A single-point (Dk, tanδ) pair is non-causal and grossly under-predicts loss above ~10 GHz.
- 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.
- "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
| Component | Wave |
|---|---|
SurfaceCurrentSample data shape | 69 |
ntff_far_field_at(samples, θ, φ, λ) integral | 69 |
ntff_radiation_pattern(samples, f, n_θ, n_φ) sweep | 69 |
HuygensSurface::new_box(box_idx, grid, freq) | 69.B |
HuygensSurface::step_sample(grid, t) running DFT | 69.B |
HuygensSurface::extract_currents() to SurfaceCurrentSample | 69.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_gcodereturns 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_modlibrary files verbatim.
Wave 67.A / 67.B — partitioned multi-physics coupling (June 3)
cad_sim::coupling::couplewithCouplingScheme::{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-lineauto_place.py.
Wave 65 — Djordjevic-Sarkar + Cannonball-Huray 60 GHz (June 2)
cad_sim::signal::DielectricMaterialwithdk_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_frequencycomposes 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 existinggenerate_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:
| Algorithm | Paper | License posture |
|---|---|---|
| LCIS BGA escape | Kong/Yan/Ozdal ICCAD 2007 | Algorithm is public-domain prior art; openie-cad's clean-room implementation has no encumbrance. |
| SALT shallow-light Steiner | Chen et al. ICCAD 2017 | Same. |
| PathFinder negotiation | McMurchie-Ebeling FPGA 1995 | Same; public-domain since 1995. |
| IQN-ILS coupling | Degroote 2009 / Chourdakis 2021 | preCICE itself is LGPLv3; openie-cad's MIT-clean re-derivation does not link or wrap preCICE. |
| Djordjevic-Sarkar dielectric | Svensson-Djordjevic 2001 | Published formula. |
| Cannonball-Huray roughness | Simonovich 2015/2019 (simplified from Huray 2010) | Published formula. |
| FDTD NTFF | Balanis textbook §6.7 | Published formula. |
| Aitken's Δ² | Aitken 1926 | Classical. |