kicad-mcp/kicad_mcp/utils/advanced_drc.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

445 lines
16 KiB
Python

"""
Advanced DRC (Design Rule Check) utilities for KiCad.
Provides sophisticated DRC rule creation, customization, and validation
beyond the basic KiCad DRC capabilities.
"""
from dataclasses import dataclass, field
from enum import Enum
import logging
from typing import Any
logger = logging.getLogger(__name__)
class RuleSeverity(Enum):
"""DRC rule severity levels."""
ERROR = "error"
WARNING = "warning"
INFO = "info"
IGNORE = "ignore"
class RuleType(Enum):
"""Types of DRC rules."""
CLEARANCE = "clearance"
TRACK_WIDTH = "track_width"
VIA_SIZE = "via_size"
ANNULAR_RING = "annular_ring"
DRILL_SIZE = "drill_size"
COURTYARD_CLEARANCE = "courtyard_clearance"
SILK_CLEARANCE = "silk_clearance"
FABRICATION = "fabrication"
ASSEMBLY = "assembly"
ELECTRICAL = "electrical"
MECHANICAL = "mechanical"
@dataclass
class DRCRule:
"""Represents a single DRC rule."""
name: str
rule_type: RuleType
severity: RuleSeverity
constraint: dict[str, Any]
condition: str | None = None # Expression for when rule applies
description: str | None = None
enabled: bool = True
custom_message: str | None = None
@dataclass
class DRCRuleSet:
"""Collection of DRC rules with metadata."""
name: str
version: str
description: str
rules: list[DRCRule] = field(default_factory=list)
technology: str | None = None # e.g., "PCB", "Flex", "HDI"
layer_count: int | None = None
board_thickness: float | None = None
created_by: str | None = None
class AdvancedDRCManager:
"""Manager for advanced DRC rules and validation."""
def __init__(self):
"""Initialize the DRC manager."""
self.rule_sets = {}
self.active_rule_set = None
self._load_default_rules()
def _load_default_rules(self) -> None:
"""Load default DRC rule sets."""
# Standard PCB rules
standard_rules = DRCRuleSet(
name="Standard PCB",
version="1.0",
description="Standard PCB manufacturing rules",
technology="PCB"
)
# Basic clearance rules
standard_rules.rules.extend([
DRCRule(
name="Min Track Width",
rule_type=RuleType.TRACK_WIDTH,
severity=RuleSeverity.ERROR,
constraint={"min_width": 0.1}, # 0.1mm minimum
description="Minimum track width for manufacturability"
),
DRCRule(
name="Standard Clearance",
rule_type=RuleType.CLEARANCE,
severity=RuleSeverity.ERROR,
constraint={"min_clearance": 0.2}, # 0.2mm minimum
description="Standard clearance between conductors"
),
DRCRule(
name="Via Drill Size",
rule_type=RuleType.VIA_SIZE,
severity=RuleSeverity.ERROR,
constraint={"min_drill": 0.2, "max_drill": 6.0},
description="Via drill size constraints"
),
DRCRule(
name="Via Annular Ring",
rule_type=RuleType.ANNULAR_RING,
severity=RuleSeverity.WARNING,
constraint={"min_annular_ring": 0.05}, # 0.05mm minimum
description="Minimum annular ring for vias"
)
])
self.rule_sets["standard"] = standard_rules
self.active_rule_set = "standard"
def create_high_density_rules(self) -> DRCRuleSet:
"""Create rules for high-density interconnect (HDI) boards."""
hdi_rules = DRCRuleSet(
name="HDI PCB",
version="1.0",
description="High-density interconnect PCB rules",
technology="HDI"
)
hdi_rules.rules.extend([
DRCRule(
name="HDI Track Width",
rule_type=RuleType.TRACK_WIDTH,
severity=RuleSeverity.ERROR,
constraint={"min_width": 0.075}, # 75μm minimum
description="Minimum track width for HDI manufacturing"
),
DRCRule(
name="HDI Clearance",
rule_type=RuleType.CLEARANCE,
severity=RuleSeverity.ERROR,
constraint={"min_clearance": 0.075}, # 75μm minimum
description="Minimum clearance for HDI boards"
),
DRCRule(
name="Microvia Size",
rule_type=RuleType.VIA_SIZE,
severity=RuleSeverity.ERROR,
constraint={"min_drill": 0.1, "max_drill": 0.15},
description="Microvia drill size constraints"
),
DRCRule(
name="BGA Escape Routing",
rule_type=RuleType.CLEARANCE,
severity=RuleSeverity.WARNING,
constraint={"min_clearance": 0.1},
condition="A.intersects(B.Type == 'BGA')",
description="Clearance around BGA escape routes"
)
])
return hdi_rules
def create_rf_rules(self) -> DRCRuleSet:
"""Create rules specifically for RF/microwave designs."""
rf_rules = DRCRuleSet(
name="RF/Microwave",
version="1.0",
description="Rules for RF and microwave PCB designs",
technology="RF"
)
rf_rules.rules.extend([
DRCRule(
name="Controlled Impedance Spacing",
rule_type=RuleType.CLEARANCE,
severity=RuleSeverity.ERROR,
constraint={"min_clearance": 0.2},
condition="A.NetClass == 'RF' or B.NetClass == 'RF'",
description="Spacing for controlled impedance traces"
),
DRCRule(
name="RF Via Stitching",
rule_type=RuleType.VIA_SIZE,
severity=RuleSeverity.WARNING,
constraint={"max_spacing": 2.0}, # Via stitching spacing
condition="Layer == 'Ground'",
description="Ground via stitching for RF designs"
),
DRCRule(
name="Microstrip Width Control",
rule_type=RuleType.TRACK_WIDTH,
severity=RuleSeverity.ERROR,
constraint={"target_width": 0.5, "tolerance": 0.05},
condition="NetClass == '50ohm'",
description="Precise width control for 50Ω traces"
)
])
return rf_rules
def create_automotive_rules(self) -> DRCRuleSet:
"""Create automotive-grade reliability rules."""
automotive_rules = DRCRuleSet(
name="Automotive",
version="1.0",
description="Automotive reliability and safety rules",
technology="Automotive"
)
automotive_rules.rules.extend([
DRCRule(
name="Safety Critical Clearance",
rule_type=RuleType.CLEARANCE,
severity=RuleSeverity.ERROR,
constraint={"min_clearance": 0.5},
condition="A.NetClass == 'Safety' or B.NetClass == 'Safety'",
description="Enhanced clearance for safety-critical circuits"
),
DRCRule(
name="Power Track Width",
rule_type=RuleType.TRACK_WIDTH,
severity=RuleSeverity.ERROR,
constraint={"min_width": 0.5},
condition="NetClass == 'Power'",
description="Minimum width for power distribution"
),
DRCRule(
name="Thermal Via Density",
rule_type=RuleType.VIA_SIZE,
severity=RuleSeverity.WARNING,
constraint={"min_density": 4}, # 4 vias per cm² for thermal
condition="Pad.ThermalPad == True",
description="Thermal via density for heat dissipation"
),
DRCRule(
name="Vibration Resistant Vias",
rule_type=RuleType.ANNULAR_RING,
severity=RuleSeverity.ERROR,
constraint={"min_annular_ring": 0.1},
description="Enhanced annular ring for vibration resistance"
)
])
return automotive_rules
def create_custom_rule(self, name: str, rule_type: RuleType,
constraint: dict[str, Any], severity: RuleSeverity = RuleSeverity.ERROR,
condition: str = None, description: str = None) -> DRCRule:
"""Create a custom DRC rule."""
return DRCRule(
name=name,
rule_type=rule_type,
severity=severity,
constraint=constraint,
condition=condition,
description=description
)
def validate_rule_syntax(self, rule: DRCRule) -> list[str]:
"""Validate rule syntax and return any errors."""
errors = []
# Validate constraint format
if rule.rule_type == RuleType.CLEARANCE:
if "min_clearance" not in rule.constraint:
errors.append("Clearance rule must specify min_clearance")
elif rule.constraint["min_clearance"] <= 0:
errors.append("Clearance must be positive")
elif rule.rule_type == RuleType.TRACK_WIDTH:
if "min_width" not in rule.constraint and "max_width" not in rule.constraint:
errors.append("Track width rule must specify min_width or max_width")
elif rule.rule_type == RuleType.VIA_SIZE:
if "min_drill" not in rule.constraint and "max_drill" not in rule.constraint:
errors.append("Via size rule must specify drill constraints")
# Validate condition syntax (basic check)
if rule.condition:
try:
# Basic syntax validation - could be more sophisticated
if not any(op in rule.condition for op in ["==", "!=", ">", "<", "intersects"]):
errors.append("Condition must contain a comparison operator")
except Exception as e:
errors.append(f"Invalid condition syntax: {e}")
return errors
def export_kicad_drc_rules(self, rule_set_name: str) -> str:
"""Export rule set as KiCad-compatible DRC rules."""
if rule_set_name not in self.rule_sets:
raise ValueError(f"Rule set '{rule_set_name}' not found")
rule_set = self.rule_sets[rule_set_name]
kicad_rules = []
kicad_rules.append(f"# DRC Rules: {rule_set.name}")
kicad_rules.append(f"# Description: {rule_set.description}")
kicad_rules.append(f"# Version: {rule_set.version}")
kicad_rules.append("")
for rule in rule_set.rules:
if not rule.enabled:
continue
kicad_rule = self._convert_to_kicad_rule(rule)
if kicad_rule:
kicad_rules.append(kicad_rule)
kicad_rules.append("")
return "\n".join(kicad_rules)
def _convert_to_kicad_rule(self, rule: DRCRule) -> str | None:
"""Convert DRC rule to KiCad rule format."""
try:
rule_lines = [f"# {rule.name}"]
if rule.description:
rule_lines.append(f"# {rule.description}")
if rule.rule_type == RuleType.CLEARANCE:
clearance = rule.constraint.get("min_clearance", 0.2)
rule_lines.append(f"(rule \"{rule.name}\"")
rule_lines.append(f" (constraint clearance (min {clearance}mm))")
if rule.condition:
rule_lines.append(f" (condition \"{rule.condition}\")")
rule_lines.append(")")
elif rule.rule_type == RuleType.TRACK_WIDTH:
if "min_width" in rule.constraint:
min_width = rule.constraint["min_width"]
rule_lines.append(f"(rule \"{rule.name}\"")
rule_lines.append(f" (constraint track_width (min {min_width}mm))")
if rule.condition:
rule_lines.append(f" (condition \"{rule.condition}\")")
rule_lines.append(")")
elif rule.rule_type == RuleType.VIA_SIZE:
rule_lines.append(f"(rule \"{rule.name}\"")
if "min_drill" in rule.constraint:
rule_lines.append(f" (constraint hole_size (min {rule.constraint['min_drill']}mm))")
if "max_drill" in rule.constraint:
rule_lines.append(f" (constraint hole_size (max {rule.constraint['max_drill']}mm))")
if rule.condition:
rule_lines.append(f" (condition \"{rule.condition}\")")
rule_lines.append(")")
return "\n".join(rule_lines)
except Exception as e:
logger.error(f"Failed to convert rule {rule.name}: {e}")
return None
def analyze_pcb_for_rule_violations(self, pcb_file_path: str,
rule_set_name: str = None) -> dict[str, Any]:
"""Analyze PCB file against rule set and report violations."""
if rule_set_name is None:
rule_set_name = self.active_rule_set
if rule_set_name not in self.rule_sets:
raise ValueError(f"Rule set '{rule_set_name}' not found")
rule_set = self.rule_sets[rule_set_name]
violations = []
# This would integrate with actual PCB analysis
# For now, return structure for potential violations
return {
"pcb_file": pcb_file_path,
"rule_set": rule_set_name,
"rule_count": len(rule_set.rules),
"violations": violations,
"summary": {
"errors": len([v for v in violations if v.get("severity") == "error"]),
"warnings": len([v for v in violations if v.get("severity") == "warning"]),
"total": len(violations)
}
}
def generate_manufacturing_constraints(self, technology: str = "standard") -> dict[str, Any]:
"""Generate manufacturing constraints for specific technology."""
constraints = {
"standard": {
"min_track_width": 0.1, # mm
"min_clearance": 0.2, # mm
"min_via_drill": 0.2, # mm
"min_annular_ring": 0.05, # mm
"aspect_ratio_limit": 8, # drill depth : diameter
"layer_count_limit": 16,
"board_thickness_range": [0.4, 6.0]
},
"hdi": {
"min_track_width": 0.075, # mm
"min_clearance": 0.075, # mm
"min_via_drill": 0.1, # mm
"min_annular_ring": 0.025, # mm
"aspect_ratio_limit": 1, # For microvias
"layer_count_limit": 20,
"board_thickness_range": [0.8, 3.2]
},
"rf": {
"min_track_width": 0.1, # mm
"min_clearance": 0.2, # mm
"impedance_tolerance": 5, # %
"via_stitching_max": 2.0, # mm spacing
"ground_plane_required": True,
"layer_symmetry_required": True
},
"automotive": {
"min_track_width": 0.15, # mm (more conservative)
"min_clearance": 0.3, # mm (enhanced reliability)
"min_via_drill": 0.25, # mm
"min_annular_ring": 0.075, # mm
"temperature_range": [-40, 125], # °C
"vibration_resistant": True
}
}
return constraints.get(technology, constraints["standard"])
def add_rule_set(self, rule_set: DRCRuleSet) -> None:
"""Add a rule set to the manager."""
self.rule_sets[rule_set.name.lower().replace(" ", "_")] = rule_set
def get_rule_set_names(self) -> list[str]:
"""Get list of available rule set names."""
return list(self.rule_sets.keys())
def set_active_rule_set(self, name: str) -> None:
"""Set the active rule set."""
if name not in self.rule_sets:
raise ValueError(f"Rule set '{name}' not found")
self.active_rule_set = name
def create_drc_manager() -> AdvancedDRCManager:
"""Create and initialize a DRC manager with default rule sets."""
manager = AdvancedDRCManager()
# Add specialized rule sets
manager.add_rule_set(manager.create_high_density_rules())
manager.add_rule_set(manager.create_rf_rules())
manager.add_rule_set(manager.create_automotive_rules())
return manager