mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 02:03:12 +02:00
Note to self: LINE_PLACER::FixRoute used to silently reclaim the UID of the transient via (as used by the SHOVE's heads tracking logic). Depending on the particular sequence of user events (the bug is next to impossible to reproduce on a touchpad), this would result in freshly placed vias disappearing... Fixes https://gitlab.com/kicad/code/kicad/issues/20999
2149 lines
60 KiB
C++
2149 lines
60 KiB
C++
/*
|
|
* KiRouter - a push-and-(sometimes-)shove PCB router
|
|
*
|
|
* Copyright (C) 2013-2017 CERN
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
|
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation, either version 3 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <optional>
|
|
#include <memory>
|
|
|
|
#include "pns_arc.h"
|
|
#include "pns_debug_decorator.h"
|
|
#include "pns_line_placer.h"
|
|
#include "pns_node.h"
|
|
#include "pns_router.h"
|
|
#include "pns_shove.h"
|
|
#include "pns_solid.h"
|
|
#include "pns_topology.h"
|
|
#include "pns_walkaround.h"
|
|
#include "pns_mouse_trail_tracer.h"
|
|
|
|
#include <wx/log.h>
|
|
|
|
namespace PNS {
|
|
|
|
LINE_PLACER::LINE_PLACER( ROUTER* aRouter ) :
|
|
PLACEMENT_ALGO( aRouter )
|
|
{
|
|
m_initial_direction = DIRECTION_45::N;
|
|
m_world = nullptr;
|
|
m_shove = nullptr;
|
|
m_currentNode = nullptr;
|
|
m_idle = true;
|
|
|
|
// Init temporary variables (do not leave uninitialized members)
|
|
m_lastNode = nullptr;
|
|
m_placingVia = false;
|
|
m_currentNet = nullptr;
|
|
m_currentLayer = 0;
|
|
m_startItem = nullptr;
|
|
m_endItem = nullptr;
|
|
m_chainedPlacement = false;
|
|
m_orthoMode = false;
|
|
m_placementCorrect = false;
|
|
}
|
|
|
|
|
|
LINE_PLACER::~LINE_PLACER()
|
|
{
|
|
}
|
|
|
|
|
|
void LINE_PLACER::setWorld( NODE* aWorld )
|
|
{
|
|
m_world = aWorld;
|
|
}
|
|
|
|
|
|
const VIA LINE_PLACER::makeVia( const VECTOR2I& aP )
|
|
{
|
|
// fixme: should belong to KICAD_IFACE
|
|
auto iface = Router()->GetInterface();
|
|
|
|
int start = m_sizes.ViaType() == VIATYPE::THROUGH ? iface->GetPNSLayerFromBoardLayer( F_Cu )
|
|
: m_sizes.GetLayerTop();
|
|
int end = m_sizes.ViaType() == VIATYPE::THROUGH ? iface->GetPNSLayerFromBoardLayer( B_Cu )
|
|
: m_sizes.GetLayerBottom();
|
|
|
|
const PNS_LAYER_RANGE layers(
|
|
start ,
|
|
end
|
|
);
|
|
|
|
return VIA( aP, layers, m_sizes.ViaDiameter(), m_sizes.ViaDrill(), nullptr, m_sizes.ViaType() );
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::ToggleVia( bool aEnabled )
|
|
{
|
|
m_placingVia = aEnabled;
|
|
|
|
if( !aEnabled )
|
|
m_head.RemoveVia();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void LINE_PLACER::setInitialDirection( const DIRECTION_45& aDirection )
|
|
{
|
|
m_initial_direction = aDirection;
|
|
|
|
if( m_tail.SegmentCount() == 0 )
|
|
m_direction = aDirection;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::handleSelfIntersections()
|
|
{
|
|
SHAPE_LINE_CHAIN::INTERSECTIONS ips;
|
|
SHAPE_LINE_CHAIN& head = m_head.Line();
|
|
SHAPE_LINE_CHAIN& tail = m_tail.Line();
|
|
|
|
// if there is no tail, there is nothing to intersect with
|
|
if( tail.PointCount() < 2 )
|
|
return false;
|
|
|
|
if( head.PointCount() < 2 )
|
|
return false;
|
|
|
|
// completely new head trace? chop off the tail
|
|
if( tail.CPoint(0) == head.CPoint(0) )
|
|
{
|
|
m_direction = m_initial_direction;
|
|
tail.Clear();
|
|
return true;
|
|
}
|
|
|
|
tail.Intersect( head, ips );
|
|
|
|
// no intesection points - nothing to reduce
|
|
if( ips.empty() )
|
|
return false;
|
|
|
|
int n = INT_MAX;
|
|
VECTOR2I ipoint;
|
|
|
|
// if there is more than one intersection, find the one that is
|
|
// closest to the beginning of the tail.
|
|
for( const SHAPE_LINE_CHAIN::INTERSECTION& i : ips )
|
|
{
|
|
if( i.index_our < n )
|
|
{
|
|
n = i.index_our;
|
|
ipoint = i.p;
|
|
}
|
|
}
|
|
|
|
// ignore the point where head and tail meet
|
|
if( ipoint == head.CPoint( 0 ) || ipoint == tail.CPoint( -1 ) )
|
|
return false;
|
|
|
|
// Intersection point is on the first or the second segment: just start routing
|
|
// from the beginning
|
|
if( n < 2 )
|
|
{
|
|
m_direction = m_initial_direction;
|
|
tail.Clear();
|
|
head.Clear();
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Clip till the last tail segment before intersection.
|
|
// Set the direction to the one of this segment.
|
|
const SEG last = tail.CSegment( n - 1 );
|
|
m_direction = DIRECTION_45( last );
|
|
tail.Remove( n, -1 );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::handlePullback()
|
|
{
|
|
SHAPE_LINE_CHAIN& head = m_head.Line();
|
|
SHAPE_LINE_CHAIN& tail = m_tail.Line();
|
|
|
|
if( head.PointCount() < 2 )
|
|
return false;
|
|
|
|
int n = tail.PointCount();
|
|
|
|
if( n == 0 )
|
|
{
|
|
return false;
|
|
}
|
|
else if( n == 1 )
|
|
{
|
|
tail.Clear();
|
|
return true;
|
|
}
|
|
|
|
DIRECTION_45 first_head, last_tail;
|
|
|
|
wxASSERT( tail.PointCount() >= 2 );
|
|
|
|
if( !head.IsPtOnArc( 0 ) )
|
|
first_head = DIRECTION_45( head.CSegment( 0 ) );
|
|
else
|
|
first_head = DIRECTION_45( head.CArcs()[head.ArcIndex(0)] );
|
|
|
|
int lastSegIdx = tail.PointCount() - 2;
|
|
|
|
if( !tail.IsPtOnArc( lastSegIdx ) )
|
|
last_tail = DIRECTION_45( tail.CSegment( lastSegIdx ) );
|
|
else
|
|
last_tail = DIRECTION_45( tail.CArcs()[tail.ArcIndex(lastSegIdx)] );
|
|
|
|
DIRECTION_45::AngleType angle = first_head.Angle( last_tail );
|
|
|
|
// case 1: we have a defined routing direction, and the currently computed
|
|
// head goes in different one.
|
|
bool pullback_1 = false; // (m_direction != DIRECTION_45::UNDEFINED && m_direction != first_head);
|
|
|
|
// case 2: regardless of the current routing direction, if the tail/head
|
|
// extremities form an acute or right angle, reduce the tail by one segment
|
|
// (and hope that further iterations) will result with a cleaner trace
|
|
bool pullback_2 = ( angle == DIRECTION_45::ANG_RIGHT || angle == DIRECTION_45::ANG_ACUTE );
|
|
|
|
if( pullback_1 || pullback_2 )
|
|
{
|
|
if( !tail.IsArcSegment( lastSegIdx ) )
|
|
{
|
|
const SEG& seg = tail.CSegment( lastSegIdx );
|
|
m_direction = DIRECTION_45( seg );
|
|
PNS_DBG( Dbg(), AddPoint, m_p_start, WHITE, 10000, wxT( "new-pstart [pullback3]" ) );
|
|
|
|
}
|
|
else
|
|
{
|
|
const SHAPE_ARC& arc = tail.CArcs()[tail.ArcIndex( lastSegIdx )];
|
|
m_direction = DIRECTION_45( arc );
|
|
}
|
|
|
|
PNS_DBG( Dbg(), Message, wxString::Format( "Placer: pullback triggered [%d] [%s %s]",
|
|
n, last_tail.Format(), first_head.Format() ) );
|
|
|
|
// erase the last point in the tail, hoping that the next iteration will
|
|
// result with a head trace that starts with a segment following our
|
|
// current direction.
|
|
if( n < 2 )
|
|
tail.Clear(); // don't leave a single-point tail
|
|
else
|
|
tail.RemoveShape( -1 );
|
|
|
|
if( !tail.SegmentCount() )
|
|
m_direction = m_initial_direction;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::reduceTail( const VECTOR2I& aEnd )
|
|
{
|
|
SHAPE_LINE_CHAIN& head = m_head.Line();
|
|
SHAPE_LINE_CHAIN& tail = m_tail.Line();
|
|
|
|
int n = tail.SegmentCount();
|
|
|
|
if( head.SegmentCount() < 1 )
|
|
return false;
|
|
|
|
// Don't attempt this for too short tails
|
|
if( n < 2 )
|
|
return false;
|
|
|
|
// Start from the segment farthest from the end of the tail
|
|
// int start_index = std::max(n - 1 - ReductionDepth, 0);
|
|
|
|
DIRECTION_45 new_direction;
|
|
VECTOR2I new_start;
|
|
int reduce_index = -1;
|
|
|
|
for( int i = tail.SegmentCount() - 1; i >= 0; i-- )
|
|
{
|
|
const SEG s = tail.CSegment( i );
|
|
DIRECTION_45 dir( s );
|
|
|
|
// calculate a replacement route and check if it matches
|
|
// the direction of the segment to be replaced
|
|
SHAPE_LINE_CHAIN replacement = dir.BuildInitialTrace( s.A, aEnd );
|
|
|
|
if( replacement.SegmentCount() < 1 )
|
|
continue;
|
|
|
|
LINE tmp( m_tail, replacement );
|
|
|
|
if( m_currentNode->CheckColliding( &tmp, ITEM::ANY_T ) )
|
|
break;
|
|
|
|
if( DIRECTION_45( replacement.CSegment( 0 ) ) == dir )
|
|
{
|
|
new_start = s.A;
|
|
new_direction = dir;
|
|
reduce_index = i;
|
|
}
|
|
}
|
|
|
|
if( reduce_index >= 0 )
|
|
{
|
|
PNS_DBG( Dbg(), Message, wxString::Format( "Placer: reducing tail: %d" , reduce_index ) );
|
|
SHAPE_LINE_CHAIN reducedLine = new_direction.BuildInitialTrace( new_start, aEnd );
|
|
|
|
m_direction = new_direction;
|
|
tail.Remove( reduce_index + 1, -1 );
|
|
head.Clear();
|
|
return true;
|
|
}
|
|
|
|
if( !tail.SegmentCount() )
|
|
m_direction = m_initial_direction;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::mergeHead()
|
|
{
|
|
SHAPE_LINE_CHAIN& head = m_head.Line();
|
|
SHAPE_LINE_CHAIN& tail = m_tail.Line();
|
|
|
|
const int ForbiddenAngles = DIRECTION_45::ANG_ACUTE
|
|
| DIRECTION_45::ANG_HALF_FULL
|
|
| DIRECTION_45::ANG_UNDEFINED;
|
|
|
|
head.Simplify();
|
|
tail.Simplify();
|
|
|
|
int n_head = head.ShapeCount();
|
|
int n_tail = tail.ShapeCount();
|
|
|
|
if( n_head < 3 )
|
|
{
|
|
PNS_DBG( Dbg(), Message, wxT( "Merge failed: not enough head segs." ) );
|
|
return false;
|
|
}
|
|
|
|
if( n_tail && head.CPoint( 0 ) != tail.CPoint( -1 ) )
|
|
{
|
|
PNS_DBG( Dbg(), Message, wxT( "Merge failed: head and tail discontinuous." ) );
|
|
return false;
|
|
}
|
|
|
|
if( m_head.CountCorners( ForbiddenAngles ) != 0 )
|
|
return false;
|
|
|
|
DIRECTION_45 dir_tail, dir_head;
|
|
|
|
if( !head.IsPtOnArc( 0 ) )
|
|
dir_head = DIRECTION_45( head.CSegment( 0 ) );
|
|
else
|
|
dir_head = DIRECTION_45( head.CArcs()[head.ArcIndex( 0 )] );
|
|
|
|
if( n_tail )
|
|
{
|
|
wxASSERT( tail.PointCount() >= 2 );
|
|
int lastSegIdx = tail.PointCount() - 2;
|
|
|
|
if( !tail.IsPtOnArc( lastSegIdx ) )
|
|
dir_tail = DIRECTION_45( tail.CSegment( -1 ) );
|
|
else
|
|
dir_tail = DIRECTION_45( tail.CArcs()[tail.ArcIndex( lastSegIdx )] );
|
|
|
|
if( dir_head.Angle( dir_tail ) & ForbiddenAngles )
|
|
return false;
|
|
}
|
|
|
|
tail.Append( head );
|
|
|
|
tail.Simplify();
|
|
|
|
int lastSegIdx = tail.PointCount() - 2;
|
|
|
|
if( !tail.IsArcSegment( lastSegIdx ) )
|
|
m_direction = DIRECTION_45( tail.CSegment( -1 ) );
|
|
else
|
|
m_direction = DIRECTION_45( tail.CArcs()[tail.ArcIndex( lastSegIdx )] );
|
|
|
|
head.Remove( 0, -1 );
|
|
|
|
PNS_DBG( Dbg(), Message, wxString::Format( "Placer: merge %d, new direction: %s" , n_head,
|
|
m_direction.Format() ) );
|
|
|
|
head.Simplify();
|
|
tail.Simplify();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::clipAndCheckCollisions( const VECTOR2I& aP, const SHAPE_LINE_CHAIN& aL,
|
|
SHAPE_LINE_CHAIN& aOut, int &thresholdDist )
|
|
{
|
|
SHAPE_LINE_CHAIN l( aL );
|
|
int idx = l.Split( aP );
|
|
|
|
if( idx < 0)
|
|
return false;
|
|
|
|
bool rv = true;
|
|
|
|
SHAPE_LINE_CHAIN l2 = l.Slice( 0, idx );
|
|
int dist = l2.Length();
|
|
|
|
PNS_DBG( Dbg(), AddPoint, aP, BLUE, 500000, wxString::Format( "hug-target-check-%d", idx ) );
|
|
PNS_DBG( Dbg(), AddShape, &l2, BLUE, 500000, wxT( "hug-target-line" ) );
|
|
|
|
if( dist < thresholdDist )
|
|
rv = false;
|
|
|
|
LINE ctest( m_head, l2 );
|
|
|
|
if( m_currentNode->CheckColliding( &ctest ).has_value() )
|
|
rv = false;
|
|
|
|
if( rv )
|
|
{
|
|
aOut = l2;
|
|
thresholdDist = dist;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::cursorDistMinimum( const SHAPE_LINE_CHAIN& aL, const VECTOR2I& aCursor,
|
|
double lengthThreshold, SHAPE_LINE_CHAIN &aOut )
|
|
{
|
|
std::vector<int> dists;
|
|
std::vector<VECTOR2I> pts;
|
|
|
|
if( aL.PointCount() == 0 )
|
|
return false;
|
|
|
|
VECTOR2I lastP = aL.CPoint(-1);
|
|
int accumulatedDist = 0;
|
|
|
|
dists.reserve( 2 * aL.PointCount() );
|
|
|
|
for( int i = 0; i < aL.SegmentCount(); i++ )
|
|
{
|
|
const SEG& s = aL.CSegment( i );
|
|
|
|
dists.push_back( ( aCursor - s.A ).EuclideanNorm() );
|
|
pts.push_back( s.A );
|
|
auto pn = s.NearestPoint( aCursor );
|
|
|
|
if( pn != s.A && pn != s.B )
|
|
{
|
|
dists.push_back( ( pn - aCursor ).EuclideanNorm() );
|
|
pts.push_back( pn );
|
|
}
|
|
|
|
accumulatedDist += s.Length();
|
|
|
|
if ( accumulatedDist > lengthThreshold )
|
|
{
|
|
lastP = s.B;
|
|
break;
|
|
}
|
|
}
|
|
|
|
dists.push_back( ( aCursor - lastP ).EuclideanNorm() );
|
|
pts.push_back( lastP );
|
|
|
|
int minDistLoc = std::numeric_limits<int>::max();
|
|
int minPLoc = -1;
|
|
int minDistGlob = std::numeric_limits<int>::max();
|
|
int minPGlob = -1;
|
|
|
|
for( int i = 0; i < dists.size(); i++ )
|
|
{
|
|
int d = dists[i];
|
|
|
|
if( d < minDistGlob )
|
|
{
|
|
minDistGlob = d;
|
|
minPGlob = i;
|
|
}
|
|
}
|
|
|
|
if( dists.size() >= 3 )
|
|
{
|
|
for( int i = 0; i < dists.size() - 3; i++ )
|
|
{
|
|
if( dists[i + 2] > dists[i + 1] && dists[i] > dists[i + 1] )
|
|
{
|
|
int d = dists[i + 1];
|
|
if( d < minDistLoc )
|
|
{
|
|
minDistLoc = d;
|
|
minPLoc = i + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( dists.back() < minDistLoc && minPLoc >= 0 )
|
|
{
|
|
minDistLoc = dists.back();
|
|
minPLoc = dists.size() - 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Too few points: just use the global
|
|
minDistLoc = minDistGlob;
|
|
minPLoc = minPGlob;
|
|
}
|
|
|
|
// fixme: I didn't make my mind yet if local or global minimum feels better. I'm leaving both
|
|
// in the code, enabling the global one by default
|
|
minPLoc = -1;
|
|
int preferred;
|
|
|
|
if( minPLoc < 0 )
|
|
{
|
|
preferred = minPGlob;
|
|
}
|
|
else
|
|
{
|
|
preferred = minPLoc;
|
|
}
|
|
|
|
int thresholdDist = 0;
|
|
|
|
if( clipAndCheckCollisions( pts[preferred], aL, aOut, thresholdDist ) )
|
|
return true;
|
|
|
|
thresholdDist = 0;
|
|
|
|
SHAPE_LINE_CHAIN l( aL ), prefL;
|
|
int minDist = std::numeric_limits<int>::max();
|
|
|
|
bool ok = false;
|
|
|
|
for( int i = 0; i < pts.size() ; i++)
|
|
{
|
|
//PNS_DBG( Dbg(), AddPoint, pts[i], BLUE, 500000, wxT( "hug-target-fallback" ) );
|
|
|
|
ok |= clipAndCheckCollisions( pts[i], aL, aOut, thresholdDist );
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::rhWalkBase( const VECTOR2I& aP, LINE& aWalkLine, int aCollisionMask,
|
|
PNS::PNS_MODE aMode, bool& aViaOk )
|
|
{
|
|
LINE walkFull( m_head );
|
|
LINE l1( m_head );
|
|
|
|
PNS_DBG( Dbg(), AddItem, &m_tail, GREEN, 100000, wxT( "walk-base-old-tail" ) );
|
|
PNS_DBG( Dbg(), AddItem, &m_head, BLUE, 100000, wxT( "walk-base-old-head" ) );
|
|
|
|
VECTOR2I walkP = aP;
|
|
|
|
WALKAROUND walkaround( m_currentNode, Router() );
|
|
|
|
walkaround.SetSolidsOnly( false );
|
|
walkaround.SetDebugDecorator( Dbg() );
|
|
walkaround.SetLogger( Logger() );
|
|
walkaround.SetIterationLimit( Settings().WalkaroundIterationLimit() );
|
|
walkaround.SetItemMask( aCollisionMask );
|
|
walkaround.SetAllowedPolicies( { WALKAROUND::WP_CCW, WALKAROUND::WP_CW } );
|
|
|
|
int round = 0;
|
|
|
|
do
|
|
{
|
|
l1.Clear();
|
|
|
|
PNS_DBG( Dbg(), BeginGroup, wxString::Format( "walk-round-%d", round ), 0 );
|
|
round++;
|
|
|
|
aViaOk = buildInitialLine( walkP, l1, aMode, round == 0 );
|
|
PNS_DBG( Dbg(), AddItem, &l1, BLUE, 20000, wxT( "walk-base-l1" ) );
|
|
|
|
if( l1.EndsWithVia() )
|
|
PNS_DBG( Dbg(), AddPoint, l1.Via().Pos(), BLUE, 100000, wxT( "walk-base-l1-via" ) );
|
|
|
|
LINE initTrack( m_tail );
|
|
initTrack.Line().Append( l1.CLine() );
|
|
initTrack.Line().Simplify();
|
|
|
|
|
|
double initialLength = initTrack.CLine().Length();
|
|
double hugThresholdLength = initialLength * Settings().WalkaroundHugLengthThreshold();
|
|
double hugThresholdLengthComplete =
|
|
2.0 * initialLength * Settings().WalkaroundHugLengthThreshold();
|
|
|
|
WALKAROUND::RESULT wr = walkaround.Route( initTrack );
|
|
std::optional<LINE> bestLine;
|
|
|
|
OPTIMIZER optimizer( m_currentNode );
|
|
|
|
optimizer.SetEffortLevel( OPTIMIZER::MERGE_SEGMENTS );
|
|
optimizer.SetCollisionMask( aCollisionMask );
|
|
|
|
using WALKAROUND::WP_CW;
|
|
using WALKAROUND::WP_CCW;
|
|
|
|
int len_cw = wr.status[WP_CW] != WALKAROUND::ST_STUCK ? wr.lines[WP_CW].CLine().Length()
|
|
: std::numeric_limits<int>::max();
|
|
int len_ccw = wr.status[WP_CCW] != WALKAROUND::ST_STUCK ? wr.lines[WP_CCW].CLine().Length()
|
|
: std::numeric_limits<int>::max();
|
|
|
|
|
|
if( wr.status[ WP_CW ] == WALKAROUND::ST_DONE )
|
|
{
|
|
PNS_DBG( Dbg(), AddItem, &wr.lines[WP_CW], BLUE, 20000, wxT( "wf-result-cw-preopt" ) );
|
|
LINE tmpHead, tmpTail;
|
|
|
|
|
|
OPTIMIZER::Optimize( &wr.lines[WP_CW], OPTIMIZER::MERGE_SEGMENTS, m_currentNode );
|
|
|
|
if( splitHeadTail( wr.lines[WP_CW], m_tail, tmpHead, tmpTail ) )
|
|
{
|
|
optimizer.Optimize( &tmpHead );
|
|
wr.lines[WP_CW].SetShape( tmpTail.CLine () );
|
|
wr.lines[WP_CW].Line().Append( tmpHead.CLine( ) );
|
|
}
|
|
|
|
PNS_DBG( Dbg(), AddItem, &wr.lines[WP_CW], RED, 20000, wxT( "wf-result-cw-postopt" ) );
|
|
len_cw = wr.lines[WP_CW].CLine().Length();
|
|
bestLine = wr.lines[WP_CW];
|
|
}
|
|
|
|
if( wr.status[WP_CCW] == WALKAROUND::ST_DONE )
|
|
{
|
|
PNS_DBG( Dbg(), AddItem, &wr.lines[WP_CCW], BLUE, 20000, wxT( "wf-result-ccw-preopt" ) );
|
|
|
|
LINE tmpHead, tmpTail;
|
|
|
|
OPTIMIZER::Optimize( &wr.lines[WP_CCW], OPTIMIZER::MERGE_SEGMENTS, m_currentNode );
|
|
|
|
if( splitHeadTail( wr.lines[WP_CCW], m_tail, tmpHead, tmpTail ) )
|
|
{
|
|
optimizer.Optimize( &tmpHead );
|
|
wr.lines[WP_CCW].SetShape( tmpTail.CLine () );
|
|
wr.lines[WP_CCW].Line().Append( tmpHead.CLine( ) );
|
|
}
|
|
|
|
PNS_DBG( Dbg(), AddItem, &wr.lines[WP_CCW], RED, 20000, wxT( "wf-result-ccw-postopt" ) );
|
|
len_ccw = wr.lines[WP_CCW].CLine().Length();
|
|
|
|
if( len_ccw < len_cw )
|
|
bestLine = wr.lines[WP_CCW];
|
|
}
|
|
|
|
int bestLength = len_cw < len_ccw ? len_cw : len_ccw;
|
|
|
|
if( bestLength < hugThresholdLengthComplete && bestLine.has_value() )
|
|
{
|
|
walkFull.SetShape( bestLine->CLine() );
|
|
walkP = walkFull.CLine().CPoint(-1);
|
|
PNS_DBGN( Dbg(), EndGroup );
|
|
continue;
|
|
}
|
|
|
|
bool validCw = false;
|
|
bool validCcw = false;
|
|
int distCcw = std::numeric_limits<int>::max();
|
|
int distCw = std::numeric_limits<int>::max();
|
|
|
|
SHAPE_LINE_CHAIN l_cw, l_ccw;
|
|
|
|
|
|
if( wr.status[WP_CW] != WALKAROUND::ST_STUCK )
|
|
{
|
|
validCw = cursorDistMinimum( wr.lines[WP_CW].CLine(), aP, hugThresholdLength, l_cw );
|
|
|
|
if( validCw )
|
|
distCw = ( aP - l_cw.CPoint( -1 ) ).EuclideanNorm();
|
|
|
|
PNS_DBG( Dbg(), AddShape, &l_cw, MAGENTA, 200000, wxString::Format( "wh-result-cw %s",
|
|
validCw ? "non-colliding"
|
|
: "colliding" ) );
|
|
}
|
|
|
|
if( wr.status[WP_CCW] != WALKAROUND::ST_STUCK )
|
|
{
|
|
validCcw = cursorDistMinimum( wr.lines[WP_CCW].CLine(), aP, hugThresholdLength, l_ccw );
|
|
|
|
if( validCcw )
|
|
distCcw = ( aP - l_ccw.CPoint( -1 ) ).EuclideanNorm();
|
|
|
|
PNS_DBG( Dbg(), AddShape, &l_ccw, MAGENTA, 200000, wxString::Format( "wh-result-ccw %s",
|
|
validCcw ? "non-colliding"
|
|
: "colliding" ) );
|
|
}
|
|
|
|
|
|
if( distCw < distCcw && validCw )
|
|
{
|
|
walkFull.SetShape( l_cw );
|
|
walkP = l_cw.CPoint(-1);
|
|
}
|
|
else if( validCcw )
|
|
{
|
|
walkFull.SetShape( l_ccw );
|
|
walkP = l_ccw.CPoint(-1);
|
|
}
|
|
else
|
|
{
|
|
PNS_DBGN( Dbg(), EndGroup );
|
|
return false;
|
|
}
|
|
|
|
PNS_DBGN( Dbg(), EndGroup );
|
|
} while( round < 2 && m_placingVia );
|
|
|
|
|
|
if( l1.EndsWithVia() )
|
|
{
|
|
VIA v ( l1.Via() );
|
|
v.SetPos( walkFull.CPoint( -1 ) );
|
|
walkFull.AppendVia( v );
|
|
}
|
|
|
|
PNS_DBG( Dbg(), AddItem, &walkFull, GREEN, 200000, wxT( "walk-full" ) );
|
|
|
|
if( walkFull.EndsWithVia() )
|
|
{
|
|
PNS_DBG( Dbg(), AddPoint, walkFull.Via().Pos(), GREEN, 200000,
|
|
wxString::Format( "walk-via ok %d", aViaOk ? 1 : 0 ) );
|
|
}
|
|
|
|
aWalkLine = walkFull;
|
|
|
|
return !walkFull.EndsWithVia() || aViaOk;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::rhWalkOnly( const VECTOR2I& aP, LINE& aNewHead, LINE& aNewTail )
|
|
{
|
|
LINE walkFull;
|
|
|
|
int effort = 0;
|
|
bool viaOk = false;
|
|
|
|
if( ! rhWalkBase( aP, walkFull, ITEM::ANY_T, RM_Walkaround, viaOk ) )
|
|
return false;
|
|
|
|
switch( Settings().OptimizerEffort() )
|
|
{
|
|
case OE_LOW:
|
|
effort = 0;
|
|
break;
|
|
|
|
case OE_MEDIUM:
|
|
case OE_FULL:
|
|
effort = OPTIMIZER::MERGE_SEGMENTS;
|
|
break;
|
|
}
|
|
|
|
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 )
|
|
&& !m_mouseTrailTracer.IsManuallyForced() )
|
|
{
|
|
effort |= OPTIMIZER::SMART_PADS;
|
|
}
|
|
|
|
if( m_currentNode->CheckColliding( &walkFull ) )
|
|
{
|
|
PNS_DBG( Dbg(), AddItem, &walkFull, GREEN, 100000, wxString::Format( "collision check fail" ) );
|
|
return false;
|
|
}
|
|
|
|
// OK, this deserves a bit of explanation. We used to calculate the walk path for the head only,
|
|
// but then the clearance epsilon was added, with the intent of improving collision resolution robustness
|
|
// (now a hull or a walk/shove line cannot collide with the 'owner' of the hull under any circumstances).
|
|
// This, however, introduced a subtle bug. For a row/column/any other 'regular' arrangement
|
|
// of overlapping hulls (think of pads of a SOP/SOIC chip or a regular via grid), walking around may
|
|
// produce a new 'head' that is not considered colliding (due to the clearance epsilon), but with
|
|
// its start point inside one of the subsequent hulls to process.
|
|
// We can't have head[0] inside any hull for the algorithm to work - therefore, we now consider the entire
|
|
// 'tail+head' trace when walking around and in case of success, reconstruct the
|
|
// 'head' and 'tail' by splitting the walk line at a point that is as close as possible to the original
|
|
// head[0], but not inside any obstacle hull.
|
|
//
|
|
// EXECUTIVE SUMMARY: asinine heuristic to make the router get stuck much less often.
|
|
|
|
if( ! splitHeadTail( walkFull, m_tail, aNewHead, aNewTail ) )
|
|
return false;
|
|
|
|
if( m_placingVia && viaOk )
|
|
{
|
|
PNS_DBG( Dbg(), AddPoint, aNewHead.CPoint(-1), RED, 1000000, wxString::Format( "VIA" ) );
|
|
|
|
aNewHead.AppendVia( makeVia( aNewHead.CPoint( -1 ) ) );
|
|
}
|
|
|
|
OPTIMIZER::Optimize( &aNewHead, effort, m_currentNode );
|
|
|
|
PNS_DBG( Dbg(), AddItem, &aNewHead, GREEN, 100000, wxString::Format( "walk-new-head" ) );
|
|
PNS_DBG( Dbg(), AddItem, &aNewTail, BLUE, 100000, wxT( "walk-new-tail" ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::rhMarkObstacles( const VECTOR2I& aP, LINE& aNewHead, LINE& aNewTail )
|
|
{
|
|
buildInitialLine( aP, m_head, RM_MarkObstacles );
|
|
m_head.SetBlockingObstacle( nullptr );
|
|
|
|
auto obs = m_currentNode->NearestObstacle( &m_head );
|
|
|
|
// If the head is in colliding state, snap to the hull of the first obstacle.
|
|
// This way, one can route tracks as tightly as possible without enabling
|
|
// the shove/walk mode that certain users find too intrusive.
|
|
if( obs )
|
|
{
|
|
int clearance = m_currentNode->GetClearance( obs->m_item, &m_head, false );
|
|
SHAPE_LINE_CHAIN hull = obs->m_item->Hull( clearance, m_head.Width(), m_head.Layer() );
|
|
VECTOR2I nearest;
|
|
|
|
DIRECTION_45::CORNER_MODE cornerMode = Settings().GetCornerMode();
|
|
|
|
if( cornerMode == DIRECTION_45::MITERED_90 || cornerMode == DIRECTION_45::ROUNDED_90 )
|
|
nearest = hull.BBox().NearestPoint( aP );
|
|
else
|
|
nearest = hull.NearestPoint( aP );
|
|
|
|
if( ( nearest - aP ).EuclideanNorm() < m_head.Width() / 2 )
|
|
buildInitialLine( nearest, m_head, RM_MarkObstacles );
|
|
}
|
|
|
|
// Note: Something like the below could be used to implement a "stop at first obstacle" mode,
|
|
// but we don't have one right now and there isn't a lot of demand for one. If we do end up
|
|
// doing that, put it in a new routing mode as "highlight collisions" mode should not have
|
|
// collision handling other than highlighting.
|
|
#if 0
|
|
if( !Settings().AllowDRCViolations() )
|
|
{
|
|
NODE::OPT_OBSTACLE obs = m_currentNode->NearestObstacle( &m_head );
|
|
|
|
if( obs && obs->m_distFirst != INT_MAX )
|
|
{
|
|
buildInitialLine( obs->m_ipFirst, m_head );
|
|
m_head.SetBlockingObstacle( obs->m_item );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
aNewHead = m_head;
|
|
aNewTail = m_tail;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::splitHeadTail( const LINE& aNewLine, const LINE& aOldTail, LINE& aNewHead,
|
|
LINE& aNewTail )
|
|
{
|
|
LINE newTail( aOldTail );
|
|
LINE newHead( aOldTail );
|
|
LINE l2( aNewLine );
|
|
|
|
newTail.RemoveVia();
|
|
newHead.Clear();
|
|
|
|
int i;
|
|
bool found = false;
|
|
int n = l2.PointCount();
|
|
|
|
if( n > 1 && aOldTail.PointCount() > 1 )
|
|
{
|
|
if( l2.CLine().PointOnEdge( aOldTail.CPoint( -1 ) ) )
|
|
{
|
|
l2.Line().Split( aOldTail.CPoint( -1 ) );
|
|
}
|
|
|
|
for( i = 0; i < aOldTail.PointCount(); i++ )
|
|
{
|
|
if( l2.CLine().Find( aOldTail.CPoint( i ) ) < 0 )
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !found )
|
|
i--;
|
|
|
|
// If the old tail doesn't have any points of the new line, we can't split it.
|
|
if( i >= l2.PointCount() )
|
|
i = l2.PointCount() - 1;
|
|
|
|
newHead.Clear();
|
|
|
|
if( i == 0 )
|
|
newTail.Clear();
|
|
else
|
|
newTail.SetShape( l2.CLine().Slice( 0, i ) );
|
|
|
|
newHead.SetShape( l2.CLine().Slice( i, -1 ) );
|
|
}
|
|
else
|
|
{
|
|
newTail.Clear();
|
|
newHead = l2;
|
|
}
|
|
|
|
PNS_DBG( Dbg(), AddItem, &newHead, BLUE, 500000, wxT( "head-post-split" ) );
|
|
|
|
aNewHead = newHead;
|
|
aNewTail = newTail;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::rhShoveOnly( const VECTOR2I& aP, LINE& aNewHead, LINE& aNewTail )
|
|
{
|
|
LINE walkSolids;
|
|
|
|
bool viaOk = false;
|
|
|
|
if( ! rhWalkBase( aP, walkSolids, ITEM::SOLID_T, RM_Shove, viaOk ) )
|
|
return false;
|
|
|
|
m_currentNode = m_shove->CurrentNode();
|
|
|
|
m_shove->SetLogger( Logger() );
|
|
m_shove->SetDebugDecorator( Dbg() );
|
|
|
|
if( m_endItem )
|
|
{
|
|
// Make sure the springback algorithm won't erase the NODE that owns m_endItem.
|
|
m_shove->SetSpringbackDoNotTouchNode( static_cast<const NODE*>( m_endItem->Owner() ) );
|
|
}
|
|
|
|
LINE newHead( walkSolids );
|
|
|
|
if( walkSolids.EndsWithVia() )
|
|
PNS_DBG( Dbg(), AddPoint, newHead.Via().Pos(), RED, 1000000, wxString::Format( "SVIA [%d]", viaOk?1:0 ) );
|
|
|
|
if( m_placingVia && viaOk )
|
|
{
|
|
newHead.AppendVia( makeVia( newHead.CPoint( -1 ) ) );
|
|
PNS_DBG( Dbg(), AddPoint, newHead.Via().Pos(), GREEN, 1000000, "shove-new-via" );
|
|
|
|
}
|
|
|
|
m_shove->ClearHeads();
|
|
m_shove->AddHeads( newHead, SHOVE::SHP_SHOVE );
|
|
bool shoveOk = m_shove->Run() == SHOVE::SH_OK;
|
|
|
|
m_currentNode = m_shove->CurrentNode();
|
|
|
|
int effort = 0;
|
|
|
|
switch( Settings().OptimizerEffort() )
|
|
{
|
|
case OE_LOW:
|
|
effort = 0;
|
|
break;
|
|
|
|
case OE_MEDIUM:
|
|
case OE_FULL:
|
|
effort = OPTIMIZER::MERGE_SEGMENTS;
|
|
break;
|
|
}
|
|
|
|
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 )
|
|
&& !m_mouseTrailTracer.IsManuallyForced() )
|
|
{
|
|
effort |= OPTIMIZER::SMART_PADS;
|
|
}
|
|
|
|
if( shoveOk )
|
|
{
|
|
if( m_shove->HeadsModified() )
|
|
newHead = m_shove->GetModifiedHead( 0 );
|
|
|
|
if( newHead.EndsWithVia() )
|
|
{
|
|
PNS_DBG( Dbg(), AddPoint, newHead.Via().Pos(), GREEN, 1000000, "shove-via-preopt" );
|
|
PNS_DBG( Dbg(), AddPoint, newHead.Via().Pos(), GREEN, 1000000, "shove-via-postopt" );
|
|
}
|
|
|
|
if( ! splitHeadTail( newHead, m_tail, aNewHead, aNewTail ) )
|
|
return false;
|
|
|
|
if( newHead.EndsWithVia() )
|
|
aNewHead.AppendVia( newHead.Via() );
|
|
|
|
OPTIMIZER::Optimize( &aNewHead, effort, m_currentNode );
|
|
PNS_DBG( Dbg(), AddItem, aNewHead.Clone(), GREEN, 1000000, "head-sh-postopt" );
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return rhWalkOnly( aP, aNewHead, aNewTail );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::routeHead( const VECTOR2I& aP, LINE& aNewHead, LINE& aNewTail )
|
|
{
|
|
switch( Settings().Mode() )
|
|
{
|
|
case RM_MarkObstacles:
|
|
return rhMarkObstacles( aP, aNewHead, aNewTail );
|
|
case RM_Walkaround:
|
|
return rhWalkOnly( aP, aNewHead, aNewTail );
|
|
case RM_Shove:
|
|
return rhShoveOnly( aP, aNewHead, aNewTail );
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::optimizeTailHeadTransition()
|
|
{
|
|
LINE linetmp = Trace();
|
|
|
|
PNS_DBG( Dbg(), Message, "optimize HT" );
|
|
|
|
// NOTE: FANOUT_CLEANUP can override posture setting at the moment
|
|
if( !m_mouseTrailTracer.IsManuallyForced() &&
|
|
OPTIMIZER::Optimize( &linetmp, OPTIMIZER::FANOUT_CLEANUP, m_currentNode ) )
|
|
{
|
|
if( linetmp.SegmentCount() < 1 )
|
|
return false;
|
|
|
|
m_head = linetmp;
|
|
m_direction = DIRECTION_45( linetmp.CSegment( 0 ) );
|
|
m_tail.Line().Clear();
|
|
|
|
return true;
|
|
}
|
|
|
|
SHAPE_LINE_CHAIN& head = m_head.Line();
|
|
SHAPE_LINE_CHAIN& tail = m_tail.Line();
|
|
|
|
int tailLookbackSegments = 3;
|
|
|
|
//if(m_currentMode() == RM_Walkaround)
|
|
// tailLookbackSegments = 10000;
|
|
|
|
int threshold = std::min( tail.PointCount(), tailLookbackSegments + 1 );
|
|
|
|
if( tail.ShapeCount() < 3 )
|
|
return false;
|
|
|
|
// assemble TailLookbackSegments tail segments with the current head
|
|
SHAPE_LINE_CHAIN opt_line = tail.Slice( -threshold, -1 );
|
|
|
|
int end = std::min(2, head.PointCount() - 1 );
|
|
|
|
opt_line.Append( head.Slice( 0, end ) );
|
|
|
|
LINE new_head( m_tail, opt_line );
|
|
|
|
// and see if it could be made simpler by merging obtuse/collnear segments.
|
|
// If so, replace the (threshold) last tail points and the head with
|
|
// the optimized line
|
|
|
|
PNS_DBG( Dbg(), AddItem, &new_head, LIGHTCYAN, 10000, wxT( "ht-newline" ) );
|
|
|
|
if( OPTIMIZER::Optimize( &new_head, OPTIMIZER::MERGE_SEGMENTS, m_currentNode ) )
|
|
{
|
|
LINE tmp( m_tail, opt_line );
|
|
|
|
head.Clear();
|
|
tail.Replace( -threshold, -1, new_head.CLine() );
|
|
tail.Simplify();
|
|
|
|
m_direction = DIRECTION_45( new_head.CSegment( -1 ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void LINE_PLACER::updatePStart( const LINE& tail )
|
|
{
|
|
if( tail.CLine().PointCount() )
|
|
m_p_start = tail.CLine().CPoint(-1);
|
|
else
|
|
m_p_start = m_currentStart;
|
|
}
|
|
|
|
void LINE_PLACER::routeStep( const VECTOR2I& aP )
|
|
{
|
|
bool fail = false;
|
|
bool go_back = false;
|
|
|
|
int i, n_iter = 1;
|
|
|
|
|
|
PNS_DBG( Dbg(), Message, wxString::Format( "routeStep: direction: %s head: %d, tail: %d shapes" ,
|
|
m_direction.Format(),
|
|
m_head.ShapeCount(),
|
|
m_tail.ShapeCount() ) );
|
|
|
|
PNS_DBG( Dbg(), BeginGroup, wxT( "route-step" ), 0 );
|
|
|
|
PNS_DBG( Dbg(), AddItem, &m_tail, WHITE, 10000, wxT( "tail-init" ) );
|
|
PNS_DBG( Dbg(), AddItem, &m_head, GREEN, 10000, wxT( "head-init" ) );
|
|
|
|
for( i = 0; i < n_iter; i++ )
|
|
{
|
|
LINE prevTail( m_tail );
|
|
LINE prevHead( m_head );
|
|
LINE newHead, newTail;
|
|
|
|
if( !go_back && Settings().FollowMouse() )
|
|
reduceTail( aP );
|
|
|
|
PNS_DBG( Dbg(), AddItem, &m_tail, WHITE, 10000, wxT( "tail-after-reduce" ) );
|
|
PNS_DBG( Dbg(), AddItem, &m_head, GREEN, 10000, wxT( "head-after-reduce" ) );
|
|
|
|
go_back = false;
|
|
|
|
updatePStart( m_tail );
|
|
|
|
if( !routeHead( aP, newHead, newTail ) )
|
|
{
|
|
m_tail = prevTail;
|
|
m_head = prevHead;
|
|
|
|
// If we fail to walk out of the initial point (no tail), instead of returning an empty
|
|
// line, return a zero-length line so that the user gets some feedback that routing is
|
|
// happening. This will get pruned later.
|
|
if( m_tail.PointCount() == 0 )
|
|
{
|
|
m_tail.Line().Append( m_p_start );
|
|
m_tail.Line().Append( m_p_start, true );
|
|
}
|
|
|
|
fail = true;
|
|
}
|
|
|
|
updatePStart( m_tail );
|
|
|
|
PNS_DBG( Dbg(), AddItem, &newHead, LIGHTGREEN, 100000, wxString::Format( "new_head [fail: %d]", fail?1:0 ) );
|
|
|
|
if( fail )
|
|
break;
|
|
|
|
PNS_DBG( Dbg(), Message, wxString::Format( "N VIA H %d T %d\n", m_head.EndsWithVia() ? 1 : 0, m_tail.EndsWithVia() ? 1 : 0 ) );
|
|
|
|
m_head = newHead;
|
|
m_tail = newTail;
|
|
|
|
if( handleSelfIntersections() )
|
|
{
|
|
n_iter++;
|
|
go_back = true;
|
|
}
|
|
|
|
PNS_DBG( Dbg(), Message, wxString::Format( "SI VIA H %d T %d\n", m_head.EndsWithVia() ? 1 : 0, m_tail.EndsWithVia() ? 1 : 0 ) );
|
|
|
|
PNS_DBG( Dbg(), AddItem, &m_tail, WHITE, 10000, wxT( "tail-after-si" ) );
|
|
PNS_DBG( Dbg(), AddItem, &m_head, GREEN, 10000, wxT( "head-after-si" ) );
|
|
|
|
if( !go_back && handlePullback() )
|
|
{
|
|
n_iter++;
|
|
m_head.Clear();
|
|
go_back = true;
|
|
}
|
|
|
|
PNS_DBG( Dbg(), Message, wxString::Format( "PB VIA H %d T %d\n", m_head.EndsWithVia() ? 1 : 0, m_tail.EndsWithVia() ? 1 : 0 ) );
|
|
|
|
PNS_DBG( Dbg(), AddItem, &m_tail, WHITE, 100000, wxT( "tail-after-pb" ) );
|
|
PNS_DBG( Dbg(), AddItem, &m_head, GREEN, 100000, wxT( "head-after-pb" ) );
|
|
}
|
|
|
|
|
|
if( !fail && Settings().FollowMouse() )
|
|
{
|
|
PNS_DBG( Dbg(), AddItem, &m_tail, WHITE, 10000, wxT( "tail-pre-merge" ) );
|
|
PNS_DBG( Dbg(), AddItem, &m_head, GREEN, 10000, wxT( "head-pre-merge" ) );
|
|
|
|
if( !optimizeTailHeadTransition() )
|
|
{
|
|
PNS_DBG( Dbg(), Message, wxString::Format( "PreM VIA H %d T %d\n", m_head.EndsWithVia() ? 1 : 0, m_tail.EndsWithVia() ? 1 : 0 ) );
|
|
|
|
mergeHead();
|
|
|
|
PNS_DBG( Dbg(), Message, wxString::Format( "PostM VIA H %d T %d\n", m_head.EndsWithVia() ? 1 : 0, m_tail.EndsWithVia() ? 1 : 0 ) );
|
|
}
|
|
|
|
PNS_DBG( Dbg(), AddItem, &m_tail, WHITE, 100000, wxT( "tail-post-merge" ) );
|
|
PNS_DBG( Dbg(), AddItem, &m_head, GREEN, 100000, wxT( "head-post-merge" ) );
|
|
}
|
|
|
|
m_last_p_end = aP;
|
|
|
|
PNS_DBGN( Dbg(), EndGroup );
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::route( const VECTOR2I& aP )
|
|
{
|
|
routeStep( aP );
|
|
|
|
if( !m_head.PointCount() )
|
|
return false;
|
|
|
|
return m_head.CPoint( -1 ) == aP;
|
|
}
|
|
|
|
|
|
const LINE LINE_PLACER::Trace() const
|
|
{
|
|
SHAPE_LINE_CHAIN l( m_tail.CLine() );
|
|
l.Append( m_head.CLine() );
|
|
|
|
// Only simplify if we have more than two points, because if we have a zero-length seg as the
|
|
// only part of the trace, we don't want it to be removed at this stage (will be the case if
|
|
// the routing start point violates DRC due to track width in shove/walk mode, for example).
|
|
if( l.PointCount() > 2 )
|
|
l.Simplify();
|
|
|
|
LINE tmp( m_head );
|
|
|
|
tmp.SetShape( l );
|
|
|
|
PNS_DBG( Dbg(), AddItem, &m_tail, GREEN, 100000, wxT( "tmp-tail" ) );
|
|
PNS_DBG( Dbg(), AddItem, &m_head, LIGHTGREEN, 100000, wxT( "tmp-head" ) );
|
|
|
|
return tmp;
|
|
}
|
|
|
|
|
|
const ITEM_SET LINE_PLACER::Traces()
|
|
{
|
|
m_currentTrace = Trace();
|
|
return ITEM_SET( &m_currentTrace );
|
|
}
|
|
|
|
|
|
void LINE_PLACER::FlipPosture()
|
|
{
|
|
// In order to fix issue 12369 get the current line placer first direction
|
|
// and copy it to the mouse trail tracer, as the current placer may have
|
|
// changed the route.
|
|
if( m_mouseTrailTracer.IsManuallyForced() == false && m_currentTrace.SegmentCount() > 0 )
|
|
{
|
|
DIRECTION_45 firstDirection( m_currentTrace.CSegment( 0 ) );
|
|
|
|
m_mouseTrailTracer.SetDefaultDirections( firstDirection, DIRECTION_45::UNDEFINED );
|
|
}
|
|
|
|
m_mouseTrailTracer.FlipPosture();
|
|
}
|
|
|
|
|
|
NODE* LINE_PLACER::CurrentNode( bool aLoopsRemoved ) const
|
|
{
|
|
if( aLoopsRemoved && m_lastNode )
|
|
return m_lastNode;
|
|
|
|
return m_currentNode;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::SplitAdjacentSegments( NODE* aNode, ITEM* aSeg, const VECTOR2I& aP )
|
|
{
|
|
if( !aSeg )
|
|
return false;
|
|
|
|
if( !aSeg->OfKind( ITEM::SEGMENT_T ) )
|
|
return false;
|
|
|
|
const JOINT* jt = aNode->FindJoint( aP, aSeg );
|
|
|
|
if( jt && jt->LinkCount() >= 1 )
|
|
return false;
|
|
|
|
SEGMENT* s_old = static_cast<SEGMENT*>( aSeg );
|
|
|
|
std::unique_ptr<SEGMENT> s_new[2] = { Clone( *s_old ), Clone( *s_old ) };
|
|
|
|
s_new[0]->SetEnds( s_old->Seg().A, aP );
|
|
s_new[1]->SetEnds( aP, s_old->Seg().B );
|
|
|
|
aNode->Remove( s_old );
|
|
aNode->Add( std::move( s_new[0] ), true );
|
|
aNode->Add( std::move( s_new[1] ), true );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::SplitAdjacentArcs( NODE* aNode, ITEM* aArc, const VECTOR2I& aP )
|
|
{
|
|
if( !aArc )
|
|
return false;
|
|
|
|
if( !aArc->OfKind( ITEM::ARC_T ) )
|
|
return false;
|
|
|
|
const JOINT* jt = aNode->FindJoint( aP, aArc );
|
|
|
|
if( jt && jt->LinkCount() >= 1 )
|
|
return false;
|
|
|
|
ARC* a_old = static_cast<ARC*>( aArc );
|
|
const SHAPE_ARC& o_arc = a_old->Arc();
|
|
|
|
std::unique_ptr<ARC> a_new[2] = { Clone( *a_old ), Clone( *a_old ) };
|
|
|
|
a_new[0]->Arc().ConstructFromStartEndCenter( o_arc.GetP0(), aP, o_arc.GetCenter(),
|
|
o_arc.IsClockwise(), o_arc.GetWidth() );
|
|
|
|
a_new[1]->Arc().ConstructFromStartEndCenter( aP, o_arc.GetP1(), o_arc.GetCenter(),
|
|
o_arc.IsClockwise(), o_arc.GetWidth() );
|
|
|
|
aNode->Remove( a_old );
|
|
aNode->Add( std::move( a_new[0] ), true );
|
|
aNode->Add( std::move( a_new[1] ), true );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::SetLayer( int aLayer )
|
|
{
|
|
if( m_idle )
|
|
{
|
|
m_currentLayer = aLayer;
|
|
return true;
|
|
}
|
|
else if( m_chainedPlacement )
|
|
{
|
|
return false;
|
|
}
|
|
else if( !m_startItem
|
|
|| ( m_startItem->OfKind( ITEM::VIA_T ) && m_startItem->Layers().Overlaps( aLayer ) )
|
|
|| ( m_startItem->OfKind( ITEM::SOLID_T ) && m_startItem->Layers().Overlaps( aLayer ) ) )
|
|
{
|
|
m_currentLayer = aLayer;
|
|
m_p_start = m_currentStart;
|
|
m_direction = m_initial_direction;
|
|
m_mouseTrailTracer.Clear();
|
|
m_head.Line().Clear();
|
|
m_tail.Line().Clear();
|
|
m_head.RemoveVia();
|
|
m_tail.RemoveVia();
|
|
m_head.SetLayer( m_currentLayer );
|
|
m_tail.SetLayer( m_currentLayer );
|
|
Move( m_currentEnd, nullptr );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::Start( const VECTOR2I& aP, ITEM* aStartItem )
|
|
{
|
|
m_placementCorrect = false;
|
|
m_currentStart = VECTOR2I( aP );
|
|
m_fixStart = VECTOR2I( aP );
|
|
m_currentEnd = VECTOR2I( aP );
|
|
m_currentNet = aStartItem ? aStartItem->Net() : Router()->GetInterface()->GetOrphanedNetHandle();
|
|
m_startItem = aStartItem;
|
|
m_placingVia = false;
|
|
m_chainedPlacement = false;
|
|
m_fixedTail.Clear();
|
|
m_endItem = nullptr;
|
|
|
|
setInitialDirection( Settings().InitialDirection() );
|
|
|
|
initPlacement();
|
|
|
|
DIRECTION_45 initialDir = m_initial_direction;
|
|
DIRECTION_45 lastSegDir = DIRECTION_45::UNDEFINED;
|
|
|
|
if( aStartItem && aStartItem->Kind() == ITEM::SEGMENT_T )
|
|
{
|
|
// If we land on a segment endpoint, assume the starting direction is continuing along
|
|
// the same direction as the endpoint. If we started in the middle, don't set a
|
|
// direction so that the posture solver is not biased.
|
|
SEG seg = static_cast<SEGMENT*>( aStartItem )->Seg();
|
|
|
|
if( aP == seg.A )
|
|
lastSegDir = DIRECTION_45( seg.Reversed() );
|
|
else if( aP == seg.B )
|
|
lastSegDir = DIRECTION_45( seg );
|
|
}
|
|
else if( aStartItem && aStartItem->Kind() == ITEM::SOLID_T &&
|
|
static_cast<SOLID*>( aStartItem )->Parent()->Type() == PCB_PAD_T )
|
|
{
|
|
double angle = static_cast<SOLID*>( aStartItem )->GetOrientation().AsDegrees();
|
|
angle = ( angle + 22.5 ) / 45.0;
|
|
initialDir = DIRECTION_45( static_cast<DIRECTION_45::Directions>( int( angle ) ) );
|
|
}
|
|
|
|
PNS_DBG( Dbg(), Message, wxString::Format( "Posture: init %s, last seg %s",
|
|
initialDir.Format(), lastSegDir.Format() ) );
|
|
|
|
m_mouseTrailTracer.Clear();
|
|
m_mouseTrailTracer.AddTrailPoint( aP );
|
|
m_mouseTrailTracer.SetTolerance( m_head.Width() );
|
|
m_mouseTrailTracer.SetDefaultDirections( m_initial_direction, DIRECTION_45::UNDEFINED );
|
|
m_mouseTrailTracer.SetMouseDisabled( !Settings().GetAutoPosture() );
|
|
|
|
NODE *n;
|
|
|
|
if ( Settings().Mode() == PNS::RM_Shove )
|
|
n = m_shove->CurrentNode();
|
|
else
|
|
n = m_currentNode;
|
|
|
|
m_fixedTail.AddStage( m_fixStart, m_currentLayer, m_placingVia, m_direction, n );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void LINE_PLACER::initPlacement()
|
|
{
|
|
m_idle = false;
|
|
|
|
m_head.Line().Clear();
|
|
m_tail.Line().Clear();
|
|
m_head.SetNet( m_currentNet );
|
|
m_tail.SetNet( m_currentNet );
|
|
m_head.SetLayer( m_currentLayer );
|
|
m_tail.SetLayer( m_currentLayer );
|
|
m_head.SetWidth( m_sizes.TrackWidth() );
|
|
m_tail.SetWidth( m_sizes.TrackWidth() );
|
|
m_head.RemoveVia();
|
|
m_tail.RemoveVia();
|
|
|
|
m_last_p_end.reset();
|
|
m_p_start = m_currentStart;
|
|
m_direction = m_initial_direction;
|
|
|
|
NODE* world = Router()->GetWorld();
|
|
|
|
world->KillChildren();
|
|
NODE* rootNode = world->Branch();
|
|
|
|
SplitAdjacentSegments( rootNode, m_startItem, m_currentStart );
|
|
|
|
setWorld( rootNode );
|
|
|
|
wxLogTrace( wxT( "PNS" ), wxT( "world %p, intitial-direction %s layer %d" ),
|
|
m_world,
|
|
m_direction.Format().c_str(),
|
|
m_currentLayer );
|
|
|
|
m_lastNode = nullptr;
|
|
m_currentNode = m_world;
|
|
|
|
m_shove = std::make_unique<SHOVE>( m_world->Branch(), Router() );
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::Move( const VECTOR2I& aP, ITEM* aEndItem )
|
|
{
|
|
LINE current;
|
|
int eiDepth = -1;
|
|
|
|
if( aEndItem && aEndItem->Owner() )
|
|
eiDepth = static_cast<const NODE*>( aEndItem->Owner() )->Depth();
|
|
|
|
if( m_lastNode )
|
|
{
|
|
delete m_lastNode;
|
|
m_lastNode = nullptr;
|
|
}
|
|
|
|
m_endItem = aEndItem;
|
|
|
|
bool reachesEnd = route( aP );
|
|
|
|
current = Trace();
|
|
|
|
if( !current.PointCount() )
|
|
m_currentEnd = m_p_start;
|
|
else
|
|
m_currentEnd = current.CLine().CPoint( -1 );
|
|
|
|
NODE* latestNode = m_currentNode;
|
|
m_lastNode = latestNode->Branch();
|
|
|
|
if( reachesEnd
|
|
&& eiDepth >= 0
|
|
&& aEndItem && latestNode->Depth() >= eiDepth
|
|
&& current.SegmentCount() )
|
|
{
|
|
if ( aEndItem->Net() == m_currentNet )
|
|
SplitAdjacentSegments( m_lastNode, aEndItem, current.CPoint( -1 ) );
|
|
|
|
if( Settings().RemoveLoops() )
|
|
removeLoops( m_lastNode, current );
|
|
}
|
|
|
|
updateLeadingRatLine();
|
|
m_mouseTrailTracer.AddTrailPoint( aP );
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::FixRoute( const VECTOR2I& aP, ITEM* aEndItem, bool aForceFinish )
|
|
{
|
|
bool fixAll = Settings().GetFixAllSegments();
|
|
bool realEnd = false;
|
|
|
|
LINE pl = Trace();
|
|
|
|
if( Settings().Mode() == RM_MarkObstacles )
|
|
{
|
|
// Mark Obstacles is sort of a half-manual, half-automated mode in which the
|
|
// user has more responsibility and authority.
|
|
|
|
if( aEndItem )
|
|
{
|
|
// The user has indicated a connection should be made. If either the trace or
|
|
// endItem is net-less, then allow the connection by adopting the net of the other.
|
|
if( m_router->GetInterface()->GetNetCode( m_currentNet ) <= 0 )
|
|
{
|
|
m_currentNet = aEndItem->Net();
|
|
pl.SetNet( m_currentNet );
|
|
}
|
|
else if( m_router->GetInterface()->GetNetCode( aEndItem->Net() ) <= 0 )
|
|
{
|
|
aEndItem->SetNet( m_currentNet );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collisions still prevent fixing unless "Allow DRC violations" is checked
|
|
// Note that collisions can occur even in walk/shove modes if the beginning of the trace
|
|
// collides (for example if the starting track width is too high).
|
|
|
|
if( !Settings().AllowDRCViolations() )
|
|
{
|
|
NODE* checkNode = ( Settings().Mode() == RM_Shove ) ? m_shove->CurrentNode() : m_world;
|
|
std::optional<OBSTACLE> obs = checkNode->CheckColliding( &pl );
|
|
|
|
if( obs )
|
|
{
|
|
// TODO: Determine why the shove node sometimes reports collisions against shoved objects.
|
|
// For now, to work around this issue, we consider only solids in shove mode.
|
|
if( Settings().Mode() != RM_Shove || obs->m_item->OfKind( ITEM::SOLID_T ) )
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const SHAPE_LINE_CHAIN& l = pl.CLine();
|
|
|
|
if( !l.SegmentCount() )
|
|
{
|
|
if( m_lastNode )
|
|
{
|
|
// Do a final optimization to the stored state
|
|
NODE::ITEM_VECTOR removed, added;
|
|
m_lastNode->GetUpdatedItems( removed, added );
|
|
|
|
if( !added.empty() && added.back()->Kind() == ITEM::SEGMENT_T )
|
|
simplifyNewLine( m_lastNode, static_cast<SEGMENT*>( added.back() ) );
|
|
}
|
|
|
|
// Nothing to commit if we have an empty line
|
|
if( !pl.EndsWithVia() )
|
|
return false;
|
|
|
|
///< @todo Determine what to do if m_lastNode is a null pointer. I'm guessing return
|
|
///< false but someone with more knowledge of the code will need to determine that..
|
|
if( m_lastNode )
|
|
{
|
|
auto newVia = Clone( pl.Via() );
|
|
newVia->ResetUid();
|
|
m_lastNode->Add( std::move( newVia ) );
|
|
m_shove->AddLockedSpringbackNode( m_lastNode );
|
|
}
|
|
|
|
m_currentNode = nullptr;
|
|
|
|
m_idle = true;
|
|
m_placementCorrect = true;
|
|
return true;
|
|
}
|
|
|
|
VECTOR2I p_pre_last = l.CPoint( -1 );
|
|
const VECTOR2I p_last = l.CPoint( -1 );
|
|
|
|
if( l.PointCount() > 2 )
|
|
p_pre_last = l.CPoint( -2 );
|
|
|
|
if( aEndItem && m_currentNet && m_currentNet == aEndItem->Net() )
|
|
realEnd = true;
|
|
|
|
if( aForceFinish )
|
|
realEnd = true;
|
|
|
|
// TODO: Rollback doesn't work properly if fix-all isn't enabled and we are placing arcs,
|
|
// so if we are, act as though we are in fix-all mode.
|
|
if( !fixAll && l.ArcCount() )
|
|
fixAll = true;
|
|
|
|
// TODO: lastDirSeg will be calculated incorrectly if we end on an arc
|
|
SEG lastDirSeg = ( !fixAll && l.SegmentCount() > 1 ) ? l.CSegment( -2 ) : l.CSegment( -1 );
|
|
DIRECTION_45 d_last( lastDirSeg );
|
|
|
|
int lastV;
|
|
|
|
if( realEnd || m_placingVia || fixAll )
|
|
lastV = l.SegmentCount();
|
|
else
|
|
lastV = std::max( 1, l.SegmentCount() - 1 );
|
|
|
|
ARC arc;
|
|
SEGMENT seg;
|
|
LINKED_ITEM* lastItem = nullptr;
|
|
int lastArc = -1;
|
|
|
|
for( int i = 0; i < lastV; i++ )
|
|
{
|
|
ssize_t arcIndex = l.ArcIndex( i );
|
|
|
|
if( arcIndex < 0 || ( lastArc >= 0 && i == lastV - 1 && !l.IsPtOnArc( lastV ) ) )
|
|
{
|
|
seg = SEGMENT( pl.CSegment( i ), m_currentNet );
|
|
seg.SetWidth( pl.Width() );
|
|
seg.SetLayer( m_currentLayer );
|
|
|
|
std::unique_ptr<SEGMENT> sp = std::make_unique<SEGMENT>( seg );
|
|
lastItem = sp.get();
|
|
|
|
if( !m_lastNode->Add( std::move( sp ) ) )
|
|
lastItem = nullptr;
|
|
}
|
|
else
|
|
{
|
|
if( arcIndex == lastArc )
|
|
continue;
|
|
|
|
arc = ARC( l.Arc( arcIndex ), m_currentNet );
|
|
arc.SetWidth( pl.Width() );
|
|
arc.SetLayer( m_currentLayer );
|
|
|
|
std::unique_ptr<ARC> ap = std::make_unique<ARC>( arc );
|
|
lastItem = ap.get();
|
|
|
|
if( !m_lastNode->Add( std::move( ap ) ) )
|
|
lastItem = nullptr;
|
|
|
|
lastArc = arcIndex;
|
|
}
|
|
}
|
|
|
|
if( pl.EndsWithVia() )
|
|
{
|
|
auto newVia = Clone( pl.Via() );
|
|
newVia->ResetUid();
|
|
m_lastNode->Add( std::move( newVia ) );
|
|
}
|
|
|
|
|
|
if( realEnd && lastItem )
|
|
simplifyNewLine( m_lastNode, lastItem );
|
|
|
|
if( !realEnd )
|
|
{
|
|
setInitialDirection( d_last );
|
|
m_currentStart = ( m_placingVia || fixAll ) ? p_last : p_pre_last;
|
|
|
|
m_fixedTail.AddStage( m_fixStart, m_currentLayer, m_placingVia, m_direction, m_currentNode );
|
|
|
|
m_fixStart = m_currentStart;
|
|
m_startItem = nullptr;
|
|
m_placingVia = false;
|
|
m_chainedPlacement = !pl.EndsWithVia();
|
|
|
|
m_p_start = m_currentStart;
|
|
m_direction = m_initial_direction;
|
|
|
|
m_head.Line().Clear();
|
|
m_tail.Line().Clear();
|
|
m_head.RemoveVia();
|
|
m_tail.RemoveVia();
|
|
m_currentNode = m_lastNode;
|
|
m_lastNode = m_lastNode->Branch();
|
|
|
|
m_shove->AddLockedSpringbackNode( m_currentNode );
|
|
|
|
DIRECTION_45 lastSegDir = pl.EndsWithVia() ? DIRECTION_45::UNDEFINED : d_last;
|
|
|
|
m_mouseTrailTracer.Clear();
|
|
m_mouseTrailTracer.SetTolerance( m_head.Width() );
|
|
m_mouseTrailTracer.AddTrailPoint( m_currentStart );
|
|
m_mouseTrailTracer.SetDefaultDirections( lastSegDir, DIRECTION_45::UNDEFINED );
|
|
|
|
m_placementCorrect = true;
|
|
}
|
|
else
|
|
{
|
|
m_shove->AddLockedSpringbackNode( m_lastNode );
|
|
m_placementCorrect = true;
|
|
m_idle = true;
|
|
}
|
|
|
|
return realEnd;
|
|
}
|
|
|
|
|
|
std::optional<VECTOR2I> LINE_PLACER::UnfixRoute()
|
|
{
|
|
FIXED_TAIL::STAGE st;
|
|
std::optional<VECTOR2I> ret;
|
|
|
|
if ( !m_fixedTail.PopStage( st ) )
|
|
return ret;
|
|
|
|
if( m_head.Line().PointCount() )
|
|
ret = m_head.Line().CPoint( 0 );
|
|
|
|
m_head.Line().Clear();
|
|
m_tail.Line().Clear();
|
|
m_startItem = nullptr;
|
|
m_p_start = st.pts[0].p;
|
|
m_fixStart = m_p_start;
|
|
m_direction = st.pts[0].direction;
|
|
m_placingVia = st.pts[0].placingVias;
|
|
m_currentNode = st.commit;
|
|
m_currentLayer = st.pts[0].layer;
|
|
m_currentStart = m_p_start;
|
|
m_head.SetLayer( m_currentLayer );
|
|
m_tail.SetLayer( m_currentLayer );
|
|
m_head.RemoveVia();
|
|
m_tail.RemoveVia();
|
|
|
|
m_mouseTrailTracer.Clear();
|
|
m_mouseTrailTracer.SetDefaultDirections( m_initial_direction, m_direction );
|
|
m_mouseTrailTracer.AddTrailPoint( m_p_start );
|
|
|
|
m_shove->RewindSpringbackTo( m_currentNode );
|
|
m_shove->UnlockSpringbackNode( m_currentNode );
|
|
|
|
if( Settings().Mode() == PNS::RM_Shove )
|
|
{
|
|
m_currentNode = m_shove->CurrentNode();
|
|
m_currentNode->KillChildren();
|
|
}
|
|
|
|
m_lastNode = m_currentNode->Branch();
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::HasPlacedAnything() const
|
|
{
|
|
return m_placementCorrect || m_fixedTail.StageCount() > 1;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::CommitPlacement()
|
|
{
|
|
if( Settings().Mode() == PNS::RM_Shove )
|
|
{
|
|
m_shove->RewindToLastLockedNode();
|
|
m_lastNode = m_shove->CurrentNode();
|
|
m_lastNode->KillChildren();
|
|
}
|
|
|
|
if( m_lastNode )
|
|
Router()->CommitRouting( m_lastNode );
|
|
|
|
m_lastNode = nullptr;
|
|
m_currentNode = nullptr;
|
|
return true;
|
|
}
|
|
|
|
|
|
void LINE_PLACER::removeLoops( NODE* aNode, LINE& aLatest )
|
|
{
|
|
if( !aLatest.SegmentCount() )
|
|
return;
|
|
|
|
if( aLatest.CLine().CPoint( 0 ) == aLatest.CLine().CPoint( -1 ) )
|
|
return;
|
|
|
|
std::set<LINKED_ITEM *> toErase;
|
|
aLatest.ClearLinks();
|
|
aNode->Add( aLatest, true );
|
|
|
|
for( int s = 0; s < aLatest.LinkCount(); s++ )
|
|
{
|
|
LINKED_ITEM* seg = aLatest.GetLink(s);
|
|
LINE ourLine = aNode->AssembleLine( seg );
|
|
JOINT a, b;
|
|
std::vector<LINE> lines;
|
|
|
|
aNode->FindLineEnds( ourLine, a, b );
|
|
|
|
if( a == b )
|
|
aNode->FindLineEnds( aLatest, a, b );
|
|
|
|
aNode->FindLinesBetweenJoints( a, b, lines );
|
|
|
|
int removedCount = 0;
|
|
int total = 0;
|
|
|
|
for( LINE& line : lines )
|
|
{
|
|
total++;
|
|
|
|
if( !( line.ContainsLink( seg ) ) && line.SegmentCount() )
|
|
{
|
|
for( LINKED_ITEM* ss : line.Links() )
|
|
toErase.insert( ss );
|
|
|
|
removedCount++;
|
|
}
|
|
}
|
|
|
|
PNS_DBG( Dbg(), Message, wxString::Format( "total segs removed: %d/%d", removedCount, total ) );
|
|
}
|
|
|
|
for( LINKED_ITEM* s : toErase )
|
|
aNode->Remove( s );
|
|
|
|
aNode->Remove( aLatest );
|
|
}
|
|
|
|
|
|
void LINE_PLACER::simplifyNewLine( NODE* aNode, LINKED_ITEM* aLatest )
|
|
{
|
|
wxASSERT( aLatest->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T ) );
|
|
|
|
// Before we assemble the final line and run the optimizer, do a separate pass to clean up
|
|
// colinear segments that exist on non-line-corner joints, as these will prevent proper assembly
|
|
// of the line and won't get cleaned up by the optimizer.
|
|
NODE::ITEM_VECTOR removed, added;
|
|
aNode->GetUpdatedItems( removed, added );
|
|
|
|
std::set<ITEM*> cleanup;
|
|
|
|
auto processJoint =
|
|
[&]( const JOINT* aJoint, ITEM* aItem )
|
|
{
|
|
if( !aJoint || aJoint->IsLineCorner() )
|
|
return;
|
|
|
|
SEG refSeg = static_cast<SEGMENT*>( aItem )->Seg();
|
|
|
|
NODE::ITEM_VECTOR toRemove;
|
|
|
|
for( ITEM* neighbor : aJoint->CLinks().CItems() )
|
|
{
|
|
if( neighbor == aItem
|
|
|| !neighbor->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T )
|
|
|| !neighbor->LayersOverlap( aItem ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( static_cast<const SEGMENT*>( neighbor )->Width()
|
|
!= static_cast<const SEGMENT*>( aItem )->Width() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const SEG& testSeg = static_cast<const SEGMENT*>( neighbor )->Seg();
|
|
|
|
if( refSeg.Contains( testSeg ) )
|
|
{
|
|
const JOINT* nA = aNode->FindJoint( neighbor->Anchor( 0 ), neighbor );
|
|
const JOINT* nB = aNode->FindJoint( neighbor->Anchor( 1 ), neighbor );
|
|
|
|
if( ( nA == aJoint && nB->LinkCount() == 1 ) ||
|
|
( nB == aJoint && nA->LinkCount() == 1 ) )
|
|
{
|
|
cleanup.insert( neighbor );
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
for( ITEM* item : added )
|
|
{
|
|
if( !item->OfKind( ITEM::SEGMENT_T ) || cleanup.count( item ) )
|
|
continue;
|
|
|
|
const JOINT* jA = aNode->FindJoint( item->Anchor( 0 ), item );
|
|
const JOINT* jB = aNode->FindJoint( item->Anchor( 1 ), item );
|
|
|
|
processJoint( jA, item );
|
|
processJoint( jB, item );
|
|
}
|
|
|
|
for( ITEM* seg : cleanup )
|
|
aNode->Remove( seg );
|
|
|
|
// And now we can proceed with assembling the final line and optimizing it.
|
|
|
|
LINE l_orig = aNode->AssembleLine( aLatest, nullptr, false, false, false );
|
|
LINE l( l_orig );
|
|
|
|
bool optimized = OPTIMIZER::Optimize( &l, OPTIMIZER::MERGE_COLINEAR, aNode );
|
|
|
|
SHAPE_LINE_CHAIN simplified( l.CLine() );
|
|
|
|
simplified.Simplify();
|
|
|
|
if( optimized || simplified.PointCount() != l.PointCount() )
|
|
{
|
|
aNode->Remove( l_orig );
|
|
l.SetShape( simplified );
|
|
aNode->Add( l );
|
|
PNS_DBG( Dbg(), AddItem, &l, RED, 100000, wxT("simplified"));
|
|
}
|
|
}
|
|
|
|
|
|
void LINE_PLACER::UpdateSizes( const SIZES_SETTINGS& aSizes )
|
|
{
|
|
m_sizes = aSizes;
|
|
|
|
if( !m_idle )
|
|
{
|
|
// If the track width continues from an existing track, we don't want to change the width.
|
|
// Disallow changing width after the first segment has been fixed because we don't want to
|
|
// go back and rip up tracks or allow DRC errors
|
|
if( m_sizes.TrackWidthIsExplicit()
|
|
|| ( !HasPlacedAnything() && ( !m_startItem || m_startItem->Kind() != ITEM::SEGMENT_T ) ) )
|
|
{
|
|
m_head.SetWidth( m_sizes.TrackWidth() );
|
|
m_tail.SetWidth( m_sizes.TrackWidth() );
|
|
m_currentTrace.SetWidth( m_sizes.TrackWidth() );
|
|
}
|
|
|
|
if( m_head.EndsWithVia() )
|
|
{
|
|
m_head.SetViaDiameter( m_sizes.ViaDiameter() );
|
|
m_head.SetViaDrill( m_sizes.ViaDrill() );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void LINE_PLACER::updateLeadingRatLine()
|
|
{
|
|
LINE current = Trace();
|
|
SHAPE_LINE_CHAIN ratLine;
|
|
TOPOLOGY topo( m_lastNode );
|
|
|
|
if( topo.LeadingRatLine( ¤t, ratLine ) )
|
|
m_router->GetInterface()->DisplayRatline( ratLine, m_currentNet );
|
|
}
|
|
|
|
|
|
void LINE_PLACER::SetOrthoMode( bool aOrthoMode )
|
|
{
|
|
m_orthoMode = aOrthoMode;
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::buildInitialLine( const VECTOR2I& aP, LINE& aHead, PNS::PNS_MODE aMode, bool aForceNoVia )
|
|
{
|
|
SHAPE_LINE_CHAIN l;
|
|
DIRECTION_45 guessedDir = m_mouseTrailTracer.GetPosture( aP );
|
|
|
|
PNS_DBG( Dbg(), Message, wxString::Format( wxT( "buildInitialLine: m_direction %s, guessedDir %s, tail points %d" ),
|
|
m_direction.Format(), guessedDir.Format(), m_tail.PointCount() ) );
|
|
|
|
DIRECTION_45::CORNER_MODE cornerMode = Settings().GetCornerMode();
|
|
// Rounded corners don't make sense when routing orthogonally (single track at a time)
|
|
if( m_orthoMode )
|
|
cornerMode = DIRECTION_45::CORNER_MODE::MITERED_45;
|
|
|
|
PNS_DBG( Dbg(), AddPoint, m_p_start, WHITE, 10000, wxT( "pstart [buildInitial]" ) );
|
|
|
|
|
|
if( m_p_start == aP )
|
|
{
|
|
l.Clear();
|
|
}
|
|
else
|
|
{
|
|
if( Settings().GetFreeAngleMode() && Settings().Mode() == RM_MarkObstacles )
|
|
{
|
|
l = SHAPE_LINE_CHAIN( { m_p_start, aP } );
|
|
}
|
|
else
|
|
{
|
|
if( !m_tail.PointCount() )
|
|
l = guessedDir.BuildInitialTrace( m_p_start, aP, false, cornerMode );
|
|
else
|
|
l = m_direction.BuildInitialTrace( m_p_start, aP, false, cornerMode );
|
|
}
|
|
|
|
if( l.SegmentCount() > 1 && m_orthoMode )
|
|
{
|
|
VECTOR2I newLast = l.CSegment( 0 ).LineProject( l.CPoint( -1 ) );
|
|
|
|
l.Remove( -1, -1 );
|
|
l.SetPoint( 1, newLast );
|
|
}
|
|
}
|
|
|
|
aHead.SetLayer( m_currentLayer );
|
|
aHead.SetShape( l );
|
|
|
|
PNS_DBG( Dbg(), AddItem, &aHead, CYAN, 10000, wxT( "initial-trace" ) );
|
|
|
|
|
|
if( !m_placingVia || aForceNoVia )
|
|
return true;
|
|
|
|
VIA v( makeVia( aP ) );
|
|
v.SetNet( aHead.Net() );
|
|
|
|
if( aMode == RM_MarkObstacles )
|
|
{
|
|
aHead.AppendVia( v );
|
|
return true;
|
|
}
|
|
|
|
const int collMask = ( aMode == RM_Walkaround ) ? ITEM::ANY_T : ITEM::SOLID_T;
|
|
const int iterLimit = Settings().ViaForcePropIterationLimit();
|
|
|
|
for( int attempt = 0; attempt < 2; attempt++)
|
|
{
|
|
VECTOR2I lead = aP - m_p_start;
|
|
VECTOR2I force;
|
|
|
|
if( attempt == 1 && m_last_p_end.has_value() )
|
|
lead = aP - m_last_p_end.value();
|
|
|
|
if( v.PushoutForce( m_currentNode, lead, force, collMask, iterLimit ) )
|
|
{
|
|
SHAPE_LINE_CHAIN line = guessedDir.BuildInitialTrace( m_p_start, aP + force, false, cornerMode );
|
|
aHead = LINE( aHead, line );
|
|
|
|
v.SetPos( v.Pos() + force );
|
|
|
|
aHead.AppendVia( v );
|
|
|
|
PNS_DBG( Dbg(), AddPoint, v.Pos(), GREEN, 1000000, "via-force-coll-2" );
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false; // via placement unsuccessful
|
|
}
|
|
|
|
|
|
void LINE_PLACER::GetModifiedNets( std::vector<NET_HANDLE>& aNets ) const
|
|
{
|
|
aNets.push_back( m_currentNet );
|
|
}
|
|
|
|
|
|
bool LINE_PLACER::AbortPlacement()
|
|
{
|
|
m_world->KillChildren();
|
|
m_lastNode = nullptr;
|
|
return true;
|
|
}
|
|
|
|
|
|
FIXED_TAIL::FIXED_TAIL( int aLineCount )
|
|
{
|
|
|
|
}
|
|
|
|
|
|
FIXED_TAIL::~FIXED_TAIL()
|
|
{
|
|
|
|
}
|
|
|
|
|
|
void FIXED_TAIL::Clear()
|
|
{
|
|
m_stages.clear();
|
|
}
|
|
|
|
|
|
void FIXED_TAIL::AddStage( const VECTOR2I& aStart, int aLayer, bool placingVias,
|
|
DIRECTION_45 direction, NODE* aNode )
|
|
{
|
|
STAGE st;
|
|
FIX_POINT pt;
|
|
|
|
pt.p = aStart;
|
|
pt.layer = aLayer;
|
|
pt.direction = direction;
|
|
pt.placingVias = placingVias;
|
|
|
|
st.pts.push_back(pt);
|
|
st.commit = aNode;
|
|
|
|
m_stages.push_back( st );
|
|
}
|
|
|
|
|
|
bool FIXED_TAIL::PopStage( FIXED_TAIL::STAGE& aStage )
|
|
{
|
|
if( !m_stages.size() )
|
|
return false;
|
|
|
|
aStage = m_stages.back();
|
|
|
|
if( m_stages.size() > 1 )
|
|
m_stages.pop_back();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
int FIXED_TAIL::StageCount() const
|
|
{
|
|
return m_stages.size();
|
|
}
|
|
|
|
}
|
|
|