Stretchable components

I created a THT footprint library for different length 1/4 W resistors and labeled them according to their length (10ths. of inches). eg. RT3.0, RT3.5, RT4.0 etc. so to change the footprint it is:

Hotkey E
Change Footprint
Click Library Box at the end of “New Footprint Library ID”
Select footprint from the library list.

All up 5 sec?

Indeed, but when you have to do that repeatedly for 20 resistors in your project to make them all fit on your breadboard it gets tedious quickly. And that is why he wrote:

On proto-boards it is also tedious to decide where to place jumpers and components then strip the ends of wires and cut copper tracks.
It is also tedious to fit a SMD or TO3 component to a proto-board.

Likewise doing the maths to set-up biasing in an original design, not to mention simulation.

There is a lot of repetitive tedious stuff. This comes with the occupation.
I wonder how a brick-layer feels.

  1. For stripping wires you can get a wire stripper to do it quickly, but I transitioned to using enameled wire some 30+ years ago. You do not need to strip it at all, but can directly solder it with a hot iron
  2. I find cutting copper tracks on those strip boards horrible. (I also never had the proper tool for it) But the main problem is with “reverse thinking” of having to remove connections that are already present. It is an area where KiCad could be of help with planning. If you plan to use that strip board and do an initial layout in KiCad you can make all the cuts (of have a CNC machine do it for you).
  3. SMT parts are often available on breakout boards, others, such as SOT-23 can easily be soldered to single row header pins (use a wire for the third pin).
  4. TO3 is obsolete. And if you still have a few, they are usually put on big heat sinks and connected with some wires to a breadboard (although breadboards are not fit for high currents either).
  5. maths is fun.
  6. Brick laying robots are already in use, but at the moment they are mostly limited to very big jobs, because they still lack flexibility and ease of programming to be cost effective for smaller and more complicated jobs. But i do expect that to change gradually.

Some people do “diamond painting” or are drawing those big round pictures with squiggly details for fun or relaxation, but you don’t hear them complaining about tedious. have a look at" “Manila folder 777-300ER”

But it is when things are perceived as tedious that people start looking for quicker or better methods.

But overall, I do not understand your comment at all. Marshal_Horn wrote he does not like the “change footprint” method, You even quoted him in on that, and in the same post you go on to recommend that exact same method. KiCad’s own libraries also have resistors sized by length and diameter. confused0024

My point was there is a lot of tedium in everything and four clicks, taking 5 seconds, to change a footprint is nothing much compared to other matters when using a proto board.
It is no worse than creating a PCB either.

THT footprints take time to manage and place.

And my point was that it is not just 5 seconds, because you can have a lot of resistors and multiple iterations during footprint placement. having to change the footprints each time is tedious annoying and distracting.

Thank you for the animated discussion.
I will see about making a script to cycle through footprints. After all, they’re already in order in the library.

1 Like

Here’s what I have so far:

import pcbnew
import os

class NextFootprint(pcbnew.ActionPlugin):
    def defaults(self):
        self.name = "next_footprint"
        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):
        # The entry function of the plugin that is executed on user action
        board = pcbnew.GetBoard()
        front = board.GetLayerID("F.Cu")
        back = board.GetLayerID("B.Cu")
        f = board.GetFootprint(board.GetFocusPosition(),front,True)
        fid = f.GetFPID()
        print(f'{str(id.GetLibItemName())} of {id.GetFullLibraryName()}')
        # How to get the list of footprints from the library??
        # Then set the new footprint
        # f.SetFPID(new footprint)

for the libraries sadly no python interface exists so you have to open them as text files and search through them for the footprint names, e.g. with regular expressions.

I’m almost there, but I need help with two things:

  • How to get the actual cursor position (or better, the selected part)
  • How to update footprints from the library
import pcbnew
import os
import wx

def next_fp(direction):
    board = pcbnew.GetBoard()
    # FIXME: Board focus position is always the same.
    print(board.GetFocusPosition())
    f = board.GetFootprint(board.GetFocusPosition(),pcbnew.F_Cu,True)
    fid = f.GetFPIDAsString()
    print(f'Selected {f.GetReference()} {fid}')
    libname,_,fpname = fid.partition(':')
    # Get the list of footprints from the library
    lib = os.path.join(os.environ['KICAD7_FOOTPRINT_DIR']
                       ,libname+'.pretty')
    footprints = os.listdir(lib)
    footprints = [x.partition('.kicad_mod')[0] for x in footprints]
    # TODO: Sort by numbers like the Footprint Library Browser does
    footprints.sort()
    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
    newfp = f'{libname}:{footprints[i]}'
    print(f'Changing to {newfp}')
    f.SetFPIDAsString(newfp) 
    # FIXME: Update footprint from library
    pass

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 = "next_footprint"
        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):
        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

I found a better way to get the selected footprints:

def get_sel(context = None):
    sel = pcbnew.GetCurrentSelection()
    for x in sel:
        print(x)
        if isinstance(x,pcbnew.FOOTPRINT):
            return x
        elif isinstance(x,pcbnew.PAD):
            return x.GetParent()
        else:
            print('not pad or footprint')

I’m still working on changing the footprint, however.
The c++ footprint dialog uses a “LoadFootprint()” function which doesn’t seem to be available in the Python bindings.

Use FootprintLoad()

Why does it return None?
Isn’t this the correct usage?
pcbnew.FootprintLoad('Diode_SMD','D_SMA')

Pass the path to .pretty folder as first argument. If you google the method or even search this forum you will find examples.

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.