Files
pinmap-to-pinlist/Releases/v1.0.1/source/pinmap_parser.py
Agent 836ad20515 v1.1.0: 增加交互提示、路径输入、窗口属性配置
- main.py: 增加show_banner()启动说明、各阶段[INFO]日志、结果摘要、任意键退出
- file_selector.py: 重写为路径输入→验证→空输入弹窗回退→不存在循环重试
- run.bat: 新建启动脚本(chcp 65001, mode con cols=80 lines=20, color 0B, title固定署名, pause)
- Code/docs/modification-assessment.md: 修改需求评估文档
2026-05-25 17:29:19 +08:00

168 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""PinMAP structure parser.
Reads a dict of {(row, col): str} cells (as produced by xls_reader / xlsx_reader),
detects the rectangular PinMAP boundary, and extracts pins in
counter-clockwise order starting from the top-left corner.
Usage
-----
>>> from pinmap_parser import parse_pinmap
>>> pinmap = parse_pinmap(cells)
"""
from models import Pin, PinMAP, StructureError
def _try_int(value: str) -> int | None:
"""Try to parse a cell value as an integer pin number.
Returns the int or None if the value is not a valid pin number.
"""
if not value or not str(value).strip():
return None
try:
return int(float(str(value).strip()))
except (ValueError, TypeError):
return None
def parse_pinmap(cells: dict[tuple[int, int], str]) -> PinMAP:
"""Parse a PinMAP from a cell dictionary and return a PinMAP object.
Algorithm
---------
1. Scan all non-empty cells to determine the rectangular boundary
[min_row..max_row] × [min_col..max_col].
2. Read A1 (0,0) as the package-info string.
3. For each of the four edges, collect pin numbers from the boundary
cell and pin names from the adjacent inner cell.
4. Walk the edges counter-clockwise (left → bottom → right → top),
deduplicating corner pins by number.
Parameters
----------
cells : dict mapping (row, col) → cell text (0-based).
Returns
-------
PinMAP
Raises
------
StructureError
If the cell map is empty, the boundary is too small, A1 is
missing, or no pins are detected.
"""
if not cells:
raise StructureError("文件为空,无单元格数据")
# ── Step 1: determine rectangular boundary ───────────────────
# Exclude (0,0) — it holds the package-info label, not PinMAP data.
pin_cells = {
rc: v for rc, v in cells.items()
if rc != (0, 0) and v and str(v).strip()
}
if not pin_cells:
raise StructureError("未检测到任何 Pin 数据")
rows = {r for r, _ in pin_cells}
cols = {c for _, c in pin_cells}
min_row, max_row = min(rows), max(rows)
min_col, max_col = min(cols), max(cols)
width = max_col - min_col + 1
height = max_row - min_row + 1
if width < 2 or height < 2:
raise StructureError(
f"方形区域太小: {width}x{height},至少需要 2x2"
)
# ── Step 2: package info from A1 ─────────────────────────────
package_info = cells.get((0, 0), "")
if not package_info or not str(package_info).strip():
raise StructureError("A1 单元格为空,缺少封装信息")
# ── Step 3: build name lookup ────────────────────────────────
# For each edge, pin names live in the cell *adjacent inward*
# from the boundary cell that holds the pin number.
#
# left : number at (r, min_col), name at (r, min_col+1)
# bottom : number at (max_row, c), name at (max_row-1, c)
# right : number at (r, max_col), name at (r, max_col-1)
# top : number at (min_row, c), name at (min_row+1, c)
name_map: dict[tuple[int, int], str] = {}
# left edge names
for r in range(min_row, max_row + 1):
name = cells.get((r, min_col + 1), "")
if name and str(name).strip():
name_map[(r, min_col)] = str(name).strip()
# bottom edge names
for c in range(min_col, max_col + 1):
name = cells.get((max_row - 1, c), "")
if name and str(name).strip():
name_map[(max_row, c)] = str(name).strip()
# right edge names
for r in range(min_row, max_row + 1):
name = cells.get((r, max_col - 1), "")
if name and str(name).strip():
name_map[(r, max_col)] = str(name).strip()
# top edge names
for c in range(min_col, max_col + 1):
name = cells.get((min_row + 1, c), "")
if name and str(name).strip():
name_map[(min_row, c)] = str(name).strip()
# ── Step 4: walk edges counter-clockwise ─────────────────────
# Deduplicate by *cell position* (corners are shared cells),
# NOT by pin number — duplicate numbers are a data error for
# the validator to catch.
pins: list[Pin] = []
seen_cells: set[tuple[int, int]] = set()
def _add_pin(r: int, c: int, edge: str, pos: int) -> None:
if (r, c) in seen_cells:
return # corner cell already processed
seen_cells.add((r, c))
num = _try_int(cells.get((r, c), ""))
if num is None:
return
pins.append(Pin(
number=num,
name=name_map.get((r, c), ""),
edge=edge,
position_on_edge=pos,
))
# 4a. Left edge: top → bottom
for r in range(min_row, max_row + 1):
_add_pin(r, min_col, "left", r - min_row)
# 4b. Bottom edge: left → right (skip min_col corner already done)
for c in range(min_col + 1, max_col + 1):
_add_pin(max_row, c, "bottom", c - min_col)
# 4c. Right edge: bottom → top (skip max_row corner already done)
for r in range(max_row - 1, min_row - 1, -1):
_add_pin(r, max_col, "right", max_row - r)
# 4d. Top edge: right → left (skip max_col corner already done)
for c in range(max_col - 1, min_col - 1, -1):
_add_pin(min_row, c, "top", max_col - c)
if not pins:
raise StructureError("未检测到任何 Pin 数据")
return PinMAP(
package_info=str(package_info).strip(),
pins=pins,
width=width,
height=height,
grid_origin=(min_row, min_col),
raw_cells=cells,
)