202 lines
9.5 KiB
Python
202 lines
9.5 KiB
Python
|
|
import sys
|
|
|
|
if sys.version_info.major >= 3:
|
|
import configparser as ConfigParser
|
|
else:
|
|
import ConfigParser
|
|
import os
|
|
from columns import ColumnList
|
|
|
|
class BomPref:
|
|
|
|
SECTION_IGNORE = "IGNORE_COLUMNS"
|
|
SECTION_GENERAL = "BOM_OPTIONS"
|
|
SECTION_ALIASES = "COMPONENT_ALIASES"
|
|
SECTION_GROUPING_FIELDS = "GROUP_FIELDS"
|
|
SECTION_REGEXCLUDES = "REGEX_EXCLUDE"
|
|
SECTION_REGINCLUDES = "REGEX_INCLUDE"
|
|
|
|
OPT_PCB_CONFIG = "pcb_configuration"
|
|
OPT_NUMBER_ROWS = "number_rows"
|
|
OPT_GROUP_CONN = "group_connectors"
|
|
OPT_USE_REGEX = "test_regex"
|
|
OPT_MERGE_BLANK = "merge_blank_fields"
|
|
OPT_IGNORE_DNF = "ignore_dnf"
|
|
|
|
OPT_CONFIG_FIELD = "configuration_field"
|
|
|
|
def __init__(self):
|
|
self.ignore = [
|
|
ColumnList.COL_PART_LIB,
|
|
ColumnList.COL_FP_LIB,
|
|
] #list of headings to ignore in BoM generation
|
|
self.ignoreDNF = True #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
|
|
self.useRegex = True #Test various columns with regex
|
|
self.boards = 1
|
|
self.mergeBlankFields = True #blanks fields will be merged when possible
|
|
self.hideHeaders = False
|
|
self.verbose = False #by default, is not verbose
|
|
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
|
|
self.aliases = [
|
|
["c", "c_small", "cap", "capacitor"],
|
|
["r", "r_small", "res", "resistor"],
|
|
["sw", "switch"],
|
|
["l", "l_small", "inductor"],
|
|
["zener","zenersmall"],
|
|
["d","diode","d_small"]
|
|
]
|
|
|
|
#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)
|
|
if not os.path.exists(file) or not os.path.isfile(file):
|
|
print("{f} is not a valid file!".format(f=file))
|
|
return
|
|
|
|
with open(file, 'rb') as configfile:
|
|
cf = ConfigParser.RawConfigParser(allow_no_value = True)
|
|
cf.optionxform=str
|
|
|
|
cf.read(file)
|
|
|
|
#read general options
|
|
if self.SECTION_GENERAL in cf.sections():
|
|
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.mergeBlankFields = self.checkOption(cf, self.OPT_MERGE_BLANK, default = True)
|
|
|
|
if cf.has_option(self.SECTION_GENERAL, self.OPT_PCB_CONFIG):
|
|
self.pcbConfig = cf.get(self.SECTION_GENERAL, self.OPT_PCB_CONFIG)
|
|
|
|
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
|
|
if self.SECTION_IGNORE in cf.sections():
|
|
self.ignore = [i for i in cf.options(self.SECTION_IGNORE)]
|
|
|
|
#read out component aliases
|
|
if self.SECTION_ALIASES in cf.sections():
|
|
self.aliases = [a.split(" ") for a in cf.options(self.SECTION_ALIASES)]
|
|
|
|
if self.SECTION_REGEXCLUDES in cf.sections():
|
|
pass
|
|
|
|
if self.SECTION_REGINCLUDES in cf.sections():
|
|
pass
|
|
|
|
#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):
|
|
file = os.path.abspath(file)
|
|
|
|
cf = ConfigParser.RawConfigParser(allow_no_value = True)
|
|
cf.optionxform=str
|
|
|
|
cf.add_section(self.SECTION_GENERAL)
|
|
cf.set(self.SECTION_GENERAL, "; General BoM options here")
|
|
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_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.set(self.SECTION_IGNORE, "; Any column heading that appears here will be excluded from the Generated BoM")
|
|
cf.set(self.SECTION_IGNORE, "; Titles are case-insensitive")
|
|
|
|
for i in self.ignore:
|
|
cf.set(self.SECTION_IGNORE, i)
|
|
|
|
#write the component grouping fields
|
|
cf.add_section(self.SECTION_GROUPING_FIELDS)
|
|
cf.set(self.SECTION_GROUPING_FIELDS, '; List of fields used for sorting individual components into groups')
|
|
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.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, "; 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:
|
|
cf.set(self.SECTION_ALIASES, " ".join(a))
|
|
|
|
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
|
|
|
|
cf.set(self.SECTION_REGINCLUDE, i[0], i[1])
|
|
|
|
cf.add_section(self.SECTION_REGEXCLUDES)
|
|
cf.set(self.SECTION_REGEXCLUDES, '; A series of regular expressions used to exclude parts from the BoM')
|
|
cf.set(self.SECTION_REGINCLUDES, '; Column names are case-insensitive')
|
|
|
|
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:
|
|
cf.write(configfile) |