Basic KiCost support.

This commit is contained in:
Salvador E. Tropea 2021-03-31 12:27:55 -03:00
parent 1290bb6995
commit 7c3f273684
25 changed files with 664 additions and 59 deletions

View File

@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New KiCost variant style.
- `skip_if_no_field` and `invert` options to the regex used in the generic
filter.
- Basic KiCost support.
### Changed
- Errors and warnings from KiAuto now are printed as errors and warnings.

View File

@ -54,7 +54,7 @@ For example, it's common that you might want for each board rev:
* Check ERC/DRC one last time (using [KiCad Automation Scripts](https://github.com/INTI-CMNB/kicad-automation-scripts/))
* Gerbers, drills and drill maps for a fab in their favourite format
* Fab docs for the assembler, including the BoM (Bill of Materials)
* Fab docs for the assembler, including the BoM (Bill of Materials) and costs spreadsheet
* Pick and place files
* PCB 3D model in STEP format
@ -483,6 +483,7 @@ The available values for *type* are:
- Bill of Materials
- `kibom` BoM in HTML or CSV format generated by [KiBoM](https://github.com/INTI-CMNB/KiBoM)
- `ibom` Interactive HTML BoM generated by [InteractiveHtmlBom](https://github.com/INTI-CMNB/InteractiveHtmlBom)
- `kicost` BoM in XLSX format with costs generated by [KiCost](https://github.com/INTI-CMNB/KiCost)
- 3D model:
- `step` *Standard for the Exchange of Product Data* for the PCB
@ -1077,6 +1078,45 @@ Next time you need this list just use an alias, like this:
variants with the ';' (semicolon) character.
This isn't related to the KiBot concept of variants.
* KiCost (KiCad Cost calculator)
* Type: `kicost`
* Description: Generates a spreadsheet containing components costs.
For more information: https://github.com/INTI-CMNB/KiCost
This output is what you get from the KiCost plug-in (eeschema).
* Valid keys:
- `comment`: [string=''] A comment for documentation purposes.
- `dir`: [string='.'] Output directory for the generated files.
- `name`: [string=''] Used to identify this particular output definition.
- `options`: [dict] Options for the `kicost` output.
* Valid keys:
- `aggregate`: [list(dict)] Add components from other projects.
* Valid keys:
- `file`: [string=''] Name of the XML to aggregate.
- `variant`: [string=' '] Variant for this project.
- `currency`: [string|list(string)=USD] Currency priority. Use ISO4217 codes (i.e. USD, EUR).
- `distributors`: [string|list(string)] Use only this distributors list. Default is all the available.
Not compatible with `no_distributors` option.
- `dnf_filter`: [string|list(string)=''] Name of the filter to mark components as not fitted.
A short-cut to use for simple cases where a variant is an overkill.
Internal variants and filters are currently ignored.
- `fields`: [string|list(string)] List of fields to be added to the global data section.
- `group_fields`: [string|list(string)] List of fields that can be different for a group.
Parts with differences in these fields are grouped together, but displayed individually.
- `ignore_fields`: [string|list(string)] List of fields to be ignored.
- `kicost_variant`: [string=''] Regular expression to match the variant field (KiCost option, not internal variants).
- `no_collapse`: [boolean=false] Do not collapse the part references (collapse=R1-R4).
- `no_distributors`: [string|list(string)] Use all but this distributors list. Default is use all the available.
Not compatible with `distributors` option.
- `no_price`: [boolean=false] Do not look for components price. For testing purposes.
- `output`: [string='%f-%i%v.%x'] Filename for the output (%i=kicost, %x=xlsx). Affected by global options.
- `show_cat_url`: [boolean=false] Include the catalogue links in the catalogue code.
- `translate_fields`: [list(dict)] Fields to rename (KiCost option, not internal filters).
* Valid keys:
- `field`: [string=''] Name of the field to rename.
- `name`: [string=''] New name.
- `variant`: [string=''] Board variant to apply.
Internal variants and filters are currently ignored.
* PcbDraw - Beautiful 2D PCB render
* Type: `pcbdraw`
* Description: Exports the PCB as a 2D model (SVG, PNG or JPG).
@ -2196,6 +2236,7 @@ The internal list of rotations is:
- **KiBoM**: Oliver Henry Walters (@SchrodingersGat)
- **Interactive HTML BoM**: @qu1ck
- **PcbDraw**: Jan Mrázek (@yaqwsx)
- **KiCost**: Dave Vandenbout (@devbisme) and Hildo Guillardi Júnior (@hildogjr)
- **Contributors**:
- **Error filters ideas**: Leandro Heck (@leoheck)
- **GitHub Actions Integration/SVG output**: @nerdyscout

View File

@ -54,7 +54,7 @@ For example, it's common that you might want for each board rev:
* Check ERC/DRC one last time (using [KiCad Automation Scripts](https://github.com/INTI-CMNB/kicad-automation-scripts/))
* Gerbers, drills and drill maps for a fab in their favourite format
* Fab docs for the assembler, including the BoM (Bill of Materials)
* Fab docs for the assembler, including the BoM (Bill of Materials) and costs spreadsheet
* Pick and place files
* PCB 3D model in STEP format
@ -352,6 +352,7 @@ The available values for *type* are:
- Bill of Materials
- `kibom` BoM in HTML or CSV format generated by [KiBoM](https://github.com/INTI-CMNB/KiBoM)
- `ibom` Interactive HTML BoM generated by [InteractiveHtmlBom](https://github.com/INTI-CMNB/InteractiveHtmlBom)
- `kicost` BoM in XLSX format with costs generated by [KiCost](https://github.com/INTI-CMNB/KiCost)
- 3D model:
- `step` *Standard for the Exchange of Product Data* for the PCB
@ -1230,6 +1231,7 @@ The internal list of rotations is:
- **KiBoM**: Oliver Henry Walters (@SchrodingersGat)
- **Interactive HTML BoM**: @qu1ck
- **PcbDraw**: Jan Mrázek (@yaqwsx)
- **KiCost**: Dave Vandenbout (@devbisme) and Hildo Guillardi Júnior (@hildogjr)
- **Contributors**:
- **Error filters ideas**: Leandro Heck (@leoheck)
- **GitHub Actions Integration/SVG output**: @nerdyscout

View File

@ -661,6 +661,59 @@ outputs:
# This isn't related to the KiBot concept of variants
variant: ''
# KiCost (KiCad Cost calculator):
# For more information: https://github.com/INTI-CMNB/KiCost
# This output is what you get from the KiCost plug-in (eeschema).
- name: 'kicost_example'
comment: 'Generates a spreadsheet containing components costs.'
type: 'kicost'
dir: 'Example/kicost_dir'
options:
# [list(dict)] Add components from other projects
aggregate:
# [string=''] Name of the XML to aggregate
- file: ''
# [string=' '] Variant for this project
variant: ' '
# [string|list(string)=USD] Currency priority. Use ISO4217 codes (i.e. USD, EUR)
currency: USD
# [string|list(string)] Use only this distributors list. Default is all the available.
# Not compatible with `no_distributors` option
distributors:
# [string|list(string)=''] Name of the filter to mark components as not fitted.
# A short-cut to use for simple cases where a variant is an overkill.
# Internal variants and filters are currently ignored
dnf_filter: ''
# [string|list(string)] List of fields to be added to the global data section
fields:
# [string|list(string)] List of fields that can be different for a group.
# Parts with differences in these fields are grouped together, but displayed individually
group_fields:
# [string|list(string)] List of fields to be ignored
ignore_fields:
# [string=''] Regular expression to match the variant field (KiCost option, not internal variants)
kicost_variant: ''
# [boolean=false] Do not collapse the part references (collapse=R1-R4)
no_collapse: false
# [string|list(string)] Use all but this distributors list. Default is use all the available.
# Not compatible with `distributors` option
no_distributors:
# [boolean=false] Do not look for components price. For testing purposes
no_price: false
# [string='%f-%i%v.%x'] Filename for the output (%i=kicost, %x=xlsx). Affected by global options
output: '%f-%i%v.%x'
# [boolean=false] Include the catalogue links in the catalogue code
show_cat_url: false
# [list(dict)] Fields to rename (KiCost option, not internal filters)
translate_fields:
# [string=''] Name of the field to rename
- field: 'mpn'
# [string=''] New name
name: 'manf#'
# [string=''] Board variant to apply.
# Internal variants and filters are currently ignored
variant: ''
# PcbDraw - Beautiful 2D PCB render:
# Uses configurable colors.
# Can also render the components if the 2D models are available

View File

@ -0,0 +1,48 @@
# KiCost variants (modern style)
This is an analysis and test of the *variants* implementation of [KiCost](https://github.com/xesscorp/KiCost)
An old style used `kicost.VARIANT:FIELD` to assign fields. So you could define `kicost.V1:DNP`.
## What goes inside the SCH
- The variants are implemented using the `variant` field (`version` is an alias).
- The `variant` field can contain more than one value, valid separators are: comma `,`, semicolon `;`, slash `/` and space ` `.
Note: spaces are removed, and a vanriant tag can't contain spaces because this is a separator. (`re.split('[,;/ ]', variants)`)
- Components without a variant are always included.
- Components with one or more variants are included only if requested (`--variant REGEX` matches any of the listed variants)
## What goes outside the SCH
- When you generate the spreadsheet you can select one or more variants using a regex (`--variant REGEX`).
- Components containing one or more variants that match the regex are added.
- No exclusion mechanism is available.
- `REGEX` isn't case sensitive.
## Where is in the code?
Source `kicost/edas/tools.py` function `remove_dnp_parts(components, variant)`.
The old mechanism is part of `kicost/edas/eda_kicad.py` function `extract_fields(part, variant)` combined with the above code.
## Conclusion
### Advantages
- Most of the information is inside the project.
- A regex allows pattern matching.
### Disadvantages
- You only have an "include only" option.
- The regex could become complex.
## Old mechanism
KiCost has a field rename mechanism. Fields named `kicost.VARIANT:FIELD` are:
- Renamed to `FIELD` if `VARIANT` matches `--variant REGEX`
- Discarded otherwise
This can be used with the DNP mechanism:
- The name of the field is `dnp` or `nopop` (case insensitive)
- If it contains anything other than 0 (evaluated as float) the component is removed.

View File

@ -4,6 +4,7 @@
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
from .registrable import RegFilter, Registrable, RegOutput
from .optionable import Optionable
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
@ -268,7 +269,8 @@ class BaseFilter(RegFilter):
rename.append({'field': k, 'name': v})
for stub in ['part#', '#', 'p#', 'pn', 'vendor#', 'vp#', 'vpn', 'num']:
for dist in DISTRIBUTORS:
base = dist[:-1]
base = dist
dist += '#'
if stub != '#':
rename.append({'field': base + stub, 'name': dist})
rename.append({'field': base + '_' + stub, 'name': dist})
@ -371,3 +373,25 @@ class BaseFilter(RegFilter):
if len(filters) == 1:
return filters[0]
return MultiFilter(filters, is_transform)
class FieldRename(Optionable):
""" Field translation """
def __init__(self):
super().__init__()
self._unkown_is_error = True
with document:
self.field = ''
""" Name of the field to rename """
self.name = ''
""" New name """
self._field_example = 'mpn'
self._name_example = 'manf#'
def config(self, parent):
super().config(parent)
if not self.field:
raise KiPlotConfigurationError("Missing or empty `field` in rename list ({})".format(str(self._tree)))
if not self.name:
raise KiPlotConfigurationError("Missing or empty `name` in rename list ({})".format(str(self._tree)))
self.field = self.field.lower()

View File

@ -6,38 +6,15 @@
"""
Implements a field renamer
"""
from .optionable import Optionable
from .error import KiPlotConfigurationError
from .gs import GS
from .misc import W_EMPTYREN
from .macros import macros, document, filter_class # noqa: F401
from .fil_base import FieldRename
from . import log
logger = log.get_logger(__name__)
class FieldRename(Optionable):
""" Field translation """
def __init__(self):
super().__init__()
self._unkown_is_error = True
with document:
self.field = ''
""" Name of the field to rename """
self.name = ''
""" New name """
self._field_example = 'mpn'
self._name_example = 'manf#'
def config(self, parent):
super().config(parent)
if not self.field:
raise KiPlotConfigurationError("Missing or empty `field` in rename list ({})".format(str(self._tree)))
if not self.name:
raise KiPlotConfigurationError("Missing or empty `name` in rename list ({})".format(str(self._tree)))
self.field = self.field.lower()
@filter_class
class Field_Rename(BaseFilter): # noqa: F821
""" Field_Rename

View File

@ -12,7 +12,7 @@ import re
from copy import deepcopy
from .gs import GS
from .optionable import Optionable
from .misc import W_NUMSUBPARTS, W_PARTMULT, DISTRIBUTORS
from .misc import W_NUMSUBPARTS, W_PARTMULT, DISTRIBUTORS_F
from .macros import macros, document, filter_class # noqa: F401
from . import log
@ -21,7 +21,7 @@ logger = log.get_logger(__name__)
class DistributorsList(Optionable):
_default = DISTRIBUTORS
_default = DISTRIBUTORS_F
@filter_class
@ -69,10 +69,10 @@ class Subparts(BaseFilter): # noqa: F821
if not self.ref_sep:
self.ref_sep = '#'
if isinstance(self.split_fields, type):
self.split_fields = DISTRIBUTORS
self.split_fields = DISTRIBUTORS_F
else:
if self.split_fields_expand:
self.split_fields.extend(DISTRIBUTORS)
self.split_fields.extend(DISTRIBUTORS_F)
# (?<!\\) is used to skip \;
self._part_sep = re.compile(r'(?<!\\)\s*['+self.separators+r']\s*')
self._qty_sep = re.compile(r'(?<!\\)\s*['+self.mult_separators+r']\s*')

View File

@ -3,6 +3,7 @@
# Copyright (c) 2020-2021 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
# The algorithm is from KiCost project (https://github.com/xesscorp/KiCost)
"""
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)

View File

@ -69,6 +69,8 @@ CMD_KIBOM = 'KiBOM_CLI.py'
URL_KIBOM = 'https://github.com/INTI-CMNB/KiBoM'
CMD_IBOM = 'generate_interactive_bom.py'
URL_IBOM = 'https://github.com/INTI-CMNB/InteractiveHtmlBom'
CMD_KICOST = 'kicost'
URL_KICOST = 'https://github.com/INTI-CMNB/KiCost'
KICAD2STEP = 'kicad2step'
PCBDRAW = 'pcbdraw'
URL_PCBDRAW = 'https://github.com/INTI-CMNB/pcbdraw'
@ -121,8 +123,13 @@ DNC = {
"fixed",
}
# KiCost distributors
DISTRIBUTORS = ['digikey#', 'farnell#', 'mouser#', 'newark#', 'rs#', 'arrow#', 'tme#', 'lcsc#']
DISTRIBUTORS = ['digikey', 'farnell', 'mouser', 'newark', 'rs', 'arrow', 'tme', 'lcsc']
DISTRIBUTORS_F = [d+'#' for d in DISTRIBUTORS]
# ISO ISO4217 currency codes
# Not all, but the ones we get from the European Central Bank (march 2021)
ISO_CURRENCIES = set(['EUR', 'USD', 'JPY', 'BGN', 'CZK', 'DKK', 'GBP', 'HUF', 'PLN', 'RON', 'SEK', 'CHF', 'ISK', 'NOK', 'HRK',
'RUB', 'TRY', 'AUD', 'BRL', 'CAD', 'CNY', 'HKD', 'IDR', 'ILS', 'INR', 'KRW', 'MXN', 'MYR', 'NZD', 'PHP',
'SGD', 'THB', 'ZAR'])
W_VARCFG = '(W001) '
W_VARPCB = '(W002) '
@ -186,6 +193,8 @@ W_NUMSUBPARTS = '(W059) '
W_PARTMULT = '(W060) '
W_EMPTYREN = '(W061) '
W_BADFIELD = '(W062) '
W_UNKDIST = '(W063) '
W_UNKCUR = '(W064) '
class Rect(object):

View File

@ -237,6 +237,22 @@ class Optionable(object):
name = re.sub(r'[?%*:|"<>]', '_', name)
return name
@staticmethod
def force_list(val):
""" Used for values that accept a string or a list of strings.
The string can be a comma separated list """
if isinstance(val, type):
# Not used
val = []
elif isinstance(val, str):
# A string
if val:
val = [v.strip() for v in val.split(',')]
else:
# Empty string
val = []
return val
class BaseOptions(Optionable):
""" A class to validate and hold output options.

199
kibot/out_kicost.py Normal file
View File

@ -0,0 +1,199 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021 Salvador E. Tropea
# Copyright (c) 2021 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
from os.path import isfile
from subprocess import check_output, STDOUT, CalledProcessError
from .misc import CMD_KICOST, URL_KICOST, BOM_ERROR, DISTRIBUTORS, W_UNKDIST, ISO_CURRENCIES, W_UNKCUR
from .error import KiPlotConfigurationError
from .optionable import Optionable
from .gs import GS
from .kiplot import check_script
from .out_base import VariantOptions
from .macros import macros, document, output_class # noqa: F401
from .fil_base import FieldRename
from . import log
logger = log.get_logger(__name__)
WARNING_MIX = "Internal variants and filters are currently ignored"
class Aggregate(Optionable):
def __init__(self):
super().__init__()
with document:
self.file = ''
""" Name of the XML to aggregate """
self.variant = ' '
""" Variant for this project """
def config(self, parent):
super().config(parent)
if not self.file:
raise KiPlotConfigurationError("Missing or empty `file` in aggregate list ({})".format(str(self._tree)))
class KiCostOptions(VariantOptions):
def __init__(self):
with document:
self.output = GS.def_global_output
""" Filename for the output (%i=kicost, %x=xlsx) """
self.no_price = False
""" Do not look for components price. For testing purposes """
self.no_collapse = False
""" Do not collapse the part references (collapse=R1-R4) """
self.show_cat_url = False
""" Include the catalogue links in the catalogue code """
self.distributors = Optionable
""" [string|list(string)] Use only this distributors list. Default is all the available.
Not compatible with `no_distributors` option """
self.no_distributors = Optionable
""" [string|list(string)] Use all but this distributors list. Default is use all the available.
Not compatible with `distributors` option """
self.currency = Optionable
""" [string|list(string)=USD] Currency priority. Use ISO4217 codes (i.e. USD, EUR) """
self.group_fields = Optionable
""" [string|list(string)] List of fields that can be different for a group.
Parts with differences in these fields are grouped together, but displayed individually """
self.ignore_fields = Optionable
""" [string|list(string)] List of fields to be ignored """
self.fields = Optionable
""" [string|list(string)] List of fields to be added to the global data section """
self.translate_fields = FieldRename
""" [list(dict)] Fields to rename (KiCost option, not internal filters) """
self.kicost_variant = ''
""" Regular expression to match the variant field (KiCost option, not internal variants) """
self.aggregate = Aggregate
""" [list(dict)] Add components from other projects """
super().__init__()
self.add_to_doc('variant', WARNING_MIX)
self.add_to_doc('dnf_filter', WARNING_MIX)
self._expand_id = 'kicost'
self._expand_ext = 'xlsx'
@staticmethod
def _validate_dis(val):
val = Optionable.force_list(val)
for v in val:
if v not in DISTRIBUTORS:
logger.warning(W_UNKDIST+'Unknown distributor `{}`'.format(v))
return val
@staticmethod
def _validate_cur(val):
val = Optionable.force_list(val)
for v in val:
if v not in ISO_CURRENCIES:
logger.warning(W_UNKCUR+'Unknown currency `{}`'.format(v))
return val
def config(self, parent):
super().config(parent)
if not self.output:
self.output = '%f.%x'
self.distributors = self._validate_dis(self.distributors)
self.no_distributors = self._validate_dis(self.no_distributors)
if self.distributors and self.no_distributors:
raise KiPlotConfigurationError('`distributors` and `no_distributors` are incompatible, choose one')
self.currency = self._validate_cur(self.currency)
self.group_fields = Optionable.force_list(self.group_fields)
self.ignore_fields = Optionable.force_list(self.ignore_fields)
self.fields = Optionable.force_list(self.fields)
# Adapt translate_fields to its use
if isinstance(self.translate_fields, type):
self.translate_fields = []
if self.translate_fields:
translate_fields = []
for f in self.translate_fields:
translate_fields.append(f.field)
translate_fields.append(f.name)
self.translate_fields = translate_fields
# Make sure aggregate is a list
if isinstance(self.aggregate, type):
self.aggregate = []
def get_targets(self, out_dir):
return [self.expand_filename(out_dir, self.output, self._expand_id, self._expand_ext)]
@staticmethod
def add_list_opt(cmd, name, val):
if val:
cmd.append('--'+name+'='+','.join(val))
@staticmethod
def add_bool_opt(cmd, name, val):
if val:
cmd.append('--'+name)
def run(self, name):
super().run(name)
# Make sure the XML is there.
# Currently we only support the XML mechanism.
netlist = GS.sch_no_ext+'.xml'
if not isfile(netlist):
logger.error('Missing netlist in XML format `{}`'.format(netlist))
logger.error('You can generate it using the `update_xml` pre-flight')
exit(BOM_ERROR)
# Check KiCost is available
check_script(CMD_KICOST, URL_KICOST)
# Construct the command
cmd = [CMD_KICOST, '-w', '-o', name, '-i', netlist]
# Add the rest of input files and their variants
if self.aggregate:
# More than one project
for p in self.aggregate:
cmd.append(p.file)
cmd.append('--variant')
# KiCost internally defaults to ' ' as a dummy variant
cmd.append(self.kicost_variant if self.kicost_variant else ' ')
for p in self.aggregate:
cmd.append(p.variant if p.variant else ' ')
else:
# Just this project
if self.kicost_variant:
cmd.extend(['--variant', self.kicost_variant])
# Pass the debug level
if GS.debug_enabled:
cmd.append('--debug={}'.format(GS.debug_level))
# Boolean options
self.add_bool_opt(cmd, 'no_price', self.no_price)
self.add_bool_opt(cmd, 'no_collapse', self.no_collapse)
self.add_bool_opt(cmd, 'show_cat_url', self.show_cat_url)
# List options
self.add_list_opt(cmd, 'include', self.distributors)
self.add_list_opt(cmd, 'exclude', self.no_distributors)
self.add_list_opt(cmd, 'currency', self.currency)
self.add_list_opt(cmd, 'group_fields', self.group_fields)
self.add_list_opt(cmd, 'ignore_fields', self.ignore_fields)
self.add_list_opt(cmd, 'fields', self.fields)
# Field translation
if self.translate_fields:
cmd.append('--translate_fields')
cmd.extend(self.translate_fields)
# Run the command
logger.debug('Running: '+str(cmd))
try:
cmd_output = check_output(cmd, stderr=STDOUT)
cmd_output_dec = cmd_output.decode()
except CalledProcessError as e:
logger.error('Failed to create costs spreadsheet, error %d', e.returncode)
if e.output:
logger.debug('Output from command: '+e.output.decode())
exit(BOM_ERROR)
logger.debug('Output from command:\n'+cmd_output_dec+'\n')
@output_class
class KiCost(BaseOutput): # noqa: F821
""" KiCost (KiCad Cost calculator)
Generates a spreadsheet containing components costs.
For more information: https://github.com/INTI-CMNB/KiCost
This output is what you get from the KiCost plug-in (eeschema). """
def __init__(self):
super().__init__()
self._sch_related = True
with document:
self.options = KiCostOptions
""" [dict] Options for the `kicost` output """

View File

@ -32,28 +32,14 @@ class IBoM(BaseVariant): # noqa: F821
self.variants_whitelist = Optionable
""" [string|list(string)=''] List of board variants to include in the BOM """
@staticmethod
def _force_list(val):
if isinstance(val, type):
# Not used
val = []
elif isinstance(val, str):
# A string
if val:
val = [v.strip() for v in val.split(',')]
else:
# Empty string
val = []
return val
def config(self, 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.dnf_filter = BaseFilter.solve_filter(self.dnf_filter, 'dnf_filter')
self.dnc_filter = BaseFilter.solve_filter(self.dnc_filter, 'dnc_filter')
self.variants_blacklist = self._force_list(self.variants_blacklist)
self.variants_whitelist = self._force_list(self.variants_whitelist)
self.variants_blacklist = self.force_list(self.variants_blacklist)
self.variants_whitelist = self.force_list(self.variants_whitelist)
def skip_component(self, c):
""" Skip components that doesn't belong to this variant. """

View File

@ -42,15 +42,8 @@ class KiBoM(BaseVariant): # noqa: F821
def config(self, parent):
# Now we can let the parent initialize the filters
super().config(parent)
# Variants, ensure a list
if isinstance(self.variant, type):
self.variant = []
elif isinstance(self.variant, str):
if self.variant:
self.variant = [self.variant]
else:
self.variant = []
self.variant = [v.lower() for v in self.variant]
# 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

View File

@ -3,6 +3,7 @@
# Copyright (c) 2020-2021 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
# The algorithm is from KiCost project (https://github.com/xesscorp/KiCost)
"""
Implements the KiCost variants mechanism.
"""

View File

@ -27,7 +27,7 @@ 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"
F 6 " " H 1000 1700 50 0001 C CNN "kicost.production:nopop"
1 1000 1700
1 0 0 -1
$EndComp

View File

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<export version="D">
<design>
<source>/home/salvador/0Data/Eccosur/kibot/tests/board_samples/kicad_5/kibom-variant_kicost.sch</source>
<date>mar 30 mar 2021 09:46:24</date>
<tool>Eeschema 5.1.9+dfsg1-1</tool>
<sheet number="1" name="/" tstamps="/">
<title_block>
<title>KiBom Test Schematic</title>
<company>https://github.com/SchrodingersGat/KiBom</company>
<rev>A</rev>
<date>2020-03-12</date>
<source>kibom-variant_kicost.sch</source>
<comment number="1" value=""/>
<comment number="2" value=""/>
<comment number="3" value=""/>
<comment number="4" value=""/>
</title_block>
</sheet>
</design>
<components>
<comp ref="C1">
<value>1nF</value>
<datasheet>~</datasheet>
<fields>
<field name="kicost.default:dnp">1</field>
<field name="kicost.production:nopop"> </field>
<field name="kicost.test:dnp">0.0</field>
</fields>
<libsource lib="Device" part="C" description="Unpolarized capacitor"/>
<sheetpath names="/" tstamps="/"/>
<tstamp>5F43BEC2</tstamp>
</comp>
<comp ref="C2">
<value>1000 pF</value>
<datasheet>~</datasheet>
<fields>
<field name="version">production,test</field>
</fields>
<libsource lib="Device" part="C" description="Unpolarized capacitor"/>
<sheetpath names="/" tstamps="/"/>
<tstamp>5F43CE1C</tstamp>
</comp>
<comp ref="R1">
<value>1k</value>
<datasheet>~</datasheet>
<fields>
<field name="kicost.test:Value">3k3</field>
</fields>
<libsource lib="Device" part="R" description="Resistor"/>
<sheetpath names="/" tstamps="/"/>
<tstamp>5F43D144</tstamp>
</comp>
<comp ref="R2">
<value>1000</value>
<datasheet>~</datasheet>
<fields>
<field name="Variant">production default</field>
</fields>
<libsource lib="Device" part="R" description="Resistor"/>
<sheetpath names="/" tstamps="/"/>
<tstamp>5F43D4BB</tstamp>
</comp>
</components>
<libparts>
<libpart lib="Device" part="C">
<description>Unpolarized capacitor</description>
<docs>~</docs>
<footprints>
<fp>C_*</fp>
</footprints>
<fields>
<field name="Reference">C</field>
<field name="Value">C</field>
</fields>
<pins>
<pin num="1" name="~" type="passive"/>
<pin num="2" name="~" type="passive"/>
</pins>
</libpart>
<libpart lib="Device" part="R">
<description>Resistor</description>
<docs>~</docs>
<footprints>
<fp>R_*</fp>
</footprints>
<fields>
<field name="Reference">R</field>
<field name="Value">R</field>
</fields>
<pins>
<pin num="1" name="~" type="passive"/>
<pin num="2" name="~" type="passive"/>
</pins>
</libpart>
</libparts>
<libraries>
<library logical="Device">
<uri>/usr/share/kicad/library/Device.lib</uri>
</library>
</libraries>
<nets>
<net code="1" name="Net-(C1-Pad1)">
<node ref="C1" pin="1"/>
</net>
<net code="2" name="Net-(C1-Pad2)">
<node ref="C1" pin="2"/>
</net>
<net code="3" name="Net-(C2-Pad1)">
<node ref="C2" pin="1"/>
</net>
<net code="4" name="Net-(C2-Pad2)">
<node ref="C2" pin="2"/>
</net>
<net code="5" name="Net-(R1-Pad1)">
<node ref="R1" pin="1"/>
</net>
<net code="6" name="Net-(R1-Pad2)">
<node ref="R1" pin="2"/>
</net>
<net code="7" name="Net-(R2-Pad1)">
<node ref="R2" pin="1"/>
</net>
<net code="8" name="Net-(R2-Pad2)">
<node ref="R2" pin="2"/>
</net>
</nets>
</export>

View File

@ -0,0 +1,8 @@
Prj:,KiBom Test Schematic,,,,Board Qty:,100
Co.:,https://github.com/SchrodingersGat/KiBom,,,,Unit Cost:,0
Global Part Info,,,,,,
Refs,Value,Footprint,Manf#,Qty,Unit$,Ext$
C1,1nF,,,100,,0
R1,1k,,,100,,0
1 Prj: KiBom Test Schematic Board Qty: 100
2 Co.: https://github.com/SchrodingersGat/KiBom Unit Cost: 0
3 Global Part Info
4 Refs Value Footprint Manf# Qty Unit$ Ext$
5 C1 1nF 100 0
6 R1 1k 100 0

View File

@ -0,0 +1,8 @@
Prj:,KiBom Test Schematic,,,,Board Qty:,100
Co.:,https://github.com/SchrodingersGat/KiBom,,,,Unit Cost:,0
Global Part Info,,,,,,
Refs,Value,Footprint,Manf#,Qty,Unit$,Ext$
R1,1k,,,100,,0
R2,1000,,,100,,0
1 Prj: KiBom Test Schematic Board Qty: 100
2 Co.: https://github.com/SchrodingersGat/KiBom Unit Cost: 0
3 Global Part Info
4 Refs Value Footprint Manf# Qty Unit$ Ext$
5 R1 1k 100 0
6 R2 1000 100 0

View File

@ -0,0 +1,9 @@
Prj:,KiBom Test Schematic,,,,Board Qty:,100
Co.:,https://github.com/SchrodingersGat/KiBom,,,,Unit Cost:,0
Global Part Info,,,,,,
Refs,Value,Footprint,Manf#,Qty,Unit$,Ext$
C2,1000 pF,,,100,,0
R1,1k,,,100,,0
R2,1000,,,100,,0
1 Prj: KiBom Test Schematic Board Qty: 100
2 Co.: https://github.com/SchrodingersGat/KiBom Unit Cost: 0
3 Global Part Info
4 Refs Value Footprint Manf# Qty Unit$ Ext$
5 C2 1000 pF 100 0
6 R1 1k 100 0
7 R2 1000 100 0

View File

@ -0,0 +1,9 @@
Prj:,KiBom Test Schematic,,,,Board Qty:,100
Co.:,https://github.com/SchrodingersGat/KiBom,,,,Unit Cost:,0
Global Part Info,,,,,,
Refs,Value,Footprint,Manf#,Qty,Unit$,Ext$
C1,1nF,,,100,,0
C2,1000 pF,,,100,,0
R1,3k3,,,100,,0
1 Prj: KiBom Test Schematic Board Qty: 100
2 Co.: https://github.com/SchrodingersGat/KiBom Unit Cost: 0
3 Global Part Info
4 Refs Value Footprint Manf# Qty Unit$ Ext$
5 C1 1nF 100 0
6 C2 1000 pF 100 0
7 R1 3k3 100 0

View File

@ -0,0 +1 @@
../5_1_6/KiCost/

View File

@ -0,0 +1 @@
../5_1_6/KiCost/

View File

@ -0,0 +1,50 @@
"""
Tests for the KiCost output.
For debug information use:
pytest-3 --log-cli-level debug
"""
import os
import sys
# Look for the 'utils' module from where the script is running
prev_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if prev_dir not in sys.path:
sys.path.insert(0, prev_dir)
# Utils import
from utils import context
import logging
import subprocess
OUT_DIR = 'KiCost'
def conver2csv(xlsx):
csv = xlsx[:-4]+'csv'
logging.debug('Converting to CSV')
p1 = subprocess.Popen(['xlsx2csv', '--skipemptycolumns', xlsx], stdout=subprocess.PIPE)
with open(csv, 'w') as f:
p2 = subprocess.Popen(['egrep', '-i', '-v', r'( date|kicost|Total purchase)'], stdin=p1.stdout, stdout=f)
p2.communicate()[0]
def check_simple(ctx, variant):
if variant:
variant = '_'+variant
name = os.path.join(OUT_DIR, 'simple'+variant+'.xlsx')
ctx.expect_out_file(name)
xlsx = ctx.get_out_path(name)
conver2csv(xlsx)
ctx.compare_txt(name[:-4]+'csv')
def test_kicost_simple(test_dir):
prj = 'kibom-variant_kicost'
ctx = context.TestContextSCH(test_dir, 'test_kicost_simple', prj, 'kicost_simple', OUT_DIR)
ctx.run()
check_simple(ctx, '')
check_simple(ctx, 'default')
check_simple(ctx, 'production')
check_simple(ctx, 'test')
ctx.clean_up()

View File

@ -0,0 +1,40 @@
# KiCost basic test
kibot:
version: 1
outputs:
- name: 'Costs'
comment: "Components costs spreadsheet"
type: kicost
dir: KiCost
options:
output: 'simple'
no_price: true
no_collapse: true
- name: 'Costs (default)'
comment: "Components costs spreadsheet default variant"
type: kicost
dir: KiCost
options:
output: 'simple_default'
no_price: true
kicost_variant: default
- name: 'Costs (production)'
comment: "Components costs spreadsheet production variant"
type: kicost
dir: KiCost
options:
output: 'simple_production'
no_price: true
kicost_variant: production
- name: 'Costs (test)'
comment: "Components costs spreadsheet test variant"
type: kicost
dir: KiCost
options:
output: 'simple_test'
no_price: true
kicost_variant: test