"""PinMAP ↔ PinList bidirectional converter Usage: 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(" 支持 PinMAP→PinList 与 PinList→PinMAP 互转") print(" 支持.xls和.xlsx格式,输出.xlsx格式") print("=" * 60) print() def wait_for_exit(): """等待用户按键后退出(Windows任意键,其他平台Enter键)""" try: import msvcrt print("按任意键退出...") msvcrt.getch() except ImportError: input("按Enter键退出...") # ── Path helpers ──────────────────────────────────────────────────── def _find_pinlist_template_path() -> str | None: """查找 PinList-Template.xlsx。 MAP→List 输出使用 PinList 模板。 搜索顺序: 1. Code/src/Template/ 目录(首要位置) 2. 项目根目录(向后兼容) 3. 当前工作目录 """ src_dir = os.path.dirname(os.path.abspath(__file__)) # 1. Code/src/Template/ 目录 template_path = os.path.join(src_dir, "Template", "PinList-Template.xlsx") if os.path.exists(template_path): return template_path # 2. 项目根目录(向后兼容) root_dir = os.path.dirname(os.path.dirname(src_dir)) template_path = os.path.join(root_dir, "PinList-Template.xlsx") if os.path.exists(template_path): return template_path # 3. 当前工作目录 cwd_template = os.path.join(os.getcwd(), "PinList-Template.xlsx") if os.path.exists(cwd_template): return cwd_template return None def _find_pinmap_template_path() -> str | None: """查找 PinMAP-Template.xlsx。 List→MAP 输出使用 PinMAP 模板。 搜索顺序: 1. Code/src/Template/ 目录(首要位置) 2. 项目根目录(向后兼容) 3. 当前工作目录 """ src_dir = os.path.dirname(os.path.abspath(__file__)) # 1. Code/src/Template/ 目录 template_path = os.path.join(src_dir, "Template", "PinMAP-Template.xlsx") if os.path.exists(template_path): return template_path # 2. 项目根目录(向后兼容) root_dir = os.path.dirname(os.path.dirname(src_dir)) template_path = os.path.join(root_dir, "PinMAP-Template.xlsx") if os.path.exists(template_path): return template_path # 3. 当前工作目录 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: """Generate output path: {original_filename}_PinList.xlsx""" base, _ = os.path.splitext(input_path) return f"{base}_PinList.xlsx" 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" # ── 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 from xlsx_reader import read_excel_cells as read_xlsx_cells from pinmap_parser import parse_pinmap from validator import validate_pinmap from pinlist_generator import generate_pinlist from xlsx_writer import write_xlsx, write_xlsx_with_style from template_reader import read_template_styles from models import FileFormatError, StructureError # ── 1. File selection ─────────────────────────────────────────── if not filepath: filepath = select_file(mode="map_to_list") if not filepath: print("未选择文件,退出。") wait_for_exit() return # ── 2. Read Excel ─────────────────────────────────────────────── print(f"[INFO] 正在读取文件: {filepath}") try: if filepath.lower().endswith('.xlsx'): cells = read_xlsx_cells(filepath) else: cells = read_excel_cells(filepath) except Exception as e: print(f"[FATAL] 文件读取失败: {e}") wait_for_exit() return print(f"[INFO] 文件读取完成,共 {len(cells)} 个非空单元格") # ── 3. Parse PinMAP ───────────────────────────────────────────── print("[INFO] 正在解析 PinMAP 结构...") try: pinmap = parse_pinmap(cells) print(f"[INFO] 解析完成: {pinmap.width}x{pinmap.height} 方形,共 {len(pinmap.pins)} 个Pin") print(f"[INFO] 封装信息: {pinmap.package_info}") except (FileFormatError, StructureError) as e: print(f"[FATAL] 结构错误: {e}") wait_for_exit() return # ── 4. Validate ───────────────────────────────────────────────── print("[INFO] 正在验证数据...") validation = validate_pinmap(pinmap) if validation.errors: print(f"[ERROR] 验证未通过,发现 {len(validation.errors)} 个错误:") for err in validation.errors: print(f" - {err.message}: {err.details}") print("\n转换终止,请修正PinMAP文件后重试。") 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 PinList ───────────────────────────────────────── print("[INFO] 正在生成 PinList...") pinlist = generate_pinlist(pinmap, validation) # ── 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[f'A{row}'] = pin_name data[f'B{row}'] = str(pin_num) # 尝试读取 PinList 模板样式 template_path = _find_pinlist_template_path() template_style = None if template_path: template_style = read_template_styles(template_path) if template_style: print(f"[INFO] 已加载 PinList 模板样式: {template_path}") else: print("[WARN] PinList 模板文件存在但解析失败,使用默认样式") else: print("[INFO] 未检测到 PinList-Template.xlsx,使用默认样式") 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: print(f"[FATAL] 输出失败: {e}") wait_for_exit() return # ── 7. Result summary ─────────────────────────────────────────── print() print("[SUCCESS] 转换完成!") print(f" 输出文件: {output_path}") print(f" 封装信息: {pinlist.package_info}") print(f" Pin数量: {len(pinlist.rows)}") # F008: 不再退出,返回主循环 # ── 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: # 尝试读取 PinMAP 模板样式 template_path = _find_pinmap_template_path() template_style = None if template_path: template_style = read_template_styles(template_path) if template_style: print(f"[INFO] 已加载 PinMAP 模板样式: {template_path}") else: print("[WARN] PinMAP 模板文件存在但解析失败,使用默认样式") else: print("[INFO] 未检测到 PinMAP-Template.xlsx,使用默认样式") 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)}") # F008: 不再退出,返回主循环 # ── Main entry ────────────────────────────────────────────────────── def main(): show_banner() # F008: 循环处理流程 while True: # ── Direction selection ───────────────────────────────────── if len(sys.argv) > 1: # Legacy mode: direct file argument → 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_choice = input("输入 Q 退出,或按 Enter 返回主菜单继续转换: ").strip().upper() if next_choice == 'Q': print("感谢使用,再见!") return # 否则继续 while 循环,回到主菜单 if __name__ == '__main__': main()