KiCad Python API (kipy) 快速入門指南

KiCad Python API (kipy) 快速入門指南

最後更新日期: 2025年4月25日 適用版本: KiCad 9.0+

這份快速入門指南旨在幫助您快速開始使用 KiCad Python API (kipy) 進行開發。通過幾個簡單的示例,您將學習如何連接到 KiCad、操作電路板並執行常見任務。

目錄

KiCad Python API 簡介

KiCad 9.0 引入了全新的 IPC API(進程間通信應用程式介面),這是一個穩定的介面,旨在替代舊版的 SWIG Python 綁定。這個新 API 的主要特點有:

  • 穩定性:設計為穩定的介面,不會因為 KiCad 內部重構而變化
  • 語言無關:支持與 Python 以外的其他語言編寫的軟體互操作
  • 進程獨立:使用 Protocol Buffers 和 NNG 透過 UNIX 套接字在進程間傳輸消息
  • 易於使用:提供了 kipy 這個官方的 Python 綁定庫,使得與 IPC API 的交互更加容易 截圖 2025-04-25 晚上10.25.43

注意:SWIG 綁定在 KiCad 9.0 中已被標記為棄用,計劃在 KiCad 10.0(預計2026年2月發布)中被完全移除。建議新的開發工作使用新的 IPC API。

## 基礎設置

