Skocz do zawartości

KiCad - własne pluginy w Pythonie (PCB Python Bindings)


rziomber

Pomocna odpowiedź

W Internecie znajdziemy wiele skryptów Pythona ułatwiających rozmieszczenie komponentów na płytkach PCB w KiCad. Niestety w większości wypadków w ogóle nie działają pod wersją 8.

Bazowy szkic jest na stronie PCB Python Bindings - Simple Plugin Example

Znalezienie w Internecie aktualnych rozwiązań zajęło mi trochę czasu, postanowiłem podzielić się więc z Wami postem.

Plugin umieszczamy w stosownym folderze. Po każdej zmianie kodu trzeba pamiętać o Odświeżeniu Pluginów z poziomu menu, o ile w momencie edycji KiCad jest uruchomiony.

Clipboard01z.thumb.png.7a7d90ea64a0d4f6af41596bbadd11d7.png

Proste przemieszczenie komponentów:

import pcbnew
board = pcbnew.GetBoard()

def move_component(move_name, x, y):
    move_component = board.FindFootprintByReference(move_name)
    if move_component:
        vector = pcbnew.VECTOR2I_MM(x, y)
        move_component.SetPosition(vector)
        pcbnew.Refresh()
        print(f"Component {move_name} moved to {vector}")
    else:
        print(f"Component {move_name} not found")

def move_component_relative(reference_name, move_name, x, y):
    move_component = board.FindFootprintByReference(move_name)
    reference_component = board.FindFootprintByReference(reference_name)

    if reference_component and move_component:
        shift_vector = pcbnew.VECTOR2I_MM(x, y)
        reference_position = reference_component.GetPosition()
        new_position = reference_position+shift_vector
        move_component.SetPosition(new_position)
        pcbnew.Refresh()
        print(f"Component {move_name} moved to {new_position}")
    else:
        print(f"Component {move_name} not found")

class PlaceComponent(pcbnew.ActionPlugin):
    def defaults(self):
        self.name = "Place Component"
        self.category = "Modify PCB"
        self.description = "Place a component by name at a specified location"

    def Run(self):
        move_component_relative("D1", "D2", 26, 0)
        move_component("U1", 80, 88)

# Register the plugin
PlaceComponent().register()

NIEOPTYMALNE (niepotrzebnie przenosi ścieżki do kolejnej linijki po przekątnej, zamiast "wężykiem"), przykładowe rozmieszczenie komponentów:

import pcbnew
board = pcbnew.GetBoard()

def move_component(move_name, x, y):
    print('vector ' + str(x) + " " + str(y))
    move_component = board.FindFootprintByReference(move_name)
    if move_component:
        vector = pcbnew.VECTOR2I_MM(x, y)
        move_component.SetPosition(vector)
        pcbnew.Refresh()
        print(f"Component {move_name} moved to {vector}")
    else:
        print(f"Component {move_name} not found")

def move_component_relative(reference_name, move_name, x, y):
    print('vector ' + str(x) + " " + str(y))
    move_component = board.FindFootprintByReference(move_name)
    reference_component = board.FindFootprintByReference(reference_name)

    if reference_component and move_component:
        shift_vector = pcbnew.VECTOR2I_MM(x, y)
        reference_position = reference_component.GetPosition()
        new_position = reference_position+shift_vector
        move_component.SetPosition(new_position)
        pcbnew.Refresh()
        print(f"Component {move_name} moved to {new_position}")
    else:
        print(f"Component {move_name} not found")

def rearrangeComponents():
    width = 3.5
    height = 3.5
    x_margin = 7
    y_margin = 3.5

    line = 0
    number_led = 1

    for i in range(0, 9):
        if i == 0 or i == 8:
            count = 2
            for j in range(count):
                move_component_relative("D1", "D"+str(number_led), (width+x_margin)*j*5, (height+y_margin)*i)
                number_led += 1
                #pygame.draw.rect(screen, WHITE, (initial_x+(width+x_margin)*j*5, initial_y+(height+y_margin)*i, width, height))
                #all_leds += 1
        else:
            if i%2 == 1:
                count = 4
                initial_spacing = width+x_margin
            else:
                count = 3
                initial_spacing = 2*width+x_margin+(x_margin-width)/2
            for j in range(count):
                move_component_relative("D1", "D"+str(number_led), (width+x_margin)*j+initial_spacing, (height+y_margin)*i)
                number_led += 1
                #pygame.draw.rect(screen, WHITE, (initial_x+(width+x_margin)*j+initial_spacing, initial_y+(height+y_margin)*i, width, height))

class PlaceComponent(pcbnew.ActionPlugin):
    def defaults(self):
        self.name = "Place Component"
        self.category = "Modify PCB"
        self.description = "Place a component by name at a specified location"

    def Run(self):
        rearrangeComponents()
        #move_component_relative("D1", "D2", 26, 0)
        #move_component("U1", 80, 88)

# Register the plugin
PlaceComponent().register()

Dzięki modułowi pygame przyspieszymy testowanie kolejnych iteracji algorytmu.

