diff --git a/python/commands/schematic.py b/python/commands/schematic.py index a435c66..1371d8a 100644 --- a/python/commands/schematic.py +++ b/python/commands/schematic.py @@ -1,132 +1,134 @@ -import logging -import os -import shutil -import uuid -from typing import Any, Optional - -from skip import Schematic - -logger = logging.getLogger("kicad_interface") - - -class SchematicManager: - """Core schematic operations using kicad-skip""" - - @staticmethod - def create_schematic(name: str, metadata: Optional[Any] = None, *, path: Optional[str] = None) -> Any: - """Create a new empty schematic from template""" - try: - # Determine template path (use template_with_symbols for component cloning support) - template_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - "..", - "templates", - "template_with_symbols.kicad_sch", - ) - - # Determine output path - base_name = name if name.endswith(".kicad_sch") else f"{name}.kicad_sch" - output_path = os.path.join(path, base_name) if path else base_name - - if os.path.exists(template_path): - # Copy template to target location - shutil.copy(template_path, output_path) - - # Regenerate UUID to ensure uniqueness for each created schematic - import re - - with open(output_path, "r", encoding="utf-8") as f: - content = f.read() - new_uuid = str(uuid.uuid4()) - content = re.sub( - r"\(uuid [0-9a-fA-F-]+\)", - f"(uuid {new_uuid})", - content, - count=1, # Only replace first (schematic) UUID - ) - with open(output_path, "w", encoding="utf-8", newline="\n") as f: - f.write(content) - - logger.info(f"Created schematic from template: {output_path}") - else: - # Fallback: create minimal schematic - logger.warning(f"Template not found at {template_path}, creating minimal schematic") - # Generate unique UUID for this schematic - schematic_uuid = str(uuid.uuid4()) - # Write with explicit UTF-8 encoding and Unix line endings for cross-platform compatibility - with open(output_path, "w", encoding="utf-8", newline="\n") as f: - f.write('(kicad_sch (version 20250114) (generator "KiCAD-MCP-Server")\n\n') - f.write(f" (uuid {schematic_uuid})\n\n") - f.write(' (paper "A4")\n\n') - f.write(" (lib_symbols\n )\n\n") - f.write(' (sheet_instances\n (path "/" (page "1"))\n )\n') - f.write(")\n") - - # Load the schematic - sch = Schematic(output_path) - logger.info(f"Loaded new schematic: {output_path}") - return sch - - except Exception as e: - logger.error(f"Error creating schematic: {e}") - raise - - @staticmethod - def load_schematic(file_path: str) -> Optional[Any]: - """Load an existing schematic""" - if not os.path.exists(file_path): - logger.error(f"Schematic file not found at {file_path}") - return None - try: - sch = Schematic(file_path) - logger.info(f"Loaded schematic from: {file_path}") - return sch - except Exception as e: - logger.error(f"Error loading schematic from {file_path}: {e}") - return None - - @staticmethod - def save_schematic(schematic: Any, file_path: str) -> bool: - """Save a schematic to file""" - try: - # kicad-skip uses write method, not save - schematic.write(file_path) - logger.info(f"Saved schematic to: {file_path}") - return True - except Exception as e: - logger.error(f"Error saving schematic to {file_path}: {e}") - return False - - @staticmethod - def get_schematic_metadata(schematic: Any) -> dict[str, Any]: - """Extract metadata from schematic""" - # kicad-skip doesn't expose a direct metadata object on Schematic. - # We can return basic info like version and generator. - metadata = { - "version": schematic.version, - "generator": schematic.generator, - # Add other relevant properties if needed - } - logger.debug("Extracted schematic metadata") - return metadata - - -if __name__ == "__main__": - # Example Usage (for testing) - # Create a new schematic - new_sch = SchematicManager.create_schematic("MyTestSchematic") - - # Save the schematic - test_file = "test_schematic.kicad_sch" - SchematicManager.save_schematic(new_sch, test_file) - - # Load the schematic - loaded_sch = SchematicManager.load_schematic(test_file) - if loaded_sch: - metadata = SchematicManager.get_schematic_metadata(loaded_sch) - print(f"Loaded schematic metadata: {metadata}") - - # Clean up test file - if os.path.exists(test_file): - os.remove(test_file) - print(f"Cleaned up {test_file}") +import logging +import os +import shutil +import uuid +from typing import Any, Optional + +from skip import Schematic + +logger = logging.getLogger("kicad_interface") + + +class SchematicManager: + """Core schematic operations using kicad-skip""" + + @staticmethod + def create_schematic( + name: str, metadata: Optional[Any] = None, *, path: Optional[str] = None + ) -> Any: + """Create a new empty schematic from template""" + try: + # Determine template path (use template_with_symbols for component cloning support) + template_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "..", + "templates", + "template_with_symbols.kicad_sch", + ) + + # Determine output path + base_name = name if name.endswith(".kicad_sch") else f"{name}.kicad_sch" + output_path = os.path.join(path, base_name) if path else base_name + + if os.path.exists(template_path): + # Copy template to target location + shutil.copy(template_path, output_path) + + # Regenerate UUID to ensure uniqueness for each created schematic + import re + + with open(output_path, "r", encoding="utf-8") as f: + content = f.read() + new_uuid = str(uuid.uuid4()) + content = re.sub( + r"\(uuid [0-9a-fA-F-]+\)", + f"(uuid {new_uuid})", + content, + count=1, # Only replace first (schematic) UUID + ) + with open(output_path, "w", encoding="utf-8", newline="\n") as f: + f.write(content) + + logger.info(f"Created schematic from template: {output_path}") + else: + # Fallback: create minimal schematic + logger.warning(f"Template not found at {template_path}, creating minimal schematic") + # Generate unique UUID for this schematic + schematic_uuid = str(uuid.uuid4()) + # Write with explicit UTF-8 encoding and Unix line endings for cross-platform compatibility + with open(output_path, "w", encoding="utf-8", newline="\n") as f: + f.write('(kicad_sch (version 20250114) (generator "KiCAD-MCP-Server")\n\n') + f.write(f" (uuid {schematic_uuid})\n\n") + f.write(' (paper "A4")\n\n') + f.write(" (lib_symbols\n )\n\n") + f.write(' (sheet_instances\n (path "/" (page "1"))\n )\n') + f.write(")\n") + + # Load the schematic + sch = Schematic(output_path) + logger.info(f"Loaded new schematic: {output_path}") + return sch + + except Exception as e: + logger.error(f"Error creating schematic: {e}") + raise + + @staticmethod + def load_schematic(file_path: str) -> Optional[Any]: + """Load an existing schematic""" + if not os.path.exists(file_path): + logger.error(f"Schematic file not found at {file_path}") + return None + try: + sch = Schematic(file_path) + logger.info(f"Loaded schematic from: {file_path}") + return sch + except Exception as e: + logger.error(f"Error loading schematic from {file_path}: {e}") + return None + + @staticmethod + def save_schematic(schematic: Any, file_path: str) -> bool: + """Save a schematic to file""" + try: + # kicad-skip uses write method, not save + schematic.write(file_path) + logger.info(f"Saved schematic to: {file_path}") + return True + except Exception as e: + logger.error(f"Error saving schematic to {file_path}: {e}") + return False + + @staticmethod + def get_schematic_metadata(schematic: Any) -> dict[str, Any]: + """Extract metadata from schematic""" + # kicad-skip doesn't expose a direct metadata object on Schematic. + # We can return basic info like version and generator. + metadata = { + "version": schematic.version, + "generator": schematic.generator, + # Add other relevant properties if needed + } + logger.debug("Extracted schematic metadata") + return metadata + + +if __name__ == "__main__": + # Example Usage (for testing) + # Create a new schematic + new_sch = SchematicManager.create_schematic("MyTestSchematic") + + # Save the schematic + test_file = "test_schematic.kicad_sch" + SchematicManager.save_schematic(new_sch, test_file) + + # Load the schematic + loaded_sch = SchematicManager.load_schematic(test_file) + if loaded_sch: + metadata = SchematicManager.get_schematic_metadata(loaded_sch) + print(f"Loaded schematic metadata: {metadata}") + + # Clean up test file + if os.path.exists(test_file): + os.remove(test_file) + print(f"Cleaned up {test_file}")