summaryrefslogtreecommitdiff
path: root/pcbnew/router/pns_optimizer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'pcbnew/router/pns_optimizer.cpp')
-rw-r--r--pcbnew/router/pns_optimizer.cpp1224
1 files changed, 1224 insertions, 0 deletions
diff --git a/pcbnew/router/pns_optimizer.cpp b/pcbnew/router/pns_optimizer.cpp
new file mode 100644
index 0000000..d18933f
--- /dev/null
+++ b/pcbnew/router/pns_optimizer.cpp
@@ -0,0 +1,1224 @@
+/*
+ * KiRouter - a push-and-(sometimes-)shove PCB router
+ *
+ * Copyright (C) 2013-2014 CERN
+ * 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 <boost/foreach.hpp>
+
+#include <geometry/shape_line_chain.h>
+#include <geometry/shape_rect.h>
+#include <geometry/shape_convex.h>
+
+#include "pns_line.h"
+#include "pns_diff_pair.h"
+#include "pns_node.h"
+#include "pns_solid.h"
+#include "pns_optimizer.h"
+#include "pns_utils.h"
+#include "pns_router.h"
+
+/**
+ * Cost Estimator Methods
+ */
+int PNS_COST_ESTIMATOR::CornerCost( const SEG& aA, const SEG& aB )
+{
+ DIRECTION_45 dir_a( aA ), dir_b( aB );
+
+ switch( dir_a.Angle( dir_b ) )
+ {
+ case DIRECTION_45::ANG_OBTUSE:
+ return 1;
+
+ case DIRECTION_45::ANG_STRAIGHT:
+ return 0;
+
+ case DIRECTION_45::ANG_ACUTE:
+ return 50;
+
+ case DIRECTION_45::ANG_RIGHT:
+ return 30;
+
+ case DIRECTION_45::ANG_HALF_FULL:
+ return 60;
+
+ default:
+ return 100;
+ }
+}
+
+
+int PNS_COST_ESTIMATOR::CornerCost( const SHAPE_LINE_CHAIN& aLine )
+{
+ int total = 0;
+
+ for( int i = 0; i < aLine.SegmentCount() - 1; ++i )
+ total += CornerCost( aLine.CSegment( i ), aLine.CSegment( i + 1 ) );
+
+ return total;
+}
+
+
+int PNS_COST_ESTIMATOR::CornerCost( const PNS_LINE& aLine )
+{
+ return CornerCost( aLine.CLine() );
+}
+
+
+void PNS_COST_ESTIMATOR::Add( PNS_LINE& aLine )
+{
+ m_lengthCost += aLine.CLine().Length();
+ m_cornerCost += CornerCost( aLine );
+}
+
+
+void PNS_COST_ESTIMATOR::Remove( PNS_LINE& aLine )
+{
+ m_lengthCost -= aLine.CLine().Length();
+ m_cornerCost -= CornerCost( aLine );
+}
+
+
+void PNS_COST_ESTIMATOR::Replace( PNS_LINE& aOldLine, PNS_LINE& aNewLine )
+{
+ m_lengthCost -= aOldLine.CLine().Length();
+ m_cornerCost -= CornerCost( aOldLine );
+ m_lengthCost += aNewLine.CLine().Length();
+ m_cornerCost += CornerCost( aNewLine );
+}
+
+
+bool PNS_COST_ESTIMATOR::IsBetter( PNS_COST_ESTIMATOR& aOther,
+ double aLengthTolerance,
+ double aCornerTolerance ) const
+{
+ if( aOther.m_cornerCost < m_cornerCost && aOther.m_lengthCost < m_lengthCost )
+ return true;
+
+ else if( aOther.m_cornerCost < m_cornerCost * aCornerTolerance &&
+ aOther.m_lengthCost < m_lengthCost * aLengthTolerance )
+ return true;
+
+ return false;
+}
+
+
+/**
+ * Optimizer
+ **/
+PNS_OPTIMIZER::PNS_OPTIMIZER( PNS_NODE* aWorld ) :
+ m_world( aWorld ),
+ m_collisionKindMask( PNS_ITEM::ANY ),
+ m_effortLevel( MERGE_SEGMENTS ),
+ m_keepPostures( false ),
+ m_restrictAreaActive( false )
+{
+}
+
+
+PNS_OPTIMIZER::~PNS_OPTIMIZER()
+{
+}
+
+
+struct PNS_OPTIMIZER::CACHE_VISITOR
+{
+ CACHE_VISITOR( const PNS_ITEM* aOurItem, PNS_NODE* aNode, int aMask ) :
+ m_ourItem( aOurItem ),
+ m_collidingItem( NULL ),
+ m_node( aNode ),
+ m_mask( aMask )
+ {}
+
+ bool operator()( PNS_ITEM* aOtherItem )
+ {
+ if( !( m_mask & aOtherItem->Kind() ) )
+ return true;
+
+ int clearance = m_node->GetClearance( aOtherItem, m_ourItem );
+
+ if( !aOtherItem->Collide( m_ourItem, clearance ) )
+ return true;
+
+ m_collidingItem = aOtherItem;
+ return false;
+ }
+
+ const PNS_ITEM* m_ourItem;
+ PNS_ITEM* m_collidingItem;
+ PNS_NODE* m_node;
+ int m_mask;
+};
+
+
+void PNS_OPTIMIZER::cacheAdd( PNS_ITEM* aItem, bool aIsStatic = false )
+{
+ if( m_cacheTags.find( aItem ) != m_cacheTags.end() )
+ return;
+
+ m_cache.Add( aItem );
+ m_cacheTags[aItem].m_hits = 1;
+ m_cacheTags[aItem].m_isStatic = aIsStatic;
+}
+
+
+void PNS_OPTIMIZER::removeCachedSegments( PNS_LINE* aLine, int aStartVertex, int aEndVertex )
+{
+ PNS_LINE::SEGMENT_REFS* segs = aLine->LinkedSegments();
+
+ if( !segs )
+ return;
+
+ if( aEndVertex < 0 )
+ aEndVertex += aLine->PointCount();
+
+ for( int i = aStartVertex; i < aEndVertex - 1; i++ )
+ {
+ PNS_SEGMENT* s = (*segs)[i];
+ m_cacheTags.erase( s );
+ m_cache.Remove( s );
+ }
+}
+
+
+void PNS_OPTIMIZER::CacheRemove( PNS_ITEM* aItem )
+{
+ if( aItem->Kind() == PNS_ITEM::LINE )
+ removeCachedSegments( static_cast<PNS_LINE*>( aItem ) );
+}
+
+
+void PNS_OPTIMIZER::CacheStaticItem( PNS_ITEM* aItem )
+{
+ cacheAdd( aItem, true );
+}
+
+
+void PNS_OPTIMIZER::ClearCache( bool aStaticOnly )
+{
+ if( !aStaticOnly )
+ {
+ m_cacheTags.clear();
+ m_cache.Clear();
+ return;
+ }
+
+ for( CachedItemTags::iterator i = m_cacheTags.begin(); i!= m_cacheTags.end(); ++i )
+ {
+ if( i->second.m_isStatic )
+ {
+ m_cache.Remove( i->first );
+ m_cacheTags.erase( i->first );
+ }
+ }
+}
+
+
+class LINE_RESTRICTIONS
+{
+ public:
+ LINE_RESTRICTIONS() {};
+ ~LINE_RESTRICTIONS() {};
+
+ void Build( PNS_NODE* aWorld, PNS_LINE* aOriginLine, const SHAPE_LINE_CHAIN& aLine, const BOX2I& aRestrictedArea, bool aRestrictedAreaEnable );
+ bool Check ( int aVertex1, int aVertex2, const SHAPE_LINE_CHAIN& aReplacement );
+ void Dump();
+
+ private:
+ int allowedAngles( PNS_NODE* aWorld, const PNS_LINE* aLine, const VECTOR2I& aP, bool aFirst );
+
+ struct RVERTEX
+ {
+ RVERTEX ( bool aRestricted, int aAllowedAngles ) :
+ restricted( aRestricted ),
+ allowedAngles( aAllowedAngles )
+ {
+ }
+
+ bool restricted;
+ int allowedAngles;
+ };
+
+ std::vector<RVERTEX> m_rs;
+};
+
+
+// fixme: use later
+int LINE_RESTRICTIONS::allowedAngles( PNS_NODE* aWorld, const PNS_LINE* aLine, const VECTOR2I& aP, bool aFirst )
+{
+ PNS_JOINT* jt = aWorld->FindJoint( aP , aLine );
+
+ if( !jt )
+ return 0xff;
+
+ DIRECTION_45 dirs [8];
+
+ int n_dirs = 0;
+
+ BOOST_FOREACH( const PNS_ITEM* item, jt->Links().CItems() )
+ {
+ if( item->OfKind( PNS_ITEM::VIA ) || item->OfKind( PNS_ITEM::SOLID ) )
+ return 0xff;
+ else if( const PNS_SEGMENT* seg = dyn_cast<const PNS_SEGMENT*>( item ) )
+ {
+ SEG s = seg->Seg();
+ if( s.A != aP )
+ s.Reverse();
+
+ if( n_dirs < 8 )
+ dirs[n_dirs++] = aFirst ? DIRECTION_45( s ) : DIRECTION_45( s ).Opposite();
+ }
+ }
+
+ const int angleMask = DIRECTION_45::ANG_OBTUSE | DIRECTION_45::ANG_HALF_FULL | DIRECTION_45::ANG_STRAIGHT;
+ int outputMask = 0xff;
+
+ for( int d = 0; d < 8; d++ )
+ {
+ DIRECTION_45 refDir( ( DIRECTION_45::Directions ) d );
+
+ for( int i = 0; i < n_dirs; i++ )
+ {
+ if( !( refDir.Angle( dirs[i] ) & angleMask ) )
+ outputMask &= ~refDir.Mask();
+ }
+ }
+
+ DrawDebugDirs( aP, outputMask, 3 );
+ return 0xff;
+}
+
+
+void LINE_RESTRICTIONS::Build( PNS_NODE* aWorld, PNS_LINE* aOriginLine, const SHAPE_LINE_CHAIN& aLine, const BOX2I& aRestrictedArea, bool aRestrictedAreaEnable )
+{
+ const SHAPE_LINE_CHAIN& l = aLine;
+ VECTOR2I v_prev;
+ int n = l.PointCount( );
+
+ m_rs.reserve( n );
+
+ for( int i = 0; i < n; i++ )
+ {
+ const VECTOR2I &v = l.CPoint( i ), v_next;
+ RVERTEX r( false, 0xff );
+
+ if( aRestrictedAreaEnable )
+ {
+ bool exiting = ( i > 0 && aRestrictedArea.Contains( v_prev ) && !aRestrictedArea.Contains( v ) );
+ bool entering = false;
+
+ if( i != l.PointCount() - 1 )
+ {
+ const VECTOR2I& v_next = l.CPoint( i + 1 );
+ entering = ( !aRestrictedArea.Contains( v ) && aRestrictedArea.Contains( v_next ) );
+ }
+
+ if( entering )
+ {
+ const SEG& sp = l.CSegment( i );
+ r.allowedAngles = DIRECTION_45( sp ).Mask();
+ }
+ else if( exiting )
+ {
+ const SEG& sp = l.CSegment( i - 1 );
+ r.allowedAngles = DIRECTION_45( sp ).Mask();
+ }
+ else
+ {
+ r.allowedAngles = ( !aRestrictedArea.Contains( v ) ) ? 0 : 0xff;
+ r.restricted = r.allowedAngles ? false : true;
+ }
+ }
+
+ v_prev = v;
+ m_rs.push_back( r );
+ }
+}
+
+
+void LINE_RESTRICTIONS::Dump()
+{
+}
+
+
+bool LINE_RESTRICTIONS::Check( int aVertex1, int aVertex2, const SHAPE_LINE_CHAIN& aReplacement )
+{
+ if( m_rs.empty( ) )
+ return true;
+
+ for( int i = aVertex1; i <= aVertex2; i++ )
+ if ( m_rs[i].restricted )
+ return false;
+
+ const RVERTEX& v1 = m_rs[ aVertex1 ];
+ const RVERTEX& v2 = m_rs[ aVertex2 ];
+
+ int m1 = DIRECTION_45( aReplacement.CSegment( 0 ) ).Mask();
+ int m2;
+
+ if( aReplacement.SegmentCount() == 1 )
+ m2 = m1;
+ else
+ m2 = DIRECTION_45( aReplacement.CSegment( 1 ) ).Mask();
+
+ return ( ( v1.allowedAngles & m1 ) != 0 ) &&
+ ( ( v2.allowedAngles & m2 ) != 0 );
+}
+
+
+bool PNS_OPTIMIZER::checkColliding( PNS_ITEM* aItem, bool aUpdateCache )
+{
+ CACHE_VISITOR v( aItem, m_world, m_collisionKindMask );
+
+ return static_cast<bool>( m_world->CheckColliding( aItem ) );
+
+ // something is wrong with the cache, need to investigate.
+ m_cache.Query( aItem->Shape(), m_world->GetMaxClearance(), v, false );
+
+ if( !v.m_collidingItem )
+ {
+ PNS_NODE::OPT_OBSTACLE obs = m_world->CheckColliding( aItem );
+
+ if( obs )
+ {
+ if( aUpdateCache )
+ cacheAdd( obs->m_item );
+
+ return true;
+ }
+ }
+ else
+ {
+ m_cacheTags[v.m_collidingItem].m_hits++;
+ return true;
+ }
+
+ return false;
+}
+
+
+bool PNS_OPTIMIZER::checkColliding( PNS_LINE* aLine, const SHAPE_LINE_CHAIN& aOptPath )
+{
+ PNS_LINE tmp( *aLine, aOptPath );
+
+ return checkColliding( &tmp );
+}
+
+
+bool PNS_OPTIMIZER::mergeObtuse( PNS_LINE* aLine )
+{
+ SHAPE_LINE_CHAIN& line = aLine->Line();
+
+ int step = line.PointCount() - 3;
+ int iter = 0;
+ int segs_pre = line.SegmentCount();
+
+ if( step < 0 )
+ return false;
+
+ SHAPE_LINE_CHAIN current_path( line );
+
+ while( 1 )
+ {
+ iter++;
+ int n_segs = current_path.SegmentCount();
+ int max_step = n_segs - 2;
+
+ if( step > max_step )
+ step = max_step;
+
+ if( step < 2 )
+ {
+ line = current_path;
+ return current_path.SegmentCount() < segs_pre;
+ }
+
+ bool found_anything = false;
+ int n = 0;
+
+ while( n < n_segs - step )
+ {
+ const SEG s1 = current_path.CSegment( n );
+ const SEG s2 = current_path.CSegment( n + step );
+ SEG s1opt, s2opt;
+
+ if( DIRECTION_45( s1 ).IsObtuse( DIRECTION_45( s2 ) ) )
+ {
+ VECTOR2I ip = *s1.IntersectLines( s2 );
+
+ if( s1.Distance( ip ) <= 1 || s2.Distance( ip ) <= 1 )
+ {
+ s1opt = SEG( s1.A, ip );
+ s2opt = SEG( ip, s2.B );
+ }
+ else
+ {
+ s1opt = SEG( s1.A, ip );
+ s2opt = SEG( ip, s2.B );
+ }
+
+ if( DIRECTION_45( s1opt ).IsObtuse( DIRECTION_45( s2opt ) ) )
+ {
+ SHAPE_LINE_CHAIN opt_path;
+ opt_path.Append( s1opt.A );
+ opt_path.Append( s1opt.B );
+ opt_path.Append( s2opt.B );
+
+ PNS_LINE opt_track( *aLine, opt_path );
+
+ if( !checkColliding( &opt_track ) )
+ {
+ current_path.Replace( s1.Index() + 1, s2.Index(), ip );
+ // removeCachedSegments(aLine, s1.Index(), s2.Index());
+ n_segs = current_path.SegmentCount();
+ found_anything = true;
+ break;
+ }
+ }
+ }
+
+ n++;
+ }
+
+ if( !found_anything )
+ {
+ if( step <= 2 )
+ {
+ line = current_path;
+ return line.SegmentCount() < segs_pre;
+ }
+
+ step--;
+ }
+ }
+
+ return line.SegmentCount() < segs_pre;
+}
+
+
+bool PNS_OPTIMIZER::mergeFull( PNS_LINE* aLine )
+{
+ SHAPE_LINE_CHAIN& line = aLine->Line();
+ int step = line.SegmentCount() - 1;
+
+ int segs_pre = line.SegmentCount();
+
+ line.Simplify();
+
+ if( step < 0 )
+ return false;
+
+ SHAPE_LINE_CHAIN current_path( line );
+
+ while( 1 )
+ {
+ int n_segs = current_path.SegmentCount();
+ int max_step = n_segs - 2;
+
+ if( step > max_step )
+ step = max_step;
+
+ if( step < 1 )
+ break;
+
+ bool found_anything = mergeStep( aLine, current_path, step );
+
+ if( !found_anything )
+ step--;
+ }
+
+ aLine->SetShape( current_path );
+
+ return current_path.SegmentCount() < segs_pre;
+}
+
+
+bool PNS_OPTIMIZER::Optimize( PNS_LINE* aLine, PNS_LINE* aResult )
+{
+ if( !aResult )
+ aResult = aLine;
+ else
+ *aResult = *aLine;
+
+ m_keepPostures = false;
+
+ bool rv = false;
+
+ if( m_effortLevel & MERGE_SEGMENTS )
+ rv |= mergeFull( aResult );
+
+ if( m_effortLevel & MERGE_OBTUSE )
+ rv |= mergeObtuse( aResult );
+
+ if( m_effortLevel & SMART_PADS )
+ rv |= runSmartPads( aResult );
+
+ if( m_effortLevel & FANOUT_CLEANUP )
+ rv |= fanoutCleanup( aResult );
+
+ return rv;
+}
+
+
+bool PNS_OPTIMIZER::mergeStep( PNS_LINE* aLine, SHAPE_LINE_CHAIN& aCurrentPath, int step )
+{
+ int n = 0;
+ int n_segs = aCurrentPath.SegmentCount();
+
+ int cost_orig = PNS_COST_ESTIMATOR::CornerCost( aCurrentPath );
+
+ LINE_RESTRICTIONS restr;
+
+ if( aLine->SegmentCount() < 4 )
+ return false;
+
+ DIRECTION_45 orig_start( aLine->CSegment( 0 ) );
+ DIRECTION_45 orig_end( aLine->CSegment( -1 ) );
+
+ restr.Build( m_world, aLine, aCurrentPath, m_restrictArea, m_restrictAreaActive );
+
+ while( n < n_segs - step )
+ {
+ const SEG s1 = aCurrentPath.CSegment( n );
+ const SEG s2 = aCurrentPath.CSegment( n + step );
+
+ SHAPE_LINE_CHAIN path[2];
+ SHAPE_LINE_CHAIN* picked = NULL;
+ int cost[2];
+
+ for( int i = 0; i < 2; i++ )
+ {
+ bool postureMatch = true;
+ SHAPE_LINE_CHAIN bypass = DIRECTION_45().BuildInitialTrace( s1.A, s2.B, i );
+ cost[i] = INT_MAX;
+
+ bool restrictionsOK = restr.Check ( n, n + step + 1, bypass );
+
+ if( n == 0 && orig_start != DIRECTION_45( bypass.CSegment( 0 ) ) )
+ postureMatch = false;
+ else if( n == n_segs - step && orig_end != DIRECTION_45( bypass.CSegment( -1 ) ) )
+ postureMatch = false;
+
+ if( restrictionsOK && (postureMatch || !m_keepPostures) && !checkColliding( aLine, bypass ) )
+ {
+ path[i] = aCurrentPath;
+ path[i].Replace( s1.Index(), s2.Index(), bypass );
+ path[i].Simplify();
+ cost[i] = PNS_COST_ESTIMATOR::CornerCost( path[i] );
+ }
+ }
+
+ if( cost[0] < cost_orig && cost[0] < cost[1] )
+ picked = &path[0];
+ else if( cost[1] < cost_orig )
+ picked = &path[1];
+
+ if( picked )
+ {
+ n_segs = aCurrentPath.SegmentCount();
+ aCurrentPath = *picked;
+ return true;
+ }
+
+ n++;
+ }
+
+ return false;
+}
+
+
+PNS_OPTIMIZER::BREAKOUT_LIST PNS_OPTIMIZER::circleBreakouts( int aWidth,
+ const SHAPE* aShape, bool aPermitDiagonal ) const
+{
+ BREAKOUT_LIST breakouts;
+
+ for( int angle = 0; angle < 360; angle += 45 )
+ {
+ const SHAPE_CIRCLE* cir = static_cast<const SHAPE_CIRCLE*>( aShape );
+ SHAPE_LINE_CHAIN l;
+ VECTOR2I p0 = cir->GetCenter();
+ VECTOR2I v0( cir->GetRadius() * M_SQRT2, 0 );
+ l.Append( p0 );
+ l.Append( p0 + v0.Rotate( angle * M_PI / 180.0 ) );
+ breakouts.push_back( l );
+ }
+
+ return breakouts;
+}
+
+
+PNS_OPTIMIZER::BREAKOUT_LIST PNS_OPTIMIZER::convexBreakouts( int aWidth,
+ const SHAPE* aShape, bool aPermitDiagonal ) const
+{
+ BREAKOUT_LIST breakouts;
+ const SHAPE_CONVEX* convex = static_cast<const SHAPE_CONVEX*>( aShape );
+
+ BOX2I bbox = convex->BBox( 0 );
+ VECTOR2I p0 = bbox.Centre();
+ // must be large enough to guarantee intersecting the convex polygon
+ int length = bbox.GetSize().EuclideanNorm() / 2 + 5;
+
+ for( int angle = 0; angle < 360; angle += ( aPermitDiagonal ? 45 : 90 ) )
+ {
+ SHAPE_LINE_CHAIN l;
+ VECTOR2I v0( p0 + VECTOR2I( length, 0 ).Rotate( angle * M_PI / 180.0 ) );
+ SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
+ int n = convex->Vertices().Intersect( SEG( p0, v0 ), intersections );
+ // if n == 1 intersected a segment
+ // if n == 2 intersected the common point of 2 segments
+ // n == 0 can not happen I think, but...
+ if( n > 0 )
+ {
+ l.Append( p0 );
+
+ // for a breakout distance relative to the distance between
+ // center and polygon edge
+ //l.Append( intersections[0].p + (v0 - p0).Resize( (intersections[0].p - p0).EuclideanNorm() * 0.4 ) );
+
+ // for an absolute breakout distance, e.g. 0.1 mm
+ l.Append( intersections[0].p + (v0 - p0).Resize( 100000 ) );
+
+ // for the breakout right on the polygon edge
+ //l.Append( intersections[0].p );
+
+ breakouts.push_back( l );
+ }
+ }
+
+ return breakouts;
+}
+
+
+PNS_OPTIMIZER::BREAKOUT_LIST PNS_OPTIMIZER::rectBreakouts( int aWidth,
+ const SHAPE* aShape, bool aPermitDiagonal ) const
+{
+ const SHAPE_RECT* rect = static_cast<const SHAPE_RECT*>(aShape);
+ VECTOR2I s = rect->GetSize(), c = rect->GetPosition() + VECTOR2I( s.x / 2, s.y / 2 );
+ BREAKOUT_LIST breakouts;
+
+ VECTOR2I d_offset;
+
+ d_offset.x = ( s.x > s.y ) ? ( s.x - s.y ) / 2 : 0;
+ d_offset.y = ( s.x < s.y ) ? ( s.y - s.x ) / 2 : 0;
+
+ VECTOR2I d_vert = VECTOR2I( 0, s.y / 2 + aWidth );
+ VECTOR2I d_horiz = VECTOR2I( s.x / 2 + aWidth, 0 );
+
+ breakouts.push_back( SHAPE_LINE_CHAIN( c, c + d_horiz ) );
+ breakouts.push_back( SHAPE_LINE_CHAIN( c, c - d_horiz ) );
+ breakouts.push_back( SHAPE_LINE_CHAIN( c, c + d_vert ) );
+ breakouts.push_back( SHAPE_LINE_CHAIN( c, c - d_vert ) );
+
+ if( aPermitDiagonal )
+ {
+ int l = aWidth + std::min( s.x, s.y ) / 2;
+ VECTOR2I d_diag;
+
+ if( s.x >= s.y )
+ {
+ breakouts.push_back( SHAPE_LINE_CHAIN( c, c + d_offset,
+ c + d_offset + VECTOR2I( l, l ) ) );
+ breakouts.push_back( SHAPE_LINE_CHAIN( c, c + d_offset,
+ c + d_offset - VECTOR2I( -l, l ) ) );
+ breakouts.push_back( SHAPE_LINE_CHAIN( c, c - d_offset,
+ c - d_offset + VECTOR2I( -l, l ) ) );
+ breakouts.push_back( SHAPE_LINE_CHAIN( c, c - d_offset,
+ c - d_offset - VECTOR2I( l, l ) ) );
+ }
+ else
+ {
+ // fixme: this could be done more efficiently
+ breakouts.push_back( SHAPE_LINE_CHAIN( c, c + d_offset,
+ c + d_offset + VECTOR2I( l, l ) ) );
+ breakouts.push_back( SHAPE_LINE_CHAIN( c, c - d_offset,
+ c - d_offset - VECTOR2I( -l, l ) ) );
+ breakouts.push_back( SHAPE_LINE_CHAIN( c, c + d_offset,
+ c + d_offset + VECTOR2I( -l, l ) ) );
+ breakouts.push_back( SHAPE_LINE_CHAIN( c, c - d_offset,
+ c - d_offset - VECTOR2I( l, l ) ) );
+ }
+ }
+
+ return breakouts;
+}
+
+
+PNS_OPTIMIZER::BREAKOUT_LIST PNS_OPTIMIZER::computeBreakouts( int aWidth,
+ const PNS_ITEM* aItem, bool aPermitDiagonal ) const
+{
+ switch( aItem->Kind() )
+ {
+ case PNS_ITEM::VIA:
+ {
+ const PNS_VIA* via = static_cast<const PNS_VIA*>( aItem );
+ return circleBreakouts( aWidth, via->Shape(), aPermitDiagonal );
+ }
+
+ case PNS_ITEM::SOLID:
+ {
+ const SHAPE* shape = aItem->Shape();
+
+ switch( shape->Type() )
+ {
+ case SH_RECT:
+ return rectBreakouts( aWidth, shape, aPermitDiagonal );
+
+ case SH_SEGMENT:
+ {
+ const SHAPE_SEGMENT* seg = static_cast<const SHAPE_SEGMENT*> (shape);
+ const SHAPE_RECT rect = ApproximateSegmentAsRect ( *seg );
+ return rectBreakouts( aWidth, &rect, aPermitDiagonal );
+ }
+
+ case SH_CIRCLE:
+ return circleBreakouts( aWidth, shape, aPermitDiagonal );
+
+ case SH_CONVEX:
+ return convexBreakouts( aWidth, shape, aPermitDiagonal );
+
+ default:
+ break;
+ }
+ }
+
+ default:
+ break;
+ }
+
+ return BREAKOUT_LIST();
+}
+
+
+PNS_ITEM* PNS_OPTIMIZER::findPadOrVia( int aLayer, int aNet, const VECTOR2I& aP ) const
+{
+ PNS_JOINT* jt = m_world->FindJoint( aP, aLayer, aNet );
+
+ if( !jt )
+ return NULL;
+
+ BOOST_FOREACH( PNS_ITEM* item, jt->LinkList() )
+ {
+ if( item->OfKind( PNS_ITEM::VIA | PNS_ITEM::SOLID ) )
+ return item;
+ }
+
+ return NULL;
+}
+
+
+int PNS_OPTIMIZER::smartPadsSingle( PNS_LINE* aLine, PNS_ITEM* aPad, bool aEnd, int aEndVertex )
+{
+ int min_cost = INT_MAX; // PNS_COST_ESTIMATOR::CornerCost( line );
+ int min_len = INT_MAX;
+ DIRECTION_45 dir;
+
+ const int ForbiddenAngles = DIRECTION_45::ANG_ACUTE | DIRECTION_45::ANG_RIGHT |
+ DIRECTION_45::ANG_HALF_FULL | DIRECTION_45::ANG_UNDEFINED;
+
+ typedef std::pair<int, SHAPE_LINE_CHAIN> RtVariant;
+ std::vector<RtVariant> variants;
+
+ PNS_SOLID* solid = dyn_cast<PNS_SOLID*>( aPad );
+
+ // don't do auto-neckdown for offset pads
+ if( solid && solid->Offset() != VECTOR2I( 0, 0 ) )
+ return -1;
+
+
+ BREAKOUT_LIST breakouts = computeBreakouts( aLine->Width(), aPad, true );
+
+ SHAPE_LINE_CHAIN line = ( aEnd ? aLine->CLine().Reverse() : aLine->CLine() );
+
+
+ int p_end = std::min( aEndVertex, std::min( 3, line.PointCount() - 1 ) );
+
+ for( int p = 1; p <= p_end; p++ )
+ {
+ BOOST_FOREACH( SHAPE_LINE_CHAIN & l, breakouts ) {
+
+ for( int diag = 0; diag < 2; diag++ )
+ {
+ SHAPE_LINE_CHAIN v;
+ SHAPE_LINE_CHAIN connect = dir.BuildInitialTrace( l.CPoint( -1 ),
+ line.CPoint( p ), diag == 0 );
+
+ DIRECTION_45 dir_bkout( l.CSegment( -1 ) );
+
+ if(!connect.SegmentCount())
+ continue;
+
+ int ang1 = dir_bkout.Angle( DIRECTION_45( connect.CSegment( 0 ) ) );
+ int ang2 = 0;
+
+ if( (ang1 | ang2) & ForbiddenAngles )
+ continue;
+
+ if( l.Length() > line.Length() )
+ continue;
+
+ v = l;
+
+ v.Append( connect );
+
+ for( int i = p + 1; i < line.PointCount(); i++ )
+ v.Append( line.CPoint( i ) );
+
+ PNS_LINE tmp( *aLine, v );
+ int cc = tmp.CountCorners( ForbiddenAngles );
+
+ if( cc == 0 )
+ {
+ RtVariant vp;
+ vp.first = p;
+ vp.second = aEnd ? v.Reverse() : v;
+ vp.second.Simplify();
+ variants.push_back( vp );
+ }
+ }
+ }
+ }
+
+ SHAPE_LINE_CHAIN l_best;
+ bool found = false;
+ int p_best = -1;
+
+ BOOST_FOREACH( RtVariant& vp, variants )
+ {
+ PNS_LINE tmp( *aLine, vp.second );
+ int cost = PNS_COST_ESTIMATOR::CornerCost( vp.second );
+ int len = vp.second.Length();
+
+ if( !checkColliding( &tmp ) )
+ {
+ if( cost < min_cost || ( cost == min_cost && len < min_len ) )
+ {
+ l_best = vp.second;
+ p_best = vp.first;
+ found = true;
+
+ if( cost == min_cost )
+ min_len = std::min( len, min_len );
+
+ min_cost = std::min( cost, min_cost );
+ }
+ }
+ }
+
+ if( found )
+ {
+ aLine->SetShape( l_best );
+ return p_best;
+ }
+
+ return -1;
+}
+
+bool PNS_OPTIMIZER::runSmartPads( PNS_LINE* aLine )
+{
+ SHAPE_LINE_CHAIN& line = aLine->Line();
+
+ if( line.PointCount() < 3 )
+ return false;
+
+ VECTOR2I p_start = line.CPoint( 0 ), p_end = line.CPoint( -1 );
+
+ PNS_ITEM* startPad = findPadOrVia( aLine->Layer(), aLine->Net(), p_start );
+ PNS_ITEM* endPad = findPadOrVia( aLine->Layer(), aLine->Net(), p_end );
+
+ int vtx = -1;
+
+ if( startPad )
+ vtx = smartPadsSingle( aLine, startPad, false, 3 );
+
+ if( endPad )
+ smartPadsSingle( aLine, endPad, true,
+ vtx < 0 ? line.PointCount() - 1 : line.PointCount() - 1 - vtx );
+
+ aLine->Line().Simplify();
+
+ return true;
+}
+
+
+bool PNS_OPTIMIZER::Optimize( PNS_LINE* aLine, int aEffortLevel, PNS_NODE* aWorld )
+{
+ PNS_OPTIMIZER opt( aWorld );
+
+ opt.SetEffortLevel( aEffortLevel );
+ opt.SetCollisionMask( -1 );
+ return opt.Optimize( aLine );
+}
+
+
+bool PNS_OPTIMIZER::fanoutCleanup( PNS_LINE* aLine )
+{
+ if( aLine->PointCount() < 3 )
+ return false;
+
+ VECTOR2I p_start = aLine->CPoint( 0 ), p_end = aLine->CPoint( -1 );
+
+ PNS_ITEM* startPad = findPadOrVia( aLine->Layer(), aLine->Net(), p_start );
+ PNS_ITEM* endPad = findPadOrVia( aLine->Layer(), aLine->Net(), p_end );
+
+ int thr = aLine->Width() * 10;
+ int len = aLine->CLine().Length();
+
+ if( !startPad )
+ return false;
+
+ bool startMatch = startPad->OfKind( PNS_ITEM::VIA | PNS_ITEM::SOLID );
+ bool endMatch = false;
+
+ if(endPad)
+ {
+ endMatch = endPad->OfKind( PNS_ITEM::VIA | PNS_ITEM::SOLID );
+ }
+ else
+ {
+ endMatch = aLine->EndsWithVia();
+ }
+
+ if( startMatch && endMatch && len < thr )
+ {
+ for( int i = 0; i < 2; i++ )
+ {
+ SHAPE_LINE_CHAIN l2 = DIRECTION_45().BuildInitialTrace( p_start, p_end, i );
+ PNS_LINE repl;
+ repl = PNS_LINE( *aLine, l2 );
+
+ if( !m_world->CheckColliding( &repl ) )
+ {
+ aLine->SetShape( repl.CLine() );
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+int findCoupledVertices( const VECTOR2I& aVertex, const SEG& aOrigSeg, const SHAPE_LINE_CHAIN& aCoupled, PNS_DIFF_PAIR* aPair, int* aIndices )
+{
+ int count = 0;
+ for ( int i = 0; i < aCoupled.SegmentCount(); i++ )
+ {
+ SEG s = aCoupled.CSegment( i );
+ VECTOR2I projOverCoupled = s.LineProject ( aVertex );
+
+ if( s.ApproxParallel ( aOrigSeg ) )
+ {
+ int64_t dist = ( projOverCoupled - aVertex ).EuclideanNorm() - aPair->Width();
+
+ if( aPair->GapConstraint().Matches( dist ) )
+ {
+ *aIndices++ = i;
+ count++;
+ }
+ }
+ }
+
+ return count;
+}
+
+
+bool verifyDpBypass( PNS_NODE* aNode, PNS_DIFF_PAIR* aPair, bool aRefIsP, const SHAPE_LINE_CHAIN& aNewRef, const SHAPE_LINE_CHAIN& aNewCoupled )
+{
+ PNS_LINE refLine ( aRefIsP ? aPair->PLine() : aPair->NLine(), aNewRef );
+ PNS_LINE coupledLine ( aRefIsP ? aPair->NLine() : aPair->PLine(), aNewCoupled );
+
+ if( aNode->CheckColliding( &refLine, &coupledLine, PNS_ITEM::ANY, aPair->Gap() - 10 ) )
+ return false;
+
+ if( aNode->CheckColliding ( &refLine ) )
+ return false;
+
+ if( aNode->CheckColliding ( &coupledLine ) )
+ return false;
+
+ return true;
+}
+
+
+bool coupledBypass( PNS_NODE* aNode, PNS_DIFF_PAIR* aPair, bool aRefIsP, const SHAPE_LINE_CHAIN& aRef, const SHAPE_LINE_CHAIN& aRefBypass, const SHAPE_LINE_CHAIN& aCoupled, SHAPE_LINE_CHAIN& aNewCoupled )
+{
+ int vStartIdx[1024]; // fixme: possible overflow
+
+ int nStarts = findCoupledVertices( aRefBypass.CPoint( 0 ), aRefBypass.CSegment( 0 ), aCoupled, aPair, vStartIdx );
+ DIRECTION_45 dir( aRefBypass.CSegment( 0 ) );
+
+ int64_t bestLength = -1;
+ bool found = false;
+ SHAPE_LINE_CHAIN bestBypass;
+ int si, ei;
+
+ for( int i=0; i< nStarts; i++ )
+ {
+ for( int j = 1; j < aCoupled.PointCount() - 1; j++ )
+ {
+ int delta = std::abs ( vStartIdx[i] - j );
+
+ if( delta > 1 )
+ {
+ const VECTOR2I& vs = aCoupled.CPoint( vStartIdx[i] );
+ SHAPE_LINE_CHAIN bypass = dir.BuildInitialTrace( vs, aCoupled.CPoint(j), dir.IsDiagonal() );
+
+ int64_t coupledLength = aPair->CoupledLength( aRef, bypass );
+
+ SHAPE_LINE_CHAIN newCoupled = aCoupled;
+
+ si = vStartIdx[i];
+ ei = j;
+
+ if(si < ei)
+ newCoupled.Replace( si, ei, bypass );
+ else
+ newCoupled.Replace( ei, si, bypass.Reverse() );
+
+ if(coupledLength > bestLength && verifyDpBypass( aNode, aPair, aRefIsP, aRef, newCoupled) )
+ {
+ bestBypass = newCoupled;
+ bestLength = coupledLength;
+ found = true;
+ }
+ }
+ }
+ }
+
+
+ if( found )
+ aNewCoupled = bestBypass;
+
+ return found;
+}
+
+
+bool checkDpColliding( PNS_NODE* aNode, PNS_DIFF_PAIR* aPair, bool aIsP, const SHAPE_LINE_CHAIN& aPath )
+{
+ PNS_LINE tmp ( aIsP ? aPair->PLine() : aPair->NLine(), aPath );
+
+ return static_cast<bool>( aNode->CheckColliding( &tmp ) );
+}
+
+
+bool PNS_OPTIMIZER::mergeDpStep( PNS_DIFF_PAIR* aPair, bool aTryP, int step )
+{
+ int n = 1;
+
+ SHAPE_LINE_CHAIN currentPath = aTryP ? aPair->CP() : aPair->CN();
+ SHAPE_LINE_CHAIN coupledPath = aTryP ? aPair->CN() : aPair->CP();
+
+ int n_segs = currentPath.SegmentCount() - 1;
+
+ int64_t clenPre = aPair->CoupledLength( currentPath, coupledPath );
+ int64_t budget = clenPre / 10; // fixme: come up with somethig more intelligent here...
+
+ while( n < n_segs - step )
+ {
+ const SEG s1 = currentPath.CSegment( n );
+ const SEG s2 = currentPath.CSegment( n + step );
+
+ DIRECTION_45 dir1( s1 );
+ DIRECTION_45 dir2( s2 );
+
+ if( dir1.IsObtuse( dir2 ) )
+ {
+ SHAPE_LINE_CHAIN bypass = DIRECTION_45().BuildInitialTrace( s1.A, s2.B, dir1.IsDiagonal() );
+ SHAPE_LINE_CHAIN newRef;
+ SHAPE_LINE_CHAIN newCoup;
+ int64_t deltaCoupled = -1, deltaUni = -1;
+
+ newRef = currentPath;
+ newRef.Replace( s1.Index(), s2.Index(), bypass );
+
+ deltaUni = aPair->CoupledLength ( newRef, coupledPath ) - clenPre + budget;
+
+ if ( coupledBypass( m_world, aPair, aTryP, newRef, bypass, coupledPath, newCoup ) )
+ {
+ deltaCoupled = aPair->CoupledLength( newRef, newCoup ) - clenPre + budget;
+
+ if( deltaCoupled >= 0 )
+ {
+ newRef.Simplify();
+ newCoup.Simplify();
+
+ aPair->SetShape( newRef, newCoup, !aTryP );
+ return true;
+ }
+ }
+ else if( deltaUni >= 0 && verifyDpBypass ( m_world, aPair, aTryP, newRef, coupledPath ) )
+ {
+ newRef.Simplify();
+ coupledPath.Simplify();
+
+ aPair->SetShape( newRef, coupledPath, !aTryP );
+ return true;
+ }
+ }
+
+ n++;
+ }
+
+ return false;
+}
+
+
+bool PNS_OPTIMIZER::mergeDpSegments( PNS_DIFF_PAIR* aPair )
+{
+ int step_p = aPair->CP().SegmentCount() - 2;
+ int step_n = aPair->CN().SegmentCount() - 2;
+
+ while( 1 )
+ {
+ int n_segs_p = aPair->CP().SegmentCount();
+ int n_segs_n = aPair->CN().SegmentCount();
+
+ int max_step_p = n_segs_p - 2;
+ int max_step_n = n_segs_n - 2;
+
+ if( step_p > max_step_p )
+ step_p = max_step_p;
+
+ if( step_n > max_step_n )
+ step_n = max_step_n;
+
+ if( step_p < 1 && step_n < 1)
+ break;
+
+ bool found_anything_p = false;
+ bool found_anything_n = false;
+
+ if( step_p > 1 )
+ found_anything_p = mergeDpStep( aPair, true, step_p );
+
+ if( step_n > 1 )
+ found_anything_n = mergeDpStep( aPair, false, step_n );
+
+ if( !found_anything_n && !found_anything_p )
+ {
+ step_n--;
+ step_p--;
+ }
+ }
+ return true;
+}
+
+
+bool PNS_OPTIMIZER::Optimize( PNS_DIFF_PAIR* aPair )
+{
+ return mergeDpSegments( aPair );
+}