kicad-mcp/kicad_mcp/utils/drc_history.py
Ryan Malloy bc0f3db97c
Some checks are pending
CI / Lint and Format (push) Waiting to run
CI / Test Python 3.11 on macos-latest (push) Waiting to run
CI / Test Python 3.12 on macos-latest (push) Waiting to run
CI / Test Python 3.13 on macos-latest (push) Waiting to run
CI / Test Python 3.10 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.11 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.12 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.13 on ubuntu-latest (push) Waiting to run
CI / Security Scan (push) Waiting to run
CI / Build Package (push) Blocked by required conditions
Implement comprehensive AI/LLM integration for KiCad MCP server
Add intelligent analysis and recommendation tools for KiCad designs:

## New AI Tools (kicad_mcp/tools/ai_tools.py)
- suggest_components_for_circuit: Smart component suggestions based on circuit analysis
- recommend_design_rules: Automated design rule recommendations for different technologies
- optimize_pcb_layout: PCB layout optimization for signal integrity, thermal, and cost
- analyze_design_completeness: Comprehensive design completeness analysis

## Enhanced Utilities
- component_utils.py: Add ComponentType enum and component classification functions
- pattern_recognition.py: Enhanced circuit pattern analysis and recommendations
- netlist_parser.py: Implement missing parse_netlist_file function for AI tools

## Key Features
- Circuit pattern recognition for power supplies, amplifiers, microcontrollers
- Technology-specific design rules (standard, HDI, RF, automotive)
- Layout optimization suggestions with implementation steps
- Component suggestion system with standard values and examples
- Design completeness scoring with actionable recommendations

## Server Integration
- Register AI tools in FastMCP server
- Integrate with existing KiCad utilities and file parsers
- Error handling and graceful fallbacks for missing data

Fixes ImportError that prevented server startup and enables advanced
AI-powered design assistance for KiCad projects.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-11 16:15:58 -06:00

184 lines
5.8 KiB
Python

"""
Utilities for tracking DRC history for KiCad projects.
This will allow users to compare DRC results over time.
"""
from datetime import datetime
import json
import os
import platform
import time
from typing import Any
# Directory for storing DRC history
if platform.system() == "Windows":
# Windows: Use APPDATA or LocalAppData
DRC_HISTORY_DIR = os.path.join(
os.environ.get("APPDATA", os.path.expanduser("~")), "kicad_mcp", "drc_history"
)
else:
# macOS/Linux: Use ~/.kicad_mcp/drc_history
DRC_HISTORY_DIR = os.path.expanduser("~/.kicad_mcp/drc_history")
def ensure_history_dir() -> None:
"""Ensure the DRC history directory exists."""
os.makedirs(DRC_HISTORY_DIR, exist_ok=True)
def get_project_history_path(project_path: str) -> str:
"""Get the path to the DRC history file for a project.
Args:
project_path: Path to the KiCad project file
Returns:
Path to the project's DRC history file
"""
# Create a safe filename from the project path
project_hash = hash(project_path) & 0xFFFFFFFF # Ensure positive hash
basename = os.path.basename(project_path)
history_filename = f"{basename}_{project_hash}_drc_history.json"
return os.path.join(DRC_HISTORY_DIR, history_filename)
def save_drc_result(project_path: str, drc_result: dict[str, Any]) -> None:
"""Save a DRC result to the project's history.
Args:
project_path: Path to the KiCad project file
drc_result: DRC result dictionary
"""
ensure_history_dir()
history_path = get_project_history_path(project_path)
# Create a history entry
timestamp = time.time()
formatted_time = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
history_entry = {
"timestamp": timestamp,
"datetime": formatted_time,
"total_violations": drc_result.get("total_violations", 0),
"violation_categories": drc_result.get("violation_categories", {}),
}
# Load existing history or create new
if os.path.exists(history_path):
try:
with open(history_path) as f:
history = json.load(f)
except (OSError, json.JSONDecodeError) as e:
print(f"Error loading DRC history: {str(e)}")
history = {"project_path": project_path, "entries": []}
else:
history = {"project_path": project_path, "entries": []}
# Add new entry and save
history["entries"].append(history_entry)
# Keep only the last 10 entries to avoid excessive storage
if len(history["entries"]) > 10:
history["entries"] = sorted(history["entries"], key=lambda x: x["timestamp"], reverse=True)[
:10
]
try:
with open(history_path, "w") as f:
json.dump(history, f, indent=2)
print(f"Saved DRC history entry to {history_path}")
except OSError as e:
print(f"Error saving DRC history: {str(e)}")
def get_drc_history(project_path: str) -> list[dict[str, Any]]:
"""Get the DRC history for a project.
Args:
project_path: Path to the KiCad project file
Returns:
List of DRC history entries, sorted by timestamp (newest first)
"""
history_path = get_project_history_path(project_path)
if not os.path.exists(history_path):
print(f"No DRC history found for {project_path}")
return []
try:
with open(history_path) as f:
history = json.load(f)
# Sort entries by timestamp (newest first)
entries = sorted(
history.get("entries", []), key=lambda x: x.get("timestamp", 0), reverse=True
)
return entries
except (OSError, json.JSONDecodeError) as e:
print(f"Error reading DRC history: {str(e)}")
return []
def compare_with_previous(
project_path: str, current_result: dict[str, Any]
) -> dict[str, Any] | None:
"""Compare current DRC result with the previous one.
Args:
project_path: Path to the KiCad project file
current_result: Current DRC result dictionary
Returns:
Comparison dictionary or None if no history exists
"""
history = get_drc_history(project_path)
if not history or len(history) < 2: # Need at least one previous entry
return None
previous = history[0] # Most recent entry
current_violations = current_result.get("total_violations", 0)
previous_violations = previous.get("total_violations", 0)
# Compare violation categories
current_categories = current_result.get("violation_categories", {})
previous_categories = previous.get("violation_categories", {})
# Find new categories
new_categories = {}
for category, count in current_categories.items():
if category not in previous_categories:
new_categories[category] = count
# Find resolved categories
resolved_categories = {}
for category, count in previous_categories.items():
if category not in current_categories:
resolved_categories[category] = count
# Find changed categories
changed_categories = {}
for category, count in current_categories.items():
if category in previous_categories and count != previous_categories[category]:
changed_categories[category] = {
"current": count,
"previous": previous_categories[category],
"change": count - previous_categories[category],
}
comparison = {
"current_violations": current_violations,
"previous_violations": previous_violations,
"change": current_violations - previous_violations,
"previous_datetime": previous.get("datetime", "unknown"),
"new_categories": new_categories,
"resolved_categories": resolved_categories,
"changed_categories": changed_categories,
}
return comparison