chore: enable strict mypy checks and fix pre-commit mypy hook

Add type annotations to all previously untyped functions and remove 9
suppressed error codes (call-arg, assignment, return-value, operator,
has-type, dict-item, misc, list-item, annotation-unchecked) by fixing
the underlying type issues.

Add [[tool.mypy.overrides]] with ignore_missing_imports for KiCAD-specific
modules (pcbnew, sexpdata, skip, cairosvg, kipy, PIL) so the pre-commit
mypy hook passes in its isolated venv. Add types-requests and pytest to
additional_dependencies in .pre-commit-config.yaml.

Also fixes several real bugs uncovered by stricter checks: incorrect static
calls to instance methods in swig_backend, wrong return type on get_size,
missing value param in BoardAPI.place_component, variable shadowing in
kicad_process.py, unqualified LibraryManager reference in kicad_interface,
and missing top-level Path import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Eugene Mikhantyev
2026-04-05 23:48:35 +01:00
parent 5a2b481db3
commit 9b1024a8f3
35 changed files with 4665 additions and 4610 deletions

View File

@@ -12,6 +12,7 @@ import logging
import os
import sys
import traceback
from pathlib import Path
from typing import Any, Dict, Optional
from resources.resource_definitions import RESOURCE_DEFINITIONS, handle_resource_read
@@ -241,7 +242,7 @@ except ImportError as e:
class KiCADInterface:
"""Main interface class to handle KiCAD operations"""
def __init__(self):
def __init__(self) -> None:
"""Initialize the interface and command handlers"""
self.board = None
self.project_filename = None
@@ -560,7 +561,7 @@ class KiCADInterface:
"connect_passthrough",
}
def _auto_save_board(self):
def _auto_save_board(self) -> None:
"""Save board to disk after SWIG mutations.
Called automatically after every board-mutating SWIG command so that
data is not lost if Claude hits the context limit before save_project.
@@ -574,7 +575,7 @@ class KiCADInterface:
except Exception as e:
logger.warning(f"Auto-save failed: {e}")
def _update_command_handlers(self):
def _update_command_handlers(self) -> None:
"""Update board reference in all command handlers"""
logger.debug("Updating board reference in command handlers")
self.project_commands.board = self.board
@@ -586,7 +587,7 @@ class KiCADInterface:
self.freerouting_commands.board = self.board
# Schematic command handlers
def _handle_create_schematic(self, params):
def _handle_create_schematic(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Create a new schematic"""
logger.info("Creating schematic")
try:
@@ -623,7 +624,7 @@ class KiCADInterface:
logger.error(f"Error creating schematic: {str(e)}")
return {"success": False, "message": str(e)}
def _handle_load_schematic(self, params):
def _handle_load_schematic(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Load an existing schematic"""
logger.info("Loading schematic")
try:
@@ -644,7 +645,7 @@ class KiCADInterface:
logger.error(f"Error loading schematic: {str(e)}")
return {"success": False, "message": str(e)}
def _handle_place_component(self, params):
def _handle_place_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Place a component on the PCB, with project-local fp-lib-table support.
If boardPath is given and differs from the currently loaded board, the
board is reloaded from boardPath before placing — prevents silent failures
@@ -679,7 +680,7 @@ class KiCADInterface:
return self.component_commands.place_component(params)
def _handle_add_schematic_component(self, params):
def _handle_add_schematic_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Add a component to a schematic using text-based injection (no sexpdata)"""
logger.info("Adding component to schematic")
try:
@@ -732,7 +733,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_delete_schematic_component(self, params):
def _handle_delete_schematic_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Remove a placed symbol from a schematic using text-based manipulation (no skip writes)"""
logger.info("Deleting schematic component")
try:
@@ -757,7 +758,7 @@ class KiCADInterface:
with open(sch_file, "r", encoding="utf-8") as f:
content = f.read()
def find_matching_paren(s, start):
def find_matching_paren(s: str, start: int) -> int:
"""Find the closing paren matching the opening paren at start."""
depth = 0
i = start
@@ -838,7 +839,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_edit_schematic_component(self, params):
def _handle_edit_schematic_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Update properties of a placed symbol in a schematic (footprint, value, reference).
Uses text-based in-place editing preserves position, UUID and all other fields.
"""
@@ -883,7 +884,7 @@ class KiCADInterface:
with open(sch_file, "r", encoding="utf-8") as f:
content = f.read()
def find_matching_paren(s, start):
def find_matching_paren(s: str, start: int) -> int:
"""Find the position of the closing paren matching the opening paren at start."""
depth = 0
i = start
@@ -928,7 +929,7 @@ class KiCADInterface:
break
search_start = end + 1
if block_start is None:
if block_start is None or block_end is None:
return {
"success": False,
"message": f"Component '{reference}' not found in schematic",
@@ -991,7 +992,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_get_schematic_component(self, params):
def _handle_get_schematic_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Return full component info: position and all field values with their (at x y angle) positions."""
logger.info("Getting schematic component info")
try:
@@ -1016,7 +1017,7 @@ class KiCADInterface:
with open(sch_file, "r", encoding="utf-8") as f:
content = f.read()
def find_matching_paren(s, start):
def find_matching_paren(s: str, start: int) -> int:
depth = 0
i = start
while i < len(s):
@@ -1058,7 +1059,7 @@ class KiCADInterface:
break
search_start = end + 1
if block_start is None:
if block_start is None or block_end is None:
return {
"success": False,
"message": f"Component '{reference}' not found in schematic",
@@ -1114,7 +1115,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_add_schematic_wire(self, params):
def _handle_add_schematic_wire(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Add a wire to a schematic using WireManager, with optional pin snapping"""
logger.info("Adding wire to schematic")
try:
@@ -1164,7 +1165,7 @@ class KiCADInterface:
for pin_num, coords in pin_locs.items():
all_pins.append((ref, pin_num, coords))
def find_nearest_pin(point, tolerance):
def find_nearest_pin(point: Any, tolerance: Any) -> Any:
"""Find the nearest pin within tolerance of a point."""
best = None
best_dist = tolerance
@@ -1238,7 +1239,7 @@ class KiCADInterface:
"errorDetails": traceback.format_exc(),
}
def _handle_add_schematic_junction(self, params):
def _handle_add_schematic_junction(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Add a junction (connection dot) to a schematic using WireManager"""
logger.info("Adding junction to schematic")
try:
@@ -1271,19 +1272,19 @@ class KiCADInterface:
"errorDetails": traceback.format_exc(),
}
def _handle_list_schematic_libraries(self, params):
def _handle_list_schematic_libraries(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""List available symbol libraries"""
logger.info("Listing schematic libraries")
try:
search_paths = params.get("searchPaths")
libraries = LibraryManager.list_available_libraries(search_paths)
libraries = SchematicLibraryManager.list_available_libraries(search_paths)
return {"success": True, "libraries": libraries}
except Exception as e:
logger.error(f"Error listing schematic libraries: {str(e)}")
return {"success": False, "message": str(e)}
def _handle_find_unconnected_pins(self, params):
def _handle_find_unconnected_pins(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""List component pins with no wire/label/power symbol touching them"""
logger.info("Finding unconnected pins")
try:
@@ -1303,7 +1304,7 @@ class KiCADInterface:
logger.error(f"Error finding unconnected pins: {e}")
return {"success": False, "message": str(e)}
def _handle_check_wire_collisions(self, params):
def _handle_check_wire_collisions(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Detect wires passing through component bodies without connecting to pins"""
logger.info("Checking wire collisions")
try:
@@ -1327,7 +1328,7 @@ class KiCADInterface:
# Footprint handlers #
# ------------------------------------------------------------------ #
def _handle_create_footprint(self, params):
def _handle_create_footprint(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Create a new .kicad_mod footprint file in a .pretty library."""
logger.info(f"create_footprint: {params.get('name')} in {params.get('libraryPath')}")
try:
@@ -1349,7 +1350,7 @@ class KiCADInterface:
logger.error(f"create_footprint error: {e}")
return {"success": False, "error": str(e)}
def _handle_edit_footprint_pad(self, params):
def _handle_edit_footprint_pad(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Edit an existing pad in a .kicad_mod file."""
logger.info(
f"edit_footprint_pad: pad {params.get('padNumber')} in {params.get('footprintPath')}"
@@ -1368,7 +1369,7 @@ class KiCADInterface:
logger.error(f"edit_footprint_pad error: {e}")
return {"success": False, "error": str(e)}
def _handle_list_footprint_libraries(self, params):
def _handle_list_footprint_libraries(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""List .pretty footprint libraries and their contents."""
logger.info("list_footprint_libraries")
try:
@@ -1378,7 +1379,7 @@ class KiCADInterface:
logger.error(f"list_footprint_libraries error: {e}")
return {"success": False, "error": str(e)}
def _handle_register_footprint_library(self, params):
def _handle_register_footprint_library(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Register a .pretty library in KiCAD's fp-lib-table."""
logger.info(f"register_footprint_library: {params.get('libraryPath')}")
try:
@@ -1398,7 +1399,7 @@ class KiCADInterface:
# Symbol creator handlers #
# ------------------------------------------------------------------ #
def _handle_create_symbol(self, params):
def _handle_create_symbol(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Create a new symbol in a .kicad_sym library."""
logger.info(f"create_symbol: {params.get('name')} in {params.get('libraryPath')}")
try:
@@ -1422,7 +1423,7 @@ class KiCADInterface:
logger.error(f"create_symbol error: {e}")
return {"success": False, "error": str(e)}
def _handle_delete_symbol(self, params):
def _handle_delete_symbol(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Delete a symbol from a .kicad_sym library."""
logger.info(f"delete_symbol: {params.get('name')} from {params.get('libraryPath')}")
try:
@@ -1435,7 +1436,7 @@ class KiCADInterface:
logger.error(f"delete_symbol error: {e}")
return {"success": False, "error": str(e)}
def _handle_list_symbols_in_library(self, params):
def _handle_list_symbols_in_library(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""List all symbols in a .kicad_sym file."""
logger.info(f"list_symbols_in_library: {params.get('libraryPath')}")
try:
@@ -1447,7 +1448,7 @@ class KiCADInterface:
logger.error(f"list_symbols_in_library error: {e}")
return {"success": False, "error": str(e)}
def _handle_register_symbol_library(self, params):
def _handle_register_symbol_library(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Register a .kicad_sym library in KiCAD's sym-lib-table."""
logger.info(f"register_symbol_library: {params.get('libraryPath')}")
try:
@@ -1463,7 +1464,7 @@ class KiCADInterface:
logger.error(f"register_symbol_library error: {e}")
return {"success": False, "error": str(e)}
def _handle_export_schematic_pdf(self, params):
def _handle_export_schematic_pdf(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Export schematic to PDF"""
logger.info("Exporting schematic to PDF")
try:
@@ -1512,7 +1513,7 @@ class KiCADInterface:
logger.error(f"Error exporting schematic to PDF: {str(e)}")
return {"success": False, "message": str(e)}
def _handle_add_schematic_net_label(self, params):
def _handle_add_schematic_net_label(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Add a net label to schematic using WireManager"""
logger.info("Adding net label to schematic")
try:
@@ -1558,7 +1559,7 @@ class KiCADInterface:
"errorDetails": traceback.format_exc(),
}
def _handle_connect_to_net(self, params):
def _handle_connect_to_net(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Connect a component pin to a named net using wire stub and label"""
logger.info("Connecting component pin to net")
try:
@@ -1595,7 +1596,7 @@ class KiCADInterface:
"errorDetails": traceback.format_exc(),
}
def _handle_connect_passthrough(self, params):
def _handle_connect_passthrough(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Connect all pins of source connector to matching pins of target connector"""
logger.info("Connecting passthrough between two connectors")
try:
@@ -1632,7 +1633,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_get_schematic_pin_locations(self, params):
def _handle_get_schematic_pin_locations(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Return exact pin endpoint coordinates for a schematic component"""
logger.info("Getting schematic pin locations")
try:
@@ -1687,7 +1688,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_get_schematic_view(self, params):
def _handle_get_schematic_view(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Get a rasterised image of the schematic (SVG export → optional PNG conversion)"""
logger.info("Getting schematic view")
import base64
@@ -1776,7 +1777,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_list_schematic_components(self, params):
def _handle_list_schematic_components(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""List all components in a schematic"""
logger.info("Listing schematic components")
try:
@@ -1869,7 +1870,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_list_schematic_nets(self, params):
def _handle_list_schematic_nets(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""List all nets in a schematic with their connections"""
logger.info("Listing schematic nets")
try:
@@ -1915,7 +1916,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_list_schematic_wires(self, params):
def _handle_list_schematic_wires(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""List all wires in a schematic"""
logger.info("Listing schematic wires")
try:
@@ -1958,7 +1959,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_list_schematic_labels(self, params):
def _handle_list_schematic_labels(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""List all net labels and power flags in a schematic"""
logger.info("Listing schematic labels")
try:
@@ -2037,7 +2038,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_move_schematic_component(self, params):
def _handle_move_schematic_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Move a schematic component to a new position, dragging connected wires."""
logger.info("Moving schematic component")
try:
@@ -2121,7 +2122,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_rotate_schematic_component(self, params):
def _handle_rotate_schematic_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Rotate a schematic component"""
logger.info("Rotating schematic component")
try:
@@ -2171,7 +2172,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_annotate_schematic(self, params):
def _handle_annotate_schematic(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Annotate unannotated components in schematic (R? -> R1, R2, ...)"""
logger.info("Annotating schematic")
try:
@@ -2249,7 +2250,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_delete_schematic_wire(self, params):
def _handle_delete_schematic_wire(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Delete a wire from the schematic matching start/end points"""
logger.info("Deleting schematic wire")
try:
@@ -2280,7 +2281,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_delete_schematic_net_label(self, params):
def _handle_delete_schematic_net_label(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Delete a net label from the schematic"""
logger.info("Deleting schematic net label")
try:
@@ -2315,7 +2316,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_export_schematic_svg(self, params):
def _handle_export_schematic_svg(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Export schematic to SVG using kicad-cli"""
logger.info("Exporting schematic SVG")
import glob
@@ -2389,7 +2390,7 @@ class KiCADInterface:
logger.error(f"Error exporting schematic SVG: {e}")
return {"success": False, "message": str(e)}
def _handle_get_net_connections(self, params):
def _handle_get_net_connections(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Get all connections for a named net"""
logger.info("Getting net connections")
try:
@@ -2409,7 +2410,7 @@ class KiCADInterface:
logger.error(f"Error getting net connections: {str(e)}")
return {"success": False, "message": str(e)}
def _handle_get_wire_connections(self, params):
def _handle_get_wire_connections(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Find all component pins reachable from a point via connected wires"""
logger.info("Getting wire connections")
try:
@@ -2456,7 +2457,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_run_erc(self, params):
def _handle_run_erc(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Run Electrical Rules Check on a schematic via kicad-cli"""
logger.info("Running ERC on schematic")
import os
@@ -2552,7 +2553,7 @@ class KiCADInterface:
logger.error(f"Error running ERC: {str(e)}")
return {"success": False, "message": str(e)}
def _handle_generate_netlist(self, params):
def _handle_generate_netlist(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Generate netlist from schematic"""
logger.info("Generating netlist from schematic")
try:
@@ -2571,7 +2572,7 @@ class KiCADInterface:
logger.error(f"Error generating netlist: {str(e)}")
return {"success": False, "message": str(e)}
def _handle_sync_schematic_to_board(self, params):
def _handle_sync_schematic_to_board(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Sync schematic netlist to PCB board (equivalent to KiCAD F8 'Update PCB from Schematic').
Reads net connections from the schematic and assigns them to the matching pads in the PCB.
"""
@@ -2694,7 +2695,7 @@ class KiCADInterface:
# Schematic analysis tools (read-only)
# ===================================================================
def _handle_get_schematic_view_region(self, params):
def _handle_get_schematic_view_region(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Export a cropped region of the schematic as an image"""
logger.info("Exporting schematic view region")
import base64
@@ -2809,7 +2810,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_find_overlapping_elements(self, params):
def _handle_find_overlapping_elements(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Detect spatially overlapping symbols, wires, and labels"""
logger.info("Finding overlapping elements in schematic")
try:
@@ -2835,7 +2836,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_get_elements_in_region(self, params):
def _handle_get_elements_in_region(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""List all wires, labels, and symbols within a rectangular region"""
logger.info("Getting elements in schematic region")
try:
@@ -2865,7 +2866,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_find_wires_crossing_symbols(self, params):
def _handle_find_wires_crossing_symbols(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Find wires that cross over component symbol bodies"""
logger.info("Finding wires crossing symbols in schematic")
try:
@@ -2891,7 +2892,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_import_svg_logo(self, params):
def _handle_import_svg_logo(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Import an SVG file as PCB graphic polygons on the silkscreen"""
logger.info("Importing SVG logo into PCB")
try:
@@ -2938,7 +2939,7 @@ class KiCADInterface:
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_snapshot_project(self, params):
def _handle_snapshot_project(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Copy the entire project folder to a snapshot directory for checkpoint/resume."""
import shutil
from datetime import datetime
@@ -3022,7 +3023,7 @@ class KiCADInterface:
logger.error(f"snapshot_project error: {e}")
return {"success": False, "message": str(e)}
def _handle_check_kicad_ui(self, params):
def _handle_check_kicad_ui(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Check if KiCAD UI is running"""
logger.info("Checking if KiCAD UI is running")
try:
@@ -3040,7 +3041,7 @@ class KiCADInterface:
logger.error(f"Error checking KiCAD UI status: {str(e)}")
return {"success": False, "message": str(e)}
def _handle_launch_kicad_ui(self, params):
def _handle_launch_kicad_ui(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Launch KiCAD UI"""
logger.info("Launching KiCAD UI")
try:
@@ -3059,7 +3060,7 @@ class KiCADInterface:
logger.error(f"Error launching KiCAD UI: {str(e)}")
return {"success": False, "message": str(e)}
def _handle_refill_zones(self, params):
def _handle_refill_zones(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Refill all copper pour zones on the board.
pcbnew.ZONE_FILLER.Fill() can cause a C++ access violation (0xC0000005)
@@ -3146,7 +3147,7 @@ print("ok")
# These methods are called automatically when IPC is available
# =========================================================================
def _ipc_route_trace(self, params):
def _ipc_route_trace(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for route_trace - adds track with real-time UI update"""
try:
# Extract parameters matching the existing route_trace interface
@@ -3189,7 +3190,7 @@ print("ok")
logger.error(f"IPC route_trace error: {e}")
return {"success": False, "message": str(e)}
def _ipc_add_via(self, params):
def _ipc_add_via(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for add_via - adds via with real-time UI update"""
try:
position = params.get("position", {})
@@ -3222,7 +3223,7 @@ print("ok")
logger.error(f"IPC add_via error: {e}")
return {"success": False, "message": str(e)}
def _ipc_add_net(self, params):
def _ipc_add_net(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for add_net"""
# Note: Net creation via IPC is limited - nets are typically created
# when components are placed. Return success for compatibility.
@@ -3234,7 +3235,7 @@ print("ok")
"net": {"name": name},
}
def _ipc_add_copper_pour(self, params):
def _ipc_add_copper_pour(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for add_copper_pour - adds zone with real-time UI update"""
try:
layer = params.get("layer", "F.Cu")
@@ -3289,7 +3290,7 @@ print("ok")
logger.error(f"IPC add_copper_pour error: {e}")
return {"success": False, "message": str(e)}
def _ipc_refill_zones(self, params):
def _ipc_refill_zones(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for refill_zones - refills all zones with real-time UI update"""
try:
success = self.ipc_board_api.refill_zones()
@@ -3304,7 +3305,7 @@ print("ok")
logger.error(f"IPC refill_zones error: {e}")
return {"success": False, "message": str(e)}
def _ipc_add_text(self, params):
def _ipc_add_text(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for add_text/add_board_text - adds text with real-time UI update"""
try:
text = params.get("text", "")
@@ -3331,7 +3332,7 @@ print("ok")
logger.error(f"IPC add_text error: {e}")
return {"success": False, "message": str(e)}
def _ipc_set_board_size(self, params):
def _ipc_set_board_size(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for set_board_size"""
try:
width = params.get("width", 100)
@@ -3353,7 +3354,7 @@ print("ok")
logger.error(f"IPC set_board_size error: {e}")
return {"success": False, "message": str(e)}
def _ipc_get_board_info(self, params):
def _ipc_get_board_info(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for get_board_info"""
try:
size = self.ipc_board_api.get_size()
@@ -3378,7 +3379,7 @@ print("ok")
logger.error(f"IPC get_board_info error: {e}")
return {"success": False, "message": str(e)}
def _ipc_place_component(self, params):
def _ipc_place_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for place_component - places component with real-time UI update"""
try:
reference = params.get("reference", params.get("componentId", ""))
@@ -3419,7 +3420,7 @@ print("ok")
logger.error(f"IPC place_component error: {e}")
return {"success": False, "message": str(e)}
def _ipc_move_component(self, params):
def _ipc_move_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for move_component - moves component with real-time UI update"""
try:
reference = params.get("reference", params.get("componentId", ""))
@@ -3444,7 +3445,7 @@ print("ok")
logger.error(f"IPC move_component error: {e}")
return {"success": False, "message": str(e)}
def _ipc_delete_component(self, params):
def _ipc_delete_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for delete_component - deletes component with real-time UI update"""
try:
reference = params.get("reference", params.get("componentId", ""))
@@ -3463,7 +3464,7 @@ print("ok")
logger.error(f"IPC delete_component error: {e}")
return {"success": False, "message": str(e)}
def _ipc_get_component_list(self, params):
def _ipc_get_component_list(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for get_component_list"""
try:
components = self.ipc_board_api.list_components()
@@ -3473,7 +3474,7 @@ print("ok")
logger.error(f"IPC get_component_list error: {e}")
return {"success": False, "message": str(e)}
def _ipc_save_project(self, params):
def _ipc_save_project(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for save_project"""
try:
success = self.ipc_board_api.save()
@@ -3486,14 +3487,14 @@ print("ok")
logger.error(f"IPC save_project error: {e}")
return {"success": False, "message": str(e)}
def _ipc_delete_trace(self, params):
def _ipc_delete_trace(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for delete_trace - Note: IPC doesn't support direct trace deletion yet"""
# IPC API doesn't have a direct delete track method
# Fall back to SWIG for this operation
logger.info("delete_trace: Falling back to SWIG (IPC doesn't support trace deletion)")
return self.routing_commands.delete_trace(params)
def _ipc_get_nets_list(self, params):
def _ipc_get_nets_list(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for get_nets_list - gets nets with real-time data"""
try:
nets = self.ipc_board_api.get_nets()
@@ -3503,7 +3504,7 @@ print("ok")
logger.error(f"IPC get_nets_list error: {e}")
return {"success": False, "message": str(e)}
def _ipc_add_board_outline(self, params):
def _ipc_add_board_outline(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for add_board_outline - adds board edge with real-time UI update.
Rounded rectangles are delegated to the SWIG path because the IPC BoardSegment
type cannot represent arcs; the SWIG path writes directly to the .kicad_pcb file
@@ -3566,7 +3567,7 @@ print("ok")
logger.error(f"IPC add_board_outline error: {e}")
return {"success": False, "message": str(e)}
def _ipc_add_mounting_hole(self, params):
def _ipc_add_mounting_hole(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for add_mounting_hole - adds mounting hole with real-time UI update"""
try:
from kipy.board_types import BoardCircle
@@ -3601,7 +3602,7 @@ print("ok")
logger.error(f"IPC add_mounting_hole error: {e}")
return {"success": False, "message": str(e)}
def _ipc_get_layer_list(self, params):
def _ipc_get_layer_list(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for get_layer_list - gets enabled layers"""
try:
layers = self.ipc_board_api.get_enabled_layers()
@@ -3611,7 +3612,7 @@ print("ok")
logger.error(f"IPC get_layer_list error: {e}")
return {"success": False, "message": str(e)}
def _ipc_rotate_component(self, params):
def _ipc_rotate_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for rotate_component - rotates component with real-time UI update"""
try:
reference = params.get("reference", params.get("componentId", ""))
@@ -3653,7 +3654,7 @@ print("ok")
logger.error(f"IPC rotate_component error: {e}")
return {"success": False, "message": str(e)}
def _ipc_get_component_properties(self, params):
def _ipc_get_component_properties(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""IPC handler for get_component_properties - gets detailed component info"""
try:
reference = params.get("reference", params.get("componentId", ""))
@@ -3677,7 +3678,7 @@ print("ok")
# Legacy IPC command handlers (explicit ipc_* commands)
# =========================================================================
def _handle_get_backend_info(self, params):
def _handle_get_backend_info(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Get information about the current backend"""
return {
"success": True,
@@ -3692,7 +3693,7 @@ print("ok")
),
}
def _handle_ipc_add_track(self, params):
def _handle_ipc_add_track(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Add a track using IPC backend (real-time)"""
if not self.use_ipc or not self.ipc_board_api:
return {"success": False, "message": "IPC backend not available"}
@@ -3718,7 +3719,7 @@ print("ok")
logger.error(f"Error adding track via IPC: {e}")
return {"success": False, "message": str(e)}
def _handle_ipc_add_via(self, params):
def _handle_ipc_add_via(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Add a via using IPC backend (real-time)"""
if not self.use_ipc or not self.ipc_board_api:
return {"success": False, "message": "IPC backend not available"}
@@ -3741,7 +3742,7 @@ print("ok")
logger.error(f"Error adding via via IPC: {e}")
return {"success": False, "message": str(e)}
def _handle_ipc_add_text(self, params):
def _handle_ipc_add_text(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Add text using IPC backend (real-time)"""
if not self.use_ipc or not self.ipc_board_api:
return {"success": False, "message": "IPC backend not available"}
@@ -3766,7 +3767,7 @@ print("ok")
logger.error(f"Error adding text via IPC: {e}")
return {"success": False, "message": str(e)}
def _handle_ipc_list_components(self, params):
def _handle_ipc_list_components(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""List components using IPC backend"""
if not self.use_ipc or not self.ipc_board_api:
return {"success": False, "message": "IPC backend not available"}
@@ -3778,7 +3779,7 @@ print("ok")
logger.error(f"Error listing components via IPC: {e}")
return {"success": False, "message": str(e)}
def _handle_ipc_get_tracks(self, params):
def _handle_ipc_get_tracks(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Get tracks using IPC backend"""
if not self.use_ipc or not self.ipc_board_api:
return {"success": False, "message": "IPC backend not available"}
@@ -3790,7 +3791,7 @@ print("ok")
logger.error(f"Error getting tracks via IPC: {e}")
return {"success": False, "message": str(e)}
def _handle_ipc_get_vias(self, params):
def _handle_ipc_get_vias(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Get vias using IPC backend"""
if not self.use_ipc or not self.ipc_board_api:
return {"success": False, "message": "IPC backend not available"}
@@ -3802,7 +3803,7 @@ print("ok")
logger.error(f"Error getting vias via IPC: {e}")
return {"success": False, "message": str(e)}
def _handle_ipc_save_board(self, params):
def _handle_ipc_save_board(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Save board using IPC backend"""
if not self.use_ipc or not self.ipc_board_api:
return {"success": False, "message": "IPC backend not available"}
@@ -3819,7 +3820,7 @@ print("ok")
# JLCPCB API handlers
def _handle_download_jlcpcb_database(self, params):
def _handle_download_jlcpcb_database(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Download JLCPCB parts database from JLCSearch API"""
try:
force = params.get("force", False)
@@ -3870,7 +3871,7 @@ print("ok")
"message": f"Failed to download database: {str(e)}",
}
def _handle_search_jlcpcb_parts(self, params):
def _handle_search_jlcpcb_parts(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Search JLCPCB parts database"""
try:
query = params.get("query")
@@ -3909,7 +3910,7 @@ print("ok")
logger.error(f"Error searching JLCPCB parts: {e}", exc_info=True)
return {"success": False, "message": f"Search failed: {str(e)}"}
def _handle_get_jlcpcb_part(self, params):
def _handle_get_jlcpcb_part(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Get detailed information for a specific JLCPCB part"""
try:
lcsc_number = params.get("lcsc_number")
@@ -3929,7 +3930,7 @@ print("ok")
logger.error(f"Error getting JLCPCB part: {e}", exc_info=True)
return {"success": False, "message": f"Failed to get part info: {str(e)}"}
def _handle_get_jlcpcb_database_stats(self, params):
def _handle_get_jlcpcb_database_stats(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Get statistics about JLCPCB database"""
try:
stats = self.jlcpcb_parts.get_database_stats()
@@ -3939,7 +3940,7 @@ print("ok")
logger.error(f"Error getting database stats: {e}", exc_info=True)
return {"success": False, "message": f"Failed to get stats: {str(e)}"}
def _handle_suggest_jlcpcb_alternatives(self, params):
def _handle_suggest_jlcpcb_alternatives(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Suggest alternative JLCPCB parts"""
try:
lcsc_number = params.get("lcsc_number")
@@ -3980,7 +3981,7 @@ print("ok")
"message": f"Failed to suggest alternatives: {str(e)}",
}
def _handle_enrich_datasheets(self, params):
def _handle_enrich_datasheets(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Enrich schematic Datasheet fields from LCSC numbers"""
try:
from pathlib import Path
@@ -3998,7 +3999,7 @@ print("ok")
"message": f"Failed to enrich datasheets: {str(e)}",
}
def _handle_get_datasheet_url(self, params):
def _handle_get_datasheet_url(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Return LCSC datasheet and product URLs for a part number"""
try:
lcsc = params.get("lcsc", "")
@@ -4024,7 +4025,7 @@ print("ok")
}
def _write_response(response_fd, response):
def _write_response(response_fd: Any, response: Any) -> None:
"""Write a JSON response to the original stdout fd.
All response output goes through this function so that stray C-level
@@ -4035,7 +4036,7 @@ def _write_response(response_fd, response):
os.write(response_fd, payload.encode("utf-8"))
def main():
def main() -> None:
"""Main entry point"""
# --- Redirect stdout so pcbnew C++ noise never reaches the TS host ---
# Save the real stdout fd for our exclusive JSON response channel.