Pcbnew, Python - determine if a part (footprint) is on/within board, or outside?

Basically, I want to generate a list of all footprints in a .kicad_pcb file, first column - reference, second column: True if part is on/within the board, False if otherwise. So, as a test, I start with Pcbnew standalone, and:

  • draw Graphic Lines on Edge.Cuts layer, so I get a board with a cutout
  • then place footprint LEDs:LED_D5.0mm_Horizontal_O1.27mm_Z15.0mm outside the board (ref LED1) ; Housings_DIP:DIP-8_W7.62mm inside the board (ref U1); Potentiometers:Potentiometer_Trimmer_ACP_CA14v_Horizontal_Px15.0mm_Py10.0mm inside the cutout/hole (ref P1); and Resistors_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal on the board (ref R1)
  • then route a couple of lines between R1 and P1 on the board’s F.Cu layer

… so it ends up like this:

Here’s the 3D rendering as well:

So, in this example, I’d like the script to tell me that R1 and U1 are on board, and P1 and LED1 are off board; basically through a list like this:

LED1 False
U1   True
P1   False
R1   True

This is how far I got - a script I called kicad-pcbnew-checkonboard.py, that shows the iteration and printout (however, of the .IsSelected() method):

#!/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-checkonboard.py')
# (here CheckOnboard should end up in locals() namespace)
# >>> CheckOnboard()

import sys
import os

import pcbnew

board = pcbnew.GetBoard()

def CheckOnboard():
  print("CheckOnboard from kicad-pcbnew-checkonboard.py:")
  for mod in board.GetModules(): # Modules(): 'SwigPyObject' object is not iterable; mod is pcbnew.MODULE
    print("{0: <10} {1}".format(mod.GetReference(), mod.IsSelected())) 

Is there something in the API, to help me determine if a footprint/module is on the board or not?


EDIT: Here’s also the .kicad_pcb file I used above: onboardtest.kicad_pcb (18.9 KB)

Good start… I’m just thinking out loud here, but I wonder if there are any python libraries used by game makers that would provide methods to do this as collision detection (or for 3-D games floor detection). I don’t know if it exists, but this is where I would start looking to see if/how other people have solved similar problems.

1 Like

Thanks @SembazuruCDE :

Yeah, I had posted this question, because I hoped the answer to it woule let me avoid thinking about that kind of stuff (get bounds of board, check if position inside board bounds is hole or not, get bounds of footprint, etc etc) - I was hoping there is a .IsOnboard() method I had missed :slight_smile:

Ok, I got somewhere:

  • There is a board.GetBoardEdgesBoundingBox() which gives you the outer bounding size of the board’s Edge.Cuts (thus it cannot possibly handle the cutout/hole) - this is returned as EDA_RECT
  • You can iterate through all pads of all modules/footprints on board, and then there is pad.HitTest() which accepts EDA_RECT as argument

Seemingly, the pad.HitTest() tells you if the pad itself lays within the EDA_RECT or not - but cannot tell how that is determined (by position, or by area of solder mask, or … how?). So if that understanding is correct, then we can reduce the question “is footprint on board?” to “do all pads of the footprint pass hitTest with the outer bounds of board’s Edge.Cuts?”

So, I tried that with 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-checkonboard.py')
# (here CheckOnboard should end up in locals() namespace)
# >>> CheckOnboard()

import sys
import os

import pcbnew

board = pcbnew.GetBoard()

def CheckOnboard():
  print("CheckOnboard from kicad-pcbnew-checkonboard.py:")
  EdgeCutsId = board.GetLayerID("Edge.Cuts")
  print("{} {} {} {} {} {} {}".format(
    EdgeCutsId, board.GetLayerName(EdgeCutsId),
    board.GetLayerType(EdgeCutsId), board.GetStandardLayerName(EdgeCutsId),
    board.IsLayerEnabled(EdgeCutsId), board.IsLayerVisible(EdgeCutsId),
    board.IsModuleLayerVisible(EdgeCutsId)
  )) # 44 Edge.Cuts 0 Edge.Cuts True True True; PCB_LAYER_ID const/aLayer
  #for drawing in board.GetDrawings(): # pcbnew.DRAWSEGMENT
  #  print(drawing)
  print(board.DrawingsList()) # pcbnew.BOARD_ITEM_List
  #for ix in board.DrawingsList(): # is also pcbnew.DRAWSEGMENT
  #  print(ix)
  boardbbe = board.GetBoardEdgesBoundingBox() # pcbnew.EDA_RECT
  print("  boardbbe: {},{} {},{}".format(boardbbe.GetLeft(), boardbbe.GetTop(), boardbbe.GetRight(),boardbbe.GetBottom() ))
  for mod in board.GetModules(): # Modules(): 'SwigPyObject' object is not iterable # pcbnew.MODULE
    pstrarr  = []
    isModuleInBoardBBEdges = True # if any of the tests below is false, this becomes false too
    for pad in mod.Pads(): # pcbnew.D_PAD
      padbb = pad.GetBoundingBox() #.GetBoundingBox(): pcbnew.EDA_RECT;
      pstr = "  pad: {},{} {},{}".format(padbb.GetLeft(), padbb.GetTop(), padbb.GetRight(),padbb.GetBottom() )
      """
      [KiCAD pcbnew scripting: pcbnew.D_PAD Class Reference: pcbnew.D_PAD.HitTest](http://docs.kicad.org/doxygen-python/classpcbnew_1_1D__PAD.html#a1e511816b04dfe51160f308c78461aba)

      HitTest(D_PAD self, wxPoint aPosition) -> bool
      HitTest(D_PAD self, EDA_RECT aRect, bool aContained, int aAccuracy=0) -> bool
      HitTest(D_PAD self, EDA_RECT aRect, bool aContained) -> bool

      There is pcbnew.D_PAD.GetBoundingBox;
      also board.HitTestForAnyFilledArea (self, aRefPos, aStartLayer, aEndLayer, aNetCode) (returns ZONE_CONTAINER)
      """
      # these two seem to be always the same, regardless of how aContained is set
      padHTt = pad.HitTest(boardbbe, True);
      padHTf = pad.HitTest(boardbbe, False);
      # note: {0: <10} prints a bool val as 0 or 1!
      pstr += " ({: <6} , {})".format("{}".format(padHTt), "{}".format(padHTf))
      pstrarr.append(pstr)
      if not(padHTt):
        isModuleInBoardBBEdges = False
    print("{0: <10} {1}".format(mod.GetReference(), isModuleInBoardBBEdges))
    print(os.linesep.join(pstrarr))

