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
[scroll-box]

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]))

[/scroll-box]
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.

adding GUI elements in pcbnew

  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

3 thoughts on “adding GUI elements in pcbnew

  • March 2, 2018 at 7:37 am
    Permalink

    Hi,
    is it also possible to add new entries into the menu or the bars via scripting?
    Thank you,
    Martin

    Reply
    • March 2, 2018 at 8:02 pm
      Permalink

      I did manage to to this one but I didn’t do a post on it. Good question though, I’ll have to revisit my notes and do a post
      In the meantime, the plugin’s mechanism is very nice. Long term, as the list of plugins grows, the ability to organize into categories would be nice.

      Reply
  • July 24, 2019 at 3:41 pm
    Permalink

    Hello!
    My plan is to create some wires and visualize the drawing of the track, so I call the pcbnew.Refresh() after every wire-part, but unfortunately, it doesn’t show up, just when the script is finished. Am I doing it wrong, or this is the default behavior? Is there a solution for that?
    Thank you,
    David

    Reply

Leave a Reply to madave91 Cancel reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.