重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
kicad-schematic by kenchangh/kicad-schematic
npx skills add https://github.com/kenchangh/kicad-schematic --skill kicad-schematic通过编写使用计算引脚位置的 Python 脚本,生成 ERC 无误的 KiCad 8/9 原理图——无需猜测坐标。同时修复现有原理图上的 ERC 错误,并处理 KiCad 8→9 的迁移。
原理图损坏的首要原因是猜测引脚位置。 当将标签连接到 IC 引脚时,必须 使用符号定义中的引脚位置和坐标变换公式来计算精确坐标。scripts/kicad_sch_helpers.py 中的辅助库会自动完成此操作。
符号库 (.kicad_sym) 使用 Y轴向上(数学惯例)。原理图 (.kicad_sch) 使用 Y轴向下(屏幕惯例)。这意味着在从库空间转换到原理图空间时,必须 对 Y 坐标取负。忘记这一点会将标签放置在距离其引脚 10-50mm 远的地方,导致大量的 pin_not_connected 和 label_dangling 错误。
变换公式 — 引脚在库中的位置为 (px, py),符号放置在原理图中的位置为 (sx, sy),旋转角度为 R:
始终使用辅助库中的 —— 切勿手动计算这些。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
pin_abs()User describes circuit
|
Read symbol libraries (.kicad_sym) to get pin positions
|
Build pin position dictionaries for every multi-pin IC
|
Write Python script using SchematicBuilder (from helper library)
- Use connect_pin() for IC pins (computes positions automatically)
- Use place_2pin_vertical() for passives (knows pin 1/2 positions)
|
Generate .kicad_sch file
|
Post-process with fix_subsymbol_names()
|
Run ERC validation: kicad-cli sch erc --format json
|
Parse errors -> fix script -> regenerate -> repeat (max 5 iterations)
在运行任何 ERC 验证之前,请确认 kicad-cli 在系统 PATH 中。运行:
which kicad-cli 2>/dev/null || where kicad-cli 2>/dev/null
如果 未找到,请检查本地 KiCad 安装并询问是否创建符号链接:
macOS:
# Check if KiCad is installed as an app
KICAD_CLI="/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli"
if [ -f "$KICAD_CLI" ]; then
echo "Found kicad-cli inside KiCad.app. Creating symlink..."
sudo ln -sf "$KICAD_CLI" /usr/local/bin/kicad-cli
echo "Done! kicad-cli is now available on PATH."
else
echo "KiCad not found. Install from https://www.kicad.org/download/macos/"
fi
Linux:
# kicad-cli is typically installed alongside KiCad via package manager
# Check common locations
for p in /usr/bin/kicad-cli /usr/local/bin/kicad-cli /snap/kicad/current/bin/kicad-cli; do
if [ -f "$p" ]; then
echo "Found kicad-cli at $p"
# If not on PATH, symlink it
if ! command -v kicad-cli &>/dev/null; then
sudo ln -sf "$p" /usr/local/bin/kicad-cli
fi
break
fi
done
# If still not found:
# Ubuntu/Debian: sudo apt install kicad
# Fedora: sudo dnf install kicad
# Arch: sudo pacman -S kicad
# Or install from https://www.kicad.org/download/linux/
Windows:
# Check standard install path
$kicadCli = "C:\Program Files\KiCad\8.0\bin\kicad-cli.exe"
if (Test-Path $kicadCli) {
Write-Host "Found kicad-cli. Add to PATH:"
Write-Host ' [Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";C:\Program Files\KiCad\8.0\bin", "User")'
} else {
Write-Host "KiCad not found. Install from https://www.kicad.org/download/windows/"
}
告诉用户你发现了什么,并在创建任何符号链接之前请求确认。如果确实没有安装 kicad-cli,请提供其操作系统的下载链接并停止——ERC 验证需要它。
在编写任何代码之前,收集:
对于 每一个 IC 和多引脚元件,读取其 .kicad_sym 定义以获取精确的引脚名称、编号、位置和类型。没有这些数据,你无法正确连接引脚。
from kicad_sch_helpers import SymbolLibrary
lib = SymbolLibrary()
lib.load_from_kicad_sym("path/to/library.kicad_sym")
# Now you know exact pin positions
ad9363 = lib.get("AD9363ABCZ")
for pin in ad9363.pins:
print(f"{pin.name} ({pin.number}): at ({pin.x}, {pin.y}), type={pin.pin_type}")
对于手动/内联方法,从库中构建引脚字典:
# Extract from .kicad_sym file — these are LIBRARY coordinates (Y-up)
AD_PINS = {
'TX1A_P': (-17.78, 25.40),
'TX1A_N': (-17.78, 22.86),
'SPI_CLK': (-17.78, -10.16),
# ... all pins
}
# SOT-23-5 packages (common for LDOs like AP2112K, ME6211):
SOT5_PINS = {
'VIN': (-7.62, 2.54), # Pin 1 - top left
'GND': ( 0.00, -7.62), # Pin 2 - bottom center
'EN': (-7.62, -2.54), # Pin 3 - bottom left
'NC': ( 7.62, -2.54), # Pin 4 - bottom right <- NOT VOUT!
'VOUT': ( 7.62, 2.54), # Pin 5 - top right <- THIS is VOUT!
}
警告 — SOT-23-5 引脚陷阱: VOUT 在 (7.62, +2.54),NC 在 (7.62, -2.54)。它们仅相距 5.08mm。混淆它们意味着你的 LDO 输出无处可去。始终根据实际的库文件进行验证。
使用 SchematicBuilder 进行所有原理图构建。关键方法是 connect_pin(),它会自动计算精确的引脚位置:
from kicad_sch_helpers import SchematicBuilder, SymbolLibrary, snap
lib = SymbolLibrary()
lib.load_from_kicad_sym("custom_symbols.kicad_sym")
sch = SchematicBuilder(symbol_lib=lib, project_name="my_project")
sch.set_lib_symbols(lib_symbols_content) # Raw S-expression for embedded symbols
# Place an IC
sch.place("CubeSat_SDR:AD9363ABCZ", "U1", "AD9363ABCZ",
x=320, y=200, footprint="CubeSat_SDR:AD9363_BGA144")
# Connect pins by NAME — coordinates computed automatically
sch.connect_pin("U1", "TX1A_P", "TX1A_P", wire_dx=-5.08)
sch.connect_pin("U1", "SPI_CLK", "SPI_CLK", wire_dy=-5.08)
sch.connect_pin("U1", "GND", "GND", wire_dy=5.08)
# For unused pins, add no-connect flags
sch.connect_pin_noconnect("U1", "AUXDAC1")
# For 2-pin passives, use convenience helpers
from kicad_sch_helpers import place_2pin_vertical
place_2pin_vertical(sch, "Device:C", "C1", "100nF",
x=snap(230), y=snap(155),
top_net="VCC_3V3", bottom_net="GND",
footprint="Capacitor_SMD:C_0402_1005Metric")
如果使用内联引脚字典(不使用 SymbolLibrary),请使用 pin_abs():
from kicad_sch_helpers import pin_abs, snap
GRID = 1.27
def wl(sch, sx, sy, pin_name, pins_dict, net, dx=0, dy=0, rot=0, label_angle=0):
"""Wire + Label: connect an IC pin to a net label."""
px, py = pin_abs(sx, sy, pins_dict[pin_name][0], pins_dict[pin_name][1], rot)
ex, ey = snap(px + dx), snap(py + dy)
if dx != 0 or dy != 0:
sch.w(px, py, ex, ey)
sch.label(net, ex, ey, label_angle)
# Usage:
wl(sch, 320, 200, 'TX1A_P', AD_PINS, "TX1A_P", dx=-7.62)
引用的每个符号都必须嵌入到原理图的 lib_symbols 中。三个关键规则:
(symbol "Device:R" ...)(symbol "R_0_1" ...) 而不是 (symbol "Device:R_0_1" ...)fix_subsymbol_names() 来捕获任何错误使用 fix_subsymbol_names() 作为后处理步骤:
from kicad_sch_helpers import fix_subsymbol_names
content = sch.build(title="My Schematic")
content = fix_subsymbol_names(content)
基于正则表达式的修复器可以处理任意深度的嵌套子符号和任何库前缀格式。
原理图中的 每个坐标 必须是 1.27mm 的倍数。使用 snap():
from kicad_sch_helpers import snap
# snap() rounds to nearest 1.27mm grid point
x = snap(123.45) # -> 124.46 (nearest multiple of 1.27)
将 snap() 应用于:元件位置、导线端点、标签位置、无连接标志位置和 PWR_FLAG 位置。connect_pin() 和 pin_abs() 函数会自动执行此操作。
对于源自电压稳压器(而非电源符号)的每个电源网络,添加一个 PWR_FLAG 以防止 "power_pin_not_driven" 错误:
# Define PWR_FLAG in lib_symbols (see references/kicad_sexpression_format.md)
# Then place on each power net:
sch.place_pwr_flag(x=70, y=78, net_name="VCC_3V3A")
经验法则:如果一个网络由输出引脚类型为 passive(而非 power_out)的元件驱动,则该网络需要一个 PWR_FLAG。这包括大多数 LDO 稳压器。
同时,在没有专用 GND 电源符号驱动的 GND 网络上放置 PWR_FLAG。
每个 IC 上的每个未使用引脚 必须 有一个无连接标志。缺少无连接标志会导致 pin_not_connected 错误。
# Using SchematicBuilder:
sch.connect_pin_noconnect("U1", "AUXDAC1")
# Or manually with pin_abs:
px, py = pin_abs(sx, sy, pin_x, pin_y, rotation)
sch.nc(px, py)
from kicad_sch_helpers import run_erc
result = run_erc("output/schematic.kicad_sch")
print(f"Errors: {result['errors']}, Warnings: {result['warnings']}")
if result['errors'] > 0:
for detail in result['details']:
if detail.get('severity') == 'error':
print(f" {detail['type']}: {detail.get('description', '')}")
对于复杂的原理图,使用验证循环:
from kicad_sch_helpers import validate_and_fix_loop
def my_fixer(erc_result, iteration):
"""Analyze ERC errors and apply fixes. Return True if fixes applied."""
error_types = erc_result.get('error_types', {})
if 'pin_not_connected' in error_types:
# Read the schematic, find unconnected pins, add connections
# ... fix logic ...
return True
if 'label_dangling' in error_types:
# Move labels to correct pin positions
# ... fix logic ...
return True
return False # No fixable errors found
final = validate_and_fix_loop("output/schematic.kicad_sch", my_fixer)
当用户有一个存在 ERC 错误的现有 .kicad_sch 文件(而非生成新原理图)时,使用此工作流程。
始终 使用 --format json -o file.json --severity-all。切勿将 kicad-cli 输出管道传输到 stdout —— 它会将 JSON 写入 -o 指定的文件。
在 macOS 上,kicad-cli 需要环境变量来查找标准库:
KICAD9_SYMBOL_DIR="/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols" \
KICAD9_FOOTPRINT_DIR="/Applications/KiCad/KiCad.app/Contents/SharedSupport/footprints" \
/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli sch erc \
--format json --severity-all -o /tmp/erc_result.json schematic.kicad_sch
或者使用辅助函数:
from kicad_sch_helpers import run_erc
result = run_erc("schematic.kicad_sch", env_vars={
"KICAD9_SYMBOL_DIR": "/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols",
"KICAD9_FOOTPRINT_DIR": "/Applications/KiCad/KiCad.app/Contents/SharedSupport/footprints",
})
import json
with open("/tmp/erc_result.json") as f:
report = json.load(f)
violations = []
for sheet in report.get("sheets", []):
violations.extend(sheet.get("violations", []))
# Categorize by severity and type
errors = [v for v in violations if v.get("severity") == "error"]
warnings = [v for v in violations if v.get("severity") == "warning"]
by_type = {}
for v in violations:
t = v.get("type", "unknown")
by_type.setdefault(t, []).append(v)
print(f"Errors: {len(errors)}, Warnings: {len(warnings)}")
for t, items in sorted(by_type.items()):
print(f" {t}: {len(items)}")
注意: JSON 格式将违规项嵌套在 sheets[].violations[] 下,而不是顶层。
切勿手动编辑 .kicad_sch 文件 —— 始终编写 Python 脚本。S-expression 格式对空格和括号平衡很敏感。
关键实用函数(均在 scripts/kicad_sch_helpers.py 中):
from kicad_sch_helpers import (
find_block, # Find balanced parenthesized block
remove_block_with_whitespace, # Clean removal preserving formatting
extract_embedded_symbol, # Extract from lib_symbols section
convert_embedded_to_library, # Embedded → standalone library format
find_by_uuid, # Locate element by UUID
remove_by_uuid, # Remove element by UUID
replace_lib_id, # Bulk lib_id replacement
replace_footprint, # Bulk footprint replacement
fix_annotation_suffixes, # Add numeric suffixes to bare refs
create_pwr_flag_block, # Generate PWR_FLAG s-expression
)
通过 UUID 移除符号:
content = remove_by_uuid(content, "uuid-string", "symbol")
在所有实例中替换 lib_id:
content = replace_lib_id(content, "Connector:Conn_01x04", "CubeSat_SDR:Conn_01x04")
添加 PWR_FLAG 以修复 power_pin_not_driven:
pwr_block = create_pwr_flag_block(
x=34.29, y=77.47, ref_num=7,
project_name="my_project",
root_uuid="5fb33c66-7637-43ae-9eef-34b4f23f6cfb"
)
# Insert before the final closing paren
last_close = content.rstrip().rfind(')')
content = content[:last_close] + '\n' + pwr_block + '\n' + content[last_close:]
在 .kicad_pro 中抑制警告:
import json
with open("project.kicad_pro") as f:
pro = json.load(f)
pro["erc"]["rule_severities"]["lib_symbol_mismatch"] = "ignore"
with open("project.kicad_pro", "w") as f:
json.dump(pro, f, indent=2)
f.write('\n')
每次批量修复后始终运行 ERC。一些修复会暴露新的问题:
在运行修复脚本之前备份原理图。 使用 shutil.copy2()。
使用 KiCad 9 验证 KiCad 8 原理图时,预计会出现以下几类问题:
| KiCad 8 名称 | KiCad 9 名称 | 备注 |
|---|---|---|
Connector:Conn_01x04 | Connector:Conn_01x04_Pin | 所有单排连接器已重命名 |
Connector:Conn_01x06 | Connector:Conn_01x06_Pin | 相同模式 |
Connector:Conn_02x20 | Connector:Conn_02x20_Pin | 所有双排连接器也是 |
Connector:SMA | 已移除 | 使用自定义库或 Connector:Coaxial |
Connector:TestPoint | Connector:TestPoint (已移动) | 可能具有不同的引脚布局 |
Regulator_Linear:AMS1117 | 有 4 个引脚 (增加了 ADJ) | 3 引脚原理图将不匹配 |
修复策略: 创建一个包含 KiCad 8 符号版本的项目级自定义库。将 lib_ids 更改为指向自定义库。不要 尝试更新到 KiCad 9 版本 —— 引脚位置不同,会破坏导线连接。
关键:不要用 KiCad 9 版本替换嵌入的 Device:C/R/L 符号。
KiCad 9 更改了无源器件的引脚位置:
Device:C 引脚在 (0, ±2.54)Device:C 引脚在 (0, ±3.81)替换嵌入的符号会破坏连接到每个电容器、电阻器和电感器的每根导线。相反,在 .kicad_pro 中抑制 lib_symbol_mismatch —— 嵌入的 KiCad 8 符号工作正常。
| KiCad 8 封装 | KiCad 9 封装 |
|---|---|
SW_Push_1P1T_NO_6x3.5mm | SW_Push_1P1T_NO_CK_PTS125Sx43SMTR |
SMA_Amphenol_901-143_Vertical | SMA_Amphenol_901-144_Vertical |
使用 replace_footprint() 进行批量更新。
KiCad 9 要求所有参考标识符以数字结尾。 像 C_RX1B_N 或 J_PWR 这样的参考标识符会在 GUI 中导致 "Item not annotated" 错误(但 CLI ERC 中不会)。
from kicad_sch_helpers import fix_annotation_suffixes
content = fix_annotation_suffixes(content) # Adds "1" suffix to bare refs
lib_symbols 中提取).kicad_pro 中抑制 lib_symbol_mismatch(安全 —— 嵌入的符号仍然有效)multiple_net_names当标准 KiCad 库在不同版本之间发生变化时,创建项目级库以保持兼容性。
sym-lib-table(在项目根目录中):
(sym_lib_table
(version 7)
(lib (name "CubeSat_SDR")(type "KiCad")(uri "${KIPRJMOD}/libraries/cubesat_sdr.kicad_sym")(options "")(descr "Project custom symbols"))
)
fp-lib-table(在项目根目录中):
(fp_lib_table
(version 7)
(lib (name "CubeSat_SDR")(type "KiCad")(uri "${KIPRJMOD}/libraries/cubesat_sdr.pretty")(options "")(descr "Project custom footprints"))
)
使用 ${KIPRJMOD} 作为可移植路径 —— 它解析为项目目录。
当从标准库符号迁移到自定义符号时:
from kicad_sch_helpers import extract_embedded_symbol, convert_embedded_to_library
# Read schematic
with open("schematic.kicad_sch") as f:
sch = f.read()
# Extract a symbol from the embedded lib_symbols section
block = extract_embedded_symbol(sch, "Connector:Conn_01x04")
# Convert from embedded format (prefix:Name) to library format (Name)
lib_block = convert_embedded_to_library(block, "Connector", "Conn_01x04")
# Append to custom library file (before the final closing paren)
with open("libraries/custom.kicad_sym") as f:
lib = f.read()
close_pos = lib.rstrip().rfind(')')
lib = lib[:close_pos] + '\t' + lib_block + '\n' + lib[close_pos:]
with open("libraries/custom.kicad_sym", "w") as f:
f.write(lib)
在 .kicad_sch 嵌入的 lib_symbols 中:顶层是 (symbol "Library:Name" ...),子符号使用 (symbol "Name_0_1" ...)。
在 .kicad_sym 库文件中:顶层是 (symbol "Name" ...),子符号使用 (symbol "Name_0_1" ...)。
唯一的区别是顶层名称失去了其库前缀。
for i, (ref, val) in enumerate(zip(refs, values)):
place_2pin_vertical(sch, "Device:C", ref, val,
x=snap(start_x + i * 8), y=snap(cap_y),
top_net=power_net, bottom_net="GND",
footprint=f"Capacitor_SMD:{fp}")
标准 KiCad 2 引脚无源器件符号具有:
在原理图中旋转 0 度,位置为 (sx, sy):
引脚 1 原理图位置:(sx, sy - 2.54)
引脚 2 原理图位置:(sx, sy + 2.54)
place_2pin_vertical(sch, "Device:C", "C1", "100nF", x=snap(100), y=snap(150), top_net="VCC_3V3", bottom_net="GND", footprint="Capacitor_SMD:C_0402_1005Metric")
# Always use connect_pin — never compute positions manually
signal_map = {
"TX1A_P": "TX1A_P",
"TX1A_N": "TX1A_N",
"SPI_CLK": "SPI_CLK",
# ... all signal pins
}
for pin_name, net_name in signal_map.items():
sch.connect_pin("U1", pin_name, net_name, wire_dx=-7.62)
# Power pins
for pin_name in ["VDDD1P3", "VDDA1P3", "VDDD1P8"]:
sch.connect_pin("U1", pin_name, f"VCC_{pin_name}", wire_dy=5.08)
# Unused pins — EVERY unused pin needs this
for pin_name in ["AUXDAC1", "AUXDAC2", "AUXADC", "TEMP_SENS"]:
sch.connect_pin_noconnect("U1", pin_name)
sch.place("CubeSat_SDR:AP2112K", "U4", "AP2112K-3.3",
x=snap(100), y=snap(180),
footprint="Package_TO_SOT_SMD:SOT-23-5")
wl(sch, 100, 180, 'VIN', SOT5_PINS, "VCC_5V", dx=-7.62)
wl(sch, 100, 180, 'EN', SOT5_PINS, "VCC_5V", dx=-7.62)
wl(sch, 100, 180, 'GND', SOT5_PINS, "GND", dy=5.08)
wl(sch, 100, 180, 'VOUT', SOT5_PINS, "VCC_3V3", dx=7.62)
# NC pin — no-connect flag, NOT a label
nc_x, nc_y = pin_abs(100, 180, *SOT5_PINS['NC'])
sch.nc(nc_x, nc_y)
# PWR_FLAG on output net
sch.place_pwr_flag(x=snap(115), y=snap(178), net_name="VCC_3V3")
这些教训来自调试一个真实的 119 个元件的 CubeSat SDR 原理图:
切勿猜测引脚位置。 即使偏离 1.27mm(一个网格单位)也会导致 ERC 错误。始终读取 .kicad_sym 文件并使用计算出的位置。
Y 轴翻转是错误的首要来源。 库 Y 向上与原理图 Y 向下意味着你必须对 Y 取负。库中位置为 (0, 25.4) 的引脚在原理图中的位置是 (sx, sy - 25.4) —— 不是 (sx, sy + 25.4)。弄错这一点会将标签放置在距离其引脚 50mm 远的地方。
SOT-23-5 VOUT 与 NC 的混淆 会悄无声息地破坏 LDO 电路。VOUT=(7.62, 2.54), NC=(7.62, -2.54)。它们仅在 Y 符号上不同。经过 Y 翻转后,VOUT 在原理图中位于 NC 上方。始终根据库文件进行验证。
子符号命名会悄无声息地破坏 KiCad。 如果你写 (symbol "Device:R_0_1" ...) 而不是 (symbol "R_0_1" ...),KiCad 可能会打开文件,但所有符号都显示为损坏。始终运行 fix_subsymbol_names()。
PWR_FLAG 的需求比你想象的更频繁。 任何由输出引脚类型为 passive 的稳压器驱动的网络都需要一个。同时,在 GND 上也添加一个。缺少 PWR_FLAG 会导致该网络上每个元件都出现 power_pin_not_driven 错误。
网格对齐可以防止数百个警告。 一个未对齐的元件会级联导致所有连接的导线和标签出现未对齐警告。从一开始就对所有内容进行对齐。
考虑每一个引脚。 系统地检查引脚列表。每个引脚必须:通过导线+标签连接、连接到电源符号、或标记为无连接。即使遗漏一个引脚也会产生错误。
括号平衡检查。 KiCad S-expressions 必须具有完美平衡的括号。在生成结束时添加检查:
depth = sum(1 if c == '(' else -1 if c == ')' else 0 for c in content) assert depth == 0, f"Parenthesis imbalance: depth={depth}"
不要用 KiCad 9 版本替换嵌入的 Device:C/R/L。 KiCad 9 将无源器件的引脚位置从 ±2.54 更改为 ±3.81。替换嵌入的符号会破坏每个导线连接。改为抑制 lib_symbol_mismatch。
kicad-cli JSON 输出到文件,而不是 stdout。 始终使用 -o /tmp/result.json。管道传输到 python 会得到空的 stdin。JSON 嵌套在 sheets[].violations[] 下,而不是顶层。
CLI ERC 不检查注释。 GUI 会标记不以数字结尾的参考标识符(例如 C_RX1B_N)为 "Item not annotated",但 CLI ERC 会悄无声息地通过。迁移时始终运行 fix_annotation_suffixes()。
macOS 的 kicad-cli 需要环境变量。 没有 KICAD9_SYMBOL_DIR 和 KICAD9_FOOTPRINT_DIR,kicad-cli 无法找到全局库,并会产生错误的 lib_symbol_issues 警告。
在 macOS 上不要使用 grep -P。 不支持 PCRE 模式。在原理图文件上进行所有模式匹配时使用 Python 正则表达式。
为自定义符号创建项目级库表。 使用 ${KIPRJMOD} 作为可移植路径。提取嵌入的符号来填充库 —— 不要从头重写它们。
在运行修复脚本之前进行备份。 括号平衡被破坏会导致原理图无法加载。在进行任何修改之前使用 shutil.copy2()。
不要抑制 ERC 错误,只抑制警告。 在 KiCad 8→9 迁移中抑制 lib_symbol_mismatch(警告)是安全的。切勿抑制实际的错误,如 pin_not_connected 或 power_pin_not_driven。
scripts/kicad_sch_helpers.py — Python 辅助库(始终使用此库)references/kicad_sexpression_format.md — KiCad S-expression 格式规范、坐标系、常见 ERC 错误及修复方法在生成任何原理图之前,请阅读 references/kicad_sexpression_format.md 以了解坐标系、子符号命名规则和 PWR_FLAG 要求。
snap() 对齐到 1.27mm 网格connect_pin() / wl() 连接,要么通过 connect_pin_noconnect() / nc() 标记fix_subsymbol_names() 修复fix_annotation_suffixes())lib_symbol_mismatch)replace_footprint()Generate ERC-clean KiCad 8/9 schematics by writing Python scripts that use computed pin positions — never guess coordinates. Also fix ERC errors on existing schematics and handle KiCad 8→9 migration.
The #1 cause of broken schematics is guessed pin positions. When connecting labels to IC pins, you MUST compute exact coordinates using the symbol definition's pin positions and the coordinate transform formula. The helper library in scripts/kicad_sch_helpers.py does this automatically.
Symbol libraries (.kicad_sym) use Y-up (math convention). Schematics (.kicad_sch) use Y-down (screen convention). This means you MUST negate the Y coordinate when transforming from library to schematic space. Forgetting this will place labels 10-50mm away from their pins, causing massive pin_not_connected and label_dangling errors.
Transform formula — pin at library (px, py), symbol placed at schematic (sx, sy) with rotation R:
Always use pin_abs() from the helper library — never compute these by hand.
User describes circuit
|
Read symbol libraries (.kicad_sym) to get pin positions
|
Build pin position dictionaries for every multi-pin IC
|
Write Python script using SchematicBuilder (from helper library)
- Use connect_pin() for IC pins (computes positions automatically)
- Use place_2pin_vertical() for passives (knows pin 1/2 positions)
|
Generate .kicad_sch file
|
Post-process with fix_subsymbol_names()
|
Run ERC validation: kicad-cli sch erc --format json
|
Parse errors -> fix script -> regenerate -> repeat (max 5 iterations)
Before running any ERC validation, verify that kicad-cli is on the system PATH. Run:
which kicad-cli 2>/dev/null || where kicad-cli 2>/dev/null
If not found , check for a local KiCad installation and offer to create a symlink:
macOS:
# Check if KiCad is installed as an app
KICAD_CLI="/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli"
if [ -f "$KICAD_CLI" ]; then
echo "Found kicad-cli inside KiCad.app. Creating symlink..."
sudo ln -sf "$KICAD_CLI" /usr/local/bin/kicad-cli
echo "Done! kicad-cli is now available on PATH."
else
echo "KiCad not found. Install from https://www.kicad.org/download/macos/"
fi
Linux:
# kicad-cli is typically installed alongside KiCad via package manager
# Check common locations
for p in /usr/bin/kicad-cli /usr/local/bin/kicad-cli /snap/kicad/current/bin/kicad-cli; do
if [ -f "$p" ]; then
echo "Found kicad-cli at $p"
# If not on PATH, symlink it
if ! command -v kicad-cli &>/dev/null; then
sudo ln -sf "$p" /usr/local/bin/kicad-cli
fi
break
fi
done
# If still not found:
# Ubuntu/Debian: sudo apt install kicad
# Fedora: sudo dnf install kicad
# Arch: sudo pacman -S kicad
# Or install from https://www.kicad.org/download/linux/
Windows:
# Check standard install path
$kicadCli = "C:\Program Files\KiCad\8.0\bin\kicad-cli.exe"
if (Test-Path $kicadCli) {
Write-Host "Found kicad-cli. Add to PATH:"
Write-Host ' [Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";C:\Program Files\KiCad\8.0\bin", "User")'
} else {
Write-Host "KiCad not found. Install from https://www.kicad.org/download/windows/"
}
Tell the user what you found and ask for confirmation before creating any symlinks. If kicad-cli is truly not installed, provide the download link for their OS and stop — ERC validation requires it.
Before writing any code, gather:
For every IC and multi-pin component , read its .kicad_sym definition to get exact pin names, numbers, positions, and types. You cannot connect pins correctly without this data.
from kicad_sch_helpers import SymbolLibrary
lib = SymbolLibrary()
lib.load_from_kicad_sym("path/to/library.kicad_sym")
# Now you know exact pin positions
ad9363 = lib.get("AD9363ABCZ")
for pin in ad9363.pins:
print(f"{pin.name} ({pin.number}): at ({pin.x}, {pin.y}), type={pin.pin_type}")
For manual/inline approaches , build a pin dictionary from the library:
# Extract from .kicad_sym file — these are LIBRARY coordinates (Y-up)
AD_PINS = {
'TX1A_P': (-17.78, 25.40),
'TX1A_N': (-17.78, 22.86),
'SPI_CLK': (-17.78, -10.16),
# ... all pins
}
# SOT-23-5 packages (common for LDOs like AP2112K, ME6211):
SOT5_PINS = {
'VIN': (-7.62, 2.54), # Pin 1 - top left
'GND': ( 0.00, -7.62), # Pin 2 - bottom center
'EN': (-7.62, -2.54), # Pin 3 - bottom left
'NC': ( 7.62, -2.54), # Pin 4 - bottom right <- NOT VOUT!
'VOUT': ( 7.62, 2.54), # Pin 5 - top right <- THIS is VOUT!
}
WARNING — SOT-23-5 pin trap: VOUT is at (7.62, +2.54) and NC is at (7.62, -2.54). These are only 5.08mm apart. Confusing them means your LDO output goes nowhere. Always verify from the actual library file.
Use SchematicBuilder for all schematic construction. The key method is connect_pin() which computes exact pin positions automatically:
from kicad_sch_helpers import SchematicBuilder, SymbolLibrary, snap
lib = SymbolLibrary()
lib.load_from_kicad_sym("custom_symbols.kicad_sym")
sch = SchematicBuilder(symbol_lib=lib, project_name="my_project")
sch.set_lib_symbols(lib_symbols_content) # Raw S-expression for embedded symbols
# Place an IC
sch.place("CubeSat_SDR:AD9363ABCZ", "U1", "AD9363ABCZ",
x=320, y=200, footprint="CubeSat_SDR:AD9363_BGA144")
# Connect pins by NAME — coordinates computed automatically
sch.connect_pin("U1", "TX1A_P", "TX1A_P", wire_dx=-5.08)
sch.connect_pin("U1", "SPI_CLK", "SPI_CLK", wire_dy=-5.08)
sch.connect_pin("U1", "GND", "GND", wire_dy=5.08)
# For unused pins, add no-connect flags
sch.connect_pin_noconnect("U1", "AUXDAC1")
# For 2-pin passives, use convenience helpers
from kicad_sch_helpers import place_2pin_vertical
place_2pin_vertical(sch, "Device:C", "C1", "100nF",
x=snap(230), y=snap(155),
top_net="VCC_3V3", bottom_net="GND",
footprint="Capacitor_SMD:C_0402_1005Metric")
If using inline pin dictionaries (without SymbolLibrary), use pin_abs():
from kicad_sch_helpers import pin_abs, snap
GRID = 1.27
def wl(sch, sx, sy, pin_name, pins_dict, net, dx=0, dy=0, rot=0, label_angle=0):
"""Wire + Label: connect an IC pin to a net label."""
px, py = pin_abs(sx, sy, pins_dict[pin_name][0], pins_dict[pin_name][1], rot)
ex, ey = snap(px + dx), snap(py + dy)
if dx != 0 or dy != 0:
sch.w(px, py, ex, ey)
sch.label(net, ex, ey, label_angle)
# Usage:
wl(sch, 320, 200, 'TX1A_P', AD_PINS, "TX1A_P", dx=-7.62)
Every symbol referenced must be embedded in the schematic's lib_symbols. Three critical rules:
(symbol "Device:R" ...)(symbol "R_0_1" ...) not (symbol "Device:R_0_1" ...)fix_subsymbol_names() to catch any mistakesUse fix_subsymbol_names() as a post-processing step:
from kicad_sch_helpers import fix_subsymbol_names
content = sch.build(title="My Schematic")
content = fix_subsymbol_names(content)
The regex-based fixer handles nested sub-symbols at any depth and any library prefix format.
Every coordinate in the schematic must be a multiple of 1.27mm. Use snap():
from kicad_sch_helpers import snap
# snap() rounds to nearest 1.27mm grid point
x = snap(123.45) # -> 124.46 (nearest multiple of 1.27)
Apply snap() to: component positions, wire endpoints, label positions, no-connect positions, and PWR_FLAG positions. The connect_pin() and pin_abs() functions do this automatically.
For every power net that originates from a voltage regulator (not a power symbol), add a PWR_FLAG to prevent "power_pin_not_driven" errors:
# Define PWR_FLAG in lib_symbols (see references/kicad_sexpression_format.md)
# Then place on each power net:
sch.place_pwr_flag(x=70, y=78, net_name="VCC_3V3A")
Rule of thumb : If a net is driven by a component whose output pin type is passive (not power_out), that net needs a PWR_FLAG. This includes most LDO regulators.
Also place PWR_FLAG on GND nets that don't have a dedicated GND power symbol driving them.
Every unused pin on every IC MUST have a no-connect flag. Missing no-connect flags cause pin_not_connected errors.
# Using SchematicBuilder:
sch.connect_pin_noconnect("U1", "AUXDAC1")
# Or manually with pin_abs:
px, py = pin_abs(sx, sy, pin_x, pin_y, rotation)
sch.nc(px, py)
from kicad_sch_helpers import run_erc
result = run_erc("output/schematic.kicad_sch")
print(f"Errors: {result['errors']}, Warnings: {result['warnings']}")
if result['errors'] > 0:
for detail in result['details']:
if detail.get('severity') == 'error':
print(f" {detail['type']}: {detail.get('description', '')}")
For complex schematics, use the validation loop:
from kicad_sch_helpers import validate_and_fix_loop
def my_fixer(erc_result, iteration):
"""Analyze ERC errors and apply fixes. Return True if fixes applied."""
error_types = erc_result.get('error_types', {})
if 'pin_not_connected' in error_types:
# Read the schematic, find unconnected pins, add connections
# ... fix logic ...
return True
if 'label_dangling' in error_types:
# Move labels to correct pin positions
# ... fix logic ...
return True
return False # No fixable errors found
final = validate_and_fix_loop("output/schematic.kicad_sch", my_fixer)
When the user has an existing .kicad_sch with ERC errors (not generating a new schematic), use this workflow.
Always use --format json -o file.json --severity-all. Never pipe kicad-cli output to stdout — it writes JSON to the file specified by -o.
On macOS, kicad-cli needs environment variables to find the standard libraries:
KICAD9_SYMBOL_DIR="/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols" \
KICAD9_FOOTPRINT_DIR="/Applications/KiCad/KiCad.app/Contents/SharedSupport/footprints" \
/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli sch erc \
--format json --severity-all -o /tmp/erc_result.json schematic.kicad_sch
Or use the helper:
from kicad_sch_helpers import run_erc
result = run_erc("schematic.kicad_sch", env_vars={
"KICAD9_SYMBOL_DIR": "/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols",
"KICAD9_FOOTPRINT_DIR": "/Applications/KiCad/KiCad.app/Contents/SharedSupport/footprints",
})
import json
with open("/tmp/erc_result.json") as f:
report = json.load(f)
violations = []
for sheet in report.get("sheets", []):
violations.extend(sheet.get("violations", []))
# Categorize by severity and type
errors = [v for v in violations if v.get("severity") == "error"]
warnings = [v for v in violations if v.get("severity") == "warning"]
by_type = {}
for v in violations:
t = v.get("type", "unknown")
by_type.setdefault(t, []).append(v)
print(f"Errors: {len(errors)}, Warnings: {len(warnings)}")
for t, items in sorted(by_type.items()):
print(f" {t}: {len(items)}")
Note: The JSON format nests violations under sheets[].violations[], not at the top level.
Never manually edit.kicad_sch files — always write Python scripts. The s-expression format is sensitive to whitespace and parenthesis balance.
Key utility functions (all in scripts/kicad_sch_helpers.py):
from kicad_sch_helpers import (
find_block, # Find balanced parenthesized block
remove_block_with_whitespace, # Clean removal preserving formatting
extract_embedded_symbol, # Extract from lib_symbols section
convert_embedded_to_library, # Embedded → standalone library format
find_by_uuid, # Locate element by UUID
remove_by_uuid, # Remove element by UUID
replace_lib_id, # Bulk lib_id replacement
replace_footprint, # Bulk footprint replacement
fix_annotation_suffixes, # Add numeric suffixes to bare refs
create_pwr_flag_block, # Generate PWR_FLAG s-expression
)
Remove a symbol by UUID:
content = remove_by_uuid(content, "uuid-string", "symbol")
Replace a lib_id across all instances:
content = replace_lib_id(content, "Connector:Conn_01x04", "CubeSat_SDR:Conn_01x04")
Add PWR_FLAG to fix power_pin_not_driven:
pwr_block = create_pwr_flag_block(
x=34.29, y=77.47, ref_num=7,
project_name="my_project",
root_uuid="5fb33c66-7637-43ae-9eef-34b4f23f6cfb"
)
# Insert before the final closing paren
last_close = content.rstrip().rfind(')')
content = content[:last_close] + '\n' + pwr_block + '\n' + content[last_close:]
Suppress warnings in .kicad_pro:
import json
with open("project.kicad_pro") as f:
pro = json.load(f)
pro["erc"]["rule_severities"]["lib_symbol_mismatch"] = "ignore"
with open("project.kicad_pro", "w") as f:
json.dump(pro, f, indent=2)
f.write('\n')
Always run ERC after each batch of fixes. Some fixes expose new issues:
Back up the schematic before running fix scripts. Use shutil.copy2().
When validating a KiCad 8 schematic with KiCad 9, expect these categories of issues:
| KiCad 8 Name | KiCad 9 Name | Notes |
|---|---|---|
Connector:Conn_01x04 | Connector:Conn_01x04_Pin | All single-row connectors renamed |
Connector:Conn_01x06 | Connector:Conn_01x06_Pin | Same pattern |
Connector:Conn_02x20 | Connector:Conn_02x20_Pin | All dual-row connectors too |
Fix strategy: Create a project-level custom library with KiCad 8 symbol versions. Change lib_ids to point to the custom library. Do NOT try to update to KiCad 9 versions — pin positions differ and will break wire connections.
CRITICAL: Do NOT replace embedded Device:C/R/L symbols with KiCad 9 versions.
KiCad 9 changed passive pin positions:
Device:C pins at (0, ±2.54)Device:C pins at (0, ±3.81)Replacing embedded symbols breaks every wire connected to every capacitor, resistor, and inductor. Instead, suppress lib_symbol_mismatch in .kicad_pro — the embedded KiCad 8 symbols work fine.
| KiCad 8 Footprint | KiCad 9 Footprint |
|---|---|
SW_Push_1P1T_NO_6x3.5mm | SW_Push_1P1T_NO_CK_PTS125Sx43SMTR |
SMA_Amphenol_901-143_Vertical | SMA_Amphenol_901-144_Vertical |
Use replace_footprint() for bulk updates.
KiCad 9 requires all reference designators to end with a digit. References like C_RX1B_N or J_PWR will cause "Item not annotated" errors in the GUI (but NOT in CLI ERC).
from kicad_sch_helpers import fix_annotation_suffixes
content = fix_annotation_suffixes(content) # Adds "1" suffix to bare refs
lib_symbols)lib_symbol_mismatch in .kicad_pro (safe — embedded symbols still work)multiple_net_names if intentional dual-naming existsWhen standard KiCad libraries change between versions, create project-level libraries to preserve compatibility.
sym-lib-table (in project root):
(sym_lib_table
(version 7)
(lib (name "CubeSat_SDR")(type "KiCad")(uri "${KIPRJMOD}/libraries/cubesat_sdr.kicad_sym")(options "")(descr "Project custom symbols"))
)
fp-lib-table (in project root):
(fp_lib_table
(version 7)
(lib (name "CubeSat_SDR")(type "KiCad")(uri "${KIPRJMOD}/libraries/cubesat_sdr.pretty")(options "")(descr "Project custom footprints"))
)
Use ${KIPRJMOD} for portable paths — it resolves to the project directory.
When migrating from standard library symbols to custom ones:
from kicad_sch_helpers import extract_embedded_symbol, convert_embedded_to_library
# Read schematic
with open("schematic.kicad_sch") as f:
sch = f.read()
# Extract a symbol from the embedded lib_symbols section
block = extract_embedded_symbol(sch, "Connector:Conn_01x04")
# Convert from embedded format (prefix:Name) to library format (Name)
lib_block = convert_embedded_to_library(block, "Connector", "Conn_01x04")
# Append to custom library file (before the final closing paren)
with open("libraries/custom.kicad_sym") as f:
lib = f.read()
close_pos = lib.rstrip().rfind(')')
lib = lib[:close_pos] + '\t' + lib_block + '\n' + lib[close_pos:]
with open("libraries/custom.kicad_sym", "w") as f:
f.write(lib)
In .kicad_sch embedded lib_symbols: top-level is (symbol "Library:Name" ...), sub-symbols use (symbol "Name_0_1" ...).
In .kicad_sym library files: top-level is (symbol "Name" ...), sub-symbols use (symbol "Name_0_1" ...).
The only difference is the top-level name loses its library prefix.
for i, (ref, val) in enumerate(zip(refs, values)):
place_2pin_vertical(sch, "Device:C", ref, val,
x=snap(start_x + i * 8), y=snap(cap_y),
top_net=power_net, bottom_net="GND",
footprint=f"Capacitor_SMD:{fp}")
Standard KiCad 2-pin passive symbols have:
With rotation 0 at schematic (sx, sy):
Pin 1 schematic position: (sx, sy - 2.54)
Pin 2 schematic position: (sx, sy + 2.54)
place_2pin_vertical(sch, "Device:C", "C1", "100nF", x=snap(100), y=snap(150), top_net="VCC_3V3", bottom_net="GND", footprint="Capacitor_SMD:C_0402_1005Metric")
# Always use connect_pin — never compute positions manually
signal_map = {
"TX1A_P": "TX1A_P",
"TX1A_N": "TX1A_N",
"SPI_CLK": "SPI_CLK",
# ... all signal pins
}
for pin_name, net_name in signal_map.items():
sch.connect_pin("U1", pin_name, net_name, wire_dx=-7.62)
# Power pins
for pin_name in ["VDDD1P3", "VDDA1P3", "VDDD1P8"]:
sch.connect_pin("U1", pin_name, f"VCC_{pin_name}", wire_dy=5.08)
# Unused pins — EVERY unused pin needs this
for pin_name in ["AUXDAC1", "AUXDAC2", "AUXADC", "TEMP_SENS"]:
sch.connect_pin_noconnect("U1", pin_name)
sch.place("CubeSat_SDR:AP2112K", "U4", "AP2112K-3.3",
x=snap(100), y=snap(180),
footprint="Package_TO_SOT_SMD:SOT-23-5")
wl(sch, 100, 180, 'VIN', SOT5_PINS, "VCC_5V", dx=-7.62)
wl(sch, 100, 180, 'EN', SOT5_PINS, "VCC_5V", dx=-7.62)
wl(sch, 100, 180, 'GND', SOT5_PINS, "GND", dy=5.08)
wl(sch, 100, 180, 'VOUT', SOT5_PINS, "VCC_3V3", dx=7.62)
# NC pin — no-connect flag, NOT a label
nc_x, nc_y = pin_abs(100, 180, *SOT5_PINS['NC'])
sch.nc(nc_x, nc_y)
# PWR_FLAG on output net
sch.place_pwr_flag(x=snap(115), y=snap(178), net_name="VCC_3V3")
These lessons came from debugging a real 119-component CubeSat SDR schematic:
Never guess pin positions. Even being off by 1.27mm (one grid unit) causes ERC errors. Always read the .kicad_sym file and use computed positions.
The Y-axis flip is the #1 source of bugs. Library Y-up vs schematic Y-down means you must negate Y. A pin at library (0, 25.4) is at schematic (sx, sy - 25.4) — NOT (sx, sy + 25.4). Getting this wrong places labels 50mm from their pins.
SOT-23-5 VOUT vs NC confusion silently breaks LDO circuits. VOUT=(7.62, 2.54), NC=(7.62, -2.54). They differ only in Y sign. After the Y-flip, VOUT is above NC in the schematic. Always verify against the library.
Sub-symbol naming breaks KiCad silently. If you write (symbol "Device:R_0_1" ...) instead of (symbol "R_0_1" ...), KiCad may open the file but all symbols appear broken. Always run fix_subsymbol_names().
PWR_FLAG is needed more often than you think. Any net driven by a regulator with passive-type output pins needs one. Also add one on GND. Missing PWR_FLAGs cause power_pin_not_driven errors on every component on that net.
Grid snapping prevents hundreds of warnings. A single off-grid component cascades into off-grid warnings for all connected wires and labels. Snap everything from the start.
Account for every pin. Go through the pin list systematically. Every pin must be either: connected via wire+label, connected to a power symbol, or flagged with no_connect. Missing even one pin produces an error.
scripts/kicad_sch_helpers.py — Python helper library (always use this)references/kicad_sexpression_format.md — KiCad S-expression format specification, coordinate system, common ERC errors and fixesRead references/kicad_sexpression_format.md before generating any schematic to understand the coordinate system, sub-symbol naming rules, and PWR_FLAG requirements.
snap()connect_pin() / wl() or flagged with connect_pin_noconnect() / nc()fix_subsymbol_names()fix_annotation_suffixes())lib_symbol_mismatch instead)replace_footprint())KICAD9_SYMBOL_DIR, KICAD9_FOOTPRINT_DIR)--severity-all to catch all warning typesWeekly Installs
51
Repository
First Seen
Feb 21, 2026
Security Audits
Gen Agent Trust HubWarnSocketPassSnykWarn
Installed on
gemini-cli51
github-copilot51
codex51
opencode51
cursor51
kimi-cli50
Skills CLI 使用指南:AI Agent 技能包管理器安装与管理教程
52,700 周安装
OpenTelemetry 语义约定指南:标准化遥测属性、跨栈关联与可观测性最佳实践
46 周安装
Flutter BLoC 模式最佳实践指南:状态管理、依赖注入与导航规范
46 周安装
skill-comply:AI编码智能体合规性自动化测试工具 - 验证Claude技能规则遵循
46 周安装
ljg-card:内容转PNG图片工具,支持长图、信息图、漫画等多种视觉化格式
46 周安装
Trello项目管理技能:Membrane集成指南与API自动化操作教程
46 周安装
Linear CLI Watch:实时监听Linear问题变更,支持自定义轮询与JSON输出的命令行工具
46 周安装
Connector:SMA| Removed |
| Use custom library or Connector:Coaxial |
Connector:TestPoint | Connector:TestPoint (moved) | May have different pin layout |
Regulator_Linear:AMS1117 | Has 4 pins (added ADJ) | 3-pin schematic won't match |
Parenthesis balance check. KiCad S-expressions must have perfectly balanced parentheses. Add a check at the end of generation:
depth = sum(1 if c == '(' else -1 if c == ')' else 0 for c in content) assert depth == 0, f"Parenthesis imbalance: depth={depth}"
Don't replace embedded Device:C/R/L with KiCad 9 versions. KiCad 9 changed passive pin positions from ±2.54 to ±3.81. Replacing embedded symbols breaks every wire connection. Suppress lib_symbol_mismatch instead.
kicad-cli JSON goes to file, not stdout. Always use -o /tmp/result.json. Piping to python gives empty stdin. The JSON is nested under sheets[].violations[], not at the top level.
CLI ERC doesn't check annotations. The GUI flags "Item not annotated" for references not ending with a digit (e.g., C_RX1B_N), but CLI ERC silently passes. Always run fix_annotation_suffixes() when migrating.
macOS kicad-cli needs environment variables. Without KICAD9_SYMBOL_DIR and KICAD9_FOOTPRINT_DIR, kicad-cli can't find the global libraries and produces false lib_symbol_issues warnings.
Don't usegrep -P on macOS. PCRE mode is not supported. Use Python regex for all pattern matching on schematic files.
Create project-level library tables for custom symbols. Use ${KIPRJMOD} for portable paths. Extract embedded symbols to populate the library — don't rewrite them from scratch.
Back up before running fix scripts. A broken parenthesis balance renders the schematic unloadable. Use shutil.copy2() before any modifications.
Don't suppress ERC errors, only warnings. Suppressing lib_symbol_mismatch (warnings) is safe for KiCad 8→9 migration. Never suppress actual errors like pin_not_connected or power_pin_not_driven.