2025-08-22 22:16:12 -07:00

240 lines
9.7 KiB
Python

"""Domain models for design rule constraints."""
from dataclasses import dataclass, field
from typing import Dict, Optional, Any
from enum import Enum
class ClearanceType(Enum):
"""Types of clearance rules."""
TRACK_TO_TRACK = "track_to_track"
TRACK_TO_PAD = "track_to_pad"
TRACK_TO_VIA = "track_to_via"
VIA_TO_VIA = "via_to_via"
PAD_TO_PAD = "pad_to_pad"
COPPER_POUR = "copper_pour"
@dataclass(frozen=True)
class NetClass:
"""Value object representing a netclass with its constraints."""
name: str
track_width: float
via_diameter: float
via_drill: float
clearance: float = 0.2 # Default clearance in mm
track_width_min: Optional[float] = None
track_width_max: Optional[float] = None
via_diameter_min: Optional[float] = None
via_diameter_max: Optional[float] = None
def __post_init__(self):
# Set min/max defaults if not provided
if self.track_width_min is None:
object.__setattr__(self, 'track_width_min', self.track_width * 0.5)
if self.track_width_max is None:
object.__setattr__(self, 'track_width_max', self.track_width * 2.0)
if self.via_diameter_min is None:
object.__setattr__(self, 'via_diameter_min', self.via_diameter * 0.8)
if self.via_diameter_max is None:
object.__setattr__(self, 'via_diameter_max', self.via_diameter * 1.5)
def is_track_width_valid(self, width: float) -> bool:
"""Check if track width is within netclass limits."""
return self.track_width_min <= width <= self.track_width_max
def is_via_size_valid(self, diameter: float) -> bool:
"""Check if via diameter is within netclass limits."""
return self.via_diameter_min <= diameter <= self.via_diameter_max
@dataclass
class DRCConstraints:
"""Domain entity containing all design rule constraints."""
# Global minimums (absolute limits)
min_track_width: float = 0.1 # mm
min_track_spacing: float = 0.1 # mm
min_via_diameter: float = 0.2 # mm
min_via_drill: float = 0.1 # mm
# Default values
default_track_width: float = 0.25 # mm
default_clearance: float = 0.2 # mm
default_via_diameter: float = 0.6 # mm
default_via_drill: float = 0.3 # mm
# Netclasses
netclasses: Dict[str, NetClass] = field(default_factory=dict)
# Advanced constraints
blind_via_enabled: bool = True
buried_via_enabled: bool = True
micro_via_enabled: bool = False
# Layer-specific constraints
layer_constraints: Dict[str, Dict[str, float]] = field(default_factory=dict)
# Clearance matrix
clearance_matrix: Dict[ClearanceType, float] = field(default_factory=dict)
def __post_init__(self):
"""Initialize default netclass and clearance matrix."""
if "Default" not in self.netclasses:
self.netclasses["Default"] = NetClass(
name="Default",
track_width=self.default_track_width,
via_diameter=self.default_via_diameter,
via_drill=self.default_via_drill,
clearance=self.default_clearance
)
# Initialize clearance matrix with defaults
if not self.clearance_matrix:
self.clearance_matrix = {
ClearanceType.TRACK_TO_TRACK: self.default_clearance,
ClearanceType.TRACK_TO_PAD: self.default_clearance,
ClearanceType.TRACK_TO_VIA: self.default_clearance,
ClearanceType.VIA_TO_VIA: self.default_clearance,
ClearanceType.PAD_TO_PAD: self.default_clearance,
ClearanceType.COPPER_POUR: self.default_clearance * 1.5,
}
def add_netclass(self, netclass: NetClass) -> None:
"""Add or update a netclass."""
self.netclasses[netclass.name] = netclass
def get_netclass(self, name: str) -> NetClass:
"""Get netclass by name, fallback to Default."""
return self.netclasses.get(name, self.netclasses["Default"])
def get_clearance_for_nets(self, net1_class: str, net2_class: str) -> float:
"""Get clearance between two netclasses."""
nc1 = self.get_netclass(net1_class)
nc2 = self.get_netclass(net2_class)
# Return the maximum clearance required by either netclass
return max(nc1.clearance, nc2.clearance)
def get_clearance(self, clearance_type: ClearanceType) -> float:
"""Get clearance for specific constraint type."""
return self.clearance_matrix.get(clearance_type, self.default_clearance)
def set_clearance(self, clearance_type: ClearanceType, value: float) -> None:
"""Set clearance for specific constraint type."""
self.clearance_matrix[clearance_type] = value
def get_layer_constraints(self, layer_name: str) -> Dict[str, float]:
"""Get layer-specific constraints."""
return self.layer_constraints.get(layer_name, {
'min_track_width': self.min_track_width,
'min_spacing': self.min_track_spacing,
})
def set_layer_constraint(self, layer_name: str, constraint: str, value: float) -> None:
"""Set layer-specific constraint."""
if layer_name not in self.layer_constraints:
self.layer_constraints[layer_name] = {}
self.layer_constraints[layer_name][constraint] = value
def validate_track_width(self, width: float, netclass: str = "Default") -> bool:
"""Validate if track width meets DRC requirements."""
if width < self.min_track_width:
return False
nc = self.get_netclass(netclass)
return nc.is_track_width_valid(width)
def validate_via_size(self, diameter: float, drill: float,
netclass: str = "Default") -> bool:
"""Validate if via size meets DRC requirements."""
if diameter < self.min_via_diameter or drill < self.min_via_drill:
return False
# Check aspect ratio (assume 1.6mm standard thickness)
aspect_ratio = 1.6 / drill
if aspect_ratio > 10: # Standard manufacturing limit
return False
nc = self.get_netclass(netclass)
return nc.is_via_size_valid(diameter)
def validate_clearance(self, distance: float, clearance_type: ClearanceType) -> bool:
"""Validate if clearance distance meets requirements."""
required_clearance = self.get_clearance(clearance_type)
return distance >= required_clearance
def get_via_types_allowed(self) -> list:
"""Get list of allowed via types."""
allowed = ["through"] # Through vias always allowed
if self.blind_via_enabled:
allowed.append("blind")
if self.buried_via_enabled:
allowed.append("buried")
if self.micro_via_enabled:
allowed.append("micro")
return allowed
def to_dict(self) -> Dict[str, Any]:
"""Convert constraints to dictionary for serialization."""
return {
'min_track_width': self.min_track_width,
'min_track_spacing': self.min_track_spacing,
'min_via_diameter': self.min_via_diameter,
'min_via_drill': self.min_via_drill,
'default_track_width': self.default_track_width,
'default_clearance': self.default_clearance,
'default_via_diameter': self.default_via_diameter,
'default_via_drill': self.default_via_drill,
'netclasses': {name: {
'name': nc.name,
'track_width': nc.track_width,
'via_diameter': nc.via_diameter,
'via_drill': nc.via_drill,
'clearance': nc.clearance
} for name, nc in self.netclasses.items()},
'blind_via_enabled': self.blind_via_enabled,
'buried_via_enabled': self.buried_via_enabled,
'micro_via_enabled': self.micro_via_enabled,
'layer_constraints': self.layer_constraints,
'clearance_matrix': {ct.value: val for ct, val in self.clearance_matrix.items()}
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'DRCConstraints':
"""Create constraints from dictionary."""
constraints = cls(
min_track_width=data.get('min_track_width', 0.1),
min_track_spacing=data.get('min_track_spacing', 0.1),
min_via_diameter=data.get('min_via_diameter', 0.2),
min_via_drill=data.get('min_via_drill', 0.1),
default_track_width=data.get('default_track_width', 0.25),
default_clearance=data.get('default_clearance', 0.2),
default_via_diameter=data.get('default_via_diameter', 0.6),
default_via_drill=data.get('default_via_drill', 0.3),
blind_via_enabled=data.get('blind_via_enabled', True),
buried_via_enabled=data.get('buried_via_enabled', True),
micro_via_enabled=data.get('micro_via_enabled', False),
)
# Load netclasses
for name, nc_data in data.get('netclasses', {}).items():
constraints.add_netclass(NetClass(
name=nc_data['name'],
track_width=nc_data['track_width'],
via_diameter=nc_data['via_diameter'],
via_drill=nc_data['via_drill'],
clearance=nc_data['clearance']
))
# Load clearance matrix
clearance_data = data.get('clearance_matrix', {})
for ct_name, value in clearance_data.items():
try:
clearance_type = ClearanceType(ct_name)
constraints.set_clearance(clearance_type, value)
except ValueError:
pass # Skip unknown clearance types
return constraints