Enhance PCB thumbnail generation with robust fallback methods

Implements a more reliable PCB thumbnail generation feature using two methods:
- Primary: pcbnew Python module for high-quality rendering
- Fallback: pcbnew_cli for environments without Python modules

Adds detailed progress reporting and comprehensive error handling.
Includes documentation in docs/thumbnail_guide.md.
This commit is contained in:
Lama 2025-03-20 03:16:14 -04:00
parent b5b5835939
commit f50a2ce1af
5 changed files with 332 additions and 1 deletions

View File

@ -158,6 +158,7 @@ The KiCad MCP Server provides several capabilities:
- Analysis tools (validate projects, generate thumbnails) - Analysis tools (validate projects, generate thumbnails)
- Export tools (extract bill of materials) - Export tools (extract bill of materials)
- Design Rule Check tools (run DRC checks, get detailed violation reports, track DRC history and improvements over time) - Design Rule Check tools (run DRC checks, get detailed violation reports, track DRC history and improvements over time)
- PCB Visualization - Generate thumbnails of PCB layouts for easy project identification ([see guide](docs/thumbnail_guide.md))
### Prompts ### Prompts
- Create new component guide - Create new component guide

73
docs/thumbnail_guide.md Normal file
View File

@ -0,0 +1,73 @@
# PCB Thumbnail Feature Guide
The KiCad MCP Server now includes a powerful PCB thumbnail generation feature, making it easier to visually browse and identify your KiCad projects. This guide explains how to use the feature and how it works behind the scenes.
## Using the PCB Thumbnail Feature
You can generate thumbnails for your KiCad PCB designs directly through Claude:
```
Please generate a thumbnail for my KiCad project at /path/to/my_project/my_project.kicad_pro
```
The tool will:
1. Find the PCB file (.kicad_pcb) associated with your project
2. Generate a visual representation of your PCB layout
3. Return an image that you can view directly in Claude
## How It Works
The thumbnail generator uses multiple methods to create PCB thumbnails, automatically falling back to alternative approaches if the primary method fails:
1. **pcbnew Python Module (Primary Method)**
- Uses KiCad's official Python API for the most accurate representation
- Renders the PCB with proper layers, components, and traces
- Requires the KiCad Python modules to be installed and accessible
2. **Command Line Interface (Fallback Method)**
- Uses KiCad's command-line tools (pcbnew_cli) when Python modules aren't available
- Creates high-quality renders similar to what you'd see in KiCad's PCB editor
- Works across different operating systems (macOS, Windows, Linux)
## Thumbnail Examples
When viewing a PCB thumbnail, you'll typically see:
- The PCB board outline (Edge.Cuts layer)
- Copper layers (F.Cu and B.Cu)
- Silkscreen layers (F.SilkS and B.SilkS)
- Mask layers (F.Mask and B.Mask)
- Component outlines and reference designators
## Tips for Best Results
For optimal thumbnail quality:
1. **Ensure KiCad is properly installed** - The thumbnail generator relies on KiCad's libraries and tools
2. **Use the full absolute path** to your project file to avoid path resolution issues
3. **Make sure your PCB has a defined board outline** (Edge.Cuts layer) for proper visualization
4. **Update to the latest KiCad version** for best compatibility with the thumbnail generator
## Troubleshooting
If you encounter issues:
- **No thumbnail generated**: Check that your project exists and contains a valid PCB file
- **Low-quality thumbnail**: Ensure your PCB has a properly defined board outline
- **"pcbnew module not found"**: This is expected if KiCad's Python modules aren't in your Python path
## Integration Ideas
The PCB thumbnail feature can be used in various ways:
1. **Project browsing**: Generate thumbnails for all your projects to visually identify them
2. **Documentation**: Include PCB thumbnails in your project documentation
3. **Design review**: Use thumbnails to quickly check PCB layouts during discussions
## Future Enhancements
The thumbnail generation feature will be expanded in future releases with:
- Higher quality rendering options
- Layer selection capabilities
- 3D rendering of PCB assemblies
- Annotation and markup support for design review

View File

@ -51,7 +51,7 @@ def create_server() -> FastMCP:
logger.debug("Registering tools...") logger.debug("Registering tools...")
register_project_tools(mcp) register_project_tools(mcp)
register_analysis_tools(mcp) register_analysis_tools(mcp)
register_export_tools(mcp) register_export_tools(mcp, kicad_modules_available)
register_drc_tools(mcp, kicad_modules_available) register_drc_tools(mcp, kicad_modules_available)
# Register prompts # Register prompts

View File

@ -1,3 +1,9 @@
""" """
Tool handlers for KiCad MCP Server. Tool handlers for KiCad MCP Server.
""" """
This package includes:
- Project management tools
- Analysis tools
- Export tools (BOM extraction, PCB thumbnail generation)
- DRC tools

View File

