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

Add: JLCPCB header picker #78

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions src/faebryk/library/Header.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,25 @@ class PadType(Enum):

class Angle(Enum):
STRAIGHT = auto()
VERTICAL90 = auto()
HORIZONTAL90 = auto()
ANGLE_90 = auto()

def __init__(
self,
horizonal_pin_count: int,
vertical_pin_count: int,
) -> None:
super().__init__()
self.horizontal_pin_count = horizonal_pin_count
self.vertical_pin_count = vertical_pin_count
self._horizontal_pin_count = horizonal_pin_count
self._vertical_pin_count = vertical_pin_count

def __preinit__(self):
self.pin_count_horizonal.merge(self.horizontal_pin_count)
self.pin_count_vertical.merge(self.vertical_pin_count)
self.pin_count_horizonal.merge(self._horizontal_pin_count)
self.pin_count_vertical.merge(self._vertical_pin_count)

pin_pitch: F.TBD[Quantity]
mating_pin_lenght: F.TBD[Quantity]
conection_pin_lenght: F.TBD[Quantity]
spacer_height: F.TBD[Quantity]
pin_type: F.TBD[PinType]
pad_type: F.TBD[PadType]
angle: F.TBD[Angle]
Expand All @@ -46,7 +48,17 @@ def __preinit__(self):
pin_count_vertical: F.TBD[int]

@L.rt_field
def unnamed(self):
return times(self.horizonal_pin_count * self.vertical_pin_count, F.Electrical)
def contact(self):
return times(
self._horizontal_pin_count * self._vertical_pin_count, F.Electrical
)

designator_prefix = L.f_field(F.has_designator_prefix_defined)("J")
designator_prefix = L.f_field(F.has_designator_prefix_defined)(
F.has_designator_prefix.Prefix.J
)

@L.rt_field
def can_attach_to_footprint(self):
return F.can_attach_to_footprint_via_pinmap(
pinmap={f"{i+1}": self.contact[i] for i in range(len(self.contact))}
)
2 changes: 1 addition & 1 deletion src/faebryk/library/_F.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@
from faebryk.library.XtalIF import XtalIF
from faebryk.library.has_pin_association_heuristic import has_pin_association_heuristic
from faebryk.library.Common_Mode_Filter import Common_Mode_Filter
from faebryk.library.Header import Header
from faebryk.library.PJ398SM import PJ398SM
from faebryk.library.RJ45_Receptacle import RJ45_Receptacle
from faebryk.library.Relay import Relay
Expand Down Expand Up @@ -114,6 +113,7 @@
from faebryk.library.Capacitor import Capacitor
from faebryk.library.Crystal import Crystal
from faebryk.library.Fuse import Fuse
from faebryk.library.Header import Header
from faebryk.library.Inductor import Inductor
from faebryk.library.Resistor import Resistor
from faebryk.library.Switch import Switch
Expand Down
13 changes: 13 additions & 0 deletions src/faebryk/libs/picker/jlcpcb/jlcpcb.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,19 @@ def filter_by_manufacturer_pn(self, partnumber: str) -> Self:
self.Q &= Q(mfr__icontains=partnumber)
return self

def filter_by_package(self, package: str | list[str]) -> Self:
assert self.Q

logger.debug(f"Possible package keywords: {package}")
description_query = Q()
if isinstance(package, str):
package = [package]
for keyword in package:
description_query |= Q(package__icontains=keyword)
self.Q &= description_query

return self

def filter_by_manufacturer(self, manufacturer: str) -> Self:
assert self.Q
if not manufacturer:
Expand Down
107 changes: 107 additions & 0 deletions src/faebryk/libs/picker/jlcpcb/picker_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,112 @@ def find_ldo(cmp: Module):
)


def find_header(cmp: Module):
"""
Find a header part in the JLCPCB database that matches the parameters of the
provided header
"""

assert isinstance(cmp, F.Header)

def pin_structure_to_pin_count(vertical: bool) -> Callable[[str], F.Constant[int]]:
"""
Database data looks like: 1x3P
"""

def f(x: str) -> F.Constant[int]:
if "x" not in x:
raise ValueError(f"Invalid pin structure: {x}")
if vertical:
return F.Constant(int(x.strip("P").split("x")[0]))
else:
return F.Constant(int(x.strip("P").split("x")[1]))

return f

