2013-09-18 17:55:16 +00:00
|
|
|
/*
|
|
|
|
* KiRouter - a push-and-(sometimes-)shove PCB router
|
|
|
|
*
|
2014-05-14 13:53:54 +00:00
|
|
|
* Copyright (C) 2013-2014 CERN
|
2013-09-18 17:55:16 +00:00
|
|
|
* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
2013-09-26 21:53:54 +00:00
|
|
|
*
|
2013-09-18 17:55:16 +00:00
|
|
|
* 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.
|
2013-09-26 21:53:54 +00:00
|
|
|
*
|
2013-09-18 17:55:16 +00:00
|
|
|
* 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.
|
2013-09-26 21:53:54 +00:00
|
|
|
*
|
2013-09-18 17:55:16 +00:00
|
|
|
* You should have received a copy of the GNU General Public License along
|
2014-05-14 13:53:54 +00:00
|
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
2013-09-18 17:55:16 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <boost/optional.hpp>
|
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
#include <colors.h>
|
|
|
|
|
2013-09-18 17:55:16 +00:00
|
|
|
#include "trace.h"
|
|
|
|
|
|
|
|
#include "pns_node.h"
|
|
|
|
#include "pns_line_placer.h"
|
|
|
|
#include "pns_walkaround.h"
|
|
|
|
#include "pns_shove.h"
|
|
|
|
#include "pns_utils.h"
|
2014-05-14 13:53:54 +00:00
|
|
|
#include "pns_router.h"
|
2015-02-18 00:29:54 +00:00
|
|
|
#include "pns_topology.h"
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
#include <class_board_item.h>
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
using boost::optional;
|
2014-03-16 17:40:23 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
PNS_LINE_PLACER::PNS_LINE_PLACER( PNS_ROUTER* aRouter ) :
|
2015-02-18 16:53:46 +00:00
|
|
|
PNS_PLACEMENT_ALGO( aRouter )
|
2013-09-18 17:55:16 +00:00
|
|
|
{
|
2014-05-14 13:53:54 +00:00
|
|
|
m_initial_direction = DIRECTION_45::N;
|
|
|
|
m_world = NULL;
|
2013-09-26 21:53:54 +00:00
|
|
|
m_shove = NULL;
|
2014-05-14 13:53:54 +00:00
|
|
|
m_currentNode = NULL;
|
2014-11-14 18:15:58 +00:00
|
|
|
m_idle = true;
|
2015-05-15 12:49:11 +00:00
|
|
|
|
|
|
|
// Init temporary variables (do not leave uninitialized members)
|
|
|
|
m_lastNode = NULL;
|
|
|
|
m_placingVia = false;
|
|
|
|
m_currentNet = 0;
|
|
|
|
m_currentLayer = 0;
|
|
|
|
m_currentMode = RM_MarkObstacles;
|
|
|
|
m_startItem = NULL;
|
|
|
|
m_chainedPlacement = false;
|
|
|
|
m_splitSeg = false;
|
|
|
|
m_orthoMode = false;
|
2014-05-16 11:37:31 +00:00
|
|
|
}
|
|
|
|
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
PNS_LINE_PLACER::~PNS_LINE_PLACER()
|
2013-09-18 17:55:16 +00:00
|
|
|
{
|
2013-09-26 21:53:54 +00:00
|
|
|
if( m_shove )
|
|
|
|
delete m_shove;
|
2013-09-18 17:55:16 +00:00
|
|
|
}
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2015-07-07 16:36:38 +00:00
|
|
|
void PNS_LINE_PLACER::setWorld( PNS_NODE* aWorld )
|
2013-09-18 17:55:16 +00:00
|
|
|
{
|
2014-05-14 13:53:54 +00:00
|
|
|
m_world = aWorld;
|
2013-09-18 17:55:16 +00:00
|
|
|
}
|
|
|
|
|
2015-07-07 16:36:38 +00:00
|
|
|
|
|
|
|
const PNS_VIA PNS_LINE_PLACER::makeVia( const VECTOR2I& aP )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2014-11-14 18:15:58 +00:00
|
|
|
const PNS_LAYERSET layers( m_sizes.GetLayerTop(), m_sizes.GetLayerBottom() );
|
|
|
|
|
2015-07-07 16:36:38 +00:00
|
|
|
return PNS_VIA( aP, layers, m_sizes.ViaDiameter(), m_sizes.ViaDrill(), -1, m_sizes.ViaType() );
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2015-02-18 00:29:54 +00:00
|
|
|
bool PNS_LINE_PLACER::ToggleVia( bool aEnabled )
|
2013-09-18 17:55:16 +00:00
|
|
|
{
|
2014-11-14 18:15:58 +00:00
|
|
|
m_placingVia = aEnabled;
|
2015-07-07 16:36:38 +00:00
|
|
|
|
2015-07-07 16:36:41 +00:00
|
|
|
if( !aEnabled )
|
|
|
|
m_head.RemoveVia();
|
|
|
|
|
2015-02-18 00:29:54 +00:00
|
|
|
return true;
|
2013-09-18 17:55:16 +00:00
|
|
|
}
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
void PNS_LINE_PLACER::setInitialDirection( const DIRECTION_45& aDirection )
|
2013-09-18 17:55:16 +00:00
|
|
|
{
|
2013-09-26 21:53:54 +00:00
|
|
|
m_initial_direction = aDirection;
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
if( m_tail.SegmentCount() == 0 )
|
|
|
|
m_direction = aDirection;
|
2013-09-18 17:55:16 +00:00
|
|
|
}
|
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
bool PNS_LINE_PLACER::handleSelfIntersections()
|
2013-09-18 17:55:16 +00:00
|
|
|
{
|
2013-10-14 18:40:36 +00:00
|
|
|
SHAPE_LINE_CHAIN::INTERSECTIONS ips;
|
2014-05-14 13:53:54 +00:00
|
|
|
SHAPE_LINE_CHAIN& head = m_head.Line();
|
|
|
|
SHAPE_LINE_CHAIN& tail = m_tail.Line();
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
// 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.
|
2016-06-29 20:07:55 +00:00
|
|
|
for( SHAPE_LINE_CHAIN::INTERSECTION i : ips )
|
2013-09-26 21:53:54 +00:00
|
|
|
{
|
|
|
|
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();
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
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 );
|
2013-10-14 18:40:36 +00:00
|
|
|
m_p_start = last.A;
|
2013-09-26 21:53:54 +00:00
|
|
|
m_direction = DIRECTION_45( last );
|
|
|
|
tail.Remove( n, -1 );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PNS_LINE_PLACER::handlePullback()
|
2013-09-18 17:55:16 +00:00
|
|
|
{
|
2014-05-14 13:53:54 +00:00
|
|
|
SHAPE_LINE_CHAIN& head = m_head.Line();
|
|
|
|
SHAPE_LINE_CHAIN& tail = m_tail.Line();
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
if( head.PointCount() < 2 )
|
2014-05-14 13:53:54 +00:00
|
|
|
return false;
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
int n = tail.PointCount();
|
|
|
|
|
|
|
|
if( n == 0 )
|
|
|
|
return false;
|
|
|
|
else if( n == 1 )
|
|
|
|
{
|
|
|
|
m_p_start = tail.CPoint( 0 );
|
|
|
|
tail.Clear();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
DIRECTION_45 first_head( head.CSegment( 0 ) );
|
|
|
|
DIRECTION_45 last_tail( tail.CSegment( -1 ) );
|
2013-09-26 21:53:54 +00:00
|
|
|
DIRECTION_45::AngleType angle = first_head.Angle( last_tail );
|
|
|
|
|
2013-10-14 11:43:57 +00:00
|
|
|
// case 1: we have a defined routing direction, and the currently computed
|
2013-09-26 21:53:54 +00:00
|
|
|
// head goes in different one.
|
|
|
|
bool pullback_1 = false; // (m_direction != DIRECTION_45::UNDEFINED && m_direction != first_head);
|
|
|
|
|
2013-10-14 11:43:57 +00:00
|
|
|
// case 2: regardless of the current routing direction, if the tail/head
|
2013-09-26 21:53:54 +00:00
|
|
|
// extremities form an acute or right angle, reduce the tail by one segment
|
|
|
|
// (and hope that further iterations) will result with a cleaner trace
|
2014-05-16 11:37:31 +00:00
|
|
|
bool pullback_2 = ( angle == DIRECTION_45::ANG_RIGHT || angle == DIRECTION_45::ANG_ACUTE );
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
if( pullback_1 || pullback_2 )
|
|
|
|
{
|
|
|
|
const SEG last = tail.CSegment( -1 );
|
|
|
|
m_direction = DIRECTION_45( last );
|
2013-10-14 18:40:36 +00:00
|
|
|
m_p_start = last.A;
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
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
|
2013-10-14 11:43:57 +00:00
|
|
|
// result with a head trace that starts with a segment following our
|
2013-09-26 21:53:54 +00:00
|
|
|
// 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;
|
2013-09-18 17:55:16 +00:00
|
|
|
}
|
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
bool PNS_LINE_PLACER::reduceTail( const VECTOR2I& aEnd )
|
2013-09-18 17:55:16 +00:00
|
|
|
{
|
2014-05-14 13:53:54 +00:00
|
|
|
SHAPE_LINE_CHAIN& head = m_head.Line();
|
|
|
|
SHAPE_LINE_CHAIN& tail = m_tail.Line();
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
int n = tail.SegmentCount();
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
if( head.SegmentCount() < 1 )
|
2014-05-14 13:53:54 +00:00
|
|
|
return false;
|
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
// 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 );
|
|
|
|
|
2013-10-14 11:43:57 +00:00
|
|
|
// calculate a replacement route and check if it matches
|
2013-09-26 21:53:54 +00:00
|
|
|
// the direction of the segment to be replaced
|
2013-10-14 18:40:36 +00:00
|
|
|
SHAPE_LINE_CHAIN replacement = dir.BuildInitialTrace( s.A, aEnd );
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
PNS_LINE tmp( m_tail, replacement );
|
|
|
|
|
|
|
|
if( m_currentNode->CheckColliding( &tmp, PNS_ITEM::ANY ) )
|
|
|
|
break;
|
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
if( DIRECTION_45( replacement.CSegment( 0 ) ) == dir )
|
2013-09-26 21:53:54 +00:00
|
|
|
{
|
2013-10-14 18:40:36 +00:00
|
|
|
new_start = s.A;
|
2013-09-26 21:53:54 +00:00
|
|
|
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;
|
2013-09-18 17:55:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
bool PNS_LINE_PLACER::checkObtusity( const SEG& aA, const SEG& aB ) const
|
2013-09-18 17:55:16 +00:00
|
|
|
{
|
2014-05-16 11:37:31 +00:00
|
|
|
const DIRECTION_45 dir_a( aA );
|
|
|
|
const DIRECTION_45 dir_b( aB );
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
return dir_a.IsObtuse( dir_b ) || dir_a == dir_b;
|
2013-09-18 17:55:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PNS_LINE_PLACER::mergeHead()
|
|
|
|
{
|
2014-05-14 13:53:54 +00:00
|
|
|
SHAPE_LINE_CHAIN& head = m_head.Line();
|
|
|
|
SHAPE_LINE_CHAIN& tail = m_tail.Line();
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2013-10-14 11:43:57 +00:00
|
|
|
const int ForbiddenAngles = DIRECTION_45::ANG_ACUTE |
|
2013-09-26 21:53:54 +00:00
|
|
|
DIRECTION_45::ANG_HALF_FULL |
|
|
|
|
DIRECTION_45::ANG_UNDEFINED;
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
head.Simplify();
|
|
|
|
tail.Simplify();
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
int n_head = head.SegmentCount();
|
|
|
|
int n_tail = tail.SegmentCount();
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
if( n_head < 3 )
|
|
|
|
{
|
|
|
|
TRACEn( 4, "Merge failed: not enough head segs." );
|
|
|
|
return false;
|
|
|
|
}
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
if( n_tail && head.CPoint( 0 ) != tail.CPoint( -1 ) )
|
|
|
|
{
|
|
|
|
TRACEn( 4, "Merge failed: head and tail discontinuous." );
|
|
|
|
return false;
|
|
|
|
}
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
if( m_head.CountCorners( ForbiddenAngles ) != 0 )
|
|
|
|
return false;
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
DIRECTION_45 dir_tail, dir_head;
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
dir_head = DIRECTION_45( head.CSegment( 0 ) );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
if( n_tail )
|
|
|
|
{
|
|
|
|
dir_tail = DIRECTION_45( tail.CSegment( -1 ) );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
if( dir_head.Angle( dir_tail ) & ForbiddenAngles )
|
|
|
|
return false;
|
|
|
|
}
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
if( !n_tail )
|
2013-10-14 18:40:36 +00:00
|
|
|
tail.Append( head.CSegment( 0 ).A );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
for( int i = 0; i < n_head - 2; i++ )
|
|
|
|
{
|
2013-10-14 18:40:36 +00:00
|
|
|
tail.Append( head.CSegment( i ).B );
|
2013-09-26 21:53:54 +00:00
|
|
|
}
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
tail.Simplify();
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
SEG last = tail.CSegment( -1 );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-10-14 18:40:36 +00:00
|
|
|
m_p_start = last.B;
|
2013-09-26 21:53:54 +00:00
|
|
|
m_direction = DIRECTION_45( last ).Right();
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
head.Remove( 0, n_head - 2 );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
TRACE( 0, "Placer: merge %d, new direction: %s", n_head % m_direction.Format().c_str() );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
head.Simplify();
|
|
|
|
tail.Simplify();
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
return true;
|
2013-09-18 17:55:16 +00:00
|
|
|
}
|
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2014-05-31 14:04:25 +00:00
|
|
|
bool PNS_LINE_PLACER::rhWalkOnly( const VECTOR2I& aP, PNS_LINE& aNewHead )
|
2013-09-18 17:55:16 +00:00
|
|
|
{
|
2015-07-02 14:09:24 +00:00
|
|
|
PNS_LINE initTrack( m_head );
|
|
|
|
PNS_LINE walkFull;
|
2014-05-14 13:53:54 +00:00
|
|
|
int effort = 0;
|
2015-07-02 14:09:24 +00:00
|
|
|
bool rv = true, viaOk;
|
|
|
|
|
2015-07-02 14:11:15 +00:00
|
|
|
viaOk = buildInitialLine( aP, initTrack );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
PNS_WALKAROUND walkaround( m_currentNode, Router() );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
walkaround.SetSolidsOnly( false );
|
|
|
|
walkaround.SetIterationLimit( Settings().WalkaroundIterationLimit() );
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
PNS_WALKAROUND::WALKAROUND_STATUS wf = walkaround.Route( initTrack, walkFull, false );
|
2014-05-14 13:53:54 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
switch( Settings().OptimizerEffort() )
|
2013-09-26 21:53:54 +00:00
|
|
|
{
|
2015-10-05 16:28:41 +00:00
|
|
|
case OE_LOW:
|
|
|
|
effort = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OE_MEDIUM:
|
|
|
|
case OE_FULL:
|
|
|
|
effort = PNS_OPTIMIZER::MERGE_SEGMENTS;
|
|
|
|
break;
|
2013-09-26 21:53:54 +00:00
|
|
|
}
|
2014-05-16 11:37:31 +00:00
|
|
|
|
|
|
|
if( Settings().SmartPads() )
|
2014-05-14 13:53:54 +00:00
|
|
|
effort |= PNS_OPTIMIZER::SMART_PADS;
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
if( wf == PNS_WALKAROUND::STUCK )
|
|
|
|
{
|
|
|
|
walkFull = walkFull.ClipToNearestObstacle( m_currentNode );
|
|
|
|
rv = true;
|
2014-05-16 11:37:31 +00:00
|
|
|
}
|
|
|
|
else if( m_placingVia && viaOk )
|
|
|
|
{
|
2015-07-07 16:36:38 +00:00
|
|
|
walkFull.AppendVia( makeVia( walkFull.CPoint( -1 ) ) );
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
PNS_OPTIMIZER::Optimize( &walkFull, effort, m_currentNode );
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2015-07-02 14:11:15 +00:00
|
|
|
if( m_currentNode->CheckColliding( &walkFull ) )
|
2015-07-10 21:42:13 +00:00
|
|
|
{
|
|
|
|
aNewHead = m_head;
|
2015-07-02 14:11:15 +00:00
|
|
|
return false;
|
2015-07-10 21:42:13 +00:00
|
|
|
}
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
m_head = walkFull;
|
|
|
|
aNewHead = walkFull;
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
return rv;
|
|
|
|
}
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
|
|
|
|
bool PNS_LINE_PLACER::rhMarkObstacles( const VECTOR2I& aP, PNS_LINE& aNewHead )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2015-07-02 14:09:24 +00:00
|
|
|
buildInitialLine( aP, m_head );
|
2014-05-14 13:53:54 +00:00
|
|
|
aNewHead = m_head;
|
2015-07-22 08:46:45 +00:00
|
|
|
return static_cast<bool>( m_currentNode->CheckColliding( &m_head ) );
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2015-07-22 08:46:56 +00:00
|
|
|
bool PNS_LINE_PLACER::rhShoveOnly( const VECTOR2I& aP, PNS_LINE& aNewHead )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2015-07-02 14:09:24 +00:00
|
|
|
PNS_LINE initTrack( m_head );
|
2014-05-14 13:53:54 +00:00
|
|
|
PNS_LINE walkSolids, l2;
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2015-07-02 14:09:24 +00:00
|
|
|
bool viaOk = buildInitialLine( aP, initTrack );
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
m_currentNode = m_shove->CurrentNode();
|
|
|
|
PNS_OPTIMIZER optimizer( m_currentNode );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
PNS_WALKAROUND walkaround( m_currentNode, Router() );
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
walkaround.SetSolidsOnly( true );
|
|
|
|
walkaround.SetIterationLimit( 10 );
|
2014-05-16 11:37:31 +00:00
|
|
|
PNS_WALKAROUND::WALKAROUND_STATUS stat_solids = walkaround.Route( initTrack, walkSolids );
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
optimizer.SetEffortLevel( PNS_OPTIMIZER::MERGE_SEGMENTS );
|
2014-05-14 13:53:54 +00:00
|
|
|
optimizer.SetCollisionMask ( PNS_ITEM::SOLID );
|
2013-09-26 21:53:54 +00:00
|
|
|
optimizer.Optimize( &walkSolids );
|
2014-05-14 13:53:54 +00:00
|
|
|
|
|
|
|
if( stat_solids == PNS_WALKAROUND::DONE )
|
2014-05-16 11:37:31 +00:00
|
|
|
l2 = walkSolids;
|
|
|
|
else
|
|
|
|
l2 = initTrack.ClipToNearestObstacle( m_shove->CurrentNode() );
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
PNS_LINE l( m_tail );
|
|
|
|
l.Line().Append( l2.CLine() );
|
|
|
|
l.Line().Simplify();
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2015-10-12 08:02:03 +00:00
|
|
|
if( l.PointCount() == 0 || l2.PointCount() == 0 )
|
|
|
|
{
|
|
|
|
aNewHead = m_head;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-07-02 14:09:24 +00:00
|
|
|
if( m_placingVia && viaOk )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2015-07-07 16:36:38 +00:00
|
|
|
PNS_VIA v1( makeVia( l.CPoint( -1 ) ) );
|
|
|
|
PNS_VIA v2( makeVia( l2.CPoint( -1 ) ) );
|
2014-05-14 13:53:54 +00:00
|
|
|
|
|
|
|
l.AppendVia( v1 );
|
|
|
|
l2.AppendVia( v2 );
|
|
|
|
}
|
|
|
|
|
2014-11-14 19:19:00 +00:00
|
|
|
l.Line().Simplify();
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
// in certain, uncommon cases there may be loops in the head+tail, In such case, we don't shove to avoid
|
|
|
|
// screwing up the database.
|
2014-05-16 11:37:31 +00:00
|
|
|
if( l.HasLoops() )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
|
|
|
aNewHead = m_head;
|
2014-11-14 19:19:00 +00:00
|
|
|
return false;
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
PNS_SHOVE::SHOVE_STATUS status = m_shove->ShoveLines( l );
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
m_currentNode = m_shove->CurrentNode();
|
|
|
|
|
2015-08-19 16:07:16 +00:00
|
|
|
if( status == PNS_SHOVE::SH_OK || status == PNS_SHOVE::SH_HEAD_MODIFIED )
|
2014-05-16 11:37:31 +00:00
|
|
|
{
|
2015-08-19 16:07:16 +00:00
|
|
|
if( status == PNS_SHOVE::SH_HEAD_MODIFIED )
|
|
|
|
{
|
|
|
|
l2 = m_shove->NewHead();
|
|
|
|
}
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
optimizer.SetWorld( m_currentNode );
|
|
|
|
optimizer.SetEffortLevel( PNS_OPTIMIZER::MERGE_OBTUSE | PNS_OPTIMIZER::SMART_PADS );
|
|
|
|
optimizer.SetCollisionMask( PNS_ITEM::ANY );
|
|
|
|
optimizer.Optimize( &l2 );
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
aNewHead = l2;
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
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->CurrentNode() );
|
2014-05-14 13:53:54 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
return false;
|
|
|
|
}
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
return false;
|
2013-09-18 17:55:16 +00:00
|
|
|
}
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
bool PNS_LINE_PLACER::routeHead( const VECTOR2I& aP, PNS_LINE& aNewHead )
|
|
|
|
{
|
|
|
|
switch( m_currentMode )
|
|
|
|
{
|
2015-10-05 16:28:41 +00:00
|
|
|
case RM_MarkObstacles:
|
|
|
|
return rhMarkObstacles( aP, aNewHead );
|
|
|
|
case RM_Walkaround:
|
|
|
|
return rhWalkOnly( aP, aNewHead );
|
|
|
|
case RM_Shove:
|
|
|
|
return rhShoveOnly( aP, aNewHead );
|
|
|
|
default:
|
|
|
|
break;
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-09-18 17:55:16 +00:00
|
|
|
|
|
|
|
bool PNS_LINE_PLACER::optimizeTailHeadTransition()
|
|
|
|
{
|
2014-05-14 13:53:54 +00:00
|
|
|
PNS_LINE tmp = Trace();
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
if( PNS_OPTIMIZER::Optimize( &tmp, PNS_OPTIMIZER::FANOUT_CLEANUP, m_currentNode ) )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2014-05-16 11:37:31 +00:00
|
|
|
if( tmp.SegmentCount() < 1 )
|
2014-05-14 13:53:54 +00:00
|
|
|
return false;
|
|
|
|
|
2014-11-14 19:19:00 +00:00
|
|
|
m_head = tmp;
|
2014-05-14 13:53:54 +00:00
|
|
|
m_p_start = tmp.CLine().CPoint( 0 );
|
|
|
|
m_direction = DIRECTION_45( tmp.CSegment( 0 ) );
|
|
|
|
m_tail.Line().Clear();
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
return true;
|
|
|
|
}
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
SHAPE_LINE_CHAIN& head = m_head.Line();
|
|
|
|
SHAPE_LINE_CHAIN& tail = m_tail.Line();
|
|
|
|
|
|
|
|
int tailLookbackSegments = 3;
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
//if(m_currentMode() == RM_Walkaround)
|
|
|
|
// tailLookbackSegments = 10000;
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
int threshold = std::min( tail.PointCount(), tailLookbackSegments + 1 );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
if( tail.SegmentCount() < 3 )
|
|
|
|
return false;
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
// assemble TailLookbackSegments tail segments with the current head
|
|
|
|
SHAPE_LINE_CHAIN opt_line = tail.Slice( -threshold, -1 );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
int end = std::min(2, head.PointCount() - 1 );
|
|
|
|
|
|
|
|
opt_line.Append( head.Slice( 0, end ) );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
PNS_LINE new_head( m_tail, opt_line );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
// and see if it could be made simpler by merging obtuse/collnear segments.
|
2013-10-14 11:43:57 +00:00
|
|
|
// If so, replace the (threshold) last tail points and the head with
|
2013-09-26 21:53:54 +00:00
|
|
|
// the optimized line
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
if( PNS_OPTIMIZER::Optimize( &new_head, PNS_OPTIMIZER::MERGE_OBTUSE, m_currentNode ) )
|
2013-09-26 21:53:54 +00:00
|
|
|
{
|
|
|
|
PNS_LINE tmp( m_tail, opt_line );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
TRACE( 0, "Placer: optimize tail-head [%d]", threshold );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
head.Clear();
|
2014-05-14 13:53:54 +00:00
|
|
|
tail.Replace( -threshold, -1, new_head.CLine() );
|
2013-09-26 21:53:54 +00:00
|
|
|
tail.Simplify();
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
m_p_start = new_head.CLine().CPoint( -1 );
|
|
|
|
m_direction = DIRECTION_45( new_head.CSegment( -1 ) );
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2013-09-18 17:55:16 +00:00
|
|
|
}
|
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
void PNS_LINE_PLACER::routeStep( const VECTOR2I& aP )
|
2013-09-18 17:55:16 +00:00
|
|
|
{
|
2013-09-26 21:53:54 +00:00
|
|
|
bool fail = false;
|
|
|
|
bool go_back = false;
|
|
|
|
|
|
|
|
int i, n_iter = 1;
|
|
|
|
|
|
|
|
PNS_LINE new_head;
|
|
|
|
|
|
|
|
TRACE( 2, "INIT-DIR: %s head: %d, tail: %d segs\n",
|
2014-05-14 13:53:54 +00:00
|
|
|
m_initial_direction.Format().c_str() % m_head.SegmentCount() %
|
|
|
|
m_tail.SegmentCount() );
|
2013-09-26 21:53:54 +00:00
|
|
|
|
|
|
|
for( i = 0; i < n_iter; i++ )
|
|
|
|
{
|
2014-05-14 13:53:54 +00:00
|
|
|
if( !go_back && Settings().FollowMouse() )
|
2013-09-26 21:53:54 +00:00
|
|
|
reduceTail( aP );
|
|
|
|
|
|
|
|
go_back = false;
|
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
if( !routeHead( aP, new_head ) )
|
2013-09-26 21:53:54 +00:00
|
|
|
fail = true;
|
|
|
|
|
|
|
|
if( !new_head.Is45Degree() )
|
|
|
|
fail = true;
|
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
if( !Settings().FollowMouse() )
|
2013-09-26 21:53:54 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
m_head = new_head;
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
if( handleSelfIntersections() )
|
|
|
|
{
|
|
|
|
n_iter++;
|
|
|
|
go_back = true;
|
|
|
|
}
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
if( !go_back && handlePullback() )
|
|
|
|
{
|
|
|
|
n_iter++;
|
|
|
|
go_back = true;
|
|
|
|
}
|
|
|
|
}
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
if( !fail )
|
|
|
|
{
|
2014-05-14 13:53:54 +00:00
|
|
|
if( optimizeTailHeadTransition() )
|
|
|
|
return;
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2013-09-26 21:53:54 +00:00
|
|
|
mergeHead();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
bool PNS_LINE_PLACER::route( const VECTOR2I& aP )
|
2013-09-26 21:53:54 +00:00
|
|
|
{
|
2014-05-14 13:53:54 +00:00
|
|
|
routeStep( aP );
|
2013-09-26 21:53:54 +00:00
|
|
|
return CurrentEnd() == aP;
|
2013-09-18 17:55:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
const PNS_LINE PNS_LINE_PLACER::Trace() const
|
2013-09-18 17:55:16 +00:00
|
|
|
{
|
2013-09-26 21:53:54 +00:00
|
|
|
PNS_LINE tmp( m_head );
|
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
tmp.SetShape( m_tail.CLine() );
|
|
|
|
tmp.Line().Append( m_head.CLine() );
|
|
|
|
tmp.Line().Simplify();
|
2013-09-26 21:53:54 +00:00
|
|
|
return tmp;
|
2013-09-18 17:55:16 +00:00
|
|
|
}
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2014-11-14 19:19:00 +00:00
|
|
|
const PNS_ITEMSET PNS_LINE_PLACER::Traces()
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2014-11-14 19:19:00 +00:00
|
|
|
m_currentTrace = Trace();
|
2014-05-14 13:53:54 +00:00
|
|
|
return PNS_ITEMSET( &m_currentTrace );
|
|
|
|
}
|
2013-09-26 21:53:54 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2013-09-18 17:55:16 +00:00
|
|
|
void PNS_LINE_PLACER::FlipPosture()
|
|
|
|
{
|
2013-09-26 21:53:54 +00:00
|
|
|
m_initial_direction = m_initial_direction.Right();
|
|
|
|
m_direction = m_direction.Right();
|
|
|
|
}
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
|
|
|
|
PNS_NODE* PNS_LINE_PLACER::CurrentNode( bool aLoopsRemoved ) const
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2014-05-16 11:37:31 +00:00
|
|
|
if( aLoopsRemoved && m_lastNode )
|
2014-05-14 13:53:54 +00:00
|
|
|
return m_lastNode;
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
return m_currentNode;
|
|
|
|
}
|
2013-09-18 17:55:16 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
|
|
|
|
void PNS_LINE_PLACER::splitAdjacentSegments( PNS_NODE* aNode, PNS_ITEM* aSeg, const VECTOR2I& aP )
|
|
|
|
{
|
|
|
|
if( aSeg && aSeg->OfKind( PNS_ITEM::SEGMENT ) )
|
|
|
|
{
|
2014-11-14 19:19:00 +00:00
|
|
|
PNS_JOINT* jt = aNode->FindJoint( aP, aSeg );
|
2014-05-14 13:53:54 +00:00
|
|
|
|
|
|
|
if( jt && jt->LinkCount() >= 1 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
PNS_SEGMENT* s_old = static_cast<PNS_SEGMENT*>( aSeg );
|
|
|
|
PNS_SEGMENT* s_new[2];
|
|
|
|
|
|
|
|
s_new[0] = s_old->Clone();
|
|
|
|
s_new[1] = s_old->Clone();
|
|
|
|
|
|
|
|
s_new[0]->SetEnds( s_old->Seg().A, aP );
|
|
|
|
s_new[1]->SetEnds( aP, s_old->Seg().B );
|
|
|
|
|
|
|
|
aNode->Remove( s_old );
|
|
|
|
aNode->Add( s_new[0], true );
|
|
|
|
aNode->Add( s_new[1], true );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2014-11-14 18:15:58 +00:00
|
|
|
bool PNS_LINE_PLACER::SetLayer( int aLayer )
|
2013-09-18 17:55:16 +00:00
|
|
|
{
|
2014-11-14 19:19:00 +00:00
|
|
|
if( m_idle )
|
2014-11-14 18:15:58 +00:00
|
|
|
{
|
|
|
|
m_currentLayer = aLayer;
|
|
|
|
return true;
|
2015-02-18 16:53:46 +00:00
|
|
|
}
|
|
|
|
else if( m_chainedPlacement )
|
|
|
|
{
|
2014-11-14 18:15:58 +00:00
|
|
|
return false;
|
2015-02-18 16:53:46 +00:00
|
|
|
}
|
|
|
|
else if( !m_startItem || ( m_startItem->OfKind( PNS_ITEM::VIA ) && m_startItem->Layers().Overlaps( aLayer ) ) ) {
|
2014-11-14 18:15:58 +00:00
|
|
|
m_currentLayer = aLayer;
|
2014-11-14 18:17:01 +00:00
|
|
|
m_splitSeg = false;
|
|
|
|
initPlacement ( m_splitSeg );
|
2014-11-14 18:15:58 +00:00
|
|
|
Move ( m_currentEnd, NULL );
|
|
|
|
return true;
|
|
|
|
}
|
2014-07-09 14:50:31 +00:00
|
|
|
|
2014-11-14 18:15:58 +00:00
|
|
|
return false;
|
2013-09-18 17:55:16 +00:00
|
|
|
}
|
|
|
|
|
2015-02-18 16:53:46 +00:00
|
|
|
|
2015-02-18 00:29:54 +00:00
|
|
|
bool PNS_LINE_PLACER::Start( const VECTOR2I& aP, PNS_ITEM* aStartItem )
|
2013-09-18 17:55:16 +00:00
|
|
|
{
|
2014-05-14 13:53:54 +00:00
|
|
|
VECTOR2I p( aP );
|
|
|
|
|
|
|
|
static int unknowNetIdx = 0; // -10000;
|
|
|
|
int net = -1;
|
|
|
|
|
|
|
|
bool splitSeg = false;
|
|
|
|
|
|
|
|
if( Router()->SnappingEnabled() )
|
|
|
|
p = Router()->SnapToItem( aStartItem, aP, splitSeg );
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
if( !aStartItem || aStartItem->Net() < 0 )
|
|
|
|
net = unknowNetIdx--;
|
|
|
|
else
|
|
|
|
net = aStartItem->Net();
|
|
|
|
|
2014-11-14 18:15:58 +00:00
|
|
|
m_currentStart = p;
|
2014-05-14 13:53:54 +00:00
|
|
|
m_currentEnd = p;
|
|
|
|
m_currentNet = net;
|
2014-11-14 18:15:58 +00:00
|
|
|
m_startItem = aStartItem;
|
|
|
|
m_placingVia = false;
|
|
|
|
m_chainedPlacement = false;
|
2014-11-14 18:17:01 +00:00
|
|
|
m_splitSeg = splitSeg;
|
2014-11-14 18:15:58 +00:00
|
|
|
|
|
|
|
setInitialDirection( Settings().InitialDirection() );
|
2014-11-14 19:19:00 +00:00
|
|
|
|
|
|
|
initPlacement( m_splitSeg );
|
2015-02-18 00:29:54 +00:00
|
|
|
return true;
|
2014-11-14 18:15:58 +00:00
|
|
|
}
|
2014-05-14 13:53:54 +00:00
|
|
|
|
2014-11-14 18:15:58 +00:00
|
|
|
void PNS_LINE_PLACER::initPlacement( bool aSplitSeg )
|
|
|
|
{
|
|
|
|
m_idle = false;
|
2014-05-14 13:53:54 +00:00
|
|
|
|
2014-11-14 18:15:58 +00:00
|
|
|
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() );
|
2015-07-07 16:36:43 +00:00
|
|
|
m_head.RemoveVia();
|
|
|
|
m_tail.RemoveVia();
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-11-14 18:15:58 +00:00
|
|
|
m_p_start = m_currentStart;
|
|
|
|
m_direction = m_initial_direction;
|
|
|
|
|
2014-11-14 19:19:00 +00:00
|
|
|
PNS_NODE* world = Router()->GetWorld();
|
|
|
|
|
2014-11-14 18:15:58 +00:00
|
|
|
world->KillChildren();
|
|
|
|
PNS_NODE* rootNode = world->Branch();
|
|
|
|
|
|
|
|
if( aSplitSeg )
|
|
|
|
splitAdjacentSegments( rootNode, m_startItem, m_currentStart );
|
|
|
|
|
2014-07-09 14:25:50 +00:00
|
|
|
setWorld( rootNode );
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-11-14 18:15:58 +00:00
|
|
|
TRACE( 1, "world %p, intitial-direction %s layer %d\n",
|
|
|
|
m_world % m_direction.Format().c_str() % aLayer );
|
2014-11-14 19:19:00 +00:00
|
|
|
|
|
|
|
m_lastNode = NULL;
|
2014-11-14 18:15:58 +00:00
|
|
|
m_currentNode = m_world;
|
|
|
|
m_currentMode = Settings().Mode();
|
|
|
|
|
|
|
|
if( m_shove )
|
|
|
|
delete m_shove;
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-11-14 18:15:58 +00:00
|
|
|
m_shove = NULL;
|
|
|
|
|
|
|
|
if( m_currentMode == RM_Shove || m_currentMode == RM_Smart )
|
|
|
|
{
|
|
|
|
m_shove = new PNS_SHOVE( m_world->Branch(), Router() );
|
2014-11-14 19:19:00 +00:00
|
|
|
}
|
2013-09-26 21:53:54 +00:00
|
|
|
}
|
2014-05-14 13:53:54 +00:00
|
|
|
|
|
|
|
|
2015-02-18 00:29:54 +00:00
|
|
|
bool PNS_LINE_PLACER::Move( const VECTOR2I& aP, PNS_ITEM* aEndItem )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
|
|
|
PNS_LINE current;
|
|
|
|
VECTOR2I p = aP;
|
|
|
|
int eiDepth = -1;
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
if( aEndItem && aEndItem->Owner() )
|
2015-08-03 19:11:51 +00:00
|
|
|
eiDepth = static_cast<PNS_NODE*>( aEndItem->Owner() )->Depth();
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
if( m_lastNode )
|
|
|
|
{
|
|
|
|
delete m_lastNode;
|
|
|
|
m_lastNode = NULL;
|
|
|
|
}
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
route( p );
|
|
|
|
|
|
|
|
current = Trace();
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
if( !current.PointCount() )
|
2014-05-14 13:53:54 +00:00
|
|
|
m_currentEnd = m_p_start;
|
|
|
|
else
|
2014-05-31 14:04:25 +00:00
|
|
|
m_currentEnd = current.CLine().CPoint( -1 );
|
2014-05-14 13:53:54 +00:00
|
|
|
|
2014-05-31 14:04:25 +00:00
|
|
|
PNS_NODE* latestNode = m_currentNode;
|
2014-05-14 13:53:54 +00:00
|
|
|
m_lastNode = latestNode->Branch();
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2015-07-10 21:42:05 +00:00
|
|
|
if( eiDepth >= 0 && aEndItem && latestNode->Depth() > eiDepth && current.SegmentCount() )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2014-05-16 11:37:31 +00:00
|
|
|
splitAdjacentSegments( m_lastNode, aEndItem, current.CPoint( -1 ) );
|
|
|
|
|
|
|
|
if( Settings().RemoveLoops() )
|
2015-08-03 19:11:51 +00:00
|
|
|
removeLoops( m_lastNode, current );
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
updateLeadingRatLine();
|
2015-02-18 00:29:54 +00:00
|
|
|
return true;
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
bool PNS_LINE_PLACER::FixRoute( const VECTOR2I& aP, PNS_ITEM* aEndItem )
|
|
|
|
{
|
|
|
|
bool realEnd = false;
|
|
|
|
int lastV;
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
PNS_LINE pl = Trace();
|
|
|
|
|
2014-07-09 14:25:50 +00:00
|
|
|
if( m_currentMode == RM_MarkObstacles &&
|
2014-05-14 13:53:54 +00:00
|
|
|
!Settings().CanViolateDRC() &&
|
|
|
|
m_world->CheckColliding( &pl ) )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const SHAPE_LINE_CHAIN& l = pl.CLine();
|
|
|
|
|
|
|
|
if( !l.SegmentCount() )
|
2015-07-02 14:09:24 +00:00
|
|
|
{
|
|
|
|
if( pl.EndsWithVia() )
|
|
|
|
{
|
|
|
|
m_lastNode->Add( pl.Via().Clone() );
|
|
|
|
Router()->CommitRouting( m_lastNode );
|
2015-09-14 16:40:29 +00:00
|
|
|
|
|
|
|
m_lastNode = NULL;
|
|
|
|
m_currentNode = NULL;
|
|
|
|
|
2015-07-02 14:09:24 +00:00
|
|
|
m_idle = true;
|
|
|
|
}
|
2015-07-07 16:36:38 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
return true;
|
2015-07-02 14:09:24 +00:00
|
|
|
}
|
2014-05-14 13:53:54 +00:00
|
|
|
|
|
|
|
VECTOR2I p_pre_last = l.CPoint( -1 );
|
|
|
|
const VECTOR2I p_last = l.CPoint( -1 );
|
|
|
|
DIRECTION_45 d_last( l.CSegment( -1 ) );
|
|
|
|
|
|
|
|
if( l.PointCount() > 2 )
|
|
|
|
p_pre_last = l.CPoint( -2 );
|
|
|
|
|
|
|
|
if( aEndItem && m_currentNet >= 0 && m_currentNet == aEndItem->Net() )
|
|
|
|
realEnd = true;
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
if( realEnd || m_placingVia )
|
2014-05-14 13:53:54 +00:00
|
|
|
lastV = l.SegmentCount();
|
|
|
|
else
|
2014-05-16 11:37:31 +00:00
|
|
|
lastV = std::max( 1, l.SegmentCount() - 1 );
|
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
PNS_SEGMENT* lastSeg = NULL;
|
|
|
|
|
|
|
|
for( int i = 0; i < lastV; i++ )
|
|
|
|
{
|
|
|
|
const SEG& s = pl.CSegment( i );
|
|
|
|
PNS_SEGMENT* seg = new PNS_SEGMENT( s, m_currentNet );
|
|
|
|
seg->SetWidth( pl.Width() );
|
|
|
|
seg->SetLayer( m_currentLayer );
|
|
|
|
m_lastNode->Add( seg );
|
|
|
|
lastSeg = seg;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( pl.EndsWithVia() )
|
|
|
|
m_lastNode->Add( pl.Via().Clone() );
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
if( realEnd )
|
2014-07-09 14:25:50 +00:00
|
|
|
simplifyNewLine( m_lastNode, lastSeg );
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-07-09 14:25:50 +00:00
|
|
|
Router()->CommitRouting( m_lastNode );
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
m_lastNode = NULL;
|
2015-09-14 16:40:29 +00:00
|
|
|
m_currentNode = NULL;
|
2014-05-14 13:53:54 +00:00
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
if( !realEnd )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
|
|
|
setInitialDirection( d_last );
|
2014-11-14 18:15:58 +00:00
|
|
|
m_currentStart = m_placingVia ? p_last : p_pre_last;
|
|
|
|
m_startItem = NULL;
|
2014-05-14 13:53:54 +00:00
|
|
|
m_placingVia = false;
|
2014-11-14 18:15:58 +00:00
|
|
|
m_chainedPlacement = !pl.EndsWithVia();
|
2014-11-14 18:17:01 +00:00
|
|
|
m_splitSeg = false;
|
2015-07-07 16:36:38 +00:00
|
|
|
initPlacement();
|
2015-03-10 14:38:27 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-11-14 18:15:58 +00:00
|
|
|
m_idle = true;
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return realEnd;
|
|
|
|
}
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2015-08-03 19:11:51 +00:00
|
|
|
void PNS_LINE_PLACER::removeLoops( PNS_NODE* aNode, PNS_LINE& aLatest )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2015-08-03 19:11:51 +00:00
|
|
|
if( !aLatest.SegmentCount() )
|
2014-05-14 13:53:54 +00:00
|
|
|
return;
|
|
|
|
|
2015-11-03 16:19:42 +00:00
|
|
|
if( aLatest.CLine().CPoint( 0 ) == aLatest.CLine().CPoint( -1 ) )
|
2015-07-02 14:09:24 +00:00
|
|
|
return;
|
|
|
|
|
2015-11-03 16:19:42 +00:00
|
|
|
std::set<PNS_SEGMENT *> toErase;
|
2015-08-03 19:11:51 +00:00
|
|
|
aNode->Add( &aLatest, true );
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2015-08-20 13:11:16 +00:00
|
|
|
for( int s = 0; s < aLatest.LinkCount(); s++ )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2015-08-03 19:11:51 +00:00
|
|
|
PNS_SEGMENT* seg = ( *aLatest.LinkedSegments() )[s];
|
|
|
|
PNS_LINE ourLine = aNode->AssembleLine( seg );
|
2014-05-14 13:53:54 +00:00
|
|
|
PNS_JOINT a, b;
|
2015-08-03 19:11:51 +00:00
|
|
|
std::vector<PNS_LINE> lines;
|
2014-05-14 13:53:54 +00:00
|
|
|
|
|
|
|
aNode->FindLineEnds( ourLine, a, b );
|
|
|
|
|
|
|
|
if( a == b )
|
|
|
|
{
|
2015-03-10 14:38:27 +00:00
|
|
|
aNode->FindLineEnds( aLatest, a, b );
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
aNode->FindLinesBetweenJoints( a, b, lines );
|
|
|
|
|
|
|
|
int removedCount = 0;
|
|
|
|
int total = 0;
|
|
|
|
|
2016-06-29 20:07:55 +00:00
|
|
|
for( PNS_LINE& line : lines )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
|
|
|
total++;
|
|
|
|
|
2015-08-03 19:11:51 +00:00
|
|
|
if( !( line.ContainsSegment( seg ) ) && line.SegmentCount() )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2016-06-29 20:07:55 +00:00
|
|
|
for( PNS_SEGMENT *ss : *line.LinkedSegments() )
|
2015-11-03 16:19:42 +00:00
|
|
|
toErase.insert( ss );
|
|
|
|
|
2015-07-22 08:46:56 +00:00
|
|
|
removedCount++;
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
TRACE( 0, "total segs removed: %d/%d\n", removedCount % total );
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2016-06-29 20:07:55 +00:00
|
|
|
for( PNS_SEGMENT *s : toErase )
|
2015-11-03 16:19:42 +00:00
|
|
|
aNode->Remove( s );
|
|
|
|
|
2015-08-03 19:11:51 +00:00
|
|
|
aNode->Remove( &aLatest );
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
|
|
|
|
void PNS_LINE_PLACER::simplifyNewLine( PNS_NODE* aNode, PNS_SEGMENT* aLatest )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2015-08-03 19:11:51 +00:00
|
|
|
PNS_LINE l = aNode->AssembleLine( aLatest );
|
|
|
|
SHAPE_LINE_CHAIN simplified( l.CLine() );
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
simplified.Simplify();
|
|
|
|
|
2015-08-03 19:11:51 +00:00
|
|
|
if( simplified.PointCount() != l.PointCount() )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2015-08-03 19:11:51 +00:00
|
|
|
PNS_LINE lnew( l );
|
|
|
|
aNode->Remove( &l );
|
|
|
|
lnew.SetShape( simplified );
|
|
|
|
aNode->Add( &lnew );
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2014-11-14 18:15:58 +00:00
|
|
|
void PNS_LINE_PLACER::UpdateSizes( const PNS_SIZES_SETTINGS& aSizes )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2014-11-14 18:15:58 +00:00
|
|
|
m_sizes = aSizes;
|
2015-03-10 14:38:27 +00:00
|
|
|
|
2014-11-14 18:15:58 +00:00
|
|
|
if( !m_idle )
|
|
|
|
{
|
2015-03-10 14:38:27 +00:00
|
|
|
initPlacement( m_splitSeg );
|
2014-11-14 18:15:58 +00:00
|
|
|
}
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
|
|
|
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2014-05-14 13:53:54 +00:00
|
|
|
void PNS_LINE_PLACER::updateLeadingRatLine()
|
|
|
|
{
|
|
|
|
PNS_LINE current = Trace();
|
2015-02-18 00:29:54 +00:00
|
|
|
SHAPE_LINE_CHAIN ratLine;
|
2015-03-10 14:38:27 +00:00
|
|
|
PNS_TOPOLOGY topo( m_lastNode );
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2015-02-18 16:53:46 +00:00
|
|
|
if( topo.LeadingRatLine( ¤t, ratLine ) )
|
2015-02-18 00:29:54 +00:00
|
|
|
Router()->DisplayDebugLine( ratLine, 5, 10000 );
|
|
|
|
}
|
2014-05-16 11:37:31 +00:00
|
|
|
|
2015-02-18 16:53:46 +00:00
|
|
|
|
|
|
|
void PNS_LINE_PLACER::SetOrthoMode( bool aOrthoMode )
|
2015-02-18 00:29:54 +00:00
|
|
|
{
|
|
|
|
m_orthoMode = aOrthoMode;
|
|
|
|
}
|
2014-11-14 19:19:00 +00:00
|
|
|
|
2015-07-02 14:09:24 +00:00
|
|
|
bool PNS_LINE_PLACER::buildInitialLine( const VECTOR2I& aP, PNS_LINE& aHead )
|
2015-02-18 00:29:54 +00:00
|
|
|
{
|
2015-07-02 14:09:24 +00:00
|
|
|
SHAPE_LINE_CHAIN l;
|
|
|
|
|
2015-07-02 14:11:15 +00:00
|
|
|
if( m_p_start == aP )
|
2015-07-02 14:09:24 +00:00
|
|
|
{
|
|
|
|
l.Clear();
|
2015-07-02 14:11:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-07-02 14:09:51 +00:00
|
|
|
if( Settings().GetFreeAngleMode() && Settings().Mode() == RM_MarkObstacles )
|
|
|
|
{
|
2015-07-02 14:11:15 +00:00
|
|
|
l = SHAPE_LINE_CHAIN( m_p_start, aP );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-07-02 14:09:51 +00:00
|
|
|
l = m_direction.BuildInitialTrace( m_p_start, aP );
|
|
|
|
}
|
2015-07-02 14:09:24 +00:00
|
|
|
|
|
|
|
if( l.SegmentCount() > 1 && m_orthoMode )
|
|
|
|
{
|
|
|
|
VECTOR2I newLast = l.CSegment( 0 ).LineProject( l.CPoint( -1 ) );
|
|
|
|
|
|
|
|
l.Remove( -1, -1 );
|
|
|
|
l.Point( 1 ) = newLast;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
aHead.SetShape( l );
|
|
|
|
|
|
|
|
if( !m_placingVia )
|
|
|
|
return true;
|
2014-05-14 13:53:54 +00:00
|
|
|
|
2015-07-02 14:11:15 +00:00
|
|
|
PNS_VIA v( makeVia( aP ) );
|
|
|
|
v.SetNet( aHead.Net() );
|
2015-02-18 16:53:46 +00:00
|
|
|
|
2015-07-02 14:11:15 +00:00
|
|
|
if( m_currentMode == RM_MarkObstacles )
|
2014-05-14 13:53:54 +00:00
|
|
|
{
|
2015-07-02 14:11:15 +00:00
|
|
|
aHead.AppendVia( v );
|
2015-07-02 14:09:24 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
VECTOR2I force;
|
|
|
|
VECTOR2I lead = aP - m_p_start;
|
|
|
|
|
|
|
|
bool solidsOnly = ( m_currentMode != RM_Walkaround );
|
2015-02-18 00:29:54 +00:00
|
|
|
|
2015-07-02 14:09:24 +00:00
|
|
|
if( v.PushoutForce( m_currentNode, lead, force, solidsOnly, 40 ) )
|
|
|
|
{
|
2015-07-02 14:11:15 +00:00
|
|
|
SHAPE_LINE_CHAIN line = m_direction.BuildInitialTrace( m_p_start, aP + force );
|
2015-07-02 14:09:24 +00:00
|
|
|
aHead = PNS_LINE( aHead, line );
|
|
|
|
|
|
|
|
v.SetPos( v.Pos() + force );
|
|
|
|
return true;
|
2014-05-14 13:53:54 +00:00
|
|
|
}
|
2015-02-18 00:29:54 +00:00
|
|
|
|
2015-07-02 14:09:24 +00:00
|
|
|
return false; // via placement unsuccessful
|
2015-02-18 00:29:54 +00:00
|
|
|
}
|
|
|
|
|
2015-02-18 16:53:46 +00:00
|
|
|
|
|
|
|
void PNS_LINE_PLACER::GetModifiedNets( std::vector<int>& aNets ) const
|
2015-02-18 00:29:54 +00:00
|
|
|
{
|
|
|
|
aNets.push_back( m_currentNet );
|
2014-05-16 11:37:31 +00:00
|
|
|
}
|
2015-08-19 16:07:16 +00:00
|
|
|
|
|
|
|
PNS_LOGGER* PNS_LINE_PLACER::Logger()
|
|
|
|
{
|
|
|
|
if( m_shove )
|
|
|
|
return m_shove->Logger();
|
|
|
|
|
|
|
|
return NULL;
|
2015-10-05 16:28:41 +00:00
|
|
|
}
|