第15章-Python插件开发实战

第十五章:Python插件开发实战

15.1 动作插件(Action Plugin)

15.1.1 插件基础结构

import pcbnew

class MyPlugin(pcbnew.ActionPlugin):
    """KiCad动作插件模板"""
    
    def defaults(self):
        """设置插件默认属性"""
        self.name = "我的插件"
        self.category = "工具"
        self.description = "这是一个示例插件"
        self.show_toolbar_button = True
        self.icon_file_name = ""  # 可选:图标文件路径
    
    def Run(self):
        """插件执行入口"""
        board = pcbnew.GetBoard()
        # 在这里实现插件功能
        pass

# 注册插件
MyPlugin().register()

15.1.2 插件安装位置

Windows:
  %APPDATA%\kicad\8.0\scripting\plugins\

Linux:
  ~/.local/share/kicad/8.0/scripting/plugins/

macOS:
  ~/Library/Preferences/kicad/8.0/scripting/plugins/

或使用:
  Pcbnew → Tools → External Plugins → Open Plugin Directory

15.1.3 带GUI的插件

import pcbnew
import wx

class GuiPlugin(pcbnew.ActionPlugin):
    def defaults(self):
        self.name = "GUI插件示例"
        self.category = "工具"
        self.description = "带图形界面的插件"
        self.show_toolbar_button = True
    
    def Run(self):
        # 创建对话框
        dlg = MyDialog(None)
        if dlg.ShowModal() == wx.ID_OK:
            # 处理用户输入
            value = dlg.GetValue()
            self.process(value)
        dlg.Destroy()
    
    def process(self, value):
        board = pcbnew.GetBoard()
        # 根据用户输入执行操作
        pass

class MyDialog(wx.Dialog):
    def __init__(self, parent):
        super().__init__(parent, title="设置", size=(300, 200))
        
        panel = wx.Panel(self)
        vbox = wx.BoxSizer(wx.VERTICAL)
        
        # 添加控件
        self.text_ctrl = wx.TextCtrl(panel)
        vbox.Add(self.text_ctrl, 0, wx.EXPAND | wx.ALL, 10)
        
        # 按钮
        btn_sizer = wx.StdDialogButtonSizer()
        ok_btn = wx.Button(panel, wx.ID_OK, "确定")
        cancel_btn = wx.Button(panel, wx.ID_CANCEL, "取消")
        btn_sizer.AddButton(ok_btn)
        btn_sizer.AddButton(cancel_btn)
        btn_sizer.Realize()
        vbox.Add(btn_sizer, 0, wx.ALIGN_CENTER | wx.ALL, 10)
        
        panel.SetSizer(vbox)
    
    def GetValue(self):
        return self.text_ctrl.GetValue()

GuiPlugin().register()

15.2 实用插件开发

15.2.1 封装统计插件

import pcbnew
import wx

class FootprintStatsPlugin(pcbnew.ActionPlugin):
    def defaults(self):
        self.name = "封装统计"
        self.category = "报告"
        self.description = "统计PCB上的封装信息"
        self.show_toolbar_button = True
    
    def Run(self):
        board = pcbnew.GetBoard()
        stats = self.collect_stats(board)
        self.show_report(stats)
    
    def collect_stats(self, board):
        """收集统计数据"""
        stats = {
            'total': 0,
            'smd': 0,
            'tht': 0,
            'top': 0,
            'bottom': 0,
            'by_package': {}
        }
        
        for fp in board.GetFootprints():
            stats['total'] += 1
            
            # SMD vs THT
            if fp.GetAttributes() & pcbnew.FP_SMD:
                stats['smd'] += 1
            else:
                stats['tht'] += 1
            
            # 顶层 vs 底层
            if fp.GetLayer() == pcbnew.F_Cu:
                stats['top'] += 1
            else:
                stats['bottom'] += 1
            
            # 按封装分类
            package = str(fp.GetFPID().GetLibItemName())
            stats['by_package'][package] = stats['by_package'].get(package, 0) + 1
        
        return stats
    
    def show_report(self, stats):
        """显示报告"""
        report = f"""封装统计报告
================
总数: {stats['total']}
SMD: {stats['smd']}
THT: {stats['tht']}
顶层: {stats['top']}
底层: {stats['bottom']}

按封装类型:
"""
        for pkg, count in sorted(stats['by_package'].items(), key=lambda x: -x[1]):
            report += f"  {pkg}: {count}\n"
        
        wx.MessageBox(report, "封装统计", wx.OK | wx.ICON_INFORMATION)

FootprintStatsPlugin().register()

15.2.2 元器件对齐插件

import pcbnew
import wx

