KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pns_walkaround.cpp
Go to the documentation of this file.
1/*
2 * KiRouter - a push-and-(sometimes-)shove PCB router
3 *
4 * Copyright (C) 2013-2014 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * Author: Tomasz Wlostowski <[email protected]>
7 *
8 * This program is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <chrono>
23#include <optional>
24
25#include <advanced_config.h>
26#include <core/typeinfo.h>
28
29#include "pns_walkaround.h"
30#include "pns_optimizer.h"
31#include "pns_router.h"
32#include "pns_debug_decorator.h"
33#include "pns_solid.h"
34
35
36namespace PNS {
37
42
43void WALKAROUND::start( const LINE& aInitialPath )
44{
45 m_iteration = 0;
46 for( int pol = 0 ; pol < MaxWalkPolicies; pol++)
47 {
48 m_currentResult.status[ pol ] = ST_IN_PROGRESS;
49 m_currentResult.lines[ pol ] = aInitialPath;
50 m_currentResult.lines[ pol ].ClearLinks();
51 }
52}
53
54
56{
58
60
61 if( ! m_restrictedSet.empty() )
62 {
63 opts.m_filter = [ this ] ( const ITEM* item, const ITEM* aRef ) -> bool
64 {
65 if( m_restrictedSet.find( item ) != m_restrictedSet.end() )
66 return true;
67 return false;
68 };
69 }
70 else
71 {
73 }
74
75 opts.m_useClearanceEpsilon = false;
76
77 return m_world->NearestObstacle( &aPath, opts );
78}
79
80
81void WALKAROUND::RestrictToCluster( bool aEnabled, const TOPOLOGY::CLUSTER& aCluster )
82{
84 m_restrictedSet.clear();
85
86 if( aEnabled )
87 {
88 for( ITEM* item : aCluster.m_items )
89 {
90 m_restrictedSet.insert( item );
91
92 if ( item->HasHole() )
93 m_restrictedSet.insert( item->Hole() );
94 }
95 }
96
97 for( ITEM* item : aCluster.m_items )
98 {
99 if( SOLID* solid = dyn_cast<SOLID*>( item ) )
100 m_restrictedVertices.push_back( solid->Anchor( 0 ) );
101 }
102}
103
104/*
105static wxString policy2string ( WALKAROUND::WALK_POLICY policy )
106{
107 switch(policy)
108 {
109 case WALKAROUND::WP_CCW: return wxT("ccw");
110 case WALKAROUND::WP_CW: return wxT("cw");
111 case WALKAROUND::WP_SHORTEST: return wxT("shortest");
112 }
113 return wxT("?");
114}
115*/
116
118{
119 TOPOLOGY topo( m_world );
120 TOPOLOGY::CLUSTER pendingClusters[MaxWalkPolicies];
121
122 for( int i = 0; i < MaxWalkPolicies; i++ )
123 {
124 if( !m_enabledPolicies[i] )
125 continue;
126
127 auto& line = m_currentResult.lines[ i ];
128 auto& status = m_currentResult.status[ i ];
129
130 PNS_DBG( Dbg(), AddItem, &line, WHITE, 10000, wxString::Format( "current (policy %d, stat %d)", i, status ) );
131
132 if( status != ST_IN_PROGRESS )
133 continue;
134
135 auto obstacle = nearestObstacle( line );
136
137 if( !obstacle )
138 {
139
140 m_currentResult.status[ i ] = ST_DONE;
141 PNS_DBG( Dbg(), Message, wxString::Format( "no-more-colls pol %d st %d", i, status ) );
142
143 continue;
144 }
145
146
147 int clusterMargin = 2 * obstacle->m_clearance + line.Width() ;
148
149 pendingClusters[ i ] = topo.AssembleCluster( obstacle->m_item, line.Layer(), 0.0, line.Net(), clusterMargin );
150 PNS_DBG( Dbg(), AddItem, obstacle->m_item, BLUE, 10000, wxString::Format( "col-item owner-depth %d cl-items=%d cl-margin=%d", static_cast<const NODE*>( obstacle->m_item->Owner() )->Depth(), (int) pendingClusters[i].m_items.size(), clusterMargin ) );
151
152 }
153
155
156 auto processCluster = [ & ] ( TOPOLOGY::CLUSTER& aCluster, LINE& aLine, bool aCw ) -> bool
157 {
158 using namespace std::chrono;
159 auto start_time = steady_clock::now();
160
162
163 PNS_DBG( Dbg(), BeginGroup, wxString::Format( "cluster-details [cw %d]", aCw?1:0 ), 1 );
164
165 for( auto& clItem : aCluster.m_items )
166 {
167 // Check for wallclock timeout
168 // Emprically, 100ms seems to be about where you're not going to find a valid path
169 // if you haven't found it by then. This allows the user to adjust their mouse position
170 // to get a better path without waiting too long.
171 auto now = steady_clock::now();
172 auto elapsed = duration_cast<milliseconds>( now - start_time ).count();
173
174 if( elapsed > timeout_ms )
175 {
176 PNS_DBG( Dbg(), Message, wxString::Format( "processCluster timeout after %d ms", timeout_ms ) );
177 PNS_DBGN( Dbg(), EndGroup );
178 return false;
179 }
180
181 int clearance = m_world->GetClearance( clItem, &aLine, false );
182 const SHAPE_LINE_CHAIN& cachedHull = m_world->GetRuleResolver()->HullCache(
183 clItem, clearance, aLine.Width(), aLine.Layer() );
184
185 SHAPE_LINE_CHAIN hull;
186
187 if( cornerMode == DIRECTION_45::MITERED_90 || cornerMode == DIRECTION_45::ROUNDED_90 )
188 {
189 BOX2I bbox = cachedHull.BBox();
190 hull.Append( bbox.GetLeft(), bbox.GetTop() );
191 hull.Append( bbox.GetRight(), bbox.GetTop() );
192 hull.Append( bbox.GetRight(), bbox.GetBottom() );
193 hull.Append( bbox.GetLeft(), bbox.GetBottom() );
194 }
195 else
196 {
197 hull = cachedHull;
198 }
199
200 LINE tmp( aLine );
201
202 aLine.Line().Simplify2();
203
204 bool stat = aLine.Walkaround( hull, tmp.Line(), aCw );
205
206 PNS_DBG( Dbg(), AddShape, &hull, YELLOW, 10000, wxString::Format( "hull stat %d", stat?1:0 ) );
207 PNS_DBG( Dbg(), AddItem, &tmp, RED, 10000, wxString::Format( "walk stat %d", stat?1:0 ) );
208 PNS_DBG( Dbg(), AddItem, clItem, WHITE, 10000, wxString::Format( "item stat %d", stat?1:0 ) );
209
210 if( !stat )
211 {
212 PNS_DBGN( Dbg(), EndGroup );
213 return false;
214 }
215
216 aLine.SetShape( tmp.CLine() );
217 }
218
219 PNS_DBGN( Dbg(), EndGroup );
220
221 return true;
222 };
223
225 {
226 bool stat = processCluster( pendingClusters[ WP_CW ], m_currentResult.lines[ WP_CW ], true );
227 if( !stat )
228 m_currentResult.status[ WP_CW ] = ST_STUCK;
229 }
230
232 {
233 bool stat = processCluster( pendingClusters[ WP_CCW ], m_currentResult.lines[ WP_CCW ], false );
234 if( !stat )
235 m_currentResult.status[ WP_CCW ] = ST_STUCK;
236 }
237
239 {
241 LINE& line = m_currentResult.lines[WP_SHORTEST];
242 LINE path_cw( line ), path_ccw( line );
243
244 auto st_cw = processCluster( pendingClusters[WP_SHORTEST], path_cw, true );
245 auto st_ccw = processCluster( pendingClusters[WP_SHORTEST], path_ccw, false );
246
248
249 bool cw_coll = st_cw ? m_world->CheckColliding( &path_cw, opts ).has_value() : false;
250 bool ccw_coll = st_ccw ? m_world->CheckColliding( &path_ccw, opts ).has_value() : false;
251
252 double lengthFactorCw = (double) path_cw.CLine().Length() / (double) m_initialLength;
253 double lengthFactorCcw = (double) path_ccw.CLine().Length() / (double) m_initialLength;
254
255 PNS_DBG( Dbg(), AddItem, &path_cw, RED, 10000, wxString::Format( "shortest-cw stat %d lf %.1f", st_cw?1:0, lengthFactorCw ) );
256 PNS_DBG( Dbg(), AddItem, &path_ccw, BLUE, 10000, wxString::Format( "shortest-ccw stat %d lf %.1f", st_ccw?1:0, lengthFactorCcw ) );
257
258
259 std::optional<LINE> shortest;
260 std::optional<LINE> shortest_alt;
261
262
263 if( st_cw && st_ccw )
264 {
265 if( ( !cw_coll && !ccw_coll ) || ( cw_coll && ccw_coll ) )
266 {
267 if( path_cw.CLine().Length() > path_ccw.CLine().Length() )
268 {
269 shortest = path_ccw;
270 shortest_alt = path_cw;
271 }
272 else
273 {
274 shortest = path_cw;
275 shortest_alt = path_ccw;
276 }
277 }
278 else if( !cw_coll )
279 shortest = path_cw;
280 else if( !ccw_coll )
281 shortest = path_ccw;
282
283 }
284 else if( st_ccw )
285 shortest = path_ccw;
286 else if( st_cw )
287 shortest = path_cw;
288
289 bool anyColliding = false;
290
291 if( shortest.has_value() )
292 {
293 PNS_DBG( Dbg(), AddItem, &shortest.value(), RED, 10000, wxString::Format( "shortest-l" ) );
294
295 for( auto& item : m_processedItems )
296 {
297 std::set<PNS::OBSTACLE> obstacles;
298 PNS::COLLISION_SEARCH_CONTEXT ctx( obstacles );
299 if( shortest->Collide( item, m_world, shortest->Layer(), &ctx ) )
300 {
301 anyColliding = true;
302 break;
303 }
304 }
305
306 PNS_DBG( Dbg(), Message, wxString::Format("check-back cc %d items %d coll %d", (int) pendingClusters[ WP_SHORTEST ].m_items.size(), (int) m_processedItems.size(), anyColliding ? 1: 0 ) );
307 }
308
309 if ( anyColliding )
310 {
311 shortest = std::move( shortest_alt );
312 }
313
314 if( !shortest )
315 {
317 }
318 else
319 {
320 m_currentResult.lines[WP_SHORTEST] = *shortest;
321 }
322
323 for( auto item : pendingClusters[ WP_SHORTEST ].m_items )
324 m_processedItems.insert( item );
325 }
326
327 return ST_IN_PROGRESS;
328}
329
330
331const WALKAROUND::RESULT WALKAROUND::Route( const LINE& aInitialPath )
332{
334
335 m_initialLength = aInitialPath.CLine().Length();
336
337 // special case for via-in-the-middle-of-track placement
338
339#if 0
340 if( aInitialPath.PointCount() <= 1 )
341 {
342 if( aInitialPath.EndsWithVia() && m_world->CheckColliding( &aInitialPath.Via(),
343 m_itemMask ) )
344 {
345 // fixme restult
346 }
347 //return RESULT( STUCK, STUCK );
348
349 return RESULT(); //( DONE, DONE, aInitialPath, aInitialPath );
350 }
351#endif
352
353 start( aInitialPath );
354
355 m_processedItems.clear();
356
357 PNS_DBG( Dbg(), AddItem, &aInitialPath, WHITE, 10000, wxT( "initial-path" ) );
358
360 {
361 singleStep();
362
363 bool stillInProgress = false;
364
365 for( int pol = 0; pol < MaxWalkPolicies; pol++ )
366 {
367 if (!m_enabledPolicies[pol])
368 continue;
369
370 auto& st = m_currentResult.status[pol];
371 auto& ln = m_currentResult.lines[pol];
372 double lengthFactor = (double) ln.CLine().Length() / (double) aInitialPath.CLine().Length();
373 // In some situations, there isn't a trivial path (or even a path at all). Hitting the
374 // iteration limit causes lag, so we can exit out early if the walkaround path gets very long
375 // compared with the initial path. If the length exceeds the initial length times this factor,
376 // fail out.
377 if( m_lengthLimitOn )
378 {
379 if( st != ST_DONE && lengthFactor > m_lengthExpansionFactor )
380 st = ST_ALMOST_DONE;
381 }
382
383 PNS_DBG( Dbg(), Message, wxString::Format( "check-wp iter %d st %d i %d lf %.1f", m_iteration, st, pol, lengthFactor ) );
384
385 if ( st == ST_IN_PROGRESS )
386 stillInProgress = true;
387 }
388
389
390 if( !stillInProgress )
391 break;
392
393 m_iteration++;
394 }
395
396
397 for( int pol = 0; pol < MaxWalkPolicies; pol++ )
398 {
399 auto& st = m_currentResult.status[pol];
400 const auto& ln = m_currentResult.lines[pol].CLine();
401
402 m_currentResult.lines[pol].ClearLinks();
403 if( st == ST_IN_PROGRESS )
404 st = ST_ALMOST_DONE;
405
406 if( ln.SegmentCount() < 1 || ln.CPoint( 0 ) != aInitialPath.CPoint( 0 ) )
407 {
408 st = ST_STUCK;
409 }
410
411 if( ln.PointCount() > 0 && ln.CLastPoint() != aInitialPath.CLastPoint() )
412 {
413 st = ST_ALMOST_DONE;
414
415 }
416 PNS_DBG( Dbg(), Message, wxString::Format( "stat=%d", st ) );
417
418 }
419
420
421 return m_currentResult;
422}
423
424void WALKAROUND::SetAllowedPolicies( std::vector<WALK_POLICY> aPolicies)
425{
426 for( int i = 0; i < MaxWalkPolicies; i++ )
427 m_enabledPolicies[i] = false;
428
429 for ( auto p : aPolicies )
430 m_enabledPolicies[p] = true;
431}
432
433}
434
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
CORNER_MODE
Corner modes.
Definition direction45.h:67
@ ROUNDED_90
H/V with filleted corners.
Definition direction45.h:71
@ MITERED_90
H/V only (90-degree corners)
Definition direction45.h:70
ROUTING_SETTINGS & Settings() const
Return the logger object, allowing to dump geometry to a file.
DEBUG_DECORATOR * Dbg() const
Base class for PNS router board items.
Definition pns_item.h:98
virtual HOLE * Hole() const
Definition pns_item.h:304
virtual bool HasHole() const
Definition pns_item.h:303
Represents a track on a PCB, connecting two non-trivial joints (that is, vias, pads,...
Definition pns_line.h:62
const VECTOR2I & CPoint(int aIdx) const
Definition pns_line.h:154
const SHAPE_LINE_CHAIN & CLine() const
Definition pns_line.h:146
const VECTOR2I & CLastPoint() const
Definition pns_line.h:155
SHAPE_LINE_CHAIN & Line()
Definition pns_line.h:145
VIA & Via()
Definition pns_line.h:207
int PointCount() const
Definition pns_line.h:149
bool EndsWithVia() const
Definition pns_line.h:199
Keep the router "world" - i.e.
Definition pns_node.h:243
std::optional< OBSTACLE > OPT_OBSTACLE
Definition pns_node.h:253
int Depth() const
Definition pns_node.h:304
DIRECTION_45::CORNER_MODE GetCornerMode() const
const CLUSTER AssembleCluster(ITEM *aStart, int aLayer, double aAreaExpansionLimit=0.0, NET_HANDLE aExcludedNet=nullptr, int aOverrideClearance=0)
void RestrictToCluster(bool aEnabled, const TOPOLOGY::CLUSTER &aCluster)
STATUS Route(const LINE &aInitialPath, LINE &aWalkPath, bool aOptimize=true)
bool m_enabledPolicies[MaxWalkPolicies]
NODE::OPT_OBSTACLE nearestObstacle(const LINE &aPath)
std::vector< VECTOR2I > m_restrictedVertices
void SetCollisionFilter(COLLISION_FILTER_FUNC aFilter)
void SetAllowedPolicies(std::vector< WALK_POLICY > aPolicies)
COLLISION_FILTER_FUNC m_collisionFilter
void start(const LINE &aInitialPath)
static constexpr int MaxWalkPolicies
std::set< ITEM * > m_processedItems
double m_lengthExpansionFactor
std::set< const ITEM * > m_restrictedSet
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
long long int Length() const
Return length of the line chain in Euclidean metric.
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
@ WHITE
Definition color4d.h:44
@ BLUE
Definition color4d.h:52
@ YELLOW
Definition color4d.h:63
@ RED
Definition color4d.h:55
int m_PNSProcessClusterTimeout
Timeout for the PNS router's processCluster wallclock timeout, in milliseconds.
Push and Shove diff pair dimensions (gap) settings dialog.
std::function< bool(const ITEM *aTestItem, const ITEM *aRefItem)> COLLISION_FILTER_FUNC
Definition pns_node.h:113
#define PNS_DBG(dbg, method,...)
#define PNS_DBGN(dbg, method)
COLLISION_FILTER_FUNC m_filter
Definition pns_node.h:122
std::vector< ITEM * > m_items
int clearance
wxString result
Test unit parsing edge cases and error handling.
Casted dyn_cast(From aObject)
A lightweight dynamic downcast.
Definition typeinfo.h:55