PNS: A new approach to arc placement

Fixes https://gitlab.com/kicad/code/kicad/-/issues/6334
This commit is contained in:
Jon Evans 2020-12-30 00:06:49 -05:00
parent f6a1d703ec
commit 79502a0d88
8 changed files with 155 additions and 94 deletions

View File

@ -194,20 +194,17 @@ public:
}
/**
* Function BuildInitialTrace()
*
* Builds a 2-segment line chain between points aP0 and aP1 and following 45-degree routing
* regime. If aStartDiagonal is true, the trace starts with a diagonal segment.
* @param aP0 starting point
* @param aP1 ending point
* @param aStartDiagonal whether the first segment has to be diagonal
* @param aRadius is the radius of curvature for rounding. If =0, do not insert arcs
* @param aFillet if true will fillet the 45-degree portion of the line chain
* @return the trace
*/
const SHAPE_LINE_CHAIN BuildInitialTrace( const VECTOR2I& aP0,
const VECTOR2I& aP1,
bool aStartDiagonal = false,
int aMaxRadius = 0 ) const;
const SHAPE_LINE_CHAIN BuildInitialTrace( const VECTOR2I& aP0, const VECTOR2I& aP1,
bool aStartDiagonal = false,
bool aFillet = false ) const;
bool operator==( const DIRECTION_45& aOther ) const
{

View File

@ -20,7 +20,7 @@
#include <geometry/direction45.h>
const SHAPE_LINE_CHAIN DIRECTION_45::BuildInitialTrace( const VECTOR2I& aP0, const VECTOR2I& aP1,
bool aStartDiagonal, int aMaxRadius ) const
bool aStartDiagonal, bool aFillet ) const
{
bool start_diagonal;
@ -34,68 +34,133 @@ const SHAPE_LINE_CHAIN DIRECTION_45::BuildInitialTrace( const VECTOR2I& aP0, con
int sw = sign( aP1.x - aP0.x );
int sh = sign( aP1.y - aP0.y );
int radius = std::min( aMaxRadius, std::min( w, h ) );
bool use_rounded = aMaxRadius > 0;
int dist90 = use_rounded ? KiROUND( ( M_SQRT2 - 1.0 ) * radius ) : 0;
int dist45 = use_rounded ? KiROUND( radius * ( 1.0 - M_SQRT1_2 ) ) : 0;
int radius = std::min( w, h );
VECTOR2I mp0, mp1, arc_offset_90, arc_offset_45;
VECTOR2I arcStart, arcEnd;
double diagLength;
int tangentLength;
// we are more horizontal than vertical?
// if( m_90deg )
// {
// if( m_dir == N || m_dir == S )
//
// }
/*
* Non-filleted case:
*
* For width greater than height, we're calculating something like this.
* mp0 will be used if we start straight; mp1 if we start diagonal.
*
* aP0 ----------------- mp0
* . \
* . \
* . \
* mp1 . . . . . . . . aP1
*
* Filleted case:
*
* For a fillet, we need to know the arc start point (A in the diagram below)
* A straight segment will be needed between aP0 and A if we are starting straight,
* or between the arc end and aP1 if we are starting diagonally.
*
* aP0 -- A --___ mp0
* . ---
* . --
* . --
* mp1 . . . . . . . . aP1
*
* For the length of this segment (tangentLength), we subtract the length of the "diagonal"
* line from the "straight" line (i.e. dist(aP0, mp0) - dist(mp0, aP1))
* In the example above, we will have a straight segment from aP0 to A, and then we can use
* the distance from A to aP1 (diagLength) to calculate the radius of the arc.
*/
if( w > h )
{
mp0 = VECTOR2I( ( w - h - dist90 ) * sw, 0 ); // direction: E
mp1 = VECTOR2I( ( h - dist45 ) * sw, ( h - dist45 ) * sh ); // direction: NE
arc_offset_90 = VECTOR2I( 0, radius * sh );
arc_offset_45 = VECTOR2I( sw * radius * M_SQRT1_2, -sh * radius * M_SQRT1_2 );
mp0 = VECTOR2I( ( w - h ) * sw, 0 ); // direction: E
mp1 = VECTOR2I( h * sw, h * sh ); // direction: NE
tangentLength = ( w - h ) - mp1.EuclideanNorm();
}
else
{
mp0 = VECTOR2I( 0, sh * ( h - w - dist90 ) ); // direction: N
mp1 = VECTOR2I( sw * ( w - dist45 ), sh * ( w - dist45 ) ); // direction: NE
arc_offset_90 = VECTOR2I( radius * sw, 0 );
arc_offset_45 = VECTOR2I( -sw * radius * M_SQRT1_2, sh * radius * M_SQRT1_2 );
mp0 = VECTOR2I( 0, sh * ( h - w ) ); // direction: N
mp1 = VECTOR2I( sw * w, sh * w ); // direction: NE
tangentLength = ( h - w ) - mp1.EuclideanNorm();
}
SHAPE_LINE_CHAIN pl;
VECTOR2I arc_center;
pl.Append( aP0 );
VECTOR2I next_point;
if( start_diagonal )
// TODO: if tangentLength zero, we could still place a small arc at the start...
if( aFillet )
{
next_point = aP0 + mp1;
arc_center = aP0 + mp1 + arc_offset_45;
double diag2 = tangentLength >= 0 ? mp1.SquaredEuclideanNorm() : mp0.SquaredEuclideanNorm();
diagLength = std::sqrt( ( 2 * diag2 ) - ( 2 * diag2 * std::cos( 3 * M_PI_4 ) ) );
int arcRadius = KiROUND( diagLength / ( 2.0 * std::cos( 67.5 * M_PI / 180.0 ) ) );
if( start_diagonal )
{
if( tangentLength >= 0 )
{
// Positive tangentLength, diagonal start: arc goes at the start
int rotationSign = ( w > h ) ? ( sw * sh * -1 ) : ( sw * sh );
arcStart = aP0 + mp1 + mp0.Resize( mp1.EuclideanNorm() );
VECTOR2D centerDir( mp0.Rotate( M_PI_2 * rotationSign ) );
VECTOR2D arcCenter( arcStart + centerDir.Resize( arcRadius ) );
SHAPE_ARC new_arc( arcCenter, aP0, 45 * rotationSign );
pl.Append( new_arc );
pl.Append( aP1 );
}
else
{
pl.Append( aP0 );
// Negative tangentLength, diagonal start: arc goes at the end
int rotationSign = ( w > h ) ? ( sw * sh * -1 ) : ( sw * sh );
arcStart = aP0 + mp1.Resize( std::abs( tangentLength ) );
VECTOR2D centerDir( mp0.Rotate( M_PI_2 * rotationSign ) );
VECTOR2D arcCenter( aP1 + centerDir.Resize( arcRadius ) );
SHAPE_ARC new_arc( arcCenter, arcStart, 45 * rotationSign );
pl.Append( new_arc );
// TODO: nicer way of fixing these up
if( new_arc.GetP1() != aP1 )
pl.Replace( -1, -1, aP1 );
}
}
else
{
if( tangentLength >= 0 )
{
pl.Append( aP0 );
// Positive tangentLength: arc goes at the end
int rotationSign = ( w > h ) ? ( sw * sh ) : ( sw * sh * -1 );
arcStart = aP0 + mp0.Resize( tangentLength );
VECTOR2D centerDir( mp0.Rotate( M_PI_2 * rotationSign ) );
VECTOR2D arcCenter( arcStart + centerDir.Resize( arcRadius ) );
SHAPE_ARC new_arc( arcCenter, arcStart, 45 * rotationSign );
pl.Append( new_arc );
// TODO: nicer way of fixing these up
if( new_arc.GetP1() != aP1 )
pl.Replace( -1, -1, aP1 );
}
else
{
// Negative tangentLength: arc goes at the start
int rotationSign = ( w > h ) ? ( sw * sh ) : ( sw * sh * -1 );
arcStart = aP0;
VECTOR2D centerDir( mp0.Rotate( M_PI_2 * rotationSign ) );
VECTOR2D arcCenter( arcStart + centerDir.Resize( arcRadius ) );
SHAPE_ARC new_arc( arcCenter, arcStart, 45 * rotationSign );
pl.Append( new_arc );
pl.Append( aP1 );
}
}
}
else
{
next_point = aP0 + mp0;
arc_center = aP0 + mp0 + arc_offset_90;
pl.Append( aP0 );
pl.Append( start_diagonal ? ( aP0 + mp1 ) : ( aP0 + mp0 ) );
pl.Append( aP1 );
}
if( use_rounded )
{
int sa = start_diagonal ? -sw * sh : sw * sh;
if( h > w )
sa = -sa;
SHAPE_ARC new_arc( arc_center, next_point, sa * 45.0 );
pl.Append( new_arc );
}
else
{
pl.Append( next_point );
}
pl.Append( aP1 );
// TODO: be careful about including P0 and P1 above, because SHAPE_LINE_CHAIN::Simplify
// does not seem to properly handle when an arc overlaps P0.
pl.Simplify();
return pl;

View File

@ -448,7 +448,7 @@ void SHAPE_LINE_CHAIN::Append( const SHAPE_LINE_CHAIN& aOtherLine )
{
const VECTOR2I p = aOtherLine.CPoint( 0 );
m_points.push_back( p );
m_shapes.push_back( ssize_t( SHAPE_IS_PT ) );
m_shapes.push_back( aOtherLine.CShapes()[0] );
m_bbox.Merge( p );
}

View File

@ -1196,7 +1196,7 @@ bool LINE_PLACER::FixRoute( const VECTOR2I& aP, ITEM* aEndItem, bool aForceFinis
{
ssize_t arcIndex = l.ArcIndex( i );
if( arcIndex < 0 )
if( arcIndex < 0 || ( lastArc >= 0 && i == lastV - 1 ) )
{
seg = SEGMENT( pl.CSegment( i ), m_currentNet );
seg.SetWidth( pl.Width() );
@ -1427,10 +1427,11 @@ void LINE_PLACER::SetOrthoMode( bool aOrthoMode )
bool LINE_PLACER::buildInitialLine( const VECTOR2I& aP, LINE& aHead )
{
SHAPE_LINE_CHAIN l;
int initial_radius = 0;
DIRECTION_45 guessedDir = m_postureSolver.GetPosture( aP );
// Rounded corners don't make sense when routing orthogonally (single track at a time)
bool fillet = !m_orthoMode && Settings().GetCornerMode() == CORNER_MODE::ROUNDED_45;
if( m_p_start == aP )
{
l.Clear();
@ -1443,15 +1444,10 @@ bool LINE_PLACER::buildInitialLine( const VECTOR2I& aP, LINE& aHead )
}
else
{
// Rounded corners don't make sense when routing orthogonally (single track at a time)
if( Settings().GetRounded() && !m_orthoMode )
initial_radius = Settings().GetMaxRadius();
if( !m_tail.PointCount() )
l = guessedDir.BuildInitialTrace( m_p_start, aP, false, initial_radius );
l = guessedDir.BuildInitialTrace( m_p_start, aP, false, fillet );
else
l = m_direction.BuildInitialTrace( m_p_start, aP, false, initial_radius );
l = m_direction.BuildInitialTrace( m_p_start, aP, false, fillet );
}
if( l.SegmentCount() > 1 && m_orthoMode )
@ -1486,7 +1482,7 @@ bool LINE_PLACER::buildInitialLine( const VECTOR2I& aP, LINE& aHead )
if( v.PushoutForce( m_currentNode, lead, force, solidsOnly, 40 ) )
{
SHAPE_LINE_CHAIN line = guessedDir.BuildInitialTrace( m_p_start, aP + force, false,
initial_radius );
fillet );
aHead = LINE( aHead, line );
v.SetPos( v.Pos() + force );

View File

@ -1071,6 +1071,10 @@ bool OPTIMIZER::Optimize( LINE* aLine, int aEffortLevel, NODE* aWorld, const VEC
if( aEffortLevel & OPTIMIZER::PRESERVE_VERTEX )
opt.SetPreserveVertex( aV );
// TODO: fix optimizer for arcs
if( aLine->ArcCount() )
return -1;
return opt.Optimize( aLine );
}

View File

@ -537,7 +537,19 @@ bool ROUTER::IsPlacingVia() const
void ROUTER::ToggleRounded()
{
m_settings->SetRounded( !m_settings->GetRounded() );
CORNER_MODE newMode = CORNER_MODE::MITERED_45;
switch( m_settings->GetCornerMode() )
{
case CORNER_MODE::MITERED_45:
newMode = CORNER_MODE::ROUNDED_45;
break;
default:
break;
}
m_settings->SetCornerMode( newMode );
}

View File

@ -51,9 +51,7 @@ ROUTING_SETTINGS::ROUTING_SETTINGS( JSON_SETTINGS* aParent, const std::string& a
m_snapToTracks = false;
m_snapToPads = false;
m_optimizeDraggedTrack = true;
m_minRadius = 0;
m_maxRadius = 1000000;
m_roundedCorners = false;
m_cornerMode = CORNER_MODE::MITERED_45;
m_autoPosture = true;
m_fixAllSegments = true;
@ -94,12 +92,13 @@ ROUTING_SETTINGS::ROUTING_SETTINGS( JSON_SETTINGS* aParent, const std::string& a
m_params.emplace_back( new PARAM<bool>( "snap_to_pads", &m_snapToPads, false ) );
m_params.emplace_back( new PARAM<bool>( "optimize_dragged_track", &m_optimizeDraggedTrack, true ) );
m_params.emplace_back( new PARAM<int>( "min_radius", &m_minRadius, 0 ) );
m_params.emplace_back( new PARAM<int>( "max_radius", &m_maxRadius, 1000000 ) );
m_params.emplace_back( new PARAM<bool>( "use_rounded", &m_roundedCorners, false ) );
m_params.emplace_back( new PARAM<bool>( "auto_posture", &m_autoPosture, true ) );
m_params.emplace_back( new PARAM<bool>( "fix_all_segments", &m_fixAllSegments, true ) );
m_params.emplace_back( new PARAM_ENUM<CORNER_MODE>( "corner_mode", &m_cornerMode,
CORNER_MODE::MITERED_45, CORNER_MODE::ROUNDED_90,
CORNER_MODE::MITERED_45 ) );
LoadFromFile();
}

View File

@ -50,6 +50,15 @@ enum PNS_OPTIMIZATION_EFFORT
OE_FULL = 2
};
///> What kind of corners to create in the line placers
enum class CORNER_MODE
{
MITERED_90, ///< H/V only (90-degree corners) (not yet implemented)
MITERED_45, ///< H/V/45 with mitered corners (default)
ROUNDED_90, ///< H/V/45 with filleted corners
ROUNDED_45 ///< H/V with filleted corners (not yet implemented)
};
/**
* ROUTING_SETTINGS
*
@ -141,8 +150,8 @@ public:
bool GetSnapToTracks() const { return m_snapToTracks; }
bool GetSnapToPads() const { return m_snapToPads; }
bool GetRounded() const { return m_roundedCorners; }
void SetRounded( bool aRound ) { m_roundedCorners = aRound; }
CORNER_MODE GetCornerMode() const { return m_cornerMode; }
void SetCornerMode( CORNER_MODE aMode ) { m_cornerMode = aMode; }
bool GetOptimizeDraggedTrack() const { return m_optimizeDraggedTrack; }
void SetOptimizeDraggedTrack( bool aEnable ) { m_optimizeDraggedTrack = aEnable; }
@ -153,25 +162,6 @@ public:
bool GetFixAllSegments() const { return m_fixAllSegments; }
void SetFixAllSegments( bool aEnable ) { m_fixAllSegments = aEnable; }
void SetMinRadius( int aRadius )
{
m_minRadius = aRadius;
if( m_maxRadius < m_minRadius )
m_maxRadius = m_minRadius;
}
void SetMaxRadius( int aRadius )
{
m_maxRadius = aRadius;
if( m_maxRadius < m_minRadius )
m_minRadius = m_maxRadius;
}
int GetMinRadius() const { return m_minRadius; }
int GetMaxRadius() const { return m_maxRadius; }
private:
bool m_shoveVias;
bool m_startDiagonal;
@ -186,13 +176,11 @@ private:
bool m_inlineDragEnabled;
bool m_snapToTracks;
bool m_snapToPads;
bool m_roundedCorners;
bool m_optimizeDraggedTrack;
bool m_autoPosture;
bool m_fixAllSegments;
int m_minRadius;
int m_maxRadius;
CORNER_MODE m_cornerMode;
PNS_MODE m_routingMode;
PNS_OPTIMIZATION_EFFORT m_optimizerEffort;