434 lines
14 KiB
Python
434 lines
14 KiB
Python
"""
|
|
Utility functions for working with KiCad component values and properties.
|
|
"""
|
|
import re
|
|
from typing import Any, Optional, Tuple, Union, Dict
|
|
|
|
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[Optional[float], Optional[str]]:
|
|
"""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[Optional[float], Optional[str]]:
|
|
"""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[Optional[float], Optional[str]]:
|
|
"""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
|