v1.2
* Added ability to group components by any arbitrary fields (set in bom.ini file) * User can determine whether blank fields are merged (or not) * Improved part grouping * User can set a 'Configuration' field to determine whether a part is loaded or not under certain circumstances * Cleanup of preferences management * Added version number
This commit is contained in:
parent
75c46504d9
commit
0afa7bb26e
|
|
@ -35,14 +35,6 @@ class Component():
|
||||||
#no match, return False
|
#no match, return False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#compare footprint with another component
|
|
||||||
def compareFootprint(self, other):
|
|
||||||
return self.getFootprint().lower() == other.getFootprint().lower()
|
|
||||||
|
|
||||||
#compare the component library of this part to another part
|
|
||||||
def compareLibName(self, other):
|
|
||||||
return self.getLibName().lower() == other.getLibName().lower()
|
|
||||||
|
|
||||||
#determine if two parts have the same name
|
#determine if two parts have the same name
|
||||||
def comparePartName(self, other):
|
def comparePartName(self, other):
|
||||||
pn1 = self.getPartName().lower()
|
pn1 = self.getPartName().lower()
|
||||||
|
|
@ -58,23 +50,42 @@ class Component():
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def compareField(self, other, field):
|
||||||
|
|
||||||
|
this_field = self.getField(field).lower()
|
||||||
|
other_field = other.getField(field).lower()
|
||||||
|
|
||||||
|
if this_field == other_field: return True
|
||||||
|
|
||||||
|
#if blank comparisons are allowed
|
||||||
|
if self.prefs.mergeBlankFields:
|
||||||
|
if this_field == "" or other_field == "":
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
#Equivalency operator is used to determine if two parts are 'equal'
|
#Equivalency operator is used to determine if two parts are 'equal'
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
"""Equlivalency operator, remember this can be easily overloaded"""
|
"""Equlivalency operator, remember this can be easily overloaded"""
|
||||||
|
|
||||||
valueResult = self.compareValue(other)
|
results = []
|
||||||
|
|
||||||
#if connector comparison is overridden, set valueResult to True
|
#'fitted' value must be the same for both parts
|
||||||
if self.prefs.groupConnectors:
|
results.append(self.isFitted() == other.isFitted())
|
||||||
if "conn" in self.getDescription().lower():
|
|
||||||
valueResult = True
|
|
||||||
|
|
||||||
if self.prefs.compareFootprints:
|
for c in self.prefs.groups:
|
||||||
fpResult = self.compareFootprint(other)
|
#perform special matches
|
||||||
else:
|
if c.lower() == ColumnList.COL_VALUE.lower():
|
||||||
fpResult = True
|
results.append(self.compareValue(other))
|
||||||
|
#match part name
|
||||||
|
elif c.lower() == ColumnList.COL_PART.lower():
|
||||||
|
results.append(self.comparePartName(other))
|
||||||
|
|
||||||
return valueResult and fpResult and self.compareLibName(other) and self.comparePartName(other) and self.isFitted() == other.isFitted()
|
#generic match
|
||||||
|
else:
|
||||||
|
results.append(self.compareField(other, c))
|
||||||
|
|
||||||
|
return all(results)
|
||||||
|
|
||||||
def setLibPart(self, part):
|
def setLibPart(self, part):
|
||||||
self.libpart = part
|
self.libpart = part
|
||||||
|
|
@ -107,7 +118,7 @@ class Component():
|
||||||
def getValue(self):
|
def getValue(self):
|
||||||
return self.element.get("value")
|
return self.element.get("value")
|
||||||
|
|
||||||
def getField(self, name, libraryToo=True):
|
def getField(self, name, ignoreCase=True, libraryToo=True):
|
||||||
"""Return the value of a field named name. The component is first
|
"""Return the value of a field named name. The component is first
|
||||||
checked for the field, and then the components library part is checked
|
checked for the field, and then the components library part is checked
|
||||||
for the field. If the field doesn't exist in either, an empty string is
|
for the field. If the field doesn't exist in either, an empty string is
|
||||||
|
|
@ -119,9 +130,39 @@ class Component():
|
||||||
in component itself
|
in component itself
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
#special fields
|
||||||
|
|
||||||
|
if name.lower() == ColumnList.COL_REFERENCE.lower():
|
||||||
|
return self.getRef()
|
||||||
|
|
||||||
|
if name.lower() == ColumnList.COL_DESCRIPTION.lower():
|
||||||
|
return self.getDescription()
|
||||||
|
|
||||||
|
if name.lower() == ColumnList.COL_DATASHEET.lower():
|
||||||
|
return self.getDatasheet()
|
||||||
|
|
||||||
|
if name.lower() == ColumnList.COL_FP.lower():
|
||||||
|
return self.getFootprint().split(":")[0]
|
||||||
|
|
||||||
|
if name.lower() == ColumnList.COL_FP.lower():
|
||||||
|
return self.getFootprint().split(":")[1]
|
||||||
|
|
||||||
|
if name.lower() == ColumnList.COL_VALUE.lower():
|
||||||
|
return self.getValue()
|
||||||
|
|
||||||
|
if name.lower() == ColumnList.COL_PART.lower():
|
||||||
|
return self.getPartName()
|
||||||
|
|
||||||
|
if name.lower() == ColumnList.COL_PART_LIB.lower():
|
||||||
|
return self.getLibName()
|
||||||
|
|
||||||
|
|
||||||
field = self.element.get("field", "name", name)
|
field = self.element.get("field", "name", name)
|
||||||
|
|
||||||
if field == "" and libraryToo:
|
if field == "" and libraryToo:
|
||||||
field = self.libpart.getField(name)
|
field = self.libpart.getField(name)
|
||||||
|
|
||||||
return field
|
return field
|
||||||
|
|
||||||
def getFieldNames(self):
|
def getFieldNames(self):
|
||||||
|
|
@ -143,12 +184,30 @@ class Component():
|
||||||
#determine if a component is FITTED or not
|
#determine if a component is FITTED or not
|
||||||
def isFitted(self):
|
def isFitted(self):
|
||||||
|
|
||||||
check = [self.getValue().lower(), self.getField("Notes").lower()]
|
check = self.getField(self.prefs.configField).lower()
|
||||||
|
|
||||||
for item in check:
|
#check the value field first
|
||||||
if any([dnf in item for dnf in DNF]): return False
|
if self.getValue().lower() in DNF or check.lower() in DNF:
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
if check == "":
|
||||||
|
return True #empty is fitted
|
||||||
|
|
||||||
|
opts = check.split(",")
|
||||||
|
|
||||||
|
result = True
|
||||||
|
|
||||||
|
for opt in opts:
|
||||||
|
#options that start with '-' are explicitly removed from certain configurations
|
||||||
|
if opt.startswith('-') and opt[1:].lower() == self.prefs.pcbConfig.lower():
|
||||||
|
result = False
|
||||||
|
break
|
||||||
|
if opt.startswith("+"):
|
||||||
|
if opt.lower() == self.prefs.pcbConfig.lower():
|
||||||
|
result = True
|
||||||
|
|
||||||
|
#by default, part is fitted
|
||||||
|
return result
|
||||||
|
|
||||||
def getFootprint(self, libraryToo=True):
|
def getFootprint(self, libraryToo=True):
|
||||||
ret = self.element.get("footprint")
|
ret = self.element.get("footprint")
|
||||||
|
|
@ -180,6 +239,7 @@ class ComponentGroup():
|
||||||
self.prefs = prefs
|
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 ""
|
||||||
return u''.join((self.fields[field]))
|
return u''.join((self.fields[field]))
|
||||||
|
|
@ -192,6 +252,8 @@ class ComponentGroup():
|
||||||
if len(self.components) == 0: return True
|
if len(self.components) == 0: return True
|
||||||
if c == self.components[0]: return True
|
if c == self.components[0]: return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
#test if a given component is already contained in this grop
|
#test if a given component is already contained in this grop
|
||||||
def containsComponent(self, c):
|
def containsComponent(self, c):
|
||||||
if self.matchComponent(c) == False: return False
|
if self.matchComponent(c) == False: return False
|
||||||
|
|
@ -260,6 +322,7 @@ class ComponentGroup():
|
||||||
self.fields[ColumnList.COL_GRP_QUANTITY] = "{n}{dnf}".format(
|
self.fields[ColumnList.COL_GRP_QUANTITY] = "{n}{dnf}".format(
|
||||||
n=q,
|
n=q,
|
||||||
dnf = " (DNF)" if not self.isFitted() else "")
|
dnf = " (DNF)" if not self.isFitted() else "")
|
||||||
|
|
||||||
self.fields[ColumnList.COL_GRP_BUILD_QUANTITY] = str(q * self.prefs.boards) if self.isFitted() else "0"
|
self.fields[ColumnList.COL_GRP_BUILD_QUANTITY] = str(q * self.prefs.boards) if self.isFitted() else "0"
|
||||||
self.fields[ColumnList.COL_VALUE] = self.components[0].getValue()
|
self.fields[ColumnList.COL_VALUE] = self.components[0].getValue()
|
||||||
self.fields[ColumnList.COL_PART] = self.components[0].getPartName()
|
self.fields[ColumnList.COL_PART] = self.components[0].getPartName()
|
||||||
|
|
@ -279,6 +342,10 @@ class ComponentGroup():
|
||||||
#retunr False if any match
|
#retunr False if any match
|
||||||
def testRegex(self):
|
def testRegex(self):
|
||||||
|
|
||||||
|
return True
|
||||||
|
#run the excusion
|
||||||
|
|
||||||
|
"""
|
||||||
for key in self.prefs.regex.keys():
|
for key in self.prefs.regex.keys():
|
||||||
reg = self.prefs.regex[key]
|
reg = self.prefs.regex[key]
|
||||||
if not type(reg) in [str, list]: continue #regex must be a string, or a list of strings
|
if not type(reg) in [str, list]: continue #regex must be a string, or a list of strings
|
||||||
|
|
@ -303,6 +370,7 @@ class ComponentGroup():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
#return a dict of the KiCAD data based on the supplied columns
|
#return a dict of the KiCAD data based on the supplied columns
|
||||||
|
|
|
||||||
|
|
@ -12,46 +12,57 @@ 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"
|
|
||||||
SECTION_EXCLUDE_PART = "EXCLUDE_COMPONENT_PART"
|
|
||||||
SECTION_EXCLUDE_DESC = "EXCLUDE_COMPONENT_DESC"
|
|
||||||
SECTION_ALIASES = "COMPONENT_ALIASES"
|
SECTION_ALIASES = "COMPONENT_ALIASES"
|
||||||
SECTION_CONFIGURATIONS = "PCB_CONFIGURATIONS"
|
SECTION_GROUPING_FIELDS = "GROUP_FIELDS"
|
||||||
|
SECTION_REGEXCLUDES = "REGEX_EXCLUDE"
|
||||||
|
SECTION_REGINCLUDES = "REGEX_INCLUDE"
|
||||||
|
|
||||||
OPT_IGNORE_DNF = "ignore_dnf"
|
OPT_PCB_CONFIG = "pcb_configuration"
|
||||||
OPT_NUMBER_ROWS = "number_rows"
|
OPT_NUMBER_ROWS = "number_rows"
|
||||||
OPT_GROUP_CONN = "group_connectors"
|
OPT_GROUP_CONN = "group_connectors"
|
||||||
OPT_USE_REGEX = "test_regex"
|
OPT_USE_REGEX = "test_regex"
|
||||||
OPT_COMP_FP = "compare_footprints"
|
OPT_MERGE_BLANK = "merge_blank_fields"
|
||||||
OPT_INC_PRICE = "calculate_price"
|
OPT_IGNORE_DNF = "ignore_dnf"
|
||||||
|
|
||||||
#list of columns which we can use regex on
|
OPT_CONFIG_FIELD = "configuration_field"
|
||||||
COL_REG_EX = [
|
|
||||||
ColumnList.COL_REFERENCE,
|
|
||||||
ColumnList.COL_DESCRIPTION,
|
|
||||||
ColumnList.COL_VALUE,
|
|
||||||
ColumnList.COL_FP,
|
|
||||||
ColumnList.COL_FP_LIB,
|
|
||||||
ColumnList.COL_PART,
|
|
||||||
ColumnList.COL_PART_LIB
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.ignore = [
|
self.ignore = [
|
||||||
ColumnList.COL_PART_LIB,
|
ColumnList.COL_PART_LIB,
|
||||||
ColumnList.COL_FP_LIB,
|
ColumnList.COL_FP_LIB,
|
||||||
] #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 = True #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
|
self.groupConnectors = True #group connectors and ignore component value
|
||||||
self.useRegex = True #Test various columns with regex
|
self.useRegex = True #Test various columns with regex
|
||||||
self.compareFootprints = True #test footprints when comparing components
|
|
||||||
self.boards = 1
|
self.boards = 1
|
||||||
|
self.mergeBlankFields = True #blanks fields will be merged when possible
|
||||||
self.hideHeaders = False
|
self.hideHeaders = False
|
||||||
self.verbose = False #by default, is not verbose
|
self.verbose = False #by default, is not verbose
|
||||||
self.configurations = [] #list of various configurations
|
self.configField = "Config" #default field used for part fitting config
|
||||||
|
self.pcbConfig = "default"
|
||||||
|
|
||||||
|
#default fields used to group components
|
||||||
|
self.groups = [
|
||||||
|
ColumnList.COL_PART,
|
||||||
|
ColumnList.COL_PART_LIB,
|
||||||
|
ColumnList.COL_VALUE,
|
||||||
|
ColumnList.COL_FP,
|
||||||
|
ColumnList.COL_FP_LIB,
|
||||||
|
#user can add custom grouping columns in bom.ini
|
||||||
|
]
|
||||||
|
|
||||||
|
self.regIncludes = [] #none by default
|
||||||
|
|
||||||
|
self.regExcludes = [
|
||||||
|
[ColumnList.COL_REFERENCE,'TP[0-9]'],
|
||||||
|
[ColumnList.COL_PART,'mount[\s-_]*hole'],
|
||||||
|
[ColumnList.COL_PART,'solder[\s-_]*bridge'],
|
||||||
|
[ColumnList.COL_PART,'test[\s-_]*point'],
|
||||||
|
[ColumnList.COL_FP,'test[\s-_]*point'],
|
||||||
|
[ColumnList.COL_FP,'mount[\s-_]*hole'],
|
||||||
|
[ColumnList.COL_FP,'fiducial'],
|
||||||
|
]
|
||||||
|
|
||||||
#default component groupings
|
#default component groupings
|
||||||
self.aliases = [
|
self.aliases = [
|
||||||
|
|
@ -63,29 +74,6 @@ class BomPref:
|
||||||
["d","diode","d_small"]
|
["d","diode","d_small"]
|
||||||
]
|
]
|
||||||
|
|
||||||
#dictionary of possible regex expressions for ignoring component row(s)
|
|
||||||
self.regex = dict.fromkeys(self.COL_REG_EX)
|
|
||||||
|
|
||||||
#default regex values
|
|
||||||
self.regex[ColumnList.COL_REFERENCE] = [
|
|
||||||
'TP[0-9]+',
|
|
||||||
]
|
|
||||||
|
|
||||||
self.regex[ColumnList.COL_PART] = [
|
|
||||||
'mounthole',
|
|
||||||
'scopetest',
|
|
||||||
'mount_hole',
|
|
||||||
'solder_bridge',
|
|
||||||
'test_point',
|
|
||||||
]
|
|
||||||
|
|
||||||
self.regex[ColumnList.COL_FP] = [
|
|
||||||
'mounthole'
|
|
||||||
]
|
|
||||||
|
|
||||||
def columnToGroup(self, col):
|
|
||||||
return "REGEXCLUDE_" + col.upper().replace(" ","_")
|
|
||||||
|
|
||||||
#check an option within the SECTION_GENERAL group
|
#check an option within the SECTION_GENERAL group
|
||||||
def checkOption(self, parser, opt, default=False):
|
def checkOption(self, parser, opt, default=False):
|
||||||
if parser.has_option(self.SECTION_GENERAL, opt):
|
if parser.has_option(self.SECTION_GENERAL, opt):
|
||||||
|
|
@ -102,6 +90,7 @@ class BomPref:
|
||||||
|
|
||||||
with open(file, 'rb') as configfile:
|
with open(file, 'rb') as configfile:
|
||||||
cf = ConfigParser.RawConfigParser(allow_no_value = True)
|
cf = ConfigParser.RawConfigParser(allow_no_value = True)
|
||||||
|
cf.optionxform=str
|
||||||
|
|
||||||
cf.read(file)
|
cf.read(file)
|
||||||
|
|
||||||
|
|
@ -111,11 +100,17 @@ class BomPref:
|
||||||
self.numberRows = self.checkOption(cf, self.OPT_NUMBER_ROWS, 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.groupConnectors = self.checkOption(cf, self.OPT_GROUP_CONN, default=True)
|
||||||
self.useRegex = self.checkOption(cf, self.OPT_USE_REGEX, default=True)
|
self.useRegex = self.checkOption(cf, self.OPT_USE_REGEX, default=True)
|
||||||
self.compareFootprints = self.checkOption(cf, self.OPT_COMP_FP, default=True)
|
self.mergeBlankFields = self.checkOption(cf, self.OPT_MERGE_BLANK, default = True)
|
||||||
|
|
||||||
#read out configurations
|
if cf.has_option(self.SECTION_GENERAL, self.OPT_PCB_CONFIG):
|
||||||
if self.SECTION_CONFIGURATIONS in cf.sections():
|
self.pcbConfig = cf.get(self.SECTION_GENERAL, self.OPT_PCB_CONFIG)
|
||||||
self.configurations = [i for i in cf.options(self.SECTION_CONFIGURATIONS)]
|
|
||||||
|
if cf.has_option(self.SECTION_GENERAL, self.OPT_CONFIG_FIELD):
|
||||||
|
self.configField = cf.get(self.SECTION_GENERAL, self.OPT_CONFIG_FIELD)
|
||||||
|
|
||||||
|
#read out grouping colums
|
||||||
|
if self.SECTION_GROUPING_FIELDS in cf.sections():
|
||||||
|
self.groups = [i for i in cf.options(self.SECTION_GROUPING_FIELDS)]
|
||||||
|
|
||||||
#read out ignored-rows
|
#read out ignored-rows
|
||||||
if self.SECTION_IGNORE in cf.sections():
|
if self.SECTION_IGNORE in cf.sections():
|
||||||
|
|
@ -125,12 +120,11 @@ class BomPref:
|
||||||
if self.SECTION_ALIASES in cf.sections():
|
if self.SECTION_ALIASES in cf.sections():
|
||||||
self.aliases = [a.split(" ") for a in cf.options(self.SECTION_ALIASES)]
|
self.aliases = [a.split(" ") for a in cf.options(self.SECTION_ALIASES)]
|
||||||
|
|
||||||
#read out the regex
|
if self.SECTION_REGEXCLUDES in cf.sections():
|
||||||
for key in self.regex.keys():
|
pass
|
||||||
section = self.columnToGroup(key)
|
|
||||||
if section in cf.sections():
|
|
||||||
self.regex[key] = [r for r in cf.options(section)]
|
|
||||||
|
|
||||||
|
if self.SECTION_REGINCLUDES in cf.sections():
|
||||||
|
pass
|
||||||
|
|
||||||
#add an option to the SECTION_GENRAL group
|
#add an option to the SECTION_GENRAL group
|
||||||
def addOption(self, parser, opt, value, comment=None):
|
def addOption(self, parser, opt, value, comment=None):
|
||||||
|
|
@ -145,6 +139,7 @@ class BomPref:
|
||||||
file = os.path.abspath(file)
|
file = os.path.abspath(file)
|
||||||
|
|
||||||
cf = ConfigParser.RawConfigParser(allow_no_value = True)
|
cf = ConfigParser.RawConfigParser(allow_no_value = True)
|
||||||
|
cf.optionxform=str
|
||||||
|
|
||||||
cf.add_section(self.SECTION_GENERAL)
|
cf.add_section(self.SECTION_GENERAL)
|
||||||
cf.set(self.SECTION_GENERAL, "; General BoM options here")
|
cf.set(self.SECTION_GENERAL, "; General BoM options here")
|
||||||
|
|
@ -152,7 +147,14 @@ class BomPref:
|
||||||
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_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_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_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_MERGE_BLANK, self.mergeBlankFields, comment="If '{opt}' option is set to 1, component groups with blank fields will be merged into the most compatible group, where possible".format(opt=self.OPT_MERGE_BLANK))
|
||||||
|
|
||||||
|
cf.set(self.SECTION_GENERAL, '; Field name used to determine if a particular part is to be fitted')
|
||||||
|
cf.set(self.SECTION_GENERAL, self.OPT_CONFIG_FIELD, self.configField)
|
||||||
|
|
||||||
|
cf.set(self.SECTION_GENERAL, "; Configuration string used to determine which components are loaded on a particular board")
|
||||||
|
cf.set(self.SECTION_GENERAL, '; Configuration string is case-insensitive')
|
||||||
|
cf.set(self.SECTION_GENERAL, self.OPT_PCB_CONFIG, self.pcbConfig)
|
||||||
|
|
||||||
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")
|
||||||
|
|
@ -161,36 +163,40 @@ 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_CONFIGURATIONS)
|
#write the component grouping fields
|
||||||
cf.set(self.SECTION_CONFIGURATIONS, '; List of PCB configuration parameters')
|
cf.add_section(self.SECTION_GROUPING_FIELDS)
|
||||||
for i in self.configurations:
|
cf.set(self.SECTION_GROUPING_FIELDS, '; List of fields used for sorting individual components into groups')
|
||||||
cf.set(self.SECTION_CONFIGURATIONS, i)
|
cf.set(self.SECTION_GROUPING_FIELDS, '; Components which match (comparing *all* fields) will be grouped together')
|
||||||
|
cf.set(self.SECTION_GROUPING_FIELDS, '; Field names are CASE-SENSITIVE!')
|
||||||
|
|
||||||
|
for i in self.groups:
|
||||||
|
cf.set(self.SECTION_GROUPING_FIELDS, i)
|
||||||
|
|
||||||
cf.add_section(self.SECTION_ALIASES)
|
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, "; 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")
|
cf.set(self.SECTION_ALIASES, "; Each line represents a space-separated list of equivalent component name values")
|
||||||
cf.set(self.SECTION_ALIASES, "; e.g. 'c c_small cap' will ensure the equivalent capacitor symbols can be grouped together")
|
cf.set(self.SECTION_ALIASES, "; e.g. 'c c_small cap' will ensure the equivalent capacitor symbols can be grouped together")
|
||||||
|
cf.set(self.SECTION_ALIASES, '; Aliases are case-insensitive')
|
||||||
|
|
||||||
for a in self.aliases:
|
for a in self.aliases:
|
||||||
cf.set(self.SECTION_ALIASES, " ".join(a))
|
cf.set(self.SECTION_ALIASES, " ".join(a))
|
||||||
|
|
||||||
for col in self.regex.keys():
|
cf.add_section(self.SECTION_REGINCLUDES)
|
||||||
|
cf.set(self.SECTION_REGINCLUDES, '; A series of regular expressions used to include parts in the BoM')
|
||||||
|
cf.set(self.SECTION_REGINCLUDES, '; Column names are case-insensitive')
|
||||||
|
for i in self.regIncludes:
|
||||||
|
if not len(i) == 2: continue
|
||||||
|
|
||||||
reg = self.regex[col]
|
cf.set(self.SECTION_REGINCLUDE, i[0], i[1])
|
||||||
|
|
||||||
section = self.columnToGroup(col)
|
cf.add_section(self.SECTION_REGEXCLUDES)
|
||||||
cf.add_section(section)
|
cf.set(self.SECTION_REGEXCLUDES, '; A series of regular expressions used to exclude parts from the BoM')
|
||||||
#comments
|
cf.set(self.SECTION_REGINCLUDES, '; Column names are case-insensitive')
|
||||||
cf.set(section, "; A list of regex to compare against the '{col}' column".format(col=col))
|
|
||||||
cf.set(section, "; If the value in the '{col}' column matches any of these expressions, the row will be excluded from the BoM".format(col=col))
|
|
||||||
|
|
||||||
if type(reg) == str:
|
|
||||||
cf.set(section, reg)
|
|
||||||
|
|
||||||
elif type(reg) == list:
|
|
||||||
for r in reg:
|
|
||||||
cf.set(section, r)
|
|
||||||
|
|
||||||
|
for i in self.regExcludes:
|
||||||
|
if not len(i) == 2: continue
|
||||||
|
|
||||||
|
cf.set(self.SECTION_REGEXCLUDES, i[0], i[1])
|
||||||
|
|
||||||
with open(file, 'wb') as configfile:
|
with open(file, 'wb') as configfile:
|
||||||
cf.write(configfile)
|
cf.write(configfile)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
KIBOM_VERSION = 1.2
|
||||||
24
KiBOM_CLI.py
24
KiBOM_CLI.py
|
|
@ -32,9 +32,9 @@ parser = argparse.ArgumentParser(description="KiBOM Bill of Materials generator
|
||||||
|
|
||||||
parser.add_argument("netlist", help='xml netlist file. Use "%%I" when running from within KiCad')
|
parser.add_argument("netlist", help='xml netlist file. Use "%%I" when running from within KiCad')
|
||||||
parser.add_argument("output", default="", 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"')
|
parser.add_argument("output", default="", 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"')
|
||||||
parser.add_argument("-b", "--boards", help="Number of boards to build (default = 1)", type=int, default=1)
|
parser.add_argument("-n", "--number", help="Number of boards to build (default = 1)", type=int, default=1)
|
||||||
parser.add_argument("-v", "--verbose", help="Enable verbose output", action='count')
|
parser.add_argument("-v", "--verbose", help="Enable verbose output", action='count')
|
||||||
parser.add_argument("-n", "--noheader", help="Do not generate file headers; data only.", action='count')
|
parser.add_argument("-r", "--revision", help="Board variant, used to determine which components are output to the BoM", type=str, default=None)
|
||||||
parser.add_argument("--cfg", help="BoM config file (script will try to use 'bom.ini' if not specified here)")
|
parser.add_argument("--cfg", help="BoM config file (script will try to use 'bom.ini' if not specified here)")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
@ -62,20 +62,22 @@ if args.cfg:
|
||||||
#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()
|
||||||
|
|
||||||
#pass various command-line options through
|
|
||||||
pref.verbose = verbose
|
|
||||||
pref.boards = args.boards
|
|
||||||
if args.noheader:
|
|
||||||
pref.hideHeaders = True
|
|
||||||
|
|
||||||
if os.path.exists(config_file):
|
if os.path.exists(config_file):
|
||||||
pref.Read(config_file)
|
pref.Read(config_file)
|
||||||
say("Config:",config_file)
|
say("Config:",config_file)
|
||||||
|
|
||||||
|
#pass various command-line options through
|
||||||
|
pref.verbose = verbose
|
||||||
|
pref.boards = args.number
|
||||||
|
|
||||||
|
if args.revision is not None:
|
||||||
|
pref.pcbConfig = args.revision
|
||||||
|
print("PCB Revision:",args.revision)
|
||||||
|
|
||||||
|
|
||||||
#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)
|
||||||
if not os.path.exists(ini):
|
pref.Write(ini)
|
||||||
pref.Write(ini)
|
say("Writing preferences file bom.ini")
|
||||||
say("Writing preferences file bom.ini")
|
|
||||||
|
|
||||||
#individual components
|
#individual components
|
||||||
components = []
|
components = []
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue