Compare commits
47 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
e49efcf21d | |
|
|
3896f1b910 | |
|
|
dc5924901b | |
|
|
2e4f5d6596 | |
|
|
b86b5371d7 | |
|
|
3d53b874e2 | |
|
|
324b3772e0 | |
|
|
7bd8c068b8 | |
|
|
4595f2a7f8 | |
|
|
103a338ece | |
|
|
ccfb6ce734 | |
|
|
3d6840cd85 | |
|
|
bd76069be3 | |
|
|
e2d71c38a0 | |
|
|
0156240a72 | |
|
|
b50bc4f6d9 | |
|
|
ef1097375c | |
|
|
13d2e43c15 | |
|
|
8e9d5f69e6 | |
|
|
5c8f6334a5 | |
|
|
d5d4c9f1a6 | |
|
|
b895cc7604 | |
|
|
1c018e6511 | |
|
|
20ead17d4a | |
|
|
79a363e49f | |
|
|
b2afeadc5c | |
|
|
58fb9fe09a | |
|
|
7a6a264c5f | |
|
|
616cdcd6bf | |
|
|
adfc509fdd | |
|
|
87bc028362 | |
|
|
f0384e501b | |
|
|
51e2b0789a | |
|
|
fcfe64e695 | |
|
|
2ab0e6a489 | |
|
|
cdc212e89e | |
|
|
499b3520a6 | |
|
|
e62e38b6ea | |
|
|
969b3843de | |
|
|
7187fe5ebc | |
|
|
4ead7e6ef5 | |
|
|
cc272dd31c | |
|
|
48dec2995e | |
|
|
4d22b52403 | |
|
|
f752a72bfe | |
|
|
a136c2f7e7 | |
|
|
ea2d7916be |
|
|
@ -53,34 +53,3 @@ jobs:
|
|||
with:
|
||||
name: Test_Output
|
||||
path: output
|
||||
|
||||
|
||||
push_to_registry:
|
||||
|
||||
name: Push Docker image to Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
||||
with:
|
||||
images: setsoft/kicad_auto
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
||||
with:
|
||||
context: tools/dev_image
|
||||
push: true
|
||||
tags: setsoft/kicad_auto:dev
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
[submodule "submodules/KiCost"]
|
||||
path = submodules/KiCost
|
||||
url = https://github.com/INTI-CMNB/KiCost.git
|
||||
url = https://github.com/hildogjr/KiCost.git
|
||||
branch = api_ext_plugin
|
||||
|
|
|
|||
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -12,17 +12,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- skip_bottom: bottom components aren't rotated.
|
||||
- XLSX BoM: option to control the logo scale (#84)
|
||||
- Import mechanism for filters and variants (#88)
|
||||
- PDF PCB Print: option `hide_excluded` to hide components marked by the
|
||||
`exclude_filter`.
|
||||
https://forum.kicad.info/t/fab-drawing-for-only-through-hole-parts/
|
||||
- Internal BoM: option to avoid merging components with empty fields.
|
||||
Is named `merge_both_blank` and defaults to true.
|
||||
- Internal BoM: when a `Value` field can't be interpreted as a `number+unit`,
|
||||
and it contain at least one space, now we try to use the text before the
|
||||
space. This helps for cases like "10K 1%".
|
||||
- Generic filter: options to match if a field is/isn't defined.
|
||||
- Internal BoM: two other options for the sorting criteria.
|
||||
|
||||
### Changed
|
||||
- Internal BoM: now components with different Tolerance, Voltage, Current
|
||||
and/or Power fields aren't grouped together.
|
||||
These fields are now part of the default `group_fields`. (#79)
|
||||
- Internal BoM: the field used for variants doesn't produce conflicts. (#100)
|
||||
|
||||
### Fixed
|
||||
- Position files now defaults to use the auxiliar origin as KiCad.
|
||||
Can be disabled to use absolute coordinates. (#87)
|
||||
- Board View: flipped output. (#89)
|
||||
- Board View: problems with netnames using spaces. (#90)
|
||||
- Schematic load: problems with fields containing double quotes. (#98)
|
||||
- `--list`: problems with layers and fields specific for the project.
|
||||
(INTI-CMNB/kibot_variants_arduprog#4)
|
||||
|
||||
|
||||
## [0.11.0] - 2021-04-25
|
||||
### Added
|
||||
|
|
|
|||
26
README.md
26
README.md
|
|
@ -305,6 +305,8 @@ Currently the only type available is `generic`.
|
|||
- `column`: [string=''] Name of the column to apply the regular expression.
|
||||
- *field*: Alias for column.
|
||||
- `invert`: [boolean=false] Invert the regex match result.
|
||||
- `match_if_field`: [boolean=false] Match if the field exists, no regex applied. Not affected by `invert`.
|
||||
- `match_if_no_field`: [boolean=false] Match if the field doesn't exists, no regex applied. Not affected by `invert`.
|
||||
- `regex`: [string=''] Regular expression to match.
|
||||
- *regexp*: Alias for regex.
|
||||
- `skip_if_no_field`: [boolean=false] Skip this test if the field doesn't exist.
|
||||
|
|
@ -326,6 +328,8 @@ Currently the only type available is `generic`.
|
|||
- `column`: [string=''] Name of the column to apply the regular expression.
|
||||
- *field*: Alias for column.
|
||||
- `invert`: [boolean=false] Invert the regex match result.
|
||||
- `match_if_field`: [boolean=false] Match if the field exists, no regex applied. Not affected by `invert`.
|
||||
- `match_if_no_field`: [boolean=false] Match if the field doesn't exists, no regex applied. Not affected by `invert`.
|
||||
- `regex`: [string=''] Regular expression to match.
|
||||
- *regexp*: Alias for regex.
|
||||
- `skip_if_no_field`: [boolean=false] Skip this test if the field doesn't exist.
|
||||
|
|
@ -713,9 +717,10 @@ Next time you need this list just use an alias, like this:
|
|||
- `ignore_dnf`: [boolean=true] Exclude DNF (Do Not Fit) components.
|
||||
- `int_qtys`: [boolean=true] Component quantities are always expressed as integers. Using the ceil() function.
|
||||
- `merge_blank_fields`: [boolean=true] Component groups with blank fields will be merged into the most compatible group, where possible.
|
||||
- `merge_both_blank`: [boolean=true] When creating groups two components with empty/missing field will be interpreted as with the same value.
|
||||
- `no_conflict`: [list(string)] List of fields where we tolerate conflicts.
|
||||
Use it to avoid undesired warnings.
|
||||
By default the field indicated in `fit_field` and the field `part` are excluded.
|
||||
By default the field indicated in `fit_field`, the field used for variants and the field `part` are excluded.
|
||||
- `no_distributors`: [string|list(string)] Exclude this distributors list. They are removed after computing `distributors`.
|
||||
- `normalize_locale`: [boolean=false] When normalizing values use the locale decimal point.
|
||||
- `normalize_values`: [boolean=false] Try to normalize the R, L and C values, producing uniform units and prefixes.
|
||||
|
|
@ -723,6 +728,7 @@ Next time you need this list just use an alias, like this:
|
|||
- `output`: [string='%f-%i%v.%x'] filename for the output (%i=bom). Affected by global options.
|
||||
- `ref_id`: [string=''] A prefix to add to all the references from this project. Used for multiple projects.
|
||||
- `ref_separator`: [string=' '] Separator used for the list of references.
|
||||
- `sort_style`: [string='type_value'] [type_value,type_value_ref,ref] Sorting criteria.
|
||||
- `source_by_id`: [boolean=false] Generate the `Source BoM` column using the reference ID instead of the project name.
|
||||
- `use_alt`: [boolean=false] Print grouped references in the alternate compressed style eg: R1-R7,R18.
|
||||
- `variant`: [string=''] Board variant, used to determine which components
|
||||
|
|
@ -737,9 +743,24 @@ Next time you need this list just use an alias, like this:
|
|||
- `hide_stats_info`: [boolean=false] Hide statistics information.
|
||||
- `highlight_empty`: [boolean=true] Use a color for empty cells. Applies only when `col_colors` is `true`.
|
||||
- `kicost`: [boolean=false] Enable KiCost worksheet creation.
|
||||
- `kicost_api_disable`: [string|list(string)=''] List of KiCost APIs to disable.
|
||||
- `kicost_api_enable`: [string|list(string)=''] List of KiCost APIs to enable.
|
||||
- `kicost_dist_desc`: [boolean=false] Used to add a column with the distributor's description. So you can chek this is the right component.
|
||||
- `logo`: [string|boolean=''] PNG file to use as logo, use false to remove.
|
||||
- `logo_scale`: [number=2] Scaling factor for the logo. Note that this value isn't honored by all spreadsheet software.
|
||||
- `max_col_width`: [number=60] [20,999] Maximum column width (characters).
|
||||
- `specs`: [boolean=false] Enable Specs worksheet creation. Contains specifications for the components.
|
||||
Works with only some KiCost APIs.
|
||||
- `specs_columns`: [list(dict)|list(string)] Which columns are included in the Specs worksheet. Use `References` for the references,
|
||||
'Row' for the order and 'Sep' to separate groups at the same level. By default all are included.
|
||||
Column names are distributor specific, the following aren't: '_desc', '_value', '_tolerance', '_footprint',
|
||||
'_power', '_current', '_voltage', '_frequency', '_temp_coeff', '_manf', '_size'.
|
||||
* Valid keys:
|
||||
- `comment`: [string=''] Used as explanation for this column. The XLSX output uses it.
|
||||
- `field`: [string=''] Name of the field to use for this column.
|
||||
- `join`: [list(string)|string=''] List of fields to join to this column.
|
||||
- `level`: [number=0] Used to group columns. The XLSX output uses it to collapse columns.
|
||||
- `name`: [string=''] Name to display in the header. The field is used when empty.
|
||||
- `style`: [string='modern-blue'] Head style: modern-blue, modern-green, modern-red and classic.
|
||||
- `title`: [string='KiBot Bill of Materials'] BoM title.
|
||||
|
||||
|
|
@ -1275,6 +1296,7 @@ Next time you need this list just use an alias, like this:
|
|||
- `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.
|
||||
- `drill_marks`: [string='full'] What to use to indicate the drill places, can be none, small or full (for real scale).
|
||||
- `hide_excluded`: [boolean=false] Hide components in the Fab layer that are marked as excluded by a variant.
|
||||
- `mirror`: [boolean=false] Print mirrored (X axis inverted). ONLY for KiCad 6.
|
||||
- `monochrome`: [boolean=false] Print in black and white.
|
||||
- `output`: [string='%f-%i%v.%x'] Filename for the output PDF (%i=layers, %x=pdf). Affected by global options.
|
||||
|
|
@ -1890,7 +1912,7 @@ KiBot: KiCad automation tool for documents generation
|
|||
Usage:
|
||||
kibot [-b BOARD] [-e SCHEMA] [-c CONFIG] [-d OUT_DIR] [-s PRE]
|
||||
[-q | -v...] [-i] [-m MKFILE] [-g DEF]... [TARGET...]
|
||||
kibot [-v...] [-c PLOT_CONFIG] --list
|
||||
kibot [-v...] [-b BOARD] [-e SCHEMA] [-c PLOT_CONFIG] --list
|
||||
kibot [-v...] [-b BOARD] [-d OUT_DIR] [-p | -P] --example
|
||||
kibot [-v...] --help-filters
|
||||
kibot [-v...] --help-list-outputs
|
||||
|
|
|
|||
|
|
@ -165,9 +165,11 @@ outputs:
|
|||
int_qtys: true
|
||||
# [boolean=true] Component groups with blank fields will be merged into the most compatible group, where possible
|
||||
merge_blank_fields: true
|
||||
# [boolean=true] When creating groups two components with empty/missing field will be interpreted as with the same value
|
||||
merge_both_blank: true
|
||||
# [list(string)] List of fields where we tolerate conflicts.
|
||||
# Use it to avoid undesired warnings.
|
||||
# By default the field indicated in `fit_field` and the field `part` are excluded
|
||||
# By default the field indicated in `fit_field`, the field used for variants and the field `part` are excluded
|
||||
no_conflict: ['Config', 'Part']
|
||||
# [string|list(string)] Exclude this distributors list. They are removed after computing `distributors`
|
||||
no_distributors:
|
||||
|
|
@ -183,6 +185,8 @@ outputs:
|
|||
ref_id: ''
|
||||
# [string=' '] Separator used for the list of references
|
||||
ref_separator: ' '
|
||||
# [string='type_value'] [type_value,type_value_ref,ref] Sorting criteria
|
||||
sort_style: 'type_value'
|
||||
# [boolean=false] Generate the `Source BoM` column using the reference ID instead of the project name
|
||||
source_by_id: false
|
||||
# [boolean=false] Print grouped references in the alternate compressed style eg: R1-R7,R18
|
||||
|
|
@ -208,12 +212,36 @@ outputs:
|
|||
highlight_empty: true
|
||||
# [boolean=false] Enable KiCost worksheet creation
|
||||
kicost: false
|
||||
# [string|list(string)=''] List of KiCost APIs to disable
|
||||
kicost_api_disable: ''
|
||||
# [string|list(string)=''] List of KiCost APIs to enable
|
||||
kicost_api_enable: ''
|
||||
# [boolean=false] Used to add a column with the distributor's description. So you can chek this is the right component
|
||||
kicost_dist_desc: false
|
||||
# [string|boolean=''] PNG file to use as logo, use false to remove
|
||||
logo: ''
|
||||
# [number=2] Scaling factor for the logo. Note that this value isn't honored by all spreadsheet software
|
||||
logo_scale: 2
|
||||
# [number=60] [20,999] Maximum column width (characters)
|
||||
max_col_width: 60
|
||||
# [boolean=false] Enable Specs worksheet creation. Contains specifications for the components.
|
||||
# Works with only some KiCost APIs
|
||||
specs: false
|
||||
# [list(dict)|list(string)] Which columns are included in the Specs worksheet. Use `References` for the references,
|
||||
# 'Row' for the order and 'Sep' to separate groups at the same level. By default all are included.
|
||||
# Column names are distributor specific, the following aren't: '_desc', '_value', '_tolerance', '_footprint',
|
||||
# '_power', '_current', '_voltage', '_frequency', '_temp_coeff', '_manf', '_size'
|
||||
specs_columns:
|
||||
# [string=''] Used as explanation for this column. The XLSX output uses it
|
||||
- comment: ''
|
||||
# [string=''] Name of the field to use for this column
|
||||
field: 'Row'
|
||||
# [list(string)|string=''] List of fields to join to this column
|
||||
join: ''
|
||||
# [number=0] Used to group columns. The XLSX output uses it to collapse columns
|
||||
level: 0
|
||||
# [string=''] Name to display in the header. The field is used when empty
|
||||
name: 'Line'
|
||||
# [string='modern-blue'] Head style: modern-blue, modern-green, modern-red and classic
|
||||
style: 'modern-blue'
|
||||
# [string='KiBot Bill of Materials'] BoM title
|
||||
|
|
@ -879,6 +907,8 @@ outputs:
|
|||
dnf_filter: ''
|
||||
# [string='full'] What to use to indicate the drill places, can be none, small or full (for real scale)
|
||||
drill_marks: 'full'
|
||||
# [boolean=false] Hide components in the Fab layer that are marked as excluded by a variant
|
||||
hide_excluded: false
|
||||
# [boolean=false] Print mirrored (X axis inverted). ONLY for KiCad 6
|
||||
mirror: false
|
||||
# [boolean=false] Print in black and white
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
Usage:
|
||||
kibot [-b BOARD] [-e SCHEMA] [-c CONFIG] [-d OUT_DIR] [-s PRE]
|
||||
[-q | -v...] [-i] [-m MKFILE] [-g DEF]... [TARGET...]
|
||||
kibot [-v...] [-c PLOT_CONFIG] --list
|
||||
kibot [-v...] [-b BOARD] [-e SCHEMA] [-c PLOT_CONFIG] --list
|
||||
kibot [-v...] [-b BOARD] [-d OUT_DIR] [-p | -P] --example
|
||||
kibot [-v...] --help-filters
|
||||
kibot [-v...] --help-list-outputs
|
||||
|
|
@ -90,7 +90,9 @@ def list_pre_and_outs(logger, outputs):
|
|||
if len(outputs):
|
||||
logger.info('Outputs:')
|
||||
for o in outputs:
|
||||
config_output(o, dry=True)
|
||||
# Note: we can't do a `dry` config because some layer and field names can be validated only if we
|
||||
# load the schematic and the PCB.
|
||||
config_output(o, dry=False)
|
||||
logger.info('- '+str(o))
|
||||
|
||||
|
||||
|
|
@ -341,15 +343,16 @@ def main():
|
|||
with open(plot_config) as cf_file:
|
||||
outputs = cr.read(cf_file)
|
||||
|
||||
# Determine the SCH file
|
||||
GS.set_sch(solve_schematic(args.schematic, args.board_file, plot_config))
|
||||
# Determine the PCB file
|
||||
GS.set_pcb(solve_board_file(GS.sch_file, args.board_file))
|
||||
|
||||
# Is just list the available targets?
|
||||
if args.list:
|
||||
list_pre_and_outs(logger, outputs)
|
||||
sys.exit(0)
|
||||
|
||||
# Determine the SCH file
|
||||
GS.set_sch(solve_schematic(args.schematic, args.board_file, plot_config))
|
||||
# Determine the PCB file
|
||||
GS.set_pcb(solve_board_file(GS.sch_file, args.board_file))
|
||||
if args.makefile:
|
||||
# Only create a makefile
|
||||
generate_makefile(args.makefile, plot_config, outputs)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ All the logic to convert a list of components into the rows and columns used to
|
|||
import locale
|
||||
from copy import deepcopy
|
||||
from math import ceil
|
||||
from .units import compare_values, comp_match
|
||||
from .units import compare_values, comp_match, get_last_warning
|
||||
from .bom_writer import write_bom
|
||||
from .columnlist import ColumnList
|
||||
from ..misc import DNF, W_FIELDCONF
|
||||
|
|
@ -70,6 +70,9 @@ def compare_field(c1, c2, field, cfg):
|
|||
# If blank comparisons are allowed
|
||||
if (c1_value == "" or c2_value == "") and cfg.merge_blank_fields:
|
||||
return True
|
||||
if not cfg.merge_both_blank and c1_value == "" and c2_value == "":
|
||||
# Avoid merging two components with empty field
|
||||
return False
|
||||
return c1_value == c2_value
|
||||
|
||||
|
||||
|
|
@ -346,7 +349,7 @@ class ComponentGroup(object):
|
|||
return row
|
||||
|
||||
|
||||
def get_value_sort(comp):
|
||||
def get_value_sort(comp, fallback_ref=False):
|
||||
""" Try to better sort R, L and C components """
|
||||
res = comp.value_sort
|
||||
if res:
|
||||
|
|
@ -358,6 +361,8 @@ def get_value_sort(comp):
|
|||
# milli Ohms
|
||||
value = "{0:15d}".format(int(value * 1000 * mult + 0.1))
|
||||
return value
|
||||
if fallback_ref:
|
||||
return comp.ref_prefix + "{0:15d}".format(_suffix_to_num(comp.ref_suffix))
|
||||
return comp.value
|
||||
|
||||
|
||||
|
|
@ -400,7 +405,14 @@ def group_components(cfg, components):
|
|||
continue
|
||||
# Cache the value used to sort
|
||||
if c.ref_prefix in RLC_PREFIX and c.value.lower() not in DNF:
|
||||
c.value_sort = comp_match(c.value, c.ref_prefix)
|
||||
c.value_sort = comp_match(c.value, c.ref_prefix, c.ref)
|
||||
if c.value_sort is None and (' ' in c.value):
|
||||
# Try with the data before a space
|
||||
value = c.value.split(' ')[0]
|
||||
value_sort = comp_match(value, c.ref_prefix)
|
||||
if value_sort is not None:
|
||||
c.value_sort = value_sort
|
||||
logger.warning(get_last_warning() + "Using `{}` for {} instead".format(value, c.ref))
|
||||
else:
|
||||
c.value_sort = None
|
||||
# Try to add the component to an existing group
|
||||
|
|
@ -429,8 +441,16 @@ def group_components(cfg, components):
|
|||
if cfg.normalize_values:
|
||||
g.fields[ColumnList.COL_VALUE_L] = normalize_value(g.components[0], decimal_point)
|
||||
# Sort the groups
|
||||
# First priority is the Type of component (e.g. R?, U?, L?)
|
||||
groups = sorted(groups, key=lambda g: [g.components[0].ref_prefix, get_value_sort(g.components[0])])
|
||||
if cfg.sort_style == 'type_value':
|
||||
# First priority is the Type of component (e.g. R?, U?, L?)
|
||||
# Second is the value
|
||||
groups = sorted(groups, key=lambda g: [g.components[0].ref_prefix, get_value_sort(g.components[0])])
|
||||
elif cfg.sort_style == 'type_value_ref':
|
||||
# First priority is the Type of component (e.g. R?, U?, L?)
|
||||
# Second is the value, but if we don't have a value we use the reference
|
||||
groups = sorted(groups, key=lambda g: [g.components[0].ref_prefix, get_value_sort(g.components[0], True)])
|
||||
else: # ref
|
||||
groups = sorted(groups, key=lambda g: [g.components[0].ref_prefix, _suffix_to_num(g.components[0].ref_suffix)])
|
||||
# Enumerate the groups and compute stats
|
||||
n_total = 0
|
||||
n_fitted = 0
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ UNIT_ALL = UNIT_R + UNIT_C + UNIT_L
|
|||
match = None
|
||||
# Current locale decimal point value
|
||||
decimal_point = None
|
||||
# Last warning
|
||||
last_warning = ''
|
||||
|
||||
|
||||
def get_last_warning():
|
||||
return last_warning
|
||||
|
||||
|
||||
def get_unit(unit, ref_prefix):
|
||||
|
|
@ -98,12 +104,13 @@ def match_string():
|
|||
return r"(\d*\.?\d*)\s*(" + group_string(PREFIX_ALL) + ")*(" + group_string(UNIT_ALL) + r")*(\d*)$"
|
||||
|
||||
|
||||
def comp_match(component, ref_prefix):
|
||||
def comp_match(component, ref_prefix, ref=None):
|
||||
"""
|
||||
Return a normalized value and units for a given component value string
|
||||
e.g. comp_match('10R2') returns (10, R)
|
||||
e.g. comp_match('3.3mOhm') returns (0.0033, R)
|
||||
"""
|
||||
global last_warning
|
||||
original = component
|
||||
# Remove useless spaces
|
||||
component = component.strip()
|
||||
|
|
@ -130,14 +137,17 @@ def comp_match(component, ref_prefix):
|
|||
# Ignore case
|
||||
match = re.compile(match_string(), flags=re.IGNORECASE)
|
||||
|
||||
where = ' in {}'.format(ref) if ref is not None else ''
|
||||
result = match.match(component)
|
||||
if not result:
|
||||
logger.warning(W_BADVAL1 + "Malformed value: `{}` (no match)".format(original))
|
||||
last_warning = W_BADVAL1
|
||||
logger.warning(W_BADVAL1 + "Malformed value: `{}` (no match{})".format(original, where))
|
||||
return None
|
||||
|
||||
value, prefix, units, post = result.groups()
|
||||
if value == '.':
|
||||
logger.warning(W_BADVAL2 + "Malformed value: `{}` (reduced to decimal point)".format(original))
|
||||
last_warning = W_BADVAL2
|
||||
logger.warning(W_BADVAL2 + "Malformed value: `{}` (reduced to decimal point{})".format(original, where))
|
||||
return None
|
||||
if value == '':
|
||||
value = '0'
|
||||
|
|
@ -148,7 +158,9 @@ def comp_match(component, ref_prefix):
|
|||
# We will also have a trailing number
|
||||
if post:
|
||||
if "." in value:
|
||||
logger.warning(W_BADVAL3 + "Malformed value: `{}` (unit split, but contains decimal point)".format(original))
|
||||
last_warning = W_BADVAL3
|
||||
logger.warning(W_BADVAL3 + "Malformed value: `{}` (unit split, but contains decimal point{})".
|
||||
format(original, where))
|
||||
return None
|
||||
value = float(value)
|
||||
postValue = float(post) / (10 ** len(post))
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ from base64 import b64decode
|
|||
from .columnlist import ColumnList
|
||||
from .kibot_logo import KIBOT_LOGO
|
||||
from .. import log
|
||||
from ..misc import W_NOKICOST, W_UNKDIST
|
||||
from ..misc import W_NOKICOST, W_UNKDIST, KICOST_ERROR, W_BADFIELD
|
||||
from ..error import trace_dump
|
||||
from ..__main__ import __version__
|
||||
try:
|
||||
from xlsxwriter import Workbook
|
||||
|
|
@ -37,14 +38,16 @@ try:
|
|||
sys.path.insert(0, rel_path)
|
||||
# Init the logger first
|
||||
logger = log.get_logger(__name__)
|
||||
from kicost.global_vars import set_logger
|
||||
from kicost.global_vars import set_logger, KiCostError
|
||||
set_logger(logger)
|
||||
from kicost import PartGroup
|
||||
from kicost.kicost import query_part_info
|
||||
from kicost.spreadsheet import create_worksheet, Spreadsheet
|
||||
from kicost.distributors import (init_distributor_dict, set_distributors_logger, get_distributors_list,
|
||||
get_dist_name_from_label, set_distributors_progress)
|
||||
get_dist_name_from_label, set_distributors_progress, is_valid_api,
|
||||
configure_from_environment, configure_apis)
|
||||
from kicost.edas import set_edas_logger
|
||||
from kicost.config import load_config
|
||||
# Progress mechanism: use the one declared in __main__ (TQDM)
|
||||
from kicost.__main__ import ProgressConsole
|
||||
set_distributors_progress(ProgressConsole)
|
||||
|
|
@ -66,10 +69,11 @@ GREY_L = "#f3f3f3"
|
|||
HEAD_COLOR_R = "#982020"
|
||||
HEAD_COLOR_G = "#009879"
|
||||
HEAD_COLOR_B = "#0e4e8e"
|
||||
DEFAULT_FMT = {'text_wrap': True, 'align': 'center_across', 'valign': 'vjustify'}
|
||||
DEFAULT_FMT = {'text_wrap': True, 'align': 'center_across', 'valign': 'vcenter'}
|
||||
KICOST_COLUMNS = {'refs': ColumnList.COL_REFERENCE,
|
||||
'desc': ColumnList.COL_DESCRIPTION,
|
||||
'qty': ColumnList.COL_GRP_BUILD_QUANTITY}
|
||||
SPECS_GENERATED = set((ColumnList.COL_REFERENCE_L, ColumnList.COL_ROW_NUMBER_L, 'sep'))
|
||||
|
||||
|
||||
def bg_color(col):
|
||||
|
|
@ -246,6 +250,75 @@ def create_color_ref(workbook, col_colors, hl_empty, fmt_cols, do_kicost, kicost
|
|||
worksheet.write_string(row, 0, label, format)
|
||||
|
||||
|
||||
def get_spec(part, name):
|
||||
if name[0] != '_':
|
||||
return part.specs.get(name, ['', ''])
|
||||
name = name[1:]
|
||||
for k, v in part.dd.items():
|
||||
val = v.extra_info.get(name, None)
|
||||
if val:
|
||||
return [name, val]
|
||||
return ['', '']
|
||||
|
||||
|
||||
def create_meta(workbook, name, columns, parts, fmt_head, fmt_cols, max_w, rename, levels, comments, join):
|
||||
worksheet = workbook.add_worksheet(name)
|
||||
col_w = []
|
||||
row_h = 1
|
||||
for c, col in enumerate(columns):
|
||||
name = rename.get(col.lower(), col) if rename else col
|
||||
worksheet.write_string(0, c, name, fmt_head)
|
||||
text_l = max(len(col), 6)
|
||||
if text_l > max_w:
|
||||
h = len(wrap(col, max_w))
|
||||
row_h = max(row_h, h)
|
||||
text_l = max_w
|
||||
col_w.append(text_l)
|
||||
if row_h > 1:
|
||||
worksheet.set_row(0, 15.0*row_h)
|
||||
for r, part in enumerate(parts):
|
||||
# Add the references as another spec
|
||||
part.specs[ColumnList.COL_REFERENCE_L] = (ColumnList.COL_REFERENCE, part.collapsed_refs)
|
||||
# Also add the row
|
||||
part.specs[ColumnList.COL_ROW_NUMBER_L] = (ColumnList.COL_ROW_NUMBER, str(r+1))
|
||||
row_h = 1
|
||||
for c, col in enumerate(columns):
|
||||
col_l = col.lower()
|
||||
if col_l == 'sep':
|
||||
col_w[c] = 0
|
||||
continue
|
||||
v = get_spec(part, col_l)
|
||||
text = v[1]
|
||||
# Append text from other fields
|
||||
if join:
|
||||
for j in join:
|
||||
if j[0] == col_l:
|
||||
for c_join in j[1:]:
|
||||
v = part.specs.get(c_join, None)
|
||||
if v:
|
||||
text += ' ' + v[1]
|
||||
text_l = len(text)
|
||||
if not text_l:
|
||||
continue
|
||||
fmt_kind = 0 if col_l in SPECS_GENERATED else 2
|
||||
worksheet.write_string(r+1, c, text, fmt_cols[fmt_kind][r % 2])
|
||||
if text_l > col_w[c]:
|
||||
if text_l > max_w:
|
||||
h = len(wrap(text, max_w))
|
||||
row_h = max(row_h, h)
|
||||
text_l = max_w
|
||||
col_w[c] = text_l
|
||||
if row_h > 1:
|
||||
worksheet.set_row(r+1, 15.0*row_h)
|
||||
for i, width in enumerate(col_w):
|
||||
ops = {'level': levels[i] if levels else 0}
|
||||
if not width:
|
||||
ops['hidden'] = 1
|
||||
if comments and comments[i]:
|
||||
worksheet.write_comment(0, i, comments[i])
|
||||
worksheet.set_column(i, i, width, None, ops)
|
||||
|
||||
|
||||
def adjust_widths(worksheet, column_widths, max_width, levels):
|
||||
c_levels = len(levels)
|
||||
for i, width in enumerate(column_widths):
|
||||
|
|
@ -375,7 +448,9 @@ def remove_unknown_distributors(distributors, available, silent):
|
|||
d = d.lower()
|
||||
if d not in available:
|
||||
# Is the label of the column?
|
||||
d = get_dist_name_from_label(d)
|
||||
new_d = get_dist_name_from_label(d)
|
||||
if new_d is not None:
|
||||
d = new_d
|
||||
if d not in available:
|
||||
if not silent:
|
||||
logger.warning(W_UNKDIST+'Unknown distributor `{}`'.format(d))
|
||||
|
|
@ -412,7 +487,40 @@ def compute_qtys(cfg, g):
|
|||
return [str(g.get_count(sch.name)) for sch in cfg.aggregate]
|
||||
|
||||
|
||||
def create_kicost_sheet(workbook, groups, image_data, fmt_title, fmt_info, fmt_subtitle, cfg):
|
||||
def create_meta_sheets(workbook, used_parts, fmt_head, fmt_cols, cfg, ss):
|
||||
if cfg.xlsx.specs:
|
||||
meta_names = ['Specs', 'Specs (DNF)']
|
||||
for ws in range(2):
|
||||
spec_cols = {}
|
||||
spec_cols_l = set()
|
||||
parts = used_parts[ws]
|
||||
for part in parts:
|
||||
for name_l, v in part.specs.items():
|
||||
spec_cols_l.add(name_l)
|
||||
spec = v[0]
|
||||
if spec in spec_cols:
|
||||
spec_cols[spec] += 1
|
||||
else:
|
||||
spec_cols[spec] = 1
|
||||
if len(spec_cols):
|
||||
columns = cfg.xlsx.s_columns
|
||||
if columns is None:
|
||||
# Use all columns, sort them by relevance (most used) and alphabetically
|
||||
c = len(parts)
|
||||
columns = sorted(spec_cols, key=lambda k: (c - spec_cols[k], k))
|
||||
columns.insert(0, ColumnList.COL_REFERENCE)
|
||||
else:
|
||||
# Inform about missing columns
|
||||
for c in columns:
|
||||
col = c.lower()
|
||||
if ((col[0] == '_' and col[1:] not in ss.extra_info_display) or
|
||||
(col[0] != '_' and col not in spec_cols_l and col not in SPECS_GENERATED)):
|
||||
logger.warning(W_BADFIELD+'Invalid Specs column name `{}` {}'.format(c, col[1:]))
|
||||
create_meta(workbook, meta_names[ws], columns, parts, fmt_head, fmt_cols, cfg.xlsx.max_col_width,
|
||||
cfg.xlsx.s_rename, cfg.xlsx.s_levels, cfg.xlsx.s_comments, cfg.xlsx.s_join)
|
||||
|
||||
|
||||
def _create_kicost_sheet(workbook, groups, image_data, fmt_title, fmt_info, fmt_subtitle, fmt_head, fmt_cols, cfg):
|
||||
if not KICOST_SUPPORT:
|
||||
logger.warning(W_NOKICOST, 'KiCost sheet requested but failed to load KiCost support')
|
||||
return
|
||||
|
|
@ -426,6 +534,19 @@ def create_kicost_sheet(workbook, groups, image_data, fmt_title, fmt_info, fmt_s
|
|||
# Force KiCost to use our logger
|
||||
set_distributors_logger(logger)
|
||||
set_edas_logger(logger)
|
||||
# Load KiCost config (includes APIs config)
|
||||
api_options = load_config()
|
||||
# Environment with overwrite
|
||||
configure_from_environment(api_options, True)
|
||||
# Filter which APIs we want
|
||||
for api in cfg.xlsx.kicost_api_disable:
|
||||
if is_valid_api(api):
|
||||
api_options[api]['enable'] = False
|
||||
for api in cfg.xlsx.kicost_api_enable:
|
||||
if is_valid_api(api):
|
||||
api_options[api]['enable'] = True
|
||||
# Configure the APIs
|
||||
configure_apis(api_options)
|
||||
# Start with a clean list of available distributors
|
||||
init_distributor_dict()
|
||||
# Create the projects information structure
|
||||
|
|
@ -437,6 +558,13 @@ def create_kicost_sheet(workbook, groups, image_data, fmt_title, fmt_info, fmt_s
|
|||
Spreadsheet.ADJUST_ROW_AND_COL_SIZE = True
|
||||
Spreadsheet.MAX_COL_WIDTH = cfg.xlsx.max_col_width
|
||||
Spreadsheet.PART_NSEQ_SEPRTR = cfg.ref_separator
|
||||
if hasattr(Spreadsheet, 'SUPPRESS_DIST_DESC'):
|
||||
Spreadsheet.SUPPRESS_DIST_DESC = not cfg.xlsx.kicost_dist_desc
|
||||
# Keep our sorting
|
||||
try:
|
||||
Spreadsheet.SORT_GROUPS = False
|
||||
except Exception:
|
||||
pass
|
||||
# Make the version less intrusive
|
||||
Spreadsheet.WRK_FORMATS['about_msg']['font_size'] = 8
|
||||
# Don 't add project info, we add our own data
|
||||
|
|
@ -475,6 +603,7 @@ def create_kicost_sheet(workbook, groups, image_data, fmt_title, fmt_info, fmt_s
|
|||
id = new_id.lower()
|
||||
if id in cfg.column_rename:
|
||||
v['label'] = cfg.column_rename[id]
|
||||
used_parts = []
|
||||
for ws in range(2):
|
||||
# Second pass is DNF
|
||||
dnf = ws == 1
|
||||
|
|
@ -523,6 +652,9 @@ def create_kicost_sheet(workbook, groups, image_data, fmt_title, fmt_info, fmt_s
|
|||
if cfg.xlsx.title:
|
||||
wks.set_row(0, 32)
|
||||
wks.merge_range(0, col1, 0, ss.globals_width, cfg.xlsx.title, fmt_title)
|
||||
used_parts.append(parts)
|
||||
# Specs sheets
|
||||
create_meta_sheets(workbook, used_parts, fmt_head, fmt_cols, cfg, ss)
|
||||
colors = {}
|
||||
colors['Best price'] = ss.wrk_formats['best_price']
|
||||
colors['No manufacturer or distributor code'] = ss.wrk_formats['not_manf_codes']
|
||||
|
|
@ -536,6 +668,15 @@ def create_kicost_sheet(workbook, groups, image_data, fmt_title, fmt_info, fmt_s
|
|||
return colors
|
||||
|
||||
|
||||
def create_kicost_sheet(workbook, groups, image_data, fmt_title, fmt_info, fmt_subtitle, fmt_head, fmt_cols, cfg):
|
||||
try:
|
||||
return _create_kicost_sheet(workbook, groups, image_data, fmt_title, fmt_info, fmt_subtitle, fmt_head, fmt_cols, cfg)
|
||||
except KiCostError as e:
|
||||
trace_dump()
|
||||
logger.error('KiCost error: `{}` ({})'.format(e.msg, e.id))
|
||||
exit(KICOST_ERROR)
|
||||
|
||||
|
||||
def write_xlsx(filename, groups, col_fields, head_names, cfg):
|
||||
"""
|
||||
Write BoM out to a XLSX file
|
||||
|
|
@ -660,7 +801,8 @@ def write_xlsx(filename, groups, col_fields, head_names, cfg):
|
|||
# Optionally add KiCost information
|
||||
kicost_colors = None
|
||||
if cfg.xlsx.kicost:
|
||||
kicost_colors = create_kicost_sheet(workbook, groups, image_data, fmt_title, fmt_info, fmt_subtitle, cfg)
|
||||
kicost_colors = create_kicost_sheet(workbook, groups, image_data, fmt_title, fmt_info, fmt_subtitle, fmt_head,
|
||||
fmt_cols, cfg)
|
||||
# Add a sheet for the color references
|
||||
create_color_ref(workbook, cfg.xlsx.col_colors, hl_empty, fmt_cols, cfg.xlsx.kicost and KICOST_SUPPORT, kicost_colors)
|
||||
|
||||
|
|
|
|||
|
|
@ -124,6 +124,10 @@ class Generic(BaseFilter): # noqa: F821
|
|||
if reg.skip_if_no_field and not c.is_field(reg.column):
|
||||
# Skip the check if the field doesn't exist
|
||||
continue
|
||||
if reg.match_if_field and c.is_field(reg.column):
|
||||
return True
|
||||
if reg.match_if_no_field and not c.is_field(reg.column):
|
||||
return True
|
||||
field_value = c.get_field_value(reg.column)
|
||||
res = reg.regex.search(field_value)
|
||||
if reg.invert:
|
||||
|
|
@ -145,6 +149,10 @@ class Generic(BaseFilter): # noqa: F821
|
|||
if reg.skip_if_no_field and not c.is_field(reg.column):
|
||||
# Skip the check if the field doesn't exist
|
||||
continue
|
||||
if reg.match_if_field and c.is_field(reg.column):
|
||||
return True
|
||||
if reg.match_if_no_field and not c.is_field(reg.column):
|
||||
return True
|
||||
field_value = c.get_field_value(reg.column)
|
||||
res = reg.regex.search(field_value)
|
||||
if reg.invert:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from .config import KiConf, un_quote
|
|||
from ..gs import GS
|
||||
from ..misc import (W_BADPOLI, W_POLICOORDS, W_BADSQUARE, W_BADCIRCLE, W_BADARC, W_BADTEXT, W_BADPIN, W_BADCOMP, W_BADDRAW,
|
||||
W_UNKDCM, W_UNKAR, W_ARNOPATH, W_ARNOREF, W_MISCFLD, W_EXTRASPC, W_NOLIB, W_INCPOS, W_NOANNO, W_MISSLIB,
|
||||
W_MISSDCM, W_MISSCMP, W_MISFLDNAME)
|
||||
W_MISSDCM, W_MISSCMP, W_MISFLDNAME, W_NOENDLIB)
|
||||
from .. import log
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
|
@ -105,16 +105,16 @@ class LibComponentField(object):
|
|||
""" A field for a component in the library.
|
||||
Almost the same as a field in the schematic, but incompatible!!! """
|
||||
# F n "text" posx posy dimension orientation visibility hjustify vjustify/italic/bold "name"
|
||||
field_re = re.compile(r'F\s*(\d+)\s+' # 0 Field number
|
||||
r'"([^"]*)"\s+' # 1 Field value
|
||||
r'(-?\d+)\s+' # 2 Pos X
|
||||
r'(-?\d+)\s+' # 3 Pos Y
|
||||
r'(\d+)\s+' # 4 Dimension
|
||||
r'([HV])\s+' # 5 Orientation
|
||||
r'([VI])\s+' # 6 Visibility
|
||||
r'([LRCBT])\s+' # 7 HJustify
|
||||
r'([LRCBT][IN][BN])\s*' # 8 VJustify+Italic+Bold
|
||||
r'("[^"]*")?') # 9 Name for user fields
|
||||
field_re = re.compile(r'F\s*(\d+)\s+' # 0 Field number
|
||||
r'"((?:[^\\]|(?:\\.))*)"\s+' # 1 Field value
|
||||
r'(-?\d+)\s+' # 2 Pos X
|
||||
r'(-?\d+)\s+' # 3 Pos Y
|
||||
r'(\d+)\s+' # 4 Dimension
|
||||
r'([HV])\s+' # 5 Orientation
|
||||
r'([VI])\s+' # 6 Visibility
|
||||
r'([LRCBT])\s+' # 7 HJustify
|
||||
r'([LRCBT][IN][BN])\s*' # 8 VJustify+Italic+Bold
|
||||
r'("(?:[^\\]|(?:\\.))*")?') # 9 Name for user fields
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
|
@ -510,6 +510,10 @@ class LibComponent(object):
|
|||
self.draw = []
|
||||
line = f.get_line()
|
||||
while not line.startswith('ENDDEF'):
|
||||
if len(line) == 0:
|
||||
# Skip empty lines
|
||||
line = f.get_line()
|
||||
continue
|
||||
if line[0] == 'F':
|
||||
# A field
|
||||
field = LibComponentField.parse(line, lib_name, f)
|
||||
|
|
@ -655,7 +659,11 @@ class SymLib(object):
|
|||
self.alias[a] = o
|
||||
else:
|
||||
raise SchLibError('Unknown library entry', line, f)
|
||||
line = f.get_line()
|
||||
try:
|
||||
line = f.get_line()
|
||||
except SchLibError:
|
||||
logger.warning(W_NOENDLIB + 'Library without end of file comment: `{}`'.format(file))
|
||||
break
|
||||
|
||||
|
||||
class DocLibEntry(object):
|
||||
|
|
@ -713,8 +721,8 @@ class DocLib(object):
|
|||
|
||||
class SchematicField(object):
|
||||
# F n "text" orientation posx posy dimension flags hjustify vjustify/italic/bold "name"
|
||||
field_re = re.compile(r'F\s*(\d+)\s+"([^"]*)"\s+([HV])\s+(-?\d+)\s+(-?\d+)\s+(\d+)\s+(\d+)'
|
||||
r'\s+([LRCBT])\s+([LRCBT][IN][BN])\s*("[^"]*")?')
|
||||
field_re = re.compile(r'F\s*(\d+)\s+"((?:[^\\]|(?:\\.))*)"\s+([HV])\s+(-?\d+)\s+(-?\d+)\s+(\d+)\s+(\d+)'
|
||||
r'\s+([LRCBT])\s+([LRCBT][IN][BN])\s*("(?:[^\\]|(?:\\.))*")?')
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ PCBDRAW_ERR = 20
|
|||
SVG_SCH_PRINT = 21
|
||||
CORRUPTED_SCH = 22
|
||||
WRONG_INSTALL = 23
|
||||
KICOST_ERROR = 24
|
||||
error_level_to_name = ['NONE',
|
||||
'INTERNAL_ERROR',
|
||||
'WRONG_ARGUMENTS',
|
||||
|
|
@ -58,6 +59,7 @@ error_level_to_name = ['NONE',
|
|||
'SVG_SCH_PRINT',
|
||||
'CORRUPTED_SCH',
|
||||
'WRONG_INSTALL',
|
||||
'KICOST_ERROR',
|
||||
]
|
||||
CMD_EESCHEMA_DO = 'eeschema_do'
|
||||
URL_EESCHEMA_DO = 'https://github.com/INTI-CMNB/kicad-automation-scripts'
|
||||
|
|
@ -201,6 +203,7 @@ W_NOKICOST = '(W066) '
|
|||
W_UNKOUT = '(W067) '
|
||||
W_NOFILTERS = '(W068) '
|
||||
W_NOVARIANTS = '(W069) '
|
||||
W_NOENDLIB = '(W070) '
|
||||
|
||||
|
||||
class Rect(object):
|
||||
|
|
|
|||
|
|
@ -104,6 +104,10 @@ class BoMRegex(Optionable):
|
|||
""" {regex} """
|
||||
self.skip_if_no_field = False
|
||||
""" Skip this test if the field doesn't exist """
|
||||
self.match_if_field = False
|
||||
""" Match if the field exists, no regex applied. Not affected by `invert` """
|
||||
self.match_if_no_field = False
|
||||
""" Match if the field doesn't exists, no regex applied. Not affected by `invert` """
|
||||
self.invert = False
|
||||
""" Invert the regex match result """
|
||||
|
||||
|
|
@ -286,6 +290,38 @@ class VariantOptions(BaseOptions):
|
|||
for gi in self.old_badhes:
|
||||
gi.SetLayer(self.badhes)
|
||||
|
||||
def remove_fab(self, board, comps_hash):
|
||||
""" Remove from Fab the excluded components. """
|
||||
ffab = board.GetLayerID('F.Fab')
|
||||
bfab = board.GetLayerID('B.Fab')
|
||||
old_ffab = []
|
||||
old_bfab = []
|
||||
rescue = board.GetLayerID('Rescue')
|
||||
for m in board.GetModules():
|
||||
ref = m.GetReference()
|
||||
c = comps_hash.get(ref, None)
|
||||
if not c.included:
|
||||
# Remove any graphical item in the *.Fab layers
|
||||
for gi in m.GraphicalItems():
|
||||
l_gi = gi.GetLayer()
|
||||
if l_gi == ffab:
|
||||
gi.SetLayer(rescue)
|
||||
old_ffab.append(gi)
|
||||
if l_gi == bfab:
|
||||
gi.SetLayer(rescue)
|
||||
old_bfab.append(gi)
|
||||
# Store the data to undo the above actions
|
||||
self.old_ffab = old_ffab
|
||||
self.old_bfab = old_bfab
|
||||
self.ffab = ffab
|
||||
self.bfab = bfab
|
||||
|
||||
def restore_fab(self, board, comps_hash):
|
||||
for gi in self.old_ffab:
|
||||
gi.SetLayer(self.ffab)
|
||||
for gi in self.old_bfab:
|
||||
gi.SetLayer(self.bfab)
|
||||
|
||||
def run(self, output_dir):
|
||||
""" Makes the list of components available """
|
||||
if not self.dnf_filter and not self.variant:
|
||||
|
|
|
|||
|
|
@ -163,9 +163,60 @@ class BoMXLSX(BoMLinkable):
|
|||
""" Head style: modern-blue, modern-green, modern-red and classic """
|
||||
self.kicost = False
|
||||
""" Enable KiCost worksheet creation """
|
||||
self.kicost_api_enable = Optionable
|
||||
""" [string|list(string)=''] List of KiCost APIs to enable """
|
||||
self.kicost_api_disable = Optionable
|
||||
""" [string|list(string)=''] List of KiCost APIs to disable """
|
||||
self.kicost_dist_desc = False
|
||||
""" Used to add a column with the distributor's description. So you can chek this is the right component """
|
||||
self.specs = False
|
||||
""" Enable Specs worksheet creation. Contains specifications for the components.
|
||||
Works with only some KiCost APIs """
|
||||
self.specs_columns = BoMColumns
|
||||
""" [list(dict)|list(string)] Which columns are included in the Specs worksheet. Use `References` for the references,
|
||||
'Row' for the order and 'Sep' to separate groups at the same level. By default all are included.
|
||||
Column names are distributor specific, the following aren't: '_desc', '_value', '_tolerance', '_footprint',
|
||||
'_power', '_current', '_voltage', '_frequency', '_temp_coeff', '_manf', '_size' """
|
||||
self.logo_scale = 2
|
||||
""" Scaling factor for the logo. Note that this value isn't honored by all spreadsheet software """
|
||||
|
||||
def process_columns_config(self, cols):
|
||||
if isinstance(cols, type):
|
||||
return (None, None, None, None, None)
|
||||
columns = []
|
||||
column_levels = []
|
||||
column_comments = []
|
||||
column_rename = {}
|
||||
join = []
|
||||
# Create the different lists
|
||||
for col in cols:
|
||||
if isinstance(col, str):
|
||||
# Just a string, add to the list of used
|
||||
new_col = col
|
||||
new_col_l = new_col.lower()
|
||||
level = 0
|
||||
comment = ''
|
||||
if new_col_l[0] == '_':
|
||||
column_rename[new_col_l] = new_col_l[1:].capitalize()
|
||||
else:
|
||||
# A complete entry
|
||||
new_col = col.field
|
||||
new_col_l = new_col.lower()
|
||||
# A column rename
|
||||
if col.name:
|
||||
column_rename[new_col_l] = col.name
|
||||
elif new_col_l[0] == '_':
|
||||
column_rename[new_col_l] = new_col_l[1:].capitalize()
|
||||
# Attach other columns
|
||||
if col.join:
|
||||
join.append(col.join)
|
||||
level = col.level
|
||||
comment = col.comment
|
||||
columns.append(new_col)
|
||||
column_levels.append(level)
|
||||
column_comments.append(comment)
|
||||
return (columns, column_levels, column_comments, column_rename, join)
|
||||
|
||||
def config(self, parent):
|
||||
super().config(parent)
|
||||
# Style
|
||||
|
|
@ -173,6 +224,18 @@ class BoMXLSX(BoMLinkable):
|
|||
self.style = 'modern-blue'
|
||||
if self.style not in VALID_STYLES:
|
||||
raise KiPlotConfigurationError('Unknown style `{}`'.format(self.style))
|
||||
# KiCost APIs
|
||||
if isinstance(self.kicost_api_enable, type):
|
||||
self.kicost_api_enable = []
|
||||
elif isinstance(self.kicost_api_enable, str):
|
||||
self.kicost_api_enable = [self.kicost_api_enable]
|
||||
if isinstance(self.kicost_api_disable, type):
|
||||
self.kicost_api_disable = []
|
||||
elif isinstance(self.kicost_api_disable, str):
|
||||
self.kicost_api_disable = [self.kicost_api_disable]
|
||||
# Specs columns
|
||||
(self.s_columns, self.s_levels, self.s_comments, self.s_rename,
|
||||
self.s_join) = self.process_columns_config(self.specs_columns)
|
||||
|
||||
|
||||
class ComponentAliases(Optionable):
|
||||
|
|
@ -270,6 +333,8 @@ class BoMOptions(BaseOptions):
|
|||
""" Connectors with the same footprints will be grouped together, independent of the name of the connector """
|
||||
self.merge_blank_fields = True
|
||||
""" Component groups with blank fields will be merged into the most compatible group, where possible """
|
||||
self.merge_both_blank = True
|
||||
""" When creating groups two components with empty/missing field will be interpreted as with the same value """
|
||||
self.group_fields = GroupFields
|
||||
""" [list(string)] List of fields used for sorting individual components into groups.
|
||||
Components which match (comparing *all* fields) will be grouped together.
|
||||
|
|
@ -293,7 +358,7 @@ class BoMOptions(BaseOptions):
|
|||
self.no_conflict = NoConflict
|
||||
""" [list(string)] List of fields where we tolerate conflicts.
|
||||
Use it to avoid undesired warnings.
|
||||
By default the field indicated in `fit_field` and the field `part` are excluded """
|
||||
By default the field indicated in `fit_field`, the field used for variants and the field `part` are excluded """
|
||||
self.aggregate = Aggregate
|
||||
""" [list(dict)] Add components from other projects """
|
||||
self.ref_id = ''
|
||||
|
|
@ -306,6 +371,8 @@ class BoMOptions(BaseOptions):
|
|||
""" [string|list(string)] Include this distributors list. Default is all the available """
|
||||
self.no_distributors = Optionable
|
||||
""" [string|list(string)] Exclude this distributors list. They are removed after computing `distributors` """
|
||||
self.sort_style = 'type_value'
|
||||
""" [type_value,type_value_ref,ref] Sorting criteria """
|
||||
self._format_example = 'CSV'
|
||||
super().__init__()
|
||||
|
||||
|
|
@ -457,6 +524,9 @@ class BoMOptions(BaseOptions):
|
|||
if isinstance(self.no_conflict, type):
|
||||
no_conflict.add(self.fit_field)
|
||||
no_conflict.add('part')
|
||||
var_field = self.variant.get_variant_field()
|
||||
if var_field is not None:
|
||||
no_conflict.add(var_field)
|
||||
else:
|
||||
for field in self.no_conflict:
|
||||
no_conflict.add(field.lower())
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ class PDF_Pcb_PrintOptions(VariantOptions):
|
|||
""" Print layers in separated pages """
|
||||
self.mirror = False
|
||||
""" Print mirrored (X axis inverted). ONLY for KiCad 6 """
|
||||
self.hide_excluded = False
|
||||
""" Hide components in the Fab layer that are marked as excluded by a variant """
|
||||
super().__init__()
|
||||
self._expand_ext = 'pdf'
|
||||
|
||||
|
|
@ -75,6 +77,8 @@ class PDF_Pcb_PrintOptions(VariantOptions):
|
|||
comps_hash = self.get_refs_hash()
|
||||
self.cross_modules(board, comps_hash)
|
||||
self.remove_paste_and_glue(board, comps_hash)
|
||||
if self.hide_excluded:
|
||||
self.remove_fab(board, comps_hash)
|
||||
# Save the PCB to a temporal file
|
||||
with NamedTemporaryFile(mode='w', suffix='.kicad_pcb', delete=False) as f:
|
||||
fname = f.name
|
||||
|
|
@ -84,6 +88,8 @@ class PDF_Pcb_PrintOptions(VariantOptions):
|
|||
fproj = self._copy_project(fname)
|
||||
self.uncross_modules(board, comps_hash)
|
||||
self.restore_paste_and_glue(board, comps_hash)
|
||||
if self.hide_excluded:
|
||||
self.restore_fab(board, comps_hash)
|
||||
return fname, fproj
|
||||
|
||||
def get_targets(self, out_dir):
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ class BaseVariant(RegVariant):
|
|||
""" [string|list(string)=''] Name of the filter to mark components as 'Do Not Change'.
|
||||
Use '_kibom_dnc' for the default KiBoM behavior """
|
||||
|
||||
def get_variant_field(self):
|
||||
''' Returns the name of the field used to determine if the component belongs to teh variant '''
|
||||
return None
|
||||
|
||||
def filter(self, comps):
|
||||
# Apply all the filters
|
||||
comps = apply_pre_transform(comps, self.pre_transform)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ class IBoM(BaseVariant): # noqa: F821
|
|||
self.variants_whitelist = Optionable
|
||||
""" [string|list(string)=''] List of board variants to include in the BOM """
|
||||
|
||||
def get_variant_field(self):
|
||||
''' Returns the name of the field used to determine if the component belongs to teh variant '''
|
||||
return self.variant_field
|
||||
|
||||
def config(self, parent):
|
||||
super().config(parent)
|
||||
self.pre_transform = BaseFilter.solve_filter(self.pre_transform, 'pre_transform', is_transform=True)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ class KiBoM(BaseVariant): # noqa: F821
|
|||
self.variant = Optionable
|
||||
""" [string|list(string)=''] Board variant(s) """
|
||||
|
||||
def get_variant_field(self):
|
||||
''' Returns the name of the field used to determine if the component belongs to teh variant '''
|
||||
return self.config_field
|
||||
|
||||
def set_def_filters(self, exclude_filter, dnf_filter, dnc_filter):
|
||||
""" Filters delegated to the variant """
|
||||
self._def_exclude_filter = exclude_filter
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ class KiCost(BaseVariant): # noqa: F821
|
|||
""" Valid separators for variants in the variant field.
|
||||
Each character is a valid separator """
|
||||
|
||||
def get_variant_field(self):
|
||||
''' Returns the name of the field used to determine if the component belongs to teh variant '''
|
||||
return self.variant_field
|
||||
|
||||
def config(self, parent):
|
||||
super().config(parent)
|
||||
self.pre_transform = BaseFilter.solve_filter(self.pre_transform, 'pre_transform',
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 7906366a496dc5cc3549152a16815aa403e0ea61
|
||||
Subproject commit 28488c34332cdaf83aa159b387a8b75b22af3f58
|
||||
|
|
@ -1915,7 +1915,7 @@ F 0 "R1" V 1093 3450 50 0000 C CNN
|
|||
F 1 "R" V 1184 3450 50 0000 C CNN
|
||||
F 2 "" V 1230 3450 50 0001 C CNN
|
||||
F 3 "~" H 1300 3450 50 0001 C CNN
|
||||
F 4 "Hi!" H 1300 3450 50 0001 C CNN "Test"
|
||||
F 4 "Hi! \"quoted text\"" H 1300 3450 50 0001 C CNN "Test"
|
||||
1 1300 3450
|
||||
0 1 1 0
|
||||
$EndComp
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ def test_auto_pcb_and_cfg_5(test_dir):
|
|||
|
||||
def test_list(test_dir):
|
||||
ctx = context.TestContext(test_dir, 'List', '3Rs', 'pre_and_position', POS_DIR)
|
||||
ctx.run(extra=['--list'], no_verbose=True, no_out_dir=True, no_board_file=True)
|
||||
ctx.run(extra=['--list'], no_verbose=True, no_out_dir=True)
|
||||
|
||||
assert ctx.search_out('run_erc: True')
|
||||
assert ctx.search_out('run_drc: True')
|
||||
|
|
|
|||
|
|
@ -23,6 +23,13 @@ MODE_SCH = 1
|
|||
MODE_PCB = 0
|
||||
# Defined as True to collect real world queries
|
||||
ADD_QUERY_TO_KNOWN = False
|
||||
# ***** DEBUG!!!
|
||||
# Test Digi-Key API. We need
|
||||
# os.environ['DIGIKEY_CACHE_TTL'] = '-1'
|
||||
# os.environ['DIGIKEY_STORAGE_PATH'] = op.abspath(op.join(op.dirname(__file__), '../../submodules/KiCost/tests/digikey'))
|
||||
# logger.setLevel(1) # Max. KiCost debug
|
||||
# ***** End of DEBUG!!!
|
||||
|
||||
|
||||
ng_ver = os.environ.get('KIAUS_USE_NIGHTLY')
|
||||
if ng_ver:
|
||||
|
|
|
|||
|
|
@ -29,3 +29,4 @@ outputs:
|
|||
name: Manufacturer P/N
|
||||
xlsx:
|
||||
kicost: true
|
||||
kicost_api_disable: 'Digi-Key'
|
||||
|
|
|
|||
|
|
@ -39,3 +39,4 @@ outputs:
|
|||
- Digi-Key
|
||||
xlsx:
|
||||
kicost: true
|
||||
kicost_api_disable: 'Digi-Key'
|
||||
|
|
|
|||
|
|
@ -36,3 +36,4 @@ outputs:
|
|||
- 'Voltage'
|
||||
xlsx:
|
||||
kicost: true
|
||||
kicost_api_disable: 'Digi-Key'
|
||||
|
|
|
|||
Loading…
Reference in New Issue