Skip to content
This repository has been archived by the owner on Dec 10, 2024. It is now read-only.

Commit

Permalink
Feature: PCB: Transformer: utils (#44)
Browse files Browse the repository at this point in the history
* Add: get_all_footprints

* Add: get_footprint_silkscreen_bbox and set_designator_position

* Add: JLCBPCB ID helpers

* Update Insert zone and text
  • Loading branch information
ruben-iteng authored Sep 9, 2024
1 parent d1b9b04 commit 71ee1d5
Showing 1 changed file with 203 additions and 13 deletions.
216 changes: 203 additions & 13 deletions src/faebryk/exporters/pcb/kicad/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
import uuid
from abc import abstractmethod
from dataclasses import fields
from enum import Enum, auto
from itertools import pairwise
from typing import Any, Callable, Iterable, List, Sequence, TypeVar
from typing import Any, Callable, Iterable, List, Optional, Sequence, TypeVar

import numpy as np
from shapely import Polygon
Expand Down Expand Up @@ -38,6 +39,7 @@
C_xy,
C_xyr,
C_xyz,
E_fill,
)
from faebryk.libs.sexp.dataclass_sexp import dataclass_dfs
from faebryk.libs.util import KeyErrorNotFound, cast_assert, find, get_key
Expand Down Expand Up @@ -295,13 +297,52 @@ def is_marked(obj) -> bool:

# Getter ---------------------------------------------------------------------------
@staticmethod
def get_fp(cmp) -> Footprint:
def get_fp(cmp: Node) -> Footprint:
return cmp.get_trait(PCB_Transformer.has_linked_kicad_footprint).get_fp()

def get_all_footprints(self) -> List[tuple[Module, Footprint]]:
return [
(cast_assert(Module, cmp), t.get_fp())
for cmp, t in self.graph.nodes_with_trait(
PCB_Transformer.has_linked_kicad_footprint
)
]

def get_net(self, net: FNet) -> Net:
nets = {pcb_net.name: pcb_net for pcb_net in self.pcb.nets}
return nets[net.get_trait(F.has_overriden_name).get_name()]

# TODO: make universal fp bbox getter (also take into account pads)
def get_footprint_silkscreen_bbox(
self, cmp: Node
) -> None | tuple[Point2D, Point2D]:
fp = self.get_fp(cmp)
silk_outline = [
geo
for geo in get_all_geos(fp)
if geo.layer == ("F.SilkS" if fp.layer.startswith("F") else "B.SilkS")
]

extremes = list[C_xy]()

for geo in silk_outline:
if isinstance(geo, C_line):
extremes.extend([geo.start, geo.end])
elif isinstance(geo, C_arc):
# TODO: calculate extremes.extend([geo.start, geo.mid, geo.end])
...
elif isinstance(geo, C_rect):
extremes.extend([geo.start, geo.end])
elif isinstance(geo, C_circle):
# TODO: calculate extremes.extend([geo.center, geo.end])
...

if not extremes:
logger.warn(f"{cmp} with fp:{fp.name} has no silk outline")
return None

return Geometry.bbox([Point2D((point.x, point.y)) for point in extremes])

def get_edge(self) -> list[Point2D]:
def geo_to_lines(
geo: Geom, fp: Footprint | None = None
Expand Down Expand Up @@ -541,21 +582,39 @@ def insert_via(
)
)

def insert_text(self, text: str, at: C_xyr, font: Font, front: bool = True):
def insert_text(
self,
text: str,
at: C_xyr,
font: Font,
layer: str = "F.SilkS",
alignment=None,
knockout: bool = False,
):
self.pcb.gr_texts.append(
GR_Text(
text=text,
at=at,
layer=C_text_layer(f"{'F' if front else 'B'}.SilkS"),
layer=C_text_layer(layer, C_text_layer.E_knockout.knockout)
if knockout
else C_text_layer(layer),
effects=C_effects(
font=font,
justify=(
C_effects.E_justify.center,
C_effects.E_justify.center,
C_effects.E_justify.mirror
if not front
else C_effects.E_justify.normal,
),
(
C_effects.E_justify.center,
C_effects.E_justify.center,
C_effects.E_justify.mirror,
)
if not layer.startswith("F.")
else (
C_effects.E_justify.center,
C_effects.E_justify.center,
C_effects.E_justify.normal,
)
)
if alignment is None
else alignment,
),
uuid=self.gen_uuid(mark=True),
)
Expand Down Expand Up @@ -636,9 +695,16 @@ def get_net_obj_bbox(self, net: Net, layer: str, tolerance=0.0):

return Geometry.rect_to_polygon(bbox)

def insert_zone(self, net: Net, layers: str | list[str], polygon: list[Point2D]):
def insert_zone(
self,
net: Net,
layers: str | list[str],
polygon: list[Point2D],
keepout: bool = False,
):
# check if exists
zones = self.pcb.zones
# TODO: zones is always emtpy list?
# TODO check bbox

if isinstance(layers, str):
Expand All @@ -665,7 +731,7 @@ def insert_zone(self, net: Net, layers: str | list[str], polygon: list[Point2D])
fill=Zone.C_fill(
enable=True,
mode=None,
hatch_thickness=0.25,
hatch_thickness=0.0,
hatch_gap=0.5,
hatch_orientation=0,
hatch_smoothing_level=0,
Expand All @@ -676,18 +742,81 @@ def insert_zone(self, net: Net, layers: str | list[str], polygon: list[Point2D])
thermal_bridge_width=0.2,
smoothing=None,
radius=1,
island_removal_mode=Zone.C_fill.E_island_removal_mode.do_not_remove,
# island_removal_mode=Zone.C_fill.E_island_removal_mode.do_not_remove, # noqa E501
island_area_min=10.0,
),
locked=False,
hatch=Zone.C_hatch(mode=Zone.C_hatch.E_mode.edge, pitch=0.5),
priority=0,
keepout=Zone.C_keepout(
tracks=Zone.C_keepout.E_keepout_bool.allowed,
vias=Zone.C_keepout.E_keepout_bool.allowed,
pads=Zone.C_keepout.E_keepout_bool.allowed,
copperpour=Zone.C_keepout.E_keepout_bool.not_allowed,
footprints=Zone.C_keepout.E_keepout_bool.allowed,
)
if keepout
else None,
connect_pads=Zone.C_connect_pads(
mode=Zone.C_connect_pads.E_mode.thermal_reliefs, clearance=0.2
),
)
)

# JLCPCB ---------------------------------------------------------------------------
class JLCPBC_QR_Size(Enum):
SMALL_5x5mm = C_xy(5, 5)
MEDIUM_8x8mm = C_xy(5, 5)
LARGE_10x10mm = C_xy(5, 5)

def insert_jlcpcb_qr(
self,
size: JLCPBC_QR_Size,
center_at: C_xy,
layer="F.SilkS",
number: bool = True,
):
assert layer.endswith("SilkS"), "JLCPCB QR code must be on silk screen layer"
if number:
self.insert_text(
"######",
at=C_xyr(center_at.x, center_at.y + size.value.y / 2 + 1, 0),
font=Font(size=C_wh(0.75, 0.75), thickness=0.15),
layer="F.Fab" if layer.startswith("F.") else "B.Fab",
)
self.insert_geo(
C_rect(
start=C_xy(
center_at.x - size.value.x / 2, center_at.y - size.value.y / 2
),
end=C_xy(
center_at.x + size.value.x / 2, center_at.y + size.value.y / 2
),
stroke=C_stroke(width=0.15, type=C_stroke.E_type.solid),
fill=E_fill.solid,
layer=layer,
uuid=self.gen_uuid(mark=True),
)
)

def insert_jlcpcb_serial(
self,
center_at: C_xyr,
layer="F.SilkS",
):
assert layer.endswith(
"SilkS"
), "JLCPCB serial number must be on silk screen layer"
self.insert_text(
"JLCJLCJLCJLC",
at=center_at,
font=Font(
size=C_wh(1, 1),
thickness=0.15,
),
layer=layer,
)

# Positioning ----------------------------------------------------------------------
def move_footprints(self):
# position modules with defined positions
Expand Down Expand Up @@ -978,3 +1107,64 @@ def set_pcb_outline_complex(
self.insert_geo(geo)

# find bounding box

class Side(Enum):
TOP = auto()
BOTTOM = auto()
LEFT = auto()
RIGHT = auto()

def set_designator_position(
self,
offset: float,
displacement: C_xy = C_xy(0, 0),
rotation: Optional[float] = None,
offset_side: Side = Side.BOTTOM,
layer: Optional[C_text_layer] = None,
font: Optional[Font] = None,
knockout: Optional[C_text_layer.E_knockout] = None,
justify: Optional[
tuple[C_effects.E_justify, C_effects.E_justify, C_effects.E_justify]
] = None,
):
for mod, fp in self.get_all_footprints():
reference = fp.propertys["Reference"]
reference.layer = (
layer
if layer
else C_text_layer(
layer="F.SilkS" if fp.layer.startswith("F") else "B.SilkS"
)
)
reference.layer.knockout = (
knockout if knockout else reference.layer.knockout
)
reference.effects.font = font if font else reference.effects.font

reference.effects.justify = (
justify if justify else reference.effects.justify
)
rot = rotation if rotation else reference.at.r

footprint_bbox = self.get_footprint_silkscreen_bbox(mod)
if not footprint_bbox:
continue
max_coord = C_xy(*footprint_bbox[1])
min_coord = C_xy(*footprint_bbox[0])

if offset_side == self.Side.BOTTOM:
reference.at = C_xyr(
displacement.x, max_coord.y + offset - displacement.y, rot
)
elif offset_side == self.Side.TOP:
reference.at = C_xyr(
displacement.x, min_coord.y - offset - displacement.y, rot
)
elif offset_side == self.Side.LEFT:
reference.at = C_xyr(
min_coord.x - offset - displacement.x, displacement.y, rot
)
elif offset_side == self.Side.RIGHT:
reference.at = C_xyr(
max_coord.x + offset + displacement.x, displacement.y, rot
)

0 comments on commit 71ee1d5

Please sign in to comment.