KiBot/kiplot/kiplot.py

828 lines
30 KiB
Python

"""
Main Kiplot code
"""
from datetime import datetime
import os
from sys import exit
import operator
from shutil import which
from glob import glob
from subprocess import (call, run, PIPE, check_output, CalledProcessError,
STDOUT)
import logging
from distutils.version import StrictVersion
import re
from . import plot_config as PCfg
from . import error
from . import log
from . import misc
logger = log.get_logger(__name__)
try:
import pcbnew
from pcbnew import GERBER_JOBFILE_WRITER
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(misc.NO_PCBNEW_MODULE)
class PlotError(error.KiPlotError):
pass
def plot_error(msg):
logger.error(msg)
exit(misc.PLOT_ERROR)
def level_debug():
""" Determine if we are in debug mode """
return logger.getEffectiveLevel() <= logging.DEBUG
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(misc.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(misc.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(misc.MISSING_TOOL)
if version is not None:
check_version(cmd, version)
def check_eeschema_do(file):
check_script(misc.CMD_EESCHEMA_DO, misc.URL_EESCHEMA_DO, '1.4.0')
sch_file = os.path.splitext(file)[0] + '.sch'
if not os.path.isfile(sch_file):
logger.error('Missing schematic file: ' + sch_file)
exit(misc.NO_SCH_FILE)
return sch_file
class Plotter(object):
"""
Main Plotter class - this is what will perform the plotting
"""
def __init__(self, cfg):
self.cfg = cfg
def plot(self, brd_file, target, invert, skip_pre):
logger.debug("Starting plot of board {}".format(brd_file))
self._preflight_checks(brd_file, skip_pre)
n = len(target)
if n == 0 and invert:
# Skip all targets
logger.debug('Skipping all outputs')
return
board = None
for op in self.cfg.outputs:
if (n == 0) or ((op.name in target) ^ invert):
logger.info('- %s (%s) [%s]' % (op.description, op.name, op.options.type))
output_dir = self._get_output_dir(op)
if (not self._output_is_schematic(op)) and (board is None):
board = self._load_board(brd_file)
try:
if self._output_is_layer(op):
self._do_layer_plot(board, output_dir, op, brd_file)
elif self._output_is_drill(op):
self._do_drill_plot(board, output_dir, op)
elif self._output_is_position(op):
self._do_position_plot(board, output_dir, op)
elif self._output_is_bom(op):
self._do_bom(output_dir, op, brd_file)
elif self._output_is_sch_print(op):
self._do_sch_print(output_dir, op, brd_file)
elif self._output_is_pcb_print(op):
self._do_pcb_print(board, output_dir, op, brd_file)
elif self._output_is_step(op):
self._do_step(output_dir, op, brd_file)
else: # pragma no cover
# We shouldn't get here, means the above if is incomplete
plot_error("Don't know how to plot type "+op.options.type)
except PlotError as e:
plot_error("In section '"+op.name+"' ("+op.options.type+"): "+str(e))
else:
logger.debug('Skipping %s output', op.name)
def _load_board(self, brd_file):
try:
board = pcbnew.LoadBoard(brd_file)
if self.cfg.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(misc.CORRUPTED_PCB)
assert board is not None
logger.debug("Board loaded")
return board
def _preflight_checks(self, brd_file, skip_pre):
logger.debug("Preflight checks")
if skip_pre is not None:
if skip_pre[0] == 'all':
logger.debug("Skipping all pre-flight actions")
return
else:
skip_list = skip_pre[0].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(misc.EXIT_BAD_ARGS)
elif skip == 'run_drc':
self.cfg.run_drc = False
logger.debug('Skipping run_drc')
elif skip == 'update_xml':
self.cfg.update_xml = False
logger.debug('Skipping update_xml')
elif skip == 'run_erc':
self.cfg.run_erc = False
logger.debug('Skipping run_erc')
elif skip == 'check_zone_fills':
self.cfg.check_zone_fills = False
logger.debug('Skipping run_erc')
else:
logger.error('Unknown action to skip: '+skip)
exit(misc.EXIT_BAD_ARGS)
# Create the filters file
filter_file = None
if (self.cfg.run_erc or self.cfg.run_drc) and self.cfg.filters:
filter_file = os.path.join(self.cfg.outdir, 'kiplot_errors.filter')
with open(filter_file, 'w') as f:
f.write(self.cfg.filters)
if self.cfg.run_erc:
self._run_erc(brd_file, filter_file)
if self.cfg.update_xml:
self._update_xml(brd_file)
if self.cfg.run_drc:
self._run_drc(brd_file, self.cfg.ignore_unconnected, filter_file)
def _run_erc(self, brd_file, filter_file):
sch_file = check_eeschema_do(brd_file)
cmd = [misc.CMD_EESCHEMA_DO, 'run_erc']
if filter_file:
cmd.extend(['-f', filter_file])
cmd.extend([sch_file, self.cfg.outdir])
# If we are in verbose mode enable debug in the child
if level_debug():
cmd.insert(1, '-vv')
cmd.insert(1, '-r')
logger.info('- Running the ERC')
logger.debug('Executing: '+str(cmd))
ret = call(cmd)
if ret:
if ret < 0:
logger.error('ERC errors: %d', -ret)
else:
logger.error('ERC returned %d', ret)
exit(misc.ERC_ERROR)
def _update_xml(self, brd_file):
sch_file = check_eeschema_do(brd_file)
cmd = [misc.CMD_EESCHEMA_DO, 'bom_xml', sch_file, self.cfg.outdir]
# If we are in verbose mode enable debug in the child
if level_debug():
cmd.insert(1, '-vv')
cmd.insert(1, '-r')
logger.info('- Updating BoM in XML format')
logger.debug('Executing: '+str(cmd))
ret = call(cmd)
if ret:
logger.error('Failed to update the BoM, error %d', ret)
exit(misc.BOM_ERROR)
def _run_drc(self, brd_file, ignore_unconnected, filter_file):
check_script(misc.CMD_PCBNEW_RUN_DRC, misc.URL_PCBNEW_RUN_DRC, '1.4.0')
cmd = [misc.CMD_PCBNEW_RUN_DRC, 'run_drc']
if filter_file:
cmd.extend(['-f', filter_file])
cmd.extend([brd_file, self.cfg.outdir])
# If we are in verbose mode enable debug in the child
if level_debug():
cmd.insert(1, '-vv')
cmd.insert(1, '-r')
if ignore_unconnected:
cmd.insert(1, '-i')
logger.info('- Running the DRC')
logger.debug('Executing: '+str(cmd))
ret = call(cmd)
if ret:
if ret < 0:
logger.error('DRC errors: %d', -ret)
else:
logger.error('DRC returned %d', ret)
exit(misc.DRC_ERROR)
def _output_is_layer(self, output):
""" All the formats for 'PCB|Plot' """
return output.options.type in [
PCfg.OutputOptions.GERBER,
PCfg.OutputOptions.POSTSCRIPT,
PCfg.OutputOptions.DXF,
PCfg.OutputOptions.SVG,
PCfg.OutputOptions.PDF,
PCfg.OutputOptions.HPGL,
]
def _output_is_drill(self, output):
""" All the drill formats' """
return output.options.type in [
PCfg.OutputOptions.EXCELLON,
PCfg.OutputOptions.GERB_DRILL,
]
def _output_is_position(self, output):
return output.options.type == PCfg.OutputOptions.POSITION
def _output_is_sch_print(self, output):
return output.options.type == PCfg.OutputOptions.PDF_SCH_PRINT
def _output_is_pcb_print(self, output):
return output.options.type == PCfg.OutputOptions.PDF_PCB_PRINT
def _output_is_step(self, output):
return output.options.type == PCfg.OutputOptions.STEP
def _output_is_bom(self, output):
return output.options.type in [
PCfg.OutputOptions.KIBOM,
PCfg.OutputOptions.IBOM,
]
def _output_is_schematic(self, output):
""" All the outputs involving the SCH and not the PCB """
return self._output_is_bom(output) or self._output_is_sch_print(output)
def _get_layer_plot_format(self, output):
"""
Gets the Pcbnew plot format for a given KiPlot output type
"""
mapping = {
PCfg.OutputOptions.GERBER: pcbnew.PLOT_FORMAT_GERBER,
PCfg.OutputOptions.POSTSCRIPT: pcbnew.PLOT_FORMAT_POST,
PCfg.OutputOptions.HPGL: pcbnew.PLOT_FORMAT_HPGL,
PCfg.OutputOptions.PDF: pcbnew.PLOT_FORMAT_PDF,
PCfg.OutputOptions.DXF: pcbnew.PLOT_FORMAT_DXF,
PCfg.OutputOptions.SVG: pcbnew.PLOT_FORMAT_SVG,
}
try:
return mapping[output.options.type]
except KeyError:
pass
raise ValueError("Don't know how to translate plot type: {}"
.format(output.options.type))
def _do_layer_plot(self, board, output_dir, output, file_name):
# fresh plot controller
plot_ctrl = pcbnew.PLOT_CONTROLLER(board)
# set up plot options for the whole output
self._configure_plot_ctrl(plot_ctrl, output, output_dir)
po = plot_ctrl.GetPlotOptions()
layer_cnt = board.GetCopperLayerCount()
# Gerber Job files aren't automagically created
# We need to assist KiCad
create_job = po.GetCreateGerberJobFile()
if create_job:
jobfile_writer = GERBER_JOBFILE_WRITER(board)
plot_ctrl.SetColorMode(True)
# plot every layer in the output
for l in output.layers:
layer = l.layer
suffix = l.suffix
desc = l.desc
# for inner layers, we can now check if the layer exists
if layer.is_inner:
if layer.layer < 1 or layer.layer >= layer_cnt - 1:
raise PlotError(
"Inner layer {} is not valid for this board"
.format(layer.layer))
# Set current layer
plot_ctrl.SetLayer(layer.layer)
# Skipping NPTH is controlled by whether or not this is
# a copper layer
is_cu = pcbnew.IsCopperLayer(layer.layer)
po.SetSkipPlotNPTH_Pads(is_cu)
plot_format = self._get_layer_plot_format(output)
# Plot single layer to file
logger.debug("Opening plot file for layer {} ({}) {} {}"
.format(layer.layer, suffix, plot_format, desc))
if not plot_ctrl.OpenPlotfile(suffix, plot_format, desc):
plot_error("OpenPlotfile failed!")
logger.debug("Plotting layer {} to {}".format(
layer.layer, plot_ctrl.GetPlotFileName()))
plot_ctrl.PlotLayer()
plot_ctrl.ClosePlot()
if create_job:
jobfile_writer.AddGbrFile(layer.layer, os.path.basename(
plot_ctrl.GetPlotFileName()))
if create_job:
base_fn = os.path.join(
os.path.dirname(plot_ctrl.GetPlotFileName()),
os.path.basename(file_name))
base_fn = os.path.splitext(base_fn)[0]
job_fn = base_fn+'-job.gbrjob'
jobfile_writer.CreateJobFile(job_fn)
def _configure_excellon_drill_writer(self, board, offset, options):
drill_writer = pcbnew.EXCELLON_WRITER(board)
to = options.type_options
mirror_y = to.mirror_y_axis
minimal_header = to.minimal_header
merge_npth = to.pth_and_npth_single_file
zeros_format = pcbnew.EXCELLON_WRITER.DECIMAL_FORMAT
drill_writer.SetOptions(mirror_y, minimal_header, offset, merge_npth)
drill_writer.SetFormat(to.metric_units, zeros_format)
return drill_writer
def _configure_gerber_drill_writer(self, board, offset, options):
drill_writer = pcbnew.GERBER_WRITER(board)
# hard coded in UI?
drill_writer.SetFormat(5)
drill_writer.SetOptions(offset)
return drill_writer
def _do_drill_plot(self, board, output_dir, output):
to = output.options.type_options
# dialog_gendrill.cpp:357
if to.use_aux_axis_as_origin:
offset = board.GetAuxOrigin()
else:
offset = pcbnew.wxPoint(0, 0)
if output.options.type == PCfg.OutputOptions.EXCELLON:
drill_writer = self._configure_excellon_drill_writer(
board, offset, output.options)
elif output.options.type == PCfg.OutputOptions.GERB_DRILL:
drill_writer = self._configure_gerber_drill_writer(
board, offset, output.options)
else:
plot_error("Can't make a writer for type "+output.options.type)
gen_drill = True
gen_map = to.generate_map
gen_report = to.generate_report
if gen_drill:
logger.debug("Generating drill files in "+output_dir)
if gen_map:
drill_writer.SetMapFileFormat(to.map_options.type)
logger.debug("Generating drill map type {} in {}"
.format(to.map_options.type, output_dir))
drill_writer.CreateDrillandMapFilesSet(output_dir, gen_drill, gen_map)
if gen_report:
drill_report_file = os.path.join(output_dir,
to.report_options.filename)
logger.debug("Generating drill report: "+drill_report_file)
drill_writer.GenDrillReportFile(drill_report_file)
def _do_position_plot_ascii(self, board, output_dir, output, columns,
modulesStr, maxSizes):
to = output.options.type_options
name = os.path.splitext(os.path.basename(board.GetFileName()))[0]
topf = None
botf = None
bothf = None
if to.separate_files_for_front_and_back:
topf = open(os.path.join(output_dir, "{}-top.pos".format(name)), 'w')
botf = open(os.path.join(output_dir, "{}-bottom.pos".format(name)),
'w')
else:
bothf = open(os.path.join(output_dir, "{}-both.pos").format(name), 'w')
files = [f for f in [topf, botf, bothf] if f is not None]
for f in files:
f.write('### Module positions - created on {} ###\n'.format(
datetime.now().strftime("%a %d %b %Y %X %Z")
))
f.write('### Printed by KiPlot\n')
unit = {'millimeters': 'mm',
'inches': 'in'}[to.units]
f.write('## Unit = {}, Angle = deg.\n'.format(unit))
if topf is not None:
topf.write('## Side : top\n')
if botf is not None:
botf.write('## Side : bottom\n')
if bothf is not None:
bothf.write('## Side : both\n')
for f in files:
f.write('# ')
for idx, col in enumerate(columns):
if idx > 0:
f.write(" ")
f.write("{0: <{width}}".format(col, width=maxSizes[idx]))
f.write('\n')
# Account for the "# " at the start of the comment column
maxSizes[0] = maxSizes[0] + 2
for m in modulesStr:
fle = bothf
if fle is None:
if m[-1] == "top":
fle = topf
else:
fle = botf
for idx, col in enumerate(m):
if idx > 0:
fle.write(" ")
fle.write("{0: <{width}}".format(col, width=maxSizes[idx]))
fle.write("\n")
for f in files:
f.write("## End\n")
if topf is not None:
topf.close()
if botf is not None:
botf.close()
if bothf is not None:
bothf.close()
def _do_position_plot_csv(self, board, output_dir, output, columns,
modulesStr):
to = output.options.type_options
name = os.path.splitext(os.path.basename(board.GetFileName()))[0]
topf = None
botf = None
bothf = None
if to.separate_files_for_front_and_back:
topf = open(os.path.join(output_dir, "{}-top-pos.csv".format(name)),
'w')
botf = open(os.path.join(output_dir, "{}-bottom-pos.csv".format(name)),
'w')
else:
bothf = open(os.path.join(output_dir, "{}-both-pos.csv").format(name),
'w')
files = [f for f in [topf, botf, bothf] if f is not None]
for f in files:
f.write(",".join(columns))
f.write("\n")
for m in modulesStr:
fle = bothf
if fle is None:
if m[-1] == "top":
fle = topf
else:
fle = botf
fle.write(",".join('"{}"'.format(e) for e in m))
fle.write("\n")
if topf is not None:
topf.close()
if botf is not None:
botf.close()
if bothf is not None:
bothf.close()
def _do_position_plot(self, board, output_dir, output):
to = output.options.type_options
columns = ["Ref", "Val", "Package", "PosX", "PosY", "Rot", "Side"]
colcount = len(columns)
# Note: the parser already checked the units are milimeters or inches
conv = 1.0
if to.units == 'millimeters':
conv = 1.0 / pcbnew.IU_PER_MM
else: # to.units == 'inches':
conv = 0.001 / pcbnew.IU_PER_MILS
# Format all strings
modules = []
for m in sorted(board.GetModules(),
key=operator.methodcaller('GetReference')):
if (to.only_smd and m.GetAttributes() == 1) or not to.only_smd:
center = m.GetCenter()
# See PLACE_FILE_EXPORTER::GenPositionData() in
# export_footprints_placefile.cpp for C++ version of this.
modules.append([
"{}".format(m.GetReference()),
"{}".format(m.GetValue()),
"{}".format(m.GetFPID().GetLibItemName()),
"{:.4f}".format(center.x * conv),
"{:.4f}".format(-center.y * conv),
"{:.4f}".format(m.GetOrientationDegrees()),
"{}".format("bottom" if m.IsFlipped() else "top")
])
# Find max width for all columns
maxlengths = [0] * colcount
for row in range(len(modules)):
for col in range(colcount):
maxlengths[col] = max(maxlengths[col], len(modules[row][col]))
# Note: the parser already checked the format is ASCII or CSV
if to.format.lower() == 'ascii':
self._do_position_plot_ascii(board, output_dir, output, columns,
modules, maxlengths)
else: # if to.format.lower() == 'csv':
self._do_position_plot_csv(board, output_dir, output, columns,
modules)
def _do_sch_print(self, output_dir, output, brd_file):
sch_file = check_eeschema_do(brd_file)
cmd = [misc.CMD_EESCHEMA_DO, 'export', '--all_pages',
'--file_format', 'pdf', sch_file, output_dir]
if level_debug():
cmd.insert(1, '-vv')
cmd.insert(1, '-r')
logger.debug('Executing: '+str(cmd))
ret = call(cmd)
if ret:
logger.error(misc.CMD_EESCHEMA_DO+' returned %d', ret)
exit(misc.PDF_SCH_PRINT)
to = output.options.type_options
if to.output:
cur = os.path.abspath(os.path.join(output_dir, os.path.splitext(os.path.basename(brd_file))[0]) + '.pdf')
new = os.path.abspath(os.path.join(output_dir, to.output))
logger.debug('Moving '+cur+' -> '+new)
os.rename(cur, new)
def _do_step(self, output_dir, op, brd_file):
to = op.options.type_options
# Output file name
output = to.output
if output is None:
output = os.path.splitext(os.path.basename(brd_file))[0]+'.step'
output = os.path.abspath(os.path.join(output_dir, output))
# Make units explicit
if to.metric_units:
units = 'mm'
else:
units = 'in'
# Base command with overwrite
cmd = [misc.KICAD2STEP, '-o', output, '-f']
# Add user options
if to.no_virtual:
cmd.append('--no-virtual')
if to.min_distance is not None:
cmd.extend(['--min-distance', "{}{}".format(to.min_distance, units)])
if to.origin == 'drill':
cmd.append('--drill-origin')
elif to.origin == 'grid':
cmd.append('--grid-origin')
else:
cmd.extend(['--user-origin', "{}{}".format(to.origin.replace(',', 'x'), units)])
# The board
cmd.append(brd_file)
# Execute and inform is successful
logger.debug('Executing: '+str(cmd))
try:
cmd_output = check_output(cmd, stderr=STDOUT)
except CalledProcessError as e: # pragma: no cover
# Current kicad2step always returns 0!!!!
# This is why I'm excluding it from coverage
logger.error('Failed to create Step file, error %d', e.returncode)
if e.output:
logger.debug('Output from command: '+e.output.decode())
exit(misc.KICAD2STEP_ERR)
logger.debug('Output from command:\n'+cmd_output.decode())
def _do_pcb_print(self, board, output_dir, output, brd_file):
check_script(misc.CMD_PCBNEW_PRINT_LAYERS,
misc.URL_PCBNEW_PRINT_LAYERS, '1.4.1')
to = output.options.type_options
# Verify the inner layers
layer_cnt = board.GetCopperLayerCount()
for l in output.layers:
layer = l.layer
# for inner layers, we can now check if the layer exists
if layer.is_inner:
if layer.layer < 1 or layer.layer >= layer_cnt - 1:
raise PlotError(
"Inner layer {} is not valid for this board"
.format(layer.layer))
cmd = [misc.CMD_PCBNEW_PRINT_LAYERS, 'export',
'--output_name', to.output_name]
if self.cfg.check_zone_fills:
cmd.append('-f')
cmd.extend([brd_file, output_dir])
if level_debug():
cmd.insert(1, '-vv')
cmd.insert(1, '-r')
# Add the layers
for l in output.layers:
cmd.append(l.layer.name)
logger.debug('Executing: '+str(cmd))
ret = call(cmd)
if ret:
logger.error(misc.CMD_PCBNEW_PRINT_LAYERS+' returned %d', ret)
exit(misc.PDF_PCB_PRINT)
def _do_bom(self, output_dir, output, brd_file):
if output.options.type == 'kibom':
self._do_kibom(output_dir, output, brd_file)
else:
self._do_ibom(output_dir, output, brd_file)
def _do_kibom(self, output_dir, output, brd_file):
check_script(misc.CMD_KIBOM, misc.URL_KIBOM)
to = output.options.type_options
format = to.format.lower()
prj = os.path.splitext(os.path.relpath(brd_file))[0]
logger.debug('Doing BoM, format '+format+' prj: '+prj)
cmd = [misc.CMD_KIBOM, prj+'.xml',
os.path.join(output_dir, os.path.basename(prj))+'.'+format]
logger.debug('Running: '+str(cmd))
try:
cmd_output = check_output(cmd, stderr=STDOUT)
except CalledProcessError as e:
logger.error('Failed to create BoM, error %d', e.returncode)
if e.output:
logger.debug('Output from command: '+e.output.decode())
exit(misc.BOM_ERROR)
for f in glob(os.path.join(output_dir, prj)+'*.tmp'):
os.remove(f)
logger.debug('Output from command:\n'+cmd_output.decode())
def _do_ibom(self, output_dir, output, brd_file):
check_script(misc.CMD_IBOM, misc.URL_IBOM)
prj = os.path.splitext(os.path.relpath(brd_file))[0]
logger.debug('Doing Interactive BoM, prj: '+prj)
cmd = [misc.CMD_IBOM, brd_file,
'--dest-dir', output_dir,
'--no-browser', ]
to = output.options.type_options
if to.blacklist:
cmd.append('--blacklist')
cmd.append(to.blacklist)
if to.name_format:
cmd.append('--name-format')
cmd.append(to.name_format)
logger.debug('Running: '+str(cmd))
try:
cmd_output = check_output(cmd, stderr=STDOUT)
except CalledProcessError as e:
logger.error('Failed to create BoM, error %d', e.returncode)
if e.output:
logger.debug('Output from command: '+e.output.decode())
exit(misc.BOM_ERROR)
logger.debug('Output from command:\n'+cmd_output.decode()+'\n')
def _configure_gerber_opts(self, po, output):
# true if gerber
po.SetUseGerberAttributes(True)
assert(output.options.type == PCfg.OutputOptions.GERBER)
gerb_opts = output.options.type_options
po.SetSubtractMaskFromSilk(gerb_opts.subtract_mask_from_silk)
po.SetUseGerberProtelExtensions(gerb_opts.use_protel_extensions)
po.SetGerberPrecision(gerb_opts.gerber_precision)
po.SetCreateGerberJobFile(gerb_opts.create_gerber_job_file)
po.SetUseGerberAttributes(gerb_opts.use_gerber_x2_attributes)
po.SetIncludeGerberNetlistInfo(gerb_opts.use_gerber_net_attributes)
def _configure_hpgl_opts(self, po, output):
assert(output.options.type == PCfg.OutputOptions.HPGL)
hpgl_opts = output.options.type_options
po.SetHPGLPenDiameter(hpgl_opts.pen_width)
def _configure_ps_opts(self, po, output):
assert(output.options.type == PCfg.OutputOptions.POSTSCRIPT)
ps_opts = output.options.type_options
po.SetWidthAdjust(ps_opts.width_adjust)
po.SetFineScaleAdjustX(ps_opts.scale_adjust_x)
po.SetFineScaleAdjustX(ps_opts.scale_adjust_y)
po.SetA4Output(ps_opts.a4_output)
def _configure_dxf_opts(self, po, output):
assert(output.options.type == PCfg.OutputOptions.DXF)
dxf_opts = output.options.type_options
po.SetDXFPlotPolygonMode(dxf_opts.polygon_mode)
def _get_output_dir(self, output):
# outdir is a combination of the config and output
outdir = os.path.join(self.cfg.outdir, output.outdir)
logger.debug("Output destination: {}".format(outdir))
if not os.path.exists(outdir):
os.makedirs(outdir)
return outdir
def _configure_plot_ctrl(self, plot_ctrl, output, output_dir):
logger.debug("Configuring plot controller for output")
po = plot_ctrl.GetPlotOptions()
po.SetOutputDirectory(output_dir)
opts = output.options.type_options
po.SetLineWidth(opts.line_width)
po.SetAutoScale(opts.auto_scale)
po.SetScale(opts.scaling)
po.SetMirror(opts.mirror_plot)
po.SetNegative(opts.negative_plot)
po.SetPlotFrameRef(opts.plot_sheet_reference)
po.SetPlotReference(opts.plot_footprint_refs)
po.SetPlotValue(opts.plot_footprint_values)
po.SetPlotInvisibleText(opts.force_plot_invisible_refs_vals)
po.SetExcludeEdgeLayer(opts.exclude_edge_layer)
po.SetPlotPadsOnSilkLayer(not opts.exclude_pads_from_silkscreen)
po.SetUseAuxOrigin(opts.use_aux_axis_as_origin)
po.SetPlotViaOnMaskLayer(not opts.tent_vias)
# in general, false, but gerber will set it back later
po.SetUseGerberAttributes(False)
# Only useful for gerber outputs
po.SetCreateGerberJobFile(False)
if output.options.type == PCfg.OutputOptions.GERBER:
self._configure_gerber_opts(po, output)
elif output.options.type == PCfg.OutputOptions.POSTSCRIPT:
self._configure_ps_opts(po, output)
elif output.options.type == PCfg.OutputOptions.DXF:
self._configure_dxf_opts(po, output)
elif output.options.type == PCfg.OutputOptions.HPGL:
self._configure_hpgl_opts(po, output)
po.SetDrillMarksType(opts.drill_marks)
# We'll come back to this on a per-layer basis
po.SetSkipPlotNPTH_Pads(False)