image.thumb.png.a30f93b4613912d11b9c114908daf439.png

import pygame
import sys

pygame.init()

screen = pygame.display.set_mode((580, 620))
pygame.display.set_caption("Shooting lights")

BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
WHITE = (255, 255, 255)

initial_x = 10
initial_y = 10

width = 35
height = 35

x_margin = 70
y_margin = 35

line = 0
running = True
    
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # Fill the screen with black
    screen.fill(BLACK)
    #all_leds = 0

    for i in range(0, 9):
        if i == 0 or i == 8:
            count = 2
            for j in range(count):
                pygame.draw.rect(screen, WHITE, (initial_x+(width+x_margin)*j*5, initial_y+(height+y_margin)*i, width, height))
                #all_leds += 1
        else:
            if i%2 == 1:
                count = 4
                initial_spacing = width+x_margin
            else:
                count = 3
                initial_spacing = 2*width+x_margin+(x_margin-width)/2
            for j in range(count):
                pygame.draw.rect(screen, WHITE, (initial_x+(width+x_margin)*j+initial_spacing, initial_y+(height+y_margin)*i, width, height))
                #all_leds += 1
        #print(all_leds)

    #pygame.draw.rect(screen, GREEN, (initial_x, initial_y, width, height))

    # Update the display
    pygame.display.flip()

# Quit pygame
pygame.quit()
sys.exit()

Możemy skorzystać z wxPython (Tkinter nie chciał się uruchomić z poziomu KiCada), by za pomocą GUI, na żywo edytować parametry pracy.

image.thumb.png.7f46d637370c909eeb5d5d654d49e90e.png

import pcbnew
import wx
#https://techoverflow.net/2023/05/13/how-to-show-wx-dialog-message-in-kicad-pcbnew-plugin/
#sudo dnf install python3-wxpython4*
board = pcbnew.GetBoard()

width = 3.5
height = 3.5
x_margin = 7
y_margin = 3.5

def move_component(move_name, x, y):
    print('vector ' + str(x) + " " + str(y))
    move_component = board.FindFootprintByReference(move_name)
    if move_component:
        vector = pcbnew.VECTOR2I_MM(x, y)
        move_component.SetPosition(vector)
        pcbnew.Refresh()
        print(f"Component {move_name} moved to {vector}")
    else:
        print(f"Component {move_name} not found")

def move_component_relative(reference_name, move_name, x, y):
    print('vector ' + str(x) + " " + str(y))
    move_component = board.FindFootprintByReference(move_name)
    reference_component = board.FindFootprintByReference(reference_name)

    if reference_component and move_component:
        shift_vector = pcbnew.VECTOR2I_MM(x, y)
        reference_position = reference_component.GetPosition()
        new_position = reference_position+shift_vector
        move_component.SetPosition(new_position)
        pcbnew.Refresh()
        print(f"Component {move_name} moved to {new_position}")
    else:
        print(f"Component {move_name} not found")

def rearrangeComponents():

    line = 0
    number_led = 1

    for i in range(0, 9):
        if i == 0 or i == 8:
            count = 2
            for j in range(count):
                move_component_relative("D1", "D"+str(number_led), (width+x_margin)*j*5, (height+y_margin)*i)
                number_led += 1
                #pygame.draw.rect(screen, WHITE, (initial_x+(width+x_margin)*j*5, initial_y+(height+y_margin)*i, width, height))
                #all_leds += 1
        else:
            if i%2 == 1:
                count = 4
                initial_spacing = width+x_margin
            else:
                count = 3
                initial_spacing = 2*width+x_margin+(x_margin-width)/2
            for j in range(count):
                move_component_relative("D1", "D"+str(number_led), (width+x_margin)*j+initial_spacing, (height+y_margin)*i)
                number_led += 1
                #pygame.draw.rect(screen, WHITE, (initial_x+(width+x_margin)*j+initial_spacing, initial_y+(height+y_margin)*i, width, height))



class PlaceComponentGUI(pcbnew.ActionPlugin):
    def defaults(self):
        self.name = "Place Component GUI"
        self.category = "Modify PCB"
        self.description = "Place a component by name at a specified location"

    def Run(self):
        app = wx.App(False)
        frame = MyForm()
        frame.Show()
        app.MainLoop()

# Register the plugin
PlaceComponentGUI().register()


