🎉 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>
215 lines
6.9 KiB
Python
215 lines
6.9 KiB
Python
"""
|
|
SWIG Backend (Legacy - DEPRECATED)
|
|
|
|
Uses the legacy SWIG-based pcbnew Python bindings.
|
|
This backend wraps the existing implementation for backward compatibility.
|
|
|
|
WARNING: SWIG bindings are deprecated as of KiCAD 9.0
|
|
and will be removed in KiCAD 10.0.
|
|
Please migrate to IPC backend.
|
|
"""
|
|
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 SWIGBackend(KiCADBackend):
|
|
"""
|
|
Legacy SWIG-based backend
|
|
|
|
Wraps existing commands/project.py, commands/component.py, etc.
|
|
for compatibility during migration period.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._connected = False
|
|
self._pcbnew = None
|
|
logger.warning(
|
|
"⚠️ Using DEPRECATED SWIG backend. "
|
|
"This will be removed in KiCAD 10.0. "
|
|
"Please migrate to IPC API."
|
|
)
|
|
|
|
def connect(self) -> bool:
|
|
"""
|
|
'Connect' to SWIG API (just validates pcbnew import)
|
|
|
|
Returns:
|
|
True if pcbnew module available
|
|
"""
|
|
try:
|
|
import pcbnew
|
|
self._pcbnew = pcbnew
|
|
version = pcbnew.GetBuildVersion()
|
|
logger.info(f"✓ Connected to pcbnew (SWIG): {version}")
|
|
self._connected = True
|
|
return True
|
|
except ImportError as e:
|
|
logger.error("pcbnew module not found")
|
|
raise APINotAvailableError(
|
|
"SWIG backend requires pcbnew module. "
|
|
"Ensure KiCAD Python module is in PYTHONPATH."
|
|
) from e
|
|
|
|
def disconnect(self) -> None:
|
|
"""Disconnect from SWIG API (no-op)"""
|
|
self._connected = False
|
|
self._pcbnew = None
|
|
logger.info("Disconnected from SWIG backend")
|
|
|
|
def is_connected(self) -> bool:
|
|
"""Check if connected"""
|
|
return self._connected
|
|
|
|
def get_version(self) -> str:
|
|
"""Get KiCAD version"""
|
|
if not self.is_connected():
|
|
raise ConnectionError("Not connected")
|
|
|
|
return self._pcbnew.GetBuildVersion()
|
|
|
|
# Project Operations
|
|
def create_project(self, path: Path, name: str) -> Dict[str, Any]:
|
|
"""Create project using existing SWIG implementation"""
|
|
if not self.is_connected():
|
|
raise ConnectionError("Not connected")
|
|
|
|
# Import existing implementation
|
|
from commands.project import ProjectCommands
|
|
|
|
try:
|
|
result = ProjectCommands.create_project(str(path), name)
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Failed to create project: {e}")
|
|
raise
|
|
|
|
def open_project(self, path: Path) -> Dict[str, Any]:
|
|
"""Open project using existing SWIG implementation"""
|
|
if not self.is_connected():
|
|
raise ConnectionError("Not connected")
|
|
|
|
from commands.project import ProjectCommands
|
|
|
|
try:
|
|
result = ProjectCommands.open_project(str(path))
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Failed to open project: {e}")
|
|
raise
|
|
|
|
def save_project(self, path: Optional[Path] = None) -> Dict[str, Any]:
|
|
"""Save project using existing SWIG implementation"""
|
|
if not self.is_connected():
|
|
raise ConnectionError("Not connected")
|
|
|
|
from commands.project import ProjectCommands
|
|
|
|
try:
|
|
path_str = str(path) if path else None
|
|
result = ProjectCommands.save_project(path_str)
|
|
return result
|
|
except Exception as e:
|
|
logger.error(f"Failed to save project: {e}")
|
|
raise
|
|
|
|
def close_project(self) -> None:
|
|
"""Close project (SWIG doesn't have explicit close)"""
|
|
logger.info("Closing project (SWIG backend)")
|
|
# SWIG backend doesn't maintain project state,
|
|
# so this is essentially a no-op
|
|
|
|
# Board Operations
|
|
def get_board(self) -> BoardAPI:
|
|
"""Get board API"""
|
|
if not self.is_connected():
|
|
raise ConnectionError("Not connected")
|
|
|
|
return SWIGBoardAPI(self._pcbnew)
|
|
|
|
|
|
class SWIGBoardAPI(BoardAPI):
|
|
"""Board API implementation wrapping SWIG/pcbnew"""
|
|
|
|
def __init__(self, pcbnew_module):
|
|
self.pcbnew = pcbnew_module
|
|
self._board = None
|
|
|
|
def set_size(self, width: float, height: float, unit: str = "mm") -> bool:
|
|
"""Set board size using existing implementation"""
|
|
from commands.board import BoardCommands
|
|
|
|
try:
|
|
result = BoardCommands.set_board_size(width, height, unit)
|
|
return result.get("success", False)
|
|
except Exception as e:
|
|
logger.error(f"Failed to set board size: {e}")
|
|
return False
|
|
|
|
def get_size(self) -> Dict[str, float]:
|
|
"""Get board size"""
|
|
# TODO: Implement using existing SWIG code
|
|
raise NotImplementedError("get_size not yet wrapped")
|
|
|
|
def add_layer(self, layer_name: str, layer_type: str) -> bool:
|
|
"""Add layer using existing implementation"""
|
|
from commands.board import BoardCommands
|
|
|
|
try:
|
|
result = BoardCommands.add_layer(layer_name, layer_type)
|
|
return result.get("success", False)
|
|
except Exception as e:
|
|
logger.error(f"Failed to add layer: {e}")
|
|
return False
|
|
|
|
def list_components(self) -> List[Dict[str, Any]]:
|
|
"""List components using existing implementation"""
|
|
from commands.component import ComponentCommands
|
|
|
|
try:
|
|
result = ComponentCommands.get_component_list()
|
|
if result.get("success"):
|
|
return result.get("components", [])
|
|
return []
|
|
except Exception as e:
|
|
logger.error(f"Failed to list components: {e}")
|
|
return []
|
|
|
|
def place_component(
|
|
self,
|
|
reference: str,
|
|
footprint: str,
|
|
x: float,
|
|
y: float,
|
|
rotation: float = 0,
|
|
layer: str = "F.Cu"
|
|
) -> bool:
|
|
"""Place component using existing implementation"""
|
|
from commands.component import ComponentCommands
|
|
|
|
try:
|
|
result = ComponentCommands.place_component(
|
|
component_id=footprint,
|
|
position={"x": x, "y": y, "unit": "mm"},
|
|
reference=reference,
|
|
rotation=rotation,
|
|
layer=layer
|
|
)
|
|
return result.get("success", False)
|
|
except Exception as e:
|
|
logger.error(f"Failed to place component: {e}")
|
|
return False
|
|
|
|
|
|
# This backend serves as a wrapper during the migration period.
|
|
# Once IPC backend is fully implemented, this can be deprecated.
|