KiBot/kiplot/kiplot.py

193 lines
5.9 KiB
Python

"""
Main Kiplot code
"""
import os
import re
from sys import exit
from shutil import which
from subprocess import (run, PIPE)
from distutils.version import StrictVersion
from .misc import (PLOT_ERROR, NO_PCBNEW_MODULE, MISSING_TOOL, CMD_EESCHEMA_DO, URL_EESCHEMA_DO, NO_SCH_FILE, CORRUPTED_PCB,
EXIT_BAD_ARGS)
from .error import (PlotError)
from .pre_base import BasePreFlight
from . import log
logger = log.get_logger(__name__)
try:
import pcbnew
except ImportError: # pragma: no cover
log.init(False, False)
logger.error("Failed to import pcbnew Python module."
" Is KiCad installed?"
" Do you need to add it to PYTHONPATH?")
exit(NO_PCBNEW_MODULE)
class GS(object):
"""
Class to keep the global settings.
Is a static class, just a placeholder for some global variables.
"""
pcb_file = None
out_dir = None
filter_file = None
debug_enabled = False
pcb_layers = None
class Layer(object):
""" A layer description """
def __init__(self, id, is_inner, name):
self.id = id
self.is_inner = is_inner
self.name = name
self.suffix = None
self.desc = None
def set_extra(self, suffix, desc):
self.suffix = suffix
self.desc = desc
def __str__(self):
return "{} ({} '{}' {})".format(self.name, self.id, self.desc, self.suffix)
def load_pcb_layers():
""" Load layer names from the PCB """
GS.pcb_layers = {}
with open(GS.pcb_file, "r") as pcb_file:
collect_layers = False
for line in pcb_file:
if collect_layers:
z = re.match(r'\s+\((\d+)\s+(\S+)', line)
if z:
res = z.groups()
# print(res[1]+'->'+res[0])
GS.pcb_layers[res[1]] = int(res[0])
else:
if re.search(r'^\s+\)$', line):
collect_layers = False
break
else:
if re.search(r'\s+\(layers', line):
collect_layers = True
def get_layer_id_from_pcb(name):
if GS.pcb_layers is None:
load_pcb_layers()
return GS.pcb_layers.get(name)
def check_version(command, version):
cmd = [command, '--version']
logger.debug('Running: '+str(cmd))
result = run(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True)
z = re.match(command + r' (\d+\.\d+\.\d+)', result.stdout)
if not z:
logger.error('Unable to determine ' + command + ' version:\n' +
result.stdout)
exit(MISSING_TOOL)
res = z.groups()
if StrictVersion(res[0]) < StrictVersion(version):
logger.error('Wrong version for `'+command+'` ('+res[0]+'), must be ' +
version+' or newer.')
exit(MISSING_TOOL)
def check_script(cmd, url, version=None):
if which(cmd) is None:
logger.error('No `'+cmd+'` command found.\n'
'Please install it, visit: '+url)
exit(MISSING_TOOL)
if version is not None:
check_version(cmd, version)
def check_eeschema_do():
check_script(CMD_EESCHEMA_DO, URL_EESCHEMA_DO, '1.4.0')
if not GS.sch_file:
logger.error('Missing schematic file')
exit(NO_SCH_FILE)
def load_board():
try:
board = pcbnew.LoadBoard(GS.pcb_file)
if BasePreFlight.get_option('check_zone_fills'):
pcbnew.ZONE_FILLER(board).Fill(board.Zones())
except OSError as e:
logger.error('Error loading PCB file. Currupted?')
logger.error(e)
exit(CORRUPTED_PCB)
assert board is not None
logger.debug("Board loaded")
return board
def preflight_checks(skip_pre):
logger.debug("Preflight checks")
if skip_pre is not None:
if skip_pre == 'all':
logger.debug("Skipping all pre-flight actions")
return
else:
skip_list = skip_pre.split(',')
for skip in skip_list:
if skip == 'all':
logger.error('All can\'t be part of a list of actions '
'to skip. Use `--skip all`')
exit(EXIT_BAD_ARGS)
else:
if not BasePreFlight.is_registered(skip):
logger.error('Unknown preflight `{}`'.format(skip))
exit(EXIT_BAD_ARGS)
o_pre = BasePreFlight.get_preflight(skip)
if not o_pre:
logger.warning('`{}` preflight is not in use, no need to skip'.format(skip))
else:
logger.debug('Skipping `{}`'.format(skip))
o_pre.disable()
BasePreFlight.run_enabled()
def get_output_dir(o_dir):
# outdir is a combination of the config and output
outdir = os.path.join(GS.out_dir, o_dir)
# Create directory if needed
logger.debug("Output destination: {}".format(outdir))
if not os.path.exists(outdir):
os.makedirs(outdir)
return outdir
def generate_outputs(outputs, target, invert, skip_pre):
logger.debug("Starting outputs for board {}".format(GS.pcb_file))
preflight_checks(skip_pre)
# Check if all must be skipped
n = len(target)
if n == 0 and invert:
# Skip all targets
logger.debug('Skipping all outputs')
return
# Generate outputs
board = None
for out in outputs:
if (n == 0) or ((out.get_name() in target) ^ invert):
logger.info('- '+str(out))
# Should we load the PCB?
if out.is_pcb() and (board is None):
board = load_board()
try:
out.run(get_output_dir(out.get_outdir()), board)
except PlotError as e:
logger.error("In output `"+str(out)+"`: "+str(e))
exit(PLOT_ERROR)
else:
logger.debug('Skipping `%s` output', str(out))