Invert Selection in Pcbnew using Python script?

When I’m in OpenGL Canvas, I can just make a normal mouse left-click - drag - release to make a rectangular selection, and all elements underneath (pads, zones, modules, tracks, drawings) are selected, and this is indicated with a highlight of the elements.

Now what I want is to “invert selection” - that is, starting from the previously made rectangular selection, deselect all selected elements, and select those elements on the board that are not selected.

I have noticed that KiCAD pcbnew scripting: pcbnew.EDA_ITEM Class Reference - has SetSelected/ClearSelected/IsSelected, so I tried this script:

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

# this script intended to be called from within KiCad's Pcbnew: Tools / Scripting Console (Python Shell)
# NB: that python shell might start from pwd: ~/kicad/kicommand
# >>> execfile('/path/to/kicad-pcbnew-invertselection.py')
# (here InvertSelection should end up in locals() namespace)
# >>> InvertSelection()


import sys
import os

import pcbnew

board = pcbnew.GetBoard()

def InvertSelection():
  print("InvertSelection from kicad-pcbnew-invertselection.py:")
  for drw in board.GetDrawings():
    print("{} {}".format(drw, drw.IsSelected())) # pcbnew.DIMENSION False; pcbnew.DRAWSEGMENT True;
    if drw.IsSelected():
      drw.ClearSelected()
    else:
      drw.SetSelected()
  for trk in board.GetTracks():
    print("{} {}".format(trk, trk.IsSelected())) # pcbnew.TRACK
    if trk.IsSelected():
      trk.ClearSelected()
    else:
      trk.SetSelected()
  for mod in board.GetModules():
    print("{} {}".format(mod, mod.IsSelected())) # pcbnew.MODULE
    if mod.IsSelected():
      mod.ClearSelected()
    else:
      mod.SetSelected()
  for pad in board.GetPads():
    print("{} {}".format(pad, pad.IsSelected())) # pcbnew.MODULE
    if pad.IsSelected():
      pad.ClearSelected()
    else:
      pad.SetSelected()
  for i in range(0, board.GetAreaCount()):
    area = board.GetArea(i); # pcbnew.ZONE_CONTAINER
    print("{} {} {}".format(i, area, area.IsSelected())) # pcbnew.MODULE
    if area.IsSelected():
      area.ClearSelected()
    else:
      area.SetSelected()

I notice that pretty much everything selected does get deselected (you have to scroll once with the mouse to force a re-rendering) except silkscreen texts; but no modules that were previously unselected are selected.

Furthermore, board.GetAreaCount() for the zones gives me 8, but I can iterate 9 times through it in my board - and yet there are only two pointer addresses mentioned?! This is what I get as printout:

0 <pcbnew.ZONE_CONTAINER; proxy of <Swig Object of type 'ZONE_CONTAINER *' at 0xef60e9b0> > True
1 <pcbnew.ZONE_CONTAINER; proxy of <Swig Object of type 'ZONE_CONTAINER *' at 0xef60e848> > True
2 <pcbnew.ZONE_CONTAINER; proxy of <Swig Object of type 'ZONE_CONTAINER *' at 0xef60e9b0> > True
3 <pcbnew.ZONE_CONTAINER; proxy of <Swig Object of type 'ZONE_CONTAINER *' at 0xef60e848> > True
4 <pcbnew.ZONE_CONTAINER; proxy of <Swig Object of type 'ZONE_CONTAINER *' at 0xef60e9b0> > True
5 <pcbnew.ZONE_CONTAINER; proxy of <Swig Object of type 'ZONE_CONTAINER *' at 0xef60e848> > True
6 <pcbnew.ZONE_CONTAINER; proxy of <Swig Object of type 'ZONE_CONTAINER *' at 0xef60e9b0> > True
7 <pcbnew.ZONE_CONTAINER; proxy of <Swig Object of type 'ZONE_CONTAINER *' at 0xef60e848> > True
8 <pcbnew.ZONE_CONTAINER; proxy of <Swig Object of type 'ZONE_CONTAINER *' at 0xef60e9b0> > True

Notice only 0xef60e848 and 0xef60e9b0 appear ?!

And finally - even if I do get a visual indication (de-highlight) of the previously selected elements after running this script (and refreshing the screen with mouse scroll/zoom in), when I hit Del key afterwards, the entire original selection is gone?!

