Initial move to app, scanning and reading basic part info works, updating info starting to work

This commit is contained in:
2025-10-02 22:45:58 +10:00
commit aaa1f7520a
15 changed files with 2241 additions and 0 deletions

48
parsers/digikey_mh10.py Normal file
View File

@@ -0,0 +1,48 @@
import re
from typing import Dict, List, Tuple
NEXT_TAG_RE = re.compile(r'(PICK|K14|30P|1P|K10|K1|D|T|Z)')
def tokenize(flat: str) -> List[Tuple[str, str]]:
tokens: List[Tuple[str, str]] = []
s = flat
p_idx = s.find('P')
if p_idx == -1: return tokens
start_val = p_idx + 1
m = NEXT_TAG_RE.search(s, start_val)
if m:
tokens.append(('P', s[start_val:m.start()].strip()))
else:
tokens.append(('P', s[start_val:].strip()))
return tokens
while m:
tag = m.group(1)
start_val = m.end()
m_next = NEXT_TAG_RE.search(s, start_val)
if m_next:
tokens.append((tag, s[start_val:m_next.start()].strip()))
m = m_next
else:
tokens.append((tag, s[start_val:].strip()))
break
return tokens
def _first(tokens: List[Tuple[str, str]], tag: str) -> str:
for t, v in tokens:
if t == tag:
return v
return ""
def parse_digikey(flat: str) -> Dict[str, str]:
tokens = tokenize(flat)
return {
'DigiKeyPart' : _first(tokens, 'P'),
'MfrPart' : _first(tokens, '1P'),
'CustomerPart' : _first(tokens, '30P'),
'InternalLot1' : _first(tokens, 'K1'),
'InternalLot2' : _first(tokens, 'K10'),
'DateCodeRaw' : _first(tokens, 'D'),
'TraceID' : _first(tokens, 'T'),
'PackageSerial': _first(tokens, 'K14'),
'PickTicket' : _first(tokens, 'PICK'),
}

119
parsers/values.py Normal file
View File

