• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

INTI-CMNB / KiAuto / 6981974383

24 Nov 2023 02:31PM UTC coverage: 88.592%. First build
6981974383

push

github

set-soft
Forced test

3021 of 3410 relevant lines covered (88.59%)

3.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.5
/kiauto/misc.py
1
# -*- coding: utf-8 -*-
2
# Copyright (c) 2020-2023 Salvador E. Tropea
3
# Copyright (c) 2020-2023 Instituto Nacional de Tecnologïa Industrial
4
# License: Apache 2.0
5
# Project: KiAuto (formerly kicad-automation-scripts)
6
import configparser
6✔
7
from contextlib import contextmanager
6✔
8
import json
6✔
9
import os
6✔
10
import re
6✔
11
from subprocess import check_output, DEVNULL
6✔
12
from sys import exit, path
6✔
13

14
# Default W,H for recording
15
REC_W = 1366
6✔
16
REC_H = 960
6✔
17

18
# Return error codes
19
# Positive values are ERC/DRC errors
20
NO_SCHEMATIC = 1
6✔
21
WRONG_ARGUMENTS = 2   # This is what argsparse uses
6✔
22
EESCHEMA_CFG_PRESENT = 11
6✔
23
KICAD_CFG_PRESENT = 3
6✔
24
NO_PCB = 4
6✔
25
PCBNEW_CFG_PRESENT = 5
6✔
26
WRONG_LAYER_NAME = 6
6✔
27
WRONG_PCB_NAME = 7
6✔
28
WRONG_SCH_NAME = 8
6✔
29
PCBNEW_ERROR = 9
6✔
30
EESCHEMA_ERROR = 10
6✔
31
NO_PCBNEW_MODULE = 11
6✔
32
USER_HOTKEYS_PRESENT = 12
6✔
33
CORRUPTED_PCB = 13
6✔
34
NO_EN_LOCALE = 14
6✔
35
MISSING_TOOL = 15
6✔
36
KICAD_DIED = 16
6✔
37
READ_ONLY_PROBLEM = 17
6✔
38
WONT_OVERWRITE = 18
6✔
39
KICAD_CLI_ERROR = 19
6✔
40
# Wait 60 s to pcbnew/eeschema window to be present
41
WAIT_START = 60
6✔
42
# Name for testing versions
43
NIGHTLY = 'nightly'
6✔
44
# Scale factor for the timeouts
45
TIME_OUT_MULT = 1.0
6✔
46

47
KICAD_VERSION_5_99 = 5099000
6✔
48
KICAD_VERSION_6_99 = 6099000
6✔
49
KICAD_VERSION_7_99 = 7099000
6✔
50
KICAD_VERSION_7_0_3 = 7000003
6✔
51
KICAD_VERSION_7_0_8 = 7000008
6✔
52
KICAD_SHARE = '/usr/share/kicad/'
6✔
53
KICAD_NIGHTLY_SHARE = '/usr/share/kicad-nightly/'
6✔
54
RULES_KEY = 'Ctrl+Shift+A'
6✔
55
# RULES_KEY = 'Alt+I'
56

57

58
@contextmanager
6✔
59
def hide_stderr():
6✔
60
    """ Low level stderr supression, used to hide KiCad bugs. """
61
    newstderr = os.dup(2)
2✔
62
    devnull = os.open('/dev/null', os.O_WRONLY)
2✔
63
    os.dup2(devnull, 2)
2✔
64
    os.close(devnull)
2✔
65
    yield
2✔
66
    os.dup2(newstderr, 2)
2✔
67

68

69
class Config(object):
6✔
70
    def __init__(self, logger, input_file=None, args=None, is_pcbnew=False):
6✔
71
        logger.debug(f'KiAuto v{__version__}')
6✔
72
        self.export_format = 'pdf'
6✔
73
        self.is_pcbnew = is_pcbnew
6✔
74
        if input_file:
6✔
75
            self.input_file = input_file
6✔
76
            self.input_no_ext = os.path.splitext(input_file)[0]
6✔
77
            #