class AlignPlugin(pcbnew.ActionPlugin):
    def defaults(self):
        self.name = "元器件对齐"
        self.category = "编辑"
        self.description = "对齐选中的元器件"
        self.show_toolbar_button = True
    
    def Run(self):
        board = pcbnew.GetBoard()
        
        # 获取选中的封装
        selected = [fp for fp in board.GetFootprints() if fp.IsSelected()]
        
        if len(selected) < 2:
            wx.MessageBox("请选择至少2个元器件", "提示", wx.OK | wx.ICON_WARNING)
            return
        
        # 显示对齐选项
        dlg = AlignDialog(None)
        if dlg.ShowModal() == wx.ID_OK:
            align_type = dlg.GetAlignType()
            self.align_footprints(selected, align_type)
        dlg.Destroy()
    
    def align_footprints(self, footprints, align_type):
        """对齐封装"""
        if align_type == "left":
            min_x = min(fp.GetPosition().x for fp in footprints)
            for fp in footprints:
                pos = fp.GetPosition()
                fp.SetPosition(pcbnew.wxPoint(min_x, pos.y))
        
        elif align_type == "right":
            max_x = max(fp.GetPosition().x for fp in footprints)
            for fp in footprints:
                pos = fp.GetPosition()
                fp.SetPosition(pcbnew.wxPoint(max_x, pos.y))
        
        elif align_type == "top":
            min_y = min(fp.GetPosition().y for fp in footprints)
            for fp in footprints:
                pos = fp.GetPosition()
                fp.SetPosition(pcbnew.wxPoint(pos.x, min_y))
        
        elif align_type == "bottom":
            max_y = max(fp.GetPosition().y for fp in footprints)
            for fp in footprints:
                pos = fp.GetPosition()
                fp.SetPosition(pcbnew.wxPoint(pos.x, max_y))
        
        elif align_type == "h_center":
            avg_y = sum(fp.GetPosition().y for fp in footprints) // len(footprints)
            for fp in footprints:
                pos = fp.GetPosition()
                fp.SetPosition(pcbnew.wxPoint(pos.x, avg_y))
        
        elif align_type == "v_center":
            avg_x = sum(fp.GetPosition().x for fp in footprints) // len(footprints)
            for fp in footprints:
                pos = fp.GetPosition()
                fp.SetPosition(pcbnew.wxPoint(avg_x, pos.y))
        
        pcbnew.Refresh()

class AlignDialog(wx.Dialog):
    def __init__(self, parent):
        super().__init__(parent, title="对齐方式", size=(200, 200))
        
        panel = wx.Panel(self)
        vbox = wx.BoxSizer(wx.VERTICAL)
        
        self.choices = [
            ("左对齐", "left"),
            ("右对齐", "right"),
            ("顶对齐", "top"),
            ("底对齐", "bottom"),
            ("水平居中", "h_center"),
            ("垂直居中", "v_center")
        ]
        
        self.radio_box = wx.RadioBox(
            panel, choices=[c[0] for c in self.choices],
            style=wx.RA_SPECIFY_COLS, majorDimension=2
        )
        vbox.Add(self.radio_box, 0, wx.ALL, 10)
        
        btn_sizer = wx.StdDialogButtonSizer()
        ok_btn = wx.Button(panel, wx.ID_OK, "确定")
        cancel_btn = wx.Button(panel, wx.ID_CANCEL, "取消")
        btn_sizer.AddButton(ok_btn)
        btn_sizer.AddButton(cancel_btn)
        btn_sizer.Realize()
        vbox.Add(btn_sizer, 0, wx.ALIGN_CENTER | wx.ALL, 10)
        
        panel.SetSizer(vbox)
    
    def GetAlignType(self):
        return self.choices[self.radio_box.GetSelection()][1]

AlignPlugin().register()

15.2.3 网络高亮插件

import pcbnew
import wx

class NetHighlightPlugin(pcbnew.ActionPlugin):
    def defaults(self):
        self.name = "网络高亮"
        self.category = "查看"
        self.description = "高亮显示特定网络的所有连接"
        self.show_toolbar_button = True
    
    def Run(self):
        board = pcbnew.GetBoard()
        
        # 获取所有网络名称
        net_names = []
        for net in board.GetNetInfo().NetsByNetcode().values():
            name = net.GetNetname()
            if name and name.strip():
                net_names.append(name)
        
        net_names.sort()
        
        # 显示选择对话框
        dlg = wx.SingleChoiceDialog(
            None, "选择要高亮的网络:", "网络选择", net_names
        )
        
        if dlg.ShowModal() == wx.ID_OK:
            selected_net = dlg.GetStringSelection()
            self.highlight_net(board, selected_net)
        
        dlg.Destroy()
    
    def highlight_net(self, board, net_name):
        """高亮指定网络"""
        net = board.FindNet(net_name)
        if net:
            # 获取连接到该网络的所有项目
            items = []
            
            # 检查走线
            for track in board.GetTracks():
                if track.GetNetname() == net_name:
                    track.SetSelected()
                    items.append(track)
            
            # 检查焊盘
            for fp in board.GetFootprints():
                for pad in fp.Pads():
                    if pad.GetNetname() == net_name:
                        items.append(pad)
            
            pcbnew.Refresh()
            wx.MessageBox(f"网络 '{net_name}' 包含 {len(items)} 个项目", "信息")

