From 0dd97cf07eee154401d4138c1ff8a6694efe136a Mon Sep 17 00:00:00 2001 From: Matti Virkkunen Date: Thu, 8 Feb 2024 09:52:20 +0200 Subject: [PATCH] Support merging multiple schematics for fab output Also added uniqueness validation for reference designators. --- kikit/fab/common.py | 60 ++++++++++++++++++++++++++++++++++++++---- kikit/fab/jlcpcb.py | 23 +++------------- kikit/fab/neodenyy1.py | 21 +++------------ kikit/fab/pcbway.py | 21 +++------------ kikit/fab_ui.py | 9 ++++--- 5 files changed, 73 insertions(+), 61 deletions(-) diff --git a/kikit/fab/common.py b/kikit/fab/common.py index a72fde34..24420469 100644 --- a/kikit/fab/common.py +++ b/kikit/fab/common.py @@ -17,13 +17,59 @@ from kikit import eeschema_v6 # import getField, getUnit, getReference from kikit import eeschema #import getField, getUnit, getReference +def filterComponents(components, refsToIgnore, ignoreField): + filtered = [] + for c in components: + if getUnit(c) != 1: + continue + reference = getReference(c) + if reference.startswith("#PWR") or reference.startswith("#FL"): + continue + if reference in refsToIgnore: + continue + if getField(c, ignoreField) is not None and getField(c, ignoreField) != "": + continue + if hasattr(c, "in_bom") and not c.in_bom: + continue + if hasattr(c, "on_board") and not c.on_board: + continue + if hasattr(c, "dnp") and c.dnp: + continue + filtered.append(c) + return filtered + # A user can still supply v5 schematics even when we run v6, therefore, # we have to load the correct schematics and provide the right getters -def extractComponents(filename): +def extractComponents(filename, refsToIgnore, ignoreField): + if isinstance(filename, (list, tuple)): + mergedComponents = [] + seenRefs = dict() + + for f in filename: + components = extractComponents(f, refsToIgnore, ignoreField) + for c in components: + ref = getReference(c) + + seen = seenRefs.get(ref) + if seen == f: + raise RuntimeError( + f"Duplicate reference designator: '{f}' defines reference '{ref}' more than once." + ) + elif seen: + raise RuntimeError( + f"Duplicate reference designator: both '{f}' and '{seen}' define reference '{ref}'. " + + "When using multiple schematics, component references must be unique across all schematics." + ) + + seenRefs[ref] = f + mergedComponents.append(c) + + return mergedComponents + if filename.endswith(".kicad_sch"): - return eeschema_v6.extractComponents(filename) + return filterComponents(eeschema_v6.extractComponents(filename), refsToIgnore, ignoreField) if filename.endswith(".sch"): - return eeschema.extractComponents(filename) + return filterComponents(eeschema.extractComponents(filename), refsToIgnore, ignoreField) raise RuntimeError(f"Unknown schematic file type specified: {filename}") def getUnit(component): @@ -223,8 +269,12 @@ def isValidBoardPath(filename): return os.path.splitext(filename)[1] in [".kicad_pcb"] def ensureValidSch(filename): - if not isValidSchPath(filename): - raise RuntimeError(f"The path {filename} is not a valid KiCAD schema file") + if isinstance(filename, (list, tuple)): + for f in filename: + ensureValidSch(f) + else: + if not isValidSchPath(filename): + raise RuntimeError(f"The path {filename} is not a valid KiCAD schema file") def ensureValidBoard(filename): if not isValidBoardPath(filename): diff --git a/kikit/fab/jlcpcb.py b/kikit/fab/jlcpcb.py index bc9a1a3b..5653d2ac 100644 --- a/kikit/fab/jlcpcb.py +++ b/kikit/fab/jlcpcb.py @@ -9,24 +9,9 @@ from kikit.common import * from kikit.export import gerberImpl -def collectBom(components, lscsFields, ignore): +def collectBom(components, lscsFields): bom = {} for c in components: - if getUnit(c) != 1: - continue - reference = getReference(c) - if reference.startswith("#PWR") or reference.startswith("#FL"): - continue - if reference in ignore: - continue - if getField(c, "JLCPCB_IGNORE") is not None and getField(c, "JLCPCB_IGNORE") != "": - continue - if hasattr(c, "in_bom") and not c.in_bom: - continue - if hasattr(c, "on_board") and not c.on_board: - continue - if hasattr(c, "dnp") and c.dnp: - continue orderCode = None for fieldName in lscsFields: orderCode = getField(c, fieldName) @@ -37,7 +22,7 @@ def collectBom(components, lscsFields, ignore): getField(c, "Footprint"), orderCode ) - bom[cType] = bom.get(cType, []) + [reference] + bom[cType] = bom.get(cType, []) + [getReference(c)] return bom def bomToCsv(bomData, filename): @@ -85,9 +70,9 @@ def exportJlcpcb(board, outputdir, assembly, schematic, ignore, field, ensureValidSch(schematic) correctionFields = [x.strip() for x in corrections.split(",")] - components = extractComponents(schematic) + components = extractComponents(schematic, refsToIgnore, "JLCPCB_IGNORE") ordercodeFields = [x.strip() for x in field.split(",")] - bom = collectBom(components, ordercodeFields, refsToIgnore) + bom = collectBom(components, ordercodeFields) posData = collectPosData(loadedBoard, correctionFields, bom=components, posFilter=noFilter, correctionFile=correctionpatterns) diff --git a/kikit/fab/neodenyy1.py b/kikit/fab/neodenyy1.py index 85536f32..82e1fccf 100644 --- a/kikit/fab/neodenyy1.py +++ b/kikit/fab/neodenyy1.py @@ -18,27 +18,14 @@ re.compile(r'Crystal:Crystal_SMD_(.*?)_.*'): 'CRYSTAL_{}' } -def collectBom(components, ignore): +def collectBom(components): bom = {} for c in components: - if getUnit(c) != 1: - continue - reference = getReference(c) - if reference.startswith("#PWR") or reference.startswith("#FL"): - continue - if reference in ignore: - continue - if hasattr(c, "in_bom") and not c.in_bom: - continue - if hasattr(c, "on_board") and not c.on_board: - continue - if hasattr(c, "dnp") and c.dnp: - continue cType = ( getField(c, "Value"), getField(c, "Footprint") ) - bom[cType] = bom.get(cType, []) + [reference] + bom[cType] = bom.get(cType, []) + [getReference(c)] return bom def transcodeFootprint(footprint): @@ -146,8 +133,8 @@ def exportNeodenYY1(board, outputdir, schematic, ignore, ensureValidSch(schematic) correctionFields = [x.strip() for x in corrections.split(",")] - components = extractComponents(schematic) - bom = collectBom(components, refsToIgnore) + components = extractComponents(schematic, refsToIgnore, "NEODENYY1_IGNORE") + bom = collectBom(components) posData = collectPosData(loadedBoard, correctionFields, bom=components, posFilter=noFilter, correctionFile=correctionpatterns) diff --git a/kikit/fab/pcbway.py b/kikit/fab/pcbway.py index 2459495f..09ba6cd4 100644 --- a/kikit/fab/pcbway.py +++ b/kikit/fab/pcbway.py @@ -28,8 +28,7 @@ def addVirtualToRefsToIgnore(refsToIgnore, board): refsToIgnore.append(footprint.GetReference()) def collectBom(components, manufacturerFields, partNumberFields, - descriptionFields, notesFields, typeFields, footprintFields, - ignore): + descriptionFields, notesFields, typeFields, footprintFields): bom = {} # Use KiCad footprint as fallback for footprint @@ -38,17 +37,6 @@ def collectBom(components, manufacturerFields, partNumberFields, descriptionFields.append("Value") for c in components: - if getUnit(c) != 1: - continue - reference = getReference(c) - if reference.startswith("#PWR") or reference.startswith("#FL") or reference in ignore: - continue - if hasattr(c, "in_bom") and not c.in_bom: - continue - if hasattr(c, "on_board") and not c.on_board: - continue - if hasattr(c, "dnp") and c.dnp: - continue manufacturer = None for manufacturerName in manufacturerFields: manufacturer = getField(c, manufacturerName) @@ -88,7 +76,7 @@ def collectBom(components, manufacturerFields, partNumberFields, notes, solderType ) - bom[cType] = bom.get(cType, []) + [reference] + bom[cType] = bom.get(cType, []) + [getReference(c)] return bom def bomToCsv(bomData, filename, nBoards, types): @@ -146,8 +134,7 @@ def exportPcbway(board, outputdir, assembly, schematic, ignore, ensureValidSch(schematic) - - components = extractComponents(schematic) + components = extractComponents(schematic, refsToIgnore, "PCBWAY_IGNORE") correctionFields = [x.strip() for x in corrections.split(",")] manufacturerFields = [x.strip() for x in manufacturer.split(",")] partNumberFields = [x.strip() for x in partnumber.split(",")] @@ -158,7 +145,7 @@ def exportPcbway(board, outputdir, assembly, schematic, ignore, addVirtualToRefsToIgnore(refsToIgnore, loadedBoard) bom = collectBom(components, manufacturerFields, partNumberFields, descriptionFields, notesFields, typeFields, - footprintFields, refsToIgnore) + footprintFields) missingFields = False for type, references in bom.items(): diff --git a/kikit/fab_ui.py b/kikit/fab_ui.py index 955d9f2a..f3d91a3c 100644 --- a/kikit/fab_ui.py +++ b/kikit/fab_ui.py @@ -37,7 +37,8 @@ def execute(fab, kwargs): @click.command() @fabCommand @click.option("--assembly/--no-assembly", help="Generate files for SMT assembly (schematics is required)") -@click.option("--schematic", type=click.Path(dir_okay=False), help="Board schematics (required for assembly files)") +@click.option("--schematic", type=click.Path(dir_okay=False), multiple=True, + help="Board schematics (required for assembly files, can be specified more than once to merge components from multiple schematics)") @click.option("--ignore", type=str, default="", help="Comma separated list of designators to exclude from SMT assembly") @click.option("--field", type=str, default="LCSC", help="Comma separated list of component fields field with LCSC order code. First existing field is used") @@ -57,7 +58,8 @@ def jlcpcb(**kwargs): @click.command() @fabCommand @click.option("--assembly/--no-assembly", help="Generate files for SMT assembly (schematics is required)") -@click.option("--schematic", type=click.Path(dir_okay=False), help="Board schematics (required for assembly files)") +@click.option("--schematic", type=click.Path(dir_okay=False), multiple=True, + help="Board schematics (required for assembly files, can be specified more than once to merge components from multiple schematics)") @click.option("--ignore", type=str, default="", help="Comma separated list of designators to exclude from SMT assembly") @click.option("--corrections", type=str, default="PCBWAY_CORRECTION", help="Comma separated list of component fields with the correction value. First existing field is used") @@ -100,7 +102,8 @@ def oshpark(**kwargs): @click.command() @fabCommand -@click.option("--schematic", type=click.Path(dir_okay=False), help="Board schematics (required for assembly files)") +@click.option("--schematic", type=click.Path(dir_okay=False), multiple=True, + help="Board schematics (required for assembly files, can be specified more than once to merge components from multiple schematics)") @click.option("--ignore", type=str, default="", help="Comma separated list of designators to exclude from SMT assembly") @click.option("--corrections", type=str, default="YY1_CORRECTION", help="Comma separated list of component fields with the correction value. First existing field is used")