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

Commit

Permalink
Tools: Add lcsc & mfr flag to libadd for auto-module (#45)
Browse files Browse the repository at this point in the history
Adding --mfr or --lcsc to libadd will now result in automatically adding
datasheets, designator, name, descriptive_properties, interfaces & pin
heuristics to the module.
  • Loading branch information
iopapamanoglou authored Sep 9, 2024
1 parent 71ee1d5 commit 6cf4dc7
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 43 deletions.
6 changes: 6 additions & 0 deletions src/faebryk/libs/picker/jlcpcb/jlcpcb.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,10 @@ def attach(
f"{indent(module.pretty_params(), ' '*4)}"
)

@property
def mfr_name(self) -> str:
return asyncio.run(Manufacturers().get_from_id(self.manufacturer_id))


class ComponentQuery:
class Error(Exception): ...
Expand Down Expand Up @@ -482,6 +486,8 @@ def filter_by_manufacturer_pn(self, partnumber: str) -> Self:

def filter_by_manufacturer(self, manufacturer: str) -> Self:
assert self.Q
if not manufacturer:
return self
manufacturer_ids = asyncio.run(Manufacturers().get_ids(manufacturer))
self.Q &= Q(manufacturer_id__in=manufacturer_ids)
return self
Expand Down
77 changes: 56 additions & 21 deletions src/faebryk/libs/picker/jlcpcb/picker_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
from faebryk.core.module import Module
from faebryk.libs.e_series import E_SERIES_VALUES
from faebryk.libs.picker.jlcpcb.jlcpcb import (
Component,
ComponentQuery,
MappingParameterDB,
)
from faebryk.libs.picker.picker import (
DescriptiveProperties,
PickError,
)
from faebryk.libs.util import KeyErrorAmbiguous, KeyErrorNotFound

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -44,7 +46,21 @@ def f(x: str) -> F.Constant[T]:
return f


def find_lcsc_part(module: Module):
def find_component_by_lcsc_id(lcsc_id: str) -> Component:
parts = ComponentQuery().filter_by_lcsc_pn(lcsc_id).get()

if len(parts) < 1:
raise KeyErrorNotFound(f"Could not find part with LCSC part number {lcsc_id}")

if len(parts) > 1:
raise KeyErrorAmbiguous(
parts, f"Found multiple parts with LCSC part number {lcsc_id}"
)

return next(iter(parts))


def find_and_attach_by_lcsc_id(module: Module):
"""
Find a part in the JLCPCB database by its LCSC part number
"""
Expand All @@ -56,23 +72,47 @@ def find_lcsc_part(module: Module):

lcsc_pn = module.get_trait(F.has_descriptive_properties).get_properties()["LCSC"]

parts = ComponentQuery().filter_by_lcsc_pn(lcsc_pn).get()

if len(parts) < 1:
raise PickError(f"Could not find part with LCSC part number {lcsc_pn}", module)

if len(parts) > 1:
try:
part = find_component_by_lcsc_id(lcsc_pn)
except KeyErrorNotFound as e:
raise PickError(
f"Could not find part with LCSC part number {lcsc_pn}", module
) from e
except KeyErrorAmbiguous:
raise PickError(f"Found no exact match for LCSC part number {lcsc_pn}", module)

if parts[0].stock < qty:
if part.stock < qty:
raise PickError(
f"Part with LCSC part number {lcsc_pn} has insufficient stock", module
)

parts[0].attach(module, [])
part.attach(module, [])


def find_manufacturer_part(module: Module):
def find_component_by_mfr(mfr: str, mfr_pn: str) -> Component:
parts = (
ComponentQuery()
.filter_by_manufacturer_pn(mfr_pn)
.filter_by_manufacturer(mfr)
.filter_by_stock(qty)
.sort_by_price()
.get()
)

if len(parts) < 1:
raise KeyErrorNotFound(
f"Could not find part with manufacturer part number {mfr_pn}"
)

if len(parts) > 1:
raise KeyErrorAmbiguous(
parts, f"Found multiple parts with manufacturer part number {mfr_pn}"
)

return next(iter(parts))


def find_and_attach_by_mfr(module: Module):
"""
Find a part in the JLCPCB database by its manufacturer part number
"""
Expand All @@ -97,19 +137,14 @@ def find_manufacturer_part(module: Module):
DescriptiveProperties.manufacturer
]

parts = (
ComponentQuery()
.filter_by_manufacturer_pn(mfr_pn)
.filter_by_manufacturer(mfr)
.filter_by_stock(qty)
.sort_by_price()
.get()
)

if len(parts) < 1:
try:
parts = [find_component_by_mfr(mfr, mfr_pn)]
except KeyErrorNotFound as e:
raise PickError(
f"Could not find part with manufacturer part number {mfr_pn}", module
)
) from e
except KeyErrorAmbiguous as e:
parts = e.duplicates

for part in parts:
try:
Expand Down
4 changes: 2 additions & 2 deletions src/faebryk/libs/picker/jlcpcb/pickers.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ def add_jlcpcb_pickers(module: Module, base_prio: int = 0) -> None:

# Generic pickers
prio = base_prio
module.add(F.has_multi_picker(prio, JLCPCBPicker(P.find_lcsc_part)))
module.add(F.has_multi_picker(prio, JLCPCBPicker(P.find_manufacturer_part)))
module.add(F.has_multi_picker(prio, JLCPCBPicker(P.find_and_attach_by_lcsc_id)))
module.add(F.has_multi_picker(prio, JLCPCBPicker(P.find_and_attach_by_mfr)))

# Type specific pickers
prio = base_prio + 1
Expand Down
53 changes: 52 additions & 1 deletion src/faebryk/libs/pycodegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@

import logging
import re
import subprocess
from pathlib import Path
from textwrap import dedent
from typing import Callable, Iterable

import black

logger = logging.getLogger(__name__)


def sanitize_name(raw):
def sanitize_name(raw, expect_arithmetic: bool = False):
sanitized = raw
# braces
sanitized = sanitized.replace("(", "")
Expand All @@ -18,6 +24,9 @@ def sanitize_name(raw):
sanitized = sanitized.replace(".", "_")
sanitized = sanitized.replace(",", "_")
sanitized = sanitized.replace("/", "_")
if not expect_arithmetic:
sanitized = sanitized.replace("-", "_")

# special symbols
sanitized = sanitized.replace("'", "")
sanitized = sanitized.replace("*", "")
Expand Down Expand Up @@ -62,3 +71,45 @@ def handle_unknown_invalid_symbold(match):
return None, to_escape

return sanitized


def gen_repeated_block[T](
generator: Iterable[T],
func: Callable[[T], str] = dedent,
requires_pass: bool = False,
) -> str:
lines = list(map(func, generator))

if not lines and requires_pass:
lines = ["pass"]

return gen_block("\n".join(lines))


def gen_block(payload: str):
return f"#__MARK_BLOCK_BEGIN\n{payload}\n#__MARK_BLOCK_END"


def fix_indent(text: str) -> str:
indent_stack = [""]

out_lines = []
for line in text.splitlines():
if "#__MARK_BLOCK_BEGIN" in line:
indent_stack.append(line.removesuffix("#__MARK_BLOCK_BEGIN"))
elif "#__MARK_BLOCK_END" in line:
indent_stack.pop()
else:
out_lines.append(indent_stack[-1] + line)

return dedent("\n".join(out_lines))


def format_and_write(code: str, path: Path):
code = code.strip()
code = black.format_file_contents(code, fast=True, mode=black.FileMode())
path.write_text(code)

print("Ruff----")
subprocess.run(["ruff", "check", "--fix", path], check=True)
print("--------")
Loading

0 comments on commit 6cf4dc7

Please sign in to comment.