944 lines
32 KiB
C++
944 lines
32 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2017 Jean-Pierre Charras, jean-pierre.charras@ujf-grenoble.fr
|
|
* Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
|
|
* Copyright (C) 2012 Wayne Stambaugh <stambaughw@verizon.net>
|
|
* Copyright (C) 1992-2017 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
|
|
*/
|
|
|
|
/**
|
|
* @file class_pad_draw_functions.cpp
|
|
*/
|
|
|
|
#include <fctsys.h>
|
|
#include <gr_basic.h>
|
|
#include <common.h>
|
|
#include <trigo.h>
|
|
#include <pcb_screen.h>
|
|
#include <class_drawpanel.h>
|
|
#include <draw_graphic_text.h>
|
|
#include <layers_id_colors_and_visibility.h>
|
|
#include <pcb_edit_frame.h>
|
|
#include <pcbnew_id.h> // ID_TRACK_BUTT
|
|
#include <pcbnew.h>
|
|
#include <class_board.h>
|
|
#include <convert_basic_shapes_to_polygon.h>
|
|
|
|
|
|
|
|
/* uncomment this line to show this pad with its specfic size and color
|
|
* when it is not on copper layers, and only one solder mask layer or solder paste layer
|
|
* is displayed for this pad
|
|
* After testing this feature,I am not sure this is a good idea
|
|
* but the code is left here.
|
|
*/
|
|
|
|
//#define SHOW_PADMASK_REAL_SIZE_AND_COLOR
|
|
|
|
|
|
// Helper class to store parameters used to draw a pad
|
|
PAD_DRAWINFO::PAD_DRAWINFO()
|
|
{
|
|
m_DrawPanel = NULL;
|
|
m_DrawMode = GR_COPY;
|
|
m_Color = BLACK;
|
|
m_HoleColor = BLACK; // could be DARKGRAY;
|
|
m_NPHoleColor = YELLOW;
|
|
m_NoNetMarkColor = BLUE;
|
|
m_PadClearance = 0;
|
|
m_Display_padnum = true;
|
|
m_Display_netname = true;
|
|
m_ShowPadFilled = true;
|
|
m_ShowNCMark = true;
|
|
m_ShowNotPlatedHole = false;
|
|
m_IsPrinting = false;
|
|
}
|
|
|
|
|
|
void D_PAD::Draw( EDA_DRAW_PANEL* aPanel, wxDC* aDC, GR_DRAWMODE aDraw_mode,
|
|
const wxPoint& aOffset )
|
|
{
|
|
wxSize mask_margin; // margin (clearance) used for some non copper layers
|
|
|
|
#ifdef SHOW_PADMASK_REAL_SIZE_AND_COLOR
|
|
int showActualMaskSize = 0; /* Layer number if the actual pad size on mask layer can
|
|
* be displayed i.e. if only one layer is shown for this pad
|
|
* and this layer is a mask (solder mask or solder paste
|
|
*/
|
|
#endif
|
|
|
|
if( m_Flags & DO_NOT_DRAW )
|
|
return;
|
|
|
|
PAD_DRAWINFO drawInfo;
|
|
|
|
drawInfo.m_Offset = aOffset;
|
|
|
|
/* We can show/hide pads from the layer manager.
|
|
* options are show/hide pads on front and/or back side of the board
|
|
* For through pads, we hide them only if both sides are hidden.
|
|
* smd pads on back are hidden for all layers (copper and technical layers)
|
|
* on back side of the board
|
|
* smd pads on front are hidden for all layers (copper and technical layers)
|
|
* on front side of the board
|
|
* ECO, edge and Draw layers and not considered
|
|
*/
|
|
|
|
BOARD* brd = GetBoard();
|
|
|
|
auto frame = static_cast<PCB_EDIT_FRAME*> ( aPanel->GetParent() );
|
|
const auto& cds = frame->Settings().Colors();
|
|
|
|
bool frontVisible = brd->IsElementVisible( LAYER_PAD_FR );
|
|
bool backVisible = brd->IsElementVisible( LAYER_PAD_BK );
|
|
|
|
if( !frontVisible && !backVisible )
|
|
return;
|
|
|
|
// If pad is only on front side (no layer on back side)
|
|
// and if hide front side pads is enabled, do not draw
|
|
if( !frontVisible && !( m_layerMask & LSET::BackMask() ).any() )
|
|
return;
|
|
|
|
// If pad is only on back side (no layer on front side)
|
|
// and if hide back side pads is enabled, do not draw
|
|
if( !backVisible && !( m_layerMask & LSET::FrontMask() ).any() )
|
|
return;
|
|
|
|
|
|
wxCHECK_RET( frame != NULL, wxT( "Panel has no parent frame window." ) );
|
|
|
|
auto displ_opts = (PCB_DISPLAY_OPTIONS*)( frame->GetDisplayOptions() );
|
|
PCB_SCREEN* screen = frame->GetScreen();
|
|
|
|
if( displ_opts && displ_opts->m_DisplayPadFill == SKETCH )
|
|
drawInfo.m_ShowPadFilled = false;
|
|
else
|
|
drawInfo.m_ShowPadFilled = true;
|
|
|
|
COLOR4D color = COLOR4D::BLACK;
|
|
|
|
if( m_layerMask[F_Cu] )
|
|
{
|
|
color = cds.GetItemColor( LAYER_PAD_FR );
|
|
}
|
|
|
|
if( m_layerMask[B_Cu] )
|
|
{
|
|
color = color.LegacyMix( cds.GetItemColor( LAYER_PAD_BK ) );
|
|
}
|
|
|
|
if( color == BLACK ) // Not on a visible copper layer (i.e. still nothing to show)
|
|
{
|
|
// If the pad is on only one tech layer, use the layer color else use DARKGRAY
|
|
LSET mask_non_copper_layers = m_layerMask & ~LSET::AllCuMask();
|
|
|
|
#ifdef SHOW_PADMASK_REAL_SIZE_AND_COLOR
|
|
mask_non_copper_layers &= brd->GetVisibleLayers();
|
|
#endif
|
|
PCB_LAYER_ID pad_layer = mask_non_copper_layers.ExtractLayer();
|
|
|
|
switch( (int) pad_layer )
|
|
{
|
|
case UNDEFINED_LAYER: // More than one layer
|
|
color = DARKGRAY;
|
|
break;
|
|
|
|
case UNSELECTED_LAYER: // Shouldn't really happen...
|
|
break;
|
|
|
|
default:
|
|
color = cds.GetLayerColor( pad_layer );
|
|
#ifdef SHOW_PADMASK_REAL_SIZE_AND_COLOR
|
|
showActualMaskSize = pad_layer;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// if SMD or connector pad and high contrast mode
|
|
if( ( aDraw_mode & GR_ALLOW_HIGHCONTRAST ) &&
|
|
( GetAttribute() == PAD_ATTRIB_SMD || GetAttribute() == PAD_ATTRIB_CONN ) &&
|
|
displ_opts && displ_opts->m_ContrastModeDisplay )
|
|
{
|
|
// when routing tracks
|
|
if( frame->GetToolId() == ID_TRACK_BUTT )
|
|
{
|
|
PCB_LAYER_ID routeTop = screen->m_Route_Layer_TOP;
|
|
PCB_LAYER_ID routeBot = screen->m_Route_Layer_BOTTOM;
|
|
|
|
// if routing between copper and component layers,
|
|
// or the current layer is one of said 2 external copper layers,
|
|
// then highlight only the current layer.
|
|
if( ( screen->m_Active_Layer == F_Cu || screen->m_Active_Layer == B_Cu ) ||
|
|
( routeTop==F_Cu && routeBot==B_Cu ) ||
|
|
( routeTop==B_Cu && routeBot==F_Cu )
|
|
)
|
|
{
|
|
if( !IsOnLayer( screen->m_Active_Layer ) )
|
|
color = COLOR4D( DARKDARKGRAY );
|
|
}
|
|
// else routing between an internal signal layer and some other
|
|
// layer. Grey out all PAD_ATTRIB_SMD pads not on current or the single
|
|
// selected external layer.
|
|
else if( !IsOnLayer( screen->m_Active_Layer )
|
|
&& !IsOnLayer( routeTop )
|
|
&& !IsOnLayer( routeBot ) )
|
|
{
|
|
color = COLOR4D( DARKDARKGRAY );
|
|
}
|
|
}
|
|
// when not edting tracks, show PAD_ATTRIB_SMD components not on active layer
|
|
// as greyed out
|
|
else
|
|
{
|
|
if( !IsOnLayer( screen->m_Active_Layer ) )
|
|
color = COLOR4D( DARKDARKGRAY );
|
|
}
|
|
}
|
|
|
|
#ifdef SHOW_PADMASK_REAL_SIZE_AND_COLOR
|
|
if( showActualMaskSize )
|
|
{
|
|
switch( showActualMaskSize )
|
|
{
|
|
case B_Mask:
|
|
case F_Mask:
|
|
mask_margin.x = mask_margin.y = GetSolderMaskMargin();
|
|
break;
|
|
|
|
case B_Paste:
|
|
case F_Paste:
|
|
mask_margin = GetSolderPasteMargin();
|
|
break;
|
|
|
|
default:
|
|
// Another layer which has no margin to handle
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// if Contrast mode is ON and a technical layer active, show pads on this
|
|
// layer so we can see pads on paste or solder layer and the size of the
|
|
// mask
|
|
if( ( aDraw_mode & GR_ALLOW_HIGHCONTRAST ) &&
|
|
displ_opts && displ_opts->m_ContrastModeDisplay && !IsCopperLayer( screen->m_Active_Layer ) )
|
|
{
|
|
if( IsOnLayer( screen->m_Active_Layer ) )
|
|
{
|
|
color = cds.GetLayerColor( screen->m_Active_Layer );
|
|
|
|
// In high contrast mode, and if the active layer is the mask
|
|
// layer shows the pad size with the mask clearance
|
|
switch( screen->m_Active_Layer )
|
|
{
|
|
case B_Mask:
|
|
case F_Mask:
|
|
mask_margin.x = mask_margin.y = GetSolderMaskMargin();
|
|
break;
|
|
|
|
case B_Paste:
|
|
case F_Paste:
|
|
mask_margin = GetSolderPasteMargin();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
color = DARKDARKGRAY;
|
|
}
|
|
// If use asks for masks to be printed, then print them.
|
|
else if( screen->m_IsPrinting )
|
|
{
|
|
if( ( IsOnLayer( B_Paste ) && brd->IsLayerVisible( B_Paste ) ) ||
|
|
( IsOnLayer( F_Paste ) && brd->IsLayerVisible( F_Paste ) ) )
|
|
{
|
|
mask_margin = GetSolderPasteMargin();
|
|
}
|
|
|
|
if( ( IsOnLayer( B_Mask ) && brd->IsLayerVisible( B_Mask ) ) ||
|
|
( IsOnLayer( F_Mask ) && brd->IsLayerVisible( F_Mask ) ) )
|
|
{
|
|
mask_margin.x = std::max( mask_margin.x, GetSolderMaskMargin() );
|
|
mask_margin.y = std::max( mask_margin.y, GetSolderMaskMargin() );
|
|
}
|
|
}
|
|
|
|
if( ( aDraw_mode & GR_HIGHLIGHT ) && !( aDraw_mode & GR_AND ) )
|
|
color.SetToLegacyHighlightColor();
|
|
|
|
bool DisplayIsol = displ_opts && displ_opts->m_DisplayPadIsol;
|
|
|
|
if( !( m_layerMask & LSET::AllCuMask() ).any() )
|
|
DisplayIsol = false;
|
|
|
|
if( ( GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED ) &&
|
|
brd->IsElementVisible( LAYER_NON_PLATEDHOLES ) )
|
|
{
|
|
drawInfo.m_ShowNotPlatedHole = true;
|
|
drawInfo.m_NPHoleColor = cds.GetItemColor( LAYER_NON_PLATEDHOLES );
|
|
}
|
|
// Don't let pads that *should* be NPTHs get lost
|
|
else if ( PadShouldBeNPTH() )
|
|
{
|
|
drawInfo.m_ShowNotPlatedHole = true;
|
|
drawInfo.m_NPHoleColor = cds.GetItemColor( LAYER_MOD_TEXT_INVISIBLE );
|
|
}
|
|
|
|
drawInfo.m_DrawMode = aDraw_mode;
|
|
drawInfo.m_Color = color;
|
|
drawInfo.m_NoNetMarkColor = cds.GetItemColor( LAYER_NO_CONNECTS );
|
|
drawInfo.m_DrawPanel = aPanel;
|
|
drawInfo.m_Mask_margin = mask_margin;
|
|
drawInfo.m_ShowNCMark = brd->IsElementVisible( LAYER_NO_CONNECTS );
|
|
drawInfo.m_IsPrinting = screen->m_IsPrinting;
|
|
color.a = 0.666;
|
|
|
|
/* Get the pad clearance. This has a meaning only for Pcbnew.
|
|
* for CvPcb GetClearance() creates debug errors because
|
|
* there is no net classes so a call to GetClearance() is made only when
|
|
* needed (never needed in CvPcb)
|
|
*/
|
|
drawInfo.m_PadClearance = DisplayIsol ? GetClearance() : 0;
|
|
|
|
// Draw the pad number
|
|
if( displ_opts && !displ_opts->m_DisplayPadNum )
|
|
drawInfo.m_Display_padnum = false;
|
|
|
|
if( displ_opts &&
|
|
(( displ_opts ->m_DisplayNetNamesMode == 0 ) || ( displ_opts->m_DisplayNetNamesMode == 2 )) )
|
|
drawInfo.m_Display_netname = false;
|
|
|
|
// Display net names is restricted to pads that are on the active layer
|
|
// in high contrast mode display
|
|
if( ( aDraw_mode & GR_ALLOW_HIGHCONTRAST ) &&
|
|
!IsOnLayer( screen->m_Active_Layer ) && displ_opts && displ_opts->m_ContrastModeDisplay )
|
|
drawInfo.m_Display_netname = false;
|
|
|
|
DrawShape( aPanel->GetClipBox(), aDC, drawInfo );
|
|
}
|
|
|
|
|
|
void D_PAD::DrawShape( EDA_RECT* aClipBox, wxDC* aDC, PAD_DRAWINFO& aDrawInfo )
|
|
{
|
|
wxPoint coord[12];
|
|
double angle = m_Orient;
|
|
int seg_width;
|
|
|
|
GRSetDrawMode( aDC, aDrawInfo.m_DrawMode );
|
|
|
|
// calculate pad shape position :
|
|
wxPoint shape_pos = ShapePos() - aDrawInfo.m_Offset;
|
|
|
|
wxSize halfsize = m_Size;
|
|
halfsize.x >>= 1;
|
|
halfsize.y >>= 1;
|
|
|
|
switch( GetShape() )
|
|
{
|
|
case PAD_SHAPE_CIRCLE:
|
|
if( aDrawInfo.m_ShowPadFilled )
|
|
GRFilledCircle( aClipBox, aDC, shape_pos.x, shape_pos.y,
|
|
halfsize.x + aDrawInfo.m_Mask_margin.x, 0,
|
|
aDrawInfo.m_Color, aDrawInfo.m_Color );
|
|
else
|
|
GRCircle( aClipBox, aDC, shape_pos.x, shape_pos.y,
|
|
halfsize.x + aDrawInfo.m_Mask_margin.x,
|
|
m_PadSketchModePenSize, aDrawInfo.m_Color );
|
|
|
|
if( aDrawInfo.m_PadClearance )
|
|
{
|
|
GRCircle( aClipBox,
|
|
aDC, shape_pos.x, shape_pos.y,
|
|
halfsize.x + aDrawInfo.m_PadClearance,
|
|
0, aDrawInfo.m_Color );
|
|
}
|
|
|
|
break;
|
|
|
|
case PAD_SHAPE_OVAL:
|
|
{
|
|
wxPoint segStart, segEnd;
|
|
seg_width = BuildSegmentFromOvalShape(segStart, segEnd, angle,
|
|
aDrawInfo.m_Mask_margin);
|
|
segStart += shape_pos;
|
|
segEnd += shape_pos;
|
|
|
|
if( aDrawInfo.m_ShowPadFilled )
|
|
{
|
|
GRFillCSegm( aClipBox, aDC, segStart.x, segStart.y, segEnd.x, segEnd.y,
|
|
seg_width, aDrawInfo.m_Color );
|
|
}
|
|
else
|
|
{
|
|
GRCSegm( aClipBox, aDC, segStart.x, segStart.y, segEnd.x, segEnd.y,
|
|
seg_width, m_PadSketchModePenSize, aDrawInfo.m_Color );
|
|
}
|
|
|
|
// Draw the clearance line
|
|
if( aDrawInfo.m_PadClearance )
|
|
{
|
|
seg_width += 2 * aDrawInfo.m_PadClearance;
|
|
GRCSegm( aClipBox, aDC, segStart.x, segStart.y, segEnd.x, segEnd.y,
|
|
seg_width, aDrawInfo.m_Color );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PAD_SHAPE_RECT:
|
|
case PAD_SHAPE_TRAPEZOID:
|
|
BuildPadPolygon( coord, aDrawInfo.m_Mask_margin, angle );
|
|
|
|
for( int ii = 0; ii < 4; ii++ )
|
|
coord[ii] += shape_pos;
|
|
|
|
GRClosedPoly( aClipBox, aDC, 4, coord, aDrawInfo.m_ShowPadFilled,
|
|
aDrawInfo.m_ShowPadFilled ? 0 : m_PadSketchModePenSize,
|
|
aDrawInfo.m_Color, aDrawInfo.m_Color );
|
|
|
|
if( aDrawInfo.m_PadClearance )
|
|
{
|
|
SHAPE_POLY_SET outline;
|
|
TransformShapeWithClearanceToPolygon( outline, aDrawInfo.m_PadClearance, ARC_APPROX_SEGMENTS_COUNT_HIGH_DEF, 1.0 );
|
|
|
|
// Draw the polygon: Inflate creates only one convex polygon
|
|
if( outline.OutlineCount() > 0 )
|
|
{
|
|
SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
|
|
|
|
if( poly.PointCount() > 0 )
|
|
{
|
|
GRClosedPoly( aClipBox, aDC, poly.PointCount(),
|
|
(wxPoint*)&poly.Point( 0 ), false, 0,
|
|
aDrawInfo.m_Color, aDrawInfo.m_Color );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PAD_SHAPE_ROUNDRECT:
|
|
{
|
|
// Use solder[Paste/Mask]size or pad size to build pad shape to draw
|
|
wxSize size( GetSize() );
|
|
size += aDrawInfo.m_Mask_margin * 2;
|
|
int corner_radius = GetRoundRectCornerRadius( size );
|
|
|
|
// Draw the polygon: Inflate creates only one convex polygon
|
|
SHAPE_POLY_SET outline;
|
|
bool filled = aDrawInfo.m_ShowPadFilled;
|
|
|
|
if( filled )
|
|
{
|
|
wxPoint centers[4];
|
|
GetRoundRectCornerCenters( centers, corner_radius, shape_pos,
|
|
size, GetOrientation() );
|
|
GRClosedPoly( aClipBox, aDC, 4, centers, true, corner_radius*2,
|
|
aDrawInfo.m_Color, aDrawInfo.m_Color );
|
|
}
|
|
else
|
|
{
|
|
TransformRoundRectToPolygon( outline, shape_pos, size, GetOrientation(),
|
|
corner_radius, 64 );
|
|
|
|
if( outline.OutlineCount() > 0 )
|
|
{
|
|
SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
|
|
|
|
if( poly.PointCount() > 0 )
|
|
{
|
|
GRClosedPoly( aClipBox, aDC, poly.PointCount(),
|
|
(wxPoint*)&poly.Point( 0 ), aDrawInfo.m_ShowPadFilled, 0,
|
|
aDrawInfo.m_Color, aDrawInfo.m_Color );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( aDrawInfo.m_PadClearance )
|
|
{
|
|
outline.RemoveAllContours();
|
|
size = GetSize();
|
|
size.x += aDrawInfo.m_PadClearance * 2;
|
|
size.y += aDrawInfo.m_PadClearance * 2;
|
|
corner_radius = GetRoundRectCornerRadius() + aDrawInfo.m_PadClearance;
|
|
|
|
TransformRoundRectToPolygon( outline, shape_pos, size, GetOrientation(),
|
|
corner_radius, ARC_APPROX_SEGMENTS_COUNT_HIGH_DEF );
|
|
|
|
if( outline.OutlineCount() > 0 )
|
|
{
|
|
// Draw the polygon: Inflate creates only one convex polygon
|
|
SHAPE_LINE_CHAIN& clearance_poly = outline.Outline( 0 );
|
|
|
|
if( clearance_poly.PointCount() > 0 )
|
|
{
|
|
GRClosedPoly( aClipBox, aDC, clearance_poly.PointCount(),
|
|
(wxPoint*)&clearance_poly.Point( 0 ), false, 0,
|
|
aDrawInfo.m_Color, aDrawInfo.m_Color );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PAD_SHAPE_CUSTOM:
|
|
{
|
|
// The full shape has 2 items
|
|
// 1- The anchor pad: a round or rect pad located at pad position
|
|
// 2- The custom complex shape
|
|
// Note: The anchor pad shape is containing by the custom complex shape polygon
|
|
// The anchor pad is shown to help user to see where is the anchor, only in sketch mode
|
|
// (In filled mode, it is merged with the basic shapes)
|
|
wxPoint pad_pos = GetPosition() - aDrawInfo.m_Offset;
|
|
|
|
// In sketch mode only: Draw the anchor pad: a round or rect pad
|
|
if( !aDrawInfo.m_ShowPadFilled )
|
|
{
|
|
if( GetAnchorPadShape() == PAD_SHAPE_RECT )
|
|
{
|
|
wxPoint poly[4];
|
|
poly[0] = wxPoint( - halfsize.x, - halfsize.y );
|
|
poly[1] = wxPoint( - halfsize.x, + halfsize.y );
|
|
poly[2] = wxPoint( + halfsize.x, + halfsize.y );
|
|
poly[3] = wxPoint( + halfsize.x, - halfsize.y );
|
|
|
|
for( int ii = 0; ii < 4; ++ii )
|
|
{
|
|
RotatePoint( &poly[ii], m_Orient );
|
|
poly[ii] += pad_pos;
|
|
}
|
|
|
|
GRClosedPoly( aClipBox, aDC, 4, poly, false, 0,
|
|
aDrawInfo.m_Color, aDrawInfo.m_Color );
|
|
}
|
|
else
|
|
{
|
|
GRCircle( aClipBox, aDC, pad_pos.x, pad_pos.y,
|
|
halfsize.x,
|
|
m_PadSketchModePenSize, aDrawInfo.m_Color );
|
|
}
|
|
}
|
|
|
|
SHAPE_POLY_SET outline; // Will contain the corners in board coordinates
|
|
outline.Append( m_customShapeAsPolygon );
|
|
CustomShapeAsPolygonToBoardPosition( &outline, pad_pos, GetOrientation() );
|
|
SHAPE_LINE_CHAIN* poly;
|
|
|
|
const int segmentToCircleCount = ARC_APPROX_SEGMENTS_COUNT_HIGH_DEF;
|
|
|
|
if( aDrawInfo.m_Mask_margin.x )
|
|
outline.InflateWithLinkedHoles( aDrawInfo.m_Mask_margin.x,
|
|
segmentToCircleCount, SHAPE_POLY_SET::PM_FAST );
|
|
|
|
// Draw the polygon: only one polygon is expected
|
|
// However we provide a multi polygon shape drawing
|
|
// ( can happen with CUSTOM pads and negative margins )
|
|
for( int jj = 0; jj < outline.OutlineCount(); ++jj )
|
|
{
|
|
poly = &outline.Outline( jj );
|
|
|
|
GRClosedPoly( aClipBox, aDC, poly->PointCount(),
|
|
(wxPoint*)&poly->Point( 0 ), aDrawInfo.m_ShowPadFilled, 0,
|
|
aDrawInfo.m_Color, aDrawInfo.m_Color );
|
|
}
|
|
|
|
if( aDrawInfo.m_PadClearance )
|
|
{
|
|
SHAPE_POLY_SET clearance_outline;
|
|
clearance_outline.Append( outline );
|
|
|
|
clearance_outline.InflateWithLinkedHoles( aDrawInfo.m_PadClearance,
|
|
segmentToCircleCount, SHAPE_POLY_SET::PM_FAST );
|
|
|
|
for( int jj = 0; jj < clearance_outline.OutlineCount(); ++jj )
|
|
{
|
|
poly = &clearance_outline.Outline( jj );
|
|
|
|
if( poly->PointCount() > 0 )
|
|
{
|
|
GRClosedPoly( aClipBox, aDC, poly->PointCount(),
|
|
(wxPoint*)&poly->Point( 0 ), false, 0,
|
|
aDrawInfo.m_Color, aDrawInfo.m_Color );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Draw the pad hole
|
|
wxPoint holepos = m_Pos - aDrawInfo.m_Offset;
|
|
int hole = m_Drill.x >> 1;
|
|
|
|
bool drawhole = hole > 0;
|
|
|
|
if( !aDrawInfo.m_ShowPadFilled && !aDrawInfo.m_ShowNotPlatedHole )
|
|
drawhole = false;
|
|
|
|
if( drawhole )
|
|
{
|
|
bool blackpenstate = false;
|
|
COLOR4D fillcolor = aDrawInfo.m_ShowNotPlatedHole? aDrawInfo.m_NPHoleColor :
|
|
aDrawInfo.m_HoleColor;
|
|
COLOR4D hole_color = fillcolor;
|
|
|
|
if( aDrawInfo.m_IsPrinting )
|
|
{
|
|
fillcolor = COLOR4D::WHITE;
|
|
blackpenstate = GetGRForceBlackPenState();
|
|
GRForceBlackPen( false );
|
|
}
|
|
else
|
|
{
|
|
GRSetDrawMode( aDC, ( aDrawInfo.m_DrawMode != GR_XOR ) ? GR_COPY : GR_XOR );
|
|
}
|
|
|
|
if( blackpenstate )
|
|
hole_color = COLOR4D::BLACK;
|
|
|
|
switch( GetDrillShape() )
|
|
{
|
|
case PAD_DRILL_SHAPE_CIRCLE:
|
|
if( aDC->LogicalToDeviceXRel( hole ) > 1 ) // hole is drawn if hole > 1pixel
|
|
GRFilledCircle( aClipBox, aDC, holepos.x, holepos.y, hole, 0,
|
|
hole_color, fillcolor );
|
|
break;
|
|
|
|
case PAD_DRILL_SHAPE_OBLONG:
|
|
{
|
|
wxPoint drl_start, drl_end;
|
|
GetOblongDrillGeometry( drl_start, drl_end, seg_width );
|
|
drl_start += holepos;
|
|
drl_end += holepos;
|
|
GRFilledSegment( aClipBox, aDC, drl_start, drl_end, seg_width, fillcolor );
|
|
GRCSegm( aClipBox, aDC, drl_start, drl_end, seg_width, hole_color );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if( aDrawInfo.m_IsPrinting )
|
|
GRForceBlackPen( blackpenstate );
|
|
}
|
|
|
|
GRSetDrawMode( aDC, aDrawInfo.m_DrawMode );
|
|
|
|
// Draw "No connect" ( / or \ or cross X ) if necessary
|
|
if( GetNetCode() == 0 && aDrawInfo.m_ShowNCMark )
|
|
{
|
|
int dx0 = std::min( halfsize.x, halfsize.y );
|
|
|
|
if( m_layerMask[F_Cu] ) /* Draw \ */
|
|
GRLine( aClipBox, aDC, holepos.x - dx0, holepos.y - dx0,
|
|
holepos.x + dx0, holepos.y + dx0, 0, aDrawInfo.m_NoNetMarkColor );
|
|
|
|
if( m_layerMask[B_Cu] ) // Draw /
|
|
GRLine( aClipBox, aDC, holepos.x + dx0, holepos.y - dx0,
|
|
holepos.x - dx0, holepos.y + dx0, 0, aDrawInfo.m_NoNetMarkColor );
|
|
}
|
|
|
|
if( !aDrawInfo.m_IsPrinting )
|
|
GRSetDrawMode( aDC, ( aDrawInfo.m_DrawMode != GR_XOR ) ? GR_COPY : GR_XOR );
|
|
|
|
// Draw the pad number
|
|
if( !aDrawInfo.m_Display_padnum && !aDrawInfo.m_Display_netname )
|
|
return;
|
|
|
|
wxPoint tpos0 = shape_pos; // Position of the centre of text
|
|
wxPoint tpos = tpos0;
|
|
wxSize AreaSize; // size of text area, normalized to AreaSize.y < AreaSize.x
|
|
int shortname_len = 0;
|
|
|
|
if( aDrawInfo.m_Display_netname )
|
|
shortname_len = GetShortNetname().Len();
|
|
|
|
if( GetShape() == PAD_SHAPE_CIRCLE )
|
|
angle = 0;
|
|
|
|
AreaSize = m_Size;
|
|
|
|
if( m_Size.y > m_Size.x )
|
|
{
|
|
angle += 900;
|
|
AreaSize.x = m_Size.y;
|
|
AreaSize.y = m_Size.x;
|
|
}
|
|
|
|
if( shortname_len > 0 ) // if there is a netname, provides room to display this netname
|
|
{
|
|
AreaSize.y /= 2; // Text used only the upper area of the
|
|
// pad. The lower area displays the net name
|
|
tpos.y -= AreaSize.y / 2;
|
|
}
|
|
|
|
// Calculate the position of text, that is the middle point of the upper
|
|
// area of the pad
|
|
RotatePoint( &tpos, shape_pos, angle );
|
|
|
|
// Draw text with an angle between -90 deg and + 90 deg
|
|
double t_angle = angle;
|
|
NORMALIZE_ANGLE_90( t_angle );
|
|
|
|
/* Note: in next calculations, texte size is calculated for 3 or more
|
|
* chars. Of course, pads numbers and nets names can have less than 3
|
|
* chars. but after some tries, i found this is gives the best look
|
|
*/
|
|
constexpr int MIN_CHAR_COUNT = 3;
|
|
|
|
unsigned int tsize;
|
|
EDA_RECT* clipBox = aDrawInfo.m_DrawPanel?
|
|
aDrawInfo.m_DrawPanel->GetClipBox() : NULL;
|
|
|
|
if( aDrawInfo.m_Display_padnum )
|
|
{
|
|
int numpad_len = std::max( (int) m_name.Length(), MIN_CHAR_COUNT );
|
|
tsize = std::min( (int) AreaSize.y, AreaSize.x / numpad_len );
|
|
|
|
if( aDC->LogicalToDeviceXRel( tsize ) >= MIN_TEXT_SIZE ) // Not drawable when size too small.
|
|
{
|
|
// tsize reserve room for marges and segments thickness
|
|
tsize = ( tsize * 7 ) / 10;
|
|
DrawGraphicHaloText( clipBox, aDC, tpos,
|
|
aDrawInfo.m_Color, BLACK, WHITE,
|
|
m_name, t_angle,
|
|
wxSize( tsize , tsize ), GR_TEXT_HJUSTIFY_CENTER,
|
|
GR_TEXT_VJUSTIFY_CENTER, tsize / 7, false, false );
|
|
|
|
}
|
|
}
|
|
|
|
// display the short netname, if exists
|
|
if( shortname_len == 0 )
|
|
return;
|
|
|
|
shortname_len = std::max( shortname_len, MIN_CHAR_COUNT );
|
|
tsize = std::min( AreaSize.y, AreaSize.x / shortname_len );
|
|
|
|
if( aDC->LogicalToDeviceXRel( tsize ) >= MIN_TEXT_SIZE ) // Not drawable in size too small.
|
|
{
|
|
tpos = tpos0;
|
|
|
|
if( aDrawInfo.m_Display_padnum )
|
|
tpos.y += AreaSize.y / 2;
|
|
|
|
RotatePoint( &tpos, shape_pos, angle );
|
|
|
|
// tsize reserve room for marges and segments thickness
|
|
tsize = ( tsize * 7 ) / 10;
|
|
DrawGraphicHaloText( clipBox, aDC, tpos,
|
|
aDrawInfo.m_Color, BLACK, WHITE,
|
|
GetShortNetname(), t_angle,
|
|
wxSize( tsize, tsize ), GR_TEXT_HJUSTIFY_CENTER,
|
|
GR_TEXT_VJUSTIFY_CENTER, tsize / 7, false, false );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Function BuildSegmentFromOvalShape
|
|
* Has meaning only for OVAL (and ROUND) pads.
|
|
* Build an equivalent segment having the same shape as the OVAL shape,
|
|
* aSegStart and aSegEnd are the ending points of the equivalent segment of the shape
|
|
* aRotation is the asked rotation of the segment (usually m_Orient)
|
|
*/
|
|
int D_PAD::BuildSegmentFromOvalShape(wxPoint& aSegStart, wxPoint& aSegEnd,
|
|
double aRotation, const wxSize& aMargin) const
|
|
{
|
|
int width;
|
|
|
|
if( m_Size.y < m_Size.x ) // Build an horizontal equiv segment
|
|
{
|
|
int delta = ( m_Size.x - m_Size.y ) / 2;
|
|
aSegStart.x = -delta - aMargin.x;
|
|
aSegStart.y = 0;
|
|
aSegEnd.x = delta + aMargin.x;
|
|
aSegEnd.y = 0;
|
|
width = m_Size.y + ( aMargin.y * 2 );
|
|
}
|
|
else // Vertical oval: build a vertical equiv segment
|
|
{
|
|
int delta = ( m_Size.y -m_Size.x ) / 2;
|
|
aSegStart.x = 0;
|
|
aSegStart.y = -delta - aMargin.y;
|
|
aSegEnd.x = 0;
|
|
aSegEnd.y = delta + aMargin.y;
|
|
width = m_Size.x + ( aMargin.x * 2 );
|
|
}
|
|
|
|
if( aRotation )
|
|
{
|
|
RotatePoint( &aSegStart, aRotation);
|
|
RotatePoint( &aSegEnd, aRotation);
|
|
}
|
|
|
|
return width;
|
|
}
|
|
|
|
|
|
void D_PAD::BuildPadPolygon( wxPoint aCoord[4], wxSize aInflateValue,
|
|
double aRotation ) const
|
|
{
|
|
wxSize delta;
|
|
wxSize halfsize;
|
|
|
|
halfsize.x = m_Size.x >> 1;
|
|
halfsize.y = m_Size.y >> 1;
|
|
|
|
switch( GetShape() )
|
|
{
|
|
case PAD_SHAPE_RECT:
|
|
// For rectangular shapes, inflate is easy
|
|
halfsize += aInflateValue;
|
|
|
|
// Verify if do not deflate more than than size
|
|
// Only possible for inflate negative values.
|
|
if( halfsize.x < 0 )
|
|
halfsize.x = 0;
|
|
|
|
if( halfsize.y < 0 )
|
|
halfsize.y = 0;
|
|
break;
|
|
|
|
case PAD_SHAPE_TRAPEZOID:
|
|
// Trapezoidal pad: verify delta values
|
|
delta.x = ( m_DeltaSize.x >> 1 );
|
|
delta.y = ( m_DeltaSize.y >> 1 );
|
|
|
|
// be sure delta values are not to large
|
|
if( (delta.x < 0) && (delta.x <= -halfsize.y) )
|
|
delta.x = -halfsize.y + 1;
|
|
|
|
if( (delta.x > 0) && (delta.x >= halfsize.y) )
|
|
delta.x = halfsize.y - 1;
|
|
|
|
if( (delta.y < 0) && (delta.y <= -halfsize.x) )
|
|
delta.y = -halfsize.x + 1;
|
|
|
|
if( (delta.y > 0) && (delta.y >= halfsize.x) )
|
|
delta.y = halfsize.x - 1;
|
|
break;
|
|
|
|
default: // is used only for rect and trap. pads
|
|
return;
|
|
}
|
|
|
|
// Build the basic rectangular or trapezoid shape
|
|
// delta is null for rectangular shapes
|
|
aCoord[0].x = -halfsize.x - delta.y; // lower left
|
|
aCoord[0].y = +halfsize.y + delta.x;
|
|
|
|
aCoord[1].x = -halfsize.x + delta.y; // upper left
|
|
aCoord[1].y = -halfsize.y - delta.x;
|
|
|
|
aCoord[2].x = +halfsize.x - delta.y; // upper right
|
|
aCoord[2].y = -halfsize.y + delta.x;
|
|
|
|
aCoord[3].x = +halfsize.x + delta.y; // lower right
|
|
aCoord[3].y = +halfsize.y - delta.x;
|
|
|
|
// Offsetting the trapezoid shape id needed
|
|
// It is assumed delta.x or/and delta.y == 0
|
|
if( GetShape() == PAD_SHAPE_TRAPEZOID && (aInflateValue.x != 0 || aInflateValue.y != 0) )
|
|
{
|
|
double angle;
|
|
wxSize corr;
|
|
|
|
if( delta.y ) // lower and upper segment is horizontal
|
|
{
|
|
// Calculate angle of left (or right) segment with vertical axis
|
|
angle = atan2( (double) m_DeltaSize.y, (double) m_Size.y );
|
|
|
|
// left and right sides are moved by aInflateValue.x in their perpendicular direction
|
|
// We must calculate the corresponding displacement on the horizontal axis
|
|
// that is delta.x +- corr.x depending on the corner
|
|
corr.x = KiROUND( tan( angle ) * aInflateValue.x );
|
|
delta.x = KiROUND( aInflateValue.x / cos( angle ) );
|
|
|
|
// Horizontal sides are moved up and down by aInflateValue.y
|
|
delta.y = aInflateValue.y;
|
|
|
|
// corr.y = 0 by the constructor
|
|
}
|
|
else if( delta.x ) // left and right segment is vertical
|
|
{
|
|
// Calculate angle of lower (or upper) segment with horizontal axis
|
|
angle = atan2( (double) m_DeltaSize.x, (double) m_Size.x );
|
|
|
|
// lower and upper sides are moved by aInflateValue.x in their perpendicular direction
|
|
// We must calculate the corresponding displacement on the vertical axis
|
|
// that is delta.y +- corr.y depending on the corner
|
|
corr.y = KiROUND( tan( angle ) * aInflateValue.y );
|
|
delta.y = KiROUND( aInflateValue.y / cos( angle ) );
|
|
|
|
// Vertical sides are moved left and right by aInflateValue.x
|
|
delta.x = aInflateValue.x;
|
|
|
|
// corr.x = 0 by the constructor
|
|
}
|
|
else // the trapezoid is a rectangle
|
|
{
|
|
delta = aInflateValue; // this pad is rectangular (delta null).
|
|
}
|
|
|
|
aCoord[0].x += -delta.x - corr.x; // lower left
|
|
aCoord[0].y += delta.y + corr.y;
|
|
|
|
aCoord[1].x += -delta.x + corr.x; // upper left
|
|
aCoord[1].y += -delta.y - corr.y;
|
|
|
|
aCoord[2].x += delta.x - corr.x; // upper right
|
|
aCoord[2].y += -delta.y + corr.y;
|
|
|
|
aCoord[3].x += delta.x + corr.x; // lower right
|
|
aCoord[3].y += delta.y - corr.y;
|
|
|
|
/* test coordinates and clamp them if the offset correction is too large:
|
|
* Note: if a coordinate is bad, the other "symmetric" coordinate is bad
|
|
* So when a bad coordinate is found, the 2 symmetric coordinates
|
|
* are set to the minimun value (0)
|
|
*/
|
|
|
|
if( aCoord[0].x > 0 ) // lower left x coordinate must be <= 0
|
|
aCoord[0].x = aCoord[3].x = 0;
|
|
|
|
if( aCoord[1].x > 0 ) // upper left x coordinate must be <= 0
|
|
aCoord[1].x = aCoord[2].x = 0;
|
|
|
|
if( aCoord[0].y < 0 ) // lower left y coordinate must be >= 0
|
|
aCoord[0].y = aCoord[1].y = 0;
|
|
|
|
if( aCoord[3].y < 0 ) // lower right y coordinate must be >= 0
|
|
aCoord[3].y = aCoord[2].y = 0;
|
|
}
|
|
|
|
if( aRotation )
|
|
{
|
|
for( int ii = 0; ii < 4; ii++ )
|
|
RotatePoint( &aCoord[ii], aRotation );
|
|
}
|
|
}
|