Added more flexibility to filters.

Support for:
- Pass all
- Negated (in addition to its internal option)
- List of filters
This commit is contained in:
Salvador E. Tropea 2020-08-29 19:32:04 -03:00
parent 4cc5494f5f
commit a1455e0f46
4 changed files with 139 additions and 53 deletions

View File

@ -371,11 +371,11 @@ Next time you need this list just use an alias, like this:
- `hide_stats_info`: [boolean=false] Hide statistics information.
- `quote_all`: [boolean=false] Enclose all values using double quotes.
- `separator`: [string=','] CSV Separator. TXT and TSV always use tab as delimiter.
- `dnc_filter`: [string='_kibom_dnc'] Name of the filter to mark components as 'Do Not Change'.
- `dnc_filter`: [string|list(string)='_kibom_dnc'] Name of the filter to mark components as 'Do Not Change'.
The default filter marks components with a DNC value or DNC in the Config field.
- `dnf_filter`: [string='_kibom_dnf'] Name of the filter to mark components as 'Do Not Fit'.
- `dnf_filter`: [string|list(string)='_kibom_dnf'] Name of the filter to mark components as 'Do Not Fit'.
The default filter marks components with a DNF value or DNF in the Config field.
- `exclude_filter`: [string='_mechanical'] Name of the filter to exclude components from BoM processing.
- `exclude_filter`: [string|list(string)='_mechanical'] Name of the filter to exclude components from BoM processing.
The default filter excludes test points, fiducial marks, mounting holes, etc.
- `fit_field`: [string='Config'] Field name used for internal filters.
- `format`: [string=''] [HTML,CSV,TXT,TSV,XML,XLSX] format for the BoM.

View File

@ -63,13 +63,13 @@ outputs:
quote_all: false
# [string=','] CSV Separator. TXT and TSV always use tab as delimiter
separator: ','
# [string='_kibom_dnc'] Name of the filter to mark components as 'Do Not Change'.
# [string|list(string)='_kibom_dnc'] Name of the filter to mark components as 'Do Not Change'.
# The default filter marks components with a DNC value or DNC in the Config field
dnc_filter: '_kibom_dnc'
# [string='_kibom_dnf'] Name of the filter to mark components as 'Do Not Fit'.
# [string|list(string)='_kibom_dnf'] Name of the filter to mark components as 'Do Not Fit'.
# The default filter marks components with a DNF value or DNF in the Config field
dnf_filter: '_kibom_dnf'
# [string='_mechanical'] Name of the filter to exclude components from BoM processing.
# [string|list(string)='_mechanical'] Name of the filter to exclude components from BoM processing.
# The default filter excludes test points, fiducial marks, mounting holes, etc
exclude_filter: '_mechanical'
# [string='Config'] Field name used for internal filters

View File

@ -3,10 +3,53 @@
# Copyright (c) 2020 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
from .registrable import RegFilter
from .registrable import RegFilter, Registrable, RegOutput
from .error import KiPlotConfigurationError
from .macros import macros, document # noqa: F401
class DummyFilter(Registrable):
""" A filter that allows all """
def __init__(self):
super().__init__()
self.name = 'Dummy'
self.type = 'dummy'
self.comment = 'A filter that does nothing'
def filter(self, comp):
return True
class MultiFilter(Registrable):
""" A filter containing a list of filters.
They are applied in sequence. """
def __init__(self, filters):
super().__init__()
self.name = ','.join([f.name for f in filters])
self.type = ','.join([f.type for f in filters])
self.comment = 'Multi-filter'
self.filters = filters
def filter(self, comp):
for f in self.filters:
if not f.filter(comp):
return False
return True
class NotFilter(Registrable):
""" A filter that returns the inverted result """
def __init__(self, filter):
super().__init__()
self.name = filter.name
self.type = '!'+filter.type
self.comment = filter.comment
self.filter = filter
def filter(self, comp):
return not self.filter(comp)
class BaseFilter(RegFilter):
def __init__(self):
super().__init__()
@ -18,3 +61,56 @@ class BaseFilter(RegFilter):
""" Type of filter """
self.comment = ''
""" A comment for documentation purposes """
@staticmethod
def solve_filter(name, def_key, def_real, creator, target_name):
""" Name can be:
- A class, meaning we have to use a default.
- A string, the name of a filter.
- A list of strings, the name of 1 or more filters.
If any of the names matches def_key we call creator asking to create the filter.
If def_real is not None we pass this name to creator. """
if isinstance(name, type):
# Nothing specified, use the default
names = [def_key]
elif isinstance(name, str):
# User provided, but only one, make a list
names = [name]
# Here we should have a list of strings
filters = []
for name in names:
if name[0] == '!':
invert = True
name = name[1:]
else:
invert = False
filter = None
if name == def_key:
# Matched the default name, translate it to the real name
if def_real:
name = def_real
# Is already defined?
if RegOutput.is_filter(name):
filter = RegOutput.get_filter(name)
else: # Nope, create it
tree = creator(name)
filter = RegFilter.get_class_for(tree['type'])()
filter.set_tree(tree)
filter.config()
RegOutput.add_filter(filter)
elif name:
# A filter that is supposed to exist
if not RegOutput.is_filter(name):
raise KiPlotConfigurationError("Unknown filter `{}` used for `{}`".format(name, target_name))
filter = RegOutput.get_filter(name)
if filter:
if invert:
filters.append(NotFilter(filter))
else:
filters.append(filter)
# Finished collecting filters
if not filters:
return DummyFilter()
if len(filters) == 1:
return filters[0]
return MultiFilter(filters)

View File

@ -16,7 +16,7 @@ from .macros import macros, document, output_class # noqa: F401
from .bom.columnlist import ColumnList, BoMError
from .bom.bom import do_bom
from .var_kibom import KiBoM
from .fil_generic import Generic
from .fil_base import BaseFilter
from . import log
logger = log.get_logger(__name__)
@ -218,14 +218,14 @@ class BoMOptions(BaseOptions):
self.csv = BoMCSV
""" [dict] Options for the CSV, TXT and TSV formats """
# * Filters
self.exclude_filter = '_mechanical'
""" Name of the filter to exclude components from BoM processing.
self.exclude_filter = Optionable
""" [string|list(string)='_mechanical'] Name of the filter to exclude components from BoM processing.
The default filter excludes test points, fiducial marks, mounting holes, etc """
self.dnf_filter = '_kibom_dnf'
""" Name of the filter to mark components as 'Do Not Fit'.
self.dnf_filter = Optionable
""" [string|list(string)='_kibom_dnf'] Name of the filter to mark components as 'Do Not Fit'.
The default filter marks components with a DNF value or DNF in the Config field """
self.dnc_filter = '_kibom_dnc'
""" Name of the filter to mark components as 'Do Not Change'.
self.dnc_filter = Optionable
""" [string|list(string)='_kibom_dnc'] Name of the filter to mark components as 'Do Not Change'.
The default filter marks components with a DNC value or DNC in the Config field """
# * Grouping criteria
self.group_connectors = True
@ -283,43 +283,30 @@ class BoMOptions(BaseOptions):
self.variant.variant = []
self.variant.name = 'default'
def _solve_exclude_filter(self):
""" Check we have a valid exclude filter. Create it if needed. """
if not RegOutput.is_filter(self.exclude_filter):
if self.exclude_filter == '_mechanical':
o = Generic()
o_tree = {'name': '_mechanical', 'type': 'generic', 'comment': 'Internal default mechanical filter'}
o_tree['exclude_any'] = BoMOptions.DEFAULT_EXCLUDE
o.set_tree(o_tree)
o.config()
RegOutput.add_filter(o)
self.exclude_filter = o
return
raise KiPlotConfigurationError("Unknown filter `{}` used for `exclude_filter`".format(self.exclude_filter))
self.exclude_filter = RegOutput.get_filter(self.exclude_filter)
@staticmethod
def _create_mechanical(name):
o_tree = {'name': name}
o_tree['type'] = 'generic'
o_tree['comment'] = 'Internal default mechanical filter'
o_tree['exclude_any'] = BoMOptions.DEFAULT_EXCLUDE
logger.debug('Creating internal filter: '+str(o_tree))
return o_tree
def _solve_dnx_filter(self, name, type, invert=False):
real_name = name
if real_name == '_kibom_'+type:
# Allow different internal filters using different config fields
real_name += '_'+self.fit_field
if not RegOutput.is_filter(real_name):
o = Generic()
o_tree = {'name': real_name, 'type': 'generic'}
o_tree['comment'] = 'Internal KiBoM '+type.upper()+' filter ('+self.fit_field+')'
o_tree['config_field'] = self.fit_field
o_tree['exclude_value'] = True
o_tree['exclude_config'] = True
o_tree['keys'] = type+'_list'
if invert:
o_tree['invert'] = True
o.set_tree(o_tree)
o.config()
RegOutput.add_filter(o)
return o
if not RegOutput.is_filter(real_name):
raise KiPlotConfigurationError("Unknown filter `{}` used for `{}_filter`".format(real_name, type))
return RegOutput.get_filter(real_name)
@staticmethod
def _create_kibom_dnx(name):
type = name[7:10]
subtype = name[11:]
o_tree = {'name': name}
o_tree['type'] = 'generic'
o_tree['comment'] = 'Internal KiBoM '+type.upper()+' filter ('+subtype+')'
o_tree['config_field'] = subtype
o_tree['exclude_value'] = True
o_tree['exclude_config'] = True
o_tree['keys'] = type+'_list'
if type[-1] == 'c':
o_tree['invert'] = True
logger.debug('Creating internal filter: '+str(o_tree))
return o_tree
def config(self):
super().config()
@ -349,11 +336,14 @@ class BoMOptions(BaseOptions):
if isinstance(self.component_aliases, type):
self.component_aliases = DEFAULT_ALIASES
# exclude_filter
self._solve_exclude_filter()
self.exclude_filter = BaseFilter.solve_filter(self.exclude_filter, '_mechanical', None,
BoMOptions._create_mechanical, 'exclude_filter')
# dnf_filter
self.dnf_filter = self._solve_dnx_filter(self.dnf_filter, 'dnf')
self.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, '_kibom_dnf', '_kibom_dnf_'+self.fit_field,
BoMOptions._create_kibom_dnx, 'dnf_filter')
# dnc_filter
self.dnc_filter = self._solve_dnx_filter(self.dnc_filter, 'dnc', True)
self.dnc_filter = BaseFilter.solve_filter(self.dnc_filter, '_kibom_dnc', '_kibom_dnc_'+self.fit_field,
BoMOptions._create_kibom_dnx, 'dnc_filter')
# Variants, make it an object
self._normalize_variant()
# Field names are handled in lowercase