78
            # As soon as we init pcbnew the following files are modified:
79
            #
80
            if os.path.isfile(self.input_no_ext+'.pro'):
6✔
81
                self.start_pro_stat = os.stat(self.input_no_ext+'.pro')
2✔
82
            else:
83
                self.start_pro_stat = None
6✔
84
            if os.path.isfile(self.input_no_ext+'.kicad_pro'):
6✔
85
                self.start_kicad_pro_stat = os.stat(self.input_no_ext+'.kicad_pro')
4✔
86
            else:
87
                self.start_kicad_pro_stat = None
6✔
88
            if os.path.isfile(self.input_no_ext+'.kicad_prl'):
6✔
89
                self.start_kicad_prl_stat = os.stat(self.input_no_ext+'.kicad_prl')
4✔
90
            else:
91
                self.start_kicad_prl_stat = None
6✔
92
        if args:
6✔
93
            # Session debug
94
            self.use_wm = args.use_wm  # Use a Window Manager, dialogs behaves in a different way
6✔
95
            self.start_x11vnc = args.start_x11vnc
6✔
96
            self.rec_width = args.rec_width
6✔
97
            self.rec_height = args.rec_height
6✔
98
            self.record = args.record
6✔
99
            self.video_dir = args.output_dir
6✔
100
            self.wait_for_key = args.wait_key
6✔
101
            self.time_out_scale = args.time_out_scale
6✔
102
            # Others
103
            if hasattr(args, 'file_format'):
6✔
104
                self.export_format = args.file_format.lower()
6✔
105
        else:
106
            # Session debug
107
            self.use_wm = False
×
108
            self.start_x11vnc = False
×
109
            self.rec_width = REC_W
×
110
            self.rec_height = REC_H
×
111
            self.record = False
×
112
            self.video_dir = None
×
113
            self.wait_for_key = False
×
114
            self.time_out_scale = 1.0
×
115
        self.colordepth = 24
6✔
116
        self.video_name = None
6✔
117
        self.video_dir = self.output_dir = ''
6✔
118
        # Executable and dirs
119
        self.eeschema = 'eeschema'
6✔
120
        self.pcbnew = 'pcbnew'
6✔
121
        self.kicad2step = 'kicad2step'
6✔
122
        self.kicad_cli = 'kicad-cli'
6✔
123
        self.kicad_conf_dir = 'kicad'
6✔
124
        ng_ver = os.environ.get('KIAUS_USE_NIGHTLY')
6✔
125
        if ng_ver:
6✔
126
            self.eeschema += '-'+NIGHTLY
×
127
            self.pcbnew += '-'+NIGHTLY
×
128
            self.kicad2step += '-'+NIGHTLY
×
129
            self.kicad_cli += '-'+NIGHTLY
×
130
            self.kicad_conf_dir += os.path.join(NIGHTLY, ng_ver)
×
131
            # Path to the Python module
132
            path.insert(0, '/usr/lib/kicad-nightly/lib/python3/dist-packages')
×
133
            os.environ['KICAD_PATH'] = '/usr/share/kicad-nightly'
×
134
        # Detect KiCad version
135
        try:
6✔
136
            import pcbnew
6✔
137
        except ImportError:
×
138
            logger.error("Failed to import pcbnew Python module."
×
139
                         " Is KiCad installed?"
140
                         " Do you need to add it to PYTHONPATH?")
141
            exit(NO_PCBNEW_MODULE)
×
142
        kicad_version = pcbnew.GetBuildVersion()
6✔
143
        try:
6✔
144
            # Debian sid may 2021 mess:
145
            really_index = kicad_version.index('really')
6✔
146
            kicad_version = kicad_version[really_index+6:]
×
147
        except ValueError:
6✔
148
            pass
6✔
149
        m = re.search(r'(\d+)\.(\d+)\.(\d+)', kicad_version)
6✔
150
        if m is None:
6✔
151
            logger.error("Unable to detect KiCad version, got: `{}`".format(kicad_version))
×
152
            exit(NO_PCBNEW_MODULE)
×
153
        self.kicad_version_major = int(m.group(1))
