adding GUI elements in pcbnew

In my last post, I talked about how you can run pcbnew headless. In many cases you actually want more GUI. For example, in my code sample for replicating module placement across multiple sheet instances, I have the pivot instances hard coded in the script. Why not do it in a GUI?

Adding GUI elements to pcbnew is what this post is about.1. The code I’m talking about can be found here.

The pcbnew GUI is written using wxWidgets/wxPython. If you’re not familiar with wxPython, here is my favorite tutorial so far. All of its python APIs work fine for me in pcbnew so far. I’ve found there are two important things to keep in mind:

  • You don’t need to do the normal app = MyApp(0); app.MainLoop() 2
  • If you do print from callback (for debugging) the text will not be in the python window. Instead, you can find it in the terminal where you invoked kicad. Keep this in mind if you usually invoke from the kicad project manager or from you OS task launcher.

At the end of this post, we’ll have a new, not particularly attractive, window in pcbnew like this:

As usual, you’ll need some imports:

import wx 
import pcbnew

To create a new window, you’ll want to create a new class derived from wx.Frame3. The initializer/constructor will create the new GUI elements and you’ll want some methods to use as event callbacks. To determine the placement of the new widgets, I’m using BoxSizer, but there are a bunch of other options.

Here are some of the high level points for when your reading the code below:

  • All widgets automagically get a unique negative id. You can pick your own numbers if you’d like. You’ll need the id when binding callbacks, given a widget pointer, just call GetId().
  • wx.StaticText is a simple text label.
  • wx.Button is a button. duh
  • wx.ComboBox gives you a scrollable list selection. I use it to pick a net or module.
  • wx.BoxSizer is just for layout. Line things up vertically or horizontally
  • To tell wx about your callback functions, use the bind method

Given that, the code below should be easy to follow, even if you’ve never done anything with wxWidgets/wxPython

class SimpleGui(wx.Frame):
    def __init__(self, parent, board):
        wx.Frame.__init__(self, parent, title="this is the title")
        self.panel = wx.Panel(self) 
        label = wx.StaticText(self.panel, label = "Hello World")
        button = wx.Button(self.panel, label="Button label", id=1)
        
        nets = board.GetNetsByName()
        self.netnames = []
        for netname, net in nets.items():
            if (str(netname) == ""):
                continue
            self.netnames.append(str(netname))
        
        netcb = wx.ComboBox(self.panel, choices=self.netnames)
        netcb.SetSelection(0)

        netsbox = wx.BoxSizer(wx.HORIZONTAL)
        netsbox.Add(wx.StaticText(self.panel, label="Nets:"))
        netsbox.Add(netcb, proportion=1)
        
        modules = board.GetModules()
        self.modulenames = []
        for mod in modules:
            self.modulenames.append("{}({})".format(mod.GetReference(), mod.GetValue()))
        modcb = wx.ComboBox(self.panel, choices=self.modulenames)
        modcb.SetSelection(0)

        modsbox = wx.BoxSizer(wx.HORIZONTAL)
        modsbox.Add(wx.StaticText(self.panel, label="Modules:"))
        modsbox.Add(modcb, proportion=1)
        
        box = wx.BoxSizer(wx.VERTICAL)
        box.Add(label,   proportion=0)
        box.Add(button,  proportion=0)
        box.Add(netsbox, proportion=0)
        box.Add(modsbox, proportion=0)
        
        self.panel.SetSizer(box)
        self.Bind(wx.EVT_BUTTON, self.OnPress, id=1)
        self.Bind(wx.EVT_COMBOBOX, self.OnSelectNet, id=netcb.GetId())
        self.Bind(wx.EVT_COMBOBOX, self.OnSelectMod, id=modcb.GetId())
        
    def OnPress(self, event):
        print("in OnPress")

    def OnSelectNet(self, event):
        item = event.GetSelection()
        print("Net {} was selected".format(self.netnames[item]))

    def OnSelectMod(self, event):
        item = event.GetSelection()
        print("Module {} was selected".format(self.modulenames[item]))

Now that we have the derived GUI class, it’s a simple matter of instantiating it.

def InitSimpleGui(board):
  sg = SimpleGui(None, board)
  sg.Show(True)
  return sg


sg = InitSimpleGui(pcbnew.GetBoard())

And that’s it. If you find this useful and if you end up creating something for pcbnew, I’d love to hear about it.


  1. I haven’t updated the replicate script yet, but I plan on it.

  2. You actually can run them, but you’ll get some undesirable behavior. In particular, the mouse icon will be stuck in “busy” mode, and the normal interactive commands won’t work

  3. There are other options, like dialogs

Leave a Reply

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