Scripting fusion 360 designs into kicad

This blog is about scripting in kicad/pcbnew. This post tries to showcase some of the things that you can do with python scripts in pcbnew.

The layout of this board was designed in fusion 360 and brought into kicad via scripting. Very little manual work was required in pcbnew; it amounted mostly to deleting stuff and changing some zone names. The scripts also did the routing and placed the many vias

The code I demonstrate can be found on my git hub here and here.

This board implements a ring light for photography (I use multiple boards to stay within the 10×10 limit of cheap fabricators:

I’ve created a video show all of the steps in kicad:

I won’t explain the code in this post, I’ll do that in future posts. I will, however, take a moment to recommend some useful python libraries:

The SciPy library is fantastic for graph algorithms like minimum spanning tree and delanay triangulation

The shapely library is very nice for polygon manipulation. Merging polygons, bloating and shrinking them…

The dxf_grabber library is how I parse dxf files. Together with shapely, you can do a lot of import stuff. 1

Hope it helps


  1. sadly, I had already implemented a bunch of code merging lines and arcs into polygons.

Zones with holes from SVG

I’ve written about zones and boundaries before. Since then, the data structs behind zones have changed a bit. Also, I didn’t cover zones with holes. The old post is still relevant and there’s a bunch I won’t repeat here.

In this post, I talk about zones with holes and with multiple boundaries. Multiple boundaries makes it easier to move the zones together as a unit.

The code associated with this post is here.

Here’s a video talking mostly about the steps needed in inkscape to draw the graphic. Also has scripting details, but the text below is probably more informative:

 

Zone basics, revisited

pcbnew zones are stored in ZONE_CONTAINERs. They are what you get when you call board.InsertArea to create a new zone. Something like this:


SCALE = 1000000.0
zone_container = board.InsertArea(powernet.GetNet(), 0, powerlayer,
                                  int(10*SCALE), int(10*SCALE),
                                  pcbnew.CPolyLine.DIAGONAL_EDGE)

That creates a single point zone on net powernet and layerpowernet1

To modify the zone, you need to access the shape_poly_set. It’s a single data struct that holds all of the coordinates.


shape_poly_set = zone_container.Outline()

To add points:


shape_poly_set.Append(int(10*SCALE), int(20*SCALE))

But there are some details hidden there. Append actually takes a bunch more arguments. From the c++ code:


/**
* Function Append
* adds a new vertex to the contour indexed by \p aOutline and \p aHole (defaults to the
* outline of the last polygon).
* @param x is the x coordinate of the new vertex.
* @param y is the y coordinate of the new vertex.
* @param aOutline is the index of the polygon.
* @param aHole is the index of the hole (-1 for the main outline),
* @param aAllowDuplication is a flag to indicate whether it is allowed to add this
* corner even if it is duplicated.
* @return int - the number of corners of the selected contour after the addition.
*/
int Append( int x, int y, int aOutline = -1, int aHole = -1,
            bool aAllowDuplication = false );

aOutline tells which boundary you want to add to. By default, you add to the last one -1. If aHole is -1, you’re telling it you want the bound, not a hole. You see, the boundaries/outlines are numbered 0+. Same with the holes.

so, with the single outline zone we’re messing with now, the call above is equivalent to these:

shape_poly_set.Append(int(10*SCALE), int(20*SCALE))
shape_poly_set.Append(int(10*SCALE), int(20*SCALE), -1)
shape_poly_set.Append(int(10*SCALE), int(20*SCALE), 0)

One last thing, you’ll probably want this line, to make the edges more visible:


zone_container.Hatch()

Additional outlines

It can be helpful to have multiple outlines on the same zone. That way, moving one moves the other. Unlike the initial zone method, InsertArea, this time all of the points are added with Append

You’ll need code like this:

shapeid = shape_poly_set.NewOutline()
shape_poly_set.Append(int(10*SCALE), int(20*SCALE), shapeid)

Adding holes

Adding holes is similar to adding outlines. Note that you are adding a hole to a particular outline:

hi = shape_poly_set.NewHole()
shape_poly_set.Append(int(10*SCALE), int(20*SCALE), -1, hi)
shape_poly_set.Append(int(10*SCALE), int(20*SCALE), outlineid, hi)

Putting it together with an SVG

So, I wanted to convert an inkscape drawing into a zone. I have a post on my other blog talking about how to parse SVG paths. The code for the parse I wrote is here. I also have a blog post one understanding and parsing svg. The main function returns instances of a class I called SVGpath, which will give you a list of polys and holes. I have a helper function to group the bounds with their holes. The result is this code for creating my zones:


# here I load from drawing.svg in the current directory. You'll want to change that path.
paths = parse_svg_path.parse_svg_path(os.path.dirname(inspect.stack()[0][1]) + '/drawing.svg')
if not paths:
     raise ValueError('wasnt able to read any paths from file')


# things are a little tricky below, because the first boundary has 
# its first point passed into the creation of the new area. subsequent
# bounds are not done that way.
zone_container = None
shape_poly_set = None
 
for path in paths:
    for shape in path.group_by_bound_and_holes():
        shapeid = None
        if not shape_poly_set: 
            # the call to GetNet() gets the netcode, an integer.
            zone_container = board.InsertArea(powernet.GetNet(), 0, powerlayer,
                                              int(shape.bound[0][0]*SCALE),
                                              int(shape.bound[0][1]*SCALE),
                                              pcbnew.CPolyLine.DIAGONAL_EDGE)
            shape_poly_set = zone_container.Outline()
            shapeid = 0
        else:
            shapeid = shape_poly_set.NewOutline()
            shape_poly_set.Append(int(shape.bound[0][0]*SCALE),
                                  int(shape.bound[0][1]*SCALE),
                                  shapeid)
            
        for pt in shape.bound[1:]:
            shape_poly_set.Append(int(pt[0]*SCALE), int(pt[1]*SCALE))

        for hole in shape.holes:
            hi = shape_poly_set.NewHole()
            # -1 to the third arg maintains the default behavior of
            # using the last outline.
            for pt in hole:
                shape_poly_set.Append(int(pt[0]*SCALE), int(pt[1]*SCALE), -1, hi)
                
        zone_container.Hatch() 


  1. where those are set is covered in the old post