New KiCost variant style.
- New internal filters `_var_rename_kicost` and `_kicost_dnp`. - New `skip_if_no_field` and `invert` options to the regex used in the generic filter.
This commit is contained in:
parent
afe80052b4
commit
1290bb6995
|
|
@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- `suparts`: Adds support for KiCost's subparts feature.
|
- `suparts`: Adds support for KiCost's subparts feature.
|
||||||
- `field_rename`: Used to rename schematic fields.
|
- `field_rename`: Used to rename schematic fields.
|
||||||
- `var_rename_kicost`: Like `var_rename` but using KiCost mechanism.
|
- `var_rename_kicost`: Like `var_rename` but using KiCost mechanism.
|
||||||
|
- New KiCost variant style.
|
||||||
|
- `skip_if_no_field` and `invert` options to the regex used in the generic
|
||||||
|
filter.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Errors and warnings from KiAuto now are printed as errors and warnings.
|
- Errors and warnings from KiAuto now are printed as errors and warnings.
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,8 @@ Currently the only type available is `generic`.
|
||||||
This filter is based on regular expressions.
|
This filter is based on regular expressions.
|
||||||
It also provides some shortcuts for common situations.
|
It also provides some shortcuts for common situations.
|
||||||
Note that matches aren't case sensitive and spaces at the beggining and the end are removed.
|
Note that matches aren't case sensitive and spaces at the beggining and the end are removed.
|
||||||
|
The internal `_mechanical` filter emulates the KiBoM behavior for default exclusions.
|
||||||
|
The internal `_kicost_dnp` filter emulates KiCost's `dnp` field.
|
||||||
* Valid keys:
|
* Valid keys:
|
||||||
- `comment`: [string=''] A comment for documentation purposes.
|
- `comment`: [string=''] A comment for documentation purposes.
|
||||||
- `config_field`: [string='Config'] Name of the field used to clasify components.
|
- `config_field`: [string='Config'] Name of the field used to clasify components.
|
||||||
|
|
@ -301,8 +303,10 @@ Currently the only type available is `generic`.
|
||||||
* Valid keys:
|
* Valid keys:
|
||||||
- `column`: [string=''] Name of the column to apply the regular expression.
|
- `column`: [string=''] Name of the column to apply the regular expression.
|
||||||
- *field*: Alias for column.
|
- *field*: Alias for column.
|
||||||
|
- `invert`: [boolean=false] Invert the regex match result.
|
||||||
- `regex`: [string=''] Regular expression to match.
|
- `regex`: [string=''] Regular expression to match.
|
||||||
- *regexp*: Alias for regex.
|
- *regexp*: Alias for regex.
|
||||||
|
- `skip_if_no_field`: [boolean=false] Skip this test if the field doesn't exist.
|
||||||
- `exclude_config`: [boolean=false] Exclude components containing a key value in the config field.
|
- `exclude_config`: [boolean=false] Exclude components containing a key value in the config field.
|
||||||
Separators are applied.
|
Separators are applied.
|
||||||
- `exclude_empty_val`: [boolean=false] Exclude components with empty 'Value'.
|
- `exclude_empty_val`: [boolean=false] Exclude components with empty 'Value'.
|
||||||
|
|
@ -320,8 +324,10 @@ Currently the only type available is `generic`.
|
||||||
* Valid keys:
|
* Valid keys:
|
||||||
- `column`: [string=''] Name of the column to apply the regular expression.
|
- `column`: [string=''] Name of the column to apply the regular expression.
|
||||||
- *field*: Alias for column.
|
- *field*: Alias for column.
|
||||||
|
- `invert`: [boolean=false] Invert the regex match result.
|
||||||
- `regex`: [string=''] Regular expression to match.
|
- `regex`: [string=''] Regular expression to match.
|
||||||
- *regexp*: Alias for regex.
|
- *regexp*: Alias for regex.
|
||||||
|
- `skip_if_no_field`: [boolean=false] Skip this test if the field doesn't exist.
|
||||||
- `invert`: [boolean=false] Invert the result of the filter.
|
- `invert`: [boolean=false] Invert the result of the filter.
|
||||||
- `keys`: [string|list(string)=dnf_list] [dnc_list,dnf_list] List of keys to match.
|
- `keys`: [string|list(string)=dnf_list] [dnc_list,dnf_list] List of keys to match.
|
||||||
The `dnf_list` and `dnc_list` internal lists can be specified as strings.
|
The `dnf_list` and `dnc_list` internal lists can be specified as strings.
|
||||||
|
|
@ -369,6 +375,7 @@ Currently the only type available is `generic`.
|
||||||
- var_rename_kicost: Var_Rename_KiCost
|
- var_rename_kicost: Var_Rename_KiCost
|
||||||
This filter implements the kicost.VARIANT:FIELD=VALUE renamer to get FIELD=VALUE when VARIANT is in use.
|
This filter implements the kicost.VARIANT:FIELD=VALUE renamer to get FIELD=VALUE when VARIANT is in use.
|
||||||
It applies the KiCost concept of variants (a regex to match the VARIANT).
|
It applies the KiCost concept of variants (a regex to match the VARIANT).
|
||||||
|
The internal `_var_rename_kicost` filter emulates the KiCost behavior.
|
||||||
* Valid keys:
|
* Valid keys:
|
||||||
- `comment`: [string=''] A comment for documentation purposes.
|
- `comment`: [string=''] A comment for documentation purposes.
|
||||||
- `name`: [string=''] Used to identify this particular filter definition.
|
- `name`: [string=''] Used to identify this particular filter definition.
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
# License: GPL-3.0
|
# License: GPL-3.0
|
||||||
# Project: KiBot (formerly KiPlot)
|
# Project: KiBot (formerly KiPlot)
|
||||||
from .registrable import RegFilter, Registrable, RegOutput
|
from .registrable import RegFilter, Registrable, RegOutput
|
||||||
from .misc import IFILT_MECHANICAL, IFILT_VAR_RENAME, IFILT_ROT_FOOTPRINT, IFILT_KICOST_RENAME, DISTRIBUTORS
|
from .misc import (IFILT_MECHANICAL, IFILT_VAR_RENAME, IFILT_ROT_FOOTPRINT, IFILT_KICOST_RENAME, DISTRIBUTORS,
|
||||||
|
IFILT_VAR_RENAME_KICOST, IFILT_KICOST_DNP)
|
||||||
from .error import KiPlotConfigurationError
|
from .error import KiPlotConfigurationError
|
||||||
from .bom.columnlist import ColumnList
|
from .bom.columnlist import ColumnList
|
||||||
from .macros import macros, document # noqa: F401
|
from .macros import macros, document # noqa: F401
|
||||||
|
|
@ -222,6 +223,14 @@ class BaseFilter(RegFilter):
|
||||||
logger.debug('Creating internal filter: '+str(o_tree))
|
logger.debug('Creating internal filter: '+str(o_tree))
|
||||||
return o_tree
|
return o_tree
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_var_rename_kicost(name):
|
||||||
|
o_tree = {'name': name}
|
||||||
|
o_tree['type'] = 'var_rename_kicost'
|
||||||
|
o_tree['comment'] = 'Internal default variant field renamer filter (KiCost style)'
|
||||||
|
logger.debug('Creating internal filter: '+str(o_tree))
|
||||||
|
return o_tree
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_rot_footprint(name):
|
def _create_rot_footprint(name):
|
||||||
o_tree = {'name': name}
|
o_tree = {'name': name}
|
||||||
|
|
@ -268,6 +277,15 @@ class BaseFilter(RegFilter):
|
||||||
logger.debug('Creating internal filter: '+str(o_tree))
|
logger.debug('Creating internal filter: '+str(o_tree))
|
||||||
return o_tree
|
return o_tree
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_kicost_dnp(name):
|
||||||
|
o_tree = {'name': name}
|
||||||
|
o_tree['type'] = 'generic'
|
||||||
|
o_tree['comment'] = 'Internal filter for KiCost `dnp` field'
|
||||||
|
# dnp = 0 is included, other dnp values are excluded
|
||||||
|
o_tree['exclude_any'] = [{'column': 'dnp', 'regex': r'^\s*0(\.0*)?\s*$', 'invert': True, 'skip_if_no_field': True}]
|
||||||
|
return o_tree
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_internal_filter(name):
|
def _create_internal_filter(name):
|
||||||
if name == IFILT_MECHANICAL:
|
if name == IFILT_MECHANICAL:
|
||||||
|
|
@ -280,6 +298,10 @@ class BaseFilter(RegFilter):
|
||||||
tree = BaseFilter._create_rot_footprint(name)
|
tree = BaseFilter._create_rot_footprint(name)
|
||||||
elif name == IFILT_KICOST_RENAME:
|
elif name == IFILT_KICOST_RENAME:
|
||||||
tree = BaseFilter._create_kicost_rename(name)
|
tree = BaseFilter._create_kicost_rename(name)
|
||||||
|
elif name == IFILT_VAR_RENAME_KICOST:
|
||||||
|
tree = BaseFilter._create_var_rename_kicost(name)
|
||||||
|
elif name == IFILT_KICOST_DNP:
|
||||||
|
tree = BaseFilter._create_kicost_dnp(name)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
filter = RegFilter.get_class_for(tree['type'])()
|
filter = RegFilter.get_class_for(tree['type'])()
|
||||||
|
|
@ -299,7 +321,10 @@ class BaseFilter(RegFilter):
|
||||||
# Nothing specified, use the default
|
# Nothing specified, use the default
|
||||||
if default is None:
|
if default is None:
|
||||||
return None
|
return None
|
||||||
names = [default]
|
if isinstance(default, list):
|
||||||
|
names = default
|
||||||
|
else:
|
||||||
|
names = [default]
|
||||||
elif isinstance(names, str):
|
elif isinstance(names, str):
|
||||||
# User provided, but only one, make a list
|
# User provided, but only one, make a list
|
||||||
if names == '_none':
|
if names == '_none':
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,9 @@ class Generic(BaseFilter): # noqa: F821
|
||||||
""" Generic filter
|
""" Generic filter
|
||||||
This filter is based on regular expressions.
|
This filter is based on regular expressions.
|
||||||
It also provides some shortcuts for common situations.
|
It also provides some shortcuts for common situations.
|
||||||
Note that matches aren't case sensitive and spaces at the beggining and the end are removed """
|
Note that matches aren't case sensitive and spaces at the beggining and the end are removed.
|
||||||
|
The internal `_mechanical` filter emulates the KiBoM behavior for default exclusions.
|
||||||
|
The internal `_kicost_dnp` filter emulates KiCost's `dnp` field """
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
with document:
|
with document:
|
||||||
|
|
@ -119,8 +121,14 @@ class Generic(BaseFilter): # noqa: F821
|
||||||
if not self.include_only: # Nothing to match against, means include all
|
if not self.include_only: # Nothing to match against, means include all
|
||||||
return True
|
return True
|
||||||
for reg in self.include_only:
|
for reg in self.include_only:
|
||||||
|
if reg.skip_if_no_field and not c.is_field(reg.column):
|
||||||
|
# Skip the check if the field doesn't exist
|
||||||
|
continue
|
||||||
field_value = c.get_field_value(reg.column)
|
field_value = c.get_field_value(reg.column)
|
||||||
if reg.regex.search(field_value):
|
res = reg.regex.search(field_value)
|
||||||
|
if reg.invert:
|
||||||
|
res = not res
|
||||||
|
if res:
|
||||||
if GS.debug_level > 1:
|
if GS.debug_level > 1:
|
||||||
logger.debug("Including '{ref}': Field '{field}' ({value}) matched '{re}'".format(
|
logger.debug("Including '{ref}': Field '{field}' ({value}) matched '{re}'".format(
|
||||||
ref=c.ref, field=reg.column, value=field_value, re=reg.regex))
|
ref=c.ref, field=reg.column, value=field_value, re=reg.regex))
|
||||||
|
|
@ -134,8 +142,14 @@ class Generic(BaseFilter): # noqa: F821
|
||||||
if not self.exclude_any: # Nothing to match against, means don't exclude any
|
if not self.exclude_any: # Nothing to match against, means don't exclude any
|
||||||
return False
|
return False
|
||||||
for reg in self.exclude_any:
|
for reg in self.exclude_any:
|
||||||
|
if reg.skip_if_no_field and not c.is_field(reg.column):
|
||||||
|
# Skip the check if the field doesn't exist
|
||||||
|
continue
|
||||||
field_value = c.get_field_value(reg.column)
|
field_value = c.get_field_value(reg.column)
|
||||||
if reg.regex.search(field_value):
|
res = reg.regex.search(field_value)
|
||||||
|
if reg.invert:
|
||||||
|
res = not res
|
||||||
|
if res:
|
||||||
if GS.debug_level > 1:
|
if GS.debug_level > 1:
|
||||||
logger.debug("Excluding '{ref}': Field '{field}' ({value}) matched '{re}'".format(
|
logger.debug("Excluding '{ref}': Field '{field}' ({value}) matched '{re}'".format(
|
||||||
ref=c.ref, field=reg.column, value=field_value, re=reg.regex))
|
ref=c.ref, field=reg.column, value=field_value, re=reg.regex))
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ logger = log.get_logger(__name__)
|
||||||
class Var_Rename_KiCost(BaseFilter): # noqa: F821
|
class Var_Rename_KiCost(BaseFilter): # noqa: F821
|
||||||
""" Var_Rename_KiCost
|
""" Var_Rename_KiCost
|
||||||
This filter implements the kicost.VARIANT:FIELD=VALUE renamer to get FIELD=VALUE when VARIANT is in use.
|
This filter implements the kicost.VARIANT:FIELD=VALUE renamer to get FIELD=VALUE when VARIANT is in use.
|
||||||
It applies the KiCost concept of variants (a regex to match the VARIANT) """
|
It applies the KiCost concept of variants (a regex to match the VARIANT).
|
||||||
|
The internal `_var_rename_kicost` filter emulates the KiCost behavior """
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._is_transform = True
|
self._is_transform = True
|
||||||
|
|
@ -53,7 +54,7 @@ class Var_Rename_KiCost(BaseFilter): # noqa: F821
|
||||||
variant = GS.variant[0]
|
variant = GS.variant[0]
|
||||||
else:
|
else:
|
||||||
variant = '('+'|'.join(GS.variant)+')'
|
variant = '('+'|'.join(GS.variant)+')'
|
||||||
var = re.compile(variant, re.IGNORECASE)
|
var_re = re.compile(variant, re.IGNORECASE)
|
||||||
for name, value in comp.get_user_fields():
|
for name, value in comp.get_user_fields():
|
||||||
name = name.strip().lower()
|
name = name.strip().lower()
|
||||||
# Remove the prefix
|
# Remove the prefix
|
||||||
|
|
@ -69,13 +70,13 @@ class Var_Rename_KiCost(BaseFilter): # noqa: F821
|
||||||
# Successfully separated
|
# Successfully separated
|
||||||
f_variant = res[0].lower()
|
f_variant = res[0].lower()
|
||||||
f_field = res[1].lower()
|
f_field = res[1].lower()
|
||||||
if var.match(f_variant):
|
if var_re.match(f_variant):
|
||||||
# Variant matched
|
# Variant matched
|
||||||
if GS.debug_level > 2:
|
if GS.debug_level > 2:
|
||||||
logger.debug('ref: {} {}: {} -> {}'.
|
logger.debug('ref: {} {}: {} -> {}'.
|
||||||
format(comp.ref, f_field, comp.get_field_value(f_field), value))
|
format(comp.ref, f_field, comp.get_field_value(f_field), value))
|
||||||
comp.set_field(f_field, value)
|
comp.set_field(f_field, value)
|
||||||
elif self.variant_to_value and var.match(name):
|
elif self.variant_to_value and var_re.match(name):
|
||||||
# The field matches the variant and the user wants to change the value
|
# The field matches the variant and the user wants to change the value
|
||||||
if GS.debug_level > 2:
|
if GS.debug_level > 2:
|
||||||
logger.debug('ref: {} value: {} -> {}'.format(comp.ref, comp.value, value))
|
logger.debug('ref: {} value: {} -> {}'.format(comp.ref, comp.value, value))
|
||||||
|
|
|
||||||
|
|
@ -835,6 +835,9 @@ class SchematicComponent(object):
|
||||||
return self.dfields[field].value
|
return self.dfields[field].value
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
def is_field(self, field):
|
||||||
|
return field in self.dfields
|
||||||
|
|
||||||
def get_free_field_number(self):
|
def get_free_field_number(self):
|
||||||
""" Looks for a field number that isn't currently in use """
|
""" Looks for a field number that isn't currently in use """
|
||||||
max_num = -1
|
max_num = -1
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,10 @@ KICAD_VERSION_5_99 = 5099000
|
||||||
# Internal filter names
|
# Internal filter names
|
||||||
IFILT_MECHANICAL = '_mechanical'
|
IFILT_MECHANICAL = '_mechanical'
|
||||||
IFILT_VAR_RENAME = '_var_rename'
|
IFILT_VAR_RENAME = '_var_rename'
|
||||||
|
IFILT_VAR_RENAME_KICOST = '_var_rename_kicost'
|
||||||
IFILT_ROT_FOOTPRINT = '_rot_footprint'
|
IFILT_ROT_FOOTPRINT = '_rot_footprint'
|
||||||
IFILT_KICOST_RENAME = '_kicost_rename'
|
IFILT_KICOST_RENAME = '_kicost_rename'
|
||||||
|
IFILT_KICOST_DNP = '_kicost_dnp'
|
||||||
# KiCad 5 GUI values for the attribute
|
# KiCad 5 GUI values for the attribute
|
||||||
UI_THT = 0 # 1 for KiCad 6
|
UI_THT = 0 # 1 for KiCad 6
|
||||||
UI_SMD = 1 # 2 for KiCad 6
|
UI_SMD = 1 # 2 for KiCad 6
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,10 @@ class BoMRegex(Optionable):
|
||||||
""" {column} """
|
""" {column} """
|
||||||
self.regexp = None
|
self.regexp = None
|
||||||
""" {regex} """
|
""" {regex} """
|
||||||
|
self.skip_if_no_field = False
|
||||||
|
""" Skip this test if the field doesn't exist """
|
||||||
|
self.invert = False
|
||||||
|
""" Invert the regex match result """
|
||||||
|
|
||||||
|
|
||||||
class VariantOptions(BaseOptions):
|
class VariantOptions(BaseOptions):
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
# Project: KiBot (formerly KiPlot)
|
# Project: KiBot (formerly KiPlot)
|
||||||
from .registrable import RegVariant
|
from .registrable import RegVariant
|
||||||
from .optionable import Optionable
|
from .optionable import Optionable
|
||||||
from .fil_base import apply_exclude_filter, apply_fitted_filter, apply_fixed_filter, BaseFilter, apply_pre_transform
|
from .fil_base import apply_exclude_filter, apply_fitted_filter, apply_fixed_filter, apply_pre_transform
|
||||||
from .macros import macros, document # noqa: F401
|
from .macros import macros, document # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -25,21 +25,20 @@ class BaseVariant(RegVariant):
|
||||||
# * Filters
|
# * Filters
|
||||||
self.pre_transform = Optionable
|
self.pre_transform = Optionable
|
||||||
""" [string|list(string)=''] Name of the filter to transform fields before applying other filters.
|
""" [string|list(string)=''] Name of the filter to transform fields before applying other filters.
|
||||||
Use '_var_rename' to transform VARIANT:FIELD fields """
|
Use '_var_rename' to transform VARIANT:FIELD fields.
|
||||||
|
Use '_var_rename_kicost' to transform kicost.VARIANT:FIELD fields.
|
||||||
|
Use '_kicost_rename' to apply KiCost field rename rules """
|
||||||
self.exclude_filter = Optionable
|
self.exclude_filter = Optionable
|
||||||
""" [string|list(string)=''] Name of the filter to exclude components from BoM processing.
|
""" [string|list(string)=''] Name of the filter to exclude components from BoM processing.
|
||||||
Use '_mechanical' for the default KiBoM behavior """
|
Use '_mechanical' for the default KiBoM behavior """
|
||||||
self.dnf_filter = Optionable
|
self.dnf_filter = Optionable
|
||||||
""" [string|list(string)=''] Name of the filter to mark components as 'Do Not Fit'.
|
""" [string|list(string)=''] Name of the filter to mark components as 'Do Not Fit'.
|
||||||
Use '_kibom_dnf' for the default KiBoM behavior """
|
Use '_kibom_dnf' for the default KiBoM behavior.
|
||||||
|
Use '_kicost_dnp'' for the default KiCost behavior """
|
||||||
self.dnc_filter = Optionable
|
self.dnc_filter = Optionable
|
||||||
""" [string|list(string)=''] Name of the filter to mark components as 'Do Not Change'.
|
""" [string|list(string)=''] Name of the filter to mark components as 'Do Not Change'.
|
||||||
Use '_kibom_dnc' for the default KiBoM behavior """
|
Use '_kibom_dnc' for the default KiBoM behavior """
|
||||||
|
|
||||||
def config(self, parent):
|
|
||||||
super().config(parent)
|
|
||||||
self.pre_transform = BaseFilter.solve_filter(self.pre_transform, 'pre_transform', is_transform=True)
|
|
||||||
|
|
||||||
def filter(self, comps):
|
def filter(self, comps):
|
||||||
# Apply all the filters
|
# Apply all the filters
|
||||||
comps = apply_pre_transform(comps, self.pre_transform)
|
comps = apply_pre_transform(comps, self.pre_transform)
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ class IBoM(BaseVariant): # noqa: F821
|
||||||
|
|
||||||
def config(self, parent):
|
def config(self, parent):
|
||||||
super().config(parent)
|
super().config(parent)
|
||||||
|
self.pre_transform = BaseFilter.solve_filter(self.pre_transform, 'pre_transform', is_transform=True)
|
||||||
self.exclude_filter = BaseFilter.solve_filter(self.exclude_filter, 'exclude_filter', IFILT_MECHANICAL)
|
self.exclude_filter = BaseFilter.solve_filter(self.exclude_filter, 'exclude_filter', IFILT_MECHANICAL)
|
||||||
self.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, 'dnf_filter')
|
self.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, 'dnf_filter')
|
||||||
self.dnc_filter = BaseFilter.solve_filter(self.dnc_filter, 'dnc_filter')
|
self.dnc_filter = BaseFilter.solve_filter(self.dnc_filter, 'dnc_filter')
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ class KiBoM(BaseVariant): # noqa: F821
|
||||||
else:
|
else:
|
||||||
self.variant = []
|
self.variant = []
|
||||||
self.variant = [v.lower() for v in self.variant]
|
self.variant = [v.lower() for v in self.variant]
|
||||||
|
self.pre_transform = BaseFilter.solve_filter(self.pre_transform, 'pre_transform', is_transform=True)
|
||||||
# Filters priority:
|
# Filters priority:
|
||||||
# 1) Defined here
|
# 1) Defined here
|
||||||
# 2) Delegated from the output format
|
# 2) Delegated from the output format
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020-2021 Salvador E. Tropea
|
||||||
|
# Copyright (c) 2020-2021 Instituto Nacional de Tecnología Industrial
|
||||||
|
# License: GPL-3.0
|
||||||
|
# Project: KiBot (formerly KiPlot)
|
||||||
|
"""
|
||||||
|
Implements the KiCost variants mechanism.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
from .gs import GS
|
||||||
|
from .misc import IFILT_VAR_RENAME_KICOST, IFILT_KICOST_RENAME, IFILT_KICOST_DNP
|
||||||
|
from .fil_base import BaseFilter
|
||||||
|
from .macros import macros, document, variant_class # noqa: F401
|
||||||
|
from . import log
|
||||||
|
|
||||||
|
logger = log.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@variant_class
|
||||||
|
class KiCost(BaseVariant): # noqa: F821
|
||||||
|
""" KiCost variant style
|
||||||
|
The `variant` field (configurable) contains one or more values.
|
||||||
|
If any of these values matches the variant regex the component is included.
|
||||||
|
By default a pre-transform filter is applied to support kicost.VARIANT:FIELD and
|
||||||
|
field name aliases used by KiCost.
|
||||||
|
Also a default `dnf_filter` implements the KiCost DNP mechanism """
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
with document:
|
||||||
|
self.variant = ''
|
||||||
|
""" Variants to match (regex) """
|
||||||
|
self.variant_field = 'variant'
|
||||||
|
""" Name of the field that stores board variant/s for component """
|
||||||
|
self.separators = ',;/ '
|
||||||
|
""" Valid separators for variants in the variant field.
|
||||||
|
Each character is a valid separator """
|
||||||
|
|
||||||
|
def config(self, parent):
|
||||||
|
super().config(parent)
|
||||||
|
self.pre_transform = BaseFilter.solve_filter(self.pre_transform, 'pre_transform',
|
||||||
|
[IFILT_VAR_RENAME_KICOST, IFILT_KICOST_RENAME], is_transform=True)
|
||||||
|
self.exclude_filter = BaseFilter.solve_filter(self.exclude_filter, 'exclude_filter')
|
||||||
|
self.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, 'dnf_filter', IFILT_KICOST_DNP)
|
||||||
|
self.dnc_filter = BaseFilter.solve_filter(self.dnc_filter, 'dnc_filter')
|
||||||
|
if not self.separators:
|
||||||
|
self.separators = ' '
|
||||||
|
else:
|
||||||
|
self.separators = '['+self.separators+']'
|
||||||
|
|
||||||
|
def filter(self, comps):
|
||||||
|
GS.variant = [self.variant]
|
||||||
|
comps = super().filter(comps)
|
||||||
|
logger.debug("Applying KiCost style variant `{}`".format(self.name))
|
||||||
|
if not self.variant_field or not self.variant:
|
||||||
|
# No variant field or not variant regex
|
||||||
|
# Just skip the process
|
||||||
|
return comps
|
||||||
|
# Apply to all the components
|
||||||
|
var_re = re.compile(self.variant, flags=re.IGNORECASE)
|
||||||
|
for c in comps:
|
||||||
|
logger.debug("{} {} {}".format(c.ref, c.fitted, c.included))
|
||||||
|
if not (c.fitted and c.included):
|
||||||
|
# Don't check if we already discarded it
|
||||||
|
continue
|
||||||
|
variants = c.get_field_value(self.variant_field)
|
||||||
|
if variants:
|
||||||
|
# The component belong to one or more variant
|
||||||
|
for v in re.split(self.separators, variants):
|
||||||
|
if var_re.match(v):
|
||||||
|
# Matched, remains
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# None of the variants matched
|
||||||
|
c.fitted = False
|
||||||
|
if GS.debug_level > 2:
|
||||||
|
logger.debug('ref: {} value: {} -> False'.format(c.ref, c.value))
|
||||||
|
return comps
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
EESchema Schematic File Version 4
|
||||||
|
EELAYER 30 0
|
||||||
|
EELAYER END
|
||||||
|
$Descr A4 11693 8268
|
||||||
|
encoding utf-8
|
||||||
|
Sheet 1 1
|
||||||
|
Title "KiBom Test Schematic"
|
||||||
|
Date "2020-03-12"
|
||||||
|
Rev "A"
|
||||||
|
Comp "https://github.com/SchrodingersGat/KiBom"
|
||||||
|
Comment1 ""
|
||||||
|
Comment2 ""
|
||||||
|
Comment3 ""
|
||||||
|
Comment4 ""
|
||||||
|
$EndDescr
|
||||||
|
Text Notes 510 730 0 79 ~ 0
|
||||||
|
This schematic serves as a test-file for the KiBot export script.\nThis is the KiCost variants style test.
|
||||||
|
Text Notes 5950 2600 0 118 ~ 0
|
||||||
|
The test tests the following \nvariants matrix:\n production test default\nC1 X\nC2 X X\nR1 X X X\nR2 X X\n
|
||||||
|
$Comp
|
||||||
|
L Device:C C1
|
||||||
|
U 1 1 5F43BEC2
|
||||||
|
P 1000 1700
|
||||||
|
F 0 "C1" H 1115 1746 50 0000 L CNN
|
||||||
|
F 1 "1nF" H 1115 1655 50 0000 L CNN
|
||||||
|
F 2 "" H 1038 1550 50 0001 C CNN
|
||||||
|
F 3 "~" H 1000 1700 50 0001 C CNN
|
||||||
|
F 4 "1" H 1000 1700 50 0001 C CNN "kicost.default:dnp"
|
||||||
|
F 5 "0.0" H 1000 1700 50 0001 C CNN "kicost.test:dnp"
|
||||||
|
F 6 "" H 1000 1700 50 0001 C CNN "kicost.production:nopop"
|
||||||
|
1 1000 1700
|
||||||
|
1 0 0 -1
|
||||||
|
$EndComp
|
||||||
|
$Comp
|
||||||
|
L Device:C C2
|
||||||
|
U 1 1 5F43CE1C
|
||||||
|
P 1450 1700
|
||||||
|
F 0 "C2" H 1565 1746 50 0000 L CNN
|
||||||
|
F 1 "1000 pF" H 1565 1655 50 0000 L CNN
|
||||||
|
F 2 "" H 1488 1550 50 0001 C CNN
|
||||||
|
F 3 "~" H 1450 1700 50 0001 C CNN
|
||||||
|
F 4 "production,test" H 1450 1700 50 0001 C CNN "version"
|
||||||
|
1 1450 1700
|
||||||
|
1 0 0 -1
|
||||||
|
$EndComp
|
||||||
|
$Comp
|
||||||
|
L Device:R R1
|
||||||
|
U 1 1 5F43D144
|
||||||
|
P 2100 1700
|
||||||
|
F 0 "R1" H 2170 1746 50 0000 L CNN
|
||||||
|
F 1 "1k" H 2170 1655 50 0000 L CNN
|
||||||
|
F 2 "" V 2030 1700 50 0001 C CNN
|
||||||
|
F 3 "~" H 2100 1700 50 0001 C CNN
|
||||||
|
F 4 "3k3" H 2100 1700 50 0001 C CNN "kicost.test:Value"
|
||||||
|
1 2100 1700
|
||||||
|
1 0 0 -1
|
||||||
|
$EndComp
|
||||||
|
$Comp
|
||||||
|
L Device:R R2
|
||||||
|
U 1 1 5F43D4BB
|
||||||
|
P 2500 1700
|
||||||
|
F 0 "R2" H 2570 1746 50 0000 L CNN
|
||||||
|
F 1 "1000" H 2570 1655 50 0000 L CNN
|
||||||
|
F 2 "" V 2430 1700 50 0001 C CNN
|
||||||
|
F 3 "~" H 2500 1700 50 0001 C CNN
|
||||||
|
F 4 "production default" H 2500 1700 50 0001 C CNN "Variant"
|
||||||
|
1 2500 1700
|
||||||
|
1 0 0 -1
|
||||||
|
$EndComp
|
||||||
|
$EndSCHEMATC
|
||||||
|
|
@ -34,6 +34,15 @@ Missing:
|
||||||
- number_boards
|
- number_boards
|
||||||
- XLSX/HTML colors (for real)
|
- XLSX/HTML colors (for real)
|
||||||
|
|
||||||
|
KiBoM Variants:
|
||||||
|
- kibom-variant_2.sch
|
||||||
|
- kibom-variant_5.sch
|
||||||
|
|
||||||
|
IBoM Variants:
|
||||||
|
- test_int_bom_variant_t2if + kibom-variant_3.sch + int_bom_var_t2i_csv
|
||||||
|
- test_int_bom_variant_t2is + kibom-variant_3.sch + int_bom_var_t2is_csv
|
||||||
|
- kibom-variant_4.sch
|
||||||
|
|
||||||
For debug information use:
|
For debug information use:
|
||||||
pytest-3 --log-cli-level debug
|
pytest-3 --log-cli-level debug
|
||||||
|
|
||||||
|
|
@ -1239,6 +1248,7 @@ def test_int_bom_variant_t2b(test_dir):
|
||||||
|
|
||||||
|
|
||||||
def test_int_bom_variant_t2c(test_dir):
|
def test_int_bom_variant_t2c(test_dir):
|
||||||
|
""" Test KiBoM variant and field rename filter, R1 must be changed to 3k3 """
|
||||||
prj = 'kibom-variant_2'
|
prj = 'kibom-variant_2'
|
||||||
ctx = context.TestContextSCH(test_dir, 'test_int_bom_variant_t2c', prj, 'int_bom_var_t2c_csv', BOM_DIR)
|
ctx = context.TestContextSCH(test_dir, 'test_int_bom_variant_t2c', prj, 'int_bom_var_t2c_csv', BOM_DIR)
|
||||||
ctx.run()
|
ctx.run()
|
||||||
|
|
@ -1287,6 +1297,7 @@ def test_int_bom_variant_t2s(test_dir):
|
||||||
|
|
||||||
|
|
||||||
def test_int_bom_variant_t2if(test_dir):
|
def test_int_bom_variant_t2if(test_dir):
|
||||||
|
""" IBoM variants test full """
|
||||||
prj = 'kibom-variant_3'
|
prj = 'kibom-variant_3'
|
||||||
ctx = context.TestContextSCH(test_dir, 'test_int_bom_variant_t2if', prj, 'int_bom_var_t2i_csv', BOM_DIR)
|
ctx = context.TestContextSCH(test_dir, 'test_int_bom_variant_t2if', prj, 'int_bom_var_t2i_csv', BOM_DIR)
|
||||||
ctx.run()
|
ctx.run()
|
||||||
|
|
@ -1303,6 +1314,7 @@ def test_int_bom_variant_t2if(test_dir):
|
||||||
|
|
||||||
|
|
||||||
def test_int_bom_variant_t2is(test_dir):
|
def test_int_bom_variant_t2is(test_dir):
|
||||||
|
""" IBoM variants test simple """
|
||||||
prj = 'kibom-variant_3'
|
prj = 'kibom-variant_3'
|
||||||
ctx = context.TestContextSCH(test_dir, 'test_int_bom_variant_t2is', prj, 'int_bom_var_t2is_csv', BOM_DIR)
|
ctx = context.TestContextSCH(test_dir, 'test_int_bom_variant_t2is', prj, 'int_bom_var_t2is_csv', BOM_DIR)
|
||||||
ctx.run(extra_debug=True)
|
ctx.run(extra_debug=True)
|
||||||
|
|
@ -1312,6 +1324,26 @@ def test_int_bom_variant_t2is(test_dir):
|
||||||
ctx.clean_up(keep_project=True)
|
ctx.clean_up(keep_project=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_int_bom_variant_t2kf(test_dir):
|
||||||
|
""" KiCost variants test full.
|
||||||
|
R1 must be changed to 3k3.
|
||||||
|
We also test the DNP mechanism. """
|
||||||
|
prj = 'kibom-variant_kicost'
|
||||||
|
ctx = context.TestContextSCH(test_dir, 'test_int_bom_variant_t2kf', prj, 'int_bom_var_t2k_csv', BOM_DIR)
|
||||||
|
ctx.run()
|
||||||
|
rows, header, info = ctx.load_csv(prj+'-bom.csv')
|
||||||
|
ref_column = header.index(REF_COLUMN_NAME)
|
||||||
|
check_kibom_test_netlist(rows, ref_column, 1, ['C1', 'C2'], ['R1', 'R2'])
|
||||||
|
rows, header, info = ctx.load_csv(prj+'-bom_(production).csv')
|
||||||
|
check_kibom_test_netlist(rows, ref_column, 2, ['C1'], ['R1', 'R2', 'C2'])
|
||||||
|
val_column = header.index(VALUE_COLUMN_NAME)
|
||||||
|
check_value(rows, ref_column, 'R1', val_column, '1k')
|
||||||
|
rows, header, info = ctx.load_csv(prj+'-bom_(test).csv')
|
||||||
|
check_kibom_test_netlist(rows, ref_column, 2, ['R2'], ['R1', 'C1', 'C2'])
|
||||||
|
check_value(rows, ref_column, 'R1', val_column, '3k3')
|
||||||
|
ctx.clean_up()
|
||||||
|
|
||||||
|
|
||||||
def test_int_bom_wrong_variant(test_dir):
|
def test_int_bom_wrong_variant(test_dir):
|
||||||
ctx = context.TestContextSCH(test_dir, 'test_int_bom_wrong_variant', 'links', 'int_bom_wrong_variant', '')
|
ctx = context.TestContextSCH(test_dir, 'test_int_bom_wrong_variant', 'links', 'int_bom_wrong_variant', '')
|
||||||
ctx.run(EXIT_BAD_CONFIG)
|
ctx.run(EXIT_BAD_CONFIG)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
# KiCost variants test
|
||||||
|
kibot:
|
||||||
|
version: 1
|
||||||
|
|
||||||
|
variants:
|
||||||
|
- name: 'production'
|
||||||
|
comment: 'Production variant'
|
||||||
|
type: kicost
|
||||||
|
file_id: '_(production)'
|
||||||
|
variant: production
|
||||||
|
|
||||||
|
- name: 'test'
|
||||||
|
comment: 'Test variant'
|
||||||
|
type: kicost
|
||||||
|
file_id: '_(test)'
|
||||||
|
variant: 't.*'
|
||||||
|
|
||||||
|
- name: 'default'
|
||||||
|
comment: 'Default variant'
|
||||||
|
type: kicost
|
||||||
|
variant: default
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
- name: 'bom_internal'
|
||||||
|
comment: "Bill of Materials in CSV format"
|
||||||
|
type: bom
|
||||||
|
dir: BoM
|
||||||
|
options:
|
||||||
|
variant: default
|
||||||
|
|
||||||
|
- name: 'bom_internal_production'
|
||||||
|
comment: "Bill of Materials in CSV format for production"
|
||||||
|
type: bom
|
||||||
|
dir: BoM
|
||||||
|
options:
|
||||||
|
variant: production
|
||||||
|
|
||||||
|
- name: 'bom_internal_test'
|
||||||
|
comment: "Bill of Materials in CSV format for test"
|
||||||
|
type: bom
|
||||||
|
dir: BoM
|
||||||
|
options:
|
||||||
|
variant: test
|
||||||
Loading…
Reference in New Issue