""" Utility functions for working with KiCad component values and properties. """ from enum import Enum import re from typing import Any class ComponentType(Enum): """Enumeration of electronic component types.""" RESISTOR = "resistor" CAPACITOR = "capacitor" INDUCTOR = "inductor" DIODE = "diode" TRANSISTOR = "transistor" IC = "integrated_circuit" CONNECTOR = "connector" CRYSTAL = "crystal" VOLTAGE_REGULATOR = "voltage_regulator" FUSE = "fuse" SWITCH = "switch" RELAY = "relay" TRANSFORMER = "transformer" LED = "led" UNKNOWN = "unknown" def extract_voltage_from_regulator(value: str) -> str: """Extract output voltage from a voltage regulator part number or description. Args: value: Regulator part number or description Returns: Extracted voltage as a string or "unknown" if not found """ # Common patterns: # 78xx/79xx series: 7805 = 5V, 7812 = 12V # LDOs often have voltage in the part number, like LM1117-3.3 # 78xx/79xx series match = re.search(r"78(\d\d)|79(\d\d)", value, re.IGNORECASE) if match: group = match.group(1) or match.group(2) # Convert code to voltage (e.g., 05 -> 5V, 12 -> 12V) try: voltage = int(group) # For 78xx series, voltage code is directly in volts if voltage < 50: # Sanity check to prevent weird values return f"{voltage}V" except ValueError: pass # Look for common voltage indicators in the string voltage_patterns = [ r"(\d+\.?\d*)V", # 3.3V, 5V, etc. r"-(\d+\.?\d*)V", # -5V, -12V, etc. (for negative regulators) r"(\d+\.?\d*)[_-]?V", # 3.3_V, 5-V, etc. r"[_-](\d+\.?\d*)", # LM1117-3.3, LD1117-3.3, etc. ] for pattern in voltage_patterns: match = re.search(pattern, value, re.IGNORECASE) if match: try: voltage = float(match.group(1)) if 0 < voltage < 50: # Sanity check # Format as integer if it's a whole number if voltage.is_integer(): return f"{int(voltage)}V" else: return f"{voltage}V" except ValueError: pass # Check for common fixed voltage regulators regulators = { "LM7805": "5V", "LM7809": "9V", "LM7812": "12V", "LM7905": "-5V", "LM7912": "-12V", "LM1117-3.3": "3.3V", "LM1117-5": "5V", "LM317": "Adjustable", "LM337": "Adjustable (Negative)", "AP1117-3.3": "3.3V", "AMS1117-3.3": "3.3V", "L7805": "5V", "L7812": "12V", "MCP1700-3.3": "3.3V", "MCP1700-5.0": "5V", } for reg, volt in regulators.items(): if re.search(re.escape(reg), value, re.IGNORECASE): return volt return "unknown" def extract_frequency_from_value(value: str) -> str: """Extract frequency information from a component value or description. Args: value: Component value or description (e.g., "16MHz", "Crystal 8MHz") Returns: Frequency as a string or "unknown" if not found """ # Common frequency patterns with various units frequency_patterns = [ r"(\d+\.?\d*)[\s-]*([kKmMgG]?)[hH][zZ]", # 16MHz, 32.768 kHz, etc. r"(\d+\.?\d*)[\s-]*([kKmMgG])", # 16M, 32.768k, etc. ] for pattern in frequency_patterns: match = re.search(pattern, value, re.IGNORECASE) if match: try: freq = float(match.group(1)) unit = match.group(2).upper() if match.group(2) else "" # Make sure the frequency is in a reasonable range if freq > 0: # Format the output if unit == "K": if freq >= 1000: return f"{freq / 1000:.3f}MHz" else: return f"{freq:.3f}kHz" elif unit == "M": if freq >= 1000: return f"{freq / 1000:.3f}GHz" else: return f"{freq:.3f}MHz" elif unit == "G": return f"{freq:.3f}GHz" else: # No unit, need to determine based on value if freq < 1000: return f"{freq:.3f}Hz" elif freq < 1000000: return f"{freq / 1000:.3f}kHz" elif freq < 1000000000: return f"{freq / 1000000:.3f}MHz" else: return f"{freq / 1000000000:.3f}GHz" except ValueError: pass # Check for common crystal frequencies if "32.768" in value or "32768" in value: return "32.768kHz" # Common RTC crystal elif "16M" in value or "16MHZ" in value.upper(): return "16MHz" # Common MCU crystal elif "8M" in value or "8MHZ" in value.upper(): return "8MHz" elif "20M" in value or "20MHZ" in value.upper(): return "20MHz" elif "27M" in value or "27MHZ" in value.upper(): return "27MHz" elif "25M" in value or "25MHZ" in value.upper(): return "25MHz" return "unknown" def extract_resistance_value(value: str) -> tuple[float | None, str | None]: """Extract resistance value and unit from component value. Args: value: Resistance value (e.g., "10k", "4.7k", "100") Returns: Tuple of (numeric value, unit) or (None, None) if parsing fails """ # Common resistance patterns # 10k, 4.7k, 100R, 1M, 10, etc. match = re.search(r"(\d+\.?\d*)([kKmMrRΩ]?)", value) if match: try: resistance = float(match.group(1)) unit = match.group(2).upper() if match.group(2) else "Ω" # Normalize unit if unit == "R" or unit == "": unit = "Ω" return resistance, unit except ValueError: pass # Handle special case like "4k7" (means 4.7k) match = re.search(r"(\d+)[kKmM](\d+)", value) if match: try: value1 = int(match.group(1)) value2 = int(match.group(2)) resistance = float(f"{value1}.{value2}") unit = "k" if "k" in value.lower() else "M" if "m" in value.lower() else "Ω" return resistance, unit except ValueError: pass return None, None def extract_capacitance_value(value: str) -> tuple[float | None, str | None]: """Extract capacitance value and unit from component value. Args: value: Capacitance value (e.g., "10uF", "4.7nF", "100pF") Returns: Tuple of (numeric value, unit) or (None, None) if parsing fails """ # Common capacitance patterns # 10uF, 4.7nF, 100pF, etc. match = re.search(r"(\d+\.?\d*)([pPnNuUμF]+)", value) if match: try: capacitance = float(match.group(1)) unit = match.group(2).lower() # Normalize unit if "p" in unit or "pf" in unit: unit = "pF" elif "n" in unit or "nf" in unit: unit = "nF" elif "u" in unit or "μ" in unit or "uf" in unit or "μf" in unit: unit = "μF" else: unit = "F" return capacitance, unit except ValueError: pass # Handle special case like "4n7" (means 4.7nF) match = re.search(r"(\d+)[pPnNuUμ](\d+)", value) if match: try: value1 = int(match.group(1)) value2 = int(match.group(2)) capacitance = float(f"{value1}.{value2}") if "p" in value.lower(): unit = "pF" elif "n" in value.lower(): unit = "nF" elif "u" in value.lower() or "μ" in value: unit = "μF" else: unit = "F" return capacitance, unit except ValueError: pass return None, None def extract_inductance_value(value: str) -> tuple[float | None, str | None]: """Extract inductance value and unit from component value. Args: value: Inductance value (e.g., "10uH", "4.7nH", "100mH") Returns: Tuple of (numeric value, unit) or (None, None) if parsing fails """ # Common inductance patterns # 10uH, 4.7nH, 100mH, etc. match = re.search(r"(\d+\.?\d*)([pPnNuUμmM][hH])", value) if match: try: inductance = float(match.group(1)) unit = match.group(2).lower() # Normalize unit if "p" in unit: unit = "pH" elif "n" in unit: unit = "nH" elif "u" in unit or "μ" in unit: unit = "μH" elif "m" in unit: unit = "mH" else: unit = "H" return inductance, unit except ValueError: pass # Handle special case like "4u7" (means 4.7uH) match = re.search(r"(\d+)[pPnNuUμmM](\d+)[hH]", value) if match: try: value1 = int(match.group(1)) value2 = int(match.group(2)) inductance = float(f"{value1}.{value2}") if "p" in value.lower(): unit = "pH" elif "n" in value.lower(): unit = "nH" elif "u" in value.lower() or "μ" in value: unit = "μH" elif "m" in value.lower(): unit = "mH" else: unit = "H" return inductance, unit except ValueError: pass return None, None def format_resistance(resistance: float, unit: str) -> str: """Format resistance value with appropriate unit. Args: resistance: Resistance value unit: Unit string (Ω, k, M) Returns: Formatted resistance string """ if unit == "Ω": return f"{resistance:.0f}Ω" if resistance.is_integer() else f"{resistance}Ω" elif unit == "k": return f"{resistance:.0f}kΩ" if resistance.is_integer() else f"{resistance}kΩ" elif unit == "M": return f"{resistance:.0f}MΩ" if resistance.is_integer() else f"{resistance}MΩ" else: return f"{resistance}{unit}" def format_capacitance(capacitance: float, unit: str) -> str: """Format capacitance value with appropriate unit. Args: capacitance: Capacitance value unit: Unit string (pF, nF, μF, F) Returns: Formatted capacitance string """ if capacitance.is_integer(): return f"{int(capacitance)}{unit}" else: return f"{capacitance}{unit}" def format_inductance(inductance: float, unit: str) -> str: """Format inductance value with appropriate unit. Args: inductance: Inductance value unit: Unit string (pH, nH, μH, mH, H) Returns: Formatted inductance string """ if inductance.is_integer(): return f"{int(inductance)}{unit}" else: return f"{inductance}{unit}" def normalize_component_value(value: str, component_type: str) -> str: """Normalize a component value string based on component type. Args: value: Raw component value string component_type: Type of component (R, C, L, etc.) Returns: Normalized value string """ if component_type == "R": resistance, unit = extract_resistance_value(value) if resistance is not None and unit is not None: return format_resistance(resistance, unit) elif component_type == "C": capacitance, unit = extract_capacitance_value(value) if capacitance is not None and unit is not None: return format_capacitance(capacitance, unit) elif component_type == "L": inductance, unit = extract_inductance_value(value) if inductance is not None and unit is not None: return format_inductance(inductance, unit) # For other component types or if parsing fails, return the original value return value def get_component_type_from_reference(reference: str) -> str: """Determine component type from reference designator. Args: reference: Component reference (e.g., R1, C2, U3) Returns: Component type letter (R, C, L, Q, etc.) """ # Extract the alphabetic prefix (component type) match = re.match(r"^([A-Za-z_]+)", reference) if match: return match.group(1) return "" def is_power_component(component: dict[str, Any]) -> bool: """Check if a component is likely a power-related component. Args: component: Component information dictionary Returns: True if the component is power-related, False otherwise """ ref = component.get("reference", "") value = component.get("value", "").upper() lib_id = component.get("lib_id", "").upper() # Check reference designator if ref.startswith(("VR", "PS", "REG")): return True # Check for power-related terms in value or library ID power_terms = ["VCC", "VDD", "GND", "POWER", "PWR", "SUPPLY", "REGULATOR", "LDO"] if any(term in value or term in lib_id for term in power_terms): return True # Check for regulator part numbers regulator_patterns = [ r"78\d\d", # 7805, 7812, etc. r"79\d\d", # 7905, 7912, etc. r"LM\d{3}", # LM317, LM337, etc. r"LM\d{4}", # LM1117, etc. r"AMS\d{4}", # AMS1117, etc. r"MCP\d{4}", # MCP1700, etc. ] if any(re.search(pattern, value, re.IGNORECASE) for pattern in regulator_patterns): return True # Not identified as a power component return False def get_component_type(value: str) -> ComponentType: """Determine component type from value string. Args: value: Component value or part number Returns: ComponentType enum value """ value_lower = value.lower() # Check for resistor patterns if (re.search(r'\d+[kmgr]?ω|ω', value_lower) or re.search(r'\d+[kmgr]?ohm', value_lower) or re.search(r'resistor', value_lower)): return ComponentType.RESISTOR # Check for capacitor patterns if (re.search(r'\d+[pnumkμ]?f', value_lower) or re.search(r'capacitor|cap', value_lower)): return ComponentType.CAPACITOR # Check for inductor patterns if (re.search(r'\d+[pnumkμ]?h', value_lower) or re.search(r'inductor|coil', value_lower)): return ComponentType.INDUCTOR # Check for diode patterns if ('diode' in value_lower or 'led' in value_lower or value_lower.startswith(('1n', 'bar', 'ss'))): if 'led' in value_lower: return ComponentType.LED return ComponentType.DIODE # Check for transistor patterns if (re.search(r'transistor|mosfet|bjt|fet', value_lower) or value_lower.startswith(('2n', 'bc', 'tip', 'irf', 'fqp'))): return ComponentType.TRANSISTOR # Check for IC patterns if (re.search(r'ic|chip|processor|mcu|cpu', value_lower) or value_lower.startswith(('lm', 'tlv', 'op', 'ad', 'max', 'lt'))): return ComponentType.IC # Check for voltage regulator patterns if (re.search(r'regulator|ldo', value_lower) or re.search(r'78\d\d|79\d\d|lm317|ams1117', value_lower)): return ComponentType.VOLTAGE_REGULATOR # Check for connector patterns if re.search(r'connector|conn|jack|plug|header', value_lower): return ComponentType.CONNECTOR # Check for crystal patterns if re.search(r'crystal|xtal|oscillator|mhz|khz', value_lower): return ComponentType.CRYSTAL # Check for fuse patterns if re.search(r'fuse|ptc', value_lower): return ComponentType.FUSE # Check for switch patterns if re.search(r'switch|button|sw', value_lower): return ComponentType.SWITCH # Check for relay patterns if re.search(r'relay', value_lower): return ComponentType.RELAY # Check for transformer patterns if re.search(r'transformer|trans', value_lower): return ComponentType.TRANSFORMER return ComponentType.UNKNOWN def get_standard_values(component_type: ComponentType) -> list[str]: """Get standard component values for a given component type. Args: component_type: Type of component Returns: List of standard values as strings """ if component_type == ComponentType.RESISTOR: return [ "1Ω", "1.2Ω", "1.5Ω", "1.8Ω", "2.2Ω", "2.7Ω", "3.3Ω", "3.9Ω", "4.7Ω", "5.6Ω", "6.8Ω", "8.2Ω", "10Ω", "12Ω", "15Ω", "18Ω", "22Ω", "27Ω", "33Ω", "39Ω", "47Ω", "56Ω", "68Ω", "82Ω", "100Ω", "120Ω", "150Ω", "180Ω", "220Ω", "270Ω", "330Ω", "390Ω", "470Ω", "560Ω", "680Ω", "820Ω", "1kΩ", "1.2kΩ", "1.5kΩ", "1.8kΩ", "2.2kΩ", "2.7kΩ", "3.3kΩ", "3.9kΩ", "4.7kΩ", "5.6kΩ", "6.8kΩ", "8.2kΩ", "10kΩ", "12kΩ", "15kΩ", "18kΩ", "22kΩ", "27kΩ", "33kΩ", "39kΩ", "47kΩ", "56kΩ", "68kΩ", "82kΩ", "100kΩ", "120kΩ", "150kΩ", "180kΩ", "220kΩ", "270kΩ", "330kΩ", "390kΩ", "470kΩ", "560kΩ", "680kΩ", "820kΩ", "1MΩ", "1.2MΩ", "1.5MΩ", "1.8MΩ", "2.2MΩ", "2.7MΩ", "3.3MΩ", "3.9MΩ", "4.7MΩ", "5.6MΩ", "6.8MΩ", "8.2MΩ", "10MΩ" ] elif component_type == ComponentType.CAPACITOR: return [ "1pF", "1.5pF", "2.2pF", "3.3pF", "4.7pF", "6.8pF", "10pF", "15pF", "22pF", "33pF", "47pF", "68pF", "100pF", "150pF", "220pF", "330pF", "470pF", "680pF", "1nF", "1.5nF", "2.2nF", "3.3nF", "4.7nF", "6.8nF", "10nF", "15nF", "22nF", "33nF", "47nF", "68nF", "100nF", "150nF", "220nF", "330nF", "470nF", "680nF", "1μF", "1.5μF", "2.2μF", "3.3μF", "4.7μF", "6.8μF", "10μF", "15μF", "22μF", "33μF", "47μF", "68μF", "100μF", "150μF", "220μF", "330μF", "470μF", "680μF", "1000μF", "1500μF", "2200μF", "3300μF", "4700μF", "6800μF", "10000μF" ] elif component_type == ComponentType.INDUCTOR: return [ "1nH", "1.5nH", "2.2nH", "3.3nH", "4.7nH", "6.8nH", "10nH", "15nH", "22nH", "33nH", "47nH", "68nH", "100nH", "150nH", "220nH", "330nH", "470nH", "680nH", "1μH", "1.5μH", "2.2μH", "3.3μH", "4.7μH", "6.8μH", "10μH", "15μH", "22μH", "33μH", "47μH", "68μH", "100μH", "150μH", "220μH", "330μH", "470μH", "680μH", "1mH", "1.5mH", "2.2mH", "3.3mH", "4.7mH", "6.8mH", "10mH", "15mH", "22mH", "33mH", "47mH", "68mH", "100mH", "150mH", "220mH", "330mH", "470mH", "680mH" ] elif component_type == ComponentType.CRYSTAL: return [ "32.768kHz", "1MHz", "2MHz", "4MHz", "8MHz", "10MHz", "12MHz", "16MHz", "20MHz", "24MHz", "25MHz", "27MHz" ] else: return []