abstract footprint file format
This commit is contained in:
parent
f8fe77771f
commit
59db22ac7c
156
fp_generator.py
Normal file
156
fp_generator.py
Normal 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 + ")")
|
100
generate.py
100
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.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()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user