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.
Zones, boundaries, and silkscreen

  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.

Leave a Reply

Your email address will not be published. Required fields are marked *