Use descriptive names for generated resistor symbols (R0402_4.7K_1%)

Symbols are now named by value+tolerance instead of internal GLE P/N,
e.g. R0402_4.7K_1%, R0402_100_5%, R0402_10.0K_0.1%.

- Add make_symbol_name() to normalise value suffix (k→K) and extract
  tolerance via regex from the Description column
- Deduplicate by sym_name: where two GLE P/Ns share the same
  value+tolerance (alternate approved vendors), keep the first row
  and report the 10 skipped duplicates at runtime
- UMPN field still carries the GLE P/N of the primary vendor

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Brent Perteet
2026-02-20 15:40:44 +00:00
parent 3131535942
commit 278b2db158

View File

@@ -6,13 +6,21 @@ Reads the approved parts list spreadsheet and generates KiCad 9 symbols
for every 0402 resistor into kicad-lib/symbols/Res_0402.kicad_sym. for every 0402 resistor into kicad-lib/symbols/Res_0402.kicad_sym.
The existing R_temp template symbol is kept in the file. One new symbol The existing R_temp template symbol is kept in the file. One new symbol
is added per row, named by the internal part number (GLE P/N), with: is added per unique value+tolerance combination, named descriptively, e.g.:
R0402_4.7K_1%
R0402_100_5%
R0402_10.0K_0.1%
Symbol fields:
Value ← Value1 (e.g. "10k", "4.7k", "100") Value ← Value1 (e.g. "10k", "4.7k", "100")
Description ← Description column Description ← Description column
UMPN ← GLE P/N UMPN ← GLE P/N (internal part number of first approved vendor)
MFG ← Mfg.1 MFG ← Mfg.1
MPN ← Mfg.1 P/N MPN ← Mfg.1 P/N
Where multiple approved vendors share the same value+tolerance, only the
first row is used (the duplicate rows are reported and skipped).
All geometry and pin definitions are copied verbatim from R_temp. All geometry and pin definitions are copied verbatim from R_temp.
Usage: Usage:
@@ -34,6 +42,24 @@ def kicad_str(value: str) -> str:
return str(value).replace('\\', '\\\\').replace('"', '\\"') return str(value).replace('\\', '\\\\').replace('"', '\\"')
def make_symbol_name(value: str, description: str) -> str:
"""
Build a descriptive KiCad symbol name, e.g. 'R0402_4.7K_1%'.
value the Value1 column (e.g. '4.7k', '100', '10.0k')
description the Description column (e.g. 'Resistor, 0402, 1%, 4.7k')
The value suffix (k, m, g) is uppercased. Tolerance is extracted
with a regex that matches patterns like '1%', '0.1%', '5%'.
"""
# Uppercase trailing unit letter(s): 4.7k → 4.7K, 10.0k → 10.0K
value_norm = re.sub(r'([a-zA-Z]+)$', lambda m: m.group(1).upper(), value.strip())
# Extract tolerance from description
tol_match = re.search(r'(\d+(?:\.\d+)?%)', description)
tolerance = tol_match.group(1) if tol_match else 'X'
return f'R0402_{value_norm}_{tolerance}'
def make_symbol(name: str, value: str, description: str, def make_symbol(name: str, value: str, description: str,
umpn: str, mfg: str, mpn: str, umpn: str, mfg: str, mpn: str,
geometry_body: str) -> str: geometry_body: str) -> str:
@@ -206,6 +232,7 @@ def main(xlsx_path: Path, sym_path: Path):
# ---- Build new symbols ---- # ---- Build new symbols ----
new_symbols = [] new_symbols = []
skipped = [] skipped = []
seen_names: dict[str, str] = {} # sym_name → GLE P/N of first occurrence
for _, row in resistors.iterrows(): for _, row in resistors.iterrows():
gle_pn = str(row['GLE P/N']).strip() gle_pn = str(row['GLE P/N']).strip()
@@ -218,16 +245,25 @@ def main(xlsx_path: Path, sym_path: Path):
skipped.append(('(no GLE P/N)', value)) skipped.append(('(no GLE P/N)', value))
continue continue
# Build descriptive symbol name, e.g. "R0402_4.7K_1%"
sym_name = make_symbol_name(value, description)
# Skip duplicate value+tolerance combinations (alternate approved vendors)
if sym_name in seen_names:
skipped.append((sym_name, f'dup of GLE {seen_names[sym_name]} (this: {gle_pn})'))
continue
seen_names[sym_name] = gle_pn
# Substitute R_temp → this symbol's name in the geometry block. # Substitute R_temp → this symbol's name in the geometry block.
# Also re-indent so sub-symbol opening parens align with the template. # Also re-indent so sub-symbol opening parens align with the template.
geom = raw_geom.replace('R_temp_0_1', f'{gle_pn}_0_1') \ geom = raw_geom.replace('R_temp_0_1', f'{sym_name}_0_1') \
.replace('R_temp_1_1', f'{gle_pn}_1_1') .replace('R_temp_1_1', f'{sym_name}_1_1')
# Each sub-symbol block starts at column 0 after extract; add two tabs. # Each sub-symbol block starts at column 0 after extract; add two tabs.
geom = '\n'.join('\t\t' + line if line.startswith('(symbol ') else line geom = '\n'.join('\t\t' + line if line.startswith('(symbol ') else line
for line in geom.splitlines()) for line in geom.splitlines())
sym = make_symbol( sym = make_symbol(
name=gle_pn, name=sym_name,
value=value, value=value,
description=description, description=description,
umpn=gle_pn, umpn=gle_pn,
@@ -237,7 +273,14 @@ def main(xlsx_path: Path, sym_path: Path):
) )
new_symbols.append(sym) new_symbols.append(sym)
print(f"Generated {len(new_symbols)} symbols ({len(skipped)} skipped)") dups = [(n, r) for n, r in skipped if n != '(no GLE P/N)']
no_pn = [(n, r) for n, r in skipped if n == '(no GLE P/N)']
print(f"Generated {len(new_symbols)} symbols "
f"({len(dups)} duplicate value/tol skipped, {len(no_pn)} missing GLE P/N)")
if dups:
print(" Skipped duplicates:")
for name, reason in dups:
print(f" {name}: {reason}")
# ---- Rebuild file from scratch (idempotent) ---- # ---- Rebuild file from scratch (idempotent) ----
# Extract the library header lines (everything before the first symbol) # Extract the library header lines (everything before the first symbol)