def angle_to_mount_type() -> Callable[[str], F.Constant[F.Header.Angle]]:
"""
Database data looks like: "Mounting Type": "Shrouded" or "Straight"
"""

def f(x: str) -> F.Constant[F.Header.Angle]:
if x == "Shrouded":
return F.Constant(F.Header.Angle.ANGLE_90)
elif x == "Straight":
return F.Constant(F.Header.Angle.STRAIGHT)
else:
raise ValueError(f"Invalid angle: {x}")

return f

mapping = []
if cmp.pad_type.get_most_narrow() == F.Header.PadType.SMD:
pad_type = "SMD"
logger.warning(f"Angle parameter is not supported for SMD header: {cmp}")
else:
pad_type = ["Plugin", "Push-Pull", "TH"]
mapping += [
MappingParameterDB(
"angle",
["Mounting Type"],
transform_fn=angle_to_mount_type(),
),
]

if cmp.pin_type.get_most_narrow() == F.Header.PinType.FEMALE:
pin_type = "Female Headers"
pin_count_source = ["Holes Structure"]
elif cmp.pin_type.get_most_narrow() == F.Header.PinType.MALE:
pin_type = "Pin Headers"
pin_count_source = ["Pin Structure"]
mapping = [
MappingParameterDB(
"mating_pin_lenght",
["Length of Mating Pin"],
),
MappingParameterDB(
"conection_pin_lenght",
["Length of End Connection Pin"],
),
]
else:
pin_type = "Headers"
pin_count_source = ["Pin Structure", "Holes Structure"]

mapping += [
MappingParameterDB(
"pin_pitch",
[
"Row Spacing",
"Pitch",
],
),
MappingParameterDB(
"spacer_height",
["Insulation Height"],
),
MappingParameterDB(
"pin_count_horizonal",
pin_count_source,
transform_fn=pin_structure_to_pin_count(vertical=False),
),
MappingParameterDB(
"pin_count_vertical",
pin_count_source,
transform_fn=pin_structure_to_pin_count(vertical=True),
),
]
(
ComponentQuery()
.filter_by_category("Connectors", pin_type)
.filter_by_package(pad_type)
.filter_by_stock(qty)
.filter_by_traits(cmp)
.sort_by_price(qty)
.filter_by_module_params_and_attach(cmp, mapping, qty)
)


# --------------------------------------------------------------------------------------

TYPE_SPECIFIC_LOOKUP = {
Expand All @@ -508,4 +614,5 @@ def find_ldo(cmp: Module):
F.Diode: find_diode,
F.MOSFET: find_mosfet,
F.LDO: find_ldo,
F.Header: find_header,
}
4 changes: 2 additions & 2 deletions src/faebryk/libs/picker/lcsc.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,8 @@ def attach(component: Module, partno: str, get_model: bool = True):
raise LCSC_PinmapException(partno, f"Failed to get pinmap: {e}") from e
component.add(F.can_attach_to_footprint_via_pinmap(pinmap))

sym = F.Symbol.with_component(component, pinmap)
sym.add(F.Symbol.has_kicad_symbol(f"lcsc:{easyeda_footprint.info.name}"))
sym = F.Symbol.with_component(component, pinmap)
sym.add(F.Symbol.has_kicad_symbol(f"lcsc:{easyeda_footprint.info.name}"))
ruben-iteng marked this conversation as resolved.
Show resolved Hide resolved

# footprint
fp = F.KicadFootprint(
Expand Down
18 changes: 18 additions & 0 deletions test/libs/picker/test_jlcpcb.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,24 @@ def test_find_ldo(self):
],
)

def test_find_header(self):
self.TestRequirements(
self,
requirement=F.Header(horizonal_pin_count=3, vertical_pin_count=1).builder(
lambda h: (
h.pad_type.merge(F.Constant(F.Header.PadType.THROUGH_HOLE)),
h.pin_type.merge(F.Constant(F.Header.PinType.MALE)),
h.angle.merge(F.Constant(F.Header.Angle.STRAIGHT)),
h.mating_pin_lenght.merge(F.Range.from_center_rel(6 * P.mm, 0.1)),
h.pin_pitch.merge(F.Constant(2.54 * P.mm)),
)
),
footprint=[
("HDR-TH_3P-P2.54-V-M", 3),
("Plugin,P=2.54mm", 3),
],
)

def tearDown(self):
# in test atexit not triggered, thus need to close DB manually
JLCPCB_DB.get().close()
Expand Down