""" Advanced DRC (Design Rule Check) utilities for KiCad. Provides sophisticated DRC rule creation, customization, and validation beyond the basic KiCad DRC capabilities. """ import json import re from dataclasses import dataclass, field from typing import Dict, List, Optional, Any, Union from enum import Enum import logging 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: Optional[str] = None # Expression for when rule applies description: Optional[str] = None enabled: bool = True custom_message: Optional[str] = None @dataclass class DRCRuleSet: """Collection of DRC rules with metadata.""" name: str version: str description: str rules: List[DRCRule] = field(default_factory=list) technology: Optional[str] = None # e.g., "PCB", "Flex", "HDI" layer_count: Optional[int] = None board_thickness: Optional[float] = None created_by: Optional[str] = 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) -> Optional[str]: """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