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:
parent
418b74b2f3
commit
d01943ed87
|
|
@ -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.
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in New Issue