在開始之前,請確保:

  1. 您已安裝 KiCad 9.0 或更高版本
  2. 在 KiCad 中啟用了 API 服務器(首選項 > 插件中啟用)
  3. 已安裝 kipy(使用 pip install kicad-python

啟用 API 服務器

要使用 kipy,您需要在 KiCad 中啟用 API 服務器:

  1. 打開 KiCad
  2. 進入 首選項 > 插件
  3. 勾選 啟用 API 服務器
  4. 重啟 KiCad 使設置生效

安裝 kipy 包

在命令行中運行以下命令安裝 kipy 包:

pip install kicad-python

如果您需要特定版本或開發版本,可以使用以下命令:

pip install kicad-python==0.1.0  # 安裝特定版本
# 或者
pip install git+https://gitlab.com/kicad/code/kicad-python.git  # 安裝開發版本
## 核心概念與架構

kipy 的核心架構基於以下幾個關鍵概念:

  1. KiCad 類 - 主要的入口點,用於連接到運行中的 KiCad 實例
  2. Board 類 - 代表一個 KiCad PCB 電路板,允許您查詢和修改電路板上的物件
  3. Project 類 - 代表一個 KiCad 項目,提供對項目級設置的訪問
  4. Wrapper 類 - 大多數 kipy 物件都繼承自 Wrapper,提供對底層 protobuf 消息的訪問
  5. 幾何類 - 提供 Vector2、Angle、Box2 等用於操作座標和幾何形狀的類

示例 1: 連接到 KiCad 並獲取版本信息

截圖 2025-04-25 晚上10.35.58

from kipy.kicad import KiCad

# 創建 KiCad 實例並連接到運行中的 KiCad
kicad = KiCad()

# 獲取 KiCad 版本
version = kicad.get_version()
print(f"連接到 KiCad 版本: {version}")

# 獲取 API 版本
api_version = kicad.get_api_version()
print(f"API 版本: {api_version}")

# 檢查版本兼容性
try:
    kicad.check_version()
    print("版本兼容!")
except Exception as e:
    print(f"版本不兼容: {e}")

示例 2: 獲取當前電路板信息

from kipy.kicad import KiCad

kicad = KiCad()

# 獲取當前活動文檔
active_doc = kicad.get_active_document()
if active_doc and active_doc.type == 2:  # 2 = DOCTYPE_BOARD
    # 獲取電路板
    board = kicad.get_board(active_doc)
    
    # 獲取基本信息
    print(f"電路板名稱: {board.name}")
    
    # 獲取軌道數量
    tracks = board.get_tracks()
    print(f"軌道數量: {len(tracks)}")
    
    # 獲取過孔數量
    vias = board.get_vias()
    print(f"過孔數量: {len(vias)}")
    
    # 獲取封裝數量
    footprints = board.get_footprints()
    print(f"封裝數量: {len(footprints)}")
    
    # 獲取網絡數量
    nets = board.get_nets()
    print(f"網絡數量: {len(nets)}")
else:
    print("當前沒有打開的電路板")

示例 3: 分析封裝和焊盤

from kipy.kicad import KiCad
from kipy.util.units import to_mm

kicad = KiCad()

# 獲取當前電路板
active_doc = kicad.get_active_document()
board = kicad.get_board(active_doc)

# 獲取所有封裝
footprints = board.get_footprints()

for fp in footprints:
    ref = fp.reference_field.text.value
    val = fp.value_field.text.value
    x_mm = to_mm(fp.position.x)
    y_mm = to_mm(fp.position.y)
    
    print(f"{ref} ({val}) 位於 ({x_mm:.2f}mm, {y_mm:.2f}mm)")
    
    # 獲取焊盤
    pads = fp.definition.pads
    print(f"  焊盤數量: {len(pads)}")
    
    # 列出焊盤信息
    for pad in pads:
        pad_num = pad.number
        net_name = pad.net.name if pad.net.name else "無網絡"
        print(f"  焊盤 {pad_num}: 連接到網絡 '{net_name}'")
    
    print()  # 空行

示例 4: 修改電路板 - 添加文本

from kipy.kicad import KiCad
from kipy.board_types import BoardText
from kipy.geometry import Vector2
from kipy.util.units import from_mm
from kipy.proto.board.board_types_pb2 import BoardLayer
from kipy.proto.common.types.enums_pb2 import HorizontalAlignment, VerticalAlignment
from datetime import datetime

kicad = KiCad()
active_doc = kicad.get_active_document()
board = kicad.get_board(active_doc)

# 開始一個提交事務
commit = board.begin_commit()

# 創建文本
text = BoardText()
text.value = "添加於 " + datetime.now().strftime("%Y-%m-%d")
text.position = Vector2.from_xy(from_mm(100), from_mm(100))
text.layer = BoardLayer.BL_F_SilkS

# 設置文本屬性
text.attributes.size = Vector2.from_xy(from_mm(1.5), from_mm(1.5))
text.attributes.horizontal_alignment = HorizontalAlignment.HA_CENTER
text.attributes.vertical_alignment = VerticalAlignment.VA_CENTER
text.attributes.bold = True

# 添加到電路板
created_items = board.create_items(text)
print(f"添加了 {len(created_items)} 個新項目")

# 提交更改
board.push_commit(commit, "添加文本標籤")
print("更改已提交")

示例 5: 創建導線和過孔

from kipy.kicad import KiCad
from kipy.board_types import Track, Via
from kipy.geometry import Vector2
from kipy.util.units import from_mm
from kipy.proto.board.board_types_pb2 import BoardLayer, ViaType

kicad = KiCad()
active_doc = kicad.get_active_document()
board = kicad.get_board(active_doc)

# 開始一個提交事務
commit = board.begin_commit()

# 創建網絡(這裡使用現有網絡)
nets = board.get_nets()
if len(nets) > 0:
    net = nets[0]  # 使用第一個網絡
    
    # 創建一個軌道
    track = Track()
    track.start = Vector2.from_xy(from_mm(100), from_mm(100))
    track.end = Vector2.from_xy(from_mm(120), from_mm(100))
    track.width = from_mm(0.25)  # 0.25mm 寬
    track.layer = BoardLayer.BL_F_Cu  # 頂層銅箔
    track.net = net
    
    # 添加軌道到電路板
    board.create_items(track)
    
    # 創建一個過孔
    via = Via()
    via.position = Vector2.from_xy(from_mm(120), from_mm(100))
    via.type = ViaType.VT_THROUGH
    via.diameter = from_mm(0.8)
    via.drill_diameter = from_mm(0.4)
    via.net = net
    
    # 添加過孔到電路板
    board.create_items(via)
    
    # 提交更改
    board.push_commit(commit, "添加軌道和過孔")
    print("添加了軌道和過孔")
else:
    board.drop_commit(commit)
    print("沒有可用的網絡")

示例 6: 創建一個簡單的 KiCad 插件

import os
import pcbnew
from kipy.kicad import KiCad
from kipy.board_types import BoardText
from kipy.geometry import Vector2
from kipy.util.units import from_mm
from kipy.proto.board.board_types_pb2 import BoardLayer
import datetime

class DateStampPlugin(pcbnew.ActionPlugin):
    def __init__(self):
        super().__init__()
        self.name = "添加日期戳記"
        self.category = "修改 PCB"
        self.description = "在電路板上添加當前日期"
        self.show_toolbar_button = True
        # 插件圖標路徑
        self.icon_file_name = os.path.join(os.path.dirname(__file__), "date_icon.png")

    def Run(self):
        # 連接到 KiCad
        kicad = KiCad()
        
        # 獲取當前電路板
        active_doc = kicad.get_active_document()
        board = kicad.get_board(active_doc)
        
        # 開始提交事務
        commit = board.begin_commit()
        
        # 創建日期文本
        date_text = BoardText()
        date_text.value = "生成日期: " + datetime.datetime.now().strftime("%Y-%m-%d")
        date_text.position = Vector2.from_xy(from_mm(150), from_mm(150))
        date_text.layer = BoardLayer.BL_F_SilkS
        
        # 設置文本屬性
        date_text.attributes.size = Vector2.from_xy(from_mm(1.5), from_mm(1.5))
        
        # 添加到電路板
        board.create_items(date_text)
        
        # 提交更改
        board.push_commit(commit, "添加日期戳記")
        
        # 通知用戶
        print("已添加日期戳記到電路板")

# 注冊插件
DateStampPlugin().register()

保存上面的代碼為 date_stamp.py,然後將其放置在 KiCad 插件目錄中:

  • Windows: %APPDATA%\kicad\9.0\scripting\plugins\
  • Linux: <sub>/.local/share/kicad/9.0/scripting/plugins/
  • macOS: </sub>/Library/Application Support/kicad/9.0/scripting/plugins/

示例 7: 透過pytho繪製舉矩形

from kipy import KiCad
from kipy.board_types import BoardRectangle, BoardLayer
from kipy.geometry import Vector2
from kipy.util.units import from_mm
from kipy.common_types import GraphicAttributes, Color

# 連線到 KiCad
kicad = KiCad()
board = kicad.get_board()
commit = board.begin_commit()

# 定義要繪製的層
layers = [
    BoardLayer.BL_F_Cu,        # 頂層銅箔
    BoardLayer.BL_B_Cu,        # 底層銅箔
    BoardLayer.BL_F_SilkS,     # 頂層絲印
    BoardLayer.BL_B_SilkS,     # 底層絲印
    BoardLayer.BL_F_Mask,      # 頂層阻焊
    BoardLayer.BL_B_Mask,      # 底層阻焊
    BoardLayer.BL_Edge_Cuts,   # 邊緣切割
    BoardLayer.BL_Dwgs_User,   # 用戶圖形層
]

# 獲取圖形元素預設設置
graphics_defaults = board.get_graphics_defaults()

# 設置方框的基本參數
center_x = from_mm(100)  # 中心點 X 座標
center_y = from_mm(100)  # 中心點 Y 座標
max_size = from_mm(50)   # 最大方框的寬度/高度
min_size = from_mm(10)   # 最小方框的寬度/高度
num_rectangles = 5       # 每一層繪製的方框數量

# 在每一層繪製同心方形
for layer_index, layer in enumerate(layers):
    # 方框間的大小差異
    size_step = (max_size - min_size) // (num_rectangles - 1)
    
    # 在當前層繪製多個同心方形
    for i in range(num_rectangles):
        # 計算當前方框的大小
        current_size = max_size - i * size_step
        
        # 計算方框的左上角和右下角座標
        half_size = current_size // 2
        top_left = Vector2.from_xy(center_x - half_size, center_y - half_size)
        bottom_right = Vector2.from_xy(center_x + half_size, center_y + half_size)
        
        # 創建方框
        rect = BoardRectangle()
        rect.top_left = top_left
        rect.bottom_right = bottom_right
        rect.layer = layer
        
        # 設置線寬
        rect.attributes.stroke.width = from_mm(0.2)
        
        # 根據層和方框大小設置不同的填充
        # 偶數層和偶數方框使用實心填充,其他使用無填充
        if (layer_index % 2 <mark> 0) and (i % 2 </mark> 0):
            rect.attributes.fill.mode = 1  # 實心填充
        else:
            rect.attributes.fill.mode = 0  # 無填充
        
        # 添加到電路板
        board.create_items(rect)

# 提交變更
board.push_commit(commit, message="在不同層繪製同心方形")
print("在多個層上繪製了同心方形")

截圖 2025-04-25 晚上10.28.10

截圖 2025-04-25 晚上10.28.31

常見任務快速參考

單位轉換

from kipy.util.units import from_mm, to_mm

# 毫米轉換為 KiCad 內部單位(納米)
position_nm = from_mm(10)  # 10mm 轉換為納米

# KiCad 內部單位轉換為毫米
size_mm = to_mm(1000000)  # 1,000,000 納米轉換為毫米

獲取層名稱

from kipy.proto.board.board_types_pb2 import BoardLayer
from kipy.util.board_layer import canonical_name

# 獲取層的標準名稱
layer = BoardLayer.BL_F_Cu
layer_name = canonical_name(layer)  # 返回 "F.Cu"

獲取選定的項目

# 獲取選定的項目
selected_items = board.get_selection()
print(f"選定了 {len(selected_items)} 個項目")

# 添加項目到選擇
board.add_to_selection(some_item)

# 清除選擇
board.clear_selection()

保存電路板

# 保存當前電路板
board.save()

# 另存為新文件
board.save_as("/path/to/new/board.kicad_pcb")

進階 API 使用技巧

處理事務提交

在 KiCad Python API 中,所有對電路板的修改都應該通過事務提交進行。這不僅可以確保修改被正確應用,還允許用戶撤銷操作。

from kipy.kicad import KiCad

kicad = KiCad()
board = kicad.get_board()

# 開始一個提交事務
commit = board.begin_commit()

# 進行修改...
# 例如創建、更新或刪除項目

try:
    # 提交更改並提供說明信息(顯示在撤銷/重做菜單中)
    board.push_commit(commit, "我的腳本修改")
    print("修改已成功提交")
except Exception as e:
    # 如果出現錯誤,放棄提交
    board.drop_commit(commit)
    print(f"修改失敗: {e}")

批量處理項目

當需要處理大量項目時,將它們批量處理可以提高效率:

from kipy.kicad import KiCad
from kipy.board_types import BoardText
from kipy.util.units import from_mm
from kipy.proto.board.board_types_pb2 import BoardLayer

kicad = KiCad()
board = kicad.get_board()
commit = board.begin_commit()

# 創建多個文本項目
texts = []
for i in range(10):
    text = BoardText()
    text.value = f"項目 {i}"
    text.position = Vector2.from_xy(from_mm(100 + i*10), from_mm(100))
    text.layer = BoardLayer.BL_F_SilkS
    texts.append(text)

# 批量創建項目
created_items = board.create_items(texts)
print(f"批量創建了 {len(created_items)} 個項目")

# 提交更改
board.push_commit(commit, "批量添加文本項目")

自定義插件目錄結構

對於複雜的插件,建議使用以下目錄結構:

my_plugin/
├── plugin.json           # 插件配置文件
├── icon.png              # 插件圖標
├── __init__.py           # 初始化文件
├── main.py               # 主要入口點
├── dialog.py             # 對話框 UI
└── utils/                # 工具函數
    ├── __init__.py
    └── helpers.py

plugin.json 文件示例:

{
    "$schema": "https://go.kicad.org/api/schemas/v1",
    "identifier": "com.example.my-plugin",
    "name": "我的 KiCad 插件",
    "description": "一個實用的 KiCad PCB 編輯器插件",
    "version": "1.0.0",
    "author": {
        "name": "您的名字",
        "contact": {
            "web": "https://example.com"
        }
    },
    "runtime": {
        "type": "python",
        "min_version": "3.9"
    },
    "actions": [
        {
            "identifier": "my-action",
            "name": "執行我的功能",
            "description": "示範插件功能",
            "show-button": true,
            "scopes": ["pcb"],
            "entrypoint": "main.py"
        }
    ]
}

與圖形用戶界面集成

KiCad Python API 可以與 wxPython 結合,創建與 KiCad 風格一致的用戶界面:

import wx
from kipy.kicad import KiCad

class MyDialog(wx.Dialog):
    def __init__(self, parent):
        wx.Dialog.__init__(self, parent, title="我的插件對話框")
        
        # 創建控件
        self.text_ctrl = wx.TextCtrl(self)
        self.button = wx.Button(self, label="確定")
        
        # 設置佈局
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(wx.StaticText(self, label="請輸入文本:"), 0, wx.ALL, 5)
        sizer.Add(self.text_ctrl, 0, wx.EXPAND|wx.ALL, 5)
        sizer.Add(self.button, 0, wx.ALIGN_RIGHT|wx.ALL, 5)
        
        self.SetSizerAndFit(sizer)
        
        # 綁定事件
        self.button.Bind(wx.EVT_BUTTON, self.on_button_click)
    
    def on_button_click(self, event):
        text = self.text_ctrl.GetValue()
        print(f"用戶輸入: {text}")
        self.EndModal(wx.ID_OK)
        
def run_plugin():
    kicad = KiCad()
    board = kicad.get_board()
    
    # 創建對話框
    app = wx.App.Get()
    with MyDialog(wx.GetApp().GetTopWindow()) as dlg:
        if dlg.ShowModal() == wx.ID_OK:
            print("對話框確認")
            # 在這裡執行操作
        else:
            print("對話框取消")

常見問題解答 (FAQ)

Q: 新的 IPC API 與舊的 SWIG 綁定有什麼區別?

A: 新的 IPC API 與舊版 SWIG 綁定相比有以下主要區別:

  1. 穩定性:IPC API 設計為穩定接口,不會隨著 KiCad 內部代碼重構而改變
  2. 進程分離:IPC API 在單獨的進程中運行,通過 IPC 機制與 KiCad 通信,而 SWIG 綁定直接在 KiCad 進程中運行
  3. 語言無關:IPC API 可以從多種編程語言訪問,而不僅僅是 Python
  4. 更現代的 API 設計:提供了更一致、更易於使用的接口
  5. 穩定的 ABI:插件不需要針對每個 KiCad 版本重新編譯

Q: 如何調試 kipy 腳本?

A: 您可以使用標準的 Python 調試技術:

  1. 使用 print 語句輸出調試信息
  2. 使用 Python 的 logging 模塊記錄信息
  3. 使用 VSCode 或 PyCharm 等 IDE 的調試器進行交互式調試
  4. 使用 try-except 塊捕獲並打印詳細的錯誤信息
import logging

# 設置日誌記錄
logging.basicConfig(level=logging.DEBUG, 
                   format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                   filename='kipy_debug.log')

try:
    # 您的代碼
    kicad = KiCad()
    board = kicad.get_board()
    logging.info(f"成功獲取電路板: {board.name}")
except Exception as e:
    logging.error(f"發生錯誤: {e}", exc_info=True)

Q: 我的 kipy 腳本無法連接到 KiCad,可能的原因是什麼?

A: 常見的連接問題包括:

  1. KiCad 中沒有啟用 API 服務器(首選項 > 插件中啟用)
  2. KiCad 版本與 kipy 版本不兼容
  3. 未運行 KiCad 或運行了多個 KiCad 實例
  4. 系統防火牆或安全軟件阻止了進程間通信

首先確保 KiCad 正在運行,並在設置中啟用了 API 服務器。然後嘗試重新安裝與您 KiCad 版本兼容的 kipy 版本。

Q: 如何創建使用 kipy 的獨立工具(非插件)?

A: 您可以創建直接使用 kipy 與運行中的 KiCad 通信的獨立 Python 腳本:

#!/usr/bin/env python3
from kipy.kicad import KiCad

def main():
    try:
        # 連接到運行中的 KiCad 實例
        kicad = KiCad()
        print(f"已連接到 KiCad {kicad.get_version()}")
        
        # 獲取當前電路板
        board = kicad.get_board()
        if not board:
            print("未找到打開的電路板")
            return
            
        # 執行您的操作
        # ...
        
    except Exception as e:
        print(f"錯誤: {e}")

if __name__ == "__main__":
    main()

運行這樣的腳本時,確保 KiCad 已經在運行並且已經打開了電路板。

Q: 我可以使用 kipy 創建完整的電路板嗎?

A: 是的,您可以從頭開始創建電路板,添加所有的元件和軌道。然而,通常更實用的做法是從現有的電路板開始,然後修改它。

Q: 我如何處理錯誤和例外?

A: kipy 會抛出 ApiErrorConnectionError 等異常。您應該處理這些異常以確保腳本在出現問題時能够正常處理:

from kipy.errors import ApiError, ConnectionError

try:
    # kipy 代碼
    kicad = KiCad()
    # ...
except ConnectionError as e:
    print(f"無法連接到 KiCad: {e}")
except ApiError as e:
    print(f"API 錯誤: {e}")

性能優化技巧

使用 KiCad Python API 處理大型電路板時,以下是一些提高性能的技巧:

  1. 批量處理:一次性提交多個更改,而不是逐個提交
  2. 限制重繪:在批量操作期間禁用重繪,完成後再恢復
  3. 使用適當的數據結構:例如使用 dict 建立網絡名稱到網絡對象的映射
  4. 避免不必要的查詢:緩存經常訪問的數據,而不是反复查詢

以下是優化批量更新操作的示例:

from kipy.kicad import KiCad

kicad = KiCad()
board = kicad.get_board()
commit = board.begin_commit()

# 預先獲取所有需要的數據
tracks = board.get_tracks()
nets = {net.name: net for net in board.get_nets()}

# 批量處理項目
to_update = []
for track in tracks:
    if track.width < from_mm(0.2):  # 找出寬度小於 0.2mm 的軌道
        track.width = from_mm(0.2)  # 設置為 0.2mm
        to_update.append(track)

# 一次性更新所有修改的軌道
if to_update:
    board.update_items(to_update)
    board.push_commit(commit, f"將 {len(to_update)} 條軌道寬度更新為 0.2mm")
    print(f"已更新 {len(to_update)} 條軌道")
else:
    board.drop_commit(commit)
    print("沒有需要更新的軌道")

實用範例與實際應用案例

自動化設計規則檢查

以下範例展示如何使用 kipy 進行設計規則檢查(DRC)並生成報告:

from kipy.kicad import KiCad
from kipy.util.units import to_mm
import csv
import datetime

def check_track_clearance(board, min_clearance_mm=0.2):
    """檢查軌道之間的最小間距"""
    tracks = board.get_tracks()
    violations = []
    
    # 這裡只是一個簡化的示例
    # 實際的間距檢查需要更複雜的算法
    for i, track1 in enumerate(tracks):
        for track2 in tracks[i+1:]:
            if track1.layer == track2.layer and track1.net != track2.net:
                # 這裡應有實際計算兩條軌道之間最小距離的代碼
                distance_mm = 0.1  # 假設值,實際需要計算
                if distance_mm < min_clearance_mm:
                    violations.append({
                        'track1': f"({to_mm(track1.start.x):.2f}, {to_mm(track1.start.y):.2f}) - ({to_mm(track1.end.x):.2f}, {to_mm(track1.end.y):.2f})",
                        'track2': f"({to_mm(track2.start.x):.2f}, {to_mm(track2.start.y):.2f}) - ({to_mm(track2.end.x):.2f}, {to_mm(track2.end.y):.2f})",
                        'distance': f"{distance_mm:.2f}mm",
                        'required': f"{min_clearance_mm:.2f}mm"
                    })
    return violations

def export_drc_report(violations, filename):
    """將違規導出為 CSV 報告"""
    with open(filename, 'w', newline='') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=['track1', 'track2', 'distance', 'required'])
        writer.writeheader()
        for v in violations:
            writer.writerow(v)
    print(f"報告已保存至 {filename}")

