188 lines
7.7 KiB
Python
188 lines
7.7 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_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"
|
|
|
|
OPT_IGNORE_DNF = "ignore_dnf"
|
|
OPT_NUMBER_ROWS = "number_rows"
|
|
OPT_GROUP_CONN = "group_connectors"
|
|
OPT_USE_REGEX = "test_regex"
|
|
|
|
#list of columns which we can use reg-ex on
|
|
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):
|
|
self.ignore = [
|
|
ColumnList.COL_PART_LIB,
|
|
ColumnList.COL_FP_LIB,
|
|
] #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
|
|
self.useRegex = True #Test various columns with regex
|
|
|
|
#default reference exclusions
|
|
self.excluded_references = [
|
|
"TP[0-9]+"
|
|
]
|
|
|
|
#default value exclusions
|
|
self.excluded_values = [
|
|
'MOUNTHOLE',
|
|
'SCOPETEST',
|
|
'MOUNT_HOLE',
|
|
'MOUNTING_HOLE',
|
|
'SOLDER_BRIDGE.*',
|
|
'test'
|
|
]
|
|
|
|
#default footprint exclusions
|
|
self.excluded_footprints = [
|
|
]
|
|
|
|
#default component groupings
|
|
self.aliases = [
|
|
["c", "c_small", "cap", "capacitor"],
|
|
["r", "r_small", "res", "resistor"],
|
|
["sw", "switch"],
|
|
["l", "l_small", "inductor"]
|
|
]
|
|
|
|
#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(" ","_")
|
|
|
|
#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.read(file)
|
|
|
|
#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"
|
|
if cf.has_option(self.SECTION_GENERAL, self.OPT_NUMBER_ROWS):
|
|
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"
|
|
if cf.has_option(self.SECTION_GENERAL, self.OPT_USE_REGEX):
|
|
self.useRegex = cf.get(self.SECTION_GENERAL, self.OPT_USE_REGEX) == "1"
|
|
|
|
#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)]
|
|
|
|
#read out the regex
|
|
for key in self.regex.keys():
|
|
section = self.columnToGroup(key)
|
|
if section in cf.sections():
|
|
self.regex[key] = [r for r in cf.options(section)]
|
|
|
|
|
|
|
|
#write KiBOM preferences to file
|
|
def Write(self, file):
|
|
file = os.path.abspath(file)
|
|
|
|
cf = ConfigParser.RawConfigParser(allow_no_value = True)
|
|
|
|
cf.add_section(self.SECTION_GENERAL)
|
|
cf.set(self.SECTION_GENERAL, "; General BoM options here")
|
|
cf.set(self.SECTION_GENERAL, "; 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))
|
|
cf.set(self.SECTION_GENERAL, self.OPT_IGNORE_DNF, 1 if self.ignoreDNF else 0)
|
|
cf.set(self.SECTION_GENERAL, "; 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))
|
|
cf.set(self.SECTION_GENERAL, self.OPT_NUMBER_ROWS, 1 if self.numberRows else 0)
|
|
cf.set(self.SECTION_GENERAL, "; 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))
|
|
cf.set(self.SECTION_GENERAL, self.OPT_GROUP_CONN, 1 if self.groupConnectors else 0)
|
|
cf.set(self.SECTION_GENERAL, "; 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))
|
|
cf.set(self.SECTION_GENERAL, self.OPT_USE_REGEX, 1 if self.useRegex 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")
|
|
cf.set(self.SECTION_IGNORE, "; Titles are case-insensitive")
|
|
|
|
for i in self.ignore:
|
|
cf.set(self.SECTION_IGNORE, 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")
|
|
for a in self.aliases:
|
|
cf.set(self.SECTION_ALIASES, " ".join(a))
|
|
|
|
for col in self.regex.keys():
|
|
|
|
reg = self.regex[col]
|
|
|
|
section = self.columnToGroup(col)
|
|
cf.add_section(section)
|
|
#comments
|
|
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)
|
|
|
|
|
|
|
|
with open(file, 'wb') as configfile:
|
|
cf.write(configfile) |