diff --git a/KiBOM/bom_writer.py b/KiBOM/bom_writer.py index cba9fb76..a23ae0a5 100644 --- a/KiBOM/bom_writer.py +++ b/KiBOM/bom_writer.py @@ -1,7 +1,11 @@ -import csv +from csv_writer import WriteCSV +from xml_writer import WriteXML +from html_writer import WriteHTML + import columns from component import * from xml.etree import ElementTree +from preferences import BomPref import os, shutil @@ -13,189 +17,59 @@ def TmpFileCopy(filename): if os.path.exists(filename) and os.path.isfile(filename): shutil.copyfile(filename, filename + ".tmp") -def link(text): - text = str(text) - for t in ["http","https","ftp","www"]: - if text.startswith(t): - return '{t}'.format(t=text) - - return text - -def WriteXML(filename, groups, source, version, date, headings = columns.ColumnList._COLUMNS_ALL, ignore=[], ignoreDNF = False): +""" +Write BoM to file +filename = output file path +groups = [list of ComponentGroup groups] +headings = [list of headings to display in the BoM file] +prefs = BomPref object +""" +def WriteBoM(filename, groups, net, headings = columns.ColumnList._COLUMNS_DEFAULT, prefs=None): filename = os.path.abspath(filename) - if not filename.endswith(".xml"): - return False - - headings = [h for h in headings if h not in ignore] - - try: - TmpFileCopy(filename) - - xml = ElementTree.Element('"KiCAD Bom"', attrib = { - '"source"' : source, - '"version"' : version, - '"date"' : date, - '"groups"' : str(len(groups)), - '"components"' : str(sum([group.getCount() for group in groups])) - }) - - for group in groups: - row = group.getKicadRow(headings) - - attrib = {} - - for i,h in enumerate(headings): - attrib['"' + h + '"'] = row[i] - - sub = ElementTree.SubElement(xml, "group", attrib=attrib) - - #write the document - tree = ElementTree.ElementTree(xml) - - tree.write(filename) - - return True - - except BaseException as e: - print(str(e)) - return False - - return True + #no preferences supplied, use defaults + if not prefs: + prefs = BomPref() -def WriteHTML(filename, groups, net, headings = columns.ColumnList._COLUMNS_ALL, ignore=[], ignoreDNF=False, numberRows=True): - - filename = os.path.abspath(filename) + #remove any headings that appear in the ignore[] list + headings = [h for h in headings if not h.lower() in [i.lower() for i in prefs.ignore]] - headings = [h for h in headings if not h in ignore] + #make a temporary copy of the output file + TmpFileCopy(filename) - try: - TmpFileCopy(filename) - - with open(filename,"w") as html: - - #header - html.write("\n") - html.write("\n") - - #PCB info - html.write("

PCB Information

\n") - html.write("
Source File: {source}\n".format(source=net.getSource())) - html.write("
Date: {date}\n".format(date=net.getDate())) - html.write("
Version: {version}\n".format(version=net.getVersion())) - html.write("
\n") - html.write("

Component Groups

