Converting a footprint to a Python script

I’ve written a program which takes a footprint (.kicad_mod file) and converts it into a Python script. The generated Python script uses KicadModTree and outputs the original footprint.

The idea is to make it easy to parameterize an existing footprint, and generate a family of similar footprints. (Terminal blocks, for example.) Also, certain types of edits are easier in script format, because the script contains a variable for each unique x or y coordinate, so this makes it easier to move things around, without having to change the coordinate each time it appears.

There are also command-line options for translating and rotating the coordinates before the script is generated, to make it easier to get pin 1 into the upper-left position to comply with the KLC.

My program is written in Haskell, using the kicad-data package, and can be easily built from source using the stack tool. However, I also have precompiled binaries for Linux, Mac, and Windows.

As an example, here is the script my program produces when run on TerminalBlock_Pheonix_PT-3.5mm_2pol.kicad_mod:

#!/usr/bin/env python3

from KicadModTree import *

footprint_name = "TerminalBlock_Pheonix_PT-3.5mm_2pol"
kicad_mod = Footprint(footprint_name)
kicad_mod.setDescription("2-way 3.5mm pitch terminal block, Phoenix PT series")

d1 = [1.2, 1.2]
p1 = [2.4, 2.4]
t1 = [1.0, 1.0]
w1 = 0.15
w2 = 0.05
x1 = 1.75
x2 = -1.9
x3 = 5.4
x4 = -1.75
x5 = 5.25
x6 = 3.5
x7 = 0.0
y1 = -4.3
y2 = 6.0
y3 = -3.3
y4 = 4.7
y5 = 4.1
y6 = 4.5
y7 = 3.0
y8 = -3.1
y9 = 0.0

kicad_mod.append(Text(type="reference", text="REF**", at=[x1, y1], layer="F.SilkS", size=t1, thickness=w1))
kicad_mod.append(Text(type="value", text=footprint_name, at=[x1, y2], layer="F.Fab", size=t1, thickness=w1))
kicad_mod.append(Line(start=[x2, y3], end=[x3, y3], layer="F.CrtYd", width=w2))
kicad_mod.append(Line(start=[x2, y4], end=[x2, y3], layer="F.CrtYd", width=w2))
kicad_mod.append(Line(start=[x3, y4], end=[x2, y4], layer="F.CrtYd", width=w2))
kicad_mod.append(Line(start=[x3, y3], end=[x3, y4], layer="F.CrtYd", width=w2))
kicad_mod.append(Line(start=[x1, y5], end=[x1, y6], layer="F.SilkS", width=w1))
kicad_mod.append(Line(start=[x4, y7], end=[x5, y7], layer="F.SilkS", width=w1))
kicad_mod.append(Line(start=[x4, y5], end=[x5, y5], layer="F.SilkS", width=w1))
kicad_mod.append(Line(start=[x4, y8], end=[x4, y6], layer="F.SilkS", width=w1))
kicad_mod.append(Line(start=[x5, y6], end=[x5, y8], layer="F.SilkS", width=w1))
kicad_mod.append(Line(start=[x5, y8], end=[x4, y8], layer="F.SilkS", width=w1))
kicad_mod.append(Pad(number="2", type=Pad.TYPE_THT, shape=Pad.SHAPE_CIRCLE, at=[x6, y9], size=p1, layers=Pad.LAYERS_THT, drill=d1))
kicad_mod.append(Pad(number="1", type=Pad.TYPE_THT, shape=Pad.SHAPE_RECT, at=[x7, y9], size=p1, layers=Pad.LAYERS_THT, drill=d1))

file_handler = KicadFileHandler(kicad_mod)
file_handler.writeFile(footprint_name + ".kicad_mod")
3 Likes

Nice work, finally someone is doing something useful with the kicad-data package I wrote. :slight_smile:

At the risk of steering the discussion away from the tool itself and towards how the tool is written: I noticed you didn’t make use of the lenses I had started implementing. For instance you could do the XY translation using the itemHandle lense. A lense that enables rotation would be interesting to add as well. Did you notice these at all?

It’s neat that you manage pick out the common variables to be re-used when generating the scripts. One idea I have been toying with with my own experiments with footprints scripting is to use SMT solvers for code synthesis to construct more advanced code patterns such as conditionals and for loops. I am planning to try out Rosette which is for Racket but Haskell likely has similar libraries for connecting to solvers. Whether it’s worth the effort though, I am not yet sure.

1 Like

No, I hadn’t really looked at the lenses. I don’t understand lenses, and the type signatures look scary, so I stayed away from them. :slight_smile:

I hadn’t thought about constructing for loops, but I had thought about finding line segments with common endpoints and turning them into a polyline (which KicadModTree strangely calls PolygoneLine) or a rectangle. Maybe I will do this in a future version.

I’ve also thought about sorting the x and y coordinates by value, so that x1 is the leftmost x coordinate, and y1 is the topmost y coordinate. I think this might aid in understanding the script.

2 Likes

Lenses are really neat, they are essentially getter/setter functions in one expression. I should have used the type aliases to simplify my type signatures. itemHandle would become:

itemHandle :: Lens' PcbnewItem V2Double

And the most basic things that you can do with this are “get” the value:

view itemHandle :: PcbnewItem -> V2Double

and “modify” it:

over itemHandle :: (V2Double -> V2Double) -> PcbnewItem -> PcbnewItem

So over allows you to transform that item according to the function you give it. In this case it will move the entire item according to how you transform the handle.

I have used Lens.Family2 to define the lenses but they should be compatible with any other Lens implementation on Hackage.

P.S. This video of a Simon Peyton Jones presentation is the best introduction to Lenses IMO, requires a login but you can use bugmenot.

1 Like

I’ve implemented these ideas (not the for loop, but the other ones) in my latest release, 0.1.0.1:

  • Eliminate redundant vertices. (i. e. two segments which can be replaced with one segment)

  • Join line segments into polylines or rectangles.

  • Sort coordinates, so that x1 is the leftmost and y1 is the topmost.

  • Propagate 3D model name from footprint to script.

  • Use separate variables for line widths on different layers.

  • Shorten script’s variable “kicad_mod” to “f”, for shorter lines.

Source and binaries are available from the same locations as before.

1 Like