kicad_bom_seeedstudio.py 3.13 KB
Newer Older
Pedro Henrique Kopper's avatar
Pedro Henrique Kopper committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
#!/usr/bin/env python3
import csv
import sys
import xml.etree.ElementTree as ET

### Natural key sorting for orders like : C1, C5, C10, C12 ... (instead of C1, C10, C12, C5...)
# http://stackoverflow.com/a/5967539
import re

def atoi(text):
    return int(text) if text.isdigit() else text

def natural_keys(text):
    '''
    alist.sort(key=natural_keys) sorts in human order
    http://nedbatchelder.com/blog/200712/human_sorting.html
    (See Toothy's implementation in the comments)
    '''
    return [ atoi(c) for c in re.split('(\d+)', text) ]
###

def parse_kicad_xml(input_file):
    """Parse the KiCad XML file and look for the part designators
    as done in the case of the official KiCad Open Parts Library:
    * OPL parts are designated with "SKU" (preferred)
    * other parts are designated with "MPN"
    """
    components = {}
    parts = {}
    missing = []

    tree = ET.parse(input_file)
    root = tree.getroot()
    for f in root.findall('./components/'):
        name = f.attrib['ref']
        info = {}
        fields = f.find('fields')
        opl, mpn = None, None
        if fields is not None:
            for x in fields:
                if x.attrib['name'].upper() == 'SKU':
                    opl = x.text
                elif x.attrib['name'].upper() == 'MPN':
                    mpn = x.text
        if opl:
            components[name] = opl
        elif mpn:
            components[name] = mpn
        else:
            missing += [name]
            continue
        if components[name] not in parts:
            parts[components[name]] = []
        parts[components[name]] += [name]
    return components, missing

def write_bom_seeed(output_file_slug, components):
    """Write the BOM according to the Seeed Studio Fusion PCBA template available at:
    https://statics3.seeedstudio.com/assets/file/fusion/bom_template_2016-08-18.csv

    ```
    Part/Designator,Manufacture Part Number/Seeed SKU,Quantity
    C1,RHA,1
    "D1,D2",CC0603KRX7R9BB102,2
    ```

    The output is a CSV file at the `output_file_slug`.csv location.
    """
    parts = {}
    for c in components:
        if components[c] not in parts:
            parts[components[c]] = []
        parts[components[c]] += [c]

    field_names = ['Part/Designator', 'Manufacture Part Number/Seeed SKU', 'Quantity']
    with open("{}.csv".format(output_file_slug), 'w') as csvfile:
        bomwriter = csv.DictWriter(csvfile, fieldnames=field_names, delimiter=',',
                    quotechar='"', quoting=csv.QUOTE_MINIMAL)
        bomwriter.writeheader()
        for p in sorted(parts.keys()):
            pieces = sorted(parts[p], key=natural_keys)
            designators = ",".join(pieces)
            bomwriter.writerow({'Part/Designator': designators,
                                'Manufacture Part Number/Seeed SKU': p,
                                'Quantity': len(pieces)})


if __name__ == "__main__":
    input_file = sys.argv[1]
    output_file = sys.argv[2]

    components, missing = parse_kicad_xml(input_file)
    write_bom_seeed(output_file, components)
    if len(missing) > 0:
        print("** Warning **: there were parts with missing SKU/MFP")
        print(missing)