@@ -0,0 +1,119 @@
import re, math
from typing import Optional, List
RE_RES_SIMPLE = re.compile(r"(?i)^\s*(\d+(?:\.\d+)?)\s*(ohm|ohms|r|k|m|kohm|mohm|kΩ|mΩ|kOhm|MOhm)?\s*$")
RE_RES_LETTER = re.compile(r"(?i)^\s*(\d+)([rkm])(\d+)?\s*$")
RE_CAP_SIMPLE = re.compile(r"(?i)^(\d+(?:\.\d+)?)(p|n|u|m|pf|nf|uf|mf|f)?$")
RE_CAP_LETTER = re.compile(r"(?i)^(\d+)([pnu])(\d+)?$")
def round_sig(x: float, sig: int) -> float:
if x == 0: return 0.0
return round(x, sig - 1 - int(math.floor(math.log10(abs(x)))))
def e_series_values(E: int, rmin=1.0, rmax=1e7, sig_digits: Optional[int]=None) -> List[float]:
if sig_digits is None: sig_digits = 2 if E <= 24 else 3
base: List[float] = []
for i in range(E):
v = round_sig(10 ** (i / E), sig_digits)
if v not in base:
base.append(v)
out = set()
min_dec = int(math.floor(math.log10(rmin)))
max_dec = int(math.ceil(math.log10(rmax)))
for d in range(min_dec - 1, max_dec + 1):
scale = 10 ** d
for b in base:
val = round_sig(b * scale, sig_digits)
if rmin <= val <= rmax:
out.add(val)
return sorted(out)
def value_to_code(ohms: float) -> str:
if ohms == 0: return "0R"
if ohms < 1e3:
unit, n = "R", ohms
elif ohms < 1e6:
unit, n = "K", ohms / 1e3
else:
unit, n = "M", ohms / 1e6
s = f"{n:.3g}"
if "e" in s or "E" in s:
dec = max(0, 3 - 1 - int(math.floor(math.log10(abs(n)))))
s = f"{n:.{dec}f}".rstrip("0").rstrip(".")
return s.replace(".", unit) if "." in s else s + unit
def parse_resistance_to_ohms(value: str, unit: Optional[str]) -> Optional[float]:
if value is None: return None
s = str(value).strip().replace(" ", "").replace("Ω","ohm").replace("","ohm")
m = RE_RES_SIMPLE.fullmatch(s)
if m and unit is None:
num = float(m.group(1)); u = (m.group(2) or "").lower()
table = {"ohm":1.0, "ohms":1.0, "r":1.0, "k":1e3, "kohm":1e3, "":1e3, "m":1e6, "mohm":1e6, "":1e6}
return num * table.get(u, 1.0)
m = RE_RES_LETTER.fullmatch(s)
if m and unit is None:
lead = int(m.group(1)); letter = m.group(2).lower(); tail = m.group(3) or ""
frac = float(f"0.{tail}") if tail else 0.0
mul = {"r":1.0, "k":1e3, "m":1e6}[letter]
return (lead + frac) * mul
try:
num = float(s)
if unit is None: return num
u = str(unit).strip().lower()
table = {"ohm":1.0, "ohms":1.0, "r":1.0, "k":1e3, "kohm":1e3, "m":1e6, "mohm":1e6}
mul = table.get(u);
return num * mul if mul else None
except ValueError:
return None
def format_ohms_for_eda(ohms: float) -> str:
if ohms == 0: return "0"
if ohms < 1_000:
v = float(ohms);
if v.is_integer(): return f"{int(v)}R"
whole = int(v); frac = int(round((v - whole) * 10))
return f"{whole}R{frac}" if frac else f"{whole}R"
if ohms < 1_000_000:
v = float(ohms) / 1e3
if v.is_integer(): return f"{int(v)}K"
whole = int(v); frac = int(round((v - whole) * 10))
return f"{whole}K{frac}" if frac else f"{whole}K"
v = float(ohms) / 1e6
if v.is_integer(): return f"{int(v)}M"
whole = int(v); frac = int(round((v - whole) * 10))
return f"{whole}M{frac}" if frac else f"{whole}M"
def parse_capacitance_to_farads(value: str, unit: Optional[str]) -> Optional[float]:
if value is None: return None
s = str(value).strip().replace(" ", "").replace("µ","u").replace("μ","u")
m = RE_CAP_SIMPLE.fullmatch(s)
if m and unit is None:
num = float(m.group(1)); su = (m.group(2) or "f").lower()
mul = {"f":1.0, "pf":1e-12, "p":1e-12, "nf":1e-9, "n":1e-9, "uf":1e-6, "u":1e-6, "mf":1e-3, "m":1e-3}.get(su,1.0)
return num * mul
m = RE_CAP_LETTER.fullmatch(s)
if m and unit is None:
lead = int(m.group(1)); letter = m.group(2).lower(); tail = m.group(3) or ""
frac = float(f"0.{tail}") if tail else 0.0
mul = {"p":1e-12,"n":1e-9,"u":1e-6}[letter]
return (lead + frac) * mul
try:
num = float(s)
if unit is None: return num
u = str(unit).strip().lower().replace("µ","u")
table = {"f":1.0, "farad":1.0, "pf":1e-12, "picofarad":1e-12, "nf":1e-9, "nanofarad":1e-9, "uf":1e-6, "microfarad":1e-6}
return num * table.get(u, 1.0)
except ValueError:
return None
def format_farads_for_eda(F: float) -> str:
if F >= 1e-6:
n = F * 1e6; unit = "u"
elif F >= 1e-9:
n = F * 1e9; unit = "n"
else:
n = F * 1e12; unit = "p"
if abs(n - round(n)) < 1e-6:
return f"{int(round(n))}{unit}"
n1 = round(n, 1); whole = int(n1); frac = int(round((n1 - whole) * 10))
return f"{whole}{unit}" if frac == 0 else f"{whole}{unit}{frac}"