mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 18:23:15 +02:00
This function convert a text shape to a set of polygons for the 3D viewer. Texts shapes are a bit special and can generate a lot of small overlapping polygons. These polygons are now merged before being added to the list of polygons handled by the 3D viewer draw functions. Locally merging polygons is usually not efficient but in this case it can speedup the 3D viewer (up to 10x). Mainly noticeable for silkscreen layers when the "copper" thickness is shown
528 lines
16 KiB
C++
528 lines
16 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2022 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 <pcb_edit_frame.h>
|
|
#include <base_units.h>
|
|
#include <bitmaps.h>
|
|
#include <board.h>
|
|
#include <board_design_settings.h>
|
|
#include <footprint.h>
|
|
#include <pcb_textbox.h>
|
|
#include <pcb_painter.h>
|
|
#include <trigo.h>
|
|
#include <string_utils.h>
|
|
#include <geometry/shape_compound.h>
|
|
#include <callback_gal.h>
|
|
#include <convert_basic_shapes_to_polygon.h>
|
|
#include <macros.h>
|
|
|
|
|
|
PCB_TEXTBOX::PCB_TEXTBOX( BOARD_ITEM* parent ) :
|
|
PCB_SHAPE( parent, PCB_TEXTBOX_T, SHAPE_T::RECT ),
|
|
EDA_TEXT( pcbIUScale )
|
|
{
|
|
SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
|
|
SetVertJustify( GR_TEXT_V_ALIGN_TOP );
|
|
SetMultilineAllowed( true );
|
|
}
|
|
|
|
|
|
PCB_TEXTBOX::~PCB_TEXTBOX()
|
|
{
|
|
}
|
|
|
|
|
|
int PCB_TEXTBOX::GetTextMargin() const
|
|
{
|
|
return KiROUND( GetTextSize().y * 0.8 );
|
|
}
|
|
|
|
|
|
VECTOR2I PCB_TEXTBOX::GetTopLeft() const
|
|
{
|
|
EDA_ANGLE rotation = GetDrawRotation();
|
|
|
|
if( rotation == ANGLE_90 )
|
|
return VECTOR2I( GetStartX(), GetEndY() );
|
|
else if( rotation == ANGLE_180 )
|
|
return GetEnd();
|
|
else if( rotation == ANGLE_270 )
|
|
return VECTOR2I( GetEndX(), GetStartY() );
|
|
else
|
|
return GetStart();
|
|
}
|
|
|
|
|
|
VECTOR2I PCB_TEXTBOX::GetBotRight() const
|
|
{
|
|
EDA_ANGLE rotation = GetDrawRotation();
|
|
|
|
if( rotation == ANGLE_90 )
|
|
return VECTOR2I( GetEndX(), GetStartY() );
|
|
else if( rotation == ANGLE_180 )
|
|
return GetStart();
|
|
else if( rotation == ANGLE_270 )
|
|
return VECTOR2I( GetStartX(), GetEndY() );
|
|
else
|
|
return GetEnd();
|
|
}
|
|
|
|
|
|
void PCB_TEXTBOX::SetTop( int aVal )
|
|
{
|
|
EDA_ANGLE rotation = GetDrawRotation();
|
|
|
|
if( rotation == ANGLE_90 || rotation == ANGLE_180 )
|
|
SetEndY( aVal );
|
|
else
|
|
SetStartY( aVal );
|
|
}
|
|
|
|
|
|
void PCB_TEXTBOX::SetBottom( int aVal )
|
|
{
|
|
EDA_ANGLE rotation = GetDrawRotation();
|
|
|
|
if( rotation == ANGLE_90 || rotation == ANGLE_180 )
|
|
SetStartY( aVal );
|
|
else
|
|
SetEndY( aVal );
|
|
}
|
|
|
|
|
|
void PCB_TEXTBOX::SetLeft( int aVal )
|
|
{
|
|
EDA_ANGLE rotation = GetDrawRotation();
|
|
|
|
if( rotation == ANGLE_180 || rotation == ANGLE_270 )
|
|
SetEndX( aVal );
|
|
else
|
|
SetStartX( aVal );
|
|
}
|
|
|
|
|
|
void PCB_TEXTBOX::SetRight( int aVal )
|
|
{
|
|
EDA_ANGLE rotation = GetDrawRotation();
|
|
|
|
if( rotation == ANGLE_180 || rotation == ANGLE_270 )
|
|
SetStartX( aVal );
|
|
else
|
|
SetEndX( aVal );
|
|
}
|
|
|
|
|
|
std::vector<VECTOR2I> PCB_TEXTBOX::GetAnchorAndOppositeCorner() const
|
|
{
|
|
std::vector<VECTOR2I> pts;
|
|
std::vector<VECTOR2I> corners = GetCorners();
|
|
EDA_ANGLE textAngle( GetDrawRotation() );
|
|
|
|
textAngle.Normalize();
|
|
|
|
pts.emplace_back( corners[0] );
|
|
|
|
if( textAngle < ANGLE_90 )
|
|
{
|
|
if( corners[1].y <= corners[0].y )
|
|
pts.emplace_back( corners[1] );
|
|
else
|
|
pts.emplace_back( corners[3] );
|
|
}
|
|
else if( textAngle < ANGLE_180 )
|
|
{
|
|
if( corners[1].x <= corners[0].x )
|
|
pts.emplace_back( corners[1] );
|
|
else
|
|
pts.emplace_back( corners[3] );
|
|
}
|
|
else if( textAngle < ANGLE_270 )
|
|
{
|
|
if( corners[1].y >= corners[0].y )
|
|
pts.emplace_back( corners[1] );
|
|
else
|
|
pts.emplace_back( corners[3] );
|
|
}
|
|
else
|
|
{
|
|
if( corners[1].x >= corners[0].x )
|
|
pts.emplace_back( corners[1] );
|
|
else
|
|
pts.emplace_back( corners[3] );
|
|
}
|
|
|
|
return pts;
|
|
}
|
|
|
|
|
|
VECTOR2I PCB_TEXTBOX::GetDrawPos() const
|
|
{
|
|
std::vector<VECTOR2I> corners = GetAnchorAndOppositeCorner();
|
|
GR_TEXT_H_ALIGN_T effectiveAlignment = GetHorizJustify();
|
|
VECTOR2I textAnchor;
|
|
VECTOR2I offset;
|
|
|
|
if( IsMirrored() )
|
|
{
|
|
switch( GetHorizJustify() )
|
|
{
|
|
case GR_TEXT_H_ALIGN_LEFT: effectiveAlignment = GR_TEXT_H_ALIGN_RIGHT; break;
|
|
case GR_TEXT_H_ALIGN_CENTER: effectiveAlignment = GR_TEXT_H_ALIGN_CENTER; break;
|
|
case GR_TEXT_H_ALIGN_RIGHT: effectiveAlignment = GR_TEXT_H_ALIGN_LEFT; break;
|
|
}
|
|
}
|
|
|
|
switch( effectiveAlignment )
|
|
{
|
|
case GR_TEXT_H_ALIGN_LEFT:
|
|
textAnchor = corners[0];
|
|
offset = VECTOR2I( GetTextMargin(), GetTextMargin() );
|
|
break;
|
|
case GR_TEXT_H_ALIGN_CENTER:
|
|
textAnchor = ( corners[0] + corners[1] ) / 2;
|
|
offset = VECTOR2I( 0, GetTextMargin() );
|
|
break;
|
|
case GR_TEXT_H_ALIGN_RIGHT:
|
|
textAnchor = corners[1];
|
|
offset = VECTOR2I( -GetTextMargin(), GetTextMargin() );
|
|
break;
|
|
}
|
|
|
|
RotatePoint( offset, GetDrawRotation() );
|
|
return textAnchor + offset;
|
|
}
|
|
|
|
|
|
double PCB_TEXTBOX::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const
|
|
{
|
|
constexpr double HIDE = std::numeric_limits<double>::max();
|
|
|
|
KIGFX::PCB_PAINTER* painter = static_cast<KIGFX::PCB_PAINTER*>( aView->GetPainter() );
|
|
KIGFX::PCB_RENDER_SETTINGS* renderSettings = painter->GetSettings();
|
|
|
|
if( aLayer == LAYER_LOCKED_ITEM_SHADOW )
|
|
{
|
|
// Hide shadow if the main layer is not shown
|
|
if( !aView->IsLayerVisible( m_layer ) )
|
|
return HIDE;
|
|
|
|
// Hide shadow on dimmed tracks
|
|
if( renderSettings->GetHighContrast() )
|
|
{
|
|
if( m_layer != renderSettings->GetPrimaryHighContrastLayer() )
|
|
return HIDE;
|
|
}
|
|
}
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
|
|
wxString PCB_TEXTBOX::GetShownText( int aDepth ) const
|
|
{
|
|
BOARD* board = dynamic_cast<BOARD*>( GetParent() );
|
|
|
|
std::function<bool( wxString* )> pcbTextResolver =
|
|
[&]( wxString* token ) -> bool
|
|
{
|
|
if( token->IsSameAs( wxT( "LAYER" ) ) )
|
|
{
|
|
*token = GetLayerName();
|
|
return true;
|
|
}
|
|
|
|
if( token->Contains( ':' ) )
|
|
{
|
|
wxString remainder;
|
|
wxString ref = token->BeforeFirst( ':', &remainder );
|
|
BOARD_ITEM* refItem = board->GetItem( KIID( ref ) );
|
|
|
|
if( refItem && refItem->Type() == PCB_FOOTPRINT_T )
|
|
{
|
|
FOOTPRINT* refFP = static_cast<FOOTPRINT*>( refItem );
|
|
|
|
if( refFP->ResolveTextVar( &remainder, aDepth + 1 ) )
|
|
{
|
|
*token = remainder;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
std::function<bool( wxString* )> boardTextResolver =
|
|
[&]( wxString* token ) -> bool
|
|
{
|
|
return board->ResolveTextVar( token, aDepth + 1 );
|
|
};
|
|
|
|
wxString text = EDA_TEXT::GetShownText();
|
|
|
|
if( board && HasTextVars() && aDepth < 10 )
|
|
text = ExpandTextVars( text, &pcbTextResolver, &boardTextResolver, board->GetProject() );
|
|
|
|
KIFONT::FONT* font = GetDrawFont();
|
|
std::vector<VECTOR2I> corners = GetAnchorAndOppositeCorner();
|
|
int colWidth = ( corners[1] - corners[0] ).EuclideanNorm();
|
|
|
|
colWidth -= GetTextMargin() * 2;
|
|
font->LinebreakText( text, colWidth, GetTextSize(), GetTextThickness(), IsBold(), IsItalic() );
|
|
|
|
return text;
|
|
}
|
|
|
|
|
|
void PCB_TEXTBOX::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
|
|
{
|
|
// Don't use GetShownText() here; we want to show the user the variable references
|
|
aList.emplace_back( _( "Text Box" ), KIUI::EllipsizeStatusText( aFrame, GetText() ) );
|
|
|
|
if( aFrame->GetName() == PCB_EDIT_FRAME_NAME && IsLocked() )
|
|
aList.emplace_back( _( "Status" ), _( "Locked" ) );
|
|
|
|
aList.emplace_back( _( "Layer" ), GetLayerName() );
|
|
aList.emplace_back( _( "Mirror" ), IsMirrored() ? _( "Yes" ) : _( "No" ) );
|
|
aList.emplace_back( _( "Angle" ), wxString::Format( "%g", GetTextAngle().AsDegrees() ) );
|
|
|
|
aList.emplace_back( _( "Font" ), GetDrawFont()->GetName() );
|
|
aList.emplace_back( _( "Text Thickness" ), aFrame->MessageTextFromValue( GetTextThickness() ) );
|
|
aList.emplace_back( _( "Text Width" ), aFrame->MessageTextFromValue( GetTextWidth() ) );
|
|
aList.emplace_back( _( "Text Height" ), aFrame->MessageTextFromValue( GetTextHeight() ) );
|
|
|
|
aList.emplace_back( _( "Box Width" ),
|
|
aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
|
|
|
|
aList.emplace_back( _( "Box Height" ),
|
|
aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ));
|
|
|
|
m_stroke.GetMsgPanelInfo( aFrame, aList );
|
|
}
|
|
|
|
|
|
void PCB_TEXTBOX::Move( const VECTOR2I& aMoveVector )
|
|
{
|
|
PCB_SHAPE::Move( aMoveVector );
|
|
EDA_TEXT::Offset( aMoveVector );
|
|
}
|
|
|
|
|
|
void PCB_TEXTBOX::Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
|
|
{
|
|
PCB_SHAPE::Rotate( aRotCentre, aAngle );
|
|
SetTextAngle( GetTextAngle() + aAngle );
|
|
|
|
if( GetTextAngle().IsCardinal() && GetShape() != SHAPE_T::RECT )
|
|
{
|
|
std::vector<VECTOR2I> corners = GetCorners();
|
|
VECTOR2I diag = corners[2] - corners[0];
|
|
EDA_ANGLE angle = GetTextAngle();
|
|
|
|
SetShape( SHAPE_T::RECT );
|
|
SetStart( corners[0] );
|
|
|
|
angle.Normalize();
|
|
|
|
if( angle == ANGLE_90 )
|
|
SetEnd( VECTOR2I( corners[0].x + abs( diag.x ), corners[0].y - abs( diag.y ) ) );
|
|
else if( angle == ANGLE_180 )
|
|
SetEnd( VECTOR2I( corners[0].x - abs( diag.x ), corners[0].y - abs( diag.y ) ) );
|
|
else if( angle == ANGLE_270 )
|
|
SetEnd( VECTOR2I( corners[0].x - abs( diag.x ), corners[0].y + abs( diag.y ) ) );
|
|
else
|
|
SetEnd( VECTOR2I( corners[0].x + abs( diag.x ), corners[0].y + abs( diag.y ) ) );
|
|
}
|
|
}
|
|
|
|
|
|
void PCB_TEXTBOX::Mirror( const VECTOR2I& aCentre, bool aMirrorAroundXAxis )
|
|
{
|
|
// the position is mirrored, but not the text (or its justification)
|
|
PCB_SHAPE::Mirror( aCentre, aMirrorAroundXAxis );
|
|
|
|
BOX2I rect( m_start, m_end - m_start );
|
|
rect.Normalize();
|
|
m_start = VECTOR2I( rect.GetLeft(), rect.GetTop() );
|
|
m_end = VECTOR2I( rect.GetRight(), rect.GetBottom() );
|
|
}
|
|
|
|
|
|
void PCB_TEXTBOX::Flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
|
|
{
|
|
if( aFlipLeftRight )
|
|
{
|
|
m_start.x = aCentre.x - ( m_start.x - aCentre.x );
|
|
m_end.x = aCentre.x - ( m_end.x - aCentre.x );
|
|
SetTextAngle( -GetTextAngle() );
|
|
}
|
|
else
|
|
{
|
|
m_start.y = aCentre.y - ( m_start.y - aCentre.y );
|
|
m_end.y = aCentre.y - ( m_end.y - aCentre.y );
|
|
SetTextAngle( ANGLE_180 - GetTextAngle() );
|
|
}
|
|
|
|
SetLayer( FlipLayer( GetLayer(), GetBoard()->GetCopperLayerCount() ) );
|
|
SetMirrored( !IsMirrored() );
|
|
}
|
|
|
|
|
|
bool PCB_TEXTBOX::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const
|
|
{
|
|
BOX2I rect = GetBoundingBox();
|
|
|
|
rect.Inflate( aAccuracy );
|
|
|
|
return rect.Contains( aPosition );
|
|
}
|
|
|
|
|
|
bool PCB_TEXTBOX::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
|
|
{
|
|
BOX2I rect = aRect;
|
|
|
|
rect.Inflate( aAccuracy );
|
|
|
|
if( aContained )
|
|
return rect.Contains( GetBoundingBox() );
|
|
|
|
return rect.Intersects( GetBoundingBox() );
|
|
}
|
|
|
|
|
|
wxString PCB_TEXTBOX::GetSelectMenuText( UNITS_PROVIDER* aUnitsProvider ) const
|
|
{
|
|
return wxString::Format( _( "PCB Text Box on %s"), GetLayerName() );
|
|
}
|
|
|
|
|
|
BITMAPS PCB_TEXTBOX::GetMenuImage() const
|
|
{
|
|
return BITMAPS::add_textbox;
|
|
}
|
|
|
|
|
|
EDA_ITEM* PCB_TEXTBOX::Clone() const
|
|
{
|
|
return new PCB_TEXTBOX( *this );
|
|
}
|
|
|
|
|
|
void PCB_TEXTBOX::SwapData( BOARD_ITEM* aImage )
|
|
{
|
|
assert( aImage->Type() == PCB_TEXTBOX_T );
|
|
|
|
std::swap( *((PCB_TEXTBOX*) this), *((PCB_TEXTBOX*) aImage) );
|
|
}
|
|
|
|
|
|
std::shared_ptr<SHAPE> PCB_TEXTBOX::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING aFlash ) const
|
|
{
|
|
std::shared_ptr<SHAPE_COMPOUND> shape = GetEffectiveTextShape();
|
|
|
|
if( PCB_SHAPE::GetStroke().GetWidth() >= 0 )
|
|
shape->AddShape( PCB_SHAPE::GetEffectiveShape( aLayer, aFlash ) );
|
|
|
|
return shape;
|
|
}
|
|
|
|
|
|
void PCB_TEXTBOX::TransformTextShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
|
|
PCB_LAYER_ID aLayer, int aClearance,
|
|
int aError, ERROR_LOC aErrorLoc ) const
|
|
{
|
|
KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
|
|
KIFONT::FONT* font = GetDrawFont();
|
|
int penWidth = GetEffectiveTextPenWidth();
|
|
|
|
// Note: this function is mainly used in 3D viewer.
|
|
// the polygonal shape of a text can have many basic shapes,
|
|
// so combining these shapes can be very useful to create a final shape
|
|
// swith a lot less vertices to speedup calculations using this final shape
|
|
// Simplify shapes is not usually always efficient, but in this case it is.
|
|
SHAPE_POLY_SET buffer;
|
|
|
|
CALLBACK_GAL callback_gal( empty_opts,
|
|
// Stroke callback
|
|
[&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
|
|
{
|
|
TransformOvalToPolygon( buffer, aPt1, aPt2, penWidth+ ( 2 * aClearance ),
|
|
aError, ERROR_INSIDE );
|
|
},
|
|
// Triangulation callback
|
|
[&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2, const VECTOR2I& aPt3 )
|
|
{
|
|
buffer.NewOutline();
|
|
|
|
for( const VECTOR2I& point : { aPt1, aPt2, aPt3 } )
|
|
buffer.Append( point.x, point.y );
|
|
} );
|
|
|
|
font->Draw( &callback_gal, GetShownText(), GetDrawPos(), GetAttributes() );
|
|
|
|
buffer.Simplify( SHAPE_POLY_SET::PM_FAST );
|
|
aCornerBuffer.Append( buffer );
|
|
}
|
|
|
|
|
|
void PCB_TEXTBOX::TransformShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
|
|
PCB_LAYER_ID aLayer, int aClearance,
|
|
int aError, ERROR_LOC aErrorLoc,
|
|
bool aIgnoreLineWidth ) const
|
|
{
|
|
// Don't use PCB_SHAPE::TransformShapeWithClearanceToPolygon. We want to treat the
|
|
// textbox as filled even if there's no background colour.
|
|
|
|
std::vector<VECTOR2I> pts = GetRectCorners();
|
|
|
|
aCornerBuffer.NewOutline();
|
|
|
|
for( const VECTOR2I& pt : pts )
|
|
aCornerBuffer.Append( pt );
|
|
|
|
int width = GetWidth() + ( 2 * aClearance );
|
|
|
|
if( width > 0 )
|
|
{
|
|
// Add in segments
|
|
TransformOvalToPolygon( aCornerBuffer, pts[0], pts[1], width, aError, aErrorLoc );
|
|
TransformOvalToPolygon( aCornerBuffer, pts[1], pts[2], width, aError, aErrorLoc );
|
|
TransformOvalToPolygon( aCornerBuffer, pts[2], pts[3], width, aError, aErrorLoc );
|
|
TransformOvalToPolygon( aCornerBuffer, pts[3], pts[0], width, aError, aErrorLoc );
|
|
}
|
|
}
|
|
|
|
|
|
static struct PCB_TEXTBOX_DESC
|
|
{
|
|
PCB_TEXTBOX_DESC()
|
|
{
|
|
PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
|
|
REGISTER_TYPE( PCB_TEXTBOX );
|
|
propMgr.AddTypeCast( new TYPE_CAST<PCB_TEXTBOX, PCB_SHAPE> );
|
|
propMgr.AddTypeCast( new TYPE_CAST<PCB_TEXTBOX, EDA_TEXT> );
|
|
propMgr.InheritsAfter( TYPE_HASH( PCB_TEXTBOX ), TYPE_HASH( PCB_SHAPE ) );
|
|
propMgr.InheritsAfter( TYPE_HASH( PCB_TEXTBOX ), TYPE_HASH( EDA_TEXT ) );
|
|
}
|
|
} _PCB_TEXTBOX_DESC;
|