def generate_bom(pcb_modules, config, extra_data, filter_layer=None): # type: (list, Config, Dict[str, dict], int) -> list """ Generate BOM from pcb layout. :param pcb_modules: list of modules on the pcb :param config: Config object :param extra_data: Extra fields data :param filter_layer: include only parts for given layer :return: BOM table (qty, value, footprint, refs) """ def convert(text): return int(text) if text.isdigit() else text.lower() def alphanum_key(key): return [convert(c) for c in re.split('([0-9]+)', key)] def natural_sort(l): """ Natural sort for strings containing numbers """ return sorted(l, key=lambda r: (alphanum_key(r[0]), r[1])) attr_dict = {0: 'Normal', 1: 'Normal+Insert', 2: 'Virtual'} # build grouped part list warning_shown = False part_groups = {} for i, m in enumerate(pcb_modules): if skip_component(m, config, extra_data, filter_layer): continue # group part refs by value and footprint value = m.GetValue() norm_value = units.componentValue(value) try: footprint = str(m.GetFPID().GetFootprintName()) except: footprint = str(m.GetFPID().GetLibItemName()) attr = m.GetAttributes() if attr in attr_dict: attr = attr_dict[attr] else: attr = str(attr) # skip virtual components if needed if config.blacklist_virtual and attr == 'Virtual': continue ref = m.GetReference() extras = [] if config.extra_fields: if ref in extra_data: extras = [ extra_data[ref].get(f, '') for f in config.extra_fields ] else: # Some components are on pcb but not in schematic data. # Show a warning about possibly outdated netlist/xml file. # Doing it only once when generating full bom is enough. if filter_layer is None: logwarn('Component %s is missing from schematic data.' % ref) warning_shown = True extras = [''] * len(config.extra_fields) group_key = (norm_value, tuple(extras), footprint, attr) valrefs = part_groups.setdefault(group_key, [value, []]) valrefs[1].append((ref, i)) if warning_shown: logwarn('Netlist/xml file is likely out of date.') # build bom table, sort refs bom_table = [] for (norm_value, extras, footprint, attr), valrefs in part_groups.items(): bom_row = (len(valrefs[1]), valrefs[0], footprint, natural_sort(valrefs[1]), extras) bom_table.append(bom_row) # sort table by reference prefix, footprint and quantity def sort_func(row): qty, _, fp, rf, extras = row prefix = re.findall('^[A-Z]*', rf[0][0])[0] if prefix in config.component_sort_order: ref_ord = config.component_sort_order.index(prefix) else: ref_ord = config.component_sort_order.index('~') return ref_ord, extras, fp, -qty, alphanum_key(rf[0][0]) if '~' not in config.component_sort_order: config.component_sort_order.append('~') bom_table = sorted(bom_table, key=sort_func) return bom_table
def generate_bom(pcb, filter_layer=None): """ Generate BOM from pcb layout. :param filter_layer: include only parts for given layer :return: BOM table (qty, value, footprint, refs) """ def convert(text): return int(text) if text.isdigit() else text.lower() def alphanum_key(key): return [convert(c) for c in re.split('([0-9]+)', key)] def natural_sort(l): """ Natural sort for strings containing numbers """ return sorted(l, key=alphanum_key) attr_dict = {0: 'Normal', 1: 'Normal+Insert', 2: 'Virtual'} # build grouped part list part_groups = {} for m in pcb.GetModules(): # filter part by layer if filter_layer is not None and filter_layer != m.GetLayer(): continue # group part refs by value and footprint value = m.GetValue() norm_value = units.componentValue(value) try: footprint = str(m.GetFPID().GetFootprintName()) except: footprint = str(m.GetFPID().GetLibItemName()) attr = m.GetAttributes() if attr in attr_dict: attr = attr_dict[attr] else: attr = str(attr) group_key = (norm_value, footprint, attr) valrefs = part_groups.setdefault(group_key, [value, []]) valrefs[1].append(m.GetReference()) # build bom table, sort refs bom_table = [] for (norm_value, footprint, attr), valrefs in part_groups.items(): if attr == 'Virtual': continue line = (len(valrefs[1]), valrefs[0], footprint, natural_sort(valrefs[1])) bom_table.append(line) # sort table by reference prefix, footprint and quantity def sort_func(row): qty, _, fp, rf = row prefix = re.findall('^[A-Z]*', rf[0])[0] ref_ord = { "C": 1, "R": 2, "L": 3, "D": 4, "Q": 5, "U": 6, "Y": 7, "X": 8, "F": 9, "SW": 10, "A": 11, "HS": 1996, "CNN": 1997, "J": 1998, "P": 1999, "NT": 2000, "MH": 2001, }.get(prefix, 1000) return ref_ord, fp, -qty, alphanum_key(rf[0]) bom_table = sorted(bom_table, key=sort_func) return bom_table