/* * KiRouter - a push-and-(sometimes-)shove PCB router * * Copyright (C) 2013 CERN * Author: Tomasz Wlostowski * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include #include #include #include "pns_line.h" #include "pns_node.h" #include "pns_optimizer.h" #include "pns_utils.h" using namespace std; /** * * Cost Estimator Methods * **/ int PNS_COST_ESTIMATOR::CornerCost( const SEG& a, const SEG& b ) { DIRECTION_45 dir_a( a ), dir_b( b ); 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.GetCLine() ); } void PNS_COST_ESTIMATOR::Add( PNS_LINE& aLine ) { m_lengthCost += aLine.GetCLine().Length(); m_cornerCost += CornerCost( aLine ); } void PNS_COST_ESTIMATOR::Remove( PNS_LINE& aLine ) { m_lengthCost -= aLine.GetCLine().Length(); m_cornerCost -= CornerCost( aLine ); } void PNS_COST_ESTIMATOR::Replace( PNS_LINE& aOldLine, PNS_LINE& aNewLine ) { m_lengthCost -= aOldLine.GetCLine().Length(); m_cornerCost -= CornerCost( aOldLine ); m_lengthCost += aNewLine.GetCLine().Length(); m_cornerCost += CornerCost( aNewLine ); } bool PNS_COST_ESTIMATOR::IsBetter( PNS_COST_ESTIMATOR& aOther, double aLengthTollerance, double aCornerTollerance ) const { if( aOther.m_cornerCost < m_cornerCost && aOther.m_lengthCost < m_lengthCost ) return true; else if( aOther.m_cornerCost < m_cornerCost * aCornerTollerance && aOther.m_lengthCost < m_lengthCost * aLengthTollerance ) 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_cache = new SHAPE_INDEX_LIST(); } PNS_OPTIMIZER::~PNS_OPTIMIZER() { // delete m_cache; } struct PNS_OPTIMIZER::CacheVisitor { CacheVisitor( 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->GetKind() ) 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].hits = 1; m_cacheTags[aItem].isStatic = aIsStatic; } void PNS_OPTIMIZER::removeCachedSegments( PNS_LINE* aLine, int aStartVertex, int aEndVertex ) { std::vector* segs = aLine->GetLinkedSegments(); if( !segs ) return; if( aEndVertex < 0 ) aEndVertex += aLine->GetCLine().PointCount(); for( int i = aStartVertex; i < aEndVertex - 1; i++ ) { PNS_SEGMENT* s = (*segs)[i]; m_cacheTags.erase( s ); m_cache.Remove( s ); } // *cacheRemove( (*segs)[i] ); } void PNS_OPTIMIZER::CacheRemove( PNS_ITEM* aItem ) { if( aItem->GetKind() == PNS_ITEM::LINE ) removeCachedSegments( static_cast (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.isStatic ) { m_cache.Remove( i->first ); m_cacheTags.erase( i->first ); } } } bool PNS_OPTIMIZER::checkColliding( PNS_ITEM* aItem, bool aUpdateCache ) { CacheVisitor v( aItem, m_world, m_collisionKindMask ); return m_world->CheckColliding( aItem ); // something is wrong with the cache, need to investigate. m_cache.Query( aItem->GetShape(), m_world->GetMaxClearance(), v, false ); if( !v.m_collidingItem ) { PNS_NODE::OptObstacle obs = m_world->CheckColliding( aItem ); if( obs ) { if( aUpdateCache ) cacheAdd( obs->item ); return true; } } else { m_cacheTags[v.m_collidingItem].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->GetLine(); 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->GetLine(); 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, int aStartVertex, int aEndVertex ) { 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 ); 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 ); if( aLine->GetCLine().SegmentCount() < 4 ) return false; DIRECTION_45 orig_start( aLine->GetCLine().CSegment( 0 ) ); DIRECTION_45 orig_end( aLine->GetCLine().CSegment( -1 ) ); while( n < n_segs - step ) { const SEG s1 = aCurrentPath.CSegment( n ); const SEG s2 = aCurrentPath.CSegment( n + step ); SHAPE_LINE_CHAIN path[2], * 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; 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( (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::BreakoutList PNS_OPTIMIZER::circleBreakouts( int aWidth, const SHAPE* aShape, bool aPermitDiagonal ) const { BreakoutList breakouts; for( int angle = 0; angle < 360; angle += 45 ) { const SHAPE_CIRCLE* cir = static_cast( 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::BreakoutList PNS_OPTIMIZER::rectBreakouts( int aWidth, const SHAPE* aShape, bool aPermitDiagonal ) const { const SHAPE_RECT* rect = static_cast(aShape); VECTOR2I s = rect->GetSize(), c = rect->GetPosition() + VECTOR2I( s.x / 2, s.y / 2 ); BreakoutList 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::BreakoutList PNS_OPTIMIZER::computeBreakouts( int aWidth, const PNS_ITEM* aItem, bool aPermitDiagonal ) const { switch( aItem->GetKind() ) { case PNS_ITEM::VIA: { const PNS_VIA* via = static_cast( aItem ); return circleBreakouts( aWidth, via->GetShape(), aPermitDiagonal ); } case PNS_ITEM::SOLID: { const SHAPE* shape = aItem->GetShape(); switch( shape->Type() ) { case SH_RECT: return rectBreakouts( aWidth, shape, aPermitDiagonal ); case SH_CIRCLE: return circleBreakouts( aWidth, shape, aPermitDiagonal ); default: break; } } default: break; } return BreakoutList(); } PNS_ITEM* PNS_OPTIMIZER::findPadOrVia( int aLayer, int aNet, const VECTOR2I& aP ) const { PNS_NODE::OptJoint jt = m_world->FindJoint( aP, aLayer, aNet ); if( !jt ) return NULL; BOOST_FOREACH( PNS_ITEM* item, jt->GetLinkList() ) { if( item->GetKind() == PNS_ITEM::VIA || item->GetKind() == 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 pair RtVariant; vector variants; BreakoutList breakouts = computeBreakouts( aLine->GetWidth(), aPad, true ); SHAPE_LINE_CHAIN line = ( aEnd ? aLine->GetCLine().Reverse() : aLine->GetCLine() ); // bool startDiagonal = DIRECTION_45( line.CSegment(0) ).IsDiagonal(); int p_end = min( aEndVertex, min( 3, line.PointCount() - 1 ) ); for( int p = 1; p <= p_end; p++ ) { BOOST_FOREACH( SHAPE_LINE_CHAIN & l, breakouts ) { // PNSDisplayDebugLine (l, 0); 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 ) ); // DIRECTION_45 dir_head ( line.CSegment(p + 1)); int ang1 = dir_bkout.Angle( DIRECTION_45( connect.CSegment( 0 ) ) ); int ang2 = 0; // int ang2 = dir_head.Angle ( DIRECTION_45(connect.CSegment(-1) )); 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 ); // tmp.GetLine().Simplify(); 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(aEnd) * PNSDisplayDebugLine (l_best, 6); * else * PNSDisplayDebugLine (l_best, 5);*/ if( cost < min_cost || ( cost == min_cost && len < min_len ) ) { l_best = vp.second; p_best = vp.first; found = true; // if(cost == min_cost) if( cost == min_cost ) min_len = std::min( len, min_len ); min_cost = std::min( cost, min_cost ); } } } if( found ) { // printf("end: %d, p-best: %d, p-end: %d, p-total: %d\n", aEnd, p_best, p_end, l_best.PointCount()); // if(!aEnd) // PNSDisplayDebugLine (l_best, 5); // else aLine->SetShape( l_best ); return p_best; } return -1; } bool PNS_OPTIMIZER::runSmartPads( PNS_LINE* aLine ) { SHAPE_LINE_CHAIN& line = aLine->GetLine(); if( line.PointCount() < 3 ) return false; VECTOR2I p_start = line.CPoint( 0 ), p_end = line.CPoint( -1 ); PNS_ITEM* startPad = findPadOrVia( aLine->GetLayer(), aLine->GetNet(), p_start ); PNS_ITEM* endPad = findPadOrVia( aLine->GetLayer(), aLine->GetNet(), 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->GetLine().Simplify(); return true; } bool PNS_OPTIMIZER::Optimize( PNS_LINE* aLine, int aEffortLevel, PNS_NODE* aWorld ) { PNS_OPTIMIZER opt( aWorld ? aWorld : aLine->GetWorld() ); opt.SetEffortLevel( aEffortLevel ); opt.SetCollisionMask( -1 ); return opt.Optimize( aLine ); }