def main():
    kicad = KiCad()
    board = kicad.get_board()
    
    print(f"正在檢查電路板: {board.name}")
    violations = check_track_clearance(board)
    
    if violations:
        print(f"發現 {len(violations)} 個間距違規")
        filename = f"drc_report_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
        export_drc_report(violations, filename)
    else:
        print("未發現間距違規")

if __name__ == "__main__":
    main()

自動生成裝配圖層

這個範例展示如何使用 kipy 在電路板上生成裝配圖層標記,用於製造和裝配:

from kipy.kicad import KiCad
from kipy.board_types import BoardText, BoardCircle
from kipy.geometry import Vector2
from kipy.util.units import from_mm, to_mm
from kipy.proto.board.board_types_pb2 import BoardLayer
import math

def create_fiducial_marks(board, layer=BoardLayer.BL_F_SilkS):
    """在電路板角落創建基準標記"""
    commit = board.begin_commit()
    
    # 獲取電路板邊界
    board_outline = board.get_board_outline()
    bb = board_outline.bounding_box
    
    # 計算角落位置(留出 5mm 邊距)
    margin = from_mm(5)
    corners = [
        Vector2.from_xy(bb.min_x + margin, bb.min_y + margin),  # 左下
        Vector2.from_xy(bb.max_x - margin, bb.min_y + margin),  # 右下
        Vector2.from_xy(bb.max_x - margin, bb.max_y - margin),  # 右上
        Vector2.from_xy(bb.min_x + margin, bb.max_y - margin)   # 左上
    ]
    
    # 創建基準標記(十字加圓)
    marks = []
    for i, pos in enumerate(corners):
        # 創建圓
        circle = BoardCircle()
        circle.center = pos
        circle.radius = from_mm(1)
        circle.layer = layer
        marks.append(circle)
        
        # 創建標籤
        text = BoardText()
        text.value = f"FID{i+1}"
        text.position = Vector2.from_xy(pos.x, pos.y + from_mm(2))
        text.layer = layer
        marks.append(text)
    
    # 創建元素並提交
    board.create_items(marks)
    board.push_commit(commit, "添加裝配基準標記")
    return len(marks)

