diff --git a/KiBOM/component.py b/KiBOM/component.py index 96e0792f..cc127683 100644 --- a/KiBOM/component.py +++ b/KiBOM/component.py @@ -1,5 +1,7 @@ from columns import ColumnList +from preferences import BomPref + import units from sort import natural_sort @@ -12,9 +14,14 @@ class Component(): with accessors. The xmlElement is held in field 'element'. """ - def __init__(self, xml_element): + def __init__(self, xml_element, prefs=None): self.element = xml_element self.libpart = None + + if not prefs: + prefs = BomPref() + + self.prefs = prefs # Set to true when this component is included in a component group self.grouped = False @@ -59,6 +66,11 @@ class Component(): valueResult = self.compareValue(other) + #if connector comparison is overridden, set valueResult to True + if self.prefs.groupConnectors: + if "conn" in self.getDescription().lower(): + valueResult = True + return valueResult and self.compareFootprint(other) and self.compareLibName(other) and self.comparePartName(other) and self.isFitted() == other.isFitted() def setLibPart(self, part): @@ -155,11 +167,16 @@ class ComponentGroup(): """ Initialize the group with no components, and default fields """ - def __init__(self): + def __init__(self, prefs=None): self.components = [] self.fields = dict.fromkeys(ColumnList._COLUMNS_PROTECTED) #columns loaded from KiCAD self.csvFields = dict.fromkeys(ColumnList._COLUMNS_DEFAULT) #columns loaded from .csv file + if not prefs: + prefs = BomPref() + + self.prefs = prefs + def getField(self, field): if not field in self.fields.keys(): return "" if not self.fields[field]: return "" diff --git a/KiBOM/netlist_reader.py b/KiBOM/netlist_reader.py index 5de54ace..83ae9b25 100644 --- a/KiBOM/netlist_reader.py +++ b/KiBOM/netlist_reader.py @@ -24,50 +24,7 @@ import pdb from component import Component, ComponentGroup from sort import natural_sort -#--------------------------------------------------------------------- - -# excluded_fields is a list of regular expressions. If any one matches a field -# from either a component or a libpart, then that will not be included as a -# column in the BOM. Otherwise all columns from all used libparts and components -# will be unionized and will appear. Some fields are impossible to blacklist, such -# as Ref, Value, Footprint, and Datasheet. Additionally Qty and Item are supplied -# unconditionally as columns, and may not be removed. -excluded_fields = [ - #'Price@1000' - ] - - -# You may exlude components from the BOM by either: -# -# 1) adding a custom field named "Installed" to your components and filling it -# with a value of "NU" (Normally Uninstalled). -# See netlist.getInterestingComponents(), or -# -# 2) blacklisting it in any of the three following lists: - - -# regular expressions which match component 'Reference' fields of components that -# are to be excluded from the BOM. -excluded_references = [ - 'TP[0-9]+' # all test points - ] - - -# regular expressions which match component 'Value' fields of components that -# are to be excluded from the BOM. -excluded_values = [ - 'MOUNTHOLE', - 'SCOPETEST', - 'MOUNT_HOLE', - 'SOLDER_BRIDGE.*' - ] - - -# regular expressions which match component 'Footprint' fields of components that -# are to be excluded from the BOM. -excluded_footprints = [ - #'MOUNTHOLE' - ] +from preferences import BomPref # When comparing part names, components will match if they are both elements of the # same set defined here @@ -318,7 +275,7 @@ class netlist(): scripts """ - def __init__(self, fname=""): + def __init__(self, fname="", prefs=None): """Initialiser for the genericNetlist class Keywords: @@ -335,6 +292,11 @@ class netlist(): self.tree = [] self._curr_element = None + + if not prefs: + prefs = BomPref() #default values + + self.prefs = prefs # component blacklist regexs, made from exluded_* above. self.excluded_references = [] @@ -359,7 +321,7 @@ class netlist(): # If this element is a component, add it to the components list if self._curr_element.name == "comp": - self.components.append(Component(self._curr_element)) + self.components.append(Component(self._curr_element, prefs=self.prefs)) # Assign the design element if self._curr_element.name == "design": @@ -449,13 +411,13 @@ class netlist(): del self.excluded_values[:] del self.excluded_footprints[:] - for rex in excluded_references: + for rex in self.prefs.excluded_references: self.excluded_references.append( re.compile( rex ) ) - for rex in excluded_values: + for rex in self.prefs.excluded_values: self.excluded_values.append( re.compile( rex ) ) - for rex in excluded_footprints: + for rex in self.prefs.excluded_footprints: self.excluded_footprints.append( re.compile( rex ) ) # the subset of components to return, considered as "interesting". @@ -495,20 +457,8 @@ class netlist(): return ret - - def groupComponents(self, components = None): - """Return a list of component lists. Components are grouped together - when the value, library and part identifiers match. - - ALSO THE FOOTPRINTS MUST MATCH YOU DINGBAT - - Keywords: - components -- is a list of components, typically an interesting subset - of all components, or None. If None, then all components are looked at. - """ - if not components: - components = self.components - + def groupComponents(self, components): + groups = [] """ diff --git a/KiBOM/preferences.py b/KiBOM/preferences.py index a89d7230..bbb75e17 100644 --- a/KiBOM/preferences.py +++ b/KiBOM/preferences.py @@ -12,9 +12,13 @@ class BomPref: SECTION_IGNORE = "IGNORE_COLUMNS" SECTION_GENERAL = "BOM_OPTIONS" + SECTION_EXCLUDE_VALUES = "EXCLUDE_COMPONENT_VALUES" + SECTION_EXCLUDE_REFS = "EXCLUDE_COMPONENT_REFS" + SECTION_EXCLUDE_FP = "EXCLUDE_COMPONENT_FP" OPT_IGNORE_DNF = "ignore_dnf" OPT_NUMBER_ROWS = "number_rows" + OPT_GROUP_CONN = "group_connectors" def __init__(self): self.ignore = [ @@ -23,6 +27,25 @@ class BomPref: ] #list of headings to ignore in BoM generation self.ignoreDNF = False #ignore rows for do-not-fit parts self.numberRows = True #add row-numbers to BoM output + self.groupConnectors = True #group connectors and ignore component value + + #default reference exclusions + self.excluded_references = [ + "TP[0-9]+" + ] + + #default value exclusions + self.excluded_values = [ + 'MOUNTHOLE', + 'SCOPETEST', + 'MOUNT_HOLE', + 'MOUNTING_HOLE', + 'SOLDER_BRIDGE.*' + ] + + #default footprint exclusions + self.excluded_footprints = [ + ] #read KiBOM preferences from file def Read(self, file, verbose=False): @@ -39,22 +62,16 @@ 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") + 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") + 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" #read out ignored-rows if self.SECTION_IGNORE in cf.sections(): self.ignore = [i for i in cf.options(self.SECTION_IGNORE)] - if verbose: - print("Preferences:") - print(self.OPT_IGNORE_DNF + ' = ' + str(self.ignoreDNF)) - print(self.OPT_NUMBER_ROWS + ' = ' + str(self.numberRows)) - - for i in self.ignore: - print("Ignoring column '" + i + "'") - #write KiBOM preferences to file def Write(self, file): file = os.path.abspath(file) @@ -67,6 +84,8 @@ class BomPref: cf.set(self.SECTION_GENERAL, self.OPT_IGNORE_DNF, 1 if self.ignoreDNF else 0) cf.set(self.SECTION_GENERAL, "; If number_rows option is set to 1, each row in the BoM will be prepended with an incrementing row number") cf.set(self.SECTION_GENERAL, self.OPT_NUMBER_ROWS, 1 if self.numberRows else 0) + cf.set(self.SECTION_GENERAL, "; If group_connectors option is set to 1, connectors with the same footprints will be grouped together, independent of the name of the connector") + cf.set(self.SECTION_GENERAL, self.OPT_GROUP_CONN, 1 if self.groupConnectors else 0) cf.add_section(self.SECTION_IGNORE) cf.set(self.SECTION_IGNORE, "; Any column heading that appears here will be excluded from the Generated BoM") @@ -75,5 +94,23 @@ class BomPref: for i in self.ignore: cf.set(self.SECTION_IGNORE, i) + cf.add_section(self.SECTION_EXCLUDE_VALUES) + cf.set(self.SECTION_EXCLUDE_VALUES, "; A series of reg-ex strings for ignoring component values") + cf.set(self.SECTION_EXCLUDE_VALUES, "; Any components with values that match any of these reg-ex strings will be ignored") + for e in self.excluded_values: + cf.set(self.SECTION_EXCLUDE_VALUES, e) + + cf.add_section(self.SECTION_EXCLUDE_REFS) + cf.set(self.SECTION_EXCLUDE_REFS, "; A series of reg-ex strings for ignoring component references") + cf.set(self.SECTION_EXCLUDE_REFS, "; Any components with references that match any of these reg-ex strings will be ignored") + for e in self.excluded_references: + cf.set(self.SECTION_EXCLUDE_REFS, e) + + cf.add_section(self.SECTION_EXCLUDE_FP) + cf.set(self.SECTION_EXCLUDE_FP, "; A series of reg-ex strings for ignoring component footprints") + cf.set(self.SECTION_EXCLUDE_FP, "; Any components with footprints that match any of these reg-ex strings will be ignored") + for e in self.excluded_footprints: + cf.set(self.SECTION_EXCLUDE_FP, e) + with open(file, 'wb') as configfile: cf.write(configfile) \ No newline at end of file diff --git a/KiBOM_CLI.py b/KiBOM_CLI.py index 93f7ad5c..fcc0eb3f 100644 --- a/KiBOM_CLI.py +++ b/KiBOM_CLI.py @@ -59,11 +59,11 @@ ignoreDNF = False numberRows = True #Look for a '.bom' preference file -pref_file = os.path.join(os.path.dirname(input_file) , ".bom") +pref_file = os.path.join(os.path.dirname(input_file) , "bom.ini") #read preferences from file. If file does not exists, default preferences will be used pref = BomPref() -pref.Read(pref_file, verbose=True) +pref.Read(pref_file) #write preference file back out (first run will generate a file with default preferences) pref.Write(pref_file) @@ -75,7 +75,7 @@ components = [] groups = [] #read out the netlist -net = netlist(input_file) +net = netlist(input_file, prefs = pref) #extract the components components = net.getInterestingComponents() diff --git a/README.md b/README.md index 42b1ea28..2cfff055 100644 --- a/README.md +++ b/README.md @@ -56,10 +56,11 @@ Multiple BoM output formats are supported: Output file format selection is set by the output filename. e.g. "bom.html" will be written to a HTML file, "bom.csv" will be written to a CSV file. ### Configuration File -BoM generation options can be configured (on a per-project basis) by editing the *.bom* file in the PCB project directory. This file is generated the first time that the KiBoM script is run, and allows configuration of the following options. +BoM generation options can be configured (on a per-project basis) by editing the *bom.ini* file in the PCB project directory. This file is generated the first time that the KiBoM script is run, and allows configuration of the following options. * Number Rows: Add row numbers to the BoM output * Ignore DNF: Component groups marked as 'DNF' (do not fit) will be excluded from the BoM output * Ignore Columns: A list of columns can be marked as 'ignore', and will not be output to the BoM file. By default, the *Part_Lib* and *Footprint_Lib* columns are ignored. +* Group Connectors: If this option is set, connector comparison based on the 'Value' field is ignored. This allows multiple connectors which are named for their function (e.g. "Power", "ICP" etc) can be grouped together. Example configuration file (.ini format) ![alt tag](example/config.png?raw=True "Configuration")