So, how can I properly perform a “selection” and “deselection” of a Pcbnew footprint/module, or rather, inverting selection, from a Pcbnew python script?

Furthermore, how can I iterate through all (visible) elements on board (so I can check their selection status), without having to specify their type? There is board.GetVisibleElements(), but it returns an int, not an array…

EDIT: this is the version I’m using:

Application: kicad
Version: (2017-11-13 revision d98fc85)-master, release build
Libraries:
    wxWidgets 3.0.2
    libcurl/7.35.0 OpenSSL/1.0.1f zlib/1.2.8 libidn/1.28 librtmp/2.3
Platform: Linux 4.4.0-112-generic x86_64, 64 bit, Little endian, wxGTK
Build Info:
    wxWidgets: 3.0.2 (wchar_t,wx containers,compatible with 2.8) GTK+ 2.24
    Boost: 1.54.0
    Curl: 7.35.0
    Compiler: GCC 4.8.4 with C++ ABI 1002

Build settings:
    USE_WX_GRAPHICS_CONTEXT=OFF
    USE_WX_OVERLAY=OFF
    KICAD_SCRIPTING=ON
    KICAD_SCRIPTING_MODULES=ON
    KICAD_SCRIPTING_WXPYTHON=ON
    KICAD_SCRIPTING_ACTION_MENU=ON
    BUILD_GITHUB_PLUGIN=ON
    KICAD_USE_OCE=OFF
    KICAD_SPICE=OFF

I am not really a C/C++ od swig expert and I am only superficially proficient in Python, but I would rather store the item to iterate over in a list and then iterate through this list. Modifying an iterable while iterating over it can result in strange results.

e.g.

modules = pcbnew.GetModules()
for mod in modules:
    # do what you need to do

Thanks @MitjaN :

I kind of doubt that is the problem - I’m aware that e.g. deleting items from an array/list while iterating it is a problem in any language, because the number of elements change; but here I’m not doing that (I think), here I have the equivalent of changing the object property that the array element points to (if we assume an array of objects is actually an array of pointers to objects, in C/C++ sense); and that means, I shouldn’t be modifying the array/list itself, even if I don’t store in a separate variable before iteration.

Furthermore, if you only print out the values, then the inversion works (i.e. just print the states of the corresponding object properties) - the problem is that, these values no longer correspond to what is shown on screen. Maybe there is a “screen refresh” command I need to call after Invert Selection is done (since I still need to zoom in/out to trigger a redraw anyway, to be able to see any change at all) - but I have no idea what that command would be …

You are probably right on the iteration thing. My knowledge of python is superficial and I don’t have any clue how swig is working.

As for the refresh, you might want to look at layerviewset plugin from @HiGreg. He had also issues with the screen refresh.

1 Like

Many thanks, @MitjaN :

Great link - indeed, .Layout(), .Refresh() and pcbnew.UpdateUserInterface() methods sound like they might help; I’ll try this when I get some time; thanks again!

OK, got a bit further on this one, but not fully - here, I just tried making a function that will toggle the selection (state) of a single footprint. I think, the most important thing I’ve realized is:

  • In the current state, a footprint/pcbnew.MODULE is actually sort of a container that contains graphic items, pads and texts; but it’s not a sort of a parent-child relationship - because if you change the selection state of pcbnew.MODULE (via mod.ClearSelected() / mod.SetSelected()), the selection state of the “children”/enclosed items is not automatically updated. In other words, you must iterate through all these “children” “manually”, and set their selection state as well.

So, now I have a function, TogSel(mod), which I think does work, except caveat emptor again:

  • In Legacy Canvas, there is no “static” visual indication of selection, so obviously this function will not function there
  • In OpenGL Canvas, it should function, but it’s buggy (still using the same version listed in OP); see below
  • Only in Cairo canvas it works as expected

You can test the function below, say by having a setup with a single module like in Is it possible to use pcbnew.GetBoard().GetFootprint() from python? - then retrieve the footprint object in a variable in the Python Shell, say with:

>>> mod = pcbnew.GetBoard().GetFootprint(pcbnew.wxPointMM(19,19), -1, False)