NetHighlightPlugin().register()

15.3 封装向导插件

15.3.1 封装向导基础

import pcbnew

class MyFootprintWizard(pcbnew.FootprintWizardPlugin):
    def GetName(self):
        return "自定义封装向导"
    
    def GetDescription(self):
        return "创建自定义封装"
    
    def GetValue(self):
        return "CustomFP"
    
    def GenerateParameterList(self):
        """定义参数"""
        self.AddParam("Pads", "pad_count", self.uInteger, 8)
        self.AddParam("Pads", "pad_width", self.uMM, 0.6)
        self.AddParam("Pads", "pad_height", self.uMM, 1.2)
        self.AddParam("Pads", "pitch", self.uMM, 1.27)
    
    def CheckParameters(self):
        """验证参数"""
        self.CheckParam("Pads", "pad_count", min_value=2, max_value=100)
        self.CheckParam("Pads", "pitch", min_value=0.5)
    
    def BuildThisFootprint(self):
        """构建封装"""
        pad_count = self.GetParam("Pads", "pad_count")
        pad_width = self.GetParam("Pads", "pad_width")
        pad_height = self.GetParam("Pads", "pad_height")
        pitch = self.GetParam("Pads", "pitch")
        
        # 计算起始位置
        total_width = (pad_count - 1) * pitch
        start_x = -total_width / 2
        
        # 创建焊盘
        for i in range(pad_count):
            pos = pcbnew.wxPointMM(start_x + i * pitch, 0)
            pad = self.CreatePad("SMD", pcbnew.PAD_SHAPE_RECT,
                                pcbnew.wxSizeMM(pad_width, pad_height))
            pad.SetName(str(i + 1))
            pad.SetPos0(pos)
            pad.SetPosition(pos)
            pad.SetLayers(pcbnew.F_Cu | pcbnew.F_Paste | pcbnew.F_Mask)
            self.module.Add(pad)
        
        # 添加丝印
        self.draw.SetLayer(pcbnew.F_SilkS)
        outline_y = pad_height / 2 + 0.3
        self.draw.Line(-total_width/2 - 0.5, outline_y,
                       total_width/2 + 0.5, outline_y)
        self.draw.Line(-total_width/2 - 0.5, -outline_y,
                       total_width/2 + 0.5, -outline_y)

MyFootprintWizard().register()

15.4 IPC API开发(KiCad 9+)

15.4.1 安装kicad-python

pip install kicad-python

15.4.2 IPC API基础

from kicad import KiCad

# 连接到运行中的KiCad实例
kicad = KiCad()

# 获取当前板
board = kicad.board

# 获取封装
for fp in board.footprints:
    print(f"{fp.reference}: {fp.value}")

# 移动封装
fp = board.footprints[0]
fp.position = (100.0, 50.0)  # 毫米

# 刷新
board.refresh()

15.4.3 IPC API高级用法

from kicad import KiCad
from kicad.board import BoardLayer

kicad = KiCad()
board = kicad.board

# 添加走线
track = board.add_track(
    start=(10.0, 10.0),
    end=(50.0, 10.0),
    width=0.25,
    layer=BoardLayer.F_Cu,
    net="GND"
)

# 添加过孔
via = board.add_via(
    position=(30.0, 30.0),
    diameter=0.8,
    drill=0.4,
    net="VCC"
)

# 保存
board.save()

15.5 插件发布

15.5.1 插件元数据

# metadata.json
{
    "name": "My Plugin",
    "description": "A useful KiCad plugin",
    "author": "Your Name",
    "maintainer": "your@email.com",
    "version": "1.0.0",
    "kicad_version": "8.0",
    "license": "MIT"
}

15.5.2 通过PCM发布

发布到Plugin and Content Manager:

1. 创建插件仓库
2. 编写metadata.json
3. 打包为ZIP
4. 提交到KiCad PCM仓库
   https://gitlab.com/kicad/addons/metadata

15.5.3 插件文档

# My Plugin

## 安装

1. 下载插件文件
2. 复制到KiCad插件目录
3. 重启KiCad
4. 在Pcbnew中启用插件

## 使用方法

1. 选择元器件
2. 点击工具栏按钮
3. 配置选项
4. 确认执行

## 注意事项

- 使用前请备份项目
- 确保KiCad版本兼容

15.6 本章小结

本章介绍了KiCad Python插件开发的实战内容:

  1. 动作插件:学会了创建基本动作插件和带GUI的插件。
  2. 实用插件:通过统计、对齐、高亮等示例掌握了插件开发。
  3. 封装向导:了解了封装向导插件的创建方法。
  4. IPC API:学习了KiCad 9+的新API使用方法。
  5. 插件发布:掌握了插件打包和发布流程。

通过本章学习,读者可以开发自己的KiCad插件,扩展软件功能。


posted @ 2026-01-10 13:19  我才是银古  阅读(28)  评论(0)    收藏  举报