🎉 Major v2.0 rebuild kickoff - Week 1 accomplished! ## Highlights ### Cross-Platform Support 🌍 - ✅ Linux primary platform (Ubuntu/Debian tested) - ✅ Windows fully supported - ✅ macOS experimental support - ✅ Platform-agnostic path handling (XDG spec) - ✅ Auto-detection of KiCAD installation ### Infrastructure 🏗️ - ✅ GitHub Actions CI/CD pipeline - ✅ Pytest framework with 20+ tests - ✅ Pre-commit hooks (Black, MyPy, ESLint) - ✅ Automated Linux installation script - ✅ Enhanced npm scripts ### IPC API Migration Prep 🚀 - ✅ Comprehensive migration plan (30 pages) - ✅ Backend abstraction layer (800+ lines) - ✅ Factory pattern with auto-detection - ✅ SWIG backward compatibility wrapper - ✅ IPC backend skeleton ready ### Documentation 📚 - ✅ Updated README (Linux installation) - ✅ CONTRIBUTING.md guide - ✅ Linux compatibility audit - ✅ IPC API migration plan - ✅ Session summaries - ✅ Platform-specific config templates ## Files Changed - 27 files created - ~3,000 lines of code/docs - 8 comprehensive documentation pages - 20+ unit tests - 5 abstraction layer modules ## Next Steps - Week 2: IPC API migration (project.py → component.py → routing.py) - Migrate from deprecated SWIG to official IPC API - JLCPCB/Digikey integration prep 🤖 Generated with Claude Code https://claude.com/claude-code Co-Authored-By: Claude <noreply@anthropic.com>
196 lines
6.4 KiB
Python
196 lines
6.4 KiB
Python
"""
|
|
IPC API Backend (KiCAD 9.0+)
|
|
|
|
Uses the official kicad-python library for inter-process communication
|
|
with a running KiCAD instance.
|
|
|
|
Note: Requires KiCAD to be running with IPC server enabled:
|
|
Preferences > Plugins > Enable IPC API Server
|
|
"""
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Optional, Dict, Any, List
|
|
|
|
from kicad_api.base import (
|
|
KiCADBackend,
|
|
BoardAPI,
|
|
ConnectionError,
|
|
APINotAvailableError
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class IPCBackend(KiCADBackend):
|
|
"""
|
|
KiCAD IPC API backend
|
|
|
|
Communicates with KiCAD via Protocol Buffers over UNIX sockets.
|
|
Requires KiCAD 9.0+ to be running with IPC enabled.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.kicad = None
|
|
self._connected = False
|
|
|
|
def connect(self) -> bool:
|
|
"""
|
|
Connect to running KiCAD instance via IPC
|
|
|
|
Returns:
|
|
True if connection successful
|
|
|
|
Raises:
|
|
ConnectionError: If connection fails
|
|
"""
|
|
try:
|
|
# Import here to allow module to load even without kicad-python
|
|
from kicad import KiCad
|
|
|
|
logger.info("Connecting to KiCAD via IPC...")
|
|
self.kicad = KiCad()
|
|
|
|
# Verify connection with version check
|
|
version = self.get_version()
|
|
logger.info(f"✓ Connected to KiCAD {version} via IPC")
|
|
self._connected = True
|
|
return True
|
|
|
|
except ImportError as e:
|
|
logger.error("kicad-python library not found")
|
|
raise APINotAvailableError(
|
|
"IPC backend requires kicad-python. "
|
|
"Install with: pip install kicad-python"
|
|
) from e
|
|
except Exception as e:
|
|
logger.error(f"Failed to connect via IPC: {e}")
|
|
logger.info(
|
|
"Ensure KiCAD is running with IPC enabled: "
|
|
"Preferences > Plugins > Enable IPC API Server"
|
|
)
|
|
raise ConnectionError(f"IPC connection failed: {e}") from e
|
|
|
|
def disconnect(self) -> None:
|
|
"""Disconnect from KiCAD"""
|
|
if self.kicad:
|
|
# kicad-python handles cleanup automatically
|
|
self.kicad = None
|
|
self._connected = False
|
|
logger.info("Disconnected from KiCAD IPC")
|
|
|
|
def is_connected(self) -> bool:
|
|
"""Check if connected"""
|
|
return self._connected and self.kicad is not None
|
|
|
|
def get_version(self) -> str:
|
|
"""Get KiCAD version"""
|
|
if not self.kicad:
|
|
raise ConnectionError("Not connected to KiCAD")
|
|
|
|
try:
|
|
# Use kicad-python's version checking
|
|
version_info = self.kicad.check_version()
|
|
return str(version_info)
|
|
except Exception as e:
|
|
logger.warning(f"Could not get version: {e}")
|
|
return "unknown"
|
|
|
|
# Project Operations
|
|
def create_project(self, path: Path, name: str) -> Dict[str, Any]:
|
|
"""
|
|
Create a new KiCAD project
|
|
|
|
TODO: Implement with IPC API
|
|
"""
|
|
if not self.is_connected():
|
|
raise ConnectionError("Not connected to KiCAD")
|
|
|
|
logger.warning("create_project not yet implemented for IPC backend")
|
|
raise NotImplementedError(
|
|
"Project creation via IPC API is not yet implemented. "
|
|
"This will be added in Week 2-3 migration."
|
|
)
|
|
|
|
def open_project(self, path: Path) -> Dict[str, Any]:
|
|
"""Open existing project"""
|
|
if not self.is_connected():
|
|
raise ConnectionError("Not connected to KiCAD")
|
|
|
|
logger.warning("open_project not yet implemented for IPC backend")
|
|
raise NotImplementedError("Coming in Week 2-3 migration")
|
|
|
|
def save_project(self, path: Optional[Path] = None) -> Dict[str, Any]:
|
|
"""Save current project"""
|
|
if not self.is_connected():
|
|
raise ConnectionError("Not connected to KiCAD")
|
|
|
|
logger.warning("save_project not yet implemented for IPC backend")
|
|
raise NotImplementedError("Coming in Week 2-3 migration")
|
|
|
|
def close_project(self) -> None:
|
|
"""Close current project"""
|
|
if not self.is_connected():
|
|
raise ConnectionError("Not connected to KiCAD")
|
|
|
|
logger.warning("close_project not yet implemented for IPC backend")
|
|
raise NotImplementedError("Coming in Week 2-3 migration")
|
|
|
|
# Board Operations
|
|
def get_board(self) -> BoardAPI:
|
|
"""Get board API"""
|
|
if not self.is_connected():
|
|
raise ConnectionError("Not connected to KiCAD")
|
|
|
|
return IPCBoardAPI(self.kicad)
|
|
|
|
|
|
class IPCBoardAPI(BoardAPI):
|
|
"""Board API implementation for IPC backend"""
|
|
|
|
def __init__(self, kicad_instance):
|
|
self.kicad = kicad_instance
|
|
self._board = None
|
|
|
|
def _get_board(self):
|
|
"""Lazy-load board instance"""
|
|
if self._board is None:
|
|
self._board = self.kicad.get_board()
|
|
return self._board
|
|
|
|
def set_size(self, width: float, height: float, unit: str = "mm") -> bool:
|
|
"""Set board size"""
|
|
logger.warning("set_size not yet implemented for IPC backend")
|
|
raise NotImplementedError("Coming in Week 2-3 migration")
|
|
|
|
def get_size(self) -> Dict[str, float]:
|
|
"""Get board size"""
|
|
logger.warning("get_size not yet implemented for IPC backend")
|
|
raise NotImplementedError("Coming in Week 2-3 migration")
|
|
|
|
def add_layer(self, layer_name: str, layer_type: str) -> bool:
|
|
"""Add layer"""
|
|
logger.warning("add_layer not yet implemented for IPC backend")
|
|
raise NotImplementedError("Coming in Week 2-3 migration")
|
|
|
|
def list_components(self) -> List[Dict[str, Any]]:
|
|
"""List components"""
|
|
logger.warning("list_components not yet implemented for IPC backend")
|
|
raise NotImplementedError("Coming in Week 2-3 migration")
|
|
|
|
def place_component(
|
|
self,
|
|
reference: str,
|
|
footprint: str,
|
|
x: float,
|
|
y: float,
|
|
rotation: float = 0,
|
|
layer: str = "F.Cu"
|
|
) -> bool:
|
|
"""Place component"""
|
|
logger.warning("place_component not yet implemented for IPC backend")
|
|
raise NotImplementedError("Coming in Week 2-3 migration")
|
|
|
|
|
|
# Note: Full implementation will be completed during Week 2-3 migration
|
|
# This is a skeleton to establish the pattern
|