kicad/pcbnew/plot_brditems_plotter.cpp

1062 lines
37 KiB
C++

/*
* 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 <algorithm> // for min
#include <bitset> // for bitset, operator&, __bi...
#include <math.h> // for abs
#include <stddef.h> // for NULL, size_t
#include <vector> // for vector, __vector_base<>...
#include <eda_item.h>
#include <geometry/seg.h> // for SEG
#include <geometry/shape_circle.h>
#include <geometry/shape_line_chain.h> // for SHAPE_LINE_CHAIN
#include <geometry/shape_poly_set.h> // for SHAPE_POLY_SET, SHAPE_P...
#include <geometry/shape_segment.h>
#include <kicad_string.h>
#include <math/util.h> // for KiROUND, Clamp
#include <math/vector2d.h> // for VECTOR2I
#include <plotter.h>
#include <plotters_specific.h>
#include <trigo.h>
#include <board_design_settings.h> // for BOARD_DESIGN_SETTINGS
#include <core/typeinfo.h> // for dyn_cast, PCB_DIMENSION_T
#include <outline_mode.h>
#include <gal/color4d.h> // for COLOR4D, operator!=
#include <gbr_metadata.h>
#include <gbr_netlist_metadata.h> // for GBR_NETLIST_METADATA
#include <layers_id_colors_and_visibility.h> // for LSET, IsCopperLayer
#include <pad_shapes.h> // for PAD_ATTRIB_NPTH
#include <pcbplot.h>
#include <pcb_plot_params.h> // for PCB_PLOT_PARAMS, PCB_PL...
#include <advanced_config.h>
#include <board.h>
#include <board_item.h> // for BOARD_ITEM, S_CIRCLE
#include <dimension.h>
#include <pcb_shape.h>
#include <fp_shape.h>
#include <footprint.h>
#include <fp_text.h>
#include <track.h>
#include <pad.h>
#include <pcb_target.h>
#include <pcb_text.h>
#include <zone.h>
#include <wx/debug.h> // for wxASSERT_MSG
#include <wx/wx.h> // for wxPoint, wxSize, wxArra...
/* class BRDITEMS_PLOTTER is a helper class to plot board items
* and a group of board items
*/
COLOR4D BRDITEMS_PLOTTER::getColor( LAYER_NUM aLayer )
{
COLOR4D color = ColorSettings()->GetColor( aLayer );
// A hack to avoid plotting a white item in white color, expecting the paper
// is also white: use a non white color:
if( color == COLOR4D::WHITE )
color = COLOR4D( LIGHTGRAY );
return color;
}
void BRDITEMS_PLOTTER::PlotPad( PAD* aPad, COLOR4D aColor, OUTLINE_MODE aPlotMode )
{
wxPoint shape_pos = aPad->ShapePos();
GBR_METADATA gbr_metadata;
bool plotOnCopperLayer = ( m_layerMask & LSET::AllCuMask() ).any();
bool plotOnExternalCopperLayer = ( m_layerMask & LSET::ExternalCuMask() ).any();
// Pad not on the solder mask layer cannot be soldered.
// therefore it can have a specific aperture attribute.
// Not yet in use.
// bool isPadOnBoardTechLayers = ( aPad->GetLayerSet() & LSET::AllBoardTechMask() ).any();
gbr_metadata.SetCmpReference( aPad->GetParent()->GetReference() );
if( plotOnCopperLayer )
{
gbr_metadata.SetNetAttribType( GBR_NETINFO_ALL );
gbr_metadata.SetCopper( true );
// Gives a default attribute, for instance for pads used as tracks in net ties:
// Connector pads and SMD pads are on external layers
// if on internal layers, they are certainly used as net tie
// and are similar to tracks: just conductor items
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
const bool useUTF8 = false;
const bool useQuoting = false;
gbr_metadata.SetPadName( aPad->GetName(), useUTF8, useQuoting );
if( !aPad->GetName().IsEmpty() )
gbr_metadata.SetPadPinFunction( aPad->GetPinFunction(), useUTF8, useQuoting );
gbr_metadata.SetNetName( aPad->GetNetname() );
// Some pads are mechanical pads ( through hole or smd )
// when this is the case, they have no pad name and/or are not plated.
// In this case gerber files have slightly different attributes.
if( aPad->GetAttribute() == PAD_ATTRIB_NPTH || aPad->GetName().IsEmpty() )
gbr_metadata.m_NetlistMetadata.m_NotInNet = true;
if( !plotOnExternalCopperLayer )
{
// the .P object attribute (GBR_NETLIST_METADATA::GBR_NETINFO_PAD)
// is used on outer layers, unless the component is embedded
// or a "etched" component (fp only drawn, not a physical component)
// Currently, Pcbnew does not handle embedded component, so we disable the .P
// attribute on internal layers
// Note the Gerber doc is not really clear about through holes pads about the .P
gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_NET |
GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
}
// Some attributes are reserved to the external copper layers:
// GBR_APERTURE_ATTRIB_CONNECTORPAD and GBR_APERTURE_ATTRIB_SMDPAD_CUDEF
// for instance.
// Pad with type PAD_ATTRIB_CONN or PAD_ATTRIB_SMD that is not on outer layer
// has its aperture attribute set to GBR_APERTURE_ATTRIB_CONDUCTOR
switch( aPad->GetAttribute() )
{
case PAD_ATTRIB_NPTH: // Mechanical pad through hole
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_WASHERPAD );
break;
case PAD_ATTRIB_PTH : // Pad through hole, a hole is also expected
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_COMPONENTPAD );
break;
case PAD_ATTRIB_CONN: // Connector pads, no solder paste but with solder mask.
if( plotOnExternalCopperLayer )
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONNECTORPAD );
break;
case PAD_ATTRIB_SMD: // SMD pads (on external copper layer only)
// with solder paste and mask
if( plotOnExternalCopperLayer )
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_SMDPAD_CUDEF );
break;
}
// Fabrication properties can have specific GBR_APERTURE_METADATA options
// that replace previous aperture attribute:
switch( aPad->GetProperty() )
{
case PAD_PROP_BGA: // Only applicable to outer layers
if( plotOnExternalCopperLayer )
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_BGAPAD_CUDEF );
break;
case PAD_PROP_FIDUCIAL_GLBL:
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_FIDUCIAL_GLBL );
break;
case PAD_PROP_FIDUCIAL_LOCAL:
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_FIDUCIAL_LOCAL );
break;
case PAD_PROP_TESTPOINT: // Only applicable to outer layers
if( plotOnExternalCopperLayer )
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_TESTPOINT );
break;
case PAD_PROP_HEATSINK:
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_HEATSINKPAD );
break;
case PAD_PROP_CASTELLATED:
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CASTELLATEDPAD );
break;
case PAD_PROP_NONE:
break;
}
// Ensure NPTH pads have *always* the GBR_APERTURE_ATTRIB_WASHERPAD attribute
if( aPad->GetAttribute() == PAD_ATTRIB_NPTH )
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_WASHERPAD );
}
else
{
gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
}
// Set plot color (change WHITE to LIGHTGRAY because
// the white items are not seen on a white paper or screen
m_plotter->SetColor( aColor != WHITE ? aColor : LIGHTGRAY);
if( aPlotMode == SKETCH )
m_plotter->SetCurrentLineWidth( GetSketchPadLineWidth(), &gbr_metadata );
switch( aPad->GetShape() )
{
case PAD_SHAPE_CIRCLE:
m_plotter->FlashPadCircle( shape_pos, aPad->GetSize().x, aPlotMode, &gbr_metadata );
break;
case PAD_SHAPE_OVAL:
m_plotter->FlashPadOval( shape_pos, aPad->GetSize(), aPad->GetOrientation(), aPlotMode,
&gbr_metadata );
break;
case PAD_SHAPE_RECT:
m_plotter->FlashPadRect( shape_pos, aPad->GetSize(), aPad->GetOrientation(), aPlotMode,
&gbr_metadata );
break;
case PAD_SHAPE_ROUNDRECT:
m_plotter->FlashPadRoundRect( shape_pos, aPad->GetSize(), aPad->GetRoundRectCornerRadius(),
aPad->GetOrientation(), aPlotMode, &gbr_metadata );
break;
case PAD_SHAPE_TRAPEZOID:
{
// Build the pad polygon in coordinates relative to the pad
// (i.e. for a pad at pos 0,0, rot 0.0). Needed to use aperture macros,
// to be able to create a pattern common to all trapezoid pads having the same shape
wxPoint coord[4];
// Order is lower left, lower right, upper right, upper left
wxSize half_size = aPad->GetSize()/2;
wxSize trap_delta = aPad->GetDelta()/2;
coord[0] = wxPoint( -half_size.x - trap_delta.y, half_size.y + trap_delta.x );
coord[1] = wxPoint( half_size.x + trap_delta.y, half_size.y - trap_delta.x );
coord[2] = wxPoint( half_size.x - trap_delta.y, -half_size.y + trap_delta.x );
coord[3] = wxPoint( -half_size.x + trap_delta.y, -half_size.y - trap_delta.x );
m_plotter->FlashPadTrapez( shape_pos, coord, aPad->GetOrientation(), aPlotMode,
&gbr_metadata );
}
break;
case PAD_SHAPE_CHAMFERED_RECT:
if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
{
static_cast<GERBER_PLOTTER*>( m_plotter )->FlashPadChamferRoundRect(
shape_pos, aPad->GetSize(),
aPad->GetRoundRectCornerRadius(),
aPad->GetChamferRectRatio(),
aPad->GetChamferPositions(),
aPad->GetOrientation(), aPlotMode, &gbr_metadata );
break;
}
KI_FALLTHROUGH;
default:
case PAD_SHAPE_CUSTOM:
{
const std::shared_ptr<SHAPE_POLY_SET>& polygons = aPad->GetEffectivePolygon();
if( polygons->OutlineCount() )
{
m_plotter->FlashPadCustom( shape_pos, aPad->GetSize(), aPad->GetOrientation(),
polygons.get(), aPlotMode, &gbr_metadata );
}
}
break;
}
}
void BRDITEMS_PLOTTER::PlotFootprintTextItems( FOOTPRINT* aFootprint )
{
FP_TEXT* textItem = &aFootprint->Reference();
LAYER_NUM textLayer = textItem->GetLayer();
// Reference and value are specfic items, not in graphic items list
if( GetPlotReference() && m_layerMask[textLayer]
&& ( textItem->IsVisible() || GetPlotInvisibleText() ) )
{
PlotFootprintTextItem( textItem, getColor( textLayer ) );
}
textItem = &aFootprint->Value();
textLayer = textItem->GetLayer();
if( GetPlotValue() && m_layerMask[textLayer]
&& ( textItem->IsVisible() || GetPlotInvisibleText() ) )
{
PlotFootprintTextItem( textItem, getColor( textLayer ) );
}
for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
{
textItem = dyn_cast<FP_TEXT*>( item );
if( !textItem )
continue;
if( !textItem->IsVisible() )
continue;
textLayer = textItem->GetLayer();
if( textLayer == Edge_Cuts || textLayer >= PCB_LAYER_ID_COUNT )
continue;
if( !m_layerMask[textLayer] )
continue;
if( textItem->GetText() == wxT( "${REFERENCE}" ) && !GetPlotReference() )
continue;
if( textItem->GetText() == wxT( "${VALUE}" ) && !GetPlotValue() )
continue;
PlotFootprintTextItem( textItem, getColor( textLayer ) );
}
}
// plot items like text and graphics, but not tracks and footprints
void BRDITEMS_PLOTTER::PlotBoardGraphicItems()
{
for( BOARD_ITEM* item : m_board->Drawings() )
{
switch( item->Type() )
{
case PCB_SHAPE_T:
PlotPcbShape( (PCB_SHAPE*) item );
break;
case PCB_TEXT_T:
if( item->GetLayer() != Edge_Cuts )
PlotPcbText( (PCB_TEXT*) item );
break;
case PCB_DIM_ALIGNED_T:
case PCB_DIM_CENTER_T:
case PCB_DIM_ORTHOGONAL_T:
case PCB_DIM_LEADER_T:
if( item->GetLayer() != Edge_Cuts )
PlotDimension( (DIMENSION_BASE*) item );
break;
case PCB_TARGET_T:
PlotPcbTarget( (PCB_TARGET*) item );
break;
default:
break;
}
}
}
void BRDITEMS_PLOTTER::PlotFootprintTextItem( FP_TEXT* aTextMod, COLOR4D aColor )
{
if( aColor == COLOR4D::WHITE )
aColor = COLOR4D( LIGHTGRAY );
m_plotter->SetColor( aColor );
// calculate some text parameters :
wxSize size = aTextMod->GetTextSize();
wxPoint pos = aTextMod->GetTextPos();
double orient = aTextMod->GetDrawRotation();
int thickness = aTextMod->GetEffectiveTextPenWidth();
if( aTextMod->IsMirrored() )
size.x = -size.x; // Text is mirrored
// Non bold texts thickness is clamped at 1/6 char size by the low level draw function.
// but in Pcbnew we do not manage bold texts and thickness up to 1/4 char size
// (like bold text) and we manage the thickness.
// So we set bold flag to true
bool allow_bold = true;
GBR_METADATA gbr_metadata;
gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
FOOTPRINT* parent = static_cast<FOOTPRINT*> ( aTextMod->GetParent() );
gbr_metadata.SetCmpReference( parent->GetReference() );
m_plotter->SetCurrentLineWidth( thickness );
m_plotter->Text( pos, aColor, aTextMod->GetShownText(), orient, size,
aTextMod->GetHorizJustify(), aTextMod->GetVertJustify(), thickness,
aTextMod->IsItalic(), allow_bold, false, &gbr_metadata );
}
void BRDITEMS_PLOTTER::PlotDimension( DIMENSION_BASE* aDim )
{
if( !m_layerMask[aDim->GetLayer()] )
return;
PCB_SHAPE draw;
draw.SetWidth( aDim->GetLineThickness() );
draw.SetLayer( aDim->GetLayer() );
COLOR4D color = ColorSettings()->GetColor( aDim->GetLayer() );
// Set plot color (change WHITE to LIGHTGRAY because
// the white items are not seen on a white paper or screen
m_plotter->SetColor( color != WHITE ? color : LIGHTGRAY);
PlotPcbText( &aDim->Text() );
for( const std::shared_ptr<SHAPE>& shape : aDim->GetShapes() )
{
switch( shape->Type() )
{
case SH_SEGMENT:
{
const SEG& seg = static_cast<const SHAPE_SEGMENT*>( shape.get() )->GetSeg();
draw.SetShape( S_SEGMENT );
draw.SetStart( wxPoint( seg.A ) );
draw.SetEnd( wxPoint( seg.B ) );
PlotPcbShape( &draw );
break;
}
case SH_CIRCLE:
{
wxPoint start( shape->Centre() );
int radius = static_cast<const SHAPE_CIRCLE*>( shape.get() )->GetRadius();
draw.SetShape( S_CIRCLE );
draw.SetFilled( false );
draw.SetStart( start );
draw.SetEnd( wxPoint( start.x + radius, start.y ) );
PlotPcbShape( &draw );
break;
}
default:
break;
}
}
}
void BRDITEMS_PLOTTER::PlotPcbTarget( PCB_TARGET* aMire )
{
int dx1, dx2, dy1, dy2, radius;
if( !m_layerMask[aMire->GetLayer()] )
return;
m_plotter->SetColor( getColor( aMire->GetLayer() ) );
PCB_SHAPE draw;
draw.SetShape( S_CIRCLE );
draw.SetFilled( false );
draw.SetWidth( aMire->GetWidth() );
draw.SetLayer( aMire->GetLayer() );
draw.SetStart( aMire->GetPosition() );
radius = aMire->GetSize() / 3;
if( aMire->GetShape() ) // shape X
radius = aMire->GetSize() / 2;
// Draw the circle
draw.SetEnd( wxPoint( draw.GetStart().x + radius, draw.GetStart().y ) );
PlotPcbShape( &draw );
draw.SetShape( S_SEGMENT );
radius = aMire->GetSize() / 2;
dx1 = radius;
dy1 = 0;
dx2 = 0;
dy2 = radius;
if( aMire->GetShape() ) // Shape X
{
dx1 = dy1 = radius;
dx2 = dx1;
dy2 = -dy1;
}
wxPoint mirePos( aMire->GetPosition() );
// Draw the X or + shape:
draw.SetStart( wxPoint( mirePos.x - dx1, mirePos.y - dy1 ) );
draw.SetEnd( wxPoint( mirePos.x + dx1, mirePos.y + dy1 ) );
PlotPcbShape( &draw );
draw.SetStart( wxPoint( mirePos.x - dx2, mirePos.y - dy2 ) );
draw.SetEnd( wxPoint( mirePos.x + dx2, mirePos.y + dy2 ) );
PlotPcbShape( &draw );
}
// Plot footprints graphic items (outlines)
void BRDITEMS_PLOTTER::PlotFootprintGraphicItems( FOOTPRINT* aFootprint )
{
for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
{
FP_SHAPE* shape = dynamic_cast<FP_SHAPE*>( item );
if( shape && m_layerMask[ shape->GetLayer() ] )
PlotFootprintGraphicItem( shape );
}
}
//* Plot a graphic item (outline) relative to a footprint
void BRDITEMS_PLOTTER::PlotFootprintGraphicItem( FP_SHAPE* aShape )
{
if( aShape->Type() != PCB_FP_SHAPE_T )
return;
m_plotter->SetColor( getColor( aShape->GetLayer() ) );
bool sketch = GetPlotMode() == SKETCH;
int thickness = aShape->GetWidth();
wxPoint pos( aShape->GetStart() );
wxPoint end( aShape->GetEnd() );
GBR_METADATA gbr_metadata;
gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
FOOTPRINT* parent = static_cast<FOOTPRINT*> ( aShape->GetParent() );
gbr_metadata.SetCmpReference( parent->GetReference() );
bool isOnCopperLayer = ( m_layerMask & LSET::AllCuMask() ).any();
if( isOnCopperLayer )
{
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_ETCHEDCMP );
gbr_metadata.SetCopper( true );
}
else if( aShape->GetLayer() == Edge_Cuts ) // happens also when plotting copper layers
{
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_EDGECUT );
}
int radius; // Circle/arc radius.
switch( aShape->GetShape() )
{
case S_SEGMENT:
m_plotter->ThickSegment( pos, end, thickness, GetPlotMode(), &gbr_metadata );
break;
case S_RECT:
{
std::vector<wxPoint> pts = aShape->GetRectCorners();
if( sketch || thickness > 0 )
{
m_plotter->ThickSegment( pts[0], pts[1], thickness, GetPlotMode(), &gbr_metadata );
m_plotter->ThickSegment( pts[1], pts[2], thickness, GetPlotMode(), &gbr_metadata );
m_plotter->ThickSegment( pts[2], pts[3], thickness, GetPlotMode(), &gbr_metadata );
m_plotter->ThickSegment( pts[3], pts[0], thickness, GetPlotMode(), &gbr_metadata );
}
if( !sketch && aShape->IsFilled() )
{
SHAPE_LINE_CHAIN poly;
for( const wxPoint& pt : pts )
poly.Append( pt );
m_plotter->PlotPoly( poly, FILL_TYPE::FILLED_SHAPE, -1, &gbr_metadata );
}
}
break;
case S_CIRCLE:
radius = KiROUND( GetLineLength( end, pos ) );
if( aShape->IsFilled() )
m_plotter->FilledCircle( pos, radius * 2 + thickness, GetPlotMode(), &gbr_metadata );
else
m_plotter->ThickCircle( pos, radius * 2, thickness, GetPlotMode(), &gbr_metadata );
break;
case S_ARC:
{
radius = KiROUND( GetLineLength( end, pos ) );
double startAngle = ArcTangente( end.y - pos.y, end.x - pos.x );
double endAngle = startAngle + aShape->GetAngle();
// when startAngle == endAngle ThickArc() doesn't know whether it's 0 deg and 360 deg
if( std::abs( aShape->GetAngle() ) == 3600.0 )
{
m_plotter->ThickCircle( pos, radius * 2, thickness, GetPlotMode(), &gbr_metadata );
}
else
{
m_plotter->ThickArc( pos, -endAngle, -startAngle, radius, thickness, GetPlotMode(),
&gbr_metadata );
}
}
break;
case S_POLYGON:
if( aShape->IsPolyShapeValid() )
{
const std::vector<wxPoint> &polyPoints = aShape->BuildPolyPointsList();
// We must compute board coordinates from m_PolyList which are relative to the parent
// position at orientation 0
FOOTPRINT *parentFootprint = aShape->GetParentFootprint();
std::vector<wxPoint> cornerList;
cornerList.reserve( polyPoints.size() );
for( wxPoint corner : polyPoints )
{
if( parentFootprint )
{
RotatePoint( &corner, parentFootprint->GetOrientation() );
corner += parentFootprint->GetPosition();
}
cornerList.push_back( corner );
}
if( sketch || thickness > 0 )
{
for( size_t i = 1; i < cornerList.size(); i++ )
{
m_plotter->ThickSegment( cornerList[i - 1], cornerList[i], thickness,
GetPlotMode(), &gbr_metadata );
}
m_plotter->ThickSegment( cornerList.back(), cornerList.front(), thickness,
GetPlotMode(), &gbr_metadata );
}
if( !sketch && aShape->IsFilled() )
{
// This must be simplified and fractured to prevent overlapping polygons
// from generating invalid Gerber files
SHAPE_LINE_CHAIN line( cornerList );
SHAPE_POLY_SET tmpPoly;
line.SetClosed( true );
tmpPoly.AddOutline( line );
tmpPoly.Fracture( SHAPE_POLY_SET::PM_FAST );
for( int jj = 0; jj < tmpPoly.OutlineCount(); ++jj )
{
SHAPE_LINE_CHAIN &poly = tmpPoly.Outline( jj );
m_plotter->PlotPoly( poly, FILL_TYPE::FILLED_SHAPE, thickness, &gbr_metadata );
}
}
}
break;
case S_CURVE:
m_plotter->BezierCurve( aShape->GetStart(), aShape->GetBezControl1(),
aShape->GetBezControl2(), aShape->GetEnd(), 0, thickness );
break;
default:
wxASSERT_MSG( false, "Unhandled FP_SHAPE shape" );
break;
}
}
// Plot a PCB Text, i.e. a text found on a copper or technical layer
void BRDITEMS_PLOTTER::PlotPcbText( PCB_TEXT* aText )
{
wxString shownText( aText->GetShownText() );
if( shownText.IsEmpty() )
return;
if( !m_layerMask[aText->GetLayer()] )
return;
GBR_METADATA gbr_metadata;
if( IsCopperLayer( aText->GetLayer() ) )
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_NONCONDUCTOR );
COLOR4D color = getColor( aText->GetLayer() );
m_plotter->SetColor( color );
wxSize size = aText->GetTextSize();
wxPoint pos = aText->GetTextPos();
double orient = aText->GetTextAngle();
int thickness = aText->GetEffectiveTextPenWidth();
if( aText->IsMirrored() )
size.x = -size.x;
// Non bold texts thickness is clamped at 1/6 char size by the low level draw function.
// but in Pcbnew we do not manage bold texts and thickness up to 1/4 char size
// (like bold text) and we manage the thickness.
// So we set bold flag to true
bool allow_bold = true;
m_plotter->SetCurrentLineWidth( thickness );
if( aText->IsMultilineAllowed() )
{
std::vector<wxPoint> positions;
wxArrayString strings_list;
wxStringSplit( shownText, strings_list, '\n' );
positions.reserve( strings_list.Count() );
aText->GetLinePositions( positions, strings_list.Count() );
for( unsigned ii = 0; ii < strings_list.Count(); ii++ )
{
wxString& txt = strings_list.Item( ii );
m_plotter->Text( positions[ii], color, txt, orient, size, aText->GetHorizJustify(),
aText->GetVertJustify(), thickness, aText->IsItalic(),
allow_bold, false, &gbr_metadata );
}
}
else
{
m_plotter->Text( pos, color, shownText, orient, size, aText->GetHorizJustify(),
aText->GetVertJustify(), thickness, aText->IsItalic(), allow_bold,
false, &gbr_metadata );
}
}
void BRDITEMS_PLOTTER::PlotFilledAreas( ZONE* aZone, SHAPE_POLY_SET& polysList )
{
if( polysList.IsEmpty() )
return;
GBR_METADATA gbr_metadata;
bool isOnCopperLayer = aZone->IsOnCopperLayer();
if( isOnCopperLayer )
{
gbr_metadata.SetNetName( aZone->GetNetname() );
gbr_metadata.SetCopper( true );
// Zones with no net name can exist.
// they are not used to connect items, so the aperture attribute cannot
// be set as conductor
if( aZone->GetNetname().IsEmpty() )
{
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_NONCONDUCTOR );
}
else
{
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_NET );
}
}
// We need a buffer to store corners coordinates:
std::vector< wxPoint > cornerList;
m_plotter->SetColor( getColor( aZone->GetLayer() ) );
m_plotter->StartBlock( nullptr ); // Clean current object attributes
/* Plot all filled areas: filled areas have a filled area and a thick
* outline (depending on the fill area option we must plot the filled area itself
* and plot the thick outline itself, if the thickness has meaning (at least is > 1)
*
* in non filled mode the outline is plotted, but not the filling items
*/
int outline_thickness = aZone->GetFilledPolysUseThickness() ? aZone->GetMinThickness() : 0;
for( int idx = 0; idx < polysList.OutlineCount(); ++idx )
{
SHAPE_LINE_CHAIN& outline = polysList.Outline( idx );
cornerList.clear();
cornerList.reserve( outline.PointCount() );
for( int ic = 0; ic < outline.PointCount(); ++ic )
{
cornerList.emplace_back( wxPoint( outline.CPoint( ic ) ) );
}
if( cornerList.size() ) // Plot the current filled area outline
{
// First, close the outline
if( cornerList[0] != cornerList[cornerList.size() - 1] )
cornerList.push_back( cornerList[0] );
// Plot the current filled area (as region for Gerber plotter
// to manage attributes) and its outline for thick outline
if( GetPlotMode() == FILLED )
{
if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
{
if( outline_thickness > 0 )
{
m_plotter->PlotPoly( cornerList, FILL_TYPE::NO_FILL, outline_thickness,
&gbr_metadata );
}
static_cast<GERBER_PLOTTER*>( m_plotter )->PlotGerberRegion( cornerList,
&gbr_metadata );
}
else
{
m_plotter->PlotPoly( cornerList, FILL_TYPE::FILLED_SHAPE, outline_thickness,
&gbr_metadata );
}
}
else
{
if( outline_thickness )
{
for( unsigned jj = 1; jj < cornerList.size(); jj++ )
{
m_plotter->ThickSegment( cornerList[jj -1], cornerList[jj],
outline_thickness, GetPlotMode(), &gbr_metadata );
}
}
m_plotter->SetCurrentLineWidth( -1 );
}
}
}
m_plotter->EndBlock( nullptr ); // Clear object attributes
}
/* Plot items type PCB_SHAPE on layers allowed by aLayerMask
*/
void BRDITEMS_PLOTTER::PlotPcbShape( PCB_SHAPE* aShape )
{
if( !m_layerMask[aShape->GetLayer()] )
return;
int radius = 0;
double StAngle = 0, EndAngle = 0;
bool sketch = GetPlotMode() == SKETCH;
int thickness = aShape->GetWidth();
m_plotter->SetColor( getColor( aShape->GetLayer() ) );
wxPoint start( aShape->GetStart() );
wxPoint end( aShape->GetEnd() );
GBR_METADATA gbr_metadata;
if( aShape->GetLayer() == Edge_Cuts )
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_EDGECUT );
if( IsCopperLayer( aShape->GetLayer() ) )
// Graphic items (PCB_SHAPE, TEXT) having no net have the NonConductor attribute
// Graphic items having a net have the Conductor attribute, but are not (yet?)
// supported in Pcbnew
gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_NONCONDUCTOR );
switch( aShape->GetShape() )
{
case S_SEGMENT:
m_plotter->ThickSegment( start, end, thickness, GetPlotMode(), &gbr_metadata );
break;
case S_CIRCLE:
radius = KiROUND( GetLineLength( end, start ) );
if( aShape->IsFilled() )
m_plotter->FilledCircle( start, radius * 2 + thickness, GetPlotMode(), &gbr_metadata );
else
m_plotter->ThickCircle( start, radius * 2, thickness, GetPlotMode(), &gbr_metadata );
break;
case S_ARC:
radius = KiROUND( GetLineLength( end, start ) );
StAngle = ArcTangente( end.y - start.y, end.x - start.x );
EndAngle = StAngle + aShape->GetAngle();
// when startAngle == endAngle ThickArc() doesn't know whether it's 0 deg and 360 deg
if( std::abs( aShape->GetAngle() ) == 3600.0 )
{
m_plotter->ThickCircle( start, radius * 2, thickness, GetPlotMode(), &gbr_metadata );
}
else
{
m_plotter->ThickArc( start, -EndAngle, -StAngle, radius, thickness, GetPlotMode(),
&gbr_metadata );
}
break;
case S_CURVE:
m_plotter->BezierCurve( aShape->GetStart(), aShape->GetBezControl1(),
aShape->GetBezControl2(), aShape->GetEnd(), 0, thickness );
break;
case S_POLYGON:
if( aShape->IsPolyShapeValid() )
{
if( sketch || thickness > 0 )
{
for( auto it = aShape->GetPolyShape().CIterateSegments( 0 ); it; it++ )
{
auto seg = it.Get();
m_plotter->ThickSegment( wxPoint( seg.A ), wxPoint( seg.B ),
thickness, GetPlotMode(), &gbr_metadata );
}
}
if( !sketch && aShape->IsFilled() )
{
m_plotter->SetCurrentLineWidth( thickness, &gbr_metadata );
// Draw the polygon: only one polygon is expected
// However we provide a multi polygon shape drawing
// ( for the future or to show a non expected shape )
// This must be simplified and fractured to prevent overlapping polygons
// from generating invalid Gerber files
auto tmpPoly = SHAPE_POLY_SET( aShape->GetPolyShape() );
tmpPoly.Fracture( SHAPE_POLY_SET::PM_FAST );
for( int jj = 0; jj < tmpPoly.OutlineCount(); ++jj )
{
SHAPE_LINE_CHAIN& poly = tmpPoly.Outline( jj );
m_plotter->PlotPoly( poly, FILL_TYPE::FILLED_SHAPE, thickness, &gbr_metadata );
}
}
}
break;
case S_RECT:
{
std::vector<wxPoint> pts = aShape->GetRectCorners();
if( sketch || thickness > 0 )
{
m_plotter->ThickSegment( pts[0], pts[1], thickness, GetPlotMode(), &gbr_metadata );
m_plotter->ThickSegment( pts[1], pts[2], thickness, GetPlotMode(), &gbr_metadata );
m_plotter->ThickSegment( pts[2], pts[3], thickness, GetPlotMode(), &gbr_metadata );
m_plotter->ThickSegment( pts[3], pts[0], thickness, GetPlotMode(), &gbr_metadata );
}
if( !sketch && aShape->IsFilled() )
{
SHAPE_LINE_CHAIN poly;
for( const wxPoint& pt : pts )
poly.Append( pt );
m_plotter->PlotPoly( poly, FILL_TYPE::FILLED_SHAPE, -1, &gbr_metadata );
}
}
break;
default:
wxASSERT_MSG( false, "Unhandled PCB_SHAPE shape" );
m_plotter->ThickSegment( start, end, thickness, GetPlotMode(), &gbr_metadata );
}
}
/** Helper function to plot a single drill mark. It compensate and clamp
* the drill mark size depending on the current plot options
*/
void BRDITEMS_PLOTTER::plotOneDrillMark( PAD_DRILL_SHAPE_T aDrillShape, const wxPoint &aDrillPos,
wxSize aDrillSize, const wxSize &aPadSize,
double aOrientation, int aSmallDrill )
{
// Small drill marks have no significance when applied to slots
if( aSmallDrill && aDrillShape == PAD_DRILL_SHAPE_CIRCLE )
aDrillSize.x = std::min( aSmallDrill, aDrillSize.x );
// Round holes only have x diameter, slots have both
aDrillSize.x -= getFineWidthAdj();
aDrillSize.x = Clamp( 1, aDrillSize.x, aPadSize.x - 1 );
if( aDrillShape == PAD_DRILL_SHAPE_OBLONG )
{
aDrillSize.y -= getFineWidthAdj();
aDrillSize.y = Clamp( 1, aDrillSize.y, aPadSize.y - 1 );
m_plotter->FlashPadOval( aDrillPos, aDrillSize, aOrientation, GetPlotMode(), NULL );
}
else
{
m_plotter->FlashPadCircle( aDrillPos, aDrillSize.x, GetPlotMode(), NULL );
}
}
void BRDITEMS_PLOTTER::PlotDrillMarks()
{
/* If small drills marks were requested prepare a clamp value to pass
to the helper function */
int smallDrill = GetDrillMarksType() == PCB_PLOT_PARAMS::SMALL_DRILL_SHAPE
? Millimeter2iu( ADVANCED_CFG::GetCfg().m_SmallDrillMarkSize ) : 0;
/* In the filled trace mode drill marks are drawn white-on-black to scrape
the underlying pad. This works only for drivers supporting color change,
obviously... it means that:
- PS, SVG and PDF output is correct (i.e. you have a 'donut' pad)
- In HPGL you can't see them
- In gerbers you can't see them, too. This is arguably the right thing to
do since having drill marks and high speed drill stations is a sure
recipe for broken tools and angry manufacturers. If you *really* want them
you could start a layer with negative polarity to scrape the film.
- In DXF they go into the 'WHITE' layer. This could be useful.
*/
if( GetPlotMode() == FILLED )
m_plotter->SetColor( WHITE );
for( TRACK* tracks : m_board->Tracks() )
{
const VIA* via = dyn_cast<const VIA*>( tracks );
if( via )
{
plotOneDrillMark( PAD_DRILL_SHAPE_CIRCLE, via->GetStart(),
wxSize( via->GetDrillValue(), 0 ),
wxSize( via->GetWidth(), 0 ), 0, smallDrill );
}
}
for( FOOTPRINT* footprint : m_board->Footprints() )
{
for( PAD* pad : footprint->Pads() )
{
if( pad->GetDrillSize().x == 0 )
continue;
plotOneDrillMark( pad->GetDrillShape(), pad->GetPosition(), pad->GetDrillSize(),
pad->GetSize(), pad->GetOrientation(), smallDrill );
}
}
if( GetPlotMode() == FILLED )
m_plotter->SetColor( BLACK );
}