def generate_assembly_labels(board):
    """為每個封裝生成裝配標籤"""
    commit = board.begin_commit()
    
    footprints = board.get_footprints()
    labels = []
    
    for fp in footprints:
        ref = fp.reference_field.text.value
        val = fp.value_field.text.value
        
        # 在元件上方2mm處創建標籤
        text = BoardText()
        text.value = f"{ref}:{val}"
        # 計算位置,考慮元件旋轉
        angle_rad = math.radians(fp.orientation.degrees)
        offset_x = -from_mm(2) * math.sin(angle_rad)
        offset_y = from_mm(2) * math.cos(angle_rad)
        text.position = Vector2.from_xy(fp.position.x + offset_x, fp.position.y + offset_y)
        text.layer = BoardLayer.BL_F_Fab  # 放在製造層
        labels.append(text)
    
    board.create_items(labels)
    board.push_commit(commit, "添加裝配標籤")
    return len(labels)

def main():
    kicad = KiCad()
    board = kicad.get_board()
    
    num_fiducials = create_fiducial_marks(board)
    print(f"已添加 {num_fiducials} 個基準標記")
    
    num_labels = generate_assembly_labels(board)
    print(f"已添加 {num_labels} 個裝配標籤")

if __name__ == "__main__":
    main()

PCB 自動佈局輔助工具

