第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插件开发的实战内容:
- 动作插件:学会了创建基本动作插件和带GUI的插件。
- 实用插件:通过统计、对齐、高亮等示例掌握了插件开发。
- 封装向导:了解了封装向导插件的创建方法。
- IPC API:学习了KiCad 9+的新API使用方法。
- 插件发布:掌握了插件打包和发布流程。
通过本章学习,读者可以开发自己的KiCad插件,扩展软件功能。

浙公网安备 33010602011771号