v1.3.0: 修复pinmap_layout周长公式,新增PinList→PinMAP反向转换完整支持
This commit is contained in:
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [v1.3.0] - 2026-06-01
|
||||||
|
|
||||||
|
### 🐛 Bug 修复
|
||||||
|
|
||||||
|
- **pinmap_layout.py**: 周长公式从 `2(rows+cols)−4` 改为 `(rows+cols)×2` — 修复角点共享策略,每条边独立包含其端点
|
||||||
|
- **pinmap_generator.py**: 角点单元格写入 `"6/7"` 格式 — 修复 v1.3 下角点引脚丢失问题
|
||||||
|
- **pinmap_parser.py**: 读取时包含角点,按 `"/"` 拆分解析多引脚序号 — 修复 roundtrip 丢失引脚问题
|
||||||
|
|
||||||
## [v1.2.0] - 2026-05-26
|
## [v1.2.0] - 2026-05-26
|
||||||
|
|
||||||
### 🐛 Bug 修复
|
### 🐛 Bug 修复
|
||||||
|
|||||||
140
Code/src/main.py
140
Code/src/main.py
@@ -33,6 +33,29 @@ def wait_for_exit():
|
|||||||
|
|
||||||
# ── Path helpers ────────────────────────────────────────────────────
|
# ── Path helpers ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _find_template_path() -> str | None:
|
||||||
|
"""查找根目录下的 PinMAP-Template.xlsx。
|
||||||
|
|
||||||
|
搜索顺序:
|
||||||
|
1. 与 run.bat 同级的根目录
|
||||||
|
2. 当前工作目录
|
||||||
|
"""
|
||||||
|
# 从 Code/src 回退到根目录
|
||||||
|
src_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
root_dir = os.path.dirname(os.path.dirname(src_dir)) # pinmap-to-pinlist/
|
||||||
|
template_path = os.path.join(root_dir, "PinMAP-Template.xlsx")
|
||||||
|
|
||||||
|
if os.path.exists(template_path):
|
||||||
|
return template_path
|
||||||
|
|
||||||
|
# 回退到当前工作目录
|
||||||
|
cwd_template = os.path.join(os.getcwd(), "PinMAP-Template.xlsx")
|
||||||
|
if os.path.exists(cwd_template):
|
||||||
|
return cwd_template
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _build_output_path_map_to_list(input_path: str) -> str:
|
def _build_output_path_map_to_list(input_path: str) -> str:
|
||||||
"""Generate output path: {original_filename}_PinList.xlsx"""
|
"""Generate output path: {original_filename}_PinList.xlsx"""
|
||||||
base, _ = os.path.splitext(input_path)
|
base, _ = os.path.splitext(input_path)
|
||||||
@@ -55,7 +78,8 @@ def run_map_to_list(filepath: str):
|
|||||||
from pinmap_parser import parse_pinmap
|
from pinmap_parser import parse_pinmap
|
||||||
from validator import validate_pinmap
|
from validator import validate_pinmap
|
||||||
from pinlist_generator import generate_pinlist
|
from pinlist_generator import generate_pinlist
|
||||||
from xlsx_writer import write_xlsx
|
from xlsx_writer import write_xlsx, write_xlsx_with_style
|
||||||
|
from template_reader import read_template_styles
|
||||||
from models import FileFormatError, StructureError
|
from models import FileFormatError, StructureError
|
||||||
|
|
||||||
# ── 1. File selection ───────────────────────────────────────────
|
# ── 1. File selection ───────────────────────────────────────────
|
||||||
@@ -126,7 +150,22 @@ def run_map_to_list(filepath: str):
|
|||||||
data[f'A{row}'] = pin_name
|
data[f'A{row}'] = pin_name
|
||||||
data[f'B{row}'] = str(pin_num)
|
data[f'B{row}'] = str(pin_num)
|
||||||
|
|
||||||
write_xlsx(data, output_path)
|
# 尝试读取模板样式(F007)
|
||||||
|
template_path = _find_template_path()
|
||||||
|
template_style = None
|
||||||
|
if template_path:
|
||||||
|
template_style = read_template_styles(template_path)
|
||||||
|
if template_style:
|
||||||
|
print(f"[INFO] 已加载模板样式: {template_path}")
|
||||||
|
else:
|
||||||
|
print("[WARN] 模板文件存在但解析失败,使用默认样式")
|
||||||
|
else:
|
||||||
|
print("[INFO] 未检测到模板文件,使用默认样式")
|
||||||
|
|
||||||
|
if template_style is not None:
|
||||||
|
write_xlsx_with_style(data, output_path, template_style)
|
||||||
|
else:
|
||||||
|
write_xlsx(data, output_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[FATAL] 输出失败: {e}")
|
print(f"[FATAL] 输出失败: {e}")
|
||||||
wait_for_exit()
|
wait_for_exit()
|
||||||
@@ -139,7 +178,7 @@ def run_map_to_list(filepath: str):
|
|||||||
print(f" 封装信息: {pinlist.package_info}")
|
print(f" 封装信息: {pinlist.package_info}")
|
||||||
print(f" Pin数量: {len(pinlist.rows)}")
|
print(f" Pin数量: {len(pinlist.rows)}")
|
||||||
|
|
||||||
wait_for_exit()
|
# F008: 不再退出,返回主循环
|
||||||
|
|
||||||
|
|
||||||
# ── Direction 2: List → MAP ────────────────────────────────────────
|
# ── Direction 2: List → MAP ────────────────────────────────────────
|
||||||
@@ -221,8 +260,17 @@ def run_list_to_map(filepath: str):
|
|||||||
print(f"[INFO] 正在生成 PinMAP 并写入: {output_path}")
|
print(f"[INFO] 正在生成 PinMAP 并写入: {output_path}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 尝试读取模板样式(优雅降级)
|
# 尝试读取模板样式(F007 — 从根目录读取而非输入文件路径)
|
||||||
template_style = read_template_styles(filepath)
|
template_path = _find_template_path()
|
||||||
|
template_style = None
|
||||||
|
if template_path:
|
||||||
|
template_style = read_template_styles(template_path)
|
||||||
|
if template_style:
|
||||||
|
print(f"[INFO] 已加载模板样式: {template_path}")
|
||||||
|
else:
|
||||||
|
print("[WARN] 模板文件存在但解析失败,使用默认样式")
|
||||||
|
else:
|
||||||
|
print("[INFO] 未检测到模板文件,使用默认样式")
|
||||||
|
|
||||||
generate_pinmap(
|
generate_pinmap(
|
||||||
entries=entries,
|
entries=entries,
|
||||||
@@ -249,7 +297,7 @@ def run_list_to_map(filepath: str):
|
|||||||
print(f" PinMAP 尺寸: {rows}×{cols}")
|
print(f" PinMAP 尺寸: {rows}×{cols}")
|
||||||
print(f" Pin数量: {len(entries)}")
|
print(f" Pin数量: {len(entries)}")
|
||||||
|
|
||||||
wait_for_exit()
|
# F008: 不再退出,返回主循环
|
||||||
|
|
||||||
|
|
||||||
# ── Main entry ──────────────────────────────────────────────────────
|
# ── Main entry ──────────────────────────────────────────────────────
|
||||||
@@ -257,41 +305,59 @@ def run_list_to_map(filepath: str):
|
|||||||
def main():
|
def main():
|
||||||
show_banner()
|
show_banner()
|
||||||
|
|
||||||
# ── Direction selection ─────────────────────────────────────────
|
# F008: 循环处理流程
|
||||||
if len(sys.argv) > 1:
|
while True:
|
||||||
# Legacy mode: direct file argument → MAP→List
|
# ── Direction selection ─────────────────────────────────────
|
||||||
direction = 1
|
if len(sys.argv) > 1:
|
||||||
filepath = sys.argv[1]
|
# Legacy mode: direct file argument → MAP→List
|
||||||
else:
|
direction = 1
|
||||||
print("请选择转换方向:")
|
filepath = sys.argv[1]
|
||||||
print(" 1 — PinMAP → PinList")
|
sys.argv = [sys.argv[0]] # 清除 argv,下次循环进入交互模式
|
||||||
print(" 2 — PinList → PinMAP")
|
else:
|
||||||
print()
|
print("请选择转换方向:")
|
||||||
|
print(" 1 — PinMAP → PinList")
|
||||||
|
print(" 2 — PinList → PinMAP")
|
||||||
|
print(" Q — 退出程序")
|
||||||
|
print()
|
||||||
|
|
||||||
while True:
|
choice = input("请输入选项 (1/2/Q): ").strip().upper()
|
||||||
choice = input("请输入选项 (1/2): ").strip()
|
if choice == 'Q':
|
||||||
if choice in ('1', '2'):
|
print("感谢使用,再见!")
|
||||||
direction = int(choice)
|
return
|
||||||
break
|
elif choice == '1':
|
||||||
print("[ERROR] 无效选项,请输入 1 或 2")
|
direction = 1
|
||||||
|
elif choice == '2':
|
||||||
|
direction = 2
|
||||||
|
else:
|
||||||
|
print("[ERROR] 无效选项,请输入 1、2 或 Q")
|
||||||
|
continue
|
||||||
|
|
||||||
filepath = None # will be selected inside the flow
|
filepath = None
|
||||||
|
|
||||||
# ── Dispatch ────────────────────────────────────────────────────
|
# ── Dispatch ────────────────────────────────────────────────
|
||||||
if direction == 1:
|
if direction == 1:
|
||||||
|
print()
|
||||||
|
print("─" * 40)
|
||||||
|
print(" 方向: PinMAP → PinList")
|
||||||
|
print("─" * 40)
|
||||||
|
print()
|
||||||
|
run_map_to_list(filepath)
|
||||||
|
else:
|
||||||
|
print()
|
||||||
|
print("─" * 40)
|
||||||
|
print(" 方向: PinList → PinMAP")
|
||||||
|
print("─" * 40)
|
||||||
|
print()
|
||||||
|
run_list_to_map(filepath)
|
||||||
|
|
||||||
|
# ── 处理完成后循环 ──────────────────────────────────────────
|
||||||
print()
|
print()
|
||||||
print("─" * 40)
|
print("=" * 40)
|
||||||
print(" 方向: PinMAP → PinList")
|
next_choice = input("输入 Q 退出,或按 Enter 返回主菜单继续转换: ").strip().upper()
|
||||||
print("─" * 40)
|
if next_choice == 'Q':
|
||||||
print()
|
print("感谢使用,再见!")
|
||||||
run_map_to_list(filepath)
|
return
|
||||||
else:
|
# 否则继续 while 循环,回到主菜单
|
||||||
print()
|
|
||||||
print("─" * 40)
|
|
||||||
print(" 方向: PinList → PinMAP")
|
|
||||||
print("─" * 40)
|
|
||||||
print()
|
|
||||||
run_list_to_map(filepath)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Validates a PinList for:
|
Validates a PinList for:
|
||||||
1. Pin numbers starting from 1 with no gaps
|
1. Pin numbers starting from 1 with no gaps
|
||||||
2. No duplicate pin numbers
|
2. No duplicate pin numbers
|
||||||
3. Total pin count matches grid perimeter (2×rows + 2×cols − 4)
|
3. Total pin count matches grid perimeter (rows + cols) × 2
|
||||||
4. Missing PinName defaults to NC (warning)
|
4. Missing PinName defaults to NC (warning)
|
||||||
5. Pin count not a multiple of 4 (info)
|
5. Pin count not a multiple of 4 (info)
|
||||||
"""
|
"""
|
||||||
@@ -22,7 +22,7 @@ def validate_pinlist(
|
|||||||
检查项:
|
检查项:
|
||||||
1. Pin 序号从 1 开始连续无缺失
|
1. Pin 序号从 1 开始连续无缺失
|
||||||
2. Pin 序号无重复
|
2. Pin 序号无重复
|
||||||
3. Pin 总数 = 2×rows + 2×cols − 4(周长匹配)
|
3. Pin 总数 = (rows + cols) × 2(周长匹配)
|
||||||
4. Pin 缺少 PinName 时默认为 NC(warning)
|
4. Pin 缺少 PinName 时默认为 NC(warning)
|
||||||
5. Pin 数量不是 4 的倍数时提示(info)
|
5. Pin 数量不是 4 的倍数时提示(info)
|
||||||
|
|
||||||
@@ -68,7 +68,8 @@ def validate_pinlist(
|
|||||||
))
|
))
|
||||||
|
|
||||||
# ── 3. 周长匹配 ──────────────────────────────────────────────
|
# ── 3. 周长匹配 ──────────────────────────────────────────────
|
||||||
expected_total = 2 * rows + 2 * cols - 4
|
# 周长公式:(rows + cols) * 2
|
||||||
|
expected_total = (rows + cols) * 2
|
||||||
actual_total = len(entries)
|
actual_total = len(entries)
|
||||||
if actual_total != expected_total:
|
if actual_total != expected_total:
|
||||||
errors.append(ValidationError(
|
errors.append(ValidationError(
|
||||||
|
|||||||
@@ -61,10 +61,17 @@ def generate_pinmap(
|
|||||||
data[name_ref] = pin_name if pin_name and pin_name.strip() else "NC"
|
data[name_ref] = pin_name if pin_name and pin_name.strip() else "NC"
|
||||||
|
|
||||||
# 再写入序号单元格(覆盖同位置的名字,确保序号优先)
|
# 再写入序号单元格(覆盖同位置的名字,确保序号优先)
|
||||||
|
# v1.3: 角点单元格被两条边共享,需写入两个引脚序号
|
||||||
|
cell_pins: dict[str, list[str]] = {}
|
||||||
for edge_name, edge in layout.items():
|
for edge_name, edge in layout.items():
|
||||||
for (pin_num, pin_name), num_cell in zip(edge.pins, edge.cells):
|
for (pin_num, pin_name), num_cell in zip(edge.pins, edge.cells):
|
||||||
num_ref = rc_to_cell_ref(num_cell[0], num_cell[1])
|
num_ref = rc_to_cell_ref(num_cell[0], num_cell[1])
|
||||||
data[num_ref] = str(pin_num)
|
if num_ref not in cell_pins:
|
||||||
|
cell_pins[num_ref] = []
|
||||||
|
cell_pins[num_ref].append(str(pin_num))
|
||||||
|
|
||||||
|
for num_ref, pins in cell_pins.items():
|
||||||
|
data[num_ref] = "/".join(pins)
|
||||||
|
|
||||||
# 3. 写入文件(应用模板样式)
|
# 3. 写入文件(应用模板样式)
|
||||||
if output_path:
|
if output_path:
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ from the top-left corner (pin 1).
|
|||||||
|
|
||||||
Edge assignment (counter-clockwise, top-left = pin 1):
|
Edge assignment (counter-clockwise, top-left = pin 1):
|
||||||
left → rows pins
|
left → rows pins
|
||||||
bottom → cols-1 pins
|
bottom → cols pins
|
||||||
right → rows-2 pins
|
right → rows pins
|
||||||
top → cols-1 pins
|
top → cols pins
|
||||||
|
|
||||||
Total: rows + (cols-1) + (rows-2) + (cols-1) = 2×rows + 2×cols − 4
|
Total: rows + cols + rows + cols = 2×rows + 2×cols = (rows + cols) × 2
|
||||||
|
|
||||||
|
v1.3: 每条边独立包含其端点,角点单元格会被两条边共享。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from models import PinListEntry, EdgePins, LayoutError
|
from models import PinListEntry, EdgePins, LayoutError
|
||||||
@@ -27,6 +29,12 @@ def calculate_layout(
|
|||||||
逆时针分配(左上角为 1 脚):
|
逆时针分配(左上角为 1 脚):
|
||||||
左边 → 下边 → 右边 → 上边
|
左边 → 下边 → 右边 → 上边
|
||||||
|
|
||||||
|
角点分配策略(v1.3:每条边独立包含其端点):
|
||||||
|
- 左边: 包含左下角
|
||||||
|
- 下边: 包含右下角
|
||||||
|
- 右边: 包含右上角
|
||||||
|
- 上边: 包含左上角
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
entries : list[PinListEntry]
|
entries : list[PinListEntry]
|
||||||
@@ -52,13 +60,13 @@ def calculate_layout(
|
|||||||
if cols < 2:
|
if cols < 2:
|
||||||
raise LayoutError(f"列数无效: {cols},至少需要 2 列")
|
raise LayoutError(f"列数无效: {cols},至少需要 2 列")
|
||||||
|
|
||||||
# ── 边分配计数 ────────────────────────────────────────────────
|
# ── 边分配计数(v1.3:每条边独立包含其端点)─────────────────
|
||||||
left_count = rows
|
left_count = rows
|
||||||
bottom_count = cols - 1
|
bottom_count = cols
|
||||||
right_count = rows - 2
|
right_count = rows
|
||||||
top_count = cols - 1
|
top_count = cols
|
||||||
|
|
||||||
total = left_count + bottom_count + right_count + top_count
|
total = (rows + cols) * 2
|
||||||
if len(entries) != total:
|
if len(entries) != total:
|
||||||
raise LayoutError(
|
raise LayoutError(
|
||||||
f"Pin数量 ({len(entries)}) 与网格周长 ({total}) 不匹配"
|
f"Pin数量 ({len(entries)}) 与网格周长 ({total}) 不匹配"
|
||||||
@@ -83,22 +91,24 @@ def calculate_layout(
|
|||||||
# 网格坐标体系(0-based):
|
# 网格坐标体系(0-based):
|
||||||
# 方形区域:行 [1..rows],列 [0..cols]
|
# 方形区域:行 [1..rows],列 [0..cols]
|
||||||
# 左边: 序号在 (r, 0), Name 在 (r, 1) 其中 r ∈ [1, rows]
|
# 左边: 序号在 (r, 0), Name 在 (r, 1) 其中 r ∈ [1, rows]
|
||||||
# 下边: 序号在 (rows, c), Name 在 (rows-1, c) 其中 c ∈ [1, cols-1]
|
# 下边: 序号在 (rows, c), Name 在 (rows-1, c) 其中 c ∈ [1, cols]
|
||||||
# 右边: 序号在 (r, cols), Name 在 (r, cols-1) 其中 r ∈ [rows-1, 2] 逆序
|
# 右边: 序号在 (r, cols), Name 在 (r, cols-1) 其中 r ∈ [rows, 1] 逆序
|
||||||
# 上边: 序号在 (1, c), Name 在 (2, c) 其中 c ∈ [cols-1, 2] 逆序
|
# 上边: 序号在 (1, c), Name 在 (2, c) 其中 c ∈ [cols, 1] 逆序
|
||||||
|
#
|
||||||
|
# v1.3: 每条边独立包含其端点,角点单元格会被两条边共享
|
||||||
#
|
#
|
||||||
|
|
||||||
# 左边:从上到下
|
# 左边:从上到下
|
||||||
left_cells = [(r, 0) for r in range(1, rows + 1)]
|
left_cells = [(r, 0) for r in range(1, rows + 1)]
|
||||||
|
|
||||||
# 下边:从左到右
|
# 下边:从左到右
|
||||||
bottom_cells = [(rows, c) for c in range(1, cols)]
|
bottom_cells = [(rows, c) for c in range(1, cols + 1)]
|
||||||
|
|
||||||
# 右边:从下到上(逆序)
|
# 右边:从下到上(逆序)
|
||||||
right_cells = [(r, cols) for r in range(rows - 1, 1, -1)]
|
right_cells = [(r, cols) for r in range(rows, 0, -1)]
|
||||||
|
|
||||||
# 上边:从右到左(逆序)
|
# 上边:从右到左(逆序)
|
||||||
top_cells = [(1, c) for c in range(cols - 1, 0, -1)]
|
top_cells = [(1, c) for c in range(cols, 0, -1)]
|
||||||
|
|
||||||
# ── 构建 EdgePins ─────────────────────────────────────────────
|
# ── 构建 EdgePins ─────────────────────────────────────────────
|
||||||
def _make_edge(edge_name: str, pin_list: list[PinListEntry],
|
def _make_edge(edge_name: str, pin_list: list[PinListEntry],
|
||||||
|
|||||||
@@ -117,41 +117,49 @@ def parse_pinmap(cells: dict[tuple[int, int], str]) -> PinMAP:
|
|||||||
if name and str(name).strip():
|
if name and str(name).strip():
|
||||||
name_map[(min_row, c)] = str(name).strip()
|
name_map[(min_row, c)] = str(name).strip()
|
||||||
|
|
||||||
# ── Step 4: walk edges counter-clockwise ─────────────────────
|
# ── Step 4: walk edges counter-clockwise (v1.3 formula) ──────
|
||||||
# Deduplicate by *cell position* (corners are shared cells),
|
# Each edge independently includes its endpoints (corners).
|
||||||
# NOT by pin number — duplicate numbers are a data error for
|
# Corner cells are read by two edges — this is expected per
|
||||||
# the validator to catch.
|
# v1.3: total = (rows + cols) × 2.
|
||||||
pins: list[Pin] = []
|
pins: list[Pin] = []
|
||||||
|
|
||||||
seen_cells: set[tuple[int, int]] = set()
|
seen_cells: set[tuple[int, int]] = set()
|
||||||
|
|
||||||
def _add_pin(r: int, c: int, edge: str, pos: int) -> None:
|
def _add_pin(r: int, c: int, edge: str, pos: int) -> None:
|
||||||
|
# Skip if this cell was already processed (corner visited by two edges)
|
||||||
if (r, c) in seen_cells:
|
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
|
return
|
||||||
pins.append(Pin(
|
seen_cells.add((r, c))
|
||||||
number=num,
|
raw = cells.get((r, c), "")
|
||||||
name=name_map.get((r, c), ""),
|
if not raw:
|
||||||
edge=edge,
|
return
|
||||||
position_on_edge=pos,
|
# Handle "6/7" format from corner cells
|
||||||
))
|
parts = str(raw).strip().split("/")
|
||||||
|
for part in parts:
|
||||||
|
num = _try_int(part)
|
||||||
|
if num is None:
|
||||||
|
continue
|
||||||
|
pins.append(Pin(
|
||||||
|
number=num,
|
||||||
|
name=name_map.get((r, c), ""),
|
||||||
|
edge=edge,
|
||||||
|
position_on_edge=pos,
|
||||||
|
))
|
||||||
|
|
||||||
# 4a. Left edge: top → bottom
|
# 4a. Left edge: top → bottom (includes bottom-left corner)
|
||||||
for r in range(min_row, max_row + 1):
|
for r in range(min_row, max_row + 1):
|
||||||
_add_pin(r, min_col, "left", r - min_row)
|
_add_pin(r, min_col, "left", r - min_row)
|
||||||
|
|
||||||
# 4b. Bottom edge: left → right (skip min_col corner already done)
|
# 4b. Bottom edge: left → right (includes bottom-right corner)
|
||||||
for c in range(min_col + 1, max_col + 1):
|
for c in range(min_col, max_col + 1):
|
||||||
_add_pin(max_row, c, "bottom", c - min_col)
|
_add_pin(max_row, c, "bottom", c - min_col)
|
||||||
|
|
||||||
# 4c. Right edge: bottom → top (skip max_row corner already done)
|
# 4c. Right edge: bottom → top (includes top-right corner)
|
||||||
for r in range(max_row - 1, min_row - 1, -1):
|
for r in range(max_row, min_row - 1, -1):
|
||||||
_add_pin(r, max_col, "right", max_row - r)
|
_add_pin(r, max_col, "right", max_row - r)
|
||||||
|
|
||||||
# 4d. Top edge: right → left (skip max_col corner already done)
|
# 4d. Top edge: right → left (includes top-left corner)
|
||||||
for c in range(max_col - 1, min_col - 1, -1):
|
for c in range(max_col, min_col - 1, -1):
|
||||||
_add_pin(min_row, c, "top", max_col - c)
|
_add_pin(min_row, c, "top", max_col - c)
|
||||||
|
|
||||||
if not pins:
|
if not pins:
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ def validate_pinlist_for_map(
|
|||||||
1. **Continuity** — pin numbers must start from 1 with no gaps.
|
1. **Continuity** — pin numbers must start from 1 with no gaps.
|
||||||
2. **Uniqueness** — no duplicate pin numbers.
|
2. **Uniqueness** — no duplicate pin numbers.
|
||||||
3. **Perimeter match** — total pin count must equal
|
3. **Perimeter match** — total pin count must equal
|
||||||
2×rows + 2×cols − 4 (the grid perimeter).
|
(rows + cols) × 2 (the grid perimeter).
|
||||||
4. **Non-multiple-of-4** — if pin count is not a multiple of 4,
|
4. **Non-multiple-of-4** — if pin count is not a multiple of 4,
|
||||||
a warning is issued (but conversion is not blocked).
|
a warning is issued (but conversion is not blocked).
|
||||||
|
|
||||||
@@ -158,7 +158,8 @@ def validate_pinlist_for_map(
|
|||||||
))
|
))
|
||||||
|
|
||||||
# ── 3. Perimeter match ───────────────────────────────────────
|
# ── 3. Perimeter match ───────────────────────────────────────
|
||||||
expected_total = 2 * rows + 2 * cols - 4
|
# 周长公式:(rows + cols) * 2
|
||||||
|
expected_total = (rows + cols) * 2
|
||||||
actual_total = len(entries)
|
actual_total = len(entries)
|
||||||
if actual_total != expected_total:
|
if actual_total != expected_total:
|
||||||
result.errors.append(ValidationError(
|
result.errors.append(ValidationError(
|
||||||
|
|||||||
BIN
Test/fixtures/sample_4x4.xlsx
vendored
BIN
Test/fixtures/sample_4x4.xlsx
vendored
Binary file not shown.
@@ -175,16 +175,13 @@ def test_list_to_map(r: TestRunner):
|
|||||||
tmpdir = tempfile.mkdtemp(prefix="pinmap_test_")
|
tmpdir = tempfile.mkdtemp(prefix="pinmap_test_")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# ── TC-LM-001: 4×4 PinList → 2×2 PinMAP (16引脚) ──
|
# ── TC-LM-001: 5×5 PinList → PinMAP (20引脚) ──
|
||||||
# 2×rows + 2×cols - 4 = 2*4 + 2*4 - 4 = 12, not 16
|
# v1.3: (r+c)*2 = (5+5)*2 = 20 pins
|
||||||
# Actually: for a 4×4 grid: 2*4 + 2*4 - 4 = 12 pins
|
|
||||||
# For 16 pins on a square: 2r + 2c - 4 = 16 → r=c=5 → 5×5 grid
|
|
||||||
# Let's use 5×5 grid for 16 pins
|
|
||||||
def _tc_lm_001(result):
|
def _tc_lm_001(result):
|
||||||
# 5×5 grid → 2*5 + 2*5 - 4 = 16 pins
|
# 5×5 grid → (5+5)*2 = 20 pins
|
||||||
data = {'A1': 'QFP-16'}
|
data = {'A1': 'QFP-20'}
|
||||||
for i in range(1, 17):
|
for i in range(1, 21):
|
||||||
row = i + 1 # row 2..17
|
row = i + 1 # row 2..21
|
||||||
data[f'A{row}'] = f'Pin{i}'
|
data[f'A{row}'] = f'Pin{i}'
|
||||||
data[f'B{row}'] = str(i)
|
data[f'B{row}'] = str(i)
|
||||||
filepath = os.path.join(tmpdir, 'test_5x5_pinlist.xlsx')
|
filepath = os.path.join(tmpdir, 'test_5x5_pinlist.xlsx')
|
||||||
@@ -192,8 +189,8 @@ def test_list_to_map(r: TestRunner):
|
|||||||
|
|
||||||
# Parse
|
# Parse
|
||||||
pkg, entries = parse_pinlist(filepath)
|
pkg, entries = parse_pinlist(filepath)
|
||||||
assert pkg == 'QFP-16', f"封装信息应为QFP-16, 实际: {pkg}"
|
assert pkg == 'QFP-20', f"封装信息应为QFP-20, 实际: {pkg}"
|
||||||
assert len(entries) == 16, f"应有16个引脚, 实际: {len(entries)}"
|
assert len(entries) == 20, f"应有20个引脚, 实际: {len(entries)}"
|
||||||
|
|
||||||
# Validate with 5×5 grid
|
# Validate with 5×5 grid
|
||||||
validation = validate_pinlist(entries, 5, 5)
|
validation = validate_pinlist(entries, 5, 5)
|
||||||
@@ -203,35 +200,32 @@ def test_list_to_map(r: TestRunner):
|
|||||||
# Generate PinMAP
|
# Generate PinMAP
|
||||||
output = generate_pinmap(entries, 5, 5, pkg, output_path=None)
|
output = generate_pinmap(entries, 5, 5, pkg, output_path=None)
|
||||||
assert 'A1' in output, "A1应有封装信息"
|
assert 'A1' in output, "A1应有封装信息"
|
||||||
assert output['A1'] == 'QFP-16'
|
assert output['A1'] == 'QFP-20'
|
||||||
|
|
||||||
result.ok(f"解析成功, 封装={pkg}, Pin数={len(entries)}, 5×5布局验证通过")
|
result.ok(f"解析成功, 封装={pkg}, Pin数={len(entries)}, 5×5布局验证通过")
|
||||||
|
|
||||||
r.run("TC-LM-001: 5×5 PinList→PinMAP (16引脚)", _tc_lm_001)
|
r.run("TC-LM-001: 5×5 PinList→PinMAP (20引脚)", _tc_lm_001)
|
||||||
|
|
||||||
# ── TC-LM-002: 长方形 PinList → 4×8 PinMAP (32引脚) ──
|
# ── TC-LM-002: 长方形 PinList → 6×10 PinMAP (32引脚) ──
|
||||||
# 2*4 + 2*8 - 4 = 8 + 16 - 4 = 20, not 32
|
# v1.3: (r+c)*2 = (6+10)*2 = 32 pins
|
||||||
# For 32 pins: 2r + 2c - 4 = 32
|
|
||||||
# Try 4×16: 2*4 + 2*16 - 4 = 8 + 32 - 4 = 36, no
|
|
||||||
# Try 6×12: 2*6 + 2*12 - 4 = 12 + 24 - 4 = 32 ✓
|
|
||||||
def _tc_lm_002(result):
|
def _tc_lm_002(result):
|
||||||
data = {'A1': 'LQFP-32'}
|
data = {'A1': 'LQFP-32'}
|
||||||
for i in range(1, 33):
|
for i in range(1, 33):
|
||||||
row = i + 1
|
row = i + 1
|
||||||
data[f'A{row}'] = f'PIN_{i:02d}'
|
data[f'A{row}'] = f'PIN_{i:02d}'
|
||||||
data[f'B{row}'] = str(i)
|
data[f'B{row}'] = str(i)
|
||||||
filepath = os.path.join(tmpdir, 'test_6x12_pinlist.xlsx')
|
filepath = os.path.join(tmpdir, 'test_6x10_pinlist.xlsx')
|
||||||
create_pinlist_xlsx(data, filepath)
|
create_pinlist_xlsx(data, filepath)
|
||||||
|
|
||||||
pkg, entries = parse_pinlist(filepath)
|
pkg, entries = parse_pinlist(filepath)
|
||||||
assert len(entries) == 32, f"应有32个引脚, 实际: {len(entries)}"
|
assert len(entries) == 32, f"应有32个引脚, 实际: {len(entries)}"
|
||||||
|
|
||||||
validation = validate_pinlist(entries, 6, 12)
|
validation = validate_pinlist(entries, 6, 10)
|
||||||
assert validation.is_valid, f"验证应通过: {validation.errors}"
|
assert validation.is_valid, f"验证应通过: {validation.errors}"
|
||||||
|
|
||||||
# Generate and write to file
|
# Generate and write to file
|
||||||
outpath = os.path.join(tmpdir, 'test_6x12_pinmap.xlsx')
|
outpath = os.path.join(tmpdir, 'test_6x10_pinmap.xlsx')
|
||||||
output = generate_pinmap(entries, 6, 12, pkg, output_path=outpath)
|
output = generate_pinmap(entries, 6, 10, pkg, output_path=outpath)
|
||||||
assert os.path.exists(outpath), "输出文件应存在"
|
assert os.path.exists(outpath), "输出文件应存在"
|
||||||
|
|
||||||
# Verify output can be read back
|
# Verify output can be read back
|
||||||
@@ -239,16 +233,16 @@ def test_list_to_map(r: TestRunner):
|
|||||||
assert (0, 0) in out_cells, "A1应有数据"
|
assert (0, 0) in out_cells, "A1应有数据"
|
||||||
assert out_cells[(0, 0)] == 'LQFP-32', f"A1应为LQFP-32, 实际: {out_cells.get((0,0))}"
|
assert out_cells[(0, 0)] == 'LQFP-32', f"A1应为LQFP-32, 实际: {out_cells.get((0,0))}"
|
||||||
|
|
||||||
result.ok(f"解析成功, 封装={pkg}, Pin数={len(entries)}, 6×12布局+文件输出验证通过")
|
result.ok(f"解析成功, 封装={pkg}, Pin数={len(entries)}, 6×10布局+文件输出验证通过")
|
||||||
|
|
||||||
r.run("TC-LM-002: 6×12 PinList→PinMAP (32引脚)", _tc_lm_002)
|
r.run("TC-LM-002: 6×10 PinList→PinMAP (32引脚)", _tc_lm_002)
|
||||||
|
|
||||||
# ── TC-LM-003: 带模板文件的转换 ──
|
# ── TC-LM-003: 带模板文件的转换 ──
|
||||||
# 先用一个正常PinMAP作为模板
|
# v1.3: 5×5 grid → (5+5)*2 = 20 pins
|
||||||
def _tc_lm_003(result):
|
def _tc_lm_003(result):
|
||||||
# 创建模板 PinMAP
|
# 创建模板 PinMAP
|
||||||
template_data = {'A1': 'QFP-16'}
|
template_data = {'A1': 'QFP-20'}
|
||||||
for i in range(1, 17):
|
for i in range(1, 21):
|
||||||
row = i + 1
|
row = i + 1
|
||||||
template_data[f'A{row}'] = f'Pin{i}'
|
template_data[f'A{row}'] = f'Pin{i}'
|
||||||
template_data[f'B{row}'] = str(i)
|
template_data[f'B{row}'] = str(i)
|
||||||
@@ -258,8 +252,8 @@ def test_list_to_map(r: TestRunner):
|
|||||||
write_xlsx_with_style(template_data, template_path)
|
write_xlsx_with_style(template_data, template_path)
|
||||||
|
|
||||||
# 创建 PinList 并写入模板同目录
|
# 创建 PinList 并写入模板同目录
|
||||||
data = {'A1': 'QFP-16'}
|
data = {'A1': 'QFP-20'}
|
||||||
for i in range(1, 17):
|
for i in range(1, 21):
|
||||||
row = i + 1
|
row = i + 1
|
||||||
data[f'A{row}'] = f'Pin{i}'
|
data[f'A{row}'] = f'Pin{i}'
|
||||||
data[f'B{row}'] = str(i)
|
data[f'B{row}'] = str(i)
|
||||||
@@ -329,10 +323,9 @@ def test_list_to_map(r: TestRunner):
|
|||||||
r.run("TC-LM-005: Pin序号重复", _tc_lm_005)
|
r.run("TC-LM-005: Pin序号重复", _tc_lm_005)
|
||||||
|
|
||||||
# ── TC-LM-006: Pin 总数不匹配 ──
|
# ── TC-LM-006: Pin 总数不匹配 ──
|
||||||
|
# v1.3: 3×4 grid → (3+4)*2 = 14 pins
|
||||||
def _tc_lm_006(result):
|
def _tc_lm_006(result):
|
||||||
# 创建8个引脚的PinList,但指定3×3网格(需要8个引脚)
|
# 创建8个引脚的PinList,但3×4网格需要14个引脚
|
||||||
# 2*3 + 2*3 - 4 = 8 → 匹配!
|
|
||||||
# 改用3×4网格(需要2*3+2*4-4=10个引脚)
|
|
||||||
data = {'A1': 'QFP-test'}
|
data = {'A1': 'QFP-test'}
|
||||||
for i in range(1, 9): # 8 pins
|
for i in range(1, 9): # 8 pins
|
||||||
row = i + 1
|
row = i + 1
|
||||||
@@ -342,7 +335,7 @@ def test_list_to_map(r: TestRunner):
|
|||||||
create_pinlist_xlsx(data, filepath)
|
create_pinlist_xlsx(data, filepath)
|
||||||
|
|
||||||
pkg, entries = parse_pinlist(filepath)
|
pkg, entries = parse_pinlist(filepath)
|
||||||
# 3×4 needs 10 pins, but we have 8
|
# 3×4 needs 14 pins, but we have 8
|
||||||
validation = validate_pinlist(entries, 3, 4)
|
validation = validate_pinlist(entries, 3, 4)
|
||||||
assert not validation.is_valid, "应验证失败"
|
assert not validation.is_valid, "应验证失败"
|
||||||
assert any("不匹配" in e.message for e in validation.errors), \
|
assert any("不匹配" in e.message for e in validation.errors), \
|
||||||
@@ -352,11 +345,12 @@ def test_list_to_map(r: TestRunner):
|
|||||||
r.run("TC-LM-006: Pin总数不匹配", _tc_lm_006)
|
r.run("TC-LM-006: Pin总数不匹配", _tc_lm_006)
|
||||||
|
|
||||||
# ── TC-LM-007: 缺少 PinName ──
|
# ── TC-LM-007: 缺少 PinName ──
|
||||||
|
# v1.3: 2×3 grid → (2+3)*2 = 10 pins
|
||||||
def _tc_lm_007(result):
|
def _tc_lm_007(result):
|
||||||
data = {'A1': 'QFP-test'}
|
data = {'A1': 'QFP-test'}
|
||||||
# 6个引脚,其中第2个缺PinName
|
# 10个引脚,其中第2个缺PinName
|
||||||
# 2×4 grid: 2*2+2*4-4=6 pins ✓
|
entries_data = [('Pin1', '1'), ('', '2'), ('Pin3', '3'), ('Pin4', '4'), ('Pin5', '5'),
|
||||||
entries_data = [('Pin1', '1'), ('', '2'), ('Pin3', '3'), ('Pin4', '4'), ('Pin5', '5'), ('Pin6', '6')]
|
('Pin6', '6'), ('Pin7', '7'), ('Pin8', '8'), ('Pin9', '9'), ('Pin10', '10')]
|
||||||
for i, (name, num) in enumerate(entries_data):
|
for i, (name, num) in enumerate(entries_data):
|
||||||
row = i + 2
|
row = i + 2
|
||||||
data[f'A{row}'] = name
|
data[f'A{row}'] = name
|
||||||
@@ -376,14 +370,12 @@ def test_list_to_map(r: TestRunner):
|
|||||||
r.run("TC-LM-007: 缺少PinName (warning)", _tc_lm_007)
|
r.run("TC-LM-007: 缺少PinName (warning)", _tc_lm_007)
|
||||||
|
|
||||||
# ── TC-LM-008: 非4倍数提示 ──
|
# ── TC-LM-008: 非4倍数提示 ──
|
||||||
|
# v1.3: (r+c)*2 is always even, but may not be multiple of 4
|
||||||
|
# (3+4)*2 = 14, 14 % 4 = 2 → not a multiple of 4
|
||||||
def _tc_lm_008(result):
|
def _tc_lm_008(result):
|
||||||
# 6个引脚 → 不是4的倍数
|
# 14个引脚 → 不是4的倍数
|
||||||
# 2r+2c-4=6 → try 3×4: 2*3+2*4-4=10, no
|
|
||||||
# try 2×5: 2*2+2*5-4=8, no
|
|
||||||
# try 4×3: 2*4+2*3-4=10, no
|
|
||||||
# Actually: 2r+2c-4=6 → r+c=5 → try r=2,c=3: 4+6-4=6 ✓
|
|
||||||
data = {'A1': 'QFP-test'}
|
data = {'A1': 'QFP-test'}
|
||||||
for i in range(1, 7):
|
for i in range(1, 15):
|
||||||
row = i + 1
|
row = i + 1
|
||||||
data[f'A{row}'] = f'Pin{i}'
|
data[f'A{row}'] = f'Pin{i}'
|
||||||
data[f'B{row}'] = str(i)
|
data[f'B{row}'] = str(i)
|
||||||
@@ -391,22 +383,17 @@ def test_list_to_map(r: TestRunner):
|
|||||||
create_pinlist_xlsx(data, filepath)
|
create_pinlist_xlsx(data, filepath)
|
||||||
|
|
||||||
pkg, entries = parse_pinlist(filepath)
|
pkg, entries = parse_pinlist(filepath)
|
||||||
validation = validate_pinlist(entries, 2, 3)
|
validation = validate_pinlist(entries, 3, 4)
|
||||||
assert validation.is_valid, f"应验证通过: {validation.errors}"
|
assert validation.is_valid, f"应验证通过: {validation.errors}"
|
||||||
# 6 % 4 != 0, 应有 info 提示
|
# 14 % 4 != 0, 应有 info 提示
|
||||||
# 注意: validate_pinlist 返回的 ValidationResult 没有 infos 字段
|
|
||||||
# 但 info 消息是附加到 warnings 中的
|
|
||||||
# 实际上看代码,infos 是单独列表,但 ValidationResult 只有 errors 和 warnings
|
|
||||||
# 所以 info 不会出现在 validation.warnings 中
|
|
||||||
# 让我们直接检查 validate_pinlist 的返回
|
|
||||||
result.ok(f"验证通过, Pin数={len(entries)} (非4倍数)")
|
result.ok(f"验证通过, Pin数={len(entries)} (非4倍数)")
|
||||||
|
|
||||||
r.run("TC-LM-008: 非4倍数提示", _tc_lm_008)
|
r.run("TC-LM-008: 非4倍数提示", _tc_lm_008)
|
||||||
|
|
||||||
# ── TC-LM-009: 布局计算正确性 ──
|
# ── TC-LM-009: 布局计算正确性 ──
|
||||||
|
# v1.3: 3×3 grid → (3+3)*2 = 12 pins
|
||||||
def _tc_lm_009(result):
|
def _tc_lm_009(result):
|
||||||
# 3×3 grid → 2*3 + 2*3 - 4 = 8 pins
|
entries = [PinListEntry(number=i, name=f'P{i}') for i in range(1, 13)]
|
||||||
entries = [PinListEntry(number=i, name=f'P{i}') for i in range(1, 9)]
|
|
||||||
layout = calculate_layout(entries, 3, 3)
|
layout = calculate_layout(entries, 3, 3)
|
||||||
|
|
||||||
# 验证四条边都有引脚
|
# 验证四条边都有引脚
|
||||||
@@ -415,26 +402,25 @@ def test_list_to_map(r: TestRunner):
|
|||||||
assert 'right' in layout, "应有right边"
|
assert 'right' in layout, "应有right边"
|
||||||
assert 'top' in layout, "应有top边"
|
assert 'top' in layout, "应有top边"
|
||||||
|
|
||||||
# 验证引脚数量分配
|
# 验证引脚数量分配 (v1.3: 每条边独立)
|
||||||
# left: rows=3, bottom: cols-1=2, right: rows-2=1, top: cols-1=2
|
# left: rows=3, bottom: cols=3, right: rows=3, top: cols=3
|
||||||
assert len(layout['left'].pins) == 3, f"left应有3个引脚, 实际: {len(layout['left'].pins)}"
|
assert len(layout['left'].pins) == 3, f"left应有3个引脚, 实际: {len(layout['left'].pins)}"
|
||||||
assert len(layout['bottom'].pins) == 2, f"bottom应有2个引脚, 实际: {len(layout['bottom'].pins)}"
|
assert len(layout['bottom'].pins) == 3, f"bottom应有3个引脚, 实际: {len(layout['bottom'].pins)}"
|
||||||
assert len(layout['right'].pins) == 1, f"right应有1个引脚, 实际: {len(layout['right'].pins)}"
|
assert len(layout['right'].pins) == 3, f"right应有3个引脚, 实际: {len(layout['right'].pins)}"
|
||||||
assert len(layout['top'].pins) == 2, f"top应有2个引脚, 实际: {len(layout['top'].pins)}"
|
assert len(layout['top'].pins) == 3, f"top应有3个引脚, 实际: {len(layout['top'].pins)}"
|
||||||
|
|
||||||
# 验证总引脚数
|
# 验证总引脚数
|
||||||
total = sum(len(e.pins) for e in layout.values())
|
total = sum(len(e.pins) for e in layout.values())
|
||||||
assert total == 8, f"总引脚数应为8, 实际: {total}"
|
assert total == 12, f"总引脚数应为12, 实际: {total}"
|
||||||
|
|
||||||
# 验证逆时针顺序: left(1,2,3) → bottom(4,5) → right(6) → top(7,8)
|
# 验证逆时针顺序: left(1,2,3) → bottom(4,5,6) → right(7,8,9) → top(10,11,12)
|
||||||
assert layout['left'].pins[0][0] == 1, "left第一个应为Pin1"
|
assert layout['left'].pins[0][0] == 1, "left第一个应为Pin1"
|
||||||
assert layout['left'].pins[-1][0] == 3, "left最后一个应为Pin3"
|
assert layout['left'].pins[-1][0] == 3, "left最后一个应为Pin3"
|
||||||
assert layout['bottom'].pins[0][0] == 4, "bottom第一个应为Pin4"
|
assert layout['bottom'].pins[0][0] == 4, "bottom第一个应为Pin4"
|
||||||
assert layout['right'].pins[0][0] == 6, "right应为Pin6"
|
assert layout['right'].pins[0][0] == 7, "right应为Pin7"
|
||||||
assert layout['top'].pins[0][0] == 7, "top第一个应为Pin7"
|
assert layout['top'].pins[0][0] == 10, "top第一个应为Pin10"
|
||||||
assert layout['top'].pins[1][0] == 8, "top第二个应为Pin8"
|
|
||||||
|
|
||||||
result.ok(f"布局计算正确: left=3, bottom=2, right=1, top=2, 逆时针顺序正确")
|
result.ok(f"布局计算正确: left=3, bottom=3, right=3, top=3, 逆时针顺序正确")
|
||||||
|
|
||||||
r.run("TC-LM-009: 布局计算正确性", _tc_lm_009)
|
r.run("TC-LM-009: 布局计算正确性", _tc_lm_009)
|
||||||
|
|
||||||
@@ -470,10 +456,10 @@ def test_list_to_map(r: TestRunner):
|
|||||||
r.run("TC-LM-011b: 无效尺寸输入(列数<2)", _tc_lm_011b)
|
r.run("TC-LM-011b: 无效尺寸输入(列数<2)", _tc_lm_011b)
|
||||||
|
|
||||||
# ── TC-LM-012: 输出文件正确性 ──
|
# ── TC-LM-012: 输出文件正确性 ──
|
||||||
|
# v1.3: 3×3 grid → (3+3)*2 = 12 pins
|
||||||
def _tc_lm_012(result):
|
def _tc_lm_012(result):
|
||||||
# 创建4×4 PinList (8 pins, 3×3 grid)
|
data = {'A1': 'QFP-12'}
|
||||||
data = {'A1': 'QFP-8'}
|
for i in range(1, 13):
|
||||||
for i in range(1, 9):
|
|
||||||
row = i + 1
|
row = i + 1
|
||||||
data[f'A{row}'] = f'Pin{i}'
|
data[f'A{row}'] = f'Pin{i}'
|
||||||
data[f'B{row}'] = str(i)
|
data[f'B{row}'] = str(i)
|
||||||
@@ -486,26 +472,26 @@ def test_list_to_map(r: TestRunner):
|
|||||||
|
|
||||||
# 读取输出并验证
|
# 读取输出并验证
|
||||||
out_cells = read_xlsx_cells(outpath)
|
out_cells = read_xlsx_cells(outpath)
|
||||||
assert out_cells[(0, 0)] == 'QFP-8', f"A1应为QFP-8, 实际: {out_cells.get((0,0))}"
|
assert out_cells[(0, 0)] == 'QFP-12', f"A1应为QFP-12, 实际: {out_cells.get((0,0))}"
|
||||||
|
|
||||||
# 验证所有8个引脚序号都在输出中
|
# 验证所有12个引脚序号都在输出中
|
||||||
found_nums = set()
|
found_nums = set()
|
||||||
for (row, col), val in out_cells.items():
|
for (row, col), val in out_cells.items():
|
||||||
if val.isdigit() and int(val) >= 1:
|
for part in str(val).split("/"):
|
||||||
found_nums.add(int(val))
|
if part.isdigit() and int(part) >= 1:
|
||||||
assert found_nums == {1, 2, 3, 4, 5, 6, 7, 8}, \
|
found_nums.add(int(part))
|
||||||
f"应包含1-8所有序号, 实际: {sorted(found_nums)}"
|
assert found_nums == {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, \
|
||||||
|
f"应包含1-12所有序号, 实际: {sorted(found_nums)}"
|
||||||
|
|
||||||
result.ok(f"输出文件验证通过: A1={out_cells[(0,0)]}, 包含Pin1-Pin8")
|
result.ok(f"输出文件验证通过: A1={out_cells[(0,0)]}, 包含Pin1-Pin12")
|
||||||
|
|
||||||
r.run("TC-LM-012: 输出文件正确性", _tc_lm_012)
|
r.run("TC-LM-012: 输出文件正确性", _tc_lm_012)
|
||||||
|
|
||||||
# ── TC-LM-013: 端到端 roundtrip (PinMAP → PinList → PinMAP) ──
|
# ── TC-LM-013: 端到端 roundtrip (PinMAP → PinList → PinMAP) ──
|
||||||
|
# v1.3: 3×3 grid → (3+3)*2 = 12 pins
|
||||||
def _tc_lm_013(result):
|
def _tc_lm_013(result):
|
||||||
# 创建一个符合周长公式的 PinList → PinMAP → PinList roundtrip
|
data = {'A1': 'QFP-12'}
|
||||||
# 3×3 grid → 2*3+2*3-4=8 pins
|
for i in range(1, 13):
|
||||||
data = {'A1': 'QFP-8'}
|
|
||||||
for i in range(1, 9):
|
|
||||||
row = i + 1
|
row = i + 1
|
||||||
data[f'A{row}'] = f'Pin{i}'
|
data[f'A{row}'] = f'Pin{i}'
|
||||||
data[f'B{row}'] = str(i)
|
data[f'B{row}'] = str(i)
|
||||||
@@ -523,15 +509,15 @@ def test_list_to_map(r: TestRunner):
|
|||||||
rt_validation = validate_pinmap(rt_pinmap)
|
rt_validation = validate_pinmap(rt_pinmap)
|
||||||
rt_pinlist = generate_pinlist(rt_pinmap, rt_validation)
|
rt_pinlist = generate_pinlist(rt_pinmap, rt_validation)
|
||||||
|
|
||||||
assert len(rt_pinlist.rows) == 8, \
|
assert len(rt_pinlist.rows) == 12, \
|
||||||
f"Roundtrip后应有8个引脚, 实际: {len(rt_pinlist.rows)}"
|
f"Roundtrip后应有12个引脚, 实际: {len(rt_pinlist.rows)}"
|
||||||
|
|
||||||
# 验证序号一致
|
# 验证序号一致
|
||||||
orig_nums = [e.number for e in entries]
|
orig_nums = [e.number for e in entries]
|
||||||
rt_nums = [num for _, num in rt_pinlist.rows]
|
rt_nums = [num for _, num in rt_pinlist.rows]
|
||||||
assert orig_nums == rt_nums, f"序号应一致: {orig_nums} vs {rt_nums}"
|
assert orig_nums == rt_nums, f"序号应一致: {orig_nums} vs {rt_nums}"
|
||||||
|
|
||||||
result.ok(f"Roundtrip成功: PinList(8) → PinMAP(3×3) → PinList({len(rt_pinlist.rows)}), 序号一致")
|
result.ok(f"Roundtrip成功: PinList(12) → PinMAP(3×3) → PinList({len(rt_pinlist.rows)}), 序号一致")
|
||||||
|
|
||||||
r.run("TC-LM-013: 端到端Roundtrip (MAP→List→MAP)", _tc_lm_013)
|
r.run("TC-LM-013: 端到端Roundtrip (MAP→List→MAP)", _tc_lm_013)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# PinMAP ↔ PinList 双向转换器 测试报告
|
# PinMAP ↔ PinList 双向转换器 测试报告
|
||||||
|
|
||||||
> **日期**: 2026-05-28
|
> **日期**: 2026-06-01
|
||||||
> **测试类型**: 集成测试 + 端到端测试
|
> **测试类型**: 集成测试 + 端到端测试
|
||||||
> **测试环境**: Python 3.x, Linux x64
|
> **测试环境**: Python 3.x, Linux x64
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
### TC-MAP-001: 标准4x4 PinMAP转换
|
### TC-MAP-001: 标准4x4 PinMAP转换
|
||||||
- **结果**: ✅ 通过
|
- **结果**: ✅ 通过
|
||||||
- **详情**: 封装=QFP44, Pin数=8, 序号递增
|
- **详情**: 封装=QFP12, Pin数=12, 序号递增
|
||||||
|
|
||||||
### TC-MAP-002: 长方形PinMAP转换
|
### TC-MAP-002: 长方形PinMAP转换
|
||||||
- **结果**: ✅ 通过
|
- **结果**: ✅ 通过
|
||||||
@@ -44,13 +44,13 @@
|
|||||||
|
|
||||||
## Part 2: List→MAP 新增功能测试
|
## Part 2: List→MAP 新增功能测试
|
||||||
|
|
||||||
### TC-LM-001: 5×5 PinList→PinMAP (16引脚)
|
### TC-LM-001: 5×5 PinList→PinMAP (20引脚)
|
||||||
- **结果**: ✅ 通过
|
- **结果**: ✅ 通过
|
||||||
- **详情**: 解析成功, 封装=QFP-16, Pin数=16, 5×5布局验证通过
|
- **详情**: 解析成功, 封装=QFP-20, Pin数=20, 5×5布局验证通过
|
||||||
|
|
||||||
### TC-LM-002: 6×12 PinList→PinMAP (32引脚)
|
### TC-LM-002: 6×10 PinList→PinMAP (32引脚)
|
||||||
- **结果**: ✅ 通过
|
- **结果**: ✅ 通过
|
||||||
- **详情**: 解析成功, 封装=LQFP-32, Pin数=32, 6×12布局+文件输出验证通过
|
- **详情**: 解析成功, 封装=LQFP-32, Pin数=32, 6×10布局+文件输出验证通过
|
||||||
|
|
||||||
### TC-LM-003: 带模板文件的转换
|
### TC-LM-003: 带模板文件的转换
|
||||||
- **结果**: ✅ 通过
|
- **结果**: ✅ 通过
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
|
|
||||||
### TC-LM-006: Pin总数不匹配
|
### TC-LM-006: Pin总数不匹配
|
||||||
- **结果**: ✅ 通过
|
- **结果**: ✅ 通过
|
||||||
- **详情**: 正确报错: Pin数量与网格周长不匹配 — 网格 3×4 需要 10 个引脚,但 PinList 有 8 个
|
- **详情**: 正确报错: Pin数量与网格周长不匹配 — 网格 3×4 需要 14 个引脚,但 PinList 有 8 个
|
||||||
|
|
||||||
### TC-LM-007: 缺少PinName (warning)
|
### TC-LM-007: 缺少PinName (warning)
|
||||||
- **结果**: ✅ 通过
|
- **结果**: ✅ 通过
|
||||||
@@ -74,11 +74,11 @@
|
|||||||
|
|
||||||
### TC-LM-008: 非4倍数提示
|
### TC-LM-008: 非4倍数提示
|
||||||
- **结果**: ✅ 通过
|
- **结果**: ✅ 通过
|
||||||
- **详情**: 验证通过, Pin数=6 (非4倍数)
|
- **详情**: 验证通过, Pin数=14 (非4倍数)
|
||||||
|
|
||||||
### TC-LM-009: 布局计算正确性
|
### TC-LM-009: 布局计算正确性
|
||||||
- **结果**: ✅ 通过
|
- **结果**: ✅ 通过
|
||||||
- **详情**: 布局计算正确: left=3, bottom=2, right=1, top=2, 逆时针顺序正确
|
- **详情**: 布局计算正确: left=3, bottom=3, right=3, top=3, 逆时针顺序正确
|
||||||
|
|
||||||
### TC-LM-010: 模板文件检测(无模板)
|
### TC-LM-010: 模板文件检测(无模板)
|
||||||
- **结果**: ✅ 通过
|
- **结果**: ✅ 通过
|
||||||
@@ -94,11 +94,11 @@
|
|||||||
|
|
||||||
### TC-LM-012: 输出文件正确性
|
### TC-LM-012: 输出文件正确性
|
||||||
- **结果**: ✅ 通过
|
- **结果**: ✅ 通过
|
||||||
- **详情**: 输出文件验证通过: A1=QFP-8, 包含Pin1-Pin8
|
- **详情**: 输出文件验证通过: A1=QFP-12, 包含Pin1-Pin12
|
||||||
|
|
||||||
### TC-LM-013: 端到端Roundtrip (MAP→List→MAP)
|
### TC-LM-013: 端到端Roundtrip (MAP→List→MAP)
|
||||||
- **结果**: ✅ 通过
|
- **结果**: ✅ 通过
|
||||||
- **详情**: Roundtrip成功: PinList(8) → PinMAP(3×3) → PinList(8), 序号一致
|
- **详情**: Roundtrip成功: PinList(12) → PinMAP(3×3) → PinList(12), 序号一致
|
||||||
|
|
||||||
### TC-LM-014: 输出路径生成
|
### TC-LM-014: 输出路径生成
|
||||||
- **结果**: ✅ 通过
|
- **结果**: ✅ 通过
|
||||||
|
|||||||
8
docs/bugs.md
Normal file
8
docs/bugs.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Bug 跟踪表
|
||||||
|
|
||||||
|
| Bug ID | 严重程度 | Bug 描述 | 复现步骤 | 期望行为 | 实际行为 | 状态 | 关联功能 |
|
||||||
|
|--------|---------|---------|---------|---------|---------|------|---------|
|
||||||
|
| BUG-001 | 中 | run.bat 换行符 + lines 设置不匹配 Windows | 在 Windows 下运行 run.bat | CRLF 换行,仅保留 `mode con cols=80` | Unix LF 换行,包含多余 `lines=50` | 已修复 | F005 |
|
||||||
|
| BUG-002 | 高 | 周长计算公式错误 | 输入 15×15 网格 + 60 Pin | 验证通过 `(rows+cols)*2=60` | 提示不匹配 `2*rows+2*cols-4=56` | 已修复 | F006 |
|
||||||
|
| BUG-003 | 中 | 双向转换未读取模板样式 | 使用模板文件进行 MAP↔List 转换 | 读取并应用模板样式 | 使用默认样式 | 已修复 | F007 |
|
||||||
|
| BUG-004 | 中 | 不支持循环处理流程 | 转换完成后继续操作 | 循环等待下一个文件,输入 Q 返回主菜单 | 处理完直接退出 | 已修复 | F008 |
|
||||||
31
docs/features.md
Normal file
31
docs/features.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# 功能清单
|
||||||
|
|
||||||
|
## 核心功能
|
||||||
|
|
||||||
|
| 功能 ID | 功能名称 | 描述 | 输入 | 输出 | 依赖 | 优先级 | 验收标准 | 审批状态 |
|
||||||
|
|--------|---------|------|------|------|------|--------|---------|---------|
|
||||||
|
| F001 | PinMAP 解析 | 解析 PinMAP Excel 文件 | Excel 文件 | 解析后的 Pin 数据 | 无 | 1 | 能正确解析 PinMAP 结构 | 已通过 |
|
||||||
|
| F002 | PinList 生成 | 从 PinMAP 生成 PinList | Pin 数据 | PinList Excel 文件 | F001 | 2 | 能正确生成 PinList | 已通过 |
|
||||||
|
| F003 | PinList 解析 | 解析 PinList Excel 文件 | Excel 文件 | 解析后的 Pin 数据 | 无 | 1 | 能正确解析 PinList 结构 | 已通过 |
|
||||||
|
| F004 | PinMAP 生成 | 从 PinList 生成 PinMAP | Pin 数据 | PinMAP Excel 文件 | F003 | 2 | 能正确生成 PinMAP | 已通过 |
|
||||||
|
|
||||||
|
## Bug 修复
|
||||||
|
|
||||||
|
| 功能 ID | 功能名称 | 描述 | 输入 | 输出 | 依赖 | 优先级 | 验收标准 | 审批状态 |
|
||||||
|
|--------|---------|------|------|------|------|--------|---------|---------|
|
||||||
|
| F005 | BAT 脚本修复 | 修复 run.bat 换行符为 CRLF,去掉 lines=50 参数 | 无 | 修复后的 run.bat | 无 | 3 | Windows 下正常运行 | 已通过 |
|
||||||
|
| F006 | 周长公式修复 | 将周长公式从 `2*rows+2*cols-4` 改为 `(rows+cols)*2` | rows, cols | 正确的周长值 | 无 | 1 | 15×15 网格 60Pin 验证通过 | 已通过 |
|
||||||
|
|
||||||
|
## 功能增强
|
||||||
|
|
||||||
|
| 功能 ID | 功能名称 | 描述 | 输入 | 输出 | 依赖 | 优先级 | 验收标准 | 审批状态 |
|
||||||
|
|--------|---------|------|------|------|------|--------|---------|---------|
|
||||||
|
| F007 | 模板读取 | MAP→List 和 List→MAP 双向转换均读取并应用模板样式 | 模板文件 | 带样式的输出文件 | 无 | 2 | 双向转换均应用模板样式 | 已通过 |
|
||||||
|
| F008 | 循环处理流程 | 处理完不退出,循环等待下一个文件,输入 Q 返回主菜单 | 用户输入 | 循环处理或返回主菜单 | 无 | 2 | 处理完不退出,Q 返回主菜单 | 已通过 |
|
||||||
|
|
||||||
|
## 优先级排序
|
||||||
|
|
||||||
|
1. **P0(必须)**:F006 周长公式修复 — 核心逻辑错误
|
||||||
|
2. **P1(重要)**:F005 BAT 脚本修复 — 影响 Windows 用户使用
|
||||||
|
3. **P2(建议)**:F007 模板读取 — 功能增强
|
||||||
|
4. **P2(建议)**:F008 循环处理流程 — 体验优化
|
||||||
621
docs/modification-assessment-v1.3.md
Normal file
621
docs/modification-assessment-v1.3.md
Normal file
@@ -0,0 +1,621 @@
|
|||||||
|
# PinMAP ↔ PinList 双向转换器 — 修改需求评估 v1.3
|
||||||
|
|
||||||
|
> **版本**: v1.3
|
||||||
|
> **日期**: 2026-05-31
|
||||||
|
> **评估人**: 脚本架构师 (Script Architect)
|
||||||
|
> **状态**: 待审批
|
||||||
|
> **变更**: 4 个 Bug 修复 + 4 个功能增强(BUG-001~004, F005~F008)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 修改需求总览
|
||||||
|
|
||||||
|
| 编号 | 类型 | 标题 | 优先级 | 复杂度 | 关联需求 |
|
||||||
|
|------|------|------|--------|--------|---------|
|
||||||
|
| BUG-001 | Bug | run.bat 换行符 + lines 设置不匹配 Windows | 中 | 低 | F005 |
|
||||||
|
| BUG-002 | Bug | 周长计算公式错误 | **高** | 中 | F006 |
|
||||||
|
| BUG-003 | Bug | 双向转换未读取模板样式 | 中 | 中 | F007 |
|
||||||
|
| BUG-004 | Bug | 不支持循环处理流程 | 中 | 中 | F008 |
|
||||||
|
| F005 | 功能 | BAT 脚本修复 | 3 | 低 | BUG-001 |
|
||||||
|
| F006 | 功能 | 周长公式修复 | **1** | 中 | BUG-002 |
|
||||||
|
| F007 | 功能 | 模板读取(双向) | 2 | 中 | BUG-003 |
|
||||||
|
| F008 | 功能 | 循环处理流程 | 2 | 中 | BUG-004 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 当前代码状态分析
|
||||||
|
|
||||||
|
### 2.1 代码库结构(v1.2.0)
|
||||||
|
|
||||||
|
```
|
||||||
|
pinmap-to-pinlist/
|
||||||
|
├── run.bat # ✏️ 需修改(BUG-001/F005)
|
||||||
|
├── Code/src/
|
||||||
|
│ ├── main.py # ✏️ 需修改(BUG-003/F007, BUG-004/F008)
|
||||||
|
│ ├── file_selector.py # (不变)
|
||||||
|
│ ├── validator.py # ✏️ 需修改(BUG-002/F006)
|
||||||
|
│ ├── pinlist_validator.py # ✏️ 需修改(BUG-002/F006)
|
||||||
|
│ ├── pinmap_layout.py # ✏️ 需修改(BUG-002/F006)
|
||||||
|
│ ├── pinlist_parser.py # (不变)
|
||||||
|
│ ├── pinmap_generator.py # (不变)
|
||||||
|
│ ├── template_reader.py # (不变)
|
||||||
|
│ ├── xlsx_writer.py # (不变)
|
||||||
|
│ ├── models.py # (不变)
|
||||||
|
│ ├── xls_reader.py # (不变)
|
||||||
|
│ ├── xlsx_reader.py # (不变)
|
||||||
|
│ ├── pinlist_generator.py # (不变)
|
||||||
|
│ └── utils.py # (不变)
|
||||||
|
└── docs/
|
||||||
|
├── bugs.md # ✏️ 需更新状态
|
||||||
|
├── features.md # ✏️ 需更新状态
|
||||||
|
└── modification-assessment-v1.3.md # 🆕 本文档
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 各 Bug 当前代码状态
|
||||||
|
|
||||||
|
#### BUG-001: run.bat 问题
|
||||||
|
|
||||||
|
**当前 run.bat 内容**:
|
||||||
|
```bat
|
||||||
|
@ECHO OFF
|
||||||
|
chcp 65001 >nul
|
||||||
|
title PinMAP转PinList -By:LeeQwQ
|
||||||
|
mode con cols=80 lines=50 ← 问题:含 lines=50
|
||||||
|
color 0B
|
||||||
|
cls
|
||||||
|
cd /d "%~dp0Code\src"
|
||||||
|
python main.py
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
EXIT
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题 1**:文件使用 Unix LF 换行符(`\n`),Windows 下应使用 CRLF(`\r\n`)。
|
||||||
|
**问题 2**:`mode con cols=80 lines=50` 中的 `lines=50` 是多余的,需求仅保留 `cols=80`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### BUG-002: 周长公式错误
|
||||||
|
|
||||||
|
**当前公式**(3 处代码):
|
||||||
|
```
|
||||||
|
expected_total = 2 * rows + 2 * cols - 4
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题**:对于 15×15 网格 + 60 Pin,当前公式计算为 `2*15+2*15-4 = 56`,但用户期望 `(15+15)*2 = 60`。
|
||||||
|
|
||||||
|
**涉及文件**:
|
||||||
|
1. `Code/src/pinlist_validator.py` — `validate_pinlist()` 中的周长匹配检查
|
||||||
|
2. `Code/src/validator.py` — `validate_pinlist_for_map()` 中的周长匹配检查
|
||||||
|
3. `Code/src/pinmap_layout.py` — `calculate_layout()` 中的 `total` 计算和 `LayoutError`
|
||||||
|
|
||||||
|
**数学分析**:
|
||||||
|
|
||||||
|
| 网格 | 当前公式 `2r+2c-4` | 新公式 `(r+c)*2` | 说明 |
|
||||||
|
|------|-------------------|-----------------|------|
|
||||||
|
| 4×8 | 20 | 24 | 当前公式少算 4 |
|
||||||
|
| 15×15 | 56 | 60 | 当前公式少算 4 |
|
||||||
|
| 2×2 | 4 | 8 | 当前公式少算 4 |
|
||||||
|
|
||||||
|
新公式 `(rows+cols)*2` 对所有尺寸均多 4 个 Pin。这意味着布局分配算法也需要相应调整。
|
||||||
|
|
||||||
|
**布局分配需同步修改**:
|
||||||
|
|
||||||
|
当前分配(`pinmap_layout.py`):
|
||||||
|
```
|
||||||
|
左边: rows 个
|
||||||
|
下边: cols-1 个
|
||||||
|
右边: rows-2 个
|
||||||
|
上边: cols-1 个
|
||||||
|
总计: 2*rows + 2*cols - 4
|
||||||
|
```
|
||||||
|
|
||||||
|
新分配(`(rows+cols)*2`):
|
||||||
|
```
|
||||||
|
左边: rows 个
|
||||||
|
下边: cols 个
|
||||||
|
右边: rows 个
|
||||||
|
上边: cols 个
|
||||||
|
总计: 2*rows + 2*cols
|
||||||
|
```
|
||||||
|
|
||||||
|
这意味着四个角点不再共享,每个角点归属一条边。需重新设计单元格坐标计算逻辑。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### BUG-003: 模板读取路径错误
|
||||||
|
|
||||||
|
**当前代码**(`main.py` → `run_list_to_map()`):
|
||||||
|
```python
|
||||||
|
template_style = read_template_styles(filepath)
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题**:`filepath` 是用户选择的 PinList 输入文件路径,而非模板文件路径。模板文件应为根目录下的 `PinMAP-Template.xlsx`。
|
||||||
|
|
||||||
|
**MAP→List 方向**(`run_map_to_list()`):
|
||||||
|
当前代码未调用 `read_template_styles()`,PinList 输出直接使用 `write_xlsx()`(无样式)。
|
||||||
|
|
||||||
|
**需要修复**:
|
||||||
|
1. List→MAP:将 `read_template_styles(filepath)` 改为读取根目录模板
|
||||||
|
2. MAP→List:增加模板读取和样式应用
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### BUG-004: 无循环处理流程
|
||||||
|
|
||||||
|
**当前 `main()` 流程**:
|
||||||
|
```python
|
||||||
|
def main():
|
||||||
|
show_banner()
|
||||||
|
# 选择方向 → 执行一次转换 → wait_for_exit() → 程序结束
|
||||||
|
```
|
||||||
|
|
||||||
|
**问题**:转换完成后直接退出,用户需重新运行程序才能处理下一个文件。
|
||||||
|
|
||||||
|
**期望流程**:
|
||||||
|
```
|
||||||
|
启动 → 选择方向 → 处理文件 → [处理完成] → 等待输入下一个文件 / Q 返回主菜单
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 逐项修改方案
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.1 BUG-001 / F005: BAT 脚本修复
|
||||||
|
|
||||||
|
**修改范围**:`run.bat`(1 个文件)
|
||||||
|
|
||||||
|
**具体修改**:
|
||||||
|
|
||||||
|
1. **换行符**:确保文件使用 CRLF(`\r\n`)换行。写入文件时指定 `newline='\r\n'`。
|
||||||
|
2. **去掉 lines=50**:将 `mode con cols=80 lines=50` 改为 `mode con cols=80`。
|
||||||
|
|
||||||
|
**修改后 run.bat**:
|
||||||
|
```bat
|
||||||
|
@ECHO OFF
|
||||||
|
chcp 65001 >nul
|
||||||
|
title PinMAP转PinList -By:LeeQwQ
|
||||||
|
mode con cols=80
|
||||||
|
color 0B
|
||||||
|
cls
|
||||||
|
|
||||||
|
cd /d "%~dp0Code\src"
|
||||||
|
python main.py
|
||||||
|
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
EXIT
|
||||||
|
```
|
||||||
|
|
||||||
|
**风险评估**:
|
||||||
|
| 风险 | 影响 | 概率 | 缓解措施 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| CRLF 写入失败 | 低 | 极低 | Python `open(path, 'w', newline='\r\n')` 保证 |
|
||||||
|
| 去掉 lines 后窗口行数变回默认 | 低 | 确认 | 默认 300 行缓冲区,足够查看日志 |
|
||||||
|
|
||||||
|
**工作量**:5 分钟
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 BUG-002 / F006: 周长公式修复
|
||||||
|
|
||||||
|
**修改范围**:3 个文件
|
||||||
|
|
||||||
|
| 文件 | 修改内容 | 修改行数 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| `pinlist_validator.py` | 周长匹配公式 + 错误提示文案 | ~5 行 |
|
||||||
|
| `validator.py` | `validate_pinlist_for_map()` 周长公式 | ~5 行 |
|
||||||
|
| `pinmap_layout.py` | 边分配计数 + 单元格坐标计算 | ~20 行 |
|
||||||
|
|
||||||
|
**具体修改**:
|
||||||
|
|
||||||
|
#### 3.2.1 `pinlist_validator.py` — `validate_pinlist()`
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 修改前:
|
||||||
|
expected_total = 2 * rows + 2 * cols - 4
|
||||||
|
|
||||||
|
# 修改后:
|
||||||
|
expected_total = (rows + cols) * 2
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2.2 `validator.py` — `validate_pinlist_for_map()`
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 修改前:
|
||||||
|
expected_total = 2 * rows + 2 * cols - 4
|
||||||
|
|
||||||
|
# 修改后:
|
||||||
|
expected_total = (rows + cols) * 2
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2.3 `pinmap_layout.py` — `calculate_layout()`
|
||||||
|
|
||||||
|
**边分配计数修改**:
|
||||||
|
```python
|
||||||
|
# 修改前:
|
||||||
|
left_count = rows
|
||||||
|
bottom_count = cols - 1
|
||||||
|
right_count = rows - 2
|
||||||
|
top_count = cols - 1
|
||||||
|
# total = 2*rows + 2*cols - 4
|
||||||
|
|
||||||
|
# 修改后:
|
||||||
|
left_count = rows
|
||||||
|
bottom_count = cols
|
||||||
|
right_count = rows
|
||||||
|
top_count = cols
|
||||||
|
# total = 2*rows + 2*cols
|
||||||
|
```
|
||||||
|
|
||||||
|
**单元格坐标计算修改**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 修改前(角点共享):
|
||||||
|
# 左边: (r, 0) r ∈ [1, rows]
|
||||||
|
# 下边: (rows, c) c ∈ [1, cols-1]
|
||||||
|
# 右边: (r, cols) r ∈ [rows-1, 2] 逆序
|
||||||
|
# 上边: (1, c) c ∈ [cols-1, 2] 逆序
|
||||||
|
|
||||||
|
# 修改后(角点不共享,每条边独立):
|
||||||
|
# 左边: (r, 0) r ∈ [1, rows]
|
||||||
|
# 下边: (rows, c) c ∈ [1, cols]
|
||||||
|
# 右边: (r, cols) r ∈ [rows, 1] 逆序
|
||||||
|
# 上边: (1, c) c ∈ [cols, 1] 逆序
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 修改后代码:
|
||||||
|
left_cells = [(r, 0) for r in range(1, rows + 1)]
|
||||||
|
bottom_cells = [(rows, c) for c in range(1, cols + 1)]
|
||||||
|
right_cells = [(r, cols) for r in range(rows, 0, -1)]
|
||||||
|
top_cells = [(1, c) for c in range(cols, 0, -1)]
|
||||||
|
```
|
||||||
|
|
||||||
|
**`get_name_cell()` 函数修改**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 修改前:
|
||||||
|
# left: (r, c+1)
|
||||||
|
# bottom: (r-1, c)
|
||||||
|
# right: (r, c-1)
|
||||||
|
# top: (r+1, c)
|
||||||
|
|
||||||
|
# 修改后:逻辑不变(Name 单元格相对于序号单元格的位置不变)
|
||||||
|
# 但需确保角点单元格 Name 不冲突
|
||||||
|
```
|
||||||
|
|
||||||
|
**风险评估**:
|
||||||
|
| 风险 | 影响 | 概率 | 缓解措施 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| 布局算法修改引入新 Bug | **高** | 中 | 对 4×8、15×15、2×2 等典型尺寸做单元测试 |
|
||||||
|
| 角点单元格 Name 重叠 | 中 | 中 | 修改 `get_name_cell()` 确保角点 Name 不冲突 |
|
||||||
|
| 已有用户数据不兼容 | 低 | 低 | 用户需重新输入正确的 PinList |
|
||||||
|
| 修改 3 个文件不一致 | 中 | 低 | 使用同一公式常量,避免硬编码 |
|
||||||
|
|
||||||
|
**工作量**:1.5 小时
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3 BUG-003 / F007: 模板读取修复
|
||||||
|
|
||||||
|
**修改范围**:1 个文件(`main.py`)
|
||||||
|
|
||||||
|
**问题根因**:
|
||||||
|
1. List→MAP:`read_template_styles(filepath)` 传入的是输入文件路径,而非模板路径
|
||||||
|
2. MAP→List:完全没有模板读取逻辑
|
||||||
|
|
||||||
|
**具体修改**:
|
||||||
|
|
||||||
|
#### 3.3.1 新增模板路径解析辅助函数
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _find_template_path() -> str | None:
|
||||||
|
"""查找根目录下的 PinMAP-Template.xlsx。
|
||||||
|
|
||||||
|
搜索顺序:
|
||||||
|
1. 与 run.bat 同级的根目录
|
||||||
|
2. 当前工作目录
|
||||||
|
"""
|
||||||
|
# 尝试从 Code/src 回退到根目录
|
||||||
|
src_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
root_dir = os.path.dirname(os.path.dirname(src_dir)) # pinmap-to-pinlist/
|
||||||
|
template_path = os.path.join(root_dir, "PinMAP-Template.xlsx")
|
||||||
|
|
||||||
|
if os.path.exists(template_path):
|
||||||
|
return template_path
|
||||||
|
|
||||||
|
# 回退到当前工作目录
|
||||||
|
cwd_template = os.path.join(os.getcwd(), "PinMAP-Template.xlsx")
|
||||||
|
if os.path.exists(cwd_template):
|
||||||
|
return cwd_template
|
||||||
|
|
||||||
|
return None
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.3.2 修改 `run_list_to_map()`
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 修改前:
|
||||||
|
template_style = read_template_styles(filepath)
|
||||||
|
|
||||||
|
# 修改后:
|
||||||
|
template_path = _find_template_path()
|
||||||
|
if template_path:
|
||||||
|
template_style = read_template_styles(template_path)
|
||||||
|
if template_style:
|
||||||
|
print(f"[INFO] 已加载模板样式: {template_path}")
|
||||||
|
else:
|
||||||
|
print("[WARN] 模板文件存在但解析失败,使用默认样式")
|
||||||
|
else:
|
||||||
|
template_style = None
|
||||||
|
print("[INFO] 未检测到模板文件,使用默认样式")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.3.3 修改 `run_map_to_list()`
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 在写入 PinList 之前,增加模板读取逻辑:
|
||||||
|
template_path = _find_template_path()
|
||||||
|
template_style = None
|
||||||
|
if template_path:
|
||||||
|
template_style = read_template_styles(template_path)
|
||||||
|
if template_style:
|
||||||
|
print(f"[INFO] 已加载模板样式: {template_path}")
|
||||||
|
|
||||||
|
# 写入时:
|
||||||
|
if template_style is not None:
|
||||||
|
write_xlsx_with_style(data, output_path, template_style)
|
||||||
|
else:
|
||||||
|
write_xlsx(data, output_path)
|
||||||
|
```
|
||||||
|
|
||||||
|
**风险评估**:
|
||||||
|
| 风险 | 影响 | 概率 | 缓解措施 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| 模板路径解析错误 | 中 | 低 | 多路径回退 + 优雅降级 |
|
||||||
|
| 模板解析失败导致崩溃 | 低 | 低 | `template_reader.py` 已有 try-except 优雅降级 |
|
||||||
|
| MAP→List 应用模板样式后 PinList 格式不符合用户预期 | 中 | 低 | 模板仅影响样式(字体/边框),不影响数据 |
|
||||||
|
|
||||||
|
**工作量**:1 小时
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.4 BUG-004 / F008: 循环处理流程
|
||||||
|
|
||||||
|
**修改范围**:1 个文件(`main.py`)
|
||||||
|
|
||||||
|
**具体修改**:
|
||||||
|
|
||||||
|
将 `main()` 改造为循环结构:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def main():
|
||||||
|
show_banner()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# ── Direction selection ───────────────────────────────
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
# Legacy mode: 直接文件参数 → MAP→List → 循环
|
||||||
|
direction = 1
|
||||||
|
filepath = sys.argv[1]
|
||||||
|
sys.argv = [sys.argv[0]] # 清除 argv,下次循环进入交互模式
|
||||||
|
else:
|
||||||
|
print("请选择转换方向:")
|
||||||
|
print(" 1 — PinMAP → PinList")
|
||||||
|
print(" 2 — PinList → PinMAP")
|
||||||
|
print(" Q — 退出程序")
|
||||||
|
print()
|
||||||
|
|
||||||
|
choice = input("请输入选项 (1/2/Q): ").strip().upper()
|
||||||
|
if choice == 'Q':
|
||||||
|
print("感谢使用,再见!")
|
||||||
|
return
|
||||||
|
elif choice == '1':
|
||||||
|
direction = 1
|
||||||
|
elif choice == '2':
|
||||||
|
direction = 2
|
||||||
|
else:
|
||||||
|
print("[ERROR] 无效选项,请输入 1、2 或 Q")
|
||||||
|
continue
|
||||||
|
|
||||||
|
filepath = None
|
||||||
|
|
||||||
|
# ── Dispatch ──────────────────────────────────────────
|
||||||
|
if direction == 1:
|
||||||
|
print()
|
||||||
|
print("─" * 40)
|
||||||
|
print(" 方向: PinMAP → PinList")
|
||||||
|
print("─" * 40)
|
||||||
|
print()
|
||||||
|
run_map_to_list(filepath)
|
||||||
|
else:
|
||||||
|
print()
|
||||||
|
print("─" * 40)
|
||||||
|
print(" 方向: PinList → PinMAP")
|
||||||
|
print("─" * 40)
|
||||||
|
print()
|
||||||
|
run_list_to_map(filepath)
|
||||||
|
|
||||||
|
# ── 处理完成后循环 ────────────────────────────────────
|
||||||
|
print()
|
||||||
|
print("=" * 40)
|
||||||
|
next_action = input("输入文件名继续处理,或按 Enter 返回主菜单,输入 Q 退出: ").strip()
|
||||||
|
if next_action.upper() == 'Q':
|
||||||
|
print("感谢使用,再见!")
|
||||||
|
return
|
||||||
|
elif next_action:
|
||||||
|
# 直接处理指定文件(自动检测方向)
|
||||||
|
filepath = next_action
|
||||||
|
# 根据文件内容自动判断方向,或默认 MAP→List
|
||||||
|
direction = 1
|
||||||
|
else:
|
||||||
|
# 返回主菜单(继续 while 循环)
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**同时需要修改 `run_map_to_list()` 和 `run_list_to_map()`**:
|
||||||
|
|
||||||
|
将两个函数末尾的 `wait_for_exit()` 替换为 `input("按 Enter 键继续...")`,这样处理完成后用户可以继续操作而不是退出。
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 修改前(两处):
|
||||||
|
wait_for_exit()
|
||||||
|
|
||||||
|
# 修改后:
|
||||||
|
input("按 Enter 键继续...")
|
||||||
|
```
|
||||||
|
|
||||||
|
**但注意**:`wait_for_exit()` 仍保留,用于:
|
||||||
|
- 致命错误(FATAL)时的退出
|
||||||
|
- 用户未选择文件时的退出
|
||||||
|
- 命令行参数模式下最后一次退出
|
||||||
|
|
||||||
|
**风险评估**:
|
||||||
|
| 风险 | 影响 | 概率 | 缓解措施 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| 循环导致内存泄漏(模块重复 import) | 低 | 低 | Python import 有缓存,重复 import 无开销 |
|
||||||
|
| 用户输入 Q 后状态混乱 | 中 | 低 | Q 只在主菜单和处理完成后接受,转换过程中不响应 |
|
||||||
|
| 命令行参数模式与循环模式冲突 | 中 | 中 | 命令行参数模式执行一次后清除 argv,进入交互循环 |
|
||||||
|
|
||||||
|
**工作量**:1 小时
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 修改影响矩阵
|
||||||
|
|
||||||
|
| 文件 | BUG-001 | BUG-002 | BUG-003 | BUG-004 | 总改动量 |
|
||||||
|
|------|---------|---------|---------|---------|---------|
|
||||||
|
| `run.bat` | ✏️ 换行+CRLF, 去lines | | | | 2 行 |
|
||||||
|
| `main.py` | | | ✏️ 模板路径 | ✏️ 循环流程 | ~40 行 |
|
||||||
|
| `pinlist_validator.py` | | ✏️ 公式 | | | ~5 行 |
|
||||||
|
| `validator.py` | | ✏️ 公式 | | | ~5 行 |
|
||||||
|
| `pinmap_layout.py` | | ✏️ 公式+布局 | | | ~20 行 |
|
||||||
|
| **合计** | **1 文件** | **3 文件** | **1 文件** | **1 文件** | **5 文件** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 优先级排序
|
||||||
|
|
||||||
|
| 优先级 | 编号 | 原因 |
|
||||||
|
|--------|------|------|
|
||||||
|
| **P0** | BUG-002 / F006 | 核心公式错误,所有 List→MAP 转换均受影响 |
|
||||||
|
| **P1** | BUG-001 / F005 | 影响 Windows 用户体验,修复简单 |
|
||||||
|
| **P2** | BUG-003 / F007 | 功能增强,模板样式对输出质量有影响 |
|
||||||
|
| **P2** | BUG-004 / F008 | 体验优化,批量处理场景需要 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 工作量估算
|
||||||
|
|
||||||
|
| 任务 | 文件 | 预估时间 | 依赖 |
|
||||||
|
|------|------|---------|------|
|
||||||
|
| BUG-001/F005 | `run.bat` | 5 分钟 | 无 |
|
||||||
|
| BUG-002/F006 | `pinlist_validator.py`, `validator.py`, `pinmap_layout.py` | 1.5 小时 | 无 |
|
||||||
|
| BUG-003/F007 | `main.py` | 1 小时 | 无 |
|
||||||
|
| BUG-004/F008 | `main.py` | 1 小时 | 无 |
|
||||||
|
| 文档更新 | `bugs.md`, `features.md` | 10 分钟 | 无 |
|
||||||
|
|
||||||
|
**总计预估**:约 3 小时
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 推荐开发顺序
|
||||||
|
|
||||||
|
```
|
||||||
|
第1轮(独立,可并行):
|
||||||
|
BUG-001/F005: BAT 脚本修复(5 分钟,任何 Agent)
|
||||||
|
|
||||||
|
第2轮(核心,必须最先):
|
||||||
|
BUG-002/F006: 周长公式修复(1.5 小时,需理解布局算法)
|
||||||
|
|
||||||
|
第3轮(独立):
|
||||||
|
BUG-003/F007: 模板读取修复(1 小时)
|
||||||
|
BUG-004/F008: 循环处理流程(1 小时)
|
||||||
|
(两者都修改 main.py,建议先后执行避免冲突)
|
||||||
|
|
||||||
|
第4轮(收尾):
|
||||||
|
文档更新:bugs.md + features.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 验收标准
|
||||||
|
|
||||||
|
### 8.1 BUG-001 / F005 验收
|
||||||
|
|
||||||
|
| 验收项 | 方法 | 预期结果 |
|
||||||
|
|--------|------|---------|
|
||||||
|
| run.bat 使用 CRLF 换行 | 二进制查看文件 | 每行末尾为 `\r\n` |
|
||||||
|
| 不含 `lines=` 参数 | 文本搜索 | 无 `lines=` 字符串 |
|
||||||
|
| 仅含 `mode con cols=80` | 文本搜索 | 仅一行 `mode con cols=80` |
|
||||||
|
| Windows 下双击运行正常 | 实际运行 | 窗口正常打开,中文显示正确 |
|
||||||
|
|
||||||
|
### 8.2 BUG-002 / F006 验收
|
||||||
|
|
||||||
|
| 验收项 | 方法 | 预期结果 |
|
||||||
|
|--------|------|---------|
|
||||||
|
| 15×15 网格 + 60 Pin 验证通过 | 输入测试 | 无错误提示,转换成功 |
|
||||||
|
| 4×8 网格 + 24 Pin 验证通过 | 输入测试 | 无错误提示,转换成功 |
|
||||||
|
| 2×2 网格 + 8 Pin 验证通过 | 输入测试 | 无错误提示,转换成功 |
|
||||||
|
| 错误 Pin 数量仍报错 | 输入 15×15+56Pin | 提示不匹配 |
|
||||||
|
| 布局计算正确 | 检查输出文件 | 四条边 Pin 分布正确 |
|
||||||
|
|
||||||
|
### 8.3 BUG-003 / F007 验收
|
||||||
|
|
||||||
|
| 验收项 | 方法 | 预期结果 |
|
||||||
|
|--------|------|---------|
|
||||||
|
| List→MAP 读取模板 | 放置模板文件后转换 | 日志显示"已加载模板样式" |
|
||||||
|
| MAP→List 读取模板 | 放置模板文件后转换 | 日志显示"已加载模板样式" |
|
||||||
|
| 无模板时优雅降级 | 不放置模板文件 | 日志显示"未检测到模板文件",使用默认样式 |
|
||||||
|
| 模板解析失败降级 | 放置损坏的模板文件 | 日志显示"解析失败",使用默认样式 |
|
||||||
|
| 输出文件样式正确 | 打开输出文件 | 字体、边框、对齐与模板一致 |
|
||||||
|
|
||||||
|
### 8.4 BUG-004 / F008 验收
|
||||||
|
|
||||||
|
| 验收项 | 方法 | 预期结果 |
|
||||||
|
|--------|------|---------|
|
||||||
|
| 处理完不退出 | 完成一次转换 | 显示"按 Enter 键继续"或循环提示 |
|
||||||
|
| 输入 Q 返回主菜单 | 处理完成后输入 Q | 返回方向选择菜单 |
|
||||||
|
| 主菜单输入 Q 退出 | 主菜单输入 Q | 程序退出 |
|
||||||
|
| 连续处理多个文件 | 连续选择文件 | 可连续处理,无需重新运行 |
|
||||||
|
| 命令行参数模式 | `run.bat input.xls` | 处理完成后进入循环 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 风险评估汇总
|
||||||
|
|
||||||
|
| 风险 | 影响 | 概率 | 缓解措施 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| 周长公式修改导致已有布局算法不一致 | **高** | 中 | 同步修改 validator + layout,确保公式统一 |
|
||||||
|
| 角点单元格 Name 冲突 | 中 | 中 | 修改 `get_name_cell()` 确保不重叠 |
|
||||||
|
| main.py 两处修改冲突 | 中 | 中 | 先完成 BUG-003,再完成 BUG-004,避免同时修改 |
|
||||||
|
| 模板路径在命令行模式下解析错误 | 低 | 低 | 使用 `__file__` 绝对路径而非 cwd |
|
||||||
|
| 循环流程中模块重复 import 性能 | 低 | 极低 | Python 有 import 缓存 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 总结
|
||||||
|
|
||||||
|
| 项目 | 内容 |
|
||||||
|
|------|------|
|
||||||
|
| 修改文件数 | 5 个(run.bat, main.py, pinlist_validator.py, validator.py, pinmap_layout.py) |
|
||||||
|
| 新增文件数 | 0 |
|
||||||
|
| 影响核心模块 | 是(pinmap_layout.py 布局算法) |
|
||||||
|
| 技术难度 | 中(周长公式 + 布局算法需同步修改) |
|
||||||
|
| 预估工作量 | ~3 小时 |
|
||||||
|
| 推荐 Agent | Python 编码 Agent(1-2 个) |
|
||||||
|
| 风险等级 | 中(公式修改需仔细验证) |
|
||||||
|
|
||||||
|
**结论**:
|
||||||
|
1. BUG-002 为最高优先级,影响所有 List→MAP 转换的正确性
|
||||||
|
2. BUG-001 修复最简单,可快速完成
|
||||||
|
3. BUG-003 和 BUG-004 都修改 `main.py`,需先后执行避免冲突
|
||||||
|
4. 所有修改均使用 Python 标准库,无新增依赖
|
||||||
|
5. 建议修改完成后运行完整测试套件验证
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*文档结束 — 请审批后进入编码阶段*
|
||||||
27
docs/requirements.md
Normal file
27
docs/requirements.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 需求规格说明书
|
||||||
|
|
||||||
|
## 项目信息
|
||||||
|
- 项目名称:pinmap-to-pinlist
|
||||||
|
- 项目 ID:PROJ-002
|
||||||
|
- 项目类型:脚本
|
||||||
|
- 技术约束:Python 脚本,支持 Windows 和 Linux
|
||||||
|
|
||||||
|
## 需求描述
|
||||||
|
PinMAP ↔ PinList 双向转换器,支持 PinMAP→PinList 与 PinList→PinMAP 互转。
|
||||||
|
|
||||||
|
## 输入/输出
|
||||||
|
| 类型 | 描述 |
|
||||||
|
|-----|------|
|
||||||
|
| 输入 | PinMAP 或 PinList Excel 文件 |
|
||||||
|
| 输出 | 转换后的 Excel 文件 |
|
||||||
|
|
||||||
|
## 边界条件
|
||||||
|
- 支持 .xls 和 .xlsx 格式
|
||||||
|
- Pin 数量必须与网格周长匹配
|
||||||
|
- 网格尺寸至少为 2x2
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
1. 能正确解析 PinMAP 结构
|
||||||
|
2. 能正确生成 PinList
|
||||||
|
3. 能正确反向转换
|
||||||
|
4. 错误处理完善
|
||||||
13
docs/tasks.md
Normal file
13
docs/tasks.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 任务进度表
|
||||||
|
|
||||||
|
| 任务 ID | 任务名称 | 负责 Agent | 当前状态 | 任务类型 | 关联功能 | 创建时间 | 完成时间 |
|
||||||
|
|--------|---------|-----------|---------|---------|---------|---------|---------|
|
||||||
|
| T001 | 架构设计 | script-architect | 已完成 | 架构设计 | F001-F005 | 2026-05-23 | 2026-05-23 |
|
||||||
|
| T002 | Python 编码 v1.2 | python-coding-agent | 已完成 | 编码实现 | F001-F005 | 2026-05-23 | 2026-05-26 |
|
||||||
|
| T003 | BAT 编码 | bat-coding-agent | 已完成 | 编码实现 | F005 | 2026-05-23 | 2026-05-23 |
|
||||||
|
| T004 | 测试验证 v1.2 | test-qa-agent | 已完成 | 测试验证 | F001-F005 | 2026-05-23 | - |
|
||||||
|
| T007 | BAT 脚本修复 v1.3 | bat-coding-agent | 已完成 | 编码实现 | F005 | 2026-05-31 | 2026-05-31 |
|
||||||
|
| T008 | Python 编码 v1.3 | python-coding-agent | 已完成 | 编码实现 | F006-F008 | 2026-05-31 | 2026-05-31 |
|
||||||
|
| T009 | 测试验证 v1.3 | test-qa-agent | 进行中 | 测试验证 | F005-F008 | 2026-05-31 | - |
|
||||||
|
| T010 | 文档生成 v1.3 | doc-gen-agent | 待分配 | 文档编写 | F005-F008 | - | - |
|
||||||
|
| T011 | 打包发布 v1.3 | package-release-agent | 待分配 | 打包发布 | F005-F008 | - | - |
|
||||||
25
run.bat
25
run.bat
@@ -1,14 +1,11 @@
|
|||||||
@ECHO OFF
|
@ECHO OFF
|
||||||
:: 初始化区
|
chcp 65001 >nul
|
||||||
chcp 65001 >nul
|
title PinMAP转PinList -By:LeeQwQ
|
||||||
title PinMAP转PinList -By:LeeQwQ
|
mode con cols=80
|
||||||
mode con cols=80 lines=50
|
color 0B
|
||||||
color 0B
|
cls
|
||||||
cls
|
cd /d "%~dp0Code\src"
|
||||||
|
python main.py
|
||||||
cd /d "%~dp0Code\src"
|
echo.
|
||||||
python main.py
|
pause
|
||||||
|
EXIT
|
||||||
echo.
|
|
||||||
pause
|
|
||||||
EXIT
|
|
||||||
|
|||||||
Reference in New Issue
Block a user