import pydoc import re from pathlib import Path from typing import Optional, List, Callable, Mapping from xml.dom import minidom from xml.etree import ElementTree from ee import EeException from ee.kicad.model import Component from ee.kicad.read_schematic import read_schematics from ee.kicad.to_bom import to_bom, to_bom_xml from ee.part import PartDb, save_db, Part from ee.xml import types, uris __all__ = [ "StrategyCallable", "MakeBomStrategy", "apply_strategies", "mpn_strategy", "part_type_strategy", "dpn_strategy_factory", "make_bom", ] StrategyCallable = Callable[[Component, Part], Optional[Part]] def apply_strategies(c: Component, part: Part, strategies: List[StrategyCallable]): for strategy in strategies: part = strategy(c, part) if part is None: return if not isinstance(part, Part): raise EeException("Values returned from strategy must be a Part, got {}".format(type(part))) return part def part_type_from_footprint(fp): lib, part_name = fp.split(":") if lib == "Capacitor_SMD": return uris.CAPACITOR elif lib == "Resistor_SMD": return uris.RESISTOR elif lib == "Diode_SMD": return uris.DIODE elif lib == "Inductor_SMD": return uris.INDUCTOR elif lib == "Crystal": return uris.CRYSTAL def part_type_from_ref_type(ref_type): if ref_type == "C": return uris.CAPACITOR elif ref_type == "R": return uris.RESISTOR elif ref_type == "D": return uris.DIODE elif ref_type == "L": return uris.INDUCTOR elif ref_type == "X": return uris.CRYSTAL elif ref_type == "Q": return uris.TRANSISTOR def part_type_strategy(component: Component, part: Part) -> Part: pt = None fp = component.footprint if fp is not None: pt = part_type_from_footprint(fp) if pt is None and component.has_ref_num: pt = part_type_from_ref_type(component.ref_type) if pt is not None: part.get_facts().append(types.Fact(uris.make_fact_key("type"), value=pt)) return part def fix_value_strategy(component: Component, part: Part) -> Part: if not component.has_ref_num: return part rt = component.ref_type v = component.value if rt in ("D", "R", "L", "C") and v == component.symbol.name: part.remove_fact(uris.make_fact_key("value")) if rt == "Q" and v == component.symbol.name and re.match("^Q_[NP]MOS_[DSG]{3}$", component.symbol.name): part.remove_fact(uris.make_fact_key("value")) return part def mpn_strategy(component: Component, part: Part) -> Part: mpn = component.get_field("mpn") if mpn is not None: part.add_mpn(mpn.value) return part def dpn_strategy_factory(dpn_mappings: Mapping[str, str]) -> StrategyCallable: def dpn_strategy(component: Component, part: Part) -> Part: for field_name, distributor in dpn_mappings: s = component.get_field(field_name) if s is None: continue part.add_spn(s.value) return part return dpn_strategy class MakeBomStrategy(): def __init__(self): self.dpn_mappings = {} self.default_strategies = [ fix_value_strategy, mpn_strategy, part_type_strategy, dpn_strategy_factory(self.dpn_mappings), ] def process_part(self, component: Component, part: Part): return self.default_process_part(component, part) def default_process_part(self, component: Component, part: Part): return apply_strategies(component, part, self.default_strategies) def work(sch, out: Path, strategy: MakeBomStrategy, new_mode, pretty): def strip(s): s = (s or "").strip() return None if len(s) == 0 else s if not new_mode: bom = to_bom_xml(sch) xml = ElementTree.tostring(bom, encoding="unicode") if pretty: xml = minidom.parseString(xml).toprettyxml(indent=" ") print(xml) else: parts = PartDb() components = to_bom(sch, require_ref=False) for c in components: xml = types.Part() part = Part(xml) if c.has_ref_num: part.add_schematic_reference(c.ref) value = strip(c.value) if value: part.facts.add(uris.make_fact_key("value"), value) footprint = strip(c.footprint) if footprint: part.facts.add(uris.make_fact_key("footprint"), footprint) i = footprint.find(":") if i >= 0: lib, footprint = footprint.split(":") part.facts.add(uris.make_fact_key("kicad-schematic-library"), lib) part.facts.add(uris.make_fact_key("kicad-schematic-footprint"), footprint) part = strategy.process_part(c, part) if part is None: continue parts.add_entry(part, True) save_db(out, parts) def make_bom(sch_file: Path, out_dir: Path, strategy_name: str, new_mode: bool, pretty: bool): sch = read_schematics(str(sch_file)) make_bom_strategy_factory = pydoc.locate(strategy_name) if not callable(make_bom_strategy_factory): raise EeException("Not a callable: {}, is a {}".format(strategy_name, type(make_bom_strategy_factory))) make_bom_strategy = make_bom_strategy_factory() # type: MakeBomStrategy if not isinstance(make_bom_strategy, MakeBomStrategy): raise EeException("Not a MakeBomStrategy: {}, is a {}".format(strategy_name, type(make_bom_strategy))) work(sch, out_dir, make_bom_strategy, new_mode, pretty)