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:
- Some basic preprocessing, now you can parametrize the YAML config.
(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.
(See #261)
- Environment and text variables expansion is now recursive.

View File

@ -640,6 +640,18 @@ global:
global:
* 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?
KiCad 6: you should set this in the Board Setup -> Board Finish -> Has castellated pads.
- *copper_finish*: Alias for pcb_finish.

View File

@ -4,6 +4,7 @@
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
import os
from .error import KiPlotConfigurationError
from .gs import GS
from .optionable import Optionable
from .kicad.config import expand_env
@ -11,6 +12,7 @@ from .macros import macros, document # noqa: F401
from .pre_filters import FiltersOptions
from .log import get_logger, set_filters
from .misc import W_MUSTBEINT
from .kicad.config import KiConf
from .kicad.sexpdata import load, SExpData, sexp_iter, Symbol
from .kicad.v6_sch import PCBLayer
@ -87,11 +89,41 @@ class Environment(Optionable):
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):
""" Global options """
def __init__(self):
super().__init__()
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
""" Has the PCB castelletad 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:
GS.global_silk_screen_color_bottom = GS.global_silk_screen_color
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__)

View File

@ -14,18 +14,19 @@ Notes about coverage:
I'm excluding all the Darwin and Windows code from coverage.
I'm not even sure the values are correct.
"""
import os
import re
import sys
import json
from io import StringIO
import csv
from glob import glob
from shutil import copy2
from io import StringIO
import json
import os
import platform
import re
from shutil import copy2
import sys
import sysconfig
from ..gs import GS
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
if sys.version_info.major >= 3:
@ -58,6 +59,21 @@ def un_quote(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):
""" Expand KiCad environment variables """
if used_extra is None:
@ -131,6 +147,7 @@ class KiConf(object):
footprint_dir = None
kicad_env = {}
lib_aliases = {}
aliases_3D = {}
def __init__(self):
raise AssertionError("KiConf is fully static, no instances allowed")
@ -144,6 +161,7 @@ class KiConf(object):
KiConf.kicad_env['KIPRJMOD'] = KiConf.dirname
KiConf.load_kicad_common()
KiConf.load_all_lib_aliases()
KiConf.load_3d_aliases()
KiConf.loaded = True
def find_kicad_common():
@ -460,6 +478,30 @@ class KiConf(object):
# Load the project's 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):
if key in data:
section = data[key]

View File

@ -226,6 +226,7 @@ W_DOWNTOOL = '(W093) '
W_NOPREFLIGHTS = '(W094) '
W_NOPART = '(W095) '
W_MAXDEPTH = '(W096) '
W_3DRESVER = '(W097) '
# 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

@ -17,12 +17,26 @@ logger = log.get_logger()
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)
if extra_debug:
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 force_used_extra:
used_extra[0] = True
return full_name
# Is it using ALIAS:xxxxx?
ind = fname.index(':')
alias_name = fname[:ind]
rest = fname[ind+1:]
@ -31,6 +45,7 @@ def do_expand_env(fname, used_extra, extra_debug):
if extra_debug:
logger.debug("- Expanded {} -> {}".format(new_fname, new_full_name))
if os.path.isfile(new_full_name):
used_extra[0] = True
return new_full_name
return full_name

View File

@ -93,6 +93,23 @@ def test_step_alias_1(test_dir):
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.kicad2step
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