Skip to main content
Guides

Reusing saved fanout trace paths

Use pcbTracePaths on <fanout /> or <breakout /> when you already have pre-generated routes from component ports to fanout exits. The routes can live in a TypeScript module or JSON file. Core preserves their geometry and routes the rest of the board from their exits.

This requires @tscircuit/core 0.0.1884 or newer and @tscircuit/props 0.0.649 or newer. If using the combined tscircuit package, check its resolved core and props versions too; accepting the prop alone does not prove it is rendered.

Complete example with via endpoints​

This small example uses a 0402 resistor whose pin 1 is at (-0.51, 0) in the fanout's local frame. The first via is in that pad. The last via creates an exit at (3, 1) on the top layer, where global routing continues to R2.

import type { FanoutTracePath } from "@tscircuit/props"

const savedPaths: FanoutTracePath[] = [
{
connection: "R1.1",
route: [
{
route_type: "via",
x: -0.51,
y: 0,
from_layer: "top",
to_layer: "bottom",
via_diameter: 0.3,
via_hole_diameter: 0.15,
},
{ route_type: "wire", x: 1, y: 1, width: 0.2, layer: "bottom" },
{
route_type: "via",
x: 3,
y: 1,
from_layer: "bottom",
to_layer: "top",
via_diameter: 0.6,
via_hole_diameter: 0.3,
},
],
},
]

export default () => (
<board width="20mm" height="16mm" autorouter={{ allowViaInPad: true }}>
<fanout name="SAVED_FANOUT" pcbTracePaths={savedPaths}>
<resistor name="R1" resistance="1k" footprint="0402" pcbX={0} pcbY={0} />
</fanout>
<resistor name="R2" resistance="1k" footprint="0402" pcbX={7} pcbY={1} />
<trace from="R1.1" to="R2.1" />
</board>
)

The route supplies copper geometry; the separate <trace /> supplies electrical connectivity. Keep both. Do not add a <fanoutpoint> or <breakoutpoint> for a port already covered by a saved path: core creates that exit automatically.

Store paths in JSON​

Each entry has a connection port selector and a route array. Use stable selectors such as R1.1 or U1.DATA0, rather than generated pcb_port_id or source_trace_id values. Save the array itself as saved-fanout.json and load it with the exported schema:

import { fanoutTracePath } from "@tscircuit/props"
import savedJson from "./saved-fanout.json"

const savedPaths = savedJson.map((path) => fanoutTracePath.parse(path))

Pass savedPaths to pcbTracePaths. FanoutTracePath is the exported input type for paths authored in TypeScript. Parsing converts unit strings to numeric millimeters and rejects malformed points or discontinuous layer transitions.

Coordinates and route format​

Coordinates are points in the fanout's local PCB frame, with +X right and +Y up (right-handed with +Z above the board). Numeric distances are millimeters; unit strings such as "0.2mm" are also accepted. Core applies the fanout's translation, rotation, and layout movement. Layer names always identify physical board layers; moving or rotating the fanout does not swap them.

Each route contains at least two points:

PointRequired fieldsOptional fields
Wireroute_type: "wire", x, y, width, layerwidth_interpolation_mode
Viaroute_type: "via", x, y, from_layer, to_layervia_diameter, via_hole_diameter

Only wire and via points are supported. Wire widths and specified via diameters must be positive. A via's from_layer must match the incoming layer, and the next point must continue on its to_layer.

Either endpoint can be a via. A leading via starts on from_layer and requires allowViaInPad: true on the board's autorouter configuration, or explicitly on the fanout's configuration. An explicit fanout setting overrides the board, including false. A trailing via creates its exit on to_layer. Core emits coincident wire contacts around endpoint vias for Circuit JSON connectivity; those contacts do not move the via or alter the saved copper.

Tapered wire segments​

Set width_interpolation_mode on a wire point to interpolate from its width to the next wire point's width. Use "linear" for a straight-sided taper or "quadratic" for a curved teardrop profile. The mode applies to the outgoing segment only. Without it, that segment keeps its starting width even if the next point has a different width.

This example narrows from the left pad of a 0402 resistor to a 0.15 mm trace, then continues to a fanout exit. The pad center is (-0.51, 0) in the fanout's local frame.

import type { FanoutTracePath } from "@tscircuit/props"

const savedPaths: FanoutTracePath[] = [
{
connection: "R1.1",
route: [
{
route_type: "wire",
x: -0.51,
y: 0,
width: 0.5,
layer: "top",
width_interpolation_mode: "quadratic",
},
{ route_type: "wire", x: -1.5, y: 0, width: 0.15, layer: "top" },
{ route_type: "wire", x: -2.5, y: 1, width: 0.15, layer: "top" },
{ route_type: "wire", x: -3.5, y: 1, width: 0.15, layer: "top" },
],
},
]

export default () => (
<board width="20mm" height="12mm">
<fanout pcbTracePaths={savedPaths}>
<resistor name="R1" resistance="1k" footprint="0402" />
</fanout>
<resistor name="R2" resistance="1k" footprint="0402" pcbX={-6} pcbY={1} />
<trace from="R1.1" to="R2.2" thickness="0.15mm" />
</board>
)

Interpolation requires a next wire point on the same layer at a different position. Do not set it on the last point, or directly before a via. To taper into a via, insert a wire point at the via's coordinates first; that point's width is the taper's ending width.

The same point format works in pcbTracePaths on <autoroutingphase />. For automatically sized pad and via teardrops, use the <trace /> teardrop controls.

Coverage and validation​

pcbTracePaths replaces automatic routing for that fanout. Supply a path for every routing connection in the fanout; partial caches are not supported. Each path describes one port-to-exit connection. Use a separate fanout for connections that should be routed automatically or for internal routes. Omitting the prop retains normal automatic fanout behavior.

A selector must resolve to a port inside the fanout. The first point must match that port's actual PCB position and an available layer after placement. Core reports an error instead of stretching a route to fit a moved component. It also rejects duplicate exits, unavailable board layers, uncovered connections, and a leading pad via when via-in-pad is disabled.

Build the circuit after loading saved paths. Inspect the emitted PCB traces, via layers, and the continuation from each exit. Schema parsing checks the data format; it does not check pad geometry, coverage, or inherited routing settings.