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()