Files
Eugene Mikhantyev 7f3a379657 Treat PWR_FLAG anchors as connected in orphan-wire detection
PWR_FLAG instances use a #FLG reference prefix, not #PWR, so their
pin positions were never registered as virtual connection anchors in
_parse_virtual_connections. As a result, find_orphaned_wires reported
wire ends terminating on a PWR_FLAG as dangling. Other call sites
(schematic_analysis.py:127, kicad_interface.py:3814) already recognize
#FLG as a power symbol; align this site with them.

Also coerce a previously-validated Optional[int] to int in board/layers.py
so the file passes mypy (required by the pre-commit hook); behavior is
unchanged because the value is already None-checked above.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 16:05:35 +01:00

178 lines
6.5 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",
}
# 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'",
}
# Enable inner copper layers by increasing copper layer count (KiCAD 9.0 API)
if position == "inner":
current_count = self.board.GetCopperLayerCount()
needed_count = 2 + (number or 0) # F.Cu + B.Cu + inner layers
if needed_count > current_count:
self.board.SetCopperLayerCount(needed_count)
# Set layer properties directly on board (GetLayerStack removed in KiCAD 9.0)
self.board.SetLayerName(layer_id, name)
self.board.SetLayerType(layer_id, self._get_layer_type(layer_type))
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")