6✔
154
        self.kicad_version_minor = int(m.group(2))
6✔
155
        self.kicad_version_patch = int(m.group(3))
6✔
156
        self.kicad_version = self.kicad_version_major*1000000+self.kicad_version_minor*1000+self.kicad_version_patch
6✔
157
        logger.debug('Detected KiCad v{}.{}.{} ({} {})'.format(self.kicad_version_major, self.kicad_version_minor,
6✔
158
                     self.kicad_version_patch, kicad_version, self.kicad_version))
159
        self.ki5 = self.kicad_version < KICAD_VERSION_5_99
6✔
160
        self.ki6 = self.kicad_version >= KICAD_VERSION_5_99
6✔
161
        self.ki7 = self.kicad_version >= KICAD_VERSION_6_99
6✔
162
        self.ki8 = self.kicad_version >= KICAD_VERSION_7_99
6✔
163
        if self.ki7:
6✔
164
            # Now part of the kicad-cli tool
165
            self.kicad2step = self.kicad_cli
2✔
166
            self.drc_dialog_name = 'Design Rules Checker'
2✔
167
        else:
168
            self.drc_dialog_name = 'DRC Control'
4✔
169
        # Config file names
170
        if not self.ki5:
6✔
171
            self.kicad_conf_path = pcbnew.GetSettingsManager().GetUserSettingsPath()
4✔
172
            # No longer needed for 202112021512+6.0.0+rc1+287+gbb08ef2f41+deb11
173
            # if ng_ver:
174
            #    self.kicad_conf_path = self.kicad_conf_path.replace('/kicad/', '/kicadnightly/')
175
        else:
176
            # Bug in KiCad (#6989), prints to stderr:
177
            # `../src/common/stdpbase.cpp(62): assert "traits" failed in Get(test_dir): create wxApp before calling this`
178
            # Found in KiCad 5.1.8, 5.1.9
179
            # So we temporarily supress stderr
180
            with hide_stderr():
2✔
181
                self.kicad_conf_path = pcbnew.GetKicadConfigPath()
2✔
182
        logger.debug('Config path {}'.format(self.kicad_conf_path))
6✔
183
        # First we solve kicad_common because it can redirect to another config dir
184
        self.conf_kicad = os.path.join(self.kicad_conf_path, 'kicad_common')
6✔
185
        self.conf_kicad_bkp = None
6✔
186
        if not self.ki5:
6✔
187
            self.conf_kicad += '.json'
4✔
188
            self.conf_kicad_json = True
4✔
189
        else:
190
            self.conf_kicad_json = False
2✔
191
        # Read the environment redefinitions used by KiCad
192
        self.env = {}
6✔
193
        if os.path.isfile(self.conf_kicad):
6✔
194
            self.load_kicad_environment(logger)
6✔
195
            if 'KICAD_CONFIG_HOME' in self.env and self.ki5:
6✔
196
                # The user is redirecting the configuration
197
                # KiCad 5 unintentionally allows it, is a bug, and won't be fixed:
198
                # https://forum.kicad.info/t/kicad-config-home-inconsistencies-and-detail/26875
199
                self.kicad_conf_path = self.env['KICAD_CONFIG_HOME']
×
200
                logger.debug('Redirecting KiCad config path to: '+self.kicad_conf_path)
×
201
        else:
202
            logger.warning('Missing KiCad main config file '+self.conf_kicad)
6✔
203
        # - eeschema config
204
        self.conf_eeschema = os.path.join(self.kicad_conf_path, 'eeschema')
6✔
205
        self.conf_eeschema_bkp = None
6✔
206
        # - pcbnew config
207
        self.conf_pcbnew = os.path.join(self.kicad_conf_path, 'pcbnew')
6✔
208
        self.conf_pcbnew_bkp = None
6✔
209
        # Config files that migrated to JSON
210
        # Note that they remain in the old format until saved
211
        if not self.ki5:
6✔
212
            self.conf_eeschema += '.json'
4✔
213
            self.conf_pcbnew += '.json'
