style: apply black formatting to changed files

This commit is contained in:
Roman PASSLER
2026-03-01 19:15:32 +01:00
parent 61356d42cb
commit 2dd9de6a52
4 changed files with 356 additions and 192 deletions

View File

@@ -7,7 +7,8 @@ import logging
import math import math
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
logger = logging.getLogger('kicad_interface') logger = logging.getLogger("kicad_interface")
class BoardOutlineCommands: class BoardOutlineCommands:
"""Handles board outline operations""" """Handles board outline operations"""
@@ -23,7 +24,7 @@ class BoardOutlineCommands:
return { return {
"success": False, "success": False,
"message": "No board is loaded", "message": "No board is loaded",
"errorDetails": "Load or create a board first" "errorDetails": "Load or create a board first",
} }
shape = params.get("shape", "rectangle") shape = params.get("shape", "rectangle")
@@ -40,7 +41,7 @@ class BoardOutlineCommands:
return { return {
"success": False, "success": False,
"message": "Invalid shape", "message": "Invalid shape",
"errorDetails": f"Shape '{shape}' not supported" "errorDetails": f"Shape '{shape}' not supported",
} }
# Convert to internal units (nanometers) # Convert to internal units (nanometers)
@@ -54,7 +55,7 @@ class BoardOutlineCommands:
return { return {
"success": False, "success": False,
"message": "Missing dimensions", "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) width_nm = int(width * scale)
@@ -63,10 +64,18 @@ class BoardOutlineCommands:
center_y_nm = int(center_y * scale) center_y_nm = int(center_y * scale)
# Create rectangle # Create rectangle
top_left = pcbnew.VECTOR2I(center_x_nm - width_nm // 2, center_y_nm - height_nm // 2) top_left = pcbnew.VECTOR2I(
top_right = pcbnew.VECTOR2I(center_x_nm + width_nm // 2, center_y_nm - height_nm // 2) 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_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 # Add lines for rectangle
self._add_edge_line(top_left, top_right, edge_layer) self._add_edge_line(top_left, top_right, edge_layer)
@@ -79,7 +88,7 @@ class BoardOutlineCommands:
return { return {
"success": False, "success": False,
"message": "Missing dimensions", "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) width_nm = int(width * scale)
@@ -90,9 +99,12 @@ class BoardOutlineCommands:
# Create rounded rectangle # Create rounded rectangle
self._add_rounded_rect( self._add_rounded_rect(
center_x_nm, center_y_nm, center_x_nm,
width_nm, height_nm, center_y_nm,
corner_radius_nm, edge_layer width_nm,
height_nm,
corner_radius_nm,
edge_layer,
) )
elif shape == "circle": elif shape == "circle":
@@ -100,7 +112,7 @@ class BoardOutlineCommands:
return { return {
"success": False, "success": False,
"message": "Missing radius", "message": "Missing radius",
"errorDetails": "Radius is required for circle" "errorDetails": "Radius is required for circle",
} }
center_x_nm = int(center_x * scale) center_x_nm = int(center_x * scale)
@@ -121,7 +133,7 @@ class BoardOutlineCommands:
return { return {
"success": False, "success": False,
"message": "Missing points", "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 # Convert points to nm
@@ -136,7 +148,7 @@ class BoardOutlineCommands:
self._add_edge_line( self._add_edge_line(
polygon_points[i], polygon_points[i],
polygon_points[(i + 1) % len(polygon_points)], polygon_points[(i + 1) % len(polygon_points)],
edge_layer edge_layer,
) )
return { return {
@@ -149,8 +161,8 @@ class BoardOutlineCommands:
"center": {"x": center_x, "y": center_y, "unit": unit}, "center": {"x": center_x, "y": center_y, "unit": unit},
"radius": radius, "radius": radius,
"cornerRadius": corner_radius, "cornerRadius": corner_radius,
"points": points "points": points,
} },
} }
except Exception as e: except Exception as e:
@@ -158,7 +170,7 @@ class BoardOutlineCommands:
return { return {
"success": False, "success": False,
"message": "Failed to add board outline", "message": "Failed to add board outline",
"errorDetails": str(e) "errorDetails": str(e),
} }
def add_mounting_hole(self, params: Dict[str, Any]) -> Dict[str, Any]: def add_mounting_hole(self, params: Dict[str, Any]) -> Dict[str, Any]:
@@ -168,7 +180,7 @@ class BoardOutlineCommands:
return { return {
"success": False, "success": False,
"message": "No board is loaded", "message": "No board is loaded",
"errorDetails": "Load or create a board first" "errorDetails": "Load or create a board first",
} }
position = params.get("position") position = params.get("position")
@@ -180,19 +192,24 @@ class BoardOutlineCommands:
return { return {
"success": False, "success": False,
"message": "Missing parameters", "message": "Missing parameters",
"errorDetails": "position and diameter are required" "errorDetails": "position and diameter are required",
} }
# Convert to internal units (nanometers) # 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) x_nm = int(position["x"] * scale)
y_nm = int(position["y"] * scale) y_nm = int(position["y"] * scale)
diameter_nm = int(diameter * 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 # Create footprint for mounting hole with unique reference
existing_mh = [ existing_mh = [
fp.GetReference() for fp in self.board.GetFootprints() fp.GetReference()
for fp in self.board.GetFootprints()
if fp.GetReference().startswith("MH") if fp.GetReference().startswith("MH")
] ]
next_num = 1 next_num = 1
@@ -207,7 +224,9 @@ class BoardOutlineCommands:
pad = pcbnew.PAD(module) pad = pcbnew.PAD(module)
pad.SetNumber(1) pad.SetNumber(1)
pad.SetShape(pcbnew.PAD_SHAPE_CIRCLE) 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.SetSize(pcbnew.VECTOR2I(pad_diameter_nm, pad_diameter_nm))
pad.SetDrillSize(pcbnew.VECTOR2I(diameter_nm, diameter_nm)) pad.SetDrillSize(pcbnew.VECTOR2I(diameter_nm, diameter_nm))
pad.SetPosition(pcbnew.VECTOR2I(0, 0)) # Position relative to module pad.SetPosition(pcbnew.VECTOR2I(0, 0)) # Position relative to module
@@ -226,8 +245,8 @@ class BoardOutlineCommands:
"position": position, "position": position,
"diameter": diameter, "diameter": diameter,
"padDiameter": pad_diameter or diameter + 1, "padDiameter": pad_diameter or diameter + 1,
"plated": plated "plated": plated,
} },
} }
except Exception as e: except Exception as e:
@@ -235,7 +254,7 @@ class BoardOutlineCommands:
return { return {
"success": False, "success": False,
"message": "Failed to add mounting hole", "message": "Failed to add mounting hole",
"errorDetails": str(e) "errorDetails": str(e),
} }
def add_text(self, params: Dict[str, Any]) -> Dict[str, Any]: def add_text(self, params: Dict[str, Any]) -> Dict[str, Any]:
@@ -245,7 +264,7 @@ class BoardOutlineCommands:
return { return {
"success": False, "success": False,
"message": "No board is loaded", "message": "No board is loaded",
"errorDetails": "Load or create a board first" "errorDetails": "Load or create a board first",
} }
text = params.get("text") text = params.get("text")
@@ -260,11 +279,13 @@ class BoardOutlineCommands:
return { return {
"success": False, "success": False,
"message": "Missing parameters", "message": "Missing parameters",
"errorDetails": "text and position are required" "errorDetails": "text and position are required",
} }
# Convert to internal units (nanometers) # 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) x_nm = int(position["x"] * scale)
y_nm = int(position["y"] * scale) y_nm = int(position["y"] * scale)
size_nm = int(size * scale) size_nm = int(size * scale)
@@ -276,7 +297,7 @@ class BoardOutlineCommands:
return { return {
"success": False, "success": False,
"message": "Invalid layer", "message": "Invalid layer",
"errorDetails": f"Layer '{layer}' does not exist" "errorDetails": f"Layer '{layer}' does not exist",
} }
# Create text # Create text
@@ -311,8 +332,8 @@ class BoardOutlineCommands:
"size": size, "size": size,
"thickness": thickness, "thickness": thickness,
"rotation": rotation, "rotation": rotation,
"mirror": mirror "mirror": mirror,
} },
} }
except Exception as e: except Exception as e:
@@ -320,10 +341,12 @@ class BoardOutlineCommands:
return { return {
"success": False, "success": False,
"message": "Failed to add text", "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""" """Add a line to the edge cuts layer"""
line = pcbnew.PCB_SHAPE(self.board) line = pcbnew.PCB_SHAPE(self.board)
line.SetShape(pcbnew.SHAPE_T_SEGMENT) line.SetShape(pcbnew.SHAPE_T_SEGMENT)
@@ -333,16 +356,30 @@ class BoardOutlineCommands:
line.SetWidth(0) # Zero width for edge cuts line.SetWidth(0) # Zero width for edge cuts
self.board.Add(line) self.board.Add(line)
def _add_rounded_rect(self, center_x_nm: int, center_y_nm: int, def _add_rounded_rect(
width_nm: int, height_nm: int, self,
radius_nm: int, layer: int) -> None: 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""" """Add a rounded rectangle to the edge cuts layer"""
if radius_nm <= 0: if radius_nm <= 0:
# If no radius, create regular rectangle # If no radius, create regular rectangle
top_left = pcbnew.VECTOR2I(center_x_nm - width_nm // 2, center_y_nm - height_nm // 2) top_left = pcbnew.VECTOR2I(
top_right = pcbnew.VECTOR2I(center_x_nm + width_nm // 2, center_y_nm - height_nm // 2) 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_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_left, top_right, layer)
self._add_edge_line(top_right, bottom_right, layer) self._add_edge_line(top_right, bottom_right, layer)
@@ -361,20 +398,16 @@ class BoardOutlineCommands:
# Calculate corner centers # Calculate corner centers
top_left_center = pcbnew.VECTOR2I( top_left_center = pcbnew.VECTOR2I(
center_x_nm - half_width + radius_nm, center_x_nm - half_width + radius_nm, center_y_nm - half_height + radius_nm
center_y_nm - half_height + radius_nm
) )
top_right_center = pcbnew.VECTOR2I( top_right_center = pcbnew.VECTOR2I(
center_x_nm + half_width - radius_nm, center_x_nm + half_width - radius_nm, center_y_nm - half_height + radius_nm
center_y_nm - half_height + radius_nm
) )
bottom_right_center = pcbnew.VECTOR2I( bottom_right_center = pcbnew.VECTOR2I(
center_x_nm + half_width - radius_nm, center_x_nm + half_width - radius_nm, center_y_nm + half_height - radius_nm
center_y_nm + half_height - radius_nm
) )
bottom_left_center = pcbnew.VECTOR2I( bottom_left_center = pcbnew.VECTOR2I(
center_x_nm - half_width + radius_nm, center_x_nm - half_width + radius_nm, center_y_nm + half_height - radius_nm
center_y_nm + half_height - radius_nm
) )
# Add arcs for corners # Add arcs for corners
@@ -388,29 +421,35 @@ class BoardOutlineCommands:
self._add_edge_line( self._add_edge_line(
pcbnew.VECTOR2I(top_left_center.x, top_left_center.y - radius_nm), pcbnew.VECTOR2I(top_left_center.x, top_left_center.y - radius_nm),
pcbnew.VECTOR2I(top_right_center.x, top_right_center.y - radius_nm), pcbnew.VECTOR2I(top_right_center.x, top_right_center.y - radius_nm),
layer layer,
) )
# Right edge # Right edge
self._add_edge_line( self._add_edge_line(
pcbnew.VECTOR2I(top_right_center.x + radius_nm, top_right_center.y), pcbnew.VECTOR2I(top_right_center.x + radius_nm, top_right_center.y),
pcbnew.VECTOR2I(bottom_right_center.x + radius_nm, bottom_right_center.y), pcbnew.VECTOR2I(bottom_right_center.x + radius_nm, bottom_right_center.y),
layer layer,
) )
# Bottom edge # Bottom edge
self._add_edge_line( self._add_edge_line(
pcbnew.VECTOR2I(bottom_right_center.x, bottom_right_center.y + radius_nm), pcbnew.VECTOR2I(bottom_right_center.x, bottom_right_center.y + radius_nm),
pcbnew.VECTOR2I(bottom_left_center.x, bottom_left_center.y + radius_nm), pcbnew.VECTOR2I(bottom_left_center.x, bottom_left_center.y + radius_nm),
layer layer,
) )
# Left edge # Left edge
self._add_edge_line( self._add_edge_line(
pcbnew.VECTOR2I(bottom_left_center.x - radius_nm, bottom_left_center.y), pcbnew.VECTOR2I(bottom_left_center.x - radius_nm, bottom_left_center.y),
pcbnew.VECTOR2I(top_left_center.x - radius_nm, top_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, def _add_corner_arc(
start_angle: float, end_angle: float, layer: int) -> None: self,
center: pcbnew.VECTOR2I,
radius: int,
start_angle: float,
end_angle: float,
layer: int,
) -> None:
"""Add an arc for a rounded corner""" """Add an arc for a rounded corner"""
# Create arc for corner # Create arc for corner
arc = pcbnew.PCB_SHAPE(self.board) arc = pcbnew.PCB_SHAPE(self.board)

View File

@@ -8,7 +8,8 @@ import logging
from typing import Dict, Any, Optional, List, Tuple from typing import Dict, Any, Optional, List, Tuple
import base64 import base64
logger = logging.getLogger('kicad_interface') logger = logging.getLogger("kicad_interface")
class ExportCommands: class ExportCommands:
"""Handles export-related KiCAD operations""" """Handles export-related KiCAD operations"""
@@ -24,7 +25,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "No board is loaded", "message": "No board is loaded",
"errorDetails": "Load or create a board first" "errorDetails": "Load or create a board first",
} }
output_dir = params.get("outputDir") output_dir = params.get("outputDir")
@@ -38,7 +39,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "Missing output directory", "message": "Missing output directory",
"errorDetails": "outputDir parameter is required" "errorDetails": "outputDir parameter is required",
} }
# Create output directory if it doesn't exist # Create output directory if it doesn't exist
@@ -84,28 +85,40 @@ class ExportCommands:
if kicad_cli and board_file and os.path.exists(board_file): if kicad_cli and board_file and os.path.exists(board_file):
import subprocess import subprocess
# Generate drill files using kicad-cli # Generate drill files using kicad-cli
cmd = [ cmd = [
kicad_cli, kicad_cli,
'pcb', 'export', 'drill', "pcb",
'--output', output_dir, "export",
'--format', 'excellon', "drill",
'--drill-origin', 'absolute', "--output",
'--excellon-separate-th', # Separate plated/non-plated output_dir,
board_file "--format",
"excellon",
"--drill-origin",
"absolute",
"--excellon-separate-th", # Separate plated/non-plated
board_file,
] ]
try: 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: if result.returncode == 0:
# Get list of generated drill files # Get list of generated drill files
for file in os.listdir(output_dir): for file in os.listdir(output_dir):
if file.endswith((".drl", ".cnc")): if file.endswith((".drl", ".cnc")):
drill_files.append(file) drill_files.append(file)
else: else:
logger.warning(f"Drill file generation failed: {result.stderr}") logger.warning(
f"Drill file generation failed: {result.stderr}"
)
except Exception as drill_error: 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: else:
logger.warning("kicad-cli not available for drill file generation") logger.warning("kicad-cli not available for drill file generation")
@@ -115,9 +128,9 @@ class ExportCommands:
"files": { "files": {
"gerber": plotted_layers, "gerber": plotted_layers,
"drill": drill_files, "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: except Exception as e:
@@ -125,7 +138,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "Failed to export Gerber files", "message": "Failed to export Gerber files",
"errorDetails": str(e) "errorDetails": str(e),
} }
def export_pdf(self, params: Dict[str, Any]) -> Dict[str, Any]: def export_pdf(self, params: Dict[str, Any]) -> Dict[str, Any]:
@@ -135,7 +148,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "No board is loaded", "message": "No board is loaded",
"errorDetails": "Load or create a board first" "errorDetails": "Load or create a board first",
} }
output_path = params.get("outputPath") output_path = params.get("outputPath")
@@ -148,7 +161,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "Missing output path", "message": "Missing output path",
"errorDetails": "outputPath parameter is required" "errorDetails": "outputPath parameter is required",
} }
# Create output directory if it doesn't exist # Create output directory if it doesn't exist
@@ -180,13 +193,15 @@ class ExportCommands:
plot_opts.SetAutoScale(True) plot_opts.SetAutoScale(True)
# Note: KiCAD 9.0 doesn't support explicit page size selection # Note: KiCAD 9.0 doesn't support explicit page size selection
# for formats other than A4. The PDF will auto-scale to fit. # 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 # Open plot for writing
# Note: For PDF, all layers are combined into a single file # Note: For PDF, all layers are combined into a single file
# KiCAD prepends the board filename to the plot file name # KiCAD prepends the board filename to the plot file name
base_name = os.path.basename(output_path).replace('.pdf', '') base_name = os.path.basename(output_path).replace(".pdf", "")
plotter.OpenPlotfile(base_name, pcbnew.PLOT_FORMAT_PDF, '') plotter.OpenPlotfile(base_name, pcbnew.PLOT_FORMAT_PDF, "")
# Plot specified layers or all enabled layers # Plot specified layers or all enabled layers
plotted_layers = [] plotted_layers = []
@@ -212,7 +227,9 @@ class ExportCommands:
# Get the actual output filename that was created # Get the actual output filename that was created
board_name = os.path.splitext(os.path.basename(self.board.GetFileName()))[0] board_name = os.path.splitext(os.path.basename(self.board.GetFileName()))[0]
actual_filename = f"{board_name}-{base_name}.pdf" 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 { return {
"success": True, "success": True,
@@ -221,8 +238,8 @@ class ExportCommands:
"path": actual_output_path, "path": actual_output_path,
"requestedPath": output_path, "requestedPath": output_path,
"layers": plotted_layers, "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: except Exception as e:
@@ -230,7 +247,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "Failed to export PDF file", "message": "Failed to export PDF file",
"errorDetails": str(e) "errorDetails": str(e),
} }
def export_svg(self, params: Dict[str, Any]) -> Dict[str, Any]: def export_svg(self, params: Dict[str, Any]) -> Dict[str, Any]:
@@ -240,7 +257,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "No board is loaded", "message": "No board is loaded",
"errorDetails": "Load or create a board first" "errorDetails": "Load or create a board first",
} }
output_path = params.get("outputPath") output_path = params.get("outputPath")
@@ -252,7 +269,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "Missing output path", "message": "Missing output path",
"errorDetails": "outputPath parameter is required" "errorDetails": "outputPath parameter is required",
} }
# Create output directory if it doesn't exist # Create output directory if it doesn't exist
@@ -290,10 +307,7 @@ class ExportCommands:
return { return {
"success": True, "success": True,
"message": "Exported SVG file", "message": "Exported SVG file",
"file": { "file": {"path": output_path, "layers": plotted_layers},
"path": output_path,
"layers": plotted_layers
}
} }
except Exception as e: except Exception as e:
@@ -301,7 +315,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "Failed to export SVG file", "message": "Failed to export SVG file",
"errorDetails": str(e) "errorDetails": str(e),
} }
def export_3d(self, params: Dict[str, Any]) -> Dict[str, Any]: def export_3d(self, params: Dict[str, Any]) -> Dict[str, Any]:
@@ -315,7 +329,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "No board is loaded", "message": "No board is loaded",
"errorDetails": "Load or create a board first" "errorDetails": "Load or create a board first",
} }
output_path = params.get("outputPath") output_path = params.get("outputPath")
@@ -329,7 +343,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "Missing output path", "message": "Missing output path",
"errorDetails": "outputPath parameter is required" "errorDetails": "outputPath parameter is required",
} }
# Get board file path # Get board file path
@@ -338,7 +352,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "Board file not found", "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 # Create output directory if it doesn't exist
@@ -351,7 +365,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "kicad-cli not found", "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 # Build command based on format
@@ -360,30 +374,39 @@ class ExportCommands:
if format_upper == "STEP": if format_upper == "STEP":
cmd = [ cmd = [
kicad_cli, kicad_cli,
'pcb', 'export', 'step', "pcb",
'--output', output_path, "export",
'--force' # Overwrite existing file "step",
"--output",
output_path,
"--force", # Overwrite existing file
] ]
# Add options based on parameters # Add options based on parameters
if not include_components: if not include_components:
cmd.append('--no-components') cmd.append("--no-components")
if include_copper: if include_copper:
cmd.extend(['--include-tracks', '--include-pads', '--include-zones']) cmd.extend(
["--include-tracks", "--include-pads", "--include-zones"]
)
if include_silkscreen: if include_silkscreen:
cmd.append('--include-silkscreen') cmd.append("--include-silkscreen")
if include_solder_mask: if include_solder_mask:
cmd.append('--include-soldermask') cmd.append("--include-soldermask")
cmd.append(board_file) cmd.append(board_file)
elif format_upper == "VRML": elif format_upper == "VRML":
cmd = [ cmd = [
kicad_cli, kicad_cli,
'pcb', 'export', 'vrml', "pcb",
'--output', output_path, "export",
'--units', 'mm', # Use mm for consistency "vrml",
'--force' "--output",
output_path,
"--units",
"mm", # Use mm for consistency
"--force",
] ]
if not include_components: if not include_components:
@@ -397,7 +420,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "Unsupported format", "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 # Execute kicad-cli command
@@ -407,7 +430,7 @@ class ExportCommands:
cmd, cmd,
capture_output=True, capture_output=True,
text=True, text=True,
timeout=300 # 5 minute timeout for 3D export timeout=300, # 5 minute timeout for 3D export
) )
if result.returncode != 0: if result.returncode != 0:
@@ -415,16 +438,13 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "3D export command failed", "message": "3D export command failed",
"errorDetails": result.stderr "errorDetails": result.stderr,
} }
return { return {
"success": True, "success": True,
"message": f"Exported {format_upper} file", "message": f"Exported {format_upper} file",
"file": { "file": {"path": output_path, "format": format_upper},
"path": output_path,
"format": format_upper
}
} }
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
@@ -432,14 +452,14 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "3D export timed out", "message": "3D export timed out",
"errorDetails": "Export took longer than 5 minutes" "errorDetails": "Export took longer than 5 minutes",
} }
except Exception as e: except Exception as e:
logger.error(f"Error exporting 3D model: {str(e)}") logger.error(f"Error exporting 3D model: {str(e)}")
return { return {
"success": False, "success": False,
"message": "Failed to export 3D model", "message": "Failed to export 3D model",
"errorDetails": str(e) "errorDetails": str(e),
} }
def export_bom(self, params: Dict[str, Any]) -> Dict[str, Any]: def export_bom(self, params: Dict[str, Any]) -> Dict[str, Any]:
@@ -449,7 +469,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "No board is loaded", "message": "No board is loaded",
"errorDetails": "Load or create a board first" "errorDetails": "Load or create a board first",
} }
output_path = params.get("outputPath") output_path = params.get("outputPath")
@@ -461,7 +481,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "Missing output path", "message": "Missing output path",
"errorDetails": "outputPath parameter is required" "errorDetails": "outputPath parameter is required",
} }
# Create output directory if it doesn't exist # Create output directory if it doesn't exist
@@ -475,7 +495,7 @@ class ExportCommands:
"reference": module.GetReference(), "reference": module.GetReference(),
"value": module.GetValue(), "value": module.GetValue(),
"footprint": module.GetFPID().GetUniStringLibId(), "footprint": module.GetFPID().GetUniStringLibId(),
"layer": self.board.GetLayerName(module.GetLayer()) "layer": self.board.GetLayerName(module.GetLayer()),
} }
# Add requested attributes # Add requested attributes
@@ -495,7 +515,7 @@ class ExportCommands:
"value": comp["value"], "value": comp["value"],
"footprint": comp["footprint"], "footprint": comp["footprint"],
"quantity": 1, "quantity": 1,
"references": [comp["reference"]] "references": [comp["reference"]],
} }
else: else:
grouped[key]["quantity"] += 1 grouped[key]["quantity"] += 1
@@ -515,7 +535,7 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "Unsupported format", "message": "Unsupported format",
"errorDetails": f"Format {format} is not supported" "errorDetails": f"Format {format} is not supported",
} }
return { return {
@@ -524,8 +544,8 @@ class ExportCommands:
"file": { "file": {
"path": output_path, "path": output_path,
"format": format, "format": format,
"componentCount": len(components) "componentCount": len(components),
} },
} }
except Exception as e: except Exception as e:
@@ -533,13 +553,14 @@ class ExportCommands:
return { return {
"success": False, "success": False,
"message": "Failed to export BOM", "message": "Failed to export BOM",
"errorDetails": str(e) "errorDetails": str(e),
} }
def _export_bom_csv(self, path: str, components: List[Dict[str, Any]]) -> None: def _export_bom_csv(self, path: str, components: List[Dict[str, Any]]) -> None:
"""Export BOM to CSV format""" """Export BOM to CSV format"""
import csv 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 = csv.DictWriter(f, fieldnames=components[0].keys())
writer.writeheader() writer.writeheader()
writer.writerows(components) writer.writerows(components)
@@ -547,6 +568,7 @@ class ExportCommands:
def _export_bom_xml(self, path: str, components: List[Dict[str, Any]]) -> None: def _export_bom_xml(self, path: str, components: List[Dict[str, Any]]) -> None:
"""Export BOM to XML format""" """Export BOM to XML format"""
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
root = ET.Element("bom") root = ET.Element("bom")
for comp in components: for comp in components:
comp_elem = ET.SubElement(root, "component") comp_elem = ET.SubElement(root, "component")
@@ -554,7 +576,7 @@ class ExportCommands:
elem = ET.SubElement(comp_elem, key) elem = ET.SubElement(comp_elem, key)
elem.text = str(value) elem.text = str(value)
tree = ET.ElementTree(root) 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: def _export_bom_html(self, path: str, components: List[Dict[str, Any]]) -> None:
"""Export BOM to HTML format""" """Export BOM to HTML format"""
@@ -571,13 +593,14 @@ class ExportCommands:
html.append(f"<td>{value}</td>") html.append(f"<td>{value}</td>")
html.append("</tr>") html.append("</tr>")
html.append("</table></body></html>") html.append("</table></body></html>")
with open(path, 'w') as f: with open(path, "w") as f:
f.write("\n".join(html)) f.write("\n".join(html))
def _export_bom_json(self, path: str, components: List[Dict[str, Any]]) -> None: def _export_bom_json(self, path: str, components: List[Dict[str, Any]]) -> None:
"""Export BOM to JSON format""" """Export BOM to JSON format"""
import json import json
with open(path, 'w') as f:
with open(path, "w") as f:
json.dump({"components": components}, f, indent=2) json.dump({"components": components}, f, indent=2)
def _find_kicad_cli(self) -> Optional[str]: def _find_kicad_cli(self) -> Optional[str]:

View File

@@ -145,18 +145,40 @@ class RoutingCommands:
net = start_pad.GetNetname() or end_pad.GetNetname() or "" net = start_pad.GetNetname() or end_pad.GetNetname() or ""
# Delegate to route_trace # Delegate to route_trace
result = self.route_trace({ result = self.route_trace(
"start": {"x": start_pos.x / scale, "y": start_pos.y / scale, "unit": "mm"}, {
"end": {"x": end_pos.x / scale, "y": end_pos.y / scale, "unit": "mm"}, "start": {
"layer": layer, "x": start_pos.x / scale,
"width": width, "y": start_pos.y / scale,
"net": net, "unit": "mm",
}) },
"end": {
"x": end_pos.x / scale,
"y": end_pos.y / scale,
"unit": "mm",
},
"layer": layer,
"width": width,
"net": net,
}
)
if result.get("success"): if result.get("success"):
result["message"] = f"Routed {from_ref}.{from_pad}{to_ref}.{to_pad} (net: {net or 'none'})" result["message"] = (
result["fromPad"] = {"ref": from_ref, "pad": from_pad, "x": start_pos.x / scale, "y": start_pos.y / scale} f"Routed {from_ref}.{from_pad}{to_ref}.{to_pad} (net: {net or 'none'})"
result["toPad"] = {"ref": to_ref, "pad": to_pad, "x": end_pos.x / scale, "y": end_pos.y / scale} )
result["fromPad"] = {
"ref": from_ref,
"pad": from_pad,
"x": start_pos.x / scale,
"y": start_pos.y / scale,
}
result["toPad"] = {
"ref": to_ref,
"pad": to_pad,
"x": end_pos.x / scale,
"y": end_pos.y / scale,
}
return result return result
@@ -886,7 +908,9 @@ class RoutingCommands:
else: else:
traces_to_copy.append(track) traces_to_copy.append(track)
filter_method = "net-based" if use_net_filter else "geometric (pads have no nets)" filter_method = (
"net-based" if use_net_filter else "geometric (pads have no nets)"
)
logger.info( logger.info(
f"copy_routing_pattern: {len(traces_to_copy)} traces, " f"copy_routing_pattern: {len(traces_to_copy)} traces, "
f"{len(vias_to_copy)} vias selected via {filter_method}" f"{len(vias_to_copy)} vias selected via {filter_method}"

View File

@@ -592,7 +592,9 @@ class KiCADInterface:
self._current_project_path = project_path self._current_project_path = project_path
local_lib = FootprintLibraryManager(project_path=project_path) local_lib = FootprintLibraryManager(project_path=project_path)
self.component_commands = ComponentCommands(self.board, local_lib) self.component_commands = ComponentCommands(self.board, local_lib)
logger.info(f"Reloaded FootprintLibraryManager with project_path={project_path}") logger.info(
f"Reloaded FootprintLibraryManager with project_path={project_path}"
)
return self.component_commands.place_component(params) return self.component_commands.place_component(params)
@@ -665,7 +667,10 @@ class KiCADInterface:
sch_file = Path(schematic_path) sch_file = Path(schematic_path)
if not sch_file.exists(): if not sch_file.exists():
return {"success": False, "message": f"Schematic not found: {schematic_path}"} return {
"success": False,
"message": f"Schematic not found: {schematic_path}",
}
with open(sch_file, "r", encoding="utf-8") as f: with open(sch_file, "r", encoding="utf-8") as f:
lines = f.read().split("\n") lines = f.read().split("\n")
@@ -676,9 +681,13 @@ class KiCADInterface:
for i, line in enumerate(lines): for i, line in enumerate(lines):
if "(lib_symbols" in line and lib_sym_start is None: if "(lib_symbols" in line and lib_sym_start is None:
lib_sym_start = i lib_sym_start = i
depth = sum(1 for c in line if c == "(") - sum(1 for c in line if c == ")") depth = sum(1 for c in line if c == "(") - sum(
1 for c in line if c == ")"
)
elif lib_sym_start is not None and lib_sym_end is None: elif lib_sym_start is not None and lib_sym_end is None:
depth += sum(1 for c in line if c == "(") - sum(1 for c in line if c == ")") depth += sum(1 for c in line if c == "(") - sum(
1 for c in line if c == ")"
)
if depth == 0: if depth == 0:
lib_sym_end = i lib_sym_end = i
break break
@@ -695,15 +704,22 @@ class KiCADInterface:
if re.match(r"\s*\(symbol\s+\(lib_id\s+\"", lines[i]): if re.match(r"\s*\(symbol\s+\(lib_id\s+\"", lines[i]):
b_start = i b_start = i
b_depth = sum(1 for c in lines[i] if c == "(") - sum(1 for c in lines[i] if c == ")") b_depth = sum(1 for c in lines[i] if c == "(") - sum(
1 for c in lines[i] if c == ")"
)
j = i + 1 j = i + 1
while j < len(lines) and b_depth > 0: while j < len(lines) and b_depth > 0:
b_depth += sum(1 for c in lines[j] if c == "(") - sum(1 for c in lines[j] if c == ")") b_depth += sum(1 for c in lines[j] if c == "(") - sum(
1 for c in lines[j] if c == ")"
)
j += 1 j += 1
b_end = j - 1 b_end = j - 1
block_text = "\n".join(lines[b_start:b_end + 1]) block_text = "\n".join(lines[b_start : b_end + 1])
if re.search(r'\(property\s+"Reference"\s+"' + re.escape(reference) + r'"', block_text): if re.search(
r'\(property\s+"Reference"\s+"' + re.escape(reference) + r'"',
block_text,
):
blocks_to_delete.append((b_start, b_end)) blocks_to_delete.append((b_start, b_end))
i = b_end + 1 i = b_end + 1
@@ -719,7 +735,7 @@ class KiCADInterface:
# Delete from back to front to preserve line indices # Delete from back to front to preserve line indices
for b_start, b_end in sorted(blocks_to_delete, reverse=True): for b_start, b_end in sorted(blocks_to_delete, reverse=True):
del lines[b_start:b_end + 1] del lines[b_start : b_end + 1]
if b_start < len(lines) and lines[b_start].strip() == "": if b_start < len(lines) and lines[b_start].strip() == "":
del lines[b_start] del lines[b_start]
@@ -727,18 +743,27 @@ class KiCADInterface:
f.write("\n".join(lines)) f.write("\n".join(lines))
deleted_count = len(blocks_to_delete) deleted_count = len(blocks_to_delete)
logger.info(f"Deleted {deleted_count} instance(s) of {reference} from {sch_file.name}") logger.info(
return {"success": True, "reference": reference, "deleted_count": deleted_count, "schematic": str(sch_file)} f"Deleted {deleted_count} instance(s) of {reference} from {sch_file.name}"
)
return {
"success": True,
"reference": reference,
"deleted_count": deleted_count,
"schematic": str(sch_file),
}
except Exception as e: except Exception as e:
logger.error(f"Error deleting schematic component: {e}") logger.error(f"Error deleting schematic component: {e}")
import traceback import traceback
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
return {"success": False, "message": str(e)} return {"success": False, "message": str(e)}
def _handle_edit_schematic_component(self, params): def _handle_edit_schematic_component(self, params):
"""Update properties of a placed symbol in a schematic (footprint, value, reference). """Update properties of a placed symbol in a schematic (footprint, value, reference).
Uses text-based in-place editing preserves position, UUID and all other fields.""" Uses text-based in-place editing preserves position, UUID and all other fields.
"""
logger.info("Editing schematic component") logger.info("Editing schematic component")
try: try:
from pathlib import Path from pathlib import Path
@@ -754,12 +779,24 @@ class KiCADInterface:
return {"success": False, "message": "schematicPath is required"} return {"success": False, "message": "schematicPath is required"}
if not reference: if not reference:
return {"success": False, "message": "reference is required"} return {"success": False, "message": "reference is required"}
if not any([new_footprint is not None, new_value is not None, new_reference is not None]): if not any(
return {"success": False, "message": "At least one of footprint, value, or newReference must be provided"} [
new_footprint is not None,
new_value is not None,
new_reference is not None,
]
):
return {
"success": False,
"message": "At least one of footprint, value, or newReference must be provided",
}
sch_file = Path(schematic_path) sch_file = Path(schematic_path)
if not sch_file.exists(): if not sch_file.exists():
return {"success": False, "message": f"Schematic not found: {schematic_path}"} return {
"success": False,
"message": f"Schematic not found: {schematic_path}",
}
with open(sch_file, "r", encoding="utf-8") as f: with open(sch_file, "r", encoding="utf-8") as f:
content = f.read() content = f.read()
@@ -769,9 +806,9 @@ class KiCADInterface:
depth = 0 depth = 0
i = start i = start
while i < len(s): while i < len(s):
if s[i] == '(': if s[i] == "(":
depth += 1 depth += 1
elif s[i] == ')': elif s[i] == ")":
depth -= 1 depth -= 1
if depth == 0: if depth == 0:
return i return i
@@ -780,7 +817,9 @@ class KiCADInterface:
# Skip lib_symbols section # Skip lib_symbols section
lib_sym_pos = content.find("(lib_symbols") lib_sym_pos = content.find("(lib_symbols")
lib_sym_end = find_matching_paren(content, lib_sym_pos) if lib_sym_pos >= 0 else -1 lib_sym_end = (
find_matching_paren(content, lib_sym_pos) if lib_sym_pos >= 0 else -1
)
# Find placed symbol blocks that match the reference # Find placed symbol blocks that match the reference
# Search for (symbol (lib_id "...") ... (property "Reference" "<ref>" ...) ...) # Search for (symbol (lib_id "...") ... (property "Reference" "<ref>" ...) ...)
@@ -800,36 +839,61 @@ class KiCADInterface:
if end < 0: if end < 0:
search_start = pos + 1 search_start = pos + 1
continue continue
block_text = content[pos:end + 1] block_text = content[pos : end + 1]
if re.search(r'\(property\s+"Reference"\s+"' + re.escape(reference) + r'"', block_text): if re.search(
r'\(property\s+"Reference"\s+"' + re.escape(reference) + r'"',
block_text,
):
block_start, block_end = pos, end block_start, block_end = pos, end
break break
search_start = end + 1 search_start = end + 1
if block_start is None: if block_start is None:
return {"success": False, "message": f"Component '{reference}' not found in schematic"} return {
"success": False,
"message": f"Component '{reference}' not found in schematic",
}
# Apply property replacements within the found block # Apply property replacements within the found block
block_text = content[block_start:block_end + 1] block_text = content[block_start : block_end + 1]
if new_footprint is not None: if new_footprint is not None:
block_text = re.sub(r'(\(property\s+"Footprint"\s+)"[^"]*"', rf'\1"{new_footprint}"', block_text) block_text = re.sub(
r'(\(property\s+"Footprint"\s+)"[^"]*"',
rf'\1"{new_footprint}"',
block_text,
)
if new_value is not None: if new_value is not None:
block_text = re.sub(r'(\(property\s+"Value"\s+)"[^"]*"', rf'\1"{new_value}"', block_text) block_text = re.sub(
r'(\(property\s+"Value"\s+)"[^"]*"', rf'\1"{new_value}"', block_text
)
if new_reference is not None: if new_reference is not None:
block_text = re.sub(r'(\(property\s+"Reference"\s+)"[^"]*"', rf'\1"{new_reference}"', block_text) block_text = re.sub(
r'(\(property\s+"Reference"\s+)"[^"]*"',
rf'\1"{new_reference}"',
block_text,
)
content = content[:block_start] + block_text + content[block_end + 1:] content = content[:block_start] + block_text + content[block_end + 1 :]
with open(sch_file, "w", encoding="utf-8") as f: with open(sch_file, "w", encoding="utf-8") as f:
f.write(content) f.write(content)
changes = {k: v for k, v in {"footprint": new_footprint, "value": new_value, "reference": new_reference}.items() if v is not None} changes = {
k: v
for k, v in {
"footprint": new_footprint,
"value": new_value,
"reference": new_reference,
}.items()
if v is not None
}
logger.info(f"Edited schematic component {reference}: {changes}") logger.info(f"Edited schematic component {reference}: {changes}")
return {"success": True, "reference": reference, "updated": changes} return {"success": True, "reference": reference, "updated": changes}
except Exception as e: except Exception as e:
logger.error(f"Error editing schematic component: {e}") logger.error(f"Error editing schematic component: {e}")
import traceback import traceback
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
return {"success": False, "message": str(e)} return {"success": False, "message": str(e)}
@@ -899,7 +963,9 @@ class KiCADInterface:
def _handle_create_footprint(self, params): def _handle_create_footprint(self, params):
"""Create a new .kicad_mod footprint file in a .pretty library.""" """Create a new .kicad_mod footprint file in a .pretty library."""
logger.info(f"create_footprint: {params.get('name')} in {params.get('libraryPath')}") logger.info(
f"create_footprint: {params.get('name')} in {params.get('libraryPath')}"
)
try: try:
creator = FootprintCreator() creator = FootprintCreator()
return creator.create_footprint( return creator.create_footprint(
@@ -921,7 +987,9 @@ class KiCADInterface:
def _handle_edit_footprint_pad(self, params): def _handle_edit_footprint_pad(self, params):
"""Edit an existing pad in a .kicad_mod file.""" """Edit an existing pad in a .kicad_mod file."""
logger.info(f"edit_footprint_pad: pad {params.get('padNumber')} in {params.get('footprintPath')}") logger.info(
f"edit_footprint_pad: pad {params.get('padNumber')} in {params.get('footprintPath')}"
)
try: try:
creator = FootprintCreator() creator = FootprintCreator()
return creator.edit_footprint_pad( return creator.edit_footprint_pad(
@@ -970,7 +1038,9 @@ class KiCADInterface:
def _handle_create_symbol(self, params): def _handle_create_symbol(self, params):
"""Create a new symbol in a .kicad_sym library.""" """Create a new symbol in a .kicad_sym library."""
logger.info(f"create_symbol: {params.get('name')} in {params.get('libraryPath')}") logger.info(
f"create_symbol: {params.get('name')} in {params.get('libraryPath')}"
)
try: try:
creator = SymbolCreator() creator = SymbolCreator()
return creator.create_symbol( return creator.create_symbol(
@@ -994,7 +1064,9 @@ class KiCADInterface:
def _handle_delete_symbol(self, params): def _handle_delete_symbol(self, params):
"""Delete a symbol from a .kicad_sym library.""" """Delete a symbol from a .kicad_sym library."""
logger.info(f"delete_symbol: {params.get('name')} from {params.get('libraryPath')}") logger.info(
f"delete_symbol: {params.get('name')} from {params.get('libraryPath')}"
)
try: try:
creator = SymbolCreator() creator = SymbolCreator()
return creator.delete_symbol( return creator.delete_symbol(
@@ -2210,7 +2282,10 @@ class KiCADInterface:
return manager.enrich_schematic(Path(schematic_path), dry_run=dry_run) return manager.enrich_schematic(Path(schematic_path), dry_run=dry_run)
except Exception as e: except Exception as e:
logger.error(f"Error enriching datasheets: {e}", exc_info=True) logger.error(f"Error enriching datasheets: {e}", exc_info=True)
return {"success": False, "message": f"Failed to enrich datasheets: {str(e)}"} return {
"success": False,
"message": f"Failed to enrich datasheets: {str(e)}",
}
def _handle_get_datasheet_url(self, params): def _handle_get_datasheet_url(self, params):
"""Return LCSC datasheet and product URLs for a part number""" """Return LCSC datasheet and product URLs for a part number"""
@@ -2232,7 +2307,10 @@ class KiCADInterface:
} }
except Exception as e: except Exception as e:
logger.error(f"Error getting datasheet URL: {e}", exc_info=True) logger.error(f"Error getting datasheet URL: {e}", exc_info=True)
return {"success": False, "message": f"Failed to get datasheet URL: {str(e)}"} return {
"success": False,
"message": f"Failed to get datasheet URL: {str(e)}",
}
def main(): def main():