179 lines
5.9 KiB
Python
179 lines
5.9 KiB
Python
"""Default designator type names (singular/plural) by letter prefix."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
# Minimal useful defaults; projects can override via DB
|
|
DEFAULT_MAPPINGS: dict[str, tuple[str, str]] = {
|
|
"C": ("Конденсатор", "Конденсаторы"),
|
|
"R": ("Резистор", "Резисторы"),
|
|
"L": ("Катушка индуктивности", "Катушки индуктивности"),
|
|
"D": ("Диод", "Диоды"),
|
|
"VD": ("Диод", "Диоды"),
|
|
"VT": ("Транзистор", "Транзисторы"),
|
|
"Q": ("Транзистор", "Транзисторы"),
|
|
"DA": ("Микросхема", "Микросхемы"),
|
|
"DD": ("Микросхема", "Микросхемы"),
|
|
"U": ("Микросхема", "Микросхемы"),
|
|
"X": ("Соединитель", "Соединители"),
|
|
"XP": ("Соединитель", "Соединители"),
|
|
"XS": ("Соединитель", "Соединители"),
|
|
"FU": ("Предохранитель", "Предохранители"),
|
|
"F": ("Предохранитель", "Предохранители"),
|
|
"SA": ("Переключатель", "Переключатели"),
|
|
"SB": ("Кнопка", "Кнопки"),
|
|
"HL": ("Индикатор", "Индикаторы"),
|
|
"HG": ("Индикатор", "Индикаторы"),
|
|
"G": ("Генератор", "Генераторы"),
|
|
"T": ("Трансформатор", "Трансформаторы"),
|
|
"TV": ("Трансформатор", "Трансформаторы"),
|
|
"K": ("Реле", "Реле"),
|
|
"B": ("Пьезоэлемент", "Пьезоэлементы"),
|
|
"Z": ("Фильтр", "Фильтры"),
|
|
}
|
|
|
|
|
|
def split_designator(designator: str) -> tuple[str, str]:
|
|
m = re.match(r"^([A-Za-z]+)(\d*)$", designator.strip())
|
|
if m:
|
|
return m.group(1), m.group(2)
|
|
for i, ch in enumerate(designator):
|
|
if ch.isdigit():
|
|
return designator[:i], designator[i:]
|
|
return designator, ""
|
|
|
|
|
|
def designator_sort_key(designator: str) -> tuple[str, int, str]:
|
|
letter, num = split_designator(designator)
|
|
try:
|
|
n = int(num) if num else 0
|
|
except ValueError:
|
|
n = 0
|
|
return letter, n, num
|
|
return letter.upper(), n, num
|
|
|
|
|
|
def prefix_from_designator(designator: str) -> str:
|
|
letter, _ = split_designator(designator)
|
|
return letter.upper()
|
|
|
|
|
|
def get_type_names(
|
|
designator: str, mappings: dict[str, tuple[str, str]] | None = None
|
|
) -> tuple[str, str]:
|
|
maps = {**DEFAULT_MAPPINGS, **(mappings or {})}
|
|
prefix = prefix_from_designator(designator)
|
|
if prefix in maps:
|
|
return maps[prefix]
|
|
# try 2-letter then 1-letter
|
|
if len(prefix) >= 2 and prefix[:2] in maps:
|
|
return maps[prefix[:2]]
|
|
if prefix[:1] in maps:
|
|
return maps[prefix[:1]]
|
|
return (prefix, prefix)
|
|
|
|
|
|
def format_designator_range(designators: list[str]) -> str:
|
|
if not designators:
|
|
return ""
|
|
if len(designators) == 1:
|
|
return designators[0]
|
|
if len(designators) == 2:
|
|
return f"{designators[0]}, {designators[1]}"
|
|
return f"{designators[0]}-{designators[-1]}"
|
|
|
|
|
|
def format_consecutive_ranges(designators: list[str]) -> str:
|
|
"""Group consecutive numbered designators into ranges."""
|
|
if not designators:
|
|
return ""
|
|
parsed = []
|
|
for d in designators:
|
|
letter, num = split_designator(d)
|
|
try:
|
|
n = int(num) if num else None
|
|
except ValueError:
|
|
n = None
|
|
parsed.append((d, letter, n))
|
|
|
|
segments: list[list[str]] = []
|
|
current: list[str] = []
|
|
last_letter = None
|
|
last_num = None
|
|
for d, letter, n in parsed:
|
|
if not current:
|
|
current = [d]
|
|
last_letter, last_num = letter, n
|
|
continue
|
|
if letter == last_letter and n is not None and last_num is not None and n == last_num + 1:
|
|
current.append(d)
|
|
last_num = n
|
|
else:
|
|
segments.append(current)
|
|
current = [d]
|
|
last_letter, last_num = letter, n
|
|
if current:
|
|
segments.append(current)
|
|
|
|
parts = []
|
|
for seg in segments:
|
|
if len(seg) == 1:
|
|
parts.append(seg[0])
|
|
elif len(seg) == 2:
|
|
parts.append(f"{seg[0]}, {seg[1]}")
|
|
else:
|
|
parts.append(f"{seg[0]}-{seg[-1]}")
|
|
return ", ".join(parts)
|
|
|
|
|
|
def split_long_text(text: str, max_length: int) -> list[str]:
|
|
if not text or len(text) <= max_length:
|
|
return [text] if text is not None else [""]
|
|
parts: list[str] = []
|
|
remaining = text
|
|
while len(remaining) > max_length:
|
|
chunk = remaining[:max_length]
|
|
split_at = chunk.rfind(",")
|
|
if split_at < max_length // 3:
|
|
split_at = chunk.rfind(" ")
|
|
if split_at < max_length // 3:
|
|
split_at = max_length
|
|
parts.append(remaining[:split_at].rstrip(", ").strip())
|
|
remaining = remaining[split_at:].lstrip(", ").strip()
|
|
if remaining:
|
|
parts.append(remaining)
|
|
return parts or [""]
|
|
|
|
|
|
def resolve_expression(value: str, props: dict[str, str]) -> str:
|
|
if not value.startswith("="):
|
|
return value
|
|
expr = value[1:]
|
|
result = []
|
|
i = 0
|
|
while i < len(expr):
|
|
ch = expr[i]
|
|
if ch in "'\"":
|
|
quote = ch
|
|
i += 1
|
|
start = i
|
|
while i < len(expr) and expr[i] != quote:
|
|
i += 1
|
|
result.append(expr[start:i])
|
|
i += 1
|
|
elif ch == "+":
|
|
i += 1
|
|
elif ch.isspace():
|
|
i += 1
|
|
else:
|
|
start = i
|
|
while i < len(expr) and expr[i] not in "+'\"":
|
|
i += 1
|
|
key = expr[start:i].strip()
|
|
val = props.get(key, "")
|
|
if val.startswith("="):
|
|
val = resolve_expression(val, props)
|
|
result.append(val)
|
|
return "".join(result)
|