下面是一個簡單的工具,用於自動排列電路板上的某些元件:

from kipy.kicad import KiCad
from kipy.geometry import Vector2
from kipy.util.units import from_mm, to_mm
import re

def arrange_components_in_grid(board, ref_pattern, rows, cols, spacing_mm):
    """將匹配模式的元件按網格排列"""
    commit = board.begin_commit()
    
    # 查找匹配的元件
    footprints = board.get_footprints()
    matching_fps = []
    
    pattern = re.compile(ref_pattern)
    for fp in footprints:
        ref = fp.reference_field.text.value
        if pattern.match(ref):
            matching_fps.append(fp)
    
    if not matching_fps:
        print(f"找不到匹配 '{ref_pattern}' 的元件")
        return 0
    
    # 最多處理 rows*cols 個元件
    count = min(len(matching_fps), rows * cols)
    
    # 設置起始位置為第一個匹配元件的位置
    start_x = matching_fps[0].position.x
    start_y = matching_fps[0].position.y
    
    # 計算間距
    spacing_x = from_mm(spacing_mm)
    spacing_y = from_mm(spacing_mm)
    
    # 按網格排列元件
    for i in range(count):
        row = i // cols
        col = i % cols
        
        fp = matching_fps[i]
        fp.position = Vector2.from_xy(
            start_x + col * spacing_x,
            start_y + row * spacing_y
        )
    
    # 更新元件位置
    board.update_items(matching_fps[:count])
    board.push_commit(commit, f"按網格排列 {count} 個元件")
    
    return count

