kicad/pcbnew/router/pns_shove.cpp

470 lines
11 KiB
C++

/*
* KiRouter - a push-and-(sometimes-)shove PCB router
*
* Copyright (C) 2013 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.or/licenses/>.
*/
#include <deque>
#include <cassert>
#include <wx/timer.h>
#include "trace.h"
#include "pns_line.h"
#include "pns_node.h"
#include "pns_walkaround.h"
#include "pns_shove.h"
#include "pns_optimizer.h"
#include "pns_via.h"
#include "pns_utils.h"
#include <profile.h>
using namespace std;
PNS_SHOVE::PNS_SHOVE( PNS_NODE *aWorld )
{
m_root = aWorld;
m_iterLimit = 100;
};
PNS_SHOVE::~PNS_SHOVE()
{
}
struct range {
range()
{
min_v = max_v = -1;
}
void add ( int x )
{
if(min_v < 0) min_v = x;
if(max_v < 0) max_v = x;
if(x < min_v)
min_v = x;
else if (x > max_v)
max_v = x;
}
int start()
{
return min_v;
}
int end()
{
return max_v;
}
int min_v, max_v;
};
// fixme: this is damn f***ing inefficient. And fails much too often due to broken direction finding algorithm.
bool PNS_SHOVE::tryShove(PNS_NODE *aNode, PNS_LINE *aHead, PNS_LINE *aObstacle, PNS_SEGMENT& aObstacleSeg, PNS_LINE *aResult, bool aInvertWinding )
{
const SHAPE_LINE_CHAIN &head = aHead->GetCLine();
bool cw = false;
int i;
if(aHead->EndsWithVia() && !aHead->GetLayers().Overlaps(aObstacle->GetLayers()))
{
int clearance = aNode->GetClearance(aHead, aObstacle);
SHAPE_LINE_CHAIN hull = aHead->GetVia().Hull( clearance - aObstacle->GetWidth() / 2 );
//SHAPE_LINE_CHAIN path_pre, path_walk_cw, path_walk_ccw, path_post;
SHAPE_LINE_CHAIN path_cw, path_ccw, *path;
aObstacle->NewWalkaround(hull, path_cw, true);
aObstacle->NewWalkaround(hull, path_ccw, false);
path = path_ccw.Length() < path_cw.Length() ? &path_ccw : &path_cw;
aResult->SetShape(*path);
//PNSDisplayDebugLine (*path, 5);
if(!aResult->Is45Degree())
{
//printf("polyset non-45\npoly %s\nendpolyset\n", aResult->GetCLine().Format().c_str());
}
/*... special case for vias? */
return !aNode->CheckColliding(aResult, aHead);
}
int ns = head.SegmentCount();
if(aHead->EndsWithVia())
ns ++;
for(i = 0; i < head.SegmentCount(); i++)
{
const PNS_SEGMENT hs (*aHead, head.CSegment(i));
if(aNode->CheckColliding(&hs, aObstacle))
{
VECTOR2I v1 = hs.GetSeg().b - hs.GetSeg().a;
VECTOR2I v2 = aObstacleSeg.GetSeg().b - aObstacleSeg.GetSeg().a;
VECTOR2I::extended_type det = v1.Cross(v2);
if(det > 0)
cw = true;
else
cw = false;
break;
}
}
if(aInvertWinding)
{
if(cw)
cw = false;
else
cw = true;
}
PNS_LINE shoved (*aObstacle);
int clearance = aNode->GetClearance(aHead, aObstacle);
range r;
for(i = 0; i < ns; i++)
{
SHAPE_LINE_CHAIN hull;
if(i < head.SegmentCount())
{
const PNS_SEGMENT hs (*aHead, head.CSegment(i));
hull = hs.Hull( clearance, 0 );
} else
hull = aHead->GetVia().Hull( clearance - aObstacle->GetWidth() / 2);
SHAPE_LINE_CHAIN path_pre, path_walk, path_post, tmp;
SHAPE_LINE_CHAIN path_pre2, path_walk2, path_post2;
//shoved.NewWalkaround(hull, path_pre, path_walk, path_post, cw);
shoved.NewWalkaround(hull, path_pre, path_walk, path_post, cw);
/*if(path_pre != path_pre2 || path_post != path_post2 || path_walk != path_walk2 )
{
TRACE(5, "polyset orig\npoly %s\npoly %s\npoly %s\nendpolyset\n", path_pre.Format().c_str() % path_walk.Format().c_str() % path_post.Format().c_str());
TRACE(5, "polyset err\npoly %s\npoly %s\npoly %s\nendpolyset\n", path_pre2.Format().c_str() % path_walk2.Format().c_str() % path_post2.Format().c_str());
}*/
tmp = shoved.GetCLine();
if(path_walk.SegmentCount())
r.add(i);
path_pre.Append(path_walk);
path_pre.Append(path_post);
path_pre.Simplify();
shoved.SetShape(path_pre);
// shoved.SetAffectedRange ( start, end );
*aResult = shoved;
if(!aResult->Is45Degree())
{
//TRACE(5, "polyset non-45\npoly %s\npoly %s\npoly %s\nendpolyset\n", tmp.Format().c_str() % hull.Format().c_str() % aResult->GetCLine().Format().c_str());
}
}
TRACE(2, "CW %d affectedRange %d-%d [total %d]", (cw?1:0) % r.start() % r.end() % ns);
return !aNode->CheckColliding(aResult, aHead);
}
PNS_SHOVE::ShoveStatus PNS_SHOVE::shoveSingleLine(PNS_NODE *aNode, PNS_LINE *aCurrent, PNS_LINE *aObstacle, PNS_SEGMENT& aObstacleSeg, PNS_LINE *aResult )
{
bool rv = tryShove(aNode, aCurrent, aObstacle, aObstacleSeg, aResult, false);
if( !rv )
rv = tryShove(aNode, aCurrent, aObstacle, aObstacleSeg, aResult, true);
if( !rv )
{
TRACEn(2, "Shove failed" );
return SH_INCOMPLETE;
}
aResult->GetLine().Simplify();
const SHAPE_LINE_CHAIN& sh_shoved = aResult->GetCLine();
const SHAPE_LINE_CHAIN& sh_orig = aObstacle->GetCLine();
if(sh_shoved.SegmentCount() > 1 && sh_shoved.CPoint(0) == sh_orig.CPoint(0) && sh_shoved.CPoint(-1) == sh_orig.CPoint(-1) )
return SH_OK;
else if (!sh_shoved.SegmentCount())
return SH_NULL;
else
return SH_INCOMPLETE;
}
bool PNS_SHOVE::reduceSpringback( PNS_LINE *aHead )
{
bool rv = false;
while(!m_nodeStack.empty())
{
SpringbackTag st_stack = m_nodeStack.back();
bool tail_ok = true;
if(!st_stack.node->CheckColliding(aHead) && tail_ok)
{
rv = true;
delete st_stack.node;
m_nodeStack.pop_back();
} else
break;
}
return rv;
}
bool PNS_SHOVE::pushSpringback( PNS_NODE *aNode, PNS_LINE *aHead, const PNS_COST_ESTIMATOR& aCost )
{
BOX2I headBB = aHead->GetCLine().BBox();
SpringbackTag st;
st.node = aNode;
st.cost = aCost;
st.length = std::max(headBB.GetWidth(), headBB.GetHeight());;
m_nodeStack.push_back(st);
return true;
}
const PNS_COST_ESTIMATOR PNS_SHOVE::TotalCost() const
{
if(m_nodeStack.empty())
return PNS_COST_ESTIMATOR();
else
return m_nodeStack.back().cost;
}
PNS_SHOVE::ShoveStatus PNS_SHOVE::ShoveLines(PNS_LINE* aCurrentHead)
{
stack <PNS_LINE *> lineStack;
PNS_NODE *node, *parent;
PNS_VIA *headVia = NULL;
bool fail = false;
int iter = 0;
PNS_LINE *head = aCurrentHead->Clone();
reduceSpringback(aCurrentHead);
parent = m_nodeStack.empty() ? m_root : m_nodeStack.back().node;
node = parent->Branch();
lineStack.push(head);
//node->Add(tail);
node->Add(head);
if(head->EndsWithVia())
{
headVia = head->GetVia().Clone();
node->Add( headVia );
}
PNS_OPTIMIZER optimizer (node);
optimizer.SetEffortLevel (PNS_OPTIMIZER::MERGE_SEGMENTS | PNS_OPTIMIZER::SMART_PADS);
optimizer.SetCollisionMask( -1 );
PNS_NODE::OptObstacle nearest;
optimizer.CacheStaticItem(head);
if(headVia)
optimizer.CacheStaticItem(headVia);
TRACE(1, "ShoveStart [root: %d jts, node: %d jts]", m_root->JointCount() % node->JointCount());
//PNS_ITEM *lastWalkSolid = NULL;
prof_counter totalRealTime;
wxLongLong t_start = wxGetLocalTimeMillis();
while(!lineStack.empty())
{
wxLongLong t_cur = wxGetLocalTimeMillis();
if ((t_cur - t_start).ToLong() > ShoveTimeLimit)
{
fail = true;
break;
}
iter++;
if(iter > m_iterLimit)
{
fail = true;
break;
}
PNS_LINE *currentLine = lineStack.top();
prof_start( &totalRealTime, false );
nearest = node->NearestObstacle(currentLine, PNS_ITEM::ANY);
prof_end( &totalRealTime );
TRACE(2,"t-nearestObstacle %lld us", (totalRealTime.value ));
if(!nearest)
{
if(lineStack.size() > 1)
{
PNS_LINE *original = lineStack.top();
PNS_LINE optimized;
int r_start, r_end;
original->GetAffectedRange(r_start, r_end);
TRACE(1, "Iter %d optimize-line [range %d-%d, total %d]", iter % r_start % r_end % original->GetCLine().PointCount() );
//lastWalkSolid = NULL;
prof_start( &totalRealTime, false );
if( optimizer.Optimize(original, &optimized) )
{
node->Remove(original);
optimizer.CacheRemove(original);
node->Add(&optimized);
if(original->BelongsTo(node))
delete original;
}
prof_end( &totalRealTime );
TRACE(2,"t-optimizeObstacle %lld us", (totalRealTime.value ));
}
lineStack.pop();
} else {
switch(nearest->item->GetKind())
{
case PNS_ITEM::SEGMENT:
{
TRACE(1, "Iter %d shove-line", iter );
PNS_SEGMENT *pseg = static_cast<PNS_SEGMENT*>(nearest->item);
PNS_LINE *collidingLine = node->AssembleLine(pseg);
PNS_LINE *shovedLine = collidingLine->CloneProperties();
prof_start( &totalRealTime, false );
ShoveStatus st = shoveSingleLine(node, currentLine, collidingLine, *pseg, shovedLine);
prof_end( &totalRealTime );
TRACE(2,"t-shoveSingle %lld us", (totalRealTime.value ));
if(st == SH_OK)
{
node->Replace(collidingLine, shovedLine);
if(collidingLine->BelongsTo( node ))
delete collidingLine;
optimizer.CacheRemove(collidingLine);
lineStack.push( shovedLine );
} else
fail = true;
//lastWalkSolid = NULL;
break;
} // case SEGMENT
case PNS_ITEM::SOLID:
case PNS_ITEM::VIA:
{
TRACE(1, "Iter %d walkaround-solid [%p]", iter % nearest->item );
if(lineStack.size() == 1)
{
fail = true;
break;
}
/* if(lastWalkSolid == nearest->item)
{
fail = true;
break;
}*/
PNS_WALKAROUND walkaround (node);
PNS_LINE *walkaroundLine = currentLine->CloneProperties();
walkaround.SetSolidsOnly(true);
walkaround.SetSingleDirection(true);
prof_start( &totalRealTime, false );
walkaround.Route(*currentLine, *walkaroundLine, false);
prof_end( &totalRealTime );
TRACE(2,"t-walkSolid %lld us", (totalRealTime.value ));
node->Replace(currentLine, walkaroundLine);
if(currentLine->BelongsTo( node ))
delete currentLine;
optimizer.CacheRemove(currentLine);
lineStack.top() = walkaroundLine;
//lastWalkSolid = nearest->item;
break;
}
default:
break;
} // switch
if(fail)
break;
}
}
node->Remove(head);
delete head;
if(headVia)
{
node->Remove(headVia);
delete headVia;
}
TRACE(1, "Shove status : %s after %d iterations" , (fail ? "FAILED" : "OK") % iter );
if(!fail)
{
pushSpringback(node, aCurrentHead, PNS_COST_ESTIMATOR());
return SH_OK;
} else {
delete node;
return SH_INCOMPLETE;
}
}