… then you can paste in the function in the same .py file you may have used in the OP, and use the same commands listed there to load the .py file (and the functions therein) in the Python Shell - and then you can type:

>>> TogSel(mod)

… in the Python Shell, followed by Enter, to run it.

In Cairo Canvas, whenever you call the function, the footprint selection will be toggled (first it will appear selected; then at next run deselected, etc)

In OpenGL Canvas:

  • if you first click the footprint, so it is visually indicated as selected, - and then run TogSel(mod) - then the script will work fine, that is, the footprint will then appear deselected; then if you run the function again, the footprint will again appear selected, etc…
  • But, if you start from a deselected footprint (or you manually deselect the selected footprint), and then run TogSel(mod) - regardless of how many times you run TogSel(mod), the footprint will appear deselected; if from a state like this, where the footprint should be indicated but isn’t, you switch to Cairo Canvas, then it should be properly shown as indicated; if you switch back from this to OpenGL Canvas again, then the footprint will appear as selected - but running the function will not toggle it (until you manually click to deselect, then select the footprint)

So, I guess this is due to some buggy initialization of the OpenGL canvas…

Anyways, here is the TogSel function:

def TogSel(inmod): # expects input footprint module; toggles selection status on it
  # note: SHAPE_POLY_SET, LIB_ID has no IsSelected()
  # SetBrightened turns it sort of green
  # NOTE: for now only works properly in Cairo Canvas; absolutely not in Legacy Canvas;
  #   in OpenGL: if you first select, then run this function, it toggles visually;
  #   if starting from unselected module, it will not toggle visually at all...
  #   also, if switching between canvases when module/footprint is selected, then in OpenGL it will remain visually selected, regardless of how many times this function is ran
  if inmod.IsSelected():
    for ibi in mod.GraphicalItems():
      print(ibi.IsSelected(), ibi, ibi.IsBrightened())
      ibi.ClearSelected()
      #ibi.ClearBrightened()
    for ipd in mod.Pads():
      print(ipd.IsSelected(), ipd, ipd.IsBrightened())
      ipd.ClearSelected()
      #ipd.ClearBrightened()
    print(inmod.Reference().IsSelected(), inmod.Reference(), inmod.Reference().IsBrightened())
    inmod.Reference().ClearSelected()
    #inmod.Reference().ClearBrightened()
    print(inmod.Value().IsSelected(), inmod.Value(), inmod.Value().IsBrightened())
    inmod.Value().ClearSelected()
    #inmod.Value().ClearBrightened()
    print(inmod.IsSelected(), inmod, inmod.IsBrightened())
    inmod.ClearSelected()
    #inmod.ClearBrightened()
  else: # not selected
    #inmod.SetBrightened()
    for ibi in mod.GraphicalItems():
      print(ibi.IsSelected(), ibi, ibi.IsBrightened())
      ibi.SetSelected()
      #ibi.SetBrightened()
    for ipd in mod.Pads():
      print(ipd.IsSelected(), ipd, ipd.IsBrightened())
      ipd.SetSelected()
      #ipd.SetBrightened()
    print(inmod.Reference().IsSelected(), inmod.Reference(), inmod.Reference().IsBrightened())
    inmod.Reference().SetSelected()
    #inmod.Reference().SetBrightened()
    print(inmod.Value().IsSelected(), inmod.Value(), inmod.Value().IsBrightened())
    inmod.Value().SetSelected()
    #inmod.Value().SetBrightened()
    print(inmod.IsSelected(), inmod, inmod.IsBrightened())
    inmod.SetSelected()
  # apparently, BOTH of these must be running - either one of them individually, will not visually indicate the selection highlight/status!
  # however, which of these runs first, seems to have no effect on the indication?!
  # board.BuildConnectivity() # no effect really
  pcbnew.UpdateUserInterface()
  pcbnew.Refresh()

Did you try the V5 nightly builds ? - there are changes to the displays there…

1 Like

Thanks @PCB_Wiz :

Actually, I didn’t yet - unfortunately, I’m in a middle of a project, and cannot afford the time at the moment… though, I do use a Kicad built from git sources, so it shouldn’t be too big of a problem once I do get the time…

In any case, good to know that V5 has had changes to displays - I’ll be more motivated to give it a go, once my project clears up a bit

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