This major update transforms the KiCad MCP server from file-based analysis to a complete EDA automation platform with real-time KiCad integration and automated routing capabilities. 🎯 Key Features Implemented: - Complete FreeRouting integration engine for automated PCB routing - Real-time KiCad IPC API integration for live board analysis - Comprehensive routing tools (automated, interactive, quality analysis) - Advanced project automation pipeline (concept to manufacturing) - AI-enhanced design analysis and optimization - 3D model analysis and mechanical constraint checking - Advanced DRC rule management and validation - Symbol library analysis and organization tools - Layer stackup analysis and impedance calculations 🛠️ Technical Implementation: - Enhanced MCP tools: 35+ new routing and automation functions - FreeRouting engine with DSN/SES workflow automation - Real-time component placement optimization via IPC API - Complete project automation from schematic to manufacturing files - Comprehensive integration testing framework 🔧 Infrastructure: - Fixed all FastMCP import statements across codebase - Added comprehensive integration test suite - Enhanced server registration for all new tool categories - Robust error handling and fallback mechanisms ✅ Testing Results: - Server startup and tool registration: ✓ PASS - Project validation with thermal camera project: ✓ PASS - Routing prerequisites detection: ✓ PASS - KiCad CLI integration (v9.0.3): ✓ PASS - Ready for KiCad IPC API enablement and FreeRouting installation 🚀 Impact: This represents the ultimate KiCad integration for Claude Code, enabling complete EDA workflow automation from concept to production-ready files. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
262 lines
10 KiB
Python
262 lines
10 KiB
Python
"""
|
|
Design Rule Check (DRC) resources for KiCad PCB files.
|
|
"""
|
|
|
|
import os
|
|
|
|
from fastmcp import FastMCP
|
|
|
|
from kicad_mcp.tools.drc_impl.cli_drc import run_drc_via_cli
|
|
from kicad_mcp.utils.drc_history import get_drc_history
|
|
from kicad_mcp.utils.file_utils import get_project_files
|
|
|
|
|
|
def register_drc_resources(mcp: FastMCP) -> None:
|
|
"""Register DRC resources with the MCP server.
|
|
|
|
Args:
|
|
mcp: The FastMCP server instance
|
|
"""
|
|
|
|
@mcp.resource("kicad://drc/history/{project_path}")
|
|
def get_drc_history_report(project_path: str) -> str:
|
|
"""Get a formatted DRC history report for a KiCad project.
|
|
|
|
Args:
|
|
project_path: Path to the KiCad project file (.kicad_pro)
|
|
|
|
Returns:
|
|
Markdown-formatted DRC history report
|
|
"""
|
|
print(f"Generating DRC history report for project: {project_path}")
|
|
|
|
if not os.path.exists(project_path):
|
|
return f"Project not found: {project_path}"
|
|
|
|
# Get history entries
|
|
history_entries = get_drc_history(project_path)
|
|
|
|
if not history_entries:
|
|
return (
|
|
"# DRC History\n\nNo DRC history available for this project. Run a DRC check first."
|
|
)
|
|
|
|
# Format results as Markdown
|
|
project_name = os.path.basename(project_path)[:-10] # Remove .kicad_pro
|
|
report = f"# DRC History for {project_name}\n\n"
|
|
|
|
# Add trend visualization
|
|
if len(history_entries) >= 2:
|
|
report += "## Trend\n\n"
|
|
|
|
# Create a simple ASCII chart of violations over time
|
|
report += "```\n"
|
|
report += "Violations\n"
|
|
|
|
# Find min/max for scaling
|
|
max_violations = max(entry.get("total_violations", 0) for entry in history_entries)
|
|
if max_violations < 10:
|
|
max_violations = 10 # Minimum scale
|
|
|
|
# Generate chart (10 rows high)
|
|
for i in range(10, 0, -1):
|
|
threshold = (i / 10) * max_violations
|
|
report += f"{int(threshold):4d} |"
|
|
|
|
for entry in reversed(history_entries): # Oldest to newest
|
|
violations = entry.get("total_violations", 0)
|
|
if violations >= threshold:
|
|
report += "*"
|
|
else:
|
|
report += " "
|
|
|
|
report += "\n"
|
|
|
|
# Add x-axis
|
|
report += " " + "-" * len(history_entries) + "\n"
|
|
report += " "
|
|
|
|
# Add dates (shortened)
|
|
for entry in reversed(history_entries):
|
|
date = entry.get("datetime", "")
|
|
if date:
|
|
# Just show month/day
|
|
shortened = date.split(" ")[0].split("-")[-2:]
|
|
report += shortened[-2][0] # First letter of month
|
|
|
|
report += "\n```\n"
|
|
|
|
# Add history table
|
|
report += "## History Entries\n\n"
|
|
report += "| Date | Time | Violations | Categories |\n"
|
|
report += "| ---- | ---- | ---------- | ---------- |\n"
|
|
|
|
for entry in history_entries:
|
|
date_time = entry.get("datetime", "Unknown")
|
|
if " " in date_time:
|
|
date, time = date_time.split(" ")
|
|
else:
|
|
date, time = date_time, ""
|
|
|
|
violations = entry.get("total_violations", 0)
|
|
categories = entry.get("violation_categories", {})
|
|
category_count = len(categories)
|
|
|
|
report += f"| {date} | {time} | {violations} | {category_count} |\n"
|
|
|
|
# Add detailed information about the most recent run
|
|
if history_entries:
|
|
most_recent = history_entries[0]
|
|
report += "\n## Most Recent Check Details\n\n"
|
|
report += f"**Date:** {most_recent.get('datetime', 'Unknown')}\n\n"
|
|
report += f"**Total Violations:** {most_recent.get('total_violations', 0)}\n\n"
|
|
|
|
categories = most_recent.get("violation_categories", {})
|
|
if categories:
|
|
report += "**Violation Categories:**\n\n"
|
|
for category, count in categories.items():
|
|
report += f"- {category}: {count}\n"
|
|
|
|
# Add comparison with first run if available
|
|
if len(history_entries) > 1:
|
|
first_run = history_entries[-1]
|
|
first_violations = first_run.get("total_violations", 0)
|
|
current_violations = most_recent.get("total_violations", 0)
|
|
|
|
report += "\n## Progress Since First Check\n\n"
|
|
report += f"**First Check Date:** {first_run.get('datetime', 'Unknown')}\n"
|
|
report += f"**First Check Violations:** {first_violations}\n"
|
|
report += f"**Current Violations:** {current_violations}\n"
|
|
|
|
if first_violations > current_violations:
|
|
fixed = first_violations - current_violations
|
|
report += f"**Progress:** You've fixed {fixed} violations! 🎉\n"
|
|
elif first_violations < current_violations:
|
|
added = current_violations - first_violations
|
|
report += f"**Alert:** {added} new violations have been introduced since the first check.\n"
|
|
else:
|
|
report += "**Status:** The number of violations has remained the same since the first check.\n"
|
|
|
|
return report
|
|
|
|
@mcp.resource("kicad://drc/{project_path}")
|
|
def get_drc_report(project_path: str) -> str:
|
|
"""Get a formatted DRC report for a KiCad project.
|
|
|
|
Args:
|
|
project_path: Path to the KiCad project file (.kicad_pro)
|
|
|
|
Returns:
|
|
Markdown-formatted DRC report
|
|
"""
|
|
print(f"Generating DRC report for project: {project_path}")
|
|
|
|
if not os.path.exists(project_path):
|
|
return f"Project not found: {project_path}"
|
|
|
|
# Get PCB file from project
|
|
files = get_project_files(project_path)
|
|
if "pcb" not in files:
|
|
return "PCB file not found in project"
|
|
|
|
pcb_file = files["pcb"]
|
|
print(f"Found PCB file: {pcb_file}")
|
|
|
|
# Try to run DRC via command line
|
|
drc_results = run_drc_via_cli(pcb_file)
|
|
|
|
if not drc_results["success"]:
|
|
error_message = drc_results.get("error", "Unknown error")
|
|
return f"# DRC Check Failed\n\nError: {error_message}"
|
|
|
|
# Format results as Markdown
|
|
project_name = os.path.basename(project_path)[:-10] # Remove .kicad_pro
|
|
pcb_name = os.path.basename(pcb_file)
|
|
|
|
report = f"# Design Rule Check Report for {project_name}\n\n"
|
|
report += f"PCB file: `{pcb_name}`\n\n"
|
|
|
|
# Add summary
|
|
total_violations = drc_results.get("total_violations", 0)
|
|
report += "## Summary\n\n"
|
|
|
|
if total_violations == 0:
|
|
report += "✅ **No DRC violations found**\n\n"
|
|
else:
|
|
report += f"❌ **{total_violations} DRC violations found**\n\n"
|
|
|
|
# Add violation categories
|
|
categories = drc_results.get("violation_categories", {})
|
|
if categories:
|
|
report += "## Violation Categories\n\n"
|
|
for category, count in categories.items():
|
|
report += f"- **{category}**: {count} violations\n"
|
|
report += "\n"
|
|
|
|
# Add detailed violations
|
|
violations = drc_results.get("violations", [])
|
|
if violations:
|
|
report += "## Detailed Violations\n\n"
|
|
|
|
# Limit to first 50 violations to keep the report manageable
|
|
displayed_violations = violations[:50]
|
|
|
|
for i, violation in enumerate(displayed_violations, 1):
|
|
message = violation.get("message", "Unknown error")
|
|
severity = violation.get("severity", "error")
|
|
|
|
# Extract location information if available
|
|
location = violation.get("location", {})
|
|
x = location.get("x", 0)
|
|
y = location.get("y", 0)
|
|
|
|
report += f"### Violation {i}\n\n"
|
|
report += f"- **Type**: {message}\n"
|
|
report += f"- **Severity**: {severity}\n"
|
|
|
|
if x != 0 or y != 0:
|
|
report += f"- **Location**: X={x:.2f}mm, Y={y:.2f}mm\n"
|
|
|
|
report += "\n"
|
|
|
|
if len(violations) > 50:
|
|
report += f"*...and {len(violations) - 50} more violations (use the `run_drc_check` tool for complete results)*\n\n"
|
|
|
|
# Add recommendations
|
|
report += "## Recommendations\n\n"
|
|
|
|
if total_violations == 0:
|
|
report += (
|
|
"Your PCB design passes all design rule checks. It's ready for manufacturing!\n\n"
|
|
)
|
|
else:
|
|
report += "To fix these violations:\n\n"
|
|
report += "1. Open your PCB in KiCad's PCB Editor\n"
|
|
report += "2. Run the DRC by clicking the 'Inspect → Design Rules Checker' menu item\n"
|
|
report += "3. Click on each error in the DRC window to locate it on the PCB\n"
|
|
report += "4. Fix the issue according to the error message\n"
|
|
report += "5. Re-run DRC to verify your fixes\n\n"
|
|
|
|
# Add common solutions for frequent error types
|
|
if categories:
|
|
most_common_error = max(categories.items(), key=lambda x: x[1])[0]
|
|
report += "### Common Solutions\n\n"
|
|
|
|
if "clearance" in most_common_error.lower():
|
|
report += "**For clearance violations:**\n"
|
|
report += "- Reroute traces to maintain minimum clearance requirements\n"
|
|
report += "- Check layer stackup and adjust clearance rules if necessary\n"
|
|
report += "- Consider adjusting trace widths\n\n"
|
|
|
|
elif "width" in most_common_error.lower():
|
|
report += "**For width violations:**\n"
|
|
report += "- Increase trace widths to meet minimum requirements\n"
|
|
report += "- Check current requirements for your traces\n\n"
|
|
|
|
elif "drill" in most_common_error.lower():
|
|
report += "**For drill violations:**\n"
|
|
report += "- Adjust hole sizes to meet manufacturing constraints\n"
|
|
report += "- Check via settings\n\n"
|
|
|
|
return report
|