KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pns_multi_dragger.cpp
Go to the documentation of this file.
1/*
2 * KiRouter - a push-and-(sometimes-)shove PCB router
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 * Author: Tomasz Wlostowski <[email protected]>
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include "pns_multi_dragger.h"
22
23#include <core/typeinfo.h>
24
25#include "pns_router.h"
26#include "pns_debug_decorator.h"
27#include "pns_walkaround.h"
28#include "pns_shove.h"
29
30namespace PNS
31{
32
34{
35 m_world = nullptr;
36 m_lastNode = nullptr;
37}
38
39
43
44// here we initialize everything that's needed for multidrag. this means:
45bool MULTI_DRAGGER::Start( const VECTOR2I& aP, ITEM_SET& aPrimitives )
46{
47 m_lastNode = nullptr;
48 m_dragStatus = false;
50
51 // check if the initial ("leader") primitive set is empty...
52 if( aPrimitives.Empty() )
53 return false;
54
55 m_mdragLines.clear();
56
57 // find all LINEs to be dragged. Indicate the LINE that contains the point (aP)
58 // as the "primary line", the multidrag algo will place all other lines in such way
59 // that the cursor position lies on the primary line.
60 for( ITEM* pitem : aPrimitives.Items() )
61 {
62 LINKED_ITEM* litem = static_cast<LINKED_ITEM*>( pitem );
63 bool redundant = false;
64 for( auto& l : m_mdragLines )
65 {
66 if( l.originalLine.ContainsLink( litem ) )
67 {
68 l.originalLeaders.push_back( litem );
69 redundant = true;
70 break;
71 }
72 }
73
74 // we can possibly have multiple SEGMENTs in aPrimitives that belong to the same line.
75 // We reject these.
76 if( !redundant )
77 {
78 MDRAG_LINE l;
79 l.originalLine = m_world->AssembleLine( litem );
80 l.originalLeaders.push_back( litem );
81 l.isDraggable = true;
82 l.mdragIndex = static_cast<int>( m_mdragLines.size() );
83 m_mdragLines.push_back( std::move( l ) );
84 }
85 }
86
87 bool anyStrictCornersFound = false;
88 bool anyStrictMidSegsFound = false;
89
90 for( auto& l : m_mdragLines )
91 {
92 const int thr = l.originalLine.Width() / 2;
93
94 const VECTOR2I& origFirst = l.originalLine.CLine().CPoint( 0 );
95 const int distFirst = ( origFirst - aP ).EuclideanNorm();
96
97 const VECTOR2I& origLast = l.originalLine.CLine().CLastPoint();
98 const int distLast = ( origLast - aP ).EuclideanNorm();
99
100 l.cornerDistance = std::min( distFirst, distLast );
101
102 bool takeFirst = false;
103 auto ilast = aPrimitives.FindVertex( origLast );
104 auto ifirst = aPrimitives.FindVertex( origFirst );
105
106 if( ilast && ifirst )
107 takeFirst = distFirst < distLast;
108 else if( ilast )
109 takeFirst = false;
110 else if( ifirst )
111 takeFirst = true;
112
113 if( ifirst || ilast )
114 {
115 if( takeFirst )
116 {
117 l.cornerIsLast = false;
118 l.leaderSegIndex = 0;
119 l.cornerDistance = distFirst;
120 l.isCorner = true;
121
122 if( distFirst <= thr )
123 {
124 l.isStrict = true;
125 l.cornerDistance = 0;
126 }
127 }
128 else
129 {
130 l.cornerIsLast = true;
131 l.leaderSegIndex = l.originalLine.SegmentCount() - 1;
132 l.cornerDistance = distLast;
133 l.isCorner = true;
134
135 if( distLast <= thr )
136 {
137 l.isStrict = true;
138 l.cornerDistance = 0;
139 }
140 }
141 }
142
143 const auto& links = l.originalLine.Links();
144
145 for( int lidx = 0; lidx < (int) links.size(); lidx++ )
146 {
147 if( auto lseg = dyn_cast<SEGMENT*>( links[lidx] ) )
148 {
149
150 if( !aPrimitives.Contains( lseg ) )
151 continue;
152
153 int d = lseg->Seg().Distance( aP );
154
155 l.midSeg = lseg->Seg();
156 l.isMidSeg = true;
157 l.leaderSegIndex = lidx;
158 l.leaderSegDistance = d + thr;
159
160 if( d < thr && !l.isStrict )
161 {
162 l.isCorner = false;
163 l.isStrict = true;
164 l.leaderSegDistance = 0;
165 }
166 }
167 }
168
169 if( l.isStrict )
170 {
171 anyStrictCornersFound |= l.isCorner;
172 anyStrictMidSegsFound |= !l.isCorner;
173 }
174 }
175
176 if( anyStrictCornersFound )
178 else if (anyStrictMidSegsFound )
180 else
181 {
182 int minLeadSegDist = std::numeric_limits<int>::max();
183 int minCornerDist = std::numeric_limits<int>::max();
184 MDRAG_LINE *bestSeg = nullptr;
185 MDRAG_LINE *bestCorner = nullptr;
186
187 for( auto& l : m_mdragLines )
188 {
189 if( l.cornerDistance < minCornerDist )
190 {
191 minCornerDist = l.cornerDistance;
192 bestCorner = &l;
193 }
194 if( l.leaderSegDistance < minLeadSegDist )
195 {
196 minLeadSegDist = l.leaderSegDistance;
197 bestSeg = &l;
198 }
199 }
200
201 if( bestCorner && bestSeg )
202 {
203 if( minCornerDist < minLeadSegDist )
204 {
206 bestCorner->isPrimaryLine = true;
207 }
208 else
209 {
211 bestSeg->isPrimaryLine = true;
212 }
213 }
214 else if ( bestCorner )
215 {
217 bestCorner->isPrimaryLine = true;
218 }
219 else if ( bestSeg )
220 {
222 bestSeg->isPrimaryLine = true;
223 }
224 else return false; // can it really happen?
225 }
226
227 if( m_dragMode == DM_CORNER )
228 {
229 for( auto& l : m_mdragLines )
230 {
231 // make sure the corner to drag is the last one
232 if ( !l.cornerIsLast )
233 {
234 l.originalLine.Reverse();
235 l.cornerIsLast = true;
236 }
237 // and if it's connected (non-trivial fanout), disregard it
238
239 const JOINT* jt = m_world->FindJoint( l.originalLine.CLastPoint(), &l.originalLine );
240
241 if( !jt )
242 {
244 break;
245 }
246
247 if( !jt->IsTrivialEndpoint() )
248 {
249 m_dragMode = DM_SEGMENT; // fallback to segment mode if non-trivial endpoints found
250 }
251 }
252 }
253
254 for( auto& l : m_mdragLines )
255 {
256 if( (anyStrictCornersFound || anyStrictMidSegsFound) && l.isStrict )
257 {
258 l.isPrimaryLine = true;
259 break;
260 }
261 }
262
263 m_origDraggedItems = aPrimitives;
264
265 if( Settings().Mode() == RM_Shove )
266 {
267 m_preShoveNode = m_world->Branch();
268
269 for( auto& l : m_mdragLines )
270 {
271 m_preShoveNode->Remove( l.originalLine );
272 }
273
274 m_shove.reset( new SHOVE( m_preShoveNode, Router() ) );
275 m_shove->SetLogger( Logger() );
276 m_shove->SetDebugDecorator( Dbg() );
277 m_shove->SetDefaultShovePolicy( SHOVE::SHP_SHOVE | SHOVE::SHP_DONT_LOCK_ENDPOINTS );
278 }
279
280 return true;
281}
282
283
287
288
290{
291 return DM_CORNER;
292}
293
294bool clipToOtherLine( NODE* aNode, const LINE& aRef, LINE& aClipped )
295{
296 std::set<OBSTACLE> obstacles;
297 COLLISION_SEARCH_CONTEXT ctx( obstacles );
298
299 constexpr int clipLengthThreshold = 100;
300
301 //DEBUG_DECORATOR* dbg = ROUTER::GetInstance()->GetInterface()->GetDebugDecorator();
302
303 LINE l( aClipped );
304 SHAPE_LINE_CHAIN tightest;
305
306 bool didClip = false;
307 int curL = l.CLine().Length();
308 int step = curL / 2 - 1;
309
310 while( step > clipLengthThreshold )
311 {
312 SHAPE_LINE_CHAIN sl_tmp( aClipped.CLine() );
313 VECTOR2I pclip = sl_tmp.PointAlong( curL );
314 int idx = sl_tmp.Split( pclip );
315 sl_tmp = sl_tmp.Slice(0, idx);
316
317 l.SetShape( sl_tmp );
318
319 //PNS_DBG( dbg, 3int, pclip, WHITE, 500000, wxT(""));
320
321 if( l.Collide( &aRef, aNode, l.Layer(), &ctx ) )
322 {
323 didClip = true;
324 curL -= step;
325 step /= 2;
326 }
327 else
328 {
329 tightest = std::move( sl_tmp );
330
331 if( didClip )
332 {
333 curL += step;
334 step /= 2;
335 }
336 else
337 {
338 break;
339 }
340 }
341 }
342
343 aClipped.SetShape( tightest );
344
345 return didClip;
346}
347
348
349
350
351const std::vector<NET_HANDLE> MULTI_DRAGGER::CurrentNets() const
352{
353 std::set<NET_HANDLE> uniqueNets;
354 for( auto &l : m_mdragLines )
355 {
356 NET_HANDLE net = l.draggedLine.Net();
357 if( net )
358 uniqueNets.insert( net );
359 }
360
361 return std::vector<NET_HANDLE>( uniqueNets.begin(), uniqueNets.end() );
362}
363
364// this is what ultimately gets called when the user clicks/releases the mouse button
365// during drag.
366bool MULTI_DRAGGER::FixRoute( bool aForceCommit )
367{
368 NODE* node = CurrentNode();
369
370 if( node )
371 {
372 // last drag status is OK?
373 if( !m_dragStatus && !Settings().AllowDRCViolations() )
374 return false;
375
376 // commit the current world state
377 Router()->CommitRouting( node );
378 return true;
379 }
380
381 return false;
382}
383
384bool MULTI_DRAGGER::tryWalkaround( NODE* aNode, LINE& aOrig, LINE& aWalk )
385{
386 WALKAROUND walkaround( aNode, Router() );
387 walkaround.SetSolidsOnly( false );
388 walkaround.SetDebugDecorator( Dbg() );
389 walkaround.SetLogger( Logger() );
390 walkaround.SetIterationLimit( Settings().WalkaroundIterationLimit() );
391 walkaround.SetLengthLimit( true, 3.0 );
393
394 aWalk = aOrig;
395
396 WALKAROUND::RESULT wr = walkaround.Route( aWalk );
397
399 {
400 aWalk = wr.lines[ WALKAROUND::WP_SHORTEST ];
401 return true;
402 }
403
404 return false;
405}
406
408{
409 const SEG origLeader = aLine.preDragLine.CSegment( aLine.leaderSegIndex );
410 const DIRECTION_45 origLeaderDir( origLeader );
411
412 for ( int i = 0; i < aLine.draggedLine.SegmentCount(); i++ )
413 {
414 const SEG& curSeg = aLine.draggedLine.CSegment(i);
415 const DIRECTION_45 curDir( curSeg );
416
417 auto ip = curSeg.IntersectLines( m_guide );
418 PNS_DBG(Dbg(), Message, wxString::Format("s %d ip=%d c=%s o=%s", i, ip?1:0, curDir.Format(), origLeaderDir.Format() ));
419 if( ip && curSeg.Contains( *ip ) )
420 {
421 if( curDir == origLeaderDir || curDir == origLeaderDir.Opposite() )
422 return i;
423 }
424 }
425
426 return -1;
427}
428
429void MULTI_DRAGGER::restoreLeaderSegments( std::vector<MDRAG_LINE>& aCompletedLines )
430{
431 m_leaderSegments.clear();
432
433 for( auto& l : aCompletedLines )
434 {
435 if( l.dragOK )
436 {
437 if( m_dragMode == DM_CORNER )
438 {
439 if( l.draggedLine.LinkCount() > 0 )
440 {
441 m_leaderSegments.push_back(
442 static_cast<PNS::ITEM*>( l.draggedLine.GetLink( -1 ) ) );
443 }
444 }
445 else
446 {
447 int newLeaderIdx = findNewLeaderSegment( l );
448 if( newLeaderIdx >= 0 && newLeaderIdx < l.draggedLine.LinkCount() )
449 {
450 m_leaderSegments.push_back(
451 static_cast<PNS::ITEM*>( l.draggedLine.GetLink( newLeaderIdx ) ) );
452 }
453 }
454 }
455 }
456}
457
458bool MULTI_DRAGGER::multidragWalkaround( std::vector<MDRAG_LINE>& aCompletedLines )
459{
460 // fixme: rewrite using shared_ptr...
461 if( m_lastNode )
462 {
463 delete m_lastNode;
464 m_lastNode = nullptr;
465 }
466
467 auto compareDragStartDist = []( const MDRAG_LINE& a, const MDRAG_LINE& b ) -> int
468 {
469 return a.dragDist < b.dragDist;
470 };
471
472 std::sort( aCompletedLines.begin(), aCompletedLines.end(), compareDragStartDist );
473
474
475 NODE* preWalkNode = m_world->Branch();
476
477 for( auto& l : aCompletedLines )
478 {
479 PNS_DBG( Dbg(), AddItem, &l.originalLine, BLUE, 100000, wxString::Format("prewalk-remove lc=%d", l.originalLine.LinkCount() ) );
480 preWalkNode->Remove( l.originalLine );
481 }
482
483 struct WALK_STATE
484 {
485 NODE *node;
486 int totalLength = 0;
487 std::vector<LINE> postWalkLines;
488 bool fail = false;
489 };
490
491 WALK_STATE walkState[2];
492
493 for( int attempt = 0; attempt < 2; attempt++ )
494 {
495 WALK_STATE *state = &walkState[ attempt ];
496 state->node = preWalkNode->Branch();
497 state->postWalkLines.resize( aCompletedLines.size() );
498
499 for( int lidx = 0; lidx < (int) aCompletedLines.size(); lidx++ )
500 {
501 MDRAG_LINE& l = aCompletedLines[attempt ? aCompletedLines.size() - 1 - lidx : lidx];
502 LINE walk( l.draggedLine );
503
504 auto result = tryWalkaround( state->node, l.draggedLine, walk );
505
506 PNS_DBG( Dbg(), AddItem, &l.draggedLine, YELLOW, 100000, wxString::Format("dragged lidx=%d attempt=%d dd=%d isPrimary=%d", lidx, attempt, l.dragDist, l.isPrimaryLine?1:0) );
507 PNS_DBG( Dbg(), AddItem, &walk, BLUE, 100000, wxString::Format("walk lidx=%d attempt=%d", lidx, attempt) );
508
509 if( result )
510 {
511 state->node->Add( walk );
512 state->totalLength += walk.CLine().Length() - l.draggedLine.CLine().Length();
513 state->postWalkLines[lidx] = walk;
514 }
515 else
516 {
517 state->fail = true;
518 break;
519 }
520 }
521 }
522
523 std::optional<int> bestAttempt;
524
525 if( !walkState[0].fail && !walkState[1].fail )
526 {
527 if ( walkState[0].totalLength < walkState[1].totalLength )
528 {
529 bestAttempt = 0;
530 }
531 else
532 {
533 bestAttempt = 1;
534 }
535 }
536 else if ( !walkState[0].fail )
537 {
538 bestAttempt = 0;
539 }
540 else if ( !walkState[1].fail )
541 {
542 bestAttempt = 1;
543 }
544
545 if( !bestAttempt )
546 {
547 delete walkState[0].node;
548 delete walkState[1].node;
549 return false;
550 }
551 else
552 {
553 for( int lidx = 0; lidx < (int) aCompletedLines.size(); lidx++ )
554 {
555 aCompletedLines[lidx].draggedLine = walkState[ *bestAttempt ].postWalkLines[ lidx ];
556 }
557
558 m_lastNode = walkState[ *bestAttempt ].node;
559 delete walkState[1 - *bestAttempt].node;
560 }
561
562 // trip asan for qa
563 /*for( auto& l : aCompletedLines )
564 for( auto lnk : l.draggedLine.Links() )
565 assert( lnk->Parent() != reinterpret_cast<BOARD_ITEM*>( 0xdeadbeef ) );*/
566
567 restoreLeaderSegments( aCompletedLines );
568
569 return true;
570}
571
572
573bool MULTI_DRAGGER::multidragMarkObstacles( std::vector<MDRAG_LINE>& aCompletedLines )
574{
575
576// fixme: rewrite using shared_ptr...
577 if( m_lastNode )
578 {
579 delete m_lastNode;
580 m_lastNode = nullptr;
581 }
582
583 // m_lastNode contains the temporary (post-modification) state. Think of it as
584 // of an efficient undo buffer. We don't change the PCB directly, but a branch of it
585 // created below. We can then commit its state (applying the modifications to the host board
586 // by calling ROUTING::CommitRouting(m_lastNode) or simply discard it.
587 m_lastNode = m_world->Branch();
588
589
590 for( int l1 = 0; l1 < (int)aCompletedLines.size(); l1++ )
591 {
592 for( int l2 = l1 + 1; l2 < (int)aCompletedLines.size(); l2++ )
593 {
594 const auto& l1l = aCompletedLines[l1].draggedLine;
595 auto l2l = aCompletedLines[l2].draggedLine;
596
597 if( clipToOtherLine( m_lastNode, l1l, l2l ) )
598 aCompletedLines[l2].draggedLine = l2l;
599 }
600 }
601
602 for ( auto&l : aCompletedLines )
603 {
604 m_lastNode->Remove( l.originalLine );
605 m_lastNode->Add( l.draggedLine );
606 }
607
608 restoreLeaderSegments( aCompletedLines );
609
610 return true;
611}
612
613bool MULTI_DRAGGER::multidragShove( std::vector<MDRAG_LINE>& aCompletedLines )
614{
615 if( m_lastNode )
616 {
617 delete m_lastNode;
618 m_lastNode = nullptr;
619 }
620
621 if( !m_shove )
622 return false;
623
624 auto compareDragStartDist = []( const MDRAG_LINE& a, const MDRAG_LINE& b ) -> int
625 {
626 return a.dragDist < b.dragDist;
627 };
628
629 std::sort( aCompletedLines.begin(), aCompletedLines.end(), compareDragStartDist );
630
631 auto iface = Router()->GetInterface();
632
633 for( auto& l : m_mdragLines )
634 {
635 PNS_DBG( Dbg(), Message, wxString::Format ( wxT("net %-30s: isCorner %d isStrict %d c-Dist %-10d l-dist %-10d leadIndex %-2d CisLast %d dragDist %-10d"),
636 iface->GetNetName( l.draggedLine.Net() ),
637 (int) l.isCorner?1:0,
638 (int) l.isStrict?1:0,
639 (int) l.cornerDistance,
640 (int) l.leaderSegDistance,
641 (int) l.leaderSegIndex,
642 (int) l.cornerIsLast?1:0,
643 (int) l.dragDist ) );
644 }
645
646
647 m_shove->SetDefaultShovePolicy( SHOVE::SHP_SHOVE );
648 m_shove->ClearHeads();
649
650 for( auto& l : aCompletedLines )
651 {
652 PNS_DBG( Dbg(), AddItem, &l.draggedLine, GREEN, 0, "dragged-line" );
653 m_shove->AddHeads( l.draggedLine, SHOVE::SHP_SHOVE | SHOVE::SHP_DONT_OPTIMIZE );
654 }
655
656 auto status = m_shove->Run();
657
658 m_lastNode = m_shove->CurrentNode()->Branch();
659
660 // Re-add any m_mdragLines that were removed from m_preShoveNode during Start() but
661 // are not part of aCompletedLines. Without this, lines that fail the drag angle check
662 // would be silently deleted from the board.
663 std::set<int> completedIndices;
664
665 for( const auto& cl : aCompletedLines )
666 completedIndices.insert( cl.mdragIndex );
667
668 for( const auto& ml : m_mdragLines )
669 {
670 if( completedIndices.find( ml.mdragIndex ) == completedIndices.end() )
671 {
672 LINE preserved( ml.originalLine );
673 preserved.ClearLinks();
674 m_lastNode->Add( preserved );
675 }
676 }
677
678 if( status == SHOVE::SH_OK )
679 {
680 for( int i = 0; i < (int) aCompletedLines.size(); i++ )
681 {
682 MDRAG_LINE&l = aCompletedLines[i];
683
684 if( m_shove->HeadsModified( i ) )
685 l.draggedLine = m_shove->GetModifiedHead( i );
686
687 // this should not be linked (assert in rt-test)
689
690 m_lastNode->Add( l.draggedLine );
691 }
692 }
693 else
694 {
695 return false;
696 }
697
698 restoreLeaderSegments( aCompletedLines );
699
700 return true;
701}
702
703// this is called every time the user moves the mouse while dragging a set of multiple tracks
705{
706 std::optional<LINE> primaryPreDrag, primaryDragged;
707
708
709
710 SEG lastPreDrag;
711 DIRECTION_45 primaryDir;
712 VECTOR2I perp;
713
714 DIRECTION_45 primaryLastSegDir;
715 std::vector<MDRAG_LINE> completed;
716
717 auto tryPosture = [&] ( int aVariant ) -> bool
718 {
719 MDRAG_LINE* primaryLine = nullptr;
720
721 for( auto &l : m_mdragLines )
722 {
723 l.dragOK = false;
724 l.preDragLine = l.originalLine;
725 //PNS_DBG( Dbg(), AddItem, &l.originalLine, GREEN, 300000, "par" );
726 if( l.isPrimaryLine )
727 {
728
729 //PNS_DBG( Dbg(), AddItem, &l.originalLine, BLUE, 300000, wxT("mdrag-prim"));
730
731 // create a copy of the primary line (pre-drag and post-drag).
732 // the pre-drag version is necessary for NODE::Remove() to be able to
733 // find out the segments before modification by the multidrag algorithm
734 primaryDragged = l.originalLine;
735 primaryDragged->ClearLinks();
736 primaryPreDrag = l.originalLine;
737 primaryLine = &l;
738
739 }
740 }
741
742 if( aVariant == 1 && (primaryPreDrag->PointCount() > 2) )
743 {
744 primaryPreDrag->Line().Remove( -1 );
745 primaryDragged->Line().Remove( -1 );
746
747 for( auto&l : m_mdragLines )
748 {
749 l.preDragLine.Line().Remove(-1);
750 }
751 }
752
753 completed.clear();
754
755 int snapThreshold = Settings().SmoothDraggedSegments() ? primaryDragged->Width() / 4 : 0;
756
757 if( m_dragMode == DM_CORNER )
758 {
759 // first, drag only the primary line
760 PNS_DBG( Dbg(), AddPoint, primaryDragged->CLastPoint(), YELLOW, 600000, wxT("mdrag-sec"));
761
762 lastPreDrag = primaryPreDrag->CSegment( -1 );
763 primaryDir = DIRECTION_45( lastPreDrag );
764
765 primaryDragged->SetSnapThreshhold( snapThreshold );
766 primaryDragged->DragCorner( aP, primaryDragged->PointCount() - 1, false );
767
768
769 if( primaryDragged->SegmentCount() > 0 )
770 {
771 SEG lastPrimDrag = primaryDragged->CSegment( -1 );
772
773 if ( aVariant == 2 )
774 lastPrimDrag = lastPreDrag;
775
776 auto lastSeg = primaryDragged->CSegment( -1 );
777 if( DIRECTION_45( lastSeg ) != primaryDir )
778 {
779 if( lastSeg.Length() < primaryDragged->Width() )
780 {
781 lastPrimDrag = lastPreDrag;
782 }
783 }
784
785 perp = (lastPrimDrag.B - lastPrimDrag.A).Perpendicular();
786 primaryLastSegDir = DIRECTION_45( lastPrimDrag );
787
788
789 PNS_DBG( Dbg(), AddItem, &(*primaryDragged), LIGHTGRAY, 100000, "prim" );
790 PNS_DBG( Dbg(), AddShape, SEG(lastPrimDrag.B, lastPrimDrag.B + perp), LIGHTGRAY, 100000, wxString::Format("prim-perp-seg") );
791 } else {
792 return false;
793 }
794
795
796
797// PNS_DBG( Dbg(), AddShape, &ll, LIGHTBLUE, 200000, "par" );
798
799 }
800 else
801 {
802
803 SHAPE_LINE_CHAIN ll2( { lastPreDrag.A, lastPreDrag.B } );
804 PNS_DBG( Dbg(), AddShape, &ll2, LIGHTYELLOW, 300000, "par" );
805 lastPreDrag = primaryDragged->CSegment( primaryLine->leaderSegIndex );
806 primaryDragged->SetSnapThreshhold( snapThreshold );
807 primaryDragged->DragSegment( aP, primaryLine->leaderSegIndex );
808 perp = (primaryLine->midSeg.B - primaryLine->midSeg.A).Perpendicular();
809 m_guide = SEG( aP, aP + perp );
810 }
811
812
814 m_draggedItems.Clear();
815
816 // now drag all other lines
817 for( auto& l : m_mdragLines )
818 {
819 //PNS_DBG( Dbg(), AddPoint, l.originalLine.CPoint( l.cornerIndex ), WHITE, 1000000, wxT("l-end"));
820 if( l.isDraggable )
821 {
822 l.dragOK = false;
823 //PNS_DBG( Dbg(), AddItem, &l.originalLine, GREEN, 100000, wxT("mdrag-sec"));
824
825 // reject nulls
826 if( l.preDragLine.SegmentCount() >= 1 )
827 {
828
829 //PNS_DBG( Dbg(), AddPoint, l.preDragLine.CPoint( l.cornerIndex ), YELLOW, 600000, wxT("mdrag-sec"));
830
831 // check the direction of the last segment of the line against the direction of
832 // the last segment of the primary line (both before dragging) and perform drag
833 // only when the directions are the same. The algorithm here is quite trival and
834 // otherwise would produce really awkward results. There's of course a TON of
835 // room for improvement here :-)
836
837 if( m_dragMode == DM_CORNER )
838 {
839 DIRECTION_45 parallelDir( l.preDragLine.CSegment( -1 ) );
840
841 auto leadAngle = primaryDir.Angle( parallelDir );
842
843 if( leadAngle == DIRECTION_45::ANG_OBTUSE
844 || leadAngle == DIRECTION_45::ANG_RIGHT
845 || leadAngle == DIRECTION_45::ANG_STRAIGHT )
846 {
847 // compute the distance between the primary line and the last point of
848 // the currently processed line
849 int dist = lastPreDrag.LineDistance( l.preDragLine.CLastPoint(), true );
850
851 // now project it on the perpendicular line we computed before
852 auto projected = aP + perp.Resize( dist );
853
854
855 LINE parallelDragged( l.preDragLine );
856
857 PNS_DBG( Dbg(), AddPoint, projected, LIGHTGRAY, 100000, "dragged-c" );
858 PNS_DBG( Dbg(), AddPoint, parallelDragged.CLastPoint(), LIGHTGRAY, 100000, wxString::Format("orig-c cil %d", l.cornerIsLast?1:0) );
859
860 parallelDragged.ClearLinks();
861 //m_lastNode->Remove( parallelDragged );
862 // drag the non-primary line's end trying to place it at the projected point
863 parallelDragged.DragCorner( projected, parallelDragged.PointCount() - 1,
864 false, primaryLastSegDir );
865
866 PNS_DBG( Dbg(), AddPoint, projected, LIGHTYELLOW, 600000,
867 wxT( "l-end" ) );
868
869 // DragCorner can collapse a very short secondary line to a single
870 // point; skip it so the later CSegment(-1) check stays valid
871 if( parallelDragged.SegmentCount() < 1 )
872 continue;
873
874 l.dragOK = true;
875
876 if( !l.isPrimaryLine )
877 {
878 l.draggedLine = parallelDragged;
879 completed.push_back( l );
880 m_draggedItems.Add( parallelDragged );
881 }
882 }
883 }
884 else if ( m_dragMode == DM_SEGMENT )
885 {
886 SEG sdrag = l.midSeg;
887 DIRECTION_45 refDir( lastPreDrag );
888 DIRECTION_45 curDir( sdrag );
889 auto ang = refDir.Angle( curDir );
890
892 {
893 int dist = lastPreDrag.LineDistance(
894 l.preDragLine.CPoint( l.leaderSegIndex ), true );
895 auto projected = aP + perp.Resize( dist );
896
897 SEG sperp( aP, aP + perp.Resize( 10000000 ) );
898 VECTOR2I startProj = sperp.LineProject( m_dragStartPoint );
899
900 SHAPE_LINE_CHAIN ll( { sperp.A, sperp.B } );
901
902
903 PNS_DBG( Dbg(), AddShape, &ll, LIGHTBLUE, 100000, "par" );
904 SHAPE_LINE_CHAIN ll2( { sdrag.A, sdrag.B } );
905 PNS_DBG( Dbg(), AddShape, &ll2, LIGHTBLUE, 100000, "sdrag" );
906 VECTOR2I v = projected - startProj;
907 l.dragDist = v.EuclideanNorm() * sign( v.Dot( perp ) );
908 l.dragOK = true;
909
910 if( !l.isPrimaryLine )
911 {
912 l.draggedLine = l.preDragLine;
913 l.draggedLine.ClearLinks();
914 l.draggedLine.SetSnapThreshhold( snapThreshold );
915 l.draggedLine.DragSegment( projected, l.leaderSegIndex, false );
916 completed.push_back( l );
917 PNS_DBG( Dbg(), AddItem, &l.draggedLine, LIGHTBLUE, 100000,
918 "dragged" );
919 }
920
921
922 PNS_DBG( Dbg(), AddPoint, startProj, LIGHTBLUE, 400000,
923 wxT( "startProj" ) );
924 PNS_DBG( Dbg(), AddPoint, projected, LIGHTRED, 400000,
925 wxString::Format( "pro dd=%d", l.dragDist ) );
926 }
927 }
928 }
929 }
930
931 if (l.isPrimaryLine)
932 {
933 l.draggedLine = *primaryDragged;
934 l.dragOK = true;
935 completed.push_back( l );
936 }
937 }
938
939 if( m_dragMode == DM_SEGMENT )
940 return true;
941 else
942 {
943 for ( const auto &l: completed )
944 {
945 if( !l.dragOK && aVariant < 2 )
946 return false;
947
948 if( l.isPrimaryLine )
949 continue;
950
951 // A degenerate dragged line has no last segment to read a direction from;
952 // reject this posture so the next variant is attempted
953 if( l.draggedLine.SegmentCount() < 1 )
954 return false;
955
956 DIRECTION_45 lastDir ( l.draggedLine.CSegment(-1) );
957
958 if( lastDir != primaryLastSegDir )
959 return false;
960 }
961 }
962
963 return true;
964 };
965
966 bool res = false;
967
968 for( int variant = 0; variant < 3; variant++ )
969 {
970 res = tryPosture( variant );
971
972 if( res )
973 break;
974 }
975
976 switch( Settings().Mode() )
977 {
978 case RM_Walkaround:
979 m_dragStatus = multidragWalkaround ( completed );
980 break;
981
982 case RM_Shove:
983 m_dragStatus = multidragShove ( completed );
984 break;
985
986 case RM_MarkObstacles:
988 break;
989
990
991
992 default:
993 break;
994 }
995
996 return m_dragStatus;
997}
998
999
1001{
1002 return m_lastNode ? m_lastNode : m_world;
1003}
1004
1005
1007{
1008 return m_draggedItems;
1009}
1010
1011
1013{
1014 // fixme: should we care?
1015 return 0;
1016}
1017
1018
1019} // namespace PNS
Represent route directions & corner angles in a 45-degree metric.
Definition direction45.h:37
AngleType Angle(const DIRECTION_45 &aOther) const
Return the type of angle between directions (this) and aOther.
const std::string Format() const
Format the direction in a human readable word.
DIRECTION_45 Opposite() const
Return a direction opposite (180 degree) to (this).
void SetDebugDecorator(DEBUG_DECORATOR *aDecorator)
Assign a debug decorator allowing this algo to draw extra graphics for visual debugging.
void SetLogger(LOGGER *aLogger)
virtual LOGGER * Logger()
ROUTER * Router() const
Return current router settings.
ROUTING_SETTINGS & Settings() const
Return the logger object, allowing to dump geometry to a file.
DEBUG_DECORATOR * Dbg() const
DRAG_ALGO(ROUTER *aRouter)
bool Empty() const
Definition pns_itemset.h:90
bool Contains(ITEM *aItem) const
std::vector< ITEM * > & Items()
Definition pns_itemset.h:95
ITEM * FindVertex(const VECTOR2I &aV) const
Base class for PNS router board items.
Definition pns_item.h:98
virtual int Layer() const
Definition pns_item.h:216
bool Collide(const ITEM *aHead, const NODE *aNode, int aLayer, COLLISION_SEARCH_CONTEXT *aCtx=nullptr) const
Check for a collision (clearance violation) with between us and item aOther.
Definition pns_item.cpp:305
A 2D point on a given set of layers and belonging to a certain net, that links together a number of b...
Definition pns_joint.h:43
bool IsTrivialEndpoint() const
Definition pns_joint.h:176
Represents a track on a PCB, connecting two non-trivial joints (that is, vias, pads,...
Definition pns_line.h:62
void SetShape(const SHAPE_LINE_CHAIN &aLine)
Return the shape of the line.
Definition pns_line.h:131
const SHAPE_LINE_CHAIN & CLine() const
Definition pns_line.h:142
const VECTOR2I & CLastPoint() const
Definition pns_line.h:151
void DragCorner(const VECTOR2I &aP, int aIndex, bool aFreeAngle=false, DIRECTION_45 aPreferredEndingDirection=DIRECTION_45())
Definition pns_line.cpp:836
int SegmentCount() const
Definition pns_line.h:144
int PointCount() const
Definition pns_line.h:145
const SEG CSegment(int aIdx) const
Set line width.
Definition pns_line.h:152
bool multidragShove(std::vector< MDRAG_LINE > &aCompletedLines)
bool multidragMarkObstacles(std::vector< MDRAG_LINE > &aCompletedLines)
std::vector< PNS::ITEM * > m_leaderSegments
virtual bool Start(const VECTOR2I &aP, ITEM_SET &aPrimitives) override
Function Start()
bool FixRoute(bool aForceCommit) override
Function FixRoute()
bool Drag(const VECTOR2I &aP) override
Function Drag()
int CurrentLayer() const override
Function CurrentLayer()
NODE * CurrentNode() const override
Function CurrentNode()
std::vector< MDRAG_LINE > m_mdragLines
bool tryWalkaround(NODE *aNode, LINE &aOrig, LINE &aWalk)
void SetMode(PNS::DRAG_MODE aDragMode) override
int findNewLeaderSegment(const MDRAG_LINE &aLine) const
void restoreLeaderSegments(std::vector< MDRAG_LINE > &aCompletedLines)
bool multidragWalkaround(std::vector< MDRAG_LINE > &aCompletedLines)
const ITEM_SET Traces() override
Function Traces()
const std::vector< NET_HANDLE > CurrentNets() const override
Function CurrentNets()
MULTI_DRAGGER(ROUTER *aRouter)
PNS::DRAG_MODE Mode() const override
std::unique_ptr< SHOVE > m_shove
Keep the router "world" - i.e.
Definition pns_node.h:242
NODE * Branch()
Create a lightweight copy (called branch) of self that tracks the changes (added/removed items) wrs t...
Definition pns_node.cpp:157
void Remove(ARC *aArc)
Remove an item from this branch.
Definition pns_node.cpp:991
ROUTER_IFACE * GetInterface() const
Definition pns_router.h:240
void CommitRouting()
bool SmoothDraggedSegments() const
Enable/disable smoothing segments during dragging.
The actual Push and Shove algorithm.
Definition pns_shove.h:46
@ SHP_DONT_OPTIMIZE
Definition pns_shove.h:65
@ SHP_DONT_LOCK_ENDPOINTS
Definition pns_shove.h:66
void SetIterationLimit(const int aIterLimit)
void SetLengthLimit(bool aEnable, double aLengthExpansionFactor)
void SetSolidsOnly(bool aSolidsOnly)
STATUS Route(const LINE &aInitialPath, LINE &aWalkPath, bool aOptimize=true)
void SetAllowedPolicies(std::vector< WALK_POLICY > aPolicies)
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
int LineDistance(const VECTOR2I &aP, bool aDetermineSide=false) const
Return the closest Euclidean distance between point aP and the line defined by the ends of segment (t...
Definition seg.cpp:742
VECTOR2I B
Definition seg.h:46
OPT_VECTOR2I IntersectLines(const SEG &aSeg) const
Compute the intersection point of lines passing through ends of (this) and aSeg.
Definition seg.h:216
bool Contains(const SEG &aSeg) const
Definition seg.h:320
VECTOR2I LineProject(const VECTOR2I &aP) const
Compute the perpendicular projection point of aP on a line passing through ends of the segment.
Definition seg.cpp:681
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const VECTOR2I PointAlong(int aPathLength) const
int Split(const VECTOR2I &aP, bool aExact=false)
Insert the point aP belonging to one of the our segments, splitting the adjacent segment in two.
const SHAPE_LINE_CHAIN Slice(int aStartIndex, int aEndIndex) const
Return a subset of this line chain containing the [start_index, end_index] range of points.
long long int Length() const
Return length of the line chain in Euclidean metric.
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:279
constexpr extended_type Dot(const VECTOR2< T > &aVector) const
Compute dot product of self with aVector.
Definition vector2d.h:542
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:381
@ LIGHTBLUE
Definition color4d.h:58
@ BLUE
Definition color4d.h:52
@ LIGHTGRAY
Definition color4d.h:43
@ LIGHTYELLOW
Definition color4d.h:45
@ GREEN
Definition color4d.h:53
@ YELLOW
Definition color4d.h:63
@ LIGHTRED
Definition color4d.h:61
Push and Shove diff pair dimensions (gap) settings dialog.
@ RM_MarkObstacles
Ignore collisions, mark obstacles.
@ RM_Walkaround
Only walk around.
@ RM_Shove
Only shove.
void * NET_HANDLE
Definition pns_item.h:55
DRAG_MODE
Definition pns_router.h:76
@ DM_CORNER
Definition pns_router.h:77
@ DM_SEGMENT
Definition pns_router.h:78
bool clipToOtherLine(NODE *aNode, const LINE &aRef, LINE &aClipped)
#define PNS_DBG(dbg, method,...)
std::vector< PNS::ITEM * > originalLeaders
LINE lines[MaxWalkPolicies]
STATUS status[MaxWalkPolicies]
VECTOR3I res
wxString result
Test unit parsing edge cases and error handling.
Casted dyn_cast(From aObject)
A lightweight dynamic downcast.
Definition typeinfo.h:56
constexpr int sign(T val)
Definition util.h:141
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683