/** * @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-2012 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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, long aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt ) { BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt ); itemplotter.SetLayerMask( 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) int 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 &= ~(SILKSCREEN_LAYER_BACK | SILKSCREEN_LAYER_FRONT ); if( layersmask_plotpads ) { for( MODULE* Module = aBoard->m_Modules; Module; Module = Module->Next() ) { for( D_PAD * pad = Module->m_Pads; pad != NULL; pad = pad->Next() ) { // See if the pad is on this layer int masklayer = pad->GetLayerMask(); if( (masklayer & layersmask_plotpads) == 0 ) continue; EDA_COLOR_T color = ColorFromInt(0); if( (layersmask_plotpads & SILKSCREEN_LAYER_BACK) ) color = aBoard->GetLayerColor( SILKSCREEN_N_BACK ); if((layersmask_plotpads & SILKSCREEN_LAYER_FRONT ) ) color = ColorFromInt( color | aBoard->GetLayerColor( SILKSCREEN_N_FRONT ) ); itemplotter.PlotPad( pad, color, LINE ); } } } // 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 module %s" ), GetChars( module->GetReference() ) ); } } // Plot filled areas for( int ii = 0; ii < aBoard->GetAreaCount(); ii++ ) { ZONE_CONTAINER* edge_zone = aBoard->GetArea( ii ); if( ( ( 1 << edge_zone->GetLayer() ) & aLayerMask ) == 0 ) 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 != NULL; seg = seg->Next() ) { if( ( ( 1 << seg->GetLayer() ) & aLayerMask ) == 0 ) continue; aPlotter->ThickSegment( seg->m_Start, seg->m_End, seg->m_Width, itemplotter.GetMode() ); } } void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, int aLayer, const PCB_PLOT_PARAMS& aPlotOpt ) { PCB_PLOT_PARAMS plotOpt = aPlotOpt; // 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. int layer_mask = GetLayerMask( aLayer ); if( !aPlotOpt.GetExcludeEdgeLayer() ) layer_mask |= EDGE_LAYER; switch( aLayer ) { case FIRST_COPPER_LAYER: case LAYER_N_2: case LAYER_N_3: case LAYER_N_4: case LAYER_N_5: case LAYER_N_6: case LAYER_N_7: case LAYER_N_8: case LAYER_N_9: case LAYER_N_10: case LAYER_N_11: case LAYER_N_12: case LAYER_N_13: case LAYER_N_14: case LAYER_N_15: case LAST_COPPER_LAYER: // Skip NPTH pads on copper layers ( only if hole size == pad size ): plotOpt.SetSkipPlotNPTH_Pads( true ); // Drill mark will be plotted, // if drill mark is SMALL_DRILL_SHAPE or FULL_DRILL_SHAPE PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt ); break; case SOLDERMASK_N_BACK: case SOLDERMASK_N_FRONT: plotOpt.SetSkipPlotNPTH_Pads( false ); // Disable plot pad holes plotOpt.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE ); PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt ); break; case SOLDERPASTE_N_BACK: case SOLDERPASTE_N_FRONT: plotOpt.SetSkipPlotNPTH_Pads( false ); // Disable plot pad holes plotOpt.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE ); PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt ); break; case SILKSCREEN_N_FRONT: case SILKSCREEN_N_BACK: PlotSilkScreen( aBoard, aPlotter, layer_mask, plotOpt ); // Gerber: Subtract soldermask from silkscreen if enabled if( aPlotter->GetPlotterType() == PLOT_FORMAT_GERBER && plotOpt.GetSubtractMaskFromSilk() ) { if( aLayer == SILKSCREEN_N_FRONT ) layer_mask = GetLayerMask( SOLDERMASK_N_FRONT ); else layer_mask = GetLayerMask( SOLDERMASK_N_BACK ); // Create the mask to substract 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; default: PlotSilkScreen( 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, long aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt ) { BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt ); itemplotter.SetLayerMask( aLayerMask ); EDA_DRAW_MODE_T plotMode = aPlotOpt.GetMode(); // 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 module %s" ), GetChars( module->GetReference() ) ); } } for( MODULE* module = aBoard->m_Modules; module; module = module->Next() ) { for( BOARD_ITEM* item = module->m_Drawings; item; item = item->Next() ) { if( ! (aLayerMask & GetLayerMask( 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->m_Pads; pad; pad = pad->Next() ) { if( (pad->GetLayerMask() & aLayerMask) == 0 ) continue; wxSize margin; double width_adj = 0; if( aLayerMask & ALL_CU_LAYERS ) width_adj = itemplotter.getFineWidthAdj(); switch( aLayerMask & ( SOLDERMASK_LAYER_BACK | SOLDERMASK_LAYER_FRONT | SOLDERPASTE_LAYER_BACK | SOLDERPASTE_LAYER_FRONT ) ) { case SOLDERMASK_LAYER_FRONT: case SOLDERMASK_LAYER_BACK: margin.x = margin.y = pad->GetSolderMaskMargin(); break; case SOLDERPASTE_LAYER_FRONT: case SOLDERPASTE_LAYER_BACK: margin = pad->GetSolderPasteMargin(); break; default: break; } 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->GetLayerMask() & LAYER_BACK) ) color = aBoard->GetVisibleElementColor( PAD_BK_VISIBLE ); if((pad->GetLayerMask() & LAYER_FRONT ) ) 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_CIRCLE: case PAD_OVAL: if( aPlotOpt.GetSkipPlotNPTH_Pads() && (pad->GetSize() == pad->GetDrillSize()) && (pad->GetAttribute() == PAD_HOLE_NOT_PLATED) ) break; // Fall through: case PAD_TRAPEZOID: case PAD_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() ) { if( track->Type() != PCB_VIA_T ) continue; SEGVIA* Via = (SEGVIA*) track; // 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 int via_mask_layer = Via->ReturnMaskLayer(); if( aPlotOpt.GetPlotViaOnMaskLayer() ) { if( via_mask_layer & LAYER_BACK ) via_mask_layer |= SOLDERMASK_LAYER_BACK; if( via_mask_layer & LAYER_FRONT ) via_mask_layer |= SOLDERMASK_LAYER_FRONT; } if( ( via_mask_layer & aLayerMask ) == 0 ) 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 & ( SOLDERMASK_LAYER_BACK | SOLDERMASK_LAYER_FRONT ) ) ) via_margin = aBoard->GetDesignSettings().m_SolderMaskMargin; if( aLayerMask & ALL_CU_LAYERS ) width_adj = itemplotter.getFineWidthAdj(); int diameter = Via->m_Width + 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->m_Shape); // 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->m_Start, diameter, plotMode ); } // Plot tracks (not vias) : for( TRACK* track = aBoard->m_Track; track; track = track->Next() ) { if( track->Type() == PCB_VIA_T ) continue; if( (GetLayerMask( track->GetLayer() ) & aLayerMask) == 0 ) continue; int width = track->m_Width + itemplotter.getFineWidthAdj(); aPlotter->SetColor( itemplotter.getColor( track->GetLayer() ) ); aPlotter->ThickSegment( track->m_Start, track->m_End, width, plotMode ); } // Plot zones (outdated, for old boards compatibility): for( TRACK* track = aBoard->m_Zone; track; track = track->Next() ) { if( (GetLayerMask( track->GetLayer() ) & aLayerMask) == 0 ) continue; int width = track->m_Width + itemplotter.getFineWidthAdj(); aPlotter->SetColor( itemplotter.getColor( track->GetLayer() ) ); aPlotter->ThickSegment( track->m_Start, track->m_End, width, plotMode ); } // Plot filled ares for( int ii = 0; ii < aBoard->GetAreaCount(); ii++ ) { ZONE_CONTAINER* zone = aBoard->GetArea( ii ); if( ( ( 1 << zone->GetLayer() ) & aLayerMask ) == 0 ) 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(); } /** 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->GetOriginAxisPosition(); } /* 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() ); 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, const wxString& aFullFileName, const wxString& aSheetDesc ) { FILE* output_file = wxFopen( aFullFileName, wxT( "wt" ) ); if( output_file == NULL ) return NULL; // Create the plotter driver and set the few plotter specific // options PLOTTER *the_plotter = NULL; switch( aPlotOpts->GetFormat() ) { case PLOT_FORMAT_DXF: the_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() ); the_plotter = PS_plotter; break; case PLOT_FORMAT_PDF: the_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 ); the_plotter = HPGL_plotter; break; case PLOT_FORMAT_GERBER: the_plotter = new GERBER_PLOTTER(); break; case PLOT_FORMAT_SVG: the_plotter = new SVG_PLOTTER(); break; default: wxASSERT( false ); } // Compute the viewport and set the other options initializePlotter( the_plotter, aBoard, aPlotOpts ); if( the_plotter->OpenFile( aFullFileName ) ) { the_plotter->StartPlot(); // Plot the frame reference if requested if( aPlotOpts->GetPlotFrameRef() ) PlotWorkSheet( the_plotter, aBoard->GetTitleBlock(), aBoard->GetPageSettings(), 1, 1, // Only one page aSheetDesc, aBoard->GetFileName() ); /* 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( the_plotter, bbox ); } return the_plotter; } // error in start_plot( ) delete the_plotter; // will close also output_file return NULL; }