@ -64,6 +64,64 @@ def register_analysis_tools(mcp: FastMCP, kicad_modules_available: bool = False)
logger.info(f"Validation result: {'valid' if result['valid'] else 'invalid'}") logger.info(f"Validation result: {'valid' if result['valid'] else 'invalid'}")
return result return result
@mcp.tool()
async def generate_pcb_thumbnail(project_path: str, ctx: Context) -> Optional[Image]:
"""Generate a thumbnail image of a KiCad PCB layout.
Args:
project_path: Path to the KiCad project file (.kicad_pro)
Returns:
Thumbnail image of the PCB or None if generation failed
"""
logger.info(f"Generating thumbnail for project: {project_path}")
if not os.path.exists(project_path):
logger.error(f"Project not found: {project_path}")
ctx.info(f"Project not found: {project_path}")
return None
# Get PCB file from project
files = get_project_files(project_path)
if "pcb" not in files:
logger.error("PCB file not found in project")
ctx.info("PCB file not found in project")
return None
pcb_file = files["pcb"]
logger.info(f"Found PCB file: {pcb_file}")
await ctx.report_progress(10, 100)
ctx.info(f"Generating thumbnail for {os.path.basename(pcb_file)}")
# Method 1: Try to use pcbnew Python module if available
if kicad_modules_available:
try:
thumbnail = await generate_thumbnail_with_pcbnew(pcb_file, ctx)
if thumbnail:
return thumbnail
# If pcbnew method failed, log it but continue to try alternative method
logger.warning("Failed to generate thumbnail with pcbnew, trying CLI method")
except Exception as e:
logger.error(f"Error using pcbnew for thumbnail: {str(e)}", exc_info=True)
ctx.info(f"Error with pcbnew method, trying alternative approach")
else:
logger.info("KiCad Python modules not available, trying CLI method")
# Method 2: Try to use command-line tools
try:
thumbnail = await generate_thumbnail_with_cli(pcb_file, ctx)
if thumbnail:
return thumbnail
except Exception as e:
logger.error(f"Error using CLI for thumbnail: {str(e)}", exc_info=True)
ctx.info(f"Error generating thumbnail with CLI method")
# If all methods fail, inform the user
ctx.info("Could not generate thumbnail for PCB - all methods failed")
return None
@mcp.tool() @mcp.tool()
async def generate_project_thumbnail(project_path: str, ctx: Context) -> Optional[Image]: async def generate_project_thumbnail(project_path: str, ctx: Context) -> Optional[Image]:
"""Generate a thumbnail of a KiCad project's PCB layout.""" """Generate a thumbnail of a KiCad project's PCB layout."""
@ -173,3 +231,196 @@ def register_analysis_tools(mcp: FastMCP, kicad_modules_available: bool = False)
logger.error(f"Error generating thumbnail: {str(e)}", exc_info=True) logger.error(f"Error generating thumbnail: {str(e)}", exc_info=True)
ctx.info(f"Error generating thumbnail: {str(e)}") ctx.info(f"Error generating thumbnail: {str(e)}")
return None return None
# Helper functions for thumbnail generation
async def generate_thumbnail_with_pcbnew(pcb_file: str, ctx: Context) -> Optional[Image]:
"""Generate PCB thumbnail using the pcbnew Python module.
Args:
pcb_file: Path to the PCB file (.kicad_pcb)
ctx: MCP context for progress reporting
Returns:
Image object containing the PCB thumbnail or None if generation failed
"""
try:
import pcbnew
logger.info("Successfully imported pcbnew module")
await ctx.report_progress(20, 100)
# Load the PCB file
logger.debug(f"Loading PCB file with pcbnew: {pcb_file}")
board = pcbnew.LoadBoard(pcb_file)
if not board:
logger.error("Failed to load PCB file with pcbnew")
return None
# Report progress
await ctx.report_progress(30, 100)
ctx.info("PCB file loaded, generating image...")
# Get board dimensions
board_box = board.GetBoardEdgesBoundingBox()
width_mm = board_box.GetWidth() / 1000000.0 # Convert to mm
height_mm = board_box.GetHeight() / 1000000.0
logger.info(f"PCB dimensions: {width_mm:.2f}mm x {height_mm:.2f}mm")
# Create temporary directory for output
with tempfile.TemporaryDirectory() as temp_dir:
logger.debug(f"Created temporary directory: {temp_dir}")
# Create PLOT_CONTROLLER for plotting
pctl = pcbnew.PLOT_CONTROLLER(board)
popt = pctl.GetPlotOptions()
# Set plot options for PNG output
popt.SetOutputDirectory(temp_dir)
popt.SetPlotFrameRef(False)
popt.SetPlotValue(True)
popt.SetPlotReference(True)
popt.SetPlotInvisibleText(False)
popt.SetPlotViaOnMaskLayer(False)
# Set color mode (if available in this version)
if hasattr(popt, "SetColorMode"):
popt.SetColorMode(True) # Color mode
# Set color theme (if available in this version)
if hasattr(popt, "SetColorTheme"):
popt.SetColorTheme("default")
# Calculate a reasonable scale to fit in a thumbnail
max_pixels = 800 # Max pixel dimension
scale = min(max_pixels / width_mm, max_pixels / height_mm) * 0.8 # 80% to leave margin
# Set plot scale if the function exists
if hasattr(popt, "SetScale"):
popt.SetScale(scale)
# Determine output filename
plot_basename = "thumbnail"
logger.debug(f"Plotting PCB to PNG")
await ctx.report_progress(50, 100)
# Plot PNG
pctl.OpenPlotfile(plot_basename, pcbnew.PLOT_FORMAT_PNG, "Thumbnail")
pctl.PlotLayer()
pctl.ClosePlot()
await ctx.report_progress(70, 100)
# The plot controller creates files with predictable names
plot_file = os.path.join(temp_dir, f"{plot_basename}.png")
if not os.path.exists(plot_file):
logger.error(f"Expected plot file not found: {plot_file}")
return None
# Read the image file
with open(plot_file, 'rb') as f:
img_data = f.read()
logger.info(f"Successfully generated thumbnail, size: {len(img_data)} bytes")
await ctx.report_progress(90, 100)
return Image(data=img_data, format="png")
except ImportError as e:
logger.error(f"Failed to import pcbnew module: {str(e)}")
return None
except Exception as e:
logger.error(f"Error generating thumbnail with pcbnew: {str(e)}", exc_info=True)
return None
async def generate_thumbnail_with_cli(pcb_file: str, ctx: Context) -> Optional[Image]:
"""Generate PCB thumbnail using command line tools.
This is a fallback method when pcbnew Python module is not available.
Args:
pcb_file: Path to the PCB file (.kicad_pcb)
ctx: MCP context for progress reporting
Returns:
Image object containing the PCB thumbnail or None if generation failed
"""
import subprocess
logger.info("Attempting to generate thumbnail using command line tools")
await ctx.report_progress(20, 100)
# Check for required command-line tools based on OS
if system == "Darwin": # macOS
pcbnew_cli = os.path.join(KICAD_APP_PATH, "Contents/MacOS/pcbnew_cli")
if not os.path.exists(pcbnew_cli) and shutil.which("pcbnew_cli") is not None:
pcbnew_cli = "pcbnew_cli" # Try to use from PATH
elif not os.path.exists(pcbnew_cli):
logger.error(f"pcbnew_cli not found at {pcbnew_cli} or in PATH")
return None
elif system == "Windows":
pcbnew_cli = os.path.join(KICAD_APP_PATH, "bin", "pcbnew_cli.exe")
if not os.path.exists(pcbnew_cli) and shutil.which("pcbnew_cli") is not None:
pcbnew_cli = "pcbnew_cli" # Try to use from PATH
elif not os.path.exists(pcbnew_cli):
logger.error(f"pcbnew_cli not found at {pcbnew_cli} or in PATH")
return None
elif system == "Linux":
pcbnew_cli = shutil.which("pcbnew_cli")
if not pcbnew_cli:
logger.error("pcbnew_cli not found in PATH")
return None
else:
logger.error(f"Unsupported operating system: {system}")
return None
await ctx.report_progress(30, 100)
ctx.info("Using KiCad command line tools for thumbnail generation")
# Create temporary directory for output
with tempfile.TemporaryDirectory() as temp_dir:
# Output PNG file
output_file = os.path.join(temp_dir, "thumbnail.png")
# Build command for generating PNG from PCB
cmd = [
pcbnew_cli,
"--export-png",
output_file,
"--page-size-inches", "8x6", # Set a reasonable page size
"--layers", "F.Cu,B.Cu,F.SilkS,B.SilkS,F.Mask,B.Mask,Edge.Cuts", # Important layers
pcb_file
]
logger.debug(f"Running command: {' '.join(cmd)}")
await ctx.report_progress(50, 100)
# Run the command
try:
process = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if process.returncode != 0:
logger.error(f"Command failed with code {process.returncode}")
logger.error(f"Error: {process.stderr}")
return None
await ctx.report_progress(70, 100)
# Check if the output file was created
if not os.path.exists(output_file):
logger.error(f"Output file not created: {output_file}")
return None
# Read the image file
with open(output_file, 'rb') as f:
img_data = f.read()
logger.info(f"Successfully generated thumbnail with CLI, size: {len(img_data)} bytes")
await ctx.report_progress(90, 100)
return Image(data=img_data, format="png")
except subprocess.TimeoutExpired:
logger.error("Command timed out after 30 seconds")
return None
except Exception as e:
logger.error(f"Error running CLI command: {str(e)}", exc_info=True)
return None