Basic KiCost support.
This commit is contained in:
parent
1290bb6995
commit
7c3f273684
|
|
@ -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.
|
||||
|
|
|
|||
43
README.md
43
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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*')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 """
|
||||
|
|
@ -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. """
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../5_1_6/KiCost/
|
||||
|
|
@ -0,0 +1 @@
|
|||
../5_1_6/KiCost/
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue