chore: normalize all tracked files to LF line endings
Mechanical application of the `.gitattributes` rules from the prior commit. All 50 files differ only in line endings — verified by `git diff --cached --ignore-all-space` being empty. Before: main had 42 CRLF + 27 LF Python files plus mixed-ending in YAML, templates, and shell scripts. After: every text file is LF (except the Windows-native *.ps1, *.bat scripts which remain CRLF per gitattributes). This eliminates the noisy-diff failure mode seen in PR #102, where a small logic change produced a 918-line diff due to whole-file CRLF→LF conversion.
This commit is contained in:
@@ -1,27 +1,27 @@
|
||||
"""
|
||||
KiCAD API Abstraction Layer
|
||||
|
||||
This module provides a unified interface to KiCAD's Python APIs,
|
||||
supporting both the legacy SWIG bindings and the new IPC API.
|
||||
|
||||
Usage:
|
||||
from kicad_api import create_backend
|
||||
|
||||
# Auto-detect best available backend
|
||||
backend = create_backend()
|
||||
|
||||
# Or specify explicitly
|
||||
backend = create_backend('ipc') # Use IPC API
|
||||
backend = create_backend('swig') # Use legacy SWIG
|
||||
|
||||
# Connect and use
|
||||
if backend.connect():
|
||||
board = backend.get_board()
|
||||
board.set_size(100, 80)
|
||||
"""
|
||||
|
||||
from kicad_api.base import KiCADBackend
|
||||
from kicad_api.factory import create_backend
|
||||
|
||||
__all__ = ["create_backend", "KiCADBackend"]
|
||||
__version__ = "2.0.0-alpha.1"
|
||||
"""
|
||||
KiCAD API Abstraction Layer
|
||||
|
||||
This module provides a unified interface to KiCAD's Python APIs,
|
||||
supporting both the legacy SWIG bindings and the new IPC API.
|
||||
|
||||
Usage:
|
||||
from kicad_api import create_backend
|
||||
|
||||
# Auto-detect best available backend
|
||||
backend = create_backend()
|
||||
|
||||
# Or specify explicitly
|
||||
backend = create_backend('ipc') # Use IPC API
|
||||
backend = create_backend('swig') # Use legacy SWIG
|
||||
|
||||
# Connect and use
|
||||
if backend.connect():
|
||||
board = backend.get_board()
|
||||
board.set_size(100, 80)
|
||||
"""
|
||||
|
||||
from kicad_api.base import KiCADBackend
|
||||
from kicad_api.factory import create_backend
|
||||
|
||||
__all__ = ["create_backend", "KiCADBackend"]
|
||||
__version__ = "2.0.0-alpha.1"
|
||||
|
||||
@@ -1,293 +1,293 @@
|
||||
"""
|
||||
Abstract base class for KiCAD API backends
|
||||
|
||||
Defines the interface that all KiCAD backends must implement.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class KiCADBackend(ABC):
|
||||
"""Abstract base class for KiCAD API backends"""
|
||||
|
||||
@abstractmethod
|
||||
def connect(self) -> bool:
|
||||
"""
|
||||
Connect to KiCAD
|
||||
|
||||
Returns:
|
||||
True if connection successful, False otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def disconnect(self) -> None:
|
||||
"""Disconnect from KiCAD and clean up resources"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_connected(self) -> bool:
|
||||
"""
|
||||
Check if currently connected to KiCAD
|
||||
|
||||
Returns:
|
||||
True if connected, False otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_version(self) -> str:
|
||||
"""
|
||||
Get KiCAD version
|
||||
|
||||
Returns:
|
||||
Version string (e.g., "9.0.0")
|
||||
"""
|
||||
pass
|
||||
|
||||
# Project Operations
|
||||
@abstractmethod
|
||||
def create_project(self, path: Path, name: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a new KiCAD project
|
||||
|
||||
Args:
|
||||
path: Directory path for the project
|
||||
name: Project name
|
||||
|
||||
Returns:
|
||||
Dictionary with project info
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def open_project(self, path: Path) -> Dict[str, Any]:
|
||||
"""
|
||||
Open an existing KiCAD project
|
||||
|
||||
Args:
|
||||
path: Path to .kicad_pro file
|
||||
|
||||
Returns:
|
||||
Dictionary with project info
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def save_project(self, path: Optional[Path] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Save the current project
|
||||
|
||||
Args:
|
||||
path: Optional new path to save to
|
||||
|
||||
Returns:
|
||||
Dictionary with save status
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def close_project(self) -> None:
|
||||
"""Close the current project"""
|
||||
pass
|
||||
|
||||
# Board Operations
|
||||
@abstractmethod
|
||||
def get_board(self) -> "BoardAPI":
|
||||
"""
|
||||
Get board API for current project
|
||||
|
||||
Returns:
|
||||
BoardAPI instance
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BoardAPI(ABC):
|
||||
"""Abstract interface for board operations"""
|
||||
|
||||
@abstractmethod
|
||||
def set_size(self, width: float, height: float, unit: str = "mm") -> bool:
|
||||
"""
|
||||
Set board size
|
||||
|
||||
Args:
|
||||
width: Board width
|
||||
height: Board height
|
||||
unit: Unit of measurement ("mm" or "in")
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_size(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get current board size
|
||||
|
||||
Returns:
|
||||
Dictionary with width, height, unit
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_layer(self, layer_name: str, layer_type: str) -> bool:
|
||||
"""
|
||||
Add a layer to the board
|
||||
|
||||
Args:
|
||||
layer_name: Name of the layer
|
||||
layer_type: Type ("copper", "technical", "user")
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list_components(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
List all components on the board
|
||||
|
||||
Returns:
|
||||
List of component dictionaries
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def place_component(
|
||||
self,
|
||||
reference: str,
|
||||
footprint: str,
|
||||
x: float,
|
||||
y: float,
|
||||
rotation: float = 0,
|
||||
layer: str = "F.Cu",
|
||||
value: str = "",
|
||||
) -> bool:
|
||||
"""
|
||||
Place a component on the board
|
||||
|
||||
Args:
|
||||
reference: Component reference (e.g., "R1")
|
||||
footprint: Footprint library path
|
||||
x: X position (mm)
|
||||
y: Y position (mm)
|
||||
rotation: Rotation angle (degrees)
|
||||
layer: Layer name
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
pass
|
||||
|
||||
# Routing Operations
|
||||
def add_track(
|
||||
self,
|
||||
start_x: float,
|
||||
start_y: float,
|
||||
end_x: float,
|
||||
end_y: float,
|
||||
width: float = 0.25,
|
||||
layer: str = "F.Cu",
|
||||
net_name: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Add a track (trace) to the board
|
||||
|
||||
Args:
|
||||
start_x: Start X position (mm)
|
||||
start_y: Start Y position (mm)
|
||||
end_x: End X position (mm)
|
||||
end_y: End Y position (mm)
|
||||
width: Track width (mm)
|
||||
layer: Layer name
|
||||
net_name: Optional net name
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def add_via(
|
||||
self,
|
||||
x: float,
|
||||
y: float,
|
||||
diameter: float = 0.8,
|
||||
drill: float = 0.4,
|
||||
net_name: Optional[str] = None,
|
||||
via_type: str = "through",
|
||||
) -> bool:
|
||||
"""
|
||||
Add a via to the board
|
||||
|
||||
Args:
|
||||
x: X position (mm)
|
||||
y: Y position (mm)
|
||||
diameter: Via diameter (mm)
|
||||
drill: Drill diameter (mm)
|
||||
net_name: Optional net name
|
||||
via_type: Via type ("through", "blind", "micro")
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
# Transaction support for undo/redo
|
||||
def begin_transaction(self, description: str = "MCP Operation") -> None:
|
||||
"""Begin a transaction for grouping operations."""
|
||||
pass # Optional - not all backends support this
|
||||
|
||||
def commit_transaction(self, description: str = "MCP Operation") -> None:
|
||||
"""Commit the current transaction."""
|
||||
pass # Optional
|
||||
|
||||
def rollback_transaction(self) -> None:
|
||||
"""Roll back the current transaction."""
|
||||
pass # Optional
|
||||
|
||||
def save(self) -> bool:
|
||||
"""Save the board."""
|
||||
raise NotImplementedError()
|
||||
|
||||
# Query operations
|
||||
def get_tracks(self) -> List[Dict[str, Any]]:
|
||||
"""Get all tracks on the board."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_vias(self) -> List[Dict[str, Any]]:
|
||||
"""Get all vias on the board."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_nets(self) -> List[Dict[str, Any]]:
|
||||
"""Get all nets on the board."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_selection(self) -> List[Dict[str, Any]]:
|
||||
"""Get currently selected items."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class BackendError(Exception):
|
||||
"""Base exception for backend errors"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ConnectionError(BackendError):
|
||||
"""Raised when connection to KiCAD fails"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class APINotAvailableError(BackendError):
|
||||
"""Raised when required API is not available"""
|
||||
|
||||
pass
|
||||
"""
|
||||
Abstract base class for KiCAD API backends
|
||||
|
||||
Defines the interface that all KiCAD backends must implement.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class KiCADBackend(ABC):
|
||||
"""Abstract base class for KiCAD API backends"""
|
||||
|
||||
@abstractmethod
|
||||
def connect(self) -> bool:
|
||||
"""
|
||||
Connect to KiCAD
|
||||
|
||||
Returns:
|
||||
True if connection successful, False otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def disconnect(self) -> None:
|
||||
"""Disconnect from KiCAD and clean up resources"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_connected(self) -> bool:
|
||||
"""
|
||||
Check if currently connected to KiCAD
|
||||
|
||||
Returns:
|
||||
True if connected, False otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_version(self) -> str:
|
||||
"""
|
||||
Get KiCAD version
|
||||
|
||||
Returns:
|
||||
Version string (e.g., "9.0.0")
|
||||
"""
|
||||
pass
|
||||
|
||||
# Project Operations
|
||||
@abstractmethod
|
||||
def create_project(self, path: Path, name: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a new KiCAD project
|
||||
|
||||
Args:
|
||||
path: Directory path for the project
|
||||
name: Project name
|
||||
|
||||
Returns:
|
||||
Dictionary with project info
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def open_project(self, path: Path) -> Dict[str, Any]:
|
||||
"""
|
||||
Open an existing KiCAD project
|
||||
|
||||
Args:
|
||||
path: Path to .kicad_pro file
|
||||
|
||||
Returns:
|
||||
Dictionary with project info
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def save_project(self, path: Optional[Path] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Save the current project
|
||||
|
||||
Args:
|
||||
path: Optional new path to save to
|
||||
|
||||
Returns:
|
||||
Dictionary with save status
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def close_project(self) -> None:
|
||||
"""Close the current project"""
|
||||
pass
|
||||
|
||||
# Board Operations
|
||||
@abstractmethod
|
||||
def get_board(self) -> "BoardAPI":
|
||||
"""
|
||||
Get board API for current project
|
||||
|
||||
Returns:
|
||||
BoardAPI instance
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BoardAPI(ABC):
|
||||
"""Abstract interface for board operations"""
|
||||
|
||||
@abstractmethod
|
||||
def set_size(self, width: float, height: float, unit: str = "mm") -> bool:
|
||||
"""
|
||||
Set board size
|
||||
|
||||
Args:
|
||||
width: Board width
|
||||
height: Board height
|
||||
unit: Unit of measurement ("mm" or "in")
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_size(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get current board size
|
||||
|
||||
Returns:
|
||||
Dictionary with width, height, unit
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add_layer(self, layer_name: str, layer_type: str) -> bool:
|
||||
"""
|
||||
Add a layer to the board
|
||||
|
||||
Args:
|
||||
layer_name: Name of the layer
|
||||
layer_type: Type ("copper", "technical", "user")
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list_components(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
List all components on the board
|
||||
|
||||
Returns:
|
||||
List of component dictionaries
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def place_component(
|
||||
self,
|
||||
reference: str,
|
||||
footprint: str,
|
||||
x: float,
|
||||
y: float,
|
||||
rotation: float = 0,
|
||||
layer: str = "F.Cu",
|
||||
value: str = "",
|
||||
) -> bool:
|
||||
"""
|
||||
Place a component on the board
|
||||
|
||||
Args:
|
||||
reference: Component reference (e.g., "R1")
|
||||
footprint: Footprint library path
|
||||
x: X position (mm)
|
||||
y: Y position (mm)
|
||||
rotation: Rotation angle (degrees)
|
||||
layer: Layer name
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
pass
|
||||
|
||||
# Routing Operations
|
||||
def add_track(
|
||||
self,
|
||||
start_x: float,
|
||||
start_y: float,
|
||||
end_x: float,
|
||||
end_y: float,
|
||||
width: float = 0.25,
|
||||
layer: str = "F.Cu",
|
||||
net_name: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Add a track (trace) to the board
|
||||
|
||||
Args:
|
||||
start_x: Start X position (mm)
|
||||
start_y: Start Y position (mm)
|
||||
end_x: End X position (mm)
|
||||
end_y: End Y position (mm)
|
||||
width: Track width (mm)
|
||||
layer: Layer name
|
||||
net_name: Optional net name
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def add_via(
|
||||
self,
|
||||
x: float,
|
||||
y: float,
|
||||
diameter: float = 0.8,
|
||||
drill: float = 0.4,
|
||||
net_name: Optional[str] = None,
|
||||
via_type: str = "through",
|
||||
) -> bool:
|
||||
"""
|
||||
Add a via to the board
|
||||
|
||||
Args:
|
||||
x: X position (mm)
|
||||
y: Y position (mm)
|
||||
diameter: Via diameter (mm)
|
||||
drill: Drill diameter (mm)
|
||||
net_name: Optional net name
|
||||
via_type: Via type ("through", "blind", "micro")
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
# Transaction support for undo/redo
|
||||
def begin_transaction(self, description: str = "MCP Operation") -> None:
|
||||
"""Begin a transaction for grouping operations."""
|
||||
pass # Optional - not all backends support this
|
||||
|
||||
def commit_transaction(self, description: str = "MCP Operation") -> None:
|
||||
"""Commit the current transaction."""
|
||||
pass # Optional
|
||||
|
||||
def rollback_transaction(self) -> None:
|
||||
"""Roll back the current transaction."""
|
||||
pass # Optional
|
||||
|
||||
def save(self) -> bool:
|
||||
"""Save the board."""
|
||||
raise NotImplementedError()
|
||||
|
||||
# Query operations
|
||||
def get_tracks(self) -> List[Dict[str, Any]]:
|
||||
"""Get all tracks on the board."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_vias(self) -> List[Dict[str, Any]]:
|
||||
"""Get all vias on the board."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_nets(self) -> List[Dict[str, Any]]:
|
||||
"""Get all nets on the board."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_selection(self) -> List[Dict[str, Any]]:
|
||||
"""Get currently selected items."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class BackendError(Exception):
|
||||
"""Base exception for backend errors"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ConnectionError(BackendError):
|
||||
"""Raised when connection to KiCAD fails"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class APINotAvailableError(BackendError):
|
||||
"""Raised when required API is not available"""
|
||||
|
||||
pass
|
||||
|
||||
@@ -1,195 +1,195 @@
|
||||
"""
|
||||
Backend factory for creating appropriate KiCAD API backend
|
||||
|
||||
Auto-detects available backends and provides fallback mechanism.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from kicad_api.base import APINotAvailableError, KiCADBackend
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_backend(backend_type: Optional[str] = None) -> KiCADBackend:
|
||||
"""
|
||||
Create appropriate KiCAD backend
|
||||
|
||||
Args:
|
||||
backend_type: Backend to use:
|
||||
- 'ipc': Use IPC API (recommended)
|
||||
- 'swig': Use legacy SWIG bindings
|
||||
- None or 'auto': Auto-detect (try IPC first, fall back to SWIG)
|
||||
|
||||
Returns:
|
||||
KiCADBackend instance
|
||||
|
||||
Raises:
|
||||
APINotAvailableError: If no backend is available
|
||||
|
||||
Environment Variables:
|
||||
KICAD_BACKEND: Override backend selection ('ipc', 'swig', or 'auto')
|
||||
"""
|
||||
# Check environment variable override
|
||||
if backend_type is None:
|
||||
backend_type = os.environ.get("KICAD_BACKEND", "auto").lower()
|
||||
|
||||
logger.info(f"Requested backend: {backend_type}")
|
||||
|
||||
# Try specific backend if requested
|
||||
if backend_type == "ipc":
|
||||
return _create_ipc_backend()
|
||||
elif backend_type == "swig":
|
||||
return _create_swig_backend()
|
||||
elif backend_type == "auto":
|
||||
return _auto_detect_backend()
|
||||
else:
|
||||
raise ValueError(f"Unknown backend type: {backend_type}")
|
||||
|
||||
|
||||
def _create_ipc_backend() -> KiCADBackend:
|
||||
"""
|
||||
Create IPC backend
|
||||
|
||||
Returns:
|
||||
IPCBackend instance
|
||||
|
||||
Raises:
|
||||
APINotAvailableError: If kicad-python not available
|
||||
"""
|
||||
try:
|
||||
from kicad_api.ipc_backend import IPCBackend
|
||||
|
||||
logger.info("Creating IPC backend")
|
||||
return IPCBackend()
|
||||
except ImportError as e:
|
||||
logger.error(f"IPC backend not available: {e}")
|
||||
raise APINotAvailableError(
|
||||
"IPC backend requires 'kicad-python' package. " "Install with: pip install kicad-python"
|
||||
) from e
|
||||
|
||||
|
||||
def _create_swig_backend() -> KiCADBackend:
|
||||
"""
|
||||
Create SWIG backend
|
||||
|
||||
Returns:
|
||||
SWIGBackend instance
|
||||
|
||||
Raises:
|
||||
APINotAvailableError: If pcbnew not available
|
||||
"""
|
||||
try:
|
||||
from kicad_api.swig_backend import SWIGBackend
|
||||
|
||||
logger.info("Creating SWIG backend")
|
||||
logger.warning(
|
||||
"SWIG backend is DEPRECATED and will be removed in KiCAD 10.0. "
|
||||
"Please migrate to IPC backend."
|
||||
)
|
||||
return SWIGBackend()
|
||||
except ImportError as e:
|
||||
logger.error(f"SWIG backend not available: {e}")
|
||||
raise APINotAvailableError(
|
||||
"SWIG backend requires 'pcbnew' module. " "Ensure KiCAD Python module is in PYTHONPATH."
|
||||
) from e
|
||||
|
||||
|
||||
def _auto_detect_backend() -> KiCADBackend:
|
||||
"""
|
||||
Auto-detect best available backend
|
||||
|
||||
Priority:
|
||||
1. IPC API (if kicad-python available and KiCAD running)
|
||||
2. SWIG API (if pcbnew available)
|
||||
|
||||
Returns:
|
||||
Best available KiCADBackend
|
||||
|
||||
Raises:
|
||||
APINotAvailableError: If no backend available
|
||||
"""
|
||||
logger.info("Auto-detecting available KiCAD backend...")
|
||||
|
||||
# Try IPC first (preferred)
|
||||
try:
|
||||
backend = _create_ipc_backend()
|
||||
# Test connection
|
||||
if backend.connect():
|
||||
logger.info("✓ IPC backend available and connected")
|
||||
return backend
|
||||
else:
|
||||
logger.warning("IPC backend available but connection failed")
|
||||
except (ImportError, APINotAvailableError) as e:
|
||||
logger.debug(f"IPC backend not available: {e}")
|
||||
|
||||
# Fall back to SWIG
|
||||
try:
|
||||
backend = _create_swig_backend()
|
||||
logger.warning(
|
||||
"Using deprecated SWIG backend. " "For best results, use IPC API with KiCAD running."
|
||||
)
|
||||
return backend
|
||||
except (ImportError, APINotAvailableError) as e:
|
||||
logger.error(f"SWIG backend not available: {e}")
|
||||
|
||||
# No backend available
|
||||
raise APINotAvailableError(
|
||||
"No KiCAD backend available. Please install either:\n"
|
||||
" - kicad-python (recommended): pip install kicad-python\n"
|
||||
" - Ensure KiCAD Python module (pcbnew) is in PYTHONPATH"
|
||||
)
|
||||
|
||||
|
||||
def get_available_backends() -> dict:
|
||||
"""
|
||||
Check which backends are available
|
||||
|
||||
Returns:
|
||||
Dictionary with backend availability:
|
||||
{
|
||||
'ipc': {'available': bool, 'version': str or None},
|
||||
'swig': {'available': bool, 'version': str or None}
|
||||
}
|
||||
"""
|
||||
results = {}
|
||||
|
||||
# Check IPC (kicad-python uses 'kipy' module name)
|
||||
try:
|
||||
import kipy
|
||||
|
||||
results["ipc"] = {"available": True, "version": getattr(kipy, "__version__", "unknown")}
|
||||
except ImportError:
|
||||
results["ipc"] = {"available": False, "version": None}
|
||||
|
||||
# Check SWIG
|
||||
try:
|
||||
import pcbnew
|
||||
|
||||
results["swig"] = {"available": True, "version": pcbnew.GetBuildVersion()}
|
||||
except ImportError:
|
||||
results["swig"] = {"available": False, "version": None}
|
||||
|
||||
return results
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Quick diagnostic
|
||||
import json
|
||||
|
||||
print("KiCAD Backend Availability:")
|
||||
print(json.dumps(get_available_backends(), indent=2))
|
||||
|
||||
print("\nAttempting to create backend...")
|
||||
try:
|
||||
backend = create_backend()
|
||||
print(f"✓ Created backend: {type(backend).__name__}")
|
||||
if backend.connect():
|
||||
print(f"✓ Connected to KiCAD: {backend.get_version()}")
|
||||
else:
|
||||
print("✗ Failed to connect to KiCAD")
|
||||
except Exception as e:
|
||||
print(f"✗ Error: {e}")
|
||||
"""
|
||||
Backend factory for creating appropriate KiCAD API backend
|
||||
|
||||
Auto-detects available backends and provides fallback mechanism.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from kicad_api.base import APINotAvailableError, KiCADBackend
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_backend(backend_type: Optional[str] = None) -> KiCADBackend:
|
||||
"""
|
||||
Create appropriate KiCAD backend
|
||||
|
||||
Args:
|
||||
backend_type: Backend to use:
|
||||
- 'ipc': Use IPC API (recommended)
|
||||
- 'swig': Use legacy SWIG bindings
|
||||
- None or 'auto': Auto-detect (try IPC first, fall back to SWIG)
|
||||
|
||||
Returns:
|
||||
KiCADBackend instance
|
||||
|
||||
Raises:
|
||||
APINotAvailableError: If no backend is available
|
||||
|
||||
Environment Variables:
|
||||
KICAD_BACKEND: Override backend selection ('ipc', 'swig', or 'auto')
|
||||
"""
|
||||
# Check environment variable override
|
||||
if backend_type is None:
|
||||
backend_type = os.environ.get("KICAD_BACKEND", "auto").lower()
|
||||
|
||||
logger.info(f"Requested backend: {backend_type}")
|
||||
|
||||
# Try specific backend if requested
|
||||
if backend_type == "ipc":
|
||||
return _create_ipc_backend()
|
||||
elif backend_type == "swig":
|
||||
return _create_swig_backend()
|
||||
elif backend_type == "auto":
|
||||
return _auto_detect_backend()
|
||||
else:
|
||||
raise ValueError(f"Unknown backend type: {backend_type}")
|
||||
|
||||
|
||||
def _create_ipc_backend() -> KiCADBackend:
|
||||
"""
|
||||
Create IPC backend
|
||||
|
||||
Returns:
|
||||
IPCBackend instance
|
||||
|
||||
Raises:
|
||||
APINotAvailableError: If kicad-python not available
|
||||
"""
|
||||
try:
|
||||
from kicad_api.ipc_backend import IPCBackend
|
||||
|
||||
logger.info("Creating IPC backend")
|
||||
return IPCBackend()
|
||||
except ImportError as e:
|
||||
logger.error(f"IPC backend not available: {e}")
|
||||
raise APINotAvailableError(
|
||||
"IPC backend requires 'kicad-python' package. " "Install with: pip install kicad-python"
|
||||
) from e
|
||||
|
||||
|
||||
def _create_swig_backend() -> KiCADBackend:
|
||||
"""
|
||||
Create SWIG backend
|
||||
|
||||
Returns:
|
||||
SWIGBackend instance
|
||||
|
||||
Raises:
|
||||
APINotAvailableError: If pcbnew not available
|
||||
"""
|
||||
try:
|
||||
from kicad_api.swig_backend import SWIGBackend
|
||||
|
||||
logger.info("Creating SWIG backend")
|
||||
logger.warning(
|
||||
"SWIG backend is DEPRECATED and will be removed in KiCAD 10.0. "
|
||||
"Please migrate to IPC backend."
|
||||
)
|
||||
return SWIGBackend()
|
||||
except ImportError as e:
|
||||
logger.error(f"SWIG backend not available: {e}")
|
||||
raise APINotAvailableError(
|
||||
"SWIG backend requires 'pcbnew' module. " "Ensure KiCAD Python module is in PYTHONPATH."
|
||||
) from e
|
||||
|
||||
|
||||
def _auto_detect_backend() -> KiCADBackend:
|
||||
"""
|
||||
Auto-detect best available backend
|
||||
|
||||
Priority:
|
||||
1. IPC API (if kicad-python available and KiCAD running)
|
||||
2. SWIG API (if pcbnew available)
|
||||
|
||||
Returns:
|
||||
Best available KiCADBackend
|
||||
|
||||
Raises:
|
||||
APINotAvailableError: If no backend available
|
||||
"""
|
||||
logger.info("Auto-detecting available KiCAD backend...")
|
||||
|
||||
# Try IPC first (preferred)
|
||||
try:
|
||||
backend = _create_ipc_backend()
|
||||
# Test connection
|
||||
if backend.connect():
|
||||
logger.info("✓ IPC backend available and connected")
|
||||
return backend
|
||||
else:
|
||||
logger.warning("IPC backend available but connection failed")
|
||||
except (ImportError, APINotAvailableError) as e:
|
||||
logger.debug(f"IPC backend not available: {e}")
|
||||
|
||||
# Fall back to SWIG
|
||||
try:
|
||||
backend = _create_swig_backend()
|
||||
logger.warning(
|
||||
"Using deprecated SWIG backend. " "For best results, use IPC API with KiCAD running."
|
||||
)
|
||||
return backend
|
||||
except (ImportError, APINotAvailableError) as e:
|
||||
logger.error(f"SWIG backend not available: {e}")
|
||||
|
||||
# No backend available
|
||||
raise APINotAvailableError(
|
||||
"No KiCAD backend available. Please install either:\n"
|
||||
" - kicad-python (recommended): pip install kicad-python\n"
|
||||
" - Ensure KiCAD Python module (pcbnew) is in PYTHONPATH"
|
||||
)
|
||||
|
||||
|
||||
def get_available_backends() -> dict:
|
||||
"""
|
||||
Check which backends are available
|
||||
|
||||
Returns:
|
||||
Dictionary with backend availability:
|
||||
{
|
||||
'ipc': {'available': bool, 'version': str or None},
|
||||
'swig': {'available': bool, 'version': str or None}
|
||||
}
|
||||
"""
|
||||
results = {}
|
||||
|
||||
# Check IPC (kicad-python uses 'kipy' module name)
|
||||
try:
|
||||
import kipy
|
||||
|
||||
results["ipc"] = {"available": True, "version": getattr(kipy, "__version__", "unknown")}
|
||||
except ImportError:
|
||||
results["ipc"] = {"available": False, "version": None}
|
||||
|
||||
# Check SWIG
|
||||
try:
|
||||
import pcbnew
|
||||
|
||||
results["swig"] = {"available": True, "version": pcbnew.GetBuildVersion()}
|
||||
except ImportError:
|
||||
results["swig"] = {"available": False, "version": None}
|
||||
|
||||
return results
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Quick diagnostic
|
||||
import json
|
||||
|
||||
print("KiCAD Backend Availability:")
|
||||
print(json.dumps(get_available_backends(), indent=2))
|
||||
|
||||
print("\nAttempting to create backend...")
|
||||
try:
|
||||
backend = create_backend()
|
||||
print(f"✓ Created backend: {type(backend).__name__}")
|
||||
if backend.connect():
|
||||
print(f"✓ Connected to KiCAD: {backend.get_version()}")
|
||||
else:
|
||||
print("✗ Failed to connect to KiCAD")
|
||||
except Exception as e:
|
||||
print(f"✗ Error: {e}")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,218 +1,218 @@
|
||||
"""
|
||||
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 Any, Dict, List, Optional
|
||||
|
||||
from kicad_api.base import APINotAvailableError, BoardAPI, ConnectionError, KiCADBackend
|
||||
|
||||
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) -> None:
|
||||
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({"filename": 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:
|
||||
params: Dict[str, Any] = {}
|
||||
if path:
|
||||
params["filename"] = str(path)
|
||||
result = ProjectCommands().save_project(params)
|
||||
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: Any) -> None:
|
||||
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(board=self._board).set_board_size(
|
||||
{"width": width, "height": height, "unit": 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, Any]:
|
||||
"""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(board=self._board).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",
|
||||
value: str = "",
|
||||
) -> bool:
|
||||
"""Place component using existing implementation"""
|
||||
from commands.component import ComponentCommands
|
||||
|
||||
try:
|
||||
result = ComponentCommands(board=self._board).place_component(
|
||||
{
|
||||
"componentId": 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.
|
||||
"""
|
||||
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 Any, Dict, List, Optional
|
||||
|
||||
from kicad_api.base import APINotAvailableError, BoardAPI, ConnectionError, KiCADBackend
|
||||
|
||||
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) -> None:
|
||||
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({"filename": 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:
|
||||
params: Dict[str, Any] = {}
|
||||
if path:
|
||||
params["filename"] = str(path)
|
||||
result = ProjectCommands().save_project(params)
|
||||
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: Any) -> None:
|
||||
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(board=self._board).set_board_size(
|
||||
{"width": width, "height": height, "unit": 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, Any]:
|
||||
"""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(board=self._board).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",
|
||||
value: str = "",
|
||||
) -> bool:
|
||||
"""Place component using existing implementation"""
|
||||
from commands.component import ComponentCommands
|
||||
|
||||
try:
|
||||
result = ComponentCommands(board=self._board).place_component(
|
||||
{
|
||||
"componentId": 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.
|
||||
|
||||
Reference in New Issue
Block a user