Files
pinmap-to-pinlist/Code/docs/architecture-design.md

32 KiB
Raw Blame History

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 关键技术难点

难点 1PinMAP 布局分配算法

问题:给定 PinListN 个 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+cols1)
  右边 (right)  = rows  2 个 Pin      → Pin (rows+cols)..(rows+2×cols3)
  上边 (top)    = cols  1 个 Pin      → Pin (rows+2×cols2)..N

验证rows + (cols1) + (rows2) + (cols1) = 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 结构

<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         # PinNameA 列,可能为空)


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 时默认为 NCwarning
    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. 缺失 PinNamewarning
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 编码 Agent1-2 个)
风险等级 中低
第三方依赖 零新增

结论

  1. PinList → PinMAP 反向转换在技术上完全可行
  2. 核心难点在于 OOXML 样式解析/写入,但可通过最小可用集逐步实现
  3. 布局分配算法清晰,公式化计算,边界条件可控
  4. 建议分 5 轮迭代开发,先完成核心转换逻辑,再增强模板支持
  5. 与现有架构完全兼容,向后无破坏性变更

文档结束 — 请审批后进入编码阶段