diff --git a/src/faebryk/libs/picker/api/api.py b/src/faebryk/libs/picker/api/api.py index f552b417..33c2fe9e 100644 --- a/src/faebryk/libs/picker/api/api.py +++ b/src/faebryk/libs/picker/api/api.py @@ -15,6 +15,7 @@ from faebryk.core.module import Module # TODO: replace with API-specific data model +from faebryk.libs.picker.common import SIvalue from faebryk.libs.picker.jlcpcb.jlcpcb import Component, MappingParameterDB from faebryk.libs.picker.lcsc import LCSC_NoDataException, LCSC_PinmapException from faebryk.libs.picker.picker import PickError @@ -104,9 +105,6 @@ def try_attach( return False -type SIvalue = str - - @dataclass_json @dataclass(frozen=True) class FootprintCandidate: diff --git a/src/faebryk/libs/picker/api/picker_lib.py b/src/faebryk/libs/picker/api/picker_lib.py index 788e8fe7..7be075ca 100644 --- a/src/faebryk/libs/picker/api/picker_lib.py +++ b/src/faebryk/libs/picker/api/picker_lib.py @@ -26,10 +26,10 @@ # re-use the existing model for components from the jlcparts dataset, but as the data # schema diverges over time we'll migrate this to separate models +from faebryk.libs.picker.common import SIvalue, generate_si_values from faebryk.libs.picker.jlcpcb.jlcpcb import Component from faebryk.libs.picker.jlcpcb.picker_lib import _MAPPINGS_BY_TYPE from faebryk.libs.picker.picker import DescriptiveProperties, PickError -from faebryk.libs.units import Quantity from faebryk.libs.util import KeyErrorAmbiguous, KeyErrorNotFound logger = logging.getLogger(__name__) @@ -40,13 +40,6 @@ qty: int = 1 -def generate_si_values( - param: Parameter, si_unit: str, e_series: E_SERIES -) -> list[Quantity]: - # FIXME: implement with new params - raise NotImplementedError() - - def find_component_by_lcsc_id(lcsc_id: str, solver: Solver) -> Component: def extract_numeric_id(lcsc_id: str) -> int: match = re.match(r"C(\d+)", lcsc_id) @@ -80,7 +73,7 @@ def find_and_attach_by_lcsc_id(module: Module, solver: Solver): # TODO: pass through errors from API try: - part = find_component_by_lcsc_id(lcsc_pn) + part = find_component_by_lcsc_id(lcsc_pn, solver) except KeyErrorNotFound as e: raise PickError( f"Could not find part with LCSC part number {lcsc_pn}", module @@ -133,7 +126,7 @@ def find_and_attach_by_mfr(module: Module, solver: Solver): mfr_pn = properties[DescriptiveProperties.partno] try: - parts = [find_component_by_mfr(mfr, mfr_pn)] + parts = [find_component_by_mfr(mfr, mfr_pn, solver)] except KeyErrorNotFound as e: raise PickError( f"Could not find part with manufacturer part number {mfr_pn}", module @@ -180,6 +173,13 @@ def _get_footprint_candidates(module: Module) -> list[FootprintCandidate]: return [] +def _generate_si_values( + value: Parameter, solver: Solver, e_series: E_SERIES | None = None +) -> list[SIvalue]: + candidate_ranges = solver.inspect_get_known_superranges(value) + return generate_si_values(candidate_ranges, e_series=e_series) + + def find_resistor(cmp: Module, solver: Solver): """ Find a resistor with matching parameters @@ -189,7 +189,9 @@ def find_resistor(cmp: Module, solver: Solver): parts = client.fetch_resistors( ResistorParams( - resistances=generate_si_values(cmp.resistance, "Ω", E_SERIES_VALUES.E96), + resistances=_generate_si_values( + cmp.resistance, solver, E_SERIES_VALUES.E96 + ), footprint_candidates=_get_footprint_candidates(cmp), qty=qty, ), @@ -207,7 +209,9 @@ def find_capacitor(cmp: Module, solver: Solver): parts = client.fetch_capacitors( CapacitorParams( - capacitances=generate_si_values(cmp.capacitance, "F", E_SERIES_VALUES.E24), + capacitances=_generate_si_values( + cmp.capacitance, solver, E_SERIES_VALUES.E24 + ), footprint_candidates=_get_footprint_candidates(cmp), qty=qty, ), @@ -225,7 +229,9 @@ def find_inductor(cmp: Module, solver: Solver): parts = client.fetch_inductors( InductorParams( - inductances=generate_si_values(cmp.inductance, "H", E_SERIES_VALUES.E24), + inductances=_generate_si_values( + cmp.inductance, solver, E_SERIES_VALUES.E24 + ), footprint_candidates=_get_footprint_candidates(cmp), qty=qty, ), @@ -257,9 +263,11 @@ def find_diode(cmp: Module, solver: Solver): parts = client.fetch_diodes( DiodeParams( - max_currents=generate_si_values(cmp.max_current, "A", E_SERIES_VALUES.E3), - reverse_working_voltages=generate_si_values( - cmp.reverse_working_voltage, "V", E_SERIES_VALUES.E3 + max_currents=_generate_si_values( + cmp.max_current, solver, E_SERIES_VALUES.E3 + ), + reverse_working_voltages=_generate_si_values( + cmp.reverse_working_voltage, solver, E_SERIES_VALUES.E3 ), footprint_candidates=_get_footprint_candidates(cmp), qty=qty, diff --git a/src/faebryk/libs/picker/common.py b/src/faebryk/libs/picker/common.py index 0eee1829..5b138478 100644 --- a/src/faebryk/libs/picker/common.py +++ b/src/faebryk/libs/picker/common.py @@ -11,6 +11,8 @@ from faebryk.core.module import Module from faebryk.core.parameter import And, Is, Parameter, ParameterOperatable, Predicate from faebryk.core.solver import Solver +from faebryk.libs.e_series import E_SERIES, e_series_intersect +from faebryk.libs.library import L from faebryk.libs.picker.jlcpcb.jlcpcb import Component from faebryk.libs.picker.lcsc import attach from faebryk.libs.picker.picker import ( @@ -18,6 +20,7 @@ has_part_picked, has_part_picked_defined, ) +from faebryk.libs.units import Quantity, to_si_str from faebryk.libs.util import ConfigFlagEnum logger = logging.getLogger(__name__) @@ -31,6 +34,7 @@ class PickerType(StrEnum): DB_PICKER_BACKEND = ConfigFlagEnum( PickerType, "PICKER", PickerType.API, "Picker backend to use" ) +type SIvalue = str class StaticPartPicker(F.has_multi_picker.Picker, ABC): @@ -139,3 +143,34 @@ def add_to_modules(modules: Iterable[Module], prio: int = 0): picker = CachePicker() for m in modules: m.add(F.has_multi_picker(prio, picker)) + + +class PickerUnboundedParameterError(Exception): + pass + + +class PickerESeriesIntersectionError(Exception): + pass + + +def generate_si_values( + value: L.Ranges[Quantity], e_series: E_SERIES | None = None +) -> list[SIvalue]: + if value.is_unbounded(): + raise PickerUnboundedParameterError(value) + + intersection = e_series_intersect(value, e_series) + if intersection.is_empty(): + raise PickerESeriesIntersectionError(f"No intersection with E-series: {value}") + si_unit = value.units + + def _get_single(single: L.Range): + assert single.min_elem() == single.max_elem() + return single.min_elem() + + si_vals = [ + to_si_str(_get_single(r), si_unit).replace("µ", "u").replace("inf", "∞") + for r in intersection + ] + + return si_vals diff --git a/src/faebryk/libs/picker/jlcpcb/jlcpcb.py b/src/faebryk/libs/picker/jlcpcb/jlcpcb.py index 72159604..3fb5ad51 100644 --- a/src/faebryk/libs/picker/jlcpcb/jlcpcb.py +++ b/src/faebryk/libs/picker/jlcpcb/jlcpcb.py @@ -31,9 +31,13 @@ from faebryk.core.solver import Solver from faebryk.libs.e_series import ( E_SERIES, - e_series_intersect, ) from faebryk.libs.library import L +from faebryk.libs.picker.common import ( + PickerESeriesIntersectionError, + PickerUnboundedParameterError, + generate_si_values, +) from faebryk.libs.picker.lcsc import ( LCSC_NoDataException, LCSC_Part, @@ -49,7 +53,6 @@ P, Quantity, UndefinedUnitError, - to_si_str, ) from faebryk.libs.util import at_exit, cast_assert, once @@ -442,21 +445,18 @@ def filter_by_si_values( tolerance_requirement: float | None = None, ) -> Self: assert self.Q + assert not self.results - if value.is_unbounded(): + try: + si_vals = generate_si_values(value, e_series) + except PickerUnboundedParameterError: return self - assert not self.results - intersection = e_series_intersect(value, e_series) - if intersection.is_empty(): - raise ComponentQuery.ParamError(value, "No intersection with E-series") - si_unit = value.units - si_vals = [ - to_si_str(r.min_elem(), si_unit).replace("µ", "u").replace("inf", "∞") - for r in intersection - ] + except PickerESeriesIntersectionError as e: + raise ComponentQuery.ParamError(value, str(e)) from e if tolerance_requirement: self.filter_by_tolerance(tolerance_requirement) + return self.filter_by_description(*si_vals) def hint_filter_parameter(