#!/usr/bin/env python3
"""
Configuration constants and settings for KiCAD schematic API.
This module centralizes all magic numbers and configuration values
to make them easily configurable and maintainable.
"""
from dataclasses import dataclass, field
from typing import Any, Dict, List, Tuple
[docs]
@dataclass
class PropertyOffsets:
"""Standard property positioning offsets relative to component position."""
reference_x: float = 2.54 # Reference label X offset
reference_y: float = -1.2701 # Reference label Y offset (above) - exact match
value_x: float = 2.54 # Value label X offset
value_y: float = 1.2699 # Value label Y offset (below) - exact match
footprint_rotation: float = 90 # Footprint property rotation
hidden_property_offset: float = 1.27 # Y spacing for hidden properties
[docs]
@dataclass
class GridSettings:
"""Standard KiCAD grid and spacing settings."""
standard_grid: float = 1.27 # Standard 50mil grid in mm
component_spacing: float = 2.54 # Standard component spacing (100mil)
unit_spacing: float = 12.7 # Multi-unit IC spacing
power_offset: Tuple[float, float] = (25.4, 0.0) # Power unit offset
[docs]
@dataclass
class SheetSettings:
"""Hierarchical sheet positioning settings."""
name_offset_y: float = -0.7116 # Sheetname position offset (above)
file_offset_y: float = 0.5846 # Sheetfile position offset (below)
default_stroke_width: float = 0.1524
default_stroke_type: str = "solid"
[docs]
@dataclass
class ToleranceSettings:
"""Tolerance values for various operations."""
position_tolerance: float = 0.1 # Point matching tolerance
wire_segment_min: float = 0.001 # Minimum wire segment length
coordinate_precision: float = 0.01 # Coordinate comparison precision
[docs]
@dataclass
class PositioningSettings:
"""Global positioning behavior settings."""
use_grid_units: bool = False # If True, all positions default to grid units
grid_size: float = 1.27 # Default grid size in mm (50 mil KiCAD standard)
[docs]
@dataclass
class DefaultValues:
"""Default values for various operations."""
project_name: str = "untitled"
stroke_width: float = 0.0
stroke_type: str = "default"
fill_type: str = "none"
font_size: float = 1.27
pin_name_size: float = 1.27
pin_number_size: float = 1.27
[docs]
@dataclass
class PaperSizeConstants:
"""Standard paper size definitions."""
default: str = "A4"
valid_sizes: List[str] = field(
default_factory=lambda: ["A4", "A3", "A2", "A1", "A0", "Letter", "Legal", "Tabloid"]
)
[docs]
@dataclass
class FieldNames:
"""Common S-expression field names to avoid typos."""
# File structure
version: str = "version"
generator: str = "generator"
generator_version: str = "generator_version"
uuid: str = "uuid"
paper: str = "paper"
# Positioning
at: str = "at"
xy: str = "xy"
pts: str = "pts"
start: str = "start"
end: str = "end"
mid: str = "mid"
center: str = "center"
radius: str = "radius"
# Styling
stroke: str = "stroke"
fill: str = "fill"
width: str = "width"
type: str = "type"
color: str = "color"
# Text/Font
font: str = "font"
size: str = "size"
effects: str = "effects"
# Components
pin: str = "pin"
property: str = "property"
symbol: str = "symbol"
lib_id: str = "lib_id"
# Graphics
polyline: str = "polyline"
arc: str = "arc"
circle: str = "circle"
rectangle: str = "rectangle"
bezier: str = "bezier"
# Connection elements
wire: str = "wire"
junction: str = "junction"
no_connect: str = "no_connect"
label: str = "label"
# Hierarchical
sheet: str = "sheet"
sheet_instances: str = "sheet_instances"
[docs]
class KiCADConfig:
"""Central configuration class for KiCAD schematic API."""
def __init__(self) -> None:
self.properties = PropertyOffsets()
self.grid = GridSettings()
self.sheet = SheetSettings()
self.tolerance = ToleranceSettings()
self.positioning = PositioningSettings()
self.defaults = DefaultValues()
self.file_format = FileFormatConstants()
self.paper = PaperSizeConstants()
self.fields = FieldNames()
# Names that should not generate title_block (for backward compatibility)
# Include test schematic names to maintain reference compatibility
self.no_title_block_names = {
"untitled",
"blank schematic",
"",
"single_resistor",
"two_resistors",
"single_wire",
"single_label",
"single_hierarchical_sheet",
}
[docs]
def should_add_title_block(self, name: str) -> bool:
"""Determine if a schematic name should generate a title block."""
if not name:
return False
return name.lower() not in self.no_title_block_names
[docs]
def get_property_position(
self,
property_name: str,
component_pos: Tuple[float, float],
offset_index: int = 0,
component_rotation: float = 0,
) -> Tuple[float, float, float]:
"""
Calculate property position relative to component, accounting for component rotation.
Args:
property_name: Name of the property (Reference, Value, etc.)
component_pos: (x, y) position of component
offset_index: Stacking offset for multiple properties
component_rotation: Rotation of the component in degrees (0, 90, 180, 270)
Returns:
Tuple of (x, y, rotation) for the property
"""
import math
x, y = component_pos
# Get base offsets (for 0Β° rotation)
if property_name == "Reference":
dx, dy = self.properties.reference_x, self.properties.reference_y
elif property_name == "Value":
dx, dy = self.properties.value_x, self.properties.value_y
elif property_name == "Footprint":
# Footprint positioned to left of component, rotated 90 degrees
return (x - 1.778, y, self.properties.footprint_rotation)
elif property_name in ["Datasheet", "Description"]:
# Hidden properties at component center
return (x, y, 0)
else:
# Other properties stacked vertically below
dx = self.properties.reference_x
dy = self.properties.value_y + (self.properties.hidden_property_offset * offset_index)
# Apply rotation transform to offsets
# Text stays at 0Β° rotation (readable), but position rotates around component
# KiCad uses clockwise rotation, so negate the angle
rotation_rad = math.radians(-component_rotation)
dx_rotated = dx * math.cos(rotation_rad) - dy * math.sin(rotation_rad)
dy_rotated = dx * math.sin(rotation_rad) + dy * math.cos(rotation_rad)
return (x + dx_rotated, y + dy_rotated, 0)
# Global configuration instance
config = KiCADConfig()