333 lines
10 KiB
Python
333 lines
10 KiB
Python
"""
|
|
Main Kiplot code
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
|
|
from . import plot_config as PCfg
|
|
from . import error
|
|
|
|
try:
|
|
import pcbnew
|
|
except ImportError:
|
|
logging.error("Failed to import pcbnew Python module."
|
|
" Do you need to add it to PYTHONPATH?")
|
|
raise
|
|
|
|
|
|
class PlotError(error.KiPlotError):
|
|
pass
|
|
|
|
|
|
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):
|
|
|
|
logging.debug("Starting plot of board {}".format(brd_file))
|
|
|
|
board = pcbnew.LoadBoard(brd_file)
|
|
|
|
logging.debug("Board loaded")
|
|
|
|
self._preflight_checks(board)
|
|
|
|
for op in self.cfg.outputs:
|
|
|
|
logging.debug("Processing output: {}".format(op.name))
|
|
|
|
# fresh plot controller
|
|
pc = pcbnew.PLOT_CONTROLLER(board)
|
|
|
|
self._configure_output_dir(pc, op)
|
|
|
|
if self._output_is_layer(op):
|
|
self._do_layer_plot(board, pc, op)
|
|
elif self._output_is_drill(op):
|
|
self._do_drill_plot(board, pc, op)
|
|
else:
|
|
raise PlotError("Don't know how to plot type {}"
|
|
.format(op.options.type))
|
|
|
|
pc.ClosePlot()
|
|
|
|
def _preflight_checks(self, board):
|
|
|
|
logging.debug("Preflight checks")
|
|
|
|
if self.cfg.check_zone_fills:
|
|
raise PlotError("Not sure if Python scripts can do zone check!")
|
|
|
|
if self.cfg.run_drc:
|
|
raise PlotError("Not sure if Python scripts can run DRC!")
|
|
|
|
def _output_is_layer(self, output):
|
|
|
|
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):
|
|
|
|
return output.options.type in [
|
|
PCfg.OutputOptions.EXCELLON,
|
|
PCfg.OutputOptions.GERB_DRILL,
|
|
]
|
|
|
|
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, plot_ctrl, output):
|
|
|
|
# set up plot options for the whole output
|
|
self._configure_plot_ctrl(plot_ctrl, output)
|
|
|
|
po = plot_ctrl.GetPlotOptions()
|
|
layer_cnt = board.GetCopperLayerCount()
|
|
|
|
# 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
|
|
logging.debug("Opening plot file for layer {} ({})"
|
|
.format(layer.layer, suffix))
|
|
plot_ctrl.OpenPlotfile(suffix, plot_format, desc)
|
|
|
|
logging.debug("Plotting layer {} to {}".format(
|
|
layer.layer, plot_ctrl.GetPlotFileName()))
|
|
plot_ctrl.PlotLayer()
|
|
|
|
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, plot_ctrl, output):
|
|
|
|
to = output.options.type_options
|
|
|
|
outdir = plot_ctrl.GetPlotOptions().GetOutputDirectory()
|
|
|
|
# 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:
|
|
raise error.PlotError("Can't make a writer for type {}"
|
|
.format(output.options.type))
|
|
|
|
gen_drill = True
|
|
gen_map = to.generate_map
|
|
gen_report = to.generate_report
|
|
|
|
if gen_drill:
|
|
logging.debug("Generating drill files in {}"
|
|
.format(outdir))
|
|
|
|
if gen_map:
|
|
drill_writer.SetMapFileFormat(to.map_options.type)
|
|
logging.debug("Generating drill map type {} in {}"
|
|
.format(to.map_options.type, outdir))
|
|
|
|
drill_writer.CreateDrillandMapFilesSet(outdir, gen_drill, gen_map)
|
|
|
|
if gen_report:
|
|
drill_report_file = os.path.join(outdir,
|
|
to.report_options.filename)
|
|
logging.debug("Generating drill report: {}"
|
|
.format(drill_report_file))
|
|
|
|
drill_writer.GenDrillReportFile(drill_report_file)
|
|
|
|
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 _configure_pdf_opts(self, po, output):
|
|
|
|
assert(output.options.type == PCfg.OutputOptions.PDF)
|
|
# pdf_opts = output.options.type_options
|
|
|
|
def _configure_svg_opts(self, po, output):
|
|
|
|
assert(output.options.type == PCfg.OutputOptions.SVG)
|
|
# pdf_opts = output.options.type_options
|
|
|
|
def _configure_output_dir(self, plot_ctrl, output):
|
|
|
|
po = plot_ctrl.GetPlotOptions()
|
|
|
|
# outdir is a combination of the config and output
|
|
outdir = os.path.join(self.cfg.outdir, output.outdir)
|
|
|
|
logging.debug("Output destination: {}".format(outdir))
|
|
|
|
po.SetOutputDirectory(outdir)
|
|
|
|
def _configure_plot_ctrl(self, plot_ctrl, output):
|
|
|
|
logging.debug("Configuring plot controller for output")
|
|
|
|
po = plot_ctrl.GetPlotOptions()
|
|
|
|
opts = output.options.type_options
|
|
|
|
po.SetLineWidth(opts.line_width)
|
|
|
|
po.SetAutoScale(opts.auto_scale)
|
|
po.SetScale(opts.scaling)
|
|
|
|
print opts.mirror_plot
|
|
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)
|
|
|
|
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.SVG:
|
|
self._configure_svg_opts(po, output)
|
|
elif output.options.type == PCfg.OutputOptions.PDF:
|
|
self._configure_pdf_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)
|