From 79ee5e3c64c0140a61324914d24049478a9cf7f5 Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Tue, 28 May 2019 14:47:24 +0200 Subject: create-bom: Don't skip unmatched parts from the BOM. part-validate-parts: Read parts from the schematic, BOM and supplier's parts for even better rules. Enhancing footprint rule to check that the BOM part has the same footprint. --- src/ee/bom.py | 14 +++-- src/ee/part/__init__.py | 3 ++ src/ee/tools/part_validate_parts.py | 99 +++++++++++++++++++++++++++-------- src/ee/tools/templates/build.ninja.j2 | 11 +++- 4 files changed, 99 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/ee/bom.py b/src/ee/bom.py index 0a0225d..dd1c016 100644 --- a/src/ee/bom.py +++ b/src/ee/bom.py @@ -139,13 +139,19 @@ def create_bom(project: Project, schematic_path: Path, out_path: Path, part_dbs: for bom_part in bom_parts: if not bom_part.selected_part: log.info("No part selected for {}".format(bom_part.part.printable_reference)) - continue + supplier_part = None + else: + supplier_part = bom_part.selected_part - supplier_part = bom_part.selected_part + uri = None # TODO: generate - part = Part(types.Part(supplier=supplier_part.supplier)) + part = Part(types.Part(uri=uri)) + # TODO: this should use the part's uri instead of schematic reference. However, right now there is no way to + # differentiate between two part-reference objects. part.add_schematic_reference(bom_part.part.get_exactly_one_schematic_reference().referenceProp) - part.add_part_reference(supplier_part.uri) + + if supplier_part: + part.add_part_reference(supplier_part.uri) out_parts.add_entry(part, True) diff --git a/src/ee/part/__init__.py b/src/ee/part/__init__.py index 3ce255e..c18dd02 100644 --- a/src/ee/part/__init__.py +++ b/src/ee/part/__init__.py @@ -237,6 +237,9 @@ class Part(object): def get_part_references(self) -> List[types.PartReference]: return self.xml.referencesProp.part_referenceProp + def get_only_part_reference(self) -> Optional[types.PartReference]: + return next(iter(self.get_part_references()), None) + def get_exactly_one_part_reference(self) -> types.PartReference: refs = self.get_part_references() if len(refs) == 0: diff --git a/src/ee/tools/part_validate_parts.py b/src/ee/tools/part_validate_parts.py index bf45ace..a9c5ba7 100644 --- a/src/ee/tools/part_validate_parts.py +++ b/src/ee/tools/part_validate_parts.py @@ -1,8 +1,10 @@ import argparse from itertools import groupby from pathlib import Path +from typing import List import ee.tools +from ee.db import ObjDb from ee.part import Part, load_db, common_fact_types @@ -46,27 +48,55 @@ class Messages(object): self.messages.extend(messages.messages) -def check_has_footprint(part: Part): - fp = part.facts.get_value(common_fact_types.footprint) - if fp is not None: - return +class Context(object): + def __init__(self, supplier_parts: ObjDb[Part]): + self.supplier_parts = supplier_parts + self.uri_idx = supplier_parts.index("uri") - return Messages(part).warning("No footprint") + def get_supplier_part(self, part_uri): + return self.uri_idx.get_single(part_uri) -def validate(f, part: Part): +def check_has_footprint(ctx: Context, bom: Part, sch: Part): + ms = Messages(sch) + + fp = sch.facts.get_value(common_fact_types.footprint) + if fp is None: + return ms.warning("No footprint in schematic part") + + part_ref = bom.get_only_part_reference() + + if part_ref is None: + return ms.warning("Part has footprint in schematic but no BOM part selected.") + + supplier_part = ctx.get_supplier_part(part_ref.part_uriProp) + + supplier_fp = supplier_part.facts.get_value(common_fact_types.footprint) + + if supplier_fp is None: + return ms.warning(f"Part has footprint in schematic and a BOM part but the BOM part doesn't have a footprint. " + f"Part's footprint: {fp}.") + if fp != supplier_fp: + return ms.warning("Part has footprint in schematic and a BOM part but their footprints do not match. " + f"Footprints: part: {fp}, BOM: {supplier_fp}.") + + ms.info("Part has footprint and BOM part. Their footprints matches.") + + +def validate(f, ctx: Context, bom_part: Part, sch_part: Part): validators = [ check_has_footprint ] - messages = Messages(part) + messages = Messages(sch_part) for validator in validators: - m = validator(part) + m = validator(ctx, bom_part, sch_part) if m: messages.append(m) - print("{}".format(part.printable_reference), file=f) - print("{}".format("=" * len(part.printable_reference)), file=f) + title = "Checking {}".format(sch_part.printable_reference) + print(title, file=f) + print("{}".format("=" * len(title)), file=f) print("", file=f) for msg in messages.errors: @@ -83,16 +113,31 @@ def validate(f, part: Part): return messages -def work(in_path: Path, out_path: Path): - in_parts = load_db(in_path) +def work(bom_path: Path, sch_path: Path, report_path: Path, part_dbs: List[Path]): + bom_parts: ObjDb[Part] = ObjDb[Part]() + bom_parts.add_unique_index("uri", lambda p: p.uri) + [bom_parts.add(Part(xml)) for xml in load_db(bom_path).iterparts()] - with out_path.open("w") as f: - messages = Messages(None) + sch_parts: ObjDb[Part] = ObjDb[Part]() + sch_parts.add_unique_index("uri", lambda p: p.uri) + ref_idx = sch_parts.add_unique_index("ref", lambda p: p.get_exactly_one_schematic_reference().referenceProp) + [sch_parts.add(Part(xml)) for xml in load_db(sch_path).iterparts()] - for xml in in_parts.iterparts(): - part = Part(xml) + supplier_parts = ObjDb[Part]() + supplier_parts.add_unique_index("uri", lambda p: p.uri) - ms = validate(f, part) + for path in part_dbs: + for xml in load_db(path).iterparts(): + supplier_parts.add(Part(xml)) + + ctx = Context(supplier_parts) + + with report_path.open("w") as f: + messages = Messages(None) + + for bom_part in bom_parts: + sch_part = ref_idx.get_single(bom_part.get_exactly_one_schematic_reference().referenceProp) + ms = validate(f, ctx, bom_part, sch_part) messages.append(ms) print("", file=f) @@ -111,16 +156,26 @@ def work(in_path: Path, out_path: Path): parser = argparse.ArgumentParser() ee.tools.add_default_argparse_group(parser) -parser.add_argument("--in", - dest="in_path", +parser.add_argument("--bom", + required=True, + metavar="PART DB") + +parser.add_argument("--sch", required=True, metavar="PART DB") -parser.add_argument("--out", +parser.add_argument("--report", required=True, - metavar="REQUIREMENTS") + metavar="FILE") + +parser.add_argument("--part-db", + dest="part_dbs", + nargs="*", + required=True, + metavar="PART DB") args = parser.parse_args() ee.tools.process_default_argparse_group(args) -work(Path(args.in_path), Path(args.out)) +part_dbs_paths = [Path(path) for path in args.part_dbs] +work(Path(args.bom), Path(args.sch), Path(args.report), part_dbs_paths) diff --git a/src/ee/tools/templates/build.ninja.j2 b/src/ee/tools/templates/build.ninja.j2 index dbfe8f0..206a487 100644 --- a/src/ee/tools/templates/build.ninja.j2 +++ b/src/ee/tools/templates/build.ninja.j2 @@ -43,7 +43,7 @@ rule part-find-requirements command = $ee part-find-requirements {{ log }} --in $in --out $out $report rule part-validate-parts - command = $ee part-validate-parts {{ log }} --in $in --out $out + command = $ee part-validate-parts {{ log }} --bom $bom --sch $sch --report $out --part-db $part_dbs rule digikey-search-parts command = $ee digikey-search-parts {{ log }} --in $in --out $out @@ -102,12 +102,17 @@ build ee/sch.xml: part-apply-souffle-post ee/kicad/souffle/out/fact.csv work = ee/kicad/souffle {%- endif %} -build $report_dir/part-validate-parts.rst: part-validate-parts ee/sch.xml +build $report_dir/part-validate-parts.rst: part-validate-parts ee/bom.xml ee/sch.xml + sch = ee/sch.xml + bom = ee/bom.xml + part_dbs ={%- for p in part_dbs %} {{ p }}.xml{% endfor %} {%- set reports=reports+["$report_dir/part-validate-parts.rst"] %} +{#- TODO: complete build ee/requirements.xml | $report_dir/requirements.rst: part-find-requirements ee/sch.xml report = --report $report_dir/requirements.rst {%- set reports=reports+["$report_dir/requirements.rst"] %} +#} {% for s in distributors %} {%- set cfg = project.cfg["supplier:" + s] if "supplier:" + s in project.cfg else None %} @@ -155,12 +160,14 @@ build ee/bom.xml | $report_dir/bom.rst: create-bom ee/sch.xml {%- for p in part_ {%- endif %} {%- set reports=reports+["$report_dir/bom.rst"] %} +{#- TODO: complete build ee/orders/index.xml: split-parts-by-supplier ee/bom.xml {%- for p in part_dbs %} {{ p }}.xml{% endfor %} order = ee/bom.xml part_dbs ={%- for p in part_dbs %} --part-db {{ p }}.xml{% endfor %} out_dir = ee/orders default ee/orders/index.xml +#} rule seeed-download-opl description = seeed-download-opl $opl -- cgit v1.2.3