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: 修改需求评估文档
This commit is contained in:
167
Releases/v1.0.1/source/pinmap_parser.py
Normal file
167
Releases/v1.0.1/source/pinmap_parser.py
Normal file
@@ -0,0 +1,167 @@
|
||||
"""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,
|
||||
)
|
||||
Reference in New Issue
Block a user