← Back to team overview

kicad-developers team mailing list archive

Script access to pcb plot

 

OK, done the deed, it seems to work. There is one thing I'm still
undecided for, mostly for the user's convenience of use.

I attached a test script which showcases most of the features.

As you can see with the BOARD you can create a PLOT_CONTROLLER. The plot
controller contains the plot setting object (HAS-A relationship).

So, in the script, you configure with the popt object and then do stuff
using the pctl object containing it.

However I see that maybe the user maybe would like to set options directly in
the controller object; there is a good design decision (for me) in
separating the interface but users usually don't get OO design nuances
(also they use the office ribbon which comes right from the deepest pit
of hell).

This could be done very easily, actually, simply using public
inheritance (use of C++ inheritance for containment is...
controversial). I don't like it but could be done.

Any comment?

-- 
Lorenzo Marcantonio
Logos Srl
# Test for batch plot scripting capabilities

# Acquire stuff from pcbnew and build a plot controller from the
# current board
from pcbnew import *
pctl = PLOT_CONTROLLER(GetBoard())

# Now we can use it to do many kinds of plot... the first thing to 
# do is access the plot options inside it
popt = pctl.AccessPlotOpts()

# Then we set all the plotfile level options. Everything has some 
# default (most of these are sane AFAIK)

# The output directory is relative to the board file
popt.SetOutputDirectory("fabs")

# I want frame reference for now, since these are always plotted if
# enabled, even if there is no layer plotted after the open
popt.SetPlotFrameRef(True)

# ISO says that type A lines on A4 thru A2 papers are 0.35mm wide...
# ISO also says how big should be the margin and stuff like that but
# at the moment these are built in kicad. More info on these stuff:
# http://www.metrication.com/drafting/
# Oh, ISO also want all the drawings in landscape except A4 that must
# be in portrait. Don't ask. DIN people and BS people want stuff
# slightly different. Don't even mention ANSI...
#
# Anyway, remember that paper size it taken from the board and for all
# plot purposes DXF and Gerber files have no paper size (so no
# autoscaling for them). On the other hand DXF and Gerber *can* use the
# auxiliary axis as origin.
popt.SetLineWidth(FromMM(0.35))

# Now we open a plot of the desired format. The last string is the
# 'sheet description' (a bonus string under the title, useful for
# describing the plot content). In eeschema that space is taken by the
# sheet path, but for a pcb that would be a (redundant) "1/1"

# As a preliminary test for all the drivers now we do a frame-only
# series of plots. The extension is the default for the plot type if not
# specified in the suffix. We have 5 output drivers now. Maybe one day
# SVG will be added, if people keep asking for it
pctl.OpenPlotfile("empty", PLOT_FORMAT_POST, "Empty PS plot ")

# Feature: the plot is closed when another one is opened
pctl.OpenPlotfile("empty", PLOT_FORMAT_PDF, "Empty PDF plot ")

pctl.OpenPlotfile("empty", PLOT_FORMAT_DXF, "Empty DXF plot ")

pctl.OpenPlotfile("empty", PLOT_FORMAT_HPGL, "Empty HPGL plot ")

pctl.OpenPlotfile("empty", PLOT_FORMAT_GERBER, "Empty GERBER plot ")

# Now that preliminaries are over, let's redo the original testcase
# (adapted from the transfer-triggered state machine)

# Some options are 'for file' and are took in account mostly when the plot is 
# started; other ones are processed for each layer
popt.SetAutoScale(False)
popt.SetScale(1)
popt.SetMirror(False)

# HPGL parameters should really go in the layer parameters but currently
# they are only set on plotter opening (it would need additions to the
# core plotter classes); maybe I'll fix that later
popt.SetHPGLPenNum(1)
popt.SetHPGLPenSpeed(10)
# Warning: HPGL pen sizes are still in mils, not IU!
popt.SetHPGLPenDiameter(7)
popt.SetHPGLPenOverlay(2)

# Let's start a somewhat convoluted example: I use a really cool mill
# driven by HPGL, so I want the pcb edge done. Here I override the default
# .plt extension, too.

# The frame is plotted right after the opening of the file (and, at the moment,
# always using color 0/BLACK). So it's a plot-level option, not a layer-level 
# one. Now I don't want it
popt.SetPlotFrameRef(False)
pctl.OpenPlotfile("EdgeMill.hpgl", PLOT_FORMAT_HPGL, "")

# The output file is open and ready to plot: we can set parameters for the
# upcoming layer. Obviously we could have set them before

# Mode: LINE, FILLED or SKETCH
popt.SetMode(LINE)

