【agent辅助pcb routing coding学习】实践1 kicad pcb 格式讲解

KiCad PCB 文件格式详解

本文档详细说明 KiCad PCB 文件(.kicad_pcb)的文件结构,包含的信息类型,以及如何解析这些数据。

目录

  1. 文件格式概述
  2. 基本语法
  3. 顶层结构
  4. 各部分详解
  5. 数据映射
  6. 解析实现

文件格式概述

格式类型

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)
)

规则

  1. 所有内容都是列表 : 用括号 () 包围
  2. 列表第一个元素是关键字: 标识数据类型
  3. 字符串用双引号 : "string value"
  4. 数字不带引号 : 123, 45.67
  5. 注释 : 使用 ; 开头(很少使用)
  6. 空白字符: 空格、换行、制表符用于格式化

示例对比

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 用户自定义层
在路由中的用途
  • copper_layers: 确定可布线的层
  • layer_id: 内部层引用(0, 1, 2...)
  • layer_name: 显示名称("F.Cu", "B.Cu"...)

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
在路由中的用途
  1. 网络选择: 用户选择要路由的网络
  2. 差分对检测 : 识别 _P/_N 后缀
  3. 电源网络识别: 识别 GND, VCC 等
  4. 进度显示: 显示正在路由的网络名

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 文件包含的主要信息

  1. 网络定义: 所有电网络的列表
  2. 封装实例: 所有器件的放置和方向
  3. 焊盘: 所有焊盘的精确位置和属性
  4. 轨道段: 所有布线轨道
  5. 过孔: 所有层间连接
  6. 填充区域: 电源平面和其他填充区域
  7. 层定义: 所有PCB层的定义
  8. 设置和层叠: 板参数和材料属性
  9. 图形对象: 文档和标注

数据提取方式

  • 正则表达式: 快速扫描和匹配关键字段
  • 括号平衡: 处理嵌套的 S-表达式
  • 坐标变换: 局部坐标 → 全局坐标
  • 索引构建: 快速查找结构

路由工具的使用

KiCad Routing Tools 从 PCB 文件中提取:

  • ✅ 路由端点(未连接的焊盘)
  • ✅ 障碍物(已布线、过孔、焊盘)
  • ✅ 层信息(用于多层布线)
  • ✅ 层叠信息(用于阻抗控制)
  • ✅ 封装信息(用于 BGA 扇出)

这些信息构成了路由算法的基础输入。

相关推荐
仙俊红2 小时前
LeetCode493周赛T3,前后缀分解
数据结构·算法·leetcode
_日拱一卒2 小时前
LeetCode(力扣):二叉树的前序遍历
java·数据结构·算法·leetcode
倾心琴心2 小时前
【agent辅助pcb routing coding学习】实践5 kicad类按类别理解
算法·agent·pcb·eda·routing
Frostnova丶2 小时前
LeetCode 50. Pow(x, n)
算法·leetcode
lierenvip2 小时前
【语义分割】12个主流算法架构介绍、数据集推荐、总结、挑战和未来发展
算法·架构
三块可乐两块冰2 小时前
机器学习笔记一
笔记·算法·机器学习
海兰2 小时前
OpenClaw安全保命指南:Skill 插件投毒风险全解析及应对措施
人工智能·安全·agent·openclaw
凤年徐2 小时前
优选算法——滑动窗口2
数据结构·c++·算法
安之若素.re2 小时前
827. 最大人工岛
算法·深度优先