Added support for 3D models aliases

- Also a global option to define them in the KiBot configuration

Related to #261
This commit is contained in:
Salvador E. Tropea 2022-09-14 09:38:47 -03:00
parent 418b74b2f3
commit d01943ed87
8 changed files with 157 additions and 8 deletions

View File

@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- General things: - General things:
- Some basic preprocessing, now you can parametrize the YAML config. - Some basic preprocessing, now you can parametrize the YAML config.
(See #233 #243) (See #233 #243)
- Support for 3D models aliases and also a global option to define
them in the KiBot configuration (See #261)
- Environment and text variables now can be used as 3D model aliases. - Environment and text variables now can be used as 3D model aliases.
(See #261) (See #261)
- Environment and text variables expansion is now recursive. - Environment and text variables expansion is now recursive.

View File

@ -640,6 +640,18 @@ global:
global: global:
* Valid keys: * Valid keys:
- `aliases_for_3d_models`: [list(dict)] List of aliases for the 3D models (KiCad 6).
KiCad stores 3D aliases with the user settings, not locally.
This makes impossible to create self contained projects.
You can define aliases here to workaround this problem.
The values defined here has precedence over the KiCad configuration.
Related to https://gitlab.com/kicad/code/kicad/-/issues/3792.
* Valid keys:
- *alias*: Alias for name.
- `name`: [string=''] Name of the alias.
- *text*: Alias for value.
- `value`: [string=''] Path to the 3D model.
- *variable*: Alias for name.
- `castellated_pads`: [boolean=false] Has the PCB castelletad pads? - `castellated_pads`: [boolean=false] Has the PCB castelletad pads?
KiCad 6: you should set this in the Board Setup -> Board Finish -> Has castellated pads. KiCad 6: you should set this in the Board Setup -> Board Finish -> Has castellated pads.
- *copper_finish*: Alias for pcb_finish. - *copper_finish*: Alias for pcb_finish.

View File

@ -4,6 +4,7 @@
# License: GPL-3.0 # License: GPL-3.0
# Project: KiBot (formerly KiPlot) # Project: KiBot (formerly KiPlot)
import os import os
from .error import KiPlotConfigurationError
from .gs import GS from .gs import GS
from .optionable import Optionable from .optionable import Optionable
from .kicad.config import expand_env from .kicad.config import expand_env
@ -11,6 +12,7 @@ from .macros import macros, document # noqa: F401
from .pre_filters import FiltersOptions from .pre_filters import FiltersOptions
from .log import get_logger, set_filters from .log import get_logger, set_filters
from .misc import W_MUSTBEINT from .misc import W_MUSTBEINT
from .kicad.config import KiConf
from .kicad.sexpdata import load, SExpData, sexp_iter, Symbol from .kicad.sexpdata import load, SExpData, sexp_iter, Symbol
from .kicad.v6_sch import PCBLayer from .kicad.v6_sch import PCBLayer
@ -87,11 +89,41 @@ class Environment(Optionable):
os.environ[n] = v os.environ[n] = v
class KiCadAlias(Optionable):
""" KiCad alias (for 3D models) """
def __init__(self):
super().__init__()
self._unkown_is_error = True
with document:
self.name = ''
""" Name of the alias """
self.variable = None
""" {name} """
self.alias = None
""" {name} """
self.value = ''
""" Path to the 3D model """
self.text = None
""" {value} """
def config(self, parent):
super().config(parent)
if not self.name:
raise KiPlotConfigurationError("Missing variable name ({})".format(str(self._tree)))
class Globals(FiltersOptions): class Globals(FiltersOptions):
""" Global options """ """ Global options """
def __init__(self): def __init__(self):
super().__init__() super().__init__()
with document: with document:
self.aliases_for_3d_models = KiCadAlias
""" [list(dict)] List of aliases for the 3D models (KiCad 6).
KiCad stores 3D aliases with the user settings, not locally.
This makes impossible to create self contained projects.
You can define aliases here to workaround this problem.
The values defined here has precedence over the KiCad configuration.
Related to https://gitlab.com/kicad/code/kicad/-/issues/3792 """
self.castellated_pads = False self.castellated_pads = False
""" Has the PCB castelletad pads? """ Has the PCB castelletad pads?
KiCad 6: you should set this in the Board Setup -> Board Finish -> Has castellated pads """ KiCad 6: you should set this in the Board Setup -> Board Finish -> Has castellated pads """
@ -337,6 +369,14 @@ class Globals(FiltersOptions):
if not GS.global_silk_screen_color_bottom: if not GS.global_silk_screen_color_bottom:
GS.global_silk_screen_color_bottom = GS.global_silk_screen_color GS.global_silk_screen_color_bottom = GS.global_silk_screen_color
set_filters(self.unparsed) set_filters(self.unparsed)
# 3D models aliases
if isinstance(self.aliases_for_3d_models, list):
KiConf.init(GS.pcb_file or GS.sch_file)
logger.debug('Adding 3D models aliases from global config')
for alias in self.aliases_for_3d_models:
KiConf.aliases_3D[alias.name] = alias.value
logger.debugl(1, '- {}={}'.format(alias.name, alias.value))
logger.debugl(1, 'Finished adding aliases')
logger = get_logger(__name__) logger = get_logger(__name__)

View File

@ -14,18 +14,19 @@ Notes about coverage:
I'm excluding all the Darwin and Windows code from coverage. I'm excluding all the Darwin and Windows code from coverage.
I'm not even sure the values are correct. I'm not even sure the values are correct.
""" """
import os import csv
import re
import sys
import json
from io import StringIO
from glob import glob from glob import glob
from shutil import copy2 from io import StringIO
import json
import os
import platform import platform
import re
from shutil import copy2
import sys
import sysconfig import sysconfig
from ..gs import GS from ..gs import GS
from .. import log from .. import log
from ..misc import W_NOCONFIG, W_NOKIENV, W_NOLIBS, W_NODEFSYMLIB, MISSING_WKS, W_MAXDEPTH from ..misc import W_NOCONFIG, W_NOKIENV, W_NOLIBS, W_NODEFSYMLIB, MISSING_WKS, W_MAXDEPTH, W_3DRESVER
# Check python version to determine which version of ConfirParser to import # Check python version to determine which version of ConfirParser to import
if sys.version_info.major >= 3: if sys.version_info.major >= 3:
@ -58,6 +59,21 @@ def un_quote(val):
return val return val
def parse_len_str(val):
if ':' not in val:
return val
pos = val.index(':')
try:
c = int(val[:pos])
except ValueError:
c = None
logger.error('Malformed 3D alias entry: '+val)
value = val[pos+1:]
if c is not None and c != len(value):
logger.error('3D alias entry error, expected len {}, but found {}'.format(c, len(value)))
return value
def expand_env(val, env, extra_env, used_extra=None): def expand_env(val, env, extra_env, used_extra=None):
""" Expand KiCad environment variables """ """ Expand KiCad environment variables """
if used_extra is None: if used_extra is None:
@ -131,6 +147,7 @@ class KiConf(object):
footprint_dir = None footprint_dir = None
kicad_env = {} kicad_env = {}
lib_aliases = {} lib_aliases = {}
aliases_3D = {}
def __init__(self): def __init__(self):
raise AssertionError("KiConf is fully static, no instances allowed") raise AssertionError("KiConf is fully static, no instances allowed")
@ -144,6 +161,7 @@ class KiConf(object):
KiConf.kicad_env['KIPRJMOD'] = KiConf.dirname KiConf.kicad_env['KIPRJMOD'] = KiConf.dirname
KiConf.load_kicad_common() KiConf.load_kicad_common()
KiConf.load_all_lib_aliases() KiConf.load_all_lib_aliases()
KiConf.load_3d_aliases()
KiConf.loaded = True KiConf.loaded = True
def find_kicad_common(): def find_kicad_common():
@ -460,6 +478,30 @@ class KiConf(object):
# Load the project's table # Load the project's table
KiConf.load_lib_aliases(os.path.join(KiConf.dirname, SYM_LIB_TABLE)) KiConf.load_lib_aliases(os.path.join(KiConf.dirname, SYM_LIB_TABLE))
def load_3d_aliases():
if not KiConf.config_dir:
return
fname = os.path.join(KiConf.config_dir, '3d', '3Dresolver.cfg')
if not os.path.isfile(fname):
logger.debug('No 3D aliases ({})'.format(fname))
return
logger.debug('Loading 3D aliases from '+fname)
with open(fname) as f:
reader = csv.reader(f)
head = next(reader)
if len(head) != 1 or head[0] != '#V1':
logger.warning(W_3DRESVER, 'Unsupported 3D resolver version ({})'.format(head))
for r in reader:
if len(r) != 3:
logger.error("3D resolver doesn't contain three values ({})".format(r))
continue
name = parse_len_str(r[0])
value = parse_len_str(r[1])
# Discard the comment (2)
logger.debugl(1, '- {}={}'.format(name, value))
KiConf.aliases_3D[name] = value
logger.debugl(1, 'Finished loading 3D aliases')
def fix_page_layout_k6_key(key, data, dest_dir): def fix_page_layout_k6_key(key, data, dest_dir):
if key in data: if key in data:
section = data[key] section = data[key]

View File

@ -226,6 +226,7 @@ W_DOWNTOOL = '(W093) '
W_NOPREFLIGHTS = '(W094) ' W_NOPREFLIGHTS = '(W094) '
W_NOPART = '(W095) ' W_NOPART = '(W095) '
W_MAXDEPTH = '(W096) ' W_MAXDEPTH = '(W096) '
W_3DRESVER = '(W097) '
# Somehow arbitrary, the colors are real, but can be different # Somehow arbitrary, the colors are real, but can be different
PCB_MAT_COLORS = {'fr1': "937042", 'fr2': "949d70", 'fr3': "adacb4", 'fr4': "332B16", 'fr5': "6cc290"} 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", PCB_FINISH_COLORS = {'hal': "8b898c", 'hasl': "8b898c", 'imag': "8b898c", 'enig': "cfb96e", 'enepig': "cfb96e",

View File

@ -17,12 +17,26 @@ logger = log.get_logger()
def do_expand_env(fname, used_extra, extra_debug): def do_expand_env(fname, used_extra, extra_debug):
# Is it using ALIAS:xxxxx?
force_used_extra = False
if ':' in fname:
ind = fname.index(':')
alias_name = fname[:ind]
rest = fname[ind+1:]
if alias_name in KiConf.aliases_3D:
# Yes, replace the alias
fname = os.path.join(KiConf.aliases_3D[alias_name], rest)
# Make sure the name we created is what kicad2step gets
force_used_extra = True
if extra_debug:
logger.debug("- Replaced alias {} -> {}".format(alias_name+':'+rest, fname))
full_name = KiConf.expand_env(fname, used_extra) full_name = KiConf.expand_env(fname, used_extra)
if extra_debug: if extra_debug:
logger.debug("- Expanded {} -> {}".format(fname, full_name)) logger.debug("- Expanded {} -> {}".format(fname, full_name))
if os.path.isfile(full_name) or ':' not in fname or GS.global_disable_3d_alias_as_env: if os.path.isfile(full_name) or ':' not in fname or GS.global_disable_3d_alias_as_env:
if force_used_extra:
used_extra[0] = True
return full_name return full_name
# Is it using ALIAS:xxxxx?
ind = fname.index(':') ind = fname.index(':')
alias_name = fname[:ind] alias_name = fname[:ind]
rest = fname[ind+1:] rest = fname[ind+1:]
@ -31,6 +45,7 @@ def do_expand_env(fname, used_extra, extra_debug):
if extra_debug: if extra_debug:
logger.debug("- Expanded {} -> {}".format(new_fname, new_full_name)) logger.debug("- Expanded {} -> {}".format(new_fname, new_full_name))
if os.path.isfile(new_full_name): if os.path.isfile(new_full_name):
used_extra[0] = True
return new_full_name return new_full_name
return full_name return full_name

View File

@ -93,6 +93,23 @@ def test_step_alias_1(test_dir):
ctx.clean_up(keep_project=True) ctx.clean_up(keep_project=True)
@pytest.mark.slow
@pytest.mark.kicad2step
@pytest.mark.skipif(context.ki5(), reason="uses 3D aliases")
def test_step_alias_2(test_dir):
prj = 'bom_w_prj'
ctx = context.TestContext(test_dir, prj, 'step_alias_2', STEP_DIR)
ctx.run(extra_debug=True)
# Check all outputs are there
name = prj+'-3D.step'
ctx.expect_out_file_d(name)
# Check the R and C 3D models are there
ctx.search_in_file_d(name, ['R_0805_2012Metric', 'R_0805_2012Metrico', 'C_0805_2012Metric'])
ctx.search_err(['Missing 3D model for R1: `(.*)R_0805_2012Metrico',
'Failed to download `(.*)R_0805_2012Metrico'], invert=True)
ctx.clean_up(keep_project=True)
@pytest.mark.slow @pytest.mark.slow
@pytest.mark.kicad2step @pytest.mark.kicad2step
def test_step_variant_1(test_dir): def test_step_variant_1(test_dir):

View File

@ -0,0 +1,20 @@
kiplot:
version: 1
global:
disable_3d_alias_as_env: true
aliases_for_3d_models:
- variable: 'ALIAS1'
text: '${KIPRJMOD}/../../data/metrico/Resistor_SMD.3dshapes'
outputs:
- name: Step
comment: "Generate 3D model (STEP)"
type: step
dir: 3D
options:
metric_units: true
origin: drill # "grid" or "drill" o "X,Y" i.e. 3.2,-10
#no_virtual: false # exclude 3D models for components with 'virtual' attribute
#min_distance: 0.01 # Minimum distance between points to treat them as separate ones (default 0.01 mm)
#output: project.step