KiCad PCB 文件格式详解
本文档详细说明 KiCad PCB 文件(.kicad_pcb)的文件结构,包含的信息类型,以及如何解析这些数据。
目录
文件格式概述
格式类型
KiCad PCB 文件 使用 S-expression (S表达式) 格式存储。
特点:
- 文本格式,人类可读
- 嵌套括号结构
- 类似 Lisp 语法
- 版本控制友好
文件扩展名 : .kicad_pcb
KiCad 版本: KiCad 9.0+ (当前代码支持版本 20241229)
基本语法
S-表达式语法
scheme
(keyword value)
(keyword "string value")
(keyword (nested data))
(keyword
(child1 data)
(child2 data)
)
规则
- 所有内容都是列表 : 用括号
()包围 - 列表第一个元素是关键字: 标识数据类型
- 字符串用双引号 :
"string value" - 数字不带引号 :
123,45.67 - 注释 : 使用
;开头(很少使用) - 空白字符: 空格、换行、制表符用于格式化
示例对比
Lisp 风格:
lisp
(define x 10)
KiCad PCB 格式:
scheme
(net 1 "GND")
(segment (start 10.5 20.3) (end 30.7 40.9))
顶层结构
完整文件结构
(kicad_pcb
(version 20241229)
(generator "pcbnew")
(generator_version "9.0")
(general
(thickness 1.6002)
(legacy_teardrops no)
)
(paper "A4")
(title_block
(title "Demo")
(rev "2.C")
(company "Kicad")
)
(layers ...)
(setup ...)
(properties ...)
(net 0 "")
(net 1 "GND")
(net 2 "+3V3")
...
(footprint "lib:FPName" ...)
(footprint "lib:FPName" ...)
...
(gr_line ...)
(gr_arc ...)
(gr_circle ...)
(segment ...)
(segment ...)
...
(via ...)
(via ...)
...
(zone ...)
(zone ...)
...
)
顶层元素说明
| 元素 | 用途 | 必需 |
|---|---|---|
kicad_pcb |
文件根标识 | ✅ |
version |
文件格式版本 | ✅ |
generator |
生成器名称 | ✅ |
generator_version |
KiCad 版本 | ✅ |
general |
通用设置 | ⚠️ |
paper |
图纸尺寸 | ⚠️ |
title_block |
标题栏信息 | ⚠️ |
layers |
层定义 | ✅ |
setup |
设置和层叠 | ⚠️ |
properties |
文件属性 | ❌ |
net |
网络定义 | ✅ |
footprint |
封装实例 | ⚠️ |
segment |
轨道段 | ⚠️ |
via |
过孔 | ⚠️ |
zone |
填充区域 | ❌ |
gr_* |
图形对象 | ❌ |
各部分详解
1. 文件头 (Header)
格式
scheme
(kicad_pcb
(version 20241229)
(generator "pcbnew")
(generator_version "9.0")
)
解析代码
python
# kicad_parser.py 不需要显式解析这些
# 它们在文件开头,用于版本验证
说明
- version: KiCad 文件格式版本号(日期格式)
- generator: 生成器名称(通常是 "pcbnew")
- generator_version: KiCad 版本号(如 "9.0")
2. 通用设置 (General)
格式
scheme
(general
(thickness 1.6002) ; 板厚度 (mm)
(legacy_teardrops no) ; 是否使用旧式泪滴
(suppress connect) ; 连接点显示
)
解析代码
python
# 当前 routing 工具不使用这些信息
# 但可以提取板厚度用于阻抗计算
thickness_match = re.search(r'\(thickness\s+([\d.]+)\)', content)
if thickness_match:
board_thickness = float(thickness_match.group(1))
3. 层定义 (Layers)
格式
scheme
(layers
(0 "F.Cu" signal "top_copper") ; 顶层铜
(1 "In1.Cu" signal "internal1") ; 内层1铜
(2 "In2.Cu" signal "internal2") ; 内层2铜
(31 "B.Cu" signal "bottom_copper") ; 底层铜
(9 "F.Adhes" user "F.Adhesive") ; 顶层粘合剂
(11 "B.Adhes" user "B.Adhesive") ; 底层粘合剂
(13 "F.Paste" user) ; 顶层锡膏
(15 "B.Paste" user) ; 底层锡膏
(5 "F.SilkS" user "F.Silkscreen") ; 顶层丝印
(7 "B.SilkS" user "B.Silkscreen") ; 底层丝印
(1 "F.Mask" user) ; 顶层阻焊
(3 "B.Mask" user) ; 底层阻焊
(17 "Dwgs.User" user "User.Drawings") ; 用户绘图
(19 "Cmts.User" user "User.Comments") ; 用户注释
(21 "Eco1.User" user "User.Eco1") ; 用户 Eco1
(23 "Eco2.User" user "User.Eco2") ; 用户 Eco2
(25 "Edge.Cuts" user) ; 板边界
(27 "Margin" user) ; 边缘
(29 "B.CrtYd" user "B.Courtyard") ; 底层装配禁区
(31 "F.CrtYd" user "F.Courtyard") ; 顶层装配禁区
(33 "B.Fab" user) ; 底层 Fab
(35 "F.Fab" user) ; 顶层 Fab
)
解析代码
python
def extract_layers(content: str) -> BoardInfo:
"""Extract layer information from PCB file."""
layers = {}
copper_layers = []
# 查找 layers 部分
layers_match = re.search(r'\(layers\s*((?:\([^)]+\)\s*)+)\)', content, re.DOTALL)
if layers_match:
layers_text = layers_match.group(1)
# 解析每个层条目: (0 "F.Cu" signal)
layer_pattern = r'\((\d+)\s+"([^"]+)"\s+(\w+)'
for m in re.finditer(layer_pattern, layers_text):
layer_id = int(m.group(1))
layer_name = m.group(2)
layer_type = m.group(3)
layers[layer_id] = layer_name
# 只收集铜层
if layer_type == 'signal' and '.Cu' in layer_name:
copper_layers.append(layer_name)
return BoardInfo(layers=layers, copper_layers=copper_layers, ...)
层类型
| 层名后缀 | 类型 | 用途 |
|---|---|---|
.Cu |
signal | 铜层(用于布线) |
.SilkS |
user | 丝印层 |
.Mask |
user | 阻焊层 |
.Paste |
user | 锡膏层 |
.Adhes |
user | 粘合剂层 |
.CrtYd |
user | 装配禁区 |
.Fab |
user | Fab 层 |
User |
user | 用户自定义层 |
在路由中的用途
4. 设置和层叠 (Setup & Stackup)
格式
scheme
(setup
(stackup
(layer "F.SilkS"
(type "Top Silk Screen")
(color "White")
)
(layer "F.Mask"
(type "Top Solder Mask")
(color "Green")
(thickness 0.01) ; mm
)
(layer "F.Cu"
(type "copper")
(thickness 0.035) ; mm: 铜厚
)
(layer "dielectric 1"
(type "core") ; 核心
(thickness 1.51) ; mm
(material "FR4") ; 材料
(epsilon_r 4.5) ; 介电常数
(loss_tangent 0.02) ; 损耗角正切
)
(layer "B.Cu"
(type "copper")
(thickness 0.035)
)
(copper_finish "None")
(dielectric_constraints no)
)
(pad_to_mask_clearance 0)
(allow_soldermask_bridges_in_footprints no)
(tenting front back) ; 阻焊开口
(aux_axis_origin 74.93 140.97) ; 辅助坐标原点
(pcbplotparams
(layerselection 0x00000000_00000000_000010f0_ffffffff)
(disableapertmacros no)
(usegerberextensions no)
(usegerberattributes yes)
(svgprecision 6)
(plotframeref no)
(mode 1)
(useauxorigin no)
)
(net_class_pattern ...) ; 网络类定义
)
解析代码
python
def extract_stackup(content: str) -> List[StackupLayer]:
"""Extract board stackup information."""
stackup = []
# 查找 stackup 部分
stackup_match = re.search(r'\(stackup\s+(.*?)\n\s*\(copper_finish',
content, re.DOTALL)
if not stackup_match:
return stackup
stackup_text = stackup_match.group(1)
# 解析每一层
layer_blocks = re.findall(
r'\(layer\s+"([^"]+)"(.*?)(?=\(layer\s+"|$)',
stackup_text,
re.DOTALL
)
for layer_name, layer_content in layer_blocks:
# 提取类型
type_match = re.search(r'\(type\s+"([^"]+)"\)', layer_content)
layer_type = type_match.group(1) if type_match else 'unknown'
# 提取厚度 (mm)
thickness_match = re.search(r'\(thickness\s+([\d.]+)\)', layer_content)
thickness = float(thickness_match.group(1)) if thickness_match else 0.0
# 提取介电常数
epsilon_match = re.search(r'\(epsilon_r\s+([\d.]+)\)', layer_content)
epsilon_r = float(epsilon_match.group(1)) if epsilon_match else 0.0
# 提取损耗角正切
loss_match = re.search(r'\(loss_tangent\s+([\d.]+)\)', layer_content)
loss_tangent = float(loss_match.group(1)) if loss_match else 0.0
# 提取材料名称
material_match = re.search(r'\(material\s+"([^"]+)"\)', layer_content)
material = material_match.group(1) if material_match else ""
# 只包含铜层和介质层
if layer_type in ('copper', 'core', 'prepreg'):
stackup.append(StackupLayer(
name=layer_name,
layer_type=layer_type,
thickness=thickness,
epsilon_r=epsilon_r,
loss_tangent=loss_tangent,
material=material
))
return stackup
在路由中的用途
| 信息 | 用途 |
|---|---|
| layer thickness | Via barrel 长度计算(长度匹配) |
| epsilon_r | 阻抗控制 |
| layer type | 区分铜层、core、prepreg |
| material | 材料信息 |
5. 网络定义 (Nets)
格式
scheme
(net 0 "") ; 空网络(未连接)
(net 1 "GND") ; 电源网络
(pin_reference "U1" 4) ; 可选:器件引用
(net 2 "+3V3") ; 电源网络
(net 3 "/RESET") ; 信号网络
(net 4 "Net-(U1-Pad5)") ; 自动生成的网络名
(net 5 "unconnected-(U9-TMS-PadB5)") ; 未连接的网络
解析代码
python
def extract_nets(content: str) -> Dict[int, Net]:
"""Extract all net definitions."""
nets = {}
# 匹配: (net 123 "net_name")
net_pattern = r'\(net\s+(\d+)\s+"([^"]*)"\)'
for m in re.finditer(net_pattern, content):
net_id = int(m.group(1))
net_name = m.group(2)
nets[net_id] = Net(net_id=net_id, name=net_name)
return nets
网络命名规则
| 模式 | 示例 | 说明 |
|---|---|---|
| 简单名称 | "GND", "VCC" |
电源网络 |
| 斜杠前缀 | "/RESET", "/CS" |
信号网络 |
| 连接点 | "Net-(U1-Pad5)" |
自动生成(器件-焊盘) |
| 未连接 | "unconnected-(U9-PadB5)" |
未连接的焊盘 |
| 差分对 | "_P", "_N" 后缀 |
人工命名(如 USB_P, USB_N) |
在路由中的用途
- 网络选择: 用户选择要路由的网络
- 差分对检测 : 识别
_P/_N后缀 - 电源网络识别: 识别 GND, VCC 等
- 进度显示: 显示正在路由的网络名
6. 封装和焊盘 (Footprints & Pads)
格式
scheme
(footprint "lib:FP_Name"
(layer "F.Cu")
(uuid "00000000-0000-0000-0000-0000322d32a0")
(at 171.958 42.037) ; X, Y 位置 (mm)
(descr "Description text...")
(tags "Resistor Axial ...") ; 搜索标签
(property "Reference" "R5"
(at 3.81 -2.31 0)
(layer "F.SilkS")
(uuid "...")
(effects
(font (size 1 1) (thickness 0.15))
)
)
(property "Value" "330" ; 器件值
(at 3.81 2.31 0)
(layer "F.Fab")
)
(fp_text reference "R5" ; 参考标识符
(at 0 0 0)
(layer "F.SilkS")
(effects (font (size 1 1)(thickness 0.15)))
)
(fp_text value "330" ; 值标识符
(at 0 0 0)
(layer "F.Fab")
)
(pad "1" thru_hole circle ; 焊盘定义
(at 10.5 20.3)
(size 1.5 1.5) ; 尺寸
(drill 0.8) ; 钻孔
(layers "*.Cu")
(remove_unused_layers no)
(net 5 "Net-(R5-Pad1)")
(uuid "...")
(pinfunction "1")
(pintype "passive")
)
(pad "2" thru_hole circle
(at 10.5 27.7)
(size 1.5 1.5)
(drill 0.8)
(layers "*.Cu")
(net 5 "Net-(R5-Pad2)")
(uuid "...")
)
(model "..." ...) ; 3D 模型
(zone "..." ...) ; 局部区域
)
焊盘类型
| 类型 | 关键字 | 说明 |
|---|---|---|
| 通孔圆焊盘 | thru_hole circle |
圆形通孔 |
| 通孔方焊盘 | thru_hole rect |
方形通孔 |
| 通孔圆角矩形 | thru_hole roundrect |
圆角矩形通孔 |
| SMD 圆形 | smd circle |
表贴圆形 |
| SMD 矩形 | smd rect |
表贴矩形 |
| SMD 圆角矩形 | smd roundrect |
表贴圆角矩形 |
| SMD 椭圆 | smd oval |
表贴椭圆 |
| 边缘连接器 | connect |
板边缘连接器 |
解析代码
python
def extract_footprints_and_pads(content: str, nets: Dict[int, Net]):
"""Extract footprints and their pads with global coordinates."""
footprints = {}
pads_by_net: Dict[int, List[Pad]] = {}
# 查找所有 footprint - 需要处理嵌套括号
footprint_starts = [m.start() for m in re.finditer(r'\(footprint\s+"', content)]
for start in footprint_starts:
# 查找匹配的右括号
depth = 0
end = start
for i, char in enumerate(content[start:], start):
if char == '(':
depth += 1
elif char == ')':
depth -= 1
if depth == 0:
end = i + 1
break
fp_text = content[start:end]
# 提取封装名称
fp_name_match = re.search(r'\(footprint\s+"([^"]+)"', fp_text)
fp_name = fp_name_match.group(1)
# 提取位置和旋转
at_match = re.search(r'\(at\s+([\d.-]+)\s+([\d.-]+)(?:\s+([\d.-]+))?\)', fp_text)
fp_x = float(at_match.group(1))
fp_y = float(at_match.group(2))
fp_rotation = float(at_match.group(3)) if at_match.group(3) else 0.0
# 提取层
layer_match = re.search(r'\(layer\s+"([^"]+)"\)', fp_text)
fp_layer = layer_match.group(1) if layer_match else "F.Cu"
# 提取 Reference
ref_match = re.search(r'\(property\s+"Reference"\s+"([^"]+)"', fp_text)
reference = ref_match.group(1) if ref_match else "?"
# 提取 Value
value_match = re.search(r'\(property\s+"Value"\s+"([^"]+)"', fp_text)
value = value_match.group(1) if value_match else ""
footprint = Footprint(
reference=reference,
footprint_name=fp_name,
x=fp_x,
y=fp_y,
rotation=fp_rotation,
layer=fp_layer,
value=value
)
# 提取焊盘
for pad_match in re.finditer(r'\(pad\s+"([^"]*)"\s+(\w+)\s+(\w+)', fp_text):
pad_num = pad_match.group(1)
pad_type = pad_match.group(2) # thru_hole, smd, etc.
pad_shape = pad_match.group(3) # circle, rect, etc.
# 查找焊盘块的结束
pad_start = pad_match.start()
pad_text = fp_text[pad_start:find_matching_paren(pad_start)]
# 提取焊盘属性
at_match = re.search(r'\(at\s+([\d.-]+)\s+([\d.-]+)\)', pad_text)
local_x = float(at_match.group(1))
local_y = float(at_match.group(2))
size_match = re.search(r'\(size\s+([\d.-]+)\s+([\d.-]+)\)', pad_text)
size_x = float(size_match.group(1))
size_y = float(size_match.group(2))
drill_match = re.search(r'\(drill\s+([\d.-]+)\)', pad_text)
drill = float(drill_match.group(1)) if drill_match else 0.0
layers_match = re.search(r'\(layers\s+"([^"]*)"\)', pad_text)
layers = layers_match.group(1).split() if layers_match else ["F.Cu"]
net_match = re.search(r'\(net\s+(\d+)\)', pad_text)
net_id = int(net_match.group(1)) if net_match else 0
net_name = nets[net_id].name if net_id in nets else ""
# 转换到全局坐标
global_x, global_y = local_to_global(fp_x, fp_y, fp_rotation, local_x, local_y)
pad = Pad(
component_ref=reference,
pad_number=pad_num,
global_x=global_x,
global_y=global_y,
local_x=local_x,
local_y=local_y,
size_x=size_x,
size_y=size_y,
shape=pad_shape,
layers=layers,
net_id=net_id,
net_name=net_name,
rotation=fp_rotation, # 使用封装旋转
pinfunction="",
pintype="",
drill=drill
)
footprint.pads.append(pad)
# 添加到按网络分组的焊盘
if net_id not in pads_by_net:
pads_by_net[net_id] = []
pads_by_net[net_id].append(pad)
footprints[reference] = footprint
return footprints, pads_by_net
坐标变换
python
def local_to_global(fp_x, fp_y, fp_rotation_deg, pad_local_x, pad_local_y):
"""
将焊盘局部坐标转换为全局坐标
关键:必须取反旋转角度!
KiCad 的旋转约定要求在使用标准旋转矩阵公式时取反角度。
"""
rad = math.radians(-fp_rotation_deg) # 关键:取反角度
cos_r = math.cos(rad)
sin_r = math.sin(rad)
global_x = fp_x + (pad_local_x * cos_r - pad_local_y * sin_r)
global_y = fp_y + (pad_local_x * sin_r + pad_local_y * cos_r)
return global_x, global_y
在路由中的用途
| 焊盘属性 | 用途 |
|---|---|
global_x, global_y |
路由起点/终点 |
net_id |
网络连接 |
layers |
确定起始层 |
drill > 0 |
通孔焊盘阻挡所有层 |
component_ref |
BGA 检测、目标交换 |
pad_number |
引脚识别 |
pinfunction |
差分对检测(如 /CS) |
7. 轨道段 (Segments)
格式
scheme
(segment
(start 123.456 78.901) ; 起点 X, Y (mm)
(end 150.234 95.678) ; 终点 X, Y (mm)
(width 0.25) ; 轨道宽度 (mm)
(layer "F.Cu") ; 层
(net 3) ; 网络 ID
(uuid "00000000-0000-0000-0000-00004abc1234")
)
解析代码
python
def extract_segments(content: str) -> List[Segment]:
"""Extract all track segments from PCB file."""
segments = []
# 匹配段块
segment_pattern = r'\(segment\s+\(start\s+([\d.-]+)\s+([\d.-]+)\)\s+\(end\s+([\d.-]+)\s+([\d.-]+)\)\s+\(width\s+([\d.-]+)\)\s+\(layer\s+"([^"]+)"\)\s+\(net\s+(\d+)\)\s+\(uuid\s+"([^"]+)"\)'
for m in re.finditer(segment_pattern, content, re.DOTALL):
segment = Segment(
start_x=float(m.group(1)),
start_y=float(m.group(2)),
end_x=float(m.group(3)),
end_y=float(m.group(4)),
width=float(m.group(5)),
layer=m.group(6),
net_id=int(m.group(7)),
uuid=m.group(8),
# 存储原始字符串用于精确文件匹配
start_x_str=m.group(1),
start_y_str=m.group(2),
end_x_str=m.group(3),
end_y_str=m.group(4)
)
segments.append(segment)
return segments
在路由中的用途
| 属性 | 用途 |
|---|---|
start_x, start_y, end_x, end_y |
障碍物位置、连通性检查 |
width |
障碍物宽度 |
layer |
障碍物层 |
net_id |
网络识别(同网络可穿过) |
uuid |
唯一标识符(用于精确匹配) |
8. 过孔 (Vias)
格式
scheme
(via
(at 100.5 200.3) ; X, Y 位置 (mm)
(size 0.6) ; 外径 (mm)
(drill 0.3) ; 钻孔直径 (mm)
(layers "F.Cu" "B.Cu") ; 连接的层
(free yes) ; 自由 via(KiCad 不会自动分配网络)
(net 5) ; 网络 ID
(uuid "00000000-0000-0000-0000-00004def5678")
)
解析代码
python
def extract_vias(content: str) -> List[Via]:
"""Extract all vias from PCB file."""
vias = []
# 匹配 via 块
# 注意:free 属性是可选的,可以在 layers 和 net 之间
via_pattern = r'\(via\s+\(at\s+([\d.-]+)\s+([\d.-]+)\)\s+\(size\s+([\d.-]+)\)\s+\(drill\s+([\d.-]+)\)\s+\(layers\s+"([^"]+)"\s+"([^"]+)"\)\s+(?:\(free\s+(yes|no)\)\s+)?\(net\s+(\d+)\)\s+\(uuid\s+"([^"]+)"\)'
for m in re.finditer(via_pattern, content, re.DOTALL):
free_value = m.group(7) # "yes", "no", 或 None
via = Via(
x=float(m.group(1)),
y=float(m.group(2)),
size=float(m.group(3)),
drill=float(m.group(4)),
layers=[m.group(5), m.group(6)],
net_id=int(m.group(8)),
uuid=m.group(9),
free=(free_value == "yes")
)
vias.append(via)
return vias
在路由中的用途
| 属性 | 用途 |
|---|---|
x, y |
障碍物位置 |
size |
障碍物尺寸 |
layers |
阻挡所有连接的层 |
net_id |
网络识别 |
drill |
钻孔信息 |
free |
是否为自由 via |
9. 填充区域 (Zones)
格式
scheme
(zone
(net 1) ; 网络 ID
(net_name "GND") ; 网络名称
(layer "B.Cu") ; 层
(uuid "00000000-0000-0000-0000-0000abcd1234")
(pts
(xy 110.5 50.2)
(xy 110.5 80.7)
(xy 150.8 80.7)
(xy 150.8 50.2)
)
(filled_areas ...) ; 可选:填充区域
(fill ...) ; 可选:填充模式
(zone_connect ...) ; 可选:连接方式
)
解析代码
python
def extract_zones(content: str) -> List[Zone]:
"""Extract all filled zones from PCB file."""
zones = []
# 查找每个 zone 块的开始
zone_start_pattern = r'\r?\n\t\(zone\s*\r?\n'
for start_match in re.finditer(zone_start_pattern, content):
# 查找匹配的右括号
start_pos = start_match.start() + len(start_match.group()) - 1
paren_count = 1
pos = start_match.end()
zone_end = None
while pos < len(content) and paren_count > 0:
char = content[pos]
if char == '(':
paren_count += 1
elif char == ')':
paren_count -= 1
if paren_count == 0:
zone_end = pos
pos += 1
if zone_end is None:
continue
zone_content = content[start_match.end():zone_end]
# 提取网络 ID
net_match = re.search(r'\(net\s+(\d+)\)', zone_content)
net_id = int(net_match.group(1)) if net_match else 0
# 提取网络名称
net_name_match = re.search(r'\(net_name\s+"([^"]*)"\)', zone_content)
net_name = net_name_match.group(1) if net_name_match else ""
# 提取层
layer_match = re.search(r'\(layer\s+"([^"]+)"\)', zone_content)
layer = layer_match.group(1) if layer_match else ""
# 提取 UUID
uuid_match = re.search(r'\(uuid\s+"([^"]+)"\)', zone_content)
uuid = uuid_match.group(1) if uuid_match else ""
# 提取多边形点 - 查找 (pts ...) 并提取 xy 坐标
pts_start = zone_content.find('(pts')
if pts_start < 0:
continue
# 查找 (pts 的匹配右括号
paren_count = 0
pts_end = pts_start
for i in range(pts_start, len(zone_content)):
if zone_content[i] == '(':
paren_count += 1
elif zone_content[i] == ')':
paren_count -= 1
if paren_count == 0:
pts_end = i
break
pts_content = zone_content[pts_start:pts_end + 1]
# 解析所有 (xy x y) 点
xy_pattern = r'\(xy\s+([\d.-]+)\s+([\d.-]+)\)'
polygon = [(float(m.group(1)), float(m.group(2)))
for m in re.finditer(xy_pattern, pts_content)]
if not polygon:
continue
zone = Zone(
net_id=net_id,
net_name=net_name,
layer=layer,
polygon=polygon,
uuid=uuid
)
zones.append(zone)
return zones
在路由中的用途
| 属性 | 用途 |
|---|---|
net_id, net_name |
确定网络 |
layer |
所在层 |
polygon |
区域边界(多边形) |
uuid |
唯一标识符 |
10. 图形对象 (Graphics)
类型
| 类型 | 关键字 | 用途 |
|---|---|---|
| 直线 | gr_line |
图形、标注 |
| 圆弧 | gr_arc |
圆弧 |
| 圆 | gr_circle |
圆 |
| 文本 | gr_text |
文本标注 |
| 矩形 | gr_rect |
矩形 |
| 多边形 | gr_poly |
多边形 |
格式示例
scheme
(gr_line
(start 100 100)
(end 200 200)
(angle 90)
(layer "Dwgs.User")
(width 0.12)
(uuid "...")
)
(gr_circle
(center 150 150)
(end 145 150)
(layer "Dwgs.User")
(width 0.12)
(uuid "...")
)
(gr_text
(at 150 150)
(text "Label")
(layer "F.SilkS")
(effects
(font
(size 1.27)
(thickness 0.15)
)
)
(uuid "...")
)
解析代码
python
# 当前 routing 工具不解析图形对象
# 它们用于文档和标注,不影响路由
数据映射
PCB 文件 → PCBData
.kicad_pcb 文件
↓
kicad_parser.py::parse_kicad_pcb()
↓
PCBData 对象
├── board_info: BoardInfo
│ ├── layers: Dict[int, str] # {0: "F.Cu", 1: "B.Cu"}
│ ├── copper_layers: List[str] # ["F.Cu", "B.Cu"]
│ ├── board_bounds: Tuple # (min_x, min_y, max_x, max_y)
│ ├── stackup: List[StackupLayer] # 层叠信息
│ └── board_outline: List[Tuple[float,float]]
│
├── nets: Dict[int, Net]
│ └── Net(net_id, name, pads)
│
├── footprints: Dict[str, Footprint]
│ └── Footprint(reference, footprint_name, x, y, rotation, layer, value, pads)
│
├── vias: List[Via]
│ └── Via(x, y, size, drill, layers, net_id, uuid, free)
│
├── segments: List[Segment]
│ └── Segment(start_x, start_y, end_x, end_y, width, layer, net_id, uuid)
│
├── pads_by_net: Dict[int, List[Pad]]
│ └── [Pad(...), Pad(...), ...]
│
└── zones: List[Zone]
└── Zone(net_id, net_name, layer, polygon, uuid)
完整示例
示例 PCB 文件片段
scheme
(kicad_pcb
(version 20241229)
(generator "pcbnew")
(generator_version "9.0")
(general
(thickness 1.6)
(legacy_teardrops no)
)
(paper "A4")
(layers
(0 "F.Cu" signal)
(31 "B.Cu" signal)
(25 "Edge.Cuts" user)
)
(setup
(stackup
(layer "F.Cu"
(type "copper")
(thickness 0.035)
)
(layer "dielectric 1"
(type "core")
(thickness 1.51)
(material "FR4")
(epsilon_r 4.5)
(loss_tangent 0.02)
)
(layer "B.Cu"
(type "copper")
(thickness 0.035)
)
(copper_finish "None")
)
)
(net 0 "")
(net 1 "GND")
(net 2 "+5V")
(net 3 "/RESET")
(net 4 "Net-(U1-Pad5)")
(footprint "Resistor_SMD:R_0805_2012
(layer "F.Cu")
(uuid "...")
(at 100.5 80.3)
(descr "Resistor, 0805")
(tags "Resistor SMD")
(property "Reference" "R1"
(at 0 0 0)
(layer "F.SilkS")
)
(property "Value" "10k"
(at 0 0 0)
(layer "F.Fab")
)
(pad "1" smd roundrect
(at -0.95 0)
(size 1.2 0.7)
(layers "F.Cu")
(net 4 "Net-(R1-Pad1)")
(uuid "...")
)
(pad "2" smd roundrect
(at 0.95 0)
(size 1.2 0.7)
(layers "F.Cu")
(net 3 "Net-(R1-Pad2)")
(uuid "...")
)
)
(segment
(start 100.5 80.3)
(end 120.7 100.5)
(width 0.25)
(layer "F.Cu")
(net 3)
(uuid "...")
)
(via
(at 110.5 90.4)
(size 0.6)
(drill 0.3)
(layers "F.Cu" "B.Cu")
(net 1)
(uuid "...")
)
(zone
(net 1)
(net_name "GND")
(layer "B.Cu")
(uuid "...")
(pts
(xy 50 50)
(xy 50 150)
(xy 200 150)
(xy 200 50)
)
(connect_patchesyes)
)
)
解析实现细节
正则表达式模式
网络模式
python
net_pattern = r'\(net\s+(\d+)\s+"([^"]*)"\)'
# 匹配: (net 1 "GND")
# Group 1: 网络 ID
# Group 2: 网络名称
焊盘模式
python
pad_pattern = r'\(pad\s+"([^"]*)"\s+(\w+)\s+(\w+)'
# 匹配: (pad "1" thru_hole circle
# Group 1: 焊盘编号
# Group 2: 焊盘类型 (thru_hole, smd, etc.)
# Group 3: 焊盘形状 (circle, rect, etc.)
轨道段模式
python
segment_pattern = r'\(segment\s+\(start\s+([\d.-]+)\s+([\d.-]+)\)\s+\(end\s+([\d.-]+)\s+([\d.-]+)\)\s+\(width\s+([\d.-]+)\)\s+\(layer\s+"([^"]+)"\)\s+\(net\s+(\d+)\)\s+\(uuid\s+"([^"]+)"\)'
过孔模式
python
via_pattern = r'\(via\s+\(at\s+([\d.-]+)\s+([\d.-]+)\)\s+\(size\s+([\d.-]+)\)\s+\(drill\s+([\d.-]+)\)\s+\(layers\s+"([^"]+)"\s+"([^"]+)"\)\s+(?:\(free\s+(yes|no)\)\s+)?\(net\s+(\d+)\)\s+\(uuid\s+"([^"]+)"\)'
嵌套括号处理
由于 S-表达式是嵌套的,需要平衡括号计数:
python
def find_matching_paren(text, start):
"""查找从 start 开始的匹配右括号"""
depth = 0
for i in range(start, len(text)):
if text[i] == '(':
depth += 1
elif text[i] == ')':
depth -= 1
if depth == 0:
return i
return -1
精度处理
python
# 位置精度
POSITION_DECIMALS = 3 # 所有坐标保留3位小数
global_x = round(global_x, POSITION_DECIMALS)
global_y = round(global_y, POSITION_DECIMALS)
文件大小和性能
典型文件大小
| 设计类型 | 元器件 | 网络数 | 文件大小 |
|---|---|---|---|
| 简单 | 50-100 | 100-500 | 100-500 KB |
| 中等 | 500-1000 | 1000-5000 | 1-5 MB |
| 复杂 | 2000+ | 5000+ | 10-50 MB |
解析性能
python
# 典型解析时间(中等复杂度 PCB)
# 文件大小: 5 MB
# 解析时间: ~0.5-2 秒
def parse_kicad_pcb(filepath: str) -> PCBData:
# 使用正则表达式,快速扫描
# 避免完整的 S-expression 解析树
# 针对关键字段进行优化
版本兼容性
KiCad 版本
| KiCad 版本 | 文件格式版本 | 兼容性 |
|---|---|---|
| 7.0.x | 20210608 | ⚠️ 部分兼容 |
| 8.0.x | 20220525 | ⚠️ 部分兼容 |
| 9.0.x | 20241229 | ✅ 完全兼容 |
| 未来 | 未来版本 | ❌ 不兼容 |
主要变化
- KiCad 7.0+ 引入了新的层命名约定
- KiCad 8.0+ 改进了网络类管理
- KiCad 9.0+ 更新了层叠格式
与其他格式的对比
| 格式 | 扩展名 | 复杂度 | 可读性 |
|---|---|---|---|
| KiCad PCB | .kicad_pcb |
中等 | ✅ 高 |
最佳实践
读取 PCB 文件
python
from kicad_parser import parse_kicad_pcb
# 解析 PCB 文件
pcb_data = parse_kicad_pcb("my_board.kicad_pcb")
# 访问数据
print(f"层数: {len(pcb_data.board_info.copper_layers)}")
print(f"网络数: {len(pcb_data.nets)}")
print(f"封装数: {len(pcb_data.footprints)}")
print(f"轨道段: {len(pcb_data.segments)}")
print(f"过孔数: {len(pcb_data.vias)}")
写入 PCB 文件
python
from kicad_writer import write_routed_output
# 写入路由结果
write_routed_output(
output_file="my_board_routed.kicad_pcb",
pcb_data=pcb_data,
new_segments=new_segments,
new_vias=new_vias
)
调试技巧
检查文件内容
bash
# 查看网络定义
grep "^ (net " my_board.kicad_pcb | head -20
# 查看封装列表
grep "^ (footprint " my_board.kicad_pcb | head -10
# 查看轨道段
grep "^ (segment " my_board.kicad_pcb | wc -l
# 查看过孔
grep "^ (via " my_board.kicad_pcb | wc -l
验证解析结果
python
# 验证网络解析
for net_id, net in pcb_data.nets.items():
print(f"Net {net_id}: {net.name} ({len(net.pads)} pads)")
# 验证焊盘解析
for ref, fp in pcb_data.footprints.items():
print(f"{ref}: {len(fp.pads)} pads at ({fp.x}, {fp.y})")
常见问题
Q: 如何处理未连接的焊盘?
A: 未连接的焊盘会被分配自动生成的网络名:
(net 123 "unconnected-(U9-PadB5)")
这些网络在路由时会被识别为 stub 端点。
Q: 如何识别差分对?
A: 通过网络命名约定:
xxx_P,xxx_N后缀xxx+,xxx-后缀- 相同的器件编号和引脚位置
python
# kicad_parser.py 中的识别
from net_queries import find_differential_pairs
diff_pairs = find_differential_pairs(pcb_data, ["*"])
Q: 如何获取板的边界?
A : 从 Edge.Cuts 层计算:
python
def extract_board_bounds(content: str):
"""Extract board bounds from Edge.Cuts graphics."""
bounds_match = re.search(
r'\(layer\s+"Edge\.Cuts".*?\(width\s+([\d.]+).*?\(height\s+([\d.]+)',
content, re.DOTALL
)
if bounds_match:
width = float(bounds_match.group(1))
height = float(bounds_match.group(2))
return (0, 0, width, height)
# 或从图形多边形计算
# ...
总结
KiCad PCB 文件包含的主要信息
- 网络定义: 所有电网络的列表
- 封装实例: 所有器件的放置和方向
- 焊盘: 所有焊盘的精确位置和属性
- 轨道段: 所有布线轨道
- 过孔: 所有层间连接
- 填充区域: 电源平面和其他填充区域
- 层定义: 所有PCB层的定义
- 设置和层叠: 板参数和材料属性
- 图形对象: 文档和标注
数据提取方式
- 正则表达式: 快速扫描和匹配关键字段
- 括号平衡: 处理嵌套的 S-表达式
- 坐标变换: 局部坐标 → 全局坐标
- 索引构建: 快速查找结构
路由工具的使用
KiCad Routing Tools 从 PCB 文件中提取:
- ✅ 路由端点(未连接的焊盘)
- ✅ 障碍物(已布线、过孔、焊盘)
- ✅ 层信息(用于多层布线)
- ✅ 层叠信息(用于阻抗控制)
- ✅ 封装信息(用于 BGA 扇出)
这些信息构成了路由算法的基础输入。