diff --git a/controller-board/autofill_schem.py b/controller-board/autofill_schem.py new file mode 100644 index 0000000..77e5205 --- /dev/null +++ b/controller-board/autofill_schem.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python3 +# TODO: make sure it also works in python2 + +# version 0.0.2 + +# updates: +# - allow multiple input files, using the later ones only for component footprint +# and distributor +# - fix code so it works with Kicad 5 + +import argparse + +DISTRIBUTOR_VALUES = ["\"Digikey\""] + +class Component(object): + def __init__(self, start): + self.designator = None + self.value = None + self.footprint = None + self.footprint_row = None + # distributor ids, mapped to (line_num, value, all fields + # (to make reconstruction easier) + self.distributors = dict() + self.cls = None + self.last_value = None + self.num_fields = 0 + + # used mainly for parser debugging + self.start = start + +class DistributorData(object): + def __init__(self, id): + self.id = id + +def quotesplit(line): + parts = line.split(" ") + ret = [] + num_quotes = 0 + incomplete_part = None + for part in parts: + num_quotes += len([None for c in part if c == '"']) + if num_quotes % 2 == 1: + if incomplete_part is None: + # just started a quoted section + incomplete_part = part + else: + incomplete_part += " " + part + else: + if incomplete_part is not None: + # ending quote is in this part + ret.append(incomplete_part + " " + part) + incomplete_part = None + else: + # normal case; pass through + ret.append(part) + return ret + +def parse_lines(f): + lines = [] + components = [] + component_start = False + component = None + + for i, line in enumerate(f): + if component_start: + if line.startswith("L"): + parts = quotesplit(line) + component.designator = parts[2].rstrip() + component.cls = component.designator[0] + elif line.startswith("F"): + parts = quotesplit(line) + component.num_fields += 1 + component.last_value = i + if parts[1].isdigit(): + field_type = int(parts[1]) + if field_type == 1: + component.value = parts[2] + elif field_type == 2: + component.footprint_row = (i, parts) + if len(parts[2]) > 2: + component.footprint = parts[2] + elif field_type > 3: + # TODO: make case insensitive + if parts[-1][:-1] in DISTRIBUTOR_VALUES: + component.distributors[parts[-1][:-1]] = DistributorData(parts[2]) + elif line.startswith("$EndComp"): + component_start = False + # ignore power nodes + if component.cls != "#": + components.append(component) + + if line.startswith("$Comp"): + component_start = True + component = Component(i) + lines.append(line) + return (lines, components) + +def infer_components(components): + # dictionaries in dictionaries: + # distributor = (distributor_type, id) so that non-unique ids can be captured + # filled_values[cls][value] = [(footprint, [designators], [distributors])] + filled_values = dict() + + # first pass to infer footprints and distributors, second pass to fill in details + for c in components: + if c.cls is None or len(c.cls) == 0: + print("Component at {} incorrectly parsed; no cls set".format(c.start)) + else: + if c.cls not in filled_values: + filled_values[c.cls] = dict() + + if c.value is not None and len(c.value) > 2: + if c.value not in filled_values[c.cls]: + filled_values[c.cls][c.value] = [] + + if c.footprint is not None and len(c.footprint) > 2: + # print(c.designator, c.value, c.footprint, c.distributors) + tosearch = filled_values[c.cls][c.value] + # find element with matching footprint + found = None + for i, fp in enumerate(tosearch): + if fp[0] == c.footprint: + found = i + break + + if found is None: + distributors = [] + for dist in c.distributors: + if len(c.distributors[dist].id) > 2 and len(dist) > 2: + distributors.append((dist, c.distributors[dist].id)) + tosearch.append((c.footprint, [c.designator], distributors)) + else: + tosearch[i][1].append(c.designator) + # append any new distributors + for dist in c.distributors: + if len(c.distributors[dist].id) > 2 and len(dist) > 2: + if not any([dist == m[0] and c.distributors[dist].id == m[1] + for m in tosearch[i][2]]): + tosearch[i][2].append((dist, c.distributors[dist].id)) + return filled_values + +def main(): + parser = argparse.ArgumentParser(description="Autofills components in an Eeschema schematic") + parser.add_argument("-i", "--include", action="append", help="additional files to read in, for component inference") + parser.add_argument("input", help="file to autofill") + parser.add_argument("output", help="file to write autofilled schematic to") + + args = parser.parse_args() + + lines = None + components = None + + with open(args.input, 'r') as f: + (lines, components) = parse_lines(f) + + print("{} lines".format(len(lines))) + print("found {} components".format(len(components))) + without_footprints = len([None for c in components if c.footprint is None]) + print("found {} components without footprints".format(without_footprints)) + + all_components = [] + all_components.extend(components) + + if args.include is not None: + print("searching additional files: {}".format(" ".join(args.include))) + + for filename in args.include: + with open(filename, 'r') as f: + (more_lines, more_components) = parse_lines(f) + print("{} more lines".format(len(more_lines))) + print("found {} more components".format(len(more_components))) + more_without_footprints = len([None for c in more_components if c.footprint is None]) + print("found {} more components without footprints".format(more_without_footprints)) + all_components.extend(more_components) + + filled_values = infer_components(all_components) + + entry_count = 0 + conflicts = [] + for cls in filled_values: + for val in filled_values[cls]: + for _ in filled_values[cls][val]: + # TODO: check for conflicts + entry_count += 1 + print("found {} filled unique component classes".format(entry_count)) + + # TODO: allow interactive distributor choice to resolve conflicts + while len(conflicts) > 0: + print("found conflicting information, cannot autofill") + for conflict in conflicts: + pass + return + + # autofill + autofill_fp = 0 + autofill_dist = 0 + to_append = [] + for c in components: + if c.cls in filled_values: + if c.value in filled_values[c.cls]: + matches = [] + if c.footprint is not None: + matches = [t for t in filled_values[c.cls][c.value] if t[0] == c.footprint] + else: + matches = filled_values[c.cls][c.value] + if len(matches) == 1: + # autofill + match = matches[0] + if ((c.footprint is None or len(c.footprint) <= 2) and + c.footprint_row is not None): + print("matched {} {} with {}".format(c.designator, c.value, match)) + # rewrite footprint + c.footprint = match[0] + row = c.footprint_row[1] + row[2] = match[0] + lines[c.footprint_row[0]] = " ".join(row) + autofill_fp += 1 + # add in distributors + dist_added = 0 + for dist in match[2]: + if dist[0] not in c.distributors: + c.distributors[dist[0]] = DistributorData(dist[1]) + # append to the field list + template_row = quotesplit(lines[c.last_value][:-1]) + row = [ + template_row[0], + str(c.num_fields), + dist[1]] + template_row[3:11] + [dist[0] + "\n"] + c.num_fields += 1 + to_append.append((c.last_value, row)) + dist_added += 1 + # mark as something to do, because appending now would + # cause the row numbers to shift for all the components after this one, + # invalidating their indices + if dist_added > 0: + autofill_dist += 1 + + # print(c.designator, c.value, c.footprint, c.distributors) + for ta in reversed(to_append): + idx = ta[0] + row = ta[1] + lines = lines[0:idx+1] + [" ".join(row)] + lines[idx+1:] + + print("autofilled {} fp, {} dist".format(autofill_fp, autofill_dist)) + # dictionary of dictionaries of components without footprints + # missing[cls][value] = [designators] + missing = dict() + + for c in components: + if c.cls is not None: + if c.cls not in missing: + missing[c.cls] = dict() + if c.value is not None and c.footprint is None: + if c.value not in missing[c.cls]: + missing[c.cls][c.value] = [] + missing[c.cls][c.value].append(c.designator) + + for cls in missing: + for value in missing[cls]: + print("NOTE: no unique footprint found for {} {}".format(value, missing[cls][value])) + + # repeat for manufacturer info + missing = dict() + + for c in components: + if c.cls is not None: + if c.cls not in missing: + missing[c.cls] = dict() + if c.value is not None and c.value != "\"DNP\"" and not bool(c.distributors): + if c.value not in missing[c.cls]: + missing[c.cls][c.value] = [] + missing[c.cls][c.value].append(c.designator) + + for cls in missing: + for value in missing[cls]: + print("NOTE: no distributor data found for {} {}".format(value, missing[cls][value])) + + + output = args.output or args.input + print("outputting to {}...".format(output)) + with open(output, "w+") as f: + for line in lines: + f.write(line) + +if __name__ == "__main__": + main() diff --git a/controller-board/controller-board.sch b/controller-board/controller-board.sch index 2444b66..6409111 100644 --- a/controller-board/controller-board.sch +++ b/controller-board/controller-board.sch @@ -33,6 +33,7 @@ F 0 "Q5" H 9600 6125 50 0000 L CNN F 1 "BUK9M52-40EX" H 9600 6050 50 0000 L CNN F 2 "Package_TO_SOT_SMD:LFPAK33" H 9600 5975 50 0001 L CIN F 3 "https://assets.nexperia.com/documents/data-sheet/BUK9M52-40E.pdf" V 9400 6050 50 0001 L CNN +F 4 "1727-7317-1-ND" V 9400 6050 50 0001 L CNN "Digikey" 1 9400 6050 1 0 0 -1 $EndComp @@ -44,6 +45,7 @@ F 0 "Q3" H 9600 4075 50 0000 L CNN F 1 "BUK9M52-40EX" H 9600 4000 50 0000 L CNN F 2 "Package_TO_SOT_SMD:LFPAK33" H 9600 3925 50 0001 L CIN F 3 "https://assets.nexperia.com/documents/data-sheet/BUK9M52-40E.pdf" V 9400 4000 50 0001 L CNN +F 4 "1727-7317-1-ND" V 9400 4000 50 0001 L CNN "Digikey" 1 9400 4000 1 0 0 -1 $EndComp @@ -55,6 +57,7 @@ F 0 "Q4" H 9600 5075 50 0000 L CNN F 1 "BUK9M52-40EX" H 9600 5000 50 0000 L CNN F 2 "Package_TO_SOT_SMD:LFPAK33" H 9600 4925 50 0001 L CIN F 3 "https://assets.nexperia.com/documents/data-sheet/BUK9M52-40E.pdf" V 9400 5000 50 0001 L CNN +F 4 "1727-7317-1-ND" V 9400 5000 50 0001 L CNN "Digikey" 1 9400 5000 1 0 0 -1 $EndComp @@ -222,8 +225,9 @@ U 1 1 5FED6801 P 3000 5900 F 0 "C3" H 3010 5970 50 0000 L CNN F 1 "0.1 uF" H 3010 5820 50 0000 L CNN -F 2 "" H 3000 5900 50 0001 C CNN +F 2 "Capacitor_SMD:C_0402_1005Metric" H 3000 5900 50 0001 C CNN F 3 "~" H 3000 5900 50 0001 C CNN +F 4 "1276-6720-1-ND" H 3000 5900 50 0001 C CNN "Digikey" 1 3000 5900 0 1 -1 0 $EndComp @@ -233,8 +237,9 @@ U 1 1 5FED7456 P 3600 5900 F 0 "C4" H 3610 5970 50 0000 L CNN F 1 "0.1 uF" H 3610 5820 50 0000 L CNN -F 2 "" H 3600 5900 50 0001 C CNN +F 2 "Capacitor_SMD:C_0402_1005Metric" H 3600 5900 50 0001 C CNN F 3 "~" H 3600 5900 50 0001 C CNN +F 4 "1276-6720-1-ND" H 3600 5900 50 0001 C CNN "Digikey" 1 3600 5900 0 -1 -1 0 $EndComp @@ -465,8 +470,9 @@ U 1 1 5FF425D8 P 3700 2500 F 0 "R4" H 3730 2520 50 0000 L CNN F 1 "1kR" H 3730 2460 50 0000 L CNN -F 2 "" H 3700 2500 50 0001 C CNN +F 2 "Resistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolder" H 3700 2500 50 0001 C CNN F 3 "~" H 3700 2500 50 0001 C CNN +F 4 "RMCF0603FT1K00CT-ND" H 3700 2500 50 0001 C CNN "Digikey" 1 3700 2500 0 -1 -1 0 $EndComp @@ -476,8 +482,9 @@ U 1 1 5FF42CBE P 3700 2600 F 0 "R5" H 3730 2620 50 0000 L CNN F 1 "1kR" H 3730 2560 50 0000 L CNN -F 2 "" H 3700 2600 50 0001 C CNN +F 2 "Resistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolder" H 3700 2600 50 0001 C CNN F 3 "~" H 3700 2600 50 0001 C CNN +F 4 "RMCF0603FT1K00CT-ND" H 3700 2600 50 0001 C CNN "Digikey" 1 3700 2600 0 -1 1 0 $EndComp @@ -495,8 +502,9 @@ U 1 1 5FF49237 P 9000 5000 F 0 "R10" H 9030 5020 50 0000 L CNN F 1 "1kR" H 9030 4960 50 0000 L CNN -F 2 "" H 9000 5000 50 0001 C CNN +F 2 "Resistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolder" H 9000 5000 50 0001 C CNN F 3 "~" H 9000 5000 50 0001 C CNN +F 4 "RMCF0603FT1K00CT-ND" H 9000 5000 50 0001 C CNN "Digikey" 1 9000 5000 0 -1 -1 0 $EndComp @@ -534,8 +542,9 @@ U 1 1 5FF5E5F4 P 9000 6050 F 0 "R11" H 9030 6070 50 0000 L CNN F 1 "1kR" H 9030 6010 50 0000 L CNN -F 2 "" H 9000 6050 50 0001 C CNN +F 2 "Resistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolder" H 9000 6050 50 0001 C CNN F 3 "~" H 9000 6050 50 0001 C CNN +F 4 "RMCF0603FT1K00CT-ND" H 9000 6050 50 0001 C CNN "Digikey" 1 9000 6050 0 -1 -1 0 $EndComp @@ -545,8 +554,9 @@ U 1 1 5FF5ECB6 P 9000 2950 F 0 "R8" H 9030 2970 50 0000 L CNN F 1 "1kR" H 9030 2910 50 0000 L CNN -F 2 "" H 9000 2950 50 0001 C CNN +F 2 "Resistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolder" H 9000 2950 50 0001 C CNN F 3 "~" H 9000 2950 50 0001 C CNN +F 4 "RMCF0603FT1K00CT-ND" H 9000 2950 50 0001 C CNN "Digikey" 1 9000 2950 0 -1 -1 0 $EndComp @@ -639,8 +649,9 @@ U 1 1 5FF87E62 P 1400 6750 F 0 "R1" H 1430 6770 50 0000 L CNN F 1 "10kR" H 1430 6710 50 0000 L CNN -F 2 "" H 1400 6750 50 0001 C CNN +F 2 "Resistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolder" H 1400 6750 50 0001 C CNN F 3 "~" H 1400 6750 50 0001 C CNN +F 4 "RNCP0603FTD10K0CT-ND" H 1400 6750 50 0001 C CNN "Digikey" 1 1400 6750 -1 0 0 -1 $EndComp @@ -650,8 +661,9 @@ U 1 1 5FF895C9 P 1500 6750 F 0 "R2" H 1530 6770 50 0000 L CNN F 1 "10kR" H 1530 6710 50 0000 L CNN -F 2 "" H 1500 6750 50 0001 C CNN +F 2 "Resistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolder" H 1500 6750 50 0001 C CNN F 3 "~" H 1500 6750 50 0001 C CNN +F 4 "RNCP0603FTD10K0CT-ND" H 1500 6750 50 0001 C CNN "Digikey" 1 1500 6750 1 0 0 -1 $EndComp @@ -732,8 +744,9 @@ U 1 1 5FFAF9CF P 7250 2450 F 0 "R7" H 7280 2470 50 0000 L CNN F 1 "10kR" H 7280 2410 50 0000 L CNN -F 2 "" H 7250 2450 50 0001 C CNN +F 2 "Resistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolder" H 7250 2450 50 0001 C CNN F 3 "~" H 7250 2450 50 0001 C CNN +F 4 "RNCP0603FTD10K0CT-ND" H 7250 2450 50 0001 C CNN "Digikey" 1 7250 2450 -1 0 0 -1 $EndComp @@ -829,8 +842,9 @@ U 1 1 5FFE9A97 P 9700 1800 F 0 "C12" H 9710 1870 50 0000 L CNN F 1 "47 uF" H 9710 1720 50 0000 L CNN -F 2 "" H 9700 1800 50 0001 C CNN +F 2 "Capacitor_THT:CP_Radial_D5.0mm_P2.00mm" H 9700 1800 50 0001 C CNN F 3 "~" H 9700 1800 50 0001 C CNN +F 4 "732-8596-1-ND" H 9700 1800 50 0001 C CNN "Digikey" 1 9700 1800 1 0 0 -1 $EndComp @@ -840,8 +854,9 @@ U 1 1 5FFE9BAB P 10050 1800 F 0 "C13" H 10060 1870 50 0000 L CNN F 1 "4.7 uF" H 10060 1720 50 0000 L CNN -F 2 "" H 10050 1800 50 0001 C CNN +F 2 "Capacitor_SMD:C_0603_1608Metric" H 10050 1800 50 0001 C CNN F 3 "~" H 10050 1800 50 0001 C CNN +F 4 "1276-1044-1-ND" H 10050 1800 50 0001 C CNN "Digikey" 1 10050 1800 1 0 0 -1 $EndComp @@ -851,8 +866,9 @@ U 1 1 5FFF0DD5 P 6500 1850 F 0 "C8" H 6510 1920 50 0000 L CNN F 1 "0.1 uF" H 6510 1770 50 0000 L CNN -F 2 "" H 6500 1850 50 0001 C CNN +F 2 "Capacitor_SMD:C_0402_1005Metric" H 6500 1850 50 0001 C CNN F 3 "~" H 6500 1850 50 0001 C CNN +F 4 "1276-6720-1-ND" H 6500 1850 50 0001 C CNN "Digikey" 1 6500 1850 0 -1 -1 0 $EndComp @@ -862,8 +878,9 @@ U 1 1 5FFF1CD2 P 6500 1500 F 0 "C7" H 6510 1570 50 0000 L CNN F 1 "4.7 uF" H 6510 1420 50 0000 L CNN -F 2 "" H 6500 1500 50 0001 C CNN +F 2 "Capacitor_SMD:C_0603_1608Metric" H 6500 1500 50 0001 C CNN F 3 "~" H 6500 1500 50 0001 C CNN +F 4 "1276-1044-1-ND" H 6500 1500 50 0001 C CNN "Digikey" 1 6500 1500 0 -1 -1 0 $EndComp @@ -901,8 +918,9 @@ U 1 1 600041FE P 5600 1850 F 0 "C6" H 5610 1920 50 0000 L CNN F 1 "0.1 uF" H 5610 1770 50 0000 L CNN -F 2 "" H 5600 1850 50 0001 C CNN +F 2 "Capacitor_SMD:C_0402_1005Metric" H 5600 1850 50 0001 C CNN F 3 "~" H 5600 1850 50 0001 C CNN +F 4 "1276-6720-1-ND" H 5600 1850 50 0001 C CNN "Digikey" 1 5600 1850 0 1 -1 0 $EndComp @@ -912,8 +930,9 @@ U 1 1 60004204 P 5600 1500 F 0 "C5" H 5610 1570 50 0000 L CNN F 1 "4.7 uF" H 5610 1420 50 0000 L CNN -F 2 "" H 5600 1500 50 0001 C CNN +F 2 "Capacitor_SMD:C_0603_1608Metric" H 5600 1500 50 0001 C CNN F 3 "~" H 5600 1500 50 0001 C CNN +F 4 "1276-1044-1-ND" H 5600 1500 50 0001 C CNN "Digikey" 1 5600 1500 0 1 -1 0 $EndComp @@ -1058,8 +1077,9 @@ U 1 1 6007F9F6 P 5550 5550 F 0 "R6" H 5580 5570 50 0000 L CNN F 1 "10kR" H 5580 5510 50 0000 L CNN -F 2 "" H 5550 5550 50 0001 C CNN +F 2 "Resistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolder" H 5550 5550 50 0001 C CNN F 3 "~" H 5550 5550 50 0001 C CNN +F 4 "RNCP0603FTD10K0CT-ND" H 5550 5550 50 0001 C CNN "Digikey" 1 5550 5550 1 0 0 -1 $EndComp @@ -1148,8 +1168,9 @@ U 1 1 600E0348 P 7300 4800 F 0 "C10" H 7310 4870 50 0000 L CNN F 1 "0.1 uF" H 7310 4720 50 0000 L CNN -F 2 "" H 7300 4800 50 0001 C CNN +F 2 "Capacitor_SMD:C_0402_1005Metric" H 7300 4800 50 0001 C CNN F 3 "~" H 7300 4800 50 0001 C CNN +F 4 "1276-6720-1-ND" H 7300 4800 50 0001 C CNN "Digikey" 1 7300 4800 0 -1 -1 0 $EndComp @@ -1177,8 +1198,9 @@ U 1 1 600EB00A P 6500 4800 F 0 "C9" H 6510 4870 50 0000 L CNN F 1 "0.1 uF" H 6510 4720 50 0000 L CNN -F 2 "" H 6500 4800 50 0001 C CNN +F 2 "Capacitor_SMD:C_0402_1005Metric" H 6500 4800 50 0001 C CNN F 3 "~" H 6500 4800 50 0001 C CNN +F 4 "1276-6720-1-ND" H 6500 4800 50 0001 C CNN "Digikey" 1 6500 4800 0 1 1 0 $EndComp @@ -1219,8 +1241,9 @@ U 1 1 60160CD6 P 2900 1650 F 0 "C?" H 2910 1720 50 0000 L CNN F 1 "0.1 uF" H 2910 1570 50 0000 L CNN -F 2 "" H 2900 1650 50 0001 C CNN +F 2 "Capacitor_SMD:C_0402_1005Metric" H 2900 1650 50 0001 C CNN F 3 "~" H 2900 1650 50 0001 C CNN +F 4 "1276-6720-1-ND" H 2900 1650 50 0001 C CNN "Digikey" 1 2900 1650 0 -1 -1 0 $EndComp @@ -1248,8 +1271,9 @@ U 1 1 6016D10C P 3600 5550 F 0 "C?" H 3610 5620 50 0000 L CNN F 1 "47 uF" H 3610 5470 50 0000 L CNN -F 2 "" H 3600 5550 50 0001 C CNN +F 2 "Capacitor_THT:CP_Radial_D5.0mm_P2.00mm" H 3600 5550 50 0001 C CNN F 3 "~" H 3600 5550 50 0001 C CNN +F 4 "732-8596-1-ND" H 3600 5550 50 0001 C CNN "Digikey" 1 3600 5550 0 -1 -1 0 $EndComp @@ -1285,8 +1309,9 @@ U 1 1 60192054 P 7650 3350 F 0 "R?" H 7680 3370 50 0000 L CNN F 1 "10kR" H 7680 3310 50 0000 L CNN -F 2 "" H 7650 3350 50 0001 C CNN +F 2 "Resistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolder" H 7650 3350 50 0001 C CNN F 3 "~" H 7650 3350 50 0001 C CNN +F 4 "RNCP0603FTD10K0CT-ND" H 7650 3350 50 0001 C CNN "Digikey" 1 7650 3350 -1 0 0 -1 $EndComp @@ -1300,8 +1325,9 @@ U 1 1 60198AF9 P 7800 3350 F 0 "R?" H 7830 3370 50 0000 L CNN F 1 "10kR" H 7830 3310 50 0000 L CNN -F 2 "" H 7800 3350 50 0001 C CNN +F 2 "Resistor_SMD:R_0603_1608Metric_Pad0.98x0.95mm_HandSolder" H 7800 3350 50 0001 C CNN F 3 "~" H 7800 3350 50 0001 C CNN +F 4 "RNCP0603FTD10K0CT-ND" H 7800 3350 50 0001 C CNN "Digikey" 1 7800 3350 1 0 0 -1 $EndComp @@ -1329,8 +1355,9 @@ U 1 1 601CC9F4 P 7800 3900 F 0 "C?" H 7810 3970 50 0000 L CNN F 1 "1 nF" H 7810 3820 50 0000 L CNN -F 2 "" H 7800 3900 50 0001 C CNN +F 2 "Capacitor_SMD:C_0402_1005Metric" H 7800 3900 50 0001 C CNN F 3 "~" H 7800 3900 50 0001 C CNN +F 4 "478-1101-1-ND" H 7800 3900 50 0001 C CNN "Digikey" 1 7800 3900 1 0 0 -1 $EndComp diff --git a/controller-board/gen_digikey.py b/controller-board/gen_digikey.py new file mode 100644 index 0000000..afb49aa --- /dev/null +++ b/controller-board/gen_digikey.py @@ -0,0 +1,54 @@ +import argparse +import csv + +def main(): + parser = argparse.ArgumentParser(description="Generates a Digikey BOM from Joost's KiCAD BOM") + parser.add_argument("input", help="input BOM") + parser.add_argument("output", help="output BOM") + parser.add_argument("--m", help="quantity multiplier", default="1") + + args = parser.parse_args() + multiplier = 1 if args.m is None else int(args.m) + + print("multiplier: {}".format(multiplier)) + + digikey_idx = None + count_idx = None + components = [] + with open(args.input, 'r') as f: + reader = csv.reader(f) + parts = next(reader) + for i, part in enumerate(parts): + if 'Digikey' == part: + digikey_idx = i + elif 'Quantity' == part: + count_idx = i + + if digikey_idx is None or count_idx is None: + print("Unable to find headers") + return + + for i, parts in enumerate(reader): + if len(parts) < max(digikey_idx, count_idx): + print("[WARN] line {} is missing fields".format(i + 1)) + continue + part_num = parts[digikey_idx] + count = parts[count_idx] + if len(part_num) == 0: + print("[WARN] line {} is missing part number".format(i + 1)) + print(parts) + continue + if count == 0: + print("[WARN] line {} has zero quantity".format(i + 1)) + if part_num == 'NoPart': + continue + components.append((part_num, count)) + + with open(args.output, 'w') as f: + # header is necessary if you don't want to miss the first part + f.write("Digikey,Quantity\n") + for c in components: + f.write("{},{}\n".format(c[0], multiplier*int(c[1]))) + +if __name__ == "__main__": + main()