diff --git a/fp_generator.py b/fp_generator.py new file mode 100644 index 0000000..b60fffd --- /dev/null +++ b/fp_generator.py @@ -0,0 +1,156 @@ +from datetime import datetime +from pathlib import Path +from typing import List, Literal, Optional, Tuple, Union + +INCH = 25.4 + + +class Footprint: + text_size = { + # layer: (size_x, size_y, stroke) + "F.SilkS": (0.030 * INCH, 0.030 * INCH, 0.007 * INCH), + "B.SilkS": (0.030 * INCH, 0.030 * INCH, 0.007 * INCH), + "F.Fab": (0.030 * INCH, 0.030 * INCH, 0.005 * INCH), + "B.Fab": (0.030 * INCH, 0.030 * INCH, 0.005 * INCH), + None: (0.030 * INCH, 0.030 * INCH, 0.005 * INCH), + } + + def __init__( + self, + name: str, + description: Optional[str] = None, + keywords: Optional[List[str]] = None, + ): + self.name = name + self.description = "" if description is None else description + self._pads = [] + self._models = [] + self._contents = ( + "(" + f'footprint "{self.name}"\n' + f' (version {datetime.now().strftime("%Y%m%d")})\n' + " (generator pcbnew)\n" + ' (layer "F.Cu")\n' + " (attr smd)\n" + ) + if description is not None: + self._contents += f' (descr "{description}")\n' + + if keywords is not None: + self._contents += f' (tags "{" ".join(keywords)}")\n' + + def add_pad( + self, + id: Union[int, str], + x: float, + y: float, + size: Union[float, Tuple[float]], + hole: Optional[float] = None, + shape: Optional[Literal["circle", "rect", "round_rect"]] = None, + ): + try: + size = (float(size), float(size)) + except ValueError: + size = tuple(size) + + if shape not in [None, "circle", "rect", "round_rect"]: + raise ValueError + + if hole is not None: + self._contents.replace("(attr smd)", "(attr through_hole)") + + self._contents += ( + f' (pad "{id}" thru_hole {shape} ' + f"(at {x} {y}) " + f"(size {size[0]} {size[1]}) " + f"(drill {hole}) " + "(layers *.Cu *.Mask)" + ")\n" + ) + + def add_model( + self, + path: Union[Path, str], + offset: Tuple[float] = (0, 0, 0), + rotate: Tuple[float] = (0, 0, 0), + scale: Tuple[float] = (1, 1, 1), + ): + self._contents += ( + f' (model "{str(path)}" ' + f"(offset (xyz {offset[0]} {offset[1]} {offset[2]})) " + f"(scale (xyz {scale[0]} {scale[1]} {scale[2]})) " + f"(rotate (xyz {rotate[0]} {rotate[1]} {rotate[2]})) " + ")\n" + ) + + def add_text( + self, + name: str, + text: str, + x: float, + y: float, + layer: str, + rotation: float = 0, + size: Optional[Tuple[float]] = None, + hidden: bool = False, + ): + if size is None: + if layer in self.text_size: + size = self.text_size[layer] + else: + size = self.text_size[None] + self._contents += ( + f' (fp_text {name} "{text}" ' + f"(at {x} {y} {rotation}) " + f'(layer "{layer}") ' + f"{'hide' if hidden else ''}" + f"(effects (font (size {size[0]} {size[1]}) (thickness {size[2]}))) " + ")\n" + ) + + def add_line( + self, + start: Tuple[float], + end: Tuple[float], + layer: str, + stroke: Optional[float] = None, + ): + if stroke is None: + if layer in self.text_size: + stroke = self.text_size[layer][2] + else: + stroke = self.text_size[None][2] + stroke = float(stroke) + + self._contents += ( + f' (fp_line (start {start[0]} {start[1]}) (end {end[0]} {end[1]}) (layer "{layer}") (width {stroke}))\n' + ) + + def add_rect( + self, + start: Tuple[float], + end: Tuple[float], + layer: str, + stroke: Optional[float] = None, + fill: bool = False, + ): + if stroke is None: + if layer in self.text_size: + stroke = self.text_size[layer][2] + else: + stroke = self.text_size[None][2] + stroke = float(stroke) + + self._contents += ( + " (fp_rect " + f"(start {start[0]} {start[1]}) " + f"(end {end[0]} {end[1]}) " + f"(stroke (width {stroke}) (type default)) " + f'(fill {"none" if not fill else "solidButIDontKnowWhatStringToUse"}) ' + f'(layer "{layer}") ' + ")\n" + ) + + def write(self, lib: Union[Path, str]): + with open(Path(lib) / f"{self.name}.kicad_mod", "w") as f: + f.write(self._contents + ")") diff --git a/generate.py b/generate.py index ae90b70..98dc0f6 100644 --- a/generate.py +++ b/generate.py @@ -13,19 +13,24 @@ from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.select import By from selenium.webdriver.support.wait import WebDriverWait +from fp_generator import INCH, Footprint + dir_ = Path(__file__).parent rng = np.random.default_rng() +SILK_WIDTH = 0.007 * INCH + 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)], + *[f"TSW-1{i:02d}-07-L-S" for i in range(1, 5)], + # *[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 @@ -66,7 +71,7 @@ def download_step_samtec(part: str) -> None: "prefs", {"download.default_directory": str(tmp)}, ) - chrome_options.add_argument("--headless") + # chrome_options.add_argument("--headless") with webdriver.Chrome( options=chrome_options, @@ -135,11 +140,6 @@ def download_step_molex(part: Union[str, int]) -> None: 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 @@ -156,8 +156,8 @@ def footprint_samtec(part: str, lib: Optional[Union[Path, str]] = None) -> str: 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', + "L": "10 µin (0.25 µm) Gold on post, Matte Tin on tail", + "G": "10 µin (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]] @@ -179,46 +179,34 @@ def footprint_samtec(part: str, lib: Optional[Union[Path, str]] = None) -> str: 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" + + fp = Footprint(fp_name, description=description, keywords=keywords) + fp.add_text("reference", "REF**", center[0], -2.032, layer="F.SilkS") + fp.add_text("value", "VAL**", center[0], center[1], rotation=90, layer="F.Fab", hidden=True) + fp.add_text("user", r"${REFERENCE}**", center[0], center[1], rotation=90, layer="F.Fab") + fp.add_rect( + [-0.05 * INCH] * 2, + (0.05 * INCH + strobe[0] * (cols - 1), 0.05 * INCH + strobe[1] * (rows - 1)), + layer="F.SilkS", + ) + fp.add_line((-0.05 * INCH, 0.05 * INCH), (0.05 * INCH, 0.05 * INCH), layer="F.SilkS") + fp.add_line((0.05 * INCH, -0.05 * INCH), (0.05 * INCH, 0.05 * INCH), layer="F.SilkS") + for pad in range(rows * cols): + fp.add_pad( + pad + 1, + strobe[0] * (pad % cols), + strobe[1] * int(pad / cols), + size=pad_size, + hole=hole_diam, + shape="rect" if pad == 0 else "circle", ) + fp.add_model( + f"/home/brendan/Documents/projects/kicad/samtec.pretty/3dshapes/{part}.stp", + (center[0], center[1], 0), + rotate=(-90, 0, 90), + ) + fp.write(lib) + elif any([part.startswith(s) for s in ["SSW-"]]): # socket raise NotImplementedError() @@ -231,15 +219,15 @@ if __name__ == "__main__": pool = Pool(10) - # for part in PARTS_SAMTEC: - # time.sleep(rng.random() * 3) - # download_step_samtec(part) - # footprint_samtec(part) + 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.map(download_step_molex, PARTS_MOLEX) pool.close()