# 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 输出需要参考模板的单元格样式。 **方案**: 1. 新增 `template_reader.py` 模块,专门解析 `xl/styles.xml` 2. 提取关键样式信息:字体、边框、填充色、对齐方式 3. 在 `pinmap_generator.py` 中应用模板样式到输出文件 4. 模板不存在时,使用默认样式(无边框、默认字体) **OOXML styles.xml 结构**: ```xml ... ... ``` **复杂度评估**:中等。需解析 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 文件,解析封装信息和引脚数据。 ```python """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 数据的完整性和正确性。 ```python """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 """ ``` **验证逻辑**: ```python # 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 分布和单元格坐标。 ```python """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) """ ``` **布局算法**: ```python 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 文件。 ```python """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 的样式信息。 ```python """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 - 列宽从 `` 元素读取,行高从 `` 元素的 `ht` 属性读取 ### 4.6 修改模块:`main.py` **修改内容**:支持双向转换,启动时选择方向。 ```python 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` **修改内容**:支持两种文件类型的过滤。 ```python 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 输入相关的数据模型。 ```python # 新增数据模型 @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 输出)。 ```python 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 的 `` 元素中添加 `s` 属性(style index) - 如果模板不存在,使用最小样式集(无边框、默认字体) ### 4.10 修改模块:`validator.py` **修改内容**:新增 `validate_pinlist()` 函数,与现有 `validate_pinmap()` 并存。 ```python # 新增函数(不修改现有 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 单元测试 ```python # 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 新增模块接口 ```python # 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 个) | | 风险等级 | 中低 | | 第三方依赖 | 零新增 | **结论**: 1. PinList → PinMAP 反向转换在技术上完全可行 2. 核心难点在于 OOXML 样式解析/写入,但可通过最小可用集逐步实现 3. 布局分配算法清晰,公式化计算,边界条件可控 4. 建议分 5 轮迭代开发,先完成核心转换逻辑,再增强模板支持 5. 与现有架构完全兼容,向后无破坏性变更 --- *文档结束 — 请审批后进入编码阶段*