Stretchable components

Thanks.
Next is the ExchangeFootprint() method.
I didn’t find an equivelent in the Python bindings, so I re-implemented it in Python. The code is on Github: https://github.com/kamocat/kicad_plugins/blob/main/nextfp.py
Based on the discussion here I used pcbnew.Refresh() after positioning the footprint, but it still doesn’t appear on the board. I must be missing something.
The PlaceFootprint() method only seems to update the position and set some undo actions.

But I still don’t see the footprint on my PCB. What is missing?

It works now, and the memory leak is fixed.
Now I wonder if I can add actions to the Undo queue.
Here’s the whole code:

import pcbnew
import os
import wx

def get_sel():
    for x in pcbnew.GetCurrentSelection():
        if isinstance(x,pcbnew.FOOTPRINT):
            return x
        elif isinstance(x,pcbnew.PAD):
            return x.GetParent()

def get_lib(libname):
    lib = os.path.join(os.environ['KICAD7_FOOTPRINT_DIR']
                       ,libname+'.pretty')
    if os.path.isdir(lib):
        footprints = pcbnew.FootprintEnumerate(lib)
        return lib,footprints
    return None,None

# Reference https://github.com/KiCad/kicad-source-mirror/blob/master/pcbnew/pcb_edit_frame.cpp#L2104
def processTextItems(aSrc,aDest):
    aDest.SetText(aSrc.GetText())
    aDest.SetLayer(aSrc.GetLayer())
    aDest.SetVisible(aSrc.IsVisible())
    aDest.SetAttributes(aSrc)
    #This function doesn't exist in the Python bindings
    #aDest.SetFPRelativePosition(aSrc.GetFPRelativePosition())
    aDest.SetLocked( aSrc.IsLocked() )

# Reference https://github.com/KiCad/kicad-source-mirror/blob/master/pcbnew/pcb_edit_frame.cpp#L2194
def exchange_footprints(aExisting, aNew):
    board = aExisting.GetParent()
    aNew.SetParent(board)
    aNew.SetPosition(aExisting.GetPosition())
    if aNew.GetLayer() != aExisting.GetLayer():
        aNew.Flip(aNew.GetPosition(), True)
    if aNew.GetOrientation() != aExisting.GetOrientation():
        aNew.SetOrientation( aExisting.GetOrientation())
    aNew.SetLocked( aExisting.IsLocked())

    for pad in aNew.Pads():
        if pad.GetNumber() is None or not pad.IsOnCopperLayer():
            pad.SetNetCode(pcbnew.NETINFO_LIST.UNCONNECTED)
            continue
        last_pad = None
        while True:
            pad_model = aExisting.FindPadByNumber( pad.GetNumber(), last_pad )
            if pad_model is None:
                break
            if pad_model.IsOnCopperLayer():
                break
            last_pad = pad_model

        if pad_model is not None:
            pad.SetLocalRatsnestVisible( pad_model.GetLocalRatsnestVisible() )
            pad.SetPinFunction( pad_model.GetPinFunction())
            pad.SetPinType( pad_model.GetPinType())
            pad.SetNetCode( pad_model.GetNetCode() )
        else:
            pad.SetNetCode( pcbnew.NETINFO_LIST.UNCONNECTED )
    processTextItems(aExisting.Reference(),aNew.Reference())
    processTextItems(aExisting.Value(),aNew.Value())
    #TODO: Process all text items
    #TODO: Copy fields
    #TODO: Copy UUID
    aNew.SetPath(aExisting.GetPath())
    board.RemoveNative(aExisting)
    board.Add(aNew)
    aNew.ClearFlags()


