尧图精选

KiCad PCB文件格式探秘:从源码角度读懂s-expression结构

🕒 发布时间:2026/10/2 11:23:28 📁 来源:尧图网络
说是“代码阅读随记”其实我一开始根本不是抱着学格式的目的去翻 KiCad 源码的。那时候手头接手一块老项目改版网上一份 .kicad_pcb 文件在别的 EDA 里打开封装位置全部漂移差了几十个 mil还有人改完直接给板厂板厂退回来说图层信息不完整。绕了一圈最后发现问题的根源不是软件 bug而是大家对这份文本格式的理解不一致。从那以后我就养成一个习惯凡是遇到 PCB 文件转换、版本兼容、批量修改这类需求先别急着点“打开”按钮直接把文件当文档读一遍再把 KiCad 源码里对应的解析和序列化逻辑翻出来比对以后基本就心里有底了。这份博文就是我的阅读随记重点讲 KiCad PCB 文件格式也就是 .kicad_pcb的结构以及怎么从源码角度理解它。适合正在啃 KiCad、或者经常要在不同 EDA 工具之间倒腾板子的工程师尤其是踩过“格式导致板子出错”这种坑的人。1. 拿到一个 .kicad_pcb先别急着双击它比你想象的更适合“阅读”很多人对 PCB 文件的印象是“二进制工程文件必须用对应软件打开”。KiCad 不一样它的电路板文件是纯文本而且是用一种叫 s-expression 的括号结构来组织的。这意味着你不需要打开任何工具用记事本、VS Code、Vim 甚至命令行 grep就能看到整块板的几乎所有信息网络、封装、走线、过孔、覆铜、板框、丝印、规则设置全部清清楚楚写在括号里。我为什么强调“阅读”这个词因为在实际工程里.kicad_pcb 文件承担的角色不只是“能打开就好”它还经常被人拿去给版本管理工具做 diff看这次改版到底动了哪些走线写脚本批量修改封装位号、走线宽度、过孔尺寸在 KiCad、嘉立创 EDA、其他工具之间来回转换做设计审查甚至人工检查某个网络是否还有未连通的走线。这些场景全都要求你理解文件结构。如果只依赖 GUI遇到问题就是“软件打不开、导入报错、位置漂移”而你根本不知道是哪里坏了。反过来如果你能读懂文件内容很多时候一眼就能看出来问题出在哪个括号、哪个属性、哪个图层上。1.1 文本格式带来的三个“白嫖”好处第一是版本管理。KiCad 的 PCB 文件用文本保存Git 就能对它做逐行 diff。比如你和同事各改了一版布局两个人到底改了哪些区域直接git diff看变化就行不需要肉眼对比截图。第二是脚本化。你可以在不打开界面的情况下用 Python 或者批处理命令去修改一组元件的位号、统一调整某类网络的最小线宽、给所有同类型封装补一个丝印标记等等。这些操作如果全靠鼠标在界面里点不仅慢还容易漏。第三是可迁移性。因为格式是开放、公开的很多第三方工具都实现了“读 KiCad 文件”的能力比如嘉立创 EDA 的导入、其他 EDA 的转换工具底层都是在解析这份文本格式。这一点也是我做这次源码阅读的动机之一。既然别人能解析那我也可以自己解析、自己生成哪怕只是临时修一个文件格式错误也不用等软件更新。1.2 哪些场景最离不开对文件格式的理解如果你是纯做设计的平时只画自己的板子那对格式只做浅层了解就够了。但下面几类场景几乎是在逼着你去读格式跨工具转换KiCad 转其它格式再转回来过程中会丢掉什么、多出什么不读文件你根本不知道团队协作连续文件版本炸了以后需要手工恢复某个元素EDA 二次开发要做 DRC 插件、封装检查工具、设计规则脚本板厂对接板厂要求的输出文件和工艺说明对应到文件里的层、宽度、间距必须能对上教学与培训很多 PCB 培训课程会用 KiCad 做入门讲格式源码对于深入理解设计数据流非常重要。我说句实在话PCB 文件格式是每个做硬件的人迟早要面对的“工业化标准”。花点时间读一遍格式再翻一翻源码节省的不只是时间更重要的是会让你在排查问题时有一种“通透感”。2. 从顶层看这份格式s-expression 构成的“树”.s-expression 如果你第一次接触可能觉得像 Lisp 语言但不用被名字吓到。它的本质就是一棵树最外层是一个根节点往下挂子节点每个子节点还可以继续往下挂。KiCad 的 PCB 文件最外层通常是(kicad_pcb (version 20230612) (generator pcbnew) ... )最外层的kicad_pcb就是整棵树的根。里面所有内容都是它的子节点。整个文件就是靠这种嵌套括号来表示层级关系的。实际打开一份文件你会发现节点大概分为几类头部信息版本号、生成器、纸张大小全局设置图层列表、图层对、设计规则、元件属性默认值板级对象板框线、辅助图形、文字网络列表每个网络的编号和名称封装对象每个封装Footprint的位号、坐标、旋转、焊盘、丝印、属性布线对象每根走线、过孔、圆弧覆铜区域Zone 的边框、填充规则、清除规则。这些节点相互之间有大量交叉引用。比如一根走线要指明它属于哪个网络和哪个图层一个焊盘要指明它的网络和封装归属。理解这种交叉就是读懂格式的关键。2.1 文件头部信息版本与生成器头部信息往往最简单也最容易被人忽视。但版本不匹配导致打开异常几乎是我见过最多的报错。KiCad 的格式一直在演进每个大版本都会修订节点定义比如新版本可能加入了圆弧的角度表示、新的 NET 类属性、新的铜图层语义。文件头里的(version ...)就是给解析器一个依据。常见结构类似(kicad_pcb (version 20230612) (generator pcbnew) (paper A4) (layers (0 F.Cu signal) ... ) )paper A4是图纸尺寸有的文件可能是自定义尺寸因为少写了一个括号就变成另一个值的情况我也遇到过。读代码时你会看到源码里有一个解析器会先读 version再根据版本号选择兼容的解析分支。这里给一句话提醒永远不要在高版本 KiCad 里直接覆盖保存一份旧版工程后又期望旧工具还能原样打开。文件头就是你最先该检查的地方。2.2 结构上的层级全局设置、网络、封装、布线继续往下细化。文件里通常先出现全局设置、图层和板框然后才是元器件和走线。我举个例子一个单层板子的图层声明一般长这样(layers (0 F.Cu signal) (31 B.Cu signal) (32 B.Adhes user) ... )每个图层有一个内部编号、一个显示名称和一个类型。这是底层互操作最容易出错的地方因为不同工具对同一编号的图层命名可能不同。接下来是网络列表。网络在文件里通常是(net 0 ) (net 1 GND) (net 2 3V3)数字是网络的内部编号引号里是网络名。注意网络 0 往往是空网络代表“没有网络”这不是错误。封装对象是文件里体积最庞大的一个分支。每个封装大概长这样(footprint R_0805 (layer F.Cu) (at 123.45 67.89) (descr ...) (fp_text reference R1 (at ...) (layer F.SilkS) ...) (pad 1 smd rect (at ...) (size ...) (net 1 GND) ...) (pad 2 smd rect (at ...) (size ...) (net 2 3V3) ...) (fp_line (start ...) (end ...) (layer F.SilkS) (width ...)) )封装下面挂很多子节点包括位号、元件名称、焊盘、丝印线。焊盘必须引用一个网络编号比如上面的(net 1 GND)这里编号和名称同时存在就是为了提高容错性。走线对象就更直观了(segment (start 10 20) (end 30 40) (width 0.25) (layer F.Cu) (net 1 GND))一个线段需要起点、终点、线宽、图层和网络。学过解析代码后你会发现源码里面对应的是一个 POINT 类和属性集合并不复杂。2.3 为什么单位是纳米你却能输入毫米读代码时很容易被坐标数字吓到那些数值动辄上百万(at 123456000 -78900000)这其实是因为 KiCad 内核存储坐标默认用纳米作为整数单位而你在界面里输入的是毫米界面输入时帮你做了换算。文件里看到的数字除以 1 000 000 就能得到毫米。这一点特别重要如果你写脚本去解析坐标却不做换算生成的结果很可能整体小了 6 个数量级。某些工具在导入时把单位搞错导致封装间距整体缩小就是这个原因。所以国内社区里经常有人说“KiCad 导到某个 EDA 之后元件重叠了”十有八九是文件单位或者坐标偏移量没有正确转换。对了还有原点偏移问题文件里的坐标都是相对于板子设计原点的不是相对于屏幕中心的。3. KiCad 代码是怎么读写这份文件的如果你想知道格式的“官方答案”不要去猜直接看源码。KiCad 的源码在 GitHub 上公开PCB 文件格式的读写逻辑基本集中在pcbnew模块下面尤其是一个叫io或者plugin的目录里具体类名叫类似KICAD_SEXPR_PLUGIN或PCB_IO这样的东西。为什么阅读源码比阅读文档更可靠因为文档可能滞后但代码是实际程序正在使用的行为。自己想实现一个转换器的时候源码就是最终依据。3.1 在源码里快速定位解析器我第一次找解析器时直接的方法是在源码目录里搜索version、kicad_pcb、segment这些关键字。你会在对应的解析文件里看到大段的字符串判断else if ( aToken T_segment )这些 token 定义和文件里的关键字是对应的。看到T_segment这个 token 定义你就能知道文件里写(segment ...)时解析器会进入哪一段处理逻辑。建议以“读取过程中的解析器 存储对象的数据结构 导出时的打印函数”为三条主线来读解析器负责把文件里的括号文本变成内存对象内存对象在 C 里是各种BOARD_ITEM子类比如PCB_TRACK、PCB_VIA、PCB_FOOTPRINT、PCB_ZONE导出时的打印函数再把内存对象变回文本。这三部分常常不在同一个文件里。你搜索关键字时可能会看到读取时的doParse和输出时的format先分清这两个方向再细看内容。3.2 那些奇奇怪怪的关键字怎么查阅读源码时你会发现文件里很多关键字和界面显示名不一样。比如fp_text对应封装上的文字比如位号fp_line对应封装上的丝印线gr_line对应板框上的直线pad对应焊盘zone对应覆铜区域keepout对应禁止布线区域。源码里会有一个枚举或者 token 列表来管理这些关键字。你只要按照“关键字 - 处理函数 - 数据类”的顺序去追就能理解每个元素是怎么被识别的。再举个实际例子KiCad 6 之后的文件里圆弧走线节点写法有变化。旧版可能是用(arc (start ...) (mid ...) (end ...))表示新版在部分版本里引入了更明确的圆心角度表示。如果不用源码去确认你在做转换器时很可能只支持了旧写法结果新版文件一多就出错。3.3 看代码时最值得琢磨的几个对象模型我看代码过程中觉得最核心的类其实是BOARD它代表整个板子。下面挂了一堆子对象比如BOARD_TRACK、BOARD_VIA、FOOTPRINT、ZONE等等。每个子对象都要有Format()方法负责把自己的内容写成 s-expression。也就是说一个元素的字段有多少基本都由这个类里的成员变量决定。比如一个过孔大概有这样的成员位置坐标直径钻孔直径起始层和结束层网络编号和名称。读解析代码时你会看到它如何把这些成员转成文本节点(via (at 12.34 56.78) (size 0.6) (drill 0.3) (layers F.Cu B.Cu) (net 1 GND) )多读几段后你会形成一种直觉格式里的任何参数最后都能在代码里找到来源。这对你做自定义脚本特别有帮助因为你能精准地知道要改哪个字段而不是靠“试试看”。4. 真实操作把“读格式”变成日常工作光讲理论没有意义我拿实际场景演示一遍怎么用格式知识解决问题。4.1 定位某一个封装先看位号文本有一次我要在一份几百个封装的板子上找某个电阻的位置如果用 GUI 打开得先找到位号再放大看坐标。用文本方式就简单得多直接搜索位号文字比如R37会看到类似(fp_text reference R37 (at 44.55 66.77) ... )at后面就是位号参考点在板面上的坐标。有时候位号离封装本体较远你还需要搜索这个封装名往上滚几行找到同级的footprint节点再去看第一个(at ...)那才是封装原点坐标。这个小技巧对批量核对位号、检查丝印重叠特别有效。4.2 手写补丁统一修改一批走线宽度有一次客户要求把电源网络上所有走线的最小宽度从 0.25mm 提高到 0.5mm。在 GUI 里一个个选中再改很容易漏。我用文本方式处理先找到电源网络的编号假设是网络 12随后把所有segment节点里(net 12 ...)对应的width全部改成0.5。当然这种操作风险高务必先备份改完以后再用 KiCad 打开检查。你可以在 VS Code 里用正则全局替换也可以通过 Python 脚本做精确匹配import re with open(board.kicad_pcb, r, encodingutf-8) as f: content f.read() # 只替换 GND 网络上走线的线宽 pattern re.compile( r(\(segment \(start [^)]\) \(end [^)]\) \(width )0\.25 r(\) \(layer [^)]\) \(net \d GND\)\)) ) new_content pattern.sub(r\g10.5\g2, content) with open(board_new.kicad_pcb, w, encodingutf-8) as f: f.write(new_content)上面这种正则只适合简单场景如果是复杂的图建议用真正的 s-expression 解析库。但思路是一样的先在文本层面理解结构再精确定位修改。4.3 用脚本检查异常网络还有一种很常见的需求检查板子上有没有只连了一根线的孤立网络或者两个焊盘距离太近。这类逻辑如果是放在 GUI 里做要一遍遍运行 DRC但如果你只是临时想看某个网络连接情况完全可以用 Python 把文件读进来统计每个网络的连线数量。思路很简单用正则或解析库提取所有segment、pad、via里的网络编号建一个“网络 - 连接点列表”的字典。哪个网络连接点特别少它就是可疑对象。实际跑下来最容易发现的问题是明明原理图中连在一起的网络怎么到了 PCB 上就变成两个 ID 了。这种多半是原理图到 PCB 的网表更新出了偏差通过文本检查很快就能定位到。5. 常见问题与避坑清单下面这些坑都是我实际踩过或见过别人踩的整理成清单给你。5.1 版本不兼容的典型现场KiCad 每次大版本更新都可能调整 s-expression 的字段。最典型的例子是封装和走线圆弧的表达方式。旧版本保存的圆弧在新版本里可能自动转换但新版本保存的文件旧版本解析器不一定认识。所以如果你是团队协作建议先约定主版本。如果你必须跨版本导出时尽量用低一些的版本格式比如 KiCad 8 可以导出为兼容格式但会丢失部分新特性。还有一个经常被忽略的不要用文本编辑器手动修改文件头里的 version 号去“骗”解析器。版本号只是一个信号真正的兼容性取决于字段结构。你强行降低版本号只会让字段无法解析。5.2 图层名、网络名里的特殊字符s-expression 里字符串用双引号包起来网络名如果包含空格、斜杠、特殊符号都会原样放在引号里。解析代码会跳过引号内部的转义字符但如果你手写脚本来匹配net一定要用(.*?)这种非贪婪匹配否则很容易被空号或嵌套引号打断。我记得有一次处理来自其它 EDA 的转换文件网络名里带了反斜杠导致我的正则匹配到下一个引号就断了最后查了半天才发现是特殊字符的问题。从那以后我强烈建议所有网络名统一用字母、数字、下划线别给自己找麻烦。5.3 改完以后到底能不能正常用手动修改文本后第一时间不要急着继续编辑应该做三件事上一步备份原文件。这个别省因为你手滑删了一个括号整个文件就废了。第二步用 KiCad 打开确认没有解析错误。KiCad 如果发现文件格式有问题通常会弹窗提示或者直接报错打不开。一旦报错马上回退不要试图在报错状态下保存。第三步用 DRC 再跑一遍。格式能解析不等于设计正确。很多改动会改变电气规则比如线宽、间距、过孔孔径必须在 DRC 里验证。5.4 一套比较稳妥的读图流程我给自己的流程是先看文件头判断版本然后全局搜索关键对象数量比如封装数、走线数、过孔数再根据需求分段读取网络和封装修改前先备份修改后一定通过正规 GUI 打开做验证。如果是要写自动化脚本不要试图用简单的正则解析整个文件。建议用现成的 s-expression 解析库把文本先转成树你再对树做操作。这样做更稳也不容易破坏括号结构。6. 写在最后一些小习惯很值钱这些东西可能没人写进手册但我用下来觉得非常有价值特别是当你发现自己频繁地和 PCB 文件格式打交道时。第一多跑源码、少猜文档。文档可能停留在旧版本源码才是活的真相。查找格式关键字时直接去 GitHub 上搜索再找到对应的解析函数基本就能把语义弄明白。第二给 PCB 工程建 Git 仓库。文本格式简直是版本管理的天选之子。每次改版都有迹可循出问题可以回退同事之间也能看到各自改动了什么。虽然 git diff 出来的内容很长但配合关键词过滤其实很清晰。第三写脚本时永远用“解析库 树结构”而不是“正则硬莽”。正则适合快速查找不适合做全量修改和生成。我见过不少用正则把焊接盘信息改乱的例子真的很惨。最后分享一点我个人的体会读懂一份 PCB 文件格式带给我的不只是“会写脚本”这一点而是让我对整个 EDA 工具链产生了更强的掌控感。以前软件某个按钮突然不生效我只能去查设置现在我会想文件里对应的字段是什么它和界面上的设置有什么关系。这种由数据格式驱动的思维方式值得每个硬件工程师花点时间培养。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →