Preferences file now passed through to the netlist reader, allowing more options to be stored in the .ini file

Changed ".bom" to "bom.ini"
Removed old code from netlist_reader.py
This commit is contained in:
Oliver 2016-05-15 20:18:03 +10:00
parent 4a304a5959
commit b69d66902c
5 changed files with 84 additions and 79 deletions

View File

@ -1,5 +1,7 @@
from columns import ColumnList from columns import ColumnList
from preferences import BomPref
import units import units
from sort import natural_sort from sort import natural_sort
@ -12,9 +14,14 @@ class Component():
with accessors. The xmlElement is held in field 'element'. 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.element = xml_element
self.libpart = None self.libpart = None
if not prefs:
prefs = BomPref()
self.prefs = prefs
# Set to true when this component is included in a component group # Set to true when this component is included in a component group
self.grouped = False self.grouped = False
@ -59,6 +66,11 @@ class Component():
valueResult = self.compareValue(other) 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() return valueResult and self.compareFootprint(other) and self.compareLibName(other) and self.comparePartName(other) and self.isFitted() == other.isFitted()
def setLibPart(self, part): def setLibPart(self, part):
@ -155,11 +167,16 @@ class ComponentGroup():
""" """
Initialize the group with no components, and default fields Initialize the group with no components, and default fields
""" """
def __init__(self): def __init__(self, prefs=None):
self.components = [] self.components = []
self.fields = dict.fromkeys(ColumnList._COLUMNS_PROTECTED) #columns loaded from KiCAD self.fields = dict.fromkeys(ColumnList._COLUMNS_PROTECTED) #columns loaded from KiCAD
self.csvFields = dict.fromkeys(ColumnList._COLUMNS_DEFAULT) #columns loaded from .csv file self.csvFields = dict.fromkeys(ColumnList._COLUMNS_DEFAULT) #columns loaded from .csv file
if not prefs:
prefs = BomPref()
self.prefs = prefs
def getField(self, field): def getField(self, field):
if not field in self.fields.keys(): return "" if not field in self.fields.keys(): return ""
if not self.fields[field]: return "" if not self.fields[field]: return ""

View File

@ -24,50 +24,7 @@ import pdb
from component import Component, ComponentGroup from component import Component, ComponentGroup
from sort import natural_sort from sort import natural_sort
#-----<Configure>---------------------------------------------------------------- from preferences import BomPref
# 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'
]
# When comparing part names, components will match if they are both elements of the # When comparing part names, components will match if they are both elements of the
# same set defined here # same set defined here
@ -318,7 +275,7 @@ class netlist():
scripts scripts
""" """
def __init__(self, fname=""): def __init__(self, fname="", prefs=None):
"""Initialiser for the genericNetlist class """Initialiser for the genericNetlist class
Keywords: Keywords:
@ -335,6 +292,11 @@ class netlist():
self.tree = [] self.tree = []
self._curr_element = None self._curr_element = None
if not prefs:
prefs = BomPref() #default values
self.prefs = prefs
# component blacklist regexs, made from exluded_* above. # component blacklist regexs, made from exluded_* above.
self.excluded_references = [] self.excluded_references = []
@ -359,7 +321,7 @@ class netlist():
# If this element is a component, add it to the components list # If this element is a component, add it to the components list
if self._curr_element.name == "comp": 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 # Assign the design element
if self._curr_element.name == "design": if self._curr_element.name == "design":
@ -449,13 +411,13 @@ class netlist():
del self.excluded_values[:] del self.excluded_values[:]
del self.excluded_footprints[:] del self.excluded_footprints[:]
for rex in excluded_references: for rex in self.prefs.excluded_references:
self.excluded_references.append( re.compile( rex ) ) 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 ) ) 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 ) ) self.excluded_footprints.append( re.compile( rex ) )
# the subset of components to return, considered as "interesting". # the subset of components to return, considered as "interesting".
@ -495,20 +457,8 @@ class netlist():
return ret return ret
def groupComponents(self, components):
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
groups = [] groups = []
""" """

View File

@ -12,9 +12,13 @@ class BomPref:
SECTION_IGNORE = "IGNORE_COLUMNS" SECTION_IGNORE = "IGNORE_COLUMNS"
SECTION_GENERAL = "BOM_OPTIONS" 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_IGNORE_DNF = "ignore_dnf"
OPT_NUMBER_ROWS = "number_rows" OPT_NUMBER_ROWS = "number_rows"
OPT_GROUP_CONN = "group_connectors"
def __init__(self): def __init__(self):
self.ignore = [ self.ignore = [
@ -23,6 +27,25 @@ class BomPref:
] #list of headings to ignore in BoM generation ] #list of headings to ignore in BoM generation
self.ignoreDNF = False #ignore rows for do-not-fit parts self.ignoreDNF = False #ignore rows for do-not-fit parts
self.numberRows = True #add row-numbers to BoM output 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 #read KiBOM preferences from file
def Read(self, file, verbose=False): def Read(self, file, verbose=False):
@ -39,22 +62,16 @@ class BomPref:
#read general options #read general options
if self.SECTION_GENERAL in cf.sections(): if self.SECTION_GENERAL in cf.sections():
if cf.has_option(self.SECTION_GENERAL, self.OPT_IGNORE_DNF): 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): 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 #read out ignored-rows
if self.SECTION_IGNORE in cf.sections(): if self.SECTION_IGNORE in cf.sections():
self.ignore = [i for i in cf.options(self.SECTION_IGNORE)] 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 #write KiBOM preferences to file
def Write(self, file): def Write(self, file):
file = os.path.abspath(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, 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, "; 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, 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.add_section(self.SECTION_IGNORE)
cf.set(self.SECTION_IGNORE, "; Any column heading that appears here will be excluded from the Generated BoM") 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: for i in self.ignore:
cf.set(self.SECTION_IGNORE, i) 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: with open(file, 'wb') as configfile:
cf.write(configfile) cf.write(configfile)

View File

@ -59,11 +59,11 @@ ignoreDNF = False
numberRows = True numberRows = True
#Look for a '.bom' preference file #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 #read preferences from file. If file does not exists, default preferences will be used
pref = BomPref() 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) #write preference file back out (first run will generate a file with default preferences)
pref.Write(pref_file) pref.Write(pref_file)
@ -75,7 +75,7 @@ components = []
groups = [] groups = []
#read out the netlist #read out the netlist
net = netlist(input_file) net = netlist(input_file, prefs = pref)
#extract the components #extract the components
components = net.getInterestingComponents() components = net.getInterestingComponents()

View File

@ -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. 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 ### 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 * 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 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. * 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) Example configuration file (.ini format)
![alt tag](example/config.png?raw=True "Configuration") ![alt tag](example/config.png?raw=True "Configuration")