pcbnew scripting doesn’t require the GUI

If you just want to process something in the pcbnew data model, you don’t have to bring up the GUI. You can just start a python job directly from the commandline. 1

Why would you do this?

In my previous life working for a large semiconductor company, we had many, many quality checks and progress trackers. Now that experience was in VLSI, but surely there are parallels in the PCB world.

Management needs a way to track the progress of the project. Engineering needs a way to enforce layout standards. An obvious example of this is design rule checking. How many opens, shorts and spacing violations are there currently? There are many other things that one could check. Maybe you have rules about the clock network. Maybe you want to check that the power grid is strong enough to handle the current its being asked to carry. Maybe the sub-design you’re working on has some restrictions on it to ensure it’ll fit into the larger design.

At the beginning of a project, we had many millions of such violations. For a while the number wouldn’t change since we were still deciding on the bigger picture stuff. Eventually, we’d change gears and want to move towards tapeout.

Of course, these quality checks don’t run in a GUI 2. You want something that can be run from the command line every night in some automated fashion.

Coming back to Kicad, there’s already such a check in the scripts directory: ddr3_length_match.py.3

From the script’s header:

Report any length problems pertaining to a SDRAM DDR3 T topology using 4 memory chips: a T into 2 Ts routing strategy from the CPU.

Once you have kicad installed, pcbnew’s python library is available to any python script. 4

python
>>> import pcbnew
>>> print pcbnew.__file__
/usr/lib/python2.7/dist-packages/pcbnew.py
>>> import sys
>>> print sys.path
['', '/usr/lib/python2.7', <a bunch of others deleted>]

What if you compiled kicad yourself?

python
>>> import sys
>>> sys.path.insert(0, "<path to your build>/kicad/install/lib/python2.7/dist-packages")
>>> import pcbnew
>>> print pcbnew.__file__
<path to your build>/kicad/install/lib/python2.7/dist-packages/pcbnew.pyc

In the case of ddr3_length_match, it simply keeps its eye on a file5. Once a second, it looks at the time stamp of the file. If it’s been updated, the script will load it and run the required checks.

Here’s the relevant code for loading the file:

pcb = pcbnew.LoadBoard(filename)
pcb.BuildListOfNets() # required so 'pcb' contains valid netclass data

There are some other interesting APIs used in there as well. For example, to see if two pads are connected only with wires and vias:

try:
  tracks = pcb.TracksInNetBetweenPoints(start_pad.GetPosition(), end_pad.GetPosition(), netcode)
except IOError as ioe:
  return False
return True

Either way, the biggest point I’m trying to make is that when working on a design, there may be some stuff you want to keep an eye on. Timing critical nets, noise safeguards, power rail loading…

Write a script that loads your design and does the checks. Maybe it continually monitors your saves, maybe you have a checkin trigger 6, nightly, or maybe you just run it by hand.


  1. Another popular method is to parse the kicad files directly, bypassing kicad. While that works, part of the point of this blog is to reduce the need for that.

  2. well, maybe they could, but not for tracking purposes

  3. this script was written by and brought to my attention by Dick Hollenbeck. He was also instrumental in making pcbnew’s python interface as useful as it is. He added a bunch of the APIs without which scripting is kinda limited

  4. my experience is limited to linux. I imagine things will be similar for Mac users. Windows always seems to be a mystery.

  5. The file name is an argument to the script

  6. in SVN or GIT, for example

Zones, boundaries, and silkscreen

UPDATE April 14, 2017 two things have changed:

  • LAYER_ID_COUNT has been renamed to PCB_LAYER_ID_COUNT
  • Zones have been changed to use the same data structure as other stuff in kicad. As a result, some of the APIs have changed
    • instead of AppendCorner, you’ll need just Append
    • The Hatch method is on the Area, not the outline. I have not updated the text below, but the code in the github will work on the latest Kicad codebase soon.

 

In previous posts, I’ve talked about the main layout components. Tracks, modules, pads. In this one, I’m focusing on lines. The board boundary, zones, and silkscreens.

At the end of this post, I’ll have a script (you can find the full script here) that redraws the board boundary to shrink wrap around all of the existing components. It’ll also generate a zone on the clk net (of course, it’s more common to use power/gnd). The zone will have the same outline as the board.

Most of the code is pretty straightforward, but I do recommend looking closely at the notes about zones at the end of this post. There are a couple things that are not immediately obvious. 1

As a reminder, I have a uml diagram in my github area of useful, pcbnew python APIs (click for a bigger view)

Some preliminaries

First, it’s helpful to have a layername->layernumber lookup table. Later on, I’ll need to know the layers for backside copper (B.Cu) and the board boundary (Edge.Cuts)

layertable = {}

numlayers = pcbnew.PCB_LAYER_ID_COUNT
for i in range(numlayers):
 layertable[board.GetLayerName(i)] = i

Next, I’ll want to compute the bounding box by starting with an empty box and adding to it. So I’ll create a bbox class. 2. Also, I have variations on min and max. Mine are different in that the value None is supported the way I want. The standard ones treat None as -inf.

def mymin(a,b):
 if (a == None):
 return b
 if (b == None):
 return a
 if (a<b):
 return a
 return b

def mymax(a,b):
 if (a == None):
 return b
 if (b == None):
 return a
 if (a>b):
 return a
 return b


class BBox:
 def __init__(self, xl=None, yl=None, xh=None, yh=None):
 self.xl = xl
 self.xh = xh
 self.yl = yl
 self.yh = yh

 def __str__(self):
 return "({},{} {},{})".format(self.xl, self.yl, self.xh, self.yh)
 
 def addPoint(self, pt):
 self.xl = mymin(self.xl, pt.x)
 self.xh = mymax(self.xh, pt.x)
 self.yl = mymin(self.yl, pt.y)
 self.yh = mymax(self.yh, pt.y)

 def addPointBloatXY(self, pt, x, y):
 self.xl = mymin(self.xl, pt.x-x)
 self.xh = mymax(self.xh, pt.x+x)
 self.yl = mymin(self.yl, pt.y-y)
 self.yh = mymax(self.yh, pt.y+y)

Computing the BBox

Start with a null bbox:

boardbbox = BBox();

Adding tracks

alltracks = board.GetTracks() 
for track in alltracks:
  boardbbox.addPoint(track.GetStart())
  boardbbox.addPoint(track.GetEnd())

Adding Pads

allpads = board.GetPads()
for pad in allpads:
  if (pad.GetShape() == pcbnew.PAD_SHAPE_RECT): 
    if ((pad.GetOrientationDegrees()==270) | (pad.GetOrientationDegrees()==90)):
      boardbbox.addPointBloatXY(pad.GetPosition(), pad.GetSize().y/2, pad.GetSize().x/2)
    else:
      boardbbox.addPointBloatXY(pad.GetPosition(), pad.GetSize().x/2, pad.GetSize().y/2)

  elif (pad.GetShape() == pcbnew.PAD_SHAPE_CIRCLE):
    boardbbox.addPointBloatXY(pad.GetPosition(), pad.GetSize().x/2, pad.GetSize().y/2)
 
  elif (pad.GetShape() == pcbnew.PAD_SHAPE_OVAL):
    boardbbox.addPointBloatXY(pad.GetPosition(), pad.GetSize().x/2, pad.GetSize().y/2)
 
  else:
    print("unknown pad shape {}({})".format(pad.GetShape(), padshapes[pad.GetShape()]))

Adding Module silkscreens and such

for mod in board.GetModules():
  for gi in mod.GraphicalItems():
    bbox = gi.GetBoundingBox()
    boardbbox.addPointBloatXY(bbox.Centre(), bbox.GetWidth()/2, bbox.GetHeight()/2)

Generating the new data

Now we have a bounding box of everything, let’s remove the old boundary before creating a new one.

for d in board.GetDrawings():
  board.Remove(d)

A new boundary

There’s be a better way to do this. I could modify the bbox class to produce a list of line segment, but I’m lazy.

edgecut = layertable['Edge.Cuts']

seg1 = pcbnew.DRAWSEGMENT(board)
board.Add(seg1)
seg1.SetStart(pcbnew.wxPoint(boardbbox.xl, boardbbox.yl))
seg1.SetEnd( pcbnew.wxPoint(boardbbox.xl, boardbbox.yh))
seg1.SetLayer(edgecut)

seg1 = pcbnew.DRAWSEGMENT(board)
board.Add(seg1)
seg1.SetStart(pcbnew.wxPoint(boardbbox.xl, boardbbox.yh))
seg1.SetEnd( pcbnew.wxPoint(boardbbox.xh, boardbbox.yh))
seg1.SetLayer(edgecut)

seg1 = pcbnew.DRAWSEGMENT(board)
board.Add(seg1)
seg1.SetStart(pcbnew.wxPoint(boardbbox.xh, boardbbox.yh))
seg1.SetEnd( pcbnew.wxPoint(boardbbox.xh, boardbbox.yl))
seg1.SetLayer(edgecut)

seg1 = pcbnew.DRAWSEGMENT(board)
board.Add(seg1)
seg1.SetStart(pcbnew.wxPoint(boardbbox.xh, boardbbox.yl))
seg1.SetEnd( pcbnew.wxPoint(boardbbox.xl, boardbbox.yl))
seg1.SetLayer(edgecut)

A new zone

Zones are a little tricky in the kicad model. There are several classes involved. To begin, here’s an interesting comment from PolyLine.h:

