From bfbda978b849830c048d75e02def6e7cad77d04a Mon Sep 17 00:00:00 2001 From: Tomasz Wlostowski Date: Tue, 30 Aug 2022 13:52:34 +0100 Subject: [PATCH] router: hole as first class objects, wip Rebased by Jeff Young 5 April 2023 (Also includes a bug-fix for highlighting collisions with edge-cut items.) --- libs/kimath/include/geometry/shape_index.h | 18 - pcbnew/router/pns_hole.cpp | 148 ++++++++ pcbnew/router/pns_hole.h | 79 +++++ pcbnew/router/pns_item.cpp | 387 +++++++++++++++++---- pcbnew/router/pns_item.h | 54 +-- pcbnew/router/pns_kicad_iface.cpp | 67 +++- pcbnew/router/pns_kicad_iface.h | 1 + pcbnew/router/pns_line.cpp | 8 + pcbnew/router/pns_line.h | 1 + pcbnew/router/pns_node.cpp | 195 ++++++----- pcbnew/router/pns_node.h | 101 ++++-- pcbnew/router/pns_router.cpp | 17 +- pcbnew/router/pns_shove.cpp | 11 +- pcbnew/router/pns_solid.cpp | 104 +----- pcbnew/router/pns_solid.h | 36 +- pcbnew/router/pns_utils.cpp | 58 +++ pcbnew/router/pns_utils.h | 2 + pcbnew/router/pns_via.cpp | 11 +- pcbnew/router/pns_via.h | 50 ++- pcbnew/router/pns_walkaround.cpp | 26 +- pcbnew/router/router_preview_item.cpp | 24 +- qa/pcbnew_utils/board_file_utils.cpp | 2 + qa/unittests/pcbnew/CMakeLists.txt | 1 + qa/unittests/pcbnew/test_pns_basics.cpp | 348 ++++++++++++++++++ 24 files changed, 1335 insertions(+), 414 deletions(-) create mode 100644 pcbnew/router/pns_hole.cpp create mode 100644 pcbnew/router/pns_hole.h create mode 100644 qa/unittests/pcbnew/test_pns_basics.cpp diff --git a/libs/kimath/include/geometry/shape_index.h b/libs/kimath/include/geometry/shape_index.h index 9566fc8803..d76d7d7922 100644 --- a/libs/kimath/include/geometry/shape_index.h +++ b/libs/kimath/include/geometry/shape_index.h @@ -49,21 +49,6 @@ static const SHAPE* shapeFunctor( T aItem ) return aItem->Shape(); } -/** - * Used by #SHAPE_INDEX to get a SHAPE* for a hole from another type. - * - * By default relies on T::GetHole() method, should be specialized if the T object - * doesn't allow that method. - * - * @param aItem generic T object. - * @return a SHAPE* object equivalent to object. - */ -template -static const SHAPE* holeFunctor( T aItem ) -{ - return aItem->Hole(); -} - /** * Used by #SHAPE_INDEX to get the bounding box of a generic T object. * @@ -78,9 +63,6 @@ BOX2I boundingBox( T aObject ) { BOX2I bbox = shapeFunctor( aObject )->BBox(); - if( holeFunctor( aObject ) ) - bbox.Merge( holeFunctor( aObject )->BBox() ); - return bbox; } diff --git a/pcbnew/router/pns_hole.cpp b/pcbnew/router/pns_hole.cpp new file mode 100644 index 0000000000..8c8fd46fa0 --- /dev/null +++ b/pcbnew/router/pns_hole.cpp @@ -0,0 +1,148 @@ +/* + * KiRouter - a push-and-(sometimes-)shove PCB router + * + * Copyright (C) 2022 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 "pns_item.h" +#include "pns_hole.h" +#include "pns_node.h" +#include "pns_utils.h" + +#include +#include +#include +#include + +namespace PNS +{ + +HOLE::~HOLE() +{ + delete m_holeShape; +} + +HOLE* HOLE::Clone() const +{ + HOLE* h = new HOLE( nullptr, m_holeShape->Clone() ); + + h->SetNet( Net() ); + h->SetLayers( Layers() ); + h->SetOwner( nullptr ); + + h->m_rank = m_rank; + h->m_marker = m_marker; + h->m_parent = m_parent; + h->m_isVirtual = m_isVirtual; + + return h; +} + +const SHAPE_LINE_CHAIN HOLE::Hull( int aClearance, int aWalkaroundThickness, int aLayer ) const +{ + if( !m_holeShape ) + return SHAPE_LINE_CHAIN(); + + if( m_holeShape->Type() == SH_CIRCLE ) + { + auto cir = static_cast( m_holeShape ); + int cl = ( aClearance + aWalkaroundThickness / 2 ); + int width = cir->GetRadius() * 2; + + // Chamfer = width * ( 1 - sqrt(2)/2 ) for equilateral octagon + return OctagonalHull( cir->GetCenter() - VECTOR2I( width / 2, width / 2 ), + VECTOR2I( width, width ), cl, + ( 2 * cl + width ) * ( 1.0 - M_SQRT1_2 ) ); + } + else if( m_holeShape->Type() == SH_COMPOUND ) + { + SHAPE_COMPOUND* cmpnd = static_cast( m_holeShape ); + + if( cmpnd->Shapes().size() == 1 ) + { + return BuildHullForPrimitiveShape( cmpnd->Shapes()[0], aClearance, + aWalkaroundThickness ); + } + else + { + SHAPE_POLY_SET hullSet; + + for( SHAPE* shape : cmpnd->Shapes() ) + { + hullSet.AddOutline( + BuildHullForPrimitiveShape( shape, aClearance, aWalkaroundThickness ) ); + } + + hullSet.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + return hullSet.Outline( 0 ); + } + } + else + { + return BuildHullForPrimitiveShape( m_holeShape, aClearance, aWalkaroundThickness ); + } +} + +bool HOLE::IsCircular() const +{ + return m_holeShape->Type() == SH_CIRCLE; +} + +int HOLE::Radius() const +{ + assert( m_holeShape->Type() == SH_CIRCLE ); + + return static_cast( m_holeShape )->GetRadius(); +} + +const VECTOR2I HOLE::Pos() const +{ + return VECTOR2I( 0, 0 ); // fixme holes +} + +void HOLE::SetCenter( const VECTOR2I& aCenter ) +{ + assert( m_holeShape->Type() == SH_CIRCLE ); + static_cast( m_holeShape )->SetCenter( aCenter ); +} + +void HOLE::SetRadius( int aRadius ) +{ + assert( m_holeShape->Type() == SH_CIRCLE ); + static_cast( m_holeShape )->SetRadius( aRadius ); +} + +void HOLE::Move( const VECTOR2I& delta ) +{ + m_holeShape->Move( delta ); +} + +HOLE* HOLE::MakeCircularHole( const VECTOR2I& pos, int radius ) +{ + auto circle = new SHAPE_CIRCLE( pos, radius ); + auto hole = new HOLE( nullptr, circle ); + hole->SetLayers( LAYER_RANGE( F_Cu, B_Cu ) ); + return hole; +} + +/*bool HOLE::collideSimple( const ITEM* aHead, const NODE* aNode, + COLLISION_SEARCH_CONTEXT* aCtx ) const +{ +}*/ + +}; // namespace PNS \ No newline at end of file diff --git a/pcbnew/router/pns_hole.h b/pcbnew/router/pns_hole.h new file mode 100644 index 0000000000..670add3100 --- /dev/null +++ b/pcbnew/router/pns_hole.h @@ -0,0 +1,79 @@ +/* + * KiRouter - a push-and-(sometimes-)shove PCB router + * + * Copyright (C) 2016-2021 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 . + */ + +#ifndef __PNS_HOLE_H +#define __PNS_HOLE_H + +#include "pns_item.h" + +#include +#include + +namespace PNS +{ + +class HOLE : public ITEM +{ +public: + HOLE( ITEM* aParentPadVia, SHAPE* aShape ) : + ITEM( ITEM::HOLE_T ), + m_holeShape( aShape ), + m_parentPadVia( aParentPadVia ) + { + } + + HOLE( const ITEM& aOther ) : ITEM( aOther ) {} + + virtual ~HOLE(); + + /** + * Return a deep copy of the item. + */ + virtual HOLE* Clone() const override; + + virtual const SHAPE_LINE_CHAIN Hull( int aClearance = 0, int aWalkaroundThickness = 0, + int aLayer = -1 ) const override; + + bool IsCircular() const; + + const VECTOR2I Pos() const; + int Radius() const; + + const SHAPE* Shape() const override { return m_holeShape; } + + ITEM* ParentPadVia() const { return m_parentPadVia; } + + void SetCenter( const VECTOR2I& aCenter ); + void SetRadius( int aRadius ); + + void Move( const VECTOR2I& delta ); + + static HOLE* MakeCircularHole( const VECTOR2I& pos, int radius ); + + +private: + SHAPE* m_holeShape; + ITEM* m_parentPadVia; +}; + +}; // namespace PNS + +#endif \ No newline at end of file diff --git a/pcbnew/router/pns_item.cpp b/pcbnew/router/pns_item.cpp index 4835fdd027..1b9cd7d93a 100644 --- a/pcbnew/router/pns_item.cpp +++ b/pcbnew/router/pns_item.cpp @@ -24,41 +24,72 @@ #include "pns_item.h" #include "pns_line.h" #include "pns_router.h" +#include "pns_utils.h" + +#include +#include typedef VECTOR2I::extended_type ecoord; namespace PNS { -bool ITEM::collideSimple( const ITEM* aOther, const NODE* aNode, - const COLLISION_SEARCH_OPTIONS& aOpts, OBSTACLE *aObsInfo ) const +static void dumpObstacles( const PNS::NODE::OBSTACLES &obstacles ) { - const ROUTER_IFACE* iface = ROUTER::GetInstance()->GetInterface(); - const SHAPE* shapeA = Shape(); - const SHAPE* holeA = Hole(); - int lineWidthA = 0; - const SHAPE* shapeB = aOther->Shape(); - const SHAPE* holeB = aOther->Hole(); - int lineWidthB = 0; - const int clearanceEpsilon = aNode->GetRuleResolver()->ClearanceEpsilon(); + printf("&&&& %d obstacles: \n", obstacles.size() ); + + for( const auto& obs : obstacles ) + { + printf("%p [%s] - %p [%s], clearance %d\n", obs.m_head, obs.m_head->KindStr().c_str(), + obs.m_item, obs.m_item->KindStr().c_str(), + obs.m_clearance ); + } +} + + +bool ITEM::collideSimple( const ITEM* aHead, const NODE* aNode, + COLLISION_SEARCH_CONTEXT* aCtx ) const +{ + const SHAPE* shapeI = Shape(); + const HOLE* holeI = Hole(); + int lineWidthI = 0; + + const SHAPE* shapeH = aHead->Shape(); + const HOLE* holeH = aHead->Hole(); + int lineWidthH = 0; + int clearanceEpsilon = aNode->GetRuleResolver()->ClearanceEpsilon(); + bool collisionsFound = false; + + printf("******************** CollideSimple %d\n", aCtx->obstacles.size() ); + + //printf( "h %p n %p t %p ctx %p\n", aHead, aNode, this, aCtx ); + + if( aHead == this ) // we cannot be self-colliding + return false; // Sadly collision routines ignore SHAPE_POLY_LINE widths so we have to pass them in as part // of the clearance value. if( m_kind == LINE_T ) - lineWidthA = static_cast( this )->Width() / 2; + lineWidthI = static_cast( this )->Width() / 2; - if( aOther->m_kind == LINE_T ) - lineWidthB = static_cast( aOther )->Width() / 2; + if( aHead->m_kind == LINE_T ) + lineWidthH = static_cast( aHead )->Width() / 2; // same nets? no collision! - if( aOpts.m_differentNetsOnly && m_net == aOther->m_net && m_net >= 0 && aOther->m_net >= 0 ) + if( aCtx && aCtx->options.m_differentNetsOnly + && m_net == aHead->m_net && m_net >= 0 && aHead->m_net >= 0 ) + { return false; + } // a pad associated with a "free" pin (NIC) doesn't have a net until it has been used - if( aOpts.m_differentNetsOnly && ( IsFreePad() || aOther->IsFreePad() ) ) + if( aCtx && aCtx->options.m_differentNetsOnly + && ( IsFreePad() || aHead->IsFreePad() ) ) + { return false; + } // check if we are not on completely different layers first - if( !m_layers.Overlaps( aOther->m_layers ) ) + if( !m_layers.Overlaps( aHead->m_layers ) ) return false; auto checkKeepout = @@ -84,74 +115,143 @@ bool ITEM::collideSimple( const ITEM* aOther, const NODE* aNode, }; const ZONE* zoneA = dynamic_cast( Parent() ); - const ZONE* zoneB = dynamic_cast( aOther->Parent() ); + const ZONE* zoneB = dynamic_cast( aHead->Parent() ); - if( zoneA && aOther->Parent() && !checkKeepout( zoneA, aOther->Parent() ) ) + if( zoneA && aHead->Parent() && !checkKeepout( zoneA, aHead->Parent() ) ) return false; if( zoneB && Parent() && !checkKeepout( zoneB, Parent() ) ) return false; - bool thisNotFlashed = !iface->IsFlashedOnLayer( this, aOther->Layer() ); - bool otherNotFlashed = !iface->IsFlashedOnLayer( aOther, Layer() ); + // fixme: this f***ing singleton must go... + ROUTER *router = ROUTER::GetInstance(); + ROUTER_IFACE* iface = router ? router->GetInterface() : nullptr; - if( aObsInfo ) + bool thisNotFlashed = false; + bool otherNotFlashed = false; + + if( iface ) { - aObsInfo->m_headIsHole = false; - aObsInfo->m_itemIsHole = false; + thisNotFlashed = !iface->IsFlashedOnLayer( this, aHead->Layer() ); + otherNotFlashed = !iface->IsFlashedOnLayer( aHead, Layer() ); } if( ( aNode->GetCollisionQueryScope() == NODE::CQS_ALL_RULES - || ( thisNotFlashed || otherNotFlashed ) ) - && ( holeA || holeB ) ) + || ( thisNotFlashed || otherNotFlashed ) ) ) { - int holeClearance = aNode->GetHoleClearance( this, aOther ); - - if( holeA && holeA->Collide( shapeB, holeClearance + lineWidthB - clearanceEpsilon ) ) + if( holeI && holeI->ParentPadVia() != aHead && holeI != aHead ) { - if( aObsInfo ) - { - aObsInfo->m_headIsHole = true; - aObsInfo->m_clearance = holeClearance; - } - return true; - } + int holeClearance = aNode->GetClearance( this, holeI ); + printf("HCH1 %d\n", holeClearance); - if( holeB && holeB->Collide( shapeA, holeClearance + lineWidthA - clearanceEpsilon ) ) - { - if( aObsInfo ) + if( holeI->Shape()->Collide( shapeH, holeClearance + lineWidthH - clearanceEpsilon ) ) { - aObsInfo->m_itemIsHole = true; - aObsInfo->m_clearance = holeClearance; - } - return true; - } - - if( holeA && holeB ) - { - int holeToHoleClearance = aNode->GetHoleToHoleClearance( this, aOther ); - - if( holeA->Collide( holeB, holeToHoleClearance - clearanceEpsilon ) ) - { - if( aObsInfo ) + if( aCtx ) { - aObsInfo->m_headIsHole = true; - aObsInfo->m_itemIsHole = true; - aObsInfo->m_clearance = holeToHoleClearance; + OBSTACLE obs; + obs.m_clearance = holeClearance; + obs.m_head = const_cast( aHead ); + obs.m_item = const_cast( holeI ); + + aCtx->obstacles.insert( obs ); + dumpObstacles( aCtx->obstacles ); + collisionsFound = true; + } + else + { + return true; + } + } + } + + if( holeH && holeH->ParentPadVia() != this && holeH != this ) + { + int holeClearance = aNode->GetClearance( this, holeH ); + + printf("HCH2 %d\n", holeClearance); + + if( holeH->Shape()->Collide( shapeI, holeClearance + lineWidthI - clearanceEpsilon ) ) + { + if( aCtx ) + { + OBSTACLE obs; + obs.m_clearance = holeClearance; + obs.m_head = const_cast( holeH ); + obs.m_item = const_cast( this ); + + aCtx->obstacles.insert( obs ); + dumpObstacles( aCtx->obstacles ); + collisionsFound = true; + } + else + { + return true; + } + } + } + + if( holeI && holeH && ( holeI != holeH ) ) + { + int holeClearance = aNode->GetClearance( holeI, holeH ); + + printf("HCH3 %d\n", holeClearance); + + if( holeI->Shape()->Collide( holeH->Shape(), holeClearance - clearanceEpsilon ) ) + { + if( aCtx ) + { + OBSTACLE obs; + obs.m_clearance = holeClearance; + + // printf("pushh3 %p %p\n", obs.m_head, obs.m_item ); + + obs.m_head = const_cast( holeH ); + obs.m_item = const_cast( holeI ); + + aCtx->obstacles.insert( obs ); + dumpObstacles( aCtx->obstacles ); + + collisionsFound = true; + } + else + { + return true; } - return true; } } } - if( !aOther->Layers().IsMultilayer() && thisNotFlashed ) + printf("HCHE\n"); + + if( !aHead->Layers().IsMultilayer() && thisNotFlashed ) return false; if( !Layers().IsMultilayer() && otherNotFlashed ) return false; - int clearance = aOpts.m_overrideClearance >= 0 ? aOpts.m_overrideClearance - : aNode->GetClearance( this, aOther ); + int clearance; + + if( aCtx && aCtx->options.m_overrideClearance >= 0 ) + { + clearance = aCtx->options.m_overrideClearance; + } + else + { + clearance = aNode->GetClearance( this, aHead ); + } + + // prevent bogus collisions between the item and its own hole. FIXME: figure out a cleaner way of doing that + if( holeI && aHead == holeI->ParentPadVia() ) + return false; + + if( holeH && this == holeH->ParentPadVia() ) + return false; + + if( holeH && this == holeH ) + return false; + + if( holeI && aHead == holeI ) + return false; if( clearance >= 0 ) { @@ -164,41 +264,69 @@ bool ITEM::collideSimple( const ITEM* aOther, const NODE* aNode, int actual; VECTOR2I pos; - if( shapeA->Collide( shapeB, clearance + lineWidthA, &actual, &pos ) ) + if( shapeH->Collide( shapeI, clearance + lineWidthH + lineWidthI - clearanceEpsilon, + &actual, &pos ) ) { if( checkCastellation && aNode->QueryEdgeExclusions( pos ) ) return false; - if( checkNetTie && aNode->GetRuleResolver()->IsNetTieExclusion( aOther, pos, this ) ) + if( checkNetTie && aNode->GetRuleResolver()->IsNetTieExclusion( aHead, pos, this ) ) return false; - if( aObsInfo ) - aObsInfo->m_clearance = clearance; - - return true; + if( aCtx ) + { + collisionsFound = true; + OBSTACLE obs; + obs.m_head = const_cast( aHead ); + obs.m_item = const_cast( this ); + obs.m_clearance = clearance; + aCtx->obstacles.insert( obs ); + } + else + { + return true; + } } } else { // Fast method - if( shapeA->Collide( shapeB, clearance + lineWidthA + lineWidthB - clearanceEpsilon ) ) + if( shapeH->Collide( shapeI, clearance + lineWidthH + lineWidthI - clearanceEpsilon ) ) { - if( aObsInfo ) - aObsInfo->m_clearance = clearance; + if( aCtx ) + { + collisionsFound = true; + OBSTACLE obs; + obs.m_head = const_cast( aHead ); + obs.m_item = const_cast( this ); + obs.m_clearance = clearance; - return true; + //printf("i %p h %p ih %p hh %p\n", this ,aHead, holeI, holeH); + printf("HCHX %d %d\n", clearance, clearance + lineWidthH + lineWidthI - clearanceEpsilon); + //printf("pushc %p %p cl %d cle %d\n", obs.m_head, obs.m_item, clearance, clearance + lineWidthH + lineWidthI - clearanceEpsilon ); + //printf("SH %s\n", shapeH->Format().c_str(), aHead ); + //printf("SI %s\n", shapeI->Format().c_str(), this ); + + aCtx->obstacles.insert( obs ); + + dumpObstacles( aCtx->obstacles ); + printf("--EndDump\n"); + } + else + { + return true; + } } } } - return false; + return collisionsFound; } -bool ITEM::Collide( const ITEM* aOther, const NODE* aNode, - const COLLISION_SEARCH_OPTIONS& aOpts, OBSTACLE *aObsInfo ) const +bool ITEM::Collide( const ITEM* aOther, const NODE* aNode, COLLISION_SEARCH_CONTEXT *aCtx ) const { - if( collideSimple( aOther, aNode, aOpts, aObsInfo ) ) + if( collideSimple( aOther, aNode, aCtx ) ) return true; // Special cases for "head" lines with vias attached at the end. Note that this does not @@ -209,15 +337,15 @@ bool ITEM::Collide( const ITEM* aOther, const NODE* aNode, { const LINE* line = static_cast( this ); - if( line->EndsWithVia() && line->Via().collideSimple( aOther, aNode, aOpts, aObsInfo ) ) + if( line->EndsWithVia() && line->Via().collideSimple( aOther, aNode, aCtx ) ) return true; } if( aOther->m_kind == LINE_T ) { - const LINE* line = static_cast( aOther ); + const LINE* line = static_cast( aOther ); // fixme - if( line->EndsWithVia() && line->Via().collideSimple( this, aNode, aOpts, aObsInfo ) ) + if( line->EndsWithVia() && line->Via().collideSimple( this, aNode, aCtx ) ) return true; } @@ -236,6 +364,8 @@ std::string ITEM::KindStr() const case JOINT_T: return "joint"; case SOLID_T: return "solid"; case DIFF_PAIR_T: return "diff-pair"; + case HOLE_T: return "hole"; + default: return "unknown"; } } @@ -245,6 +375,113 @@ ITEM::~ITEM() { } +HOLE::~HOLE() +{ + delete m_holeShape; +} + +HOLE* HOLE::Clone() const +{ + HOLE* h = new HOLE( m_parentPadVia, m_holeShape->Clone() ); + + h->SetNet( Net() ); + h->SetLayers( Layers() ); + + h->m_rank = m_rank; + h->m_marker = m_marker; + h->m_parent = m_parent; + h->m_isVirtual = m_isVirtual; + + return h; +} + +const SHAPE_LINE_CHAIN HOLE::Hull( int aClearance, int aWalkaroundThickness, int aLayer ) const +{ + if( !m_holeShape ) + return SHAPE_LINE_CHAIN(); + + if( m_holeShape->Type() == SH_CIRCLE ) + { + auto cir = static_cast( m_holeShape ); + int cl = ( aClearance + aWalkaroundThickness / 2 ); + int width = cir->GetRadius() * 2; + + // Chamfer = width * ( 1 - sqrt(2)/2 ) for equilateral octagon + return OctagonalHull( cir->GetCenter() - VECTOR2I( width / 2, width / 2 ), VECTOR2I( width, width ), cl, + ( 2 * cl + width ) * ( 1.0 - M_SQRT1_2 ) ); + } + else if( m_holeShape->Type() == SH_COMPOUND ) + { + SHAPE_COMPOUND* cmpnd = static_cast( m_holeShape ); + + if ( cmpnd->Shapes().size() == 1 ) + { + return BuildHullForPrimitiveShape( cmpnd->Shapes()[0], aClearance, + aWalkaroundThickness ); + } + else + { + SHAPE_POLY_SET hullSet; + + for( SHAPE* shape : cmpnd->Shapes() ) + { + hullSet.AddOutline( BuildHullForPrimitiveShape( shape, aClearance, + aWalkaroundThickness ) ); + } + + hullSet.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + return hullSet.Outline( 0 ); + } + } + else + { + return BuildHullForPrimitiveShape( m_holeShape, aClearance, aWalkaroundThickness ); + } +} + +bool HOLE::IsCircular() const +{ + return m_holeShape->Type() == SH_CIRCLE; +} + +int HOLE::Radius() const +{ + assert( m_holeShape->Type() == SH_CIRCLE ); + + return static_cast( m_holeShape )->GetRadius(); +} + +const VECTOR2I HOLE::Pos() const +{ + return VECTOR2I( 0, 0 ); // fixme holes +} + +void HOLE::SetCenter( const VECTOR2I& aCenter ) +{ + assert( m_holeShape->Type() == SH_CIRCLE ); + static_cast( m_holeShape )->SetCenter( aCenter ); +} +void HOLE::SetRadius( int aRadius ) +{ + assert( m_holeShape->Type() == SH_CIRCLE ); + static_cast( m_holeShape )->SetRadius( aRadius ); +} + + +void HOLE::Move( const VECTOR2I& delta ) +{ + m_holeShape->Move( delta ); +} + +HOLE* HOLE::MakeCircularHole( const VECTOR2I& pos, int radius ) +{ + auto circle = new SHAPE_CIRCLE( pos, radius ); + auto hole = new HOLE( nullptr, circle ); + hole->SetLayers( LAYER_RANGE( F_Cu, B_Cu ) ); + return hole; +} + + const std::string ITEM::Format() const { std::stringstream ss; diff --git a/pcbnew/router/pns_item.h b/pcbnew/router/pns_item.h index 7461d99d32..eea5ff9aec 100644 --- a/pcbnew/router/pns_item.h +++ b/pcbnew/router/pns_item.h @@ -24,6 +24,7 @@ #define __PNS_ITEM_H #include +#include #include #include @@ -44,28 +45,13 @@ enum LineMarker { MK_DP_COUPLED = ( 1 << 5 ) }; -struct OBSTACLE; +class ITEM; +class HOLE; +struct COLLISION_SEARCH_CONTEXT; -struct COLLISION_SEARCH_OPTIONS -{ - bool m_differentNetsOnly = true; - int m_overrideClearance = -1; - int m_limitCount = -1; - int m_kindMask = -1; - bool m_useClearanceEpsilon = true; -}; - -/** - * Dummy interface for ITEMs that can own other ITEMs - */ class ITEM_OWNER {}; -/** - * Base class for an item belonging to some container. - * - * Container can be another ITEM, ITEM_SET or a NODE. - */ class OWNABLE_ITEM { public: @@ -95,7 +81,6 @@ protected: const ITEM_OWNER *m_owner; }; - /** * Base class for PNS router board items. * @@ -117,7 +102,8 @@ public: ARC_T = 16, VIA_T = 32, DIFF_PAIR_T = 64, - ANY_T = 0xff + HOLE_T = 128, + ANY_T = 0xffff }; ITEM( PnsKind aKind ) @@ -170,12 +156,6 @@ public: return SHAPE_LINE_CHAIN(); } - virtual const SHAPE_LINE_CHAIN HoleHull( int aClearance, int aWalkaroundThickness = 0, - int aLayer = -1 ) const - { - return SHAPE_LINE_CHAIN(); - } - /** * Return the type (kind) of the item. */ @@ -216,7 +196,7 @@ public: { return Layers().Overlaps( aOther->Layers() ); } - + /** * Check for a collision (clearance violation) with between us and item \a aOther. * @@ -226,9 +206,8 @@ public: * @param aOther is the item to check collision against. * @return true, if a collision was found. */ - bool Collide( const ITEM* aOther, const NODE* aNode, - const COLLISION_SEARCH_OPTIONS& aOpts = COLLISION_SEARCH_OPTIONS(), - OBSTACLE *aObsInfo = nullptr ) const; + bool Collide( const ITEM* aHead, const NODE* aNode, + COLLISION_SEARCH_CONTEXT* aCtx = nullptr ) const; /** * Return the geometrical shape of the item. Used for collision detection and spatial indexing. @@ -238,11 +217,6 @@ public: return nullptr; } - virtual const SHAPE* Hole() const - { - return nullptr; - } - virtual void Mark( int aMarker ) const { m_marker = aMarker; } virtual void Unmark( int aMarker = -1 ) const { m_marker &= ~aMarker; } virtual int Marker() const { return m_marker; } @@ -279,18 +253,20 @@ public: void SetIsCompoundShapePrimitive() { m_isCompoundShapePrimitive = true; } bool IsCompoundShapePrimitive() const { return m_isCompoundShapePrimitive; } + virtual bool HasHole() const { return false; } + virtual HOLE *Hole() const { return nullptr; } + virtual void SetHole( HOLE* aHole ) {}; + virtual const std::string Format() const; private: - bool collideSimple( const ITEM* aOther, const NODE* aNode, - const COLLISION_SEARCH_OPTIONS& aOpts, - OBSTACLE *aObsInfo = nullptr ) const; + bool collideSimple( const ITEM* aHead, const NODE* aNode, + COLLISION_SEARCH_CONTEXT* aCtx ) const; protected: PnsKind m_kind; BOARD_ITEM* m_parent; - NODE* m_owner; LAYER_RANGE m_layers; bool m_movable; diff --git a/pcbnew/router/pns_kicad_iface.cpp b/pcbnew/router/pns_kicad_iface.cpp index a60c02adba..17e0806536 100644 --- a/pcbnew/router/pns_kicad_iface.cpp +++ b/pcbnew/router/pns_kicad_iface.cpp @@ -106,10 +106,6 @@ public: virtual int Clearance( const PNS::ITEM* aA, const PNS::ITEM* aB, bool aUseClearanceEpsilon = true ) override; - virtual int HoleClearance( const PNS::ITEM* aA, const PNS::ITEM* aB, - bool aUseClearanceEpsilon = true ) override; - virtual int HoleToHoleClearance( const PNS::ITEM* aA, const PNS::ITEM* aB, - bool aUseClearanceEpsilon = true ) override; virtual int DpCoupledNet( int aNet ) override; virtual int DpNetPolarity( int aNet ) override; @@ -153,8 +149,6 @@ private: int m_clearanceEpsilon; std::unordered_map m_clearanceCache; - std::unordered_map m_holeClearanceCache; - std::unordered_map m_holeToHoleClearanceCache; }; @@ -252,16 +246,31 @@ bool PNS_PCBNEW_RULE_RESOLVER::IsNetTieExclusion( const PNS::ITEM* aItem, } -bool isCopper( const PNS::ITEM* aItem ) +static bool isCopper( const PNS::ITEM* aItem ) { + if ( !aItem ) + return false; + const BOARD_ITEM *parent = aItem->Parent(); return !parent || parent->IsOnCopperLayer(); } -bool isEdge( const PNS::ITEM* aItem ) +static bool isHole( const PNS::ITEM* aItem ) { + if ( !aItem ) + return false; + + return aItem->OfKind( PNS::ITEM::HOLE_T ); +} + + +static bool isEdge( const PNS::ITEM* aItem ) +{ + if ( !aItem ) + return false; + const BOARD_ITEM *parent = aItem->Parent(); return parent && ( parent->IsOnLayer( Edge_Cuts ) || parent->IsOnLayer( Margin ) ); @@ -382,8 +391,6 @@ void PNS_PCBNEW_RULE_RESOLVER::ClearCacheForItem( const PNS::ITEM* aItem ) void PNS_PCBNEW_RULE_RESOLVER::ClearCaches() { m_clearanceCache.clear(); - m_holeClearanceCache.clear(); - m_holeToHoleClearanceCache.clear(); } @@ -414,7 +421,23 @@ int PNS_PCBNEW_RULE_RESOLVER::Clearance( const PNS::ITEM* aA, const PNS::ITEM* a for( int layer = layers.Start(); layer <= layers.End(); ++layer ) { - if( isCopper( aA ) && ( !aB || isCopper( aB ) ) ) + if( isHole( aA ) && isHole( aB) ) + { + if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_HOLE_TO_HOLE, aA, aB, layer, &constraint ) ) + { + if( constraint.m_Value.Min() > rv ) + rv = constraint.m_Value.Min(); + } + } + else if( isHole( aA ) || isHole( aB ) ) + { + if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_HOLE_CLEARANCE, aA, aB, layer, &constraint ) ) + { + if( constraint.m_Value.Min() > rv ) + rv = constraint.m_Value.Min(); + } + } + else if( isCopper( aA ) && ( !aB || isCopper( aB ) ) ) { if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_CLEARANCE, aA, aB, layer, &constraint ) ) { @@ -422,8 +445,7 @@ int PNS_PCBNEW_RULE_RESOLVER::Clearance( const PNS::ITEM* aA, const PNS::ITEM* a rv = constraint.m_Value.Min(); } } - - if( isEdge( aA ) || ( aB && isEdge( aB ) ) ) + else if( isEdge( aA ) || ( aB && isEdge( aB ) ) ) { if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_EDGE_CLEARANCE, aA, aB, layer, &constraint ) ) { @@ -440,6 +462,7 @@ int PNS_PCBNEW_RULE_RESOLVER::Clearance( const PNS::ITEM* aA, const PNS::ITEM* a return rv; } +#if 0 int PNS_PCBNEW_RULE_RESOLVER::HoleClearance( const PNS::ITEM* aA, const PNS::ITEM* aB, bool aUseClearanceEpsilon ) @@ -464,6 +487,8 @@ int PNS_PCBNEW_RULE_RESOLVER::HoleClearance( const PNS::ITEM* aA, const PNS::ITE #define HAS_PLATED_HOLE( a ) ( a )->IsRoutable() + // JEY TODO: this is a new test introduced after Tom's brach. How does it fit + // in the new architecture? if( IsCopperLayer( layer ) && ( HAS_PLATED_HOLE( aA ) || HAS_PLATED_HOLE( aB ) ) && QueryConstraint( PNS::CONSTRAINT_TYPE::CT_CLEARANCE, aA, aB, layer, &constraint ) @@ -508,6 +533,8 @@ int PNS_PCBNEW_RULE_RESOLVER::HoleToHoleClearance( const PNS::ITEM* aA, const PN return rv; } +#endif + bool PNS_KICAD_IFACE_BASE::inheritTrackWidth( PNS::ITEM* aItem, int* aInheritedWidth ) { @@ -1140,7 +1167,12 @@ std::unique_ptr PNS_KICAD_IFACE_BASE::syncPad( PAD* aPad ) solid->SetOffset( VECTOR2I( offset.x, offset.y ) ); if( aPad->GetDrillSize().x > 0 ) - solid->SetHole( aPad->GetEffectiveHoleShape()->Clone() ); + { + SHAPE_SEGMENT* slot = (SHAPE_SEGMENT*) aPad->GetEffectiveHoleShape()->Clone(); + PNS::HOLE* hole = new PNS::HOLE( solid.get(), slot ); + + solid->SetHole( hole ); + } // We generate a single SOLID for a pad, so we have to treat it as ALWAYS_FLASHED and then // perform layer-specific flashing tests internally. @@ -1214,9 +1246,10 @@ std::unique_ptr PNS_KICAD_IFACE_BASE::syncVia( PCB_VIA* aVia ) via->SetIsFree( aVia->GetIsFree() ); - BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); - via->SetHole( SHAPE_CIRCLE( aVia->GetPosition(), - aVia->GetDrillValue() / 2 ) ); + SHAPE* holeShape = new SHAPE_CIRCLE( aVia->GetPosition(), aVia->GetDrillValue() / 2 ); + PNS::HOLE* viaHole = new PNS::HOLE( via.get(), holeShape ); + + via->SetHole( viaHole ); return via; } diff --git a/pcbnew/router/pns_kicad_iface.h b/pcbnew/router/pns_kicad_iface.h index 0bb489f32d..3b903855d3 100644 --- a/pcbnew/router/pns_kicad_iface.h +++ b/pcbnew/router/pns_kicad_iface.h @@ -36,6 +36,7 @@ class PCB_DISPLAY_OPTIONS; class PCB_TOOL_BASE; class FOOTPRINT; class PAD; +class EDA_TEXT; namespace PNS { diff --git a/pcbnew/router/pns_line.cpp b/pcbnew/router/pns_line.cpp index 9d5dda34c2..55849f5fd1 100644 --- a/pcbnew/router/pns_line.cpp +++ b/pcbnew/router/pns_line.cpp @@ -28,6 +28,8 @@ #include "pns_node.h" #include "pns_via.h" #include "pns_utils.h" +#include "pns_router.h" +#include "pns_debug_decorator.h" #include @@ -545,6 +547,12 @@ bool LINE::Walkaround( const SHAPE_LINE_CHAIN& aObstacle, SHAPE_LINE_CHAIN& aPat const SHAPE_LINE_CHAIN SEGMENT::Hull( int aClearance, int aWalkaroundThickness, int aLayer ) const { + /*DEBUG_DECORATOR* debugDecorator = ROUTER::GetInstance()->GetInterface()->GetDebugDecorator(); + + PNS_DBG( debugDecorator, Message, wxString::Format( wxT( "seghull %d %d" ), aWalkaroundThickness, aClearance ) ); + PNS_DBG(debugDecorator, AddShape, &m_seg, RED, 0, wxT("theseg") ); + */ + return SegmentHull( m_seg, aClearance, aWalkaroundThickness ); } diff --git a/pcbnew/router/pns_line.h b/pcbnew/router/pns_line.h index 2e4f062b26..3fdd10ddad 100644 --- a/pcbnew/router/pns_line.h +++ b/pcbnew/router/pns_line.h @@ -196,6 +196,7 @@ public: void AppendVia( const VIA& aVia ); void RemoveVia() { m_hasVia = false; } + VIA& Via() { return m_via; } const VIA& Via() const { return m_via; } void SetViaDiameter( int aDiameter ) { m_via.SetDiameter( aDiameter ); } diff --git a/pcbnew/router/pns_node.cpp b/pcbnew/router/pns_node.cpp index 7784d83cc7..e25f71f6bb 100644 --- a/pcbnew/router/pns_node.cpp +++ b/pcbnew/router/pns_node.cpp @@ -87,10 +87,20 @@ NODE::~NODE() m_joints.clear(); + std::vector toDelete; + + toDelete.reserve( m_index->Size() ); + for( ITEM* item : *m_index ) { - if( item->BelongsTo( this ) ) - delete item; + if( item->BelongsTo( this ) && item->OfKind( ITEM::HOLE_T ) ) + toDelete.push_back( item ); + } + + for( ITEM* item : toDelete ) + { + printf("del item %p type %s\n", item, item->KindStr().c_str() ); + delete item; } releaseGarbage(); @@ -105,34 +115,13 @@ int NODE::GetClearance( const ITEM* aA, const ITEM* aB, bool aUseClearanceEpsilo if( !m_ruleResolver ) return 100000; - if( aA->IsVirtual() || aB->IsVirtual() ) - return 0; - - return m_ruleResolver->Clearance( aA, aB, aUseClearanceEpsilon ); -} - - -int NODE::GetHoleClearance( const ITEM* aA, const ITEM* aB, bool aUseClearanceEpsilon ) const -{ - if( !m_ruleResolver ) - return 0; - - if( aA->IsVirtual() || aB->IsVirtual() ) - return 0; - - return m_ruleResolver->HoleClearance( aA, aB, aUseClearanceEpsilon ); -} - - -int NODE::GetHoleToHoleClearance( const ITEM* aA, const ITEM* aB, bool aUseClearanceEpsilon ) const -{ - if( !m_ruleResolver ) - return 0; if( aA->IsVirtual() || aB->IsVirtual() ) return 0; - return m_ruleResolver->HoleToHoleClearance( aA, aB, aUseClearanceEpsilon ); + int cl = m_ruleResolver->Clearance( aA, aB, aUseClearanceEpsilon ); + + return cl; } @@ -211,16 +200,11 @@ bool OBSTACLE_VISITOR::visit( ITEM* aCandidate ) // function object that visits potential obstacles and performs the actual collision refining struct NODE::DEFAULT_OBSTACLE_VISITOR : public OBSTACLE_VISITOR { - OBSTACLES& m_tab; - int m_matchCount; - const COLLISION_SEARCH_OPTIONS& m_opts; + COLLISION_SEARCH_CONTEXT* m_ctx; - DEFAULT_OBSTACLE_VISITOR( NODE::OBSTACLES& aTab, const ITEM* aItem, - const COLLISION_SEARCH_OPTIONS& aOpts ) : + DEFAULT_OBSTACLE_VISITOR( COLLISION_SEARCH_CONTEXT* aCtx, const ITEM* aItem ) : OBSTACLE_VISITOR( aItem ), - m_tab( aTab ), - m_opts( aOpts ), - m_matchCount( 0 ) + m_ctx( aCtx ) { } @@ -230,26 +214,16 @@ struct NODE::DEFAULT_OBSTACLE_VISITOR : public OBSTACLE_VISITOR bool operator()( ITEM* aCandidate ) override { - if( !aCandidate->OfKind( m_opts.m_kindMask ) ) + if( !aCandidate->OfKind( m_ctx->options.m_kindMask ) ) return true; if( visit( aCandidate ) ) return true; - if( !aCandidate->Collide( m_item, m_node, m_opts ) ) + if( !aCandidate->Collide( m_item, m_node, m_ctx ) ) return true; - OBSTACLE obs; - - obs.m_head = m_item; - obs.m_item = aCandidate; - obs.m_distFirst = INT_MAX; - obs.m_maxFanoutWidth = 0; - m_tab.push_back( obs ); - - m_matchCount++; - - if( m_opts.m_limitCount > 0 && m_matchCount >= m_opts.m_limitCount ) + if( m_ctx->options.m_limitCount > 0 && m_ctx->obstacles.size() >= m_ctx->options.m_limitCount ) return false; return true; @@ -258,13 +232,15 @@ struct NODE::DEFAULT_OBSTACLE_VISITOR : public OBSTACLE_VISITOR int NODE::QueryColliding( const ITEM* aItem, NODE::OBSTACLES& aObstacles, - const COLLISION_SEARCH_OPTIONS& aOpts ) + const COLLISION_SEARCH_OPTIONS& aOpts ) const { + COLLISION_SEARCH_CONTEXT ctx( aObstacles, aOpts ); + /// By default, virtual items cannot collide if( aItem->IsVirtual() ) return 0; - DEFAULT_OBSTACLE_VISITOR visitor( aObstacles, aItem, aOpts ); + DEFAULT_OBSTACLE_VISITOR visitor( &ctx, aItem ); #ifdef DEBUG assert( allocNodes.find( this ) != allocNodes.end() ); @@ -276,7 +252,7 @@ int NODE::QueryColliding( const ITEM* aItem, NODE::OBSTACLES& aObstacles, m_index->Query( aItem, m_maxClearance, visitor ); // if we haven't found enough items, look in the root branch as well. - if( !isRoot() && ( visitor.m_matchCount < aOpts.m_limitCount || aOpts.m_limitCount < 0 ) ) + if( !isRoot() && ( ctx.obstacles.size() < aOpts.m_limitCount || aOpts.m_limitCount < 0 ) ) { visitor.SetWorld( m_root, this ); m_root->m_index->Query( aItem, m_maxClearance, visitor ); @@ -286,22 +262,24 @@ int NODE::QueryColliding( const ITEM* aItem, NODE::OBSTACLES& aObstacles, } -NODE::OPT_OBSTACLE NODE::NearestObstacle( const LINE* aLine, int aKindMask, - const std::set* aRestrictedSet, +NODE::OPT_OBSTACLE NODE::NearestObstacle( const LINE* aLine, const COLLISION_SEARCH_OPTIONS& aOpts ) { - const int clearanceEpsilon = GetRuleResolver()->ClearanceEpsilon(); - OBSTACLES obstacleList; - obstacleList.reserve( 100 ); + const int clearanceEpsilon = GetRuleResolver()->ClearanceEpsilon(); + OBSTACLES obstacleList; + std::vector tmpSegs; + + tmpSegs.reserve( aLine->CLine().SegmentCount() ); + for( int i = 0; i < aLine->CLine().SegmentCount(); i++ ) { - // Note: Clearances between &s and other items are cached, + // Note: Clearances between tmpSegs.back() and other items are cached, // which means they'll be the same for all segments in the line. // Disabling the cache will lead to slowness. - const SEGMENT s( *aLine, aLine->CLine().CSegment( i ) ); - QueryColliding( &s, obstacleList, aOpts ); + tmpSegs.emplace_back( *aLine, aLine->CLine().CSegment( i ) ); + QueryColliding( &tmpSegs.back(), obstacleList, aOpts ); } if( aLine->EndsWithVia() ) @@ -317,22 +295,15 @@ NODE::OPT_OBSTACLE NODE::NearestObstacle( const LINE* aLine, int aKindMask, nearest.m_maxFanoutWidth = 0; auto updateNearest = - [&]( const SHAPE_LINE_CHAIN::INTERSECTION& pt, ITEM* obstacle, - const SHAPE_LINE_CHAIN& hull, bool isHole ) + [&]( const SHAPE_LINE_CHAIN::INTERSECTION& pt, const OBSTACLE& obstacle ) { int dist = aLine->CLine().PathLength( pt.p, pt.index_their ); if( dist < nearest.m_distFirst ) { + nearest = obstacle; nearest.m_distFirst = dist; nearest.m_ipFirst = pt.p; - nearest.m_item = obstacle; - nearest.m_itemIsHole = isHole; - // JEY TODO: are these no longer needed? - //nearest.m_hull = hull; - - //obstacle->Mark( isHole ? obstacle->Marker() | MK_HOLE - // : obstacle->Marker() & ~MK_HOLE ); } }; @@ -343,7 +314,7 @@ NODE::OPT_OBSTACLE NODE::NearestObstacle( const LINE* aLine, int aKindMask, for( const OBSTACLE& obstacle : obstacleList ) { - if( aRestrictedSet && aRestrictedSet->find( obstacle.m_item ) == aRestrictedSet->end() ) + if( aOpts.m_restrictedSet && aOpts.m_restrictedSet->count( obstacle.m_item ) == 0 ) continue; int clearance = @@ -360,23 +331,24 @@ NODE::OPT_OBSTACLE NODE::NearestObstacle( const LINE* aLine, int aKindMask, { //debugDecorator->AddPoint( ip.p, ip.valid?3:6, 100000, (const char *) wxString::Format("obstacle-isect-point-%d" ).c_str() ); if( ip.valid ) - updateNearest( ip, obstacle.m_item, obstacleHull, false ); + updateNearest( ip, obstacle ); } if( aLine->EndsWithVia() ) { const VIA& via = aLine->Via(); - // Don't use via.Drill(); it doesn't include the plating thickness + // JEY TODO: clean up (or re-enable) all this commented-out stuff.... + //const HOLE* viaHole = via.Hole(); - int viaHoleRadius = static_cast( via.Hole() )->GetRadius(); + //int viaHoleRadius = static_cast( via.Hole() )->GetRadius(); int viaClearance = GetClearance( obstacle.m_item, &via, aOpts.m_useClearanceEpsilon ) + via.Diameter() / 2; - int holeClearance = GetHoleClearance( obstacle.m_item, &via, aOpts.m_useClearanceEpsilon ) - + viaHoleRadius; + //int holeClearance = GetClearance( obstacle.m_item, viaHole, aOpts.m_useClearanceEpsilon ) + // + viaHoleRadius; - if( holeClearance > viaClearance ) - viaClearance = holeClearance; + //if( holeClearance > viaClearance ) + // viaClearance = holeClearance; obstacleHull = obstacle.m_item->Hull( viaClearance, 0, layer ); //debugDecorator->AddLine( obstacleHull, 3 ); @@ -387,9 +359,9 @@ NODE::OPT_OBSTACLE NODE::NearestObstacle( const LINE* aLine, int aKindMask, // obstacleHull.Intersect( aLine->CLine(), intersectingPts, true ); for( const SHAPE_LINE_CHAIN::INTERSECTION& ip : intersectingPts ) - updateNearest( ip, obstacle.m_item, obstacleHull, false ); + updateNearest( ip, obstacle ); } - +#if 0 if( ( m_collisionQueryScope == CQS_ALL_RULES || !ROUTER::GetInstance()->GetInterface()->IsFlashedOnLayer( obstacle.m_item, layer ) ) @@ -438,10 +410,11 @@ NODE::OPT_OBSTACLE NODE::NearestObstacle( const LINE* aLine, int aKindMask, updateNearest( ip, obstacle.m_item, obstacleHull, true ); } } +#endif } if( nearest.m_distFirst == INT_MAX ) - nearest.m_item = obstacleList[0].m_item; + nearest = (*obstacleList.begin()); return nearest; } @@ -470,8 +443,6 @@ NODE::OPT_OBSTACLE NODE::CheckColliding( const ITEM* aItemA, int aKindMask ) opts.m_limitCount = 1; opts.m_overrideClearance = 1; - obs.reserve( 100 ); - if( aItemA->Kind() == ITEM::LINE_T ) { int n = 0; @@ -488,7 +459,7 @@ NODE::OPT_OBSTACLE NODE::CheckColliding( const ITEM* aItemA, int aKindMask ) n += QueryColliding( &s, obs, opts ); if( n ) - return OPT_OBSTACLE( obs[0] ); + return OPT_OBSTACLE( *obs.begin() ); } if( line->EndsWithVia() ) @@ -496,12 +467,12 @@ NODE::OPT_OBSTACLE NODE::CheckColliding( const ITEM* aItemA, int aKindMask ) n += QueryColliding( &line->Via(), obs, opts ); if( n ) - return OPT_OBSTACLE( obs[0] ); + return OPT_OBSTACLE( *obs.begin() ); } } else if( QueryColliding( aItemA, obs, opts ) > 0 ) { - return OPT_OBSTACLE( obs[0] ); + return OPT_OBSTACLE( *obs.begin() ); } return OPT_OBSTACLE(); @@ -568,6 +539,9 @@ const ITEM_SET NODE::HitTest( const VECTOR2I& aPoint ) const void NODE::addSolid( SOLID* aSolid ) { + if( aSolid->HasHole() ) + addHole( aSolid->Hole() ); + if( aSolid->IsRoutable() ) linkJoint( aSolid->Pos(), aSolid->Layers(), aSolid->Net(), aSolid ); @@ -584,18 +558,53 @@ void NODE::Add( std::unique_ptr< SOLID > aSolid ) void NODE::addVia( VIA* aVia ) { + if( aVia->HasHole() ) + addHole( aVia->Hole() ); + linkJoint( aVia->Pos(), aVia->Layers(), aVia->Net(), aVia ); m_index->Add( aVia ); } +void NODE::addHole( HOLE* aHole ) +{ + // do we need holes in the connection graph? + //linkJoint( aHole->Pos(), aHole->Layers(), aHole->Net(), aHole ); + + m_index->Add( aHole ); +} + + void NODE::Add( std::unique_ptr< VIA > aVia ) { aVia->SetOwner( this ); addVia( aVia.release() ); } +void NODE::Add( ITEM* aItem, bool aAllowRedundant ) +{ + aItem->SetOwner( this ); + + switch( aItem->Kind() ) + { + case ITEM::ARC_T: + addArc( static_cast( aItem ) ); + break; + case ITEM::SEGMENT_T: + addSegment( static_cast( aItem ) ); + break; + case ITEM::VIA_T: + addVia( static_cast( aItem ) ); + break; + case ITEM::SOLID_T: + addSolid( static_cast( aItem ) ); + break; + default: + assert( false ); + } +} + void NODE::Add( LINE& aLine, bool aAllowRedundant ) { @@ -725,6 +734,7 @@ void NODE::Add( std::unique_ptr< ITEM > aItem, bool aAllowRedundant ) Add( *l ); break; } + default: assert( false ); } @@ -754,18 +764,37 @@ void NODE::doRemove( ITEM* aItem ) // case 1: removing an item that is stored in the root node from any branch: // mark it as overridden, but do not remove if( aItem->BelongsTo( m_root ) && !isRoot() ) + { m_override.insert( aItem ); + if( aItem->HasHole() ) + m_override.insert( aItem->Hole() ); + } + // case 2: the item belongs to this branch or a parent, non-root branch, // or the root itself and we are the root: remove from the index else if( !aItem->BelongsTo( m_root ) || isRoot() ) + { m_index->Remove( aItem ); + if( aItem->HasHole() ) + m_index->Remove( aItem->Hole() ); + } + // the item belongs to this particular branch: un-reference it if( aItem->BelongsTo( this ) ) { aItem->SetOwner( nullptr ); + m_root->m_garbageItems.insert( aItem ); + + HOLE *hole = aItem->Hole(); + + if( hole ) + { + m_index->Remove( hole ); // hole is not directly owned by NODE but by the parent SOLID/VIA. + hole->SetOwner( nullptr ); + } } } diff --git a/pcbnew/router/pns_node.h b/pcbnew/router/pns_node.h index a067357f44..a77bc46c96 100644 --- a/pcbnew/router/pns_node.h +++ b/pcbnew/router/pns_node.h @@ -25,7 +25,7 @@ #include #include -#include +#include #include #include @@ -46,10 +46,6 @@ class INDEX; class ROUTER; class NODE; -/** - * An abstract function object, returning a design rule (clearance, diff pair gap, etc) required - * between two items. - */ enum class CONSTRAINT_TYPE { @@ -64,6 +60,11 @@ enum class CONSTRAINT_TYPE CT_HOLE_TO_HOLE = 9 }; +/** + * An abstract function object, returning a design rule (clearance, diff pair gap, etc) required + * between two items. + */ + struct CONSTRAINT { CONSTRAINT_TYPE m_Type; @@ -74,16 +75,68 @@ struct CONSTRAINT wxString m_ToName; }; + +/** + * Hold an object colliding with another object, along with some useful data about the collision. + */ +struct OBSTACLE +{ + ITEM* m_head = nullptr; ///< Line we search collisions against + ITEM* m_item = nullptr; ///< Item found to be colliding with m_head + VECTOR2I m_ipFirst; ///< First intersection between m_head and m_hull + int m_clearance; + VECTOR2I m_pos; + int m_distFirst; ///< ... and the distance thereof + int m_maxFanoutWidth; ///< worst case (largest) width of the tracks connected to the item + + CONSTRAINT_TYPE m_violatingConstraint; + + bool operator==(const OBSTACLE& other) const + { + return m_head == other.m_head && m_item == other.m_item; + } + + bool operator<(const OBSTACLE& other) const + { + if( (uintptr_t)m_head < (uintptr_t)other.m_head ) + return true; + else if ( m_head == other.m_head ) + return (uintptr_t)m_item < (uintptr_t)other.m_item; + return false; + } +}; + + +struct COLLISION_SEARCH_OPTIONS +{ + bool m_differentNetsOnly = true; + int m_overrideClearance = -1; + int m_limitCount = -1; + int m_kindMask = -1; + bool m_useClearanceEpsilon = true; + std::set* m_restrictedSet = nullptr; +}; + + +struct COLLISION_SEARCH_CONTEXT +{ + COLLISION_SEARCH_CONTEXT( std::set& aObs, const COLLISION_SEARCH_OPTIONS aOpts ) : + obstacles( aObs ), + options( aOpts ) + { + } + + std::set& obstacles; + const COLLISION_SEARCH_OPTIONS options; +}; + + class RULE_RESOLVER { public: virtual ~RULE_RESOLVER() {} virtual int Clearance( const ITEM* aA, const ITEM* aB, bool aUseClearanceEpsilon = true ) = 0; - virtual int HoleClearance( const ITEM* aA, const ITEM* aB, - bool aUseClearanceEpsilon = true ) = 0; - virtual int HoleToHoleClearance( const ITEM* aA, const ITEM* aB, - bool aUseClearanceEpsilon = true ) = 0; virtual int DpCoupledNet( int aNet ) = 0; virtual int DpNetPolarity( int aNet ) = 0; @@ -108,23 +161,6 @@ public: virtual int ClearanceEpsilon() const { return 0; } }; -/** - * Hold an object colliding with another object, along with some useful data about the collision. - */ -struct OBSTACLE -{ - const ITEM* m_head; ///< Line we search collisions against - ITEM* m_item; ///< Item found to be colliding with m_head - VECTOR2I m_ipFirst; ///< First intersection between m_head and m_hull - int m_clearance; - int m_actual; - int m_walkaroundThickness; - bool m_headIsHole = false; - bool m_itemIsHole = false; - VECTOR2I m_pos; - int m_distFirst; ///< ... and the distance thereof - int m_maxFanoutWidth; ///< worst case (largest) width of the tracks connected to the item -}; class OBSTACLE_VISITOR { @@ -171,16 +207,13 @@ public: typedef std::optional OPT_OBSTACLE; typedef std::vector ITEM_VECTOR; - typedef std::vector OBSTACLES; + typedef std::set OBSTACLES; NODE(); ~NODE(); ///< Return the expected clearance between items a and b. int GetClearance( const ITEM* aA, const ITEM* aB, bool aUseClearanceEpsilon = true ) const; - int GetHoleClearance( const ITEM* aA, const ITEM* aB, bool aUseClearanceEpsilon = true ) const; - int GetHoleToHoleClearance( const ITEM* aA, const ITEM* aB, - bool aUseClearanceEpsilon = true ) const; ///< Return the pre-set worst case clearance between any pair of items. int GetMaxClearance() const @@ -227,7 +260,7 @@ public: * @return number of obstacles found */ int QueryColliding( const ITEM* aItem, OBSTACLES& aObstacles, - const COLLISION_SEARCH_OPTIONS& aOpts = COLLISION_SEARCH_OPTIONS() ); + const COLLISION_SEARCH_OPTIONS& aOpts = COLLISION_SEARCH_OPTIONS() ) const; int QueryJoints( const BOX2I& aBox, std::vector& aJoints, LAYER_RANGE aLayerMask = LAYER_RANGE::All(), int aKindMask = ITEM::ANY_T ); @@ -241,8 +274,7 @@ public: * @param aRestrictedSet is an optional set of items that should be considered as obstacles * @return the obstacle, if found, otherwise empty. */ - OPT_OBSTACLE NearestObstacle( const LINE* aLine, int aKindMask = ITEM::ANY_T, - const std::set* aRestrictedSet = nullptr, + OPT_OBSTACLE NearestObstacle( const LINE* aLine, const COLLISION_SEARCH_OPTIONS& aOpts = COLLISION_SEARCH_OPTIONS() ); /** @@ -289,6 +321,8 @@ public: void Add( LINE& aLine, bool aAllowRedundant = false ); + void Add( ITEM* aItem, bool aAllowRedundant = false ); + void AddEdgeExclusion( std::unique_ptr aShape ); bool QueryEdgeExclusions( const VECTOR2I& aPos ) const; @@ -450,6 +484,7 @@ private: void addSegment( SEGMENT* aSeg ); void addVia( VIA* aVia ); void addArc( ARC* aVia ); + void addHole( HOLE* aHole ); void removeSolidIndex( SOLID* aSeg ); void removeSegmentIndex( SEGMENT* aSeg ); diff --git a/pcbnew/router/pns_router.cpp b/pcbnew/router/pns_router.cpp index a541d4cb5c..8035a208cd 100644 --- a/pcbnew/router/pns_router.cpp +++ b/pcbnew/router/pns_router.cpp @@ -153,8 +153,8 @@ const ITEM_SET ROUTER::QueryHoverItems( const VECTOR2I& aP, bool aUseClearance ) PNS::ITEM_SET ret; - for( OBSTACLE& obstacle : obs ) - ret.Add( obstacle.m_item, false ); + for( const OBSTACLE& obstacle : obs ) + ret.Add( obstacle.m_item, false ); return ret; } @@ -646,19 +646,15 @@ void ROUTER::markViolations( NODE* aNode, ITEM_SET& aCurrent, NODE::ITEM_VECTOR& int clearance; bool removeOriginal = true; - bool holeOnly = false; // JEY TODO ( ( itemToMark->Marker() & MK_HOLE ) - //&& !( itemToMark->Marker() & MK_VIOLATION ) ); - if( holeOnly ) - clearance = aNode->GetHoleClearance( currentItem, itemToMark ); - else - clearance = aNode->GetClearance( currentItem, itemToMark ); + clearance = aNode->GetClearance( currentItem, itemToMark ); if( itemToMark->Layers().IsMultilayer() && !currentItem->Layers().IsMultilayer() ) tmp->SetLayer( currentItem->Layer() ); if( itemToMark->Kind() == ITEM::SOLID_T ) { + #if 0 // fixme holes if( holeOnly || !m_iface->IsFlashedOnLayer( itemToMark, currentItem->Layer() ) ) { SOLID* solid = static_cast( tmp.get() ); @@ -671,6 +667,7 @@ void ROUTER::markViolations( NODE* aNode, ITEM_SET& aCurrent, NODE::ITEM_VECTOR& removeOriginal = false; } } + #endif if( itemToMark->IsCompoundShapePrimitive() ) { @@ -708,7 +705,7 @@ void ROUTER::markViolations( NODE* aNode, ITEM_SET& aCurrent, NODE::ITEM_VECTOR& if( GetDragger() ) draggedItems = GetDragger()->Traces(); - for( OBSTACLE& obs : obstacles ) + for( const OBSTACLE& obs : obstacles ) { // Don't mark items being dragged; only board items they collide with if( draggedItems.Contains( obs.m_item ) ) @@ -788,7 +785,7 @@ bool ROUTER::movePlacing( const VECTOR2I& aP, ITEM* aEndItem ) { const VIA& via = l->Via(); int viaClearance = GetRuleResolver()->Clearance( &via, nullptr ); - int holeClearance = GetRuleResolver()->HoleClearance( &via, nullptr ); + int holeClearance = 0; // GetRuleResolver()->HoleClearance( &via, nullptr ); // fixme holes if( holeClearance + via.Drill() / 2 > viaClearance + via.Diameter() / 2 ) viaClearance = holeClearance + via.Drill() / 2 - via.Diameter() / 2; diff --git a/pcbnew/router/pns_shove.cpp b/pcbnew/router/pns_shove.cpp index 8794b25e05..b0b4333c60 100644 --- a/pcbnew/router/pns_shove.cpp +++ b/pcbnew/router/pns_shove.cpp @@ -140,10 +140,12 @@ int SHOVE::getClearance( const ITEM* aA, const ITEM* aB ) const int SHOVE::getHoleClearance( const ITEM* aA, const ITEM* aB ) const { - if( m_forceClearance >= 0 ) + /* if( m_forceClearance >= 0 ) return m_forceClearance; - return m_currentNode->GetHoleClearance( aA, aB ); + return m_currentNode->GetHoleClearance( aA, aB );*/ + + return -1; // fixme hole } @@ -1342,7 +1344,10 @@ SHOVE::SHOVE_STATUS SHOVE::shoveIteration( int aIter ) for( ITEM::PnsKind search_order : { ITEM::SOLID_T, ITEM::VIA_T, ITEM::SEGMENT_T } ) { - nearest = m_currentNode->NearestObstacle( ¤tLine, search_order ); + COLLISION_SEARCH_OPTIONS opts; + opts.m_kindMask = search_order; + + nearest = m_currentNode->NearestObstacle( ¤tLine, opts ); if( nearest ) { diff --git a/pcbnew/router/pns_solid.cpp b/pcbnew/router/pns_solid.cpp index 1ea9a6d7b0..3b8ff7449d 100644 --- a/pcbnew/router/pns_solid.cpp +++ b/pcbnew/router/pns_solid.cpp @@ -37,65 +37,13 @@ namespace PNS { -static const SHAPE_LINE_CHAIN buildHullForPrimitiveShape( const SHAPE* aShape, int aClearance, - int aWalkaroundThickness ) -{ - int cl = aClearance + ( aWalkaroundThickness + 1 )/ 2; - - switch( aShape->Type() ) - { - case SH_RECT: - { - const SHAPE_RECT* rect = static_cast( aShape ); - return OctagonalHull( rect->GetPosition(), - rect->GetSize(), - cl, - 0 ); - } - - case SH_CIRCLE: - { - const SHAPE_CIRCLE* circle = static_cast( aShape ); - int r = circle->GetRadius(); - return OctagonalHull( circle->GetCenter() - VECTOR2I( r, r ), - VECTOR2I( 2 * r, 2 * r ), - cl, - 2.0 * ( 1.0 - M_SQRT1_2 ) * ( r + cl ) ); - } - - case SH_SEGMENT: - { - const SHAPE_SEGMENT* seg = static_cast( aShape ); - return SegmentHull( *seg, aClearance, aWalkaroundThickness ); - } - - case SH_ARC: - { - const SHAPE_ARC* arc = static_cast( aShape ); - return ArcHull( *arc, aClearance, aWalkaroundThickness ); - } - - case SH_SIMPLE: - { - const SHAPE_SIMPLE* convex = static_cast( aShape ); - return ConvexHull( *convex, cl ); - } - - default: - wxFAIL_MSG( wxString::Format( wxT( "Unsupported hull shape: %d (%s)." ), - aShape->Type(), - SHAPE_TYPE_asString( aShape->Type() ) ) ); - break; - } - - return SHAPE_LINE_CHAIN(); -} - const SHAPE_LINE_CHAIN SOLID::Hull( int aClearance, int aWalkaroundThickness, int aLayer ) const { - if( !ROUTER::GetInstance()->GetInterface()->IsFlashedOnLayer( this, aLayer ) ) - return HoleHull( aClearance, aWalkaroundThickness, aLayer ); + //if( !ROUTER::GetInstance()->GetInterface()->IsFlashedOnLayer( this, aLayer ) ) + // return HoleHull( aClearance, aWalkaroundThickness, aLayer ); + + // fixme holes if( !m_shape ) return SHAPE_LINE_CHAIN(); @@ -106,7 +54,7 @@ const SHAPE_LINE_CHAIN SOLID::Hull( int aClearance, int aWalkaroundThickness, in if ( cmpnd->Shapes().size() == 1 ) { - return buildHullForPrimitiveShape( cmpnd->Shapes()[0], aClearance, + return BuildHullForPrimitiveShape( cmpnd->Shapes()[0], aClearance, aWalkaroundThickness ); } else @@ -115,7 +63,7 @@ const SHAPE_LINE_CHAIN SOLID::Hull( int aClearance, int aWalkaroundThickness, in for( SHAPE* shape : cmpnd->Shapes() ) { - hullSet.AddOutline( buildHullForPrimitiveShape( shape, aClearance, + hullSet.AddOutline( BuildHullForPrimitiveShape( shape, aClearance, aWalkaroundThickness ) ); } @@ -125,42 +73,7 @@ const SHAPE_LINE_CHAIN SOLID::Hull( int aClearance, int aWalkaroundThickness, in } else { - return buildHullForPrimitiveShape( m_shape, aClearance, aWalkaroundThickness ); - } -} - - -const SHAPE_LINE_CHAIN SOLID::HoleHull( int aClearance, int aWalkaroundThickness, int aLayer ) const -{ - if( !m_hole ) - return SHAPE_LINE_CHAIN(); - - if( m_hole->Type() == SH_COMPOUND ) - { - SHAPE_COMPOUND* cmpnd = static_cast( m_hole ); - - if ( cmpnd->Shapes().size() == 1 ) - { - return buildHullForPrimitiveShape( cmpnd->Shapes()[0], aClearance, - aWalkaroundThickness ); - } - else - { - SHAPE_POLY_SET hullSet; - - for( SHAPE* shape : cmpnd->Shapes() ) - { - hullSet.AddOutline( buildHullForPrimitiveShape( shape, aClearance, - aWalkaroundThickness ) ); - } - - hullSet.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); - return hullSet.Outline( 0 ); - } - } - else - { - return buildHullForPrimitiveShape( m_hole, aClearance, aWalkaroundThickness ); + return BuildHullForPrimitiveShape( m_shape, aClearance, aWalkaroundThickness ); } } @@ -171,6 +84,7 @@ ITEM* SOLID::Clone() const return solid; } + void SOLID::SetPos( const VECTOR2I& aCenter ) { VECTOR2I delta = aCenter - m_pos; @@ -178,6 +92,8 @@ void SOLID::SetPos( const VECTOR2I& aCenter ) if( m_shape ) m_shape->Move( delta ); + //printf("Hole@%p\n", m_hole); + if( m_hole ) m_hole->Move( delta ); diff --git a/pcbnew/router/pns_solid.h b/pcbnew/router/pns_solid.h index 3ea0e4fa40..131cc9ca43 100644 --- a/pcbnew/router/pns_solid.h +++ b/pcbnew/router/pns_solid.h @@ -46,8 +46,10 @@ public: ~SOLID() { + if ( m_hole ) + delete m_hole; + delete m_shape; - delete m_hole; } SOLID( const SOLID& aSolid ) : @@ -77,26 +79,16 @@ public: const SHAPE* Shape() const override { return m_shape; } - const SHAPE* Hole() const override { return m_hole; } const SHAPE_LINE_CHAIN Hull( int aClearance = 0, int aWalkaroundThickness = 0, int aLayer = -1 ) const override; - const SHAPE_LINE_CHAIN HoleHull( int aClearance, int aWalkaroundThickness, - int aLayer ) const override; - void SetShape( SHAPE* shape ) { delete m_shape; m_shape = shape; } - void SetHole( SHAPE* shape ) - { - delete m_hole; - m_hole = shape; - } - const VECTOR2I& Pos() const { return m_pos; } void SetPos( const VECTOR2I& aCenter ); @@ -119,13 +111,33 @@ public: EDA_ANGLE GetOrientation() const { return m_orientation; } void SetOrientation( const EDA_ANGLE& aOrientation ) { m_orientation = aOrientation; } + virtual void SetHole( HOLE* aHole ) override + { + if( m_hole ) + { + assert( m_hole->Owner() == nullptr ); + } + + m_hole = aHole; + m_hole->SetNet( Net() ); + m_hole->SetOwner( this ); + + if( m_hole ) + { + m_hole->SetLayers( m_layers ); // fixme: backdrill vias can have hole layer set different than copper layer set + } + } + + virtual bool HasHole() const override { return m_hole != nullptr; } + virtual HOLE *Hole() const override { return m_hole; } + private: VECTOR2I m_pos; SHAPE* m_shape; - SHAPE* m_hole; VECTOR2I m_offset; int m_padToDie; EDA_ANGLE m_orientation; + HOLE* m_hole; }; } diff --git a/pcbnew/router/pns_utils.cpp b/pcbnew/router/pns_utils.cpp index c50543a8d5..4734b3b8e0 100644 --- a/pcbnew/router/pns_utils.cpp +++ b/pcbnew/router/pns_utils.cpp @@ -463,4 +463,62 @@ void HullIntersection( const SHAPE_LINE_CHAIN& hull, const SHAPE_LINE_CHAIN& lin } } + +const SHAPE_LINE_CHAIN BuildHullForPrimitiveShape( const SHAPE* aShape, int aClearance, + int aWalkaroundThickness ) +{ + int cl = aClearance + ( aWalkaroundThickness + 1 )/ 2; + + switch( aShape->Type() ) + { + case SH_RECT: + { + const SHAPE_RECT* rect = static_cast( aShape ); + return OctagonalHull( rect->GetPosition(), + rect->GetSize(), + cl, + 0 ); + } + + case SH_CIRCLE: + { + const SHAPE_CIRCLE* circle = static_cast( aShape ); + int r = circle->GetRadius(); + return OctagonalHull( circle->GetCenter() - VECTOR2I( r, r ), + VECTOR2I( 2 * r, 2 * r ), + cl, + 2.0 * ( 1.0 - M_SQRT1_2 ) * ( r + cl ) ); + } + + case SH_SEGMENT: + { + const SHAPE_SEGMENT* seg = static_cast( aShape ); + return SegmentHull( *seg, aClearance, aWalkaroundThickness ); + } + + case SH_ARC: + { + const SHAPE_ARC* arc = static_cast( aShape ); + return ArcHull( *arc, aClearance, aWalkaroundThickness ); + } + + case SH_SIMPLE: + { + const SHAPE_SIMPLE* convex = static_cast( aShape ); + + return ConvexHull( *convex, cl ); + } + default: + { + wxFAIL_MSG( wxString::Format( wxT( "Unsupported hull shape: %d (%s)." ), + aShape->Type(), + SHAPE_TYPE_asString( aShape->Type() ) ) ); + break; + } + } + + return SHAPE_LINE_CHAIN(); +} + + } diff --git a/pcbnew/router/pns_utils.h b/pcbnew/router/pns_utils.h index 4ea7dcd64c..ff1872d32a 100644 --- a/pcbnew/router/pns_utils.h +++ b/pcbnew/router/pns_utils.h @@ -64,6 +64,8 @@ OPT_BOX2I ChangedArea( const LINE& aLineA, const LINE& aLineB ); void HullIntersection( const SHAPE_LINE_CHAIN& hull, const SHAPE_LINE_CHAIN& line, SHAPE_LINE_CHAIN::INTERSECTIONS& ips ); +const SHAPE_LINE_CHAIN BuildHullForPrimitiveShape( const SHAPE* aShape, int aClearance, + int aWalkaroundThickness ); } diff --git a/pcbnew/router/pns_via.cpp b/pcbnew/router/pns_via.cpp index a713cd0d5b..f2d4d95234 100644 --- a/pcbnew/router/pns_via.cpp +++ b/pcbnew/router/pns_via.cpp @@ -36,6 +36,7 @@ bool VIA::PushoutForce( NODE* aNode, const ITEM* aOther, VECTOR2I& aForce ) VECTOR2I elementForces[4], force; size_t nf = 0; + #if 0 if( aNode->GetCollisionQueryScope() == NODE::CQS_ALL_RULES ) { int holeClearance = aNode->GetHoleClearance( this, aOther ); @@ -49,6 +50,7 @@ bool VIA::PushoutForce( NODE* aNode, const ITEM* aOther, VECTOR2I& aForce ) aOther->Shape()->Collide( Hole(), holeClearance, &elementForces[nf++] ); } + #endif aOther->Shape()->Collide( Shape(), clearance, &elementForces[nf++] ); @@ -155,7 +157,7 @@ const SHAPE_LINE_CHAIN VIA::Hull( int aClearance, int aWalkaroundThickness, int int width = m_diameter; if( !ROUTER::GetInstance()->GetInterface()->IsFlashedOnLayer( this, aLayer ) ) - width = m_hole.GetRadius() * 2; + width = m_hole->Radius() * 2; // Chamfer = width * ( 1 - sqrt(2)/2 ) for equilateral octagon return OctagonalHull( m_pos - VECTOR2I( width / 2, width / 2 ), @@ -164,16 +166,17 @@ const SHAPE_LINE_CHAIN VIA::Hull( int aClearance, int aWalkaroundThickness, int } +#if 0 const SHAPE_LINE_CHAIN VIA::HoleHull( int aClearance, int aWalkaroundThickness, int aLayer ) const { int cl = ( aClearance + aWalkaroundThickness / 2 ); - int width = m_hole.GetRadius() * 2; + int width = m_hole->GetRadius() * 2; // Chamfer = width * ( 1 - sqrt(2)/2 ) for equilateral octagon return OctagonalHull( m_pos - VECTOR2I( width / 2, width / 2 ), VECTOR2I( width, width ), cl, ( 2 * cl + width ) * ( 1.0 - M_SQRT1_2 ) ); } - +#endif VIA* VIA::Clone() const { @@ -185,7 +188,7 @@ VIA* VIA::Clone() const v->m_diameter = m_diameter; v->m_drill = m_drill; v->m_shape = SHAPE_CIRCLE( m_pos, m_diameter / 2 ); - v->m_hole = SHAPE_CIRCLE( m_pos, m_drill / 2 ); + v->m_hole = m_hole->Clone(); v->m_rank = m_rank; v->m_marker = m_marker; v->m_viaType = m_viaType; diff --git a/pcbnew/router/pns_via.h b/pcbnew/router/pns_via.h index 34f2627cf8..ee303cbe4e 100644 --- a/pcbnew/router/pns_via.h +++ b/pcbnew/router/pns_via.h @@ -30,6 +30,7 @@ #include "pns_item.h" #include "pns_linked_item.h" +#include "pns_hole.h" namespace PNS { @@ -57,6 +58,7 @@ public: m_viaType = VIATYPE::THROUGH; m_isFree = false; m_isVirtual = false; + m_hole = nullptr; } VIA( const VECTOR2I& aPos, const LAYER_RANGE& aLayers, int aDiameter, int aDrill, @@ -69,7 +71,8 @@ public: m_diameter = aDiameter; m_drill = aDrill; m_shape = SHAPE_CIRCLE( aPos, aDiameter / 2 ); - m_hole = SHAPE_CIRCLE( m_pos, aDrill / 2 ); + m_hole = HOLE::MakeCircularHole( m_pos, aDrill / 2 ); + m_hole->SetNet( aNet ); m_viaType = aViaType; m_isFree = false; m_isVirtual = false; @@ -83,7 +86,7 @@ public: m_pos = aB.m_pos; m_diameter = aB.m_diameter; m_shape = SHAPE_CIRCLE( m_pos, m_diameter / 2 ); - m_hole = SHAPE_CIRCLE( m_pos, aB.m_drill / 2 ); + m_hole = aB.m_hole->Clone(); m_marker = aB.m_marker; m_rank = aB.m_rank; m_drill = aB.m_drill; @@ -92,6 +95,12 @@ public: m_isVirtual = aB.m_isVirtual; } + virtual ~VIA() + { + if ( m_hole ) + delete m_hole; + } + static inline bool ClassOf( const ITEM* aItem ) { return aItem && VIA_T == aItem->Kind(); @@ -103,7 +112,8 @@ public: { m_pos = aPos; m_shape.SetCenter( aPos ); - m_hole.SetCenter( aPos ); + if( m_hole ) + m_hole->SetCenter( aPos ); } VIATYPE ViaType() const { return m_viaType; } @@ -122,7 +132,8 @@ public: void SetDrill( int aDrill ) { m_drill = aDrill; - m_hole.SetRadius( m_drill / 2 ); + if( m_hole ) + m_hole->SetRadius( m_drill / 2 ); } bool IsFree() const { return m_isFree; } @@ -135,17 +146,11 @@ public: const SHAPE* Shape() const override { return &m_shape; } - const SHAPE_CIRCLE* Hole() const override { return &m_hole; } - void SetHole( const SHAPE_CIRCLE& aHole ) { m_hole = aHole; } - VIA* Clone() const override; const SHAPE_LINE_CHAIN Hull( int aClearance = 0, int aWalkaroundThickness = 0, int aLayer = -1 ) const override; - const SHAPE_LINE_CHAIN HoleHull( int aClearance = 0, int aWalkaroundThickness = 0, - int aLayer = -1 ) const override; - virtual VECTOR2I Anchor( int n ) const override { return m_pos; @@ -160,6 +165,26 @@ public: const VIA_HANDLE MakeHandle() const; + virtual void SetHole( HOLE* aHole ) override + { + if( m_hole ) + { + assert( m_hole->Owner() == nullptr ); + } + + m_hole = aHole; + m_hole->SetNet( Net() ); + m_hole->SetOwner( this ); + + if( m_hole ) + { + m_hole->SetLayers( m_layers ); // fixme: backdrill vias can have hole layer set different than copper layer set + } + } + + virtual bool HasHole() const override { return true; } + virtual HOLE *Hole() const override { return m_hole; } + virtual const std::string Format() const override; private: @@ -167,10 +192,9 @@ private: int m_drill; VECTOR2I m_pos; SHAPE_CIRCLE m_shape; - SHAPE_CIRCLE m_hole; VIATYPE m_viaType; bool m_isFree; - + HOLE* m_hole; }; @@ -181,7 +205,7 @@ public: VIA( aPos, LAYER_RANGE( aLayer, aLayer ), aDiameter, aDiameter / 2, aNet ) { m_isVirtual = true; - SetHole( SHAPE_CIRCLE( Pos(), 1 ) ); + //SetHole( SHAPE_CIRCLE( Pos(), 1 ) ); } }; diff --git a/pcbnew/router/pns_walkaround.cpp b/pcbnew/router/pns_walkaround.cpp index c26ac61ea8..2d9bdfd9b0 100644 --- a/pcbnew/router/pns_walkaround.cpp +++ b/pcbnew/router/pns_walkaround.cpp @@ -43,12 +43,17 @@ void WALKAROUND::start( const LINE& aInitialPath ) NODE::OPT_OBSTACLE WALKAROUND::nearestObstacle( const LINE& aPath ) { COLLISION_SEARCH_OPTIONS opts; + + opts.m_kindMask = m_itemMask; + + if( ! m_restrictedSet.empty() ) + opts.m_restrictedSet = &m_restrictedSet; + else + opts.m_restrictedSet = nullptr; + opts.m_useClearanceEpsilon = false; - NODE::OPT_OBSTACLE obs = m_world->NearestObstacle( &aPath, m_itemMask, - m_restrictedSet.empty() ? nullptr - : &m_restrictedSet, - opts ); + NODE::OPT_OBSTACLE obs = m_world->NearestObstacle( &aPath, opts ); if( m_restrictedSet.empty() ) return obs; @@ -91,14 +96,13 @@ WALKAROUND::WALKAROUND_STATUS WALKAROUND::singleStep( LINE& aPath, bool aWinding SHAPE_LINE_CHAIN path_walk; - - SHAPE_LINE_CHAIN hull = current_obs->m_item->Hull( current_obs->m_clearance, - current_obs->m_walkaroundThickness ); + + SHAPE_LINE_CHAIN hull = current_obs->m_item->Hull( current_obs->m_clearance, aPath.Width() ); bool s_cw = aPath.Walkaround( hull, path_walk, aWindingDirection ); PNS_DBG( Dbg(), BeginGroup, "hull/walk", 1 ); - PNS_DBG( Dbg(), AddShape, &hull, RED, 0, wxString::Format( "hull-%s-%d", aWindingDirection ? wxT( "cw" ) : wxT( "ccw" ), m_iteration ) ); + PNS_DBG( Dbg(), AddShape, &hull, RED, 0, wxString::Format( "hull-%s-%d-cl %d", aWindingDirection ? wxT( "cw" ) : wxT( "ccw" ), m_iteration, current_obs->m_clearance ) ); PNS_DBG( Dbg(), AddShape, &aPath.CLine(), GREEN, 0, wxString::Format( "path-%s-%d", aWindingDirection ? wxT( "cw" ) : wxT( "ccw" ), m_iteration ) ); PNS_DBG( Dbg(), AddShape, &path_walk, BLUE, 0, wxString::Format( "result-%s-%d", aWindingDirection ? wxT( "cw" ) : wxT( "ccw" ), m_iteration ) ); PNS_DBG( Dbg(), Message, wxString::Format( wxT( "Stat cw %d" ), !!s_cw ) ); @@ -180,6 +184,12 @@ const WALKAROUND::RESULT WALKAROUND::Route( const LINE& aInitialPath ) if( s_cw != IN_PROGRESS && s_ccw != IN_PROGRESS ) break; + double lcw = path_cw.Line().Length() / (double)aInitialPath.CLine().Length(); + double lccw = path_ccw.Line().Length() / (double)aInitialPath.CLine().Length(); + + PNS_DBG( Dbg(), Message, wxString::Format( wxT( "lcw %.1f lccw %.1f" ), lcw, lccw ) ); + + // Safety valve if( m_lengthLimitOn && path_cw.Line().Length() > lengthLimit && path_ccw.Line().Length() > lengthLimit ) break; diff --git a/pcbnew/router/router_preview_item.cpp b/pcbnew/router/router_preview_item.cpp index 18f110f02f..7deafd81c3 100644 --- a/pcbnew/router/router_preview_item.cpp +++ b/pcbnew/router/router_preview_item.cpp @@ -38,12 +38,26 @@ using namespace KIGFX; ROUTER_PREVIEW_ITEM::ROUTER_PREVIEW_ITEM( const PNS::ITEM* aItem, KIGFX::VIEW* aView ) : - EDA_ITEM( NOT_USED ) + EDA_ITEM( NOT_USED ), + m_view( aView ), + m_shape( nullptr ), + m_hole( nullptr ) { - m_view = aView; + BOARD_ITEM* boardItem = aItem ? aItem->Parent() : nullptr; - m_shape = aItem ? aItem->Shape()->Clone() : nullptr; - m_hole = aItem && aItem->Hole() ? aItem->Hole()->Clone() : 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 ; @@ -145,7 +159,7 @@ void ROUTER_PREVIEW_ITEM::Update( const PNS::ITEM* aItem ) m_hole = nullptr; if( aItem->Hole() ) - m_hole = aItem->Hole()->Clone(); + m_hole = aItem->Hole()->Shape()->Clone(); break; diff --git a/qa/pcbnew_utils/board_file_utils.cpp b/qa/pcbnew_utils/board_file_utils.cpp index a4d6d63d62..bba6fddcee 100644 --- a/qa/pcbnew_utils/board_file_utils.cpp +++ b/qa/pcbnew_utils/board_file_utils.cpp @@ -96,6 +96,8 @@ std::unique_ptr ReadBoardFromFileOrStream( const std::string& aFilename, std::istream* in_stream = nullptr; std::ifstream file_stream; + printf("RD from %s\n", aFilename.c_str() ); + if( aFilename.empty() ) { // no file, read stdin diff --git a/qa/unittests/pcbnew/CMakeLists.txt b/qa/unittests/pcbnew/CMakeLists.txt index c138943865..c18d32b88a 100644 --- a/qa/unittests/pcbnew/CMakeLists.txt +++ b/qa/unittests/pcbnew/CMakeLists.txt @@ -35,6 +35,7 @@ set( QA_PCBNEW_SRCS test_board_item.cpp test_graphics_import_mgr.cpp test_lset.cpp + test_pns_basics.cpp test_pad_numbering.cpp test_libeval_compiler.cpp test_save_load.cpp diff --git a/qa/unittests/pcbnew/test_pns_basics.cpp b/qa/unittests/pcbnew/test_pns_basics.cpp new file mode 100644 index 0000000000..2563bc8fa4 --- /dev/null +++ b/qa/unittests/pcbnew/test_pns_basics.cpp @@ -0,0 +1,348 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2021-2023 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 +#include + +#include +#include + +#include +#include +#include +#include +#include + +static bool isCopper( const PNS::ITEM* aItem ) +{ + if( !aItem ) + return false; + + BOARD_ITEM* parent = aItem->Parent(); + + if ( !parent ) + { + return LSET::AllCuMask().Contains( (PCB_LAYER_ID) aItem->Layer() ); + } + + if( parent && parent->Type() == PCB_PAD_T ) + { + PAD* pad = static_cast( parent ); + return pad->IsOnCopperLayer() && pad->GetAttribute() != PAD_ATTRIB::NPTH; + } + + return true; +} + + +static bool isHole( const PNS::ITEM* aItem ) +{ + if( !aItem ) + return false; + + return aItem->OfKind( PNS::ITEM::HOLE_T ); +} + + +static bool isEdge( const PNS::ITEM* aItem ) +{ + if( !aItem ) + return false; + + const BOARD_ITEM* parent = aItem->Parent(); + + if ( !parent ) + { + return aItem->Layer() == Edge_Cuts; + } + + return parent && ( parent->IsOnLayer( Edge_Cuts ) || parent->IsOnLayer( Margin ) ); +} + + +class MOCK_RULE_RESOLVER : public PNS::RULE_RESOLVER +{ +public: + MOCK_RULE_RESOLVER() : m_clearanceEpsilon( 10 ) + { + } + + virtual ~MOCK_RULE_RESOLVER() {} + + virtual int Clearance( const PNS::ITEM* aA, const PNS::ITEM* aB ) override + { + PNS::CONSTRAINT constraint; + int rv = -1; + int layer; + + if( !aA->Layers().IsMultilayer() || !aB || aB->Layers().IsMultilayer() ) + layer = aA->Layer(); + else + layer = aB->Layer(); + + if( isHole( aA ) && isHole( aB ) ) + { + if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_HOLE_TO_HOLE, aA, aB, layer, + &constraint ) ) + rv = constraint.m_Value.Min(); + } + else if( isHole( aA ) || isHole( aB ) ) + { + if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_HOLE_CLEARANCE, aA, aB, layer, + &constraint ) ) + rv = constraint.m_Value.Min(); + } + else if( isCopper( aA ) && ( !aB || isCopper( aB ) ) ) + { + if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_CLEARANCE, aA, aB, layer, &constraint ) ) + rv = constraint.m_Value.Min(); + } + else if( isEdge( aA ) || ( aB && isEdge( aB ) ) ) + { + if( QueryConstraint( PNS::CONSTRAINT_TYPE::CT_EDGE_CLEARANCE, aA, aB, layer, + &constraint ) ) + { + if( constraint.m_Value.Min() > rv ) + rv = constraint.m_Value.Min(); + } + } + + return rv; + } + + + virtual int DpCoupledNet( int aNet ) override { return -1; } + virtual int DpNetPolarity( int aNet ) override { return -1; } + virtual bool DpNetPair( const PNS::ITEM* aItem, int& aNetP, int& aNetN ) override + { + return false; + } + virtual bool IsDiffPair( const PNS::ITEM* aA, const PNS::ITEM* aB ) override { return false; } + + virtual bool QueryConstraint( PNS::CONSTRAINT_TYPE aType, const PNS::ITEM* aItemA, + const PNS::ITEM* aItemB, int aLayer, + PNS::CONSTRAINT* aConstraint ) override + { + ITEM_KEY key; + + key.a = aItemA; + key.b = aItemB; + key.type = aType; + + auto it = m_ruleMap.find( key ); + if( it == m_ruleMap.end() ) + { + int cl; + switch( aType ) + { + case PNS::CONSTRAINT_TYPE::CT_CLEARANCE: cl = m_defaultClearance; break; + case PNS::CONSTRAINT_TYPE::CT_HOLE_TO_HOLE: cl = m_defaultHole2Hole; break; + case PNS::CONSTRAINT_TYPE::CT_HOLE_CLEARANCE: cl = m_defaultHole2Copper; break; + default: return false; + } + + //printf("GetDef %s %s %d cl %d\n", aItemA->KindStr().c_str(), aItemB->KindStr().c_str(), aType, cl ); + + aConstraint->m_Type = aType; + aConstraint->m_Value.SetMin( cl ); + + return true; + } else { + *aConstraint = it->second; + } + + return true; + } + + virtual wxString NetName( int aNet ) override { return wxT( "noname" ); } + + int ClearanceEpsilon() const override { return m_clearanceEpsilon; } + + struct ITEM_KEY + { + const PNS::ITEM* a = nullptr; + const PNS::ITEM* b = nullptr; + PNS::CONSTRAINT_TYPE type; + + bool operator==( const ITEM_KEY& other ) const + { + return a == other.a && b == other.b && type == other.type; + } + + bool operator<( const ITEM_KEY& other ) const + { + if( a < other.a ) + return true; + else if ( a == other.a ) + { + if( b < other.b ) + return true; + else if ( b == other.b ) + { + return type < other.type; + } + } + + return false; + } + }; + + virtual bool IsInNetTie( const PNS::ITEM* aA ) override { return false; } + virtual bool IsNetTieExclusion( const PNS::ITEM* aItem, const VECTOR2I& aCollisionPos, + const PNS::ITEM* aCollidingItem ) override + { + return false; + } + + void AddMockRule( PNS::CONSTRAINT_TYPE aType, const PNS::ITEM* aItemA, const PNS::ITEM* aItemB, + PNS::CONSTRAINT aConstraint ) + { + ITEM_KEY key; + + key.a = aItemA; + key.b = aItemB; + key.type = aType; + + m_ruleMap[key] = aConstraint; + } + + int m_defaultClearance = 200000; + int m_defaultHole2Hole = 220000; + int m_defaultHole2Copper = 210000; + +private: + std::map m_ruleMap; + + int m_clearanceEpsilon; +}; + +struct PNS_TEST_FIXTURE; + +class MOCK_PNS_KICAD_IFACE : public PNS_KICAD_IFACE_BASE +{ +public: + MOCK_PNS_KICAD_IFACE( PNS_TEST_FIXTURE *aFixture ) : + m_testFixture( aFixture ) {} + ~MOCK_PNS_KICAD_IFACE() {} + + void HideItem( PNS::ITEM* aItem ) override {}; + void DisplayItem( const PNS::ITEM* aItem, int aClearance, bool aEdit = false ) override {}; + PNS::RULE_RESOLVER* GetRuleResolver() override; + +private: + PNS_TEST_FIXTURE* m_testFixture; +}; + + +struct PNS_TEST_FIXTURE +{ + PNS_TEST_FIXTURE() : m_settingsManager( true /* headless */ ) + { + m_router = new PNS::ROUTER; + m_iface = new MOCK_PNS_KICAD_IFACE( this ); + m_router->SetInterface( m_iface ); + } + + SETTINGS_MANAGER m_settingsManager; + PNS::ROUTER* m_router; + MOCK_RULE_RESOLVER m_ruleResolver; + MOCK_PNS_KICAD_IFACE *m_iface; + //std::unique_ptr m_board; +}; + + +PNS::RULE_RESOLVER* MOCK_PNS_KICAD_IFACE::GetRuleResolver() +{ + return &m_testFixture->m_ruleResolver; +} + +static void dumpObstacles( const PNS::NODE::OBSTACLES &obstacles ) +{ + for ( const auto& obs : obstacles ) + { + printf("%p [%s] - %p [%s], clearance %d\n", obs.m_head, obs.m_head->KindStr().c_str(), + obs.m_item, obs.m_item->KindStr().c_str(), + obs.m_clearance ); + } +} + +BOOST_FIXTURE_TEST_CASE( PNSHoleCollisions, PNS_TEST_FIXTURE ) +{ + PNS::VIA* v1 = new PNS::VIA ( VECTOR2I( 0, 1000000 ), LAYER_RANGE( F_Cu, B_Cu ), 500000, 100000 ); + PNS::VIA* v2 = new PNS::VIA ( VECTOR2I( 0, 2000000 ), LAYER_RANGE( F_Cu, B_Cu ), 500000, 100000 ); + + std::unique_ptr world ( new PNS::NODE ); + + world->SetMaxClearance( 10000000 ); + world->SetRuleResolver( &m_ruleResolver ); + + world->AddRaw( v1 ); + world->AddRaw( v2 ); + + BOOST_TEST_MESSAGE( "via to via, no violations" ); + { + PNS::NODE::OBSTACLES obstacles; + int count = world->QueryColliding( v1, obstacles ); + BOOST_CHECK_EQUAL( obstacles.size(), 0 ); + BOOST_CHECK_EQUAL( count, 0 ); + } + + BOOST_TEST_MESSAGE( "via to via, forced copper to copper violation" ); + { + PNS::NODE::OBSTACLES obstacles; + m_ruleResolver.m_defaultClearance = 1000000; + world->QueryColliding( v1, obstacles ); + dumpObstacles( obstacles ); + + BOOST_CHECK_EQUAL( obstacles.size(), 1 ); + const auto first = *obstacles.begin(); + + BOOST_CHECK_EQUAL( first.m_head, v1 ); + BOOST_CHECK_EQUAL( first.m_item, v2 ); + BOOST_CHECK_EQUAL( first.m_clearance, m_ruleResolver.m_defaultClearance ); + } + + BOOST_TEST_MESSAGE( "via to via, forced copper to hole violation" ); + { + PNS::NODE::OBSTACLES obstacles; + m_ruleResolver.m_defaultClearance = 200000; + m_ruleResolver.m_defaultHole2Copper = 1000000; + + world->QueryColliding( v1, obstacles ); + dumpObstacles( obstacles ); + + + + BOOST_CHECK_EQUAL( obstacles.size(), 2 ); + auto iter = obstacles.begin(); + const auto first = *iter++; + const auto second = *iter; + + BOOST_CHECK_EQUAL( first.m_head, v1 ); + BOOST_CHECK_EQUAL( first.m_item, v2 ); + BOOST_CHECK_EQUAL( first.m_clearance, m_ruleResolver.m_defaultHole2Copper ); + } + + +} +