feat: PinMAP转PinList v1.2.0 - 新增PinList转PinMAP反向转换功能

This commit is contained in:
2026-05-28 01:53:51 +08:00
parent 853f10a73b
commit 3228c1a2e6
18 changed files with 3781 additions and 977 deletions

View File

@@ -1,19 +1,21 @@
"""PinMAP PinList converter
"""PinMAP PinList bidirectional converter
Usage:
python main.py # Interactive file selection
python main.py input.xls # Specify file via command line
python main.py # Interactive — choose direction + file
python main.py input.xls # MAP→List mode (legacy, specify file directly)
"""
import sys
import os
# ── Banner ──────────────────────────────────────────────────────────
def show_banner():
"""显示程序启动说明"""
print("=" * 60)
print(" PinMAP PinList 转换器")
print(" 将Excel格式的PinMAP文件转换为PinList格式")
print(" PinMAP PinList 双向转换器")
print(" 支持 PinMAPPinList 与 PinList→PinMAP 互转")
print(" 支持.xls和.xlsx格式输出.xlsx格式")
print("=" * 60)
print()
@@ -29,19 +31,26 @@ def wait_for_exit():
input("按Enter键退出...")
def build_output_path(input_path: str) -> str:
# ── Path helpers ────────────────────────────────────────────────────
def _build_output_path_map_to_list(input_path: str) -> str:
"""Generate output path: {original_filename}_PinList.xlsx"""
base, _ = os.path.splitext(input_path)
return f"{base}_PinList.xlsx"
def main():
# ── Banner ──────────────────────────────────────────────────
show_banner()
def _build_output_path_list_to_map(input_path: str) -> str:
"""Generate output path: {original_filename}_PinMAP.xlsx"""
base, _ = os.path.splitext(input_path)
return f"{base}_PinMAP.xlsx"
# ── imports (local to avoid circular issues) ────────────────
# ── Direction 1: MAP → List ────────────────────────────────────────
def run_map_to_list(filepath: str):
"""执行 PinMAP → PinList 转换流程。"""
from file_selector import select_file
from xls_reader import read_excel_cells # auto-detects .xls
from xls_reader import read_excel_cells
from xlsx_reader import read_excel_cells as read_xlsx_cells
from pinmap_parser import parse_pinmap
from validator import validate_pinmap
@@ -49,18 +58,16 @@ def main():
from xlsx_writer import write_xlsx
from models import FileFormatError, StructureError
# ── 1. File selection ───────────────────────────────────────
if len(sys.argv) > 1:
filepath = sys.argv[1]
else:
filepath = select_file()
# ── 1. File selection ───────────────────────────────────────────
if not filepath:
filepath = select_file(mode="map_to_list")
if not filepath:
print("未选择文件,退出。")
wait_for_exit()
return
# ── 2. Read Excel ───────────────────────────────────────────
# ── 2. Read Excel ───────────────────────────────────────────────
print(f"[INFO] 正在读取文件: {filepath}")
try:
if filepath.lower().endswith('.xlsx'):
@@ -74,7 +81,7 @@ def main():
print(f"[INFO] 文件读取完成,共 {len(cells)} 个非空单元格")
# ── 3. Parse PinMAP ─────────────────────────────────────────
# ── 3. Parse PinMAP ─────────────────────────────────────────────
print("[INFO] 正在解析 PinMAP 结构...")
try:
pinmap = parse_pinmap(cells)
@@ -85,7 +92,7 @@ def main():
wait_for_exit()
return
# ── 4. Validate ─────────────────────────────────────────────
# ── 4. Validate ─────────────────────────────────────────────────
print("[INFO] 正在验证数据...")
validation = validate_pinmap(pinmap)
@@ -97,7 +104,6 @@ def main():
wait_for_exit()
return
# Print warnings (non-fatal — continue processing)
if validation.warnings:
print(f"[WARN] 发现 {len(validation.warnings)} 个警告:")
for warn in validation.warnings:
@@ -105,18 +111,18 @@ def main():
else:
print("[INFO] 验证通过")
# ── 5. Generate PinList ─────────────────────────────────────
# ── 5. Generate PinList ─────────────────────────────────────────
print("[INFO] 正在生成 PinList...")
pinlist = generate_pinlist(pinmap, validation)
# ── 6. Write XLSX ───────────────────────────────────────────
output_path = build_output_path(filepath)
# ── 6. Write XLSX ───────────────────────────────────────────────
output_path = _build_output_path_map_to_list(filepath)
print(f"[INFO] 正在写入输出文件: {output_path}")
try:
data = {}
data['A1'] = pinlist.package_info
for i, (pin_name, pin_num) in enumerate(pinlist.rows):
row = i + 2 # data rows start at row 2
row = i + 2
data[f'A{row}'] = pin_name
data[f'B{row}'] = str(pin_num)
@@ -126,7 +132,7 @@ def main():
wait_for_exit()
return
# ── 7. Result summary ───────────────────────────────────────
# ── 7. Result summary ───────────────────────────────────────────
print()
print("[SUCCESS] 转换完成!")
print(f" 输出文件: {output_path}")
@@ -136,5 +142,157 @@ def main():
wait_for_exit()
# ── Direction 2: List → MAP ────────────────────────────────────────
def run_list_to_map(filepath: str):
"""执行 PinList → PinMAP 转换流程。"""
from file_selector import select_file
from pinlist_parser import parse_pinlist
from pinlist_validator import validate_pinlist
from pinmap_generator import generate_pinmap, generate_output_path
from template_reader import read_template_styles
from models import StructureError, LayoutError
# ── 1. File selection ───────────────────────────────────────────
if not filepath:
filepath = select_file(mode="list_to_map")
if not filepath:
print("未选择文件,退出。")
wait_for_exit()
return
# ── 2. Input PinMAP dimensions ──────────────────────────────────
while True:
try:
rows_input = input("请输入 PinMAP 行数: ").strip()
rows = int(rows_input)
if rows < 2:
print("[ERROR] 行数至少为 2")
continue
break
except ValueError:
print("[ERROR] 请输入有效的整数")
while True:
try:
cols_input = input("请输入 PinMAP 列数: ").strip()
cols = int(cols_input)
if cols < 2:
print("[ERROR] 列数至少为 2")
continue
break
except ValueError:
print("[ERROR] 请输入有效的整数")
print(f"[INFO] PinMAP 尺寸: {rows}× {cols}")
# ── 3. Parse PinList ────────────────────────────────────────────
print(f"[INFO] 正在解析 PinList 文件: {filepath}")
try:
package_info, entries = parse_pinlist(filepath)
print(f"[INFO] 解析完成: 封装信息 '{package_info}', 共 {len(entries)} 个引脚")
except StructureError as e:
print(f"[FATAL] 解析失败: {e}")
wait_for_exit()
return
# ── 4. Validate ─────────────────────────────────────────────────
print("[INFO] 正在验证数据...")
validation = validate_pinlist(entries, rows, cols)
if validation.errors:
print(f"[ERROR] 验证未通过,发现 {len(validation.errors)} 个错误:")
for err in validation.errors:
print(f" - {err.message}: {err.details}")
print("\n转换终止请修正PinList文件或网格尺寸后重试。")
wait_for_exit()
return
if validation.warnings:
print(f"[WARN] 发现 {len(validation.warnings)} 个警告:")
for warn in validation.warnings:
print(f" - {warn.message}: {warn.details}")
else:
print("[INFO] 验证通过")
# ── 5. Generate PinMAP ──────────────────────────────────────────
output_path = generate_output_path(filepath)
print(f"[INFO] 正在生成 PinMAP 并写入: {output_path}")
try:
# 尝试读取模板样式(优雅降级)
template_style = read_template_styles(filepath)
generate_pinmap(
entries=entries,
rows=rows,
cols=cols,
package_info=package_info,
template_style=template_style,
output_path=output_path,
)
except LayoutError as e:
print(f"[FATAL] 布局计算失败: {e}")
wait_for_exit()
return
except Exception as e:
print(f"[FATAL] 输出失败: {e}")
wait_for_exit()
return
# ── 6. Result summary ───────────────────────────────────────────
print()
print("[SUCCESS] 转换完成!")
print(f" 输出文件: {output_path}")
print(f" 封装信息: {package_info}")
print(f" PinMAP 尺寸: {rows}×{cols}")
print(f" Pin数量: {len(entries)}")
wait_for_exit()
# ── Main entry ──────────────────────────────────────────────────────
def main():
show_banner()
# ── Direction selection ─────────────────────────────────────────
if len(sys.argv) > 1:
# Legacy mode: direct file argument → MAP→List
direction = 1
filepath = sys.argv[1]
else:
print("请选择转换方向:")
print(" 1 — PinMAP → PinList")
print(" 2 — PinList → PinMAP")
print()
while True:
choice = input("请输入选项 (1/2): ").strip()
if choice in ('1', '2'):
direction = int(choice)
break
print("[ERROR] 无效选项,请输入 1 或 2")
filepath = None # will be selected inside the flow
# ── 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)
if __name__ == '__main__':
main()