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:
Michael Parment
2026-04-08 10:43:13 +02:00
36 changed files with 4805 additions and 4662 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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.

View File

@@ -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}")

View File

@@ -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]:

View File

@@ -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()

View File

@@ -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:

View File

@@ -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

View 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

View File

@@ -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 = [

View File

@@ -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

View File

@@ -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:

View File

@@ -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.

View File

@@ -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:

View File

@@ -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]]

View File

@@ -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.

View File

@@ -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

View File

@@ -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.