fix: merge upstream type annotations into create_schematic signature
Combined our path parameter fix with upstream's type annotation additions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import logging
|
||||
import os
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from skip import Schematic
|
||||
|
||||
@@ -25,7 +25,7 @@ class ComponentManager:
|
||||
_dynamic_loader = None
|
||||
|
||||
@classmethod
|
||||
def get_dynamic_loader(cls):
|
||||
def get_dynamic_loader(cls) -> Any:
|
||||
"""Get or create dynamic symbol loader instance"""
|
||||
if cls._dynamic_loader is None and DYNAMIC_LOADING_AVAILABLE:
|
||||
cls._dynamic_loader = DynamicSymbolLoader()
|
||||
@@ -86,7 +86,7 @@ class ComponentManager:
|
||||
"""
|
||||
|
||||
# Helper function to check if template exists in schematic
|
||||
def template_exists(schematic, template_ref):
|
||||
def template_exists(schematic: Any, template_ref: str) -> bool:
|
||||
"""Check if template exists by iterating symbols (handles special characters)"""
|
||||
for symbol in schematic.symbol:
|
||||
if (
|
||||
@@ -165,7 +165,7 @@ class ComponentManager:
|
||||
@staticmethod
|
||||
def add_component(
|
||||
schematic: Schematic, component_def: dict, schematic_path: Optional[Path] = None
|
||||
):
|
||||
) -> Any:
|
||||
"""
|
||||
Add a component to the schematic by cloning from template
|
||||
|
||||
@@ -265,7 +265,7 @@ class ComponentManager:
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def remove_component(schematic: Schematic, component_ref: str):
|
||||
def remove_component(schematic: Schematic, component_ref: str) -> bool:
|
||||
"""Remove a component from the schematic by reference designator"""
|
||||
try:
|
||||
# kicad-skip doesn't have a direct remove_symbol method by reference.
|
||||
@@ -278,17 +278,17 @@ class ComponentManager:
|
||||
|
||||
if symbol_to_remove:
|
||||
schematic.symbol._elements.remove(symbol_to_remove)
|
||||
print(f"Removed component {component_ref} from schematic.")
|
||||
logger.info(f"Removed component {component_ref} from schematic.")
|
||||
return True
|
||||
else:
|
||||
print(f"Component with reference {component_ref} not found.")
|
||||
logger.warning(f"Component with reference {component_ref} not found.")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Error removing component {component_ref}: {e}")
|
||||
logger.error(f"Error removing component {component_ref}: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def update_component(schematic: Schematic, component_ref: str, new_properties: dict):
|
||||
def update_component(schematic: Schematic, component_ref: str, new_properties: dict) -> bool:
|
||||
"""Update component properties by reference designator"""
|
||||
try:
|
||||
symbol_to_update = None
|
||||
@@ -302,29 +302,28 @@ class ComponentManager:
|
||||
if key in symbol_to_update.property:
|
||||
symbol_to_update.property[key].value = value
|
||||
else:
|
||||
# Add as a new property if it doesn't exist
|
||||
symbol_to_update.property.append(key, value)
|
||||
print(f"Updated properties for component {component_ref}.")
|
||||
logger.info(f"Updated properties for component {component_ref}.")
|
||||
return True
|
||||
else:
|
||||
print(f"Component with reference {component_ref} not found.")
|
||||
logger.warning(f"Component with reference {component_ref} not found.")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Error updating component {component_ref}: {e}")
|
||||
logger.error(f"Error updating component {component_ref}: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_component(schematic: Schematic, component_ref: str):
|
||||
def get_component(schematic: Schematic, component_ref: str) -> Any:
|
||||
"""Get a component by reference designator"""
|
||||
for symbol in schematic.symbol:
|
||||
if symbol.reference == component_ref:
|
||||
print(f"Found component with reference {component_ref}.")
|
||||
logger.debug(f"Found component with reference {component_ref}.")
|
||||
return symbol
|
||||
print(f"Component with reference {component_ref} not found.")
|
||||
logger.warning(f"Component with reference {component_ref} not found.")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def search_components(schematic: Schematic, query: str):
|
||||
def search_components(schematic: Schematic, query: str) -> List[Any]:
|
||||
"""Search for components matching criteria (basic implementation)"""
|
||||
# This is a basic search, could be expanded to use regex or more complex logic
|
||||
matching_components = []
|
||||
@@ -339,13 +338,13 @@ class ComponentManager:
|
||||
)
|
||||
):
|
||||
matching_components.append(symbol)
|
||||
print(f"Found {len(matching_components)} components matching query '{query}'.")
|
||||
logger.debug(f"Found {len(matching_components)} components matching query '{query}'.")
|
||||
return matching_components
|
||||
|
||||
@staticmethod
|
||||
def get_all_components(schematic: Schematic):
|
||||
def get_all_components(schematic: Schematic) -> List[Any]:
|
||||
"""Get all components in schematic"""
|
||||
print(f"Retrieving all {len(schematic.symbol)} components.")
|
||||
logger.debug(f"Retrieving all {len(schematic.symbol)} components.")
|
||||
return list(schematic.symbol)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from skip import Schematic
|
||||
|
||||
@@ -25,14 +25,14 @@ class ConnectionManager:
|
||||
_pin_locator = None
|
||||
|
||||
@classmethod
|
||||
def get_pin_locator(cls):
|
||||
def get_pin_locator(cls) -> Any:
|
||||
"""Get or create pin locator instance"""
|
||||
if cls._pin_locator is None and WIRE_MANAGER_AVAILABLE:
|
||||
cls._pin_locator = PinLocator()
|
||||
return cls._pin_locator
|
||||
|
||||
@staticmethod
|
||||
def add_net_label(schematic: Schematic, net_name: str, position: list):
|
||||
def add_net_label(schematic: Schematic, net_name: str, position: list) -> Any:
|
||||
"""
|
||||
Add a net label to the schematic
|
||||
|
||||
@@ -57,7 +57,9 @@ class ConnectionManager:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def connect_to_net(schematic_path: Path, component_ref: str, pin_name: str, net_name: str):
|
||||
def connect_to_net(
|
||||
schematic_path: Path, component_ref: str, pin_name: str, net_name: str
|
||||
) -> bool:
|
||||
"""
|
||||
Connect a component pin to a named net using a wire stub and label
|
||||
|
||||
@@ -132,7 +134,7 @@ class ConnectionManager:
|
||||
target_ref: str,
|
||||
net_prefix: str = "PIN",
|
||||
pin_offset: int = 0,
|
||||
):
|
||||
) -> Dict[str, List[str]]:
|
||||
"""
|
||||
Connect all pins of source_ref to matching pins of target_ref via shared net labels.
|
||||
Useful for passthrough adapters: J1 pin N <-> J2 pin N on net {net_prefix}_{N}.
|
||||
@@ -203,7 +205,7 @@ class ConnectionManager:
|
||||
@staticmethod
|
||||
def get_net_connections(
|
||||
schematic: Schematic, net_name: str, schematic_path: Optional[Path] = None
|
||||
):
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Get all connections for a named net using wire graph analysis
|
||||
|
||||
@@ -221,7 +223,7 @@ class ConnectionManager:
|
||||
connections = []
|
||||
tolerance = 0.5 # 0.5mm tolerance for point coincidence (grid spacing consideration)
|
||||
|
||||
def points_coincide(p1, p2):
|
||||
def points_coincide(p1: Any, p2: Any) -> bool:
|
||||
"""Check if two points are the same (within tolerance)"""
|
||||
if not p1 or not p2:
|
||||
return False
|
||||
@@ -324,8 +326,8 @@ class ConnectionManager:
|
||||
continue
|
||||
|
||||
# Check if pin coincides with any wire point
|
||||
for wire_pt in connected_wire_points:
|
||||
if points_coincide(pin_loc, list(wire_pt)):
|
||||
for wire_pt_tup in connected_wire_points:
|
||||
if points_coincide(pin_loc, list(wire_pt_tup)):
|
||||
connections.append({"component": ref, "pin": pin_num})
|
||||
break # Pin found, no need to check more wire points
|
||||
|
||||
@@ -344,8 +346,10 @@ class ConnectionManager:
|
||||
symbol_y = float(symbol_pos[1])
|
||||
|
||||
# Check if symbol is near any wire point (within 10mm)
|
||||
for wire_pt in connected_wire_points:
|
||||
dist = ((symbol_x - wire_pt[0]) ** 2 + (symbol_y - wire_pt[1]) ** 2) ** 0.5
|
||||
for wire_pt_tup in connected_wire_points:
|
||||
dist = (
|
||||
(symbol_x - wire_pt_tup[0]) ** 2 + (symbol_y - wire_pt_tup[1]) ** 2
|
||||
) ** 0.5
|
||||
if dist < 10.0: # 10mm proximity threshold
|
||||
connections.append({"component": ref, "pin": "unknown"})
|
||||
break # Only add once per component
|
||||
@@ -361,7 +365,9 @@ class ConnectionManager:
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def generate_netlist(schematic: Schematic, schematic_path: Optional[Path] = None):
|
||||
def generate_netlist(
|
||||
schematic: Schematic, schematic_path: Optional[Path] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate a netlist from the schematic
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ No API key required.
|
||||
import logging
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
logger = logging.getLogger("kicad_interface")
|
||||
|
||||
@@ -49,7 +49,7 @@ class DatasheetManager:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _find_lib_symbols_range(lines: List[str]):
|
||||
def _find_lib_symbols_range(lines: List[str]) -> Tuple[Optional[int], Optional[int]]:
|
||||
"""
|
||||
Find the line range of the (lib_symbols ...) section.
|
||||
Returns (start, end) line indices or (None, None) if not found.
|
||||
|
||||
@@ -238,7 +238,10 @@ class FootprintCreator:
|
||||
changes.append(f"drill (inserted)→{new_drill}")
|
||||
if shape:
|
||||
block, n = re.subn(
|
||||
r'(pad\s+"[^"]*"\s+\w+\s+)\w+', lambda m: m.group(1) + shape, block, count=1
|
||||
r'(pad\s+"[^"]*"\s+\w+\s+)\w+',
|
||||
lambda m: str(m.group(1)) + shape,
|
||||
block,
|
||||
count=1,
|
||||
)
|
||||
if n:
|
||||
changes.append(f"shape→{shape}")
|
||||
|
||||
@@ -95,6 +95,8 @@ def _build_freerouting_cmd(
|
||||
"""Build the command to run Freerouting."""
|
||||
if use_docker:
|
||||
docker_exe = _find_docker()
|
||||
if docker_exe is None:
|
||||
raise RuntimeError("Docker/Podman executable not found")
|
||||
board_dir = os.path.dirname(dsn_path)
|
||||
dsn_name = os.path.basename(dsn_path)
|
||||
ses_name = os.path.basename(ses_path)
|
||||
@@ -120,6 +122,8 @@ def _build_freerouting_cmd(
|
||||
]
|
||||
else:
|
||||
java_exe = _find_java()
|
||||
if java_exe is None:
|
||||
raise RuntimeError("Java executable not found")
|
||||
return [
|
||||
java_exe,
|
||||
"-jar",
|
||||
@@ -136,7 +140,7 @@ def _build_freerouting_cmd(
|
||||
class FreeroutingCommands:
|
||||
"""Handles Freerouting autoroute operations."""
|
||||
|
||||
def __init__(self, board=None):
|
||||
def __init__(self, board: Any = None) -> None:
|
||||
self.board = board
|
||||
|
||||
def _resolve_execution_mode(self, jar_path: str) -> Dict[str, Any]:
|
||||
|
||||
@@ -11,7 +11,7 @@ import os
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
logger = logging.getLogger("kicad_interface")
|
||||
|
||||
@@ -38,10 +38,10 @@ class JLCPCBPartsManager:
|
||||
db_path = str(data_dir / "jlcpcb_parts.db")
|
||||
|
||||
self.db_path = db_path
|
||||
self.conn = None
|
||||
self.conn: Optional[sqlite3.Connection] = None
|
||||
self._init_database()
|
||||
|
||||
def _init_database(self):
|
||||
def _init_database(self) -> None:
|
||||
"""Initialize SQLite database with schema"""
|
||||
self.conn = sqlite3.connect(self.db_path)
|
||||
self.conn.row_factory = sqlite3.Row # Return rows as dicts
|
||||
@@ -90,7 +90,9 @@ class JLCPCBPartsManager:
|
||||
self.conn.commit()
|
||||
logger.info(f"Initialized JLCPCB parts database at {self.db_path}")
|
||||
|
||||
def import_parts(self, parts: List[Dict], progress_callback=None):
|
||||
def import_parts(
|
||||
self, parts: List[Dict], progress_callback: Optional[Callable[..., Any]] = None
|
||||
) -> None:
|
||||
"""
|
||||
Import parts into database from JLCPCB API response
|
||||
|
||||
@@ -167,7 +169,9 @@ class JLCPCBPartsManager:
|
||||
else:
|
||||
return "Extended" # Default to Extended
|
||||
|
||||
def import_jlcsearch_parts(self, parts: List[Dict], progress_callback=None):
|
||||
def import_jlcsearch_parts(
|
||||
self, parts: List[Dict], progress_callback: Optional[Callable[..., Any]] = None
|
||||
) -> None:
|
||||
"""
|
||||
Import parts into database from JLCSearch API response
|
||||
|
||||
@@ -452,7 +456,7 @@ class JLCPCBPartsManager:
|
||||
alternatives = [p for p in alternatives if p["lcsc"] != lcsc_number]
|
||||
|
||||
# Sort by: Basic first, then by price, then by stock
|
||||
def sort_key(p):
|
||||
def sort_key(p: Dict[str, Any]) -> Tuple[int, float, int]:
|
||||
is_basic = 1 if p.get("library_type") == "Basic" else 0
|
||||
try:
|
||||
prices = json.loads(p.get("price_json", "[]"))
|
||||
@@ -467,7 +471,7 @@ class JLCPCBPartsManager:
|
||||
|
||||
return alternatives[:limit]
|
||||
|
||||
def close(self):
|
||||
def close(self) -> None:
|
||||
"""Close database connection"""
|
||||
if self.conn:
|
||||
self.conn.close()
|
||||
|
||||
@@ -7,7 +7,7 @@ jlcsearch service at https://jlcsearch.tscircuit.com/
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Callable, Dict, List, Optional
|
||||
from typing import Any, Callable, Dict, List, Optional, Union
|
||||
|
||||
import requests
|
||||
|
||||
@@ -24,12 +24,12 @@ class JLCSearchClient:
|
||||
|
||||
BASE_URL = "https://jlcsearch.tscircuit.com"
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
"""Initialize JLCSearch API client"""
|
||||
pass
|
||||
|
||||
def search_components(
|
||||
self, category: str = "components", limit: int = 100, offset: int = 0, **filters
|
||||
self, category: str = "components", limit: int = 100, offset: int = 0, **filters: Dict
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Search components in JLCSearch database
|
||||
@@ -87,7 +87,7 @@ class JLCSearchClient:
|
||||
- stock: Available stock
|
||||
- price1: Price per unit
|
||||
"""
|
||||
filters = {}
|
||||
filters: Dict[str, Any] = {}
|
||||
if resistance is not None:
|
||||
filters["resistance"] = resistance
|
||||
if package:
|
||||
@@ -109,7 +109,7 @@ class JLCSearchClient:
|
||||
Returns:
|
||||
List of capacitor dicts
|
||||
"""
|
||||
filters = {}
|
||||
filters: Dict[str, Any] = {}
|
||||
if capacitance is not None:
|
||||
filters["capacitance"] = capacitance
|
||||
if package:
|
||||
|
||||
@@ -35,7 +35,7 @@ class LibraryManager:
|
||||
self.footprint_cache: Dict[str, List[str]] = {} # library -> [footprint names]
|
||||
self._load_libraries()
|
||||
|
||||
def _load_libraries(self):
|
||||
def _load_libraries(self) -> None:
|
||||
"""Load libraries from fp-lib-table files"""
|
||||
# Load global libraries
|
||||
global_table = self._get_global_fp_lib_table()
|
||||
@@ -78,7 +78,7 @@ class LibraryManager:
|
||||
|
||||
return None
|
||||
|
||||
def _parse_fp_lib_table(self, table_path: Path):
|
||||
def _parse_fp_lib_table(self, table_path: Path) -> None:
|
||||
"""
|
||||
Parse fp-lib-table file
|
||||
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import glob
|
||||
import logging
|
||||
|
||||
# Symbol class might not be directly importable in the current version
|
||||
import os
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from skip import Schematic
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LibraryManager:
|
||||
"""Manage symbol libraries"""
|
||||
|
||||
@staticmethod
|
||||
def list_available_libraries(search_paths=None):
|
||||
def list_available_libraries(search_paths: Optional[List[str]] = None) -> Dict[str, List[str]]:
|
||||
"""List all available symbol libraries"""
|
||||
if search_paths is None:
|
||||
# Default library paths based on common KiCAD installations
|
||||
@@ -31,11 +35,11 @@ class LibraryManager:
|
||||
matching_libs = glob.glob(path_pattern, recursive=True)
|
||||
libraries.extend(matching_libs)
|
||||
except Exception as e:
|
||||
print(f"Error searching for libraries at {path_pattern}: {e}")
|
||||
logger.error(f"Error searching for libraries at {path_pattern}: {e}")
|
||||
|
||||
# Extract library names from paths
|
||||
library_names = [os.path.splitext(os.path.basename(lib))[0] for lib in libraries]
|
||||
print(
|
||||
logger.info(
|
||||
f"Found {len(library_names)} libraries: {', '.join(library_names[:10])}{'...' if len(library_names) > 10 else ''}"
|
||||
)
|
||||
|
||||
@@ -43,7 +47,7 @@ class LibraryManager:
|
||||
return {"paths": libraries, "names": library_names}
|
||||
|
||||
@staticmethod
|
||||
def list_library_symbols(library_path):
|
||||
def list_library_symbols(library_path: str) -> List[Any]:
|
||||
"""List all symbols in a library"""
|
||||
try:
|
||||
# kicad-skip doesn't provide a direct way to simply list symbols in a library
|
||||
@@ -54,30 +58,30 @@ class LibraryManager:
|
||||
# A potential approach would be to load the library file using KiCAD's Python API
|
||||
# or by parsing the library file format.
|
||||
# KiCAD symbol libraries are .kicad_sym files which are S-expression format
|
||||
print(
|
||||
logger.warning(
|
||||
f"Attempted to list symbols in library {library_path}. This requires advanced implementation."
|
||||
)
|
||||
return []
|
||||
except Exception as e:
|
||||
print(f"Error listing symbols in library {library_path}: {e}")
|
||||
logger.error(f"Error listing symbols in library {library_path}: {e}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def get_symbol_details(library_path, symbol_name):
|
||||
def get_symbol_details(library_path: str, symbol_name: str) -> Dict[str, Any]:
|
||||
"""Get detailed information about a symbol"""
|
||||
try:
|
||||
# Similar to list_library_symbols, this might require a more direct approach
|
||||
# using KiCAD's Python API or by parsing the symbol library.
|
||||
print(
|
||||
logger.warning(
|
||||
f"Attempted to get details for symbol {symbol_name} in library {library_path}. This requires advanced implementation."
|
||||
)
|
||||
return {}
|
||||
except Exception as e:
|
||||
print(f"Error getting symbol details for {symbol_name} in {library_path}: {e}")
|
||||
logger.error(f"Error getting symbol details for {symbol_name} in {library_path}: {e}")
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def search_symbols(query, search_paths=None):
|
||||
def search_symbols(query: str, search_paths: Optional[List[str]] = None) -> List[Any]:
|
||||
"""Search for symbols matching criteria"""
|
||||
try:
|
||||
# This would typically involve:
|
||||
@@ -89,16 +93,18 @@ class LibraryManager:
|
||||
libraries = LibraryManager.list_available_libraries(search_paths)
|
||||
|
||||
results = []
|
||||
print(
|
||||
logger.warning(
|
||||
f"Searched for symbols matching '{query}'. This requires advanced implementation."
|
||||
)
|
||||
return results
|
||||
except Exception as e:
|
||||
print(f"Error searching for symbols matching '{query}': {e}")
|
||||
logger.error(f"Error searching for symbols matching '{query}': {e}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def get_default_symbol_for_component_type(component_type, search_paths=None):
|
||||
def get_default_symbol_for_component_type(
|
||||
component_type: str, search_paths: Optional[List[str]] = None
|
||||
) -> Dict[str, str]:
|
||||
"""Get a recommended default symbol for a given component type"""
|
||||
# This method provides a simplified way to get a symbol for common component types
|
||||
# It's useful when the user doesn't specify a particular library/symbol
|
||||
|
||||
@@ -55,7 +55,7 @@ class SymbolLibraryManager:
|
||||
self.symbol_cache: Dict[str, List[SymbolInfo]] = {} # library -> [SymbolInfo]
|
||||
self._load_libraries()
|
||||
|
||||
def _load_libraries(self):
|
||||
def _load_libraries(self) -> None:
|
||||
"""Load libraries from sym-lib-table files"""
|
||||
# Load global libraries
|
||||
global_table = self._get_global_sym_lib_table()
|
||||
@@ -98,7 +98,7 @@ class SymbolLibraryManager:
|
||||
|
||||
return None
|
||||
|
||||
def _parse_sym_lib_table(self, table_path: Path):
|
||||
def _parse_sym_lib_table(self, table_path: Path) -> None:
|
||||
"""
|
||||
Parse sym-lib-table file
|
||||
|
||||
@@ -370,7 +370,7 @@ class SymbolLibraryManager:
|
||||
query_lower = query.lower()
|
||||
|
||||
# Determine which libraries to search
|
||||
libraries_to_search = self.libraries.keys()
|
||||
libraries_to_search: list[str] = list(self.libraries.keys())
|
||||
if library_filter:
|
||||
filter_lower = library_filter.lower()
|
||||
libraries_to_search = [
|
||||
|
||||
@@ -9,7 +9,7 @@ import logging
|
||||
import math
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import sexpdata
|
||||
from sexpdata import Symbol
|
||||
@@ -21,7 +21,7 @@ logger = logging.getLogger("kicad_interface")
|
||||
class PinLocator:
|
||||
"""Locate pins on symbol instances in KiCad schematics"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
"""Initialize pin locator with empty cache"""
|
||||
self.pin_definition_cache = {} # Cache: "lib_id:symbol_name" -> pin_data
|
||||
self._schematic_cache: Dict[str, object] = {} # Cache: path -> loaded Schematic
|
||||
@@ -41,9 +41,9 @@ class PinLocator:
|
||||
"2": {"x": 0, "y": -3.81, "angle": 90, "length": 1.27, "name": "~", "type": "passive"}
|
||||
}
|
||||
"""
|
||||
pins = {}
|
||||
pins: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
def extract_pins_recursive(sexp):
|
||||
def extract_pins_recursive(sexp: Any) -> None:
|
||||
"""Recursively search for pin definitions"""
|
||||
if not isinstance(sexp, list):
|
||||
return
|
||||
|
||||
@@ -115,7 +115,7 @@ class RoutingCommands:
|
||||
"errorDetails": f"'{ref}' does not exist on the board",
|
||||
}
|
||||
|
||||
def find_pad(ref: str, pad_num: str):
|
||||
def find_pad(ref: str, pad_num: str) -> Any:
|
||||
fp = footprints[ref]
|
||||
for pad in fp.Pads():
|
||||
if pad.GetNumber() == pad_num:
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
import os
|
||||
import shutil
|
||||
import uuid
|
||||
from typing import Any, Optional
|
||||
|
||||
from skip import Schematic
|
||||
|
||||
@@ -12,7 +13,7 @@ class SchematicManager:
|
||||
"""Core schematic operations using kicad-skip"""
|
||||
|
||||
@staticmethod
|
||||
def create_schematic(name, path=None, metadata=None):
|
||||
def create_schematic(name: str, path: Optional[str] = None, metadata: Optional[Any] = None) -> Any:
|
||||
"""Create a new empty schematic from template"""
|
||||
try:
|
||||
# Determine template path (use template_with_symbols for component cloning support)
|
||||
@@ -71,7 +72,7 @@ class SchematicManager:
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
def load_schematic(file_path):
|
||||
def load_schematic(file_path: str) -> Optional[Any]:
|
||||
"""Load an existing schematic"""
|
||||
if not os.path.exists(file_path):
|
||||
logger.error(f"Schematic file not found at {file_path}")
|
||||
@@ -85,7 +86,7 @@ class SchematicManager:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def save_schematic(schematic, file_path):
|
||||
def save_schematic(schematic: Any, file_path: str) -> bool:
|
||||
"""Save a schematic to file"""
|
||||
try:
|
||||
# kicad-skip uses write method, not save
|
||||
@@ -97,7 +98,7 @@ class SchematicManager:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_schematic_metadata(schematic):
|
||||
def get_schematic_metadata(schematic: Any) -> dict[str, Any]:
|
||||
"""Extract metadata from schematic"""
|
||||
# kicad-skip doesn't expose a direct metadata object on Schematic.
|
||||
# We can return basic info like version and generator.
|
||||
|
||||
@@ -782,7 +782,7 @@ def find_wires_crossing_symbols(schematic_path: Path) -> List[Dict[str, Any]]:
|
||||
collisions = []
|
||||
|
||||
# Pre-compute per-symbol data
|
||||
symbol_data = []
|
||||
symbol_data: List[Dict[str, Any]] = []
|
||||
for sym in symbols:
|
||||
ref = sym["reference"]
|
||||
if sym["is_power"] or ref.startswith("_TEMPLATE") or not ref:
|
||||
|
||||
@@ -129,7 +129,7 @@ def _parse_path_tokens(tokens: List[str]) -> List[Polygon]:
|
||||
cx_ = cos_phi * cxp - sin_phi * cyp + (x1 + x2) / 2
|
||||
cy_ = sin_phi * cxp + cos_phi * cyp + (y1 + y2) / 2
|
||||
|
||||
def angle(ux, uy, vx, vy):
|
||||
def angle(ux: float, uy: float, vx: float, vy: float) -> float:
|
||||
a = math.acos(
|
||||
max(-1, min(1, (ux * vx + uy * vy) / (math.hypot(ux, uy) * math.hypot(vx, vy))))
|
||||
)
|
||||
@@ -293,10 +293,10 @@ def _parse_path_tokens(tokens: List[str]) -> List[Polygon]:
|
||||
def _parse_transform(transform_str: str) -> List[List[float]]:
|
||||
"""Parse SVG transform attribute, return list of 3×3 matrix rows [a,b,c; d,e,f; 0,0,1]."""
|
||||
|
||||
def identity():
|
||||
def identity() -> List[List[float]]:
|
||||
return [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
|
||||
|
||||
def mat_mul(A, B):
|
||||
def mat_mul(A: List[List[float]], B: List[List[float]]) -> List[List[float]]:
|
||||
return [[sum(A[r][k] * B[k][c] for k in range(3)) for c in range(3)] for r in range(3)]
|
||||
|
||||
result = identity()
|
||||
@@ -345,7 +345,7 @@ def _apply_transform(pts: List[Point], mat: List[List[float]]) -> List[Point]:
|
||||
return out
|
||||
|
||||
|
||||
def _mat_mul(A, B):
|
||||
def _mat_mul(A: List[List[float]], B: List[List[float]]) -> List[List[float]]:
|
||||
return [[sum(A[r][k] * B[k][c] for k in range(3)) for c in range(3)] for r in range(3)]
|
||||
|
||||
|
||||
@@ -366,7 +366,7 @@ def _get_attr(el: ET.Element, name: str, default: Optional[str] = None) -> Optio
|
||||
return default
|
||||
|
||||
|
||||
def _identity():
|
||||
def _identity() -> List[List[float]]:
|
||||
return [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ coordinate matching, mirroring KiCad's own connectivity algorithm.
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Set, Tuple
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||
|
||||
from commands.pin_locator import PinLocator
|
||||
|
||||
@@ -22,7 +22,7 @@ def _to_iu(x_mm: float, y_mm: float) -> Tuple[int, int]:
|
||||
return (round(x_mm * _IU_PER_MM), round(y_mm * _IU_PER_MM))
|
||||
|
||||
|
||||
def _parse_wires(schematic) -> List[List[Tuple[int, int]]]:
|
||||
def _parse_wires(schematic: Any) -> List[List[Tuple[int, int]]]:
|
||||
"""Extract wire endpoints from a schematic object as IU tuples."""
|
||||
all_wires = []
|
||||
for wire in schematic.wire:
|
||||
@@ -67,7 +67,9 @@ def _build_adjacency(
|
||||
return adjacency, iu_to_wires
|
||||
|
||||
|
||||
def _parse_virtual_connections(schematic, schematic_path):
|
||||
def _parse_virtual_connections(
|
||||
schematic: Any, schematic_path: Any
|
||||
) -> Tuple[Dict[Tuple[int, int], str], Dict[str, List[Tuple[int, int]]]]:
|
||||
"""Return virtual connectivity from net labels and power symbols.
|
||||
|
||||
Returns a tuple of:
|
||||
@@ -175,8 +177,8 @@ def _find_connected_wires(
|
||||
|
||||
def _find_pins_on_net(
|
||||
net_points: Set[Tuple[int, int]],
|
||||
schematic_path,
|
||||
schematic,
|
||||
schematic_path: Any,
|
||||
schematic: Any,
|
||||
) -> List[Dict]:
|
||||
"""Find component pins that land on net points using exact IU matching.
|
||||
|
||||
@@ -216,7 +218,7 @@ def _find_pins_on_net(
|
||||
|
||||
|
||||
def get_wire_connections(
|
||||
schematic, schematic_path: str, x_mm: float, y_mm: float
|
||||
schematic: Any, schematic_path: str, x_mm: float, y_mm: float
|
||||
) -> Optional[Dict]:
|
||||
"""Find all component pins reachable from a point via connected wires, net labels, and power symbols.
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ All methods operate on in-memory sexpdata lists (no disk I/O).
|
||||
import logging
|
||||
import math
|
||||
import uuid
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import sexpdata
|
||||
from sexpdata import Symbol
|
||||
@@ -55,7 +55,7 @@ class WireDragger:
|
||||
"""Pure-logic helpers for wire-endpoint dragging during component moves."""
|
||||
|
||||
@staticmethod
|
||||
def find_symbol(sch_data: list, reference: str):
|
||||
def find_symbol(sch_data: list, reference: str) -> Any:
|
||||
"""
|
||||
Find a placed symbol by reference designator.
|
||||
|
||||
@@ -218,7 +218,7 @@ class WireDragger:
|
||||
junction_k = _K["junction"]
|
||||
at_k = _K["at"]
|
||||
|
||||
def find_new(x: float, y: float):
|
||||
def find_new(x: float, y: float) -> Optional[Tuple[float, float]]:
|
||||
for (ox, oy), (nx, ny) in old_to_new.items():
|
||||
if _coords_match(x, y, ox, oy, eps):
|
||||
return nx, ny
|
||||
|
||||
@@ -11,7 +11,7 @@ import math
|
||||
import tempfile
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Tuple
|
||||
from typing import Any, List, Optional, Tuple
|
||||
|
||||
import sexpdata
|
||||
from sexpdata import Symbol
|
||||
@@ -255,7 +255,7 @@ class WireManager:
|
||||
|
||||
@staticmethod
|
||||
def _parse_wire(
|
||||
wire_item,
|
||||
wire_item: Any,
|
||||
) -> Optional[Tuple[Tuple[float, float], Tuple[float, float], float, str]]:
|
||||
"""
|
||||
Parse a wire S-expression item in a single pass.
|
||||
|
||||
Reference in New Issue
Block a user