From c39d21f3cf0bce8368f756c34d8efb8ed74fa74f Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Sat, 28 Jan 2023 17:59:23 -0300 Subject: [PATCH] [Global Options] Added field_lcsc_part: to select the LCSC/JLCPCB part field --- CHANGELOG.md | 2 + README.md | 13 +- docs/README.in | 3 +- docs/samples/generic_plot.kibot.yaml | 18 ++- kibot/fil_generic.py | 6 +- kibot/globals.py | 3 + kibot/gs.py | 3 +- kibot/misc.py | 2 + kibot/optionable.py | 63 ++++++++- kibot/out_base.py | 8 +- kibot/out_bom.py | 18 +-- kibot/out_gerber.py | 8 +- kibot/out_kibom.py | 13 +- .../config_templates/JLCPCB.kibot.yaml | 4 +- .../kicad_6/lcsc_field_known.kicad_sch | 131 ++++++++++++++++++ .../kicad_6/lcsc_field_unknown.kicad_sch | 131 ++++++++++++++++++ tests/test_plot/test_misc.py | 31 +++++ .../yaml_samples/lcsc_field_detect.kibot.yaml | 6 + .../lcsc_field_specified.kibot.yaml | 9 ++ 19 files changed, 444 insertions(+), 28 deletions(-) create mode 100644 tests/board_samples/kicad_6/lcsc_field_known.kicad_sch create mode 100644 tests/board_samples/kicad_6/lcsc_field_unknown.kicad_sch create mode 100644 tests/yaml_samples/lcsc_field_detect.kibot.yaml create mode 100644 tests/yaml_samples/lcsc_field_specified.kibot.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d13e362..c800450f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Internal templates import - Better support for wrong pre-flight options (#360) - A mechanism to cache downloaded 3D models +- Global options: + - field_lcsc_part: to select the LCSC/JLCPCB part field - New outputs: - `vrml` export the 3D model in Virtual Reality Modeling Language (#349) - `ps_sch_print`, `dxf_sch_print` and `hpgl_sch_print` variants of diff --git a/README.md b/README.md index b2f3e2f0..bda8a3d5 100644 --- a/README.md +++ b/README.md @@ -790,6 +790,8 @@ global: This is because the plating reduces the hole, so you need to use a bigger drill. For more information consult: https://www.eurocircuits.com/pcb-design-guidelines/drilled-holes/. - `field_3D_model`: [string='_3D_model'] Name for the field controlling the 3D models used for a component. + - `field_lcsc_part`: [string=''] The name of the schematic field that contains the part number for the LCSC/JLCPCB distributor. + When empty KiBot will try to discover it. - `filters`: [list(dict)] KiBot warnings to be ignored. * Valid keys: - `error`: [string=''] Error id we want to exclude. @@ -917,6 +919,7 @@ filters: Column names are case-insensitive. * Valid keys: - `column`: [string=''] Name of the column to apply the regular expression. + Use `_field_lcsc_part` to get the value defined in the global options. - *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`. @@ -942,6 +945,7 @@ filters: If empty this rule is ignored. * Valid keys: - `column`: [string=''] Name of the column to apply the regular expression. + Use `_field_lcsc_part` to get the value defined in the global options. - *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`. @@ -1732,6 +1736,7 @@ Notes: Can be just the name of the field. * Valid keys: - **`field`**: [string=''] Name of the field to use for this column. + Use `_field_lcsc_part` to get the value defined in the global options. - **`name`**: [string=''] Name to display in the header. The field is used when empty. - `comment`: [string=''] Used as explanation for this column. The XLSX output uses it. - `join`: [list(dict)|list(string)|string=''] List of fields to join to this column. @@ -1826,6 +1831,7 @@ Notes: '_power', '_current', '_voltage', '_frequency', '_temp_coeff', '_manf', '_size'. * Valid keys: - **`field`**: [string=''] Name of the field to use for this column. + Use `_field_lcsc_part` to get the value defined in the global options. - **`name`**: [string=''] Name to display in the header. The field is used when empty. - `comment`: [string=''] Used as explanation for this column. The XLSX output uses it. - `join`: [list(dict)|list(string)|string=''] List of fields to join to this column. @@ -1870,6 +1876,7 @@ Notes: Can be just the name of the field. * Valid keys: - **`field`**: [string=''] Name of the field to use for this column. + Use `_field_lcsc_part` to get the value defined in the global options. - **`name`**: [string=''] Name to display in the header. The field is used when empty. - `comment`: [string=''] Used as explanation for this column. The XLSX output uses it. - `join`: [list(dict)|list(string)|string=''] List of fields to join to this column. @@ -2723,6 +2730,7 @@ Notes: Can be just the name of the field. * Valid keys: - **`field`**: [string=''] Name of the field to use for this column. + Use `_field_lcsc_part` to get the value defined in the global options. - **`name`**: [string=''] Name to display in the header. The field is used when empty. - `join`: [list(string)|string=''] List of fields to join to this column. - **`fit_field`**: [string='Config'] Field name used to determine if a particular part is to be fitted (also DNC and variants). @@ -2766,6 +2774,7 @@ Notes: regex: 'fiducial'. * Valid keys: - `column`: [string=''] Name of the column to apply the regular expression. + Use `_field_lcsc_part` to get the value defined in the global options. - *field*: Alias for column. - `regex`: [string=''] Regular expression to match. - *regexp*: Alias for regex. @@ -2779,6 +2788,7 @@ Notes: If empty all the components are included. * Valid keys: - `column`: [string=''] Name of the column to apply the regular expression. + Use `_field_lcsc_part` to get the value defined in the global options. - *field*: Alias for column. - `regex`: [string=''] Regular expression to match. - *regexp*: Alias for regex. @@ -5093,7 +5103,8 @@ They include support for: - _FusionPCB_drill: Drill files - _FusionPCB_compress: Gerbers and drill files compressed in a ZIP - _FusionPCB: _FusionPCB_gerbers+_FusionPCB_drill -- [JLCPCB](https://jlcpcb.com/): contain fabrication outputs compatible with JLC PCB +- [JLCPCB](https://jlcpcb.com/): contain fabrication outputs compatible with JLC PCB. + Use the `field_lcsc_part` global option to specify the LCSC part number field if KiBot fails to detect it. - _JLCPCB_gerbers: Gerbers. - _JLCPCB_drill: Drill files - _JLCPCB_position: Pick and place, applies the `_rot_footprint` filter. You can change this filter. diff --git a/docs/README.in b/docs/README.in index 67627d92..4d8a0d93 100644 --- a/docs/README.in +++ b/docs/README.in @@ -1297,7 +1297,8 @@ They include support for: - _FusionPCB_drill: Drill files - _FusionPCB_compress: Gerbers and drill files compressed in a ZIP - _FusionPCB: _FusionPCB_gerbers+_FusionPCB_drill -- [JLCPCB](https://jlcpcb.com/): contain fabrication outputs compatible with JLC PCB +- [JLCPCB](https://jlcpcb.com/): contain fabrication outputs compatible with JLC PCB. + Use the `field_lcsc_part` global option to specify the LCSC part number field if KiBot fails to detect it. - _JLCPCB_gerbers: Gerbers. - _JLCPCB_drill: Drill files - _JLCPCB_position: Pick and place, applies the `_rot_footprint` filter. You can change this filter. diff --git a/docs/samples/generic_plot.kibot.yaml b/docs/samples/generic_plot.kibot.yaml index b7f491d6..0c78c1fc 100644 --- a/docs/samples/generic_plot.kibot.yaml +++ b/docs/samples/generic_plot.kibot.yaml @@ -295,7 +295,8 @@ outputs: columns: # [string=''] Used as explanation for this column. The XLSX output uses it - comment: '' - # [string=''] Name of the field to use for this column + # [string=''] Name of the field to use for this column. + # Use `_field_lcsc_part` to get the value defined in the global options field: 'Row' # [list(dict)|list(string)|string=''] List of fields to join to this column join: @@ -333,7 +334,8 @@ outputs: cost_extra_columns: # [string=''] Used as explanation for this column. The XLSX output uses it - comment: '' - # [string=''] Name of the field to use for this column + # [string=''] Name of the field to use for this column. + # Use `_field_lcsc_part` to get the value defined in the global options field: 'Row' # [list(dict)|list(string)|string=''] List of fields to join to this column join: @@ -552,7 +554,8 @@ outputs: 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 + # [string=''] Name of the field to use for this column. + # Use `_field_lcsc_part` to get the value defined in the global options field: 'Row' # [list(dict)|list(string)|string=''] List of fields to join to this column join: @@ -1295,7 +1298,8 @@ outputs: # [list(dict)|list(string)] List of columns to display. # Can be just the name of the field columns: - # [string=''] Name of the field to use for this column + # [string=''] Name of the field to use for this column. + # Use `_field_lcsc_part` to get the value defined in the global options - field: 'Row' # [list(string)|string=''] List of fields to join to this column join: '' @@ -1337,7 +1341,8 @@ outputs: # - column: Footprint # regex: 'fiducial' exclude_any: - # [string=''] Name of the column to apply the regular expression + # [string=''] Name of the column to apply the regular expression. + # Use `_field_lcsc_part` to get the value defined in the global options - column: '' # `field` is an alias for `column` # [string=''] Regular expression to match @@ -1365,7 +1370,8 @@ outputs: # Column names are case-insensitive. # If empty all the components are included include_only: - # [string=''] Name of the column to apply the regular expression + # [string=''] Name of the column to apply the regular expression. + # Use `_field_lcsc_part` to get the value defined in the global options - column: '' # `field` is an alias for `column` # [string=''] Regular expression to match diff --git a/kibot/fil_generic.py b/kibot/fil_generic.py index c7f15071..f53839ce 100644 --- a/kibot/fil_generic.py +++ b/kibot/fil_generic.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2020 Salvador E. Tropea -# Copyright (c) 2020 Instituto Nacional de Tecnología Industrial +# Copyright (c) 2020-2023 Salvador E. Tropea +# Copyright (c) 2020-2023 Instituto Nacional de Tecnología Industrial # License: GPL-3.0 # Project: KiBot (formerly KiPlot) # Description: Implements the KiBoM and IBoM filters. @@ -123,6 +123,7 @@ class Generic(BaseFilter): # noqa: F821 if not self.include_only: # Nothing to match against, means include all return True for reg in self.include_only: + reg.column = Optionable.solve_field_name(reg.column) if reg.skip_if_no_field and not c.is_field(reg.column): # Skip the check if the field doesn't exist continue @@ -148,6 +149,7 @@ class Generic(BaseFilter): # noqa: F821 if not self.exclude_any: # Nothing to match against, means don't exclude any return False for reg in self.exclude_any: + reg.column = Optionable.solve_field_name(reg.column) if reg.skip_if_no_field and not c.is_field(reg.column): # Skip the check if the field doesn't exist continue diff --git a/kibot/globals.py b/kibot/globals.py index be55c558..b4819dcf 100644 --- a/kibot/globals.py +++ b/kibot/globals.py @@ -240,6 +240,9 @@ class Globals(FiltersOptions): more precedence than KiCad paths defined in the GUI. You can make reference to any OS environment variable using ${VARIABLE}. The KIPRJMOD is also available for expansion """ + self.field_lcsc_part = '' + """ The name of the schematic field that contains the part number for the LCSC/JLCPCB distributor. + When empty KiBot will try to discover it """ self.set_doc('filters', " [list(dict)] KiBot warnings to be ignored ") self._filter_what = 'KiBot warnings' self.filters = FilterOptionsKiBot diff --git a/kibot/gs.py b/kibot/gs.py index 2818ed90..90bd3d7b 100644 --- a/kibot/gs.py +++ b/kibot/gs.py @@ -119,10 +119,11 @@ class GS(object): global_edge_plating = None global_extra_pth_drill = None global_field_3D_model = None + global_field_lcsc_part = None global_hide_excluded = None + global_impedance_controlled = None global_kiauto_time_out_scale = None global_kiauto_wait_start = None - global_impedance_controlled = None # This value will overwrite GS.def_global_output if defined # Classes supporting global "output" option must call super().__init__() # after defining its own options to allow Optionable do the overwrite. diff --git a/kibot/misc.py b/kibot/misc.py index b1c47962..630d2436 100644 --- a/kibot/misc.py +++ b/kibot/misc.py @@ -128,6 +128,8 @@ DNC = { # KiCost distributors DISTRIBUTORS = ['arrow', 'digikey', 'farnell', 'lcsc', 'mouser', 'newark', 'rs', 'tme'] DISTRIBUTORS_F = [d+'#' for d in DISTRIBUTORS] +DISTRIBUTORS_STUBS = ['part#', '#', 'p#', 'pn', 'vendor#', 'vp#', 'vpn', 'num'] +DISTRIBUTORS_STUBS_SEPS = '_- ' # ISO ISO4217 currency codes # Not all, but the ones we get from the European Central Bank (march 2021) ISO_CURRENCIES = {'EUR', 'USD', 'JPY', 'BGN', 'CZK', 'DKK', 'GBP', 'HUF', 'PLN', 'RON', 'SEK', 'CHF', 'ISK', 'NOK', 'HRK', diff --git a/kibot/optionable.py b/kibot/optionable.py index 3c0b3e8d..b2465056 100644 --- a/kibot/optionable.py +++ b/kibot/optionable.py @@ -11,7 +11,7 @@ import re from re import compile from .error import KiPlotConfigurationError from .gs import GS -from .misc import W_UNKOPS +from .misc import W_UNKOPS, DISTRIBUTORS_STUBS, DISTRIBUTORS_STUBS_SEPS from . import log logger = log.get_logger() @@ -468,6 +468,67 @@ class Optionable(object): def color_str_to_rgb(self, color): return self.color_to_rgb(self.parse_one_color(color)) + @staticmethod + def _solve_field_name(field, empty_when_none): + """ Applies special replacements for field """ + # The global name for the LCSC part field + if GS.global_field_lcsc_part: + logger.debug('- User selected: '+GS.global_field_lcsc_part) + return GS.global_field_lcsc_part + # No name defined, try to figure out + if not GS.sch and GS.sch_file: + GS.load_sch() + if not GS.sch: + logger.debug("- No schematic loaded, can't search the name") + return '' if empty_when_none else 'LCSC#' + if hasattr(GS.sch, '_field_lcsc_part'): + return GS.sch._field_lcsc_part + # Look for a common name + fields = {f.lower() for f in GS.sch.get_field_names([])} + for stub in DISTRIBUTORS_STUBS: + fld = 'lcsc'+stub + if fld in fields: + GS.sch._field_lcsc_part = fld + return fld + if stub != '#': + for sep in DISTRIBUTORS_STUBS_SEPS: + fld = 'lcsc'+sep+stub + if fld in fields: + GS.sch._field_lcsc_part = fld + return fld + if 'lcsc' in fields: + GS.sch._field_lcsc_part = 'LCSC' + return 'LCSC' + # Look for a field that only contains LCSC codes + comps = GS.sch.get_components() + lcsc_re = re.compile(r'C\d+') + for f in fields: + found = False + for c in comps: + val = c.get_field_value(f).strip() + if not val: + continue + if lcsc_re.match(val): + found = True + else: + break + if found: + GS.sch._field_lcsc_part = f + return f + logger.debug('- No LCSC field found') + res = '' if empty_when_none else 'LCSC#' + GS.sch._field_lcsc_part = res + return res + + @staticmethod + def solve_field_name(field, empty_when_none=False): + if field != '_field_lcsc_part': + return field + logger.debug('Looking for LCSC field name') + field = Optionable._solve_field_name(field, empty_when_none) + logger.debug('Using {} as LCSC field name'.format(field)) + return field + class BaseOptions(Optionable): """ A class to validate and hold output options. diff --git a/kibot/out_base.py b/kibot/out_base.py index 9063d12f..0287cbd9 100644 --- a/kibot/out_base.py +++ b/kibot/out_base.py @@ -199,7 +199,8 @@ class BoMRegex(Optionable): self._unkown_is_error = True with document: self.column = '' - """ Name of the column to apply the regular expression """ + """ Name of the column to apply the regular expression. + Use `_field_lcsc_part` to get the value defined in the global options """ self.regex = '' """ Regular expression to match """ self.field = None @@ -215,6 +216,11 @@ class BoMRegex(Optionable): self.invert = False """ Invert the regex match result """ + def config(self, parent): + super().config(parent) + if not self.column: + raise KiPlotConfigurationError("Missing or empty `column` in field regex ({})".format(str(self._tree))) + class VariantOptions(BaseOptions): """ BaseOptions plus generic support for variants. """ diff --git a/kibot/out_bom.py b/kibot/out_bom.py index a22acf1e..fdb17992 100644 --- a/kibot/out_bom.py +++ b/kibot/out_bom.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2020-2022 Salvador E. Tropea -# Copyright (c) 2020-2022 Instituto Nacional de Tecnología Industrial +# Copyright (c) 2020-2023 Salvador E. Tropea +# Copyright (c) 2020-2023 Instituto Nacional de Tecnología Industrial # License: MIT # Project: KiBot (formerly KiPlot) """ @@ -22,7 +22,7 @@ from copy import deepcopy import os import re from .gs import GS -from .misc import W_BADFIELD, W_NEEDSPCB, DISTRIBUTORS, W_NOPART, W_MISSREF +from .misc import W_BADFIELD, W_NEEDSPCB, DISTRIBUTORS, W_NOPART, W_MISSREF, DISTRIBUTORS_STUBS, DISTRIBUTORS_STUBS_SEPS from .optionable import Optionable, BaseOptions from .registrable import RegOutput from .error import KiPlotConfigurationError @@ -137,7 +137,8 @@ class BoMColumns(Optionable): self._unkown_is_error = True with document: self.field = '' - """ *Name of the field to use for this column """ + """ *Name of the field to use for this column. + Use `_field_lcsc_part` to get the value defined in the global options """ self.name = '' """ *Name to display in the header. The field is used when empty """ self.join = BoMJoinField @@ -153,9 +154,10 @@ class BoMColumns(Optionable): super().config(parent) if not self.field: raise KiPlotConfigurationError("Missing or empty `field` in columns list ({})".format(str(self._tree))) + self.field = self.solve_field_name(self.field) + field = self.field.lower() # Ensure this is None or a list # Also arrange it as field, cols... - field = self.field.lower() if isinstance(self.join, type): self.join = None elif isinstance(self.join, str): @@ -1030,12 +1032,12 @@ class BoM(BaseOutput): # noqa: F821 mpn_fields = fld_set.intersection(mpn_set) # Look for distributor part number dpn_set = set() - for stub in ['part#', '#', 'p#', 'pn', 'vendor#', 'vp#', 'vpn', 'num']: + for stub in DISTRIBUTORS_STUBS: for dist in DISTRIBUTORS: dpn_set.add(dist+stub) if stub != '#': - dpn_set.add(dist+'_'+stub) - dpn_set.add(dist+'-'+stub) + for sep in DISTRIBUTORS_STUBS_SEPS: + dpn_set.add(dist+sep+stub) dpn_fields = fld_set.intersection(dpn_set) # Collect the used distributors dists = set() diff --git a/kibot/out_gerber.py b/kibot/out_gerber.py index 78ed0e81..21ef2589 100644 --- a/kibot/out_gerber.py +++ b/kibot/out_gerber.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2020 Salvador E. Tropea -# Copyright (c) 2020 Instituto Nacional de Tecnología Industrial +# Copyright (c) 2020-2023 Salvador E. Tropea +# Copyright (c) 2020-2023 Instituto Nacional de Tecnología Industrial # Copyright (c) 2018 John Beard # License: GPL-3.0 # Project: KiBot (formerly KiPlot) @@ -8,6 +8,7 @@ import os from pcbnew import (PLOT_FORMAT_GERBER, FromMM, ToMM) from .gs import GS +from .optionable import Optionable from .out_any_layer import (AnyLayer, AnyLayerOptions) from .error import KiPlotConfigurationError from .macros import macros, document, output_class # noqa: F401 @@ -122,6 +123,7 @@ class Gerber(AnyLayer): # Filter the list of layers using the ones we are interested on useful = GS.get_useful_layers(USEFUL_LAYERS, layers, include_copper=True) tpl_layers = [AnyLayer.layer2dict(la) for la in useful] + lcsc_field = Optionable.solve_field_name('_field_lcsc_part', empty_when_none=True) # Add the list of layers to the templates for tpl in templates: outs_used = [] @@ -135,7 +137,7 @@ class Gerber(AnyLayer): skip = True out['run_by_default'] = False out['options'] = {'pre_transform': ['_kicost_rename', '_rot_footprint']} - if out['type'] == 'bom' and not GS.sch_file: + if out['type'] == 'bom' and (not GS.sch_file or (out['name'].startswith('JLCPCB') and not lcsc_field)): skip = True out['run_by_default'] = False if out['type'] == 'compress': diff --git a/kibot/out_kibom.py b/kibot/out_kibom.py index e30cf2ec..8588f5a9 100644 --- a/kibot/out_kibom.py +++ b/kibot/out_kibom.py @@ -35,7 +35,8 @@ class KiBoMRegex(Optionable): self._unkown_is_error = True with document: self.column = '' - """ Name of the column to apply the regular expression """ + """ Name of the column to apply the regular expression. + Use `_field_lcsc_part` to get the value defined in the global options """ self.regex = '' """ Regular expression to match """ self.field = None @@ -44,6 +45,12 @@ class KiBoMRegex(Optionable): """ {regex} """ self._category = 'Schematic/BoM' + def config(self, parent): + super().config(parent) + if not self.column: + raise KiPlotConfigurationError("Missing or empty `column` in regex ({})".format(str(self._tree))) + self.column = Optionable.solve_field_name(self.column) + def __str__(self): return self.column+'\t'+self.regex @@ -55,7 +62,8 @@ class KiBoMColumns(Optionable): self._unkown_is_error = True with document: self.field = '' - """ *Name of the field to use for this column """ + """ *Name of the field to use for this column. + Use `_field_lcsc_part` to get the value defined in the global options """ self.name = '' """ *Name to display in the header. The field is used when empty """ self.join = Optionable @@ -67,6 +75,7 @@ class KiBoMColumns(Optionable): super().config(parent) if not self.field: raise KiPlotConfigurationError("Missing or empty `field` in columns list ({})".format(str(self._tree))) + self.column = Optionable.solve_field_name(self.column) if isinstance(self.join, type): self.join = None elif isinstance(self.join, list): diff --git a/kibot/resources/config_templates/JLCPCB.kibot.yaml b/kibot/resources/config_templates/JLCPCB.kibot.yaml index 7feb2047..52e81998 100644 --- a/kibot/resources/config_templates/JLCPCB.kibot.yaml +++ b/kibot/resources/config_templates/JLCPCB.kibot.yaml @@ -9,7 +9,7 @@ filters: comment: 'Only parts with JLC (LCSC) code' type: generic include_only: - - column: 'LCSC#' + - column: _field_lcsc_part regex: '^C\d+' groups: @@ -108,7 +108,7 @@ outputs: - field: References name: Designator - Footprint - - field: 'LCSC#' + - field: _field_lcsc_part name: 'LCSC Part #' csv: hide_pcb_info: true diff --git a/tests/board_samples/kicad_6/lcsc_field_known.kicad_sch b/tests/board_samples/kicad_6/lcsc_field_known.kicad_sch new file mode 100644 index 00000000..5fc09115 --- /dev/null +++ b/tests/board_samples/kicad_6/lcsc_field_known.kicad_sch @@ -0,0 +1,131 @@ +(kicad_sch (version 20211123) (generator eeschema) + + (uuid db0a65e7-fab8-4ee6-8724-c1011468bd27) + + (paper "A4") + + (lib_symbols + (symbol "Device:R" (pin_numbers hide) (pin_names (offset 0)) (in_bom yes) (on_board yes) + (property "Reference" "R" (id 0) (at 2.032 0 90) + (effects (font (size 1.27 1.27))) + ) + (property "Value" "R" (id 1) (at 0 0 90) + (effects (font (size 1.27 1.27))) + ) + (property "Footprint" "" (id 2) (at -1.778 0 90) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Datasheet" "~" (id 3) (at 0 0 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "ki_keywords" "R res resistor" (id 4) (at 0 0 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "ki_description" "Resistor" (id 5) (at 0 0 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "ki_fp_filters" "R_*" (id 6) (at 0 0 0) + (effects (font (size 1.27 1.27)) hide) + ) + (symbol "R_0_1" + (rectangle (start -1.016 -2.54) (end 1.016 2.54) + (stroke (width 0.254) (type default) (color 0 0 0 0)) + (fill (type none)) + ) + ) + (symbol "R_1_1" + (pin passive line (at 0 3.81 270) (length 1.27) + (name "~" (effects (font (size 1.27 1.27)))) + (number "1" (effects (font (size 1.27 1.27)))) + ) + (pin passive line (at 0 -3.81 90) (length 1.27) + (name "~" (effects (font (size 1.27 1.27)))) + (number "2" (effects (font (size 1.27 1.27)))) + ) + ) + ) + ) + + + (symbol (lib_id "Device:R") (at 114.3 54.61 0) (unit 1) + (in_bom yes) (on_board yes) (fields_autoplaced) + (uuid 3ba877ba-6c15-4bf9-911a-bede4b8103e2) + (property "Reference" "R2" (id 0) (at 116.078 53.7753 0) + (effects (font (size 1.27 1.27)) (justify left)) + ) + (property "Value" "R" (id 1) (at 116.078 56.3122 0) + (effects (font (size 1.27 1.27)) (justify left)) + ) + (property "Footprint" "" (id 2) (at 112.522 54.61 90) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Datasheet" "~" (id 3) (at 114.3 54.61 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "LCSC_pn" "C1234" (id 4) (at 127 54.61 0) + (effects (font (size 1.27 1.27)) hide) + ) + (pin "1" (uuid 39c3be48-48be-4598-a62d-ee41b899e213)) + (pin "2" (uuid 1b30cb63-49b1-4ba7-b097-7ae72754941f)) + ) + + (symbol (lib_id "Device:R") (at 127 54.61 0) (unit 1) + (in_bom yes) (on_board yes) (fields_autoplaced) + (uuid 615091a6-0d61-4a51-9c02-509e158ea9d6) + (property "Reference" "R3" (id 0) (at 128.778 53.7753 0) + (effects (font (size 1.27 1.27)) (justify left)) + ) + (property "Value" "R" (id 1) (at 128.778 56.3122 0) + (effects (font (size 1.27 1.27)) (justify left)) + ) + (property "Footprint" "" (id 2) (at 125.222 54.61 90) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Datasheet" "~" (id 3) (at 127 54.61 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "LCSC_pn" "C1234" (id 4) (at 127 54.61 0) + (effects (font (size 1.27 1.27)) hide) + ) + (pin "1" (uuid 20f2f92a-ae98-4133-ba84-f5d7a9c977ae)) + (pin "2" (uuid e088d214-94d0-44a2-a744-aa518c868708)) + ) + + (symbol (lib_id "Device:R") (at 101.6 54.61 0) (unit 1) + (in_bom yes) (on_board yes) (fields_autoplaced) + (uuid d90f5601-6146-47ff-a6ef-b6f3da310882) + (property "Reference" "R1" (id 0) (at 103.378 53.7753 0) + (effects (font (size 1.27 1.27)) (justify left)) + ) + (property "Value" "R" (id 1) (at 103.378 56.3122 0) + (effects (font (size 1.27 1.27)) (justify left)) + ) + (property "Footprint" "" (id 2) (at 99.822 54.61 90) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Datasheet" "~" (id 3) (at 101.6 54.61 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "LCSC_pn" "C1234" (id 4) (at 127 54.61 0) + (effects (font (size 1.27 1.27)) hide) + ) + (pin "1" (uuid 672f8777-9560-4f26-bcf9-d47c285b54e6)) + (pin "2" (uuid b9f1a732-28ab-496c-93f7-3f99c9c56900)) + ) + + (sheet_instances + (path "/" (page "1")) + ) + + (symbol_instances + (path "/d90f5601-6146-47ff-a6ef-b6f3da310882" + (reference "R1") (unit 1) (value "R") (footprint "") + ) + (path "/3ba877ba-6c15-4bf9-911a-bede4b8103e2" + (reference "R2") (unit 1) (value "R") (footprint "") + ) + (path "/615091a6-0d61-4a51-9c02-509e158ea9d6" + (reference "R3") (unit 1) (value "R") (footprint "") + ) + ) +) diff --git a/tests/board_samples/kicad_6/lcsc_field_unknown.kicad_sch b/tests/board_samples/kicad_6/lcsc_field_unknown.kicad_sch new file mode 100644 index 00000000..b3a5a055 --- /dev/null +++ b/tests/board_samples/kicad_6/lcsc_field_unknown.kicad_sch @@ -0,0 +1,131 @@ +(kicad_sch (version 20211123) (generator eeschema) + + (uuid db0a65e7-fab8-4ee6-8724-c1011468bd27) + + (paper "A4") + + (lib_symbols + (symbol "Device:R" (pin_numbers hide) (pin_names (offset 0)) (in_bom yes) (on_board yes) + (property "Reference" "R" (id 0) (at 2.032 0 90) + (effects (font (size 1.27 1.27))) + ) + (property "Value" "R" (id 1) (at 0 0 90) + (effects (font (size 1.27 1.27))) + ) + (property "Footprint" "" (id 2) (at -1.778 0 90) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Datasheet" "~" (id 3) (at 0 0 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "ki_keywords" "R res resistor" (id 4) (at 0 0 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "ki_description" "Resistor" (id 5) (at 0 0 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "ki_fp_filters" "R_*" (id 6) (at 0 0 0) + (effects (font (size 1.27 1.27)) hide) + ) + (symbol "R_0_1" + (rectangle (start -1.016 -2.54) (end 1.016 2.54) + (stroke (width 0.254) (type default) (color 0 0 0 0)) + (fill (type none)) + ) + ) + (symbol "R_1_1" + (pin passive line (at 0 3.81 270) (length 1.27) + (name "~" (effects (font (size 1.27 1.27)))) + (number "1" (effects (font (size 1.27 1.27)))) + ) + (pin passive line (at 0 -3.81 90) (length 1.27) + (name "~" (effects (font (size 1.27 1.27)))) + (number "2" (effects (font (size 1.27 1.27)))) + ) + ) + ) + ) + + + (symbol (lib_id "Device:R") (at 114.3 54.61 0) (unit 1) + (in_bom yes) (on_board yes) (fields_autoplaced) + (uuid 3ba877ba-6c15-4bf9-911a-bede4b8103e2) + (property "Reference" "R2" (id 0) (at 116.078 53.7753 0) + (effects (font (size 1.27 1.27)) (justify left)) + ) + (property "Value" "R" (id 1) (at 116.078 56.3122 0) + (effects (font (size 1.27 1.27)) (justify left)) + ) + (property "Footprint" "" (id 2) (at 112.522 54.61 90) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Datasheet" "~" (id 3) (at 114.3 54.61 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Cryptic" "C1234" (id 4) (at 127 54.61 0) + (effects (font (size 1.27 1.27)) hide) + ) + (pin "1" (uuid 39c3be48-48be-4598-a62d-ee41b899e213)) + (pin "2" (uuid 1b30cb63-49b1-4ba7-b097-7ae72754941f)) + ) + + (symbol (lib_id "Device:R") (at 127 54.61 0) (unit 1) + (in_bom yes) (on_board yes) (fields_autoplaced) + (uuid 615091a6-0d61-4a51-9c02-509e158ea9d6) + (property "Reference" "R3" (id 0) (at 128.778 53.7753 0) + (effects (font (size 1.27 1.27)) (justify left)) + ) + (property "Value" "R" (id 1) (at 128.778 56.3122 0) + (effects (font (size 1.27 1.27)) (justify left)) + ) + (property "Footprint" "" (id 2) (at 125.222 54.61 90) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Datasheet" "~" (id 3) (at 127 54.61 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Cryptic" "C1234" (id 4) (at 127 54.61 0) + (effects (font (size 1.27 1.27)) hide) + ) + (pin "1" (uuid 20f2f92a-ae98-4133-ba84-f5d7a9c977ae)) + (pin "2" (uuid e088d214-94d0-44a2-a744-aa518c868708)) + ) + + (symbol (lib_id "Device:R") (at 101.6 54.61 0) (unit 1) + (in_bom yes) (on_board yes) (fields_autoplaced) + (uuid d90f5601-6146-47ff-a6ef-b6f3da310882) + (property "Reference" "R1" (id 0) (at 103.378 53.7753 0) + (effects (font (size 1.27 1.27)) (justify left)) + ) + (property "Value" "R" (id 1) (at 103.378 56.3122 0) + (effects (font (size 1.27 1.27)) (justify left)) + ) + (property "Footprint" "" (id 2) (at 99.822 54.61 90) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Datasheet" "~" (id 3) (at 101.6 54.61 0) + (effects (font (size 1.27 1.27)) hide) + ) + (property "Cryptic" "C1234" (id 4) (at 127 54.61 0) + (effects (font (size 1.27 1.27)) hide) + ) + (pin "1" (uuid 672f8777-9560-4f26-bcf9-d47c285b54e6)) + (pin "2" (uuid b9f1a732-28ab-496c-93f7-3f99c9c56900)) + ) + + (sheet_instances + (path "/" (page "1")) + ) + + (symbol_instances + (path "/d90f5601-6146-47ff-a6ef-b6f3da310882" + (reference "R1") (unit 1) (value "R") (footprint "") + ) + (path "/3ba877ba-6c15-4bf9-911a-bede4b8103e2" + (reference "R2") (unit 1) (value "R") (footprint "") + ) + (path "/615091a6-0d61-4a51-9c02-509e158ea9d6" + (reference "R3") (unit 1) (value "R") (footprint "") + ) + ) +) diff --git a/tests/test_plot/test_misc.py b/tests/test_plot/test_misc.py index a98d64e3..6573a60b 100644 --- a/tests/test_plot/test_misc.py +++ b/tests/test_plot/test_misc.py @@ -1559,3 +1559,34 @@ def test_sub_pcb_bp(test_dir): ctx.expect_out_file(fname_b+'charger.kicad_pcb') ctx.expect_out_file(fname_b+'connector.kicad_pcb') ctx.clean_up(keep_project=True) + + +@pytest.mark.skipif(context.ki5(), reason="Needs porting") +def test_lcsc_field_known(test_dir): + """ Test we can detect a known LCSC field name """ + prj = 'lcsc_field_known' + ctx = context.TestContextSCH(test_dir, prj, 'lcsc_field_detect', 'JLCPCB') + ctx.run(extra=['_JLCPCB_bom']) + r, _, _ = ctx.load_csv(prj+'_bom_jlc.csv') + assert r[0][3] == 'C1234' + + +@pytest.mark.skipif(context.ki5(), reason="Needs porting") +def test_lcsc_field_unknown(test_dir): + """ Test we can detect an unknown LCSC field name """ + prj = 'lcsc_field_unknown' + ctx = context.TestContextSCH(test_dir, prj, 'lcsc_field_detect', 'JLCPCB') + ctx.run(extra=['_JLCPCB_bom']) + r, _, _ = ctx.load_csv(prj+'_bom_jlc.csv') + assert r[0][3] == 'C1234' + + +@pytest.mark.skipif(context.ki5(), reason="Needs porting") +def test_lcsc_field_specified(test_dir): + """ Test we select the field """ + prj = 'lcsc_field_unknown' + ctx = context.TestContextSCH(test_dir, prj, 'lcsc_field_specified', 'JLCPCB') + ctx.run(extra=['_JLCPCB_bom']) + assert ctx.search_err('User selected.*Cryptic') + r, _, _ = ctx.load_csv(prj+'_bom_jlc.csv') + assert r[0][3] == 'C1234' diff --git a/tests/yaml_samples/lcsc_field_detect.kibot.yaml b/tests/yaml_samples/lcsc_field_detect.kibot.yaml new file mode 100644 index 00000000..8af945e9 --- /dev/null +++ b/tests/yaml_samples/lcsc_field_detect.kibot.yaml @@ -0,0 +1,6 @@ +# Example KiBot config file +kibot: + version: 1 + +import: + - file: JLCPCB diff --git a/tests/yaml_samples/lcsc_field_specified.kibot.yaml b/tests/yaml_samples/lcsc_field_specified.kibot.yaml new file mode 100644 index 00000000..c3dc24e9 --- /dev/null +++ b/tests/yaml_samples/lcsc_field_specified.kibot.yaml @@ -0,0 +1,9 @@ +# Example KiBot config file +kibot: + version: 1 + +global: + field_lcsc_part: Cryptic + +import: + - file: JLCPCB