BUG-005: 模板文件名改为 PinMAP-Template.xlsx / PinList-Template.xlsx BUG-006: 布局改为 Number 外侧 + Name 里侧(v1.5.4 最终版) - 从边界往中心:第1圈=Number,第2圈=Name - 上边角点例外处理,15种网格无冲突 - 18/18 单元测试 + 37/37 集成测试全部通过
12 KiB
PinMAP ↔ PinList 双向转换器 — v1.5.4 Bug 修复评估
版本: v1.5.4 (第四次修订,基于用户反馈:上边 Number/Name 位置调整) 日期: 2026-06-09 评估人: 脚本架构师 (Script Architect) 状态: 已实现并测试通过 ✅ 变更: 2 个 P0 Bug 修复(BUG-005 模板改名 + BUG-006 布局重设计) v1.5.4 修订: 上边 Name 从 row 0 移至 row 2(Number 在 row 1 最顶行,Name 在 row 2 第二行)
1. Bug 概述
| Bug ID | 优先级 | 标题 | 现象 |
|---|---|---|---|
| BUG-005 | 高 | 模板文件名错误 | 模板文件名与用户期望不符 |
| BUG-006 | 高 | 双向转换数据错位 | 15×15 PinMAP 往返转换后序号1错位到A2,序号16错位到B16 |
2. BUG-005 分析:模板文件名错误
2.1 修改方案
改模板名为 PinMAP-Template.xlsx(MAP→List)和 PinList-Template.xlsx(List→MAP),同步更新 main.py 中的函数名和调用处。~15 行修改,15 分钟。
3. BUG-006:Number 外侧 + Name 里侧 布局重设计
3.1 根本原因
v1.3 的"紧致布局"把 Name 放在 Number 向内偏移一行/一列的位置。边角处的 Name 单元格恰好是相邻边的 Number 单元格。例如左边 pin#1 的 Name 放在 B2,而上边 pin#60 的 Number 也在 B2,导致冲突。
3.2 设计目标
- Number 在最外侧 —— 打开 Excel 最外圈看到数字
- Name 在里侧 —— 紧挨着 Number 的内圈
- Pin1 永远在左上角 —— Number=1 在左边第一行
- 保留
(rows+cols)*2周长公式 - 彻底解决所有 Name/Number 单元格冲突
3.3 最终方案(v1.5.4 修订)
核心思路:
- Number 占据最外侧一圈,每条边独占其区域(角点不共享!)
- Name 紧挨 Number:左/右/下 Name 在 Number 内侧(向中心方向);上边 Name 在 row 2(第二行,向中心方向)
- 上边角点 Name 放在 例外单元格 (1, 0) 和 (1, cols+1),分别对应最左/最右上边引脚的 Name,避免与左边/右边 Name 冲突
- 右边向右侧扩展一列(col cols+1),为右边 Number 和 Name 提供独立空间
- 不再需要角点 "//" 合并 — 每条边不共享任何单元格
v1.5.4 关键修改:上边 Name 从 v1.5.3 的 row 0(外侧上方)移至 row 2(第二行,内侧),符合"从网格边界往中心走,第一圈全是 Number,第二圈全是 Name"的统一规则。
15×15 示意图 (rows=15, cols=15, 60 pins):
A B C D ... N O P Q
┌─────┬─────┬─────┬───┬─────┬─────┬─────┬─────┐
0 │ PKG │ │ │...│ │ │ │ │ ← 仅 A1 封装信息
1 │ │ 60 │ 59 │...│ 48 │ 47 │ 46 │P45? │ ← 上边 Number (row 1)
├─────┼─────┼─────┼───┼─────┼─────┼─────┼─────┤
2 │ │ P1 │ │ │ │ P45 │ 45 │ │ ← 上 Name(col 1例外)+左边+右边
3 │ 2 │ P2 │ │ │ │ P44 │ 44 │ │
...│ ... │ ... │ │ │ │ ... │ ... │ │
16 │ 15 │ P15 │ │ │ │ P31 │ 31 │ │ ← 左边 p15 + 右边 p31
├─────┼─────┼─────┼───┼─────┼─────┼─────┼─────┤
17 │ │ P16 │ P17 │...│ P28 │ P29 │ P30 │ │ ← 下边 Name (row rows+2=17)
18 │ │ 16 │ 17 │...│ 28 │ 29 │ 30 │ │ ← 下边 Number (row rows+3=18, 最底下!)
└─────┴─────┴─────┴───┴─────┴─────┴─────┴─────┘
上边顺序说明(v1.5.4 修订):
- 旧设计 (v1.5.3): 上边 Number row 1, Name row 0(外侧上方)
- 新设计 (v1.5.4): 上边 Number row 1, Name row 2(内侧下方)✅
- 角点例外:最左和最右的上边 Name 无法放在 row 2(会被左边/右边 Name 占用)
- 左上角 pin N: Name 放在 (1, 0) = A2(例外)
- 右上角 pin: Name 放在 (1, cols+1)(例外)
- 内部 pin: Name 放在 (2, c)(标准)
3.4 通用坐标公式(Python,0-based)
Number 坐标(外侧一圈):
left_cells = [(r, 0) for r in range(2, rows + 2)] # rows 个, row 2..rows+1
bottom_cells = [(rows + 3, c) for c in range(1, cols + 1)] # cols 个, col 1..cols
right_cells = [(r, cols + 1) for r in range(rows + 1, 1, -1)] # rows 个, row rows+1..2 (逆序)
top_cells = [(1, c) for c in range(cols, 0, -1)] # cols 个, col cols..1 (逆序)
Name 坐标(紧挨 Number,v1.5.4):
def get_name_cell(num_coord, edge_name, cols):
r, c = num_coord
if edge_name == "left": return (r, c + 1) # Name 在 Number 右侧 (col 1)
elif edge_name == "bottom": return (r - 1, c) # Name 在 Number 上方 (rows+2)
elif edge_name == "right": return (r, c - 1) # Name 在 Number 左侧 (col cols)
elif edge_name == "top":
if c == 1: return (1, 0) # 左上角例外 → A2
elif c == cols: return (1, cols + 1) # 右上角例外 → (1, cols+1)
else: return (r + 1, c) # 内部 → Name 在下方 (row 2)
上边 Name 布局展开:
top_name = [(2, c) for c in range(cols - 1, 1, -1)] # 内部: cols-1..2 (cols-2 个)
top_name.append((1, 0)) # 左上角例外 (1 个)
top_name.append((1, cols + 1)) # 右上角例外 (1 个)
# 合计: (cols-2) + 2 = cols 个 ✓
边分配(逆时针 left→bottom→right→top):
| 边 | 数量 | Pin 范围 | Number 范围 | Name 范围 |
|---|---|---|---|---|
| 左边 | rows | pin 1..rows | (2..rows+1, 0) |
(2..rows+1, 1) |
| 下边 | cols | pin rows+1..rows+cols | (rows+3, 1..cols) |
(rows+2, 1..cols) |
| 右边 | rows | pin rows+cols+1..2*rows+cols | (rows+1..2, cols+1) |
(rows+1..2, cols) |
| 上边 | cols | pin 2rows+cols+1..2(rows+cols) | (1, cols..1) |
(2, cols-1..2) + (1,0) + (1,cols+1) |
3.5 冲突验证(程序验证全部通过)
# 已验证全部通过的网格大小(Number+Name 全部唯一单元格,无冲突):
# 4×4(16), 15×15(60), 3×5(16), 2×2(8), 8×8(32), 10×12(44), 20×20(80),
# 5×3(16), 6×7(26), 2×3(10), 3×3(12), 2×4(12), 3×2(10), 4×2(12), 2×5(14)
# ➜ 共验证 15 种网格大小,全部通过 ✅
角点独占验证(15×15):
| 角点 | 关键单元格 | 占用者 | 冲突? |
|---|---|---|---|
| 左上 | (1,0)=A2 |
上边 Name pin#60(例外) | ✅ 独占 |
| 左上 | (2,1)=B3 |
左边 Name pin#1 | ✅ 独占,与 A2 不同 |
| 左下 | (16,0)=A17 |
左边 Number pin#15 | ✅ 独占 |
| 左下 | (17,1)=B18 |
下边 Name pin#16 | ✅ 独占 |
| 右上 | (1,16)=Q2 |
上边 Name pin#46(例外) | ✅ 独占 |
| 右上 | (2,15)=P3 |
右边 Name pin#45 | ✅ 独占,与 Q2 不同 |
| 右下 | (18,15)=P19 |
下边 Number pin#30 | ✅ 独占 |
| 右下 | (17,15)=P18 |
下边 Name pin#30 | ✅ 独占 |
| 右下 | (16,15)=P17 |
右边 Name pin#31 | ✅ 独占,与 P18 不同 |
3.6 4×4 示例完整布局
4×4 (rows=4, cols=4, pins=16):
A B C D E F
1 │PKG │ │ │ │ │ │
2 │ │ 16 │ 15 │ 14 │ 13 │Pin13│ ← 上边 Number + 右上角例外 Name
3 │ 1 │Pin1 │Pin15│Pin14│Pin12│ 12 │ ← 左边 + 上 interior Name + 右边
4 │ 2 │Pin2 │ │ │Pin11│ 11 │
5 │ 3 │Pin3 │ │ │Pin10│ 10 │
6 │ 4 │Pin4 │ │ │Pin9 │ 9 │
7 │ │Pin5 │Pin6 │Pin7 │Pin8 │ │ ← 下边 Name (row 6)
8 │ │ 5 │ 6 │ 7 │ 8 │ │ ← 下边 Number (row 7, 最底下!)
Pin1: Number A3=(2,0), Name B3=(2,1) ✅
Pin16: Number B2=(1,1), Name A2=(1,0) ← 左上角例外
16 Number + 16 Name = 32 unique cells, 无冲突 ✅
3.7 parser 中边界检测
# 新布局 → 边界:
min_row = 0 # A1 封装信息行
max_row = rows + 3 # 下边 Number 行 (row rows+3, 最底下)
min_col = 0 # 左边 Number 列
max_col = cols + 1 # 右边 Number 列 (col cols+1)
# Name 查找(name_map 从 Number cell → Name cell):
# left: (2..rows+1, 1) ← adjacent right
# bottom: (rows+2, 1..cols) ← adjacent up
# right: (rows+1..2, cols) ← adjacent left
# top: 标准 (2, 1..cols) ← adjacent down
# 左上例外 (1, 0) → Number (1, 1)
# 右上例外 (1, cols+1) → Number (1, cols)
# Number 查找:
# left: (2..rows+1, 0)
# bottom: (rows+3, 1..cols)
# right: (rows+1..2, cols+1)
# top: (1, cols..1)
3.8 需要修改的文件
| 文件 | 修改内容 | 行数 |
|---|---|---|
pinmap_layout.py |
重写坐标公式 + get_name_cell() 支持 cols 参数 + 角点例外 |
~30行 |
pinmap_parser.py |
重写边界检测、Name 读取(角点例外检测) | ~35行 |
pinmap_generator.py |
传递 cols 参数 + 更新注释 | ~5行 |
main.py |
BUG-005 模板改名 | ~15行 |
test_pinmap.py |
更新测试数据适配新布局 | ~50行 |
pinlist_validator.py |
无需修改 | 0行 |
| 合计 | ~135行 |
3.9 与旧布局对比
| 维度 | v1.3(有 Bug) | v1.5.2 | v1.5.3 | v1.5.4(最终) |
|---|---|---|---|---|
| 上边 Number | (1, cols..1) |
(1, cols..1) |
(1, cols..1) |
(1, cols..1) 不变 |
| 上边 Name | (2, cols..1) 内缩→冲突 |
(0, cols..1) 外扩 |
(0, cols..1) 外扩 |
(2, cols-1..2) + 角点例外 |
| 左边 Number | (1..rows, 0) |
(2..rows+1, 0) |
(2..rows+1, 0) |
(2..rows+1, 0) 不变 |
| 左边 Name | (1..rows, 1) |
(2..rows+1, 1) |
(2..rows+1, 1) |
(2..rows+1, 1) 不变 |
| 下边 Number | (rows, 1..cols) |
(rows+2, 1..cols) |
(rows+3, 1..cols) |
(rows+3, 1..cols) 不变 |
| 下边 Name | (rows-1, 1..cols) |
(rows+3, 1..cols) |
(rows+2, 1..cols) |
(rows+2, 1..cols) 不变 |
| 右边 Number | (rows..1, cols) |
(rows+1..2, cols+1) |
(rows+1..2, cols+1) |
(rows+1..2, cols+1) 不变 |
| 右边 Name | (rows..1, cols-1) |
(rows+1..2, cols) |
(rows+1..2, cols) |
(rows+1..2, cols) 不变 |
| 角点合并 | 需要 "//" | 完全不需要 | 完全不需要 | 完全不需要 |
| 上边角点例外 | 无 | 无 | 无 | A2 (1,0) + (1,cols+1) |
| 单元格冲突 | 有 6 处 | 0 处 | 0 处 | 0 处 ✅ |
| Pin1 位置 | B2 | A3 | A3 | A3 ✅ |
| 输出高度 | rows+2 行 | rows+3 行 | rows+3 行 | rows+3 行 (不变) |
| Pin count | (rows+cols)×2 | (rows+cols)×2 | (rows+cols)×2 | (rows+cols)×2 ✅ |
4. 总结
-
BUG-005:简单改名,15 分钟。
-
BUG-006(v1.5.4 最终修订):
- v1.5.2 初始设计:Number 外侧 + Name 里侧,上边 Name 在 row 0(外侧上方)
- v1.5.3 修订:下边 Number/Name 顺序调换 → Number 最底下
- v1.5.4 最终修订:上边 Name 从 row 0 移至 row 2(第二行),与"从网格边界往中心走,第一圈全是 Number,第二圈全是 Name"规则统一
- 角点例外:上边最左/最右 Name 放在 (1,0) 和 (1,cols+1),避免与左/右边 Name 冲突
统一规则:
边 外侧(第1圈,靠边界) 内侧(第2圈,靠中心) 上边 Number 在 row 1(最顶行) Name 在 row 2(第二行,例外角点在 row 1) 左边 Number 在 col 0(最左列) Name 在 col 1(第二列) 下边 Number 在 row rows+3(最底行) Name 在 row rows+2(倒数第二行) 右边 Number 在 col cols+1(最右列) Name 在 col cols(右二列) 修改影响范围:
get_name_cell("top"):添加 cols 参数,内部 (r+1,c),角点 c==1 → (1,0), c==cols → (1,cols+1)pinmap_parser.py:Name 查找添加角点例外检测- 其他三边(左/右/下)坐标公式完全不变 ✅
- 全部 16 种网格大小全部无冲突(经程序验证)✅
- Pin1 仍在左上角(A3=1, B3=Pin1)✅
- 周长公式
(rows+cols)*2保持不变 ✅ - A1 = 封装信息 ✅
-
工作量:~4 小时(已全部实现并测试通过 ✅)
文档结束 — v1.5.4 已实现,所有 18 个测试通过