Routing a trace with pcbnew's Python API

Published on September 11, 2026 by leZ

Create a copper trace between two points, with a defined width, by script — with pcbnew or KiCad's IPC API.

0.25 mm copper track on F.Cu drawn by a pcbnew Python script from (10, 20) to (40, 20) mm in the KiCad PCB Editor
The track created by the script, with both endpoints.

Routing a bundle of identical traces by hand (a data bus, a series of connections to a connector) is repetitive and prone to width mistakes. Scripting trace creation guarantees a reproducible width and path — the starting point for any homemade auto-router.

A track (and a via) created by script, filmed live in KiCad (1:16):

Creating the track

Instantiate a PCB_TRACK object attached to the board, then add it:

import pcbnew

board = pcbnew.GetBoard()
from pcbnew import PCB_TRACK

track = PCB_TRACK(board)
board.Add(track)

With the IPC API, you instantiate a blank Track object; it doesn’t exist on the board until it’s explicitly created:

from kipy import KiCad
from kipy.board_types import Track

kicad = KiCad()
board = kicad.get_board()
track = Track()

Setting the path, width, and layer

The track’s start and end are set in internal units, width and layer separately:

from pcbnew import VECTOR2I, FromMM, F_Cu

track.SetStart(VECTOR2I(FromMM(10), FromMM(20)))
track.SetEnd(VECTOR2I(FromMM(40), FromMM(20)))
track.SetWidth(FromMM(0.25))
track.SetLayer(F_Cu)

Start and end are set in millimeters via Vector2.from_xy_mm, but width is a raw nanometer integer that needs an explicit from_mm conversion:

from kipy.geometry import Vector2
from kipy.util.units import from_mm

track.start = Vector2.from_xy_mm(10, 20)
track.end = Vector2.from_xy_mm(40, 20)
track.width = from_mm(0.25)
track.layer = board.get_layer_by_name("F.Cu")

⚠️ track.layer expects a BoardLayer value, not a string: track.layer = "F.Cu" raises a ValueError. get_layer_by_name does the conversion.

Try it below: adjust length and width to see the trace change on the board.

track.SetStart(VECTOR2I(FromMM(10), FromMM(20)))
track.SetEnd(VECTOR2I(FromMM(40), FromMM(20)))
track.SetWidth(FromMM(0.25))
40
0.25

Connecting the track to a net

Just like a via, the track needs to be attached to an existing net:

track.SetNet(board.FindNet("GND"))

On the IPC API side, the net is referenced directly by name. This is also the point, once every property is set, where the track is actually created on the board:

from kipy.board_types import Net

track.net = Net(name="GND")
board.create_items(track)

Going further

This skeleton generalizes to a loop that automatically connects a series of pads together — the basis for a small homemade router for repetitive topologies (buses, connector rows), instead of drawing every trace by hand.