kicad/pcbnew/router/pns_line_placer.cpp

715 lines
18 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 <vector>
#include <boost/foreach.hpp>
#include <boost/optional.hpp>
#include "trace.h"
#include "pns_node.h"
#include "pns_line_placer.h"
#include "pns_walkaround.h"
#include "pns_shove.h"
#include "pns_utils.h"
using namespace std;
using boost::optional;
PNS_LINE_PLACER::PNS_LINE_PLACER( PNS_NODE *aWorld )
{
m_initial_direction = DIRECTION_45(DIRECTION_45::N);
m_follow_mouse = false;
m_smoothing_step = 100000;
m_smooth_mouse = false;
m_iteration = 0;
m_world = aWorld;
m_mode = RM_Smart;
m_follow_mouse = true;
m_shove = NULL;
};
PNS_LINE_PLACER::~PNS_LINE_PLACER()
{
if(m_shove)
delete m_shove;
}
void PNS_LINE_PLACER::ApplySettings ( const PNS_ROUTING_SETTINGS& aSettings )
{
m_follow_mouse = aSettings.m_followMouse;
m_mode = aSettings.m_routingMode;
m_walkaroundIterationLimit = aSettings.m_walkaroundIterationLimit;
m_smartPads = aSettings.m_smartPads;
}
void PNS_LINE_PLACER::StartPlacement(const VECTOR2I& aStart, int aNet, int aWidth, int aLayer )
{
m_direction = m_initial_direction;
TRACE(1, "world %p, intitial-direction %s layer %d\n", m_world % m_direction.Format().c_str() % aLayer);
m_head.SetNet(aNet);
m_tail.SetNet(aNet);
m_head.SetWidth(aWidth);
m_tail.SetWidth(aWidth);
m_head.GetLine().Clear();
m_tail.GetLine().Clear();
m_head.SetLayer(aLayer);
m_tail.SetLayer(aLayer);
m_iteration = 0;
m_p_start = aStart;
m_currentNode = m_world->Branch();
m_head.SetWorld(m_currentNode);
m_tail.SetWorld(m_currentNode);
//if(m_shove)
// delete m_shove;
m_shove = new PNS_SHOVE(m_currentNode);
m_placingVia = false;
}
void PNS_LINE_PLACER::SetInitialDirection(const DIRECTION_45& aDirection)
{
m_initial_direction = aDirection;
if(m_tail.GetCLine().SegmentCount() == 0)
m_direction = aDirection;
}
/**
* Function handleSelfIntersections()
*
* Checks if the head of the track intersects its tail. If so, cuts the tail up to the
* intersecting segment and fixes the head direction to match the last segment before the cut.
* @return true if the line has been changed.
*/
bool PNS_LINE_PLACER::handleSelfIntersections()
{
SHAPE_LINE_CHAIN::Intersections ips;
SHAPE_LINE_CHAIN& head = m_head.GetLine();
SHAPE_LINE_CHAIN& tail = m_tail.GetLine();
// if there is no tail, there is nothing to intersect with
if(tail.PointCount() < 2)
return false;
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.
BOOST_FOREACH(SHAPE_LINE_CHAIN::Intersection i, ips)
{
if (i.our.Index() < n)
{
n = i.our.Index();
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_p_start = tail.Point(0);
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_p_start = last.a;
m_direction = DIRECTION_45(last);
tail.Remove(n, -1);
return true;
}
return false;
}
/**
* Function handlePullback()
*
* Deals with pull-back: reduces the tail if head trace is moved backwards wrs
* to the current tail direction.
* @return true if the line has been changed.
*/
bool PNS_LINE_PLACER::handlePullback()
{
SHAPE_LINE_CHAIN& head = m_head.GetLine();
SHAPE_LINE_CHAIN& tail = m_tail.GetLine();
int n = tail.PointCount();
if(n == 0)
return false;
else if (n == 1)
{
m_p_start = tail.CPoint(0);
tail.Clear();
return true;
}
DIRECTION_45 first_head (head.Segment(0));
DIRECTION_45 last_tail (tail.Segment(-1));
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)
{
const SEG last = tail.CSegment(-1);
m_direction = DIRECTION_45(last);
m_p_start = last.a;
TRACE(0, "Placer: pullback triggered [%d] [%s %s]", n % last_tail.Format().c_str() % first_head.Format().c_str());
// 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.Remove(-1, -1);
if( !tail.SegmentCount() )
m_direction = m_initial_direction;
return true;
}
return false;
}
/**
* Function reduceTail()
*
* Attempts to reduce the numer of segments in the tail by trying to replace a certain number
* of latest tail segments with a direct trace leading to aEnd that does not collide with anything.
* @param aEnd: current routing destination point.
* @return true if the line has been changed.
*/
bool PNS_LINE_PLACER::reduceTail(const VECTOR2I& aEnd)
{
SHAPE_LINE_CHAIN& head = m_head.GetLine();
SHAPE_LINE_CHAIN& tail = m_tail.GetLine();
int n = tail.SegmentCount();
// 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;
DIRECTION_45 head_dir ( head.Segment(0) );
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);
PNS_LINE tmp (m_tail, replacement);
if (m_currentNode->CheckColliding(&tmp, PNS_ITEM::ANY))
break;
if(DIRECTION_45(replacement.Segment(0)) == dir)
{
new_start = s.a;
new_direction = dir;
reduce_index = i;
}
}
if(reduce_index >= 0)
{
TRACE(0, "Placer: reducing tail: %d", reduce_index);
SHAPE_LINE_CHAIN reducedLine = new_direction.BuildInitialTrace(new_start, aEnd);
m_p_start = new_start;
m_direction = new_direction;
tail.Remove(reduce_index+1, -1);
head.Clear();
return true;
}
if( !tail.SegmentCount() )
m_direction = m_initial_direction;
return false;
}
/**
* Function checkObtusity()
*
* Helper that checks if segments a and b form an obtuse angle (in 45-degree regime).
* @return true, if angle (a, b) is obtuse
*/
bool PNS_LINE_PLACER::checkObtusity(const SEG& a, const SEG& b) const
{
const DIRECTION_45 dir_a(a);
const DIRECTION_45 dir_b(b);
return dir_a.IsObtuse(dir_b) || dir_a == dir_b;
}
/**
* Function mergeHead()
*
* Moves "estabished" segments from the head to the tail if certain conditions are met.
* @return true, if the line has been changed.
*/
bool PNS_LINE_PLACER::mergeHead()
{
SHAPE_LINE_CHAIN& head = m_head.GetLine();
SHAPE_LINE_CHAIN& tail = m_tail.GetLine();
const int ForbiddenAngles = DIRECTION_45::ANG_ACUTE | DIRECTION_45::ANG_HALF_FULL | DIRECTION_45::ANG_UNDEFINED;
head.Simplify();
tail.Simplify();
int n_head = head.SegmentCount();
int n_tail = tail.SegmentCount();
if( n_head < 3 )
{
TRACEn(4, "Merge failed: not enough head segs.");
return false;
}
if (n_tail && head.CPoint(0) != tail.CPoint(-1))
{
TRACEn(4, "Merge failed: head and tail discontinuous.");
return false;
}
if( m_head.CountCorners(ForbiddenAngles) != 0 )
return false;
DIRECTION_45 dir_tail, dir_head;
dir_head = DIRECTION_45(head.CSegment(0));
if(n_tail)
{
dir_tail = DIRECTION_45(tail.CSegment(-1));
if(dir_head.Angle(dir_tail) & ForbiddenAngles)
return false;
}
if(!n_tail)
tail.Append(head.CSegment(0).a);
for (int i = 0; i < n_head - 2; i++)
{
tail.Append(head.CSegment(i).b);
}
tail.Simplify();
SEG last = tail.CSegment(-1);
m_p_start = last.b;
m_direction = DIRECTION_45(last).Right();
head.Remove(0, n_head - 2);
TRACE(0, "Placer: merge %d, new direction: %s", n_head % m_direction.Format().c_str());
head.Simplify();
tail.Simplify();
return true;
}
bool PNS_LINE_PLACER::handleViaPlacement ( PNS_LINE& aHead )
{
if(!m_placingVia)
return true;
PNS_LAYERSET allLayers (0, 15);
PNS_VIA v (aHead.GetCLine().CPoint(-1), allLayers, m_viaDiameter, aHead.GetNet());
v.SetDrill(m_viaDrill);
VECTOR2I force;
VECTOR2I lead = aHead.GetCLine().CPoint(-1) - aHead.GetCLine().CPoint(0);
if( v.PushoutForce ( m_shove->GetCurrentNode(), lead, force, true, 20 ) )
{
SHAPE_LINE_CHAIN line = m_direction.BuildInitialTrace(aHead.GetCLine().CPoint(0), aHead.GetCLine().CPoint(-1) + force);
aHead = PNS_LINE(aHead, line);
v.SetPos(v.GetPos() + force);
return true;
}
return false;
}
/**
* Function routeHead()
*
* Computes the head trace between the current start point (m_p_start) and point aP,
* starting with direction defined in m_direction. The trace walks around all
* colliding solid or non-movable items. Movable segments are ignored, as they'll be handled
* later by the shove algorithm.
*/
bool PNS_LINE_PLACER::routeHead(const VECTOR2I& aP, PNS_LINE& aNewHead, bool aCwWalkaround)
{
// STAGE 1: route a simple two-segment trace between m_p_start and aP...
SHAPE_LINE_CHAIN line = m_direction.BuildInitialTrace(m_p_start, aP);
PNS_LINE initTrack (m_head, line);
PNS_LINE walkFull, walkSolids;
if(m_mode == RM_Ignore)
{
aNewHead = initTrack;
return true;
}
handleViaPlacement(initTrack);
m_currentNode = m_shove->GetCurrentNode();
PNS_OPTIMIZER optimizer(m_currentNode);
PNS_WALKAROUND walkaround( m_currentNode );
walkaround.SetSolidsOnly(false);
walkaround.SetIterationLimit(m_mode == RM_Walkaround ? 8 : 5 );
//walkaround.SetApproachCursor(true, aP);
PNS_WALKAROUND::WalkaroundStatus wf = walkaround.Route(initTrack, walkFull);
#if 0
if(m_mode == RM_Walkaround)
{
// walkaround.
// PNSDisplayDebugLine (walkFull.GetCLine(), 4);
if(wf == PNS_WALKAROUND::STUCK)
{
aNewHead = m_head;
aNewHead.SetShape(walkFull.GetCLine());
aNewHead = aNewHead.ClipToNearestObstacle(m_currentNode);
return false;
}
aNewHead = m_head;
aNewHead.SetShape(walkFull.GetCLine());
// printf("nh w %d l %d\n", aNewHead.GetWidth(), aNewHead.GetLayers().Start());
return true;
}
#endif
PNS_COST_ESTIMATOR cost_walk, cost_orig;
walkaround.SetApproachCursor ( false, aP );
walkaround.SetSolidsOnly(true);
walkaround.SetIterationLimit( 10 );
PNS_WALKAROUND::WalkaroundStatus stat_solids = walkaround.Route(initTrack, walkSolids);
optimizer.SetEffortLevel ( PNS_OPTIMIZER::MERGE_SEGMENTS );
optimizer.SetCollisionMask (PNS_ITEM::SOLID);
optimizer.Optimize(&walkSolids);
#if 0
optimizer.SetCollisionMask (-1);
optimizer.Optimize(&walkFull);
#endif
cost_orig.Add(initTrack);
cost_walk.Add(walkFull);
if(m_mode == RM_Smart || m_mode == RM_Shove)
{
PNS_LINE l2;
bool walk_better = cost_orig.IsBetter(cost_walk, 1.5, 10.0);
walk_better = false;
#if 0
printf("RtTrk width %d %d %d", initTrack.GetWidth(), walkFull.GetWidth(), walkSolids.GetWidth());
printf("init-coll %d\n", m_currentNode->CheckColliding(&initTrack)? 1: 0);
printf("total cost: walk cor %.0f len %.0f orig cor %.0f len %.0f walk-better %d\n",
cost_walk.GetCornerCost(), cost_walk.GetLengthCost(),
cost_orig.GetCornerCost(), cost_orig.GetLengthCost(),
walk_better );
#endif
if(m_mode == RM_Smart && wf == PNS_WALKAROUND::DONE && walk_better && walkFull.GetCLine().CPoint(-1) == initTrack.GetCLine().CPoint(-1))
l2 = walkFull;
else if (stat_solids == PNS_WALKAROUND::DONE)
l2 = walkSolids;
else
l2 = initTrack.ClipToNearestObstacle(m_shove->GetCurrentNode());
PNS_LINE l ( m_tail );
l.GetLine().Append( l2.GetCLine() );
l.GetLine().Simplify();
if(m_placingVia)
{
PNS_LAYERSET allLayers(0,15);
PNS_VIA v1( l.GetCLine().CPoint(-1), allLayers, m_viaDiameter );
PNS_VIA v2( l2.GetCLine().CPoint(-1), allLayers, m_viaDiameter );
v1.SetDrill(m_viaDrill);
v2.SetDrill(m_viaDrill);
l.AppendVia ( v1 );
l2.AppendVia ( v2 );
}
PNS_SHOVE::ShoveStatus status = m_shove->ShoveLines(&l);
m_currentNode = m_shove->GetCurrentNode();
if (status == PNS_SHOVE::SH_OK)
{
optimizer.SetWorld (m_currentNode);
optimizer.ClearCache();
optimizer.SetEffortLevel( PNS_OPTIMIZER::MERGE_OBTUSE | PNS_OPTIMIZER::SMART_PADS );
optimizer.SetCollisionMask (-1);
optimizer.Optimize(&l2);
aNewHead = l2;
return true;
} else {
walkaround.SetWorld( m_currentNode );
walkaround.SetSolidsOnly(false);
walkaround.SetIterationLimit( 10 );
walkaround.SetApproachCursor ( true, aP );
walkaround.Route(initTrack, l2);
aNewHead = l2.ClipToNearestObstacle (m_shove->GetCurrentNode());
//aNewHead = l2;
return false;
}
}
return false;
}
/**
* Function optimizeTailHeadTransition()
*
* Tries to reduce the corner count of the most recent part of tail/head by merging
* obtuse/collinear segments.
* @return true, if the line has been changed.
*/
bool PNS_LINE_PLACER::optimizeTailHeadTransition()
{
SHAPE_LINE_CHAIN& head = m_head.GetLine();
SHAPE_LINE_CHAIN& tail = m_tail.GetLine();
const int TailLookbackSegments = 5;
int threshold = min(tail.PointCount(), TailLookbackSegments + 1);
if(tail.SegmentCount() < 3)
return false;
// assemble TailLookbackSegments tail segments with the current head
SHAPE_LINE_CHAIN opt_line = tail.Slice(-threshold, -1);
opt_line.Append(head);
// opt_line.Simplify();
PNS_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
//if(PNS_OPTIMIZER::Optimize(&new_head, PNS_OPTIMIZER::MERGE_SEGMENTS))
if(new_head.MergeSegments())
{
PNS_LINE tmp(m_tail, opt_line);
TRACE(0, "Placer: optimize tail-head [%d]", threshold);
head.Clear();
tail.Replace(-threshold, -1, new_head.GetCLine());
tail.Simplify();
m_p_start = new_head.GetCLine().CPoint(-1);
m_direction = DIRECTION_45(new_head.GetCLine().CSegment(-1));
return true;
}
return false;
}
/**
* Function routeStep()
*
* Performs a single routing alorithm step, for the end point aP.
* @param aP ending point of current route
* @return true, if the line has been changed.
*/
void PNS_LINE_PLACER::routeStep(const VECTOR2I& aP)
{
bool fail = false;
bool go_back = false;
int i, n_iter = 1;
PNS_LINE new_head;
m_follow_mouse = true;
TRACE(2,"INIT-DIR: %s head: %d, tail: %d segs\n", m_initial_direction.Format().c_str() % m_head.GetCLine().SegmentCount() % m_tail.GetCLine().SegmentCount());
for(i = 0; i < n_iter; i++)
{
if(!go_back && m_follow_mouse)
reduceTail(aP);
go_back = false;
if(!routeHead(aP, new_head, true))
fail = true;
if(!new_head.Is45Degree())
fail = true;
if(!m_follow_mouse)
return;
m_head = new_head;
if(handleSelfIntersections())
{
n_iter++;
go_back = true;
}
if(!go_back && handlePullback())
{
n_iter++;
go_back = true;
}
}
if(!fail)
{
if(optimizeTailHeadTransition())
return;
mergeHead();
}
}
/**
* Function Route()
*
* Re-routes the current track to point aP. Returns true, when routing has completed
* successfully (i.e. the trace end has reached point aP), and false if the trace was stuck somewhere
* on the way. May call routeStep() repetitively due to mouse smoothing.
* @param aP ending point of current route.
* @return true, if the routing is complete.
*/
bool PNS_LINE_PLACER::Route(const VECTOR2I& aP)
{
if(m_smooth_mouse)
{
VECTOR2I p_cur = m_p_start;
VECTOR2I step = (aP - m_p_start).Resize(m_smoothing_step);
do
{
if ((p_cur - aP).EuclideanNorm() <= m_smoothing_step)
p_cur = aP;
else
p_cur += step;
routeStep(p_cur);
} while (p_cur != aP);
} else
routeStep(aP);
return CurrentEnd() == aP;
}
const PNS_LINE PNS_LINE_PLACER::GetTrace() const
{
PNS_LINE tmp(m_head);
tmp.SetShape( m_tail.GetCLine() );
tmp.GetLine().Append( m_head.GetCLine() );
tmp.GetLine().Simplify();
return tmp;
}
void PNS_LINE_PLACER::FlipPosture()
{
m_initial_direction = m_initial_direction.Right();
m_direction = m_direction.Right();
}
void PNS_LINE_PLACER::GetUpdatedItems( PNS_NODE::ItemVector& aRemoved, PNS_NODE::ItemVector& aAdded)
{
return m_shove->GetCurrentNode()->GetUpdatedItems(aRemoved, aAdded);
}
PNS_NODE *PNS_LINE_PLACER::GetCurrentNode() const
{
return m_shove->GetCurrentNode();
}