Python plugin: non modal dialog and problems at exit

KiKit plugin v1.3.0 has a non-blocking dialog. Is nice because you can change parameters and apply it to see the results, no need to open the dialog again.

In order to achieve it the plugin creates the dialog at start-up and then uses Show/Hide when the user opens/closes the dialog. Isn’t destroyed (well to be exact is destroyed if the plugin object is deleted, but this doesn’t happen on exit).

KiCad v7 can handle it (don’t know how), but this is blocking v6 and you don’t see why. KiCad doesn’t finish because a window is still there, just that you don’t see it (unless it was visible at exit).

This is very bad, you have to kill KiCad process.

I tried to fix it but found that the dialog never gets notified of KiCad exiting. Any ideas of how to solve it?

I currently have a partial solution: the dialog is created when the user asks for it and destroyed on close. So it remains opened. But if it was opened when you exit you’ll need to manually close the dialog. This is much better than have to kill the process, but looks quite lame.

You can find the pcbnew window and add your own listener to close event. Don’t forget to call evt.Skip() to pass handling back to native code after you’ve done your cleanup.

import pcbnew
import wx

class DeletionTest(pcbnew.ActionPlugin):
    def defaults(self):
        super().defaults() = "DeletionTest"
        self.description = "DeletionTest"

    def find_pcbnew_window(self):
        windows = wx.GetTopLevelWindows()
        pcbneww = [w for w in windows if "pcb editor" in w.GetTitle().lower()]
        if len(pcbneww) != 1:
            return None
        return pcbneww[0]

    def Run(self):
        pcbnew_window = self.find_pcbnew_window()
        if not pcbnew_window:
        pcbnew_window.Bind(wx.EVT_CLOSE, self.OnParentClose)
        wx.MessageBox("Close event bound")

    def OnParentClose(self, evt):
        wx.MessageBox("Parent is closed")
        evt.Skip()  # this is important to pass handling to native code

    def __del__(self):
        # This doesn't work because python GC doesn't get called when pcbnew closes
        # it only gets called when plugins are reloaded
        wx.MessageBox("Plugin object is deleted")


Thanks! it worked as expected.

The only important thing I changed was the hard coded pcb editor text, I used: wx.GetTranslation("PCB Editor").lower()

It would be better to use the internal name of the window which is set in the KiCad source code. Unfortunately I don’t remember what it is and what is the method to get it, but it’s used in the python shell source inside KiCad. I once suggested it for the Replicate Layout plugin, but I don’t know if @MitjaN implemented it. There were problems with recognizing the window because the human readable UI text may change.

I can’t believe how stupid I was, while implementing it I realized that the real problem is that the code called the wx.Dialog constructor using None as parent. Using the detected pcbnew_window as parent propagates the main window close.

1 Like

This string is used in PCB_EDIT_FRAME::UpdateTitle() and as the default value for PCB_BASE_EDIT_FRAME initialization (both in pcbnew/pcb_edit_frame.cpp)

The string is also used in other parts of the code, not a good idea.

From what I see the classes doesn’t keep a copy and relies on GetTitle(), I tried searching for it and couldn’t find a call that looks exported.

This works for me:

self.frame = wx.FindWindowByName("PcbFrame")
1 Like

Excellent, thanks!

This returns the needed value

Now I found what you was talking about. Is in scripting/kicad_pyshell/ in the KiCadPyShell.init() code:

    def __init__(self, parent):
        # Search if a pcbnew frame is open, because import pcbnew can be made only if it exists.
        # frame names are "SchematicFrame" and "PcbFrame"
        # Note we must do this before the KiCadEditorNotebookFrame __init__ call because it ends up calling _setup back on us
        frame = wx.FindWindowByName( "PcbFrame" )

        self.isPcbframe = frame is not None

        KiCadEditorNotebookFrame.__init__(self, parent)