Issue #32 - Unknown command errors: - Register get_board_extents in command_routes (was implemented but not registered) - Implement find_component command with pattern matching on reference/value/footprint - Add schemas for both commands Issue #30 - PCB routing replication (Phase 1): - Implement get_component_pads: returns all pads with positions, nets, shapes - Implement get_pad_position: returns specific pad coordinates and properties - Implement query_traces: query traces by net, layer, or bounding box - Add schemas for all new commands Issue #26 - Schematic workflow: - Add missing schemas for add_schematic_connection, add_schematic_net_label, connect_to_net, get_net_connections, and generate_netlist Issue #19 - macOS Python path detection: - Add Python 3.13 to version detection - Add alternative KiCAD installation paths (user Applications, capitalization variants) - Add Homebrew Python fallback paths for Apple Silicon and Intel Macs - Expand platform_helper.py with same improvements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -483,7 +483,244 @@ class ComponentCommands:
|
||||
"message": "Failed to get component list",
|
||||
"errorDetails": str(e)
|
||||
}
|
||||
|
||||
|
||||
def find_component(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Find components matching search criteria (reference, value, or footprint pattern)"""
|
||||
try:
|
||||
if not self.board:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "No board is loaded",
|
||||
"errorDetails": "Load or create a board first"
|
||||
}
|
||||
|
||||
# Get search parameters
|
||||
reference_pattern = params.get("reference", "").lower()
|
||||
value_pattern = params.get("value", "").lower()
|
||||
footprint_pattern = params.get("footprint", "").lower()
|
||||
|
||||
if not reference_pattern and not value_pattern and not footprint_pattern:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Missing search criteria",
|
||||
"errorDetails": "At least one of reference, value, or footprint pattern is required"
|
||||
}
|
||||
|
||||
matches = []
|
||||
for module in self.board.GetFootprints():
|
||||
ref = module.GetReference().lower()
|
||||
val = module.GetValue().lower()
|
||||
fp = module.GetFPIDAsString().lower()
|
||||
|
||||
# Check if component matches all provided patterns
|
||||
match = True
|
||||
if reference_pattern and reference_pattern not in ref:
|
||||
match = False
|
||||
if value_pattern and value_pattern not in val:
|
||||
match = False
|
||||
if footprint_pattern and footprint_pattern not in fp:
|
||||
match = False
|
||||
|
||||
if match:
|
||||
pos = module.GetPosition()
|
||||
matches.append({
|
||||
"reference": module.GetReference(),
|
||||
"value": module.GetValue(),
|
||||
"footprint": module.GetFPIDAsString(),
|
||||
"position": {
|
||||
"x": pos.x / 1000000,
|
||||
"y": pos.y / 1000000,
|
||||
"unit": "mm"
|
||||
},
|
||||
"rotation": module.GetOrientation().AsDegrees(),
|
||||
"layer": self.board.GetLayerName(module.GetLayer())
|
||||
})
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"matchCount": len(matches),
|
||||
"components": matches
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error finding components: {str(e)}")
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Failed to find components",
|
||||
"errorDetails": str(e)
|
||||
}
|
||||
|
||||
def get_component_pads(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Get all pads for a component with their positions and net connections"""
|
||||
try:
|
||||
if not self.board:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "No board is loaded",
|
||||
"errorDetails": "Load or create a board first"
|
||||
}
|
||||
|
||||
reference = params.get("reference")
|
||||
if not reference:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Missing reference",
|
||||
"errorDetails": "reference parameter is required"
|
||||
}
|
||||
|
||||
# Find the component
|
||||
module = self.board.FindFootprintByReference(reference)
|
||||
if not module:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Component not found",
|
||||
"errorDetails": f"Could not find component: {reference}"
|
||||
}
|
||||
|
||||
pads = []
|
||||
for pad in module.Pads():
|
||||
pos = pad.GetPosition()
|
||||
size = pad.GetSize()
|
||||
|
||||
# Get pad shape as string
|
||||
shape_map = {
|
||||
pcbnew.PAD_SHAPE_CIRCLE: "circle",
|
||||
pcbnew.PAD_SHAPE_RECT: "rect",
|
||||
pcbnew.PAD_SHAPE_OVAL: "oval",
|
||||
pcbnew.PAD_SHAPE_TRAPEZOID: "trapezoid",
|
||||
pcbnew.PAD_SHAPE_ROUNDRECT: "roundrect",
|
||||
pcbnew.PAD_SHAPE_CHAMFERED_RECT: "chamfered_rect",
|
||||
pcbnew.PAD_SHAPE_CUSTOM: "custom"
|
||||
}
|
||||
shape = shape_map.get(pad.GetShape(), "unknown")
|
||||
|
||||
# Get pad type
|
||||
type_map = {
|
||||
pcbnew.PAD_ATTRIB_PTH: "through_hole",
|
||||
pcbnew.PAD_ATTRIB_SMD: "smd",
|
||||
pcbnew.PAD_ATTRIB_CONN: "connector",
|
||||
pcbnew.PAD_ATTRIB_NPTH: "npth"
|
||||
}
|
||||
pad_type = type_map.get(pad.GetAttribute(), "unknown")
|
||||
|
||||
pads.append({
|
||||
"name": pad.GetName(),
|
||||
"number": pad.GetNumber(),
|
||||
"position": {
|
||||
"x": pos.x / 1000000,
|
||||
"y": pos.y / 1000000,
|
||||
"unit": "mm"
|
||||
},
|
||||
"net": pad.GetNetname(),
|
||||
"netCode": pad.GetNetCode(),
|
||||
"shape": shape,
|
||||
"type": pad_type,
|
||||
"size": {
|
||||
"x": size.x / 1000000,
|
||||
"y": size.y / 1000000,
|
||||
"unit": "mm"
|
||||
},
|
||||
"drillSize": pad.GetDrillSize().x / 1000000 if pad.GetDrillSize().x > 0 else None
|
||||
})
|
||||
|
||||
# Get component position for reference
|
||||
comp_pos = module.GetPosition()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"reference": reference,
|
||||
"componentPosition": {
|
||||
"x": comp_pos.x / 1000000,
|
||||
"y": comp_pos.y / 1000000,
|
||||
"unit": "mm"
|
||||
},
|
||||
"padCount": len(pads),
|
||||
"pads": pads
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting component pads: {str(e)}")
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Failed to get component pads",
|
||||
"errorDetails": str(e)
|
||||
}
|
||||
|
||||
def get_pad_position(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Get the position of a specific pad on a component"""
|
||||
try:
|
||||
if not self.board:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "No board is loaded",
|
||||
"errorDetails": "Load or create a board first"
|
||||
}
|
||||
|
||||
reference = params.get("reference")
|
||||
pad_name = params.get("padName") or params.get("padNumber")
|
||||
|
||||
if not reference:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Missing reference",
|
||||
"errorDetails": "reference parameter is required"
|
||||
}
|
||||
if not pad_name:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Missing pad identifier",
|
||||
"errorDetails": "padName or padNumber parameter is required"
|
||||
}
|
||||
|
||||
# Find the component
|
||||
module = self.board.FindFootprintByReference(reference)
|
||||
if not module:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Component not found",
|
||||
"errorDetails": f"Could not find component: {reference}"
|
||||
}
|
||||
|
||||
# Find the specific pad
|
||||
pad = module.FindPadByNumber(str(pad_name))
|
||||
if not pad:
|
||||
# List available pads in error message
|
||||
available_pads = [p.GetNumber() for p in module.Pads()]
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Pad not found",
|
||||
"errorDetails": f"Pad '{pad_name}' not found on {reference}. Available pads: {', '.join(available_pads)}"
|
||||
}
|
||||
|
||||
pos = pad.GetPosition()
|
||||
size = pad.GetSize()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"reference": reference,
|
||||
"padName": pad.GetNumber(),
|
||||
"position": {
|
||||
"x": pos.x / 1000000,
|
||||
"y": pos.y / 1000000,
|
||||
"unit": "mm"
|
||||
},
|
||||
"net": pad.GetNetname(),
|
||||
"netCode": pad.GetNetCode(),
|
||||
"size": {
|
||||
"x": size.x / 1000000,
|
||||
"y": size.y / 1000000,
|
||||
"unit": "mm"
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting pad position: {str(e)}")
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Failed to get pad position",
|
||||
"errorDetails": str(e)
|
||||
}
|
||||
|
||||
def place_component_array(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Place an array of components in a grid or circular pattern"""
|
||||
try:
|
||||
|
||||
@@ -366,7 +366,123 @@ class RoutingCommands:
|
||||
"message": "Failed to get nets list",
|
||||
"errorDetails": str(e)
|
||||
}
|
||||
|
||||
|
||||
def query_traces(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Query traces by net, layer, or bounding box"""
|
||||
try:
|
||||
if not self.board:
|
||||
return {
|
||||
"success": False,
|
||||
"message": "No board is loaded",
|
||||
"errorDetails": "Load or create a board first"
|
||||
}
|
||||
|
||||
# Get filter parameters
|
||||
net_name = params.get("net")
|
||||
layer = params.get("layer")
|
||||
bbox = params.get("boundingBox") # {x1, y1, x2, y2, unit}
|
||||
include_vias = params.get("includeVias", False)
|
||||
|
||||
scale = 1000000 # nm to mm conversion factor
|
||||
traces = []
|
||||
vias = []
|
||||
|
||||
# Process tracks
|
||||
for track in self.board.Tracks():
|
||||
# Check if it's a via
|
||||
is_via = track.Type() == pcbnew.PCB_VIA_T
|
||||
|
||||
if is_via and not include_vias:
|
||||
continue
|
||||
|
||||
# Filter by net
|
||||
if net_name and track.GetNetname() != net_name:
|
||||
continue
|
||||
|
||||
# Filter by layer (only for tracks, not vias)
|
||||
if layer and not is_via:
|
||||
layer_id = self.board.GetLayerID(layer)
|
||||
if track.GetLayer() != layer_id:
|
||||
continue
|
||||
|
||||
# Filter by bounding box
|
||||
if bbox:
|
||||
bbox_unit = bbox.get("unit", "mm")
|
||||
bbox_scale = scale if bbox_unit == "mm" else 25400000
|
||||
x1 = int(bbox.get("x1", 0) * bbox_scale)
|
||||
y1 = int(bbox.get("y1", 0) * bbox_scale)
|
||||
x2 = int(bbox.get("x2", 0) * bbox_scale)
|
||||
y2 = int(bbox.get("y2", 0) * bbox_scale)
|
||||
|
||||
if is_via:
|
||||
pos = track.GetPosition()
|
||||
if not (x1 <= pos.x <= x2 and y1 <= pos.y <= y2):
|
||||
continue
|
||||
else:
|
||||
start = track.GetStart()
|
||||
end = track.GetEnd()
|
||||
# Check if either endpoint is within bbox
|
||||
start_in = x1 <= start.x <= x2 and y1 <= start.y <= y2
|
||||
end_in = x1 <= end.x <= x2 and y1 <= end.y <= y2
|
||||
if not (start_in or end_in):
|
||||
continue
|
||||
|
||||
if is_via:
|
||||
pos = track.GetPosition()
|
||||
vias.append({
|
||||
"uuid": str(track.m_Uuid),
|
||||
"position": {
|
||||
"x": pos.x / scale,
|
||||
"y": pos.y / scale,
|
||||
"unit": "mm"
|
||||
},
|
||||
"net": track.GetNetname(),
|
||||
"netCode": track.GetNetCode(),
|
||||
"diameter": track.GetWidth() / scale,
|
||||
"drill": track.GetDrillValue() / scale
|
||||
})
|
||||
else:
|
||||
start = track.GetStart()
|
||||
end = track.GetEnd()
|
||||
traces.append({
|
||||
"uuid": str(track.m_Uuid),
|
||||
"net": track.GetNetname(),
|
||||
"netCode": track.GetNetCode(),
|
||||
"layer": self.board.GetLayerName(track.GetLayer()),
|
||||
"width": track.GetWidth() / scale,
|
||||
"start": {
|
||||
"x": start.x / scale,
|
||||
"y": start.y / scale,
|
||||
"unit": "mm"
|
||||
},
|
||||
"end": {
|
||||
"x": end.x / scale,
|
||||
"y": end.y / scale,
|
||||
"unit": "mm"
|
||||
},
|
||||
"length": track.GetLength() / scale
|
||||
})
|
||||
|
||||
result = {
|
||||
"success": True,
|
||||
"traceCount": len(traces),
|
||||
"traces": traces
|
||||
}
|
||||
|
||||
if include_vias:
|
||||
result["viaCount"] = len(vias)
|
||||
result["vias"] = vias
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error querying traces: {str(e)}")
|
||||
return {
|
||||
"success": False,
|
||||
"message": "Failed to query traces",
|
||||
"errorDetails": str(e)
|
||||
}
|
||||
|
||||
def create_netclass(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Create a new net class with specified properties"""
|
||||
try:
|
||||
|
||||
@@ -288,6 +288,7 @@ class KiCADInterface:
|
||||
"get_board_info": self.board_commands.get_board_info,
|
||||
"get_layer_list": self.board_commands.get_layer_list,
|
||||
"get_board_2d_view": self.board_commands.get_board_2d_view,
|
||||
"get_board_extents": self.board_commands.get_board_extents,
|
||||
"add_board_outline": self.board_commands.add_board_outline,
|
||||
"add_mounting_hole": self.board_commands.add_mounting_hole,
|
||||
"add_text": self.board_commands.add_text,
|
||||
@@ -301,6 +302,9 @@ class KiCADInterface:
|
||||
"edit_component": self.component_commands.edit_component,
|
||||
"get_component_properties": self.component_commands.get_component_properties,
|
||||
"get_component_list": self.component_commands.get_component_list,
|
||||
"find_component": self.component_commands.find_component,
|
||||
"get_component_pads": self.component_commands.get_component_pads,
|
||||
"get_pad_position": self.component_commands.get_pad_position,
|
||||
"place_component_array": self.component_commands.place_component_array,
|
||||
"align_components": self.component_commands.align_components,
|
||||
"duplicate_component": self.component_commands.duplicate_component,
|
||||
@@ -310,6 +314,7 @@ class KiCADInterface:
|
||||
"route_trace": self.routing_commands.route_trace,
|
||||
"add_via": self.routing_commands.add_via,
|
||||
"delete_trace": self.routing_commands.delete_trace,
|
||||
"query_traces": self.routing_commands.query_traces,
|
||||
"get_nets_list": self.routing_commands.get_nets_list,
|
||||
"create_netclass": self.routing_commands.create_netclass,
|
||||
"add_copper_pour": self.routing_commands.add_copper_pour,
|
||||
|
||||
@@ -224,6 +224,22 @@ BOARD_TOOLS = [
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_board_extents",
|
||||
"title": "Get Board Bounding Box",
|
||||
"description": "Returns the bounding box extents of the PCB board including all edge cuts, components, and traces.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"enum": ["mm", "inch"],
|
||||
"description": "Unit for returned coordinates (default: mm)",
|
||||
"default": "mm"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "add_mounting_hole",
|
||||
"title": "Add Mounting Hole",
|
||||
@@ -440,6 +456,66 @@ COMPONENT_TOOLS = [
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "find_component",
|
||||
"title": "Find Components",
|
||||
"description": "Searches for components matching specified criteria. Supports partial matching on reference, value, or footprint patterns.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reference": {
|
||||
"type": "string",
|
||||
"description": "Reference designator pattern to match (e.g., 'R1', 'U', 'C2')"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "Value pattern to match (e.g., '10k', '100nF')"
|
||||
},
|
||||
"footprint": {
|
||||
"type": "string",
|
||||
"description": "Footprint pattern to match (e.g., '0805', 'SOIC')"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_component_pads",
|
||||
"title": "Get Component Pads",
|
||||
"description": "Returns all pads for a component with their positions, net connections, sizes, and shapes.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reference": {
|
||||
"type": "string",
|
||||
"description": "Component reference designator (e.g., U1, R5)"
|
||||
}
|
||||
},
|
||||
"required": ["reference"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_pad_position",
|
||||
"title": "Get Pad Position",
|
||||
"description": "Returns the position and properties of a specific pad on a component.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"reference": {
|
||||
"type": "string",
|
||||
"description": "Component reference designator"
|
||||
},
|
||||
"padName": {
|
||||
"type": "string",
|
||||
"description": "Pad name or number (e.g., '1', '2', 'A1')"
|
||||
},
|
||||
"padNumber": {
|
||||
"type": "string",
|
||||
"description": "Alternative to padName - pad number"
|
||||
}
|
||||
},
|
||||
"required": ["reference"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "place_component_array",
|
||||
"title": "Place Component Array",
|
||||
@@ -671,6 +747,40 @@ ROUTING_TOOLS = [
|
||||
"required": ["traceId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "query_traces",
|
||||
"title": "Query Traces",
|
||||
"description": "Queries traces on the board with optional filters by net, layer, or bounding box. Returns trace details including UUID, positions, width, and length.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"net": {
|
||||
"type": "string",
|
||||
"description": "Filter by net name (e.g., 'GND', 'VCC')"
|
||||
},
|
||||
"layer": {
|
||||
"type": "string",
|
||||
"description": "Filter by layer name (e.g., 'F.Cu', 'B.Cu')"
|
||||
},
|
||||
"boundingBox": {
|
||||
"type": "object",
|
||||
"description": "Filter by bounding box region",
|
||||
"properties": {
|
||||
"x1": {"type": "number", "description": "Left X coordinate"},
|
||||
"y1": {"type": "number", "description": "Top Y coordinate"},
|
||||
"x2": {"type": "number", "description": "Right X coordinate"},
|
||||
"y2": {"type": "number", "description": "Bottom Y coordinate"},
|
||||
"unit": {"type": "string", "enum": ["mm", "inch"], "default": "mm"}
|
||||
}
|
||||
},
|
||||
"includeVias": {
|
||||
"type": "boolean",
|
||||
"description": "Include vias in the result",
|
||||
"default": False
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_nets_list",
|
||||
"title": "List All Nets",
|
||||
@@ -1164,6 +1274,132 @@ SCHEMATIC_TOOLS = [
|
||||
"required": ["points"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "add_schematic_connection",
|
||||
"title": "Add Junction/Connection Point",
|
||||
"description": "Adds a junction (connection point) at the specified location on the schematic where wires cross and should connect.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"schematicPath": {
|
||||
"type": "string",
|
||||
"description": "Path to schematic file"
|
||||
},
|
||||
"x": {
|
||||
"type": "number",
|
||||
"description": "X coordinate on schematic"
|
||||
},
|
||||
"y": {
|
||||
"type": "number",
|
||||
"description": "Y coordinate on schematic"
|
||||
}
|
||||
},
|
||||
"required": ["schematicPath", "x", "y"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"schematicPath": {
|
||||
"type": "string",
|
||||
"description": "Path to schematic file"
|
||||
},
|
||||
"netName": {
|
||||
"type": "string",
|
||||
"description": "Name of the net (e.g., VCC, GND, SDA)"
|
||||
},
|
||||
"x": {
|
||||
"type": "number",
|
||||
"description": "X coordinate on schematic"
|
||||
},
|
||||
"y": {
|
||||
"type": "number",
|
||||
"description": "Y coordinate on schematic"
|
||||
},
|
||||
"rotation": {
|
||||
"type": "number",
|
||||
"description": "Rotation angle in degrees (0, 90, 180, 270)",
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"required": ["schematicPath", "netName", "x", "y"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "connect_to_net",
|
||||
"title": "Connect Pin to Net",
|
||||
"description": "Intelligently connects a component pin to a named net, automatically routing wires as needed.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"schematicPath": {
|
||||
"type": "string",
|
||||
"description": "Path to schematic file"
|
||||
},
|
||||
"reference": {
|
||||
"type": "string",
|
||||
"description": "Component reference designator (e.g., R1, U3)"
|
||||
},
|
||||
"pinNumber": {
|
||||
"type": "string",
|
||||
"description": "Pin number or name on the component"
|
||||
},
|
||||
"netName": {
|
||||
"type": "string",
|
||||
"description": "Name of the net to connect to"
|
||||
}
|
||||
},
|
||||
"required": ["schematicPath", "reference", "pinNumber", "netName"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_net_connections",
|
||||
"title": "Get Net Connections",
|
||||
"description": "Returns all components and pins connected to a specified net.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"schematicPath": {
|
||||
"type": "string",
|
||||
"description": "Path to schematic file"
|
||||
},
|
||||
"netName": {
|
||||
"type": "string",
|
||||
"description": "Name of the net to query"
|
||||
}
|
||||
},
|
||||
"required": ["schematicPath", "netName"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "generate_netlist",
|
||||
"title": "Generate Netlist",
|
||||
"description": "Generates a netlist from the schematic showing all components and their net connections.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"schematicPath": {
|
||||
"type": "string",
|
||||
"description": "Path to schematic file"
|
||||
},
|
||||
"outputPath": {
|
||||
"type": "string",
|
||||
"description": "Optional path to save netlist file"
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"enum": ["kicad", "json", "spice"],
|
||||
"description": "Netlist output format",
|
||||
"default": "json"
|
||||
}
|
||||
},
|
||||
"required": ["schematicPath"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_schematic_libraries",
|
||||
"title": "List Symbol Libraries",
|
||||
|
||||
@@ -91,14 +91,32 @@ class PlatformHelper:
|
||||
paths = [p for p in candidates if p.exists()]
|
||||
|
||||
elif PlatformHelper.is_macos():
|
||||
# macOS: Check application bundle
|
||||
kicad_app = Path("/Applications/KiCad/KiCad.app")
|
||||
if kicad_app.exists():
|
||||
# Check Python framework path
|
||||
for version in ["3.9", "3.10", "3.11", "3.12"]:
|
||||
path = kicad_app / "Contents" / "Frameworks" / "Python.framework" / "Versions" / version / "lib" / f"python{version}" / "site-packages"
|
||||
if path.exists():
|
||||
paths.append(path)
|
||||
# macOS: Check multiple KiCAD application bundle locations
|
||||
kicad_app_paths = [
|
||||
Path("/Applications/KiCad/KiCad.app"),
|
||||
Path("/Applications/KiCAD/KiCad.app"), # Alternative capitalization
|
||||
Path.home() / "Applications" / "KiCad" / "KiCad.app", # User Applications
|
||||
]
|
||||
|
||||
# Check Python framework paths in each KiCAD installation
|
||||
for kicad_app in kicad_app_paths:
|
||||
if kicad_app.exists():
|
||||
for version in ["3.9", "3.10", "3.11", "3.12", "3.13"]:
|
||||
path = kicad_app / "Contents" / "Frameworks" / "Python.framework" / "Versions" / version / "lib" / f"python{version}" / "site-packages"
|
||||
if path.exists():
|
||||
paths.append(path)
|
||||
|
||||
# Also check Homebrew Python site-packages (if pcbnew installed via pip)
|
||||
homebrew_paths = [
|
||||
Path("/opt/homebrew/lib/python3.12/site-packages"), # Apple Silicon
|
||||
Path("/opt/homebrew/lib/python3.11/site-packages"),
|
||||
Path("/usr/local/lib/python3.12/site-packages"), # Intel Mac
|
||||
Path("/usr/local/lib/python3.11/site-packages"),
|
||||
]
|
||||
for hp in homebrew_paths:
|
||||
pcbnew_path = hp / "pcbnew.py"
|
||||
if pcbnew_path.exists():
|
||||
paths.append(hp)
|
||||
|
||||
if not paths:
|
||||
logger.warning(f"No KiCAD Python paths found for {PlatformHelper.get_platform_name()}")
|
||||
@@ -142,6 +160,8 @@ class PlatformHelper:
|
||||
elif PlatformHelper.is_macos():
|
||||
patterns = [
|
||||
"/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/*.kicad_sym",
|
||||
"/Applications/KiCAD/KiCad.app/Contents/SharedSupport/symbols/*.kicad_sym",
|
||||
str(Path.home() / "Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols/*.kicad_sym"),
|
||||
]
|
||||
|
||||
# Add user library paths for all platforms
|
||||
|
||||
@@ -75,13 +75,39 @@ function findPythonExecutable(scriptPath: string): string {
|
||||
return kicadPython;
|
||||
}
|
||||
} else if (isMac) {
|
||||
// macOS: Try KiCAD's bundled Python (check multiple versions)
|
||||
const kicadPythonVersions = ['3.9', '3.10', '3.11', '3.12'];
|
||||
for (const version of kicadPythonVersions) {
|
||||
const kicadPython = `/Applications/KiCad/KiCad.app/Contents/Frameworks/Python.framework/Versions/${version}/bin/python3`;
|
||||
if (existsSync(kicadPython)) {
|
||||
logger.info(`Found KiCAD bundled Python at: ${kicadPython}`);
|
||||
return kicadPython;
|
||||
// macOS: Try KiCAD's bundled Python (check multiple versions and locations)
|
||||
const kicadPythonVersions = ['3.9', '3.10', '3.11', '3.12', '3.13'];
|
||||
|
||||
// Standard KiCAD installation paths
|
||||
const kicadAppPaths = [
|
||||
'/Applications/KiCad/KiCad.app',
|
||||
'/Applications/KiCAD/KiCad.app', // Alternative capitalization
|
||||
`${process.env.HOME}/Applications/KiCad/KiCad.app`, // User Applications folder
|
||||
];
|
||||
|
||||
// Check all KiCAD app locations with all Python versions
|
||||
for (const appPath of kicadAppPaths) {
|
||||
for (const version of kicadPythonVersions) {
|
||||
const kicadPython = `${appPath}/Contents/Frameworks/Python.framework/Versions/${version}/bin/python3`;
|
||||
if (existsSync(kicadPython)) {
|
||||
logger.info(`Found KiCAD bundled Python at: ${kicadPython}`);
|
||||
return kicadPython;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to Homebrew Python (if pcbnew is installed via pip)
|
||||
const homebrewPaths = [
|
||||
'/opt/homebrew/bin/python3', // Apple Silicon
|
||||
'/usr/local/bin/python3', // Intel Mac
|
||||
'/opt/homebrew/bin/python3.12',
|
||||
'/opt/homebrew/bin/python3.11',
|
||||
];
|
||||
|
||||
for (const path of homebrewPaths) {
|
||||
if (existsSync(path)) {
|
||||
logger.info(`Found Homebrew Python at: ${path} (ensure pcbnew is importable)`);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
} else if (isLinux) {
|
||||
|
||||
Reference in New Issue
Block a user