fix: schematic pin connection reliability

- add_schematic_net_label: warn in description that coords must be exact pin endpoints; recommend connect_to_net instead
- connect_to_net: stub wire direction now follows pin angle (was hardcoded +X)
- pin_locator.py: add get_pin_angle() and _get_lib_id() helpers
- new tool: get_schematic_pin_locations(schematicPath, reference) → returns exact x/y of every pin endpoint, so Claude can place labels correctly
This commit is contained in:
Tom
2026-03-06 11:29:52 +01:00
parent 68fd4509b9
commit 1d390f4fed
5 changed files with 161 additions and 2 deletions

View File

@@ -256,7 +256,16 @@ class ConnectionManager:
return False
# Add a small wire stub from the pin (2.54mm = 0.1 inch, standard grid spacing)
stub_end = [pin_loc[0] + 2.54, pin_loc[1]]
# Stub direction follows the pin's outward angle from the PinLocator
pin_angle_deg = getattr(locator, '_last_pin_angle', 0)
try:
pin_angle_deg = locator.get_pin_angle(schematic_path, component_ref, pin_name) or 0
except Exception:
pin_angle_deg = 0
import math as _math
angle_rad = _math.radians(pin_angle_deg)
stub_end = [round(pin_loc[0] + 2.54 * _math.cos(angle_rad), 4),
round(pin_loc[1] - 2.54 * _math.sin(angle_rad), 4)]
# Create wire stub using WireManager
wire_success = WireManager.add_wire(schematic_path, pin_loc, stub_end)

View File

@@ -179,6 +179,63 @@ class PinLocator:
return (rotated_x, rotated_y)
def _get_lib_id(self, schematic_path: Path, symbol_reference: str) -> Optional[str]:
"""Helper: return the lib_id string for a placed symbol"""
try:
sch_key = str(schematic_path)
if sch_key not in self._schematic_cache:
self._schematic_cache[sch_key] = Schematic(sch_key)
sch = self._schematic_cache[sch_key]
for symbol in sch.symbol:
if symbol.property.Reference.value == symbol_reference:
return symbol.lib_id.value if hasattr(symbol, "lib_id") else None
except Exception:
pass
return None
def get_pin_angle(
self, schematic_path: Path, symbol_reference: str, pin_number: str
) -> Optional[float]:
"""
Get the outward angle of a pin endpoint in degrees (0=right, 90=up, 180=left, 270=down).
This is the direction a wire stub must extend to stay connected to the pin.
Returns angle in degrees, or None if pin not found.
"""
try:
sch_key = str(schematic_path)
if sch_key not in self._schematic_cache:
self._schematic_cache[sch_key] = Schematic(sch_key)
sch = self._schematic_cache[sch_key]
target_symbol = None
for symbol in sch.symbol:
if symbol.property.Reference.value == symbol_reference:
target_symbol = symbol
break
if not target_symbol:
return None
symbol_at = target_symbol.at.value
symbol_rotation = float(symbol_at[2]) if len(symbol_at) > 2 else 0.0
lib_id = target_symbol.lib_id.value if hasattr(target_symbol, "lib_id") else None
if not lib_id:
return None
pins = self.get_symbol_pins(schematic_path, lib_id)
if pin_number not in pins:
return None
# Pin definition angle + symbol rotation = absolute outward direction
pin_def_angle = pins[pin_number].get("angle", 0)
absolute_angle = (pin_def_angle + symbol_rotation) % 360
return absolute_angle
except Exception:
return None
def get_pin_location(
self, schematic_path: Path, symbol_reference: str, pin_number: str
) -> Optional[List[float]]:

View File

@@ -375,6 +375,7 @@ class KiCADInterface:
"add_schematic_connection": self._handle_add_schematic_connection,
"add_schematic_net_label": self._handle_add_schematic_net_label,
"connect_to_net": self._handle_connect_to_net,
"get_schematic_pin_locations": self._handle_get_schematic_pin_locations,
"get_net_connections": self._handle_get_net_connections,
"generate_netlist": self._handle_generate_netlist,
"list_schematic_libraries": self._handle_list_schematic_libraries,
@@ -1271,6 +1272,47 @@ class KiCADInterface:
"errorDetails": traceback.format_exc(),
}
def _handle_get_schematic_pin_locations(self, params):
"""Return exact pin endpoint coordinates for a schematic component"""
logger.info("Getting schematic pin locations")
try:
from pathlib import Path
from commands.pin_locator import PinLocator
schematic_path = params.get("schematicPath")
reference = params.get("reference")
if not all([schematic_path, reference]):
return {"success": False, "message": "Missing required parameters: schematicPath, reference"}
locator = PinLocator()
all_pins = locator.get_all_symbol_pins(Path(schematic_path), reference)
if not all_pins:
return {"success": False, "message": f"No pins found for {reference} — check reference and schematic path"}
# Enrich with pin names and angles from the symbol definition
pins_def = locator.get_symbol_pins(
Path(schematic_path),
locator._get_lib_id(Path(schematic_path), reference),
) if hasattr(locator, "_get_lib_id") else {}
result = {}
for pin_num, coords in all_pins.items():
entry = {"x": coords[0], "y": coords[1]}
if pin_num in pins_def:
entry["name"] = pins_def[pin_num].get("name", pin_num)
entry["angle"] = locator.get_pin_angle(Path(schematic_path), reference, pin_num) or 0
result[pin_num] = entry
return {"success": True, "reference": reference, "pins": result}
except Exception as e:
logger.error(f"Error getting pin locations: {e}")
import traceback
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_get_net_connections(self, params):
"""Get all connections for a named net"""
logger.info("Getting net connections")

View File

@@ -1388,7 +1388,7 @@ SCHEMATIC_TOOLS = [
{
"name": "add_schematic_net_label",
"title": "Add Net Label",
"description": "Adds a net label to assign a name to a wire/net on the schematic.",
"description": "Adds a net label at exact coordinates on a schematic wire or pin endpoint. WARNING: x/y must match an existing wire endpoint or pin endpoint exactly — placing the label even 0.01mm away from a pin will result in an unconnected pin ERC error. To connect a component pin to a net by reference and pin number (recommended), use connect_to_net instead.",
"inputSchema": {
"type": "object",
"properties": {
@@ -1463,6 +1463,25 @@ SCHEMATIC_TOOLS = [
"required": ["schematicPath", "netName"]
}
},
{
"name": "get_schematic_pin_locations",
"title": "Get Schematic Pin Locations",
"description": "Returns the exact absolute coordinates of all pins on a schematic component. Use this BEFORE placing net labels with add_schematic_net_label to get the correct x/y position for each pin endpoint.",
"inputSchema": {
"type": "object",
"properties": {
"schematicPath": {
"type": "string",
"description": "Path to the schematic file"
},
"reference": {
"type": "string",
"description": "Component reference designator (e.g., U1, R1, J2)"
}
},
"required": ["schematicPath", "reference"]
}
},
{
"name": "generate_netlist",
"title": "Generate Netlist",