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

INTI-CMNB / KiAuto / 6734789687

02 Nov 2023 03:30PM UTC coverage: 88.563% (-0.2%) from 88.801%
6734789687

push

github-actions

set-soft
[Fixed] Removed debug "error" from kicad2step_do

Closes #34

3020 of 3410 relevant lines covered (88.56%)

3.78 hits per line

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

87.24
/src/eeschema_do
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
# Copyright (c) 2020-2023 Salvador E. Tropea
4
# Copyright (c) 2020-2023 Instituto Nacional de Tecnologïa Industrial
5
# Copyright (c) 2019 Jesse Vincent (@obra)
6
# Copyright (c) 2018-2019 Seppe Stas (@seppestas) (Productize SPRL)
7
# Based on ideas by: Scott Bezek (@scottbez1)
8
# License: Apache 2.0
9
# Project: KiAuto (formerly kicad-automation-scripts)
10
# Adapted from: https://github.com/obra/kicad-automation-scripts
11
"""
6✔
12
Various schematic operations
13

14
This program runs eeschema and can:
15
1) Export (plot) the schematic
16
2) Generate the netlist
17
3) Generate the BoM in XML format
18
4) Run the ERC
19
The process is graphical and very delicated.
20
"""
21

22
import argparse
6✔
23
import atexit
6✔
24
import json
6✔
25
import os
6✔
26
import re
6✔
27
import shlex
6✔
28
import shutil
6✔
29
import subprocess
6✔
30
import sys
6✔
31
import time
6✔
32

33
# Look for the 'kiauto' module from where the script is running
34
script_dir = os.path.dirname(os.path.abspath(__file__))
6✔
35
sys.path.insert(0, os.path.dirname(script_dir))
6✔
36
# kiauto import
37
# Log functionality first
38
from kiauto import log
6✔
39
log.set_domain(os.path.splitext(os.path.basename(__file__))[0])
6✔
40
logger = None
6✔
41

42
from kiauto.file_util import (load_filters, wait_for_file_created_by_process, apply_filters, list_errors, list_warnings,
6✔
43
                              check_kicad_config_dir, restore_config, backup_config, check_lib_table, create_user_hotkeys,
44
                              check_input_file, memorize_project, restore_project, get_log_files, create_kicad_config,
45
                              set_time_out_scale as set_time_out_scale_f, add_filter)
46
from kiauto.misc import (REC_W, REC_H, __version__, NO_SCHEMATIC, EESCHEMA_CFG_PRESENT, KICAD_CFG_PRESENT, RULES_KEY,
6✔
47
                         WAIT_START, WRONG_SCH_NAME, EESCHEMA_ERROR, Config, KICAD_VERSION_5_99, WONT_OVERWRITE,
48
                         USER_HOTKEYS_PRESENT, __copyright__, __license__, TIME_OUT_MULT, get_en_locale, KICAD_CLI_ERROR,
49
                         KICAD_VERSION_7_0_3, KICAD_VERSION_7_0_8)
50
from kiauto.interposer import (check_interposer, dump_interposer_dialog, start_queue, wait_start_by_msg,
6✔
51
                               set_kicad_process, open_dialog_i, wait_kicad_ready_i,
52
                               paste_output_file_i, exit_kicad_i, paste_text_i, wait_queue,
53
                               paste_bogus_filename, setup_interposer_filename, send_keys, wait_create_i)
54
from kiauto.ui_automation import (PopenContext, xdotool, wait_for_window, wait_not_focused, recorded_xvfb,
6✔
55
                                  wait_point, text_replace, set_time_out_scale, wait_xserver, wait_window_get_ref,
56
                                  wait_window_change, open_dialog_with_retry, ShowInfoAction)
57

58
TITLE_CONFIRMATION = '^Confirmation$'
6✔
59
TITLE_REMAP_SYMBOLS = '^Remap Symbols$'
6✔
60
TITLE_ERROR = '^(KiCad Schematic Editor )?Error$'
6✔
61
TITLE_WARNING = '^Warning$'
6✔
62
# KiCad 7.0.8 for already open (not reliable)
63
TITLE_FILE_OPEN_WARNING = '^File Open Warning$'
6✔
64
KI6_LOADING = r'^\[no schematic loaded\] '
6✔
65

66

67
def debug_output(res):
6✔
68
    if res.stdout:
2✔
69
        logger.debug('- Output from command: '+res.stdout.decode())
2✔
70

71

72
def run_command(command, check=True):
6✔
73
    logger.debug('Executing: '+shlex.join(command))
2✔
74
    try:
2✔
75
        res = subprocess.run(command, check=check, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
2✔
76
    except subprocess.CalledProcessError as e:
×
77
        logger.error('Running {} returned {}'.format(e.cmd, e.returncode))
×
78
        debug_output(e)
×
79
        exit(KICAD_CLI_ERROR)
×
80
    debug_output(res)
2✔
81
    return res.stdout.decode().rstrip()
2✔
82

83

84
def dismiss_library_error():
6✔
85
    # The "Error" modal pops up if libraries required by the schematic have
86
    # not been found. This can be ignored as all symbols are placed inside the
87
    # *-cache.lib file:
88
    # There -should- be a way to disable it, but I haven't the magic to drop in the config file yet
89
    nf_title = TITLE_ERROR
3✔
90
    wait_for_window(nf_title, nf_title, 3)
3✔
91
    if cfg.kicad_version >= KICAD_VERSION_5_99:
3✔
92
        logger.error('Eeschema created an error dialog, expect a fail')
2✔
93
        if cfg.ki7:
2✔
94
            exit(EESCHEMA_ERROR)
1✔
95
    else:
96
        logger.warning('Missing library, please fix it')
1✔
97
    xdotool(['search', '--onlyvisible', '--name', nf_title, 'windowfocus'])
2✔
98
    xdotool(['key', 'Escape'])
2✔
99
    xdotool(['key', 'Escape'])
2✔
100
    xdotool(['key', 'Escape'])
2✔
101

102

103
def dismiss_remap_helper(cfg):
6✔
104
    # The "Remap Symbols" windows pop up if the uses the project symbol library
105
    # the older list look up method for loading library symbols.
106
    # This can be ignored as we're just trying to output data and don't
107
    # want to mess with the actual project.
108
    logger.debug('Check for symbol remapping')
1✔
109
    wait_for_window('Remap Symbols', TITLE_REMAP_SYMBOLS, 3)
1✔
110
    if cfg.kicad_version >= KICAD_VERSION_5_99:
1✔
111
        xdotool(['key', 'Return'])
×
112
        wait_for_window('Project Rescue Helper', 'Project Rescue Helper', 3)
×
113
        time.sleep(2)
×
114
        xdotool(['key', 'Return'])
×
115
        wait_for_window('Remap Symbols', TITLE_REMAP_SYMBOLS, 3)
×
116
        time.sleep(2)
×
117
        xdotool(['key', 'Tab', 'Tab', 'Return', 'Escape'])
×
118
    else:
119
        xdotool(['key', 'Escape'])
1✔
120
    logger.warning('Schematic needs update')
1✔
121

122

123
def dismiss_warning():
6✔
124
    nf_title = TITLE_WARNING
1✔
125
    wait_for_window(nf_title, nf_title, 1)
1✔
126

127
    logger.debug('Dismiss eeschema warning')
1✔
128
    xdotool(['search', '--onlyvisible', '--name', nf_title, 'windowfocus'])
1✔
129
    logger.debug('Found, sending Return')
1✔
130
    xdotool(['key', 'Return'])
1✔
131

132

133
def dismiss_already_running():
6✔
134
    # The "Confirmation" modal pops up if eeschema is already running
135
    nf_title = TITLE_CONFIRMATION
1✔
136
    wait_for_window(nf_title, nf_title, 1)
1✔
137
    logger.info('Dismiss eeschema already running')
1✔
138
    xdotool(['search', '--onlyvisible', '--name', nf_title, 'windowfocus'])
1✔
139
    logger.debug('Found, sending Return')
1✔
140
    xdotool(['key', 'Return'])
1✔
141
    logger.debug('Wait a little, this dialog is slow')
1✔
142
    time.sleep(5)
1✔
143

144

145
def dismiss_already_opened():
6✔
146
    nf_title = TITLE_FILE_OPEN_WARNING
×
147
    wait_for_window(nf_title, nf_title, 1)
×
148
    logger.info('Dismiss file already opened')
×
149
    xdotool(['search', '--onlyvisible', '--name', nf_title, 'windowfocus'])
×
150
    logger.debug('Found, sending Return')
×
151
    xdotool(['key', 'Left', 'Return'])
×
152

153

154
def wait_eeschema(cfg, time, others=None):
6✔
155
    return wait_for_window('Main eeschema window', cfg.ee_window_title, time, others=others, popen_obj=cfg.popen_obj)
6✔
156

157

158
def wait_eeschema_start(cfg):
6✔
159
    wait_start = cfg.wait_start
3✔
160
    retry = 3
3✔
161
    retry_loading = False
3✔
162
    while retry:
3✔
163
        failed_focuse = False
3✔
164
        other = None
3✔
165
        try:
3✔
166
            wait_eeschema(cfg, wait_start, others=[TITLE_CONFIRMATION, TITLE_REMAP_SYMBOLS, TITLE_ERROR, TITLE_WARNING,
3✔
167
                          TITLE_FILE_OPEN_WARNING, KI6_LOADING])
168
        except RuntimeError:  # pragma: no cover
169
            logger.debug('Time-out waiting for eeschema, will retry')
170
            failed_focuse = True
171
        except ValueError as err:
3✔
172
            other = str(err)
3✔
173
            logger.debug('Found "'+other+'" window instead of eeschema')
3✔
174
            failed_focuse = True
3✔
175
        except subprocess.CalledProcessError:
×
176
            logger.debug('* Eeschema is no longer running (returned {})'.format(cfg.popen_obj.poll()))
×
177
        if not failed_focuse:
3✔
178
            return True
3✔
179
        # Failed to find the window
180
        # Did we find a dialog?
181
        if other == TITLE_REMAP_SYMBOLS:
3✔
182
            dismiss_remap_helper(cfg)
1✔
183
        elif other == TITLE_ERROR:
3✔
184
            dismiss_library_error()
3✔
185
            if cfg.kicad_version >= KICAD_VERSION_5_99:
2✔
186
                time.sleep(1)
1✔
187
                return False
1✔
188
        elif other == TITLE_CONFIRMATION:
3✔
189
            dismiss_already_running()
1✔
190
        elif other == TITLE_FILE_OPEN_WARNING:
3✔
191
            dismiss_already_opened()
×
192
        elif other == TITLE_WARNING:
3✔
193
            dismiss_warning()
1✔
194
            # This is crazy, if we miss a lib we get an "Error", pressing ESC solves it
195
            # If we have a damaged file we get a "Warning", pressing ESC fails ...
196
            logger.error('eeschema reported an error')
1✔
197
            exit(EESCHEMA_ERROR)
1✔
198
        elif other == KI6_LOADING:
2✔
199
            # KiCad is loading the file
200
            if not retry_loading:
2✔
201
                # Give the same time-out, but waiting in steps of 1 s
202
                retry = WAIT_START
2✔
203
                retry_loading = True
2✔
204
                wait_start = 1
2✔
205
        else:
206
            # One more time, just 5 seconds
207
            if retry > 2:
×
208
                retry = 2
×
209
            wait_start = 5
×
210
        retry -= 1
3✔
211
        if retry:
3✔
212
            time.sleep(1)
3✔
213
    logger.error('Time-out waiting for eeschema, giving up')
×
214
    exit(EESCHEMA_ERROR)
×
215

216

217
def exit_eeschema(cfg):
6✔
218
    if cfg.use_interposer:
6✔
219
        exit_kicad_i(cfg)
3✔
220
        return
3✔
221
    # Wait until the dialog is closed, useful when more than one file are created
222
    id = wait_eeschema(cfg, 10)[0]
3✔
223

224
    send_keys(cfg, 'Exiting eeschema', 'ctrl+q')
3✔
225
    try:
3✔
226
        wait_not_focused(id, 5)
3✔
227
    except RuntimeError:  # pragma: no cover
228
        logger.debug('EEschema not exiting, will retry')
229
        pass
230
    # Dismiss any dialog. I.e. failed to write the project
231
    xdotool(['key', 'Return', 'ctrl+q'])
3✔
232
    try:
3✔
233
        wait_not_focused(id, 5)
3✔
234
    except RuntimeError:  # pragma: no cover
235
        logger.debug('EEschema not exiting, will kill')
236
        pass
237
    # If we failed to exit we will kill it
238
    cfg.popen_obj.terminate()
3✔
239

240

241
def wait_create(cfg, name):
6✔
242
    cfg.logger.info('Wait for '+name+' file creation')
3✔
243
    wait_point(cfg)
3✔
244
    wait_for_file_created_by_process(cfg.eeschema_pid, cfg.output_file)
3✔
245

246

247
def eeschema_plot_schematic_cli(cfg):
6✔
248
    outf = cfg.output_file if cfg.export_format == 'pdf' else os.path.dirname(cfg.output_file)
2✔
249
    cmd = [cfg.kicad_cli, 'sch', 'export', cfg.export_format, '-o', outf, '-t', cfg.color_theme]
2✔
250
    if cfg.monochrome:
2✔
251
        cmd.append('--black-and-white')
2✔
252
    if cfg.no_frame:
2✔
253
        cmd.append('--exclude-drawing-sheet')
2✔
254
    if not cfg.background_color:
2✔
255
        cmd.append('--no-background-color')
2✔
256
    if not cfg.all_pages:
2✔
257
        cmd.append('--plot-one')
2✔
258
    cmd.append(cfg.input_file)
2✔
259
    run_command(cmd)
2✔
260

261

262
def eeschema_plot_schematic(cfg):
6✔
263
    if cfg.ki5:
4✔
264
        open_keys = ['key', 'alt+f', 'l']
2✔
265
    else:
266
        open_keys = ['key', 'ctrl+shift+p']
2✔
267
    if cfg.use_interposer:
4✔
268
        eeschema_plot_schematic_i(cfg, open_keys)
2✔
269
    else:
270
        eeschema_plot_schematic_n(cfg, open_keys)
2✔
271

272

273
def eeschema_plot_schematic_i(cfg, open_keys):
6✔
274
    # Open the "Plot Schematic Options"
275
    dialog, _ = open_dialog_i(cfg, 'Plot Schematic Options', open_keys)
2✔
276
    # Make sure we are in the "Output directory:" box
277
    send_keys(cfg, 'Select the output dir widget', 'alt+o')
2✔
278
    # Paste the output_dir
279
    paste_output_file_i(cfg, use_dir=True)
2✔
280
    # Press the corresponding Plot button
281
    send_keys(cfg, 'Plot', 'alt+a' if cfg.all_pages else 'alt+c')
2✔
282
    # Wait for the file
283
    wait_create_i(cfg, 'plot')
2✔
284
    # Close the dialog
285
    send_keys(cfg, 'Closing dialog', 'Escape', closes=dialog)
2✔
286

287

288
def eeschema_plot_schematic_n(cfg, open_keys):
6✔
289
    # Open the dialog
290
    open_dialog_with_retry('Open File->pLot', open_keys, 'Plot dialog', 'Plot Schematic Options', cfg)
2✔
291
    # With a WM we usually get the "Browse" button selected.
292
    # Without it we usually are in the input box.
293
    # For this reason we move to the left and select all.
294
    logger.info('Clear input text')
2✔
295
    wait_point(cfg)
2✔
296
    xdotool(['key', 'Left'])
2✔
297
    # Paste the file name
298
    logger.info('Paste output directory')
2✔
299
    wait_point(cfg)
2✔
300
    text_replace(cfg.output_dir)
2✔
301
    # Press the "Plot xxx" button
302
    logger.info('Move to the "plot" button')
2✔
303
    wait_point(cfg)
2✔
304
    # We try to select the "Plot xxx" button.
305
    command_list = ['key', 'shift+Tab', 'shift+Tab']
2✔
306
    if cfg.all_pages:
2✔
307
        command_list.extend(['Right']*3)  # Force the one at the right (1 to 3 positions)
2✔
308
    else:
309
        command_list.extend(['Left']*3)  # Force the one at the left (0 to 3 positions)
2✔
310
    logger.debug(str(command_list)+'   '+str(len(command_list)))
2✔
311
    xdotool(command_list)
2✔
312
    logger.info('Plot')
2✔
313
    wait_point(cfg)
2✔
314
    xdotool(['key', 'Return'])
2✔
315
    # Wait for the file
316
    wait_create(cfg, 'plot')
2✔
317
    # Close the dialog
318
    send_keys(cfg, 'Closing window', 'Escape')
2✔
319

320

321
def eeschema_parse_erc(cfg):
6✔
322
    with open(cfg.output_file, 'rt') as f:
6✔
323
        lines = f.read().splitlines()
6✔
324
        last_line = lines[-1]
6✔
325
    cont = False
6✔
326
    is_err = False
6✔
327
    if cfg.kicad_version >= KICAD_VERSION_5_99:
6✔
328
        err_regex = re.compile(r'^\[(\S+)\]: (.*)')
4✔
329
    else:
330
        err_regex = re.compile(r'^ErrType\((\d+)\): (.*)')
2✔
331
    errors_excl = warnings_excl = 0
6✔
332
    excluded = False
6✔
333
    for i, line in enumerate(lines):
6✔
334
        m = err_regex.search(line)
6✔
335
        if m:
6✔
336
            msg = '({}) {}'.format(m.group(1), m.group(2))
6✔
337
            if cfg.kicad_version >= KICAD_VERSION_5_99 and i < len(lines):
6✔
338
                # KiCad 6 moved the severity to the next line
339
                line = lines[i+1]
4✔
340
                if '; Severity' not in line and i+3 < len(lines):
4✔
341
                    if lines[i+2] == '':
2✔
342
                        # KiCad 7.0.2 bug: a message with two extra \n
343
                        # Error reading simulation model from symbol '#PWR03':\nFailed to read simulation model from fields.\n
344
                        line = lines[i+3]
×
345
                    else:
346
                        # KiCad 7.0.5 bug: a message with one extra \n
347
                        line = lines[i+2]
2✔
348
                    msg += ' '+line
2✔
349
            # Discard messages explicitly excluded using the GUI
350
            excluded = '(excluded)' in line
6✔
351
            if r'Severity: error' in line:
6✔
352
                is_err = True
6✔
353
                if excluded:
6✔
354
                    errors_excl += 1
2✔
355
                else:
356
                    cfg.errs.append(msg)
6✔
357
            else:
358
                is_err = False
6✔
359
                if excluded:
6✔
360
                    warnings_excl += 1
×
361
                else:
362
                    cfg.wrns.append(msg)
6✔
363
            cont = True
6✔
364
            continue
6✔
365
        if cont and line.startswith('    '):
6✔
366
            if excluded:
6✔
367
                continue
×
368
            if is_err:
6✔
369
                if len(cfg.errs):
6✔
370
                    cfg.errs.append(cfg.errs.pop()+'\n'+line)
6✔
371
            else:
372
                if len(cfg.wrns):
6✔
373
                    cfg.wrns.append(cfg.wrns.pop()+'\n'+line)
6✔
374
            continue
6✔
375
        cont = False
6✔
376
    logger.debug('Last line: '+last_line)
6✔
377
    m = re.search(r'^ \*\* ERC messages: ([0-9]+) +Errors ([0-9]+) +Warnings ([0-9]+)+$', last_line)
6✔
378
    # messages = m.group(1)
379
    errors = int(m.group(2))-errors_excl
6✔
380
    warnings = int(m.group(3))-warnings_excl
6✔
381
    # Apply the warnings_as_errors option
382
    if cfg.warnings_as_errors:
6✔
383
        cfg.errs += cfg.wrns
6✔
384
        cfg.wrns = []
6✔
385
        return errors+warnings, 0
6✔
386
    return errors, warnings
6✔
387

388

389
def eeschema_run_erc_schematic_5_1_n(cfg):
6✔
390
    # Open the ERC dialog
391
    logger.info('Open Tools->Electrical Rules Checker')
1✔
392
    wait_point(cfg)
1✔
393
    xdotool(['key', 'alt+i', 'c'])
1✔
394
    # Wait dialog
395
    wait_for_window('Electrical Rules Checker dialog', 'Electrical Rules Checker')
1✔
396
    wait_point(cfg)
1✔
397
    # Enable the "Create ERC file report"
398
    xdotool(['key', 'Tab', 'Tab', 'Tab', 'Tab', 'space', 'Return'])
1✔
399
    # Wait for the save dialog
400
    wait_for_window('ERC File save dialog', 'ERC File')
1✔
401
    # Paste the name
402
    logger.info('Pasting output file')
1✔
403
    wait_point(cfg)
1✔
404
    text_replace(cfg.output_file)
1✔
405
    # Run the ERC
406
    logger.info('Run ERC')
1✔
407
    wait_point(cfg)
1✔
408
    xdotool(['key', 'Return'])
1✔
409
    # Wait for report created
410
    wait_create(cfg, 'ERC')
1✔
411
    # Close the ERC dialog
412
    logger.info('Exit ERC')
1✔
413
    wait_point(cfg)
1✔
414
    xdotool(['key', 'shift+Tab', 'Return'])
1✔
415

416

417
def eeschema_run_erc_schematic_5_1_i(cfg):
6✔
418
    # Open the ERC dialog
419
    dialog, _ = open_dialog_i(cfg, 'Electrical Rules Checker', ['key', 'alt+i', 'c'], no_main=True)
1✔
420
    # Enable the "Create ERC file report"
421
    send_keys(cfg, 'Enable report creation', 'alt+c')
1✔
422
    # Wait for the save dialog
423
    file_dlg, _ = open_dialog_i(cfg, 'ERC File', 'Return')
1✔
424
    # Paste the name
425
    paste_bogus_filename(cfg)
1✔
426
    # Run the ERC
427
    send_keys(cfg, 'Run ERC', 'Return', closes=file_dlg, delay_io=True)
1✔
428
    # Wait for report created
429
    wait_create_i(cfg, 'ERC')
1✔
430
    # Close the ERC dialog
431
    send_keys(cfg, 'Exit ERC', 'alt+l', closes=dialog)
1✔
432

433

434
def eeschema_run_erc_schematic_6_0_n(cfg):
6✔
435
    # Open the ERC dialog
436
    keys = ['key', 'Escape', RULES_KEY.lower()]
2✔
437
    erc_name = 'Electrical Rules Checker dialog'
2✔
438
    erc_title = 'Electrical Rules Checker'
2✔
439
    erc_msg = 'Open Tools->Electrical Rules Checker'
2✔
440
    id = open_dialog_with_retry(erc_msg, keys, erc_name, erc_title, cfg)
2✔
441
    wait_point(cfg)
2✔
442
    # Run the ERC
443
    logger.info('Run ERC')
2✔
444
    wait_point(cfg)
2✔
445
    wait_window_get_ref(id[0], 108, 27, 100, 10)
2✔
446
    xdotool(['key', 'Return'])
2✔
447
    #
448
    # Currently is impossible to know when it finished.
449
    #
450
    try_again = False
2✔
451
    try:
2✔
452
        wait_window_change(id[0], 108, 27, 100, 10, 45*cfg.time_out_scale, 3*cfg.time_out_scale)
2✔
453
    except subprocess.TimeoutExpired:
×
454
        try_again = True
×
455
    if try_again:
2✔
456
        # KiCad 7.0.2 bug, closes ERC dialog after finishing
457
        logger.warning('Time out capturing from the '+erc_name+', trying to open it again')
×
458
        id = open_dialog_with_retry(erc_msg, keys, erc_name, erc_title, cfg)
×
459
    # Save the report
460
    logger.info('Open the save dialog')
2✔
461
    wait_point(cfg)
2✔
462
    keys_save = ['key', 'Tab', 'Tab', 'Tab', 'Tab', 'Tab', 'Tab', 'Tab', 'Tab']
2✔
463
    if cfg.kicad_version >= KICAD_VERSION_7_0_8:
2✔
464
        keys_save.extend(['Tab', 'Tab'])
1✔
465
    keys_save.append('Return')
2✔
466
    xdotool(keys_save)
2✔
467
    # Wait for the save dialog
468
    try_again = False
2✔
469
    try:
2✔
470
        wait_for_window('ERC File save dialog', 'Save Report to File')
2✔
471
    except RuntimeError:
×
472
        if cfg.ki7:
×
473
            try_again = True
×
474
        else:
475
            raise
×
476
    if try_again:
2✔
477
        # KiCad 7.0.2 bug, closes ERC dialog after finishing
478
        found = False
×
479
        try:
×
480
            wait_for_window(erc_name, erc_title, timeout=1)
×
481
            found = True
×
482
        except RuntimeError:
×
483
            pass
×
484
        if found:
×
485
            logger.error(erc_name+' not responding')
×
486
            exit(EESCHEMA_ERROR)
×
487
        logger.warning(erc_name+' suddenly closed, trying to open it again')
×
488
        id = open_dialog_with_retry(erc_msg, keys, erc_name, erc_title, cfg)
×
489
        xdotool(keys_save)
×
490
        wait_for_window('ERC File save dialog', 'Save Report to File')
×
491
    # Paste the name
492
    logger.info('Pasting output file')
2✔
493
    wait_point(cfg)
2✔
494
    text_replace(cfg.output_file)
2✔
495
    # Wait for report created
496
    wait_point(cfg)
2✔
497
    xdotool(['key', 'Return'])
2✔
498
    wait_create(cfg, 'ERC')
2✔
499
    # Close the ERC dialog
500
    logger.info('Exit ERC')
2✔
501
    wait_point(cfg)
2✔
502
    xdotool(['key', 'Escape'])
2✔
503
    # KiCad 7.0.7 lack of ESC support
504
    xdotool(['key', 'shift+Tab', 'shift+Tab', 'Return'])
2✔
505

506

507
def eeschema_run_erc_schematic_6_0_i(cfg):
6✔
508
    # KiCad 7.99 particularities:
509
    # 1. Forces ".rpt" in the name, no matter what name was selected in the dialog and no matter if it already has an extension
510
    # 2. The ERC dialog isn't destroyed, most probably hidden
511
    # Open the ERC dialog
512
    title = 'Electrical Rules Checker'
2✔
513
    dialog, _ = open_dialog_i(cfg, title, RULES_KEY.lower(), no_main=True)
2✔
514
    # Run the ERC
515
    send_keys(cfg, 'Run ERC', 'Return')
2✔
516
    # Wait for completion. The Close button is refreshed at the end
517
    res = wait_queue(cfg, ['GTK:Button Label:C_lose', 'GTK:Window Destroy:'+title])
2✔
518
    if res.startswith('GTK:Window Destroy:'):
2✔
519
        # Ugly hack for 7.99 should be removed
520
        logger.error(title+' destroyed (before completion)')
×
521
        dialog, _ = open_dialog_i(cfg, title, RULES_KEY.lower(), no_main=True)
×
522
        time.sleep(3)
×
523
        wait_kicad_ready_i(cfg)
×
524
        send_keys(cfg, 'Run ERC', 'Return')
×
525
        wait_queue(cfg, 'GTK:Button Label:C_lose')
×
526
    # Save the report
527
    reopen = False
2✔
528
    try:
2✔
529
        # Catch the ERC unintentional close
530
        file_dlg, _ = open_dialog_i(cfg, 'Save Report to File', 'alt+s', raise_if=['GTK:Window Destroy:'+title])
2✔
531
    except InterruptedError:
×
532
        reopen = True
×
533
    if reopen:
2✔
534
        logger.error(title+' destroyed (after completion)')
×
535
        # KiCad 7.0.2 bug, in some cases the dialog is closed, we just need to open it again
536
        dialog, _ = open_dialog_i(cfg, title, RULES_KEY.lower(), no_main=True)
×
537
        file_dlg, _ = open_dialog_i(cfg, 'Save Report to File', 'alt+s')
×
538
    # Paste the output file
539
    paste_bogus_filename(cfg)
2✔
540
    send_keys(cfg, 'Create the report', 'Return', closes=file_dlg, delay_io=True)
2✔
541
    # Wait until created
542
    wait_create_i(cfg, 'ERC', forced_ext='rpt')
2✔
543
    # Close the ERC dialog
544
    # KiCad 7.0.7 no longer closes this dialog with ESC
545
    # send_keys(cfg, 'Exit ERC', 'Escape', closes=dialog, no_destroy=cfg.ki8)
546
    send_keys(cfg, 'Exit ERC', 'alt+l', closes=dialog, no_destroy=cfg.ki8)
2✔
547

548

549
def eeschema_run_erc_schematic(cfg):
6✔
550
    if cfg.ki5:
6✔
551
        if cfg.use_interposer:
2✔
552
            eeschema_run_erc_schematic_5_1_i(cfg)
1✔
553
        else:
554
            eeschema_run_erc_schematic_5_1_n(cfg)
1✔
555
    else:
556
        if cfg.use_interposer:
4✔
557
            eeschema_run_erc_schematic_6_0_i(cfg)
2✔
558
        else:
559
            eeschema_run_erc_schematic_6_0_n(cfg)
2✔
560

561

562
def eeschema_netlist_commands_cli(cfg):
6✔
563
    cmd = [cfg.kicad_cli, 'sch', 'export', 'netlist', '-o', cfg.output_file, '--format', 'kicadsexpr', cfg.input_file]
2✔
564
    run_command(cmd)
2✔
565

566

567
def eeschema_netlist_commands(cfg):
6✔
568
    # KiCad 5.1 vs 6 differences
569
    if cfg.ki5:
4✔
570
        open_keys = ['key', 'alt+t', 'n']
2✔
571
        generate_keys = ['Tab', 'Tab', 'Return']
2✔
572
        dialog_name = 'Netlist'
2✔
573
    else:
574
        open_keys = ['key', 'ctrl+shift+n']
2✔
575
        generate_keys = ['Return']
2✔
576
        dialog_name = 'Export Netlist'
2✔
577
    if cfg.use_interposer:
4✔
578
        eeschema_netlist_commands_i(cfg, open_keys, dialog_name)
2✔
579
    else:
580
        eeschema_netlist_commands_n(cfg, open_keys, generate_keys, dialog_name)
2✔
581

582

583
def eeschema_netlist_commands_i(cfg, open_keys, dialog_name):
6✔
584
    # Open the "Export Netlist" dialog
585
    open_dialog_i(cfg, dialog_name, open_keys)
2✔
586
    # Start to generate the netlist (select file name)
587
    file_dlg, _ = open_dialog_i(cfg, 'Save Netlist File', 'alt+e')
2✔
588
    # Paste the output file
589
    paste_bogus_filename(cfg)
2✔
590
    # Confirm the name and generate the netlist (both dialogs closes)
591
    send_keys(cfg, 'Generate Netlist', 'Return', closes=[file_dlg, dialog_name], delay_io=True)
2✔
592
    # Wait until created
593
    wait_create_i(cfg, 'Netlist')
2✔
594

595

596
def eeschema_netlist_commands_n(cfg, open_keys, generate_keys, dialog_name):
6✔
597
    # Open the dialog
598
    open_dialog_with_retry('Open Tools->Generate Netlist File', open_keys, 'Netlist dialog', dialog_name, cfg)
2✔
599
    # Start to generate the netlist (select file name)
600
    wait_point(cfg)
2✔
601
    xdotool(['key']+generate_keys)
2✔
602
    try:
2✔
603
        wait_for_window('Netlist File save dialog', 'Save Netlist File', others=['Plugin Properties'])
2✔
604
        failed_focuse = False
2✔
605
    except ValueError as err:  # pragma: no cover
606
        # Sometimes the dialog starts with the "Generate" button selected and we move to the
607
        # 'Plugin Properties'. In this case we go back to the generate button.
608
        # I exclude it from coverage because I can't reproduce it in the tests.
609
        other = str(err)
610
        logger.debug('Found "'+other+'" window instead of Netlist')
611
        failed_focuse = True
612
        pass
613
    if failed_focuse:  # pragma: no cover
614
        logger.debug('Closing the plugin properties window')
615
        xdotool(['key', 'Escape'])
616
        wait_for_window('Netlist dialog', 'Netlist')
617
        logger.debug('Trying again')
618
        xdotool(['key', 'shift+Tab', 'shift+Tab', 'Return'])
619
        wait_for_window('Netlist File save dialog', 'Save Netlist File')
620
    logger.info('Pasting output file')
2✔
621
    wait_point(cfg)
2✔
622
    text_replace(cfg.output_file)
2✔
623
    # Confirm the name and generate the netlist
624
    send_keys(cfg, 'Generate Netlist', ['key', 'Return'])
2✔
625
    # Wait until created
626
    wait_create(cfg, 'Netlist')
2✔
627

628

629
def eeschema_bom_xml_commands_cli(cfg):
6✔
630
    cmd = [cfg.kicad_cli, 'sch', 'export', 'netlist', '-o', cfg.output_file, '--format', 'kicadxml', cfg.input_file]
2✔
631
    run_command(cmd)
2✔
632

633

634
def eeschema_bom_xml_commands(cfg):
6✔
635
    # KiCad 5.1 vs 6 differences
636
    if cfg.ki5:
4✔
637
        open_keys = ['key', 'alt+t', 'm']
2✔
638
        exit_keys = ['Tab', 'Tab', 'Tab', 'Tab', 'Tab', 'Tab', 'Tab', 'Tab', 'Tab', 'Return']
2✔
639
    else:
640
        open_keys = ['key', 'ctrl+shift+b']
2✔
641
        exit_keys = ['Escape']
2✔
642
    if cfg.use_interposer:
4✔
643
        eeschema_bom_xml_commands_i(cfg, open_keys)
2✔
644
    else:
645
        eeschema_bom_xml_commands_n(cfg, open_keys, exit_keys)
2✔
646

647

648
def eeschema_bom_xml_commands_i(cfg, open_keys):
6✔
649
    # Open the dialog
650
    # TODO: When we open this dialog we start to get some random messages from the interposer, why?!
651
    # Seems to be related to the fork done by KiCad.
652
    dialog, _ = open_dialog_i(cfg, 'Bill of Material', open_keys)
2✔
653
    # Select the command input and paste the command
654
    send_keys(cfg, 'Select the output file name', 'alt+o')
2✔
655
    paste_text_i(cfg, 'Paste bogus command', 'echo "%I"')
2✔
656
    # Generate the netlist
657
    send_keys(cfg, 'Generate Netlist', 'Return')
2✔
658
    # Wait until the file is created
659
    wait_create_i(cfg, 'BoM')
2✔
660
    # Close the dialog
661
    send_keys(cfg, 'Closing dialog', 'alt+l', closes=dialog)
2✔
662

663

664
def eeschema_bom_xml_commands_n(cfg, open_keys, exit_keys):
6✔
665
    # Open the dialog
666
    open_dialog_with_retry('Open Tools->Generate Bill of Materials', open_keys, 'Bill of Material dialog',
2✔
667
                           'Bill of Material', cfg)
668
    # Select the command input and paste the command
669
    logger.info('Paste xslt command')
2✔
670
    wait_point(cfg)
2✔
671
    xdotool(['key', 'Tab', 'Tab', 'Tab', 'Tab', 'Tab', 'Tab'])
2✔
672
    text_replace('echo "%I"')
2✔
673
    # Generate the netlist
674
    send_keys(cfg, 'Generate Netlist', 'Return')
2✔
675
    # Wait until the file is created
676
    wait_create(cfg, 'BoM')
2✔
677
    # Close the dialog
678
    send_keys(cfg, 'Closing dialog', ['key']+exit_keys)
2✔
679
    time.sleep(0.4)
2✔
680

681

682
def create_eeschema_config(cfg):
6✔
683
    logger.debug('Creating an eeschema config')
6✔
684
    # HPGL:0 ??:1 PS:2 DXF:3 PDF:4 SVG:5
685
    index = ['hpgl', '---', 'ps', 'dxf', 'pdf', 'svg'].index(cfg.export_format)
6✔
686
    logger.debug('Selecting plot format %s (%d)', cfg.export_format, index)
6✔
687
    with open(cfg.conf_eeschema, "wt") as text_file:
6✔
688
        if cfg.conf_eeschema_json:
6✔
689
            plot_ops = {}
4✔
690
            plot_ops['format'] = index
4✔
691
            plot_ops['color'] = not cfg.monochrome
4✔
692
            plot_ops['frame_reference'] = not cfg.no_frame
4✔
693
            plot_ops['color_theme'] = cfg.color_theme.lower()
4✔
694
            plot_ops['background_color'] = cfg.background_color
4✔
695
            plot_ops['hpgl_origin'] = cfg.hpgl_origin
4✔
696
            # plot_ops['hpgl_paper_size'] = ??
697
            plot_ops['hpgl_pen_size'] = cfg.hpgl_pen_size
4✔
698
            eeconf = {'plot': plot_ops}
4✔
699
            eeconf['system'] = {"first_run_shown": True, "never_show_rescue_dialog": True}
4✔
700
            eeconf['appearance'] = {"show_sexpr_file_convert_warning": False, "color_theme": cfg.color_theme.lower()}
4✔
701
            eeconf['window'] = {"size_x": cfg.rec_width, "size_y": cfg.rec_height,
4✔
702
                                # Select the user grid and the provided grid. X/Y the same.
703
                                "grid": {"last_size": 7, "user_grid_x": "%d mil" % cfg.grid,
704
                                         "user_grid_y": "%d mil" % cfg.grid}}
705
            text = json.dumps(eeconf)
4✔
706
            text_file.write(text)
4✔
707
            logger.debug(text)
4✔
708
        else:
709
            text_file.write('RescueNeverShow=1\n')
2✔
710
            text_file.write('ShowIllegalSymbolLibDialog=0\n')
2✔
711
            text_file.write('ShowSheetFileNameCaseSensitivityDlg=0\n')
2✔
712
            text_file.write('PlotFormat=%d\n' % index)
2✔
713
            text_file.write('PlotModeColor=%d\n' % (not cfg.monochrome))
2✔
714
            text_file.write('PlotFrameRef=%d\n' % (not cfg.no_frame))
2✔
715

716

717
def restore_output_file(cfg):
6✔
718
    if cfg.output_file_renamed and os.path.exists(cfg.output_file_renamed):
×
719
        if os.path.exists(cfg.output_file):
×
720
            logger.warning('Removing `{}` to restore the original'.format(cfg.output_file))
×
721
            os.remove(cfg.output_file)
×
722
        logger.debug('Restoring: {} -> {}'.format(cfg.output_file_renamed, cfg.output_file))
×
723
        os.rename(cfg.output_file_renamed, cfg.output_file)
×
724

725

726
def remove_output(cfg):
6✔
727
    cfg.output_file_renamed = None
6✔
728
    if cfg.wanted_output_file and cfg.wanted_output_file != cfg.output_file:
6✔
729
        # KiCad can generate this name
730
        if os.path.exists(cfg.output_file):
×
731
            logger.debug(cfg.output_file+' found and we need to preserve it')
×
732
            # The name that KiCad will use belongs to a file that is already there
733
            cfg.output_file_renamed = cfg.output_file+'.renamed_by_kiauto'
×
734
            if os.path.exists(cfg.output_file_renamed):
×
735
                # Could be from a failed run
736
                logger.error('The `{}` temporal file exists, please solve it'.format(cfg.output_file_renamed))
×
737
                exit(WONT_OVERWRITE)
×
738
            # Move away the name that KiCad will use
739
            os.rename(cfg.output_file, cfg.output_file_renamed)
×
740
            # Restore it at exit
741
            atexit.register(restore_output_file, cfg)
×
742
    if os.path.exists(cfg.output_file):
6✔
743
        logger.debug('Removing old file')
6✔
744
        os.remove(cfg.output_file)
6✔
745
        # Note: what if we are exporting multiple files and *all* of them exists?
746
        # No problem KiCad will overwrite them without even asking ;-)
747

748

749
def name_with_out_dir(cfg, name):
6✔
750
    return name if os.path.isabs(name) else os.path.join(cfg.output_dir, name)
×
751

752

753
def set_output_file(cfg, ext, user_out, can_name_out=True):
6✔
754
    """ Set the cfg.output_file member using cfg.output_file_no_ext and the extension. """
755
    wanted_output_file = None
6✔
756
    if user_out is not None:
6✔
757
        wanted_output_file = name_with_out_dir(cfg, user_out[0])
×
758
    default_output_file = cfg.output_file_no_ext+'.'+ext
6✔
759
    if can_name_out:
6✔
760
        # Outputs that can choose a name
761
        cfg.output_file = wanted_output_file if wanted_output_file else default_output_file
6✔
762
        cfg.wanted_output_file = None
6✔
763
    else:
764
        # Outputs where KiCad forces a name
765
        # The name we can get
766
        cfg.output_file = default_output_file
6✔
767
        # The name requested by the user
768
        cfg.wanted_output_file = wanted_output_file
6✔
769

770

771
def wait_eeschema_start_by_msg(cfg):
6✔
772
    wait_start_by_msg(cfg)
3✔
773
    # Make sure eeschema has the focus, I saw problems with WM pop-ups getting the focus
774
    wait_eeschema(cfg, 10)
3✔
775
    return False
3✔
776

777

778
def can_be_done_using_cli(command, cfg):
6✔
779
    if not cfg.ki7:
6✔
780
        return False
4✔
781
    if command in {'netlist', 'bom_xml'}:
2✔
782
        return True
2✔
783
    # Export can be partially done using the CLI
784
    if command == 'export':
2✔
785
        if cfg.export_format in {'svg', 'pdf'} and cfg.all_pages:
2✔
786
            return True
2✔
787
        # The kicad-cli for v8 can do all, try the command
788
        res = run_command(['kicad-cli', 'sch', 'export', cfg.export_format], check=False)
2✔
789
        return 'Usage: '+cfg.export_format in res
2✔
790
    return False
2✔
791

792

793
if __name__ == '__main__':
6✔
794
    parser = argparse.ArgumentParser(description='KiCad schematic automation')
6✔
795
    subparsers = parser.add_subparsers(help='Command:', dest='command')
6✔
796

797
    parser.add_argument('schematic', help='KiCad schematic file')
6✔
798
    parser.add_argument('output_dir', help='Output directory (ignored for bom_xml)')
6✔
799

800
    # iImnrsSvVw
801
    parser.add_argument('--disable_interposer', '-I', help='Avoid using the interposer lib', action='store_true')
6✔
802
    parser.add_argument('--info', '-n', help='Show information about the installation', action=ShowInfoAction, nargs=0)
6✔
803
    parser.add_argument('--interposer_sniff', '-i', help="Log interposer info, but don't use it", action='store_true')
6✔
804
    parser.add_argument('--record', '-r', help='Record the UI automation', action='store_true')
6✔
805
    parser.add_argument('--rec_width', help='Record width ['+str(REC_W)+']', type=int, default=REC_W)
6✔
806
    parser.add_argument('--rec_height', help='Record height ['+str(REC_H)+']', type=int, default=REC_H)
6✔
807
    parser.add_argument('--separate_info', '-S', help='Send info debug level to stdout', action='store_true')
6✔
808
    parser.add_argument('--start_x11vnc', '-s', help='Start x11vnc (debug)', action='store_true')
6✔
809
    parser.add_argument('--use_wm', '-m', help='Use a window manager (fluxbox)', action='store_true')
6✔
810
    parser.add_argument('--verbose', '-v', action='count', default=0)
6✔
811
    parser.add_argument('--version', '-V', action='version', version='%(prog)s '+__version__+' - ' +
6✔
812
                        __copyright__+' - License: '+__license__)
813
    parser.add_argument('--wait_key', '-w', help='Wait for key to advance (debug)', action='store_true')
6✔
814
    parser.add_argument('--wait_start', help='Timeout to pcbnew start ['+str(WAIT_START)+']', type=int, default=WAIT_START)
6✔
815
    parser.add_argument('--time_out_scale', help='Timeout multiplier, affects most timeouts',
6✔
816
                        type=float, default=TIME_OUT_MULT)
817

818
    # abcfFmoOp
819
    export_parser = subparsers.add_parser('export', help='Export a schematic')
6✔
820
    export_parser.add_argument('--file_format', '-f', help='Export file format',
6✔
821
                               choices=['svg', 'pdf', 'ps', 'dxf', 'hpgl'], default='pdf')
822
    export_parser.add_argument('--all_pages', '-a', help='Plot all schematic pages in one file', action='store_true')
6✔
823
    export_parser.add_argument('--monochrome', '-m', help='Black and white output', action='store_true')
6✔
824
    export_parser.add_argument('--no_frame', '-F', help='No frame and title block', action='store_true')
6✔
825
    export_parser.add_argument('--output_name', '-o', nargs=1, help='Name of the output file')
6✔
826
    export_parser.add_argument('--color_theme', '-c', type=str, help='Name of the color theme [_builtin_default] (KiCad 6+)',
6✔
827
                               default='_builtin_default')
828
    export_parser.add_argument('--background_color', '-b', action='store_true', help='Use the background color (KiCad 6+)')
6✔
829
    export_parser.add_argument('--hpgl_origin', '-O', type=int, choices=range(4), default=0,
6✔
830
                               help='HPGL origin: 0 bottom left, 1 centered, 2 page fit, 3 content fit (KiCad 6+)')
831
    export_parser.add_argument('--hpgl_pen_size', '-p', type=float, help='HPGL pen size in mm [0.4826] (KiCad 6+)',
6✔
832
                               default=0.4826)
833

834
    erc_parser = subparsers.add_parser('run_erc', help='Run Electrical Rules Checker on a schematic')
6✔
835
    erc_parser.add_argument('--errors_filter', '-f', nargs=1, help='File with filters to exclude errors')
6✔
836
    erc_parser.add_argument('--output_name', '-o', nargs=1, help='Name of the output file')
6✔
837
    erc_parser.add_argument('--warnings_as_errors', '-w', help='Treat warnings as errors', action='store_true')
6✔
838
    erc_parser.add_argument('--grid', '-g', type=int, help='Grid size for the checks [mils] (KiCad 7)', default=50)
6✔
839

840
    netlist_parser = subparsers.add_parser('netlist', help='Create the netlist')
6✔
841
    netlist_parser.add_argument('--output_name', '-o', nargs=1, help='Name of the output file')
6✔
842

843
    bom_xml_parser = subparsers.add_parser('bom_xml', help='Create the BoM in XML format')
6✔
844
    bom_xml_parser.add_argument('--output_name', '-o', nargs=1, help='Name of the output file')
6✔
845

846
    args = parser.parse_args()
6✔
847
    logger = log.init(args.separate_info)
6✔
848
    # Set the verbosity
849
    log.set_level(logger, args.verbose)
6✔
850

851
    cfg = Config(logger, args.schematic, args)
6✔
852
    logger.debug('Setting timeout scale to {}'.format(cfg.time_out_scale))
6✔
853
    set_time_out_scale(cfg.time_out_scale)
6✔
854
    set_time_out_scale_f(cfg.time_out_scale)
6✔
855
    cfg.verbose = args.verbose
6✔
856
    cfg.video_name = args.command+'_eeschema_screencast.ogv'
6✔
857
    cfg.all_pages = getattr(args, 'all_pages', False)
6✔
858
    cfg.monochrome = getattr(args, 'monochrome', False)
6✔
859
    cfg.no_frame = getattr(args, 'no_frame', False)
6✔
860
    cfg.warnings_as_errors = getattr(args, 'warnings_as_errors', False)
6✔
861
    cfg.grid = getattr(args, 'grid', 50)
6✔
862
    cfg.wait_start = args.wait_start
6✔
863
    cfg.color_theme = getattr(args, 'color_theme', '_builtin_default')
6✔
864
    cfg.background_color = getattr(args, 'background_color', False)
6✔
865
    cfg.hpgl_origin = getattr(args, 'hpgl_origin', 0)
6✔
866
    cfg.hpgl_pen_size = getattr(args, 'hpgl_pen_size', 0.19)
6✔
867
    # Make sure the input file exists and has an extension
868
    check_input_file(cfg, NO_SCHEMATIC, WRONG_SCH_NAME)
6✔
869
    # Load filters
870
    if args.command == 'run_erc':
6✔
871
        if args.errors_filter:
6✔
872
            load_filters(cfg, args.errors_filter[0])
6✔
873
        if cfg.ki7 and cfg.kicad_version < KICAD_VERSION_7_0_3:
6✔
874
            # Bug introduced in 7.0.2. But I want to potentially cover 7.0.2.1
875
            logger.debug("Adding filters to workaround KiCad 7.0.2 simulation models bug")
×
876
            add_filter(cfg, 'simulation_model_issue', r"from symbol '#")
×
877

878
    memorize_project(cfg)
6✔
879
    # Create output dir if it doesn't exist
880
    output_dir = os.path.abspath(args.output_dir)+'/'
6✔
881
    cfg.video_dir = cfg.output_dir = output_dir
6✔
882
    #
883
    # Configure KiCad in a deterministic way
884
    #
885
    # Force english + UTF-8
886
    os.environ['LANG'] = get_en_locale(logger)
6✔
887
    # Ensure we have a config dir
888
    check_kicad_config_dir(cfg)
6✔
889
    # Back-up the current eeschema configuration
890
    cfg.conf_eeschema_bkp = backup_config('Eeschema', cfg.conf_eeschema, EESCHEMA_CFG_PRESENT, cfg)
6✔
891
    # Create a suitable configuration
892
    create_eeschema_config(cfg)
6✔
893
    # Back-up the current kicad_common configuration
894
    cfg.conf_kicad_bkp = backup_config('KiCad common', cfg.conf_kicad, KICAD_CFG_PRESENT, cfg)
6✔
895
    # Create a suitable configuration
896
    create_kicad_config(cfg)
6✔
897
    if cfg.kicad_version >= KICAD_VERSION_5_99:
6✔
898
        # KiCad 6 breaks menu short-cuts, but we can configure user hotkeys
899
        # Back-up the current user.hotkeys configuration
900
        cfg.conf_hotkeys_bkp = backup_config('User hotkeys', cfg.conf_hotkeys, USER_HOTKEYS_PRESENT, cfg)
4✔
901
        # Create a suitable configuration
902
        create_user_hotkeys(cfg)
4✔
903
    # Make sure the user has sym-lib-table
904
    check_lib_table(cfg.user_sym_lib_table, cfg.sys_sym_lib_table)
6✔
905
    #
906
    # Setup filenames
907
    #
908
    input_file_no_ext = os.path.splitext(os.path.basename(cfg.input_file))[0]
6✔
909
    cfg.input_file = os.path.abspath(cfg.input_file)
6✔
910
    cfg.output_file_no_ext = os.path.join(output_dir, input_file_no_ext)
6✔
911
    error_level = 0
6✔
912
    # Setup the output file name
913
    use_low_level_io = False
6✔
914
    if args.command == 'export':
6✔
915
        # Export
916
        ext = cfg.export_format
6✔
917
        if ext == 'hpgl':
6✔
918
            ext = 'plt'
6✔
919
        set_output_file(cfg, ext, args.output_name, can_name_out=False)
6✔
920
    elif args.command == 'netlist':
6✔
921
        # Netlist
922
        set_output_file(cfg, 'net', args.output_name)
6✔
923
    elif args.command == 'bom_xml':
6✔
924
        # BoM XML
925
        cfg.output_file = os.path.splitext(cfg.input_file)[0]+'.xml'
6✔
926
        cfg.wanted_output_file = name_with_out_dir(cfg, args.output_name[0]) if args.output_name is not None else None
6✔
927
        use_low_level_io = cfg.ki5
6✔
928
    elif args.command == 'run_erc':
6✔
929
        # Run ERC
930
        set_output_file(cfg, 'erc', args.output_name)
6✔
931
    remove_output(cfg)
6✔
932
    logger.debug('Output file: '+cfg.output_file)
6✔
933
    os.makedirs(os.path.dirname(cfg.output_file), exist_ok=True)
6✔
934
    if cfg.wanted_output_file:
6✔
935
        logger.debug('Wanted output file: '+cfg.wanted_output_file)
×
936
        os.makedirs(os.path.dirname(cfg.wanted_output_file), exist_ok=True)
×
937
    #
938
    # Do all the work
939
    #
940
    if can_be_done_using_cli(args.command, cfg):
6✔
941
        if args.command == 'netlist':
2✔
942
            eeschema_netlist_commands_cli(cfg)
2✔
943
        elif args.command == 'bom_xml':
2✔
944
            eeschema_bom_xml_commands_cli(cfg)
2✔
945
        else:  # export
946
            eeschema_plot_schematic_cli(cfg)
2✔
947
        do_retry = False
2✔
948
        cfg.use_interposer = cfg.enable_interposer = False
2✔
949
    else:
950
        # Interposer settings
951
        check_interposer(args, logger, cfg)
6✔
952
        flog_out, flog_err, cfg.flog_int = get_log_files(output_dir, 'eeschema', also_interposer=cfg.enable_interposer)
6✔
953
        if cfg.enable_interposer:
6✔
954
            flog_out = subprocess.PIPE
3✔
955
            atexit.register(dump_interposer_dialog, cfg)
3✔
956
        os.environ['KIAUTO_INTERPOSER_LOWLEVEL_IO'] = '1' if use_low_level_io else ''
6✔
957
        # When using the interposer inform the output file name using the environment
958
        setup_interposer_filename(cfg)
6✔
959
        for retry in range(3):
6✔
960
            do_retry = False
6✔
961
            problem = False
6✔
962
            with recorded_xvfb(cfg, retry):
6✔
963
                # Run Eeschema
964
                logger.debug('Starting '+cfg.eeschema)
6✔
965
                with PopenContext([cfg.eeschema, cfg.input_file], close_fds=True, start_new_session=True,
6✔
966
                                  bufsize=1, text=True, stderr=flog_err, stdout=flog_out) as eeschema_proc:
967
                    # Avoid patching our childs
968
                    os.environ['LD_PRELOAD'] = ''
6✔
969
                    cfg.eeschema_pid = eeschema_proc.pid
6✔
970
                    set_kicad_process(cfg, eeschema_proc.pid)
6✔
971
                    cfg.popen_obj = eeschema_proc
6✔
972
                    logger.debug('EEschema PID: '+str(eeschema_proc.pid))
6✔
973
                    start_queue(cfg)
6✔
974
                    # Wait for Eeschema
975
                    can_retry = wait_eeschema_start_by_msg(cfg) if cfg.use_interposer else wait_eeschema_start(cfg)
6✔
976
                    if eeschema_proc.poll() is not None:
6✔
977
                        logger.debug('Checking if the X server still there')
1✔
978
                        wait_xserver(cfg.output_dir, 0)
1✔
979
                        if can_retry:
1✔
980
                            do_retry = True
×
981
                        else:
982
                            problem = True
1✔
983
                    else:
984
                        if args.command == 'export':
6✔
985
                            # Export
986
                            eeschema_plot_schematic(cfg)
4✔
987
                        elif args.command == 'netlist':
6✔
988
                            # Netlist
989
                            eeschema_netlist_commands(cfg)
4✔
990
                        elif args.command == 'bom_xml':
6✔
991
                            # BoM XML
992
                            eeschema_bom_xml_commands(cfg)
4✔
993
                        elif args.command == 'run_erc':
6✔
994
                            # Run ERC
995
                            eeschema_run_erc_schematic(cfg)
6✔
996
                            errors, warnings = eeschema_parse_erc(cfg)
6✔
997
                            skip_err, skip_wrn = apply_filters(cfg, 'ERC error/s', 'ERC warning/s')
6✔
998
                            errors = errors-skip_err
6✔
999
                            warnings = warnings-skip_wrn
6✔
1000
                            if warnings > 0:
6✔
1001
                                logger.warning(str(warnings)+' ERC warnings detected')
6✔
1002
                                list_warnings(cfg)
6✔
1003
                            if errors > 0:
6✔
1004
                                logger.error(str(errors)+' ERC errors detected')
6✔
1005
                                list_errors(cfg)
6✔
1006
                                error_level = -errors
6✔
1007
                            else:
1008
                                logger.info('No errors')
6✔
1009
                        # Exit
1010
                        exit_eeschema(cfg)
6✔
1011
            if not do_retry:
6✔
1012
                break
6✔
1013
            logger.warning("Eeschema failed to start retrying ...")
×
1014
        if do_retry:
6✔
1015
            logger.error("Eeschema failed to start try with --time_out_scale")
×
1016
            error_level = EESCHEMA_ERROR
×
1017
        if problem:
6✔
1018
            logger.error("Eeschema failed to start or reported a fatal error")
1✔
1019
            error_level = EESCHEMA_ERROR
1✔
1020
    #
1021
    # Exit clean-up
1022
    #
1023
    # The following code is here only to make coverage tool properly meassure atexit code.
1024
    if cfg.wanted_output_file and cfg.wanted_output_file != cfg.output_file:
6✔
1025
        logger.debug('Changing KiCad name to user selected name {} -> {}'.format(cfg.output_file, cfg.wanted_output_file))
×
1026
        os.makedirs(output_dir, exist_ok=True)
×
1027
        shutil.move(cfg.output_file, cfg.wanted_output_file)
×
1028
    atexit.unregister(restore_project)
6✔
1029
    restore_project(cfg)
6✔
1030
    atexit.unregister(restore_config)
6✔
1031
    restore_config(cfg)
6✔
1032
    # We dump the dialog only on abnormal situations
1033
    if cfg.use_interposer:
6✔
1034
        logger.debug('Removing interposer dialog ({})'.format(cfg.flog_int.name))
3✔
1035
        atexit.unregister(dump_interposer_dialog)
3✔
1036
        cfg.flog_int.close()
3✔
1037
        os.remove(cfg.flog_int.name)
3✔
1038
    exit(error_level)
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