Files
Wayne Stambaugh 5c1c24cdab Pcbnew: fix footprint selection bug.
Do not remove footprint objects from list of selected objects when there
are no drawable objects other than the reference and value text.

Increase the minimum size rectangle for footprints from 0.025mm to 1mm
when the footprint has no drawing objects so that it is easier to select
the footprint in this case.

Add debugging code to show the footprint bounding box and polygon outline
so that it's possible to tell the difference between selection areas.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/8379
2021-06-24 14:13:30 -04:00

1311 lines
42 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013-2019 CERN
* Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
*
* @author Tomasz Wlostowski <[email protected]>
* @author Maciej Suminski <[email protected]>
*
* 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 <class_board.h>
#include <class_track.h>
#include <class_module.h>
#include <class_pad.h>
#include <class_drawsegment.h>
#include <class_zone.h>
#include <class_pcb_text.h>
#include <colors_design_settings.h>
#include <class_marker_pcb.h>
#include <class_dimension.h>
#include <class_pcb_target.h>
#include <class_marker_pcb.h>
#include <layers_id_colors_and_visibility.h>
#include <pcb_painter.h>
#include <pcb_display_options.h>
#include <gal/graphics_abstraction_layer.h>
#include <convert_basic_shapes_to_polygon.h>
#include <geometry/shape_line_chain.h>
using namespace KIGFX;
PCB_RENDER_SETTINGS::PCB_RENDER_SETTINGS()
{
m_backgroundColor = COLOR4D( 0.0, 0.0, 0.0, 1.0 );
m_padNumbers = true;
m_netNamesOnPads = true;
m_netNamesOnTracks = true;
m_netNamesOnVias = true;
m_zoneOutlines = true;
m_displayZone = DZ_SHOW_FILLED;
m_clearance = CL_NONE;
m_sketchBoardGfx = false;
m_sketchFpGfx = false;
m_sketchFpTxtfx = false;
m_selectionCandidateColor = COLOR4D( 0.0, 1.0, 0.0, 0.75 );
// By default everything should be displayed as filled
for( unsigned int i = 0; i < arrayDim( m_sketchMode ); ++i )
{
m_sketchMode[i] = false;
}
COLORS_DESIGN_SETTINGS dummyCds( FRAME_PCB );
ImportLegacyColors( &dummyCds );
update();
}
void PCB_RENDER_SETTINGS::ImportLegacyColors( const COLORS_DESIGN_SETTINGS* aSettings )
{
// Init board layers colors:
for( int i = 0; i < PCB_LAYER_ID_COUNT; i++ )
{
m_layerColors[i] = aSettings->GetLayerColor( i );
// Guard: if the alpah channel is too small, the layer is not visible.
// clamp it to 0.2
if( m_layerColors[i].a < 0.2 )
m_layerColors[i].a = 0.2;
}
// Init specific graphic layers colors:
for( int i = GAL_LAYER_ID_START; i < GAL_LAYER_ID_END; i++ )
m_layerColors[i] = aSettings->GetItemColor( i );
// Default colors for specific layers (not really board layers).
m_layerColors[LAYER_VIAS_HOLES] = COLOR4D( 0.5, 0.4, 0.0, 0.8 );
m_layerColors[LAYER_PADS_PLATEDHOLES] = aSettings->GetItemColor( LAYER_PCB_BACKGROUND );
m_layerColors[LAYER_VIAS_NETNAMES] = COLOR4D( 0.2, 0.2, 0.2, 0.9 );
m_layerColors[LAYER_PADS_NETNAMES] = COLOR4D( 1.0, 1.0, 1.0, 0.9 );
m_layerColors[LAYER_PAD_FR_NETNAMES] = COLOR4D( 1.0, 1.0, 1.0, 0.9 );
m_layerColors[LAYER_PAD_BK_NETNAMES] = COLOR4D( 1.0, 1.0, 1.0, 0.9 );
m_layerColors[LAYER_DRC] = COLOR4D( 1.0, 0.0, 0.0, 0.8 );
// LAYER_PADS_TH, LAYER_NON_PLATEDHOLES, LAYER_ANCHOR ,LAYER_RATSNEST,
// LAYER_VIA_THROUGH, LAYER_VIA_BBLIND, LAYER_VIA_MICROVIA
// are initialized from aSettings
// These colors are not actually used. Set just in case...
m_layerColors[LAYER_MOD_TEXT_FR] = m_layerColors[F_SilkS];
m_layerColors[LAYER_MOD_TEXT_BK] = m_layerColors[B_SilkS];
// Netnames for copper layers
for( LSEQ cu = LSET::AllCuMask().CuStack(); cu; ++cu )
{
const COLOR4D lightLabel( 0.8, 0.8, 0.8, 0.7 );
const COLOR4D darkLabel = lightLabel.Inverted();
PCB_LAYER_ID layer = *cu;
if( m_layerColors[layer].GetBrightness() > 0.5 )
m_layerColors[GetNetnameLayer( layer )] = darkLabel;
else
m_layerColors[GetNetnameLayer( layer )] = lightLabel;
}
update();
}
void PCB_RENDER_SETTINGS::LoadDisplayOptions( const PCB_DISPLAY_OPTIONS* aOptions,
bool aShowPageLimits )
{
if( aOptions == NULL )
return;
m_hiContrastEnabled = aOptions->m_ContrastModeDisplay;
m_padNumbers = aOptions->m_DisplayPadNum;
m_sketchBoardGfx = !aOptions->m_DisplayDrawItemsFill;
m_sketchFpGfx = !aOptions->m_DisplayModEdgeFill;
m_sketchFpTxtfx = !aOptions->m_DisplayModTextFill;
// Whether to draw tracks, vias & pads filled or as outlines
m_sketchMode[LAYER_PADS_TH] = !aOptions->m_DisplayPadFill;
m_sketchMode[LAYER_VIA_THROUGH] = !aOptions->m_DisplayViaFill;
m_sketchMode[LAYER_VIA_BBLIND] = !aOptions->m_DisplayViaFill;
m_sketchMode[LAYER_VIA_MICROVIA] = !aOptions->m_DisplayViaFill;
m_sketchMode[LAYER_TRACKS] = !aOptions->m_DisplayPcbTrackFill;
// Net names display settings
switch( aOptions->m_DisplayNetNamesMode )
{
case 0:
m_netNamesOnPads = false;
m_netNamesOnTracks = false;
break;
case 1:
m_netNamesOnPads = true;
m_netNamesOnTracks = false;
break;
case 2:
m_netNamesOnPads = false;
m_netNamesOnTracks = true;
break;
case 3:
m_netNamesOnPads = true;
m_netNamesOnTracks = true;
break;
}
// Zone display settings
switch( aOptions->m_DisplayZonesMode )
{
case 0:
m_displayZone = DZ_SHOW_FILLED;
break;
case 1:
m_displayZone = DZ_HIDE_FILLED;
break;
case 2:
m_displayZone = DZ_SHOW_OUTLINED;
break;
}
// Clearance settings
switch( aOptions->m_ShowTrackClearanceMode )
{
case PCB_DISPLAY_OPTIONS::DO_NOT_SHOW_CLEARANCE:
m_clearance = CL_NONE;
break;
case PCB_DISPLAY_OPTIONS::SHOW_CLEARANCE_NEW_TRACKS:
m_clearance = CL_NEW | CL_TRACKS;
break;
case PCB_DISPLAY_OPTIONS::SHOW_CLEARANCE_NEW_TRACKS_AND_VIA_AREAS:
m_clearance = CL_NEW | CL_TRACKS | CL_VIAS;
break;
case PCB_DISPLAY_OPTIONS::SHOW_CLEARANCE_NEW_AND_EDITED_TRACKS_AND_VIA_AREAS:
m_clearance = CL_NEW | CL_EDITED | CL_TRACKS | CL_VIAS;
break;
case PCB_DISPLAY_OPTIONS::SHOW_CLEARANCE_ALWAYS:
m_clearance = CL_NEW | CL_EDITED | CL_EXISTING | CL_TRACKS | CL_VIAS;
break;
}
if( aOptions->m_DisplayPadIsol )
m_clearance |= CL_PADS;
m_showPageLimits = aShowPageLimits;
}
const COLOR4D& PCB_RENDER_SETTINGS::GetColor( const VIEW_ITEM* aItem, int aLayer ) const
{
int netCode = -1;
const EDA_ITEM* item = dynamic_cast<const EDA_ITEM*>( aItem );
if( item )
{
// Selection disambiguation
if( item->IsBrightened() )
{
return m_selectionCandidateColor;
}
// Don't let pads that *should* be NPTHs get lost
if( item->Type() == PCB_PAD_T && dyn_cast<const D_PAD*>( item )->PadShouldBeNPTH() )
aLayer = LAYER_MOD_TEXT_INVISIBLE;
if( item->IsSelected() )
{
return m_layerColorsSel[aLayer];
}
// Try to obtain the netcode for the item
if( const BOARD_CONNECTED_ITEM* conItem = dyn_cast<const BOARD_CONNECTED_ITEM*> ( item ) )
netCode = conItem->GetNetCode();
if( item->Type() == PCB_MARKER_T )
return m_layerColors[aLayer];
// For vias, some layers depend on other layers in high contrast mode
if( m_hiContrastEnabled && item->Type() == PCB_VIA_T &&
( aLayer == LAYER_VIAS_HOLES ||
aLayer == LAYER_VIA_THROUGH ||
aLayer == LAYER_VIA_MICROVIA ||
aLayer == LAYER_VIA_BBLIND ) )
{
const VIA* via = static_cast<const VIA*>( item );
const BOARD* pcb = static_cast<const BOARD*>( item->GetParent() );
bool viaActiveLayer = false;
for( auto activeLayer : m_activeLayers )
{
auto lay_id = static_cast<PCB_LAYER_ID>( activeLayer );
viaActiveLayer |= via->IsOnLayer( lay_id ) && pcb->IsLayerVisible( lay_id );
}
if( viaActiveLayer )
return m_layerColors[aLayer];
else
return m_hiContrastColor;
}
}
// Single net highlight mode
if( m_highlightEnabled && netCode == m_highlightNetcode )
return m_layerColorsHi[aLayer];
// Return grayish color for non-highlighted layers in the high contrast mode
if( m_hiContrastEnabled && m_activeLayers.count( aLayer ) == 0 )
return m_hiContrastColor;
// Catch the case when highlight and high-contraste modes are enabled
// and we are drawing a not highlighted track
if( m_highlightEnabled )
return m_layerColorsDark[aLayer];
// No special modificators enabled
return m_layerColors[aLayer];
}
PCB_PAINTER::PCB_PAINTER( GAL* aGal ) :
PAINTER( aGal )
{
}
int PCB_PAINTER::getLineThickness( int aActualThickness ) const
{
// if items have 0 thickness, draw them with the outline
// width, otherwise respect the set value (which, no matter
// how small will produce something)
if( aActualThickness == 0 )
return m_pcbSettings.m_outlineWidth;
return aActualThickness;
}
int PCB_PAINTER::getDrillShape( const D_PAD* aPad ) const
{
return aPad->GetDrillShape();
}
VECTOR2D PCB_PAINTER::getDrillSize( const D_PAD* aPad ) const
{
return VECTOR2D( aPad->GetDrillSize() );
}
int PCB_PAINTER::getDrillSize( const VIA* aVia ) const
{
return aVia->GetDrillValue();
}
bool PCB_PAINTER::Draw( const VIEW_ITEM* aItem, int aLayer )
{
const EDA_ITEM* item = dynamic_cast<const EDA_ITEM*>( aItem );
if( !item )
return false;
// the "cast" applied in here clarifies which overloaded draw() is called
switch( item->Type() )
{
case PCB_SEGZONE_T:
case PCB_TRACE_T:
draw( static_cast<const TRACK*>( item ), aLayer );
break;
case PCB_VIA_T:
draw( static_cast<const VIA*>( item ), aLayer );
break;
case PCB_PAD_T:
draw( static_cast<const D_PAD*>( item ), aLayer );
break;
case PCB_LINE_T:
case PCB_MODULE_EDGE_T:
draw( static_cast<const DRAWSEGMENT*>( item ), aLayer );
break;
case PCB_TEXT_T:
draw( static_cast<const TEXTE_PCB*>( item ), aLayer );
break;
case PCB_MODULE_TEXT_T:
draw( static_cast<const TEXTE_MODULE*>( item ), aLayer );
break;
case PCB_MODULE_T:
draw( static_cast<const MODULE*>( item ), aLayer );
break;
case PCB_ZONE_AREA_T:
draw( static_cast<const ZONE_CONTAINER*>( item ), aLayer );
break;
case PCB_DIMENSION_T:
draw( static_cast<const DIMENSION*>( item ), aLayer );
break;
case PCB_TARGET_T:
draw( static_cast<const PCB_TARGET*>( item ) );
break;
case PCB_MARKER_T:
draw( static_cast<const MARKER_PCB*>( item ) );
break;
default:
// Painter does not know how to draw the object
return false;
}
// Draw bounding boxes after drawing objects so they can be seen.
#if 0
// Show bounding boxes of painted objects for debugging.
EDA_RECT box = item->GetBoundingBox();
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
if( item->Type() == PCB_MODULE_T )
m_gal->SetStrokeColor( item->IsSelected() ? COLOR4D( 1.0, 0.2, 0.2, 1 ) :
COLOR4D( MAGENTA ) );
else
m_gal->SetStrokeColor( item->IsSelected() ? COLOR4D( 1.0, 0.2, 0.2, 1 ) :
COLOR4D( 0.2, 0.2, 0.2, 1 ) );
m_gal->SetLineWidth( 1.5 / m_gal->GetWorldScale() );
m_gal->DrawRectangle( box.GetOrigin(), box.GetEnd() );
if( item->Type() == PCB_MODULE_T )
{
m_gal->SetStrokeColor( item->IsSelected() ? COLOR4D( 1.0, 0.2, 0.2, 1 ) :
COLOR4D( CYAN ) );
const MODULE* fp = static_cast<const MODULE*>( item );
if( fp )
{
SHAPE_POLY_SET convex = fp->GetBoundingPoly();
m_gal->DrawPolyline( convex.COutline( 0 ) );
}
}
#endif
return true;
}
void PCB_PAINTER::draw( const TRACK* aTrack, int aLayer )
{
VECTOR2D start( aTrack->GetStart() );
VECTOR2D end( aTrack->GetEnd() );
int width = aTrack->GetWidth();
if( m_pcbSettings.m_netNamesOnTracks && IsNetnameLayer( aLayer ) )
{
// If there is a net name - display it on the track
if( aTrack->GetNetCode() > NETINFO_LIST::UNCONNECTED )
{
VECTOR2D line = ( end - start );
double length = line.EuclideanNorm();
// Check if the track is long enough to have a netname displayed
if( length < 10 * width )
return;
const wxString& netName = aTrack->GetShortNetname();
VECTOR2D textPosition = start + line / 2.0; // center of the track
double textOrientation;
if( end.y == start.y ) // horizontal
textOrientation = 0;
else if( end.x == start.x ) // vertical
textOrientation = M_PI / 2;
else
textOrientation = -atan( line.y / line.x );
double textSize = width;
m_gal->SetIsStroke( true );
m_gal->SetIsFill( false );
m_gal->SetStrokeColor( m_pcbSettings.GetColor( NULL, aLayer ) );
m_gal->SetLineWidth( width / 10.0 );
m_gal->SetFontBold( false );
m_gal->SetFontItalic( false );
m_gal->SetTextMirrored( false );
m_gal->SetGlyphSize( VECTOR2D( textSize * 0.7, textSize * 0.7 ) );
m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER );
m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_CENTER );
m_gal->BitmapText( netName, textPosition, textOrientation );
}
}
else if( IsCopperLayer( aLayer ) )
{
// Draw a regular track
const COLOR4D& color = m_pcbSettings.GetColor( aTrack, aLayer );
bool outline_mode = m_pcbSettings.m_sketchMode[LAYER_TRACKS];
m_gal->SetStrokeColor( color );
m_gal->SetFillColor( color );
m_gal->SetIsStroke( outline_mode );
m_gal->SetIsFill( not outline_mode );
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
m_gal->DrawSegment( start, end, width );
// Clearance lines
constexpr int clearanceFlags = PCB_RENDER_SETTINGS::CL_EXISTING | PCB_RENDER_SETTINGS::CL_TRACKS;
if( ( m_pcbSettings.m_clearance & clearanceFlags ) == clearanceFlags )
{
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
m_gal->SetStrokeColor( color );
m_gal->DrawSegment( start, end, width + aTrack->GetClearance() * 2 );
}
}
}
void PCB_PAINTER::draw( const VIA* aVia, int aLayer )
{
VECTOR2D center( aVia->GetStart() );
double radius = 0.0;
// Draw description layer
if( IsNetnameLayer( aLayer ) )
{
VECTOR2D position( center );
// Is anything that we can display enabled?
if( m_pcbSettings.m_netNamesOnVias )
{
bool displayNetname = ( !aVia->GetNetname().empty() );
double maxSize = PCB_RENDER_SETTINGS::MAX_FONT_SIZE;
double size = aVia->GetWidth();
// Font size limits
if( size > maxSize )
size = maxSize;
m_gal->Save();
m_gal->Translate( position );
// Default font settings
m_gal->ResetTextAttributes();
m_gal->SetStrokeColor( m_pcbSettings.GetColor( NULL, aLayer ) );
// Set the text position to the pad shape position (the pad position is not the best place)
VECTOR2D textpos( 0.0, 0.0 );
if( displayNetname )
{
// calculate the size of net name text:
double tsize = 1.5 * size / aVia->GetShortNetname().Length();
tsize = std::min( tsize, size );
// Use a smaller text size to handle interline, pen size..
tsize *= 0.7;
VECTOR2D namesize( tsize, tsize );
m_gal->SetGlyphSize( namesize );
m_gal->SetLineWidth( namesize.x / 12.0 );
m_gal->BitmapText( aVia->GetShortNetname(), textpos, 0.0 );
}
m_gal->Restore();
}
return;
}
// Choose drawing settings depending on if we are drawing via's pad or hole
if( aLayer == LAYER_VIAS_HOLES )
radius = getDrillSize( aVia ) / 2.0;
else
radius = aVia->GetWidth() / 2.0;
bool sketchMode = false;
const COLOR4D& color = m_pcbSettings.GetColor( aVia, aLayer );
switch( aVia->GetViaType() )
{
case VIA_THROUGH:
sketchMode = m_pcbSettings.m_sketchMode[LAYER_VIA_THROUGH];
break;
case VIA_BLIND_BURIED:
sketchMode = m_pcbSettings.m_sketchMode[LAYER_VIA_BBLIND];
break;
case VIA_MICROVIA:
sketchMode = m_pcbSettings.m_sketchMode[LAYER_VIA_MICROVIA];
break;
default:
assert( false );
break;
}
if( aVia->GetViaType() == VIA_BLIND_BURIED && !m_pcbSettings.GetDrawIndividualViaLayers() )
{
// Buried vias are drawn in a special way to indicate the top and bottom layers
PCB_LAYER_ID layerTop, layerBottom;
aVia->LayerPair( &layerTop, &layerBottom );
if( aLayer == LAYER_VIAS_HOLES )
{ // TODO outline mode
m_gal->SetIsFill( true );
m_gal->SetIsStroke( false );
m_gal->SetFillColor( color );
m_gal->DrawCircle( center, radius );
}
else
{
double width = ( aVia->GetWidth() - aVia->GetDrillValue() ) / 2.0;
m_gal->SetLineWidth( width );
m_gal->SetIsFill( true );
m_gal->SetIsStroke( false );
m_gal->SetFillColor( color );
if( aLayer == layerTop )
{
m_gal->DrawArc( center, radius, 0.0, M_PI / 2.0 );
}
else if( aLayer == layerBottom )
{
m_gal->DrawArc( center, radius, M_PI, 3.0 * M_PI / 2.0 );
}
else if( aLayer == LAYER_VIA_BBLIND )
{
m_gal->DrawArc( center, radius, M_PI / 2.0, M_PI );
m_gal->DrawArc( center, radius, 3.0 * M_PI / 2.0, 2.0 * M_PI );
}
}
}
else
{
// Regular vias
m_gal->SetIsFill( !sketchMode );
m_gal->SetIsStroke( sketchMode );
if( sketchMode )
{
// Outline mode
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
m_gal->SetStrokeColor( color );
}
else
{
// Filled mode
m_gal->SetFillColor( color );
}
m_gal->DrawCircle( center, radius );
}
// Clearance lines
constexpr int clearanceFlags = PCB_RENDER_SETTINGS::CL_EXISTING | PCB_RENDER_SETTINGS::CL_VIAS;
if( ( m_pcbSettings.m_clearance & clearanceFlags ) == clearanceFlags
&& aLayer != LAYER_VIAS_HOLES )
{
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
m_gal->SetStrokeColor( color );
m_gal->DrawCircle( center, radius + aVia->GetClearance() );
}
}
void PCB_PAINTER::draw( const D_PAD* aPad, int aLayer )
{
PAD_SHAPE_T shape;
double m, n;
double orientation = aPad->GetOrientation();
// Draw description layer
if( IsNetnameLayer( aLayer ) )
{
VECTOR2D position( aPad->ShapePos() );
// Is anything that we can display enabled?
if( m_pcbSettings.m_netNamesOnPads || m_pcbSettings.m_padNumbers )
{
bool displayNetname = ( m_pcbSettings.m_netNamesOnPads && !aPad->GetNetname().empty() );
VECTOR2D padsize = VECTOR2D( aPad->GetSize() );
double maxSize = PCB_RENDER_SETTINGS::MAX_FONT_SIZE;
double size = padsize.y;
// Keep the size ratio for the font, but make it smaller
if( padsize.x < padsize.y )
{
orientation += 900.0;
size = padsize.x;
std::swap( padsize.x, padsize.y );
}
else if( padsize.x == padsize.y )
{
// If the text is displayed on a symmetrical pad, do not rotate it
orientation = 0.0;
}
// Font size limits
if( size > maxSize )
size = maxSize;
m_gal->Save();
m_gal->Translate( position );
// do not display descriptions upside down
NORMALIZE_ANGLE_90( orientation );
m_gal->Rotate( DECIDEG2RAD( -orientation ) );
// Default font settings
m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER );
m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_CENTER );
m_gal->SetFontBold( false );
m_gal->SetFontItalic( false );
m_gal->SetTextMirrored( false );
m_gal->SetStrokeColor( m_pcbSettings.GetColor( NULL, aLayer ) );
m_gal->SetIsStroke( true );
m_gal->SetIsFill( false );
// Set the text position to the pad shape position (the pad position is not the best place)
VECTOR2D textpos( 0.0, 0.0 );
// Divide the space, to display both pad numbers and netnames
// and set the Y text position to display 2 lines
if( displayNetname && m_pcbSettings.m_padNumbers )
{
size = size / 2.0;
textpos.y = size / 2.0;
}
if( displayNetname )
{
// calculate the size of net name text:
double tsize = 1.5 * padsize.x / aPad->GetShortNetname().Length();
tsize = std::min( tsize, size );
// Use a smaller text size to handle interline, pen size..
tsize *= 0.7;
VECTOR2D namesize( tsize, tsize );
m_gal->SetGlyphSize( namesize );
m_gal->SetLineWidth( namesize.x / 12.0 );
m_gal->BitmapText( aPad->GetShortNetname(), textpos, 0.0 );
}
if( m_pcbSettings.m_padNumbers )
{
const wxString& padName = aPad->GetName();
textpos.y = -textpos.y;
double tsize = 1.5 * padsize.x / padName.Length();
tsize = std::min( tsize, size );
// Use a smaller text size to handle interline, pen size..
tsize *= 0.7;
tsize = std::min( tsize, size );
VECTOR2D numsize( tsize, tsize );
m_gal->SetGlyphSize( numsize );
m_gal->SetLineWidth( numsize.x / 12.0 );
m_gal->BitmapText( padName, textpos, 0.0 );
}
m_gal->Restore();
}
return;
}
// Pad drawing
COLOR4D color;
// Pad holes color is type specific
if( aLayer == LAYER_PADS_PLATEDHOLES || aLayer == LAYER_NON_PLATEDHOLES )
{
// Hole color is the background color for plated holes, but a specific color
// for not plated holes (LAYER_NON_PLATEDHOLES color layer )
if( aPad->GetAttribute() == PAD_ATTRIB_HOLE_NOT_PLATED )
color = m_pcbSettings.GetColor( nullptr, LAYER_NON_PLATEDHOLES );
// Don't let pads that *should* be NPTH get lost
else if( aPad->PadShouldBeNPTH() )
color = m_pcbSettings.GetColor( aPad, aLayer );
else
color = m_pcbSettings.GetBackgroundColor();
}
else
{
color = m_pcbSettings.GetColor( aPad, aLayer );
}
VECTOR2D size;
if( m_pcbSettings.m_sketchMode[LAYER_PADS_TH] )
{
// Outline mode
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
m_gal->SetStrokeColor( color );
}
else
{
// Filled mode
m_gal->SetIsFill( true );
m_gal->SetIsStroke( false );
m_gal->SetFillColor( color );
}
m_gal->Save();
m_gal->Translate( VECTOR2D( aPad->GetPosition() ) );
m_gal->Rotate( -aPad->GetOrientationRadians() );
int custom_margin = 0; // a clearance/margin for custom shape, for solder paste/mask
// Choose drawing settings depending on if we are drawing a pad itself or a hole
if( aLayer == LAYER_PADS_PLATEDHOLES || aLayer == LAYER_NON_PLATEDHOLES )
{
// Drawing hole: has same shape as PAD_CIRCLE or PAD_OVAL
size = getDrillSize( aPad ) / 2.0;
shape = getDrillShape( aPad ) == PAD_DRILL_SHAPE_OBLONG ? PAD_SHAPE_OVAL : PAD_SHAPE_CIRCLE;
}
else if( aLayer == F_Mask || aLayer == B_Mask )
{
// Drawing soldermask
int soldermaskMargin = aPad->GetSolderMaskMargin();
custom_margin = soldermaskMargin;
m_gal->Translate( VECTOR2D( aPad->GetOffset() ) );
size = VECTOR2D( aPad->GetSize().x / 2.0 + soldermaskMargin,
aPad->GetSize().y / 2.0 + soldermaskMargin );
shape = aPad->GetShape();
}
else if( aLayer == F_Paste || aLayer == B_Paste )
{
// Drawing solderpaste
wxSize solderpasteMargin = aPad->GetSolderPasteMargin();
// try to find a clearance which can be used for custom shapes
custom_margin = solderpasteMargin.x;
m_gal->Translate( VECTOR2D( aPad->GetOffset() ) );
size = VECTOR2D( aPad->GetSize().x / 2.0 + solderpasteMargin.x,
aPad->GetSize().y / 2.0 + solderpasteMargin.y );
shape = aPad->GetShape();
}
else
{
// Drawing every kind of pad
m_gal->Translate( VECTOR2D( aPad->GetOffset() ) );
size = VECTOR2D( aPad->GetSize() ) / 2.0;
shape = aPad->GetShape();
}
switch( shape )
{
case PAD_SHAPE_OVAL:
if( size.y >= size.x )
{
m = ( size.y - size.x );
n = size.x;
m_gal->DrawArc( VECTOR2D( 0, -m ), n, -M_PI, 0 );
m_gal->DrawArc( VECTOR2D( 0, m ), n, M_PI, 0 );
if( m_pcbSettings.m_sketchMode[LAYER_PADS_TH] )
{
m_gal->DrawLine( VECTOR2D( -n, -m ), VECTOR2D( -n, m ) );
m_gal->DrawLine( VECTOR2D( n, -m ), VECTOR2D( n, m ) );
}
else
{
m_gal->DrawRectangle( VECTOR2D( -n, -m ), VECTOR2D( n, m ) );
}
}
else
{
m = ( size.x - size.y );
n = size.y;
m_gal->DrawArc( VECTOR2D( -m, 0 ), n, M_PI / 2, 3 * M_PI / 2 );
m_gal->DrawArc( VECTOR2D( m, 0 ), n, M_PI / 2, -M_PI / 2 );
if( m_pcbSettings.m_sketchMode[LAYER_PADS_TH] )
{
m_gal->DrawLine( VECTOR2D( -m, -n ), VECTOR2D( m, -n ) );
m_gal->DrawLine( VECTOR2D( -m, n ), VECTOR2D( m, n ) );
}
else
{
m_gal->DrawRectangle( VECTOR2D( -m, -n ), VECTOR2D( m, n ) );
}
}
break;
case PAD_SHAPE_RECT:
m_gal->DrawRectangle( VECTOR2D( -size.x, -size.y ), VECTOR2D( size.x, size.y ) );
break;
case PAD_SHAPE_ROUNDRECT:
{
SHAPE_POLY_SET polySet;
wxSize prsize( size.x * 2, size.y * 2 );
const int segmentToCircleCount = 64;
const int corner_radius = aPad->GetRoundRectCornerRadius( prsize );
TransformRoundRectToPolygon( polySet, wxPoint( 0, 0 ), prsize,
0.0, corner_radius, segmentToCircleCount );
m_gal->DrawPolygon( polySet );
break;
}
case PAD_SHAPE_CUSTOM:
{ // Draw the complex custom shape
// Use solder[Paste/Mask]size or pad size to build pad shape
// however, solder[Paste/Mask] size has no actual meaning for a
// custom shape, because it is a set of basic shapes
// We use the custom_margin (good for solder mask, but approximative
// for solder paste).
if( custom_margin )
{
SHAPE_POLY_SET outline;
outline.Append( aPad->GetCustomShapeAsPolygon() );
const int segmentToCircleCount = ARC_APPROX_SEGMENTS_COUNT_HIGH_DEF;
// outline polygon can have holes linked to the main outline.
// So use InflateWithLinkedHoles(), not Inflate() that can create
// bad shapes if custom_margin is < 0
outline.InflateWithLinkedHoles( custom_margin, segmentToCircleCount, SHAPE_POLY_SET::PM_FAST );
m_gal->DrawPolygon( outline );
}
else
{
// Draw the polygon: only one polygon is expected
// However we provide a multi polygon shape drawing
// ( for the future or to show even an incorrect shape
m_gal->DrawPolygon( aPad->GetCustomShapeAsPolygon() );
}
}
break;
case PAD_SHAPE_TRAPEZOID:
{
std::deque<VECTOR2D> pointList;
wxPoint corners[4];
VECTOR2D padSize = VECTOR2D( aPad->GetSize().x, aPad->GetSize().y ) / 2;
VECTOR2D deltaPadSize = size - padSize; // = solder[Paste/Mask]Margin or 0
aPad->BuildPadPolygon( corners, wxSize( deltaPadSize.x, deltaPadSize.y ), 0.0 );
SHAPE_POLY_SET polySet;
polySet.NewOutline();
polySet.Append( VECTOR2I( corners[0] ) );
polySet.Append( VECTOR2I( corners[1] ) );
polySet.Append( VECTOR2I( corners[2] ) );
polySet.Append( VECTOR2I( corners[3] ) );
m_gal->DrawPolygon( polySet );
}
break;
case PAD_SHAPE_CIRCLE:
m_gal->DrawCircle( VECTOR2D( 0.0, 0.0 ), size.x );
break;
}
m_gal->Restore();
// Clearance lines
// It has to be called after GAL::Restore() as TransformShapeWithClearanceToPolygon()
// returns already transformed coordinates
constexpr int clearanceFlags = /*PCB_RENDER_SETTINGS::CL_EXISTING |*/ PCB_RENDER_SETTINGS::CL_PADS;
if( ( m_pcbSettings.m_clearance & clearanceFlags ) == clearanceFlags
&& ( aLayer == LAYER_PAD_FR
|| aLayer == LAYER_PAD_BK
|| aLayer == LAYER_PADS_TH ) )
{
SHAPE_POLY_SET polySet;
constexpr int SEGCOUNT = 64;
aPad->TransformShapeWithClearanceToPolygon( polySet, aPad->GetClearance(), SEGCOUNT, 1.0 );
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
m_gal->SetIsStroke( true );
m_gal->SetIsFill( false );
m_gal->SetStrokeColor( color );
m_gal->DrawPolygon( polySet );
}
}
void PCB_PAINTER::draw( const DRAWSEGMENT* aSegment, int aLayer )
{
const COLOR4D& color = m_pcbSettings.GetColor( aSegment, aSegment->GetLayer() );
bool sketch = ( aSegment->Type() == PCB_LINE_T && m_pcbSettings.m_sketchBoardGfx )
|| ( aSegment->Type() == PCB_MODULE_EDGE_T && m_pcbSettings.m_sketchFpGfx );
int thickness = getLineThickness( aSegment->GetWidth() );
VECTOR2D start( aSegment->GetStart() );
VECTOR2D end( aSegment->GetEnd() );
m_gal->SetIsFill( !sketch );
m_gal->SetIsStroke( sketch );
m_gal->SetFillColor( color );
m_gal->SetStrokeColor( color );
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
switch( aSegment->GetShape() )
{
case S_SEGMENT:
m_gal->DrawSegment( start, end, thickness );
break;
case S_RECT:
wxASSERT_MSG( false, "Not tested yet" );
m_gal->DrawRectangle( start, end );
break;
case S_ARC:
m_gal->DrawArcSegment( start, aSegment->GetRadius(),
DECIDEG2RAD( aSegment->GetArcAngleStart() ),
DECIDEG2RAD( aSegment->GetArcAngleStart() + aSegment->GetAngle() ),
thickness );
break;
case S_CIRCLE:
if( sketch )
{
m_gal->DrawCircle( start, aSegment->GetRadius() - thickness / 2 );
m_gal->DrawCircle( start, aSegment->GetRadius() + thickness / 2 );
}
else
{
m_gal->SetLineWidth( thickness );
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
m_gal->DrawCircle( start, aSegment->GetRadius() );
}
break;
case S_POLYGON:
{
SHAPE_POLY_SET& shape = ((DRAWSEGMENT*)aSegment)->GetPolyShape();
if( shape.OutlineCount() == 0 )
break;
// On Opengl, a not convex filled polygon is usually drawn by using triangles as primitives.
// CacheTriangulation() can create basic triangle primitives to draw the polygon solid shape
// on Opengl.
// GLU tesselation is much slower, so currently we are using our tesselation.
if( m_gal->IsOpenGlEngine() && !shape.IsTriangulationUpToDate() )
{
shape.CacheTriangulation();
}
m_gal->Save();
if( MODULE* module = aSegment->GetParentModule() )
{
m_gal->Translate( module->GetPosition() );
m_gal->Rotate( -module->GetOrientationRadians() );
}
m_gal->SetLineWidth( thickness );
m_gal->SetIsFill( aSegment->IsPolygonFilled() );
m_gal->SetIsStroke( true );
m_gal->DrawPolygon( shape );
m_gal->Restore();
break;
}
case S_CURVE:
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
m_gal->SetLineWidth( thickness );
// Use thickness as filter value to convert the curve to polyline
// when the curve is not supported
m_gal->DrawCurve( VECTOR2D( aSegment->GetStart() ),
VECTOR2D( aSegment->GetBezControl1() ),
VECTOR2D( aSegment->GetBezControl2() ),
VECTOR2D( aSegment->GetEnd() ), thickness );
break;
case S_LAST:
break;
}
}
void PCB_PAINTER::draw( const TEXTE_PCB* aText, int aLayer )
{
wxString shownText( aText->GetShownText() );
if( shownText.Length() == 0 )
return;
const COLOR4D& color = m_pcbSettings.GetColor( aText, aText->GetLayer() );
VECTOR2D position( aText->GetTextPos().x, aText->GetTextPos().y );
if( m_pcbSettings.m_sketchMode[aLayer] )
{
// Outline mode
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
}
else
{
// Filled mode
m_gal->SetLineWidth( getLineThickness( aText->GetThickness() ) );
}
m_gal->SetStrokeColor( color );
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
m_gal->SetTextAttributes( aText );
m_gal->StrokeText( shownText, position, aText->GetTextAngleRadians() );
}
void PCB_PAINTER::draw( const TEXTE_MODULE* aText, int aLayer )
{
wxString shownText( aText->GetShownText() );
if( shownText.Length() == 0 )
return;
bool sketch = m_pcbSettings.m_sketchFpTxtfx;
const COLOR4D& color = m_pcbSettings.GetColor( aText, aLayer );
VECTOR2D position( aText->GetTextPos().x, aText->GetTextPos().y );
// Currently, draw text routines do not know the true outline mode.
// so draw the text in "line" mode (no thickness)
if( sketch )
{
// Outline mode
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
}
else
{
// Filled mode
m_gal->SetLineWidth( getLineThickness( aText->GetThickness() ) );
}
m_gal->SetStrokeColor( color );
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
m_gal->SetTextAttributes( aText );
m_gal->StrokeText( shownText, position, aText->GetDrawRotationRadians() );
// Draw the umbilical line
if( aText->IsSelected() )
{
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
m_gal->SetStrokeColor( COLOR4D( 0.0, 0.0, 1.0, 1.0 ) );
m_gal->DrawLine( position, aText->GetParent()->GetPosition() );
}
}
void PCB_PAINTER::draw( const MODULE* aModule, int aLayer )
{
if( aLayer == LAYER_ANCHOR )
{
const COLOR4D color = m_pcbSettings.GetColor( aModule, LAYER_ANCHOR );
// Draw anchor
m_gal->SetStrokeColor( color );
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
// Keep the size constant, not related to the scale
double anchorSize = 5.0 / m_gal->GetWorldScale();
VECTOR2D center = aModule->GetPosition();
m_gal->DrawLine( center - VECTOR2D( anchorSize, 0 ), center + VECTOR2D( anchorSize, 0 ) );
m_gal->DrawLine( center - VECTOR2D( 0, anchorSize ), center + VECTOR2D( 0, anchorSize ) );
}
}
void PCB_PAINTER::draw( const ZONE_CONTAINER* aZone, int aLayer )
{
if( !aZone->IsOnLayer( (PCB_LAYER_ID) aLayer ) )
return;
const COLOR4D& color = m_pcbSettings.GetColor( aZone, aLayer );
std::deque<VECTOR2D> corners;
PCB_RENDER_SETTINGS::DISPLAY_ZONE_MODE displayMode = m_pcbSettings.m_displayZone;
// Draw the outline
const SHAPE_POLY_SET* outline = aZone->Outline();
if( m_pcbSettings.m_zoneOutlines && outline && outline->OutlineCount() > 0 )
{
m_gal->SetStrokeColor( color );
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
// Draw each contour (main contour and holes)
/* This line:
* m_gal->DrawPolygon( *outline );
* should be enough, but currently does not work to draw holes contours in a complex polygon
* so each contour is draw as a simple polygon
*/
// Draw the main contour
m_gal->DrawPolyline( outline->COutline( 0 ) );
// Draw holes
int holes_count = outline->HoleCount( 0 );
for( int ii = 0; ii < holes_count; ++ii )
m_gal->DrawPolyline( outline->CHole( 0, ii ) );
// Draw hatch lines
for( const SEG& hatchLine : aZone->GetHatchLines() )
m_gal->DrawLine( hatchLine.A, hatchLine.B );
}
// Draw the filling
if( displayMode != PCB_RENDER_SETTINGS::DZ_HIDE_FILLED )
{
const SHAPE_POLY_SET& polySet = aZone->GetFilledPolysList();
if( polySet.OutlineCount() == 0 ) // Nothing to draw
return;
// Set up drawing options
m_gal->SetStrokeColor( color );
m_gal->SetFillColor( color );
m_gal->SetLineWidth( aZone->GetMinThickness() );
if( displayMode == PCB_RENDER_SETTINGS::DZ_SHOW_FILLED )
{
m_gal->SetIsFill( true );
m_gal->SetIsStroke( true );
}
else if( displayMode == PCB_RENDER_SETTINGS::DZ_SHOW_OUTLINED )
{
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
}
m_gal->DrawPolygon( polySet );
}
}
void PCB_PAINTER::draw( const DIMENSION* aDimension, int aLayer )
{
const COLOR4D& strokeColor = m_pcbSettings.GetColor( aDimension, aLayer );
m_gal->SetStrokeColor( strokeColor );
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
m_gal->SetLineWidth( getLineThickness( aDimension->GetWidth() ) );
// Draw an arrow
m_gal->DrawLine( VECTOR2D( aDimension->m_crossBarO ), VECTOR2D( aDimension->m_crossBarF ) );
m_gal->DrawLine( VECTOR2D( aDimension->m_featureLineGO ),
VECTOR2D( aDimension->m_featureLineGF ) );
m_gal->DrawLine( VECTOR2D( aDimension->m_featureLineDO ),
VECTOR2D( aDimension->m_featureLineDF ) );
m_gal->DrawLine( VECTOR2D( aDimension->m_crossBarF ), VECTOR2D( aDimension->m_arrowD1F ) );
m_gal->DrawLine( VECTOR2D( aDimension->m_crossBarF ), VECTOR2D( aDimension->m_arrowD2F ) );
m_gal->DrawLine( VECTOR2D( aDimension->m_crossBarO ), VECTOR2D( aDimension->m_arrowG1F ) );
m_gal->DrawLine( VECTOR2D( aDimension->m_crossBarO ), VECTOR2D( aDimension->m_arrowG2F ) );
// Draw text
TEXTE_PCB& text = aDimension->Text();
VECTOR2D position( text.GetTextPos().x, text.GetTextPos().y );
m_gal->SetLineWidth( text.GetThickness() );
m_gal->SetTextAttributes( &text );
m_gal->StrokeText( text.GetShownText(), position, text.GetTextAngleRadians() );
}
void PCB_PAINTER::draw( const PCB_TARGET* aTarget )
{
const COLOR4D& strokeColor = m_pcbSettings.GetColor( aTarget, aTarget->GetLayer() );
VECTOR2D position( aTarget->GetPosition() );
double size, radius;
m_gal->SetLineWidth( getLineThickness( aTarget->GetWidth() ) );
m_gal->SetStrokeColor( strokeColor );
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
m_gal->Save();
m_gal->Translate( position );
if( aTarget->GetShape() )
{
// shape x
m_gal->Rotate( M_PI / 4.0 );
size = 2.0 * aTarget->GetSize() / 3.0;
radius = aTarget->GetSize() / 2.0;
}
else
{
// shape +
size = aTarget->GetSize() / 2.0;
radius = aTarget->GetSize() / 3.0;
}
m_gal->DrawLine( VECTOR2D( -size, 0.0 ), VECTOR2D( size, 0.0 ) );
m_gal->DrawLine( VECTOR2D( 0.0, -size ), VECTOR2D( 0.0, size ) );
m_gal->DrawCircle( VECTOR2D( 0.0, 0.0 ), radius );
m_gal->Restore();
}
void PCB_PAINTER::draw( const MARKER_PCB* aMarker )
{
SHAPE_LINE_CHAIN polygon;
aMarker->ShapeToPolygon( polygon );
auto strokeColor = m_pcbSettings.GetColor( aMarker, LAYER_DRC );
m_gal->Save();
m_gal->Translate( aMarker->GetPosition() );
m_gal->SetFillColor( strokeColor );
m_gal->SetIsFill( true );
m_gal->SetIsStroke( false );
m_gal->DrawPolygon( polygon );
m_gal->Restore();
}
const double PCB_RENDER_SETTINGS::MAX_FONT_SIZE = Millimeter2iu( 10.0 );