From b43c63edcaef6126ceee26b5c6ca7284ec207187 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 6 Mar 2026 20:42:42 +0100 Subject: [PATCH] feat: add snapshot_project tool for step checkpoints (resume from Step2 without full redo) --- python/kicad_interface.py | 37 ++++++++++++++++++++++++++++++++++ python/schemas/tool_schemas.py | 22 ++++++++++++++++++++ src/tools/registry.ts | 1 + 3 files changed, 60 insertions(+) diff --git a/python/kicad_interface.py b/python/kicad_interface.py index 6026396..0bc1b66 100644 --- a/python/kicad_interface.py +++ b/python/kicad_interface.py @@ -294,6 +294,7 @@ class KiCADInterface: "create_project": self.project_commands.create_project, "open_project": self.project_commands.open_project, "save_project": self.project_commands.save_project, + "snapshot_project": self._handle_snapshot_project, "get_project_info": self.project_commands.get_project_info, # Board commands "set_board_size": self.board_commands.set_board_size, @@ -1625,6 +1626,42 @@ class KiCADInterface: logger.error(traceback.format_exc()) return {"success": False, "message": str(e)} + def _handle_snapshot_project(self, params): + """Copy the entire project folder to a snapshot directory for checkpoint/resume.""" + import shutil + from datetime import datetime + try: + step = params.get("step", "") + label = params.get("label", "") + # Determine project directory from loaded board or explicit path + project_dir = None + if self.board: + board_file = self.board.GetFileName() + if board_file: + project_dir = str(Path(board_file).parent) + if not project_dir: + project_dir = params.get("projectPath") + if not project_dir or not os.path.isdir(project_dir): + return {"success": False, "message": "Could not determine project directory for snapshot"} + + base_name = Path(project_dir).name + ts = datetime.now().strftime("%Y%m%d_%H%M%S") + suffix_parts = [p for p in [f"step{step}" if step else "", label, ts] if p] + snapshot_name = base_name + "_snapshot_" + "_".join(suffix_parts) + snapshot_dir = str(Path(project_dir).parent / snapshot_name) + + shutil.copytree(project_dir, snapshot_dir) + logger.info(f"Project snapshot saved: {snapshot_dir}") + return { + "success": True, + "message": f"Snapshot saved: {snapshot_name}", + "snapshotPath": snapshot_dir, + "sourceDir": project_dir, + } + except Exception as e: + logger.error(f"snapshot_project error: {e}") + return {"success": False, "message": str(e)} + def _handle_check_kicad_ui(self, params): """Check if KiCAD UI is running""" logger.info("Checking if KiCAD UI is running") diff --git a/python/schemas/tool_schemas.py b/python/schemas/tool_schemas.py index d692da6..16d6e37 100644 --- a/python/schemas/tool_schemas.py +++ b/python/schemas/tool_schemas.py @@ -70,6 +70,28 @@ PROJECT_TOOLS = [ } } }, + { + "name": "snapshot_project", + "title": "Snapshot Project (Checkpoint)", + "description": "Copies the entire project folder to a new timestamped snapshot directory so you can resume from this checkpoint later without redoing earlier steps. Call this after every successfully completed design step (e.g. after Step 1 schematic, after Step 2 PCB layout) before asking for user confirmation to proceed.", + "inputSchema": { + "type": "object", + "properties": { + "step": { + "type": "string", + "description": "Step number or name to include in snapshot folder name, e.g. '1' or '2'" + }, + "label": { + "type": "string", + "description": "Optional short label, e.g. 'schematic_ok' or 'layout_ok'" + }, + "projectPath": { + "type": "string", + "description": "Project directory path. Auto-detected from loaded board if omitted." + } + } + } + }, { "name": "get_project_info", "title": "Get Project Information", diff --git a/src/tools/registry.ts b/src/tools/registry.ts index f01e089..6eec4fb 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -125,6 +125,7 @@ export const directToolNames = [ "create_project", "open_project", "save_project", + "snapshot_project", "get_project_info", // Core PCB operations