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
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>
184 lines
5.8 KiB
Python
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
|