# Drill shape
# For compatibility with the old plot routines drill marks are only done on
# copper layers. Due to the way they are done in PlotMode=1 they *always*
# end up in the white layer which is both strange and useful (since they are
# often used without the track data). I think that this needs some discussion.
# Maybe the best solution would be a separate function to plot them
popt.SetDrillMarksType(PCB_PLOT_PARAMS.NO_DRILL_SHAPE)

# ViaOnMask is mostly for gerbers, but implemented for other drivers too
popt.SetPlotViaOnMaskLayer(False)

# ExcludeEdge set to 0 is a convenience; you could have (almost) the same
# result plotting the edge layer before switching plotfile
popt.SetExcludeEdgeLayer(True)
popt.SetPlotReference(True)
popt.SetPlotValue(True)
popt.SetPlotOtherText(True)
popt.SetPlotInvisibleText(False)
popt.SetPlotPadsOnSilkLayer(False)

# All options are set, plot a layer (you have to know the layer number!)
pctl.PlotLayer(EDGE_N)

# I want to do a postscript plot for toner transfer
# Small drill mark, mirror image. The mirror option has to be set *before*
# opening the file, since is plot-level. Good luck doing tone transfers with
# dual sides...
popt.SetMirror(True)
pctl.OpenPlotfile("TonerTransfer", PLOT_FORMAT_POST, "")
popt.SetExcludeEdgeLayer(False)
popt.SetMode(FILLED)
popt.SetDrillMarksType(PCB_PLOT_PARAMS.SMALL_DRILL_SHAPE)
pctl.PlotLayer(LAYER_N_BACK)

# And to finish it up a silk pdf to help me assemble the board
# I have to reset the options I don't need (of course I could always set
# *every* option for each plot, as it was suggested in the mailing list)
popt.SetDrillMarksType(PCB_PLOT_PARAMS.NO_DRILL_SHAPE)
popt.SetMirror(False)

# Make it big, so I can read it better...
# Scaling and autoscaling triggers automatic centering of the board
# bounding box.
popt.SetScale(1.5)
pctl.OpenPlotfile("Silk", PLOT_FORMAT_PDF, "Assembly guide")
pctl.PlotLayer(SILKSCREEN_N_FRONT)
popt.SetScale(1)

# From now on I got rich and decided to do a more 'conventional' fabrication
# So lets start with a conventional set of gerbers

# This is used only by gerbers and DXF, but it's best to leave it off
# otherwise...
popt.SetUseAuxOrigin(True)

# This by gerbers only (also the name is truly horrid!)
popt.SetSubtractMaskFromSilk(True)

# Once the defaults are set it become pretty easy...
# I have a Turing-complete programming language here: I'll use it...

plot_plan = [
    ( "Top", LAYER_N_FRONT, "Top layer" ),
    ( "Bottom", LAYER_N_BACK, "Bottom layer" ),
    ( "PasteTop", SOLDERPASTE_N_FRONT, "Paste top" ),
    ( "SilkTop", SILKSCREEN_N_FRONT, "Silk top" ),
    ( "MaskBottom", SOLDERMASK_N_BACK, "Mask bottom" ),
    ( "MaskTop", SOLDERMASK_N_FRONT, "Mask top" ),
    ( "Drawings", DRAW_N, "Fabrication" ),
    ( "Comment", COMMENT_N, "Comments" ),
    ( "Edge", EDGE_N, "Edges" ),
]

# I don't know if this is idiomatic python but is effective (maybe
# dictionaries are more readable than tuples...) 
# The sheet description is passed but will be unused without frame
# references
# PLEASE NOTE this is mostly equivalent to the default plot dialog loop!
for layer_info in plot_plan:
    pctl.OpenPlotfile(layer_info[0], PLOT_FORMAT_GERBER, layer_info[2])
    pctl.PlotLayer(layer_info[1])

# Our fabricators want two additional gerbers:
# An assembly with no silk trim and all and only the references
# (you'll see that even holes have designators, obviously)
popt.SetSubtractMaskFromSilk(False)
popt.SetPlotReference(True)
popt.SetPlotValue(False)
popt.SetPlotOtherText(False)
popt.SetPlotInvisibleText(True)
pctl.OpenPlotfile("AssyTop", PLOT_FORMAT_GERBER, "Assembly top")
pctl.PlotLayer(SILKSCREEN_N_FRONT)

# And a gerber with only the component outlines (really!)
popt.SetPlotReference(False)
popt.SetPlotInvisibleText(False)
pctl.OpenPlotfile("AssyOutlinesTop", PLOT_FORMAT_GERBER, "Assembly outline top")
pctl.PlotLayer(SILKSCREEN_N_FRONT)

# The same could be done for the bottom side, if there were components
popt.SetUseAuxOrigin(False)

