feat: add connect_passthrough tool for FFC/ribbon cable pin-to-pin wiring
This commit is contained in:
@@ -291,6 +291,77 @@ class ConnectionManager:
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def connect_passthrough(
|
||||
schematic_path: Path,
|
||||
source_ref: str,
|
||||
target_ref: str,
|
||||
net_prefix: str = "PIN",
|
||||
pin_offset: int = 0,
|
||||
):
|
||||
"""
|
||||
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}.
|
||||
|
||||
Args:
|
||||
schematic_path: Path to .kicad_sch file
|
||||
source_ref: Reference of the first connector (e.g., "J1")
|
||||
target_ref: Reference of the second connector (e.g., "J2")
|
||||
net_prefix: Prefix for generated net names (default: "PIN" -> PIN_1, PIN_2, ...)
|
||||
pin_offset: Add this value to the pin number when building the net name (default 0)
|
||||
|
||||
Returns:
|
||||
dict with 'connected' list and 'failed' list
|
||||
"""
|
||||
if not WIRE_MANAGER_AVAILABLE:
|
||||
logger.error("WireManager/PinLocator not available")
|
||||
return {"connected": [], "failed": ["WireManager unavailable"]}
|
||||
|
||||
locator = ConnectionManager.get_pin_locator()
|
||||
if not locator:
|
||||
return {"connected": [], "failed": ["PinLocator unavailable"]}
|
||||
|
||||
# Get all pins of source and target
|
||||
src_pins = locator.get_all_symbol_pins(schematic_path, source_ref) or {}
|
||||
tgt_pins = locator.get_all_symbol_pins(schematic_path, target_ref) or {}
|
||||
|
||||
if not src_pins:
|
||||
return {"connected": [], "failed": [f"No pins found on {source_ref}"]}
|
||||
if not tgt_pins:
|
||||
return {"connected": [], "failed": [f"No pins found on {target_ref}"]}
|
||||
|
||||
connected = []
|
||||
failed = []
|
||||
|
||||
for pin_num in sorted(src_pins.keys(), key=lambda x: int(x) if x.isdigit() else 0):
|
||||
try:
|
||||
net_name = f"{net_prefix}_{int(pin_num) + pin_offset}" if pin_num.isdigit() else f"{net_prefix}_{pin_num}"
|
||||
|
||||
ok_src = ConnectionManager.connect_to_net(
|
||||
schematic_path, source_ref, pin_num, net_name
|
||||
)
|
||||
if not ok_src:
|
||||
failed.append(f"{source_ref}/{pin_num}")
|
||||
continue
|
||||
|
||||
if pin_num in tgt_pins:
|
||||
ok_tgt = ConnectionManager.connect_to_net(
|
||||
schematic_path, target_ref, pin_num, net_name
|
||||
)
|
||||
if not ok_tgt:
|
||||
failed.append(f"{target_ref}/{pin_num}")
|
||||
continue
|
||||
else:
|
||||
failed.append(f"{target_ref}/{pin_num} (pin not found)")
|
||||
continue
|
||||
|
||||
connected.append(f"{source_ref}/{pin_num} <-> {target_ref}/{pin_num} [{net_name}]")
|
||||
except Exception as e:
|
||||
failed.append(f"{source_ref}/{pin_num}: {e}")
|
||||
|
||||
logger.info(f"connect_passthrough: {len(connected)} connected, {len(failed)} failed")
|
||||
return {"connected": connected, "failed": failed}
|
||||
|
||||
@staticmethod
|
||||
def get_net_connections(
|
||||
schematic: Schematic, net_name: str, schematic_path: Optional[Path] = None
|
||||
|
||||
@@ -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,
|
||||
"connect_passthrough": self._handle_connect_passthrough,
|
||||
"get_schematic_pin_locations": self._handle_get_schematic_pin_locations,
|
||||
"get_net_connections": self._handle_get_net_connections,
|
||||
"generate_netlist": self._handle_generate_netlist,
|
||||
@@ -1272,6 +1273,39 @@ class KiCADInterface:
|
||||
"errorDetails": traceback.format_exc(),
|
||||
}
|
||||
|
||||
def _handle_connect_passthrough(self, params):
|
||||
"""Connect all pins of source connector to matching pins of target connector"""
|
||||
logger.info("Connecting passthrough between two connectors")
|
||||
try:
|
||||
from pathlib import Path
|
||||
|
||||
schematic_path = params.get("schematicPath")
|
||||
source_ref = params.get("sourceRef")
|
||||
target_ref = params.get("targetRef")
|
||||
net_prefix = params.get("netPrefix", "PIN")
|
||||
pin_offset = int(params.get("pinOffset", 0))
|
||||
|
||||
if not all([schematic_path, source_ref, target_ref]):
|
||||
return {"success": False, "message": "Missing required parameters: schematicPath, sourceRef, targetRef"}
|
||||
|
||||
result = ConnectionManager.connect_passthrough(
|
||||
Path(schematic_path), source_ref, target_ref, net_prefix, pin_offset
|
||||
)
|
||||
|
||||
n_ok = len(result["connected"])
|
||||
n_fail = len(result["failed"])
|
||||
return {
|
||||
"success": n_fail == 0,
|
||||
"message": f"Passthrough complete: {n_ok} connected, {n_fail} failed",
|
||||
"connected": result["connected"],
|
||||
"failed": result["failed"],
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error in connect_passthrough: {str(e)}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
return {"success": False, "message": str(e)}
|
||||
|
||||
def _handle_get_schematic_pin_locations(self, params):
|
||||
"""Return exact pin endpoint coordinates for a schematic component"""
|
||||
logger.info("Getting schematic pin locations")
|
||||
|
||||
@@ -1483,7 +1483,38 @@ SCHEMATIC_TOOLS = [
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "generate_netlist",
|
||||
"name": "connect_passthrough",
|
||||
"title": "Connect Passthrough (Pin-to-Pin)",
|
||||
"description": "Connects all pins of a source connector to the matching pins of a target connector using shared net labels. Ideal for passthrough adapters where J1 pin N connects directly to J2 pin N. Each pair gets a net label '{netPrefix}_{pinNumber}'. Use this instead of calling connect_to_net 15 times for FFC/ribbon cable passthroughs.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"schematicPath": {
|
||||
"type": "string",
|
||||
"description": "Path to the schematic file"
|
||||
},
|
||||
"sourceRef": {
|
||||
"type": "string",
|
||||
"description": "Reference of the source connector (e.g., J1)"
|
||||
},
|
||||
"targetRef": {
|
||||
"type": "string",
|
||||
"description": "Reference of the target connector (e.g., J2)"
|
||||
},
|
||||
"netPrefix": {
|
||||
"type": "string",
|
||||
"description": "Prefix for generated net names, e.g. 'CSI' produces CSI_1, CSI_2, ... (default: PIN)"
|
||||
},
|
||||
"pinOffset": {
|
||||
"type": "integer",
|
||||
"description": "Add this value to the pin number when building net names (default: 0)"
|
||||
}
|
||||
},
|
||||
"required": ["schematicPath", "sourceRef", "targetRef"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "generate_netlist",,
|
||||
"title": "Generate Netlist",
|
||||
"description": "Generates a netlist from the schematic showing all components and their net connections.",
|
||||
"inputSchema": {
|
||||
|
||||
@@ -417,6 +417,34 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
},
|
||||
);
|
||||
|
||||
// Connect all pins of source connector to matching pins of target connector (passthrough)
|
||||
server.tool(
|
||||
"connect_passthrough",
|
||||
"Connects all pins of a source connector (e.g. J1) to matching pins of a target connector (e.g. J2) via shared net labels — pin N gets net '{netPrefix}_{N}'. Use this for FFC/ribbon cable passthrough adapters instead of calling connect_to_net for every pin.",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
sourceRef: z.string().describe("Source connector reference (e.g. J1)"),
|
||||
targetRef: z.string().describe("Target connector reference (e.g. J2)"),
|
||||
netPrefix: z.string().optional().describe("Net name prefix, e.g. 'CSI' → CSI_1, CSI_2 (default: PIN)"),
|
||||
pinOffset: z.number().optional().describe("Add to pin number when building net name (default: 0)"),
|
||||
},
|
||||
async (args: { schematicPath: string; sourceRef: string; targetRef: string; netPrefix?: string; pinOffset?: number }) => {
|
||||
const result = await callKicadScript("connect_passthrough", args);
|
||||
if (result.success !== false || (result.connected && result.connected.length > 0)) {
|
||||
const lines: string[] = [];
|
||||
if (result.connected?.length) lines.push(`Connected (${result.connected.length}): ${result.connected.slice(0, 5).join(", ")}${result.connected.length > 5 ? " ..." : ""}`);
|
||||
if (result.failed?.length) lines.push(`Failed (${result.failed.length}): ${result.failed.join(", ")}`);
|
||||
return {
|
||||
content: [{ type: "text", text: result.message + "\n" + lines.join("\n") }],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [{ type: "text", text: `Passthrough failed: ${result.message || "Unknown error"}` }],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Generate netlist
|
||||
server.tool(
|
||||
"generate_netlist",
|
||||
|
||||
Reference in New Issue
Block a user