class MyForm(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Move components", size=(400, 400))

        panel = wx.Panel(self, wx.ID_ANY)
        sizer = wx.BoxSizer(wx.VERTICAL)

        # Reference component
        x_margin_label = wx.StaticText(panel, label="x_margin:")
        self.x_margin_input = wx.TextCtrl(panel)
        global x_margin
        self.x_margin_input.SetValue(str(x_margin))
        sizer.Add(x_margin_label, 0, wx.ALL, 5)
        sizer.Add(self.x_margin_input, 0, wx.ALL | wx.EXPAND, 5)

        y_margin_label = wx.StaticText(panel, label="y_margin:")
        self.y_margin_input = wx.TextCtrl(panel)
        global y_margin
        self.y_margin_input.SetValue(str(y_margin))
        sizer.Add(y_margin_label, 0, wx.ALL, 5)
        sizer.Add(self.y_margin_input, 0, wx.ALL | wx.EXPAND, 5)

        move_button = wx.Button(panel, label="Move")
        move_button.Bind(wx.EVT_BUTTON, self.on_move)
        sizer.Add(move_button, 0, wx.ALL | wx.CENTER, 5)

        panel.SetSizer(sizer)

    def on_move(self, event):
        global x_margin
        global y_margin
        x_margin = float(self.x_margin_input.GetValue())
        y_margin = float(self.y_margin_input.GetValue())
        rearrangeComponents()

Reimplementacja funkcji, która już istnieje w menu kontekstowym komponentu:

image.thumb.png.7dfa03f44d8f8a3c90b89ff3cc8c4824.png

import pcbnew
import wx
#https://techoverflow.net/2023/05/13/how-to-show-wx-dialog-message-in-kicad-pcbnew-plugin/
#sudo dnf install python3-wxpython4*
board = pcbnew.GetBoard()

def move_component(move_name, x, y):
    print('vector ' + str(x) + " " + str(y))
    move_component = board.FindFootprintByReference(move_name)
    if move_component:
        vector = pcbnew.VECTOR2I_MM(x, y)
        move_component.SetPosition(vector)
        pcbnew.Refresh()
        print(f"Component {move_name} moved to {vector}")
    else:
        print(f"Component {move_name} not found")

def move_component_relative(reference_name, move_name, x, y):
    print('vector ' + str(x) + " " + str(y))
    move_component = board.FindFootprintByReference(move_name)
    reference_component = board.FindFootprintByReference(reference_name)

    if reference_component and move_component:
        shift_vector = pcbnew.VECTOR2I_MM(x, y)
        reference_position = reference_component.GetPosition()
        new_position = reference_position+shift_vector
        move_component.SetPosition(new_position)
        pcbnew.Refresh()
        print(f"Component {move_name} moved to {new_position}")
    else:
        print(f"Component {move_name} not found")

class MyForm(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Move components", size=(400, 400))

        panel = wx.Panel(self, wx.ID_ANY)
        sizer = wx.BoxSizer(wx.VERTICAL)

        # Reference component
        ref_label = wx.StaticText(panel, label="Reference component:")
        self.ref_input = wx.TextCtrl(panel)
        sizer.Add(ref_label, 0, wx.ALL, 5)
        sizer.Add(self.ref_input, 0, wx.ALL | wx.EXPAND, 5)

        move_label = wx.StaticText(panel, label="Move component:")
        self.move_input = wx.TextCtrl(panel)
        sizer.Add(move_label, 0, wx.ALL, 5)
        sizer.Add(self.move_input, 0, wx.ALL | wx.EXPAND, 5)

        shift_label = wx.StaticText(panel, label="Shift vector:")
        sizer.Add(shift_label, 0, wx.ALL, 5)

        x_label = wx.StaticText(panel, label="X:")
        self.x_input = wx.TextCtrl(panel)
        sizer.Add(x_label, 0, wx.ALL, 5)
        sizer.Add(self.x_input, 0, wx.ALL | wx.EXPAND, 5)

        y_label = wx.StaticText(panel, label="Y:")
        self.y_input = wx.TextCtrl(panel)
        sizer.Add(y_label, 0, wx.ALL, 5)
        sizer.Add(self.y_input, 0, wx.ALL | wx.EXPAND, 5)

        move_button = wx.Button(panel, label="Move")
        move_button.Bind(wx.EVT_BUTTON, self.on_move)
        sizer.Add(move_button, 0, wx.ALL | wx.CENTER, 5)

        panel.SetSizer(sizer)

    def on_move(self, event):
        ref = self.ref_input.GetValue()
        move = self.move_input.GetValue()
        x_shift = float(self.x_input.GetValue())
        y_shift = float(self.y_input.GetValue())

        print(f"Reference: {ref}, Move: {move}, X shift: {x_shift}, Y shift: {y_shift}")
        move_component_relative(ref, move, x_shift, y_shift)

class DialogExamplePlugin(pcbnew.ActionPlugin):
    def defaults(self):
        self.name = "Move components GUI"
        #self.category = "A descriptive category name"
        self.description = "GUI interface to arrangement components"
        self.show_toolbar_button = False

    def Run(self):
        app = wx.App(False)
        frame = MyForm()
        frame.Show()
        app.MainLoop()

DialogExamplePlugin().register()

Warto uruchomić KiCada z poziomu linii poleceń. Dzięki temu będziemy mieli podgląd w terminalu do tego, co wypisywane jest funkcją print().

Jeśli zainstalowaliśmy aplikację pod Linuksem z poziomu Flatpaka:

flatpak run org.kicad.KiCad

Przykładowy efekt pracy:

image.thumb.png.41a4ae1907ef7035aa96a4373f61fa5c.png

Edytowano przez rziomber
Link do komentarza
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.