abstract footprint file format

This commit is contained in:
Brendan Haines 2022-09-23 01:14:27 -06:00
parent f8fe77771f
commit 59db22ac7c
2 changed files with 204 additions and 60 deletions

156
fp_generator.py Normal file
View File

@ -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 + ")")

View File

@ -13,19 +13,24 @@ from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import By from selenium.webdriver.support.select import By
from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support.wait import WebDriverWait
from fp_generator import INCH, Footprint
dir_ = Path(__file__).parent dir_ = Path(__file__).parent
rng = np.random.default_rng() rng = np.random.default_rng()
SILK_WIDTH = 0.007 * INCH
PARTS_SAMTEC = [ PARTS_SAMTEC = [
# Pin Headers # Pin Headers
*[f"TSW-1{i:02d}-07-L-S" for i in range(1, 51)], *[f"TSW-1{i:02d}-07-L-S" for i in range(1, 5)],
*[f"TSW-1{i:02d}-07-L-D" for i in range(1, 51)], # *[f"TSW-1{i:02d}-07-L-S" 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-D" for i in range(1, 51)],
# *[f"TSW-1{i:02d}-07-L-Q" for i in range(1, 51)], # # *[f"TSW-1{i:02d}-07-L-T" for i in range(1, 51)],
*[f"TSW-2{i:02d}-07-L-S" for i in range(2, 26)], # # *[f"TSW-1{i:02d}-07-L-Q" for i in range(1, 51)],
*[f"TSW-2{i:02d}-07-L-D" for i in range(2, 26)], # *[f"TSW-2{i:02d}-07-L-S" 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-D" for i in range(2, 26)],
# *[f"TSW-2{i:02d}-07-L-Q" 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 # Sockets
# SSW # SSW
# SMH # SMH
@ -66,7 +71,7 @@ def download_step_samtec(part: str) -> None:
"prefs", "prefs",
{"download.default_directory": str(tmp)}, {"download.default_directory": str(tmp)},
) )
chrome_options.add_argument("--headless") # chrome_options.add_argument("--headless")
with webdriver.Chrome( with webdriver.Chrome(
options=chrome_options, 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}") 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: def footprint_samtec(part: str, lib: Optional[Union[Path, str]] = None) -> str:
if any([part.startswith(s) for s in ["TSW-", "HTSW-"]]): if any([part.startswith(s) for s in ["TSW-", "HTSW-"]]):
# pinheader # pinheader
@ -156,8 +156,8 @@ def footprint_samtec(part: str, lib: Optional[Union[Path, str]] = None) -> str:
lead_style = "vertical" lead_style = "vertical"
plating = { plating = {
"F": "Gold flash on post, Matte Tin on tail", "F": "Gold flash on post, Matte Tin on tail",
"L": '10 µ" (0.25 µm) Gold on post, Matte Tin on tail', "L": "10 µin (0.25 µm) Gold on post, Matte Tin on tail",
"G": '10 µ" (0.25 µm) Gold on post, Gold flash on balance', "G": "10 µin (0.25 µm) Gold on post, Gold flash on balance",
"T": "Matte Tin", "T": "Matte Tin",
}[match[5]] }[match[5]]
cols = {"S": 1, "D": 2, "T": 3, "Q": 4}[match[6]] 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: if lib is None:
lib = dir_ / "samtec.pretty" lib = dir_ / "samtec.pretty"
lib = Path(lib) lib = Path(lib)
with open(lib / f"{fp_name}.kicad_mod", "w") as f:
f.write( fp = Footprint(fp_name, description=description, keywords=keywords)
"\n".join( 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)
f'(footprint "{fp_name}" (version {now.strftime("%Y%m%d")}) (generator pcbnew) (layer "F.Cu")', fp.add_text("user", r"${REFERENCE}**", center[0], center[1], rotation=90, layer="F.Fab")
r" (attr through_hole)", fp.add_rect(
f' (descr "{description}")', [-0.05 * INCH] * 2,
f' (tags "{" ".join(keywords)}")', (0.05 * INCH + strobe[0] * (cols - 1), 0.05 * INCH + strobe[1] * (rows - 1)),
f' (fp_text reference "REF**" (at {center[0]} {-2.032}) (layer "F.SilkS")', 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")",
) )
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",
) )
+ "\n" 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-"]]): elif any([part.startswith(s) for s in ["SSW-"]]):
# socket # socket
raise NotImplementedError() raise NotImplementedError()
@ -231,15 +219,15 @@ if __name__ == "__main__":
pool = Pool(10) pool = Pool(10)
# for part in PARTS_SAMTEC: for part in PARTS_SAMTEC:
# time.sleep(rng.random() * 3) # time.sleep(rng.random() * 3)
# download_step_samtec(part) # download_step_samtec(part)
# footprint_samtec(part) footprint_samtec(part)
# pool.map(download_step_samtec, PARTS_SAMTEC) # pool.map(download_step_samtec, PARTS_SAMTEC)
# pool.map(footprint_samtec, PARTS_SAMTEC) # pool.map(footprint_samtec, PARTS_SAMTEC)
# for part in PARTS_MOLEX: # for part in PARTS_MOLEX:
# download_step_molex(part) # download_step_molex(part)
pool.map(download_step_molex, PARTS_MOLEX) # pool.map(download_step_molex, PARTS_MOLEX)
pool.close() pool.close()