import os from pcbnew import (GERBER_JOBFILE_WRITER, PCB_PLOT_PARAMS, FromMM, PLOT_CONTROLLER, IsCopperLayer, SKETCH) from .out_base import (BaseOutput) from .error import (PlotError, KiPlotConfigurationError) from .kiplot import (GS) from kiplot.macros import macros, document # noqa: F401 from . import log logger = log.get_logger(__name__) AUTO_SCALE = 0 class AnyLayer(BaseOutput): def __init__(self, name, type, description): super(AnyLayer, self).__init__(name, type, description) # Options with document: self.exclude_edge_layer = True """ do not include the PCB edge layer """ self.exclude_pads_from_silkscreen = False """ do not plot the component pads in the silk screen """ self.plot_sheet_reference = False """ currently without effect """ self.plot_footprint_refs = True """ include the footprint references """ self.plot_footprint_values = True """ include the footprint values """ self.force_plot_invisible_refs_vals = False """ include references and values even when they are marked as invisible """ self.tent_vias = True """ cover the vias """ # pragma: no cover # Mappings to KiCad values self._drill_marks_map = { 'none': PCB_PLOT_PARAMS.NO_DRILL_SHAPE, 'small': PCB_PLOT_PARAMS.SMALL_DRILL_SHAPE, 'full': PCB_PLOT_PARAMS.FULL_DRILL_SHAPE, } def config(self, outdir, options, layers): super().config(outdir, options, layers) # We need layers if not self._layers: raise KiPlotConfigurationError("Missing `layers` list") def _configure_plot_ctrl(self, po, output_dir): logger.debug("Configuring plot controller for output") po.SetOutputDirectory(output_dir) po.SetLineWidth(FromMM(self.get_line_width())) # Scaling/Autoscale scaling = self.get_scaling() if scaling == AUTO_SCALE: po.SetAutoScale(True) po.SetScale(1) else: po.SetAutoScale(False) po.SetScale(scaling) po.SetMirror(self.get_mirror_plot()) po.SetNegative(self.get_negative_plot()) po.SetPlotFrameRef(self.plot_sheet_reference) po.SetPlotReference(self.plot_footprint_refs) po.SetPlotValue(self.plot_footprint_values) po.SetPlotInvisibleText(self.force_plot_invisible_refs_vals) po.SetExcludeEdgeLayer(self.exclude_edge_layer) po.SetPlotPadsOnSilkLayer(not self.exclude_pads_from_silkscreen) po.SetUseAuxOrigin(self.get_use_aux_axis_as_origin()) po.SetPlotViaOnMaskLayer(not self.tent_vias) # in general, false, but gerber will set it back later po.SetUseGerberAttributes(False) # Only useful for gerber outputs po.SetCreateGerberJobFile(False) # How we draw drill marks po.SetDrillMarksType(self.get_drill_marks()) # We'll come back to this on a per-layer basis po.SetSkipPlotNPTH_Pads(False) if self.get_sketch_plot(): po.SetPlotMode(SKETCH) def get_plot_format(self): return self._plot_format def run(self, output_dir, board): # fresh plot controller plot_ctrl = PLOT_CONTROLLER(board) # set up plot options for the whole output po = plot_ctrl.GetPlotOptions() self._configure_plot_ctrl(po, output_dir) 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 self._layers: suffix = l.suffix desc = l.desc id = l.get_layer_id_from_name(layer_cnt) # Set current layer plot_ctrl.SetLayer(id) # Skipping NPTH is controlled by whether or not this is # a copper layer is_cu = IsCopperLayer(id) po.SetSkipPlotNPTH_Pads(is_cu) plot_format = self.get_plot_format() # Plot single layer to file logger.debug("Opening plot file for layer `{}` format `{}`".format(l, plot_format)) if not plot_ctrl.OpenPlotfile(suffix, plot_format, desc): raise PlotError("OpenPlotfile failed!") logger.debug("Plotting layer `{}` to `{}`".format(l, plot_ctrl.GetPlotFileName())) plot_ctrl.PlotLayer() plot_ctrl.ClosePlot() if create_job: jobfile_writer.AddGbrFile(id, os.path.basename(plot_ctrl.GetPlotFileName())) if create_job: base_fn = os.path.join( os.path.dirname(plot_ctrl.GetPlotFileName()), os.path.basename(GS.pcb_file)) base_fn = os.path.splitext(base_fn)[0] job_fn = base_fn+'-job.gbrjob' jobfile_writer.CreateJobFile(job_fn) # Default values # We concentrate all the KiCad plot initialization in one place. # Here we provide default values for settings not contained in an output object # TODO: avoid them? def get_line_width(self): return self.line_width if 'line_width' in self.__dict__ else 0 def get_scaling(self): return self.scaling if 'scaling' in self.__dict__ else 1 def get_mirror_plot(self): return self.mirror_plot if 'mirror_plot' in self.__dict__ else False def get_negative_plot(self): return self.negative_plot if 'negative_plot' in self.__dict__ else False def get_use_aux_axis_as_origin(self): return self.use_aux_axis_as_origin if 'use_aux_axis_as_origin' in self.__dict__ else False def get_drill_marks(self): return self.drill_marks if '_drill_marks' in self.__dict__ else PCB_PLOT_PARAMS.NO_DRILL_SHAPE def get_sketch_plot(self): return self.sketch_plot if 'sketch_plot' in self.__dict__ else False