Python footprint automation - corrupting footprint save file

Hi everyone,

I am currently trying to use the KiCad Python interface to automatically place footprints. Unfortunately, when my script finishes running, it non-deterministically corrupts the footprint save file. Obviously there is something I am missing, but I can’t seem to find it.

Firstly, I start with a pre-made footprint library called beans, and using the KiCad GUI footprint editor tool I create one footprint called My_PCI_footprint. This creates the following files on the filesystem:

beans.pretty/
-> beans.pretty/ (empty directory)
-> My_PCI_footprint.kicad_mod

Consequently, My_PCI_footprint.kicad_mod looks like this:

The idea is, that I want to use the API function ImportSettingsFrom function, and use that one pad as a “master pad”. When I finish running the script, the footprint save file gets broken, and each time in a different way:


corrupt2

Well, you get the idea. Note that I commented out ImportSettingsFrom thinking that maybe that was what was causing the problem, which is why they are all default attributes.

Also another problem: For some reason, the pads are all set to position (0, 0) despite the fact that I explicitly set it in the script for each pad.

Here is the script:

Script source
import sys, os;
import pcbnew;

def dbg_report_pad(pad):
    pad_shape_t_strtab = [
        "PAD_SHAPE_CIRCLE",
        "PAD_SHAPE_RECT",
        "PAD_SHAPE_OVAL",
        "PAD_SHAPE_TRAPEZOID",
        "PAD_SHAPE_ROUNDRECT",
        "PAD_SHAPE_CHAMFERED_RECT",
        "PAD_SHAPE_CUSTOM"
    ];
    
    print("Pad: \"{}\"".format(pad.GetName()));
    print("\tPad class: {}".format(pad.GetClass()));
    print("\tPad shape: {} ({})".format(pad.GetShape(),
                                        pad_shape_t_strtab[pad.GetShape()]));
    print("\tAnchor pad shape: {} ({})".format(pad.GetAnchorPadShape(),
                                               pad_shape_t_strtab[pad.GetAnchorPadShape()]));
    print("\tPosition: ({}mm, {}mm)".format(pcbnew.ToMM(pad.GetPosition().x),
                                            pcbnew.ToMM(pad.GetPosition().y)));
    size = pad.GetSize();
    print("\tSize: ({}mm, {}mm)".format(pcbnew.ToMM(size.GetWidth()),
                                        pcbnew.ToMM(size.GetHeight())));
    

print("#### Loading plugin");
plugin = pcbnew.GetPluginForPath("beans.pretty");
print("plugin object: " + str(plugin));
print("plugin name: " + plugin.PluginName());
print("\t");

print("#### Enumerating footprints");
footprints = plugin.footprintPyEnumerate("beans.pretty", False);
print("Found footprints:");
for fp_name in footprints:
    print("\t{}".format(fp_name));
print("\t");

print("#### Loading beans footprint in enumerator list");
fp = plugin.GetEnumeratedFootprint("beans.pretty", footprints[0]);
print("footprint object: {}".format(fp));
print("\t");

print("#### Loading pads");
pads = fp.Pads();
print("Pads object: {}".format(pads));

master_pad = None;

for idx, p in enumerate(pads):
    if idx == 0:
        master_pad = p;
    
    dbg_report_pad(p);

ypos = pcbnew.FromMM(-4.0);
xpos = pcbnew.FromMM(2.0);
xincr = pcbnew.FromMM(1.27);

for i in range(10):
    padname = "abracadabra" + str(i);
    
    new_pad = pcbnew.D_PAD(fp);
    print("Instianted:\n\tD_PAD {}\n\tchild of MODULE {}\n".format(new_pad, fp));

    #new_pad.ImportSettingsFromMaster(master_pad);

    print("Adding new pad at coords ({}, {})".format(xpos, ypos));
    print("Current coords: ({}, {})".format(new_pad.GetPosition().x,
                                            new_pad.GetPosition().y));

    new_pad.SetPosition(pcbnew.wxPoint(xpos, ypos));
    print("New coords: ({}, {})".format(new_pad.GetPosition().x,
                                        new_pad.GetPosition().y));
    new_pad.SetName(padname);
    
    fp.Add(new_pad);

    xpos += xincr;

