/* * KiRouter - a push-and-(sometimes-)shove PCB router * * Copyright (C) 2013-2014 CERN * Copyright (C) 2016-2023 KiCad Developers, see AUTHORS.txt for contributors. * Author: Tomasz Wlostowski * * 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 . */ #include #include #include #include #include #include #include #include "router_preview_item.h" #include "pns_arc.h" #include "pns_line.h" #include "pns_segment.h" #include "pns_via.h" using namespace KIGFX; ROUTER_PREVIEW_ITEM::ROUTER_PREVIEW_ITEM( const PNS::ITEM* aItem, KIGFX::VIEW* aView, int aFlags ) : EDA_ITEM( NOT_USED ), m_view( aView ), m_shape( nullptr ), m_hole( nullptr ), m_flags( aFlags ) { BOARD_ITEM* boardItem = aItem ? aItem->BoardItem() : nullptr; // A PNS::SOLID for an edge-cut item must have 0 width for collision calculations, but when // highlighting an edge we want to show it with its parent PCB_SHAPE's shape. if( boardItem && boardItem->IsOnLayer( Edge_Cuts ) ) { m_shape = boardItem->GetEffectiveShape()->Clone(); } else if( aItem ) { m_shape = aItem->Shape()->Clone(); if( aItem->Hole() ) m_hole = aItem->Hole()->Shape()->Clone(); } m_clearance = -1; m_originLayer = m_layer = LAYER_SELECT_OVERLAY ; m_showClearance = false; // initialize variables, overwritten by Update( aItem ), if aItem != NULL m_type = PR_SHAPE; m_width = ( aFlags & PNS_SEMI_SOLID ) ? 1 : 0; m_depth = m_originDepth = aView->GetLayerOrder( m_originLayer ); if( aItem ) Update( aItem ); } ROUTER_PREVIEW_ITEM::ROUTER_PREVIEW_ITEM( const SHAPE& aShape, KIGFX::VIEW* aView ) : EDA_ITEM( NOT_USED ), m_flags( 0 ) { m_view = aView; m_shape = aShape.Clone(); m_hole = nullptr; m_clearance = -1; m_originLayer = m_layer = LAYER_SELECT_OVERLAY; m_showClearance = false; // initialize variables, overwritten by Update( aItem ), if aItem != NULL m_type = PR_SHAPE; m_width = 0; m_depth = m_originDepth = aView->GetLayerOrder( m_originLayer ); } ROUTER_PREVIEW_ITEM::~ROUTER_PREVIEW_ITEM() { delete m_shape; delete m_hole; } void ROUTER_PREVIEW_ITEM::Update( const PNS::ITEM* aItem ) { m_originLayer = aItem->Layers().Start(); if( const PNS::LINE* l = dyn_cast( aItem ) ) { if( !l->SegmentCount() ) return; } else if( const PNS::VIA* v = dyn_cast( aItem ) ) { if( v->IsVirtual() ) return; } assert( m_originLayer >= 0 ); m_layer = m_originLayer; m_color = getLayerColor( m_originLayer ); m_color.a = 0.8; m_depth = m_originDepth - ( ( aItem->Layers().Start() + 1 ) * LayerDepthFactor ); switch( aItem->Kind() ) { case PNS::ITEM::LINE_T: m_type = PR_SHAPE; m_width = static_cast( aItem )->Width(); break; case PNS::ITEM::ARC_T: m_type = PR_SHAPE; m_width = static_cast( aItem )->Width(); break; case PNS::ITEM::SEGMENT_T: m_type = PR_SHAPE; m_width = static_cast( aItem )->Width(); break; case PNS::ITEM::VIA_T: m_originLayer = m_layer = LAYER_VIAS; m_type = PR_SHAPE; m_width = 0; m_color = COLOR4D( 0.7, 0.7, 0.7, 0.8 ); m_depth = m_originDepth - ( PCB_LAYER_ID_COUNT * LayerDepthFactor ); delete m_shape; m_shape = nullptr; if( aItem->Shape() ) m_shape = aItem->Shape()->Clone(); delete m_hole; m_hole = nullptr; if( aItem->Hole() ) m_hole = aItem->Hole()->Shape()->Clone(); break; case PNS::ITEM::SOLID_T: m_type = PR_SHAPE; break; default: break; } if( aItem->Marker() & PNS::MK_VIOLATION ) m_flags |= PNS_COLLISION; if( m_flags & PNS_COLLISION ) m_color = COLOR4D( 0, 1, 0, 1 ); if( m_flags & PNS_HOVER_ITEM ) m_color = m_color.WithAlpha( 1.0 ); } const BOX2I ROUTER_PREVIEW_ITEM::ViewBBox() const { BOX2I bbox; switch( m_type ) { case PR_SHAPE: if( m_shape ) { bbox = m_shape->BBox(); bbox.Inflate( m_width / 2 ); } if( m_hole ) bbox.Merge( m_hole->BBox() ); return bbox; case PR_POINT: bbox = BOX2I ( m_pos - VECTOR2I( 100000, 100000 ), VECTOR2I( 200000, 200000 ) ); return bbox; default: break; } return bbox; } void ROUTER_PREVIEW_ITEM::drawLineChain( const SHAPE_LINE_CHAIN_BASE* aL, KIGFX::GAL* gal ) const { wxCHECK( aL, /* void */ ); gal->SetIsFill( false ); for( int s = 0; s < aL->GetSegmentCount(); s++ ) { SEG seg = aL->GetSegment( s ); if( seg.A == seg.B ) { gal->SetIsFill( true ); gal->SetIsStroke( false ); gal->DrawCircle( seg.A, gal->GetLineWidth() / 2 ); gal->SetIsFill( false ); gal->SetIsStroke( true ); } else { gal->DrawLine( seg.A, seg.B ); } } const SHAPE_LINE_CHAIN* lineChain = dynamic_cast( aL ); for( size_t s = 0; lineChain && s < lineChain->ArcCount(); s++ ) { const SHAPE_ARC& arc = lineChain->CArcs()[s]; EDA_ANGLE start_angle = arc.GetStartAngle(); EDA_ANGLE angle = arc.GetCentralAngle(); gal->DrawArc( arc.GetCenter(), arc.GetRadius(), start_angle, angle); } if( aL->IsClosed() ) gal->DrawLine( aL->GetSegment( -1 ).B, aL->GetSegment( 0 ).A ); } void ROUTER_PREVIEW_ITEM::drawShape( const SHAPE* aShape, KIGFX::GAL* gal ) const { bool holeDrawn = false; switch( aShape->Type() ) { case SH_POLY_SET_TRIANGLE: { const SHAPE_LINE_CHAIN_BASE* l = (const SHAPE_LINE_CHAIN_BASE*) aShape; if( m_showClearance && m_clearance > 0 ) { gal->SetLineWidth( m_width + 2 * m_clearance ); drawLineChain( l, gal ); } gal->SetLayerDepth( m_depth ); gal->SetLineWidth( m_width ); gal->SetStrokeColor( m_color ); gal->SetFillColor( m_color ); drawLineChain( l, gal ); break; } case SH_LINE_CHAIN: { const SHAPE_LINE_CHAIN* l = (const SHAPE_LINE_CHAIN*) aShape; const int w = m_width; if( m_showClearance && m_clearance > 0 ) { gal->SetLineWidth( w + 2 * m_clearance ); drawLineChain( l, gal ); } gal->SetLayerDepth( m_depth ); gal->SetLineWidth( w ); gal->SetStrokeColor( m_color ); gal->SetFillColor( m_color ); drawLineChain( l, gal ); break; } case SH_SEGMENT: { const SHAPE_SEGMENT* s = (const SHAPE_SEGMENT*) aShape; const int w = s->GetWidth(); gal->SetIsStroke( false ); if( m_showClearance && m_clearance > 0 ) { gal->SetLineWidth( w + 2 * m_clearance ); gal->DrawSegment( s->GetSeg().A, s->GetSeg().B, s->GetWidth() + 2 * m_clearance ); } gal->SetLayerDepth( m_depth ); gal->SetLineWidth( w ); gal->SetFillColor( m_color ); gal->DrawSegment( s->GetSeg().A, s->GetSeg().B, s->GetWidth() ); break; } case SH_CIRCLE: { const SHAPE_CIRCLE* c = static_cast( aShape ); gal->SetStrokeColor( m_color ); if( m_showClearance && m_clearance > 0 ) { gal->SetIsStroke( false ); gal->DrawCircle( c->GetCenter(), c->GetRadius() + m_clearance ); } gal->SetLayerDepth( m_depth ); if( m_hole && dynamic_cast( m_hole ) ) { const SHAPE_CIRCLE* h = static_cast( m_hole ); int halfWidth = m_width / 2; gal->SetIsStroke( true ); gal->SetIsFill( false ); gal->SetLineWidth( halfWidth + c->GetRadius() - h->GetRadius() ); gal->DrawCircle( c->GetCenter(), ( halfWidth + c->GetRadius() + h->GetRadius() ) / 2 ); holeDrawn = true; } else { gal->SetIsStroke( m_width ? true : false ); gal->SetLineWidth( m_width ); gal->SetFillColor( m_color ); gal->DrawCircle( c->GetCenter(), c->GetRadius() ); } break; } case SH_RECT: { const SHAPE_RECT* r = (const SHAPE_RECT*) aShape; gal->SetFillColor( m_color ); if( m_showClearance && m_clearance > 0 ) { VECTOR2I p0( r->GetPosition() ), s( r->GetSize() ); gal->SetIsStroke( true ); gal->SetLineWidth( 2 * m_clearance ); gal->DrawLine( p0, VECTOR2I( p0.x + s.x, p0.y ) ); gal->DrawLine( p0, VECTOR2I( p0.x, p0.y + s.y ) ); gal->DrawLine( p0 + s , VECTOR2I( p0.x + s.x, p0.y ) ); gal->DrawLine( p0 + s, VECTOR2I( p0.x, p0.y + s.y ) ); } gal->SetLayerDepth( m_depth ); gal->SetIsStroke( m_width ? true : false ); gal->SetLineWidth( m_width); gal->SetStrokeColor( m_color ); gal->DrawRectangle( r->GetPosition(), r->GetPosition() + r->GetSize() ); break; } case SH_SIMPLE: { const SHAPE_SIMPLE* c = (const SHAPE_SIMPLE*) aShape; std::deque polygon = std::deque(); for( int i = 0; i < c->PointCount(); i++ ) { polygon.push_back( c->CDPoint( i ) ); } gal->SetFillColor( m_color ); if( m_showClearance && m_clearance > 0 ) { gal->SetIsStroke( true ); gal->SetLineWidth( 2 * m_clearance ); // need the implicit last segment to be explicit for DrawPolyline polygon.push_back( c->CDPoint( 0 ) ); gal->DrawPolyline( polygon ); } gal->SetLayerDepth( m_depth ); gal->SetIsStroke( m_width ? true : false ); gal->SetLineWidth( m_width ); gal->SetStrokeColor( m_color ); gal->DrawPolygon( polygon ); break; } case SH_ARC: { const SHAPE_ARC* arc = static_cast( aShape ); const int w = arc->GetWidth(); EDA_ANGLE start_angle = arc->GetStartAngle(); EDA_ANGLE angle = arc->GetCentralAngle(); gal->SetIsFill( false ); gal->SetIsStroke( true ); if( m_showClearance && m_clearance > 0 ) { gal->SetLineWidth( w + 2 * m_clearance ); gal->DrawArc( arc->GetCenter(), arc->GetRadius(), start_angle, angle ); } gal->SetLayerDepth( m_depth ); gal->SetStrokeColor( m_color ); gal->SetFillColor( m_color ); gal->SetLineWidth( w ); gal->DrawArc( arc->GetCenter(), arc->GetRadius(), start_angle, angle ); break; } case SH_COMPOUND: wxFAIL_MSG( wxT( "Router preview item: nested compound shapes not supported" ) ); break; case SH_POLY_SET: wxFAIL_MSG( wxT( "Router preview item: SHAPE_POLY_SET not supported" ) ); break; case SH_NULL: break; } if( m_hole && !holeDrawn ) { gal->SetLayerDepth( m_depth ); gal->SetIsStroke( true ); gal->SetIsFill( false ); gal->SetStrokeColor( m_color ); gal->SetLineWidth( 1 ); SHAPE_CIRCLE* circle = dynamic_cast( m_hole ); SHAPE_SEGMENT* slot = dynamic_cast( m_hole ); if( circle ) gal->DrawCircle( circle->GetCenter(), circle->GetRadius() ); else if( slot ) gal->DrawSegment( slot->GetSeg().A, slot->GetSeg().B, slot->GetWidth() ); } } void ROUTER_PREVIEW_ITEM::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const { GAL* gal = aView->GetGAL(); //col.Brighten(0.7); if( m_type == PR_SHAPE ) { if( !m_shape ) return; // N.B. The order of draw here is important // Cairo doesn't current support z-ordering, so we need // to draw the clearance first to ensure it is in the background gal->SetLayerDepth( m_originDepth ); //TODO(snh) Add configuration option for the color/alpha here gal->SetStrokeColor( COLOR4D( DARKDARKGRAY ).WithAlpha( 0.9 ) ); gal->SetFillColor( COLOR4D( DARKDARKGRAY ).WithAlpha( 0.7 ) ); gal->SetIsStroke( m_width ? true : false ); gal->SetIsFill( true ); // Semi-solids (ie: rule areas) which are not in collision are sketched (ie: outline only) if( ( m_flags & PNS_SEMI_SOLID ) > 0 && ( m_flags & PNS_COLLISION ) == 0 ) gal->SetIsFill( false ); if( m_shape->HasIndexableSubshapes() ) { std::vector subshapes; m_shape->GetIndexableSubshapes( subshapes ); for( const SHAPE* shape : subshapes ) drawShape( shape, gal ); } else { drawShape( m_shape, gal ); } } } const COLOR4D ROUTER_PREVIEW_ITEM::getLayerColor( int aLayer ) const { auto settings = static_cast( m_view->GetPainter()->GetSettings() ); COLOR4D color = settings->GetLayerColor( aLayer ); if( m_flags & PNS_HEAD_TRACE ) return color.Saturate( 1.0 ); else if( m_flags & PNS_HOVER_ITEM ) return color.Brightened( 0.7 ); return color; }