163 lines
4.8 KiB
Python
163 lines
4.8 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,
|
|
lib: Union[Path, str],
|
|
description: Optional[str] = None,
|
|
keywords: Optional[List[str]] = None,
|
|
):
|
|
self.name = name
|
|
self.lib = Path(lib)
|
|
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 TypeError:
|
|
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" if hole is not None else "smd"} {shape} '
|
|
+ f"(at {x} {y}) "
|
|
+ f"(size {size[0]} {size[1]}) "
|
|
+ (f"(drill {hole}) " if hole is not None else "")
|
|
+ ("(layers *.Cu *.Mask)" if hole is not None else '(layers "F.Cu" "F.Paste" "F.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(path).relative_to(self.lib))}" '
|
|
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):
|
|
with open(self.lib / f"{self.name}.kicad_mod", "w") as f:
|
|
f.write(self._contents + ")")
|