"""Component geometry handling for KiCad schematic placement.
This module provides base classes for handling component-specific geometry including:
- Bounding box calculations
- Pin location mapping
- Component dimensions
"""
import math
from dataclasses import dataclass
from typing import Dict, Optional, Tuple
[docs]
@dataclass
class PinLocation:
"""Represents a pin's location relative to component center."""
x: float # X offset from center
y: float # Y offset from center
side: str # Which side of component (left, right, top, bottom)
[docs]
@dataclass
class ComponentDimensions:
"""Represents a component's dimensions."""
width: float
height: float
pin_grid: float = 2.54 # Default grid spacing
[docs]
class ComponentGeometryHandler:
"""Base class for handling component geometry and pin locations."""
[docs]
def __init__(self, component_type: str):
"""Initialize with component type."""
self.component_type = component_type
self._dimensions = self._get_default_dimensions()
self._pin_locations: Dict[str, PinLocation] = {}
self._setup_pin_locations()
def _get_default_dimensions(self) -> ComponentDimensions:
"""Get default dimensions for this component type."""
# Default dimensions - override in subclasses
return ComponentDimensions(width=5.08, height=15.24)
def _setup_pin_locations(self) -> None:
"""Set up pin locations for this component type."""
# Override in subclasses to define pin locations
pass
[docs]
def get_pin_location(
self, pin_number: str, rotation: float = 0
) -> Optional[Tuple[float, float]]:
"""Get pin location relative to component center, accounting for rotation."""
if pin_number not in self._pin_locations:
return None
pin = self._pin_locations[pin_number]
# Apply rotation transformation
theta = math.radians(rotation)
rotated_x = pin.x * math.cos(theta) - pin.y * math.sin(theta)
rotated_y = pin.x * math.sin(theta) + pin.y * math.cos(theta)
return (rotated_x, rotated_y)
[docs]
def get_bounding_box(
self, x: float, y: float, rotation: float
) -> Tuple[float, float, float, float]:
"""Get component bounding box at given position and rotation."""
width = self._dimensions.width
height = self._dimensions.height
# For 90/270 degree rotations, swap width and height
if rotation in [90, 270]:
width, height = height, width
# Calculate corners relative to center
left = x - width / 2
top = y - height / 2
return (left, top, width, height)
[docs]
class ResistorGeometryHandler(ComponentGeometryHandler):
"""Geometry handler for resistor components."""
def _get_default_dimensions(self) -> ComponentDimensions:
"""Get resistor-specific dimensions."""
dims = ComponentDimensions(
width=5.08, # Standard resistor symbol width
height=10.16, # Reduced height for better spacing
)
print(f"Creating resistor with dimensions: {dims}")
return dims
def _setup_pin_locations(self) -> None:
"""Set up resistor pin locations."""
# Standard resistor has pins at top and bottom center
pin1 = PinLocation(x=0, y=5.08, side="top") # Pin 1 at top
pin2 = PinLocation(x=0, y=-5.08, side="bottom") # Pin 2 at bottom
print(f"Setting up resistor pins:")
print(f" Pin 1 at: ({pin1.x}, {pin1.y})")
print(f" Pin 2 at: ({pin2.x}, {pin2.y})")
self._pin_locations = {"1": pin1, "2": pin2}
[docs]
class PowerSymbolGeometryHandler(ComponentGeometryHandler):
"""Geometry handler for power symbols."""
def _get_default_dimensions(self) -> ComponentDimensions:
"""Get power symbol dimensions."""
dims = ComponentDimensions(
width=5.08, # Increased width for better visibility
height=5.08, # Increased height for better spacing
)
print(f"Creating power symbol with dimensions: {dims}")
return dims
def _setup_pin_locations(self) -> None:
"""Set up power symbol pin locations."""
# Power symbols have pin at bottom center
pin_loc = PinLocation(x=0, y=2.54, side="bottom") # Offset pin from center
print(f"Setting up power symbol pin at: ({pin_loc.x}, {pin_loc.y})")
self._pin_locations = {"1": pin_loc}
[docs]
def create_geometry_handler(
component_type: str, library: str
) -> ComponentGeometryHandler:
"""Factory function to create appropriate geometry handler for component."""
if library == "Device" and component_type == "R":
return ResistorGeometryHandler(component_type)
elif library == "power":
return PowerSymbolGeometryHandler(component_type)
else:
# Default to base handler for unknown components
return ComponentGeometryHandler(component_type)