aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ee/digikey/bom.py66
-rw-r--r--src/ee/part/bom.py62
-rw-r--r--src/ee/tools/bom_to_csv.py55
-rw-r--r--src/ee/tools/digikey_create_bom.py6
-rw-r--r--src/ee/tools/templates/build.ninja.j29
5 files changed, 127 insertions, 71 deletions
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