---
# System prepended metadata

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

---

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

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

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

## 目錄
- [KiCad Python API 簡介](#kicad-python-api-簡介)
- [基礎設置](#基礎設置)
- [連接到 KiCad](#示例-1-連接到-kicad-並獲取版本信息)
- [操作電路板](#示例-2-獲取當前電路板信息)
- [分析封裝](#示例-3-分析封裝和焊盤)
- [修改電路板](#示例-4-修改電路板---添加文本)
- [創建元素](#示例-5-創建導線和過孔)
- [創建插件](#示例-6-創建一個簡單的-kicad-插件)
- [進階 API 使用技巧](#進階-api-使用技巧)
- [與圖形用戶界面集成](#與圖形用戶界面集成)
- [常見問題解答](#常見問題解答-faq)
- [性能優化技巧](#性能優化技巧)
- [實用範例與實際應用案例](#實用範例與實際應用案例)
- [核心概念與架構](#核心概念與架構)
- [API 參考](#api-參考)

## 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](https://hackmd.io/_uploads/ryZ42Gtkel.png)

:::warning
注意：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 包：

```bash
pip install kicad-python
```

:::info
如果您需要特定版本或開發版本，可以使用以下命令：
```bash
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](https://hackmd.io/_uploads/SJyxRMK1xg.png)

```python
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: 獲取當前電路板信息

```python
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: 分析封裝和焊盤

```python
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: 修改電路板 - 添加文本

```python
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: 創建導線和過孔

```python
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 插件

```python
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: `~/.local/share/kicad/9.0/scripting/plugins/`
- macOS: `~/Library/Application Support/kicad/9.0/scripting/plugins/`

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

```python
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 == 0) and (i % 2 == 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](https://hackmd.io/_uploads/HJJB2zFyle.png)

![截圖 2025-04-25 晚上10.28.31](https://hackmd.io/_uploads/BysEhGK1gx.png)




## 常見任務快速參考

### 單位轉換

```python
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 納米轉換為毫米
```

### 獲取層名稱

```python
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"
```

### 獲取選定的項目

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

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

# 清除選擇
board.clear_selection()
```

### 保存電路板

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

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

## 進階 API 使用技巧

### 處理事務提交

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

```python
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}")
```

### 批量處理項目

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

```python
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 文件示例：

```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 風格一致的用戶界面：

```python
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 塊捕獲並打印詳細的錯誤信息

```python
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 腳本：

```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 會抛出 `ApiError` 和 `ConnectionError` 等異常。您應該處理這些異常以確保腳本在出現問題時能够正常處理：

```python
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. **避免不必要的查詢**：緩存經常訪問的數據，而不是反复查詢

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

```python
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）並生成報告：

```python
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 在電路板上生成裝配圖層標記，用於製造和裝配：

```python
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 自動佈局輔助工具

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

```python
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測試點

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

```python
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 版本控制的集成：

```python
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）並與外部系統集成：

```python
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**：表示二維軸對齊框

## 下一步

- 瀏覽 [KiCad 開發文檔](https://dev-docs.kicad.org/en/apis-and-binding/ipc-api/) 了解更詳細的 API 信息
- 查看 [kipy 源代碼](https://gitlab.com/kicad/code/kicad-python) 以深入了解實現細節
- 加入 [KiCad 論壇](https://forum.kicad.info/) 與社區討論 API 使用
- 探索 [kipy 示例目錄](https://gitlab.com/kicad/code/kicad-python/-/tree/master/examples) 中的更多示例


