kicad/pcbnew/router/pns_shove.cpp

2047 lines
56 KiB
C++
Raw Normal View History

/*
* 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 <tomasz.wlostowski@cern.ch>
2013-09-26 21:53:54 +00:00
*
* 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.
2013-09-26 21:53:54 +00:00
*
* 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.
2013-09-26 21:53:54 +00:00
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <deque>
#include <cassert>
#include <math/box2.h>
#include <wx/log.h>
#include "pns_arc.h"
#include "pns_line.h"
#include "pns_node.h"
#include "pns_debug_decorator.h"
#include "pns_walkaround.h"
#include "pns_shove.h"
#include "pns_solid.h"
#include "pns_optimizer.h"
#include "pns_via.h"
#include "pns_utils.h"
#include "pns_router.h"
#include "pns_topology.h"
#include "time_limit.h"
// fixme - move all logger calls to debug decorator
typedef VECTOR2I::extended_type ecoord;
namespace PNS {
void SHOVE::replaceItems( ITEM* aOld, std::unique_ptr< ITEM > aNew )
{
OPT_BOX2I changed_area = ChangedArea( aOld, aNew.get() );
if( changed_area )
m_affectedArea = m_affectedArea ? m_affectedArea->Merge( *changed_area ) : *changed_area;
m_currentNode->Replace( aOld, std::move( aNew ) );
}
2021-07-19 23:56:05 +00:00
void SHOVE::replaceLine( LINE& aOld, LINE& aNew, bool aIncludeInChangedArea, NODE* aNode )
{
if( aIncludeInChangedArea )
{
OPT_BOX2I changed_area = ChangedArea( aOld, aNew );
if( changed_area )
{
SHAPE_RECT r( *changed_area );
2023-08-07 21:40:56 +00:00
PNS_DBG( Dbg(), AddShape, &r, BLUE, 0, wxT( "shove-changed-area" ) );
m_affectedArea = m_affectedArea ? m_affectedArea->Merge( *changed_area )
: *changed_area;
}
}
bool foundPredecessor = false;
LINE* rootLine = nullptr;
// Keep track of the 'root lines', i.e. the unmodified (pre-shove) versions
// of the affected tracks in a map. The optimizer can then query the pre-shove shape
2021-07-19 23:56:05 +00:00
// for each shoved line and perform additional constraint checks (i.e. prevent overzealous
// optimizations)
2021-07-19 23:56:05 +00:00
// Check if the shoved line already has an ancestor (e.g. line from a previous shove
// iteration/cursor movement)
for( LINKED_ITEM* link : aOld.Links() )
{
auto oldLineIter = m_rootLineHistory.find( link );
2021-07-19 23:56:05 +00:00
if( oldLineIter != m_rootLineHistory.end() )
{
rootLine = oldLineIter->second;
foundPredecessor = true;
break;
}
}
// If found, use it, otherwise, create new entry in the map (we have a genuine new 'root' line)
if( !foundPredecessor )
{
for( LINKED_ITEM* link : aOld.Links() )
{
if( ! rootLine )
rootLine = aOld.Clone();
2021-07-19 23:56:05 +00:00
m_rootLineHistory[link] = rootLine;
}
}
// Now update the NODE (calling Replace invalidates the Links() in a LINE)
if( aNode )
aNode->Replace( aOld, aNew );
else
m_currentNode->Replace( aOld, aNew );
// point the Links() of the new line to its oldest ancestor
for( LINKED_ITEM* link : aNew.Links() )
m_rootLineHistory[ link ] = rootLine;
}
2021-07-19 23:56:05 +00:00
int SHOVE::getClearance( const ITEM* aA, const ITEM* aB ) const
{
2015-02-18 16:53:46 +00:00
if( m_forceClearance >= 0 )
return m_forceClearance;
int clearance = m_currentNode->GetClearance( aA, aB, false );
if( aA->HasHole() )
clearance = std::max( clearance, m_currentNode->GetClearance( aA->Hole(), aB, false ) );
if( aB->HasHole() )
clearance = std::max( clearance, m_currentNode->GetClearance( aA, aB->Hole(), false ) );
return clearance;
}
2021-07-19 23:56:05 +00:00
void SHOVE::sanityCheck( LINE* aOld, LINE* aNew )
{
assert( aOld->CPoint( 0 ) == aNew->CPoint( 0 ) );
assert( aOld->CPoint( -1 ) == aNew->CPoint( -1 ) );
}
SHOVE::SHOVE( NODE* aWorld, ROUTER* aRouter ) :
ALGO_BASE( aRouter )
{
m_optFlagDisableMask = 0;
m_forceClearance = -1;
2013-09-26 21:53:54 +00:00
m_root = aWorld;
m_currentNode = aWorld;
SetDebugDecorator( aRouter->GetInterface()->GetDebugDecorator() );
// Initialize other temporary variables:
2021-07-19 23:56:05 +00:00
m_draggedVia = nullptr;
m_iter = 0;
m_multiLineMode = false;
2020-03-30 07:24:56 +00:00
m_restrictSpringbackTagId = 0;
m_springbackDoNotTouchNode = nullptr;
}
2013-09-26 21:53:54 +00:00
SHOVE::~SHOVE()
{
std::unordered_set<LINE*> alreadyDeleted;
2021-07-19 23:56:05 +00:00
for( auto& it : m_rootLineHistory )
{
auto it2 = alreadyDeleted.find( it.second );
2021-07-19 23:56:05 +00:00
if( it2 == alreadyDeleted.end() )
{
alreadyDeleted.insert( it.second );
delete it.second;
}
}
}
LINE SHOVE::assembleLine( const LINKED_ITEM* aSeg, int* aIndex )
2013-09-26 21:53:54 +00:00
{
return m_currentNode->AssembleLine( const_cast<LINKED_ITEM*>( aSeg ), aIndex, true );
}
2013-09-26 21:53:54 +00:00
2021-07-19 23:56:05 +00:00
2021-01-05 16:40:34 +00:00
// A dumb function that checks if the shoved line is shoved the right way, e.g. visually
// "outwards" of the line/via applying pressure on it. Unfortunately there's no mathematical
// concept of orientation of an open curve, so we use some primitive heuristics: if the shoved
// line wraps around the start of the "pusher", it's likely shoved in wrong direction.
2013-09-26 21:53:54 +00:00
2021-07-19 23:56:05 +00:00
// Update: there's no concept of an orientation of an open curve, but nonetheless Tom's dumb
// as.... (censored)
// Two open curves put together make a closed polygon... Tom should learn high school geometry!
2021-01-05 16:40:34 +00:00
bool SHOVE::checkShoveDirection( const LINE& aCurLine, const LINE& aObstacleLine,
const LINE& aShovedLine ) const
{
2021-01-05 16:40:34 +00:00
SHAPE_LINE_CHAIN::POINT_INSIDE_TRACKER checker( aCurLine.CPoint( 0) );
checker.AddPolyline( aObstacleLine.CLine() );
checker.AddPolyline( aShovedLine.CLine().Reverse() );
bool inside = checker.IsInside();
2014-11-14 19:19:00 +00:00
return !inside;
}
2021-01-05 16:40:34 +00:00
/*
* Push aObstacleLine away from aCurLine's via by the clearance distance, returning the result
* in aResultLine.
*
* Must be called only when aCurLine itself is on another layer (or has no segments) so that it
* can be ignored.
*/
SHOVE::SHOVE_STATUS SHOVE::shoveLineFromLoneVia( const LINE& aCurLine, const LINE& aObstacleLine,
LINE& aResultLine )
{
2021-01-05 16:40:34 +00:00
// Build a hull for aCurLine's via and re-walk aObstacleLine around it.
int obstacleLineWidth = aObstacleLine.Width();
const VIA& via = aCurLine.Via();
int clearance = getClearance( &via, &aObstacleLine );
HOLE* viaHole = via.Hole();
int holeClearance = getClearance( viaHole, &aObstacleLine );
if( holeClearance + via.Drill() / 2 > clearance + via.Diameter() / 2 )
clearance = holeClearance + via.Drill() / 2 - via.Diameter() / 2;
2021-01-05 16:40:34 +00:00
SHAPE_LINE_CHAIN hull = aCurLine.Via().Hull( clearance, obstacleLineWidth, aCurLine.Layer() );
SHAPE_LINE_CHAIN path_cw;
SHAPE_LINE_CHAIN path_ccw;
2013-09-26 21:53:54 +00:00
2021-01-05 16:40:34 +00:00
if( ! aObstacleLine.Walkaround( hull, path_cw, true ) )
return SH_INCOMPLETE;
2021-01-05 16:40:34 +00:00
if( ! aObstacleLine.Walkaround( hull, path_ccw, false ) )
return SH_INCOMPLETE;
const SHAPE_LINE_CHAIN& shortest = path_ccw.Length() < path_cw.Length() ? path_ccw : path_cw;
if( shortest.PointCount() < 2 )
return SH_INCOMPLETE;
2021-01-05 16:40:34 +00:00
if( aObstacleLine.CPoint( -1 ) != shortest.CPoint( -1 ) )
return SH_INCOMPLETE;
2021-01-05 16:40:34 +00:00
if( aObstacleLine.CPoint( 0 ) != shortest.CPoint( 0 ) )
return SH_INCOMPLETE;
2021-01-05 16:40:34 +00:00
aResultLine.SetShape( shortest );
2013-09-26 21:53:54 +00:00
2021-01-05 16:40:34 +00:00
if( aResultLine.Collide( &aCurLine, m_currentNode ) )
return SH_INCOMPLETE;
2013-09-26 21:53:54 +00:00
return SH_OK;
}
/*
2021-01-05 16:40:34 +00:00
* Re-walk aObstacleLine around the given set of hulls, returning the result in aResultLine.
*/
2021-01-05 16:40:34 +00:00
SHOVE::SHOVE_STATUS SHOVE::shoveLineToHullSet( const LINE& aCurLine, const LINE& aObstacleLine,
LINE& aResultLine, const HULL_SET& aHulls )
{
2021-01-05 16:40:34 +00:00
const SHAPE_LINE_CHAIN& obs = aObstacleLine.CLine();
int attempt;
2013-09-26 21:53:54 +00:00
PNS_DBG( Dbg(), BeginGroup, "shove-details", 1 );
for( attempt = 0; attempt < 4; attempt++ )
{
bool invertTraversal = ( attempt >= 2 );
bool clockwise = attempt % 2;
int vFirst = -1, vLast = -1;
2013-09-26 21:53:54 +00:00
2021-01-05 16:40:34 +00:00
LINE l( aObstacleLine );
2021-06-03 20:35:42 +00:00
SHAPE_LINE_CHAIN path( l.CLine() );
2013-09-26 21:53:54 +00:00
for( int i = 0; i < (int) aHulls.size(); i++ )
{
const SHAPE_LINE_CHAIN& hull = aHulls[invertTraversal ? aHulls.size() - 1 - i : i];
2013-09-26 21:53:54 +00:00
PNS_DBG( Dbg(), AddShape, &hull, YELLOW, 10000, wxString::Format( "hull[%d]", i ) );
PNS_DBG( Dbg(), AddShape, &path, WHITE, l.Width(), wxString::Format( "path[%d]", i ) );
PNS_DBG( Dbg(), AddShape, &obs, LIGHTGRAY, aObstacleLine.Width(), wxString::Format( "obs[%d]", i ) );
2021-07-19 23:56:05 +00:00
if( !l.Walkaround( hull, path, clockwise ) )
{
2022-02-05 02:06:25 +00:00
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "Fail-Walk %s %s %d\n" ),
2021-07-19 23:56:05 +00:00
hull.Format().c_str(),
l.CLine().Format().c_str(),
clockwise? 1 : 0) );
PNS_DBGN( Dbg(), EndGroup );
return SH_INCOMPLETE;
}
PNS_DBG( Dbg(), AddShape, &path, WHITE, l.Width(), wxString::Format( "path-presimp[%d]", i ) );
path.Simplify();
PNS_DBG( Dbg(), AddShape, &path, WHITE, l.Width(), wxString::Format( "path-postsimp[%d]", i ) );
l.SetShape( path );
}
2014-11-14 19:19:00 +00:00
2016-08-15 15:16:53 +00:00
for( int i = 0; i < std::min( path.PointCount(), obs.PointCount() ); i++ )
2013-09-26 21:53:54 +00:00
{
if( path.CPoint( i ) != obs.CPoint( i ) )
{
vFirst = i;
break;
}
2013-09-26 21:53:54 +00:00
}
int k = obs.PointCount() - 1;
2021-07-19 23:56:05 +00:00
for( int i = path.PointCount() - 1; i >= 0 && k >= 0; i--, k-- )
{
if( path.CPoint( i ) != obs.CPoint( k ) )
{
vLast = i;
break;
}
}
2013-09-26 21:53:54 +00:00
2021-01-05 16:40:34 +00:00
if( ( vFirst < 0 || vLast < 0 ) && !path.CompareGeometry( aObstacleLine.CLine() ) )
{
2022-02-05 02:06:25 +00:00
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "attempt %d fail vfirst-last" ),
attempt ) );
continue;
}
2013-09-26 21:53:54 +00:00
if( path.CPoint( -1 ) != obs.CPoint( -1 ) || path.CPoint( 0 ) != obs.CPoint( 0 ) )
{
2022-02-05 02:06:25 +00:00
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "attempt %d fail vend-start\n" ),
attempt ) );
continue;
}
2013-09-26 21:53:54 +00:00
2021-01-05 16:40:34 +00:00
if( !checkShoveDirection( aCurLine, aObstacleLine, l ) )
{
2022-02-05 02:06:25 +00:00
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "attempt %d fail direction-check" ),
2021-07-19 23:56:05 +00:00
attempt ) );
2021-01-05 16:40:34 +00:00
aResultLine.SetShape( l.CLine() );
continue;
}
2013-09-26 21:53:54 +00:00
if( path.SelfIntersecting() )
{
2022-02-05 02:06:25 +00:00
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "attempt %d fail self-intersect" ),
2021-07-19 23:56:05 +00:00
attempt ) );
continue;
}
2014-11-14 19:19:00 +00:00
2021-01-05 16:40:34 +00:00
bool colliding = l.Collide( &aCurLine, m_currentNode );
2014-11-14 19:19:00 +00:00
2021-01-05 16:40:34 +00:00
if(( aCurLine.Marker() & MK_HEAD ) && !colliding )
{
const JOINT* jtStart = m_currentNode->FindJoint( aCurLine.CPoint( 0 ), &aCurLine );
2014-11-14 19:19:00 +00:00
for( ITEM* item : jtStart->LinkList() )
{
if( item->Collide( &l, m_currentNode ) )
colliding = true;
}
}
2014-11-14 19:19:00 +00:00
if( colliding )
{
2022-02-05 02:06:25 +00:00
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "attempt %d fail coll-check" ),
attempt ) );
continue;
}
2013-09-26 21:53:54 +00:00
2021-01-05 16:40:34 +00:00
aResultLine.SetShape( l.CLine() );
2014-11-14 19:19:00 +00:00
PNS_DBGN( Dbg(), EndGroup );
return SH_OK;
}
2013-09-26 21:53:54 +00:00
PNS_DBGN( Dbg(), EndGroup );
return SH_INCOMPLETE;
}
2013-09-26 21:53:54 +00:00
/*
2021-01-05 16:40:34 +00:00
* Push aObstacleLine line away from aCurLine by the clearance distance, and return the result in
* aResultLine.
*/
2021-01-05 16:40:34 +00:00
SHOVE::SHOVE_STATUS SHOVE::ShoveObstacleLine( const LINE& aCurLine, const LINE& aObstacleLine,
LINE& aResultLine )
{
2021-01-05 16:40:34 +00:00
aResultLine.ClearLinks();
2013-09-26 21:53:54 +00:00
bool obstacleIsHead = false;
2013-09-26 21:53:54 +00:00
2021-01-05 16:40:34 +00:00
for( LINKED_ITEM* s : aObstacleLine.Links() )
{
if( s->Marker() & MK_HEAD )
{
obstacleIsHead = true;
2013-09-26 21:53:54 +00:00
break;
}
}
SHOVE_STATUS rv;
2021-01-05 16:40:34 +00:00
bool viaOnEnd = aCurLine.EndsWithVia();
2013-09-26 21:53:54 +00:00
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "shove process-single: voe1 %d voe2 %d" ),
aCurLine.EndsWithVia()?1:0, aObstacleLine.EndsWithVia()?1:0 ) );
if( viaOnEnd && ( !aCurLine.LayersOverlap( &aObstacleLine ) || aCurLine.SegmentCount() == 0 ) )
{
2021-01-05 16:40:34 +00:00
// Shove aObstacleLine to the hull of aCurLine's via.
rv = shoveLineFromLoneVia( aCurLine, aObstacleLine, aResultLine );
}
else
{
2021-01-05 16:40:34 +00:00
// Build a set of hulls around the segments of aCurLine. Hulls are at the clearance
// distance + aObstacleLine's linewidth so that when re-walking aObstacleLine along the
// hull it will be at the appropriate clearance.
int obstacleLineWidth = aObstacleLine.Width();
int clearance = getClearance( &aCurLine, &aObstacleLine );
2021-01-05 16:40:34 +00:00
int currentLineSegmentCount = aCurLine.SegmentCount();
HULL_SET hulls;
2021-01-05 16:40:34 +00:00
hulls.reserve( currentLineSegmentCount + 1 );
2013-09-26 21:53:54 +00:00
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "shove process-single: cur net %d obs %d cl %d" ),
m_router->GetInterface()->GetNetCode( aCurLine.Net() ),
m_router->GetInterface()->GetNetCode( aObstacleLine.Net() ),
clearance ) );
2021-01-05 16:40:34 +00:00
for( int i = 0; i < currentLineSegmentCount; i++ )
2013-09-26 21:53:54 +00:00
{
2021-01-05 16:40:34 +00:00
SEGMENT seg( aCurLine, aCurLine.CSegment( i ) );
// Arcs need additional clearance to ensure the hulls are always bigger than the arc
if( aCurLine.CLine().IsArcSegment( i ) )
{
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "shove add-extra-clearance %d" ),
SHAPE_ARC::DefaultAccuracyForPCB() ) );
clearance += KiROUND( SHAPE_ARC::DefaultAccuracyForPCB() );
}
SHAPE_LINE_CHAIN hull = seg.Hull( clearance, obstacleLineWidth, aObstacleLine.Layer() );
hulls.push_back( hull );
2013-09-26 21:53:54 +00:00
}
if( viaOnEnd )
{
2021-01-05 16:40:34 +00:00
const VIA& via = aCurLine.Via();
int viaClearance = getClearance( &via, &aObstacleLine );
HOLE* viaHole = via.Hole();
int holeClearance = getClearance( viaHole, &aObstacleLine );
if( holeClearance + via.Drill() / 2 > viaClearance + via.Diameter() / 2 )
viaClearance = holeClearance + via.Drill() / 2 - via.Diameter() / 2;
2021-01-05 16:40:34 +00:00
hulls.push_back( aCurLine.Via().Hull( viaClearance, obstacleLineWidth ) );
}
2013-09-26 21:53:54 +00:00
2021-01-05 16:40:34 +00:00
rv = shoveLineToHullSet( aCurLine, aObstacleLine, aResultLine, hulls );
}
2014-11-14 19:19:00 +00:00
if( obstacleIsHead )
2021-01-05 16:40:34 +00:00
aResultLine.Mark( aResultLine.Marker() | MK_HEAD );
2013-09-26 21:53:54 +00:00
return rv;
}
2013-09-26 21:53:54 +00:00
/*
* TODO describe....
*/
SHOVE::SHOVE_STATUS SHOVE::onCollidingSegment( LINE& aCurrent, SEGMENT* aObstacleSeg )
{
int segIndex;
LINE obstacleLine = assembleLine( aObstacleSeg, &segIndex );
LINE shovedLine( obstacleLine );
SEGMENT tmp( *aObstacleSeg );
2014-11-14 19:19:00 +00:00
2016-08-15 15:16:48 +00:00
if( obstacleLine.HasLockedSegments() )
{
PNS_DBG(Dbg(), Message, "try walk (locked segments)");
2016-08-15 15:16:48 +00:00
return SH_TRY_WALK;
}
2016-08-15 15:16:48 +00:00
2021-01-05 16:40:34 +00:00
SHOVE_STATUS rv = ShoveObstacleLine( aCurrent, obstacleLine, shovedLine );
2013-09-26 21:53:54 +00:00
const double extensionWalkThreshold = 1.0;
double obsLen = obstacleLine.CLine().Length();
double shovedLen = shovedLine.CLine().Length();
double extensionFactor = 0.0;
if( obsLen != 0.0f )
extensionFactor = shovedLen / obsLen - 1.0;
if( extensionFactor > extensionWalkThreshold )
return SH_TRY_WALK;
assert( obstacleLine.LayersOverlap( &shovedLine ) );
if( Dbg() )
{
PNS_DBG( Dbg(), AddItem, aObstacleSeg, BLUE, 0, wxT( "shove-changed-area" ) );
PNS_DBG( Dbg(), AddItem, &aCurrent, RED, 10000, wxString::Format( "current-line [l %d v %d]", aCurrent.Layer(), aCurrent.EndsWithVia() ) );
PNS_DBG( Dbg(), AddItem, &obstacleLine, GREEN, 10000, wxString::Format( "obstacle-line [l %d v %d]", obstacleLine.Layer(), obstacleLine.EndsWithVia() ) );
PNS_DBG( Dbg(), AddItem, &shovedLine, BLUE, 10000, wxT( "shoved-line" ) );
}
2014-11-14 19:19:00 +00:00
if( rv == SH_OK )
{
if( shovedLine.Marker() & MK_HEAD )
{
if( m_multiLineMode )
return SH_INCOMPLETE;
2015-02-18 16:53:46 +00:00
m_newHead = shovedLine;
}
2014-11-14 19:19:00 +00:00
int rank = aCurrent.Rank();
shovedLine.SetRank( rank - 1 );
sanityCheck( &obstacleLine, &shovedLine );
replaceLine( obstacleLine, shovedLine );
if( !pushLineStack( shovedLine ) )
2015-07-02 14:09:32 +00:00
rv = SH_INCOMPLETE;
2013-09-26 21:53:54 +00:00
}
return rv;
}
/*
* TODO describe....
*/
SHOVE::SHOVE_STATUS SHOVE::onCollidingArc( LINE& aCurrent, ARC* aObstacleArc )
{
int segIndex;
LINE obstacleLine = assembleLine( aObstacleArc, &segIndex );
LINE shovedLine( obstacleLine );
ARC tmp( *aObstacleArc );
if( obstacleLine.HasLockedSegments() )
return SH_TRY_WALK;
2021-01-05 16:40:34 +00:00
SHOVE_STATUS rv = ShoveObstacleLine( aCurrent, obstacleLine, shovedLine );
const double extensionWalkThreshold = 1.0;
double obsLen = obstacleLine.CLine().Length();
double shovedLen = shovedLine.CLine().Length();
double extensionFactor = 0.0;
if( obsLen != 0.0f )
extensionFactor = shovedLen / obsLen - 1.0;
if( extensionFactor > extensionWalkThreshold )
return SH_TRY_WALK;
assert( obstacleLine.LayersOverlap( &shovedLine ) );
PNS_DBG( Dbg(), AddItem, &tmp, WHITE, 10000, wxT( "obstacle-arc" ) );
PNS_DBG( Dbg(), AddItem, &aCurrent, RED, 10000, wxT( "current-line" ) );
PNS_DBG( Dbg(), AddItem, &obstacleLine, GREEN, 10000, wxT( "obstacle-line" ) );
PNS_DBG( Dbg(), AddItem, &shovedLine, BLUE, 10000, wxT( "shoved-line" ) );
if( rv == SH_OK )
{
if( shovedLine.Marker() & MK_HEAD )
{
if( m_multiLineMode )
return SH_INCOMPLETE;
m_newHead = shovedLine;
}
int rank = aCurrent.Rank();
shovedLine.SetRank( rank - 1 );
sanityCheck( &obstacleLine, &shovedLine );
replaceLine( obstacleLine, shovedLine );
if( !pushLineStack( shovedLine ) )
rv = SH_INCOMPLETE;
}
return rv;
}
/*
* TODO describe....
*/
SHOVE::SHOVE_STATUS SHOVE::onCollidingLine( LINE& aCurrent, LINE& aObstacle )
{
LINE shovedLine( aObstacle );
2021-01-05 16:40:34 +00:00
SHOVE_STATUS rv = ShoveObstacleLine( aCurrent, aObstacle, shovedLine );
PNS_DBG( Dbg(), AddItem, &aObstacle, RED, 100000, wxT( "obstacle-line" ) );
PNS_DBG( Dbg(), AddItem, &aCurrent, GREEN, 150000, wxT( "current-line" ) );
PNS_DBG( Dbg(), AddItem, &shovedLine, BLUE, 200000, wxT( "shoved-line" ) );
if( rv == SH_OK )
2013-09-26 21:53:54 +00:00
{
if( shovedLine.Marker() & MK_HEAD )
{
2015-02-18 16:53:46 +00:00
if( m_multiLineMode )
return SH_INCOMPLETE;
m_newHead = shovedLine;
}
sanityCheck( &aObstacle, &shovedLine );
replaceLine( aObstacle, shovedLine );
int rank = aObstacle.Rank();
shovedLine.SetRank( rank - 1 );
2014-11-14 19:19:00 +00:00
if( !pushLineStack( shovedLine ) )
{
2015-07-02 14:09:32 +00:00
rv = SH_INCOMPLETE;
}
2013-09-26 21:53:54 +00:00
}
return rv;
}
/*
* TODO describe....
*/
SHOVE::SHOVE_STATUS SHOVE::onCollidingSolid( LINE& aCurrent, ITEM* aObstacle, OBSTACLE& aObstacleInfo )
{
WALKAROUND walkaround( m_currentNode, Router() );
LINE walkaroundLine( aCurrent );
2014-11-14 19:19:00 +00:00
if( aCurrent.EndsWithVia() )
{
VIA vh = aCurrent.Via();
2021-07-19 23:56:05 +00:00
VIA* via = nullptr;
const JOINT* jtStart = m_currentNode->FindJoint( vh.Pos(), &aCurrent );
if( !jtStart )
return SH_INCOMPLETE;
for( ITEM* item : jtStart->LinkList() )
{
if( item->OfKind( ITEM::VIA_T ) )
{
via = (VIA*) item;
break;
}
}
2014-11-14 19:19:00 +00:00
if( via && via->Collide( aObstacle, m_currentNode ) )
return onCollidingVia( aObstacle, via, aObstacleInfo );
}
TOPOLOGY topo( m_currentNode );
2014-11-14 19:19:00 +00:00
std::set<ITEM*> cluster = topo.AssembleCluster( aObstacle, aCurrent.Layers().Start() );
PNS_DBG( Dbg(), BeginGroup, "walk-cluster", 1 );
for( ITEM* item : cluster )
PNS_DBG( Dbg(), AddItem, item, RED, 10000, wxT( "cl-item" ) );
PNS_DBGN( Dbg(), EndGroup );
walkaround.SetDebugDecorator( Dbg() );
walkaround.SetSolidsOnly( false );
walkaround.RestrictToSet( true, cluster );
walkaround.SetIterationLimit( 16 ); // fixme: make configurable
int currentRank = aCurrent.Rank();
int nextRank;
2016-08-15 15:16:48 +00:00
bool success = false;
for( int attempt = 0; attempt < 2; attempt++ )
{
if( attempt == 1 || Settings().JumpOverObstacles() )
nextRank = currentRank - 1;
else
nextRank = currentRank + 10000;
WALKAROUND::WALKAROUND_STATUS status = walkaround.Route( aCurrent, walkaroundLine, false );
2016-08-15 15:16:48 +00:00
if( status != WALKAROUND::DONE )
2016-08-15 15:16:48 +00:00
continue;
walkaroundLine.ClearLinks();
walkaroundLine.Unmark();
walkaroundLine.Line().Simplify();
if( walkaroundLine.HasLoops() )
2016-08-15 15:16:48 +00:00
continue;
if( aCurrent.Marker() & MK_HEAD )
{
walkaroundLine.Mark( MK_HEAD );
if( m_multiLineMode )
2016-08-15 15:16:48 +00:00
continue;
m_newHead = walkaroundLine;
}
sanityCheck( &aCurrent, &walkaroundLine );
if( !m_lineStack.empty() )
{
LINE lastLine = m_lineStack.front();
if( lastLine.Collide( &walkaroundLine, m_currentNode ) )
{
LINE dummy( lastLine );
2021-01-05 16:40:34 +00:00
if( ShoveObstacleLine( walkaroundLine, lastLine, dummy ) == SH_OK )
2016-08-15 15:16:48 +00:00
{
success = true;
break;
2016-08-15 15:16:48 +00:00
}
2021-07-19 23:56:05 +00:00
}
else
{
2016-08-15 15:16:48 +00:00
success = true;
break;
2016-08-15 15:16:48 +00:00
}
}
}
2014-11-14 19:19:00 +00:00
2016-08-15 15:16:48 +00:00
if(!success)
return SH_INCOMPLETE;
replaceLine( aCurrent, walkaroundLine );
walkaroundLine.SetRank( nextRank );
2014-11-14 19:19:00 +00:00
PNS_DBG( Dbg(), AddItem, &aCurrent, RED, 10000, wxT( "current-line" ) );
PNS_DBG( Dbg(), AddItem, &walkaroundLine, BLUE, 10000, wxT( "walk-line" ) );
popLineStack();
2015-07-02 14:11:15 +00:00
if( !pushLineStack( walkaroundLine ) )
2015-07-02 14:09:32 +00:00
return SH_INCOMPLETE;
return SH_OK;
}
2013-09-26 21:53:54 +00:00
/*
* Pops NODE stackframes which no longer collide with aHeadSet. Optionally sets aDraggedVia
* to the dragged via of the last unpopped state.
*/
NODE* SHOVE::reduceSpringback( const ITEM_SET& aHeadSet, VIA_HANDLE& aDraggedVia )
{
2013-09-26 21:53:54 +00:00
while( !m_nodeStack.empty() )
{
SPRINGBACK_TAG& spTag = m_nodeStack.back();
2013-09-26 21:53:54 +00:00
// Prevent the springback algo from erasing NODEs that might contain items used by the ROUTER_TOOL/LINE_PLACER.
// I noticed this can happen for the endItem provided to LINE_PLACER::Move() and cause a nasty crash.
if( spTag.m_node == m_springbackDoNotTouchNode )
break;
std::optional<OBSTACLE> obs = spTag.m_node->CheckColliding( aHeadSet );
if( !obs && !spTag.m_locked )
2013-09-26 21:53:54 +00:00
{
aDraggedVia = spTag.m_draggedVia;
aDraggedVia.valid = true;
2014-11-14 19:19:00 +00:00
delete spTag.m_node;
2013-09-26 21:53:54 +00:00
m_nodeStack.pop_back();
}
else
2021-07-19 23:56:05 +00:00
{
break;
2021-07-19 23:56:05 +00:00
}
2013-09-26 21:53:54 +00:00
}
return m_nodeStack.empty() ? m_root : m_nodeStack.back().m_node;
}
2013-09-26 21:53:54 +00:00
/*
* Push the current NODE on to the stack. aDraggedVia is the dragged via *before* the push
* (which will be restored in the event the stackframe is popped).
*/
bool SHOVE::pushSpringback( NODE* aNode, const OPT_BOX2I& aAffectedArea, VIA* aDraggedVia )
{
SPRINGBACK_TAG st;
OPT_BOX2I prev_area;
2015-02-18 16:53:46 +00:00
if( !m_nodeStack.empty() )
prev_area = m_nodeStack.back().m_affectedArea;
2013-09-26 21:53:54 +00:00
if( aDraggedVia )
{
st.m_draggedVia = aDraggedVia->MakeHandle();
}
st.m_node = aNode;
if( aAffectedArea )
{
if( prev_area )
2016-08-15 15:16:53 +00:00
st.m_affectedArea = prev_area->Merge( *aAffectedArea );
else
st.m_affectedArea = aAffectedArea;
2021-07-19 23:56:05 +00:00
}
else
{
st.m_affectedArea = prev_area;
2021-07-19 23:56:05 +00:00
}
2021-07-19 23:56:05 +00:00
st.m_seq = ( m_nodeStack.empty() ? 1 : m_nodeStack.back().m_seq + 1 );
st.m_locked = false;
2020-03-30 07:24:56 +00:00
2013-09-26 21:53:54 +00:00
m_nodeStack.push_back( st );
2013-09-26 21:53:54 +00:00
return true;
}
2013-09-26 21:53:54 +00:00
/*
* Push or shove a via by at least aForce. (The via might be pushed or shoved slightly further
* to keep it from landing on an existing joint.)
*/
SHOVE::SHOVE_STATUS SHOVE::pushOrShoveVia( VIA* aVia, const VECTOR2I& aForce, int aCurrentRank )
{
LINE_PAIR_VEC draggedLines;
2015-02-18 16:53:46 +00:00
VECTOR2I p0( aVia->Pos() );
const JOINT* jt = m_currentNode->FindJoint( p0, aVia );
VECTOR2I p0_pushed( p0 + aForce );
2022-02-05 02:06:25 +00:00
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "via force [%d %d]\n" ), aForce.x, aForce.y ) );
2021-06-03 20:35:42 +00:00
// nothing to do...
if ( aForce.x == 0 && aForce.y == 0 )
return SH_OK;
2015-08-07 15:07:06 +00:00
if( !jt )
{
PNS_DBG( Dbg(), Message, wxT( "weird, can't find the center-of-via joint\n" ) );
2015-08-07 15:07:06 +00:00
return SH_INCOMPLETE;
}
if( Settings().ShoveVias() == false || aVia->IsLocked() )
return SH_TRY_WALK;
if( jt->IsLocked() )
return SH_INCOMPLETE;
// make sure pushed via does not overlap with any existing joint
while( true )
{
const JOINT* jt_next = m_currentNode->FindJoint( p0_pushed, aVia );
2015-02-18 16:53:46 +00:00
if( !jt_next )
break;
2015-02-18 16:53:46 +00:00
p0_pushed += aForce.Resize( 2 );
}
2015-02-18 16:53:46 +00:00
std::unique_ptr<VIA> pushedVia = Clone( *aVia );
pushedVia->SetPos( p0_pushed );
pushedVia->Mark( aVia->Marker() );
2013-09-26 21:53:54 +00:00
for( ITEM* item : jt->LinkList() )
{
if( item->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T ) )
{
LINKED_ITEM* li = static_cast<LINKED_ITEM*>( item );
LINE_PAIR lp;
int segIndex;
2014-11-14 19:19:00 +00:00
lp.first = assembleLine( li, &segIndex );
2014-11-14 19:19:00 +00:00
2016-08-15 15:16:48 +00:00
if( lp.first.HasLockedSegments() )
return SH_TRY_WALK;
assert( segIndex == 0 || ( segIndex == ( lp.first.SegmentCount() - 1 ) ) );
if( segIndex == 0 )
lp.first.Reverse();
lp.second = lp.first;
lp.second.ClearLinks();
lp.second.DragCorner( p0_pushed, lp.second.CLine().Find( p0 ) );
lp.second.AppendVia( *pushedVia );
draggedLines.push_back( lp );
}
}
pushedVia->SetRank( aCurrentRank - 1 );
PNS_DBG( Dbg(), Message, wxString::Format("PushViaRank %d\n", pushedVia->Rank() ) );
if( aVia->Marker() & MK_HEAD ) // push
{
m_draggedVia = pushedVia.get();
}
else
{ // shove
if( jt->IsStitchingVia() )
pushLineStack( LINE( *pushedVia ) );
}
2021-06-03 20:35:42 +00:00
PNS_DBG( Dbg(), AddPoint, aVia->Pos(), LIGHTGREEN, 100000, "via-pre" );
PNS_DBG( Dbg(), AddPoint, pushedVia->Pos(), LIGHTRED, 100000, "via-post" );
replaceItems( aVia, std::move( pushedVia ) );
for( LINE_PAIR lp : draggedLines )
2013-09-26 21:53:54 +00:00
{
if( lp.first.Marker() & MK_HEAD )
{
lp.second.Mark( MK_HEAD );
2015-02-18 16:53:46 +00:00
2015-09-30 09:18:19 +00:00
if( m_multiLineMode )
return SH_INCOMPLETE;
m_newHead = lp.second;
}
unwindLineStack( &lp.first );
if( lp.second.SegmentCount() )
{
replaceLine( lp.first, lp.second );
lp.second.SetRank( aCurrentRank - 1 );
2015-07-02 14:11:15 +00:00
PNS_DBG( Dbg(), Message, wxString::Format("PushViaF %p %d\n", &lp.second, lp.second.SegmentCount() ) );
if( !pushLineStack( lp.second, true ) )
2015-07-02 14:09:32 +00:00
return SH_INCOMPLETE;
}
else
{
m_currentNode->Remove( lp.first );
}
PNS_DBG( Dbg(), AddItem, &lp.first, LIGHTGREEN, 10000, wxT( "fan-pre" ) );
PNS_DBG( Dbg(), AddItem, &lp.second, LIGHTRED, 10000, wxT( "fan-post" ) );
2013-09-26 21:53:54 +00:00
}
return SH_OK;
}
/*
* Calculate the minimum translation vector required to resolve a collision with a via and
* shove the via by that distance.
*/
SHOVE::SHOVE_STATUS SHOVE::onCollidingVia( ITEM* aCurrent, VIA* aObstacleVia, OBSTACLE& aObstacleInfo )
{
assert( aObstacleVia );
int clearance = getClearance( aCurrent, aObstacleVia );
VECTOR2I mtv;
int rank = -1;
2021-04-18 22:43:30 +00:00
bool lineCollision = false;
bool viaCollision = false;
VECTOR2I mtvLine, mtvVia;
PNS_DBG( Dbg(), BeginGroup, "push-via-by-line", 1 );
if( aCurrent->OfKind( ITEM::LINE_T ) )
{
VIA vtmp ( *aObstacleVia );
if( aObstacleInfo.m_maxFanoutWidth > 0 && aObstacleInfo.m_maxFanoutWidth > aObstacleVia->Diameter() )
{
vtmp.SetDiameter( aObstacleInfo.m_maxFanoutWidth );
}
LINE* currentLine = (LINE*) aCurrent;
PNS_DBG( Dbg(), AddItem, currentLine, LIGHTRED, 10000, wxT( "current-line" ) );
2023-08-07 21:40:56 +00:00
if( currentLine->EndsWithVia() )
{
PNS_DBG( Dbg(), AddItem, &currentLine->Via(), LIGHTRED, 10000, wxT( "current-line-via" ) );
}
2023-08-07 21:40:56 +00:00
PNS_DBG( Dbg(), AddItem, &vtmp, LIGHTRED, 100000, wxT( "orig-via" ) );
2013-09-26 21:53:54 +00:00
lineCollision = vtmp.Shape()->Collide( currentLine->Shape(),
2021-07-19 23:56:05 +00:00
clearance + currentLine->Width() / 2,
&mtvLine );
2021-04-18 22:43:30 +00:00
// Check the via if present. Via takes priority.
if( currentLine->EndsWithVia() )
{
const VIA& currentVia = currentLine->Via();
int viaClearance = getClearance( &currentVia, &vtmp );
2021-04-18 22:43:30 +00:00
viaCollision = currentVia.Shape()->Collide( vtmp.Shape(), viaClearance, &mtvVia );
}
}
else if( aCurrent->OfKind( ITEM::SOLID_T ) )
2013-09-26 21:53:54 +00:00
{
2021-04-18 22:43:30 +00:00
// TODO: is this possible at all? We don't shove solids.
return SH_INCOMPLETE;
}
2021-04-18 22:43:30 +00:00
// fixme: we may have a sign issue in Collide(CIRCLE, LINE_CHAIN)
if( viaCollision )
mtv = -mtvVia;
2021-04-18 22:43:30 +00:00
else if ( lineCollision )
mtv = -mtvLine;
else
mtv = VECTOR2I(0, 0);
SHOVE::SHOVE_STATUS st = pushOrShoveVia( aObstacleVia, -mtv, aCurrent->Rank() );
2023-08-07 21:40:56 +00:00
PNS_DBG( Dbg(), Message, wxString::Format("push-or-shove-via st %d", st ) );
2021-06-03 20:35:42 +00:00
PNS_DBGN( Dbg(), EndGroup );
return st;
}
/*
* TODO describe....
*/
SHOVE::SHOVE_STATUS SHOVE::onReverseCollidingVia( LINE& aCurrent, VIA* aObstacleVia )
{
int n = 0;
LINE cur( aCurrent );
cur.ClearLinks();
const JOINT* jt = m_currentNode->FindJoint( aObstacleVia->Pos(), aObstacleVia );
LINE shoved( aCurrent );
shoved.ClearLinks();
2013-09-26 21:53:54 +00:00
cur.RemoveVia();
unwindLineStack( &aCurrent );
2013-09-26 21:53:54 +00:00
for( ITEM* item : jt->LinkList() )
{
if( item->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T ) && item->LayersOverlap( &aCurrent ) )
2013-09-26 21:53:54 +00:00
{
LINKED_ITEM* li = static_cast<LINKED_ITEM*>( item );
LINE head = assembleLine( li );
2014-11-14 19:19:00 +00:00
head.AppendVia( *aObstacleVia );
2021-01-05 16:40:34 +00:00
SHOVE_STATUS st = ShoveObstacleLine( head, cur, shoved );
2014-11-14 19:19:00 +00:00
if( st != SH_OK )
{
2023-08-07 21:40:56 +00:00
PNS_DBG( Dbg(), BeginGroup, "on-reverse-via-fail-shove", m_iter );
PNS_DBG( Dbg(), AddItem, aObstacleVia, LIGHTRED, 100000, wxT( "the-via" ) );
PNS_DBG( Dbg(), AddItem, &aCurrent, LIGHTGREEN, 10000, wxT( "current-line" ) );
PNS_DBG( Dbg(), AddItem, &shoved, LIGHTRED, 10000, wxT( "shoved-line" ) );
PNS_DBGN( Dbg(), EndGroup );
return st;
}
2014-11-14 19:19:00 +00:00
cur.SetShape( shoved.CLine() );
n++;
2013-09-26 21:53:54 +00:00
}
}
if( !n )
{
2023-08-07 21:40:56 +00:00
PNS_DBG( Dbg(), BeginGroup, "on-reverse-via-fail-lonevia", m_iter );
PNS_DBG( Dbg(), AddItem, aObstacleVia, LIGHTRED, 100000, wxT( "the-via" ) );
PNS_DBG( Dbg(), AddItem, &aCurrent, LIGHTGREEN, 10000, wxT( "current-line" ) );
PNS_DBGN( Dbg(), EndGroup );
2014-11-14 19:19:00 +00:00
LINE head( aCurrent );
head.Line().Clear();
head.AppendVia( *aObstacleVia );
head.ClearLinks();
2021-01-05 16:40:34 +00:00
SHOVE_STATUS st = ShoveObstacleLine( head, aCurrent, shoved );
if( st != SH_OK )
return st;
cur.SetShape( shoved.CLine() );
}
if( aCurrent.EndsWithVia() )
shoved.AppendVia( aCurrent.Via() );
2023-08-07 21:40:56 +00:00
PNS_DBG( Dbg(), BeginGroup, "on-reverse-via", m_iter );
PNS_DBG( Dbg(), AddItem, aObstacleVia, YELLOW, 0, wxT( "rr-the-via" ) );
PNS_DBG( Dbg(), AddItem, &aCurrent, BLUE, 0, wxT( "rr-current-line" ) );
PNS_DBG( Dbg(), AddItem, &shoved, GREEN, 0, wxT( "rr-shoved-line" ) );
2023-08-07 21:40:56 +00:00
PNS_DBGN( Dbg(), EndGroup );
int currentRank = aCurrent.Rank();
replaceLine( aCurrent, shoved );
2013-09-26 21:53:54 +00:00
if( !pushLineStack( shoved ) )
2015-07-02 14:09:32 +00:00
return SH_INCOMPLETE;
shoved.SetRank( currentRank );
2014-11-14 19:19:00 +00:00
return SH_OK;
}
2013-09-26 21:53:54 +00:00
void SHOVE::unwindLineStack( const LINKED_ITEM* aSeg )
{
int d = 0;
for( std::vector<LINE>::iterator i = m_lineStack.begin(); i != m_lineStack.end() ; )
{
if( i->ContainsLink( aSeg ) )
{
PNS_DBG(Dbg(), Message, wxString::Format("Unwind lc %d (depth %d/%d)", i->SegmentCount(), d, (int)m_lineStack.size() ) );
i = m_lineStack.erase( i );
}
else
{
i++;
}
d++;
}
2013-09-26 21:53:54 +00:00
for( std::vector<LINE>::iterator i = m_optimizerQueue.begin(); i != m_optimizerQueue.end() ; )
{
if( i->ContainsLink( aSeg ) )
i = m_optimizerQueue.erase( i );
else
i++;
2014-11-14 19:19:00 +00:00
}
}
void SHOVE::unwindLineStack( const ITEM* aItem )
{
if( aItem->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T ) )
{
unwindLineStack( static_cast<const LINKED_ITEM*>( aItem ) );
}
else if( aItem->OfKind( ITEM::LINE_T ) )
{
const LINE* l = static_cast<const LINE*>( aItem );
for( const LINKED_ITEM* seg : l->Links() )
unwindLineStack( seg );
}
}
2014-11-14 19:19:00 +00:00
bool SHOVE::pushLineStack( const LINE& aL, bool aKeepCurrentOnTop )
{
if( !aL.IsLinkedChecked() && aL.SegmentCount() != 0 )
{
PNS_DBG( Dbg(), AddItem, &aL, BLUE, 10000, wxT( "push line stack failed" ) );
2015-07-02 14:09:32 +00:00
return false;
}
2014-11-14 19:19:00 +00:00
if( aKeepCurrentOnTop && m_lineStack.size() > 0)
{
m_lineStack.insert( m_lineStack.begin() + m_lineStack.size() - 1, aL );
}
else
{
m_lineStack.push_back( aL );
}
m_optimizerQueue.push_back( aL );
2015-07-02 14:09:32 +00:00
return true;
}
2021-07-19 23:56:05 +00:00
void SHOVE::popLineStack( )
{
LINE& l = m_lineStack.back();
for( std::vector<LINE>::iterator i = m_optimizerQueue.begin(); i != m_optimizerQueue.end(); )
{
bool found = false;
2020-12-02 18:42:37 +00:00
for( LINKED_ITEM* s : l.Links() )
2013-09-26 21:53:54 +00:00
{
if( i->ContainsLink( s ) )
{
i = m_optimizerQueue.erase( i );
found = true;
break;
}
}
if( !found )
i++;
}
2013-09-26 21:53:54 +00:00
m_lineStack.pop_back();
}
2013-09-26 21:53:54 +00:00
bool SHOVE::fixupViaCollisions( const LINE* aCurrent, OBSTACLE& obs )
{
// if the current obstacle is a via, consider also the lines connected to it
// if their widths are larger or equal than the via diameter, the shove algorithm
// will very likely fail in the subsequent iterations (as our base assumption is track
// ends can never move on their own, only dragged by force-propagated vias
// our colliding item is a via: just find the max width of the traces connected to it
if( obs.m_item->OfKind( ITEM::VIA_T ) )
{
const VIA* v = static_cast<const VIA*>( obs.m_item );
int maxw = 0;
const JOINT* jv = m_currentNode->FindJoint( v->Pos(), v );
ITEM_SET links( jv->CLinks() );
for( ITEM* link : links )
{
if( link->OfKind( ITEM::SEGMENT_T ) ) // consider segments ...
{
const SEGMENT* seg = static_cast<const SEGMENT*>( link );
maxw = std::max( seg->Width(), maxw );
}
else if( link->OfKind( ITEM::ARC_T ) ) // ... or arcs
{
const ARC* arc = static_cast<const ARC*>( link );
maxw = std::max( arc->Width(), maxw );
}
}
obs.m_maxFanoutWidth = 0;
if( maxw > 0 && maxw >= v->Diameter() )
{
obs.m_maxFanoutWidth = maxw + 1;
PNS_DBG( Dbg(), Message,
wxString::Format( "Fixup via: new-w %d via-w %d", maxw, v->Diameter() ) );
return true;
}
return false;
}
// our colliding item is a segment. check if it has a via on either of the ends.
if( !obs.m_item->OfKind( ITEM::SEGMENT_T ) )
return false;
const SEGMENT* s = static_cast<const SEGMENT*>( obs.m_item );
const JOINT* ja = m_currentNode->FindJoint( s->Seg().A, s );
const JOINT* jb = m_currentNode->FindJoint( s->Seg().B, s );
VIA* vias[] = { ja->Via(), jb->Via() };
for( int i = 0; i < 2; i++ )
{
VIA* v = vias[i];
// via diameter is larger than the segment width - cool, the force propagation algo
// will be able to deal with it, no need to intervene
if( !v || v->Diameter() > s->Width() )
continue;
VIA vtest( *v );
vtest.SetDiameter( s->Width() );
// enlarge the via to the width of the segment
if( vtest.Collide( aCurrent, m_currentNode ) )
{
// if colliding, drop the segment in the shove iteration loop and force-propagate the via instead
obs.m_item = v;
obs.m_maxFanoutWidth = s->Width() + 1;
return true;
}
}
return false;
}
/*
* Resolve the next collision.
*/
SHOVE::SHOVE_STATUS SHOVE::shoveIteration( int aIter )
{
LINE currentLine = m_lineStack.back();
NODE::OPT_OBSTACLE nearest;
SHOVE_STATUS st = SH_NULL;
2014-11-14 19:19:00 +00:00
if( Dbg() )
Dbg()->SetIteration( aIter );
PNS_DBG( Dbg(), AddItem, &currentLine, RED, currentLine.Width(),
wxString::Format( wxT( "current-coll-chk rank %d" ), currentLine.Rank() ) );
for( ITEM::PnsKind search_order : { ITEM::SOLID_T, ITEM::VIA_T, ITEM::SEGMENT_T, ITEM::HOLE_T } )
{
COLLISION_SEARCH_OPTIONS opts;
opts.m_kindMask = search_order;
nearest = m_currentNode->NearestObstacle( &currentLine, opts );
2021-07-19 23:56:05 +00:00
if( nearest )
{
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "nearest %p %s rank %d" ),
nearest->m_item,
nearest->m_item->KindStr(),
nearest->m_item->Rank() ) );
PNS_DBG( Dbg(), AddShape, nearest->m_item->Shape(), YELLOW, 10000, wxT( "nearest" ) );
2023-08-07 21:40:56 +00:00
}
if( nearest )
break;
2014-11-14 19:19:00 +00:00
}
if( !nearest )
{
m_lineStack.pop_back();
PNS_DBG( Dbg(), Message, wxT( "no-nearest-item ") );
return SH_OK;
}
2013-09-26 21:53:54 +00:00
bool viaFixup = fixupViaCollisions( &currentLine, *nearest );
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "iter %d: VF %d" ), aIter, viaFixup?1:0 ) );
ITEM* ni = nearest->m_item;
2013-09-26 21:53:54 +00:00
UNITS_PROVIDER up( pcbIUScale, EDA_UNITS::MILLIMETRES );
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "NI: %s (%s)" ),
ni->Format(),
ni->Parent() ? ni->Parent()->GetItemDescription( &up )
: wxString( wxT( "null" ) ) ) );
unwindLineStack( ni );
if( !ni->OfKind( ITEM::SOLID_T ) && ni->Rank() >= 0 && ni->Rank() > currentLine.Rank() )
{
// Collision with a higher-ranking object (ie: one that we've already shoved)
//
switch( ni->Kind() )
{
case ITEM::VIA_T:
2015-10-05 16:28:41 +00:00
{
PNS_DBG( Dbg(), BeginGroup, wxString::Format( wxT( "iter %d: reverse-collide-via" ), aIter ), 0 );
2013-09-26 21:53:54 +00:00
if( currentLine.EndsWithVia() && nearest->m_item->Collide( &currentLine.Via(), m_currentNode ) )
2015-10-05 16:28:41 +00:00
{
st = SH_INCOMPLETE;
}
2015-10-05 16:28:41 +00:00
else
{
st = onReverseCollidingVia( currentLine, (VIA*) ni );
2015-10-05 16:28:41 +00:00
}
PNS_DBGN( Dbg(), EndGroup );
2015-10-05 16:28:41 +00:00
break;
}
2013-09-26 21:53:54 +00:00
case ITEM::SEGMENT_T:
2015-10-05 16:28:41 +00:00
{
PNS_DBG( Dbg(), BeginGroup, wxString::Format( wxT( "iter %d: reverse-collide-segment " ),
aIter ), 0 );
LINE revLine = assembleLine( static_cast<SEGMENT*>( ni ) );
popLineStack();
st = onCollidingLine( revLine, currentLine );
2021-07-19 23:56:05 +00:00
if( !pushLineStack( revLine ) )
{
return SH_INCOMPLETE;
}
PNS_DBGN( Dbg(), EndGroup );
break;
}
case ITEM::ARC_T:
{
//TODO(snh): Handle Arc shove separate from track
PNS_DBG( Dbg(), BeginGroup, wxString::Format( wxT( "iter %d: reverse-collide-arc " ), aIter ), 0 );
LINE revLine = assembleLine( static_cast<ARC*>( ni ) );
2015-07-02 14:09:32 +00:00
popLineStack();
2015-10-05 16:28:41 +00:00
st = onCollidingLine( revLine, currentLine );
2021-07-19 23:56:05 +00:00
PNS_DBGN( Dbg(), EndGroup );
if( !pushLineStack( revLine ) )
2015-10-05 16:28:41 +00:00
return SH_INCOMPLETE;
break;
}
2013-09-26 21:53:54 +00:00
2015-10-05 16:28:41 +00:00
default:
assert( false );
2013-09-26 21:53:54 +00:00
}
}
else
{
// Collision with a lower-ranking object or a solid
//
switch( ni->Kind() )
2013-09-26 21:53:54 +00:00
{
case ITEM::SEGMENT_T:
PNS_DBG( Dbg(), BeginGroup, wxString::Format( wxT( "iter %d: collide-segment " ), aIter ), 0 );
2016-08-15 15:16:48 +00:00
st = onCollidingSegment( currentLine, (SEGMENT*) ni );
if( st == SH_TRY_WALK )
st = onCollidingSolid( currentLine, ni, *nearest );
PNS_DBGN( Dbg(), EndGroup );
break;
2013-09-26 21:53:54 +00:00
//TODO(snh): Customize Arc collide
case ITEM::ARC_T:
PNS_DBG( Dbg(), BeginGroup, wxString::Format( wxT( "iter %d: collide-arc " ), aIter ), 0 );
st = onCollidingArc( currentLine, static_cast<ARC*>( ni ) );
if( st == SH_TRY_WALK )
st = onCollidingSolid( currentLine, ni, *nearest );
PNS_DBGN( Dbg(), EndGroup );
break;
case ITEM::VIA_T:
PNS_DBG( Dbg(), BeginGroup, wxString::Format( wxT( "iter %d: collide-via (fixup: %d)" ), aIter, 0 ), 0 );
st = onCollidingVia( &currentLine, (VIA*) ni, *nearest );
2016-08-15 15:16:48 +00:00
if( st == SH_TRY_WALK )
st = onCollidingSolid( currentLine, ni, *nearest );
PNS_DBGN( Dbg(), EndGroup );
break;
2013-09-26 21:53:54 +00:00
2023-08-06 00:13:15 +00:00
case ITEM::HOLE_T:
case ITEM::SOLID_T:
PNS_DBG( Dbg(), BeginGroup, wxString::Format( wxT( "iter %d: walk-solid " ), aIter ), 0);
2023-08-06 00:13:15 +00:00
st = onCollidingSolid( currentLine, ni, *nearest );
PNS_DBGN( Dbg(), EndGroup );
break;
2014-11-14 19:19:00 +00:00
default:
break;
}
}
2013-09-26 21:53:54 +00:00
return st;
}
2013-09-26 21:53:54 +00:00
/*
* Resolve collisions.
* Each iteration pushes the next colliding object out of the way. Iterations are continued as
* long as they propagate further collisions, or until the iteration timeout or max iteration
* count is reached.
*/
SHOVE::SHOVE_STATUS SHOVE::shoveMainLoop()
{
SHOVE_STATUS st = SH_OK;
2013-09-26 21:53:54 +00:00
m_affectedArea = OPT_BOX2I();
2021-07-19 23:56:05 +00:00
PNS_DBG( Dbg(), Message, wxString::Format( "ShoveStart [root: %d jts, current: %d jts]",
m_root->JointCount(),
m_currentNode->JointCount() ) );
2013-09-26 21:53:54 +00:00
int iterLimit = Settings().ShoveIterationLimit();
TIME_LIMIT timeLimit = Settings().ShoveTimeLimit();
2013-09-26 21:53:54 +00:00
m_iter = 0;
2013-09-26 21:53:54 +00:00
timeLimit.Restart();
2013-09-26 21:53:54 +00:00
if( m_lineStack.empty() && m_draggedVia )
{
// If we're shoving a free via then push a proxy LINE (with the via on the end) onto
// the stack.
pushLineStack( LINE( *m_draggedVia ) );
}
while( !m_lineStack.empty() )
{
2021-07-19 23:56:05 +00:00
PNS_DBG( Dbg(), Message, wxString::Format( "iter %d: node %p stack %d ", m_iter,
m_currentNode, (int) m_lineStack.size() ) );
2021-06-03 20:35:42 +00:00
st = shoveIteration( m_iter );
2013-09-26 21:53:54 +00:00
m_iter++;
2014-11-14 19:19:00 +00:00
if( st == SH_INCOMPLETE || timeLimit.Expired() || m_iter >= iterLimit )
{
PNS_DBG( Dbg(), Message, wxString::Format( "Fail [time limit expired: %d iter %d iter limit %d",
timeLimit.Expired()?1:0, m_iter, iterLimit ) );
st = SH_INCOMPLETE;
break;
}
}
2013-09-26 21:53:54 +00:00
return st;
}
2013-09-26 21:53:54 +00:00
OPT_BOX2I SHOVE::totalAffectedArea() const
{
OPT_BOX2I area;
2015-02-18 16:53:46 +00:00
if( !m_nodeStack.empty() )
area = m_nodeStack.back().m_affectedArea;
if( area && m_affectedArea)
area->Merge( *m_affectedArea );
else if( !area )
area = m_affectedArea;
return area;
}
2013-09-26 21:53:54 +00:00
SHOVE::SHOVE_STATUS SHOVE::ShoveLines( const LINE& aCurrentHead )
{
SHOVE_STATUS st = SH_OK;
2013-09-26 21:53:54 +00:00
m_multiLineMode = false;
2015-02-18 16:53:46 +00:00
PNS_DBG( Dbg(), Message,
wxString::Format( "Shove start, lc = %d", aCurrentHead.SegmentCount() ) )
// empty head? nothing to shove...
if( !aCurrentHead.SegmentCount() && !aCurrentHead.EndsWithVia() )
2015-07-07 16:37:03 +00:00
return SH_INCOMPLETE;
LINE head( aCurrentHead );
head.ClearLinks();
2014-11-14 19:19:00 +00:00
m_lineStack.clear();
m_optimizerQueue.clear();
m_newHead = OPT_LINE();
2021-07-19 23:56:05 +00:00
// Pop NODEs containing previous shoves which are no longer necessary
//
ITEM_SET headSet;
headSet.Add( aCurrentHead );
2013-09-26 21:53:54 +00:00
VIA_HANDLE dummyVia;
NODE* parent = reduceSpringback( headSet, dummyVia );
2013-09-26 21:53:54 +00:00
// Create a new NODE to store this version of the world
m_currentNode = parent->Branch();
m_currentNode->ClearRanks();
m_currentNode->Add( head );
m_currentNode->LockJoint( head.CPoint(0), &head, true );
2014-11-14 19:19:00 +00:00
if( !head.EndsWithVia() )
m_currentNode->LockJoint( head.CPoint( -1 ), &head, true );
head.Mark( MK_HEAD );
head.SetRank( 100000 );
2013-09-26 21:53:54 +00:00
PNS_DBG( Dbg(), AddItem, &head, CYAN, 0, wxT( "head, after shove" ) );
if( head.EndsWithVia() )
{
std::unique_ptr< VIA >headVia = Clone( head.Via() );
headVia->Mark( MK_HEAD );
headVia->SetRank( 100000 );
m_currentNode->Add( std::move( headVia ) );
2013-09-26 21:53:54 +00:00
}
if( !pushLineStack( head ) )
{
delete m_currentNode;
m_currentNode = parent;
2015-07-02 14:09:32 +00:00
return SH_INCOMPLETE;
}
2015-07-02 14:09:32 +00:00
st = shoveMainLoop();
if( st == SH_OK )
{
runOptimizer( m_currentNode );
2013-09-26 21:53:54 +00:00
if( m_newHead )
2016-08-15 15:16:53 +00:00
st = m_currentNode->CheckColliding( &( *m_newHead ) ) ? SH_INCOMPLETE : SH_HEAD_MODIFIED;
else
st = m_currentNode->CheckColliding( &head ) ? SH_INCOMPLETE : SH_OK;
2014-11-14 19:19:00 +00:00
}
m_currentNode->RemoveByMarker( MK_HEAD );
PNS_DBG( Dbg(), Message, wxString::Format( "Shove status : %s after %d iterations",
( ( st == SH_OK || st == SH_HEAD_MODIFIED ) ? "OK" : "FAILURE"), m_iter ) );
if( st == SH_OK || st == SH_HEAD_MODIFIED )
{
pushSpringback( m_currentNode, m_affectedArea, nullptr );
}
else
{
delete m_currentNode;
2014-11-14 19:19:00 +00:00
m_currentNode = parent;
2014-11-14 19:19:00 +00:00
m_newHead = OPT_LINE();
2013-09-26 21:53:54 +00:00
}
2023-08-07 21:40:56 +00:00
if( m_newHead )
2016-08-15 15:16:48 +00:00
m_newHead->Unmark();
if( m_newHead && head.EndsWithVia() )
{
VIA v = head.Via();
v.SetPos( m_newHead->CPoint( -1 ) );
m_newHead->AppendVia(v);
}
return st;
}
SHOVE::SHOVE_STATUS SHOVE::ShoveMultiLines( const ITEM_SET& aHeadSet )
{
SHOVE_STATUS st = SH_OK;
m_multiLineMode = true;
ITEM_SET headSet;
for( const ITEM* item : aHeadSet.CItems() )
{
const LINE* headOrig = static_cast<const LINE*>( item );
2015-02-18 16:53:46 +00:00
// empty head? nothing to shove...
if( !headOrig->SegmentCount() )
return SH_INCOMPLETE;
headSet.Add( *headOrig );
2015-02-18 16:53:46 +00:00
}
m_lineStack.clear();
m_optimizerQueue.clear();
2021-07-19 23:56:05 +00:00
VIA_HANDLE dummyVia;
NODE* parent = reduceSpringback( headSet, dummyVia );
m_currentNode = parent->Branch();
m_currentNode->ClearRanks();
int n = 0;
for( const ITEM* item : aHeadSet.CItems() )
{
const LINE* headOrig = static_cast<const LINE*>( item );
LINE head( *headOrig );
head.ClearLinks();
2015-02-18 16:53:46 +00:00
m_currentNode->Add( head );
head.Mark( MK_HEAD );
head.SetRank( 100000 );
n++;
2015-09-30 09:18:19 +00:00
if( !pushLineStack( head ) )
2015-07-02 14:09:32 +00:00
return SH_INCOMPLETE;
if( head.EndsWithVia() )
{
std::unique_ptr< VIA > headVia = Clone( head.Via() );
headVia->Mark( MK_HEAD );
headVia->SetRank( 100000 );
m_currentNode->Add( std::move( headVia ) );
}
}
st = shoveMainLoop();
2015-07-02 14:11:15 +00:00
if( st == SH_OK )
runOptimizer( m_currentNode );
m_currentNode->RemoveByMarker( MK_HEAD );
PNS_DBG( Dbg(), Message, wxString::Format( "Shove status : %s after %d iterations",
( st == SH_OK ? "OK" : "FAILURE"), m_iter ) );
if( st == SH_OK )
{
pushSpringback( m_currentNode, m_affectedArea, nullptr );
}
else
{
delete m_currentNode;
m_currentNode = parent;
}
return st;
}
2021-07-19 23:56:05 +00:00
static VIA* findViaByHandle ( NODE *aNode, const VIA_HANDLE& handle )
{
const JOINT* jt = aNode->FindJoint( handle.pos, handle.layers.Start(), handle.net );
if( !jt )
return nullptr;
for( ITEM* item : jt->LinkList() )
{
if( item->OfKind( ITEM::VIA_T ) )
{
if( item->Net() == handle.net && item->Layers().Overlaps(handle.layers) )
return static_cast<VIA*>( item );
}
}
return nullptr;
}
2015-02-18 16:53:46 +00:00
2021-07-19 23:56:05 +00:00
SHOVE::SHOVE_STATUS SHOVE::ShoveDraggingVia( const VIA_HANDLE aOldVia, const VECTOR2I& aWhere,
VIA_HANDLE& aNewVia )
{
2021-07-19 23:56:05 +00:00
SHOVE_STATUS st = SH_OK;
2014-11-14 19:19:00 +00:00
m_lineStack.clear();
m_optimizerQueue.clear();
m_newHead = OPT_LINE();
2021-07-19 23:56:05 +00:00
m_draggedVia = nullptr;
2020-12-02 18:42:37 +00:00
VIA* viaToDrag = findViaByHandle( m_currentNode, aOldVia );
if( !viaToDrag )
return SH_INCOMPLETE;
// Pop NODEs containing previous shoves which are no longer necessary
ITEM_SET headSet;
VIA headVia ( *viaToDrag );
headVia.SetPos( aWhere );
headSet.Add( &headVia, false ); // headVia is on stack
VIA_HANDLE prevViaHandle;
NODE* parent = reduceSpringback( headSet, prevViaHandle );
if( prevViaHandle.valid )
{
aNewVia = prevViaHandle;
viaToDrag = findViaByHandle( parent, prevViaHandle );
}
// Create a new NODE to store this version of the world
m_currentNode = parent->Branch();
m_currentNode->ClearRanks();
viaToDrag->Mark( MK_HEAD );
viaToDrag->SetRank( 100000 );
// Push the via to its new location
st = pushOrShoveVia( viaToDrag, ( aWhere - viaToDrag->Pos() ), 0 );
// Shove any colliding objects out of the way
if( st == SH_OK )
st = shoveMainLoop();
2015-07-02 14:11:15 +00:00
if( st == SH_OK )
{
runOptimizer( m_currentNode );
2013-09-26 21:53:54 +00:00
// m_draggedVia will only be updated if pushOrShoveVia makes progress...
// this should probably be refactored/cleaned up.
PNS::VIA* viaCheck = m_draggedVia ? m_draggedVia : viaToDrag;
st = m_currentNode->CheckColliding( viaCheck ) ? SH_INCOMPLETE : SH_OK;
}
if( st == SH_OK || st == SH_HEAD_MODIFIED )
2013-09-26 21:53:54 +00:00
{
wxLogTrace( "PNS","setNewV %p", m_draggedVia );
if( !m_draggedVia )
m_draggedVia = viaToDrag;
aNewVia = m_draggedVia->MakeHandle();
pushSpringback( m_currentNode, m_affectedArea, viaToDrag );
2013-09-26 21:53:54 +00:00
}
else
{
delete m_currentNode;
m_currentNode = parent;
}
return st;
}
LINE* SHOVE::findRootLine( LINE *aLine )
{
for( LINKED_ITEM* link : aLine->Links() )
{
if( SEGMENT* seg = dyn_cast<SEGMENT*>( link ) )
{
auto it = m_rootLineHistory.find( seg );
if( it != m_rootLineHistory.end() )
return it->second;
}
}
return nullptr;
}
void SHOVE::runOptimizer( NODE* aNode )
{
OPTIMIZER optimizer( aNode );
2021-02-25 16:18:13 +00:00
int optFlags = 0;
int n_passes = 0;
PNS_OPTIMIZATION_EFFORT effort = Settings().OptimizerEffort();
OPT_BOX2I area = totalAffectedArea();
int maxWidth = 0;
for( LINE& line : m_optimizerQueue )
maxWidth = std::max( line.Width(), maxWidth );
2015-02-18 16:53:46 +00:00
if( area )
{
area->Inflate( maxWidth );
area = area->Intersect( VisibleViewArea() );
}
switch( effort )
{
case OE_LOW:
optFlags |= OPTIMIZER::MERGE_OBTUSE;
n_passes = 1;
break;
case OE_MEDIUM:
optFlags |= OPTIMIZER::MERGE_SEGMENTS;
n_passes = 2;
break;
case OE_FULL:
optFlags = OPTIMIZER::MERGE_SEGMENTS;
n_passes = 2;
break;
default:
break;
}
optFlags |= OPTIMIZER::LIMIT_CORNER_COUNT;
if( area )
2021-02-25 16:18:13 +00:00
{
SHAPE_RECT r( *area );
PNS_DBG( Dbg(), AddShape, &r, BLUE, 0, wxT( "opt-area" ) );
2021-02-25 16:18:13 +00:00
optFlags |= OPTIMIZER::RESTRICT_AREA;
optimizer.SetRestrictArea( *area, false );
}
DIRECTION_45::CORNER_MODE cornerMode = Settings().GetCornerMode();
// Smart Pads is incompatible with 90-degree mode for now
if( Settings().SmartPads()
&& ( cornerMode == DIRECTION_45::MITERED_45 || cornerMode == DIRECTION_45::ROUNDED_45 ) )
{
optFlags |= OPTIMIZER::SMART_PADS;
}
optimizer.SetEffortLevel( optFlags & ~m_optFlagDisableMask );
optimizer.SetCollisionMask( ITEM::ANY_T );
for( int pass = 0; pass < n_passes; pass++ )
{
std::reverse( m_optimizerQueue.begin(), m_optimizerQueue.end() );
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "optimize %d lines, pass %d"), (int)m_optimizerQueue.size(), (int)pass ) );
for( LINE& line : m_optimizerQueue)
{
if( !( line.Marker() & MK_HEAD ) )
{
LINE optimized;
LINE* root = findRootLine( &line );
if( optimizer.Optimize( &line, &optimized, root ) )
{
PNS_DBG( Dbg(), AddShape, &line.CLine(), BLUE, 0, wxT( "shove-pre-opt" ) );
if( root )
PNS_DBG( Dbg(), AddItem, root, RED, 0, wxT( "shove-root-opt" ) );
replaceLine( line, optimized, false, aNode );
line = optimized; // keep links in the lines in the queue up to date
PNS_DBG( Dbg(), AddShape, &line.CLine(), GREEN, 0, wxT( "shove-post-opt" ) );
}
}
}
}
}
NODE* SHOVE::CurrentNode()
{
return m_nodeStack.empty() ? m_root : m_nodeStack.back().m_node;
}
const LINE SHOVE::NewHead() const
{
assert( m_newHead );
return *m_newHead;
}
2014-11-14 19:19:00 +00:00
void SHOVE::SetInitialLine( LINE& aInitial )
{
m_root = m_root->Branch();
m_root->Remove( aInitial );
}
bool SHOVE::AddLockedSpringbackNode( NODE* aNode )
{
SPRINGBACK_TAG sp;
sp.m_node = aNode;
sp.m_locked = true;
m_nodeStack.push_back(sp);
return true;
}
bool SHOVE::RewindSpringbackTo( NODE* aNode )
{
bool found = false;
auto iter = m_nodeStack.begin();
while( iter != m_nodeStack.end() )
{
if ( iter->m_node == aNode )
{
found = true;
break;
}
2021-07-19 23:56:05 +00:00
iter++;
}
if( !found )
return false;
auto start = iter;
aNode->KillChildren();
m_nodeStack.erase( start, m_nodeStack.end() );
return true;
}
bool SHOVE::RewindToLastLockedNode()
{
if( m_nodeStack.empty() )
return false;
while( !m_nodeStack.back().m_locked && m_nodeStack.size() > 1 )
m_nodeStack.pop_back();
return m_nodeStack.back().m_locked;
}
void SHOVE::UnlockSpringbackNode( NODE* aNode )
{
auto iter = m_nodeStack.begin();
while( iter != m_nodeStack.end() )
{
if ( iter->m_node == aNode )
{
iter->m_locked = false;
break;
}
2021-07-19 23:56:05 +00:00
iter++;
}
}
void SHOVE::DisablePostShoveOptimizations( int aMask )
{
m_optFlagDisableMask = aMask;
}
void SHOVE::SetSpringbackDoNotTouchNode( const NODE *aNode )
{
m_springbackDoNotTouchNode = aNode;
}
}