## For documentation we also want a general layout PDF
## I usually use a shell script to merge the ps files and then distill the result
## Now I can do it with a control file. As a bonus I can have references in a
## different colour, too.

popt.SetPlotReference(True)
popt.SetPlotValue(True)
popt.SetPlotOtherText(True)
popt.SetPlotInvisibleText(False)
# Remember that the frame is always in color 0 (BLACK) and should be requested
# before opening the plot
popt.SetPlotFrameRef(True)
pctl.OpenPlotfile("Layout", PLOT_FORMAT_PDF, "General layout")

# Start with the drawings (could be put at the end, depending on preferences...)
# Mostly if you want dimensioning over or under the other stuff
pctl.PlotLayer(DRAW_N)
# Each layer can be plotted in a different colour. This is useful.
# Also references and values on silks can have a different colour from the rest
# of the silk. This will be very useful in the assembly DXF
# Also these colours suck on paper since they are the ones I use with a *black*
# screen background...

# Do the PCB edges in yellow
popt.SetColor(YELLOW)
pctl.PlotLayer(EDGE_N)

## Comments in, uhmm... green
popt.SetColor(GREEN)
pctl.PlotLayer(COMMENT_N)

# Careful ordering of the layers when plotting gives the desired results
# This board isn't very exciting because both solder mask and paste adjustment
# are done by the fabricator, so the lines overlap. Anyway it's a good way to
# see if you forgot some solder paste aperture (it happens :D)

# I'm too lazy to think about a loop for this; it is left as an exercise
# for the reader...

# Bottom mask as lines only, in red
popt.SetMode(LINE)
popt.SetColor(RED)
pctl.PlotLayer(SOLDERMASK_N_BACK)

# Top mask as lines only, in blue
popt.SetColor(BLUE)
pctl.PlotLayer(SOLDERMASK_N_FRONT)

# Top paste in light blue, filled
popt.SetColor(BLUE)
popt.SetMode(FILLED)
pctl.PlotLayer(SOLDERPASTE_N_FRONT)

# Top Silk in cyan, filled, references in dark cyan
popt.SetReferenceColor(DARKCYAN)
popt.SetColor(CYAN)
pctl.PlotLayer(SILKSCREEN_N_FRONT)

# To end it all, do a big DXF export with all the previous stuff and more

# This forces text to be output as strokes instead of text entities; you
# can try with PLOTTEXTMODE_NATIVE to see the result in your cad
# (results *will* vary... depending on your CAD font and alignment
# capability); Autocad with the isocp3 font matches perfectly the kicad iso 
# font. Probably it's because both come from the same ISO shx...
popt.SetTextMode(PLOTTEXTMODE_STROKE)
pctl.OpenPlotfile("Assembly", PLOT_FORMAT_DXF, "Master Assembly")

# We want *everything*
popt.SetPlotReference(True)
popt.SetPlotValue(True)
popt.SetPlotOtherText(True)
popt.SetPlotInvisibleText(True)

# Remember than the DXF driver assigns colours to layers. This means that
# we will be able to turn references on and off simply using their layers
# Also most of the layer are now plotted in 'line' mode, because DXF handles
# fill mode almost like sketch mode (this is to keep compatibility with
# most CAD programs; most of the advanced primitive attributes required are
# handled only by recent autocads...); also the entry level cads (qcad
# and derivatives) simply don't handle polyline widths...

# Here I'm using numbers for colors and layers, I'm too lazy too look them up:P
popt.SetReferenceColor(19)
popt.SetValueColor(21)

popt.SetColor(0)
popt.SetMode(LINE)
pctl.PlotLayer(24)
popt.SetColor(14)
pctl.PlotLayer(28)
popt.SetColor(2)
pctl.PlotLayer(25)
popt.SetColor(4)
pctl.PlotLayer(22)
popt.SetColor(1)
pctl.PlotLayer(23)
popt.SetColor(9)
pctl.PlotLayer(19)
popt.SetColor(3)
pctl.PlotLayer(21)

# Export the copper layers too... exporting one of them in filled mode with
# drill marks will put the marks in the WHITE later (since it tries to blank
# the pads...); these will be obviously great reference points for snap
# and stuff in the cad. A pctl function to only plot them would be
# better anyway...

popt.SetColor(17)
popt.SetMode(FILLED)
popt.SetDrillMarksType(PCB_PLOT_PARAMS.FULL_DRILL_SHAPE)
pctl.PlotLayer(15)
popt.SetColor(20)
popt.SetDrillMarksType(PCB_PLOT_PARAMS.NO_DRILL_SHAPE)
pctl.PlotLayer(0)

# At the end you have to close the last plot, otherwise you don't know when
# the object will be recycled!
pctl.ClosePlot()

# We have just generated 21 plotfiles with a single script

Follow ups