4✔
214
            self.conf_eeschema_json = True
4✔
215
            self.conf_pcbnew_json = True
4✔
216
            self.pro_ext = 'kicad_pro'
4✔
217
            self.prl_ext = 'kicad_prl'
4✔
218
            self.conf_colors = os.path.join(self.kicad_conf_path, 'colors', 'user.json')
4✔
219
            self.conf_colors_bkp = None
4✔
220
            self.conf_3dview = os.path.join(self.kicad_conf_path, '3d_viewer.json')
4✔
221
            self.conf_3dview_bkp = None
4✔
222
        else:
223
            self.conf_eeschema_json = False
2✔
224
            self.conf_pcbnew_json = False
2✔
225
            self.pro_ext = 'pro'
2✔
226
            self.prl_ext = None
2✔
227
            self.conf_colors = self.conf_colors_bkp = None
2✔
228
            self.conf_3dview = self.conf_3dview_bkp = None
2✔
229
        # - hotkeys
230
        self.conf_hotkeys = os.path.join(self.kicad_conf_path, 'user.hotkeys')
6✔
231
        self.conf_hotkeys_bkp = None
6✔
232
        # share path
233
        kpath = os.environ.get('KICAD_PATH')
6✔
234
        if kpath is None and self.env:
6✔
235
            kpath = self.env.get('KICAD_PATH')
2✔
236
        if kpath is None:
6✔
237
            if os.path.isdir(KICAD_SHARE):
6✔
238
                kpath = KICAD_SHARE
6✔
239
            elif os.path.isdir(KICAD_NIGHTLY_SHARE):
×
240
                kpath = KICAD_NIGHTLY_SHARE
×
241
        if kpath is None:
6✔
242
            logger.warning('Missing KiCad share dir')
×
243
        else:
244
            logger.debug('KiCad share dir: '+kpath)
6✔
245
            vname = 'KICAD'+str(self.kicad_version_major)+'_FOOTPRINT_DIR'
6✔
246
            # KiCad 7.0.0 rc2 workaround (also 7.0.1 and 7.99 april 2023)
247
            # KiCad bug: https://gitlab.com/kicad/code/kicad/-/issues/13815
248
            if vname not in os.environ:
6✔
249
                # Footprint dir not defined, and needed for DRC
250
                if self.env and vname in self.env:
6✔
251
                    os.environ[vname] = self.env[vname]
×
252
                else:
253
                    os.environ[vname] = os.path.join(kpath, 'footprints')
6✔
254
                logger.debug('Defining {} = {}'.format(vname, os.environ[vname]))
6✔
255
        # - sym-lib-table
256
        self.user_sym_lib_table = os.path.join(self.kicad_conf_path, 'sym-lib-table')
6✔
257
        self.user_fp_lib_table = os.path.join(self.kicad_conf_path, 'fp-lib-table')
6✔
258
        self.sys_sym_lib_table = [KICAD_SHARE+'template/sym-lib-table']
6✔
259
        self.sys_fp_lib_table = [KICAD_SHARE+'template/fp-lib-table']
6✔
260
        if ng_ver:
6✔
261
            # 20200912: sym-lib-table is missing
262
            self.sys_sym_lib_table.insert(0, KICAD_NIGHTLY_SHARE+'template/sym-lib-table')
×
263
            self.sys_fp_lib_table.insert(0, KICAD_NIGHTLY_SHARE+'template/fp-lib-table')
×
264
        if kpath is not None:
6✔
265
            self.sys_sym_lib_table.insert(0, os.path.join(kpath, 'template/sym-lib-table'))
6✔
266
            self.sys_fp_lib_table.insert(0, os.path.join(kpath, 'template/fp-lib-table'))
6✔
267
        # Some details about the UI
268
        if not self.ki5:
6✔
269
            # KiCad 5.99.0
270
            # self.ee_window_title = r'\[.*\] — Eeschema$'  # "PROJECT [HIERARCHY_PATH] - Eeschema"
271
            if self.ki7:
4✔
272
                self.ee_window_title = r'.* — Schematic Editor$'  # "SHEET [HIERARCHY_PATH]? - Schematic Editor"