def main():
    kicad = KiCad()
    board = kicad.get_board()
    
    # 例如,將所有 LED 元件排成 3x4 網格,間距 10mm
    count = arrange_components_in_grid(board, r"LED\d+", 3, 4, 10)
    print(f"已排列 {count} 個元件")

if __name__ == "__main__":
    main()

自動創建PCB測試點

以下範例展示如何為特定網絡自動添加測試點:

from kipy.kicad import KiCad
from kipy.board_types import Via
from kipy.geometry import Vector2
from kipy.util.units import from_mm
from kipy.proto.board.board_types_pb2 import ViaType

def add_test_points(board, net_names, diameter_mm=1.0, drill_mm=0.5):
    """為指定網絡添加測試點"""
    commit = board.begin_commit()
    
    # 獲取所有網絡
    nets = board.get_nets()
    net_dict = {net.name: net for net in nets if net.name}
    
    # 查找匹配的網絡
    test_points = []
    for net_name in net_names:
        if net_name in net_dict:
            net = net_dict[net_name]
            
            # 獲取這個網絡的軌道,找到一個合適的位置
            tracks = [t for t in board.get_tracks() if t.net.code == net.code]
            if tracks:
                # 使用第一條軌道的中點作為測試點位置
                track = tracks[0]
                pos_x = (track.start.x + track.end.x) / 2
                pos_y = (track.start.y + track.end.y) / 2
                
                # 創建測試點(使用過孔)
                via = Via()
                via.position = Vector2.from_xy(pos_x, pos_y)
                via.type = ViaType.VT_THROUGH
                via.diameter = from_mm(diameter_mm)
                via.drill_diameter = from_mm(drill_mm)
                via.net = net
                
                test_points.append(via)
                print(f"為網絡 '{net_name}' 添加測試點")
            else:
                print(f"網絡 '{net_name}' 沒有軌道,無法添加測試點")
        else:
            print(f"找不到網絡 '{net_name}'")
    
    if test_points:
        board.create_items(test_points)
        board.push_commit(commit, f"添加 {len(test_points)} 個測試點")
        return len(test_points)
    else:
        board.drop_commit(commit)
        return 0

