Example #1
0
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
Example #2
0
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