32 KiB
PinMAP ↔ PinList 双向转换器 — 全局架构设计
版本: v2.0
日期: 2026-05-28
架构师: 脚本架构师 (Script Architect)
状态: 待审批
变更: 新增 PinList → PinMAP 反向转换 + 模板格式支持
1. 项目概述
1.1 背景
将 Excel 格式的 PinMAP 文件(方形封装引脚布局图)与 PinList 格式(引脚序号列表)进行双向自动转换,消除手动抄录的低效与错误风险。
- PinMAP → PinList(已有):方形布局图 → 扁平引脚列表
- PinList → PinMAP(新增):扁平引脚列表 → 方形布局图
1.2 核心规则
- PinMAP 为方形/长方形结构,引脚沿四条边分布,左上角为 1 脚,逆时针排序
- 四条边遍历顺序:左→下→右→上(counter-clockwise)
- 四个角点被相邻两边共享(不重复计数)
- 总 Pin 数 = 2 × width + 2 × height − 4
1.3 约束
| 约束项 | 说明 |
|---|---|
| 运行平台 | Windows |
| 技术栈 | Python 标准库,零第三方依赖 |
| 输入格式 | .xls(必须支持)、.xlsx(优先支持) |
| 输出格式 | .xlsx 仅 |
| 交互方式 | 命令行 + 文件选择对话框 |
| 模板 | 可选,根目录 PinMAP-Template.xlsx |
2. 技术可行性评估
2.1 PinList → PinMAP 反向转换
可行性:✅ 完全可行
| 子任务 | 技术可行性 | 说明 |
|---|---|---|
| PinList 读取与解析 | ✅ 低难度 | 复用现有 xls_reader/xlsx_reader,解析 A/B 两列即可 |
| Pin 数据验证 | ✅ 低难度 | 连续性、唯一性检查逻辑与现有 validator 类似 |
| 尺寸输入 | ✅ 低难度 | 命令行 input() 交互,输入行数/列数 |
| PinMAP 布局计算 | ✅ 中难度 | 核心算法:根据 width/height 将 Pin 分配到四条边 |
| PinMAP 生成与输出 | ✅ 中难度 | 需要增强 xlsx_writer 支持模板样式 |
| 模板检测与读取 | ✅ 中难度 | 需新增 xlsx style 读取能力(styles.xml 解析) |
2.2 关键技术难点
难点 1:PinMAP 布局分配算法
问题:给定 PinList(N 个 Pin,已按序号 1..N 排序)和尺寸 (rows, cols),如何将 Pin 分配到四条边?
公式(逆时针,左上角为 1 脚):
总 Pin 数 N = 2 × cols + 2 × rows − 4
边分配(按遍历顺序):
左边 (left) = rows 个 Pin → Pin 1..rows
下边 (bottom) = cols − 1 个 Pin → Pin (rows+1)..(rows+cols−1)
右边 (right) = rows − 2 个 Pin → Pin (rows+cols)..(rows+2×cols−3)
上边 (top) = cols − 1 个 Pin → Pin (rows+2×cols−2)..N
验证:rows + (cols−1) + (rows−2) + (cols−1) = 2×rows + 2×cols − 4 = N ✓
示例:4×8 网格(rows=4, cols=8, N=20)
左边:Pin 1-4 (4个)
下边:Pin 5-12 (8个)
右边:Pin 13-15 (3个)
上边:Pin 16-20 (5个)
总计:4+8+3+5 = 20 ✓
非 4 倍数处理:提示用户 "Pin 数量 {N} 不是 4 的倍数,四条边将不均匀分布",不影响转换。
难点 2:模板样式读取
问题:从零实现的 xlsx_reader 目前只读取单元格值,不读取样式(字体、边框、背景色等)。PinMAP 输出需要参考模板的单元格样式。
方案:
- 新增
template_reader.py模块,专门解析xl/styles.xml - 提取关键样式信息:字体、边框、填充色、对齐方式
- 在
pinmap_generator.py中应用模板样式到输出文件 - 模板不存在时,使用默认样式(无边框、默认字体)
OOXML styles.xml 结构:
<stylesSheet>
<fonts count="1">
<font><sz val="11"/><name val="Calibri"/></font>
</fonts>
<fills count="2">...</fills>
<borders count="1">...</borders>
<cellXfs count="1">
<xf numFmtId="0" fontId="0" fillId="0" borderId="0"/>
</cellXfs>
</stylesSheet>
复杂度评估:中等。需解析 XML 结构,提取 cellXfs(单元格格式)与 fonts/borders/fills 的关联关系。
难点 3:双向交互流程
问题:main.py 需要支持两种转换方向,交互流程不同。
方案:启动时让用户选择方向,然后进入对应的流程。保持与现有 PinMAP→PinList 一致的交互风格(Banner → 文件选择 → 处理 → 结果摘要)。
3. 模块划分
3.1 现有模块(PinMAP → PinList 方向)
Code/src/
├── main.py # 入口:流程编排(需修改)
├── file_selector.py # 文件选择(需修改)
├── xls_reader.py # XLS 解析引擎(不变)
├── xlsx_reader.py # XLSX 解析引擎(不变)
├── pinmap_parser.py # PinMAP 结构解析(不变)
├── validator.py # 数据验证(需修改)
├── pinlist_generator.py # PinList 生成(不变)
├── xlsx_writer.py # XLSX 输出引擎(需修改)
├── models.py # 数据模型(需修改)
└── utils.py # 工具函数(不变)
3.2 新增模块(PinList → PinMAP 方向)
| 模块 | 文件 | 职责 |
|---|---|---|
| PinList 解析器 | pinlist_parser.py |
读取 PinList xlsx,解析 A1 封装信息 + A/B 列 PinName/序号 |
| PinList 验证器 | pinlist_validator.py |
验证 Pin 序号连续性、唯一性、与周长匹配 |
| PinMAP 布局计算 | pinmap_layout.py |
根据尺寸将 Pin 分配到四条边,计算单元格坐标 |
| PinMAP 生成器 | pinmap_generator.py |
组装 PinMAP 单元格数据,应用模板样式 |
| 模板读取器 | template_reader.py |
读取 PinMAP-Template.xlsx 的样式信息 |
3.3 修改后完整目录结构
pinmap-to-pinlist/
├── Code/
│ ├── src/
│ │ ├── main.py # ✏️ 修改:双向选择 + 双流程
│ │ ├── file_selector.py # ✏️ 修改:支持两种文件类型过滤
│ │ ├── models.py # ✏️ 修改:新增 PinList 输入模型
│ │ ├── validator.py # ✏️ 修改:新增 PinList 验证函数
│ │ ├── xlsx_writer.py # ✏️ 修改:支持样式写入
│ │ │
│ │ ├── xls_reader.py # (不变)
│ │ ├── xlsx_reader.py # (不变)
│ │ ├── pinmap_parser.py # (不变)
│ │ ├── pinlist_generator.py # (不变)
│ │ ├── utils.py # (不变)
│ │ │
│ │ ├── pinlist_parser.py # 🆕 PinList 读取与解析
│ │ ├── pinlist_validator.py # 🆕 PinList 数据验证
│ │ ├── pinmap_layout.py # 🆕 PinMAP 布局计算
│ │ ├── pinmap_generator.py # 🆕 PinMAP 生成与输出
│ │ └── template_reader.py # 🆕 模板样式读取
│ └── docs/
│ ├── architecture-design.md # ✏️ 更新
│ └── modification-assessment.md
├── Test/
│ └── fixtures/
│ ├── sample_4x4.xlsx # 现有 PinMAP 测试
│ ├── sample_rect.xlsx
│ ├── sample_pinlist.xlsx # 🆕 PinList 测试
│ └── PinMAP-Template.xlsx # 🆕 模板文件(可选)
├── run.bat # (不变)
└── Releases/
4. 模块详细设计
4.1 新增模块:pinlist_parser.py
职责:读取 PinList 格式的 xlsx 文件,解析封装信息和引脚数据。
"""PinList parser — reads a flat pin list from an Excel file."""
from dataclasses import dataclass
@dataclass
class PinListEntry:
"""A single pin entry from the PinList."""
number: int # Pin 序号(B 列)
name: str # PinName(A 列,可能为空)
class PinListParser:
"""Parse a PinList Excel file."""
def __init__(self, filepath: str):
self._filepath = filepath
def parse(self) -> tuple[str, list[PinListEntry]]:
"""
解析 PinList 文件。
Returns
-------
(package_info, entries)
package_info: A1 单元格的封装信息
entries: 按 Pin 序号排序的引脚列表
Raises
------
StructureError
A1 为空、A/B 列无数据、序号非整数等
"""
# 1. 读取文件(复用 xlsx_reader / xls_reader)
# 2. 提取 A1 封装信息
# 3. 解析 A 列 (PinName) 和 B 列 (Pin序号)
# 4. 按 Pin 序号排序
# 5. 返回 (package_info, entries)
解析规则:
- A1 单元格 = 封装信息(与 PinMAP 方向一致)
- A 列(从 A2 开始)= PinName
- B 列(从 B2 开始)= Pin 序号
- 读取到第一个空行为止
- Pin 序号可能不是按顺序排列的(需要在 validator 中检查)
4.2 新增模块:pinlist_validator.py
职责:验证 PinList 数据的完整性和正确性。
"""PinList validator — checks pin data integrity."""
from models import ValidationResult, ValidationError
def validate_pinlist(
entries: list[PinListEntry],
rows: int,
cols: int,
) -> ValidationResult:
"""
验证 PinList 数据。
检查项:
1. Pin 序号从 1 开始连续无缺失
2. Pin 序号无重复
3. Pin 总数 = 2×rows + 2×cols − 4(周长匹配)
4. Pin 缺少 PinName 时默认为 NC(warning)
5. Pin 数量不是 4 的倍数时提示(info)
Parameters
----------
entries : list[PinListEntry]
已按序号排序的引脚列表
rows : int
用户输入的 PinMAP 行数
cols : int
用户输入的 PinMAP 列数
Returns
-------
ValidationResult
"""
验证逻辑:
# 1. 连续性检查
numbers = sorted(e.number for e in entries)
expected = list(range(1, len(numbers) + 1))
if numbers != expected:
missing = set(expected) - set(numbers)
errors.append(f"Pin序号不连续,缺失: {sorted(missing)}")
# 2. 唯一性检查
if len(numbers) != len(set(numbers)):
errors.append("Pin序号存在重复")
# 3. 周长匹配
expected_total = 2 * rows + 2 * cols - 4
if len(entries) != expected_total:
errors.append(
f"Pin数量 ({len(entries)}) 与网格周长 ({expected_total}) 不匹配"
)
# 4. 缺失 PinName(warning)
missing_names = [e for e in entries if not e.name or not e.name.strip()]
if missing_names:
warnings.append(f"{len(missing_names)} 个引脚缺少 PinName,将默认为 NC")
# 5. 非 4 倍数提示(info)
if len(entries) % 4 != 0:
infos.append(f"Pin数量 ({len(entries)}) 不是 4 的倍数,四条边将不均匀分布")
4.3 新增模块:pinmap_layout.py
职责:根据 PinMAP 尺寸和 PinList 数据,计算每条边的 Pin 分布和单元格坐标。
"""PinMAP layout calculator — distributes pins to four edges."""
from dataclasses import dataclass
from typing import NamedTuple
class CellRef(NamedTuple):
"""Excel cell reference (row, col), 0-based."""
row: int
col: int
@dataclass
class EdgePins:
"""Pins assigned to one edge of the PinMAP."""
edge: str # "left" | "bottom" | "right" | "top"
pins: list[tuple[int, str]] # [(number, name), ...]
cells: list[CellRef] # 对应的单元格坐标
def calculate_layout(
entries: list[PinListEntry],
rows: int,
cols: int,
) -> dict[str, EdgePins]:
"""
计算 PinMAP 布局。
逆时针分配(左上角为 1 脚):
左边 → 下边 → 右边 → 上边
Parameters
----------
entries : list[PinListEntry]
已按序号排序的引脚列表
rows : int
PinMAP 行数
cols : int
PinMAP 列数
Returns
-------
dict[str, EdgePins]
{"left": ..., "bottom": ..., "right": ..., "top": ...}
Raises
------
StructureError
尺寸无效(rows < 2 或 cols < 2)
"""
布局算法:
def calculate_layout(entries, rows, cols):
"""
网格坐标体系(0-based):
假设方形区域从 (1, 0) 开始(Excel 的 A2),
方形区域:行 [1..rows],列 [0..cols-1]
四条边的单元格坐标:
左边 (left): 序号在 (r, 0), Name 在 (r, 1) 其中 r ∈ [1, rows]
下边 (bottom): 序号在 (rows, c), Name 在 (rows-1, c) 其中 c ∈ [1, cols-1]
右边 (right): 序号在 (r, cols-1), Name 在 (r, cols-2) 其中 r ∈ [rows-1, 2]
上边 (top): 序号在 (1, c), Name 在 (2, c) 其中 c ∈ [cols-2, 1]
Pin 分配(按逆时针遍历顺序):
左边: Pin 1..rows → rows 个
下边: Pin (rows+1)..(rows+cols-1) → cols-1 个
右边: Pin (rows+cols)..(rows+2*cols-3) → rows-2 个
上边: Pin (rows+2*cols-2)..N → cols-1 个
"""
示例:4×8 网格
左边 (4个): Pin 1-4
序号: (1,0)=1, (2,0)=2, (3,0)=3, (4,0)=4
Name: (1,1), (2,1), (3,1), (4,1)
下边 (8个): Pin 5-12
序号: (4,1)=5, (4,2)=6, ..., (4,8)=12
Name: (3,1), (3,2), ..., (3,8)
右边 (3个): Pin 13-15
序号: (3,8)=13, (2,8)=14, (1,8)=15
Name: (3,7), (2,7), (1,7)
上边 (5个): Pin 16-20
序号: (1,7)=16, (1,6)=17, ..., (1,3)=20
Name: (2,7), (2,6), ..., (2,3)
4.4 新增模块:pinmap_generator.py
职责:根据布局计算结果,生成 PinMAP 的单元格数据字典,并写入 xlsx 文件。
"""PinMAP generator — builds PinMAP cell data and writes to xlsx."""
from pinmap_layout import calculate_layout, EdgePins
from template_reader import TemplateStyle
from xlsx_writer import write_xlsx_with_style
def generate_pinmap(
entries: list[PinListEntry],
rows: int,
cols: int,
package_info: str,
template_style: TemplateStyle | None = None,
output_path: str | None = None,
) -> dict[str, str]:
"""
生成 PinMAP 布局并写入文件。
Parameters
----------
entries : list[PinListEntry]
PinList 数据
rows : int
PinMAP 行数
cols : int
PinMAP 列数
package_info : str
封装信息(写入 A1)
template_style : TemplateStyle | None
模板样式(可选)
output_path : str | None
输出文件路径
Returns
-------
dict[str, str]
单元格数据字典 {"A1": "封装", "A2": "1", "B2": "Pin1", ...}
"""
# 1. 计算布局
layout = calculate_layout(entries, rows, cols)
# 2. 构建单元格数据
data = {"A1": package_info}
for edge_name, edge in layout.items():
for (pin_num, pin_name), cell in zip(edge.pins, edge.cells):
data[f"{cell_ref(cell)}"] = str(pin_num)
name_cell = get_name_cell(cell, edge_name)
data[f"{cell_ref(name_cell)}"] = pin_name or "NC"
# 3. 写入文件(应用模板样式)
if output_path:
write_xlsx_with_style(data, output_path, template_style)
return data
4.5 新增模块:template_reader.py
职责:读取 PinMAP-Template.xlsx 的样式信息。
"""Template reader — extracts cell styles from a template xlsx file."""
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class FontStyle:
"""Font style."""
name: str = "Calibri"
size: float = 11.0
bold: bool = False
italic: bool = False
color: str = "000000"
@dataclass
class BorderStyle:
"""Border style for a cell."""
top: str = "none" # none | thin | medium | thick
bottom: str = "none"
left: str = "none"
right: str = "none"
color: str = "000000"
@dataclass
class FillStyle:
"""Cell fill style."""
pattern_type: str = "none" # none | solid | gray125
fg_color: str = ""
@dataclass
class AlignmentStyle:
"""Cell alignment."""
horizontal: str = "center" # left | center | right
vertical: str = "center"
@dataclass
class TemplateStyle:
"""Complete style extracted from template."""
fonts: list[FontStyle] = field(default_factory=list)
borders: list[BorderStyle] = field(default_factory=list)
fills: list[FillStyle] = field(default_factory=list)
cell_xfs: list[dict] = field(default_factory=list) # xf index → style mapping
column_widths: dict[int, float] = field(default_factory=dict)
row_heights: dict[int, float] = field(default_factory=dict)
class TemplateReader:
"""Read styles from a template xlsx file."""
def __init__(self, filepath: str):
self._filepath = filepath
def read_styles(self) -> TemplateStyle:
"""
读取模板文件的样式信息。
解析 xl/styles.xml 中的:
- fonts, fills, borders, cellXfs
- 列宽和行高(从 sheetData 读取)
Returns
-------
TemplateStyle
"""
实现要点:
- 复用
xlsx_reader.py的 ZIP 解压和 XML 解析能力 - 新增解析
xl/styles.xml的逻辑 - 提取
cellXfs中每个 xf 索引对应的 fontId/borderId/fillId - 列宽从
<cols>元素读取,行高从<row>元素的ht属性读取
4.6 修改模块:main.py
修改内容:支持双向转换,启动时选择方向。
def main():
show_banner()
# ── 选择转换方向 ──────────────────────────────────────────
direction = select_direction() # "map_to_list" or "list_to_map"
if direction == "map_to_list":
run_map_to_list()
else:
run_list_to_map()
def select_direction() -> str:
"""让用户选择转换方向。"""
print("请选择转换方向:")
print(" 1. PinMAP → PinList (方形布局图转引脚列表)")
print(" 2. PinList → PinMAP (引脚列表转方形布局图)")
while True:
choice = input("请输入选项 (1/2): ").strip()
if choice == "1":
return "map_to_list"
elif choice == "2":
return "list_to_map"
print("无效选项,请重新输入")
def run_map_to_list():
"""PinMAP → PinList 流程(现有逻辑,增加模板格式参考)"""
# 1. 文件选择(PinMAP 文件)
# 2. 读取 Excel
# 3. 解析 PinMAP
# 4. 验证
# 5. 生成 PinList
# 6. 写入 xlsx(如有模板则参考格式)
# 7. 结果摘要
def run_list_to_map():
"""PinList → PinMAP 流程(新增)"""
# 1. 文件选择(PinList 文件)
# 2. 解析 PinList
# 3. 输入尺寸(行数、列数)
# 4. 验证 PinList 数据
# 5. 计算布局
# 6. 生成 PinMAP
# 7. 写入 xlsx(应用模板样式)
# 8. 结果摘要
4.7 修改模块:file_selector.py
修改内容:支持两种文件类型的过滤。
def select_file(direction: str) -> Optional[str]:
"""
文件选择流程。
Parameters
----------
direction : str
"map_to_list" → 选择 PinMAP 文件
"list_to_map" → 选择 PinList 文件
"""
if direction == "map_to_list":
title = "选择 PinMAP 文件"
else:
title = "选择 PinList 文件"
# 复用现有逻辑,仅修改提示文本和对话框标题
4.8 修改模块:models.py
修改内容:新增 PinList 输入相关的数据模型。
# 新增数据模型
@dataclass
class PinListEntry:
"""PinList 中的单个引脚条目。"""
number: int
name: str
@dataclass
class PinMAPLayout:
"""计算出的 PinMAP 布局。"""
package_info: str
rows: int
cols: int
edges: dict[str, EdgePins] # left/bottom/right/top
cells: dict[str, str] # 单元格数据 {"A2": "1", "B2": "Pin1", ...}
# 新增异常
class LayoutError(PinMapError):
"""布局计算错误(尺寸无效、Pin 数量不匹配等)。"""
4.9 修改模块:xlsx_writer.py
修改内容:支持样式写入(用于 PinMAP 输出)。
def write_xlsx_with_style(
data: dict[str, str],
output_path: str,
style: TemplateStyle | None = None,
):
"""
写入 xlsx 文件,可选应用模板样式。
Parameters
----------
data : dict[str, str]
单元格数据
output_path : str
输出路径
style : TemplateStyle | None
模板样式(可选)
"""
# 现有 write_xlsx() 保持不变(用于 PinList 输出)
# 新增 write_xlsx_with_style() 用于 PinMAP 输出(需要样式)
样式写入实现:
- 在 OOXML 中生成
xl/styles.xml - 在 sheet1.xml 的
<c>元素中添加s属性(style index) - 如果模板不存在,使用最小样式集(无边框、默认字体)
4.10 修改模块:validator.py
修改内容:新增 validate_pinlist() 函数,与现有 validate_pinmap() 并存。
# 新增函数(不修改现有 validate_pinmap)
def validate_pinlist(entries, rows, cols) -> ValidationResult:
"""验证 PinList 数据(独立函数,不修改现有 validate_pinmap)"""
建议:将 validate_pinlist() 放在独立的 pinlist_validator.py 中,保持单一职责。
5. 数据流设计
5.1 PinMAP → PinList 流程(现有 + 模板增强)
PinMAP 文件 (.xls/.xlsx)
│
▼
xls_reader / xlsx_reader
│ → dict[(row,col), str]
▼
pinmap_parser.parse_pinmap()
│ → PinMAP
▼
validator.validate_pinmap()
│ → ValidationResult
▼
pinlist_generator.generate_pinlist()
│ → PinList
▼
xlsx_writer.write_xlsx() ← 如有模板,参考格式
│
▼
PinList 文件 (.xlsx)
5.2 PinList → PinMAP 流程(新增)
PinList 文件 (.xls/.xlsx)
│
▼
pinlist_parser.parse()
│ → (package_info, entries)
▼
用户输入:行数、列数
│
▼
pinlist_validator.validate_pinlist()
│ → ValidationResult
│
├─ 缺失 PinName → 提示确认,默认 NC
├─ 非 4 倍数 → 提示
└─ 错误 → 终止
▼
pinmap_layout.calculate_layout()
│ → dict[str, EdgePins]
▼
pinmap_generator.generate_pinmap()
│ → cells dict + 写入文件
│
└─ 检测 PinMAP-Template.xlsx
├─ 存在 → template_reader.read_styles() → 应用样式
└─ 不存在 → 默认样式
▼
PinMAP 文件 (.xlsx)
6. PinList 文件格式规范
6.1 格式定义
| 单元格 | 内容 | 说明 |
|---|---|---|
| A1 | 封装信息 | 如 "QFN-20"、"BGA-256" |
| A2 | PinName1 | 引脚名称 |
| B2 | 1 | Pin 序号 |
| A3 | PinName2 | 引脚名称 |
| B3 | 2 | Pin 序号 |
| ... | ... | ... |
| An | PinNameN | 引脚名称 |
| Bn | N | Pin 序号 |
6.2 示例
A1: "QFN-20"
A2: "VCC" B2: "1"
A3: "GND" B3: "2"
A4: "IN1" B4: "3"
A5: "IN2" B5: "4"
A6: "OUT1" B6: "5"
...
A21: "RST" B21: "20"
6.3 约束
- A1 不能为空(封装信息)
- B 列必须为整数(Pin 序号)
- A 列可为空(缺失时默认 NC)
- Pin 序号从 1 开始连续
- 读取到第一个空行(A 列和 B 列都为空)为止
7. 模板格式规范
7.1 模板文件
- 文件名:
PinMAP-Template.xlsx - 位置:程序根目录(与
run.bat同级) - 作用:提供输出 PinMAP 文件的样式参考
7.2 模板内容
模板文件应包含:
- 正确的方形布局结构(用于参考单元格位置)
- 单元格样式:字体、字号、边框、填充色、对齐方式
- 列宽和行高设置
7.3 模板使用方式
- 模板仅用于样式参考,不用于结构参考
- 程序从模板提取:字体、边框、填充、对齐、列宽、行高
- 实际引脚数据由 PinList 决定
- 模板不存在时,使用默认样式(无边框、Calibri 11号、居中对齐)
8. 交互流程设计
8.1 启动 Banner
============================================================
PinMAP ↔ PinList 双向转换器
自动在方形布局图与引脚列表之间转换
支持.xls和.xlsx格式,输出.xlsx格式
============================================================
请选择转换方向:
1. PinMAP → PinList (方形布局图转引脚列表)
2. PinList → PinMAP (引脚列表转方形布局图)
请输入选项 (1/2):
8.2 PinList → PinMAP 完整交互流程
[INFO] 正在读取 PinList 文件: C:\test\pinlist.xlsx
[INFO] 文件读取完成,共 20 个引脚
[INFO] 封装信息: QFN-20
请输入 PinMAP 尺寸:
行数: 4
列数: 8
[INFO] 正在验证数据...
[WARN] 检测到 2 个引脚缺少 PinName,将默认为 NC
[INFO] Pin数量 (20) 是 4 的倍数,四条边均匀分布
[INFO] 验证通过
[INFO] 正在计算 PinMAP 布局...
[INFO] 布局计算完成: 4×8 网格,左边4个 + 下边8个 + 右边3个 + 上边5个
[INFO] 检测到模板文件: PinMAP-Template.xlsx
[INFO] 正在生成 PinMAP...
[INFO] 正在写入输出文件: C:\test\pinlist_PinMAP.xlsx
[SUCCESS] 转换完成!
输出文件: C:\test\pinlist_PinMAP.xlsx
封装信息: QFN-20
网格尺寸: 4×8
Pin数量: 20
模板: 已应用
8.3 错误处理交互
[ERROR] Pin序号不连续,缺失: [3, 7, 15]
转换终止,请修正 PinList 文件后重试。
按 Enter 键退出...
[ERROR] Pin数量 (18) 与网格周长 (20) 不匹配
网格 4×8 需要 20 个引脚,但 PinList 只有 18 个
转换终止,请检查尺寸或 PinList 数据。
按 Enter 键退出...
9. 工作量评估
9.1 开发任务拆分
| 任务编号 | 任务 | 文件 | 工作量 | 依赖 |
|---|---|---|---|---|
| T1 | PinList 解析器 | pinlist_parser.py |
1h | 无 |
| T2 | PinList 验证器 | pinlist_validator.py |
1h | T1 |
| T3 | PinMAP 布局计算 | pinmap_layout.py |
2h | T1, T2 |
| T4 | PinMAP 生成器 | pinmap_generator.py |
1.5h | T3 |
| T5 | 模板读取器 | template_reader.py |
2.5h | 无 |
| T6 | xlsx_writer 样式支持 | xlsx_writer.py |
2h | T5 |
| T7 | main.py 双向改造 | main.py |
1.5h | T1-T6 |
| T8 | file_selector 修改 | file_selector.py |
0.5h | T7 |
| T9 | models.py 扩展 | models.py |
0.5h | T1 |
| T10 | 测试用例 | test_pinlist.py |
2h | T1-T9 |
| T11 | 测试数据文件 | Test/fixtures/ |
1h | T10 |
总计预估:约 15.5 小时(约 2 个工作日)
9.2 复杂度分级
| 复杂度 | 任务 | 原因 |
|---|---|---|
| 低 | T1, T2, T8, T9 | 逻辑简单,复用现有代码 |
| 中 | T3, T4, T7 | 核心业务逻辑,需仔细处理边界条件 |
| 高 | T5, T6, T10 | OOXML 样式解析/写入复杂,测试覆盖全面 |
9.3 推荐开发顺序
第1轮(并行):
T1: PinList 解析器
T5: 模板读取器(可独立开发)
第2轮(依赖 T1):
T2: PinList 验证器
T3: PinMAP 布局计算
第3轮(依赖 T2, T3):
T4: PinMAP 生成器
T6: xlsx_writer 样式支持
第4轮(集成):
T7: main.py 双向改造
T8: file_selector 修改
T9: models.py 扩展
第5轮(测试):
T10: 测试用例
T11: 测试数据文件
10. 风险评估
| 风险 | 影响 | 概率 | 缓解措施 |
|---|---|---|---|
| OOXML styles.xml 解析复杂度高 | 高 | 中 | 先实现最小可用样式集(字体+边框),逐步完善 |
| Pin 数量与周长不匹配的用户输入 | 中 | 高 | 在 validator 中严格检查,给出明确的错误提示 |
| 模板文件存在但格式异常 | 中 | 低 | 优雅降级:模板解析失败时回退到默认样式 |
| 非 4 倍数 Pin 数量的边界处理 | 低 | 中 | 明确文档化分配公式,测试各种尺寸组合 |
| 双向交互流程的用户体验 | 中 | 低 | 保持与现有流程一致的交互风格 |
11. 与现有架构的兼容性
11.1 向后兼容
- 现有 PinMAP → PinList 流程完全不受影响
- 新增模块与现有模块松耦合,通过接口交互
- 模板功能为可选增强,不影响无模板场景
11.2 代码复用
| 现有模块 | 复用方式 |
|---|---|
xls_reader.py |
PinList 解析直接复用 |
xlsx_reader.py |
PinList 解析 + 模板读取复用 ZIP/XML 解析 |
models.py |
扩展新增数据模型 |
utils.py |
坐标转换工具直接复用 |
xlsx_writer.py |
扩展新增样式写入能力 |
11.3 零第三方依赖保持
所有新增功能仍使用 Python 标准库实现:
- Excel 读写:
zipfile+xml.etree.ElementTree+struct - 文件选择:
tkinter.filedialog - 样式解析:
xml.etree.ElementTree
12. 测试策略
12.1 单元测试
# test_pinlist.py
def test_pinlist_parse_basic():
"""基本 PinList 解析"""
def test_pinlist_parse_unsorted():
"""PinList 序号非顺序排列"""
def test_pinlist_parse_missing_names():
"""缺失 PinName 的处理"""
def test_pinlist_validate_continuous():
"""连续性验证"""
def test_pinlist_validate_duplicate():
"""重复序号验证"""
def test_pinlist_validate_perimeter_match():
"""周长匹配验证"""
def test_layout_4x4():
"""4×4 网格布局计算"""
def test_layout_4x8():
"""4×8 长方形布局计算"""
def test_layout_non_multiple_of_4():
"""非 4 倍数 Pin 数量布局"""
def test_template_read():
"""模板样式读取"""
def test_pinmap_generate():
"""PinMAP 生成与写入"""
12.2 集成测试
| 测试场景 | 输入 | 预期 |
|---|---|---|
| 正向转换 | sample_4x4.xlsx | 生成 PinList,与手动结果一致 |
| 反向转换 | 生成的 PinList | 还原为原始 PinMAP |
| 往返转换 | PinMAP → PinList → PinMAP | 与原始 PinMAP 一致 |
| 模板应用 | PinList + Template | 输出文件样式与模板一致 |
| 无模板 | PinList(无模板) | 输出文件使用默认样式 |
| 错误输入 | 缺失序号的 PinList | 报错并终止 |
| 尺寸不匹配 | PinList + 错误尺寸 | 报错并终止 |
13. 接口契约
13.1 新增模块接口
# pinlist_parser.py
def parse_pinlist(filepath: str) -> tuple[str, list[PinListEntry]]:
"""
输入: PinList 文件路径
输出: (封装信息, 按序号排序的引脚列表)
"""
# pinlist_validator.py
def validate_pinlist(
entries: list[PinListEntry],
rows: int,
cols: int,
) -> ValidationResult:
"""
输入: 引脚列表 + 尺寸
输出: ValidationResult
"""
# pinmap_layout.py
def calculate_layout(
entries: list[PinListEntry],
rows: int,
cols: int,
) -> dict[str, EdgePins]:
"""
输入: 引脚列表 + 尺寸
输出: 四条边的引脚分配
"""
# pinmap_generator.py
def generate_pinmap(
entries: list[PinListEntry],
rows: int,
cols: int,
package_info: str,
template_style: TemplateStyle | None = None,
output_path: str | None = None,
) -> dict[str, str]:
"""
输入: 引脚数据 + 尺寸 + 封装 + 可选模板样式
输出: 单元格数据字典
"""
# template_reader.py
def read_template_styles(filepath: str) -> TemplateStyle | None:
"""
输入: 模板文件路径
输出: 模板样式(不存在或解析失败时返回 None)
"""
14. 总结
| 项目 | 内容 |
|---|---|
| 新增模块数 | 5 个 |
| 修改模块数 | 5 个 |
| 不变模块数 | 5 个 |
| 技术难度 | 中(核心难点在 OOXML 样式处理) |
| 预估工作量 | ~15.5 小时(约 2 个工作日) |
| 推荐 Agent | Python 编码 Agent(1-2 个) |
| 风险等级 | 中低 |
| 第三方依赖 | 零新增 |
结论:
- PinList → PinMAP 反向转换在技术上完全可行
- 核心难点在于 OOXML 样式解析/写入,但可通过最小可用集逐步实现
- 布局分配算法清晰,公式化计算,边界条件可控
- 建议分 5 轮迭代开发,先完成核心转换逻辑,再增强模板支持
- 与现有架构完全兼容,向后无破坏性变更
文档结束 — 请审批后进入编码阶段