def main():
    kicad = KiCad()
    board = kicad.get_board()
    
    # 指定要添加測試點的網絡名稱
    net_names = ["GND", "VCC", "RST", "SCL", "SDA"]
    count = add_test_points(board, net_names)
    
    print(f"共添加了 {count} 個測試點")

if __name__ == "__main__":
    main()

與其他工具集成

與 Git 版本控制集成

這個範例展示如何使用 kipy 實現 KiCad 與 Git 版本控制的集成:

import os
import subprocess
from kipy.kicad import KiCad
import datetime

def git_commit_pcb_changes(board, commit_message=None):
    """保存電路板並將更改提交到 Git"""
    # 獲取電路板文件路徑
    board_path = board.filename
    
    if not os.path.isfile(board_path):
        print("電路板尚未保存,無法提交到 Git")
        return False
    
    # 保存電路板
    board.save()
    print(f"已保存電路板到: {board_path}")
    
    # 獲取 Git 倉庫根目錄
    try:
        repo_root = subprocess.check_output(
            ["git", "rev-parse", "--show-toplevel"], 
            cwd=os.path.dirname(board_path),
            text=True
        ).strip()
    except subprocess.CalledProcessError:
        print("當前目錄不是 Git 倉庫")
        return False
    
    # 獲取相對路徑
    rel_path = os.path.relpath(board_path, repo_root)
    
    # 添加到暫存區
    try:
        subprocess.run(
            ["git", "add", rel_path], 
            cwd=repo_root, 
            check=True
        )
        
        # 創建提交信息
        if not commit_message:
            commit_message = f"更新電路板設計 {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}"
        
        # 提交更改
        subprocess.run(
            ["git", "commit", "-m", commit_message], 
            cwd=repo_root, 
            check=True
        )
        
        print(f"已將電路板更改提交到 Git: {commit_message}")
        return True
    except subprocess.CalledProcessError as e:
        print(f"Git 操作失敗: {e}")
        return False

