161 lines
4.6 KiB
Python
161 lines
4.6 KiB
Python
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),
|
|
None: (0.025 * INCH, 0.025 * INCH, 0.005 * INCH),
|
|
}
|
|
|
|
line_size = {
|
|
"F_SilkS": 0.007,
|
|
"B_SilkS": 0.007,
|
|
None: 0.005,
|
|
}
|
|
|
|
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 + ")")
|