BUG-005: 模板文件名改为 PinMAP-Template.xlsx / PinList-Template.xlsx BUG-006: 布局改为 Number 外侧 + Name 里侧(v1.5.4 最终版) - 从边界往中心:第1圈=Number,第2圈=Name - 上边角点例外处理,15种网格无冲突 - 18/18 单元测试 + 37/37 集成测试全部通过
257 lines
12 KiB
Markdown
257 lines
12 KiB
Markdown
# 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 设计目标
|
||
|
||
1. **Number 在最外侧** —— 打开 Excel 最外圈看到数字
|
||
2. **Name 在里侧** —— 紧挨着 Number 的内圈
|
||
3. **Pin1 永远在左上角** —— Number=1 在左边第一行
|
||
4. **保留 `(rows+cols)*2` 周长公式**
|
||
5. **彻底解决所有 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 坐标(外侧一圈)**:
|
||
|
||
```python
|
||
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)**:
|
||
|
||
```python
|
||
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 布局展开**:
|
||
|
||
```python
|
||
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 2*rows+cols+1..2*(rows+cols) | `(1, cols..1)` | `(2, cols-1..2)` + `(1,0)` + `(1,cols+1)` |
|
||
|
||
### 3.5 冲突验证(程序验证全部通过)
|
||
|
||
```python
|
||
# 已验证全部通过的网格大小(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 中边界检测
|
||
|
||
```python
|
||
# 新布局 → 边界:
|
||
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. 总结
|
||
|
||
1. **BUG-005**:简单改名,15 分钟。
|
||
|
||
2. **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 = 封装信息 ✅
|
||
|
||
3. 工作量:~4 小时(已全部实现并测试通过 ✅)
|
||
|
||
---
|
||
|
||
*文档结束 — v1.5.4 已实现,所有 18 个测试通过*
|