def next_fp(direction):
    board = pcbnew.GetBoard()
    # TODO: Handle more than one item
    f = get_sel()
    if f is None:
        return
    f.ClearSelected()
    fid = f.GetFPIDAsString()
    print(f'Selected {f.GetReference()} {fid}')
    libname,_,fpname = fid.partition(':')
    # Get the list of footprints from the library
    lib,footprints = get_lib(libname)
    i = footprints.index(fpname)
    i += direction
    if i < 0:
        i = 0
    elif i >= len(footprints):
        i = len(footprints)-1
    # Set the footprint to the next
    newfid = f'{libname}:{footprints[i]}'
    print(f'Changing to {newfid}')
    newfp = pcbnew.FootprintLoad(lib,footprints[i])
    newfp.SetFPIDAsString(newfid)
    exchange_footprints(f, newfp)
    newfp.SetSelected()
    pcbnew.Refresh()

def next_fp_callback(context):
    next_fp(1)

def prev_fp_callback(context):
    next_fp(-1)

def findPcbnewWindow():
    """Find the window for the PCBNEW application."""
    windows = wx.GetTopLevelWindows()
    pcbnew = [w for w in windows if "PCB Editor" in w.GetTitle()]
    if len(pcbnew) != 1:
        raise Exception("Cannot find pcbnew window from title matching!")
    return pcbnew[0]

class NextFp(pcbnew.ActionPlugin):
    def defaults(self):
        self.name = "Replace Footprint Test"
        self.category = "placement"
        self.description = "Cycles through footprints. Intended for changing resistor length while prototyping."
        self.show_toolbar_button = False # Optional, defaults to False
        self.icon_file_name = os.path.join(os.path.dirname(__file__), 'simple_plugin.png') # Optional, defaults to ""

    def Run(self):
        next_fp(1)

mainFrame = findPcbnewWindow()
next_fp_button = wx.NewId()
prev_fp_button = wx.NewId()
accel_tbl = wx.AcceleratorTable([(wx.ACCEL_SHIFT,  ord('J'), next_fp_button )
                                 ,(wx.ACCEL_SHIFT,  ord('K'), prev_fp_button )
                                 ])
mainFrame.Bind(wx.EVT_TOOL, next_fp_callback, id=next_fp_button)
mainFrame.Bind(wx.EVT_TOOL, prev_fp_callback, id=prev_fp_button)
mainFrame.SetAcceleratorTable(accel_tbl)

NextFp().register() # Instantiate and register to Pcbnew
1 Like

I confine my retirement projects to one/two sided PCB’s so that I can CNC-Mill them. This usually necessitates using Jumper-Wires.

Leaving it to my imagination, I seldom bother to fool around with BreadBoards in Kicad but, I have done it in Kicad.

Regarding Resistors, one thing not mentioned (or, I missed it) is associating different 3D-Step files (or, WRL) with a single Footprint.
This way, you eliminate changing Footprints and simply turn on/off visibility.

It does carry the burden of not having Silk/Courtyard/etc that match but, I seldom care about that (and don’t have them turned on in Viewer’s Pref’s). And, I don’t worry about Pad/THT until I know how I want the layout…

Screenshots… just threw some items together

There are a couple things in here that I want to address.

  • The footprint exchange does change the 3D model along with the footprint. It loads the default 3D model for the library footprint, which should be fine unless you’re using an unusual part like a DIP pressure sensor.
  • I like how you made your breadboard. It hadn’t occurred to me that if you make no courtyard, you can place components directly on top of it.
  • I don’t like the idea of making footprints or 3D models for the jumpers. When breadboarding, jumpers replace the traces, so I use the traces to represent them. Occasionally I’ll need a third layer for jumpers that cross each other.

Yes, as it should. I was just pointing out a way to save a step or two and see result quickly.

Note the Protoboard it’s not the correct one (sorry, should have caught that). Below shows the correct Protoboard with connected Pads so the full line of connections is just like a BreadBoard

However you do it ia okay with me :wink:
But, sometimes laying a trace and crossing-over other traces causes grief… So, I just draw a Silk or User-Layer graphic instead of a Trace. The only reason for the 3D jumper model is for pretty 3dView.

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