diff --git a/python/commands/board/outline.py b/python/commands/board/outline.py index e826000..d003213 100644 --- a/python/commands/board/outline.py +++ b/python/commands/board/outline.py @@ -7,7 +7,8 @@ import logging import math from typing import Dict, Any, Optional -logger = logging.getLogger('kicad_interface') +logger = logging.getLogger("kicad_interface") + class BoardOutlineCommands: """Handles board outline operations""" @@ -23,7 +24,7 @@ class BoardOutlineCommands: return { "success": False, "message": "No board is loaded", - "errorDetails": "Load or create a board first" + "errorDetails": "Load or create a board first", } shape = params.get("shape", "rectangle") @@ -40,46 +41,54 @@ class BoardOutlineCommands: return { "success": False, "message": "Invalid shape", - "errorDetails": f"Shape '{shape}' not supported" + "errorDetails": f"Shape '{shape}' not supported", } # Convert to internal units (nanometers) scale = 1000000 if unit == "mm" else 25400000 # mm or inch to nm - + # Create drawing for edge cuts edge_layer = self.board.GetLayerID("Edge.Cuts") - + if shape == "rectangle": if width is None or height is None: return { "success": False, "message": "Missing dimensions", - "errorDetails": "Both width and height are required for rectangle" + "errorDetails": "Both width and height are required for rectangle", } width_nm = int(width * scale) height_nm = int(height * scale) center_x_nm = int(center_x * scale) center_y_nm = int(center_y * scale) - + # Create rectangle - top_left = pcbnew.VECTOR2I(center_x_nm - width_nm // 2, center_y_nm - height_nm // 2) - top_right = pcbnew.VECTOR2I(center_x_nm + width_nm // 2, center_y_nm - height_nm // 2) - bottom_right = pcbnew.VECTOR2I(center_x_nm + width_nm // 2, center_y_nm + height_nm // 2) - bottom_left = pcbnew.VECTOR2I(center_x_nm - width_nm // 2, center_y_nm + height_nm // 2) - + top_left = pcbnew.VECTOR2I( + center_x_nm - width_nm // 2, center_y_nm - height_nm // 2 + ) + top_right = pcbnew.VECTOR2I( + center_x_nm + width_nm // 2, center_y_nm - height_nm // 2 + ) + bottom_right = pcbnew.VECTOR2I( + center_x_nm + width_nm // 2, center_y_nm + height_nm // 2 + ) + bottom_left = pcbnew.VECTOR2I( + center_x_nm - width_nm // 2, center_y_nm + height_nm // 2 + ) + # Add lines for rectangle self._add_edge_line(top_left, top_right, edge_layer) self._add_edge_line(top_right, bottom_right, edge_layer) self._add_edge_line(bottom_right, bottom_left, edge_layer) self._add_edge_line(bottom_left, top_left, edge_layer) - + elif shape == "rounded_rectangle": if width is None or height is None: return { "success": False, "message": "Missing dimensions", - "errorDetails": "Both width and height are required for rounded rectangle" + "errorDetails": "Both width and height are required for rounded rectangle", } width_nm = int(width * scale) @@ -87,26 +96,29 @@ class BoardOutlineCommands: center_x_nm = int(center_x * scale) center_y_nm = int(center_y * scale) corner_radius_nm = int(corner_radius * scale) - + # Create rounded rectangle self._add_rounded_rect( - center_x_nm, center_y_nm, - width_nm, height_nm, - corner_radius_nm, edge_layer + center_x_nm, + center_y_nm, + width_nm, + height_nm, + corner_radius_nm, + edge_layer, ) - + elif shape == "circle": if radius is None: return { "success": False, "message": "Missing radius", - "errorDetails": "Radius is required for circle" + "errorDetails": "Radius is required for circle", } center_x_nm = int(center_x * scale) center_y_nm = int(center_y * scale) radius_nm = int(radius * scale) - + # Create circle circle = pcbnew.PCB_SHAPE(self.board) circle.SetShape(pcbnew.SHAPE_T_CIRCLE) @@ -121,7 +133,7 @@ class BoardOutlineCommands: return { "success": False, "message": "Missing points", - "errorDetails": "At least 3 points are required for polygon" + "errorDetails": "At least 3 points are required for polygon", } # Convert points to nm @@ -130,13 +142,13 @@ class BoardOutlineCommands: x_nm = int(point["x"] * scale) y_nm = int(point["y"] * scale) polygon_points.append(pcbnew.VECTOR2I(x_nm, y_nm)) - + # Add lines for polygon for i in range(len(polygon_points)): self._add_edge_line( - polygon_points[i], - polygon_points[(i + 1) % len(polygon_points)], - edge_layer + polygon_points[i], + polygon_points[(i + 1) % len(polygon_points)], + edge_layer, ) return { @@ -149,8 +161,8 @@ class BoardOutlineCommands: "center": {"x": center_x, "y": center_y, "unit": unit}, "radius": radius, "cornerRadius": corner_radius, - "points": points - } + "points": points, + }, } except Exception as e: @@ -158,7 +170,7 @@ class BoardOutlineCommands: return { "success": False, "message": "Failed to add board outline", - "errorDetails": str(e) + "errorDetails": str(e), } def add_mounting_hole(self, params: Dict[str, Any]) -> Dict[str, Any]: @@ -168,7 +180,7 @@ class BoardOutlineCommands: return { "success": False, "message": "No board is loaded", - "errorDetails": "Load or create a board first" + "errorDetails": "Load or create a board first", } position = params.get("position") @@ -180,19 +192,24 @@ class BoardOutlineCommands: return { "success": False, "message": "Missing parameters", - "errorDetails": "position and diameter are required" + "errorDetails": "position and diameter are required", } # Convert to internal units (nanometers) - scale = 1000000 if position.get("unit", "mm") == "mm" else 25400000 # mm or inch to nm + scale = ( + 1000000 if position.get("unit", "mm") == "mm" else 25400000 + ) # mm or inch to nm x_nm = int(position["x"] * scale) y_nm = int(position["y"] * scale) diameter_nm = int(diameter * scale) - pad_diameter_nm = int(pad_diameter * scale) if pad_diameter else diameter_nm + scale # 1mm larger by default + pad_diameter_nm = ( + int(pad_diameter * scale) if pad_diameter else diameter_nm + scale + ) # 1mm larger by default # Create footprint for mounting hole with unique reference existing_mh = [ - fp.GetReference() for fp in self.board.GetFootprints() + fp.GetReference() + for fp in self.board.GetFootprints() if fp.GetReference().startswith("MH") ] next_num = 1 @@ -202,20 +219,22 @@ class BoardOutlineCommands: module = pcbnew.FOOTPRINT(self.board) module.SetReference(f"MH{next_num}") module.SetValue(f"MountingHole_{diameter}mm") - + # Create the pad for the hole pad = pcbnew.PAD(module) pad.SetNumber(1) pad.SetShape(pcbnew.PAD_SHAPE_CIRCLE) - pad.SetAttribute(pcbnew.PAD_ATTRIB_PTH if plated else pcbnew.PAD_ATTRIB_NPTH) + pad.SetAttribute( + pcbnew.PAD_ATTRIB_PTH if plated else pcbnew.PAD_ATTRIB_NPTH + ) pad.SetSize(pcbnew.VECTOR2I(pad_diameter_nm, pad_diameter_nm)) pad.SetDrillSize(pcbnew.VECTOR2I(diameter_nm, diameter_nm)) pad.SetPosition(pcbnew.VECTOR2I(0, 0)) # Position relative to module module.Add(pad) - + # Position the mounting hole module.SetPosition(pcbnew.VECTOR2I(x_nm, y_nm)) - + # Add to board self.board.Add(module) @@ -226,8 +245,8 @@ class BoardOutlineCommands: "position": position, "diameter": diameter, "padDiameter": pad_diameter or diameter + 1, - "plated": plated - } + "plated": plated, + }, } except Exception as e: @@ -235,7 +254,7 @@ class BoardOutlineCommands: return { "success": False, "message": "Failed to add mounting hole", - "errorDetails": str(e) + "errorDetails": str(e), } def add_text(self, params: Dict[str, Any]) -> Dict[str, Any]: @@ -245,7 +264,7 @@ class BoardOutlineCommands: return { "success": False, "message": "No board is loaded", - "errorDetails": "Load or create a board first" + "errorDetails": "Load or create a board first", } text = params.get("text") @@ -260,11 +279,13 @@ class BoardOutlineCommands: return { "success": False, "message": "Missing parameters", - "errorDetails": "text and position are required" + "errorDetails": "text and position are required", } # Convert to internal units (nanometers) - scale = 1000000 if position.get("unit", "mm") == "mm" else 25400000 # mm or inch to nm + scale = ( + 1000000 if position.get("unit", "mm") == "mm" else 25400000 + ) # mm or inch to nm x_nm = int(position["x"] * scale) y_nm = int(position["y"] * scale) size_nm = int(size * scale) @@ -276,7 +297,7 @@ class BoardOutlineCommands: return { "success": False, "message": "Invalid layer", - "errorDetails": f"Layer '{layer}' does not exist" + "errorDetails": f"Layer '{layer}' does not exist", } # Create text @@ -297,7 +318,7 @@ class BoardOutlineCommands: pcb_text.SetTextAngle(int(rotation * 10)) pcb_text.SetMirrored(mirror) - + # Add to board self.board.Add(pcb_text) @@ -311,8 +332,8 @@ class BoardOutlineCommands: "size": size, "thickness": thickness, "rotation": rotation, - "mirror": mirror - } + "mirror": mirror, + }, } except Exception as e: @@ -320,10 +341,12 @@ class BoardOutlineCommands: return { "success": False, "message": "Failed to add text", - "errorDetails": str(e) + "errorDetails": str(e), } - def _add_edge_line(self, start: pcbnew.VECTOR2I, end: pcbnew.VECTOR2I, layer: int) -> None: + def _add_edge_line( + self, start: pcbnew.VECTOR2I, end: pcbnew.VECTOR2I, layer: int + ) -> None: """Add a line to the edge cuts layer""" line = pcbnew.PCB_SHAPE(self.board) line.SetShape(pcbnew.SHAPE_T_SEGMENT) @@ -333,96 +356,112 @@ class BoardOutlineCommands: line.SetWidth(0) # Zero width for edge cuts self.board.Add(line) - def _add_rounded_rect(self, center_x_nm: int, center_y_nm: int, - width_nm: int, height_nm: int, - radius_nm: int, layer: int) -> None: + def _add_rounded_rect( + self, + center_x_nm: int, + center_y_nm: int, + width_nm: int, + height_nm: int, + radius_nm: int, + layer: int, + ) -> None: """Add a rounded rectangle to the edge cuts layer""" if radius_nm <= 0: # If no radius, create regular rectangle - top_left = pcbnew.VECTOR2I(center_x_nm - width_nm // 2, center_y_nm - height_nm // 2) - top_right = pcbnew.VECTOR2I(center_x_nm + width_nm // 2, center_y_nm - height_nm // 2) - bottom_right = pcbnew.VECTOR2I(center_x_nm + width_nm // 2, center_y_nm + height_nm // 2) - bottom_left = pcbnew.VECTOR2I(center_x_nm - width_nm // 2, center_y_nm + height_nm // 2) - + top_left = pcbnew.VECTOR2I( + center_x_nm - width_nm // 2, center_y_nm - height_nm // 2 + ) + top_right = pcbnew.VECTOR2I( + center_x_nm + width_nm // 2, center_y_nm - height_nm // 2 + ) + bottom_right = pcbnew.VECTOR2I( + center_x_nm + width_nm // 2, center_y_nm + height_nm // 2 + ) + bottom_left = pcbnew.VECTOR2I( + center_x_nm - width_nm // 2, center_y_nm + height_nm // 2 + ) + self._add_edge_line(top_left, top_right, layer) self._add_edge_line(top_right, bottom_right, layer) self._add_edge_line(bottom_right, bottom_left, layer) self._add_edge_line(bottom_left, top_left, layer) return - + # Calculate corner centers half_width = width_nm // 2 half_height = height_nm // 2 - + # Ensure radius is not larger than half the smallest dimension max_radius = min(half_width, half_height) if radius_nm > max_radius: radius_nm = max_radius - + # Calculate corner centers top_left_center = pcbnew.VECTOR2I( - center_x_nm - half_width + radius_nm, - center_y_nm - half_height + radius_nm + center_x_nm - half_width + radius_nm, center_y_nm - half_height + radius_nm ) top_right_center = pcbnew.VECTOR2I( - center_x_nm + half_width - radius_nm, - center_y_nm - half_height + radius_nm + center_x_nm + half_width - radius_nm, center_y_nm - half_height + radius_nm ) bottom_right_center = pcbnew.VECTOR2I( - center_x_nm + half_width - radius_nm, - center_y_nm + half_height - radius_nm + center_x_nm + half_width - radius_nm, center_y_nm + half_height - radius_nm ) bottom_left_center = pcbnew.VECTOR2I( - center_x_nm - half_width + radius_nm, - center_y_nm + half_height - radius_nm + center_x_nm - half_width + radius_nm, center_y_nm + half_height - radius_nm ) - + # Add arcs for corners self._add_corner_arc(top_left_center, radius_nm, 180, 270, layer) self._add_corner_arc(top_right_center, radius_nm, 270, 0, layer) self._add_corner_arc(bottom_right_center, radius_nm, 0, 90, layer) self._add_corner_arc(bottom_left_center, radius_nm, 90, 180, layer) - + # Add lines for straight edges # Top edge self._add_edge_line( pcbnew.VECTOR2I(top_left_center.x, top_left_center.y - radius_nm), pcbnew.VECTOR2I(top_right_center.x, top_right_center.y - radius_nm), - layer + layer, ) # Right edge self._add_edge_line( pcbnew.VECTOR2I(top_right_center.x + radius_nm, top_right_center.y), pcbnew.VECTOR2I(bottom_right_center.x + radius_nm, bottom_right_center.y), - layer + layer, ) # Bottom edge self._add_edge_line( pcbnew.VECTOR2I(bottom_right_center.x, bottom_right_center.y + radius_nm), pcbnew.VECTOR2I(bottom_left_center.x, bottom_left_center.y + radius_nm), - layer + layer, ) # Left edge self._add_edge_line( pcbnew.VECTOR2I(bottom_left_center.x - radius_nm, bottom_left_center.y), pcbnew.VECTOR2I(top_left_center.x - radius_nm, top_left_center.y), - layer + layer, ) - def _add_corner_arc(self, center: pcbnew.VECTOR2I, radius: int, - start_angle: float, end_angle: float, layer: int) -> None: + def _add_corner_arc( + self, + center: pcbnew.VECTOR2I, + radius: int, + start_angle: float, + end_angle: float, + layer: int, + ) -> None: """Add an arc for a rounded corner""" # Create arc for corner arc = pcbnew.PCB_SHAPE(self.board) arc.SetShape(pcbnew.SHAPE_T_ARC) arc.SetCenter(center) - + # Calculate start and end points start_x = center.x + int(radius * math.cos(math.radians(start_angle))) start_y = center.y + int(radius * math.sin(math.radians(start_angle))) end_x = center.x + int(radius * math.cos(math.radians(end_angle))) end_y = center.y + int(radius * math.sin(math.radians(end_angle))) - + arc.SetStart(pcbnew.VECTOR2I(start_x, start_y)) arc.SetEnd(pcbnew.VECTOR2I(end_x, end_y)) arc.SetLayer(layer) diff --git a/python/commands/export.py b/python/commands/export.py index d773431..5c74a66 100644 --- a/python/commands/export.py +++ b/python/commands/export.py @@ -8,7 +8,8 @@ import logging from typing import Dict, Any, Optional, List, Tuple import base64 -logger = logging.getLogger('kicad_interface') +logger = logging.getLogger("kicad_interface") + class ExportCommands: """Handles export-related KiCAD operations""" @@ -24,7 +25,7 @@ class ExportCommands: return { "success": False, "message": "No board is loaded", - "errorDetails": "Load or create a board first" + "errorDetails": "Load or create a board first", } output_dir = params.get("outputDir") @@ -38,7 +39,7 @@ class ExportCommands: return { "success": False, "message": "Missing output directory", - "errorDetails": "outputDir parameter is required" + "errorDetails": "outputDir parameter is required", } # Create output directory if it doesn't exist @@ -47,7 +48,7 @@ class ExportCommands: # Create plot controller plotter = pcbnew.PLOT_CONTROLLER(self.board) - + # Set up plot options plot_opts = plotter.GetPlotOptions() plot_opts.SetOutputDirectory(output_dir) @@ -84,28 +85,40 @@ class ExportCommands: if kicad_cli and board_file and os.path.exists(board_file): import subprocess + # Generate drill files using kicad-cli cmd = [ kicad_cli, - 'pcb', 'export', 'drill', - '--output', output_dir, - '--format', 'excellon', - '--drill-origin', 'absolute', - '--excellon-separate-th', # Separate plated/non-plated - board_file + "pcb", + "export", + "drill", + "--output", + output_dir, + "--format", + "excellon", + "--drill-origin", + "absolute", + "--excellon-separate-th", # Separate plated/non-plated + board_file, ] try: - result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=60 + ) if result.returncode == 0: # Get list of generated drill files for file in os.listdir(output_dir): if file.endswith((".drl", ".cnc")): drill_files.append(file) else: - logger.warning(f"Drill file generation failed: {result.stderr}") + logger.warning( + f"Drill file generation failed: {result.stderr}" + ) except Exception as drill_error: - logger.warning(f"Could not generate drill files: {str(drill_error)}") + logger.warning( + f"Could not generate drill files: {str(drill_error)}" + ) else: logger.warning("kicad-cli not available for drill file generation") @@ -115,9 +128,9 @@ class ExportCommands: "files": { "gerber": plotted_layers, "drill": drill_files, - "map": ["job.gbrjob"] if generate_map_file else [] + "map": ["job.gbrjob"] if generate_map_file else [], }, - "outputDir": output_dir + "outputDir": output_dir, } except Exception as e: @@ -125,7 +138,7 @@ class ExportCommands: return { "success": False, "message": "Failed to export Gerber files", - "errorDetails": str(e) + "errorDetails": str(e), } def export_pdf(self, params: Dict[str, Any]) -> Dict[str, Any]: @@ -135,7 +148,7 @@ class ExportCommands: return { "success": False, "message": "No board is loaded", - "errorDetails": "Load or create a board first" + "errorDetails": "Load or create a board first", } output_path = params.get("outputPath") @@ -148,7 +161,7 @@ class ExportCommands: return { "success": False, "message": "Missing output path", - "errorDetails": "outputPath parameter is required" + "errorDetails": "outputPath parameter is required", } # Create output directory if it doesn't exist @@ -180,13 +193,15 @@ class ExportCommands: plot_opts.SetAutoScale(True) # Note: KiCAD 9.0 doesn't support explicit page size selection # for formats other than A4. The PDF will auto-scale to fit. - logger.warning(f"Page size '{page_size}' requested, but KiCAD 9.0 only supports A4 explicitly. Using auto-scale instead.") + logger.warning( + f"Page size '{page_size}' requested, but KiCAD 9.0 only supports A4 explicitly. Using auto-scale instead." + ) # Open plot for writing # Note: For PDF, all layers are combined into a single file # KiCAD prepends the board filename to the plot file name - base_name = os.path.basename(output_path).replace('.pdf', '') - plotter.OpenPlotfile(base_name, pcbnew.PLOT_FORMAT_PDF, '') + base_name = os.path.basename(output_path).replace(".pdf", "") + plotter.OpenPlotfile(base_name, pcbnew.PLOT_FORMAT_PDF, "") # Plot specified layers or all enabled layers plotted_layers = [] @@ -212,7 +227,9 @@ class ExportCommands: # Get the actual output filename that was created board_name = os.path.splitext(os.path.basename(self.board.GetFileName()))[0] actual_filename = f"{board_name}-{base_name}.pdf" - actual_output_path = os.path.join(os.path.dirname(output_path), actual_filename) + actual_output_path = os.path.join( + os.path.dirname(output_path), actual_filename + ) return { "success": True, @@ -221,8 +238,8 @@ class ExportCommands: "path": actual_output_path, "requestedPath": output_path, "layers": plotted_layers, - "pageSize": page_size if page_size == "A4" else "auto-scaled" - } + "pageSize": page_size if page_size == "A4" else "auto-scaled", + }, } except Exception as e: @@ -230,7 +247,7 @@ class ExportCommands: return { "success": False, "message": "Failed to export PDF file", - "errorDetails": str(e) + "errorDetails": str(e), } def export_svg(self, params: Dict[str, Any]) -> Dict[str, Any]: @@ -240,7 +257,7 @@ class ExportCommands: return { "success": False, "message": "No board is loaded", - "errorDetails": "Load or create a board first" + "errorDetails": "Load or create a board first", } output_path = params.get("outputPath") @@ -252,7 +269,7 @@ class ExportCommands: return { "success": False, "message": "Missing output path", - "errorDetails": "outputPath parameter is required" + "errorDetails": "outputPath parameter is required", } # Create output directory if it doesn't exist @@ -261,7 +278,7 @@ class ExportCommands: # Create plot controller plotter = pcbnew.PLOT_CONTROLLER(self.board) - + # Set up plot options plot_opts = plotter.GetPlotOptions() plot_opts.SetOutputDirectory(os.path.dirname(output_path)) @@ -290,10 +307,7 @@ class ExportCommands: return { "success": True, "message": "Exported SVG file", - "file": { - "path": output_path, - "layers": plotted_layers - } + "file": {"path": output_path, "layers": plotted_layers}, } except Exception as e: @@ -301,7 +315,7 @@ class ExportCommands: return { "success": False, "message": "Failed to export SVG file", - "errorDetails": str(e) + "errorDetails": str(e), } def export_3d(self, params: Dict[str, Any]) -> Dict[str, Any]: @@ -315,7 +329,7 @@ class ExportCommands: return { "success": False, "message": "No board is loaded", - "errorDetails": "Load or create a board first" + "errorDetails": "Load or create a board first", } output_path = params.get("outputPath") @@ -329,7 +343,7 @@ class ExportCommands: return { "success": False, "message": "Missing output path", - "errorDetails": "outputPath parameter is required" + "errorDetails": "outputPath parameter is required", } # Get board file path @@ -338,7 +352,7 @@ class ExportCommands: return { "success": False, "message": "Board file not found", - "errorDetails": "Board must be saved before exporting 3D models" + "errorDetails": "Board must be saved before exporting 3D models", } # Create output directory if it doesn't exist @@ -351,7 +365,7 @@ class ExportCommands: return { "success": False, "message": "kicad-cli not found", - "errorDetails": "KiCAD CLI tool not found. Install KiCAD 8.0+ or set PATH." + "errorDetails": "KiCAD CLI tool not found. Install KiCAD 8.0+ or set PATH.", } # Build command based on format @@ -360,30 +374,39 @@ class ExportCommands: if format_upper == "STEP": cmd = [ kicad_cli, - 'pcb', 'export', 'step', - '--output', output_path, - '--force' # Overwrite existing file + "pcb", + "export", + "step", + "--output", + output_path, + "--force", # Overwrite existing file ] # Add options based on parameters if not include_components: - cmd.append('--no-components') + cmd.append("--no-components") if include_copper: - cmd.extend(['--include-tracks', '--include-pads', '--include-zones']) + cmd.extend( + ["--include-tracks", "--include-pads", "--include-zones"] + ) if include_silkscreen: - cmd.append('--include-silkscreen') + cmd.append("--include-silkscreen") if include_solder_mask: - cmd.append('--include-soldermask') + cmd.append("--include-soldermask") cmd.append(board_file) elif format_upper == "VRML": cmd = [ kicad_cli, - 'pcb', 'export', 'vrml', - '--output', output_path, - '--units', 'mm', # Use mm for consistency - '--force' + "pcb", + "export", + "vrml", + "--output", + output_path, + "--units", + "mm", # Use mm for consistency + "--force", ] if not include_components: @@ -397,7 +420,7 @@ class ExportCommands: return { "success": False, "message": "Unsupported format", - "errorDetails": f"Format {format} is not supported. Use 'STEP' or 'VRML'." + "errorDetails": f"Format {format} is not supported. Use 'STEP' or 'VRML'.", } # Execute kicad-cli command @@ -407,7 +430,7 @@ class ExportCommands: cmd, capture_output=True, text=True, - timeout=300 # 5 minute timeout for 3D export + timeout=300, # 5 minute timeout for 3D export ) if result.returncode != 0: @@ -415,16 +438,13 @@ class ExportCommands: return { "success": False, "message": "3D export command failed", - "errorDetails": result.stderr + "errorDetails": result.stderr, } return { "success": True, "message": f"Exported {format_upper} file", - "file": { - "path": output_path, - "format": format_upper - } + "file": {"path": output_path, "format": format_upper}, } except subprocess.TimeoutExpired: @@ -432,14 +452,14 @@ class ExportCommands: return { "success": False, "message": "3D export timed out", - "errorDetails": "Export took longer than 5 minutes" + "errorDetails": "Export took longer than 5 minutes", } except Exception as e: logger.error(f"Error exporting 3D model: {str(e)}") return { "success": False, "message": "Failed to export 3D model", - "errorDetails": str(e) + "errorDetails": str(e), } def export_bom(self, params: Dict[str, Any]) -> Dict[str, Any]: @@ -449,7 +469,7 @@ class ExportCommands: return { "success": False, "message": "No board is loaded", - "errorDetails": "Load or create a board first" + "errorDetails": "Load or create a board first", } output_path = params.get("outputPath") @@ -461,7 +481,7 @@ class ExportCommands: return { "success": False, "message": "Missing output path", - "errorDetails": "outputPath parameter is required" + "errorDetails": "outputPath parameter is required", } # Create output directory if it doesn't exist @@ -475,7 +495,7 @@ class ExportCommands: "reference": module.GetReference(), "value": module.GetValue(), "footprint": module.GetFPID().GetUniStringLibId(), - "layer": self.board.GetLayerName(module.GetLayer()) + "layer": self.board.GetLayerName(module.GetLayer()), } # Add requested attributes @@ -495,7 +515,7 @@ class ExportCommands: "value": comp["value"], "footprint": comp["footprint"], "quantity": 1, - "references": [comp["reference"]] + "references": [comp["reference"]], } else: grouped[key]["quantity"] += 1 @@ -515,7 +535,7 @@ class ExportCommands: return { "success": False, "message": "Unsupported format", - "errorDetails": f"Format {format} is not supported" + "errorDetails": f"Format {format} is not supported", } return { @@ -524,8 +544,8 @@ class ExportCommands: "file": { "path": output_path, "format": format, - "componentCount": len(components) - } + "componentCount": len(components), + }, } except Exception as e: @@ -533,13 +553,14 @@ class ExportCommands: return { "success": False, "message": "Failed to export BOM", - "errorDetails": str(e) + "errorDetails": str(e), } def _export_bom_csv(self, path: str, components: List[Dict[str, Any]]) -> None: """Export BOM to CSV format""" import csv - with open(path, 'w', newline='') as f: + + with open(path, "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=components[0].keys()) writer.writeheader() writer.writerows(components) @@ -547,6 +568,7 @@ class ExportCommands: def _export_bom_xml(self, path: str, components: List[Dict[str, Any]]) -> None: """Export BOM to XML format""" import xml.etree.ElementTree as ET + root = ET.Element("bom") for comp in components: comp_elem = ET.SubElement(root, "component") @@ -554,7 +576,7 @@ class ExportCommands: elem = ET.SubElement(comp_elem, key) elem.text = str(value) tree = ET.ElementTree(root) - tree.write(path, encoding='utf-8', xml_declaration=True) + tree.write(path, encoding="utf-8", xml_declaration=True) def _export_bom_html(self, path: str, components: List[Dict[str, Any]]) -> None: """Export BOM to HTML format""" @@ -571,13 +593,14 @@ class ExportCommands: html.append(f"