You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							2067 lines
						
					
					
						
							56 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							2067 lines
						
					
					
						
							56 KiB
						
					
					
				| /* | |
|  * KiRouter - a push-and-(sometimes-)shove PCB router | |
|  * | |
|  * Copyright (C) 2013-2017 CERN | |
|  * Copyright (C) 2016-2021 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 "pns_utils.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 = 0; | |
|     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 ) | |
| { | |
|     const LAYER_RANGE layers( m_sizes.ViaType() == VIATYPE::THROUGH ? F_Cu : m_sizes.GetLayerTop(), | |
|                               m_sizes.ViaType() == VIATYPE::THROUGH ? B_Cu : m_sizes.GetLayerBottom() ); | |
| 
 | |
|     return VIA( aP, layers, m_sizes.ViaDiameter(), m_sizes.ViaDrill(), -1, 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 ) | |
|     { | |
|         lastSegIdx = tail.PrevShape( -1 ); | |
| 
 | |
|         if( !tail.IsPtOnArc( 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; | |
| } | |
| 
 | |
| 
 | |
| VECTOR2I closestProjectedPoint( const SHAPE_LINE_CHAIN& line, const VECTOR2I& p ) | |
| { | |
|     // Keep distances squared for performance | |
|     SEG::ecoord min_dist_sq = VECTOR2I::ECOORD_MAX; | |
|     VECTOR2I    closest; | |
| 
 | |
|     for( int i = 0; i < line.SegmentCount(); i++ ) | |
|     { | |
|         const SEG& s = line.CSegment( i ); | |
|         VECTOR2I   a = s.NearestPoint( p ); | |
|         int        d_sq = (a - p).SquaredEuclideanNorm(); | |
| 
 | |
|         if( d_sq < min_dist_sq ) | |
|         { | |
|             min_dist_sq = d_sq; | |
|             closest = a; | |
|         } | |
|     } | |
| 
 | |
|     return closest; | |
| } | |
| 
 | |
| bool LINE_PLACER::clipAndCheckCollisions( VECTOR2I aP, 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, | |
|                               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 ); | |
| 
 | |
|     int round = 0; | |
| 
 | |
|     do | |
|     { | |
|         l1.Clear(); | |
| 
 | |
|         PNS_DBG( Dbg(), BeginGroup, wxString::Format( "walk-round-%d", round ), 0 ); | |
|         round++; | |
| 
 | |
|         aViaOk = buildInitialLine( walkP, l1, round == 0 ); | |
|         PNS_DBG( Dbg(), AddItem, &l1, BLUE, 20000, wxT( "walk-base-l1" ) ); | |
|         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 ); | |
| 
 | |
|         int len_cw = wr.statusCw != WALKAROUND::STUCK ? wr.lineCw.CLine().Length() | |
|                                                       : std::numeric_limits<int>::max(); | |
|         int len_ccw = wr.statusCcw != WALKAROUND::STUCK ? wr.lineCcw.CLine().Length() | |
|                                                         : std::numeric_limits<int>::max(); | |
| 
 | |
|         if( wr.statusCw == WALKAROUND::DONE ) | |
|         { | |
|             PNS_DBG( Dbg(), AddItem, &wr.lineCw, BLUE, 20000, wxT( "wf-result-cw-preopt" ) ); | |
|             LINE tmpHead, tmpTail; | |
| 
 | |
|             if( splitHeadTail( wr.lineCw, m_tail, tmpHead, tmpTail ) ) | |
|             { | |
|                 optimizer.Optimize( &tmpHead ); | |
|                 wr.lineCw.SetShape( tmpTail.CLine () ); | |
|                 wr.lineCw.Line().Append( tmpHead.CLine( ) ); | |
|             } | |
| 
 | |
|             PNS_DBG( Dbg(), AddItem, &wr.lineCw, RED, 20000, wxT( "wf-result-cw-postopt" ) ); | |
|             len_cw = wr.lineCw.CLine().Length(); | |
|             bestLine = wr.lineCw; | |
|         } | |
| 
 | |
|         if( wr.statusCcw == WALKAROUND::DONE ) | |
|         { | |
|             PNS_DBG( Dbg(), AddItem, &wr.lineCcw, BLUE, 20000, wxT( "wf-result-ccw-preopt" ) ); | |
| 
 | |
|             LINE tmpHead, tmpTail; | |
| 
 | |
|             if( splitHeadTail( wr.lineCw, m_tail, tmpHead, tmpTail ) ) | |
|             { | |
|                 optimizer.Optimize( &tmpHead ); | |
|                 wr.lineCw.SetShape( tmpTail.CLine () ); | |
|                 wr.lineCw.Line().Append( tmpHead.CLine( ) ); | |
|             } | |
| 
 | |
|             PNS_DBG( Dbg(), AddItem, &wr.lineCcw, RED, 20000, wxT( "wf-result-ccw-postopt" ) ); | |
|             len_ccw = wr.lineCcw.CLine().Length(); | |
|             if( len_ccw < len_cw ) | |
|                 { | |
|                 bestLine = wr.lineCcw; | |
|                 } | |
|             } | |
| 
 | |
|         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.statusCw != WALKAROUND::STUCK ) | |
|                 { | |
|             validCw = cursorDistMinimum( wr.lineCw.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.statusCcw != WALKAROUND::STUCK ) | |
|         { | |
|             validCcw = cursorDistMinimum( wr.lineCcw.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" ) ); | |
|     PNS_DBG( Dbg(), AddPoint, walkFull.Via().Pos(), GREEN, 200000, wxString::Format( "walk-via ok %d", aViaOk?1:0 ) ); | |
| 
 | |
|     aWalkLine = walkFull; | |
| 
 | |
|     return true; | |
| } | |
| 
 | |
| 
 | |
| 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, 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 ); | |
|     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 cl = m_currentNode->GetClearance( obs->m_item, &m_head, false ); | |
|         auto hull = obs->m_item->Hull( cl, m_head.Width() ); | |
| 
 | |
|         auto nearest = hull.NearestPoint( aP ); | |
| 
 | |
|         if( ( nearest - aP ).EuclideanNorm() < m_head.Width() / 2 ) | |
|         { | |
|             buildInitialLine( nearest, m_head ); | |
|         } | |
|     } | |
| 
 | |
|     // 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--; | |
| 
 | |
|         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, 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( 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" ); | |
|          | |
|     } | |
| 
 | |
|     SHOVE::SHOVE_STATUS status = m_shove->ShoveLines( newHead ); | |
| 
 | |
|     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( status == SHOVE::SH_OK  || status == SHOVE::SH_HEAD_MODIFIED ) | |
|     { | |
|         if( status == SHOVE::SH_HEAD_MODIFIED ) | |
|             newHead = m_shove->NewHead(); | |
| 
 | |
|         OPTIMIZER optimizer( m_currentNode ); | |
| 
 | |
|         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.SetEffortLevel( effort ); | |
|         optimizer.SetCollisionMask( ITEM::ANY_T ); | |
|         optimizer.Optimize( &aNewHead ); | |
| 
 | |
|         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; | |
|             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() ); | |
|     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() | |
| { | |
|     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; | |
| 
 | |
|     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::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 = std::max( 0, aStartItem ? aStartItem->Net() : 0 ); | |
|     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; | |
|     VECTOR2I p = aP; | |
|     int eiDepth = -1; | |
| 
 | |
|     if( aEndItem && aEndItem->Owner() ) | |
|         eiDepth = static_cast<NODE*>( aEndItem->Owner() )->Depth(); | |
| 
 | |
|     if( m_lastNode ) | |
|     { | |
|         delete m_lastNode; | |
|         m_lastNode = nullptr; | |
|     } | |
| 
 | |
|     m_endItem = aEndItem; | |
| 
 | |
|     bool reachesEnd = route( p ); | |
| 
 | |
|     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() ) | |
|     { | |
|         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_currentNet <= 0 ) | |
|             { | |
|                 m_currentNet = aEndItem->Net(); | |
|                 pl.SetNet( m_currentNet ); | |
|             } | |
|             else if (aEndItem->Net() <= 0 ) | |
|             { | |
|                 aEndItem->SetNet( m_currentNet ); | |
|             } | |
|         } | |
| 
 | |
|         // Collisions still prevent fixing unless "Allow DRC violations" is checked | |
|         if( !Settings().AllowDRCViolations() && m_world->CheckColliding( &pl ) ) | |
|             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 ) | |
|         { | |
|             m_lastNode->Add( Clone( pl.Via() ) ); | |
|             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 >= 0 && 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() ) | |
|         m_lastNode->Add( Clone( pl.Via() ) ); | |
| 
 | |
|     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; | |
| } | |
| 
 | |
| 
 | |
| bool LINE_PLACER::UnfixRoute() | |
| { | |
|     FIXED_TAIL::STAGE st; | |
| 
 | |
|     if ( !m_fixedTail.PopStage( st ) ) | |
|     { | |
|         return false; | |
|     } | |
| 
 | |
|     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 true; | |
| } | |
| 
 | |
| 
 | |
| 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; | |
|     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 = | |
|             [&]( JOINT* aJoint, ITEM* aItem ) | |
|             { | |
|                 if( !aJoint || aJoint->IsLineCorner() ) | |
|                     return; | |
| 
 | |
|                 SEG refSeg = static_cast<SEGMENT*>( aItem )->Seg(); | |
| 
 | |
|                 NODE::ITEM_VECTOR toRemove; | |
| 
 | |
|                 for( ITEM* neighbor : aJoint->Links() ) | |
|                 { | |
|                     if( neighbor == aItem | |
|                         || !neighbor->OfKind( ITEM::SEGMENT_T | ITEM::ARC_T ) | |
|                         || !neighbor->LayersOverlap( aItem ) ) | |
|                     { | |
|                         continue; | |
|                     } | |
| 
 | |
|                     if( static_cast<SEGMENT*>( neighbor )->Width() | |
|                             != static_cast<SEGMENT*>( aItem )->Width() ) | |
|                     { | |
|                         continue; | |
|                     } | |
| 
 | |
|                     const SEG& testSeg = static_cast<SEGMENT*>( neighbor )->Seg(); | |
| 
 | |
|                     if( refSeg.Contains( testSeg ) ) | |
|                     { | |
|                         JOINT* nA = aNode->FindJoint( neighbor->Anchor( 0 ), neighbor ); | |
|                         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; | |
| 
 | |
|         JOINT* jA = aNode->FindJoint( item->Anchor( 0 ), item ); | |
|         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 = aNode->AssembleLine( aLatest ); | |
| 
 | |
|     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 ); | |
|         l.SetShape( simplified ); | |
|         aNode->Add( l ); | |
|     } | |
| } | |
| 
 | |
| 
 | |
| 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, bool aForceNoVia ) | |
| { | |
|     SHAPE_LINE_CHAIN l; | |
|     DIRECTION_45 guessedDir = m_mouseTrailTracer.GetPosture( aP ); | |
| 
 | |
|     PNS_DBG( Dbg(), Message, | |
|                 wxString::Format( "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( Settings().Mode() == RM_MarkObstacles ) | |
|     { | |
|         aHead.AppendVia( v ); | |
|         return true; | |
|     } | |
| 
 | |
|     const int collMask = ( Settings().Mode() == 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<int>& aNets ) const | |
| { | |
|     aNets.push_back( m_currentNet ); | |
| } | |
| 
 | |
| 
 | |
| bool LINE_PLACER::AbortPlacement() | |
| { | |
|     m_world->KillChildren(); | |
|     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(); | |
| } | |
| 
 | |
| } | |
| 
 |