kicad-source/gerbview/gerbview_painter.cpp
jean-pierre charras 388397f97d Protect TesselatePolygon() against degenerated polygons (less than 3 corners) to avoid crashes.
Use TesselatePolygon() to draw polygons in Gerbview instead of GLU tesselation, much slower.
Add helper methods in GAL to know if the current GAL engine is Cairo, OpenGL or something else,
useful to optimize drawing code.
2018-12-18 12:49:14 +01:00

568 lines
17 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017 Jon Evans <jon@craftyjon.com>
* Copyright (C) 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <colors_design_settings.h>
#include <gerbview_painter.h>
#include <gal/graphics_abstraction_layer.h>
#include <convert_basic_shapes_to_polygon.h>
#include <convert_to_biu.h>
#include <gerbview.h>
#include <gerber_draw_item.h>
#include <gerber_file_image.h>
using namespace KIGFX;
GERBVIEW_RENDER_SETTINGS::GERBVIEW_RENDER_SETTINGS()
{
m_backgroundColor = COLOR4D::BLACK;
m_spotFill = true;
m_lineFill = true;
m_polygonFill = true;
m_showNegativeItems = false;
m_showCodes = false;
m_diffMode = true;
m_componentHighlightString = "";
m_netHighlightString = "";
m_attributeHighlightString = "";
update();
}
void GERBVIEW_RENDER_SETTINGS::ImportLegacyColors( const COLORS_DESIGN_SETTINGS* aSettings )
{
for( int i = GERBVIEW_LAYER_ID_START;
i < GERBVIEW_LAYER_ID_START + GERBER_DRAWLAYERS_COUNT; i++ )
{
COLOR4D baseColor = aSettings->GetLayerColor( i );
if( m_diffMode )
baseColor.a = 0.75;
m_layerColors[i] = baseColor;
m_layerColorsHi[i] = baseColor.Brightened( 0.5 );
m_layerColorsSel[i] = baseColor.Brightened( 0.8 );
m_layerColorsDark[i] = baseColor.Darkened( 0.25 );
}
for( int i = LAYER_DCODES; i < GERBVIEW_LAYER_ID_END; i++ )
m_layerColors[i] = aSettings->GetLayerColor( i );
for( int i = GAL_LAYER_ID_START; i < GAL_LAYER_ID_END; i++ )
m_layerColors[i] = aSettings->GetLayerColor( i );
update();
}
void GERBVIEW_RENDER_SETTINGS::LoadDisplayOptions( const GBR_DISPLAY_OPTIONS* aOptions )
{
if( aOptions == NULL )
return;
m_spotFill = aOptions->m_DisplayFlashedItemsFill;
m_lineFill = aOptions->m_DisplayLinesFill;
m_polygonFill = aOptions->m_DisplayPolygonsFill;
m_showNegativeItems = aOptions->m_DisplayNegativeObjects;
m_showCodes = aOptions->m_DisplayDCodes;
m_diffMode = aOptions->m_DiffMode;
m_hiContrastEnabled = aOptions->m_HighContrastMode;
m_showPageLimits = aOptions->m_DisplayPageLimits;
m_backgroundColor = aOptions->m_BgDrawColor;
update();
}
const COLOR4D& GERBVIEW_RENDER_SETTINGS::GetColor( const VIEW_ITEM* aItem, int aLayer ) const
{
const EDA_ITEM* item = static_cast<const EDA_ITEM*>( aItem );
static const COLOR4D transparent = COLOR4D( 0, 0, 0, 0 );
const GERBER_DRAW_ITEM* gbrItem = nullptr;
if( item && item->Type() == GERBER_DRAW_ITEM_T )
gbrItem = static_cast<const GERBER_DRAW_ITEM*>( item );
// All DCODE layers stored under a single color setting
if( IsDCodeLayer( aLayer ) )
return m_layerColors[ LAYER_DCODES ];
if( item && item->IsSelected() )
return m_layerColorsSel[aLayer];
if( gbrItem && gbrItem->GetLayerPolarity() )
{
if( m_showNegativeItems )
return m_layerColors[LAYER_NEGATIVE_OBJECTS];
else
return transparent;
}
if( !m_netHighlightString.IsEmpty() && gbrItem &&
m_netHighlightString == gbrItem->GetNetAttributes().m_Netname )
return m_layerColorsHi[aLayer];
if( !m_componentHighlightString.IsEmpty() && gbrItem &&
m_componentHighlightString == gbrItem->GetNetAttributes().m_Cmpref )
return m_layerColorsHi[aLayer];
if( !m_attributeHighlightString.IsEmpty() && gbrItem && gbrItem->GetDcodeDescr() &&
m_attributeHighlightString == gbrItem->GetDcodeDescr()->m_AperFunction )
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];
}
GERBVIEW_PAINTER::GERBVIEW_PAINTER( GAL* aGal ) :
PAINTER( aGal )
{
}
// TODO(JE): Pull up to PAINTER?
int GERBVIEW_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_gerbviewSettings.m_outlineWidth;
return aActualThickness;
}
bool GERBVIEW_PAINTER::Draw( const VIEW_ITEM* aItem, int aLayer )
{
const EDA_ITEM* item = static_cast<const EDA_ITEM*>( aItem );
// the "cast" applied in here clarifies which overloaded draw() is called
switch( item->Type() )
{
case GERBER_DRAW_ITEM_T:
draw( static_cast<GERBER_DRAW_ITEM*>( const_cast<EDA_ITEM*>( item ) ), aLayer );
break;
default:
// Painter does not know how to draw the object
return false;
}
return true;
}
// TODO(JE) aItem can't be const because of GetDcodeDescr()
// Probably that can be refactored in GERBER_DRAW_ITEM to allow const here.
void GERBVIEW_PAINTER::draw( /*const*/ GERBER_DRAW_ITEM* aItem, int aLayer )
{
VECTOR2D start( aItem->GetABPosition( aItem->m_Start ) ); // TODO(JE) Getter
VECTOR2D end( aItem->GetABPosition( aItem->m_End ) ); // TODO(JE) Getter
int width = aItem->m_Size.x; // TODO(JE) Getter
bool isFilled = true;
COLOR4D color;
// TODO(JE) This doesn't actually work properly for ImageNegative
bool isNegative = ( aItem->GetLayerPolarity() ^ aItem->m_GerberImageFile->m_ImageNegative );
// Draw DCODE overlay text
if( IsDCodeLayer( aLayer ) )
{
wxString codeText;
VECTOR2D textPosition;
double textSize;
double orient;
if( !aItem->GetTextD_CodePrms( textSize, textPosition, orient ) )
return;
color = m_gerbviewSettings.GetColor( aItem, aLayer );
codeText.Printf( "D%d", aItem->m_DCode );
m_gal->SetIsStroke( true );
m_gal->SetIsFill( false );
m_gal->SetStrokeColor( color );
m_gal->SetFillColor( COLOR4D( 0, 0, 0, 0 ) );
m_gal->SetLineWidth( textSize/10 );
m_gal->SetFontBold( false );
m_gal->SetFontItalic( false );
m_gal->SetTextMirrored( false );
m_gal->SetGlyphSize( VECTOR2D( textSize, textSize) );
m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER );
m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_CENTER );
m_gal->BitmapText( codeText, textPosition, orient );
return;
}
color = m_gerbviewSettings.GetColor( aItem, aLayer );
// TODO: Should brightened color be a preference?
if( aItem->IsBrightened() )
color = COLOR4D( 0.0, 1.0, 0.0, 0.75 );
m_gal->SetNegativeDrawMode( isNegative );
m_gal->SetStrokeColor( color );
m_gal->SetFillColor( color );
m_gal->SetIsFill( isFilled );
m_gal->SetIsStroke( !isFilled );
switch( aItem->m_Shape )
{
case GBR_POLYGON:
{
isFilled = m_gerbviewSettings.m_polygonFill;
m_gal->SetIsFill( isFilled );
m_gal->SetIsStroke( !isFilled );
if( isNegative && !isFilled )
{
m_gal->SetNegativeDrawMode( false );
m_gal->SetStrokeColor( GetSettings()->GetColor( aItem, aLayer ) );
}
if( !isFilled )
m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
SHAPE_POLY_SET absolutePolygon = aItem->m_Polygon;
for( auto it = absolutePolygon.Iterate( 0 ); it; ++it )
*it = aItem->GetABPosition( *it );
// Degenerated polygons (having < 3 points) are drawn as lines
// to avoid issues in draw polygon functions
if( !isFilled || absolutePolygon.COutline( 0 ).PointCount() < 3 )
m_gal->DrawPolyline( absolutePolygon.COutline( 0 ) );
else
{
// 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
if( m_gal->IsOpenGlEngine() )
absolutePolygon.CacheTriangulation();
m_gal->DrawPolygon( absolutePolygon );
}
break;
}
case GBR_CIRCLE:
{
isFilled = m_gerbviewSettings.m_lineFill;
double radius = GetLineLength( aItem->m_Start, aItem->m_End );
m_gal->DrawCircle( start, radius );
break;
}
case GBR_ARC:
{
isFilled = m_gerbviewSettings.m_lineFill;
// These are swapped because wxDC fills arcs counterclockwise and GAL
// fills them clockwise.
wxPoint arcStart = aItem->m_End;
wxPoint arcEnd = aItem->m_Start;
// Gerber arcs are 3-point (start, center, end)
// GAL needs center, radius, start angle, end angle
double radius = GetLineLength( arcStart, aItem->m_ArcCentre );
VECTOR2D center = aItem->GetABPosition( aItem->m_ArcCentre );
VECTOR2D startVec = VECTOR2D( aItem->GetABPosition( arcStart ) ) - center;
VECTOR2D endVec = VECTOR2D( aItem->GetABPosition( arcEnd ) ) - center;
m_gal->SetIsFill( isFilled );
m_gal->SetIsStroke( !isFilled );
m_gal->SetLineWidth( isFilled ? width : m_gerbviewSettings.m_outlineWidth );
double startAngle = startVec.Angle();
double endAngle = endVec.Angle();
// GAL fills in direction of increasing angle, so we have to convert
// the angle from the -PI to PI domain of atan2() to ensure that
// the arc goes in the right direction
if( startAngle > endAngle )
endAngle += (2 * M_PI);
// 360-degree arcs are stored in the file with start equal to end
if( arcStart == arcEnd )
{
startAngle = 0;
endAngle = 2 * M_PI;
}
m_gal->DrawArcSegment( center, radius, startAngle, endAngle, width );
// Arc Debugging
// m_gal->SetLineWidth( 5 );
// m_gal->SetStrokeColor( COLOR4D( 0.0, 1.0, 0.0, 1.0 ) );
// m_gal->DrawLine( center, aItem->GetABPosition( arcStart ) );
// m_gal->SetStrokeColor( COLOR4D( 1.0, 0.0, 0.0, 1.0 ) );
// m_gal->DrawLine( center, aItem->GetABPosition( arcEnd ) );
break;
}
case GBR_SPOT_CIRCLE:
case GBR_SPOT_RECT:
case GBR_SPOT_OVAL:
case GBR_SPOT_POLY:
case GBR_SPOT_MACRO:
{
isFilled = m_gerbviewSettings.m_spotFill;
drawFlashedShape( aItem, isFilled );
break;
}
case GBR_SEGMENT:
{
/* Plot a line from m_Start to m_End.
* Usually, a round pen is used, but some gerber files use a rectangular pen
* In fact, any aperture can be used to plot a line.
* currently: only a square pen is handled (I believe using a polygon gives a strange plot).
*/
isFilled = m_gerbviewSettings.m_lineFill;
m_gal->SetIsFill( isFilled );
m_gal->SetIsStroke( !isFilled );
if( isNegative && !isFilled )
m_gal->SetStrokeColor( GetSettings()->GetColor( aItem, aLayer ) );
// TODO(JE) Refactor this to allow const aItem
D_CODE* code = aItem->GetDcodeDescr();
if( code && code->m_Shape == APT_RECT )
{
if( aItem->m_Polygon.OutlineCount() == 0 )
aItem->ConvertSegmentToPolygon();
drawPolygon( aItem, aItem->m_Polygon, isFilled );
}
else
{
if( !isFilled )
m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
m_gal->DrawSegment( start, end, width );
}
break;
}
default:
wxASSERT_MSG( false, wxT( "GERBER_DRAW_ITEM shape is unknown!" ) );
break;
}
// Enable for bounding box debugging
#if 0
const BOX2I& bb = aItem->ViewBBox();
m_gal->SetIsStroke( true );
m_gal->SetIsFill( true );
m_gal->SetLineWidth( 3 );
m_gal->SetStrokeColor( COLOR4D(0.9, 0.9, 0, 0.4) );
m_gal->SetFillColor( COLOR4D(0.9, 0.9, 0, 0.1) );
m_gal->DrawRectangle( bb.GetOrigin(), bb.GetEnd() );
#endif
}
void GERBVIEW_PAINTER::drawPolygon( GERBER_DRAW_ITEM* aParent,
SHAPE_POLY_SET& aPolygon,
bool aFilled )
{
for( auto it = aPolygon.Iterate( 0 ); it; ++it )
*it = aParent->GetABPosition( *it );
if( !m_gerbviewSettings.m_polygonFill )
m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
if( !aFilled )
{
for( int i = 0; i < aPolygon.OutlineCount(); i++ )
m_gal->DrawPolyline( aPolygon.COutline( i ) );
}
else
m_gal->DrawPolygon( aPolygon );
}
void GERBVIEW_PAINTER::drawFlashedShape( GERBER_DRAW_ITEM* aItem, bool aFilled )
{
D_CODE* code = aItem->GetDcodeDescr();
wxASSERT_MSG( code, wxT( "drawFlashedShape: Item has no D_CODE!" ) );
if( !code )
return;
m_gal->SetIsFill( aFilled );
m_gal->SetIsStroke( !aFilled );
m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
switch( aItem->m_Shape )
{
case GBR_SPOT_CIRCLE:
{
int radius = code->m_Size.x >> 1;
VECTOR2D start( aItem->GetABPosition( aItem->m_Start ) );
if( !aFilled || code->m_DrillShape == APT_DEF_NO_HOLE )
{
m_gal->DrawCircle( start, radius );
}
else // rectangular hole
{
if( code->m_Polygon.OutlineCount() == 0 )
code->ConvertShapeToPolygon();
SHAPE_POLY_SET poly = code->m_Polygon;
poly.Move( aItem->m_Start );
drawPolygon( aItem, poly, aFilled );
}
break;
}
case GBR_SPOT_RECT:
{
wxPoint codeStart;
wxPoint aShapePos = aItem->m_Start;
codeStart.x = aShapePos.x - code->m_Size.x / 2;
codeStart.y = aShapePos.y - code->m_Size.y / 2;
wxPoint codeEnd = codeStart + code->m_Size;
codeStart = aItem->GetABPosition( codeStart );
codeEnd = aItem->GetABPosition( codeEnd );
if( !aFilled || code->m_DrillShape == APT_DEF_NO_HOLE )
{
m_gal->DrawRectangle( VECTOR2D( codeStart ), VECTOR2D( codeEnd ) );
}
else
{
if( code->m_Polygon.OutlineCount() == 0 )
code->ConvertShapeToPolygon();
SHAPE_POLY_SET poly = code->m_Polygon;
poly.Move( aItem->m_Start );
drawPolygon( aItem, poly, aFilled );
}
break;
}
case GBR_SPOT_OVAL:
{
int radius = 0;
wxPoint codeStart = aItem->m_Start;
wxPoint codeEnd = aItem->m_Start;
if( code->m_Size.x > code->m_Size.y ) // horizontal oval
{
int delta = (code->m_Size.x - code->m_Size.y) / 2;
codeStart.x -= delta;
codeEnd.x += delta;
radius = code->m_Size.y;
}
else // horizontal oval
{
int delta = (code->m_Size.y - code->m_Size.x) / 2;
codeStart.y -= delta;
codeEnd.y += delta;
radius = code->m_Size.x;
}
codeStart = aItem->GetABPosition( codeStart );
codeEnd = aItem->GetABPosition( codeEnd );
if( !aFilled || code->m_DrillShape == APT_DEF_NO_HOLE )
{
m_gal->DrawSegment( codeStart, codeEnd, radius );
}
else
{
if( code->m_Polygon.OutlineCount() == 0 )
code->ConvertShapeToPolygon();
SHAPE_POLY_SET poly = code->m_Polygon;
poly.Move( aItem->m_Start );
drawPolygon( aItem, poly, aFilled );
}
break;
}
case GBR_SPOT_POLY:
{
if( code->m_Polygon.OutlineCount() == 0 )
code->ConvertShapeToPolygon();
SHAPE_POLY_SET poly = code->m_Polygon;
poly.Move( aItem->m_Start );
drawPolygon( aItem, poly, aFilled );
break;
}
case GBR_SPOT_MACRO:
drawApertureMacro( aItem, aFilled );
break;
default:
wxASSERT_MSG( false, wxT( "Unknown Gerber flashed shape!" ) );
break;
}
}
void GERBVIEW_PAINTER::drawApertureMacro( GERBER_DRAW_ITEM* aParent, bool aFilled )
{
D_CODE* code = aParent->GetDcodeDescr();
APERTURE_MACRO* macro = code->GetMacro();
SHAPE_POLY_SET* macroShape = macro->GetApertureMacroShape( aParent, aParent->m_Start );
if( !m_gerbviewSettings.m_polygonFill )
m_gal->SetLineWidth( m_gerbviewSettings.m_outlineWidth );
if( !aFilled )
{
for( int i = 0; i < macroShape->OutlineCount(); i++ )
m_gal->DrawPolyline( macroShape->COutline( i ) );
}
else
m_gal->DrawPolygon( *macroShape );
}
const double GERBVIEW_RENDER_SETTINGS::MAX_FONT_SIZE = Millimeter2iu( 10.0 );