… I get this printout:

CheckOnboard()
CheckOnboard from kicad-pcbnew-checkonboard.py:
44 Edge.Cuts 0 Edge.Cuts True True True
<pcbnew.BOARD_ITEM_List; proxy of <Swig Object of type 'DLIST< BOARD_ITEM > *' at 0xe6fc0c80> >
  boardbbe: 20244999,17704999 68655001,50875001
R1         True
  pad: 23330000,38570000 24930000,40170000 (True   , True)
  pad: 23330000,28410000 24930000,30010000 (True   , True)
P1         True
  pad: 29310000,28200000 31650000,30540000 (True   , True)
  pad: 44310000,33200000 46650000,35540000 (True   , True)
  pad: 29310000,38200000 31650000,40540000 (True   , True)
U1         True
  pad: 55080000,29680000 56680000,31280000 (True   , True)
  pad: 62700000,37300000 64300000,38900000 (True   , True)
  pad: 55080000,32220000 56680000,33820000 (True   , True)
  pad: 62700000,34760000 64300000,36360000 (True   , True)
  pad: 55080000,34760000 56680000,36360000 (True   , True)
  pad: 62700000,32220000 64300000,33820000 (True   , True)
  pad: 55080000,37300000 56680000,38900000 (True   , True)
  pad: 62700000,29680000 64300000,31280000 (True   , True)
LED1       False
  pad: 72760000,29580000 74560000,31380000 (False  , False)
  pad: 75300000,29580000 77100000,31380000 (False  , False)

So, it seems to detect generally inside or outside of the board fine - except it cannot handle the cutout/hole in the middle of the board (P1 should be false, but it’s still shown as True).

If anyone knows what criteria I could use to also detect that - that would be great to know!

you can get a module bounding box module.GetBoundingBox() which returns a EDA_RECT which contains whole footprint including text items. Or you could use module.GetFootprintRect() which returns tight EDA_RECT.

Then you can test wheather board contains or intersects with footprint .Intersects and Contains methods on board bounding box

As for the cutouts I don’t see a simple solution

1 Like

What about circle Board/cutouts ? Do you want this to work for those too ?

Possibly you could iterate on the cutlines, (either delete or change layers?), until no cutlines are left.
Each pass reports contained outlines, which means P1 remains true in last pass.
Such iteration would work on multiple cutouts, but it has changed the database, so some save.restore is needed ?

1 Like

Thanks for the responses, all!

Very good to know this!

Yeah, me neither… The only thing I could think of, is that the 3D viewer correctly interprets the cutout; and so then one could in principle sample: is the entirety of the rect (footprint, or pads only) over a “pcb”, or is it over “air”, so to speak - but cannot express this more precisely at the moment…

I don’t really need it now - but of course, it would be great if it worked for those, too!

As I mentioned above, if one could think in terms of the 3D viewer, and reduce the problem to “footprint over PCB”/“footprint over air”, then probably circle cutouts would not be a problem either… I guess :slight_smile:

Arent cutlines only allowed on Edge.Cuts layers? If so, why would I need to change layers? If you mean to keep track of which cutlines are processed, one can always create an array with their IDs, and pop from that one…

I think the problem here is that the cutlines/graphic lines in PCBnew are just lines - as such, I guess PCBnew has no idea those lines are supposed to be a closed rectangle, say; so I guess one would have to iterate through cutlines, see which cutlines overlap at end points and thus form a shape, then create some sort of a shape object to keep track of this (and once created, pop/untrack all the cutout lines it contains in batch); then iterate through all shape objects, order them from biggest to smallest, and then interpret the biggest as a PCB, next smaller as a hole, next smaller as a PCB etc - or whatever the 3D viewer uses to interpret this.

Yeah, not trivial, that’s for sure…

I was working on your earlier comments around this

ie that board.GetBoardEdgesBoundingBox() seems to use Edge.Cuts, so moving lines off that helps.

Of course, you need to decide which line segments to move, with a simple rectangle that’s not too hard.
The more average board polyline is not so simple…

1 Like

Thanks for the response, @PCB_Wiz :

Ah, indeed - thanks for clarifying; now I get what you meant; but as you also noted:

Yeah, if one looks for a general solution beyond rectangles, things start to get complicated… The more I think of this, the more I wonder how it is that the 3D viewer draws the PCB - but unfortunately, cannot afford the time to look into it right now…

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