[Import] Preflights now can be imported

- Also fixed the filters and variants configuration, now delayed.

Fixes #221
Related to #181
This commit is contained in:
Salvador E. Tropea 2022-07-12 13:39:43 -03:00
parent 9ce18ae39b
commit 3e05ab8b6a
11 changed files with 325 additions and 19 deletions

View File

@ -10,7 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
The user also gets more information when something is missing.
It can be disabled from the command line.
- A global option to cross components without a body (#219)
- Now you can nest imports (import from an imported file) (#218)
- Imports:
- Now you can nest imports (import from an imported file) (#218)
- Preflights can be imported (#181)
## [1.2.0] - 2022-06-15
### Added

View File

@ -78,7 +78,7 @@ test_docker_local_1:
# Also change the owner of the files to the current user (we run as root like in GitHub)
#docker run --rm -it -v $(CWD):$(CWD) --workdir="$(CWD)" setsoft/kicad_auto_test:latest '/bin/bash'
docker run --rm -v $(CWD):$(CWD) --workdir="$(CWD)" setsoft/kicad_auto_test:latest \
/bin/bash -c "flake8 . --count --statistics ; python3-coverage run -a src/kibot --help-outputs > /dev/null; pytest-3 --log-cli-level debug -k 'test_print_pcb_svg_simple_2' --test_dir=output ; $(PY_COV) html; chown -R $(USER_ID):$(GROUP_ID) output/ tests/board_samples/ tests/.config/kiplot/plugins/__pycache__/ tests/test_plot/fake_pcbnew/__pycache__/ tests/.config/kibot/plugins/__pycache__/ .coverage htmlcov/"
/bin/bash -c "flake8 . --count --statistics ; python3-coverage run -a src/kibot --help-outputs > /dev/null; pytest-3 --log-cli-level debug -k 'test_position_rot_4' --test_dir=output ; $(PY_COV) html; chown -R $(USER_ID):$(GROUP_ID) output/ tests/board_samples/ tests/.config/kiplot/plugins/__pycache__/ tests/test_plot/fake_pcbnew/__pycache__/ tests/.config/kibot/plugins/__pycache__/ .coverage htmlcov/"
#$(PY_COV) report
#x-www-browser htmlcov/index.html
# The coverage used in the image is incompatible

View File

@ -3115,6 +3115,7 @@ In this case you must use the more general syntax:
import:
- file: FILE_CONTAINING_THE_YAML_DEFINITIONS
outputs: LIST_OF_OUTPUTS
preflights: LIST_OF_PREFLIGHTS
filters: LIST_OF_FILTERS
variants: LIST_OF_VARIANTS
global: LIST_OF_GLOBALS

View File

@ -1138,6 +1138,7 @@ In this case you must use the more general syntax:
import:
- file: FILE_CONTAINING_THE_YAML_DEFINITIONS
outputs: LIST_OF_OUTPUTS
preflights: LIST_OF_PREFLIGHTS
filters: LIST_OF_FILTERS
variants: LIST_OF_VARIANTS
global: LIST_OF_GLOBALS

View File

@ -16,7 +16,7 @@ from collections import OrderedDict
from .error import (KiPlotConfigurationError, config_error)
from .misc import (NO_YAML_MODULE, EXIT_BAD_ARGS, EXAMPLE_CFG, WONT_OVERWRITE, W_NOOUTPUTS, W_UNKOUT, W_NOFILTERS,
W_NOVARIANTS, W_NOGLOBALS, TRY_INSTALL_CHECK)
W_NOVARIANTS, W_NOGLOBALS, TRY_INSTALL_CHECK, W_NOPREFLIGHTS)
from .gs import GS
from .registrable import RegOutput, RegVariant, RegFilter, RegDependency
from .pre_base import BasePreFlight
@ -134,7 +134,7 @@ class CfgYamlReader(object):
config_error("`outputs` must be a list")
return outputs
def _parse_variant(self, o_tree, kind, reg_class):
def _parse_variant_or_filter(self, o_tree, kind, reg_class):
kind_f = kind[0].upper()+kind[1:]
try:
name = str(o_tree['name'])
@ -156,17 +156,16 @@ class CfgYamlReader(object):
logger.debug("Parsing "+kind+" "+name_type)
o_var = reg_class.get_class_for(otype)()
o_var.set_tree(o_tree)
try:
o_var.config(None)
except KiPlotConfigurationError as e:
config_error("In section `"+name_type+"`: "+str(e))
o_var.name = name
o_var._name_type = name_type
# Don't configure it yet, wait until we finish loading (could be an import)
return o_var
def _parse_variants(self, v):
variants = {}
if isinstance(v, list):
for o in v:
o_var = self._parse_variant(o, 'variant', RegVariant)
o_var = self._parse_variant_or_filter(o, 'variant', RegVariant)
variants[o_var.name] = o_var
else:
config_error("`variants` must be a list")
@ -176,17 +175,19 @@ class CfgYamlReader(object):
filters = {}
if isinstance(v, list):
for o in v:
o_fil = self._parse_variant(o, 'filter', RegFilter)
o_fil = self._parse_variant_or_filter(o, 'filter', RegFilter)
self.configure_variant_or_filter(o_fil)
filters[o_fil.name] = o_fil
else:
config_error("`filters` must be a list")
return filters
def _parse_preflight(self, pf):
def _parse_preflights(self, pf):
logger.debug("Parsing preflight options: {}".format(pf))
if not isinstance(pf, dict):
config_error("Incorrect `preflight` section")
preflights = []
for k, v in pf.items():
if not BasePreFlight.is_registered(k):
config_error("Unknown preflight: `{}`".format(k))
@ -195,7 +196,8 @@ class CfgYamlReader(object):
o_pre = BasePreFlight.get_class_for(k)(k, v)
except KiPlotConfigurationError as e:
config_error("In preflight '"+k+"': "+str(e))
BasePreFlight.add_preflight(o_pre)
preflights.append(o_pre)
return preflights
def _parse_global(self, gb):
""" Get global options """
@ -258,6 +260,27 @@ class CfgYamlReader(object):
logger.warning(W_NOOUTPUTS+"No outputs found in `{}`".format(fn_rel))
return sel_outs
def _parse_import_preflights(self, pre, explicit_pres, fn_rel, data, imported):
sel_pres = []
if (pre is None or len(pre) > 0) and 'preflight' in data:
i_pres = imported.preflights+self._parse_preflights(data['preflight'])
if pre is not None:
for p in i_pres:
if p._name in pre:
sel_pres.append(p)
pre.remove(p._name)
for p in pre:
logger.warning(W_UNKOUT+"can't import `{}` preflight from `{}` (missing)".format(p, fn_rel))
else:
sel_pres = i_pres
if len(sel_pres) == 0:
logger.warning(W_NOPREFLIGHTS+"No preflights found in `{}`".format(fn_rel))
else:
logger.debug('Preflights loaded from `{}`: {}'.format(fn_rel, list(map(lambda c: c._name, sel_pres))))
if pre is None and explicit_pres and 'preflight' not in data:
logger.warning(W_NOPREFLIGHTS+"No preflights found in `{}`".format(fn_rel))
return sel_pres
def _parse_import_filters(self, filters, explicit_fils, fn_rel, data, imported):
sel_fils = {}
if (filters is None or len(filters) > 0) and 'filters' in data:
@ -324,6 +347,17 @@ class CfgYamlReader(object):
logger.warning(W_NOGLOBALS+"No globals found in `{}`".format(fn_rel))
return sel_globals
def configure_variant_or_filter(self, o_var):
try:
o_var.config(None)
except KiPlotConfigurationError as e:
config_error("In section `"+o_var._name_type+"`: "+str(e))
def configure_variants(self, variants):
logger.debug('Configuring variants')
for o_var in variants.values():
self.configure_variant_or_filter(o_var)
def _parse_import(self, imp, name, apply=True, depth=0):
""" Get imports """
logger.debug("Parsing imports: {}".format(imp))
@ -342,13 +376,15 @@ class CfgYamlReader(object):
filters = []
vars = []
globals = []
pre = []
explicit_outs = True
explicit_fils = False
explicit_vars = False
explicit_globals = False
explicit_pres = False
elif isinstance(entry, dict):
fn = outs = filters = vars = globals = None
explicit_outs = explicit_fils = explicit_vars = explicit_globals = False
fn = outs = filters = vars = globals = pre = None
explicit_outs = explicit_fils = explicit_vars = explicit_globals = explicit_pres = False
for k, v in entry.items():
if k == 'file':
if not isinstance(v, str):
@ -357,6 +393,9 @@ class CfgYamlReader(object):
elif k == 'outputs':
outs = self._parse_import_items(k, fn, v)
explicit_outs = True
elif k == 'preflights':
pre = self._parse_import_items(k, fn, v)
explicit_pres = True
elif k == 'filters':
filters = self._parse_import_items(k, fn, v)
explicit_fils = True
@ -388,6 +427,8 @@ class CfgYamlReader(object):
# Parse and filter all stuff, add them to all_collected
# Outputs
all_collected.outputs.extend(self._parse_import_outputs(outs, explicit_outs, fn_rel, data, imported))
# Preflights
all_collected.preflights.extend(self._parse_import_preflights(pre, explicit_pres, fn_rel, data, imported))
# Filters
all_collected.filters.update(self._parse_import_filters(filters, explicit_fils, fn_rel, data, imported))
# Variants
@ -396,13 +437,15 @@ class CfgYamlReader(object):
all_collected.globals.update(self._parse_import_globals(globals, explicit_globals, fn_rel, data, imported))
if apply:
# This is the main import (not a recursive one) apply the results
RegOutput.add_filters(all_collected.filters)
self.configure_variants(all_collected.variants)
RegOutput.add_variants(all_collected.variants)
self.imported_globals = all_collected.globals
BasePreFlight.add_preflights(all_collected.preflights)
try:
RegOutput.add_outputs(all_collected.outputs, fn_rel)
except KiPlotConfigurationError as e:
config_error(str(e))
RegOutput.add_filters(all_collected.filters)
RegOutput.add_variants(all_collected.variants)
self.imported_globals = all_collected.globals
return all_collected
def load_yaml(self, fstream):
@ -432,14 +475,16 @@ class CfgYamlReader(object):
if k == 'kiplot' or k == 'kibot':
version = self._check_version(v)
elif k == 'preflight':
self._parse_preflight(v)
BasePreFlight.add_preflights(self._parse_preflights(v))
elif k == 'global':
self._parse_global(v)
globals_found = True
elif k == 'import':
self._parse_import(v, fstream.name)
elif k == 'variants':
RegOutput.add_variants(self._parse_variants(v))
variants = self._parse_variants(v)
self.configure_variants(variants)
RegOutput.add_variants(variants)
elif k == 'filters':
RegOutput.add_filters(self._parse_filters(v))
elif k == 'outputs':

View File

@ -222,6 +222,7 @@ W_MISSTOOL = '(W090) '
W_NOTYET = '(W091) '
W_NOMATCH = '(W092) '
W_DOWNTOOL = '(W093) '
W_NOPREFLIGHTS = '(W094) '
# Somehow arbitrary, the colors are real, but can be different
PCB_MAT_COLORS = {'fr1': "937042", 'fr2': "949d70", 'fr3': "adacb4", 'fr4': "332B16", 'fr5': "6cc290"}
PCB_FINISH_COLORS = {'hal': "8b898c", 'hasl': "8b898c", 'imag': "8b898c", 'enig': "cfb96e", 'enepig': "cfb96e",

View File

@ -33,6 +33,11 @@ class BasePreFlight(Registrable):
def add_preflight(o_pre):
BasePreFlight._in_use[o_pre._name] = o_pre
@staticmethod
def add_preflights(pre):
for p in pre:
BasePreFlight._in_use[p._name] = p
@staticmethod
def get_preflight(name):
return BasePreFlight._in_use.get(name)

View File

@ -841,6 +841,25 @@ def test_import_6(test_dir):
ctx.clean_up()
@pytest.mark.skipif(context.ki5(), reason="too slow on KiCad 5")
def test_import_7(test_dir):
""" Import a preflight """
prj = 'test_v5'
ctx = context.TestContext(test_dir, prj, 'import_test_7')
ctx.run(extra=[])
ctx.expect_out_file('test_v5-drc.txt')
ctx.clean_up()
def test_import_8(test_dir):
""" Import a preflight """
prj = 'light_control'
ctx = context.TestContext(test_dir, prj, 'import_test_8')
ctx.run(extra=[])
ctx.expect_out_file('JLCPCB/light_control_cpl_jlc.csv')
ctx.clean_up()
def test_disable_default_1(test_dir):
""" Disable in the same file and out-of-order """
prj = 'test_v5'

View File

@ -0,0 +1,5 @@
kibot:
version: 1
import:
- file: drc.kibot.yaml

View File

@ -0,0 +1,43 @@
# MIT License
# Copyright (c) 2021 Neil Enns
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# This script is called by the .github/workflows/release.yml workflow to generate documentation
# (PDFs, interactive BOMs, board images) and fabrication files for JLCPCB and PCBWay.
kibot:
version: 1
global:
# Sets the default output filename for all generated files. You likely don't have to change this.
output: "%f-%i.%x"
filters:
- name: "exclude_any"
type: "generic"
comment: "Exclude logos, testpoints, fiducials and power"
exclude_any:
- column: Reference
regex: "^(G|#|TP|F).*"
# This imports the KiBot scripts that actually do all the generation. If you want to
# generate different things/more things then change these to point to other .kibot.yaml files.
import:
- file: import_test_8b.kibot.yaml

View File

@ -0,0 +1,183 @@
# MIT License
# Copyright (c) 2021 Neil Enns
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# This KiBot script generates gerbers, drill, bom, and position files for submitting
# two layer boards to JLCPCB.
variants:
- name: rotated
comment: "Just a place holder for the rotation filter"
type: kibom
variant: rotated
pre_transform: fix_rotation
filters:
- name: only_jlc_parts
comment: "Only parts with JLC code"
type: generic
include_only:
- column: "LCSC"
regex: '^C\d+'
- name: fix_rotation
comment: "Adjust rotation for JLC"
type: rot_footprint
negative_bottom: true
# Rotation list from https://github.com/matthewlai/JLCKicadTools/blob/master/jlc_kicad_tools/cpl_rotations_db.csv
rotations:
- ["^R_Array_Convex_", 90]
- ["^R_Array_Concave_", 90]
- ["^SOT-223", 180]
- ["^SOT-23", 180]
- ["^SOT-89", 180]
- ["^TSOT-23", 180]
- ["^SOT-353", 180]
- ["^SOT-363", 180]
- ["^QFN-", 270]
- ["^LQFP-", 270]
- ["^TQFP-", 270]
- ["^SOP-4_", 0]
- ["^SOP-(?!18_)", 270]
- ["^TSSOP-", 270]
- ["^SSOP-", 270]
- ["^DFN-", 270]
- ["^SOIC-", 270]
- ["^SOP-18_", 0]
- ["^VSSOP-10_", 270]
- ["^VSSOP-8_", 270]
- ["^TSOP-6", 270]
- ["^CP_EIA-", 180]
- ["^CP_Elec_8x10.5", 180]
- ["^CP_Elec_6.3x7.7", 180]
- ["^CP_Elec_8x6.7", 180]
- ["^CP_Elec_8x10", 180]
- ["^CP_Elec_10x10", 180]
- ["^(.*?_|V)?QFN-(16|20|24|28|40)(-|_|$)", 270]
- ["^Bosch_LGA-8_2x2.5mm_P0.65mm_ClockwisePinNumbering", 90]
- ["^PowerPAK_SO-8_Single", 270]
- ["^HTSSOP-28-1EP_4.4x9.7mm*", 270]
- ["^PUIAudio_SMT_0825_S_4_R*", 270]
- ["^USB_C_Receptacle_HRO_TYPE-C-31-M-12*", 180]
- ["ESP32-WROOM-32", 270]
- ["^SOIC127P798X216-8N", -90]
- ["^SW_DIP_SPSTx01_Slide_Copal_CHS-01B_W7.62mm_P1.27mm", -180]
- ["^BatteryHolder_Keystone_1060_1x2032", -180]
- ["^SO-14", -90]
- ["^HTSSOP-", 270]
- ["^USB_C_Receptacle_XKB_U262-16XN-4BVC11", 180]
- ["^Relay_DPDT_Omron_G6K-2F-Y", 270]
- ["^RP2040-QFN-56", 270]
outputs:
- name: JLCPCB_gerbers
comment: Gerbers - JLCPCB
type: gerber
dir: JLCPCB
options: &gerber_options
exclude_edge_layer: true
exclude_pads_from_silkscreen: true
plot_sheet_reference: false
plot_footprint_refs: true
plot_footprint_values: false
force_plot_invisible_refs_vals: false
tent_vias: true
use_protel_extensions: false
create_gerber_job_file: false
disable_aperture_macros: true
gerber_precision: 4.6
use_gerber_x2_attributes: false
use_gerber_net_attributes: false
line_width: 0.1
subtract_mask_from_silk: true
layers:
- F.Cu
- B.Cu
- F.SilkS
- B.SilkS
- F.Mask
- B.Mask
- Edge.Cuts
- name: JLCPCB_drill
comment: Drill - JLCPCB
type: excellon
dir: JLCPCB
options:
pth_and_npth_single_file: false
pth_id: "PTH"
npth_id: "NPTH"
metric_units: false
- name: JLCPCB_position
comment: "Pick and place - JLCPCB"
type: position
dir: JLCPCB
options:
variant: rotated
output: "%f_cpl_jlc.%x"
format: CSV
units: millimeters
separate_files_for_front_and_back: false
only_smd: true
columns:
- id: Ref
name: Designator
- Val
- Package
- id: PosX
name: "Mid X"
- id: PosY
name: "Mid Y"
- id: Rot
name: Rotation
- id: Side
name: Layer
- name: JLCPCB_bom
comment: "BOM - JLCPCB"
type: bom
dir: JLCPCB
options:
output: "%f_%i_jlc.%x"
exclude_filter: only_jlc_parts
ref_separator: ","
columns:
- field: Value
name: Comment
- field: References
name: Designator
- Footprint
- field: "LCSC"
name: "LCSC Part #"
csv:
hide_pcb_info: true
hide_stats_info: true
quote_all: true
- name: JLCPCB
comment: "Gerber and drill zip - JLCPCB"
type: compress
dir: JLCPCB
options:
files:
- from_output: JLCPCB_gerbers
dest: /
- from_output: JLCPCB_drill
dest: /