2✔
273
            else:
274
                # KiCad 6.x
275
                self.ee_window_title = r'\[.*\] — Schematic Editor$'  # "PROJECT [HIERARCHY_PATH] - Schematic Editor"
2✔
276
            self.pn_window_title = r'.* — PCB Editor$'  # "PROJECT - PCB Editor"
4✔
277
            self.pn_simple_window_title = 'PCB Editor'
4✔
278
            kind = 'PCB' if self.is_pcbnew else 'Schematic'
4✔
279
            self.window_title_end = ' — '+kind+' Editor'
4✔
280
        else:
281
            # KiCad 5.1.6
282
            self.ee_window_title = r'Eeschema.*\.sch'  # "Eeschema - file.sch"
2✔
283
            self.pn_window_title = r'^Pcbnew'
2✔
284
            self.pn_simple_window_title = 'Pcbnew'
2✔
285
        # Collected errors and unconnecteds (warnings)
286
        self.errs = []
6✔
287
        self.wrns = []
6✔
288
        # Error filters
289
        self.err_filters = []
6✔
290

291
    def load_kicad_environment(self, logger):
6✔
292
        self.env = {}
6✔
293
        if self.conf_kicad_json:
6✔
294
            env = self.get_config_vars_json(self.conf_kicad)
4✔
295
            if env:
4✔
296
                self.env = env
×
297
        else:
298
            env = self.get_config_vars_ini(self.conf_kicad)
2✔
299
            if env:
2✔
300
                for k, v in env.items():
2✔
301
                    self.env[k.upper()] = v
2✔
302
        logger.debug('KiCad environment: '+str(self.env))
6✔
303

304
    @staticmethod
6✔
305
    def get_config_vars_json(file):
6✔
306
        with open(file, "rt") as f:
4✔
307
            data = json.load(f)
4✔
308
        if 'environment' in data and 'vars' in data['environment']:
4✔
309
            return data['environment']['vars']
4✔
310
        return None
×
311

312
    @staticmethod
6✔
313
    def get_config_vars_ini(file):
6✔
314
        config = configparser.ConfigParser()
2✔
315
        with open(file, "rt") as f:
2✔
316
            data = f.read()
2✔
317
        config.read_string('[Various]\n'+data)
2✔
318
        if 'EnvironmentVariables' in config:
2✔
319
            return config['EnvironmentVariables']
2✔
320
        return None
×
321

322

323
def get_en_locale(logger):
6✔
324
    ''' Looks for a usable locale with english as language '''
325
    try:
6✔
326
        res = check_output(['locale', '-a'], stderr=DEVNULL).decode('utf8')
6✔
327
    except FileNotFoundError:
×
328
        logger.warning("The `locale` command isn't installed. Guessing `C.UTF-8` is supported")
×
329
        res = 'C.UTF-8'
×
330
    found = re.search(r'en(.*)UTF-?8', res, re.I)
6✔
331
    if found:
6✔
332
        res = found.group(0)
×
333
    else:
334
        found = re.search(r'C\.UTF-?8', res, re.I)
6✔
335
        if found:
6✔
336
            res = found.group(0)
6✔
337
        else:
338
            logger.error("No suitable english locale found. Please add `en_US.utf8` to your system")
×
339
            exit(NO_EN_LOCALE)
×
340
    logger.debug('English locale: '+res)
6✔
341
    return res
6✔
342

343

344
__author__ = 'Salvador E. Tropea'
6✔
345
__copyright__ = 'Copyright 2018-2023, INTI/Productize SPRL'
6✔
346
__credits__ = ['Salvador E. Tropea', 'Seppe Stas', 'Jesse Vincent', 'Scott Bezek']
6✔
347
__license__ = 'Apache 2.0'
6✔
348
__email__ = 'stropea@inti.gob.ar'
6✔
349
__status__ = 'stable'
6✔
350
__url__ = 'https://github.com/INTI-CMNB/KiAuto/'
6✔
351
__version__ = '2.2.8'
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc