New filter `field_modify`

- Also added `pre_transform` shortcut.
This commit is contained in:
Salvador E. Tropea 2022-10-03 13:11:01 -03:00
parent 82b299a159
commit 925d3596dd
7 changed files with 114 additions and 10 deletions

View File

@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Command line option to specify warnings to be excluded. Useful for
warnings issued before applying the global options (i.e during
import). (#296)
- `pre_transform` filter to outputs supporting variants.
- New outputs:
- PCB_Variant: saves a PCB with filters and variants applied.
- Copy_Files: used to copy files to the output directory. (#279)
@ -45,7 +46,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Internal BoM: Now you can aggregate components using CSV files. (See #248)
- Now you can check PCB and schematic parity using the `update_xml` preflight
(See #297)
- New filter `urlify` to convert URLs in fields to HTML links (#311)
- New filters:
- `urlify` to convert URLs in fields to HTML links (#311)
- `field_modify` a more generic field transformer
### Fixed
- Problems to compress netlists. (#287)

66
kibot/fil_field_modify.py Normal file
View File

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022 Salvador E. Tropea
# Copyright (c) 2022 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
# Description: Applies changes to fields
import re
from .optionable import Optionable
from .error import KiPlotConfigurationError
from .fil_base import BaseFilter
from .macros import macros, document, filter_class # noqa: F401
from . import log
logger = log.get_logger()
@filter_class
class Field_Modify(BaseFilter): # noqa: F821
""" Field_Modify
Changes the content of one or more fields """
def __init__(self):
super().__init__()
self._is_transform = True
with document:
self.fields = Optionable
""" [string|list(string)='Datasheet'] Fields to convert """
self.regex = r'(https?://\S+)'
""" Regular expression to match the field content.
Only fields that matches will be modified.
An empty regex will match anything.
The example matches an HTTP URL """
self.replace = r'<a href="\1">\1</a>'
""" Text to replace, can contain references to sub-expressions.
The example converts an HTTP URL into an HTML link, like the URLify filter """
self.include = Optionable
""" [string|list(string)=''] Name of the filter to select which components will be affected.
Applied to all if nothing specified here """
self._fields_example = 'Datasheet'
self._include_solved = False
def config(self, parent):
super().config(parent)
if isinstance(self.fields, type):
self.fields = ['Datasheet']
self.fields = Optionable.force_list(self.fields)
try:
self._regex = re.compile(self.regex)
except Exception as e:
raise KiPlotConfigurationError('Invalid regular expression '+str(e))
def filter(self, comp):
""" Apply the regex substitution """
if not self._include_solved:
self.include = BaseFilter.solve_filter(self.include, 'include')
self._include_solved = True
if self.include and not self.include.filter(comp):
return
for fld in self.fields:
value = comp.get_field_value(fld)
if not value:
continue
new_value = self._regex.sub(self.replace, value)
if new_value != value:
logger.debugl(2, '{}: {} -> {}'.format(fld, value, new_value))
comp.set_field(fld, new_value)

View File

@ -22,7 +22,7 @@ else:
from pcbnew import EDGE_MODULE, wxPoint, LSET
from .registrable import RegOutput
from .optionable import Optionable, BaseOptions
from .fil_base import BaseFilter, apply_fitted_filter, reset_filters
from .fil_base import BaseFilter, apply_fitted_filter, reset_filters, apply_pre_transform
from .kicad.config import KiConf
from .macros import macros, document # noqa: F401
from .error import KiPlotConfigurationError
@ -196,6 +196,9 @@ class VariantOptions(BaseOptions):
self.dnf_filter = Optionable
""" [string|list(string)='_none'] Name of the filter to mark components as not fitted.
A short-cut to use for simple cases where a variant is an overkill """
self.pre_transform = Optionable
""" [string|list(string)='_none'] Name of the filter to transform fields before applying other filters.
A short-cut to use for simple cases where a variant is an overkill """
super().__init__()
self._comps = None
self.undo_3d_models = {}
@ -205,6 +208,7 @@ class VariantOptions(BaseOptions):
super().config(parent)
self.variant = RegOutput.check_variant(self.variant)
self.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, 'dnf_filter')
self.pre_transform = BaseFilter.solve_filter(self.pre_transform, 'pre_transform', is_transform=True)
def get_refs_hash(self):
if not self._comps:
@ -715,7 +719,7 @@ class VariantOptions(BaseOptions):
def run(self, output_dir):
""" Makes the list of components available """
if not self.dnf_filter and not self.variant:
if not self.dnf_filter and not self.variant and not self.pre_transform:
return
load_sch()
# Get the components list from the schematic
@ -723,6 +727,7 @@ class VariantOptions(BaseOptions):
get_board_comps_data(comps)
# Apply the filter
reset_filters(comps)
comps = apply_pre_transform(comps, self.pre_transform)
apply_fitted_filter(comps, self.dnf_filter)
# Apply the variant
if self.variant:

View File

@ -428,6 +428,9 @@ class BoMOptions(BaseOptions):
self.csv = BoMCSV
""" *[dict] Options for the CSV, TXT and TSV formats """
# * Filters
self.pre_transform = Optionable
""" [string|list(string)='_none'] Name of the filter to transform fields before applying other filters.
This option is for simple cases, consider using a full variant for complex cases """
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.
@ -551,8 +554,8 @@ class BoMOptions(BaseOptions):
self.variant.variant = []
self.variant.name = 'default'
# Delegate any filter to the variant
self.variant.set_def_filters(self.exclude_filter, self.dnf_filter, self.dnc_filter)
self.exclude_filter = self.dnf_filter = self.dnc_filter = None
self.variant.set_def_filters(self.exclude_filter, self.dnf_filter, self.dnc_filter, self.pre_transform)
self.exclude_filter = self.dnf_filter = self.dnc_filter = self.pre_transform = None
self.variant.config(self) # Fill or adjust any detail
def process_columns_config(self, cols, valid_columns, extra_columns, add_all=True):
@ -662,6 +665,7 @@ class BoMOptions(BaseOptions):
if isinstance(self.component_aliases, type):
self.component_aliases = DEFAULT_ALIASES
# Filters
self.pre_transform = BaseFilter.solve_filter(self.pre_transform, 'pre_transform')
self.exclude_filter = BaseFilter.solve_filter(self.exclude_filter, 'exclude_filter')
self.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, 'dnf_filter')
self.dnc_filter = BaseFilter.solve_filter(self.dnc_filter, 'dnc_filter')
@ -840,6 +844,7 @@ class BoMOptions(BaseOptions):
self.aggregate_comps(comps)
# Apply all the filters
reset_filters(comps)
comps = apply_pre_transform(comps, self.pre_transform)
apply_exclude_filter(comps, self.exclude_filter)
apply_fitted_filter(comps, self.dnf_filter)
apply_fixed_filter(comps, self.dnc_filter)

View File

@ -202,7 +202,7 @@ class IBoMOptions(VariantOptions):
self.blacklist += to_remove
# Convert attributes into options
for k, v in self.get_attrs_gen():
if not v or k in ['output', 'variant', 'dnf_filter']:
if not v or k in ['output', 'variant', 'dnf_filter', 'pre_transform']:
continue
cmd.append(BaseOutput.attr2longopt(k)) # noqa: F821
if not isinstance(v, bool): # must be str/(int, float)

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020 Salvador E. Tropea
# Copyright (c) 2020 Instituto Nacional de Tecnología Industrial
# Copyright (c) 2020-2022 Salvador E. Tropea
# Copyright (c) 2020-2022 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
# Description: Implements the KiBoM variants mechanism.
@ -22,6 +22,7 @@ class KiBoM(BaseVariant): # noqa: F821
+VARIANT includes the component only if we are using this variant """
def __init__(self):
super().__init__()
self._def_pre_transform = None
self._def_exclude_filter = None
self._def_dnf_filter = None
self._def_dnc_filter = None
@ -43,22 +44,25 @@ class KiBoM(BaseVariant): # noqa: F821
""" Returns the name of the field used to determine if the component belongs to the variant """
return self.config_field
def set_def_filters(self, exclude_filter, dnf_filter, dnc_filter):
def set_def_filters(self, exclude_filter, dnf_filter, dnc_filter, pre_transform):
""" Filters delegated to the variant """
self._def_exclude_filter = exclude_filter
self._def_dnf_filter = dnf_filter
self._def_dnc_filter = dnc_filter
self._def_pre_transform = pre_transform
def config(self, parent):
# Now we can let the parent initialize the filters
super().config(parent)
# Variants, ensure a lowercase list
self.variant = [v.lower() for v in self.force_list(self.variant)]
self.pre_transform = BaseFilter.solve_filter(self.pre_transform, 'pre_transform', is_transform=True)
# Filters priority:
# 1) Defined here
# 2) Delegated from the output format
# 3) KiBoM default behavior
# pre_transform
self.pre_transform = BaseFilter.solve_filter(self.pre_transform, 'pre_transform', self._def_pre_transform,
is_transform=True)
# exclude_filter
if not self._def_exclude_filter:
self._def_exclude_filter = IFILT_MECHANICAL

View File

@ -0,0 +1,21 @@
# Example KiBot config file
kibot:
version: 1
filters:
- name: 'not_u2'
type: generic
exclude_refs: ['U2']
- name: ds_link
type: field_modify
#include: 'not_u2'
outputs:
- name: 'bom_internal'
comment: "iBoM with datasheet"
type: ibom
dir: BoM
options:
extra_fields: Datasheet
# pre_transform: _datasheet_link
pre_transform: ds_link