diff --git a/KiBOM/__init__.py b/KiBOM/__init__.py
index ba0fc5e2..fb79574d 100644
--- a/KiBOM/__init__.py
+++ b/KiBOM/__init__.py
@@ -2,4 +2,6 @@ import columns
import netlist_reader
import units
import component
-import sort
\ No newline at end of file
+import sort
+import preferences
+import bom_writer
\ No newline at end of file
diff --git a/KiBOM/columns.py b/KiBOM/columns.py
index 8cceefd3..d6d803f6 100644
--- a/KiBOM/columns.py
+++ b/KiBOM/columns.py
@@ -12,6 +12,14 @@ class ColumnList:
#default columns for groups
COL_GRP_QUANTITY = 'Quantity'
+ COL_GRP_TOTAL_COST = 'Total Cost' #Total cost based on quantity
+ COL_GRP_BUILD_QUANTITY = 'Build Quantity'
+
+ #generated columns
+ _COLUMNS_GEN = [
+ COL_GRP_QUANTITY,
+ COL_GRP_BUILD_QUANTITY,
+ ]
#default columns
_COLUMNS_DEFAULT = [
@@ -23,6 +31,7 @@ class ColumnList:
COL_FP,
COL_FP_LIB,
COL_GRP_QUANTITY,
+ COL_GRP_BUILD_QUANTITY,
COL_DATASHEET
]
@@ -71,7 +80,7 @@ class ColumnList:
def RemoveColumnByName(self, name):
#first check if this is in an immutable colum
- if name in self._COLUMNS_DEFAULT:
+ if name in self._COLUMNS_PROTECTED:
return
#column does not exist, return
diff --git a/KiBOM/component.py b/KiBOM/component.py
index d01f462a..2a7526b6 100644
--- a/KiBOM/component.py
+++ b/KiBOM/component.py
@@ -182,7 +182,7 @@ class ComponentGroup():
def getField(self, field):
if not field in self.fields.keys(): return ""
if not self.fields[field]: return ""
- return str(self.fields[field])
+ return u''.join((self.fields[field]))
def getCount(self):
return len(self.components)
@@ -255,9 +255,12 @@ class ComponentGroup():
#update 'global' fields
self.fields[ColumnList.COL_REFERENCE] = self.getRefs()
+
+ q = self.getCount()
self.fields[ColumnList.COL_GRP_QUANTITY] = "{n}{dnf}".format(
- n = self.getCount(),
+ n=q,
dnf = " (DNF)" if not self.isFitted() else "")
+ self.fields[ColumnList.COL_GRP_BUILD_QUANTITY] = str(q * self.prefs.buildNumber) if self.isFitted() else "0"
self.fields[ColumnList.COL_VALUE] = self.components[0].getValue()
self.fields[ColumnList.COL_PART] = self.components[0].getPartName()
self.fields[ColumnList.COL_PART_LIB] = self.components[0].getLibName()
@@ -303,7 +306,18 @@ class ComponentGroup():
#return a dict of the KiCAD data based on the supplied columns
+ #NOW WITH UNICODE SUPPORT!
def getRow(self, columns):
- row = [self.getField(key) for key in columns]
- #print(row)
+ row = []
+ for key in columns:
+ val = self.getField(key)
+
+ if val is None:
+ val = ""
+ else:
+ val = u'' + val
+ val = val.encode('utf-8')
+
+ row.append(val)
+
return row
\ No newline at end of file
diff --git a/KiBOM/csv_writer.py b/KiBOM/csv_writer.py
index edad802b..5dfc581d 100644
--- a/KiBOM/csv_writer.py
+++ b/KiBOM/csv_writer.py
@@ -1,3 +1,5 @@
+# _*_ coding:latin-1 _*_
+
import csv
import columns
from component import *
@@ -25,6 +27,11 @@ def WriteCSV(filename, groups, net, headings, prefs):
else:
return False
+ nGroups = len(groups)
+ nTotal = sum([g.getCount() for g in groups])
+ nFitted = sum([g.getCount() for g in groups if g.isFitted()])
+ nBuild = nFitted * prefs.buildNumber
+
with open(filename, "w") as f:
writer = csv.writer(f, delimiter=delimiter, lineterminator="\n")
@@ -43,8 +50,10 @@ def WriteCSV(filename, groups, net, headings, prefs):
row = group.getRow(headings)
if prefs.numberRows:
- row = [rowCount] + row
+ row = [str(rowCount)] + row
+ #deal with unicode characters
+ #row = [el.decode('latin-1') for el in row]
writer.writerow(row)
try:
@@ -58,8 +67,12 @@ def WriteCSV(filename, groups, net, headings, prefs):
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(["Component Groups:",nGroups])
+ writer.writerow(["Component Count:",nTotal])
+ writer.writerow(["Fitted Components:", nFitted])
+ if prefs.buildNumber > 0:
+ writer.writerow(["Number of PCBs:",prefs.buildNumber])
+ writer.writerow(["Total components:", nBuild])
writer.writerow(["Schematic Version:",net.getVersion()])
writer.writerow(["Schematic Date:",net.getSheetDate()])
writer.writerow(["BoM Date:",net.getDate()])
diff --git a/KiBOM/html_writer.py b/KiBOM/html_writer.py
index 367e7c34..4c2a561a 100644
--- a/KiBOM/html_writer.py
+++ b/KiBOM/html_writer.py
@@ -1,3 +1,4 @@
+
import columns
from component import *
import os
@@ -9,7 +10,7 @@ BG_USER = "#E6F9FF"
#return a background color for a given column title
def bgColor(col):
#auto-generated columns
- if col == ColumnList.COL_GRP_QUANTITY:
+ if col in ColumnList._COLUMNS_GEN:
return BG_GEN
#kicad protected columns
elif col in ColumnList._COLUMNS_PROTECTED:
@@ -19,7 +20,7 @@ def bgColor(col):
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)
@@ -41,12 +42,21 @@ def WriteHTML(filename, groups, net, headings, prefs):
print("{fn} is not a valid html file".format(fn=filename))
return False
+ nGroups = len(groups)
+ nTotal = sum([g.getCount() for g in groups])
+ nFitted = sum([g.getCount() for g in groups if g.isFitted()])
+ nBuild = nFitted * prefs.buildNumber
+
with open(filename,"w") as html:
#header
html.write("\n")
+ html.write("
\n")
+ html.write('\t\n') #UTF-8 encoding for unicode support
+ html.write("\n")
html.write("\n")
+
#PCB info
html.write("KiBoM PCB Bill of Materials
\n")
html.write('\n')
@@ -55,8 +65,12 @@ def WriteHTML(filename, groups, net, headings, prefs):
html.write("| Schematic Version | {version} |
\n".format(version=net.getVersion()))
html.write("| Schematic Date | {date} |
\n".format(date=net.getSheetDate()))
html.write("| KiCad Version | {version} |
\n".format(version=net.getTool()))
- 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("| Component Groups | {n} |
\n".format(n=nGroups))
+ html.write("| Component Count (per PCB) | {n} |
\n".format(n=nTotal))
+ html.write("| Fitted Components (per PCB) | {n} |
\n".format(n=nFitted))
+ if prefs.buildNumber > 0:
+ html.write("| Number of PCBs | {n} |
\n".format(n=prefs.buildNumber))
+ html.write("Total Component Count (for {n} PCBs) | {t} |
\n".format(n=prefs.buildNumber, t=nBuild))
html.write("
\n")
html.write("
\n")
html.write("Component Groups
\n")
@@ -93,7 +107,7 @@ def WriteHTML(filename, groups, net, headings, prefs):
html.write("\n")
if prefs.numberRows:
- html.write('\t| {n} | '.format(n=rowCount))
+ html.write('\t{n} | \n'.format(n=rowCount))
for n, r in enumerate(row):
bg = bgColor(headings[n])
diff --git a/KiBOM/preferences.py b/KiBOM/preferences.py
index d356e32e..9350a781 100644
--- a/KiBOM/preferences.py
+++ b/KiBOM/preferences.py
@@ -18,12 +18,15 @@ class BomPref:
SECTION_EXCLUDE_PART = "EXCLUDE_COMPONENT_PART"
SECTION_EXCLUDE_DESC = "EXCLUDE_COMPONENT_DESC"
SECTION_ALIASES = "COMPONENT_ALIASES"
+ SECTION_CONFIGURATIONS = "PCB_CONFIGURATIONS"
OPT_IGNORE_DNF = "ignore_dnf"
OPT_NUMBER_ROWS = "number_rows"
OPT_GROUP_CONN = "group_connectors"
OPT_USE_REGEX = "test_regex"
OPT_COMP_FP = "compare_footprints"
+ OPT_INC_PRICE = "calculate_price"
+ OPT_BUILD_NUMBER = 'build_quantity'
#list of columns which we can use regex on
COL_REG_EX = [
@@ -46,6 +49,9 @@ class BomPref:
self.groupConnectors = True #group connectors and ignore component value
self.useRegex = True #Test various columns with regex
self.compareFootprints = True #test footprints when comparing components
+ self.buildNumber = 0
+ self.verbose = False #by default, is not verbose
+ self.configurations = [] #list of various configurations
#default reference exclusions
self.excluded_references = [
@@ -71,7 +77,9 @@ class BomPref:
["c", "c_small", "cap", "capacitor"],
["r", "r_small", "res", "resistor"],
["sw", "switch"],
- ["l", "l_small", "inductor"]
+ ["l", "l_small", "inductor"],
+ ["zener","zenersmall"],
+ ["d","diode","d_small"]
]
#dictionary of possible regex expressions for ignoring component row(s)
@@ -97,6 +105,13 @@ class BomPref:
def columnToGroup(self, col):
return "REGEXCLUDE_" + col.upper().replace(" ","_")
+ #check an option within the SECTION_GENERAL group
+ def checkOption(self, parser, opt, default=False):
+ if parser.has_option(self.SECTION_GENERAL, opt):
+ return parser.get(self.SECTION_GENERAL, opt).lower() in ["1","true","yes"]
+ else:
+ return default
+
#read KiBOM preferences from file
def Read(self, file, verbose=False):
file = os.path.abspath(file)
@@ -111,16 +126,23 @@ class BomPref:
#read general options
if self.SECTION_GENERAL in cf.sections():
- if cf.has_option(self.SECTION_GENERAL, self.OPT_IGNORE_DNF):
- self.ignoreDNF = cf.get(self.SECTION_GENERAL, self.OPT_IGNORE_DNF) == "1"
- if cf.has_option(self.SECTION_GENERAL, self.OPT_NUMBER_ROWS):
- self.numberRows = cf.get(self.SECTION_GENERAL, self.OPT_NUMBER_ROWS) == "1"
- if cf.has_option(self.SECTION_GENERAL, self.OPT_GROUP_CONN):
- self.groupConnectors = cf.get(self.SECTION_GENERAL, self.OPT_GROUP_CONN) == "1"
- if cf.has_option(self.SECTION_GENERAL, self.OPT_USE_REGEX):
- self.useRegex = cf.get(self.SECTION_GENERAL, self.OPT_USE_REGEX) == "1"
- if cf.has_option(self.SECTION_GENERAL, self.OPT_COMP_FP):
- self.compareFootprints = cf.get(self.SECTION_GENERAL, self.OPT_COMP_FP) == "1"
+ self.ignoreDNF = self.checkOption(cf, self.OPT_IGNORE_DNF, default=True)
+ self.numberRows = self.checkOption(cf, self.OPT_NUMBER_ROWS, default=True)
+ self.groupConnectors = self.checkOption(cf, self.OPT_GROUP_CONN, default=True)
+ self.useRegex = self.checkOption(cf, self.OPT_USE_REGEX, default=True)
+ self.compareFootprints = self.checkOption(cf, self.OPT_COMP_FP, default=True)
+
+ if cf.has_option(self.SECTION_GENERAL, self.OPT_BUILD_NUMBER):
+ try:
+ self.buildNumber = int(cf.get(self.SECTION_GENERAL, self.OPT_BUILD_NUMBER))
+ if self.buildNumber < 1:
+ self.buildNumber = 0
+ except:
+ pass
+
+ #read out configurations
+ if self.SECTION_CONFIGURATIONS in cf.sections():
+ self.configurations = [i for i in cf.options(self.SECTION_CONFIGURATIONS)]
#read out ignored-rows
if self.SECTION_IGNORE in cf.sections():
@@ -136,7 +158,14 @@ class BomPref:
if section in cf.sections():
self.regex[key] = [r for r in cf.options(section)]
-
+
+ #add an option to the SECTION_GENRAL group
+ def addOption(self, parser, opt, value, comment=None):
+ if comment:
+ if not comment.startswith(";"):
+ comment = "; " + comment
+ parser.set(self.SECTION_GENERAL, comment)
+ parser.set(self.SECTION_GENERAL, opt, "1" if value else "0")
#write KiBOM preferences to file
def Write(self, file):
@@ -146,16 +175,12 @@ class BomPref:
cf.add_section(self.SECTION_GENERAL)
cf.set(self.SECTION_GENERAL, "; General BoM options here")
- cf.set(self.SECTION_GENERAL, "; If '{opt}' option is set to 1, rows that are not to be fitted on the PCB will not be written to the BoM file".format(opt=self.OPT_IGNORE_DNF))
- cf.set(self.SECTION_GENERAL, self.OPT_IGNORE_DNF, 1 if self.ignoreDNF else 0)
- cf.set(self.SECTION_GENERAL, "; If '{opt}' option is set to 1, each row in the BoM will be prepended with an incrementing row number".format(opt=self.OPT_NUMBER_ROWS))
- cf.set(self.SECTION_GENERAL, self.OPT_NUMBER_ROWS, 1 if self.numberRows else 0)
- cf.set(self.SECTION_GENERAL, "; If '{opt}' option is set to 1, connectors with the same footprints will be grouped together, independent of the name of the connector".format(opt=self.OPT_GROUP_CONN))
- cf.set(self.SECTION_GENERAL, self.OPT_GROUP_CONN, 1 if self.groupConnectors else 0)
- cf.set(self.SECTION_GENERAL, "; If '{opt}' option is set to 1, each component group will be tested against a number of regular-expressions (specified, per column, below). If any matches are found, the row is ignored in the output file".format(opt=self.OPT_USE_REGEX))
- cf.set(self.SECTION_GENERAL, self.OPT_USE_REGEX, 1 if self.useRegex else 0)
- cf.set(self.SECTION_GENERAL, "; If '{opt}' option is set to 1, two components must have the same footprint to be grouped together. If '{opt}' is not set, then footprint comparison is ignored.".format(opt=self.OPT_COMP_FP))
- cf.set(self.SECTION_GENERAL, self.OPT_COMP_FP, 1 if self.compareFootprints else 0)
+ self.addOption(cf, self.OPT_IGNORE_DNF, self.ignoreDNF, comment="If '{opt}' option is set to 1, rows that are not to be fitted on the PCB will not be written to the BoM file".format(opt=self.OPT_IGNORE_DNF))
+ self.addOption(cf, self.OPT_NUMBER_ROWS, self.numberRows, comment="If '{opt}' option is set to 1, each row in the BoM will be prepended with an incrementing row number".format(opt=self.OPT_NUMBER_ROWS))
+ self.addOption(cf, self.OPT_GROUP_CONN, self.groupConnectors, comment="If '{opt}' option is set to 1, connectors with the same footprints will be grouped together, independent of the name of the connector".format(opt=self.OPT_GROUP_CONN))
+ self.addOption(cf, self.OPT_USE_REGEX, self.useRegex, comment="If '{opt}' option is set to 1, each component group will be tested against a number of regular-expressions (specified, per column, below). If any matches are found, the row is ignored in the output file".format(opt=self.OPT_USE_REGEX))
+ self.addOption(cf, self.OPT_COMP_FP, self.compareFootprints, comment="If '{opt}' option is set to 1, two components must have the same footprint to be grouped together. If '{opt}' is not set, then footprint comparison is ignored.".format(opt=self.OPT_COMP_FP))
+ self.addOption(cf, self.OPT_BUILD_NUMBER, self.buildNumber, comment="; '{opt}' is the number of boards to build, which is used to calculate total parts quantity. If this is set to zero (0) then it is ignored".format(opt=self.OPT_BUILD_NUMBER))
cf.add_section(self.SECTION_IGNORE)
cf.set(self.SECTION_IGNORE, "; Any column heading that appears here will be excluded from the Generated BoM")
@@ -164,6 +189,11 @@ class BomPref:
for i in self.ignore:
cf.set(self.SECTION_IGNORE, i)
+ cf.add_section(self.SECTION_CONFIGURATIONS)
+ cf.set(self.SECTION_CONFIGURATION, '; List of PCB configuration parameters')
+ for i in self.configurations:
+ cf.set(self.SECTION_CONFIGURATIONS, i)
+
cf.add_section(self.SECTION_ALIASES)
cf.set(self.SECTION_ALIASES, "; A series of values which are considered to be equivalent for the part name")
cf.set(self.SECTION_ALIASES, "; Each line represents a space-separated list of equivalent component name values")
diff --git a/KiBOM/units.py b/KiBOM/units.py
index b8f63c45..8ad4f1c8 100644
--- a/KiBOM/units.py
+++ b/KiBOM/units.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# _*_ coding:utf-8 _*_
"""
diff --git a/KiBOM/xml_writer.py b/KiBOM/xml_writer.py
index 58f1c1db..5ffe418c 100644
--- a/KiBOM/xml_writer.py
+++ b/KiBOM/xml_writer.py
@@ -18,19 +18,32 @@ def WriteXML(filename, groups, net, headings, prefs):
if not filename.endswith(".xml"):
return False
- xml = ElementTree.Element('KiCAD_BOM', attrib = {
- 'Schematic_Source' : net.getSource(),
- 'Schematic_Version' : net.getVersion(),
- 'Schematic_Date' : net.getSheetDate(),
- 'BOM_Date' : net.getDate(),
- 'KiCad_Version' : net.getTool(),
- 'groups' : str(len(groups)),
- 'components' : str(sum([group.getCount() for group in groups]))
- })
+ nGroups = len(groups)
+ nTotal = sum([g.getCount() for g in groups])
+ nFitted = sum([g.getCount() for g in groups if g.isFitted()])
+ nBuild = nFitted * prefs.buildNumber
+
+ attrib = {}
+
+ attrib['Schematic_Source'] = net.getSource()
+ attrib['Schematic_Version'] = net.getVersion()
+ attrib['Schematic_Date'] = net.getSheetDate()
+ attrib['BOM_Date'] = net.getDate()
+ attrib['KiCad_Version'] = net.getTool()
+ attrib['Component_Groups'] = str(nGroups)
+ attrib['Component_Count'] = str(nTotal)
+ attrib['Fitted_Components'] = str(nFitted)
+
+ if prefs.buildNumber > 0:
+ attrib['Number_of_PCBs'] = str(prefs.buildNumber)
+ attrib['Total_Components'] = str(nBuild)
+
+ xml = ElementTree.Element('KiCAD_BOM', attrib = attrib, encoding='utf-8')
for group in groups:
if prefs.ignoreDNF and not group.isFitted():
continue
+
row = group.getRow(headings)
attrib = {}
@@ -40,13 +53,12 @@ def WriteXML(filename, groups, net, headings, prefs):
h = h.replace('"','')
h = h.replace("'",'')
- attrib[h] = row[i]
+ attrib[h] = str(row[i]).decode('ascii',errors='ignore')
sub = ElementTree.SubElement(xml, "group", attrib=attrib)
with open(filename,"w") as output:
- out = ElementTree.tostring(xml, 'utf-8')
-
+ out = ElementTree.tostring(xml, encoding="utf-8")
output.write(minidom.parseString(out).toprettyxml(indent="\t"))
return True
diff --git a/KiBOM_CLI.py b/KiBOM_CLI.py
index fcc0eb3f..9d2fabac 100644
--- a/KiBOM_CLI.py
+++ b/KiBOM_CLI.py
@@ -6,67 +6,91 @@ import sys
import os
import shutil
+import argparse
+
here = os.path.abspath(os.path.dirname(sys.argv[0]))
sys.path.append(here)
+sys.path.append(os.path.join(here,"KiBOM"))
from KiBOM.columns import ColumnList
from KiBOM.netlist_reader import *
from KiBOM.bom_writer import *
from KiBOM.preferences import BomPref
+verbose = False
+
def close(*arg):
print(*arg)
sys.exit(0)
-if len(sys.argv) < 2:
- close("No input file supplied")
+def say(*arg):
+ if verbose:
+ print(*arg)
-input_file = sys.argv[1].replace("\\",os.path.sep).replace("/",os.path.sep)
+parser = argparse.ArgumentParser(description="KiBOM Bill of Materials generator script")
-input_file = os.path.abspath(input_file)
+parser.add_argument("netlist", help='xml netlist file. Use "%%I" when running from within KiCad')
+parser.add_argument("--output", "-o", help='BoM output file name.\nUse "%%O" when running from within KiCad to use the default output name (csv file).\nFor e.g. HTML output, use "%%O.html"\r\nIf output file is unspecified, default output filename (csv format) will be used', default=None)
+
+parser.add_argument("-v", "--verbose", help="Enable verbose output", action='count')
+parser.add_argument("--cfg", help="BoM config file (script will try to use 'bom.ini' if not specified here)")
+
+args = parser.parse_args()
+input_file = args.netlist
+
if not input_file.endswith(".xml"):
- close("Supplied file is not .xml")
-
-#work out an output file
-ext = ".csv"
-
-if len(sys.argv) < 3:
- #no output file supplied, assume .csv
- output_file = input_file.replace(".xml",".csv")
-else:
- output_file = sys.argv[2].replace("\\",os.path.sep).replace("/",os.path.sep)
+ close("{i} is not a valid xml file".format(i=input_file))
- valid = False
+verbose = args.verbose is not None
- for e in [".xml",".csv",".txt",".tsv",".html"]:
- if output_file.endswith(e):
- valid = True
- ext = e
- break
- if not valid:
- output_file += ext
-
- output_file = os.path.abspath(output_file)
-
-print("Input File: " + input_file)
-print("Output File: " + output_file)
+input_file = os.path.abspath(input_file)
-#preferences
-ignore = []
-ignoreDNF = False
-numberRows = True
+say("Input:",input_file)
-#Look for a '.bom' preference file
-pref_file = os.path.join(os.path.dirname(input_file) , "bom.ini")
+output_file = args.output
+
+if output_file is None:
+ output_file = input_file.replace(".xml","_bom.csv")
+
+#enfore a proper extension
+valid = False
+extensions = [".xml",".csv",".txt",".tsv",".html"]
+for e in extensions:
+ if output_file.endswith(e):
+ valid = True
+ break
+if not valid:
+ close("Extension must be one of",extensions)
+
+output_file = os.path.abspath(output_file)
+
+say("Output:",output_file)
+
+#look for a config file!
+#bom.ini by default
+ini = os.path.abspath(os.path.join(os.path.dirname(input_file), "bom.ini"))
+
+config_file = ini #default value
+#user can overwrite with a specific config file
+if args.cfg:
+ config_file = args.cfg
#read preferences from file. If file does not exists, default preferences will be used
pref = BomPref()
-pref.Read(pref_file)
+
+#verbosity options
+pref.verbose = verbose
+
+if os.path.exists(config_file):
+ pref.Read(config_file)
+ say("Config:",config_file)
#write preference file back out (first run will generate a file with default preferences)
-pref.Write(pref_file)
+if not os.path.exists(ini):
+ pref.Write(ini)
+ say("Writing preferences file bom.ini")
#individual components
components = []
@@ -90,6 +114,10 @@ for g in groups:
for f in g.fields:
columns.AddColumn(f)
+if pref.buildNumber < 1:
+ columns.RemoveColumn(ColumnList.COL_GRP_BUILD_QUANTITY)
+ say("Removing:",ColumnList.COL_GRP_BUILD_QUANTITY)
+
#Finally, write the BoM out to file
result = WriteBoM(output_file, groups, net, columns.columns, pref)
diff --git a/README.md b/README.md
index 8696f0d6..2db7e51c 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,26 @@ KiBoM intelligently groups components based on multiple factors, and can generat
BoM options are user-configurable in a per-project configuration file.
+## Usage
+
+The *KiBOM_CLI* script can be run directly from KiCad or from the command line. For command help, run the script with the *-h* flag e.g.
+
+ python KiBOM_CLI.py -h
+
+
+
+**netlist** The netlist must be provided to the script. When running from KiCAD use "%I"
+
+**--output / -o** If provided, this is the path to the BoM output. If not provided, the script will use the same name as the input file, with the suffix "_bom.csv"
+
+**--cfg** If provided, this is the BOM config file that will be used. If not provided, options will be loaded from "bom.ini"
+
+**--verbose / -v** Enable extra debugging information
+
+To run from KiCad, simply add the same command line in the *Bill of Materials* script window. e.g. to generate a HTML output:
+
+
+
## Features
### Intelligent Component Grouping
@@ -107,7 +127,9 @@ Hit the "Generate" button, and the output window should show that the BoM genera
### HTML Output
The output HTML file is generated as follows:
-
+
+
+
Here the components are correctly grouped, with links to datasheets where appropriate, and fields color-coded.
diff --git a/__init__.py b/__init__.py
index 90b1d514..f234336b 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,3 +1,2 @@
-
-import sys
-sys.path.append(".")
\ No newline at end of file
+import KiBOM
+from KiBOM import *
\ No newline at end of file
diff --git a/example/help.png b/example/help.png
new file mode 100644
index 00000000..8dbb526f
Binary files /dev/null and b/example/help.png differ
diff --git a/example/html_eg.png b/example/html_eg.png
new file mode 100644
index 00000000..15bd8e03
Binary files /dev/null and b/example/html_eg.png differ