summaryrefslogtreecommitdiff
path: root/pcbnew/plot_board_layers.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'pcbnew/plot_board_layers.cpp')
-rw-r--r--pcbnew/plot_board_layers.cpp1040
1 files changed, 1040 insertions, 0 deletions
diff --git a/pcbnew/plot_board_layers.cpp b/pcbnew/plot_board_layers.cpp
new file mode 100644
index 0000000..34c08fd
--- /dev/null
+++ b/pcbnew/plot_board_layers.cpp
@@ -0,0 +1,1040 @@
+/**
+ * @file plot_board_layers.cpp
+ * @brief Functions to plot one board layer (silkscreen layers or other layers).
+ * Silkscreen layers have specific requirement for pads (not filled) and texts
+ * (with option to remove them from some copper areas (pads...)
+ */
+
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 1992-2015 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+
+#include <fctsys.h>
+#include <common.h>
+#include <plot_common.h>
+#include <base_struct.h>
+#include <drawtxt.h>
+#include <trigo.h>
+#include <wxBasePcbFrame.h>
+#include <macros.h>
+
+#include <class_board.h>
+#include <class_module.h>
+#include <class_track.h>
+#include <class_edge_mod.h>
+#include <class_pcb_text.h>
+#include <class_zone.h>
+#include <class_drawsegment.h>
+#include <class_mire.h>
+#include <class_dimension.h>
+
+#include <pcbnew.h>
+#include <pcbplot.h>
+
+// Local
+/* Plot a solder mask layer.
+ * Solder mask layers have a minimum thickness value and cannot be drawn like standard layers,
+ * unless the minimum thickness is 0.
+ */
+static void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter,
+ LSET aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt,
+ int aMinThickness );
+
+/* Creates the plot for silkscreen layers
+ * Silkscreen layers have specific requirement for pads (not filled) and texts
+ * (with option to remove them from some copper areas (pads...)
+ */
+void PlotSilkScreen( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
+ const PCB_PLOT_PARAMS& aPlotOpt )
+{
+ BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
+ itemplotter.SetLayerSet( aLayerMask );
+
+ // Plot edge layer and graphic items
+ itemplotter.PlotBoardGraphicItems();
+
+ // Plot footprint outlines :
+ itemplotter.Plot_Edges_Modules();
+
+ // Plot pads (creates pads outlines, for pads on silkscreen layers)
+ LSET layersmask_plotpads = aLayerMask;
+
+ // Calculate the mask layers of allowed layers for pads
+
+ if( !aPlotOpt.GetPlotPadsOnSilkLayer() ) // Do not plot pads on silk screen layers
+ layersmask_plotpads.set( B_SilkS, false ).set( F_SilkS, false );
+
+ if( layersmask_plotpads.any() )
+ {
+ for( MODULE* Module = aBoard->m_Modules; Module; Module = Module->Next() )
+ {
+ for( D_PAD * pad = Module->Pads(); pad; pad = pad->Next() )
+ {
+ // See if the pad is on this layer
+ LSET masklayer = pad->GetLayerSet();
+ if( !( masklayer & layersmask_plotpads ).any() )
+ continue;
+
+ EDA_COLOR_T color = ColorFromInt( 0 );
+
+ if( layersmask_plotpads[B_SilkS] )
+ color = aBoard->GetLayerColor( B_SilkS );
+
+ if( layersmask_plotpads[F_SilkS] )
+ color = ColorFromInt( color | aBoard->GetLayerColor( F_SilkS ) );
+
+ itemplotter.PlotPad( pad, color, SKETCH );
+ }
+ }
+ }
+
+ // Plot footprints fields (ref, value ...)
+ for( MODULE* module = aBoard->m_Modules; module; module = module->Next() )
+ {
+ if( ! itemplotter.PlotAllTextsModule( module ) )
+ {
+ wxLogMessage( _( "Your BOARD has a bad layer number for footprint %s" ),
+ GetChars( module->GetReference() ) );
+ }
+ }
+
+ // Plot filled areas
+ for( int ii = 0; ii < aBoard->GetAreaCount(); ii++ )
+ {
+ ZONE_CONTAINER* edge_zone = aBoard->GetArea( ii );
+
+ if( !aLayerMask[ edge_zone->GetLayer() ] )
+ continue;
+
+ itemplotter.PlotFilledAreas( edge_zone );
+ }
+
+ // Plot segments used to fill zone areas (outdated, but here for old boards
+ // compatibility):
+ for( SEGZONE* seg = aBoard->m_Zone; seg; seg = seg->Next() )
+ {
+ if( !aLayerMask[ seg->GetLayer() ] )
+ continue;
+
+ aPlotter->ThickSegment( seg->GetStart(), seg->GetEnd(), seg->GetWidth(),
+ itemplotter.GetPlotMode() );
+ }
+}
+
+void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, LAYER_ID aLayer,
+ const PCB_PLOT_PARAMS& aPlotOpt )
+{
+ PCB_PLOT_PARAMS plotOpt = aPlotOpt;
+ int soldermask_min_thickness = aBoard->GetDesignSettings().m_SolderMaskMinWidth;
+
+ // Set a default color and the text mode for this layer
+ aPlotter->SetColor( aPlotOpt.GetColor() );
+ aPlotter->SetTextMode( aPlotOpt.GetTextMode() );
+
+ // Specify that the contents of the "Edges Pcb" layer are to be plotted
+ // in addition to the contents of the currently specified layer.
+ LSET layer_mask( aLayer );
+
+ if( !aPlotOpt.GetExcludeEdgeLayer() )
+ layer_mask.set( Edge_Cuts );
+
+ if( IsCopperLayer( aLayer ) )
+ {
+ // Skip NPTH pads on copper layers ( only if hole size == pad size ):
+ // Drill mark will be plotted,
+ // if drill mark is SMALL_DRILL_SHAPE or FULL_DRILL_SHAPE
+ if( plotOpt.GetFormat() == PLOT_FORMAT_DXF )
+ {
+ plotOpt.SetSkipPlotNPTH_Pads( false );
+ PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt );
+ }
+ else
+ {
+ plotOpt.SetSkipPlotNPTH_Pads( true );
+ PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
+ }
+ }
+ else
+ {
+ switch( aLayer )
+ {
+ case B_Mask:
+ case F_Mask:
+ plotOpt.SetSkipPlotNPTH_Pads( false );
+ // Disable plot pad holes
+ plotOpt.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE );
+
+ // Plot solder mask:
+ if( soldermask_min_thickness == 0 )
+ {
+ if( plotOpt.GetFormat() == PLOT_FORMAT_DXF )
+ PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt );
+ else
+ PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
+ }
+ else
+ PlotSolderMaskLayer( aBoard, aPlotter, layer_mask, plotOpt,
+ soldermask_min_thickness );
+
+ break;
+
+ case B_Adhes:
+ case F_Adhes:
+ case B_Paste:
+ case F_Paste:
+ plotOpt.SetSkipPlotNPTH_Pads( false );
+ // Disable plot pad holes
+ plotOpt.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE );
+
+ if( plotOpt.GetFormat() == PLOT_FORMAT_DXF )
+ PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt );
+ else
+ PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
+ break;
+
+ case F_SilkS:
+ case B_SilkS:
+ if( plotOpt.GetFormat() == PLOT_FORMAT_DXF && plotOpt.GetDXFPlotPolygonMode() )
+ // PlotLayerOutlines() is designed only for DXF plotters.
+ // and must not be used for other plot formats
+ PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt );
+ else
+ PlotSilkScreen( aBoard, aPlotter, layer_mask, plotOpt );
+
+ // Gerber: Subtract soldermask from silkscreen if enabled
+ if( aPlotter->GetPlotterType() == PLOT_FORMAT_GERBER
+ && plotOpt.GetSubtractMaskFromSilk() )
+ {
+ if( aLayer == F_SilkS )
+ layer_mask = LSET( F_Mask );
+ else
+ layer_mask = LSET( B_Mask );
+
+ // Create the mask to subtract by creating a negative layer polarity
+ aPlotter->SetLayerPolarity( false );
+
+ // Disable plot pad holes
+ plotOpt.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE );
+
+ // Plot the mask
+ PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
+ }
+ break;
+
+ // These layers are plotted like silk screen layers.
+ // Mainly, pads on these layers are not filled.
+ // This is not necessary the best choice.
+ case Dwgs_User:
+ case Cmts_User:
+ case Eco1_User:
+ case Eco2_User:
+ case Edge_Cuts:
+ case Margin:
+ case F_CrtYd:
+ case B_CrtYd:
+ case F_Fab:
+ case B_Fab:
+ plotOpt.SetSkipPlotNPTH_Pads( false );
+ plotOpt.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE );
+
+ if ( plotOpt.GetFormat() == PLOT_FORMAT_DXF && plotOpt.GetDXFPlotPolygonMode() )
+ // PlotLayerOutlines() is designed only for DXF plotters.
+ // and must not be used for other plot formats
+ PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt );
+ else
+ PlotSilkScreen( aBoard, aPlotter, layer_mask, plotOpt );
+ break;
+
+ default:
+ plotOpt.SetSkipPlotNPTH_Pads( false );
+ plotOpt.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE );
+
+ if ( plotOpt.GetFormat() == PLOT_FORMAT_DXF && plotOpt.GetDXFPlotPolygonMode() )
+ // PlotLayerOutlines() is designed only for DXF plotters.
+ // and must not be used for other plot formats
+ PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt );
+ else
+ PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
+ break;
+ }
+ }
+}
+
+
+/* Plot a copper layer or mask.
+ * Silk screen layers are not plotted here.
+ */
+void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter,
+ LSET aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt )
+{
+ BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
+
+ itemplotter.SetLayerSet( aLayerMask );
+
+ EDA_DRAW_MODE_T plotMode = aPlotOpt.GetPlotMode();
+
+ // Plot edge layer and graphic items
+ itemplotter.PlotBoardGraphicItems();
+
+ // Draw footprint shapes without pads (pads will plotted later)
+ // We plot here module texts, but they are usually on silkscreen layer,
+ // so they are not plot here but plot by PlotSilkScreen()
+ // Plot footprints fields (ref, value ...)
+ for( MODULE* module = aBoard->m_Modules; module; module = module->Next() )
+ {
+ if( ! itemplotter.PlotAllTextsModule( module ) )
+ {
+ wxLogMessage( _( "Your BOARD has a bad layer number for footprint %s" ),
+ GetChars( module->GetReference() ) );
+ }
+ }
+
+ for( MODULE* module = aBoard->m_Modules; module; module = module->Next() )
+ {
+ for( BOARD_ITEM* item = module->GraphicalItems(); item; item = item->Next() )
+ {
+ if( !aLayerMask[ item->GetLayer() ] )
+ continue;
+
+ switch( item->Type() )
+ {
+ case PCB_MODULE_EDGE_T:
+ itemplotter.Plot_1_EdgeModule( (EDGE_MODULE*) item );
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ // Plot footprint pads
+ for( MODULE* module = aBoard->m_Modules; module; module = module->Next() )
+ {
+ for( D_PAD* pad = module->Pads(); pad; pad = pad->Next() )
+ {
+ if( (pad->GetLayerSet() & aLayerMask) == 0 )
+ continue;
+
+ wxSize margin;
+ double width_adj = 0;
+
+ if( ( aLayerMask & LSET::AllCuMask() ).any() )
+ width_adj = itemplotter.getFineWidthAdj();
+
+ static const LSET speed( 4, B_Mask, F_Mask, B_Paste, F_Paste );
+
+ LSET anded = ( speed & aLayerMask );
+
+ if( anded == LSET( F_Mask ) || anded == LSET( B_Mask ) )
+ {
+ margin.x = margin.y = pad->GetSolderMaskMargin();
+ }
+ else if( anded == LSET( F_Paste ) || anded == LSET( B_Paste ) )
+ {
+ margin = pad->GetSolderPasteMargin();
+ }
+
+ wxSize padPlotsSize;
+ padPlotsSize.x = pad->GetSize().x + ( 2 * margin.x ) + width_adj;
+ padPlotsSize.y = pad->GetSize().y + ( 2 * margin.y ) + width_adj;
+
+ // Don't draw a null size item :
+ if( padPlotsSize.x <= 0 || padPlotsSize.y <= 0 )
+ continue;
+
+ EDA_COLOR_T color = BLACK;
+
+ if( pad->GetLayerSet()[B_Cu] )
+ color = aBoard->GetVisibleElementColor( PAD_BK_VISIBLE );
+
+ if( pad->GetLayerSet()[F_Cu] )
+ color = ColorFromInt( color | aBoard->GetVisibleElementColor( PAD_FR_VISIBLE ) );
+
+ // Temporary set the pad size to the required plot size:
+ wxSize tmppadsize = pad->GetSize();
+ pad->SetSize( padPlotsSize );
+ switch( pad->GetShape() )
+ {
+ case PAD_SHAPE_CIRCLE:
+ case PAD_SHAPE_OVAL:
+ if( aPlotOpt.GetSkipPlotNPTH_Pads() &&
+ (pad->GetSize() == pad->GetDrillSize()) &&
+ (pad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED) )
+ break;
+
+ // Fall through:
+ case PAD_SHAPE_TRAPEZOID:
+ case PAD_SHAPE_RECT:
+ default:
+ itemplotter.PlotPad( pad, color, plotMode );
+ break;
+ }
+
+ pad->SetSize( tmppadsize ); // Restore the pad size
+ }
+ }
+
+ // Plot vias on copper layers, and if aPlotOpt.GetPlotViaOnMaskLayer() is true,
+ // plot them on solder mask
+ for( TRACK* track = aBoard->m_Track; track; track = track->Next() )
+ {
+ const VIA* Via = dyn_cast<const VIA*>( track );
+
+ if( !Via )
+ continue;
+
+ // vias are not plotted if not on selected layer, but if layer
+ // is SOLDERMASK_LAYER_BACK or SOLDERMASK_LAYER_FRONT,vias are drawn,
+ // only if they are on the corresponding external copper layer
+ LSET via_mask_layer = Via->GetLayerSet();
+
+ if( aPlotOpt.GetPlotViaOnMaskLayer() )
+ {
+ if( via_mask_layer[B_Cu] )
+ via_mask_layer.set( B_Mask );
+
+ if( via_mask_layer[F_Cu] )
+ via_mask_layer.set( F_Mask );
+ }
+
+ if( !( via_mask_layer & aLayerMask ).any() )
+ continue;
+
+ int via_margin = 0;
+ double width_adj = 0;
+
+ // If the current layer is a solder mask, use the global mask
+ // clearance for vias
+ if( aLayerMask[B_Mask] || aLayerMask[F_Mask] )
+ via_margin = aBoard->GetDesignSettings().m_SolderMaskMargin;
+
+ if( ( aLayerMask & LSET::AllCuMask() ).any() )
+ width_adj = itemplotter.getFineWidthAdj();
+
+ int diameter = Via->GetWidth() + 2 * via_margin + width_adj;
+
+ // Don't draw a null size item :
+ if( diameter <= 0 )
+ continue;
+
+ EDA_COLOR_T color = aBoard->GetVisibleElementColor(VIAS_VISIBLE + Via->GetViaType());
+ // Set plot color (change WHITE to LIGHTGRAY because
+ // the white items are not seen on a white paper or screen
+ aPlotter->SetColor( color != WHITE ? color : LIGHTGRAY);
+ aPlotter->FlashPadCircle( Via->GetStart(), diameter, plotMode );
+ }
+
+ // Plot tracks (not vias) :
+ for( TRACK* track = aBoard->m_Track; track; track = track->Next() )
+ {
+ if( track->Type() == PCB_VIA_T )
+ continue;
+
+ if( !aLayerMask[track->GetLayer()] )
+ continue;
+
+ int width = track->GetWidth() + itemplotter.getFineWidthAdj();
+ aPlotter->SetColor( itemplotter.getColor( track->GetLayer() ) );
+ aPlotter->ThickSegment( track->GetStart(), track->GetEnd(), width, plotMode );
+ }
+
+ // Plot zones (outdated, for old boards compatibility):
+ for( TRACK* track = aBoard->m_Zone; track; track = track->Next() )
+ {
+ if( !aLayerMask[track->GetLayer()] )
+ continue;
+
+ int width = track->GetWidth() + itemplotter.getFineWidthAdj();
+ aPlotter->SetColor( itemplotter.getColor( track->GetLayer() ) );
+ aPlotter->ThickSegment( track->GetStart(), track->GetEnd(), width, plotMode );
+ }
+
+ // Plot filled ares
+ for( int ii = 0; ii < aBoard->GetAreaCount(); ii++ )
+ {
+ ZONE_CONTAINER* zone = aBoard->GetArea( ii );
+
+ if( !aLayerMask[zone->GetLayer()] )
+ continue;
+
+ itemplotter.PlotFilledAreas( zone );
+ }
+
+ // Adding drill marks, if required and if the plotter is able to plot them:
+ if( aPlotOpt.GetDrillMarksType() != PCB_PLOT_PARAMS::NO_DRILL_SHAPE )
+ itemplotter.PlotDrillMarks();
+}
+
+
+// Seems like we want to plot from back to front?
+static const LAYER_ID plot_seq[] = {
+
+ B_Adhes, // 32
+ F_Adhes,
+ B_Paste,
+ F_Paste,
+ B_SilkS,
+ B_Mask,
+ F_Mask,
+ Dwgs_User,
+ Cmts_User,
+ Eco1_User,
+ Eco2_User,
+ Edge_Cuts,
+ Margin,
+
+ F_CrtYd, // CrtYd & Body are footprint only
+ B_CrtYd,
+ F_Fab,
+ B_Fab,
+
+ B_Cu,
+ In30_Cu,
+ In29_Cu,
+ In28_Cu,
+ In27_Cu,
+ In26_Cu,
+ In25_Cu,
+ In24_Cu,
+ In23_Cu,
+ In22_Cu,
+ In21_Cu,
+ In20_Cu,
+ In19_Cu,
+ In18_Cu,
+ In17_Cu,
+ In16_Cu,
+ In15_Cu,
+ In14_Cu,
+ In13_Cu,
+ In12_Cu,
+ In11_Cu,
+ In10_Cu,
+ In9_Cu,
+ In8_Cu,
+ In7_Cu,
+ In6_Cu,
+ In5_Cu,
+ In4_Cu,
+ In3_Cu,
+ In2_Cu,
+ In1_Cu,
+ F_Cu,
+
+ F_SilkS,
+};
+
+
+/* Plot outlines of copper, for copper layer
+ */
+void PlotLayerOutlines( BOARD* aBoard, PLOTTER* aPlotter,
+ LSET aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt )
+{
+
+ BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
+ itemplotter.SetLayerSet( aLayerMask );
+
+ SHAPE_POLY_SET outlines;
+
+ for( LSEQ seq = aLayerMask.Seq( plot_seq, DIM( plot_seq ) ); seq; ++seq )
+ {
+ LAYER_ID layer = *seq;
+
+ outlines.RemoveAllContours();
+ aBoard->ConvertBrdLayerToPolygonalContours( layer, outlines );
+
+ outlines.Simplify();
+
+ // Plot outlines
+ std::vector< wxPoint > cornerList;
+
+ // Now we have one or more basic polygons: plot each polygon
+ for( int ii = 0; ii < outlines.OutlineCount(); ii++ )
+ {
+ for(int kk = 0; kk <= outlines.HoleCount (ii); kk++ )
+ {
+ cornerList.clear();
+ const SHAPE_LINE_CHAIN& path = (kk == 0) ? outlines.COutline( ii ) : outlines.CHole( ii, kk - 1 );
+
+ for( int jj = 0; jj < path.PointCount(); jj++ )
+ cornerList.push_back( wxPoint( path.CPoint( jj ).x , path.CPoint( jj ).y ) );
+
+
+ // Ensure the polygon is closed
+ if( cornerList[0] != cornerList[cornerList.size() - 1] )
+ cornerList.push_back( cornerList[0] );
+
+ aPlotter->PlotPoly( cornerList, NO_FILL );
+ }
+ }
+
+ // Plot pad holes
+ if( aPlotOpt.GetDrillMarksType() != PCB_PLOT_PARAMS::NO_DRILL_SHAPE )
+ {
+ for( MODULE* module = aBoard->m_Modules; module; module = module->Next() )
+ {
+ for( D_PAD* pad = module->Pads(); pad; pad = pad->Next() )
+ {
+ wxSize hole = pad->GetDrillSize();
+
+ if( hole.x == 0 || hole.y == 0 )
+ continue;
+
+ if( hole.x == hole.y )
+ aPlotter->Circle( pad->GetPosition(), hole.x, NO_FILL );
+ else
+ {
+ wxPoint drl_start, drl_end;
+ int width;
+ pad->GetOblongDrillGeometry( drl_start, drl_end, width );
+ aPlotter->ThickSegment( pad->GetPosition() + drl_start,
+ pad->GetPosition() + drl_end, width, SKETCH );
+ }
+ }
+ }
+ }
+
+ // Plot vias holes
+ for( TRACK* track = aBoard->m_Track; track; track = track->Next() )
+ {
+ const VIA* via = dyn_cast<const VIA*>( track );
+
+ if( via && via->IsOnLayer( layer ) ) // via holes can be not through holes
+ {
+ aPlotter->Circle( via->GetPosition(), via->GetDrillValue(), NO_FILL );
+ }
+ }
+ }
+}
+
+
+/* Plot a solder mask layer.
+ * Solder mask layers have a minimum thickness value and cannot be drawn like standard layers,
+ * unless the minimum thickness is 0.
+ * Currently the algo is:
+ * 1 - build all pad shapes as polygons with a size inflated by
+ * mask clearance + (min width solder mask /2)
+ * 2 - Merge shapes
+ * 3 - deflate result by (min width solder mask /2)
+ * 4 - oring result by all pad shapes as polygons with a size inflated by
+ * mask clearance only (because deflate sometimes creates shape artifacts)
+ * 5 - draw result as polygons
+ *
+ * TODO:
+ * make this calculation only for shapes with clearance near than (min width solder mask)
+ * (using DRC algo)
+ * plot all other shapes by flashing the basing shape
+ * (shapes will be better, and calculations faster)
+ */
+void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter,
+ LSET aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt,
+ int aMinThickness )
+{
+ LAYER_ID layer = aLayerMask[B_Mask] ? B_Mask : F_Mask;
+ int inflate = aMinThickness/2;
+
+ BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
+ itemplotter.SetLayerSet( aLayerMask );
+
+ // Plot edge layer and graphic items
+ // They do not have a solder Mask margin, because they are only graphic items
+ // on this layer (like logos), not actually areas around pads.
+ itemplotter.PlotBoardGraphicItems();
+
+ for( MODULE* module = aBoard->m_Modules; module; module = module->Next() )
+ {
+ for( BOARD_ITEM* item = module->GraphicalItems(); item; item = item->Next() )
+ {
+ if( layer != item->GetLayer() )
+ continue;
+
+ switch( item->Type() )
+ {
+ case PCB_MODULE_EDGE_T:
+ itemplotter.Plot_1_EdgeModule( (EDGE_MODULE*) item );
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ // Build polygons for each pad shape.
+ // the size of the shape on solder mask should be:
+ // size of pad + clearance around the pad.
+ // clearance = solder mask clearance + extra margin
+ // extra margin is half the min width for solder mask
+ // This extra margin is used to merge too close shapes
+ // (distance < aMinThickness), and will be removed when creating
+ // the actual shapes
+ SHAPE_POLY_SET areas; // Contains shapes to plot
+ SHAPE_POLY_SET initialPolys; // Contains exact shapes to plot
+
+ /* calculates the coeff to compensate radius reduction of holes clearance
+ * due to the segment approx ( 1 /cos( PI/circleToSegmentsCount )
+ */
+ int circleToSegmentsCount = 32;
+ double correction = 1.0 / cos( M_PI / circleToSegmentsCount );
+
+ // Plot pads
+ for( MODULE* module = aBoard->m_Modules; module; module = module->Next() )
+ {
+ // add shapes with exact size
+ module->TransformPadsShapesWithClearanceToPolygon( layer,
+ initialPolys, 0,
+ circleToSegmentsCount, correction );
+ // add shapes inflated by aMinThickness/2
+ module->TransformPadsShapesWithClearanceToPolygon( layer,
+ areas, inflate,
+ circleToSegmentsCount, correction );
+ }
+
+ // Plot vias on solder masks, if aPlotOpt.GetPlotViaOnMaskLayer() is true,
+ if( aPlotOpt.GetPlotViaOnMaskLayer() )
+ {
+ // The current layer is a solder mask,
+ // use the global mask clearance for vias
+ int via_clearance = aBoard->GetDesignSettings().m_SolderMaskMargin;
+ int via_margin = via_clearance + inflate;
+
+ for( TRACK* track = aBoard->m_Track; track; track = track->Next() )
+ {
+ const VIA* via = dyn_cast<const VIA*>( track );
+
+ if( !via )
+ continue;
+
+ // vias are plotted only if they are on the corresponding
+ // external copper layer
+ LSET via_set = via->GetLayerSet();
+
+ if( via_set[B_Cu] )
+ via_set.set( B_Mask );
+
+ if( via_set[F_Cu] )
+ via_set.set( F_Mask );
+
+ if( !( via_set & aLayerMask ).any() )
+ continue;
+
+ via->TransformShapeWithClearanceToPolygon( areas, via_margin,
+ circleToSegmentsCount,
+ correction );
+ via->TransformShapeWithClearanceToPolygon( initialPolys, via_clearance,
+ circleToSegmentsCount,
+ correction );
+ }
+ }
+
+ // Add filled zone areas.
+#if 0 // Set to 1 if a solder mask margin must be applied to zones on solder mask
+ int zone_margin = aBoard->GetDesignSettings().m_SolderMaskMargin;
+#else
+ int zone_margin = 0;
+#endif
+
+ for( int ii = 0; ii < aBoard->GetAreaCount(); ii++ )
+ {
+ ZONE_CONTAINER* zone = aBoard->GetArea( ii );
+
+ if( zone->GetLayer() != layer )
+ continue;
+
+ zone->TransformOutlinesShapeWithClearanceToPolygon( areas,
+ inflate+zone_margin, false );
+ zone->TransformOutlinesShapeWithClearanceToPolygon( initialPolys,
+ zone_margin, false );
+ }
+
+ // To avoid a lot of code, use a ZONE_CONTAINER
+ // to handle and plot polygons, because our polygons look exactly like
+ // filled areas in zones
+ // Note, also this code is not optimized: it creates a lot of copy/duplicate data
+ // However it is not complex, and fast enough for plot purposes (copy/convert data
+ // is only a very small calculation time for these calculations)
+ ZONE_CONTAINER zone( aBoard );
+ zone.SetArcSegmentCount( 32 );
+ zone.SetMinThickness( 0 ); // trace polygons only
+ zone.SetLayer ( layer );
+
+ areas.BooleanAdd( initialPolys );
+ areas.Inflate( -inflate, circleToSegmentsCount );
+
+ // Combine the current areas to initial areas. This is mandatory because
+ // inflate/deflate transform is not perfect, and we want the initial areas perfectly kept
+ areas.BooleanAdd( initialPolys );
+ areas.Fracture();
+
+ zone.AddFilledPolysList( areas );
+
+ itemplotter.PlotFilledAreas( &zone );
+}
+
+
+
+/** Set up most plot options for plotting a board (especially the viewport)
+ * Important thing:
+ * page size is the 'drawing' page size,
+ * paper size is the physical page size
+ */
+static void initializePlotter( PLOTTER *aPlotter, BOARD * aBoard,
+ PCB_PLOT_PARAMS *aPlotOpts )
+{
+ PAGE_INFO pageA4( wxT( "A4" ) );
+ const PAGE_INFO& pageInfo = aBoard->GetPageSettings();
+ const PAGE_INFO* sheet_info;
+ double paperscale; // Page-to-paper ratio
+ wxSize paperSizeIU;
+ wxSize pageSizeIU( pageInfo.GetSizeIU() );
+ bool autocenter = false;
+
+ /* Special options: to fit the sheet to an A4 sheet replace
+ the paper size. However there is a difference between
+ the autoscale and the a4paper option:
+ - Autoscale fits the board to the paper size
+ - A4paper fits the original paper size to an A4 sheet
+ - Both of them fit the board to an A4 sheet
+ */
+ if( aPlotOpts->GetA4Output() ) // Fit paper to A4
+ {
+ sheet_info = &pageA4;
+ paperSizeIU = pageA4.GetSizeIU();
+ paperscale = (double) paperSizeIU.x / pageSizeIU.x;
+ autocenter = true;
+ }
+ else
+ {
+ sheet_info = &pageInfo;
+ paperSizeIU = pageSizeIU;
+ paperscale = 1;
+
+ // Need autocentering only if scale is not 1:1
+ autocenter = (aPlotOpts->GetScale() != 1.0);
+ }
+
+ EDA_RECT bbox = aBoard->ComputeBoundingBox();
+ wxPoint boardCenter = bbox.Centre();
+ wxSize boardSize = bbox.GetSize();
+
+ double compound_scale;
+
+ /* Fit to 80% of the page if asked; it could be that the board is empty,
+ * in this case regress to 1:1 scale */
+ if( aPlotOpts->GetAutoScale() && boardSize.x > 0 && boardSize.y > 0 )
+ {
+ double xscale = (paperSizeIU.x * 0.8) / boardSize.x;
+ double yscale = (paperSizeIU.y * 0.8) / boardSize.y;
+
+ compound_scale = std::min( xscale, yscale ) * paperscale;
+ }
+ else
+ compound_scale = aPlotOpts->GetScale() * paperscale;
+
+
+ /* For the plot offset we have to keep in mind the auxiliary origin
+ too: if autoscaling is off we check that plot option (i.e. autoscaling
+ overrides auxiliary origin) */
+ wxPoint offset( 0, 0);
+
+ if( autocenter )
+ {
+ offset.x = KiROUND( boardCenter.x - ( paperSizeIU.x / 2.0 ) / compound_scale );
+ offset.y = KiROUND( boardCenter.y - ( paperSizeIU.y / 2.0 ) / compound_scale );
+ }
+ else
+ {
+ if( aPlotOpts->GetUseAuxOrigin() )
+ offset = aBoard->GetAuxOrigin();
+ }
+
+ /* Configure the plotter object with all the stuff computed and
+ most of that taken from the options */
+ aPlotter->SetPageSettings( *sheet_info );
+
+ aPlotter->SetViewport( offset, IU_PER_DECIMILS, compound_scale,
+ aPlotOpts->GetMirror() );
+
+ // has meaning only for gerber plotter. Must be called only after SetViewport
+ aPlotter->SetGerberCoordinatesFormat( aPlotOpts->GetGerberPrecision() );
+
+ aPlotter->SetDefaultLineWidth( aPlotOpts->GetLineWidth() );
+ aPlotter->SetCreator( wxT( "PCBNEW" ) );
+ aPlotter->SetColorMode( false ); // default is plot in Black and White.
+ aPlotter->SetTextMode( aPlotOpts->GetTextMode() );
+}
+
+/** Prefill in black an area a little bigger than the board to prepare for the
+ * negative plot */
+static void FillNegativeKnockout( PLOTTER *aPlotter, const EDA_RECT &aBbbox )
+{
+ const int margin = 5 * IU_PER_MM; // Add a 5 mm margin around the board
+ aPlotter->SetNegative( true );
+ aPlotter->SetColor( WHITE ); // Which will be plotted as black
+ EDA_RECT area = aBbbox;
+ area.Inflate( margin );
+ aPlotter->Rect( area.GetOrigin(), area.GetEnd(), FILLED_SHAPE );
+ aPlotter->SetColor( BLACK );
+}
+
+/** Calculate the effective size of HPGL pens and set them in the
+ * plotter object */
+static void ConfigureHPGLPenSizes( HPGL_PLOTTER *aPlotter,
+ PCB_PLOT_PARAMS *aPlotOpts )
+{
+ /* Compute pen_dim (the value is given in mils) in pcb units,
+ with plot scale (if Scale is 2, pen diameter value is always m_HPGLPenDiam
+ so apparent pen diam is actually pen diam / Scale */
+ int pen_diam = KiROUND( aPlotOpts->GetHPGLPenDiameter() * IU_PER_MILS /
+ aPlotOpts->GetScale() );
+
+ // compute pen_overlay (value comes in mils) in pcb units with plot scale
+ if( aPlotOpts->GetHPGLPenOverlay() < 0 )
+ aPlotOpts->SetHPGLPenOverlay( 0 );
+
+ if( aPlotOpts->GetHPGLPenOverlay() >= aPlotOpts->GetHPGLPenDiameter() )
+ aPlotOpts->SetHPGLPenOverlay( aPlotOpts->GetHPGLPenDiameter() - 1 );
+
+ int pen_overlay = KiROUND( aPlotOpts->GetHPGLPenOverlay() * IU_PER_MILS /
+ aPlotOpts->GetScale() );
+
+ // Set HPGL-specific options and start
+ aPlotter->SetPenSpeed( aPlotOpts->GetHPGLPenSpeed() );
+ aPlotter->SetPenNumber( aPlotOpts->GetHPGLPenNum() );
+ aPlotter->SetPenOverlap( pen_overlay );
+ aPlotter->SetPenDiameter( pen_diam );
+}
+
+/** Open a new plotfile using the options (and especially the format)
+ * specified in the options and prepare the page for plotting.
+ * Return the plotter object if OK, NULL if the file is not created
+ * (or has a problem)
+ */
+PLOTTER* StartPlotBoard( BOARD *aBoard, PCB_PLOT_PARAMS *aPlotOpts,
+ int aLayer,
+ const wxString& aFullFileName,
+ const wxString& aSheetDesc )
+{
+ // Create the plotter driver and set the few plotter specific
+ // options
+ PLOTTER* plotter = NULL;
+
+ switch( aPlotOpts->GetFormat() )
+ {
+ case PLOT_FORMAT_DXF:
+ plotter = new DXF_PLOTTER();
+ break;
+
+ case PLOT_FORMAT_POST:
+ PS_PLOTTER* PS_plotter;
+ PS_plotter = new PS_PLOTTER();
+ PS_plotter->SetScaleAdjust( aPlotOpts->GetFineScaleAdjustX(),
+ aPlotOpts->GetFineScaleAdjustY() );
+ plotter = PS_plotter;
+ break;
+
+ case PLOT_FORMAT_PDF:
+ plotter = new PDF_PLOTTER();
+ break;
+
+ case PLOT_FORMAT_HPGL:
+ HPGL_PLOTTER* HPGL_plotter;
+ HPGL_plotter = new HPGL_PLOTTER();
+
+ /* HPGL options are a little more convoluted to compute, so
+ they're split in an other function */
+ ConfigureHPGLPenSizes( HPGL_plotter, aPlotOpts );
+ plotter = HPGL_plotter;
+ break;
+
+ case PLOT_FORMAT_GERBER:
+ plotter = new GERBER_PLOTTER();
+ break;
+
+ case PLOT_FORMAT_SVG:
+ plotter = new SVG_PLOTTER();
+ break;
+
+ default:
+ wxASSERT( false );
+ return NULL;
+ }
+
+ // Compute the viewport and set the other options
+
+ // page layout is not mirrored, so temporary change mirror option
+ // just to plot the page layout
+ PCB_PLOT_PARAMS plotOpts = *aPlotOpts;
+
+ if( plotOpts.GetPlotFrameRef() && plotOpts.GetMirror() )
+ plotOpts.SetMirror( false );
+
+ initializePlotter( plotter, aBoard, &plotOpts );
+
+ if( plotter->OpenFile( aFullFileName ) )
+ {
+ plotter->ClearHeaderLinesList();
+
+ // For the Gerber "file function" attribute, set the layer number
+ if( plotter->GetPlotterType() == PLOT_FORMAT_GERBER )
+ {
+ bool useX2mode = plotOpts.GetUseGerberAttributes();
+
+ if( useX2mode )
+ AddGerberX2Attribute( plotter, aBoard, aLayer );
+ else
+ plotter->AddLineToHeader( GetGerberFileFunctionAttribute(
+ aBoard, aLayer, true ) );
+ }
+
+ plotter->StartPlot();
+
+ // Plot the frame reference if requested
+ if( aPlotOpts->GetPlotFrameRef() )
+ {
+ PlotWorkSheet( plotter, aBoard->GetTitleBlock(),
+ aBoard->GetPageSettings(),
+ 1, 1, // Only one page
+ aSheetDesc, aBoard->GetFileName() );
+
+ if( aPlotOpts->GetMirror() )
+ initializePlotter( plotter, aBoard, aPlotOpts );
+ }
+
+ /* When plotting a negative board: draw a black rectangle
+ * (background for plot board in white) and switch the current
+ * color to WHITE; note the color inversion is actually done
+ * in the driver (if supported) */
+ if( aPlotOpts->GetNegative() )
+ {
+ EDA_RECT bbox = aBoard->ComputeBoundingBox();
+ FillNegativeKnockout( plotter, bbox );
+ }
+
+ return plotter;
+ }
+
+ delete plotter;
+ return NULL;
+}