kicad/generate.py

246 lines
9.4 KiB
Python

import time
import zipfile
from datetime import datetime
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Optional, Union
import numpy as np
import regex as re
import requests
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import By
from selenium.webdriver.support.wait import WebDriverWait
dir_ = Path(__file__).parent
rng = np.random.default_rng()
PARTS_SAMTEC = [
# Pin Headers
*[f"TSW-1{i:02d}-07-L-S" for i in range(1, 51)],
*[f"TSW-1{i:02d}-07-L-D" for i in range(1, 51)],
# *[f"TSW-1{i:02d}-07-L-T" for i in range(1, 51)],
# *[f"TSW-1{i:02d}-07-L-Q" for i in range(1, 51)],
*[f"TSW-2{i:02d}-07-L-S" for i in range(2, 26)],
*[f"TSW-2{i:02d}-07-L-D" for i in range(2, 26)],
# *[f"TSW-2{i:02d}-07-L-T" for i in range(2, 26)],
# *[f"TSW-2{i:02d}-07-L-Q" for i in range(2, 26)],
# Sockets
# SSW
# SMH
# Discrete Wire
# TFM
# SFM
# High Speed Board to Board
# *np.flatten(
# [[f"LSHM-1{i:02d}-{h:02.1f}-L-DV-A-S-TR" for i in [5, 10, 20, 30, 40, 50]] for h in [2.5, 3.0, 4.0, 6.0]]
# ),
]
PARTS_MOLEX = [
# Micro-Fit 3.0 Single Row Headers
*[f"43650-{i:02d}10" for i in range(2, 13)], # 1 row, RA, press-fit retention clip
*[f"43650-{i:02d}13" for i in range(2, 13)], # 1 row, RA, solder tab
*[f"43650-{i:02d}22" for i in range(2, 13)], # 1 row, Vertical, press-fit retention clip
*[f"43650-{i:02d}25" for i in range(2, 13)], # 1 row, RA, solder tab
# Micro-Fit 3.0 Dual Row Headers
*[f"43045-{i:02d}07" for i in range(2, 25, 2)], # 2 row, RA, press-fit retention clip
*[f"43045-{i:02d}10" for i in range(2, 25, 2)], # 2 row, RA, solder tab
*[f"43045-{i:02d}16" for i in range(2, 25, 2)], # 2 row, Vertical, press-fit retention clip
*[f"43045-{i:02d}19" for i in range(2, 25, 2)], # 2 row, RA, solder tab
# Eurostyle 5.08mm Headers
# "39531-0002",
# Eurostyle 5.08mm Plugs
# "39530-0002",
]
def download_step_samtec(part: str) -> None:
print(part)
with TemporaryDirectory(prefix=str(dir_ / "tmp") + "/") as tmp:
tmp = Path(tmp)
chrome_options = webdriver.ChromeOptions()
chrome_options.add_experimental_option(
"prefs",
{"download.default_directory": str(tmp)},
)
chrome_options.add_argument("--headless")
with webdriver.Chrome(
options=chrome_options,
) as browser:
browser.get(f"https://www.snapeda.com/parts/{part.upper()}/Samtec/embed/?ref=samtec")
wait = WebDriverWait(browser, 30)
wait.until(
EC.element_to_be_clickable(browser.find_element(by=By.ID, value="download_traceparts_3d_model"))
).click()
wait.until(
EC.element_to_be_clickable(
browser.find_element(
by=By.ID,
value="samtec-checkbox-3d-modal-download-individual-btn",
)
)
).click()
# wait for download to finish
while len(list((Path(tmp).glob("*.zip")))) == 0:
time.sleep(0.1)
# print(list((Path(tmp).glob("*.zip"))))
# unzip
with zipfile.ZipFile(list((Path(tmp).glob("*.zip")))[0]) as zip:
zip.extractall(path=str(tmp))
# move
filename = f"{part.upper()}.stp"
# if filename not in list(Path(tmp).glob("*.stp")):
# raise ValueError(
# f"Name mismatch: {filename} does not exist. Found {[f.name for f in list(Path(tmp).glob('*.stp'))]}"
# )
(Path(tmp) / filename).rename(dir_ / "samtec.pretty" / "3dshapes" / filename)
def download_step_molex(part: Union[str, int]) -> None:
# remove dash
part = f'{int(str(part).replace("-", "")):09d}'
print(part)
with TemporaryDirectory(prefix=str(dir_ / "tmp") + "/") as tmp:
tmp = Path(tmp)
for p in [part, part[:5] + "-" + part[5:]]:
try:
zipname = f"{part}_stp.zip"
# download
with open(tmp / zipname, "wb") as f:
f.write(requests.get(f"https://www.molex.com/pdm_docs/stp/{p}_stp.zip").content)
# unzip
with zipfile.ZipFile(tmp / zipname) as zip:
zip.extractall(path=str(tmp))
# move
filename = f"{part}.stp"
(Path(tmp) / filename).rename(dir_ / "molex.pretty" / "3dshapes" / filename)
return
except zipfile.BadZipFile:
print(f"zip does not exist for part {p}")
raise ValueError(f"Could not download .stp for part {part}")
now = datetime.now()
INCH = 25.4
SILK_WIDTH = 0.007 * INCH
def footprint_samtec(part: str, lib: Optional[Union[Path, str]] = None) -> str:
if any([part.startswith(s) for s in ["TSW-", "HTSW-"]]):
# pinheader
match = re.match(
r"(H?)TSW-([12])(\d\d)-(\d\d)-([FLGT])-([SDTQ])(-R[AE])?(-NA)?(-LL)?(-LC)?(-LA)?(-(\d\d\d))?",
part.upper(),
)
high_temp = match[1] != ""
spacing = int(match[2])
rows = int(match[3])
# lead_style = {
# 5:
# }[int(match[4])]
lead_style = "vertical"
plating = {
"F": "Gold flash on post, Matte Tin on tail",
"L": '10 µ" (0.25 µm) Gold on post, Matte Tin on tail',
"G": '10 µ" (0.25 µm) Gold on post, Gold flash on balance',
"T": "Matte Tin",
}[match[5]]
cols = {"S": 1, "D": 2, "T": 3, "Q": 4}[match[6]]
strobe = [(2 if cols == 4 else 1) * 0.1 * INCH, spacing * 0.1 * INCH]
if cols == 4:
# only outer columns filled
cols = 2
fp_name = f"PinHeader_{cols}x{rows:02d}_0.1in_{part}"
description = (
f"Header, Male, {cols}x{rows:02d}, {lead_style}, {plating}{', high temperature' if high_temp else ''}"
)
keywords = ["header", "pin"]
hole_diam = 0.040 * INCH
pad_size = 0.075 * INCH
center = [strobe[0] * (cols - 1) / 2, strobe[1] * (rows - 1) / 2]
if lib is None:
lib = dir_ / "samtec.pretty"
lib = Path(lib)
with open(lib / f"{fp_name}.kicad_mod", "w") as f:
f.write(
"\n".join(
(
f'(footprint "{fp_name}" (version {now.strftime("%Y%m%d")}) (generator pcbnew) (layer "F.Cu")',
r" (attr through_hole)",
f' (descr "{description}")',
f' (tags "{" ".join(keywords)}")',
f' (fp_text reference "REF**" (at {center[0]} {-2.032}) (layer "F.SilkS")',
r" (effects (font (size 0.762 0.762) (thickness 0.127)))",
r" )",
f' (fp_text value "VAL**" (at {center[0]} {center[1]} 90) (layer "F.Fab") hide',
r" (effects (font (size 0.762 0.762) (thickness 0.127)))",
r" )",
r' (fp_text user "${REFERENCE}" ' f'(at {center[0]} {center[1]} 90) (layer "F.Fab")',
r" (effects (font (size 1 1) (thickness 0.15)))",
r" )",
f" (fp_rect (start {-0.05 * INCH} {-0.05 * INCH}) "
f"(end {0.05 * INCH + strobe[0] * (cols - 1)} {0.05 * INCH + strobe[1] * (rows - 1)})",
f' (stroke (width {SILK_WIDTH}) (type default)) (fill none) (layer "F.SilkS")' r" )",
f" (fp_line (start {-0.05 * INCH} {0.05 * INCH}) (end {0.05 * INCH} {0.05 * INCH}) "
f'(layer "F.SilkS") (width {SILK_WIDTH}))',
f" (fp_line (start {0.05 * INCH} {-0.05 * INCH}) (end {0.05 * INCH} {0.05 * INCH}) "
f'(layer "F.SilkS") (width {SILK_WIDTH}))',
*[
f' (pad "{pad + 1}" thru_hole {"rect" if pad == 0 else "circle"} '
f"(at {strobe[0] * (pad % cols)} {strobe[1] * int(pad / cols)}) "
f"(size {pad_size} {pad_size}) (drill {hole_diam}) (layers *.Cu *.Mask))"
for pad in range(rows * cols)
],
f' (model "/home/brendan/Documents/projects/kicad/samtec.pretty/3dshapes/{part}.stp"',
f" (offset (xyz {center[0]} {-center[1]} 0))",
r" (scale (xyz 1 1 1))",
r" (rotate (xyz -90 0 90))",
r" )",
r")",
)
)
+ "\n"
)
elif any([part.startswith(s) for s in ["SSW-"]]):
# socket
raise NotImplementedError()
else:
raise ValueError(f"Unknown part family for {part}")
if __name__ == "__main__":
from multiprocessing import Pool
pool = Pool(10)
# for part in PARTS_SAMTEC:
# time.sleep(rng.random() * 3)
# download_step_samtec(part)
# footprint_samtec(part)
# pool.map(download_step_samtec, PARTS_SAMTEC)
# pool.map(footprint_samtec, PARTS_SAMTEC)
# for part in PARTS_MOLEX:
# download_step_molex(part)
pool.map(download_step_molex, PARTS_MOLEX)
pool.close()