print("Current pads in footprint module:");
pads = fp.Pads()
for i, p in enumerate(pads):
    print("{}:".format(i));
    dbg_report_pad(p);

print("Ok, saving...");
plugin.FootprintSave("beans.pretty", fp);

And here is the log that it prints. Note that just before saving, I iterate over the fp.Pads(), and the output is totally sane.

Output log
#### Loading plugin
plugin object: <pcbnew.PLUGIN; proxy of <Swig Object of type 'PLUGIN *' at 0x0000000004151fc0> >
plugin name: KiCad
	
#### Enumerating footprints
Found footprints:
	My_PCI_footprint
	
#### Loading beans footprint in enumerator list
footprint object: <pcbnew.MODULE; proxy of <Swig Object of type 'MODULE *' at 0x0000000003432060> >
	
#### Loading pads
Pads object: <pcbnew.PAD_List; proxy of <Swig Object of type 'DLIST< D_PAD > *' at 0x0000000003432090> >
Pad: "1"
	Pad class: PAD
	Pad shape: 4 (PAD_SHAPE_ROUNDRECT)
	Anchor pad shape: 0 (PAD_SHAPE_CIRCLE)
	Position: (0.0mm, -6.35mm)
	Size: (1.106mm, 3.0mm)
Instianted:
	D_PAD <pcbnew.D_PAD; proxy of <Swig Object of type 'D_PAD *' at 0x00000000034320f0> >
	child of MODULE <pcbnew.MODULE; proxy of <Swig Object of type 'MODULE *' at 0x0000000003432060> >

Adding new pad at coords (2000000, -4000000)
Current coords: (0, 0)
New coords: (2000000, -4000000)
Instianted:
	D_PAD <pcbnew.D_PAD; proxy of <Swig Object of type 'D_PAD *' at 0x00000000034320c0> >
	child of MODULE <pcbnew.MODULE; proxy of <Swig Object of type 'MODULE *' at 0x0000000003432060> >

Adding new pad at coords (3270000, -4000000)
Current coords: (0, 0)
New coords: (3270000, -4000000)
Instianted:
	D_PAD <pcbnew.D_PAD; proxy of <Swig Object of type 'D_PAD *' at 0x0000000003432180> >
	child of MODULE <pcbnew.MODULE; proxy of <Swig Object of type 'MODULE *' at 0x0000000003432060> >

Adding new pad at coords (4540000, -4000000)
Current coords: (0, 0)
New coords: (4540000, -4000000)
Instianted:
	D_PAD <pcbnew.D_PAD; proxy of <Swig Object of type 'D_PAD *' at 0x00000000034320f0> >
	child of MODULE <pcbnew.MODULE; proxy of <Swig Object of type 'MODULE *' at 0x0000000003432060> >

Adding new pad at coords (5810000, -4000000)
Current coords: (0, 0)
New coords: (5810000, -4000000)
Instianted:
	D_PAD <pcbnew.D_PAD; proxy of <Swig Object of type 'D_PAD *' at 0x00000000034320c0> >
	child of MODULE <pcbnew.MODULE; proxy of <Swig Object of type 'MODULE *' at 0x0000000003432060> >

Adding new pad at coords (7080000, -4000000)
Current coords: (0, 0)
New coords: (7080000, -4000000)
Instianted:
	D_PAD <pcbnew.D_PAD; proxy of <Swig Object of type 'D_PAD *' at 0x0000000003432180> >
	child of MODULE <pcbnew.MODULE; proxy of <Swig Object of type 'MODULE *' at 0x0000000003432060> >

Adding new pad at coords (8350000, -4000000)
Current coords: (0, 0)
New coords: (8350000, -4000000)
Instianted:
	D_PAD <pcbnew.D_PAD; proxy of <Swig Object of type 'D_PAD *' at 0x00000000034320f0> >
	child of MODULE <pcbnew.MODULE; proxy of <Swig Object of type 'MODULE *' at 0x0000000003432060> >

Adding new pad at coords (9620000, -4000000)
Current coords: (0, 0)
New coords: (9620000, -4000000)
Instianted:
	D_PAD <pcbnew.D_PAD; proxy of <Swig Object of type 'D_PAD *' at 0x00000000034320c0> >
	child of MODULE <pcbnew.MODULE; proxy of <Swig Object of type 'MODULE *' at 0x0000000003432060> >

