From 59bd4c4acfd4545c1c365db3dcad47ac3ba1f949 Mon Sep 17 00:00:00 2001 From: Eugene Mikhantyev Date: Sun, 15 Mar 2026 23:17:25 +0000 Subject: [PATCH] style: apply Black formatting to changed files Co-Authored-By: Claude Sonnet 4.6 --- python/commands/schematic_analysis.py | 215 ++++-- python/kicad_interface.py | 36 +- python/schemas/tool_schemas.py | 954 +++++++++++------------- python/tests/conftest.py | 1 + python/tests/test_schematic_analysis.py | 135 +++- 5 files changed, 731 insertions(+), 610 deletions(-) diff --git a/python/commands/schematic_analysis.py b/python/commands/schematic_analysis.py index 487db15..09d284d 100644 --- a/python/commands/schematic_analysis.py +++ b/python/commands/schematic_analysis.py @@ -22,6 +22,7 @@ logger = logging.getLogger("kicad_interface") # S-expression parsing helpers # --------------------------------------------------------------------------- + def _load_sexp(schematic_path: Path) -> list: """Load schematic file and return parsed S-expression data.""" with open(schematic_path, "r", encoding="utf-8") as f: @@ -122,20 +123,21 @@ def _parse_symbols(sexp_data: list) -> List[Dict[str, Any]]: reference = str(sub[2]).strip('"') is_power = reference.startswith("#PWR") or reference.startswith("#FLG") - symbols.append({ - "reference": reference, - "lib_id": lib_id, - "x": x, - "y": y, - "rotation": rotation, - "mirror_x": mirror_x, - "mirror_y": mirror_y, - "is_power": is_power, - }) + symbols.append( + { + "reference": reference, + "lib_id": lib_id, + "x": x, + "y": y, + "rotation": rotation, + "mirror_x": mirror_x, + "mirror_y": mirror_y, + "is_power": is_power, + } + ) return symbols - def _parse_lib_symbol_graphics(symbol_def: list) -> List[Tuple[float, float]]: """ Parse graphical body elements from a lib_symbol definition and return @@ -167,22 +169,36 @@ def _parse_lib_symbol_graphics(symbol_def: list) -> List[Tuple[float, float]]: for sub in sexp[1:]: if isinstance(sub, list) and len(sub) > 0 and sub[0] == Symbol("pts"): for pt in sub[1:]: - if isinstance(pt, list) and len(pt) >= 3 and pt[0] == Symbol("xy"): + if ( + isinstance(pt, list) + and len(pt) >= 3 + and pt[0] == Symbol("xy") + ): points.append((float(pt[1]), float(pt[2]))) elif tag == Symbol("circle"): # (circle (center x y) (radius r) ...) cx, cy, r = 0.0, 0.0, 0.0 for sub in sexp[1:]: - if isinstance(sub, list) and len(sub) >= 3 and sub[0] == Symbol("center"): + if ( + isinstance(sub, list) + and len(sub) >= 3 + and sub[0] == Symbol("center") + ): cx, cy = float(sub[1]), float(sub[2]) - elif isinstance(sub, list) and len(sub) >= 2 and sub[0] == Symbol("radius"): + elif ( + isinstance(sub, list) + and len(sub) >= 2 + and sub[0] == Symbol("radius") + ): r = float(sub[1]) if r > 0: - points.extend([ - (cx - r, cy - r), - (cx + r, cy + r), - ]) + points.extend( + [ + (cx - r, cy - r), + (cx + r, cy + r), + ] + ) elif tag == Symbol("arc"): # (arc (start x y) (mid x y) (end x y) ...) @@ -196,7 +212,11 @@ def _parse_lib_symbol_graphics(symbol_def: list) -> List[Tuple[float, float]]: for sub in sexp[1:]: if isinstance(sub, list) and len(sub) > 0 and sub[0] == Symbol("pts"): for pt in sub[1:]: - if isinstance(pt, list) and len(pt) >= 3 and pt[0] == Symbol("xy"): + if ( + isinstance(pt, list) + and len(pt) >= 3 + and pt[0] == Symbol("xy") + ): points.append((float(pt[1]), float(pt[2]))) else: @@ -223,8 +243,11 @@ def _extract_lib_symbols(sexp_data: list) -> Dict[str, Dict]: """ lib_symbols_section = None for item in sexp_data: - if (isinstance(item, list) and len(item) > 0 - and item[0] == Symbol("lib_symbols")): + if ( + isinstance(item, list) + and len(item) > 0 + and item[0] == Symbol("lib_symbols") + ): lib_symbols_section = item break @@ -233,8 +256,7 @@ def _extract_lib_symbols(sexp_data: list) -> Dict[str, Dict]: result: Dict[str, Dict] = {} for item in lib_symbols_section[1:]: - if (isinstance(item, list) and len(item) > 1 - and item[0] == Symbol("symbol")): + if isinstance(item, list) and len(item) > 1 and item[0] == Symbol("symbol"): symbol_name = str(item[1]).strip('"') result[symbol_name] = { "pins": PinLocator.parse_symbol_definition(item), @@ -247,6 +269,7 @@ def _extract_lib_symbols(sexp_data: list) -> Dict[str, Dict]: # Geometry helpers # --------------------------------------------------------------------------- + def compute_symbol_bbox( schematic_path: Path, reference: str, @@ -266,8 +289,14 @@ def compute_symbol_bbox( def _line_segment_intersects_aabb( - x1: float, y1: float, x2: float, y2: float, - box_min_x: float, box_min_y: float, box_max_x: float, box_max_y: float, + x1: float, + y1: float, + x2: float, + y2: float, + box_min_x: float, + box_min_y: float, + box_max_x: float, + box_max_y: float, ) -> bool: """ Test whether line segment (x1,y1)→(x2,y2) intersects an axis-aligned bounding box. @@ -301,8 +330,12 @@ def _line_segment_intersects_aabb( def _point_in_rect( - px: float, py: float, - min_x: float, min_y: float, max_x: float, max_y: float, + px: float, + py: float, + min_x: float, + min_y: float, + max_x: float, + max_y: float, ) -> bool: """Check if a point is within a rectangle.""" return min_x <= px <= max_x and min_y <= py <= max_y @@ -325,10 +358,13 @@ def _aabb_overlap( def _transform_local_point( - lx: float, ly: float, - sym_x: float, sym_y: float, + lx: float, + ly: float, + sym_x: float, + sym_y: float, rotation: float, - mirror_x: bool, mirror_y: bool, + mirror_x: bool, + mirror_y: bool, ) -> Tuple[float, float]: """ Transform a point from local symbol coordinates to absolute schematic @@ -424,11 +460,11 @@ def _compute_symbol_bbox_direct( return (min_x, min_y, max_x, max_y) - # --------------------------------------------------------------------------- # Tool 3: find_overlapping_elements # --------------------------------------------------------------------------- + def find_overlapping_elements( schematic_path: Path, tolerance: float = 0.5 ) -> Dict[str, Any]: @@ -453,7 +489,11 @@ def find_overlapping_elements( lib_defs = _extract_lib_symbols(sexp_data) # --- Symbol-symbol overlap using bounding-box intersection (O(n²)) --- - non_template_symbols = [s for s in symbols if not s["reference"].startswith("_TEMPLATE") and s["reference"]] + non_template_symbols = [ + s + for s in symbols + if not s["reference"].startswith("_TEMPLATE") and s["reference"] + ] # Pre-compute bounding boxes for all non-template symbols symbol_bboxes = [] @@ -463,7 +503,9 @@ def find_overlapping_elements( graphics_points = lib_data.get("graphics_points", []) bbox = None if pin_defs: - bbox = _compute_symbol_bbox_direct(sym, pin_defs, graphics_points=graphics_points) + bbox = _compute_symbol_bbox_direct( + sym, pin_defs, graphics_points=graphics_points + ) symbol_bboxes.append((sym, bbox)) for i in range(len(symbol_bboxes)): @@ -482,10 +524,16 @@ def find_overlapping_elements( if overlap_detected: entry = { - "element1": {"reference": s1["reference"], "libId": s1["lib_id"], - "position": {"x": s1["x"], "y": s1["y"]}}, - "element2": {"reference": s2["reference"], "libId": s2["lib_id"], - "position": {"x": s2["x"], "y": s2["y"]}}, + "element1": { + "reference": s1["reference"], + "libId": s1["lib_id"], + "position": {"x": s1["x"], "y": s1["y"]}, + }, + "element2": { + "reference": s2["reference"], + "libId": s2["lib_id"], + "position": {"x": s2["x"], "y": s2["y"]}, + }, "distance": round(dist, 4), } # Flag power symbol pairs specifically @@ -502,13 +550,21 @@ def find_overlapping_elements( l2 = labels[j] dist = _distance((l1["x"], l1["y"]), (l2["x"], l2["y"])) if dist < tolerance: - overlapping_labels.append({ - "element1": {"name": l1["name"], "type": l1["type"], - "position": {"x": l1["x"], "y": l1["y"]}}, - "element2": {"name": l2["name"], "type": l2["type"], - "position": {"x": l2["x"], "y": l2["y"]}}, - "distance": round(dist, 4), - }) + overlapping_labels.append( + { + "element1": { + "name": l1["name"], + "type": l1["type"], + "position": {"x": l1["x"], "y": l1["y"]}, + }, + "element2": { + "name": l2["name"], + "type": l2["type"], + "position": {"x": l2["x"], "y": l2["y"]}, + }, + "distance": round(dist, 4), + } + ) # --- Wire-wire collinear overlap --- for i in range(len(wires)): @@ -574,8 +630,14 @@ def _check_wire_overlap( min2, max2 = min(proj_s2, proj_e2), max(proj_s2, proj_e2) if min1 < max2 and min2 < max1: return { - "wire1": {"start": {"x": s1[0], "y": s1[1]}, "end": {"x": e1[0], "y": e1[1]}}, - "wire2": {"start": {"x": s2[0], "y": s2[1]}, "end": {"x": e2[0], "y": e2[1]}}, + "wire1": { + "start": {"x": s1[0], "y": s1[1]}, + "end": {"x": e1[0], "y": e1[1]}, + }, + "wire2": { + "start": {"x": s2[0], "y": s2[1]}, + "end": {"x": e2[0], "y": e2[1]}, + }, "type": "collinear_overlap", } @@ -586,9 +648,13 @@ def _check_wire_overlap( # Tool 4: get_elements_in_region # --------------------------------------------------------------------------- + def get_elements_in_region( schematic_path: Path, - x1: float, y1: float, x2: float, y2: float, + x1: float, + y1: float, + x2: float, + y2: float, ) -> Dict[str, Any]: """ List all wires, labels, and symbols within a rectangular region. @@ -637,23 +703,31 @@ def get_elements_in_region( region_wires = [] for w in wires: s, e = w["start"], w["end"] - if (_point_in_rect(s[0], s[1], min_x, min_y, max_x, max_y) or - _point_in_rect(e[0], e[1], min_x, min_y, max_x, max_y) or - _line_segment_intersects_aabb(s[0], s[1], e[0], e[1], min_x, min_y, max_x, max_y)): - region_wires.append({ - "start": {"x": s[0], "y": s[1]}, - "end": {"x": e[0], "y": e[1]}, - }) + if ( + _point_in_rect(s[0], s[1], min_x, min_y, max_x, max_y) + or _point_in_rect(e[0], e[1], min_x, min_y, max_x, max_y) + or _line_segment_intersects_aabb( + s[0], s[1], e[0], e[1], min_x, min_y, max_x, max_y + ) + ): + region_wires.append( + { + "start": {"x": s[0], "y": s[1]}, + "end": {"x": e[0], "y": e[1]}, + } + ) # Labels: include if position is within bounds region_labels = [] for lbl in labels: if _point_in_rect(lbl["x"], lbl["y"], min_x, min_y, max_x, max_y): - region_labels.append({ - "name": lbl["name"], - "type": lbl["type"], - "position": {"x": lbl["x"], "y": lbl["y"]}, - }) + region_labels.append( + { + "name": lbl["name"], + "type": lbl["type"], + "position": {"x": lbl["x"], "y": lbl["y"]}, + } + ) return { "symbols": region_symbols, @@ -671,6 +745,7 @@ def get_elements_in_region( # Tool 5: check_wire_collisions # --------------------------------------------------------------------------- + def _compute_pin_positions_direct( sym: Dict[str, Any], pin_defs: Dict[str, Dict] ) -> Dict[str, List[float]]: @@ -748,7 +823,9 @@ def find_wires_crossing_symbols(schematic_path: Path) -> List[Dict[str, Any]]: continue graphics_points = lib_data.get("graphics_points", []) - bbox = _compute_symbol_bbox_direct(sym, pin_defs, margin=margin, graphics_points=graphics_points) + bbox = _compute_symbol_bbox_direct( + sym, pin_defs, margin=margin, graphics_points=graphics_points + ) if bbox is None: continue @@ -757,11 +834,13 @@ def find_wires_crossing_symbols(schematic_path: Path) -> List[Dict[str, Any]]: for pos in pin_positions.values(): pin_set.add((pos[0], pos[1])) - symbol_data.append({ - "sym": sym, - "bbox": bbox, - "pin_set": pin_set, - }) + symbol_data.append( + { + "sym": sym, + "bbox": bbox, + "pin_set": pin_set, + } + ) # Test each wire against each symbol bbox for w in wires: @@ -810,7 +889,8 @@ def find_wires_crossing_symbols(schematic_path: Path) -> List[Dict[str, Any]]: continue # Wire terminates at pin from outside sym = sd["sym"] - collisions.append({ + collisions.append( + { "wire": { "start": {"x": sx, "y": sy}, "end": {"x": ex, "y": ey}, @@ -821,6 +901,7 @@ def find_wires_crossing_symbols(schematic_path: Path) -> List[Dict[str, Any]]: "position": {"x": sym["x"], "y": sym["y"]}, }, "intersectionType": "passes_through", - }) + } + ) return collisions diff --git a/python/kicad_interface.py b/python/kicad_interface.py index 2c5ef16..7a92cbe 100644 --- a/python/kicad_interface.py +++ b/python/kicad_interface.py @@ -2597,19 +2597,34 @@ class KiCADInterface: svg_output = None try: - cmd = [kicad_cli, "sch", "export", "svg", "--output", tmp_dir, schematic_path] + cmd = [ + kicad_cli, + "sch", + "export", + "svg", + "--output", + tmp_dir, + schematic_path, + ] result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) if result.returncode != 0: - return {"success": False, "message": f"SVG export failed: {result.stderr}"} + return { + "success": False, + "message": f"SVG export failed: {result.stderr}", + } # kicad-cli names the file after the schematic svg_files = [f for f in os.listdir(tmp_dir) if f.endswith(".svg")] if not svg_files: - return {"success": False, "message": "kicad-cli produced no SVG output"} + return { + "success": False, + "message": "kicad-cli produced no SVG output", + } svg_output = os.path.join(tmp_dir, svg_files[0]) import xml.etree.ElementTree as ET + tree = ET.parse(svg_output) root = tree.getroot() @@ -2642,8 +2657,13 @@ class KiCADInterface: try: from cairosvg import svg2png except ImportError: - return {"success": False, "message": "PNG export requires the 'cairosvg' package. Install it with: pip install cairosvg"} - png_data = svg2png(url=cropped_svg_path, output_width=width, output_height=height) + return { + "success": False, + "message": "PNG export requires the 'cairosvg' package. Install it with: pip install cairosvg", + } + png_data = svg2png( + url=cropped_svg_path, output_width=width, output_height=height + ) return { "success": True, "imageData": base64.b64encode(png_data).decode("utf-8"), @@ -2651,15 +2671,16 @@ class KiCADInterface: } finally: import shutil + shutil.rmtree(tmp_dir, ignore_errors=True) except Exception as e: logger.error(f"Error in get_schematic_view_region: {e}") import traceback + logger.error(traceback.format_exc()) return {"success": False, "message": str(e)} - def _handle_find_overlapping_elements(self, params): """Detect spatially overlapping symbols, wires, and labels""" logger.info("Finding overlapping elements in schematic") @@ -2681,6 +2702,7 @@ class KiCADInterface: except Exception as e: logger.error(f"Error finding overlapping elements: {e}") import traceback + logger.error(traceback.format_exc()) return {"success": False, "message": str(e)} @@ -2709,6 +2731,7 @@ class KiCADInterface: except Exception as e: logger.error(f"Error getting elements in region: {e}") import traceback + logger.error(traceback.format_exc()) return {"success": False, "message": str(e)} @@ -2733,6 +2756,7 @@ class KiCADInterface: except Exception as e: logger.error(f"Error checking wire collisions: {e}") import traceback + logger.error(traceback.format_exc()) return {"success": False, "message": str(e)} diff --git a/python/schemas/tool_schemas.py b/python/schemas/tool_schemas.py index 6eb3c4c..d71a50c 100644 --- a/python/schemas/tool_schemas.py +++ b/python/schemas/tool_schemas.py @@ -27,19 +27,19 @@ PROJECT_TOOLS = [ "projectName": { "type": "string", "description": "Name of the project (used for file naming)", - "minLength": 1 + "minLength": 1, }, "path": { "type": "string", - "description": "Directory path where project will be created (defaults to current working directory)" + "description": "Directory path where project will be created (defaults to current working directory)", }, "template": { "type": "string", - "description": "Optional path to template board file to copy settings from" - } + "description": "Optional path to template board file to copy settings from", + }, }, - "required": ["projectName"] - } + "required": ["projectName"], + }, }, { "name": "open_project", @@ -50,11 +50,11 @@ PROJECT_TOOLS = [ "properties": { "filename": { "type": "string", - "description": "Path to .kicad_pro or .kicad_pcb file" + "description": "Path to .kicad_pro or .kicad_pcb file", } }, - "required": ["filename"] - } + "required": ["filename"], + }, }, { "name": "save_project", @@ -65,10 +65,10 @@ PROJECT_TOOLS = [ "properties": { "filename": { "type": "string", - "description": "Optional new path to save the board (if not provided, saves to current location)" + "description": "Optional new path to save the board (if not provided, saves to current location)", } - } - } + }, + }, }, { "name": "snapshot_project", @@ -79,28 +79,25 @@ PROJECT_TOOLS = [ "properties": { "step": { "type": "string", - "description": "Step number or name to include in snapshot folder name, e.g. '1' or '2'" + "description": "Step number or name to include in snapshot folder name, e.g. '1' or '2'", }, "label": { "type": "string", - "description": "Optional short label, e.g. 'schematic_ok' or 'layout_ok'" + "description": "Optional short label, e.g. 'schematic_ok' or 'layout_ok'", }, "projectPath": { "type": "string", - "description": "Project directory path. Auto-detected from loaded board if omitted." - } - } - } + "description": "Project directory path. Auto-detected from loaded board if omitted.", + }, + }, + }, }, { "name": "get_project_info", "title": "Get Project Information", "description": "Retrieves metadata and properties of the currently open project including name, paths, and board status.", - "inputSchema": { - "type": "object", - "properties": {} - } - } + "inputSchema": {"type": "object", "properties": {}}, + }, ] # ============================================================================= @@ -118,16 +115,16 @@ BOARD_TOOLS = [ "width": { "type": "number", "description": "Board width in millimeters", - "minimum": 1 + "minimum": 1, }, "height": { "type": "number", "description": "Board height in millimeters", - "minimum": 1 - } + "minimum": 1, + }, }, - "required": ["width", "height"] - } + "required": ["width", "height"], + }, }, { "name": "add_board_outline", @@ -139,44 +136,47 @@ BOARD_TOOLS = [ "shape": { "type": "string", "enum": ["rectangle", "rounded_rectangle", "circle", "polygon"], - "description": "Shape type for the board outline" + "description": "Shape type for the board outline", }, "width": { "type": "number", "description": "Width in mm (for rectangle/rounded_rectangle)", - "minimum": 1 + "minimum": 1, }, "height": { "type": "number", "description": "Height in mm (for rectangle/rounded_rectangle)", - "minimum": 1 + "minimum": 1, }, "x": { "type": "number", - "description": "X coordinate of the top-left corner in mm (default: 0). Board extends from x to x+width." + "description": "X coordinate of the top-left corner in mm (default: 0). Board extends from x to x+width.", }, "y": { "type": "number", - "description": "Y coordinate of the top-left corner in mm (default: 0). Board extends from y to y+height." + "description": "Y coordinate of the top-left corner in mm (default: 0). Board extends from y to y+height.", }, "radius": { "type": "number", "description": "Corner radius in mm for rounded_rectangle, or radius for circle", - "minimum": 0 + "minimum": 0, }, "points": { "type": "array", "description": "Array of {x, y} point objects in mm (for polygon shape only)", "items": { "type": "object", - "properties": {"x": {"type": "number"}, "y": {"type": "number"}}, - "required": ["x", "y"] + "properties": { + "x": {"type": "number"}, + "y": {"type": "number"}, + }, + "required": ["x", "y"], }, - "minItems": 3 - } + "minItems": 3, + }, }, - "required": ["shape"] - } + "required": ["shape"], + }, }, { "name": "add_layer", @@ -187,16 +187,16 @@ BOARD_TOOLS = [ "properties": { "layerName": { "type": "string", - "description": "Name of the layer to add" + "description": "Name of the layer to add", }, "layerType": { "type": "string", "enum": ["signal", "power", "mixed", "jumper"], - "description": "Type of layer (for copper layers)" - } + "description": "Type of layer (for copper layers)", + }, }, - "required": ["layerName"] - } + "required": ["layerName"], + }, }, { "name": "set_active_layer", @@ -207,29 +207,23 @@ BOARD_TOOLS = [ "properties": { "layerName": { "type": "string", - "description": "Name of the layer to make active (e.g., F.Cu, B.Cu, Edge.Cuts)" + "description": "Name of the layer to make active (e.g., F.Cu, B.Cu, Edge.Cuts)", } }, - "required": ["layerName"] - } + "required": ["layerName"], + }, }, { "name": "get_layer_list", "title": "List Board Layers", "description": "Returns a list of all layers in the board with their properties.", - "inputSchema": { - "type": "object", - "properties": {} - } + "inputSchema": {"type": "object", "properties": {}}, }, { "name": "get_board_info", "title": "Get Board Information", "description": "Retrieves comprehensive board information including dimensions, layer count, component count, and design rules.", - "inputSchema": { - "type": "object", - "properties": {} - } + "inputSchema": {"type": "object", "properties": {}}, }, { "name": "get_board_2d_view", @@ -242,16 +236,16 @@ BOARD_TOOLS = [ "type": "number", "description": "Image width in pixels (default: 800)", "minimum": 100, - "default": 800 + "default": 800, }, "height": { "type": "number", "description": "Image height in pixels (default: 600)", "minimum": 100, - "default": 600 - } - } - } + "default": 600, + }, + }, + }, }, { "name": "get_board_extents", @@ -264,10 +258,10 @@ BOARD_TOOLS = [ "type": "string", "enum": ["mm", "inch"], "description": "Unit for returned coordinates (default: mm)", - "default": "mm" + "default": "mm", } - } - } + }, + }, }, { "name": "add_mounting_hole", @@ -276,22 +270,16 @@ BOARD_TOOLS = [ "inputSchema": { "type": "object", "properties": { - "x": { - "type": "number", - "description": "X coordinate in millimeters" - }, - "y": { - "type": "number", - "description": "Y coordinate in millimeters" - }, + "x": {"type": "number", "description": "X coordinate in millimeters"}, + "y": {"type": "number", "description": "Y coordinate in millimeters"}, "diameter": { "type": "number", "description": "Hole diameter in millimeters", - "minimum": 0.1 - } + "minimum": 0.1, + }, }, - "required": ["x", "y", "diameter"] - } + "required": ["x", "y", "diameter"], + }, }, { "name": "import_svg_logo", @@ -302,43 +290,43 @@ BOARD_TOOLS = [ "properties": { "pcbPath": { "type": "string", - "description": "Path to the .kicad_pcb file" + "description": "Path to the .kicad_pcb file", }, "svgPath": { "type": "string", - "description": "Path to the SVG logo file" + "description": "Path to the SVG logo file", }, "x": { "type": "number", - "description": "X position of the logo top-left corner in mm" + "description": "X position of the logo top-left corner in mm", }, "y": { "type": "number", - "description": "Y position of the logo top-left corner in mm" + "description": "Y position of the logo top-left corner in mm", }, "width": { "type": "number", "description": "Target width of the logo in mm (height scaled to preserve aspect ratio)", - "minimum": 0.1 + "minimum": 0.1, }, "layer": { "type": "string", "description": "PCB layer name, e.g. F.SilkS or B.SilkS (default: F.SilkS)", - "default": "F.SilkS" + "default": "F.SilkS", }, "strokeWidth": { "type": "number", "description": "Outline stroke width in mm (0 = no outline, default 0)", - "default": 0 + "default": 0, }, "filled": { "type": "boolean", "description": "Fill polygons with solid layer colour (default true)", - "default": True - } + "default": True, + }, }, - "required": ["pcbPath", "svgPath", "x", "y", "width"] - } + "required": ["pcbPath", "svgPath", "x", "y", "width"], + }, }, { "name": "add_board_text", @@ -350,37 +338,31 @@ BOARD_TOOLS = [ "text": { "type": "string", "description": "Text content to add", - "minLength": 1 - }, - "x": { - "type": "number", - "description": "X coordinate in millimeters" - }, - "y": { - "type": "number", - "description": "Y coordinate in millimeters" + "minLength": 1, }, + "x": {"type": "number", "description": "X coordinate in millimeters"}, + "y": {"type": "number", "description": "Y coordinate in millimeters"}, "layer": { "type": "string", "description": "Layer name (e.g., F.SilkS, B.SilkS, F.Cu)", - "default": "F.SilkS" + "default": "F.SilkS", }, "size": { "type": "number", "description": "Text size in millimeters", "minimum": 0.1, - "default": 1.0 + "default": 1.0, }, "thickness": { "type": "number", "description": "Text thickness in millimeters", "minimum": 0.01, - "default": 0.15 - } + "default": 0.15, + }, }, - "required": ["text", "x", "y"] - } - } + "required": ["text", "x", "y"], + }, + }, ] # ============================================================================= @@ -397,36 +379,30 @@ COMPONENT_TOOLS = [ "properties": { "reference": { "type": "string", - "description": "Component reference designator (e.g., R1, C2, U3)" + "description": "Component reference designator (e.g., R1, C2, U3)", }, "footprint": { "type": "string", - "description": "Footprint library:name (e.g., Resistor_SMD:R_0805_2012Metric)" - }, - "x": { - "type": "number", - "description": "X coordinate in millimeters" - }, - "y": { - "type": "number", - "description": "Y coordinate in millimeters" + "description": "Footprint library:name (e.g., Resistor_SMD:R_0805_2012Metric)", }, + "x": {"type": "number", "description": "X coordinate in millimeters"}, + "y": {"type": "number", "description": "Y coordinate in millimeters"}, "rotation": { "type": "number", "description": "Rotation angle in degrees (0-360)", "minimum": 0, "maximum": 360, - "default": 0 + "default": 0, }, "layer": { "type": "string", "enum": ["F.Cu", "B.Cu"], "description": "Board layer (top or bottom)", - "default": "F.Cu" - } + "default": "F.Cu", + }, }, - "required": ["reference", "footprint", "x", "y"] - } + "required": ["reference", "footprint", "x", "y"], + }, }, { "name": "move_component", @@ -437,19 +413,19 @@ COMPONENT_TOOLS = [ "properties": { "reference": { "type": "string", - "description": "Component reference designator" + "description": "Component reference designator", }, "x": { "type": "number", - "description": "New X coordinate in millimeters" + "description": "New X coordinate in millimeters", }, "y": { "type": "number", - "description": "New Y coordinate in millimeters" - } + "description": "New Y coordinate in millimeters", + }, }, - "required": ["reference", "x", "y"] - } + "required": ["reference", "x", "y"], + }, }, { "name": "rotate_component", @@ -460,15 +436,15 @@ COMPONENT_TOOLS = [ "properties": { "reference": { "type": "string", - "description": "Component reference designator" + "description": "Component reference designator", }, "angle": { "type": "number", - "description": "Rotation angle in degrees (positive = counterclockwise)" - } + "description": "Rotation angle in degrees (positive = counterclockwise)", + }, }, - "required": ["reference", "angle"] - } + "required": ["reference", "angle"], + }, }, { "name": "delete_component", @@ -479,11 +455,11 @@ COMPONENT_TOOLS = [ "properties": { "reference": { "type": "string", - "description": "Component reference designator" + "description": "Component reference designator", } }, - "required": ["reference"] - } + "required": ["reference"], + }, }, { "name": "edit_component", @@ -494,19 +470,16 @@ COMPONENT_TOOLS = [ "properties": { "reference": { "type": "string", - "description": "Component reference designator" - }, - "value": { - "type": "string", - "description": "New component value" + "description": "Component reference designator", }, + "value": {"type": "string", "description": "New component value"}, "footprint": { "type": "string", - "description": "New footprint library:name" - } + "description": "New footprint library:name", + }, }, - "required": ["reference"] - } + "required": ["reference"], + }, }, { "name": "get_component_properties", @@ -517,20 +490,17 @@ COMPONENT_TOOLS = [ "properties": { "reference": { "type": "string", - "description": "Component reference designator" + "description": "Component reference designator", } }, - "required": ["reference"] - } + "required": ["reference"], + }, }, { "name": "get_component_list", "title": "List All Components", "description": "Returns a list of all components on the board with their properties.", - "inputSchema": { - "type": "object", - "properties": {} - } + "inputSchema": {"type": "object", "properties": {}}, }, { "name": "find_component", @@ -541,18 +511,18 @@ COMPONENT_TOOLS = [ "properties": { "reference": { "type": "string", - "description": "Reference designator pattern to match (e.g., 'R1', 'U', 'C2')" + "description": "Reference designator pattern to match (e.g., 'R1', 'U', 'C2')", }, "value": { "type": "string", - "description": "Value pattern to match (e.g., '10k', '100nF')" + "description": "Value pattern to match (e.g., '10k', '100nF')", }, "footprint": { "type": "string", - "description": "Footprint pattern to match (e.g., '0805', 'SOIC')" - } - } - } + "description": "Footprint pattern to match (e.g., '0805', 'SOIC')", + }, + }, + }, }, { "name": "get_component_pads", @@ -563,11 +533,11 @@ COMPONENT_TOOLS = [ "properties": { "reference": { "type": "string", - "description": "Component reference designator (e.g., U1, R5)" + "description": "Component reference designator (e.g., U1, R5)", } }, - "required": ["reference"] - } + "required": ["reference"], + }, }, { "name": "get_pad_position", @@ -578,19 +548,19 @@ COMPONENT_TOOLS = [ "properties": { "reference": { "type": "string", - "description": "Component reference designator" + "description": "Component reference designator", }, "padName": { "type": "string", - "description": "Pad name or number (e.g., '1', '2', 'A1')" + "description": "Pad name or number (e.g., '1', '2', 'A1')", }, "padNumber": { "type": "string", - "description": "Alternative to padName - pad number" - } + "description": "Alternative to padName - pad number", + }, }, - "required": ["reference"] - } + "required": ["reference"], + }, }, { "name": "place_component_array", @@ -601,61 +571,68 @@ COMPONENT_TOOLS = [ "properties": { "referencePrefix": { "type": "string", - "description": "Reference prefix (e.g., 'R' for R1, R2, R3...)" + "description": "Reference prefix (e.g., 'R' for R1, R2, R3...)", }, "startNumber": { "type": "integer", "description": "Starting number for references", "minimum": 1, - "default": 1 + "default": 1, }, "footprint": { "type": "string", - "description": "Footprint library:name" + "description": "Footprint library:name", }, "pattern": { "type": "string", "enum": ["grid", "circular"], - "description": "Array pattern type" + "description": "Array pattern type", }, "count": { "type": "integer", "description": "Total number of components to place", - "minimum": 1 + "minimum": 1, }, "startX": { "type": "number", - "description": "Starting X coordinate in millimeters" + "description": "Starting X coordinate in millimeters", }, "startY": { "type": "number", - "description": "Starting Y coordinate in millimeters" + "description": "Starting Y coordinate in millimeters", }, "spacingX": { "type": "number", - "description": "Horizontal spacing in mm (for grid pattern)" + "description": "Horizontal spacing in mm (for grid pattern)", }, "spacingY": { "type": "number", - "description": "Vertical spacing in mm (for grid pattern)" + "description": "Vertical spacing in mm (for grid pattern)", }, "radius": { "type": "number", - "description": "Circle radius in mm (for circular pattern)" + "description": "Circle radius in mm (for circular pattern)", }, "rows": { "type": "integer", "description": "Number of rows (for grid pattern)", - "minimum": 1 + "minimum": 1, }, "columns": { "type": "integer", "description": "Number of columns (for grid pattern)", - "minimum": 1 - } + "minimum": 1, + }, }, - "required": ["referencePrefix", "footprint", "pattern", "count", "startX", "startY"] - } + "required": [ + "referencePrefix", + "footprint", + "pattern", + "count", + "startX", + "startY", + ], + }, }, { "name": "align_components", @@ -668,20 +645,20 @@ COMPONENT_TOOLS = [ "type": "array", "description": "Array of component reference designators to align", "items": {"type": "string"}, - "minItems": 2 + "minItems": 2, }, "direction": { "type": "string", "enum": ["horizontal", "vertical"], - "description": "Alignment direction" + "description": "Alignment direction", }, "spacing": { "type": "number", - "description": "Spacing between components in mm (optional, for even distribution)" - } + "description": "Spacing between components in mm (optional, for even distribution)", + }, }, - "required": ["references", "direction"] - } + "required": ["references", "direction"], + }, }, { "name": "duplicate_component", @@ -692,26 +669,26 @@ COMPONENT_TOOLS = [ "properties": { "sourceReference": { "type": "string", - "description": "Reference of component to duplicate" + "description": "Reference of component to duplicate", }, "newReference": { "type": "string", - "description": "Reference designator for the new component" + "description": "Reference designator for the new component", }, "offsetX": { "type": "number", "description": "X offset from original position in mm", - "default": 0 + "default": 0, }, "offsetY": { "type": "number", "description": "Y offset from original position in mm", - "default": 0 - } + "default": 0, + }, }, - "required": ["sourceReference", "newReference"] - } - } + "required": ["sourceReference", "newReference"], + }, + }, ] # ============================================================================= @@ -729,15 +706,15 @@ ROUTING_TOOLS = [ "netName": { "type": "string", "description": "Name of the net (e.g., VCC, GND, SDA)", - "minLength": 1 + "minLength": 1, }, "netClass": { "type": "string", - "description": "Optional net class to assign (must exist first)" - } + "description": "Optional net class to assign (must exist first)", + }, }, - "required": ["netName"] - } + "required": ["netName"], + }, }, { "name": "route_trace", @@ -746,19 +723,16 @@ ROUTING_TOOLS = [ "inputSchema": { "type": "object", "properties": { - "netName": { - "type": "string", - "description": "Net name for this trace" - }, + "netName": {"type": "string", "description": "Net name for this trace"}, "layer": { "type": "string", "description": "Layer to route on (e.g., F.Cu, B.Cu)", - "default": "F.Cu" + "default": "F.Cu", }, "width": { "type": "number", "description": "Trace width in millimeters", - "minimum": 0.1 + "minimum": 0.1, }, "points": { "type": "array", @@ -767,13 +741,13 @@ ROUTING_TOOLS = [ "type": "array", "items": {"type": "number"}, "minItems": 2, - "maxItems": 2 + "maxItems": 2, }, - "minItems": 2 - } + "minItems": 2, + }, }, - "required": ["points", "width"] - } + "required": ["points", "width"], + }, }, { "name": "add_via", @@ -782,31 +756,25 @@ ROUTING_TOOLS = [ "inputSchema": { "type": "object", "properties": { - "x": { - "type": "number", - "description": "X coordinate in millimeters" - }, - "y": { - "type": "number", - "description": "Y coordinate in millimeters" - }, + "x": {"type": "number", "description": "X coordinate in millimeters"}, + "y": {"type": "number", "description": "Y coordinate in millimeters"}, "diameter": { "type": "number", "description": "Via diameter in millimeters", - "minimum": 0.1 + "minimum": 0.1, }, "drill": { "type": "number", "description": "Drill diameter in millimeters", - "minimum": 0.1 + "minimum": 0.1, }, "netName": { "type": "string", - "description": "Net name to assign to this via" - } + "description": "Net name to assign to this via", + }, }, - "required": ["x", "y", "diameter", "drill"] - } + "required": ["x", "y", "diameter", "drill"], + }, }, { "name": "delete_trace", @@ -817,7 +785,7 @@ ROUTING_TOOLS = [ "properties": { "uuid": { "type": "string", - "description": "UUID of a specific trace to delete" + "description": "UUID of a specific trace to delete", }, "position": { "type": "object", @@ -825,25 +793,29 @@ ROUTING_TOOLS = [ "properties": { "x": {"type": "number", "description": "X coordinate"}, "y": {"type": "number", "description": "Y coordinate"}, - "unit": {"type": "string", "enum": ["mm", "inch"], "default": "mm"} + "unit": { + "type": "string", + "enum": ["mm", "inch"], + "default": "mm", + }, }, - "required": ["x", "y"] + "required": ["x", "y"], }, "net": { "type": "string", - "description": "Delete all traces on this net (bulk delete)" + "description": "Delete all traces on this net (bulk delete)", }, "layer": { "type": "string", - "description": "Filter by layer when using net-based deletion" + "description": "Filter by layer when using net-based deletion", }, "includeVias": { "type": "boolean", "description": "Include vias in net-based deletion", - "default": False - } - } - } + "default": False, + }, + }, + }, }, { "name": "query_traces", @@ -854,11 +826,11 @@ ROUTING_TOOLS = [ "properties": { "net": { "type": "string", - "description": "Filter by net name (e.g., 'GND', 'VCC')" + "description": "Filter by net name (e.g., 'GND', 'VCC')", }, "layer": { "type": "string", - "description": "Filter by layer name (e.g., 'F.Cu', 'B.Cu')" + "description": "Filter by layer name (e.g., 'F.Cu', 'B.Cu')", }, "boundingBox": { "type": "object", @@ -868,16 +840,20 @@ ROUTING_TOOLS = [ "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"} - } + "unit": { + "type": "string", + "enum": ["mm", "inch"], + "default": "mm", + }, + }, }, "includeVias": { "type": "boolean", "description": "Include vias in the result", - "default": False - } - } - } + "default": False, + }, + }, + }, }, { "name": "modify_trace", @@ -888,7 +864,7 @@ ROUTING_TOOLS = [ "properties": { "uuid": { "type": "string", - "description": "UUID of the trace to modify" + "description": "UUID of the trace to modify", }, "position": { "type": "object", @@ -896,24 +872,22 @@ ROUTING_TOOLS = [ "properties": { "x": {"type": "number", "description": "X coordinate"}, "y": {"type": "number", "description": "Y coordinate"}, - "unit": {"type": "string", "enum": ["mm", "inch"], "default": "mm"} + "unit": { + "type": "string", + "enum": ["mm", "inch"], + "default": "mm", + }, }, - "required": ["x", "y"] - }, - "width": { - "type": "number", - "description": "New trace width in mm" + "required": ["x", "y"], }, + "width": {"type": "number", "description": "New trace width in mm"}, "layer": { "type": "string", - "description": "New layer name (e.g., 'F.Cu', 'B.Cu')" + "description": "New layer name (e.g., 'F.Cu', 'B.Cu')", }, - "net": { - "type": "string", - "description": "New net name to assign" - } - } - } + "net": {"type": "string", "description": "New net name to assign"}, + }, + }, }, { "name": "copy_routing_pattern", @@ -925,34 +899,31 @@ ROUTING_TOOLS = [ "sourceRefs": { "type": "array", "items": {"type": "string"}, - "description": "Source component references (e.g., ['U1', 'U2', 'U3'])" + "description": "Source component references (e.g., ['U1', 'U2', 'U3'])", }, "targetRefs": { "type": "array", "items": {"type": "string"}, - "description": "Target component references (e.g., ['U4', 'U5', 'U6'])" + "description": "Target component references (e.g., ['U4', 'U5', 'U6'])", }, "includeVias": { "type": "boolean", "description": "Include vias in the pattern copy", - "default": True + "default": True, }, "traceWidth": { "type": "number", - "description": "Override trace width in mm (uses original if not specified)" - } + "description": "Override trace width in mm (uses original if not specified)", + }, }, - "required": ["sourceRefs", "targetRefs"] - } + "required": ["sourceRefs", "targetRefs"], + }, }, { "name": "get_nets_list", "title": "List All Nets", "description": "Returns a list of all electrical nets defined on the board.", - "inputSchema": { - "type": "object", - "properties": {} - } + "inputSchema": {"type": "object", "properties": {}}, }, { "name": "create_netclass", @@ -964,29 +935,29 @@ ROUTING_TOOLS = [ "name": { "type": "string", "description": "Net class name", - "minLength": 1 + "minLength": 1, }, "traceWidth": { "type": "number", "description": "Default trace width in millimeters", - "minimum": 0.1 + "minimum": 0.1, }, "clearance": { "type": "number", "description": "Clearance in millimeters", - "minimum": 0.1 + "minimum": 0.1, }, "viaDiameter": { "type": "number", - "description": "Via diameter in millimeters" + "description": "Via diameter in millimeters", }, "viaDrill": { "type": "number", - "description": "Via drill diameter in millimeters" - } + "description": "Via drill diameter in millimeters", + }, }, - "required": ["name", "traceWidth", "clearance"] - } + "required": ["name", "traceWidth", "clearance"], + }, }, { "name": "add_copper_pour", @@ -997,22 +968,22 @@ ROUTING_TOOLS = [ "properties": { "netName": { "type": "string", - "description": "Net to connect this copper pour to (e.g., GND, VCC)" + "description": "Net to connect this copper pour to (e.g., GND, VCC)", }, "layer": { "type": "string", - "description": "Layer for the copper pour (e.g., F.Cu, B.Cu)" + "description": "Layer for the copper pour (e.g., F.Cu, B.Cu)", }, "priority": { "type": "integer", "description": "Pour priority (higher priorities fill first)", "minimum": 0, - "default": 0 + "default": 0, }, "clearance": { "type": "number", "description": "Clearance from other objects in millimeters", - "minimum": 0.1 + "minimum": 0.1, }, "outline": { "type": "array", @@ -1021,13 +992,13 @@ ROUTING_TOOLS = [ "type": "array", "items": {"type": "number"}, "minItems": 2, - "maxItems": 2 + "maxItems": 2, }, - "minItems": 3 - } + "minItems": 3, + }, }, - "required": ["netName", "layer", "outline"] - } + "required": ["netName", "layer", "outline"], + }, }, { "name": "route_differential_pair", @@ -1038,23 +1009,20 @@ ROUTING_TOOLS = [ "properties": { "positiveName": { "type": "string", - "description": "Positive signal net name" + "description": "Positive signal net name", }, "negativeName": { "type": "string", - "description": "Negative signal net name" - }, - "layer": { - "type": "string", - "description": "Layer to route on" + "description": "Negative signal net name", }, + "layer": {"type": "string", "description": "Layer to route on"}, "width": { "type": "number", - "description": "Trace width in millimeters" + "description": "Trace width in millimeters", }, "gap": { "type": "number", - "description": "Gap between traces in millimeters" + "description": "Gap between traces in millimeters", }, "points": { "type": "array", @@ -1063,14 +1031,14 @@ ROUTING_TOOLS = [ "type": "array", "items": {"type": "number"}, "minItems": 2, - "maxItems": 2 + "maxItems": 2, }, - "minItems": 2 - } + "minItems": 2, + }, }, - "required": ["positiveName", "negativeName", "width", "gap", "points"] - } - } + "required": ["positiveName", "negativeName", "width", "gap", "points"], + }, + }, ] # ============================================================================= @@ -1082,10 +1050,7 @@ LIBRARY_TOOLS = [ "name": "list_libraries", "title": "List Footprint Libraries", "description": "Lists all available footprint libraries accessible to KiCAD.", - "inputSchema": { - "type": "object", - "properties": {} - } + "inputSchema": {"type": "object", "properties": {}}, }, { "name": "search_footprints", @@ -1097,15 +1062,15 @@ LIBRARY_TOOLS = [ "query": { "type": "string", "description": "Search query (e.g., '0805', 'SOIC', 'QFP')", - "minLength": 1 + "minLength": 1, }, "library": { "type": "string", - "description": "Optional library to restrict search to" - } + "description": "Optional library to restrict search to", + }, }, - "required": ["query"] - } + "required": ["query"], + }, }, { "name": "list_library_footprints", @@ -1117,11 +1082,11 @@ LIBRARY_TOOLS = [ "library": { "type": "string", "description": "Library name (e.g., Resistor_SMD, Connector_PinHeader)", - "minLength": 1 + "minLength": 1, } }, - "required": ["library"] - } + "required": ["library"], + }, }, { "name": "get_footprint_info", @@ -1130,18 +1095,12 @@ LIBRARY_TOOLS = [ "inputSchema": { "type": "object", "properties": { - "library": { - "type": "string", - "description": "Library name" - }, - "footprint": { - "type": "string", - "description": "Footprint name" - } + "library": {"type": "string", "description": "Library name"}, + "footprint": {"type": "string", "description": "Footprint name"}, }, - "required": ["library", "footprint"] - } - } + "required": ["library", "footprint"], + }, + }, ] # ============================================================================= @@ -1159,36 +1118,33 @@ DESIGN_RULE_TOOLS = [ "clearance": { "type": "number", "description": "Minimum clearance between copper in millimeters", - "minimum": 0.1 + "minimum": 0.1, }, "trackWidth": { "type": "number", "description": "Minimum track width in millimeters", - "minimum": 0.1 + "minimum": 0.1, }, "viaDiameter": { "type": "number", - "description": "Minimum via diameter in millimeters" + "description": "Minimum via diameter in millimeters", }, "viaDrill": { "type": "number", - "description": "Minimum via drill diameter in millimeters" + "description": "Minimum via drill diameter in millimeters", }, "microViaD iameter": { "type": "number", - "description": "Minimum micro-via diameter in millimeters" - } - } - } + "description": "Minimum micro-via diameter in millimeters", + }, + }, + }, }, { "name": "get_design_rules", "title": "Get Current Design Rules", "description": "Retrieves the currently configured design rules from the board.", - "inputSchema": { - "type": "object", - "properties": {} - } + "inputSchema": {"type": "object", "properties": {}}, }, { "name": "run_drc", @@ -1200,20 +1156,17 @@ DESIGN_RULE_TOOLS = [ "includeWarnings": { "type": "boolean", "description": "Include warnings in addition to errors", - "default": True + "default": True, } - } - } + }, + }, }, { "name": "get_drc_violations", "title": "Get DRC Violations", "description": "Returns a list of design rule violations from the most recent DRC run.", - "inputSchema": { - "type": "object", - "properties": {} - } - } + "inputSchema": {"type": "object", "properties": {}}, + }, ] # ============================================================================= @@ -1230,21 +1183,21 @@ EXPORT_TOOLS = [ "properties": { "outputPath": { "type": "string", - "description": "Directory path for output files" + "description": "Directory path for output files", }, "layers": { "type": "array", "description": "List of layers to export (if not provided, exports all copper and mask layers)", - "items": {"type": "string"} + "items": {"type": "string"}, }, "includeDrillFiles": { "type": "boolean", "description": "Include drill files (Excellon format)", - "default": True - } + "default": True, + }, }, - "required": ["outputPath"] - } + "required": ["outputPath"], + }, }, { "name": "export_pdf", @@ -1255,22 +1208,22 @@ EXPORT_TOOLS = [ "properties": { "outputPath": { "type": "string", - "description": "Path for output PDF file" + "description": "Path for output PDF file", }, "layers": { "type": "array", "description": "Layers to include in PDF", - "items": {"type": "string"} + "items": {"type": "string"}, }, "colorMode": { "type": "string", "enum": ["color", "black_white"], "description": "Color mode for output", - "default": "color" - } + "default": "color", + }, }, - "required": ["outputPath"] - } + "required": ["outputPath"], + }, }, { "name": "export_svg", @@ -1281,16 +1234,16 @@ EXPORT_TOOLS = [ "properties": { "outputPath": { "type": "string", - "description": "Path for output SVG file" + "description": "Path for output SVG file", }, "layers": { "type": "array", "description": "Layers to include in SVG", - "items": {"type": "string"} - } + "items": {"type": "string"}, + }, }, - "required": ["outputPath"] - } + "required": ["outputPath"], + }, }, { "name": "export_3d", @@ -1301,22 +1254,22 @@ EXPORT_TOOLS = [ "properties": { "outputPath": { "type": "string", - "description": "Path for output 3D file" + "description": "Path for output 3D file", }, "format": { "type": "string", "enum": ["step", "vrml"], "description": "3D model format", - "default": "step" + "default": "step", }, "includeComponents": { "type": "boolean", "description": "Include 3D component models", - "default": True - } + "default": True, + }, }, - "required": ["outputPath"] - } + "required": ["outputPath"], + }, }, { "name": "export_bom", @@ -1327,23 +1280,23 @@ EXPORT_TOOLS = [ "properties": { "outputPath": { "type": "string", - "description": "Path for output BOM file" + "description": "Path for output BOM file", }, "format": { "type": "string", "enum": ["csv", "xml", "html"], "description": "BOM output format", - "default": "csv" + "default": "csv", }, "groupByValue": { "type": "boolean", "description": "Group components with same value together", - "default": True - } + "default": True, + }, }, - "required": ["outputPath"] - } - } + "required": ["outputPath"], + }, + }, ] # ============================================================================= @@ -1360,15 +1313,12 @@ SCHEMATIC_TOOLS = [ "properties": { "filename": { "type": "string", - "description": "Path for the new schematic file (.kicad_sch)" + "description": "Path for the new schematic file (.kicad_sch)", }, - "title": { - "type": "string", - "description": "Schematic title" - } + "title": {"type": "string", "description": "Schematic title"}, }, - "required": ["filename"] - } + "required": ["filename"], + }, }, { "name": "load_schematic", @@ -1379,11 +1329,11 @@ SCHEMATIC_TOOLS = [ "properties": { "filename": { "type": "string", - "description": "Path to schematic file (.kicad_sch)" + "description": "Path to schematic file (.kicad_sch)", } }, - "required": ["filename"] - } + "required": ["filename"], + }, }, { "name": "add_schematic_component", @@ -1394,27 +1344,21 @@ SCHEMATIC_TOOLS = [ "properties": { "reference": { "type": "string", - "description": "Reference designator (e.g., R1, C2, U3)" + "description": "Reference designator (e.g., R1, C2, U3)", }, "symbol": { "type": "string", - "description": "Symbol library:name (e.g., Device:R, Device:C)" + "description": "Symbol library:name (e.g., Device:R, Device:C)", }, "value": { "type": "string", - "description": "Component value (e.g., 10k, 0.1uF)" + "description": "Component value (e.g., 10k, 0.1uF)", }, - "x": { - "type": "number", - "description": "X coordinate on schematic" - }, - "y": { - "type": "number", - "description": "Y coordinate on schematic" - } + "x": {"type": "number", "description": "X coordinate on schematic"}, + "y": {"type": "number", "description": "Y coordinate on schematic"}, }, - "required": ["reference", "symbol", "x", "y"] - } + "required": ["reference", "symbol", "x", "y"], + }, }, { "name": "add_schematic_wire", @@ -1430,13 +1374,13 @@ SCHEMATIC_TOOLS = [ "type": "array", "items": {"type": "number"}, "minItems": 2, - "maxItems": 2 + "maxItems": 2, }, - "minItems": 2 + "minItems": 2, } }, - "required": ["points"] - } + "required": ["points"], + }, }, { "name": "add_schematic_connection", @@ -1447,19 +1391,13 @@ SCHEMATIC_TOOLS = [ "properties": { "schematicPath": { "type": "string", - "description": "Path to schematic file" + "description": "Path to schematic file", }, - "x": { - "type": "number", - "description": "X coordinate on schematic" - }, - "y": { - "type": "number", - "description": "Y coordinate on schematic" - } + "x": {"type": "number", "description": "X coordinate on schematic"}, + "y": {"type": "number", "description": "Y coordinate on schematic"}, }, - "required": ["schematicPath", "x", "y"] - } + "required": ["schematicPath", "x", "y"], + }, }, { "name": "add_schematic_net_label", @@ -1470,28 +1408,22 @@ SCHEMATIC_TOOLS = [ "properties": { "schematicPath": { "type": "string", - "description": "Path to schematic file" + "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" + "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 - } + "default": 0, + }, }, - "required": ["schematicPath", "netName", "x", "y"] - } + "required": ["schematicPath", "netName", "x", "y"], + }, }, { "name": "connect_to_net", @@ -1502,23 +1434,23 @@ SCHEMATIC_TOOLS = [ "properties": { "schematicPath": { "type": "string", - "description": "Path to schematic file" + "description": "Path to schematic file", }, "reference": { "type": "string", - "description": "Component reference designator (e.g., R1, U3)" + "description": "Component reference designator (e.g., R1, U3)", }, "pinNumber": { "type": "string", - "description": "Pin number or name on the component" + "description": "Pin number or name on the component", }, "netName": { "type": "string", - "description": "Name of the net to connect to" - } + "description": "Name of the net to connect to", + }, }, - "required": ["schematicPath", "reference", "pinNumber", "netName"] - } + "required": ["schematicPath", "reference", "pinNumber", "netName"], + }, }, { "name": "get_net_connections", @@ -1529,15 +1461,15 @@ SCHEMATIC_TOOLS = [ "properties": { "schematicPath": { "type": "string", - "description": "Path to schematic file" + "description": "Path to schematic file", }, "netName": { "type": "string", - "description": "Name of the net to query" - } + "description": "Name of the net to query", + }, }, - "required": ["schematicPath", "netName"] - } + "required": ["schematicPath", "netName"], + }, }, { "name": "get_schematic_pin_locations", @@ -1548,15 +1480,15 @@ SCHEMATIC_TOOLS = [ "properties": { "schematicPath": { "type": "string", - "description": "Path to the schematic file" + "description": "Path to the schematic file", }, "reference": { "type": "string", - "description": "Component reference designator (e.g., U1, R1, J2)" - } + "description": "Component reference designator (e.g., U1, R1, J2)", + }, }, - "required": ["schematicPath", "reference"] - } + "required": ["schematicPath", "reference"], + }, }, { "name": "connect_passthrough", @@ -1567,27 +1499,27 @@ SCHEMATIC_TOOLS = [ "properties": { "schematicPath": { "type": "string", - "description": "Path to the schematic file" + "description": "Path to the schematic file", }, "sourceRef": { "type": "string", - "description": "Reference of the source connector (e.g., J1)" + "description": "Reference of the source connector (e.g., J1)", }, "targetRef": { "type": "string", - "description": "Reference of the target connector (e.g., J2)" + "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)" + "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)" - } + "description": "Add this value to the pin number when building net names (default: 0)", + }, }, - "required": ["schematicPath", "sourceRef", "targetRef"] - } + "required": ["schematicPath", "sourceRef", "targetRef"], + }, }, { "name": "run_erc", @@ -1598,11 +1530,11 @@ SCHEMATIC_TOOLS = [ "properties": { "schematicPath": { "type": "string", - "description": "Path to the .kicad_sch schematic file" + "description": "Path to the .kicad_sch schematic file", } }, - "required": ["schematicPath"] - } + "required": ["schematicPath"], + }, }, { "name": "sync_schematic_to_board", @@ -1613,14 +1545,14 @@ SCHEMATIC_TOOLS = [ "properties": { "schematicPath": { "type": "string", - "description": "Path to .kicad_sch file. If omitted, auto-detected from current board path." + "description": "Path to .kicad_sch file. If omitted, auto-detected from current board path.", }, "boardPath": { "type": "string", - "description": "Path to .kicad_pcb file. If omitted, uses currently loaded board." - } - } - } + "description": "Path to .kicad_pcb file. If omitted, uses currently loaded board.", + }, + }, + }, }, { "name": "generate_netlist", @@ -1631,21 +1563,21 @@ SCHEMATIC_TOOLS = [ "properties": { "schematicPath": { "type": "string", - "description": "Path to schematic file" + "description": "Path to schematic file", }, "outputPath": { "type": "string", - "description": "Optional path to save netlist file" + "description": "Optional path to save netlist file", }, "format": { "type": "string", "enum": ["kicad", "json", "spice"], "description": "Netlist output format", - "default": "json" - } + "default": "json", + }, }, - "required": ["schematicPath"] - } + "required": ["schematicPath"], + }, }, { "name": "list_schematic_libraries", @@ -1657,10 +1589,10 @@ SCHEMATIC_TOOLS = [ "searchPaths": { "type": "array", "description": "Optional additional paths to search for libraries", - "items": {"type": "string"} + "items": {"type": "string"}, } - } - } + }, + }, }, { "name": "export_schematic_pdf", @@ -1671,15 +1603,12 @@ SCHEMATIC_TOOLS = [ "properties": { "schematicPath": { "type": "string", - "description": "Path to schematic file" + "description": "Path to schematic file", }, - "outputPath": { - "type": "string", - "description": "Path for output PDF" - } + "outputPath": {"type": "string", "description": "Path for output PDF"}, }, - "required": ["schematicPath", "outputPath"] - } + "required": ["schematicPath", "outputPath"], + }, }, # --- Schematic Analysis Tools (read-only) --- { @@ -1691,40 +1620,40 @@ SCHEMATIC_TOOLS = [ "properties": { "schematicPath": { "type": "string", - "description": "Path to the .kicad_sch schematic file" + "description": "Path to the .kicad_sch schematic file", }, "x1": { "type": "number", - "description": "Left X coordinate of the region in mm" + "description": "Left X coordinate of the region in mm", }, "y1": { "type": "number", - "description": "Top Y coordinate of the region in mm" + "description": "Top Y coordinate of the region in mm", }, "x2": { "type": "number", - "description": "Right X coordinate of the region in mm" + "description": "Right X coordinate of the region in mm", }, "y2": { "type": "number", - "description": "Bottom Y coordinate of the region in mm" + "description": "Bottom Y coordinate of the region in mm", }, "format": { "type": "string", "enum": ["png", "svg"], - "description": "Output image format (default: png)" + "description": "Output image format (default: png)", }, "width": { "type": "integer", - "description": "Output image width in pixels (default: 800)" + "description": "Output image width in pixels (default: 800)", }, "height": { "type": "integer", - "description": "Output image height in pixels (default: 600)" - } + "description": "Output image height in pixels (default: 600)", + }, }, - "required": ["schematicPath", "x1", "y1", "x2", "y2"] - } + "required": ["schematicPath", "x1", "y1", "x2", "y2"], + }, }, { "name": "find_overlapping_elements", @@ -1735,15 +1664,15 @@ SCHEMATIC_TOOLS = [ "properties": { "schematicPath": { "type": "string", - "description": "Path to the .kicad_sch schematic file" + "description": "Path to the .kicad_sch schematic file", }, "tolerance": { "type": "number", - "description": "Distance threshold in mm for label proximity and wire collinearity checks. Symbol overlap uses bounding-box intersection. (default: 0.5)" - } + "description": "Distance threshold in mm for label proximity and wire collinearity checks. Symbol overlap uses bounding-box intersection. (default: 0.5)", + }, }, - "required": ["schematicPath"] - } + "required": ["schematicPath"], + }, }, { "name": "get_elements_in_region", @@ -1754,27 +1683,27 @@ SCHEMATIC_TOOLS = [ "properties": { "schematicPath": { "type": "string", - "description": "Path to the .kicad_sch schematic file" + "description": "Path to the .kicad_sch schematic file", }, "x1": { "type": "number", - "description": "Left X coordinate of the region in mm" + "description": "Left X coordinate of the region in mm", }, "y1": { "type": "number", - "description": "Top Y coordinate of the region in mm" + "description": "Top Y coordinate of the region in mm", }, "x2": { "type": "number", - "description": "Right X coordinate of the region in mm" + "description": "Right X coordinate of the region in mm", }, "y2": { "type": "number", - "description": "Bottom Y coordinate of the region in mm" - } + "description": "Bottom Y coordinate of the region in mm", + }, }, - "required": ["schematicPath", "x1", "y1", "x2", "y2"] - } + "required": ["schematicPath", "x1", "y1", "x2", "y2"], + }, }, { "name": "find_wires_crossing_symbols", @@ -1785,12 +1714,12 @@ SCHEMATIC_TOOLS = [ "properties": { "schematicPath": { "type": "string", - "description": "Path to the .kicad_sch schematic file" + "description": "Path to the .kicad_sch schematic file", } }, - "required": ["schematicPath"] - } - } + "required": ["schematicPath"], + }, + }, ] # ============================================================================= @@ -1802,10 +1731,7 @@ UI_TOOLS = [ "name": "check_kicad_ui", "title": "Check KiCAD UI Status", "description": "Checks if KiCAD user interface is currently running and returns process information.", - "inputSchema": { - "type": "object", - "properties": {} - } + "inputSchema": {"type": "object", "properties": {}}, }, { "name": "launch_kicad_ui", @@ -1816,16 +1742,16 @@ UI_TOOLS = [ "properties": { "projectPath": { "type": "string", - "description": "Optional path to project file to open in UI" + "description": "Optional path to project file to open in UI", }, "autoLaunch": { "type": "boolean", "description": "Whether to automatically launch if not running", - "default": True - } - } - } - } + "default": True, + }, + }, + }, + }, ] # ============================================================================= @@ -1835,9 +1761,17 @@ UI_TOOLS = [ TOOL_SCHEMAS: Dict[str, Any] = {} # Combine all tool categories -for tool in (PROJECT_TOOLS + BOARD_TOOLS + COMPONENT_TOOLS + ROUTING_TOOLS + - LIBRARY_TOOLS + DESIGN_RULE_TOOLS + EXPORT_TOOLS + - SCHEMATIC_TOOLS + UI_TOOLS): +for tool in ( + PROJECT_TOOLS + + BOARD_TOOLS + + COMPONENT_TOOLS + + ROUTING_TOOLS + + LIBRARY_TOOLS + + DESIGN_RULE_TOOLS + + EXPORT_TOOLS + + SCHEMATIC_TOOLS + + UI_TOOLS +): TOOL_SCHEMAS[tool["name"]] = tool # Total: 46 tools with comprehensive schemas diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 81d3b93..249d74f 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -33,6 +33,7 @@ except ImportError: class _FakeSchematic: """Minimal stand-in for skip.Schematic used in PinLocator cache.""" + def __init__(self, path: str): self.path = path self.symbol = [] diff --git a/python/tests/test_schematic_analysis.py b/python/tests/test_schematic_analysis.py index c7c4bcd..c36290a 100644 --- a/python/tests/test_schematic_analysis.py +++ b/python/tests/test_schematic_analysis.py @@ -39,7 +39,6 @@ from commands.schematic_analysis import ( find_wires_crossing_symbols, ) - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- @@ -127,6 +126,7 @@ def _make_led_sexp(ref: str, x: float, y: float, rotation: float = 0) -> str: # Unit tests — geometry helpers # =================================================================== + class TestGeometryHelpers: """Test low-level geometry utilities.""" @@ -174,6 +174,7 @@ class TestGeometryHelpers: # Unit tests — S-expression parsers # =================================================================== + class TestSexpParsers: """Test S-expression parsing functions with synthetic data.""" @@ -223,6 +224,7 @@ class TestSexpParsers: # Unit tests — analysis functions with mocked PinLocator # =================================================================== + class TestAABBOverlap: """Test AABB overlap helper.""" @@ -254,14 +256,18 @@ class TestFindOverlappingElements: def test_overlapping_symbols_detected(self): # Two resistors at nearly the same position — bboxes fully overlap - extra = _make_resistor_sexp("R1", 100, 100) + _make_resistor_sexp("R2", 100.1, 100) + extra = _make_resistor_sexp("R1", 100, 100) + _make_resistor_sexp( + "R2", 100.1, 100 + ) tmp = _make_temp_schematic(extra) result = find_overlapping_elements(tmp, tolerance=0.5) assert result["totalOverlaps"] >= 1 assert len(result["overlappingSymbols"]) >= 1 def test_well_separated_symbols_not_flagged(self): - extra = _make_resistor_sexp("R1", 100, 100) + _make_resistor_sexp("R2", 200, 200) + extra = _make_resistor_sexp("R1", 100, 100) + _make_resistor_sexp( + "R2", 200, 200 + ) tmp = _make_temp_schematic(extra) result = find_overlapping_elements(tmp, tolerance=0.5) assert result["totalOverlaps"] == 0 @@ -288,7 +294,9 @@ class TestFindOverlappingElements: """ # R1 at y=100, R2 at y=105 — pin spans [96.19, 103.81] and [101.19, 108.81] # These overlap in Y from 101.19 to 103.81 - extra = _make_resistor_sexp("R1", 100, 100) + _make_resistor_sexp("R2", 100, 105) + extra = _make_resistor_sexp("R1", 100, 100) + _make_resistor_sexp( + "R2", 100, 105 + ) tmp = _make_temp_schematic(extra) result = find_overlapping_elements(tmp, tolerance=0.5) assert result["totalOverlaps"] >= 1, ( @@ -302,7 +310,9 @@ class TestFindOverlappingElements: R pins at y ±3.81, but different X positions far enough apart. """ - extra = _make_resistor_sexp("R1", 100, 100) + _make_resistor_sexp("R2", 110, 100) + extra = _make_resistor_sexp("R1", 100, 100) + _make_resistor_sexp( + "R2", 110, 100 + ) tmp = _make_temp_schematic(extra) result = find_overlapping_elements(tmp, tolerance=0.5) assert result["totalOverlaps"] == 0 @@ -355,6 +365,7 @@ class TestComputeSymbolBbox: def test_returns_none_for_unknown_symbol(self): tmp = _make_temp_schematic() from commands.pin_locator import PinLocator + locator = PinLocator() result = compute_symbol_bbox(tmp, "NONEXISTENT", locator) assert result is None @@ -409,8 +420,7 @@ class TestIntegrationFindWiresCrossingSymbols: result = find_wires_crossing_symbols(tmp) # The wire must not be reported against the far-away R? at (200, 100) collisions_at_200 = [ - c for c in result - if abs(c["component"]["position"]["x"] - 200) < 0.5 + c for c in result if abs(c["component"]["position"]["x"] - 200) < 0.5 ] assert len(collisions_at_200) == 0, ( "Wire at x≈100 must not be flagged against the R? at x=200; " @@ -431,9 +441,9 @@ class TestIntegrationFindWiresCrossingSymbols: tmp = _make_temp_schematic(extra) result = find_wires_crossing_symbols(tmp) d1_crossings = [c for c in result if c["component"]["reference"] == "D1"] - assert len(d1_crossings) >= 1, ( - "Wire starting at pin but passing through body must be detected" - ) + assert ( + len(d1_crossings) >= 1 + ), "Wire starting at pin but passing through body must be detected" def test_wire_terminating_at_pin_from_outside(self): """A wire that arrives at a pin from outside the component body @@ -448,17 +458,17 @@ class TestIntegrationFindWiresCrossingSymbols: tmp = _make_temp_schematic(extra) result = find_wires_crossing_symbols(tmp) d1_crossings = [c for c in result if c["component"]["reference"] == "D1"] - assert len(d1_crossings) == 0, ( - "Wire terminating at pin from outside should not be flagged" - ) + assert ( + len(d1_crossings) == 0 + ), "Wire terminating at pin from outside should not be flagged" def test_wire_shorts_component_pins_detected_as_collision(self): """Regression: a wire connecting pin1→pin2 of the same component must be reported even though both endpoints land on pins.""" r_sexp = _make_resistor_sexp("R_short", 100.0, 100.0) wire_sexp = ( - '(wire (pts (xy 100 103.81) (xy 100 96.19))\n' - ' (stroke (width 0) (type default))\n' + "(wire (pts (xy 100 103.81) (xy 100 96.19))\n" + " (stroke (width 0) (type default))\n" ' (uuid "aaaaaaaa-0000-0000-0000-000000000001"))' ) sch = _make_temp_schematic(r_sexp + "\n" + wire_sexp) @@ -511,6 +521,7 @@ class TestIntegrationGetElementsInRegion: # Unit tests — _check_wire_overlap # =================================================================== + class TestCheckWireOverlap: """Test wire overlap detection for horizontal, vertical, and diagonal cases.""" @@ -613,6 +624,7 @@ class TestIntegrationDiagonalWireOverlap: # Unit tests — _extract_lib_symbols # =================================================================== + class TestExtractLibSymbols: """Test _extract_lib_symbols helper.""" @@ -684,6 +696,7 @@ class TestExtractLibSymbols: # Unit tests — _parse_lib_symbol_graphics # =================================================================== + class TestParseLibSymbolGraphics: """Test graphics extraction from lib_symbol definitions.""" @@ -745,6 +758,7 @@ class TestParseLibSymbolGraphics: # Unit tests — _transform_local_point # =================================================================== + class TestTransformLocalPoint: """Test local→absolute coordinate transform.""" @@ -776,6 +790,7 @@ class TestTransformLocalPoint: # Unit tests — _compute_symbol_bbox_direct with graphics # =================================================================== + class TestComputeSymbolBboxWithGraphics: """Test that bounding box computation uses graphics points when available.""" @@ -783,14 +798,36 @@ class TestComputeSymbolBboxWithGraphics: """Device:R rectangle is (-1.016, -2.54) to (1.016, 2.54) in local coords. Pins at (0, ±3.81). Placed at (100, 100) with no rotation. Bbox should span from pin-to-pin in Y and use rectangle width in X.""" - sym = {"x": 100.0, "y": 100.0, "rotation": 0, "mirror_x": False, "mirror_y": False} + sym = { + "x": 100.0, + "y": 100.0, + "rotation": 0, + "mirror_x": False, + "mirror_y": False, + } pin_defs = { - "1": {"x": 0, "y": 3.81, "angle": 270, "length": 1.27, "name": "~", "type": "passive"}, - "2": {"x": 0, "y": -3.81, "angle": 90, "length": 1.27, "name": "~", "type": "passive"}, + "1": { + "x": 0, + "y": 3.81, + "angle": 270, + "length": 1.27, + "name": "~", + "type": "passive", + }, + "2": { + "x": 0, + "y": -3.81, + "angle": 90, + "length": 1.27, + "name": "~", + "type": "passive", + }, } graphics_points = [(-1.016, -2.54), (1.016, 2.54)] - bbox = _compute_symbol_bbox_direct(sym, pin_defs, graphics_points=graphics_points) + bbox = _compute_symbol_bbox_direct( + sym, pin_defs, graphics_points=graphics_points + ) assert bbox is not None min_x, min_y, max_x, max_y = bbox # X should come from rectangle: 100 ± 1.016 @@ -802,10 +839,30 @@ class TestComputeSymbolBboxWithGraphics: def test_fallback_without_graphics(self): """Without graphics_points, should use the old degenerate expansion.""" - sym = {"x": 100.0, "y": 100.0, "rotation": 0, "mirror_x": False, "mirror_y": False} + sym = { + "x": 100.0, + "y": 100.0, + "rotation": 0, + "mirror_x": False, + "mirror_y": False, + } pin_defs = { - "1": {"x": 0, "y": 3.81, "angle": 270, "length": 1.27, "name": "~", "type": "passive"}, - "2": {"x": 0, "y": -3.81, "angle": 90, "length": 1.27, "name": "~", "type": "passive"}, + "1": { + "x": 0, + "y": 3.81, + "angle": 270, + "length": 1.27, + "name": "~", + "type": "passive", + }, + "2": { + "x": 0, + "y": -3.81, + "angle": 90, + "length": 1.27, + "name": "~", + "type": "passive", + }, } bbox = _compute_symbol_bbox_direct(sym, pin_defs) @@ -817,15 +874,37 @@ class TestComputeSymbolBboxWithGraphics: def test_rotated_symbol_graphics(self): """Graphics points should be rotated along with the symbol.""" - sym = {"x": 100.0, "y": 100.0, "rotation": 90, "mirror_x": False, "mirror_y": False} + sym = { + "x": 100.0, + "y": 100.0, + "rotation": 90, + "mirror_x": False, + "mirror_y": False, + } pin_defs = { - "1": {"x": 0, "y": 3.81, "angle": 270, "length": 1.27, "name": "~", "type": "passive"}, - "2": {"x": 0, "y": -3.81, "angle": 90, "length": 1.27, "name": "~", "type": "passive"}, + "1": { + "x": 0, + "y": 3.81, + "angle": 270, + "length": 1.27, + "name": "~", + "type": "passive", + }, + "2": { + "x": 0, + "y": -3.81, + "angle": 90, + "length": 1.27, + "name": "~", + "type": "passive", + }, } # Rectangle corners in local coords graphics_points = [(-1.016, -2.54), (1.016, 2.54)] - bbox = _compute_symbol_bbox_direct(sym, pin_defs, graphics_points=graphics_points) + bbox = _compute_symbol_bbox_direct( + sym, pin_defs, graphics_points=graphics_points + ) assert bbox is not None min_x, min_y, max_x, max_y = bbox # After 90° rotation, X and Y swap roles @@ -856,7 +935,9 @@ class TestIntegrationGraphicsBbox: assert len(graphics_points) >= 2, "Should have extracted rectangle points" - bbox = _compute_symbol_bbox_direct(r1, pin_defs, graphics_points=graphics_points) + bbox = _compute_symbol_bbox_direct( + r1, pin_defs, graphics_points=graphics_points + ) assert bbox is not None min_x, min_y, max_x, max_y = bbox # Rectangle is ±1.016 in X, NOT ±1.5 from degenerate expansion