PNS: Add support for 90-degree corner modes

Fixes https://gitlab.com/kicad/code/kicad/-/issues/6902
This commit is contained in:
Johannes Pfister 2021-11-03 02:14:23 +00:00 committed by Jon Evans
parent e4b61de792
commit 64f77b3596
11 changed files with 218 additions and 107 deletions

View File

@ -59,6 +59,18 @@ public:
UNDEFINED = -1
};
/**
* Corner modes.
* A corner can either be 45° or 90° and can be fillet/rounded or mitered
*/
enum CORNER_MODE
{
MITERED_45 = 0, ///< H/V/45 with mitered corners (default)
ROUNDED_45 = 1, ///< H/V/45 with filleted corners
MITERED_90 = 2, ///< H/V only (90-degree corners)
ROUNDED_90 = 3, ///< H/V with filleted corners
};
/**
* Represent kind of angle formed by vectors heading in two DIRECTION_45s.
*/
@ -215,12 +227,13 @@ public:
* @param aP0 starting point
* @param aP1 ending point
* @param aStartDiagonal whether the first segment has to be diagonal
* @param aFillet if true will fillet the 45-degree portion of the line chain
* @param aMode How the corner is made. If it is a 90° corner, aStartDiagonal means
* start with the shorter direction first / use arc before the straight segment.
* @return the trace
*/
const SHAPE_LINE_CHAIN BuildInitialTrace( const VECTOR2I& aP0, const VECTOR2I& aP1,
bool aStartDiagonal = false,
bool aFillet = false ) const;
bool aStartDiagonal = false,
CORNER_MODE aMode = CORNER_MODE::MITERED_45 ) const;
bool operator==( const DIRECTION_45& aOther ) const
{
@ -295,7 +308,6 @@ public:
}
private:
/**
* Calculate the direction from a vector. If the vector's angle is not a multiple of 45
* degrees, the direction is rounded to the nearest octant.

View File

@ -21,7 +21,9 @@
const SHAPE_LINE_CHAIN DIRECTION_45::BuildInitialTrace( const VECTOR2I& aP0, const VECTOR2I& aP1,
bool aStartDiagonal, bool aFillet ) const
bool aStartDiagonal,
CORNER_MODE aMode ) const
{
bool startDiagonal;
@ -35,70 +37,94 @@ 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 );
VECTOR2I mp0, mp1;
/*
* 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.
*/
int tangentLength;
if( w > h )
{
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 ) ); // direction: N
mp1 = VECTOR2I( sw * w, sh * w ); // direction: NE
tangentLength = ( h - w ) - mp1.EuclideanNorm();
}
bool is90mode = aMode == CORNER_MODE::ROUNDED_90 || aMode == CORNER_MODE::MITERED_90;
SHAPE_LINE_CHAIN pl;
// Shortcut where we can generate just one segment and quit. Avoids more complicated handling
// of precision errors if filleting is enabled
// TODO: needs refactoring if we support 90-degree arcs via this function
if( w == h || w == 0 || h == 0 )
if( w == 0 || h == 0 || ( !is90mode && h == w ) )
{
pl.Append( aP0 );
pl.Append( aP1 );
return pl;
}
// TODO: if tangentLength zero, we could still place a small arc at the start...
if( aFillet )
VECTOR2I mp0, mp1;
int tangentLength;
if( is90mode )
{
SHAPE_ARC arc;
VECTOR2I arcEndpoint;
if( startDiagonal == ( h >= w ) )
{
mp0 = VECTOR2I( w * sw, 0 ); // direction: E
}
else
{
mp0 = VECTOR2I( 0, sh * h ); // direction: N
}
}
else
{
if( w > h )
{
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 ) ); // direction: N
mp1 = VECTOR2I( sw * w, sh * w ); // direction: NE
tangentLength = ( h - w ) - mp1.EuclideanNorm();
}
}
SHAPE_ARC arc;
VECTOR2I arcEndpoint;
switch( aMode )
{
case CORNER_MODE::MITERED_45:
/*
* 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
*
*/
pl.Append( aP0 );
pl.Append( startDiagonal ? ( aP0 + mp1 ) : ( aP0 + mp0 ) );
pl.Append( aP1 );
break;
case CORNER_MODE::ROUNDED_45:
{
/*
* 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 )
{
pl.Append( aP0 );
pl.Append( aP1 );
break;
}
double diag2 = tangentLength >= 0 ? mp1.SquaredEuclideanNorm() : mp0.SquaredEuclideanNorm();
double diagLength = std::sqrt( ( 2 * diag2 ) - ( 2 * diag2 * std::cos( 3 * M_PI_4 ) ) );
@ -188,12 +214,94 @@ const SHAPE_LINE_CHAIN DIRECTION_45::BuildInitialTrace( const VECTOR2I& aP0, con
pl.Append( aP1 );
}
}
break;
}
else
{
case CORNER_MODE::MITERED_90:
/*
* For width greater than height, we're calculating something like this.
*
* <-mp0->
* aP0 -------------------+
* |
* |
* |
* aP1
*/
pl.Append( aP0 );
pl.Append( startDiagonal ? ( aP0 + mp1 ) : ( aP0 + mp0 ) );
pl.Append( aP0 + mp0 );
pl.Append( aP1 );
break;
case CORNER_MODE::ROUNDED_90:
/*
* For a fillet, we need to know the arc end point
* A straight segment will be needed between aP0 and arcEnd in case distance aP0,mp0 is bigger
* than the distance mp0,aP1, if the distance is shorter the straigth segment is between
* arcEnd and aP1. If both distances are equal, we don't need a straight segment.
*
* aP0 ----- arcEnd ---__
* --
* \
* |
* arcCenter aP1
*
* For the length of the radius we use the shorter of the horizontal and vertical distance.
*/
SHAPE_ARC arc;
if( w == h ) // we only need one arc without a straigth line.
{
arc.ConstructFromStartEndCenter( aP0, aP1, aP1 - mp0, sh == sw != startDiagonal );
pl.Append( arc );
return pl;
}
VECTOR2I arcEnd; // Arc position that is not at aP0 nor aP1
VECTOR2I arcCenter;
if( startDiagonal ) //Means start with the arc first
{
if( h > w ) // Arc followed by a vertical line
{
int y = aP0.y + ( w * sh );
arcEnd = VECTOR2I( aP1.x, y );
arcCenter = VECTOR2I( aP0.x, y );
arc.ConstructFromStartEndCenter( aP0, arcEnd, arcCenter, sh != sw );
pl.Append( arc );
pl.Append( aP1 );
}
else // Arc followed by a horizontal line
{
int x = aP0.x + ( h * sw );
arcEnd = VECTOR2I( x, aP1.y );
arcCenter = VECTOR2I( x, aP0.y );
arc.ConstructFromStartEndCenter( aP0, arcEnd, arcCenter, sh == sw );
pl.Append( arc );
pl.Append( aP1 );
}
}
else
{
if( w > h ) // Horizontal line followed by the arc
{
int x = aP1.x - ( h * sw );
arcEnd = VECTOR2I( x, aP0.y );
arcCenter = VECTOR2I( x, aP1.y );
pl.Append( aP0 );
arc.ConstructFromStartEndCenter( arcEnd, aP1, arcCenter, sh != sw );
pl.Append( arc );
}
else // Vertical line followed by the arc
{
int y = aP1.y - ( w * sh );
arcEnd = VECTOR2I( aP0.x, y );
arcCenter = VECTOR2I( aP1.x, y );
pl.Append( aP0 );
arc.ConstructFromStartEndCenter( arcEnd, aP1, arcCenter, sh == sw );
pl.Append( arc );
}
}
break;
}
pl.Simplify();

View File

@ -1656,8 +1656,10 @@ bool LINE_PLACER::buildInitialLine( const VECTOR2I& aP, LINE& aHead, bool aForce
wxLogTrace( "PNS", "buildInitialLine: m_direction %s, guessedDir %s, tail points %d",
m_direction.Format(), guessedDir.Format(), m_tail.PointCount() );
DIRECTION_45::CORNER_MODE cornerMode = Settings().GetCornerMode();
// 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_orthoMode )
cornerMode = DIRECTION_45::CORNER_MODE::MITERED_45;
if( m_p_start == aP )
{
@ -1672,9 +1674,9 @@ bool LINE_PLACER::buildInitialLine( const VECTOR2I& aP, LINE& aHead, bool aForce
else
{
if( !m_tail.PointCount() )
l = guessedDir.BuildInitialTrace( m_p_start, aP, false, fillet );
l = guessedDir.BuildInitialTrace( m_p_start, aP, false, cornerMode );
else
l = m_direction.BuildInitialTrace( m_p_start, aP, false, fillet );
l = m_direction.BuildInitialTrace( m_p_start, aP, false, cornerMode );
}
if( l.SegmentCount() > 1 && m_orthoMode )
@ -1708,8 +1710,8 @@ bool LINE_PLACER::buildInitialLine( const VECTOR2I& aP, LINE& aHead, bool aForce
if( v.PushoutForce( m_currentNode, lead, force, solidsOnly, 40 ) )
{
SHAPE_LINE_CHAIN line = guessedDir.BuildInitialTrace( m_p_start, aP + force, false,
fillet );
SHAPE_LINE_CHAIN line =
guessedDir.BuildInitialTrace( m_p_start, aP + force, false, cornerMode );
aHead = LINE( aHead, line );
v.SetPos( v.Pos() + force );

View File

@ -111,7 +111,7 @@ DIRECTION_45 MOUSE_TRAIL_TRACER::GetPosture( const VECTOR2I& aP )
DEBUG_DECORATOR* dbg = ROUTER::GetInstance()->GetInterface()->GetDebugDecorator();
VECTOR2I p0 = m_trail.CPoint( 0 );
double refLength = SEG( p0, aP ).Length();
SHAPE_LINE_CHAIN straight( DIRECTION_45().BuildInitialTrace( p0, aP, false, false ) );
SHAPE_LINE_CHAIN straight( DIRECTION_45().BuildInitialTrace( p0, aP, false ) );
straight.SetClosed( true );
straight.Append( m_trail.Reverse() );
@ -121,7 +121,7 @@ DIRECTION_45 MOUSE_TRAIL_TRACER::GetPosture( const VECTOR2I& aP )
double areaS = straight.Area();
SHAPE_LINE_CHAIN diag( DIRECTION_45().BuildInitialTrace( p0, aP, true, false ) );
SHAPE_LINE_CHAIN diag( DIRECTION_45().BuildInitialTrace( p0, aP, true ) );
diag.Append( m_trail.Reverse() );
diag.SetClosed( true );
diag.Simplify();

View File

@ -586,10 +586,8 @@ bool OPTIMIZER::mergeColinear( LINE* aLine )
if( s1.SquaredLength() == 0 || s2.SquaredLength() == 0 )
continue;
if( s1.Collinear( s2 ) )
if( s1.Collinear( s2 ) && !line.IsPtOnArc( segIdx + 1 ) )
{
// We should not see a collinear vertex inside an arc
wxASSERT( !line.IsPtOnArc( segIdx + 1 ) );
line.Remove( segIdx + 1 );
}
}

View File

@ -813,21 +813,19 @@ bool ROUTER::IsPlacingVia() const
}
void ROUTER::ToggleRounded()
void ROUTER::ToggleCornerMode()
{
CORNER_MODE newMode = CORNER_MODE::MITERED_45;
DIRECTION_45::CORNER_MODE mode = m_settings->GetCornerMode();
switch( m_settings->GetCornerMode() )
{
case CORNER_MODE::MITERED_45:
newMode = CORNER_MODE::ROUNDED_45;
break;
default:
break;
case DIRECTION_45::CORNER_MODE::MITERED_45: mode = DIRECTION_45::CORNER_MODE::ROUNDED_45; break;
case DIRECTION_45::CORNER_MODE::ROUNDED_45: mode = DIRECTION_45::CORNER_MODE::MITERED_90; break;
case DIRECTION_45::CORNER_MODE::MITERED_90: mode = DIRECTION_45::CORNER_MODE::ROUNDED_90; break;
case DIRECTION_45::CORNER_MODE::ROUNDED_90: mode = DIRECTION_45::CORNER_MODE::MITERED_45; break;
}
m_settings->SetCornerMode( newMode );
m_settings->SetCornerMode( mode );
}

View File

@ -160,7 +160,7 @@ public:
void ToggleViaPlacement();
void SetOrthoMode( bool aEnable );
void ToggleRounded();
void ToggleCornerMode();
int GetCurrentLayer() const;
const std::vector<int> GetCurrentNets() const;

View File

@ -50,7 +50,7 @@ ROUTING_SETTINGS::ROUTING_SETTINGS( JSON_SETTINGS* aParent, const std::string& a
m_snapToTracks = false;
m_snapToPads = false;
m_optimizeEntireDraggedTrack = false;
m_cornerMode = CORNER_MODE::MITERED_45;
m_cornerMode = DIRECTION_45::CORNER_MODE::MITERED_45;
m_walkaroundHugLengthThreshold = 1.5;
m_autoPosture = true;
m_fixAllSegments = true;
@ -95,9 +95,9 @@ ROUTING_SETTINGS::ROUTING_SETTINGS( JSON_SETTINGS* aParent, const std::string& a
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 ) );
m_params.emplace_back( new PARAM_ENUM<DIRECTION_45::CORNER_MODE>(
"corner_mode", &m_cornerMode, DIRECTION_45::CORNER_MODE::MITERED_45,
DIRECTION_45::CORNER_MODE::ROUNDED_90, DIRECTION_45::CORNER_MODE::MITERED_45 ) );
m_params.emplace_back( new PARAM<double>( "walkaround_hug_length_threshold", &m_walkaroundHugLengthThreshold, 1.5 ) );

View File

@ -26,6 +26,7 @@
#include <cstdio>
#include <settings/nested_settings.h>
#include <geometry/direction45.h>
#include "time_limit.h"
@ -51,15 +52,6 @@ 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 with filleted corners (not yet implemented)
ROUNDED_45 ///< H/V/45 with filleted corners
};
/**
* Contain all persistent settings of the router, such as the mode, optimization effort, etc.
*/
@ -149,8 +141,8 @@ public:
bool GetSnapToTracks() const { return m_snapToTracks; }
bool GetSnapToPads() const { return m_snapToPads; }
CORNER_MODE GetCornerMode() const { return m_cornerMode; }
void SetCornerMode( CORNER_MODE aMode ) { m_cornerMode = aMode; }
DIRECTION_45::CORNER_MODE GetCornerMode() const { return m_cornerMode; }
void SetCornerMode( DIRECTION_45::CORNER_MODE aMode ) { m_cornerMode = aMode; }
bool GetOptimizeEntireDraggedTrack() const { return m_optimizeEntireDraggedTrack; }
void SetOptimizeEntireDraggedTrack( bool aEnable ) { m_optimizeEntireDraggedTrack = aEnable; }
@ -180,7 +172,7 @@ private:
bool m_autoPosture;
bool m_fixAllSegments;
CORNER_MODE m_cornerMode;
DIRECTION_45::CORNER_MODE m_cornerMode;
PNS_MODE m_routingMode;
PNS_OPTIMIZATION_EFFORT m_optimizerEffort;

View File

@ -157,11 +157,11 @@ static const TOOL_ACTION ACT_SwitchPosture( "pcbnew.InteractiveRouter.SwitchPost
_( "Switches posture of the currently routed track." ),
BITMAPS::change_entry_orient );
static const TOOL_ACTION ACT_SwitchRounding( "pcbnew.InteractiveRouter.SwitchRounding",
static const TOOL_ACTION ACT_SwitchCornerMode( "pcbnew.InteractiveRouter.SwitchRounding",
AS_CONTEXT,
MD_CTRL + '/', "",
_( "Track Corner Mode" ),
_( "Switches between sharp and rounded corners when routing tracks." ),
_( "Switches between sharp/rounded and 45°/90° corners when routing tracks." ),
BITMAPS::switch_corner_rounding_shape );
#undef _
@ -469,7 +469,7 @@ bool ROUTER_TOOL::Init()
menu.AddItem( ACT_SelLayerAndPlaceBlindVia, SELECTION_CONDITIONS::ShowAlways );
menu.AddItem( ACT_SelLayerAndPlaceMicroVia, SELECTION_CONDITIONS::ShowAlways );
menu.AddItem( ACT_SwitchPosture, SELECTION_CONDITIONS::ShowAlways );
menu.AddItem( ACT_SwitchRounding, SELECTION_CONDITIONS::ShowAlways );
menu.AddItem( ACT_SwitchCornerMode, SELECTION_CONDITIONS::ShowAlways );
menu.AddSeparator();
@ -1202,9 +1202,9 @@ void ROUTER_TOOL::performRouting()
m_router->Move( m_endSnapPoint, m_endItem );
m_startItem = nullptr;
}
else if( evt->IsAction( &ACT_SwitchRounding ) )
else if( evt->IsAction( &ACT_SwitchCornerMode ) )
{
m_router->ToggleRounded();
m_router->ToggleCornerMode();
updateMessagePanel();
updateEndItem( *evt );
m_router->Move( m_endSnapPoint, m_endItem ); // refresh
@ -2063,10 +2063,10 @@ void ROUTER_TOOL::updateMessagePanel()
{
switch( m_router->Settings().GetCornerMode() )
{
case PNS::CORNER_MODE::MITERED_45: cornerMode = _( "45-degree" ); break;
case PNS::CORNER_MODE::ROUNDED_45: cornerMode = _( "45-degree rounded" ); break;
case PNS::CORNER_MODE::MITERED_90: cornerMode = _( "90-degree" ); break;
case PNS::CORNER_MODE::ROUNDED_90: cornerMode = _( "90-degree rounded" ); break;
case DIRECTION_45::CORNER_MODE::MITERED_45: cornerMode = _( "45-degree" ); break;
case DIRECTION_45::CORNER_MODE::ROUNDED_45: cornerMode = _( "45-degree rounded" ); break;
case DIRECTION_45::CORNER_MODE::MITERED_90: cornerMode = _( "90-degree" ); break;
case DIRECTION_45::CORNER_MODE::ROUNDED_90: cornerMode = _( "90-degree rounded" ); break;
default: break;
}
}

View File

@ -79,7 +79,8 @@ bool PNS_LOG_FILE::Load( const std::string& logName, const std::string boardName
m_routerSettings->SetMode( (PNS::PNS_MODE) wxAtoi( tokens.GetNextToken() ) );
m_routerSettings->SetRemoveLoops( wxAtoi( tokens.GetNextToken() ) );
m_routerSettings->SetFixAllSegments( wxAtoi( tokens.GetNextToken() ) );
m_routerSettings->SetCornerMode( (PNS::CORNER_MODE) wxAtoi( tokens.GetNextToken() ) );
m_routerSettings->SetCornerMode(
(DIRECTION_45::CORNER_MODE) wxAtoi( tokens.GetNextToken() ) );
}
}