Adding new pad at coords (10890000, -4000000)
Current coords: (0, 0)
New coords: (10890000, -4000000)
Instianted:
	D_PAD <pcbnew.D_PAD; proxy of <Swig Object of type 'D_PAD *' at 0x0000000003432180> >
	child of MODULE <pcbnew.MODULE; proxy of <Swig Object of type 'MODULE *' at 0x0000000003432060> >

Adding new pad at coords (12160000, -4000000)
Current coords: (0, 0)
New coords: (12160000, -4000000)
Instianted:
	D_PAD <pcbnew.D_PAD; proxy of <Swig Object of type 'D_PAD *' at 0x00000000034320f0> >
	child of MODULE <pcbnew.MODULE; proxy of <Swig Object of type 'MODULE *' at 0x0000000003432060> >

Adding new pad at coords (13430000, -4000000)
Current coords: (0, 0)
New coords: (13430000, -4000000)
Current pads in footprint module:
0:
Pad: "abracadabra9"
	Pad class: PAD
	Pad shape: 0 (PAD_SHAPE_CIRCLE)
	Anchor pad shape: 0 (PAD_SHAPE_CIRCLE)
	Position: (13.43mm, -4.0mm)
	Size: (1.524mm, 1.524mm)
1:
Pad: "abracadabra8"
	Pad class: PAD
	Pad shape: 0 (PAD_SHAPE_CIRCLE)
	Anchor pad shape: 0 (PAD_SHAPE_CIRCLE)
	Position: (12.16mm, -4.0mm)
	Size: (1.524mm, 1.524mm)
2:
Pad: "abracadabra7"
	Pad class: PAD
	Pad shape: 0 (PAD_SHAPE_CIRCLE)
	Anchor pad shape: 0 (PAD_SHAPE_CIRCLE)
	Position: (10.89mm, -4.0mm)
	Size: (1.524mm, 1.524mm)
3:
Pad: "abracadabra6"
	Pad class: PAD
	Pad shape: 0 (PAD_SHAPE_CIRCLE)
	Anchor pad shape: 0 (PAD_SHAPE_CIRCLE)
	Position: (9.62mm, -4.0mm)
	Size: (1.524mm, 1.524mm)
4:
Pad: "abracadabra5"
	Pad class: PAD
	Pad shape: 0 (PAD_SHAPE_CIRCLE)
	Anchor pad shape: 0 (PAD_SHAPE_CIRCLE)
	Position: (8.35mm, -4.0mm)
	Size: (1.524mm, 1.524mm)
5:
Pad: "abracadabra4"
	Pad class: PAD
	Pad shape: 0 (PAD_SHAPE_CIRCLE)
	Anchor pad shape: 0 (PAD_SHAPE_CIRCLE)
	Position: (7.08mm, -4.0mm)
	Size: (1.524mm, 1.524mm)
6:
Pad: "abracadabra3"
	Pad class: PAD
	Pad shape: 0 (PAD_SHAPE_CIRCLE)
	Anchor pad shape: 0 (PAD_SHAPE_CIRCLE)
	Position: (5.81mm, -4.0mm)
	Size: (1.524mm, 1.524mm)
7:
Pad: "abracadabra2"
	Pad class: PAD
	Pad shape: 0 (PAD_SHAPE_CIRCLE)
	Anchor pad shape: 0 (PAD_SHAPE_CIRCLE)
	Position: (4.54mm, -4.0mm)
	Size: (1.524mm, 1.524mm)
8:
Pad: "abracadabra1"
	Pad class: PAD
	Pad shape: 0 (PAD_SHAPE_CIRCLE)
	Anchor pad shape: 0 (PAD_SHAPE_CIRCLE)
	Position: (3.27mm, -4.0mm)
	Size: (1.524mm, 1.524mm)
9:
Pad: "abracadabra0"
	Pad class: PAD
	Pad shape: 0 (PAD_SHAPE_CIRCLE)
	Anchor pad shape: 0 (PAD_SHAPE_CIRCLE)
	Position: (2.0mm, -4.0mm)
	Size: (1.524mm, 1.524mm)
