Added more flexibility to filters.
Support for: - Pass all - Negated (in addition to its internal option) - List of filters
This commit is contained in:
parent
4cc5494f5f
commit
a1455e0f46
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue