mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 10:13:19 +02:00
Previously, all overlapping polygons (pads and min thickness areas to remove) were merged. Drawback: pads attributes are lost. In Gerber this is annoying. Now the pads are plot as flashed or Regions items, and min thickness areas are added but shapes are not merged and keep their attributes.
1257 lines
45 KiB
C++
1257 lines
45 KiB
C++
/**
|
|
* @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-2020 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 <plotter.h>
|
|
#include <base_struct.h>
|
|
#include <gr_text.h>
|
|
#include <geometry/geometry_utils.h>
|
|
#include <trigo.h>
|
|
#include <pcb_base_frame.h>
|
|
#include <macros.h>
|
|
#include <math/util.h> // for KiROUND
|
|
|
|
#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_pcb_target.h>
|
|
#include <class_dimension.h>
|
|
|
|
#include <pcbnew.h>
|
|
#include <pcbplot.h>
|
|
#include <gbr_metadata.h>
|
|
|
|
/*
|
|
* 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( auto Module : aBoard->Modules() )
|
|
{
|
|
aPlotter->StartBlock( NULL );
|
|
|
|
for( auto pad : Module->Pads() )
|
|
{
|
|
// See if the pad is on this layer
|
|
LSET masklayer = pad->GetLayerSet();
|
|
if( !( masklayer & layersmask_plotpads ).any() )
|
|
continue;
|
|
|
|
COLOR4D color = COLOR4D::BLACK;
|
|
|
|
if( layersmask_plotpads[B_SilkS] )
|
|
color = aPlotter->ColorSettings()->GetColor( B_SilkS );
|
|
|
|
if( layersmask_plotpads[F_SilkS] )
|
|
{
|
|
color = ( color == COLOR4D::BLACK ) ?
|
|
aPlotter->ColorSettings()->GetColor( F_SilkS ) : color;
|
|
}
|
|
|
|
itemplotter.PlotPad( pad, color, SKETCH );
|
|
}
|
|
|
|
aPlotter->EndBlock( NULL );
|
|
}
|
|
}
|
|
|
|
// Plot footprints fields (ref, value ...)
|
|
for( auto module : aBoard->Modules() )
|
|
{
|
|
if( ! itemplotter.PlotAllTextsModule( module ) )
|
|
{
|
|
wxLogMessage( _( "Your BOARD has a bad layer number for footprint %s" ),
|
|
module->GetReference() );
|
|
}
|
|
}
|
|
|
|
// Plot filled areas
|
|
aPlotter->StartBlock( NULL );
|
|
|
|
// Plot all zones together so we don't end up with divots where zones touch each other.
|
|
ZONE_CONTAINER* zone = nullptr;
|
|
SHAPE_POLY_SET aggregateArea;
|
|
|
|
for( ZONE_CONTAINER* candidate : aBoard->Zones() )
|
|
{
|
|
if( !aLayerMask[ candidate->GetLayer() ] )
|
|
continue;
|
|
|
|
if( !zone )
|
|
zone = candidate;
|
|
|
|
aggregateArea.BooleanAdd( candidate->GetFilledPolysList(), SHAPE_POLY_SET::PM_FAST );
|
|
}
|
|
|
|
if( zone )
|
|
{
|
|
aggregateArea.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
itemplotter.PlotFilledAreas( zone, aggregateArea );
|
|
}
|
|
|
|
aPlotter->EndBlock( NULL );
|
|
}
|
|
|
|
void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_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 texts:
|
|
for( auto module : aBoard->Modules() )
|
|
{
|
|
if( ! itemplotter.PlotAllTextsModule( module ) )
|
|
{
|
|
wxLogMessage( _( "Your BOARD has a bad layer number for footprint %s" ),
|
|
module->GetReference() );
|
|
}
|
|
}
|
|
|
|
// Draw footprint other graphic items:
|
|
for( auto module : aBoard->Modules() )
|
|
{
|
|
for( auto item : module->GraphicalItems() )
|
|
{
|
|
if( item->Type() == PCB_MODULE_EDGE_T && aLayerMask[ item->GetLayer() ] )
|
|
itemplotter.Plot_1_EdgeModule( (EDGE_MODULE*) item );
|
|
}
|
|
}
|
|
|
|
// Plot footprint pads
|
|
for( auto module : aBoard->Modules() )
|
|
{
|
|
aPlotter->StartBlock( NULL );
|
|
|
|
for( auto pad : module->Pads() )
|
|
{
|
|
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();
|
|
}
|
|
|
|
// Now offset the pad size by margin + width_adj
|
|
// this is easy for most shapes, but not for a trapezoid or a custom shape
|
|
wxSize padPlotsSize;
|
|
wxSize extraSize = margin * 2;
|
|
extraSize.x += width_adj;
|
|
extraSize.y += width_adj;
|
|
|
|
// Store these parameters that can be modified to plot inflated/deflated pads shape
|
|
wxSize deltaSize = pad->GetDelta(); // has meaning only for trapezoidal pads
|
|
PAD_SHAPE_T padShape = pad->GetShape();
|
|
double padCornerRadius = pad->GetRoundRectCornerRadius();
|
|
|
|
if( pad->GetShape() == PAD_SHAPE_TRAPEZOID )
|
|
{ // The easy way is to use BuildPadPolygon to calculate
|
|
// size and delta of the trapezoidal pad after offseting:
|
|
wxPoint coord[4];
|
|
pad->BuildPadPolygon( coord, extraSize/2, 0.0 );
|
|
// Calculate the size and delta from polygon corners coordinates:
|
|
// coord[0] is the lower left
|
|
// coord[1] is the upper left
|
|
// coord[2] is the upper right
|
|
// coord[3] is the lower right
|
|
|
|
// the size is the distance between middle of segments
|
|
// (left/right or top/bottom)
|
|
// size X is the dist between left and right middle points:
|
|
padPlotsSize.x = ( ( -coord[0].x + coord[3].x ) // the lower segment X length
|
|
+ ( -coord[1].x + coord[2].x ) ) // the upper segment X length
|
|
/ 2; // the Y size is the half sum
|
|
// size Y is the dist between top and bottom middle points:
|
|
padPlotsSize.y = ( ( coord[0].y - coord[1].y ) // the left segment Y lenght
|
|
+ ( coord[3].y - coord[2].y ) ) // the right segment Y lenght
|
|
/ 2; // the Y size is the half sum
|
|
|
|
// calculate the delta ( difference of lenght between 2 opposite edges )
|
|
// The delta.x is the delta along the X axis, therefore the delta of Y lenghts
|
|
wxSize delta;
|
|
|
|
if( coord[0].y != coord[3].y )
|
|
delta.x = coord[0].y - coord[3].y;
|
|
else
|
|
delta.y = coord[1].x - coord[0].x;
|
|
|
|
pad->SetDelta( delta );
|
|
}
|
|
else
|
|
padPlotsSize = pad->GetSize() + extraSize;
|
|
|
|
// Don't draw a null size item :
|
|
if( padPlotsSize.x <= 0 || padPlotsSize.y <= 0 )
|
|
continue;
|
|
|
|
COLOR4D color = COLOR4D::BLACK;
|
|
|
|
if( pad->GetLayerSet()[B_Cu] )
|
|
color = aPlotOpt.ColorSettings()->GetColor( LAYER_PAD_BK );
|
|
|
|
if( pad->GetLayerSet()[F_Cu] )
|
|
color = color.LegacyMix( aPlotOpt.ColorSettings()->GetColor( LAYER_PAD_FR ) );
|
|
|
|
// Temporary set the pad size to the required plot size:
|
|
wxSize tmppadsize = pad->GetSize();
|
|
|
|
switch( pad->GetShape() )
|
|
{
|
|
case PAD_SHAPE_CIRCLE:
|
|
case PAD_SHAPE_OVAL:
|
|
pad->SetSize( padPlotsSize );
|
|
|
|
if( aPlotOpt.GetSkipPlotNPTH_Pads() &&
|
|
( aPlotOpt.GetDrillMarksType() == PCB_PLOT_PARAMS::NO_DRILL_SHAPE ) &&
|
|
( pad->GetSize() == pad->GetDrillSize() ) &&
|
|
( pad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED ) )
|
|
break;
|
|
|
|
itemplotter.PlotPad( pad, color, plotMode );
|
|
break;
|
|
|
|
case PAD_SHAPE_RECT:
|
|
if( margin.x > 0 )
|
|
{
|
|
pad->SetShape( PAD_SHAPE_ROUNDRECT );
|
|
pad->SetSize( padPlotsSize );
|
|
pad->SetRoundRectCornerRadius( margin.x );
|
|
}
|
|
// Fall through
|
|
case PAD_SHAPE_TRAPEZOID:
|
|
case PAD_SHAPE_ROUNDRECT:
|
|
case PAD_SHAPE_CHAMFERED_RECT:
|
|
pad->SetSize( padPlotsSize );
|
|
itemplotter.PlotPad( pad, color, plotMode );
|
|
break;
|
|
|
|
case PAD_SHAPE_CUSTOM:
|
|
{
|
|
// inflate/deflate a custom shape is a bit complex.
|
|
// so build a similar pad shape, and inflate/deflate the polygonal shape
|
|
D_PAD dummy( *pad );
|
|
SHAPE_POLY_SET shape;
|
|
pad->MergePrimitivesAsPolygon( &shape );
|
|
// Shape polygon can have holes so use InflateWithLinkedHoles(), not Inflate()
|
|
// which can create bad shapes if margin.x is < 0
|
|
int maxError = aBoard->GetDesignSettings().m_MaxError;
|
|
int numSegs = std::max( GetArcToSegmentCount( margin.x, maxError, 360.0 ), 6 );
|
|
shape.InflateWithLinkedHoles( margin.x, numSegs, SHAPE_POLY_SET::PM_FAST );
|
|
dummy.DeletePrimitivesList();
|
|
dummy.AddPrimitivePoly( shape, 0, false );
|
|
dummy.MergePrimitivesAsPolygon();
|
|
|
|
// Be sure the anchor pad is not bigger than the deflated shape because this
|
|
// anchor will be added to the pad shape when plotting the pad. So now the
|
|
// polygonal shape is built, we can clamp the anchor size
|
|
if( margin.x < 0 ) // we expect margin.x = margin.y for custom pads
|
|
dummy.SetSize( padPlotsSize );
|
|
|
|
itemplotter.PlotPad( &dummy, color, plotMode );
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Restore the pad parameters modified by the plot code
|
|
pad->SetSize( tmppadsize );
|
|
pad->SetDelta( deltaSize );
|
|
pad->SetShape( padShape );
|
|
pad->SetRoundRectCornerRadius( padCornerRadius );
|
|
}
|
|
|
|
aPlotter->EndBlock( NULL );
|
|
}
|
|
|
|
// Plot vias on copper layers, and if aPlotOpt.GetPlotViaOnMaskLayer() is true,
|
|
// plot them on solder mask
|
|
|
|
GBR_METADATA gbr_metadata;
|
|
|
|
bool isOnCopperLayer = ( aLayerMask & LSET::AllCuMask() ).any();
|
|
|
|
if( isOnCopperLayer )
|
|
{
|
|
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_VIAPAD );
|
|
gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_NET );
|
|
}
|
|
|
|
aPlotter->StartBlock( NULL );
|
|
|
|
for( auto track : aBoard->Tracks() )
|
|
{
|
|
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;
|
|
|
|
// Some vias can be not connected (no net).
|
|
// Set the m_NotInNet for these vias to force a empty net name in gerber file
|
|
gbr_metadata.m_NetlistMetadata.m_NotInNet = Via->GetNetname().IsEmpty();
|
|
|
|
gbr_metadata.SetNetName( Via->GetNetname() );
|
|
|
|
COLOR4D color = aPlotOpt.ColorSettings()->GetColor(
|
|
LAYER_VIAS + static_cast<int>( 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, &gbr_metadata );
|
|
}
|
|
|
|
aPlotter->EndBlock( NULL );
|
|
aPlotter->StartBlock( NULL );
|
|
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
|
|
|
|
// Plot tracks (not vias) :
|
|
for( auto track : aBoard->Tracks() )
|
|
{
|
|
if( track->Type() == PCB_VIA_T )
|
|
continue;
|
|
|
|
if( !aLayerMask[track->GetLayer()] )
|
|
continue;
|
|
|
|
// Some track segments can be not connected (no net).
|
|
// Set the m_NotInNet for these segments to force a empty net name in gerber file
|
|
gbr_metadata.m_NetlistMetadata.m_NotInNet = track->GetNetname().IsEmpty();
|
|
|
|
gbr_metadata.SetNetName( track->GetNetname() );
|
|
int width = track->GetWidth() + itemplotter.getFineWidthAdj();
|
|
aPlotter->SetColor( itemplotter.getColor( track->GetLayer() ) );
|
|
aPlotter->ThickSegment( track->GetStart(), track->GetEnd(), width, plotMode, &gbr_metadata );
|
|
}
|
|
|
|
aPlotter->EndBlock( NULL );
|
|
|
|
// Plot filled ares
|
|
aPlotter->StartBlock( NULL );
|
|
|
|
// Plot all zones of the same layer & net together so we don't end up with divots where
|
|
// zones touch each other.
|
|
std::set<ZONE_CONTAINER*> plotted;
|
|
|
|
for( ZONE_CONTAINER* zone : aBoard->Zones() )
|
|
{
|
|
if( !aLayerMask[ zone->GetLayer() ] || plotted.count( zone ) )
|
|
continue;
|
|
|
|
plotted.insert( zone );
|
|
|
|
SHAPE_POLY_SET aggregateArea = zone->GetFilledPolysList();
|
|
bool needFracture = false; // If 2 or more filled areas are combined, resulting
|
|
// aggregateArea will be simplified and fractured
|
|
// (Long calculation time)
|
|
|
|
for( ZONE_CONTAINER* candidate : aBoard->Zones() )
|
|
{
|
|
if( !aLayerMask[ candidate->GetLayer() ] || plotted.count( candidate ) )
|
|
continue;
|
|
|
|
if( candidate->GetNetCode() != zone->GetNetCode() )
|
|
continue;
|
|
|
|
// Merging zones of the same net can be done only for areas
|
|
// having compatible settings for drawings:
|
|
// use or not outline thickness, and if using outline thickness,
|
|
// having the same thickness
|
|
// because after merging only one outline thickness is used
|
|
if( candidate->GetFilledPolysUseThickness() != zone->GetFilledPolysUseThickness() )
|
|
// Should not happens, because usually the same option is used for filling
|
|
continue;
|
|
|
|
if( zone->GetFilledPolysUseThickness() &&
|
|
( candidate->GetMinThickness() != zone->GetMinThickness() ) )
|
|
continue;
|
|
|
|
plotted.insert( candidate );
|
|
aggregateArea.Append( candidate->GetFilledPolysList() );
|
|
needFracture = true;
|
|
}
|
|
|
|
if( needFracture )
|
|
{
|
|
aggregateArea.Unfracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
aggregateArea.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
}
|
|
|
|
itemplotter.PlotFilledAreas( zone, aggregateArea );
|
|
}
|
|
aPlotter->EndBlock( NULL );
|
|
|
|
// 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 PCB_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, arrayDim( plot_seq ) ); seq; ++seq )
|
|
{
|
|
PCB_LAYER_ID layer = *seq;
|
|
|
|
outlines.RemoveAllContours();
|
|
aBoard->ConvertBrdLayerToPolygonalContours( layer, outlines );
|
|
|
|
outlines.Simplify( SHAPE_POLY_SET::PM_FAST );
|
|
|
|
// 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.emplace_back( (wxPoint) path.CPoint( jj ) );
|
|
|
|
// 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 )
|
|
{
|
|
int smallDrill = (aPlotOpt.GetDrillMarksType() == PCB_PLOT_PARAMS::SMALL_DRILL_SHAPE)
|
|
? SMALL_DRILL : INT_MAX;
|
|
|
|
for( auto module : aBoard->Modules() )
|
|
{
|
|
for( auto pad : module->Pads() )
|
|
{
|
|
wxSize hole = pad->GetDrillSize();
|
|
|
|
if( hole.x == 0 || hole.y == 0 )
|
|
continue;
|
|
|
|
if( hole.x == hole.y )
|
|
{
|
|
hole.x = std::min( smallDrill, hole.x );
|
|
aPlotter->Circle( pad->GetPosition(), hole.x, NO_FILL );
|
|
}
|
|
else
|
|
{
|
|
// Note: small drill marks have no significance when applied to slots
|
|
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, NULL );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Plot vias holes
|
|
for( auto track : aBoard->Tracks() )
|
|
{
|
|
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
|
|
*
|
|
* We have 2 algos:
|
|
* the initial algo, that create polygons for every shape, inflate and deflate polygons
|
|
* with Min Thickness/2, and merges the result.
|
|
* Drawback: pads attributes are lost (annoying in Gerber)
|
|
* the new algo:
|
|
* create initial polygons for every shape (pad or polygon),
|
|
* inflate and deflate polygons
|
|
* with Min Thickness/2, and merges the result (like initial algo)
|
|
* remove all initial polygons.
|
|
* The remaining polygons are areas with thickness < min thickness
|
|
* plot all initial shapes by flashing (or using regions) for pad and polygons
|
|
* (shapes will be better) and remaining polygons to
|
|
* remove areas with thickness < min thickness from final mask
|
|
*
|
|
* TODO: remove old code after more testing.
|
|
*/
|
|
#define NEW_ALGO 1
|
|
extern void KeepPolyInside( bool aInside );
|
|
|
|
void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
|
|
const PCB_PLOT_PARAMS& aPlotOpt, int aMinThickness )
|
|
{
|
|
PCB_LAYER_ID layer = aLayerMask[B_Mask] ? B_Mask : F_Mask;
|
|
|
|
// Set the current arc to segment max approx error
|
|
int currMaxError = aBoard->GetDesignSettings().m_MaxError;
|
|
aBoard->GetDesignSettings().m_MaxError = Millimeter2iu( 0.005 );
|
|
|
|
// We remove 1nm as we expand both sides of the shapes, so allowing for
|
|
// a strictly greater than or equal comparison in the shape separation (boolean add)
|
|
// means that we will end up with separate shapes that then are shrunk
|
|
int inflate = aMinThickness/2 - 1;
|
|
|
|
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 graphic items
|
|
// on this layer (like logos), not actually areas around pads.
|
|
|
|
// Normal mode to generate polygons from shapes with arcs, if any:
|
|
DisableArcRadiusCorrection( false );
|
|
|
|
itemplotter.PlotBoardGraphicItems();
|
|
|
|
for( auto module : aBoard->Modules() )
|
|
{
|
|
for( auto item : module->GraphicalItems() )
|
|
{
|
|
itemplotter.PlotAllTextsModule( module );
|
|
|
|
if( item->Type() == PCB_MODULE_EDGE_T && item->GetLayer() == layer )
|
|
itemplotter.Plot_1_EdgeModule( (EDGE_MODULE*) item );
|
|
}
|
|
}
|
|
|
|
// Build polygons for each pad shape. The size of the shape on solder mask should be size
|
|
// of pad + clearance around the pad, where clearance = solder mask clearance + extra margin.
|
|
// Extra margin is half the min width for solder mask, which is used to merge too-close shapes
|
|
// (distance < aMinThickness), and will be removed when creating the actual shapes.
|
|
|
|
// Will contain shapes inflated by inflate value that will be merged and deflated by
|
|
// inflate value to build final polygons
|
|
// After calculations the remaining polygons are polygons to plot
|
|
SHAPE_POLY_SET areas;
|
|
// Will contain exact shapes of all items on solder mask
|
|
SHAPE_POLY_SET initialPolys;
|
|
|
|
#if NEW_ALGO
|
|
// Generate polygons with arcs inside the shape or exact shape
|
|
// to minimize shape changes created by arc to segment size correction.
|
|
DisableArcRadiusCorrection( true );
|
|
#endif
|
|
|
|
// Plot pads
|
|
for( auto module : aBoard->Modules() )
|
|
{
|
|
// add shapes with their exact mask layer size in initialPolys
|
|
module->TransformPadsShapesWithClearanceToPolygon( layer, initialPolys, 0 );
|
|
// add shapes inflated by aMinThickness/2 in areas
|
|
module->TransformPadsShapesWithClearanceToPolygon( layer, areas, inflate );
|
|
}
|
|
|
|
// 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( auto track : aBoard->Tracks() )
|
|
{
|
|
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;
|
|
|
|
// add shapes with their exact mask layer size in initialPolys
|
|
via->TransformShapeWithClearanceToPolygon( initialPolys, via_clearance );
|
|
// add shapes inflated by aMinThickness/2 in areas
|
|
via->TransformShapeWithClearanceToPolygon( areas, via_margin );
|
|
}
|
|
}
|
|
|
|
// 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( ZONE_CONTAINER* zone : aBoard->Zones() )
|
|
{
|
|
if( zone->GetLayer() != layer )
|
|
continue;
|
|
|
|
// Some intersecting zones, despite being on the same layer, cannot be
|
|
// merged due to other parameters such as fillet radius. The filled areas will end up
|
|
// effectively merged though, so we want to keep the corners of such intersections sharp.
|
|
std::set<VECTOR2I> colinearCorners;
|
|
zone->GetColinearCorners( aBoard, colinearCorners );
|
|
|
|
// add shapes inflated by aMinThickness/2 in areas
|
|
zone->TransformOutlinesShapeWithClearanceToPolygon( areas, inflate + zone_margin, false,
|
|
&colinearCorners );
|
|
// add shapes with their exact mask layer size in initialPolys
|
|
zone->TransformOutlinesShapeWithClearanceToPolygon( initialPolys, zone_margin, false,
|
|
&colinearCorners );
|
|
}
|
|
|
|
int maxError = aBoard->GetDesignSettings().m_MaxError;
|
|
int numSegs = std::max( GetArcToSegmentCount( inflate, maxError, 360.0 ), 12 );
|
|
|
|
// Merge all polygons: After deflating, not merged (not overlapping) polygons
|
|
// will have the initial shape (with perhaps small changes due to deflating transform)
|
|
areas.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
areas.Deflate( inflate, numSegs );
|
|
|
|
// Restore initial settings:
|
|
aBoard->GetDesignSettings().m_MaxError = currMaxError;
|
|
|
|
// Restore normal option to build polygons from item shapes:
|
|
DisableArcRadiusCorrection( false );
|
|
|
|
#if !NEW_ALGO
|
|
// 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.SetMinThickness( 0 ); // trace polygons only
|
|
zone.SetLayer( layer );
|
|
// 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, SHAPE_POLY_SET::PM_FAST );
|
|
areas.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
|
|
itemplotter.PlotFilledAreas( &zone, areas );
|
|
#else
|
|
|
|
// Remove initial shapes: each shape will be added later, as flashed item or region
|
|
// with a suitable attribute.
|
|
// Do not merge pads is mandatory in Gerber files: They must be indentified as pads
|
|
|
|
// we deflate areas in polygons, to avoid after subtracting initial shapes
|
|
// having small artifacts due to approximations during polygon transforms
|
|
areas.BooleanSubtract( initialPolys, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
|
|
// Slightly inflate polygons to avoid any gap between them and other shapes,
|
|
// These gaps are created by arc to segments approximations
|
|
areas.Inflate( Millimeter2iu( 0.002 ),6 );
|
|
|
|
// Now, only polygons with a too small thickness are stored in areas.
|
|
areas.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
|
|
// Plot each initial shape (pads and polygons on mask layer), with suitable attributes:
|
|
PlotStandardLayer( aBoard, aPlotter, aLayerMask, aPlotOpt );
|
|
|
|
// Add shapes corresponding to areas having too small thickness.
|
|
std::vector<wxPoint> cornerList;
|
|
|
|
for( int ii = 0; ii < areas.OutlineCount(); ii++ )
|
|
{
|
|
cornerList.clear();
|
|
const SHAPE_LINE_CHAIN& path = areas.COutline( ii );
|
|
|
|
// polygon area in mm^2 :
|
|
double curr_area = path.Area() / ( IU_PER_MM * IU_PER_MM );
|
|
|
|
// Skip very small polygons: they are certainly artifacts created by
|
|
// arc approximations and polygon transforms
|
|
// (inflate/deflate transforms)
|
|
constexpr double poly_min_area_mm2 = 0.01; // 0.01 mm^2 gives a good filtering
|
|
|
|
if( curr_area < poly_min_area_mm2 )
|
|
continue;
|
|
|
|
for( int jj = 0; jj < path.PointCount(); jj++ )
|
|
cornerList.emplace_back( (wxPoint) path.CPoint( jj ) );
|
|
|
|
// Ensure the polygon is closed
|
|
if( cornerList[0] != cornerList[cornerList.size() - 1] )
|
|
cornerList.push_back( cornerList[0] );
|
|
|
|
aPlotter->PlotPoly( cornerList, FILLED_SHAPE );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/**
|
|
* 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() )
|
|
{
|
|
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();
|
|
}
|
|
|
|
aPlotter->SetPageSettings( *sheet_info );
|
|
|
|
aPlotter->SetViewport( offset, IU_PER_MILS/10, 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() );
|
|
|
|
aPlotter->SetColorSettings( aPlotOpts->ColorSettings() );
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 penDiam (the value is given in mils) in pcb units, with plot scale (if Scale is 2,
|
|
// penDiam value is always m_HPGLPenDiam so apparent penDiam is actually penDiam / Scale
|
|
int penDiam = KiROUND( aPlotOpts->GetHPGLPenDiameter() * IU_PER_MILS / aPlotOpts->GetScale() );
|
|
|
|
// Set HPGL-specific options and start
|
|
aPlotter->SetPenSpeed( aPlotOpts->GetHPGLPenSpeed() );
|
|
aPlotter->SetPenNumber( aPlotOpts->GetHPGLPenNum() );
|
|
aPlotter->SetPenDiameter( penDiam );
|
|
}
|
|
|
|
|
|
/**
|
|
* 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:
|
|
DXF_PLOTTER* DXF_plotter;
|
|
DXF_plotter = new DXF_PLOTTER();
|
|
DXF_plotter->SetUnits(
|
|
static_cast<DXF_PLOTTER::DXF_UNITS>( aPlotOpts->GetDXFPlotUnits() ) );
|
|
|
|
plotter = 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 get their own 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 temporarily change mirror option for 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.GetUseGerberX2format();
|
|
|
|
GERBER_PLOTTER* gbrplotter = static_cast <GERBER_PLOTTER*> ( plotter );
|
|
gbrplotter->UseX2format( useX2mode );
|
|
gbrplotter->UseX2NetAttributes( plotOpts.GetIncludeGerberNetlistInfo() );
|
|
|
|
// Attributes can be added using X2 format or as comment (X1 format)
|
|
AddGerberX2Attribute( plotter, aBoard, aLayer, not useX2mode );
|
|
}
|
|
|
|
plotter->StartPlot();
|
|
|
|
// Plot the frame reference if requested
|
|
if( aPlotOpts->GetPlotFrameRef() )
|
|
{
|
|
PlotWorkSheet( plotter, aBoard->GetTitleBlock(), aBoard->GetPageSettings(),
|
|
1, 1, 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;
|
|
}
|