Skip to main content
Glama
routing.py28.5 kB
""" Routing-related command implementations for KiCAD interface """ import os import pcbnew import logging import math from typing import Dict, Any, Optional, List, Tuple logger = logging.getLogger('kicad_interface') class RoutingCommands: """Handles routing-related KiCAD operations""" def __init__(self, board: Optional[pcbnew.BOARD] = None): """Initialize with optional board instance""" self.board = board def add_net(self, params: Dict[str, Any]) -> Dict[str, Any]: """Add a new net to the PCB""" try: if not self.board: return { "success": False, "message": "No board is loaded", "errorDetails": "Load or create a board first" } name = params.get("name") net_class = params.get("class") if not name: return { "success": False, "message": "Missing net name", "errorDetails": "name parameter is required" } # Create new net netinfo = self.board.GetNetInfo() nets_map = netinfo.NetsByName() if nets_map.has_key(name): net = nets_map[name] else: net = pcbnew.NETINFO_ITEM(self.board, name) self.board.Add(net) # Set net class if provided if net_class: net_classes = self.board.GetNetClasses() if net_classes.Find(net_class): net.SetClass(net_classes.Find(net_class)) return { "success": True, "message": f"Added net: {name}", "net": { "name": name, "class": net_class if net_class else "Default", "netcode": net.GetNetCode() } } except Exception as e: logger.error(f"Error adding net: {str(e)}") return { "success": False, "message": "Failed to add net", "errorDetails": str(e) } def route_trace(self, params: Dict[str, Any]) -> Dict[str, Any]: """Route a trace between two points or pads""" try: if not self.board: return { "success": False, "message": "No board is loaded", "errorDetails": "Load or create a board first" } start = params.get("start") end = params.get("end") layer = params.get("layer", "F.Cu") width = params.get("width") net = params.get("net") via = params.get("via", False) if not start or not end: return { "success": False, "message": "Missing parameters", "errorDetails": "start and end points are required" } # Get layer ID layer_id = self.board.GetLayerID(layer) if layer_id < 0: return { "success": False, "message": "Invalid layer", "errorDetails": f"Layer '{layer}' does not exist" } # Get start point start_point = self._get_point(start) end_point = self._get_point(end) # Create track segment track = pcbnew.PCB_TRACK(self.board) track.SetStart(start_point) track.SetEnd(end_point) track.SetLayer(layer_id) # Set width (default to board's current track width) if width: track.SetWidth(int(width * 1000000)) # Convert mm to nm else: track.SetWidth(self.board.GetDesignSettings().GetCurrentTrackWidth()) # Set net if provided if net: netinfo = self.board.GetNetInfo() nets_map = netinfo.NetsByName() if nets_map.has_key(net): net_obj = nets_map[net] track.SetNet(net_obj) # Add track to board self.board.Add(track) # Add via if requested and net is specified if via and net: via_point = end_point self.add_via({ "position": { "x": via_point.x / 1000000, "y": via_point.y / 1000000, "unit": "mm" }, "net": net }) return { "success": True, "message": "Added trace", "trace": { "start": { "x": start_point.x / 1000000, "y": start_point.y / 1000000, "unit": "mm" }, "end": { "x": end_point.x / 1000000, "y": end_point.y / 1000000, "unit": "mm" }, "layer": layer, "width": track.GetWidth() / 1000000, "net": net } } except Exception as e: logger.error(f"Error routing trace: {str(e)}") return { "success": False, "message": "Failed to route trace", "errorDetails": str(e) } def add_via(self, params: Dict[str, Any]) -> Dict[str, Any]: """Add a via at the specified location""" try: if not self.board: return { "success": False, "message": "No board is loaded", "errorDetails": "Load or create a board first" } position = params.get("position") size = params.get("size") drill = params.get("drill") net = params.get("net") from_layer = params.get("from_layer", "F.Cu") to_layer = params.get("to_layer", "B.Cu") if not position: return { "success": False, "message": "Missing position", "errorDetails": "position parameter is required" } # Create via via = pcbnew.PCB_VIA(self.board) # Set position scale = 1000000 if position["unit"] == "mm" else 25400000 # mm or inch to nm x_nm = int(position["x"] * scale) y_nm = int(position["y"] * scale) via.SetPosition(pcbnew.VECTOR2I(x_nm, y_nm)) # Set size and drill (default to board's current via settings) design_settings = self.board.GetDesignSettings() via.SetWidth(int(size * 1000000) if size else design_settings.GetCurrentViaSize()) via.SetDrill(int(drill * 1000000) if drill else design_settings.GetCurrentViaDrill()) # Set layers from_id = self.board.GetLayerID(from_layer) to_id = self.board.GetLayerID(to_layer) if from_id < 0 or to_id < 0: return { "success": False, "message": "Invalid layer", "errorDetails": "Specified layers do not exist" } via.SetLayerPair(from_id, to_id) # Set net if provided if net: netinfo = self.board.GetNetInfo() nets_map = netinfo.NetsByName() if nets_map.has_key(net): net_obj = nets_map[net] via.SetNet(net_obj) # Add via to board self.board.Add(via) return { "success": True, "message": "Added via", "via": { "position": { "x": position["x"], "y": position["y"], "unit": position["unit"] }, "size": via.GetWidth() / 1000000, "drill": via.GetDrill() / 1000000, "from_layer": from_layer, "to_layer": to_layer, "net": net } } except Exception as e: logger.error(f"Error adding via: {str(e)}") return { "success": False, "message": "Failed to add via", "errorDetails": str(e) } def delete_trace(self, params: Dict[str, Any]) -> Dict[str, Any]: """Delete a trace from the PCB""" try: if not self.board: return { "success": False, "message": "No board is loaded", "errorDetails": "Load or create a board first" } trace_uuid = params.get("traceUuid") position = params.get("position") if not trace_uuid and not position: return { "success": False, "message": "Missing parameters", "errorDetails": "Either traceUuid or position must be provided" } # Find track by UUID if trace_uuid: track = None for item in self.board.Tracks(): if str(item.m_Uuid) == trace_uuid: track = item break if not track: return { "success": False, "message": "Track not found", "errorDetails": f"Could not find track with UUID: {trace_uuid}" } self.board.Remove(track) return { "success": True, "message": f"Deleted track: {trace_uuid}" } # Find track by position if position: scale = 1000000 if position["unit"] == "mm" else 25400000 # mm or inch to nm x_nm = int(position["x"] * scale) y_nm = int(position["y"] * scale) point = pcbnew.VECTOR2I(x_nm, y_nm) # Find closest track closest_track = None min_distance = float('inf') for track in self.board.Tracks(): dist = self._point_to_track_distance(point, track) if dist < min_distance: min_distance = dist closest_track = track if closest_track and min_distance < 1000000: # Within 1mm self.board.Remove(closest_track) return { "success": True, "message": "Deleted track at specified position" } else: return { "success": False, "message": "No track found", "errorDetails": "No track found near specified position" } except Exception as e: logger.error(f"Error deleting trace: {str(e)}") return { "success": False, "message": "Failed to delete trace", "errorDetails": str(e) } def get_nets_list(self, params: Dict[str, Any]) -> Dict[str, Any]: """Get a list of all nets in the PCB""" try: if not self.board: return { "success": False, "message": "No board is loaded", "errorDetails": "Load or create a board first" } nets = [] netinfo = self.board.GetNetInfo() for net_code in range(netinfo.GetNetCount()): net = netinfo.GetNetItem(net_code) if net: nets.append({ "name": net.GetNetname(), "code": net.GetNetCode(), "class": net.GetClassName() }) return { "success": True, "nets": nets } except Exception as e: logger.error(f"Error getting nets list: {str(e)}") return { "success": False, "message": "Failed to get nets list", "errorDetails": str(e) } def create_netclass(self, params: Dict[str, Any]) -> Dict[str, Any]: """Create a new net class with specified properties""" try: if not self.board: return { "success": False, "message": "No board is loaded", "errorDetails": "Load or create a board first" } name = params.get("name") clearance = params.get("clearance") track_width = params.get("trackWidth") via_diameter = params.get("viaDiameter") via_drill = params.get("viaDrill") uvia_diameter = params.get("uviaDiameter") uvia_drill = params.get("uviaDrill") diff_pair_width = params.get("diffPairWidth") diff_pair_gap = params.get("diffPairGap") nets = params.get("nets", []) if not name: return { "success": False, "message": "Missing netclass name", "errorDetails": "name parameter is required" } # Get net classes net_classes = self.board.GetNetClasses() # Create new net class if it doesn't exist if not net_classes.Find(name): netclass = pcbnew.NETCLASS(name) net_classes.Add(netclass) else: netclass = net_classes.Find(name) # Set properties scale = 1000000 # mm to nm if clearance is not None: netclass.SetClearance(int(clearance * scale)) if track_width is not None: netclass.SetTrackWidth(int(track_width * scale)) if via_diameter is not None: netclass.SetViaDiameter(int(via_diameter * scale)) if via_drill is not None: netclass.SetViaDrill(int(via_drill * scale)) if uvia_diameter is not None: netclass.SetMicroViaDiameter(int(uvia_diameter * scale)) if uvia_drill is not None: netclass.SetMicroViaDrill(int(uvia_drill * scale)) if diff_pair_width is not None: netclass.SetDiffPairWidth(int(diff_pair_width * scale)) if diff_pair_gap is not None: netclass.SetDiffPairGap(int(diff_pair_gap * scale)) # Add nets to net class netinfo = self.board.GetNetInfo() nets_map = netinfo.NetsByName() for net_name in nets: if nets_map.has_key(net_name): net = nets_map[net_name] net.SetClass(netclass) return { "success": True, "message": f"Created net class: {name}", "netClass": { "name": name, "clearance": netclass.GetClearance() / scale, "trackWidth": netclass.GetTrackWidth() / scale, "viaDiameter": netclass.GetViaDiameter() / scale, "viaDrill": netclass.GetViaDrill() / scale, "uviaDiameter": netclass.GetMicroViaDiameter() / scale, "uviaDrill": netclass.GetMicroViaDrill() / scale, "diffPairWidth": netclass.GetDiffPairWidth() / scale, "diffPairGap": netclass.GetDiffPairGap() / scale, "nets": nets } } except Exception as e: logger.error(f"Error creating net class: {str(e)}") return { "success": False, "message": "Failed to create net class", "errorDetails": str(e) } def add_copper_pour(self, params: Dict[str, Any]) -> Dict[str, Any]: """Add a copper pour (zone) to the PCB""" try: if not self.board: return { "success": False, "message": "No board is loaded", "errorDetails": "Load or create a board first" } layer = params.get("layer", "F.Cu") net = params.get("net") clearance = params.get("clearance") min_width = params.get("minWidth", 0.2) points = params.get("points", []) priority = params.get("priority", 0) fill_type = params.get("fillType", "solid") # solid or hatched if not points or len(points) < 3: return { "success": False, "message": "Missing points", "errorDetails": "At least 3 points are required for copper pour outline" } # Get layer ID layer_id = self.board.GetLayerID(layer) if layer_id < 0: return { "success": False, "message": "Invalid layer", "errorDetails": f"Layer '{layer}' does not exist" } # Create zone zone = pcbnew.ZONE(self.board) zone.SetLayer(layer_id) # Set net if provided if net: netinfo = self.board.GetNetInfo() nets_map = netinfo.NetsByName() if nets_map.has_key(net): net_obj = nets_map[net] zone.SetNet(net_obj) # Set zone properties scale = 1000000 # mm to nm zone.SetAssignedPriority(priority) if clearance is not None: zone.SetLocalClearance(int(clearance * scale)) zone.SetMinThickness(int(min_width * scale)) # Set fill type if fill_type == "hatched": zone.SetFillMode(pcbnew.ZONE_FILL_MODE_HATCH_PATTERN) else: zone.SetFillMode(pcbnew.ZONE_FILL_MODE_POLYGONS) # Create outline outline = zone.Outline() outline.NewOutline() # Create a new outline contour first # Add points to outline for point in points: scale = 1000000 if point.get("unit", "mm") == "mm" else 25400000 x_nm = int(point["x"] * scale) y_nm = int(point["y"] * scale) outline.Append(pcbnew.VECTOR2I(x_nm, y_nm)) # Add point to outline # Add zone to board self.board.Add(zone) # Fill zone # Note: Zone filling can cause issues with SWIG API # Comment out for now - zones will be filled when board is saved/opened in KiCAD # filler = pcbnew.ZONE_FILLER(self.board) # filler.Fill(self.board.Zones()) return { "success": True, "message": "Added copper pour", "pour": { "layer": layer, "net": net, "clearance": clearance, "minWidth": min_width, "priority": priority, "fillType": fill_type, "pointCount": len(points) } } except Exception as e: logger.error(f"Error adding copper pour: {str(e)}") return { "success": False, "message": "Failed to add copper pour", "errorDetails": str(e) } def route_differential_pair(self, params: Dict[str, Any]) -> Dict[str, Any]: """Route a differential pair between two sets of points or pads""" try: if not self.board: return { "success": False, "message": "No board is loaded", "errorDetails": "Load or create a board first" } start_pos = params.get("startPos") end_pos = params.get("endPos") net_pos = params.get("netPos") net_neg = params.get("netNeg") layer = params.get("layer", "F.Cu") width = params.get("width") gap = params.get("gap") if not start_pos or not end_pos or not net_pos or not net_neg: return { "success": False, "message": "Missing parameters", "errorDetails": "startPos, endPos, netPos, and netNeg are required" } # Get layer ID layer_id = self.board.GetLayerID(layer) if layer_id < 0: return { "success": False, "message": "Invalid layer", "errorDetails": f"Layer '{layer}' does not exist" } # Get nets netinfo = self.board.GetNetInfo() nets_map = netinfo.NetsByName() net_pos_obj = nets_map[net_pos] if nets_map.has_key(net_pos) else None net_neg_obj = nets_map[net_neg] if nets_map.has_key(net_neg) else None if not net_pos_obj or not net_neg_obj: return { "success": False, "message": "Nets not found", "errorDetails": "One or both nets specified for the differential pair do not exist" } # Get start and end points start_point = self._get_point(start_pos) end_point = self._get_point(end_pos) # Calculate offset vectors for the two traces # First, get the direction vector from start to end dx = end_point.x - start_point.x dy = end_point.y - start_point.y length = math.sqrt(dx * dx + dy * dy) if length <= 0: return { "success": False, "message": "Invalid points", "errorDetails": "Start and end points must be different" } # Normalize direction vector dx /= length dy /= length # Get perpendicular vector px = -dy py = dx # Set default gap if not provided if gap is None: gap = 0.2 # mm # Convert to nm gap_nm = int(gap * 1000000) # Calculate offsets offset_x = int(px * gap_nm / 2) offset_y = int(py * gap_nm / 2) # Create positive and negative trace points pos_start = pcbnew.VECTOR2I(int(start_point.x + offset_x), int(start_point.y + offset_y)) pos_end = pcbnew.VECTOR2I(int(end_point.x + offset_x), int(end_point.y + offset_y)) neg_start = pcbnew.VECTOR2I(int(start_point.x - offset_x), int(start_point.y - offset_y)) neg_end = pcbnew.VECTOR2I(int(end_point.x - offset_x), int(end_point.y - offset_y)) # Create positive trace pos_track = pcbnew.PCB_TRACK(self.board) pos_track.SetStart(pos_start) pos_track.SetEnd(pos_end) pos_track.SetLayer(layer_id) pos_track.SetNet(net_pos_obj) # Create negative trace neg_track = pcbnew.PCB_TRACK(self.board) neg_track.SetStart(neg_start) neg_track.SetEnd(neg_end) neg_track.SetLayer(layer_id) neg_track.SetNet(net_neg_obj) # Set width if width: trace_width_nm = int(width * 1000000) pos_track.SetWidth(trace_width_nm) neg_track.SetWidth(trace_width_nm) else: # Get default width from design rules or net class trace_width = self.board.GetDesignSettings().GetCurrentTrackWidth() pos_track.SetWidth(trace_width) neg_track.SetWidth(trace_width) # Add tracks to board self.board.Add(pos_track) self.board.Add(neg_track) return { "success": True, "message": "Added differential pair traces", "diffPair": { "posNet": net_pos, "negNet": net_neg, "layer": layer, "width": pos_track.GetWidth() / 1000000, "gap": gap, "length": length / 1000000 } } except Exception as e: logger.error(f"Error routing differential pair: {str(e)}") return { "success": False, "message": "Failed to route differential pair", "errorDetails": str(e) } def _get_point(self, point_spec: Dict[str, Any]) -> pcbnew.VECTOR2I: """Convert point specification to KiCAD point""" if "x" in point_spec and "y" in point_spec: scale = 1000000 if point_spec.get("unit", "mm") == "mm" else 25400000 x_nm = int(point_spec["x"] * scale) y_nm = int(point_spec["y"] * scale) return pcbnew.VECTOR2I(x_nm, y_nm) elif "pad" in point_spec and "componentRef" in point_spec: module = self.board.FindFootprintByReference(point_spec["componentRef"]) if module: pad = module.FindPadByName(point_spec["pad"]) if pad: return pad.GetPosition() raise ValueError("Invalid point specification") def _point_to_track_distance(self, point: pcbnew.VECTOR2I, track: pcbnew.PCB_TRACK) -> float: """Calculate distance from point to track segment""" start = track.GetStart() end = track.GetEnd() # Vector from start to end v = pcbnew.VECTOR2I(end.x - start.x, end.y - start.y) # Vector from start to point w = pcbnew.VECTOR2I(point.x - start.x, point.y - start.y) # Length of track squared c1 = v.x * v.x + v.y * v.y if c1 == 0: return self._point_distance(point, start) # Projection coefficient c2 = float(w.x * v.x + w.y * v.y) / c1 if c2 < 0: return self._point_distance(point, start) elif c2 > 1: return self._point_distance(point, end) # Point on line proj = pcbnew.VECTOR2I( int(start.x + c2 * v.x), int(start.y + c2 * v.y) ) return self._point_distance(point, proj) def _point_distance(self, p1: pcbnew.VECTOR2I, p2: pcbnew.VECTOR2I) -> float: """Calculate distance between two points""" dx = p1.x - p2.x dy = p1.y - p2.y return (dx * dx + dy * dy) ** 0.5

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mixelpixx/KiCAD-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server