\n") - - #component groups - html.write('\n') - - #row titles: - html.write("\n") - if numberRows: - html.write("\t\n") - for h in headings: - html.write("\t\n".format(h=h)) - html.write("\n") - - rowCount = 0 - - for i,group in enumerate(groups): - - if ignoreDNF and not group.isFitted(): continue - - row = group.getKicadRow(headings) - - rowCount += 1 - - if numberRows: - row = [rowCount] + row - - html.write("\n") - - for r in row: - html.write("\t\n".format(val=link(r))) - - - html.write("\n") - - html.write("
{h}
{val}
\n") - - html.write("") - - - except BaseException as e: - print(str(e)) - return False - - return True + #if no extension is given, assume .csv (and append!) - - - -def WriteCSV(filename, groups, source, version, date, headings = columns.ColumnList._COLUMNS_ALL, ignore=[], ignoreDNF=False, numberRows=True): + if len(filename.split('.')) < 2: + filename += ".csv" - filename = os.path.abspath(filename) + ext = filename.split('.')[-1].lower() - #delimeter is assumed from file extension - if filename.endswith(".csv"): - delimiter = "," - elif filename.endswith(".tsv") or filename.endswith(".txt"): - delimiter = "\t" + result = False + + #CSV file writing + if ext in ["csv","csv","txt"]: + if WriteCSV(filename, groups, net, headings, prefs): + print("CSV Output -> {fn}".format(fn=filename)) + result = True + else: + print("Error writing CSV output") + + elif ext in ["htm","html"]: + if WriteHTML(filename, groups, net, headings, prefs): + print("HTML Output -> {fn}".format(fn=filename)) + result = True + else: + print("Error writing HTML output") + + elif ext in ["xml"]: + if WriteXML(filename, groups, net, headings, prefs): + print("XML Output -> {fn}".format(fn=filename)) + result = True + else: + print("Error writing XML output") + else: - return False - - headings = [h for h in headings if h not in ignore] #copy across the headings - - try: - #make a copy of the file - TmpFileCopy(filename) - - with open(filename, "w") as f: - - writer = csv.writer(f, delimiter=delimiter, lineterminator="\n") + print("Unsupported file extension: {ext}".format(ext=ext)) - if numberRows: - writer.writerow(["Component"] + headings) - else: - writer.writerow(headings) - - count = 0 - rowCount = 1 - - for i, group in enumerate(groups): - if ignoreDNF and not group.isFitted(): continue - - row = group.getKicadRow(headings) - - if numberRows: - row = [rowCount] + row - - writer.writerow(row) - - try: - count += group.getCount() - except: - pass - - rowCount += 1 - - #blank rows - for i in range(5): - writer.writerow([]) - - writer.writerow(["Component Count:",componentCount]) - writer.writerow(["Component Groups:",len(groups)]) - writer.writerow(["Source:",source]) - writer.writerow(["Version:",version]) - writer.writerow(["Date:",date]) - - return True - - - except BaseException as e: - print(str(e)) - return False - - return True - \ No newline at end of file + return result \ No newline at end of file diff --git a/KiBOM/csv_writer.py b/KiBOM/csv_writer.py new file mode 100644 index 00000000..c2f4b82a --- /dev/null +++ b/KiBOM/csv_writer.py @@ -0,0 +1,76 @@ +import csv +import columns +from component import * +import os, shutil +from preferences import BomPref + +""" +Write BoM out to a CSV file +filename = path to output file (must be a .csv, .txt or .tsv file) +groups = [list of ComponentGroup groups] +net = netlist object +headings = [list of headings to display in the BoM file] +prefs = BomPref object +""" + +def WriteCSV(filename, groups, net, headings, prefs): + + filename = os.path.abspath(filename) + + #delimeter is assumed from file extension + if filename.endswith(".csv"): + delimiter = "," + elif filename.endswith(".tsv") or filename.endswith(".txt"): + delimiter = "\t" + else: + return False + + try: + + with open(filename, "w") as f: + + writer = csv.writer(f, delimiter=delimiter, lineterminator="\n") + + if prefs.numberRows: + writer.writerow(["Component"] + headings) + else: + writer.writerow(headings) + + count = 0 + rowCount = 1 + + for i, group in enumerate(groups): + if prefs.ignoreDNF and not group.isFitted(): continue + + row = group.getRow(headings) + + if prefs.numberRows: + row = [rowCount] + row + + writer.writerow(row) + + try: + count += group.getCount() + except: + pass + + rowCount += 1 + + #blank rows + for i in range(5): + writer.writerow([]) + + writer.writerow(["Component Count:",sum([g.getCount() for g in groups])]) + writer.writerow(["Component Groups:",len(groups)]) + writer.writerow(["Source:",net.getSource()]) + writer.writerow(["Version:",net.getVersion()]) + writer.writerow(["Date:",net.getDate()]) + + return True + + + except BaseException as e: + print(str(e)) + return False + + return True \ No newline at end of file diff --git a/KiBOM/html_writer.py b/KiBOM/html_writer.py new file mode 100644 index 00000000..b2a70225 --- /dev/null +++ b/KiBOM/html_writer.py @@ -0,0 +1,114 @@ +import columns +from component import * +import os + +BG_GEN = "#E6FFEE" +BG_KICAD = "#FFE6B3" +BG_USER = "#E6F9FF" + +#return a background color for a given column title +def bgColor(col): + #auto-generated columns + if col == ColumnList.COL_GRP_QUANTITY: + return BG_GEN + #kicad protected columns + elif col in ColumnList._COLUMNS_PROTECTED: + return BG_KICAD + #additional user columns + else: + return BG_USER + +def link(text): + text = str(text) + for t in ["http","https","ftp","www"]: + if text.startswith(t): + return '{t}'.format(t=text) + + return text + +""" +Write BoM out to a HTML file +filename = path to output file (must be a .htm or .html file) +groups = [list of ComponentGroup groups] +net = netlist object +headings = [list of headings to display in the BoM file] +prefs = BomPref object +""" + +def WriteHTML(filename, groups, net, headings, prefs): + + if not filename.endswith(".html") and not filename.endswith(".htm"): + print("{fn} is not a valid html file".format(fn=filename)) + return False + + try: + + with open(filename,"w") as html: + + #header + html.write("\n") + html.write("\n") + + #PCB info + html.write("

KiBOM PCB Bill of Materials

\n") + html.write("
Source File: {source}\n".format(source=net.getSource())) + html.write("
Date: {date}\n".format(date=net.getDate())) + html.write("
Schematic Version: {version}\n".format(version=net.getVersion())) + html.write("
Total Components: {n}\n".format(n = sum([g.getCount() for g in groups]))) + html.write("
Component Groups: {n}\n".format(n=len(groups))) + html.write("
\n") + html.write("

Component Groups

\n") + html.write('

Kicad Fields (default)

\n'.format(bg=BG_KICAD)) + html.write('

Generated Fields

\n'.format(bg=BG_GEN)) + html.write('

User Fields

\n'.format(bg=BG_USER)) + + #component groups + html.write('\n') + + #row titles: + html.write("\n") + if prefs.numberRows: + html.write("\t\n") + for i,h in enumerate(headings): + #cell background color + bg = bgColor(h) + html.write('\t\n'.format( + h=h, + bg = ' bgcolor="{c}"'.format(c=bg) if bg else '')) + html.write("\n") + + rowCount = 0 + + for i,group in enumerate(groups): + + if prefs.ignoreDNF and not group.isFitted(): continue + + row = group.getRow(headings) + + rowCount += 1 + + + html.write("\n") + + if prefs.numberRows: + html.write("\t\n".format(n=rowCount)) + + for n, r in enumerate(row): + bg = bgColor(headings[n]) + + html.write('\t\n'.format(bg=' bgcolor={c}'.format(c=bg) if bg else '', val=link(r))) + + + html.write("\n") + + html.write("
{h}
{n}{val}
\n") + html.write("

\n") + + html.write("") + + + except BaseException as e: + print(str(e)) + return False + + return True \ No newline at end of file diff --git a/KiBOM/xml_writer.py b/KiBOM/xml_writer.py new file mode 100644 index 00000000..1333ba37 --- /dev/null +++ b/KiBOM/xml_writer.py @@ -0,0 +1,58 @@ +import columns +from component import * +from xml.etree import ElementTree +from xml.dom import minidom +from preferences import BomPref + +""" +Write BoM out to an XML file +filename = path to output file (must be a .xml) +groups = [list of ComponentGroup groups] +net = netlist object +headings = [list of headings to display in the BoM file] +prefs = BomPref object +""" + +def WriteXML(filename, groups, net, headings, prefs): + + if not filename.endswith(".xml"): + return False + + try: + xml = ElementTree.Element('KiCAD_BOM', attrib = { + 'source' : net.getSource(), + 'version' : net.getVersion(), + 'date' : net.getDate(), + 'groups' : str(len(groups)), + 'components' : str(sum([group.getCount() for group in groups])) + }) + + for group in groups: + if prefs.ignoreDNF and not group.isFitted(): + continue + row = group.getRow(headings) + + attrib = {} + + for i,h in enumerate(headings): + h = h.replace(' ','_') #replace spaces, xml no likey + h = h.replace('"','') + h = h.replace("'",'') + + attrib[h] = row[i] + + sub = ElementTree.SubElement(xml, "group", attrib=attrib) + + with open(filename,"w") as output: + out = ElementTree.tostring(xml, 'utf-8') + + output.write(minidom.parseString(out).toprettyxml(indent="\t")) + + return True + + except BaseException as e: + print(str(e)) + return False + + return True +