Skip to main content
Advanced

Create or Use a Custom Autorouter

Use algorithmFn when you want to create or use a custom autorouter in your tscircuit project without running a separate autorouter server. The function receives the board's SimpleRouteJson routing problem and returns a GenericLocalAutorouter.

info

We recommend using the default autorouter; custom autorouters are for advanced use cases.

This example recreates the tscircuit/example-custom-autorouter project. The board is a four-wire crossover adapter. The custom router draws Manhattan routes, cycles traces across the available layers, and uses obstacles from the SimpleRouteJson input to route around the center mounting hole.

import { SimpleCustomAutorouter } from "./src/simple-custom-autorouter"

const LEFT_HEADER_X = -13
const RIGHT_HEADER_X = 13

export default () => (
<board
width="34mm"
height="22mm"
layers={4}
minTraceWidth={0.18}
nominalTraceWidth={0.22}
minViaPadDiameter={0.55}
minViaHoleDiameter={0.3}
pcbStyle={{
viaPadDiameter: 0.55,
viaHoleDiameter: 0.3,
}}
autorouter={{
algorithmFn: async (simpleRouteJson) =>
new SimpleCustomAutorouter(simpleRouteJson),
}}
>
<pinheader
name="J_LEFT"
pinCount={4}
pitch="2.54mm"
pcbX={LEFT_HEADER_X}
pcbY={0}
pcbOrientation="vertical"
schX={-5}
schY={0}
schFacingDirection="right"
/>
<pinheader
name="J_RIGHT"
pinCount={4}
pitch="2.54mm"
pcbX={RIGHT_HEADER_X}
pcbY={0}
pcbOrientation="vertical"
schX={5}
schY={0}
schFacingDirection="left"
/>

<hole name="CENTER_MOUNT" diameter="3.2mm" pcbX={0} pcbY={0} />

<silkscreentext
text="CUSTOM ROUTER"
pcbX={0}
pcbY={9}
fontSize="1mm"
anchorAlignment="center"
/>

<trace from=".J_LEFT > .pin1" to=".J_RIGHT > .pin4" />
<trace from=".J_LEFT > .pin2" to=".J_RIGHT > .pin3" />
<trace from=".J_LEFT > .pin3" to=".J_RIGHT > .pin2" />
<trace from=".J_LEFT > .pin4" to=".J_RIGHT > .pin1" />
</board>
)
PCB Circuit Preview

Use an Autorouter in a Board

Pass an object with algorithmFn to the autorouter prop. The function can construct your own router, call a library, or wrap an existing routing implementation:

<board
autorouter={{
algorithmFn: async (simpleRouteJson) =>
new SimpleCustomAutorouter(simpleRouteJson),
}}
>
{/* components and traces */}
</board>

algorithmFn receives a SimpleRouteJson object. Return an object that implements GenericLocalAutorouter, usually with these methods:

  • start() to begin asynchronous routing and emit a complete event
  • stop() to cancel routing
  • on("progress" | "complete" | "error", callback) to report router state
  • solveSync() to return SimplifiedPcbTrace[] synchronously when supported

Create a Router

The router output is a list of simplified PCB traces. Each trace should use the source trace ID when one is present, then return a route made of wire and optional via points.

The example's SimpleCustomAutorouter is intentionally small, but it shows the full shape: event handlers, sync solving, trace IDs, route widths, layer selection, obstacle checks, and progress/error reporting.

Customize Implicit Breakout Points

Use implicitBreakoutPointSolverFn when the normal autorouter should still route traces, but your application needs to choose where connections leave <breakout /> regions. The callback runs during PCB layout and must synchronously return the PCB-world positions and layers of the generated breakout points.

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

const placeBreakoutPoints: ImplicitBreakoutPointSolverFn = (input) => {
const connections = input.connections.flatMap((connection) =>
"type" in connection ? connection.connections : [connection],
)

return {
breakoutPoints: input.regions.flatMap((region) => {
const regionConnections = connections.filter((connection) =>
connection.endpoints.some(
(endpoint) => endpoint.regionId === region.regionId,
),
)

return regionConnections.map((connection, index) => {
const offset = input.boundaryPointSpacing * (index + 1)

return {
regionId: region.regionId,
connectionId: connection.connectionId,
layer: "top",
x:
region.edge === "left"
? region.bounds.minX
: region.edge === "right"
? region.bounds.maxX
: region.bounds.minX + offset,
y:
region.edge === "bottom"
? region.bounds.minY
: region.edge === "top"
? region.bounds.maxY
: region.bounds.minY + offset,
}
})
}),
}
}

export default () => (
<breakout
autorouter={{ implicitBreakoutPointSolverFn: placeBreakoutPoints }}
>
{/* components */}
</breakout>
)

A production solver should also check boundary length, bus ordering, differential pairs, and layer constraints.

The input contains:

FieldDescription
regionsBreakout IDs, rectangular bounds, and selected boundary edges. Coordinates are PCB world-space millimeters.
connectionsOrdinary connections or differential pairs. Each endpoint identifies its region and position. The type includes optional externalDestination, but Core does not currently populate it.
busesBus IDs, member connection IDs, and optional ordered target layers.
boundaryPointSpacingRecommended spacing between generated boundary points, in millimeters.

Return { breakoutPoints } synchronously. Every point must provide regionId, connectionId, layer, x, and y on the matching region boundary, with exactly one point per (regionId, connectionId) pair; differential-pair members count separately. Missing, unknown, or duplicate pairs and Promise returns all cause layout to fail.

Run the Custom Autorouter Example Locally

git clone https://github.com/tscircuit/example-custom-autorouter
cd example-custom-autorouter
bun install
bun run typecheck
bun run build

Use bun run dev to open the interactive tscircuit viewer.

For a custom autorouter that runs as an HTTP service instead of inside the tscircuit process, see the Autorouting API.