From b4eeed35d78f82ea988d70de177d522ca20257ea Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Fri, 14 Jun 2019 21:42:18 +0200 Subject: bom-to-csv: Starting on a generic tool to generate CSV files from BOMs. --- src/ee/digikey/bom.py | 66 +++++------------------------------ src/ee/part/bom.py | 62 ++++++++++++++++++++++++++++---- src/ee/tools/bom_to_csv.py | 55 +++++++++++++++++++++++++++++ src/ee/tools/digikey_create_bom.py | 6 +--- src/ee/tools/templates/build.ninja.j2 | 9 +++-- 5 files changed, 127 insertions(+), 71 deletions(-) create mode 100644 src/ee/tools/bom_to_csv.py (limited to 'src') diff --git a/src/ee/digikey/bom.py b/src/ee/digikey/bom.py index 36b0143..398e03b 100644 --- a/src/ee/digikey/bom.py +++ b/src/ee/digikey/bom.py @@ -1,72 +1,24 @@ import csv from pathlib import Path -from typing import List, MutableMapping, Optional +from typing import List -from ee.db import ObjDb -from ee.digikey import DigikeyStore -from ee.logging import log -from ee.part import Part, load_db -from ee.part.bom import load_bom, check_bom, join_refs +from ee.part.bom import load_bom, check_bom, generate_bom, join_refs __all__ = ["create_bom"] -class BomLine(object): - def __init__(self, uri, part: Part): - self.uri = uri - self.part = part - self.refs = [] - - def add_ref(self, ref): - self.refs.append(ref) - - @classmethod - def header(cls) -> List[str]: - return ["Quantity", "Digi-Key Part Number", "Customer Reference"] - - def to_rows(self) -> List[str]: - return [str(len(self.refs)), self.part.get_exactly_one_spn().valueProp, join_refs(self.refs)] - - -def gen_bom(allow_incomplete, bom_parts: ObjDb[Part], supplier_parts: ObjDb[Part]) -> Optional[List[List[str]]]: - lines: MutableMapping[str, BomLine] = {} - - uri_idx = supplier_parts.index("uri") - - for part in bom_parts.values: - pr_obj = part.get_only_part_reference() - - if allow_incomplete and pr_obj is None: - log.debug("Skipping part without part reference: {}".format(part.printable_reference)) - continue - elif pr_obj is None: - log.warn("Unresolved part: {}".format(part.printable_reference)) - return - - part_uri = pr_obj.part_uriProp - - if part_uri not in lines: - lines[part_uri] = line = BomLine(part_uri, uri_idx.get_single(part_uri)) - else: - line = lines[part_uri] - - line.add_ref(part.get_exactly_one_schematic_reference().referenceProp) - - return sorted(line.to_rows() for line in lines.values()) - - -def create_bom(bom_path: Path, part_files: List[Path], out_path: Path, store_code, allow_incomplete): - store = DigikeyStore.from_store_code(store_code) - +def create_bom(bom_path: Path, part_files: List[Path], out_path: Path, allow_incomplete): bom_parts, supplier_parts = load_bom(bom_path, part_files) check_bom(bom_parts, supplier_parts) - bom = gen_bom(allow_incomplete, bom_parts, supplier_parts) + lines = generate_bom(allow_incomplete, bom_parts, supplier_parts) - if bom is None: + if lines is None: return with out_path.open("w") as f: w = csv.writer(f) - w.writerow(BomLine.header()) - w.writerows(bom) + w.writerow(["Quantity", "Digi-Key Part Number", "Customer Reference"]) + + for line in lines: + w.writerows([str(len(line.refs)), line.part.get_exactly_one_spn().valueProp, join_refs(line.refs)]) diff --git a/src/ee/part/bom.py b/src/ee/part/bom.py index 83ae348..0bf284a 100644 --- a/src/ee/part/bom.py +++ b/src/ee/part/bom.py @@ -1,22 +1,37 @@ import re import sys +from functools import total_ordering from itertools import groupby from pathlib import Path -from typing import List +from typing import List, Optional, MutableMapping from ee import EeException from ee.db import ObjDb +from ee.logging import log from ee.part import Part, load_db -__all__ = ["load_bom"] +__all__ = [ + "load_bom", + "check_bom", + "BomLine", + "generate_bom", + "split_ref", + "join_refs", +] -def uri_fn(part: Part): - return part.uri +@total_ordering +class BomLine(object): + def __init__(self, uri, part: Part): + self.uri = uri + self.part = part + self.refs = [] + def add_ref(self, ref): + self.refs.append(ref) -def supplier_fn(part: Part): - return part.supplier + def __lt__(self, other): + return self.uri < other.uri class BomItem(object): @@ -26,6 +41,12 @@ class BomItem(object): def load_bom(bom_path: Path, part_files: List[Path]) -> (ObjDb[Part](), ObjDb[Part]()): + def uri_fn(part: Part): + return part.uri + + def supplier_fn(part: Part): + return part.supplier + bom: ObjDb[Part] = ObjDb[Part]() for xml in load_db(bom_path).iterparts(): @@ -46,6 +67,35 @@ def check_bom(bom: ObjDb[Part](), parts: ObjDb[Part]()): pass +def generate_bom(allow_incomplete, bom_parts: ObjDb[Part], supplier_parts: ObjDb[Part], group_by_ref=True) -> \ + Optional[List[BomLine]]: + lines: MutableMapping[str, BomLine] = {} + + uri_idx = supplier_parts.index("uri") + + for part in bom_parts.values: + pr = part.get_only_part_reference() + + if allow_incomplete and pr is None: + log.debug("Skipping part without part reference: {}".format(part.printable_reference)) + continue + elif pr is None: + log.warn("Unresolved part: {}".format(part.printable_reference)) + return + + part_uri = pr.part_uriProp + + if part_uri not in lines: + supplier_part = uri_idx.get_single(part_uri) + lines[part_uri] = line = BomLine(part_uri, supplier_part) + else: + line = lines[part_uri] + + line.add_ref(part.get_exactly_one_schematic_reference().referenceProp) + + return sorted(lines.values()) + + def split_ref(ref): """Split "C12" into a tuple that's useful for sorting by component reference. diff --git a/src/ee/tools/bom_to_csv.py b/src/ee/tools/bom_to_csv.py new file mode 100644 index 0000000..181eb4e --- /dev/null +++ b/src/ee/tools/bom_to_csv.py @@ -0,0 +1,55 @@ +import argparse +import csv +from pathlib import Path + +import ee.tools +from ee.part.bom import load_bom, check_bom, generate_bom, join_refs + +parser = argparse.ArgumentParser() +ee.tools.add_default_argparse_group(parser) + +parser.add_argument("--bom", + required=True, + metavar="PART DB") + +parser.add_argument("--out", + required=True, + metavar="CSV") + +parser.add_argument("--part-db", + nargs="*", + required=True, + metavar="PART DB") + +parser.add_argument("--allow-incomplete", + default=False, + action="store_true") + +args = parser.parse_args() +ee.tools.process_default_argparse_group(args) + +bom_path = Path(args.bom) +part_files = [Path(p) for p in args.part_db] +out_path = Path(args.out) + +bom_parts, supplier_parts = load_bom(bom_path, part_files) +check_bom(bom_parts, supplier_parts) + +lines = generate_bom(args.allow_incomplete, bom_parts, supplier_parts) + +if lines is not None: + with out_path.open("w") as f: + w = csv.writer(f) + w.writerow(["Quantity", "MPN", "SPN", "References"]) + + for line in lines: + mpn = line.part.get_only_mpn() + spn = line.part.get_only_spn() + + row = [ + str(len(line.refs)), + mpn.valueProp if mpn else "", + spn.valueProp if spn else "", + join_refs(line.refs), + ] + w.writerow(row) diff --git a/src/ee/tools/digikey_create_bom.py b/src/ee/tools/digikey_create_bom.py index a83da83..eaea121 100644 --- a/src/ee/tools/digikey_create_bom.py +++ b/src/ee/tools/digikey_create_bom.py @@ -20,14 +20,10 @@ parser.add_argument("--part-db", required=True, metavar="PART DB") -parser.add_argument("--store", - default="us", - metavar="STORE CODE") - parser.add_argument("--allow-incomplete", action="store_true") args = parser.parse_args() ee.tools.process_default_argparse_group(args) -create_bom(Path(args.bom), [Path(p) for p in args.part_db], Path(args.out), args.store, args.allow_incomplete) +create_bom(Path(args.bom), [Path(p) for p in args.part_db], Path(args.out), args.allow_incomplete) diff --git a/src/ee/tools/templates/build.ninja.j2 b/src/ee/tools/templates/build.ninja.j2 index 1ab6102..016c7da 100644 --- a/src/ee/tools/templates/build.ninja.j2 +++ b/src/ee/tools/templates/build.ninja.j2 @@ -12,12 +12,15 @@ pcb = {{ pcb | ninja_path }} {%- endif %} {%- set log=log if log is defined else "--log=warn" %} -rule kicad-gerber - command = $ee kicad-gerber --pcb $in --output-dir $dir --index $out - rule mkzip command = $ee mkzip --include $in --zip $out +rule bom-to-csv + command = $ee bom-to-csv {{ log }} --bom $in --out $out $part_dbs $args + +rule kicad-gerber + command = $ee kicad-gerber --pcb $in --output-dir $dir --index $out + rule kicad-make-bom command = $ee kicad-make-bom {{ log }} --sch $sch --out $out --uuid $uuid -- cgit v1.2.3