10:
Pad: "1"
	Pad class: PAD
	Pad shape: 4 (PAD_SHAPE_ROUNDRECT)
	Anchor pad shape: 0 (PAD_SHAPE_CIRCLE)
	Position: (0.0mm, -6.35mm)
	Size: (1.106mm, 3.0mm)
Ok, saving...

I don’t know anything about the plugin interface but that you are reading from and writing back into the same file gives me pause. Just as an easy experiment, can you read from a copy of beans.pretty but write into beans.pretty and see what you get?

Ok, if I’d actually paid any attention to the documentation I would have realised that PLUGIN is infact an abstract base class that is an interface through which one can customize how the FootprintLoad, FootprintSave, and etc. methods are implemented. I (only) just realised this after perusing through here.

One needs to use an actual implementation of the PLUGIN class, and in this case I suppose PCB_IO is the one to use from the Python interface.

So, I now have in the code

pcb = pcbnew.PCB_IO();
fp = pcb.FootprintLoad("beans.pretty", "My_PCI_footprint");

And now I actually get sane output in My_PCI_footprint.kicad_mod:

(module My_PCI_footprint (layer F.Cu) (tedit 5DFE82D2)
  (fp_text reference REF** (at 0 -2.54) (layer F.SilkS)
    (effects (font (size 1 1) (thickness 0.15)))
  )
  (fp_text value Val** (at 0 0) (layer F.SilkS)
    (effects (font (size 1.27 1.27) (thickness 0.15)))
  )
  (pad 1 smd roundrect (at 0 -6.35) (size 1.106 3) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.05))
  (pad abracadabra0 thru_hole circle (at 0 0) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask))
  (pad abracadabra1 thru_hole circle (at 0 0) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask))
  (pad abracadabra2 thru_hole circle (at 0 0) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask))
  (pad abracadabra3 thru_hole circle (at 0 0) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask))
  (pad abracadabra4 thru_hole circle (at 0 0) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask))
  (pad abracadabra5 thru_hole circle (at 0 0) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask))
  (pad abracadabra6 thru_hole circle (at 0 0) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask))
  (pad abracadabra7 thru_hole circle (at 0 0) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask))
  (pad abracadabra8 thru_hole circle (at 0 0) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask))
  (pad abracadabra9 thru_hole circle (at 0 0) (size 1.524 1.524) (drill 0.762) (layers *.Cu *.Mask))
)

Still need to figure out why the positions aren’t set though…

1 Like

Ok, I found out why the position wasn’t being set - there are two member functions of the D_PAD class:

SetPos0 is the one that actually works. Here is final result in My_PCI_footprint:

(module My_PCI_footprint (layer F.Cu) (tedit 5DFE82D2)
  (fp_text reference REF** (at 0 -2.54) (layer F.SilkS)
    (effects (font (size 1 1) (thickness 0.15)))
  )
  (fp_text value Val** (at 0 0) (layer F.SilkS)
    (effects (font (size 1.27 1.27) (thickness 0.15)))
  )
  (pad 1 smd roundrect (at 0 -6.35) (size 1.106 3) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.05))
  (pad abracadabra0 smd roundrect (at 2 -4) (size 1.106 3) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.05))
  (pad abracadabra1 smd roundrect (at 3.27 -4) (size 1.106 3) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.05))
  (pad abracadabra2 smd roundrect (at 4.54 -4) (size 1.106 3) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.05))
  (pad abracadabra3 smd roundrect (at 5.81 -4) (size 1.106 3) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.05))
  (pad abracadabra4 smd roundrect (at 7.08 -4) (size 1.106 3) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.05))
  (pad abracadabra5 smd roundrect (at 8.35 -4) (size 1.106 3) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.05))
  (pad abracadabra6 smd roundrect (at 9.62 -4) (size 1.106 3) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.05))
  (pad abracadabra7 smd roundrect (at 10.89 -4) (size 1.106 3) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.05))
  (pad abracadabra8 smd roundrect (at 12.16 -4) (size 1.106 3) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.05))
  (pad abracadabra9 smd roundrect (at 13.43 -4) (size 1.106 3) (layers F.Cu F.Paste F.Mask) (roundrect_rratio 0.05))
)

I only wonder now, what is SetPosition/GetPosition used for.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.