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:
@@ -256,7 +256,16 @@ class ConnectionManager:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Add a small wire stub from the pin (2.54mm = 0.1 inch, standard grid spacing)
|
# 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
|
# Create wire stub using WireManager
|
||||||
wire_success = WireManager.add_wire(schematic_path, pin_loc, stub_end)
|
wire_success = WireManager.add_wire(schematic_path, pin_loc, stub_end)
|
||||||
|
|||||||
@@ -179,6 +179,63 @@ class PinLocator:
|
|||||||
|
|
||||||
return (rotated_x, rotated_y)
|
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(
|
def get_pin_location(
|
||||||
self, schematic_path: Path, symbol_reference: str, pin_number: str
|
self, schematic_path: Path, symbol_reference: str, pin_number: str
|
||||||
) -> Optional[List[float]]:
|
) -> Optional[List[float]]:
|
||||||
|
|||||||
@@ -375,6 +375,7 @@ class KiCADInterface:
|
|||||||
"add_schematic_connection": self._handle_add_schematic_connection,
|
"add_schematic_connection": self._handle_add_schematic_connection,
|
||||||
"add_schematic_net_label": self._handle_add_schematic_net_label,
|
"add_schematic_net_label": self._handle_add_schematic_net_label,
|
||||||
"connect_to_net": self._handle_connect_to_net,
|
"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,
|
"get_net_connections": self._handle_get_net_connections,
|
||||||
"generate_netlist": self._handle_generate_netlist,
|
"generate_netlist": self._handle_generate_netlist,
|
||||||
"list_schematic_libraries": self._handle_list_schematic_libraries,
|
"list_schematic_libraries": self._handle_list_schematic_libraries,
|
||||||
@@ -1271,6 +1272,47 @@ class KiCADInterface:
|
|||||||
"errorDetails": traceback.format_exc(),
|
"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):
|
def _handle_get_net_connections(self, params):
|
||||||
"""Get all connections for a named net"""
|
"""Get all connections for a named net"""
|
||||||
logger.info("Getting net connections")
|
logger.info("Getting net connections")
|
||||||
|
|||||||
@@ -1388,7 +1388,7 @@ SCHEMATIC_TOOLS = [
|
|||||||
{
|
{
|
||||||
"name": "add_schematic_net_label",
|
"name": "add_schematic_net_label",
|
||||||
"title": "Add 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": {
|
"inputSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -1463,6 +1463,25 @@ SCHEMATIC_TOOLS = [
|
|||||||
"required": ["schematicPath", "netName"]
|
"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",
|
"name": "generate_netlist",
|
||||||
"title": "Generate Netlist",
|
"title": "Generate Netlist",
|
||||||
|
|||||||
@@ -385,6 +385,38 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Get pin locations for a schematic component
|
||||||
|
server.tool(
|
||||||
|
"get_schematic_pin_locations",
|
||||||
|
"Returns the exact x/y coordinates of every pin on a schematic component. Use this before add_schematic_net_label to place labels correctly on pin endpoints.",
|
||||||
|
{
|
||||||
|
schematicPath: z.string().describe("Path to the schematic file"),
|
||||||
|
reference: z.string().describe("Component reference designator (e.g. U1, R1, J2)"),
|
||||||
|
},
|
||||||
|
async (args: { schematicPath: string; reference: string }) => {
|
||||||
|
const result = await callKicadScript("get_schematic_pin_locations", args);
|
||||||
|
if (result.success && result.pins) {
|
||||||
|
const lines = Object.entries(result.pins as Record<string, any>).map(
|
||||||
|
([pinNum, data]: [string, any]) =>
|
||||||
|
` Pin ${pinNum} (${data.name || pinNum}): x=${data.x}, y=${data.y}, angle=${data.angle ?? 0}°`
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: `Pin locations for ${args.reference}:\n${lines.join("\n")}`,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: `Failed to get pin locations: ${result.message || "Unknown error"}`,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Generate netlist
|
// Generate netlist
|
||||||
server.tool(
|
server.tool(
|
||||||
"generate_netlist",
|
"generate_netlist",
|
||||||
|
|||||||
Reference in New Issue
Block a user