PinMAP ↔ PinList 双向转换器
将 Excel 格式的 PinMAP(方形封装引脚布局图)与 PinList(引脚序号列表)互相转换,消除手动抄录的低效与错误风险。
- PinMAP → PinList:自动识别方形/长方形结构,逆时针提取引脚,生成线性列表
- PinList → PinMAP:根据引脚列表和网格尺寸,自动计算布局并生成方形封装图
项目简介
在 IC 封装设计中,PinMAP 以方形/长方形矩阵形式展示引脚分布,而 PinList 则以线性列表形式提供引脚序号对照。本项目通过纯 Python 实现,自动完成 PinMAP 与 PinList 之间的双向转换,支持 .xls 和 .xlsx 两种格式。
版本: v1.5.0
发布日期: 2026-06-06
运行平台: Windows(tkinter GUI)/ Linux(命令行回退)
技术栈: Python 标准库,零第三方依赖
功能特性
核心功能
| 功能 | 说明 |
|---|---|
| PinMAP → PinList | 自动识别方形/长方形结构,沿四条边(左→下→右→上)逆时针提取引脚,生成 PinList |
| PinList → PinMAP | 根据引脚列表和网格尺寸,自动计算布局并生成 PinMAP |
| 数据验证 | 双向验证:检测序号不连续、序号重复、PinName 缺失、A1 封装信息缺失、周长匹配 |
| 模板样式 | MAP→List 使用 BallList-Template.xlsx,List→MAP 使用 BallMAP-Template.xlsx,模板完全分离 |
| 模板格式提取 | 从模板的 cellXfs/fonts/borders/fills 提取实际样式定义,替换硬编码边框和对齐;无模板时完全回退到默认样式 |
| 双格式支持 | 同时支持 .xls(BIFF8 引擎)和 .xlsx(OOXML 引擎) |
| 双模式运行 | GUI 文件选择对话框 + 命令行参数模式 |
验证规则
PinMAP → PinList 验证
- 序号连续性:Pin 序号必须为 1~N 连续整数,无间隔
- 序号唯一性:每个 Pin 序号只能出现一次,无重复
- PinName 完整性:缺失 PinName 的引脚自动标记为 "NC"(警告级别,不中断流程)
- 结构完整性:方形区域至少 2×2,A1 单元格必须包含封装信息
PinList → PinMAP 验证
- 序号连续性:Pin 序号必须从 1 开始连续无缺失
- 序号唯一性:每个 Pin 序号只能出现一次,无重复
- 周长匹配:Pin 总数 = 2×rows + 2×cols − 4(与网格周长一致)
- PinName 完整性:缺失 PinName 的引脚自动标记为 "NC"(警告级别)
- 非 4 倍数提示:Pin 数量不是 4 的倍数时提示(信息级别)
技术栈
零第三方依赖
本项目完全使用 Python 标准库实现,不依赖任何第三方包。
| 模块 | 用途 | 标准库 |
|---|---|---|
xls_reader.py |
BIFF8 解析引擎(~19KB OLE2 解析) | struct |
xlsx_reader.py |
XLSX 读取引擎(ZIP + XML 解析) | zipfile, xml.etree.ElementTree |
xlsx_writer.py |
XLSX 写入引擎(OOXML 构建) | zipfile, xml.etree.ElementTree |
file_selector.py |
文件选择对话框 | tkinter.filedialog |
pinmap_parser.py |
PinMAP 结构解析 | 纯 Python |
pinmap_layout.py |
PinMAP 布局计算 | 纯 Python |
pinmap_generator.py |
PinMAP 生成与输出 | 纯 Python |
pinlist_parser.py |
PinList 文件解析 | 纯 Python |
pinlist_validator.py |
PinList 数据验证 | collections.Counter |
pinlist_generator.py |
PinList 生成 | 纯 Python |
validator.py |
PinMAP 数据验证 | collections.Counter |
template_reader.py |
模板样式提取(含 cellXfs/xfId/applyAlignment/wrapText) | zipfile, xml.etree.ElementTree |
models.py |
数据模型 | dataclasses |
utils.py |
工具函数 | 纯 Python |
核心技术亮点
- BIFF8 手动解析:从零实现 OLE2 复合文档 + BIFF8 记录流解析,支持 SST、LABELSST、NUMBER、FORMULA、RK、MULRK、LABEL 等记录类型
- OOXML 手动构建:不使用 openpyxl/xlrd,纯手工构建
[Content_Types].xml、workbook.xml、sharedStrings.xml、sheet1.xml等 OOXML 结构 - 布局算法:根据网格尺寸自动计算四边引脚分配,支持任意 rows×cols 的矩形封装
- 模板样式引擎:从 xlsx 文件中提取字体、填充、边框、列宽、行高等样式并应用到输出文件
- 模块化架构:解析 → 验证 → 生成 → 输出,各模块职责清晰,接口契约明确
使用方式
模板使用说明(v1.5.0)
从 v1.5.0 开始,两个方向的转换使用各自独立的模板文件:
| 转换方向 | 模板文件 | 查找位置 |
|---|---|---|
| MAP→List | BallList-Template.xlsx |
项目根目录 → 当前工作目录 |
| List→MAP | BallMAP-Template.xlsx |
项目根目录 → 当前工作目录 |
模板格式提取机制
程序从模板的 OOXML styles.xml 和 sheet1.xml 中提取具体的样式定义(字体、边框、填充、对齐、列宽、行高),然后写入输出的 <styleSheet> 中。这种方式是提取式(读取具体属性值)而非直接复制 cellXf 引用,确保即使模板结构复杂也能正确提取关键样式属性。
xl/styles.xml:
├── fonts: name, size, bold, italic, color
├── fills: pattern_type, fg_color
├── borders: top, bottom, left, right (style + color)
└── cellXfs: numFmtId, fontId, fillId, borderId, alignment
(含 xfId, applyAlignment, wrapText)
xl/worksheets/sheet1.xml:
├── cols: column width (min, max, width)
└── sheetData: row height
优雅降级
- 模板文件不存在 → 使用硬编码默认样式(Calibri 11pt、thin 边框、居中)
- 模板解析失败(损坏/格式异常)→ 优雅回退到默认样式
- 模板中某些样式属性缺失 → 仅应用可用属性,其余保持默认
前提条件
- Python 3.6+(推荐 3.8+)
- Windows 环境(GUI 模式需要 tkinter)
- Linux/Mac 环境(仅命令行模式)
交互式模式(推荐)
python main.py
运行后显示转换方向选择菜单:
============================================================
PinMAP ↔ PinList 双向转换器
支持 PinMAP→PinList 与 PinList→PinMAP 互转
支持.xls和.xlsx格式,输出.xlsx格式
============================================================
请选择转换方向:
1 — PinMAP → PinList
2 — PinList → PinMAP
请输入选项 (1/2):
命令行模式
PinMAP → PinList
# 基本用法
python main.py input.xlsx
# 支持 .xls 格式
python main.py input.xls
# 输出文件自动命名为 input_PinList.xlsx
PinList → PinMAP
# 命令行模式:需提供文件路径
python main.py input_PinList.xlsx
# 运行后需要手动输入 PinMAP 尺寸:
# 请输入 PinMAP 行数: 6
# 请输入 PinMAP 列数: 6
# 输出文件自动命名为 input_PinList_PinMAP.xlsx
注意:命令行模式下直接传入文件参数时,默认走 PinMAP → PinList 方向。如需 PinList → PinMAP,请使用交互式模式(不带参数运行)选择方向 2。
GUI 模式
# 不带参数运行,弹出方向选择 + 文件选择对话框
python main.py
选择方向后,在对话框中选择 .xls 或 .xlsx 文件,点击"打开"即可开始转换。
使用示例
示例 1:PinMAP → PinList
输入 PinMAP(方形封装):
A B C D E F
1 QFP-44
2 Pin6 6
3 Pin5 5
4 1 Pin1
5 2 Pin2
6 Pin3 Pin4
7 3 4
运行命令:
python main.py QFP44.xlsx
输出:
[INFO] 正在读取文件: QFP44.xlsx
[INFO] 文件读取完成,共 16 个非空单元格
[INFO] 正在解析 PinMAP 结构...
[INFO] 解析完成: 6x6 方形,共 8 个Pin
[INFO] 封装信息: QFP-44
[INFO] 正在验证数据...
[INFO] 验证通过
[INFO] 正在生成 PinList...
[INFO] 正在写入输出文件: QFP44_PinList.xlsx
[SUCCESS] 转换完成!
输出文件: QFP44_PinList.xlsx
封装信息: QFP-44
Pin数量: 8
输出 PinList:
A B
1 QFP-44
2 Pin1 1
3 Pin2 2
4 Pin3 3
5 Pin4 4
6 Pin5 5
7 Pin6 6
示例 2:PinList → PinMAP
输入 PinList:
A B
1 QFN-20
2 VCC 1
3 GND 2
4 IO0 3
5 IO1 4
6 IO2 5
7 IO3 6
8 IO4 7
9 IO5 8
10 IO6 9
11 IO7 10
12 NC 11
13 NC 12
14 NC 13
15 NC 14
16 NC 15
17 NC 16
18 NC 17
19 NC 18
20 NC 19
21 NC 20
运行命令:
python main.py
# 选择方向: 2 (PinList → PinMAP)
# 选择文件: QFN20_PinList.xlsx
# 输入行数: 6
# 输入列数: 6
输出:
[INFO] 正在解析 PinList 文件: QFN20_PinList.xlsx
[INFO] 解析完成: 封装信息 'QFN-20', 共 20 个引脚
[INFO] 正在验证数据...
[INFO] 验证通过
[INFO] 正在生成 PinMAP 并写入: QFN20_PinList_PinMAP.xlsx
[SUCCESS] 转换完成!
输出文件: QFN20_PinList_PinMAP.xlsx
封装信息: QFN-20
PinMAP 尺寸: 6×6
Pin数量: 20
输出 PinMAP(6×6 网格,20 个引脚):
A B C D E F
1 QFN-20 IO8 IO7
2 1 VCC IO6 IO5
3 2 GND IO4 IO3
4 3 IO0 IO2 IO1
5 4 IO1 NC NC
6 20 19 18 17 16
示例 3:尺寸不匹配错误
当 PinList 引脚数与网格周长不匹配时:
[ERROR] 验证未通过,发现 1 个错误:
- Pin数量与网格周长不匹配: 网格 6×6 需要 20 个引脚,但 PinList 有 24 个
转换终止,请修正PinList文件或网格尺寸后重试。
示例 4:使用模板样式
PinList → PinMAP 转换时,程序会自动尝试从同目录下的模板文件读取样式(字体、边框、列宽等),使输出 PinMAP 的格式与目标模板保持一致。
项目结构
pinmap-to-pinlist/
├── Code/
│ ├── src/
│ │ ├── main.py # 主入口:流程编排 + 双向转换
│ │ ├── file_selector.py # 文件选择(GUI + 命令行回退)
│ │ ├── xls_reader.py # XLS (BIFF8) 读取引擎
│ │ ├── xlsx_reader.py # XLSX 读取引擎
│ │ ├── xlsx_writer.py # XLSX 写入引擎(含样式支持)
│ │ ├── pinmap_parser.py # PinMAP 结构解析
│ │ ├── pinmap_layout.py # PinMAP 布局计算(List→MAP)
│ │ ├── pinmap_generator.py # PinMAP 生成与输出(List→MAP)
│ │ ├── pinlist_parser.py # PinList 文件解析(List→MAP)
│ │ ├── pinlist_validator.py # PinList 数据验证(List→MAP)
│ │ ├── pinlist_generator.py # PinList 生成(MAP→List)
│ │ ├── validator.py # PinMAP 数据验证(MAP→List)
│ │ ├── template_reader.py # 模板样式提取(List→MAP)
│ │ ├── models.py # 数据模型
│ │ ├── utils.py # 工具函数
│ │ └── test_pinmap.py # 单元测试
│ └── docs/
│ ├── README.md # 本文档
│ ├── QUICKSTART.md # 快速入门指南
│ ├── RELEASE.md # 版本发布说明
│ ├── architecture-design.md # 架构设计文档
│ └── team.md # 团队成员
├── Test/
│ ├── fixtures/ # 测试夹具
│ │ ├── sample_4x4.xlsx # 标准 4×4 PinMAP
│ │ ├── sample_rect.xlsx # 长方形 PinMAP
│ │ ├── error_gap.xlsx # 序号不连续测试
│ │ ├── error_dup.xlsx # 序号重复测试
│ │ ├── error_empty_a1.xlsx # A1 为空测试
│ │ ├── warning_missing.xlsx # PinName 缺失测试
│ │ ├── BallList-Template.xlsx # MAP→List 样式模板(测试用)
│ │ ├── BallMAP-Template.xlsx # List→MAP 样式模板(测试用)
│ │ ├── template_corrupt.xlsx # 损坏模板回退测试
│ │ ├── template_minimal.xlsx # 最小模板测试
│ │ └── template_narrow.xlsx # 窄列宽模板测试
│ └── test_report.md # 测试报告
├── README.md # 项目根目录 README
├── CHANGELOG.md # 变更日志
└── .gitignore
测试情况
单元测试
运行 python test_pinmap.py(在 Code/src/ 目录下):
基础功能测试(v1.0−v1.2)
| 测试用例 | 说明 | 状态 |
|---|---|---|
test_4x4_parse |
4×4 方形 PinMAP 解析 | ✅ 通过 |
test_4x4_validate |
4×4 方形验证 | ✅ 通过 |
test_missing_names_warning |
PinName 缺失警告 | ✅ 通过 |
test_duplicate_numbers |
序号重复检测 | ✅ 通过 |
test_gap_in_numbers |
序号不连续检测 | ✅ 通过 |
test_empty_cells |
空单元格处理 | ✅ 通过 |
test_no_pins |
无引脚数据检测 | ✅ 通过 |
test_rectangular_parse |
长方形 PinMAP 解析 | ✅ 通过 |
test_12pin_square |
12 引脚方形解析 | ✅ 通过 |
F012 回归测试(v1.5.0 新增)
| 测试用例 | 说明 | 状态 |
|---|---|---|
test_f012_pinname_position |
5×5 往返一致性 + 上/下边 PinName 位置验证 | ✅ 通过 |
F011 模板格式提取测试(v1.5.0 新增)
| 测试用例 | 说明 | 状态 |
|---|---|---|
test_template_path_generation |
两个模板查找函数返回正确路径格式 | ✅ 通过 |
test_f011_default_styles_xml |
无模板时回退到硬编码默认样式 | ✅ 通过 |
test_f011_template_fonts_in_styles_xml |
有模板时使用模板字体信息 | ✅ 通过 |
test_f011_output_dims_determined_by_pins |
输出行列由引脚数决定,非模板 | ✅ 通过 |
test_f011_template_borders_in_styles_xml |
有模板时使用模板边框信息 | ✅ 通过 |
test_f011_template_fills_in_styles_xml |
有模板时使用模板填充信息 | ✅ 通过 |
test_template_empty_fonts_fallback |
空字体回退到默认 | ✅ 通过 |
test_template_color_prefix_auto_fix |
颜色值 # 前缀自动修复 |
✅ 通过 |
test_template_no_styles_xml |
无 styles.xml 时优雅降级 | ✅ 通过 |
集成测试
| 测试用例 | 输入文件 | 说明 | 状态 |
|---|---|---|---|
| TC001 | sample_4x4.xlsx |
标准 4×4 转换(8 Pin) | ✅ 通过 |
| TC002 | sample_rect.xlsx |
长方形转换(13 Pin) | ✅ 通过 |
| TC003 | error_gap.xlsx |
序号不连续检测 | ✅ 通过 |
| TC004 | error_dup.xlsx |
序号重复检测 | ✅ 通过 |
| TC005 | warning_missing.xlsx |
PinName 缺失警告 | ✅ 通过 |
| TC006 | error_empty_a1.xlsx |
A1 为空检测 | ✅ 通过 |
结论:所有 18 个单元测试 + 6 个集成测试全部通过,无阻塞性问题。详见 Test/test_report.md。
解析算法说明
PinMAP → PinList:逆时针提取
PinMAP 以方形/长方形矩阵展示引脚分布:
col A(0) col B(1) col C(2) col D(3)
row 0 [A1=封装]
row 1 [1] [2] [3] [4] ← 上边 Pin 序号
row 2 [PinName] [ ] [PinName] ← PinName 行
row 3 [PinName] [ ] [PinName]
row 4 [13] [12] [11] [10] ← 下边 Pin 序号
引脚沿四条边逆时针提取:
- 左边:从上到下
- 下边:从左到右
- 右边:从下到上
- 上边:从右到左
角点单元格只计数一次(按单元格位置去重)。
PinList → PinMAP:布局计算
根据用户输入的 rows × cols 网格尺寸,将引脚列表按逆时针分配到四条边:
总引脚数 = 2 × rows + 2 × cols − 4
左边: rows 个引脚(从上到下)
下边: cols − 1 个引脚(从左到右)
右边: rows − 2 个引脚(从下到上)
上边: cols − 1 个引脚(从右到左)
PinName 与序号的相对位置:
左边:序号在 (r, 0),PinName 在 (r, 1) → Name 在序号右侧
下边:序号在 (rows, c),PinName 在 (rows-1, c) → Name 在序号上方
右边:序号在 (r, cols),PinName 在 (r, cols-1) → Name 在序号左侧
上边:序号在 (1, c),PinName 在 (2, c) → Name 在序号下方
PinList 输出规则(MAP→List)
- A1 单元格:封装信息(从 PinMAP 的 A1 复制)
- A 列:PinName(缺失时自动设为 "NC")
- B 列:Pin 序号
- 按 Pin 序号递增排序
PinMAP 输出规则(List→MAP)
- A1 单元格:封装信息(从 PinList 的 A1 读取)
- 四边分布:序号 + PinName 按布局算法填入网格
- 缺失 PinName 自动设为 "NC"
- 可选:应用模板样式(字体、边框、列宽、行高)
错误处理
| 级别 | 类型 | 行为 |
|---|---|---|
[FATAL] |
文件格式错误 / 结构错误 / 布局计算失败 | 终止处理,显示错误信息 |
[ERROR] |
数据验证错误(重复/不连续/周长不匹配) | 终止处理,显示详细错误 |
[WARN] |
PinName 缺失 | 提示警告,自动设为 "NC",继续处理 |
[INFO] |
解析进度信息 / 非 4 倍数提示 | 仅显示,不影响流程 |
[SUCCESS] |
转换完成 | 显示输出文件路径和统计信息 |
许可证
内部项目