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.
177 lines
6.3 KiB
Python
177 lines
6.3 KiB
Python
"""
|
|
Board layer command implementations for KiCAD interface
|
|
"""
|
|
|
|
import logging
|
|
from typing import Any, Dict, Optional
|
|
|
|
import pcbnew
|
|
|
|
logger = logging.getLogger("kicad_interface")
|
|
|
|
|
|
class BoardLayerCommands:
|
|
"""Handles board layer operations"""
|
|
|
|
def __init__(self, board: Optional[pcbnew.BOARD] = None):
|
|
"""Initialize with optional board instance"""
|
|
self.board = board
|
|
|
|
def add_layer(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Add a new layer to the PCB"""
|
|
try:
|
|
if not self.board:
|
|
return {
|
|
"success": False,
|
|
"message": "No board is loaded",
|
|
"errorDetails": "Load or create a board first",
|
|
}
|
|
|
|
name = params.get("name")
|
|
layer_type = params.get("type")
|
|
position = params.get("position")
|
|
number = params.get("number")
|
|
|
|
if not name or not layer_type or not position:
|
|
return {
|
|
"success": False,
|
|
"message": "Missing parameters",
|
|
"errorDetails": "name, type, and position are required",
|
|
}
|
|
|
|
# Get layer stack
|
|
layer_stack = self.board.GetLayerStack()
|
|
|
|
# Determine layer ID based on position and number
|
|
layer_id = None
|
|
if position == "inner":
|
|
if number is None:
|
|
return {
|
|
"success": False,
|
|
"message": "Missing layer number",
|
|
"errorDetails": "number is required for inner layers",
|
|
}
|
|
layer_id = pcbnew.In1_Cu + (number - 1)
|
|
elif position == "top":
|
|
layer_id = pcbnew.F_Cu
|
|
elif position == "bottom":
|
|
layer_id = pcbnew.B_Cu
|
|
|
|
if layer_id is None:
|
|
return {
|
|
"success": False,
|
|
"message": "Invalid layer position",
|
|
"errorDetails": "position must be 'top', 'bottom', or 'inner'",
|
|
}
|
|
|
|
# Set layer properties
|
|
layer_stack.SetLayerName(layer_id, name)
|
|
layer_stack.SetLayerType(layer_id, self._get_layer_type(layer_type))
|
|
|
|
# Enable the layer
|
|
self.board.SetLayerEnabled(layer_id, True)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Added layer: {name}",
|
|
"layer": {"name": name, "type": layer_type, "position": position, "number": number},
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error adding layer: {str(e)}")
|
|
return {"success": False, "message": "Failed to add layer", "errorDetails": str(e)}
|
|
|
|
def set_active_layer(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Set the active layer for PCB operations"""
|
|
try:
|
|
if not self.board:
|
|
return {
|
|
"success": False,
|
|
"message": "No board is loaded",
|
|
"errorDetails": "Load or create a board first",
|
|
}
|
|
|
|
layer = params.get("layer")
|
|
if not layer:
|
|
return {
|
|
"success": False,
|
|
"message": "No layer specified",
|
|
"errorDetails": "layer parameter is required",
|
|
}
|
|
|
|
# Find layer ID by name
|
|
layer_id = self.board.GetLayerID(layer)
|
|
if layer_id < 0:
|
|
return {
|
|
"success": False,
|
|
"message": "Layer not found",
|
|
"errorDetails": f"Layer '{layer}' does not exist",
|
|
}
|
|
|
|
# Set active layer
|
|
self.board.SetActiveLayer(layer_id)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Set active layer to: {layer}",
|
|
"layer": {"name": layer, "id": layer_id},
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error setting active layer: {str(e)}")
|
|
return {
|
|
"success": False,
|
|
"message": "Failed to set active layer",
|
|
"errorDetails": str(e),
|
|
}
|
|
|
|
def get_layer_list(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Get a list of all layers in the PCB"""
|
|
try:
|
|
if not self.board:
|
|
return {
|
|
"success": False,
|
|
"message": "No board is loaded",
|
|
"errorDetails": "Load or create a board first",
|
|
}
|
|
|
|
layers = []
|
|
for layer_id in range(pcbnew.PCB_LAYER_ID_COUNT):
|
|
if self.board.IsLayerEnabled(layer_id):
|
|
layers.append(
|
|
{
|
|
"name": self.board.GetLayerName(layer_id),
|
|
"type": self._get_layer_type_name(self.board.GetLayerType(layer_id)),
|
|
"id": layer_id,
|
|
# Note: isActive removed - GetActiveLayer() doesn't exist in KiCAD 9.0
|
|
# Active layer is a UI concept not applicable to headless scripting
|
|
}
|
|
)
|
|
|
|
return {"success": True, "layers": layers}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting layer list: {str(e)}")
|
|
return {"success": False, "message": "Failed to get layer list", "errorDetails": str(e)}
|
|
|
|
def _get_layer_type(self, type_name: str) -> int:
|
|
"""Convert layer type name to KiCAD layer type constant"""
|
|
type_map = {
|
|
"copper": pcbnew.LT_SIGNAL,
|
|
"technical": pcbnew.LT_SIGNAL,
|
|
"user": pcbnew.LT_SIGNAL, # LT_USER removed in KiCAD 9.0, use LT_SIGNAL instead
|
|
"signal": pcbnew.LT_SIGNAL,
|
|
}
|
|
return type_map.get(type_name.lower(), pcbnew.LT_SIGNAL)
|
|
|
|
def _get_layer_type_name(self, type_id: int) -> str:
|
|
"""Convert KiCAD layer type constant to name"""
|
|
type_map = {
|
|
pcbnew.LT_SIGNAL: "signal",
|
|
pcbnew.LT_POWER: "power",
|
|
pcbnew.LT_MIXED: "mixed",
|
|
pcbnew.LT_JUMPER: "jumper",
|
|
}
|
|
# Note: LT_USER was removed in KiCAD 9.0
|
|
return type_map.get(type_id, "unknown")
|