// A polyline contains one or more contours, where each contour
// is defined by a list of corners and side-styles
// There may be multiple contours in a polyline.
// The last contour may be open or closed, any others must be closed.
// All of the corners and side-styles are concatenated into 2 arrays,
// separated by setting the end_contour flag of the last corner of
// each contour.
//
// When used for copper (or technical layers) areas, the first contour is the outer edge
// of the area, subsequent ones are "holes" in the copper.

I want to create my zone on the clk net on the backside, so I need a pointer to the net and the layer (using the layer table generated in preliminaries

nets = board.GetNetsByName()
clknet = nets.find("/clk").value()[1]
backlayer = layertable['B.Cu']

Now let’s create the zone. It’s a little different from creating the board boundary. If you’ve used the zone creation GUI command, the order of events should make sense.

newarea = board.InsertArea(clknet.GetNet(), 0, backlayer, boardbbox.xl, boardbbox.yl, pcbnew.CPolyLine.DIAGONAL_EDGE)

newoutline = newarea.Outline()
newoutline.AppendCorner(boardbbox.xl, boardbbox.yh);
newoutline.AppendCorner(boardbbox.xh, boardbbox.yh);
newoutline.AppendCorner(boardbbox.xh, boardbbox.yl);

This next line shouldn’t really be necessary but without it, saving to file will yield a file that won’t load.3

newoutline.CloseLastContour()

I don’t know why this is necessary. When calling InsertArea above, DIAGONAL_EDGE was passed. If you save/restore the file, the zone will come back hatched. Before then, the zone boundary will just be a line. Omit this if you are using pcbnew.CPolyLine.NO_HATCH

newoutline.Hatch()

Please leave a comment with questions or if you’d like me cover some other topic.


  1. I had to read the C++ code to figure it out

  2. wxBox probably already has something for this, but I’m not familiar enough with it. So I’m writing one

  3. The parenthesis won’t line up; there won’t be enough closing parens. I’d argue that AppendCorner should automatically do it.

Replicating pcbnew new for arrayed sheets

Many circuit designs can have repeated structures. Sadly, pcbnew doesn’t have any features to make the placement and routing any more productive.

Here is a demo of a script I wrote to help:

If you have X copies of the same sheet in eeschema, the script will allow you to place and route one of them and them apply that to the other sheets.

How’s it work?

If you call GetPath() on the modules in your design, you’ll get values like these (note the the path is only the /number/number part):

mod Q1 path /587DA765/58758821
mod Q2 path /5875D13C/58758821
mod Q3 path /5875D13D/58758821
mod Q4 path /5875D13E/58758821
mod R1 path /587DA765/5875882F
mod R2 path /587DA765/5875883D
mod R3 path /5875D13C/5875882F
mod R4 path /5875D13C/5875883D

The first number tells you which sheet instance the module belongs to. The second is an identifier for the module. So from the information above we can tell that these are all in the same sheet:

mod Q1 path /587DA765/58758821
mod R1 path /587DA765/5875882F
mod R2 path /587DA765/5875883D

Similarly, we know that these are the same transistor, just in different instances of the sheet:

mod Q1 path /587DA765/58758821
mod Q2 path /5875D13C/58758821
mod Q3 path /5875D13D/58758821
mod Q4 path /5875D13E/58758821

So if I have placed and routed sheet 587DA765, the script just needs to find all of the members of 5875D13C and simply adjust their positions by some constant increment.

The script can be found here in my github repository for kicad

Querying pcbnew to generate a basic svg file

In a previous post, I gave some basic query capabilies in pcbnew. In this post, I’ll use that to generate a simple svg file.

The result looks like this:

Not the most interesting layout and a bunch of detail is missing, but I was happy to get this far. The code is hopefully self-explanatory. After the last post, I don’t really know what I can add.

The code can be found in my github

Preliminaries

I need to get each of pcbnew’s layer colors. Some of the color names are modified to match svr colors. The real answer is to get the rgb values from colorrefs, but those aren’t exposed in python today

Edit Nov 15, 2017 RGB values are now available in the form of the COLOR4D structure.


colors = board.Colors()

c4 = colors.GetLayerColor(track.GetLayer())

r = int(round(c4.r*255)

g = int(round(c4.g*255)),
b = int(round(c4.b*255))

The colornames map below won’t work anymore.

End edit

colornames = {
 pcbnew.BLACK: 'BLACK', 
 pcbnew.DARKDARKGRAY: 'DARKSLATEGRAY', # 'DARKDARKGRAY', 
 pcbnew.DARKGRAY: 'DARKGRAY', 
 pcbnew.LIGHTGRAY: 'LIGHTGRAY', 
 pcbnew.WHITE: 'WHITE', 
 pcbnew.LIGHTYELLOW: 'LIGHTYELLOW', 
 pcbnew.DARKBLUE: 'DARKBLUE', 
 pcbnew.DARKGREEN: 'DARKGREEN', 
 pcbnew.DARKCYAN: 'DARKCYAN', 
 pcbnew.DARKRED: 'DARKRED', 
 pcbnew.DARKMAGENTA: 'DARKMAGENTA', 
 pcbnew.DARKBROWN: 'MAROON', # 'DARKBROWN', 
 pcbnew.BLUE: 'BLUE', 
 pcbnew.GREEN: 'GREEN', 
 pcbnew.CYAN: 'CYAN', 
 pcbnew.RED: 'RED', 
 pcbnew.MAGENTA: 'MAGENTA', 
 pcbnew.BROWN: 'BROWN', 
 pcbnew.LIGHTBLUE: 'LIGHTBLUE', 
 pcbnew.LIGHTGREEN: 'LIGHTGREEN', 
 pcbnew.LIGHTCYAN: 'LIGHTCYAN', 
 pcbnew.LIGHTRED: 'INDIANRED', # 'LIGHTRED', 
 pcbnew.LIGHTMAGENTA: 'LIGHTPINK', # 'LIGHTMAGENTA', 
 pcbnew.YELLOW: 'YELLOW', 
 pcbnew.PUREBLUE: 'MEDIUMBLUE', # 'PUREBLUE', 
 pcbnew.PUREGREEN: 'LAWNGREEN', # 'PUREGREEN', 
 pcbnew.PURECYAN: 'DARKTURQUOISE', # 'PURECYAN', 
 pcbnew.PURERED: 'FIREBRICK', # 'PURERED', 
 pcbnew.PUREMAGENTA: 'DARKORCHID', # PUREMAGENTA', 
 pcbnew.PUREYELLOW: 'KHAKI', # PUREYELLOW' 
}

padshapes = {
 pcbnew.PAD_SHAPE_CIRCLE: "PAD_SHAPE_CIRCLE",
 pcbnew.PAD_SHAPE_OVAL: "PAD_SHAPE_OVAL",
 pcbnew.PAD_SHAPE_RECT: "PAD_SHAPE_RECT",
 pcbnew.PAD_SHAPE_TRAPEZOID: "PAD_SHAPE_TRAPEZOID" 
}
# new in the most recent kicad code
if hasattr(pcbnew, 'PAD_SHAPE_ROUNDRECT'):
 padshapes[pcbnew.PAD_SHAPE_ROUNDRECT] = "PAD_SHAPE_ROUNDRECT",

Now we can get to it. Need to get the board, get the boundary coordinates, and set the scale factor

board = pcbnew.GetBoard()
boardbbox = board.ComputeBoundingBox()
boardxl = boardbbox.GetX()
boardyl = boardbbox.GetY()
boardwidth = boardbbox.GetWidth()
boardheight = boardbbox.GetHeight()

# the internal coorinate space of pcbnew is 10E-6 mm. (a millionth of a mm)
# the coordinate 121550000 corresponds to 121.550000

SCALE = 1000000.0

Writing an SVG

Here is where things happen. I’m sure if kicad include the svgwrite package or if it picks it up from my python install area. I did need to install it to python to do some experiments before doing anything in Kicad.

Edit Nov 15, 2017 

Due to kicad’s change to color management (in favor of RGB over colornames), the code below is not uptodate. Go to github for the latest working version.

End edit

import sys, os
import svgwrite

print("working in the dir " + os.getcwd())
name = "output.svg"
# A4 is approximately 21x29
dwg = svgwrite.Drawing(name, size=('21cm', '29cm'), profile='full', debug=True)

dwg.viewbox(width=boardwidth, height=boardheight, minx=boardxl, miny=boardyl)
background = dwg.add(dwg.g(id='bg', stroke='white'))
background.add(dwg.rect(insert=(boardxl, boardyl), size=(boardwidth, boardheight), fill='white'))

svglayers = {}
for colorcode, colorname in colornames.items():
 layer = dwg.add(dwg.g(id='layer_'+colorname, stroke=colorname.lower(), stroke_linecap="round"))
 svglayers[colorcode] = layer

alltracks = board.GetTracks() 
for track in alltracks:
 # print("{}->{}".format(track.GetStart(), track.GetEnd()))
 # print("{},{}->{},{} width {} layer {}".format(track.GetStart().x/SCALE, track.GetStart().y/SCALE,
 # track.GetEnd().x/SCALE, track.GetEnd().y/SCALE,
 # track.GetWidth()/SCALE,
 # track.GetLayer()) 
 # )
 layercolor = board.GetLayerColor(track.GetLayer())
 svglayers[layercolor].add(dwg.line(start=(track.GetStart().x,
 track.GetStart().y),
 end=(track.GetEnd().x,
 track.GetEnd().y),
 stroke_width=track.GetWidth()
 ))


svgpads = dwg.add(dwg.g(id='pads', stroke='red',fill='orange'))
allpads = board.GetPads()

for pad in allpads:
 mod = pad.GetParent()
 name = pad.GetPadName()
 if (0):
 print("pad {}({}) on {}({}) at {},{} shape {} size {},{}"
 .format(name,
 pad.GetNet().GetNetname(),
 mod.GetReference(),
 mod.GetValue(),
 pad.GetPosition().x, pad.GetPosition().y,
 padshapes[pad.GetShape()],
 pad.GetSize().x, pad.GetSize().y
 ))
 if (pad.GetShape() == pcbnew.PAD_SHAPE_RECT):
 if ((pad.GetOrientationDegrees()==270) | (pad.GetOrientationDegrees()==90)):
 svgpads.add(dwg.rect(insert=(pad.GetPosition().x-pad.GetSize().x/2,
 pad.GetPosition().y-pad.GetSize().y/2),
 size=(pad.GetSize().y, pad.GetSize().x)))
 else:
 svgpads.add(dwg.rect(insert=(pad.GetPosition().x-pad.GetSize().x/2,
 pad.GetPosition().y-pad.GetSize().y/2),
 size=(pad.GetSize().x, pad.GetSize().y)))
 elif (pad.GetShape() == pcbnew.PAD_SHAPE_CIRCLE):
 svgpads.add(dwg.circle(center=(pad.GetPosition().x, pad.GetPosition().y),
 r=pad.GetSize().x))
 elif (pad.GetShape() == pcbnew.PAD_SHAPE_OVAL):
 svgpads.add(dwg.ellipse(center=(pad.GetPosition().x, pad.GetPosition().y),
 r=(pad.GetSize().x/2, pad.GetSize().y/2)))
 else:
 print("unknown pad shape {}({})".format(pad.GetShape(), padshapes[pad.GetShape()]))


 
dwg.save()

 

modifying pcbnew layout from python

I my previous post, I talked about querying for information about your layout. In this one I’ll show you how to create your own wires/vias from python. I also cover moving modules. Most of the code is self-explanatory but I find it helpful to have sample “cookbook” code.

The main thing to keep in mind when creating new objects is that even though you have to pass a board pointer to the constructors, you still have to call board.Add(obj). Also, you have to add it before setting the net. If you try to set the net before, it’ll do nothing.

Remember that the units are 1E-6mm. So if you have a mm value multiply it by a million.

Layers

First, let’s generate our layer mapping

layertable = {}

numlayers = pcbnew.LAYER_ID_COUNT
for i in range(numlayers):
 layertable[i] = board.GetLayerName(i)
 print("{} {}".format(i, board.GetLayerName(i)))

Add a track

track = pcbnew.TRACK(board)
track.SetStart(pcbnew.wxPoint(136144000, 95504000))
track.SetEnd(pcbnew.wxPoint(176144000, 95504000))
track.SetWidth(1614400)
track.SetLayer(layertable["F.Cu"])

board.Add(track)
track.SetNet(clknet)

Add a via

In this case, I’m going to copy an existing via. Note that there is also the clone method, but doing it this way you’ll know how to generate a via from scratch.

There isn’t yet a direct way to query a via for its layers. The way I work around this is by looping through all layers and calling IsOnLayer This is one of the reasons I’m showing how to copy an existing via.

The via types will be one of these:

  • pcbnew.VIA_THROUGH
  • pcbnew.VIA_BLIND_BURIED
  • pcbnew.VIA_MICROVIA

The width if the via is the diameter. If you forget to set this, you’ll get funny behavior where via disappears from the display when you zoom in.

newvia = pcbnew.VIA(board)
# need to add before SetNet will work, 
# so just doing it first
board.Add(newvia)

toplayer=-1
bottomlayer=pcbnew.LAYER_ID_COUNT
for l in range(pcbnew.LAYER_ID_COUNT):
   if not track.IsOnLayer(l):
      continue
   toplayer = max(toplayer, l)
   bottomlayer = min(bottomlayer, l)

# now that I have the top and bottom layers, I tell the new
# via
newvia.SetLayerPair(toplayer, bottomlayer)
newvia.SetPosition(pcbnew.wxPoint(track.GetPosition().x+offset[0],
                                  track.GetPosition().y+offset[1]))
newvia.SetViaType(oldvia.GetViaType())
newvia.SetWidth(oldvia.GetWidth())
newvia.SetNet(tonet)

Moving a module

I haven’t tried creating a new module yet. I prefer to let the netlist importer do this for me. I do, however, find it useful to be able to move modules. They all come in on top of each other. There are a variety of placement algorithms one might want to implement. 1

Note that the orientation is degrees*10.0

peer.SetPosition(pcbnew.wxPoint(newx, newy))
peer.SetOrientation(180*10)

Class diagram

I’ve created a class diagram to help me remember. Click to enlarge. Also available in my github


  1. In my previous professional life, one of the more interesting ones I saw was using linear programming. You start with everything in the middle. You create a set of equations representing net connectivity as well as cell overlaps. Solve the equations. Repeat. It was good for a couple hundred thousand cells. Much more than what PCB requires. Simulated Annealing would likely be easier here.

The basics of scripting in pcbnew

I’ve found that, so far, 1 I’m able to do all of the layout queries and manipulations I’ve wanted to do.

Note that these examples don’t work on the latest release as of Feb 2017 (4.0.5). You want one of the nightly builds, also in the kicad download area. Or you can build it

The interface is lacking some consistency but it’s fine if you have a map of the classes (click for a larger version or download it from here):

 

In this post, I’ll focus on querying a board for information about it’s contents. The code can be found in this github repo.

Getting started

To invoke any of these examples, you’ll want pcbnew’s scripting window. Tools->scripting console

You’ll probably want these in your scripts

import pcbnew

# most queries start with a board
board = pcbnew.GetBoard()

Nets

Want to know all of the nets in your board?
Nets can be looked up in two ways:

  • by name
  • by netcode – a unique integer identifier for your net.

If you run this code:

# returns a dictionary netcode:netinfo_item
netcodes = board.GetNetsByNetcode()

# list off all of the nets in the board.
for netcode, net in netcodes.items():
    print("netcode {}, name {}".format(netcode, net.GetNetname()))

# here's another way of doing the same thing.
print("here's the other way to do it")
nets = board.GetNetsByName()
for netname, net in nets.items():
    print("method2 netcode {}, name{}".format(net.GetNet(), netname))

# maybe you just want a single net
# the find method returns an iterator to all matching nets.
# the value of an iterator is a tuple: name, netinfo
clknet = nets.find("/clk").value()[1]
clkclass = clknet.GetNetClass()

print("net {} is on netclass {}".format(clknet.GetNetname(),
 clkclass))

You’ll get something like this:

netcode 49, name /ihg
netcode 50, name /ihh
netcode 51, name /data_in
netcode 52, name /data_out
netcode 53, name /clk
here's the other way to do it
method2 netcode 0, name
method2 netcode 23, name+5V
method2 netcode 53, name/clk
method2 netcode 55, name/data_contd
method2 netcode 51, name/data_in

Physical dimensions

The coordinate space of kicad_pcb is in mm. At the beginning of this wiki about Kicad’s Board_File_Format

“All physical units are in mils (1/1000th inch) unless otherwise noted.”

Then later in historical notes, it says,

“As of 2013, the PCBnew application creates ‘.kicad_pcb’ files that begin with (kicad_pcb (version 3)”. All distances are in millimeters.

In short, for the data that I’ve recently created, the internal coordinate space of pcbnew is 10E-6 mm. (a millionth of a mm)
For example, the coordinate 121550000 corresponds to 121.550000mm

SCALE = 1000000.0

boardbbox = board.ComputeBoundingBox()
boardxl = boardbbox.GetX()
boardyl = boardbbox.GetY()
boardwidth = boardbbox.GetWidth()
boardheight = boardbbox.GetHeight()

print("this board is at position {},{} {} wide and {} high".format(boardxl,
    boardyl,
    boardwidth,
    boardheight))

Modules/Pads

Each of your placed modules can be found with its reference name. The module connection points are pads, of course.

# generate a LUT with shape integers to a string
padshapes = {
    pcbnew.PAD_SHAPE_CIRCLE: "PAD_SHAPE_CIRCLE",
    pcbnew.PAD_SHAPE_OVAL: "PAD_SHAPE_OVAL",
    pcbnew.PAD_SHAPE_RECT: "PAD_SHAPE_RECT",
    pcbnew.PAD_SHAPE_TRAPEZOID: "PAD_SHAPE_TRAPEZOID"
}
# new in the most recent kicad code
if hasattr(pcbnew, 'PAD_SHAPE_ROUNDRECT'):
    padshapes[pcbnew.PAD_SHAPE_ROUNDRECT] = "PAD_SHAPE_ROUNDRECT",

modref = "U1"
mod = board.FindModuleByReference(modref)
for pad in mod.Pads():
    print("pad {}({}) on {}({}) at {},{} shape {} size {},{}"
        .format(pad.GetPadName(),
                pad.GetNet().GetNetname(),
                mod.GetReference(),
                mod.GetValue(),
                pad.GetPosition().x, pad.GetPosition().y,
                padshapes[pad.GetShape()],
                pad.GetSize().x, pad.GetSize().y
    ))

Gives you this:

pad 1(/ilb) on U1(74HC595) at 127635000,106520000 shape PAD_SHAPE_RECT size 1500000,600000
pad 2(/ilc) on U1(74HC595) at 126365000,106520000 shape PAD_SHAPE_RECT size 1500000,600000
pad 3(/ild) on U1(74HC595) at 125095000,106520000 shape PAD_SHAPE_RECT size 1500000,600000

Layer

Most of the pcb data is on a layer. pcbnew stores layers as numbers. Here we can print them all out

layertable = {}

numlayers = pcbnew.LAYER_ID_COUNT
   for i in range(numlayers):
       layertable[i] = board.GetLayerName(i)
       print("{} {}".format(i, board.GetLayerName(i)))

Which gives you this:

0 F.Cu
1 In1.Cu
2 In2.Cu
3 In3.Cu
...

Tracks (wires and vias)

A wire is stored in a TRACK object. Vias are in a class derived from TRACK. Let’s list some of them.

# clk net was defined above as was SCALE
clktracks = board.TracksInNet(clknet.GetNet())
   for track in clktracks:
       print("{},{}->{},{} width {} layer {}".format(track.GetStart().x/SCALE,
             track.GetStart().y/SCALE,
             track.GetEnd().x/SCALE,
             track.GetEnd().y/SCALE,
             track.GetWidth()/SCALE,
             layertable[track.GetLayer()]))

And here are the wires

121.92,97.79->126.492,97.79 width 0.25 layer F.Cu
127.762,100.33->125.984,100.33 width 0.25 layer F.Cu
127.762,99.314->127.762,100.33 width 0.25 layer B.Cu

So that’s how to query data. In my next post, I’ll talk about making changes


  1. thanks to recent work by Kicad contributor/developer Dick Hollenbeck

Compiling Kicad on Ubuntu

Edit Dec 29, 2017: added cmake flag enabling external plugins

In my last post, I mentioned that compiling Kicad is something most would not be willing to do.

Most of the scripting in this blog will not work in version 4.0.5 which the most recent as of this writing. I recommend to install either the nightly or build yourself. If you just want the latest verion of Kicad, then you can find nightly builds in the Kicad Downloads Area

Well, this post is about the steps needed to run your own compiled version on Ubuntu 1. These steps worked for me on a freshly installed VirtualBox Ubuntu install.

The scripting support in the latest source based kicad has progressed a bit beyond the released Kicad. There are one or two APIs that have appeared and the scripting window has some IDE’ish features added 2. I do want to make some C++ changes, so I have the need to compile. I was also helpful to step through execution to understand some of the APIs

VirtualBox Ubuntu install

These are my notes from installing Ubuntu VirtualBox.

  • create virtual machine
  • linux – ubuntu 64
  • 4 GB ram
  • virtual disk image
  • file location and size to 32GB

 

virtualbox settings->storage->controller IDE->empty = set to ubuntu-16.04.1-desktop-amd64.iso

start machine

install ubuntu
english
no install updates
erase and install
write the changes to disks – continue
set time zone
set keyboard

put in login details

install

it will want to reboot. do that now.
it will say to remove the install medium. VirtualBox seems to do that automagically

At this point you’ll have a clean install of ubuntu.

Take a snapshot so you’ll have it later.

in VirtualBox manager in the upper right. click snapshots
click on the blue camera and take a snapshot

start the machine again
go the the devices menu at the top-> shared clipboard -> bidirectional
devices menu -> insert guest additions
click run on the popup
give your password

start an xterm

Build Kicad

Based on a list here: https://gist.github.com/ceremcem/4024c0a4a8649e858855 with some additions:

sudo apt-get -y install libwxgtk3.0-0v5 libglew-dev libcairo2-dev libbz2-dev doxygen libssl-dev libboost-dev libboost-thread-dev libboost-context-dev libboost-filesystem-dev libboost-iostreams-dev libboost-locale-dev libboost-program-options-dev swig python-wxgtk3.0* git cmake libwxgtk3.0 libglm-dev libcurl3 libcurl3-dev python-dev

mkdir kicad
cd kicad
mkdir build
mkdir install

git clone -b master https://git.launchpad.net/kicad
cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DwxWidgets_USE_DEBUG=ON -DKICAD_SCRIPTING_WXPYTHON=ON -DKICAD_SCRIPTING=ON -DKICAD_SCRIPTING_MODULES=ON -DKICAD_SCRIPTING_ACTION_MENU=ON -DCMAKE_INSTALL_PREFIX=`realpath ../install` ../kicad

make
make install

 

If you install into something other than /usr/lib; if you use prefix as is shown above, you’ll need to do this:

export LD_LIBRARY_PATH=`realpath ../install/lib/`

This is for Ubuntu 16.04.1. If these directions stop working, please leave a comment below.


  1. I have basically stopped using the Windows platform. Adobe Lightroom and Autodesk Fusion are the only reasons I bother with it at all. Sorry.

  2. it’s very possible that the IDE stuff is a freebie due to using a more recent Python component.

Real Scripting. The most important feature a tool can have.

One of the dreams of Free and Open Source Software (FOSS) is that if a program doesn’t do what you want, you can add it. This is frequently said, but I don’t think it’s as true as we’d like it to be or at least not as true as it could be. Kicad is open source and used by many. It’s also missing a lot of features, but what would it take for someone to contribute?

First, it’s written in C++ which is intimidating to many. I’d guess that for many (most?) folks who want to design a PCB, C++ means Kicad is effectively closed source.

Second, even if one is comfortable with C++, many (most?) compiled projects are not so easy to replicate. Just building and running the unmodified code is an obstacle.

So most potential contributors don’t even make it out of the gate. Scripting capabilities lower the bar. Few will compile code, many might consider trying a simple hello world script.

There are lots of examples of tools with scripting out there. Adobe products, for example, have scripting. Photoshop has a macro feature that enables user to automate many tasks. Lightroom goes further. 1. Even artsy people who are completely terrified by the idea of programming can use these.

A better example is the Firefox and later Chrome browsers. To write an addon/extension doesn’t require one to know C++ or even compile anything. Javascript is all that’s needed. In the case of Chrome, the debugger is always right there. 2. These browsers are successful largely (entirely?) because of the wealth of extensions available.

Yet another example is Blender. Everything is exposed to the scripting language. Everything. Menus, bindings, direct node/edge/face manipulation. There’s nothing you do from C++ that isn’t available in the scripting language.

Kicad’s pcbnew has had python support for some time now; I wrote a message on kicad-users about it almost four years ago. Things have progressed a little, but not much. You no longer have to recompile pcbnew to use it, which is a big step.

The purpose of this blog is to help things along. I hope to better document the existing scripting features to help new potential user make real changes to pcbnew 3

At this point in the post, if you just want to get into scripting, continue to my next posts. The rest of this one is just commentary on why I think this is an important topic.

Who am I. Who is this guy?

Up til two years ago 4, I worked for a well know semiconductor manufacturer. You’ve heard of the company and it’s almost guaranteed that you’ve owned one or more of their products. I worked there for a bit over 20 years in the CAD/EDA 5 department. For 14 of those years, I worked on and supported the VLSI 6 layout editing tools used to design the most well know of their products.

The earliest of the editors didn’t have scripting. They weren’t even written in C++ which was a young/new language at the time. 7. When I took from being a tool developer to a year and a half of active floor support, I made an important niche for myself by writing simple editing macros. In meetings, when someone reported a simple/repetitive task that’s too difficult on the tools, a frequent answer was, “let’s get Miles to write a macro”. It was a nice position to be in. Some of those macros were hard to write, but most were pretty trivial… Trivial if you knew how to do it.

Eventually that generation of tools wasn’t good enough and a new one was written. 8 The new one was written in C++ with a comprehensive TCL interface 9. You can do anything through the TCL interface. The only reason to move to C++ is runtime performance. 10

The result of this scripting capability is that great new features came from some unlikely places. This company has an army of polygon pushers with no training in software. Many of these came up with some great stuff that tool developers later supported. I’ve venture to say that today, the majority of the tools’ capabilities originated this way.

There’s a small handful of kicad developers 11. There are lots of kicad users.

How many of them would try to dig through the C++ code?

How many of them would pull up the scripting window and enter a couple python commands?

That’s the power I’d like to harness.


  1. I personally, think their “scripting” is a joke. They only enable you to invoke existing functionality. You can’t really create your own new functions. I would love to add a levels feature to Lightroom. I’ve had another idea for dealing with white balance. Alas,… can’t do it.

  2. right click->inspect

  3. I would love to see the same thing happen with eeschema

  4. in other words, I don’t work there now and I don’t represent them in any way

  5. Computer Aided Design is what most people think of it, but Electronic Design Automation is the term used by industry

  6. although I worked on chip design tools, which are not the same as PCB design tools, I think these lessons still apply

  7. Mainsail was the language that was used. I liked using it.

  8. This was all internal. Only a handful of companies do this kind of stuff and no one does nearly the amount of hand design. It made sense to develop these tools in-house and that continues to this day.

  9. Like with MAINSAIL, people hate on TCL. I think it’s a fine language that can do most things I want. A notable exception is closures/lambdas, but C++ doesn’t really have those either. C++ lambdas are very limited.

  10. There was one guy switched his code to C++ for the sole reason of preventing his users from messing with it. He claimed that too often, users would modify it poorly/incorrectly and then complain to him.  It was a cop out.

  11. I don’t 100% know that this is true; I’m new to the kicad world, but I’ll bet you a dollar.