🔧 Tool Enhancements: - Update all MCP tools to use FastMCP instead of legacy Context - Improve IPC client with proper kicad-python integration - Streamline function signatures for better performance - Remove unnecessary Context dependencies from pattern recognition ⚡ Performance Improvements: - Simplified function calls for faster execution - Better error handling and logging - Enhanced IPC connection management with socket path support - Optimized pattern recognition without blocking operations 🛠️ Technical Updates: - BOM tools: Remove Context dependency for cleaner API - DRC tools: Streamline CLI integration - Export tools: Update thumbnail generation with FastMCP - Netlist tools: Enhance extraction performance - Pattern tools: Non-blocking circuit pattern recognition - IPC client: Add proper kicad-python socket connection These improvements make the MCP tools more reliable and performant for real-time KiCad automation workflows. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
218 lines
8.4 KiB
Python
218 lines
8.4 KiB
Python
"""
|
|
Export tools for KiCad projects.
|
|
"""
|
|
|
|
import asyncio
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
|
|
from fastmcp import FastMCP, Image
|
|
|
|
from kicad_mcp.config import KICAD_APP_PATH, system
|
|
from kicad_mcp.utils.file_utils import get_project_files
|
|
|
|
|
|
def register_export_tools(mcp: FastMCP) -> None:
|
|
"""Register export tools with the MCP server.
|
|
|
|
Args:
|
|
mcp: The FastMCP server instance
|
|
"""
|
|
|
|
@mcp.tool()
|
|
async def generate_pcb_thumbnail(project_path: str):
|
|
"""Generate a thumbnail image of a KiCad PCB layout using kicad-cli.
|
|
|
|
Args:
|
|
project_path: Path to the KiCad project file (.kicad_pro)
|
|
ctx: Context for MCP communication
|
|
|
|
Returns:
|
|
Thumbnail image of the PCB or None if generation failed
|
|
"""
|
|
try:
|
|
# Access the context
|
|
app_context = ctx.request_context.lifespan_context
|
|
# Removed check for kicad_modules_available as we now use CLI
|
|
|
|
print(f"Generating thumbnail via CLI for project: {project_path}")
|
|
|
|
if not os.path.exists(project_path):
|
|
print(f"Project not found: {project_path}")
|
|
await 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:
|
|
print("PCB file not found in project")
|
|
await ctx.info("PCB file not found in project")
|
|
return None
|
|
|
|
pcb_file = files["pcb"]
|
|
print(f"Found PCB file: {pcb_file}")
|
|
|
|
# Check cache
|
|
cache_key = f"thumbnail_cli_{pcb_file}_{os.path.getmtime(pcb_file)}"
|
|
if hasattr(app_context, "cache") and cache_key in app_context.cache:
|
|
print(f"Using cached CLI thumbnail for {pcb_file}")
|
|
return app_context.cache[cache_key]
|
|
|
|
await ctx.report_progress(10, 100)
|
|
await ctx.info(f"Generating thumbnail for {os.path.basename(pcb_file)} using kicad-cli")
|
|
|
|
# Use command-line tools
|
|
try:
|
|
thumbnail = await generate_thumbnail_with_cli(pcb_file, ctx)
|
|
if thumbnail:
|
|
# Cache the result if possible
|
|
if hasattr(app_context, "cache"):
|
|
app_context.cache[cache_key] = thumbnail
|
|
print("Thumbnail generated successfully via CLI.")
|
|
return thumbnail
|
|
else:
|
|
print("generate_thumbnail_with_cli returned None")
|
|
await ctx.info("Failed to generate thumbnail using kicad-cli.")
|
|
return None
|
|
except Exception as e:
|
|
print(f"Error calling generate_thumbnail_with_cli: {str(e)}", exc_info=True)
|
|
await ctx.info(f"Error generating thumbnail with kicad-cli: {str(e)}")
|
|
return None
|
|
|
|
except asyncio.CancelledError:
|
|
print("Thumbnail generation cancelled")
|
|
raise # Re-raise to let MCP know the task was cancelled
|
|
except Exception as e:
|
|
print(f"Unexpected error in thumbnail generation: {str(e)}")
|
|
await ctx.info(f"Error: {str(e)}")
|
|
return None
|
|
|
|
@mcp.tool()
|
|
async def generate_project_thumbnail(project_path: str):
|
|
"""Generate a thumbnail of a KiCad project's PCB layout (Alias for generate_pcb_thumbnail)."""
|
|
# This function now just calls the main CLI-based thumbnail generator
|
|
print(
|
|
f"generate_project_thumbnail called, redirecting to generate_pcb_thumbnail for {project_path}"
|
|
)
|
|
return await generate_pcb_thumbnail(project_path, ctx)
|
|
|
|
|
|
# Helper functions for thumbnail generation
|
|
async def generate_thumbnail_with_cli(pcb_file: str):
|
|
"""Generate PCB thumbnail using command line tools.
|
|
This is a fallback method when the kicad Python module is not available or fails.
|
|
|
|
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:
|
|
print("Attempting to generate thumbnail using KiCad CLI tools")
|
|
await ctx.report_progress(20, 100)
|
|
|
|
# --- Determine Output Path ---
|
|
project_dir = os.path.dirname(pcb_file)
|
|
project_name = os.path.splitext(os.path.basename(pcb_file))[0]
|
|
output_file = os.path.join(project_dir, f"{project_name}_thumbnail.svg")
|
|
# ---------------------------
|
|
|
|
# Check for required command-line tools based on OS
|
|
kicad_cli = None
|
|
if system == "Darwin": # macOS
|
|
kicad_cli_path = os.path.join(KICAD_APP_PATH, "Contents/MacOS/kicad-cli")
|
|
if os.path.exists(kicad_cli_path):
|
|
kicad_cli = kicad_cli_path
|
|
elif shutil.which("kicad-cli") is not None:
|
|
kicad_cli = "kicad-cli" # Try to use from PATH
|
|
else:
|
|
print(f"kicad-cli not found at {kicad_cli_path} or in PATH")
|
|
return None
|
|
elif system == "Windows":
|
|
kicad_cli_path = os.path.join(KICAD_APP_PATH, "bin", "kicad-cli.exe")
|
|
if os.path.exists(kicad_cli_path):
|
|
kicad_cli = kicad_cli_path
|
|
elif shutil.which("kicad-cli.exe") is not None:
|
|
kicad_cli = "kicad-cli.exe"
|
|
elif shutil.which("kicad-cli") is not None:
|
|
kicad_cli = "kicad-cli" # Try to use from PATH (without .exe)
|
|
else:
|
|
print(f"kicad-cli not found at {kicad_cli_path} or in PATH")
|
|
return None
|
|
elif system == "Linux":
|
|
kicad_cli = shutil.which("kicad-cli")
|
|
if not kicad_cli:
|
|
print("kicad-cli not found in PATH")
|
|
return None
|
|
else:
|
|
print(f"Unsupported operating system: {system}")
|
|
return None
|
|
|
|
await ctx.report_progress(30, 100)
|
|
await ctx.info("Using KiCad command line tools for thumbnail generation")
|
|
|
|
# Build command for generating SVG from PCB using kicad-cli (changed from PNG)
|
|
cmd = [
|
|
kicad_cli,
|
|
"pcb",
|
|
"export",
|
|
"svg", # <-- Changed format to svg
|
|
"--output",
|
|
output_file,
|
|
"--layers",
|
|
"F.Cu,B.Cu,F.SilkS,B.SilkS,F.Mask,B.Mask,Edge.Cuts", # Keep relevant layers
|
|
# Consider adding options like --black-and-white if needed
|
|
pcb_file,
|
|
]
|
|
|
|
print(f"Running command: {' '.join(cmd)}")
|
|
await ctx.report_progress(50, 100)
|
|
|
|
# Run the command
|
|
try:
|
|
process = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=30)
|
|
print(f"Command successful: {process.stdout}")
|
|
|
|
await ctx.report_progress(70, 100)
|
|
|
|
# Check if the output file was created
|
|
if not os.path.exists(output_file):
|
|
print(f"Output file not created: {output_file}")
|
|
return None
|
|
|
|
# Read the image file
|
|
with open(output_file, "rb") as f:
|
|
img_data = f.read()
|
|
|
|
print(f"Successfully generated thumbnail with CLI, size: {len(img_data)} bytes")
|
|
await ctx.report_progress(90, 100)
|
|
# Inform user about the saved file
|
|
await ctx.info(f"Thumbnail saved to: {output_file}")
|
|
return Image(data=img_data, format="svg") # <-- Changed format to svg
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Command '{' '.join(e.cmd)}' failed with code {e.returncode}")
|
|
print(f"Stderr: {e.stderr}")
|
|
print(f"Stdout: {e.stdout}")
|
|
await ctx.info(f"KiCad CLI command failed: {e.stderr or e.stdout}")
|
|
return None
|
|
except subprocess.TimeoutExpired:
|
|
print(f"Command timed out after 30 seconds: {' '.join(cmd)}")
|
|
await ctx.info("KiCad CLI command timed out")
|
|
return None
|
|
except Exception as e:
|
|
print(f"Error running CLI command: {str(e)}", exc_info=True)
|
|
await ctx.info(f"Error running KiCad CLI: {str(e)}")
|
|
return None
|
|
|
|
except asyncio.CancelledError:
|
|
print("CLI thumbnail generation cancelled")
|
|
raise
|
|
except Exception as e:
|
|
print(f"Unexpected error in CLI thumbnail generation: {str(e)}")
|
|
await ctx.info(f"Unexpected error: {str(e)}")
|
|
return None
|