def main():
    kicad = KiCad()
    board = kicad.get_board()
    
    # 進行一些修改...
    
    # 保存並提交修改
    git_commit_pcb_changes(board, "添加新的電源元件和軌道")

if __name__ == "__main__":
    main()

與 BOM 管理系統集成

以下範例展示如何生成物料清單(BOM)並與外部系統集成:

from kipy.kicad import KiCad
import csv
import json
import requests

def generate_bom(board):
    """生成電路板的物料清單"""
    footprints = board.get_footprints()
    
    # 按值和封裝分組元件
    components = {}
    for fp in footprints:
        ref = fp.reference_field.text.value
        val = fp.value_field.text.value
        pkg = fp.definition.identifier.lib_id.name if hasattr(fp.definition, 'identifier') else "Unknown"
        
        key = f"{val}|{pkg}"
        if key not in components:
            components[key] = {
                'value': val,
                'package': pkg,
                'references': [],
                'quantity': 0
            }
        
        components[key]['references'].append(ref)
        components[key]['quantity'] += 1
    
    # 轉換為列表
    bom_list = list(components.values())
    
    # 為每個項目添加引用字符串
    for item in bom_list:
        item['references_str'] = ", ".join(sorted(item['references']))
    
    return bom_list

def export_bom_csv(bom_list, filename):
    """將 BOM 導出為 CSV 文件"""
    with open(filename, 'w', newline='') as csvfile:
        writer = csv.DictWriter(
            csvfile, 
            fieldnames=['value', 'package', 'references_str', 'quantity'],
            extrasaction='ignore'
        )
        writer.writeheader()
        writer.writerows(bom_list)
    print(f"BOM 已導出至 {filename}")
    return filename

def upload_bom_to_system(bom_list, api_url, api_key):
    """將 BOM 上傳到外部系統"""
    headers = {
        'Content-Type': 'application/json',
        'Authorization': f'Bearer {api_key}'
    }
    
    payload = {
        'project_name': 'My KiCad Project',
        'components': bom_list
    }
    
    try:
        response = requests.post(api_url, headers=headers, json=payload)
        response.raise_for_status()
        print(f"BOM 已成功上傳: {response.json().get('message', '')}")
        return True
    except requests.exceptions.RequestException as e:
        print(f"上傳失敗: {e}")
        return False

def main():
    kicad = KiCad()
    board = kicad.get_board()
    
    # 生成 BOM
    bom_list = generate_bom(board)
    
    # 導出為 CSV
    export_bom_csv(bom_list, "bom_export.csv")
    
    # 上傳到外部系統(需要實際的 API 端點和密鑰)
    # upload_bom_to_system(bom_list, "https://api.example.com/bom", "your_api_key")

if __name__ == "__main__":
    main()

API 參考

主要類和模塊

  • kipy.kicad:包含 KiCad 類,用於連接到 KiCad 實例
  • kipy.board_types:包含 Board、Track、Via、Footprint 等類
  • kipy.geometry:包含 Vector2、Angle、Box2 等幾何類
  • kipy.util:包含單位轉換和其他實用功能
  • kipy.proto:包含底層 protobuf 類型定義
  • kipy.errors:包含異常類型

常用常數

  • BoardLayer:定義電路板層(如 BL_F_Cu、BL_B_Cu、BL_F_SilkS)
  • ViaType:定義過孔類型(如 VT_THROUGH、VT_BLIND_BURIED)
  • DocumentType:定義文檔類型(如 DOCTYPE_BOARD、DOCTYPE_SCHEMATIC)

幾何操作

  • Vector2:表示二維向量或點
  • Angle:表示角度,提供度和弧度之間的轉換
  • Box2:表示二維軸對齊框

下一步


本文最初發布於 HackMD @BASHCAT

留言

這個網誌中的熱門文章

Arduino 課本可能沒教的事(1)

SI4432 搭配Arduino

燒錄 Arduino mini Pro 燒錄