Most of the time, the modules in your design will be introduced via netlist import from eeschema. An important exception to this is mounting holes. Your design may need some holes to enable you to screw the resulting board onto some sort of case/enclosure. In the GUI, you’d do this via the “Add footprints” command. This works, but what if you want to ensure that the resulting holes end up in specific locations? Script it!
Compared to other scripting tasks in pcbnew, figuring out how to add footprints to a design was a pain. In the end, it’s pretty easy1
My designs are pretty simple2, amounting to rectangular boards. My “enclosures” tend to be a piece of wood to which I mount a board with some drywall screws. Because of this simplicity, it doesn’t really matter where my mounting holes are. Still, I would like for them to at least be in a regular pattern. So I put together a script to put one in each corner of the board’s boundary.
First off, while pcbnew’s add footprint command can access kicad’s footprint libraries on GITHUB, this is not something I’ve achieved yet.3 For the code in this post to work, you’ll need to clone at least one of kicad’s footprint repos. For example, something like this:
git clone https://github.com/KiCad/Connectors.pretty.git
Don’t forget where you put it. In the case of the script, I have a variable footprint_lib. You’ll want to change this variable4. While we’re at it, let’s add some other boilerplate.
footprint_lib = '/bubba/electronicsDS/kicad/development/footprints/Connectors.pretty' board = pcbnew.GetBoard() # 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
Since I want to put my mounting holes in the four corners of the board, I have to compute the four corners:
rect = None for d in board.GetDrawings(): if (d.GetLayerName() != "Edge.Cuts"): continue if (rect == None): rect = d.GetBoundingBox() else: rect.Merge(d.GetBoundingBox())
While the module class has a GetBoundingBox function, that box includes stuff like reference designators. So I have a function to compute the bounding box of metals, solder masks and such.
def GetModBBox(mod): modbox = None for pad in mod.Pads(): if (modbox == None): modbox = pad.GetBoundingBox() else: modbox.Merge(pad.GetBoundingBox()) for gi in mod.GraphicalItems(): if (modbox == None): modbox = gi.GetBoundingBox() else: modbox.Merge(gi.GetBoundingBox()) return modbox
Since I want to put a mounting hole in each corner, I generate a list of points from a bounding rectangle. Much better than cut/pasting a bunch of module code.
def GetRectCorners(rect): return [pcbnew.wxPoint(rect.Centre().x-rect.GetWidth()/2, rect.Centre().y-rect.GetHeight()/2), pcbnew.wxPoint(rect.Centre().x-rect.GetWidth()/2, rect.Centre().y+rect.GetHeight()/2), pcbnew.wxPoint(rect.Centre().x+rect.GetWidth()/2, rect.Centre().y+rect.GetHeight()/2), pcbnew.wxPoint(rect.Centre().x+rect.GetWidth()/2, rect.Centre().y-rect.GetHeight()/2)]
How to actually add a footprint.
Now we finally get to the “hard” part, adding the footprint. The ability to add a footprint is exposed in an API in the PCB_IO plugin. I don’t yet understand the role that plugin’s play in the context of pcbnew; most plugins are python scripts. PCB_IO is one written in C++.
It comes down to two easy lines. One to construct an instance of the PCB_IO plugin, and the second to instantiate the footprint. As I mentioned earlier in this post footprint_lib contains a path to a directory where you’ve put your kicad_mod files.
io = pcbnew.PCB_IO() mod = io.FootprintLoad(footprint_lib, "1pin")
Everything after is easy.
board = pcbnew.GetBoard() board.Add(mod)
I want to put four of these in the four corners of the boundary bounding box defined by rect above. First I shrink rect by the size of the footprint module. 5. I have to do a little bit of math to size the box. SetWidth/SetHeight are relative to the bottom left corner, something I’ve had to get used to. I wanted it to be relative to the center.
mod = io.FootprintLoad(footprint_lib, "1pin") modbox = GetModBBox(mod); rect.SetWidth(rect.GetWidth() - modbox.GetWidth()) rect.SetHeight(rect.GetHeight() - modbox.GetHeight()) rect.SetX(rect.GetX() + modbox.GetWidth()/2) rect.SetY(rect.GetY() + modbox.GetHeight()/2)
And now I finally create and place the holes:
for point in GetRectCorners(rect): mod = io.FootprintLoad(footprint_lib, "1pin") modbox = GetModBBox(mod) point.x = point.x - modbox.Centre().x + mod.GetPosition().x point.y = point.y - modbox.Centre().y + mod.GetPosition().y mod.SetPosition(point) point.y - modbox.Centre().y)) board.Add(mod)
Again, the script can be found on my github.
Makes me think of times in my career when I spent a couple full days debugging a problem that was fixed with a single character. I’m sure most C/C++ programmers have experienced this with “=” vs “==” in an if statement. In this case, it’s not a matter of bugs, but that I don’t know the code that well.I should probably try to interact some with the kicad developers. ↩
I’m a software guy. I entered college intending to major EE, but my aptitude for programming eclipsed circuit stuff. Still, I pine for the ability to design circuits.↩
I didn’t actually try, but I did keep and eye out for relevant code while tracking down the APIs needed to add a module↩
“bubba”, in the path below, is one of my drives. I think of it as a name that only big guys have. As disks go and as technology has progressed, it really not that big anymore.↩
EDA_RECT is nice enough to have an API called inflate. Sadly, it takes wxCoord as arguments. I haven’t found a way to create one or these via the existing python APIs.↩