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:
parent
4a304a5959
commit
b69d66902c
|
|
@ -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 ""
|
||||||
|
|
|
||||||
|
|
@ -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 = []
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||

|

|
||||||
|
|
|
||||||
Loading…
Reference in New Issue