KiCad PCB EDA Suite
Loading...
Searching...
No Matches
edit_tool_move_fct.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2013-2017 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Maciej Suminski <[email protected]>
7 * @author Tomasz Wlostowski <[email protected]>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, you may find one here:
21 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
22 * or you may search the http://www.gnu.org website for the version 2 license,
23 * or you may write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 */
26
27#include <functional>
28#include <algorithm>
29#include <limits>
30#include <kiplatform/ui.h>
31#include <board.h>
32#include <board_commit.h>
35#include <pad.h>
36#include <pcb_group.h>
37#include <pcb_generator.h>
38#include <pcb_edit_frame.h>
39#include <spread_footprints.h>
40#include <tool/tool_manager.h>
41#include <tools/pcb_actions.h>
43#include <tools/edit_tool.h>
45#include <tools/drc_tool.h>
47#include <router/router_tool.h>
49#include <zone_filler.h>
50#include <drc/drc_engine.h>
52#include <view/view_controls.h>
53
55#include <wx/richmsgdlg.h>
56#include <wx/choicdlg.h>
57#include <unordered_set>
58#include <unordered_map>
59
60
61static bool PromptConnectedPadDecision( PCB_BASE_EDIT_FRAME* aFrame, const std::vector<PAD*>& aPads,
62 const wxString& aDialogTitle, bool& aIncludeConnectedPads )
63{
64 if( aPads.empty() )
65 {
66 aIncludeConnectedPads = true;
67 return true;
68 }
69
70 std::unordered_set<PAD*> uniquePads( aPads.begin(), aPads.end() );
71
72 wxString msg;
73 msg.Printf( _( "%zu unselected pad(s) are connected to these nets. How do you want to proceed?" ),
74 uniquePads.size() );
75
76 wxString details;
77 details << _( "Connected tracks, vias, and other non-zone copper items will still swap nets"
78 " even if you ignore the unselected pads." )
79 << "\n \n" // Add space so GTK doesn't eat the newlines
80 << _( "Unselected pads:" ) << '\n';
81
82 for( PAD* pad : uniquePads )
83 {
84 const FOOTPRINT* fp = pad->GetParentFootprint();
85 details << wxS( " • " ) << ( fp ? fp->GetReference() : _( "<no reference designator>" ) ) << wxS( ":" )
86 << pad->GetNumber() << '\n';
87 }
88
89
90 wxRichMessageDialog dlg( aFrame, msg, aDialogTitle, wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_WARNING );
91 dlg.SetYesNoLabels( _( "Ignore Unselected Pads" ), _( "Swap All Connected Pads" ) );
92 dlg.SetExtendedMessage( details );
93
94 int ret = dlg.ShowModal();
95
96 if( ret == wxID_CANCEL )
97 return false;
98
99 aIncludeConnectedPads = ( ret == wxID_NO );
100 return true;
101}
102
103
104int EDIT_TOOL::Swap( const TOOL_EVENT& aEvent )
105{
106 if( isRouterActive() )
107 {
108 wxBell();
109 return 0;
110 }
111
112 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
113 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
114 {
115 sTool->FilterCollectorForMarkers( aCollector );
116 sTool->FilterCollectorForHierarchy( aCollector, true );
117 sTool->FilterCollectorForFreePads( aCollector );
118
119 // Iterate from the back so we don't have to worry about removals.
120 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
121 {
122 BOARD_ITEM* item = aCollector[i];
123
124 if( item->Type() == PCB_TRACE_T )
125 aCollector.Remove( item );
126 }
127
128 sTool->FilterCollectorForLockedItems( aCollector );
129 } );
130
131 if( selection.Size() < 2 )
132 return 0;
133
134 BOARD_COMMIT localCommit( this );
135 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
136
137 if( !commit )
138 commit = &localCommit;
139
140 std::vector<EDA_ITEM*> sorted = selection.GetItemsSortedBySelectionOrder();
141
142 // Save items, so changes can be undone
143 for( EDA_ITEM* item : selection )
144 commit->Modify( item, nullptr, RECURSE_MODE::RECURSE );
145
146 for( size_t i = 0; i < sorted.size() - 1; i++ )
147 {
148 EDA_ITEM* edaItemA = sorted[i];
149 EDA_ITEM* edaItemB = sorted[( i + 1 ) % sorted.size()];
150
151 if( !edaItemA->IsBOARD_ITEM() || !edaItemB->IsBOARD_ITEM() )
152 continue;
153
154 BOARD_ITEM* a = static_cast<BOARD_ITEM*>( edaItemA );
155 BOARD_ITEM* b = static_cast<BOARD_ITEM*>( edaItemB );
156
157 // Swap X,Y position
158 VECTOR2I aPos = a->GetPosition(), bPos = b->GetPosition();
159 std::swap( aPos, bPos );
160 a->SetPosition( aPos );
161 b->SetPosition( bPos );
162
163 // Handle footprints specially. They can be flipped to the back of the board which
164 // requires a special transformation.
165 if( a->Type() == PCB_FOOTPRINT_T && b->Type() == PCB_FOOTPRINT_T )
166 {
167 FOOTPRINT* aFP = static_cast<FOOTPRINT*>( a );
168 FOOTPRINT* bFP = static_cast<FOOTPRINT*>( b );
169
170 // Store initial orientation of footprints, before flipping them.
171 EDA_ANGLE aAngle = aFP->GetOrientation();
172 EDA_ANGLE bAngle = bFP->GetOrientation();
173
174 // Flip both if needed
175 if( aFP->IsFlipped() != bFP->IsFlipped() )
176 {
177 aFP->Flip( aPos, FLIP_DIRECTION::TOP_BOTTOM );
178 bFP->Flip( bPos, FLIP_DIRECTION::TOP_BOTTOM );
179 }
180
181 // Set orientation
182 std::swap( aAngle, bAngle );
183 aFP->SetOrientation( aAngle );
184 bFP->SetOrientation( bAngle );
185 }
186 // We can also do a layer swap safely for two objects of the same type,
187 // except groups which don't support layer swaps.
188 else if( a->Type() == b->Type() && a->Type() != PCB_GROUP_T )
189 {
190 // Swap layers
191 PCB_LAYER_ID aLayer = a->GetLayer(), bLayer = b->GetLayer();
192 std::swap( aLayer, bLayer );
193 a->SetLayer( aLayer );
194 b->SetLayer( bLayer );
195 }
196 }
197
198 if( !localCommit.Empty() )
199 localCommit.Push( _( "Swap" ) );
200
202
203 return 0;
204}
205
206
208{
209 if( isRouterActive() )
210 {
211 wxBell();
212 return 0;
213 }
214
216
217 if( selection.Size() < 2 || !selection.OnlyContains( { PCB_PAD_T } ) )
218 return 0;
219
220 // Get selected pads in selection order, because swapping is cyclic and we let the user pick
221 // the rotation order
222 std::vector<EDA_ITEM*> orderedPads = selection.GetItemsSortedBySelectionOrder();
223 std::vector<PAD*> pads;
224 const size_t padsCount = orderedPads.size();
225
226 for( EDA_ITEM* it : orderedPads )
227 pads.push_back( static_cast<PAD*>( static_cast<BOARD_ITEM*>( it ) ) );
228
229 // Record original nets and build selected set for quick membership tests
230 std::vector<int> originalNets( padsCount );
231 std::unordered_set<PAD*> selectedPads;
232
233 for( size_t i = 0; i < padsCount; ++i )
234 {
235 originalNets[i] = pads[i]->GetNetCode();
236 selectedPads.insert( pads[i] );
237 }
238
239 // If all nets are the same, nothing to do
240 bool allSame = true;
241
242 for( size_t i = 1; i < padsCount; ++i )
243 {
244 if( originalNets[i] != originalNets[0] )
245 {
246 allSame = false;
247 break;
248 }
249 }
250
251 if( allSame )
252 return 0;
253
254 // Desired new nets are a cyclic rotation of original nets (like Swap positions)
255 auto newNetForIndex =
256 [&]( size_t i )
257 {
258 return originalNets[( i + 1 ) % padsCount];
259 };
260
261 // Take an event commit since we will eventually support this while actively routing the board
262 BOARD_COMMIT localCommit( this );
263 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
264
265 if( !commit )
266 commit = &localCommit;
267
268 // Connectivity to find items connected to each pad
269 std::shared_ptr<CONNECTIVITY_DATA> connectivity = board()->GetConnectivity();
270
271 // Accumulate changes: for each item, assign the resulting new net
272 std::unordered_map<BOARD_CONNECTED_ITEM*, int> itemNewNets;
273 std::vector<PAD*> nonSelectedPadsToChange;
274
275 for( size_t i = 0; i < padsCount; ++i )
276 {
277 PAD* pad = pads[i];
278 int fromNet = originalNets[i];
279 int toNet = newNetForIndex( i );
280
281 // For each connected item, if it matches fromNet, schedule it for toNet
282 for( BOARD_CONNECTED_ITEM* ci : connectivity->GetConnectedItems( pad, 0 ) )
283 {
284 switch( ci->Type() )
285 {
286 case PCB_TRACE_T:
287 case PCB_ARC_T:
288 case PCB_VIA_T:
289 case PCB_PAD_T:
290 break;
291 // Exclude zones, user probably doesn't want to change zone nets
292 default:
293 continue;
294 }
295
296 if( ci->GetNetCode() != fromNet )
297 continue;
298
299 // Track conflicts: if already assigned a different new net, just overwrite (last wins)
300 itemNewNets[ci] = toNet;
301
302 if( ci->Type() == PCB_PAD_T )
303 {
304 PAD* otherPad = static_cast<PAD*>( ci );
305
306 if( !selectedPads.count( otherPad ) )
307 nonSelectedPadsToChange.push_back( otherPad );
308 }
309 }
310 }
311
312 bool includeConnectedPads = true;
313
314 if( !PromptConnectedPadDecision( frame(), nonSelectedPadsToChange, _( "Swap Pad Nets" ), includeConnectedPads ) )
315 return 0;
316
317 // Apply changes
318 // 1) Selected pads get their new nets directly
319 for( size_t i = 0; i < padsCount; ++i )
320 {
321 commit->Modify( pads[i] );
322 pads[i]->SetNetCode( newNetForIndex( i ) );
323 }
324
325 // 2) Connected items propagate, depending on user choice
326 for( const auto& itemNewNet : itemNewNets )
327 {
328 BOARD_CONNECTED_ITEM* item = itemNewNet.first;
329 int newNet = itemNewNet.second;
330
331 if( item->Type() == PCB_PAD_T )
332 {
333 PAD* p = static_cast<PAD*>( item );
334
335 if( selectedPads.count( p ) )
336 continue; // already changed above
337
338 if( !includeConnectedPads )
339 continue; // skip non-selected pads if requested
340 }
341
342 commit->Modify( item );
343 item->SetNetCode( newNet );
344 }
345
346 if( !localCommit.Empty() )
347 localCommit.Push( _( "Swap Pad Nets" ) );
348
349 // Ensure connectivity visuals update
352
353 return 0;
354}
355
356
358{
359 if( isRouterActive() )
360 {
361 wxBell();
362 return 0;
363 }
364
365 auto showError =
366 [this]()
367 {
368 frame()->ShowInfoBarError( _( "Gate swapping must be performed on pads within one multi-gate "
369 "footprint." ) );
370 };
371
373
374 // Get our sanity checks out of the way to clean up later loops
375 FOOTPRINT* targetFp = nullptr;
376 bool fail = false;
377
378 for( EDA_ITEM* it : selection )
379 {
380 // This shouldn't happen due to the filter, but just in case
381 if( it->Type() != PCB_PAD_T )
382 {
383 fail = true;
384 break;
385 }
386
387 FOOTPRINT* fp = static_cast<PAD*>( static_cast<BOARD_ITEM*>( it ) )->GetParentFootprint();
388
389 if( !targetFp )
390 {
391 targetFp = fp;
392 }
393 else if( fp && targetFp != fp )
394 {
395 fail = true;
396 break;
397 }
398 }
399
400 if( fail || !targetFp || targetFp->GetUnitInfo().size() < 2 )
401 {
402 showError();
403 return 0;
404 }
405
406
407 const auto& units = targetFp->GetUnitInfo();
408
409 // Collect unit hits and ordered unit list based on selection order
410 std::vector<bool> unitHit( units.size(), false );
411 std::vector<int> unitOrder;
412
413 std::vector<EDA_ITEM*> orderedPads = selection.GetItemsSortedBySelectionOrder();
414
415 for( EDA_ITEM* it : orderedPads )
416 {
417 PAD* pad = static_cast<PAD*>( static_cast<BOARD_ITEM*>( it ) );
418
419 const wxString& padNum = pad->GetNumber();
420 int unitIdx = -1;
421
422 for( size_t i = 0; i < units.size(); ++i )
423 {
424 for( const auto& p : units[i].m_pins )
425 {
426 if( p == padNum )
427 {
428 unitIdx = static_cast<int>( i );
429
430 if( !unitHit[i] )
431 unitOrder.push_back( unitIdx );
432
433 unitHit[i] = true;
434 break;
435 }
436 }
437
438 if( unitIdx >= 0 )
439 break;
440 }
441 }
442
443 // Determine active units from selection order: 0 -> bail, 1 -> single-unit flow, 2+ -> cycle
444 std::vector<int> activeUnitIdx;
445 int sourceIdx = -1;
446
447 if( unitOrder.size() >= 2 )
448 {
449 activeUnitIdx = unitOrder;
450 sourceIdx = unitOrder.front();
451 }
452 // If we only have one gate selected, we must have a target unit name parameter to proceed
453 else if( unitOrder.size() == 1 && aEvent.HasParameter() )
454 {
455 sourceIdx = unitOrder.front();
456 wxString targetUnitByName = aEvent.Parameter<wxString>();
457
458 int targetIdx = -1;
459
460 for( size_t i = 0; i < units.size(); ++i )
461 {
462 if( static_cast<int>( i ) == sourceIdx )
463 continue;
464
465 if( units[i].m_pins.size() == units[sourceIdx].m_pins.size() && units[i].m_unitName == targetUnitByName )
466 targetIdx = static_cast<int>( i );
467 }
468
469 if( targetIdx < 0 )
470 {
471 showError();
472 return 0;
473 }
474
475 activeUnitIdx.push_back( sourceIdx );
476 activeUnitIdx.push_back( targetIdx );
477 }
478 else
479 {
480 showError();
481 return 0;
482 }
483
484 // Verify equal pin counts across all active units
485 const size_t pinCount = units[activeUnitIdx.front()].m_pins.size();
486
487 for( int idx : activeUnitIdx )
488 {
489 if( units[idx].m_pins.size() != pinCount )
490 {
491 frame()->ShowInfoBarError( _( "Gate swapping must be performed on gates with equal pin counts." ) );
492 return 0;
493 }
494 }
495
496 // Build per-unit pad arrays and net vectors
497 const size_t unitCount = activeUnitIdx.size();
498 std::vector<std::vector<PAD*>> unitPads( unitCount );
499 std::vector<std::vector<int>> unitNets( unitCount );
500
501 for( size_t ui = 0; ui < unitCount; ++ui )
502 {
503 int uidx = activeUnitIdx[ui];
504 const auto& pins = units[uidx].m_pins;
505
506 for( size_t pi = 0; pi < pinCount; ++pi )
507 {
508 PAD* p = targetFp->FindPadByNumber( pins[pi] );
509
510 if( !p )
511 {
512 frame()->ShowInfoBarError( _( "Gate swapping failed: pad in unit missing from footprint." ) );
513 return 0;
514 }
515
516 unitPads[ui].push_back( p );
517 unitNets[ui].push_back( p->GetNetCode() );
518 }
519 }
520
521 // If all unit nets match across positions, nothing to do
522 bool allSame = true;
523
524 for( size_t pi = 0; pi < pinCount && allSame; ++pi )
525 {
526 int refNet = unitNets[0][pi];
527
528 for( size_t ui = 1; ui < unitCount; ++ui )
529 {
530 if( unitNets[ui][pi] != refNet )
531 {
532 allSame = false;
533 break;
534 }
535 }
536 }
537
538 if( allSame )
539 {
540 frame()->ShowInfoBarError( _( "Gate swapping has no effect: all selected gates have identical nets." ) );
541 return 0;
542 }
543
544 // TODO: someday support swapping while routing and take that commit
545 BOARD_COMMIT localCommit( this );
546 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
547
548 if( !commit )
549 commit = &localCommit;
550
551 std::shared_ptr<CONNECTIVITY_DATA> connectivity = board()->GetConnectivity();
552
553 // Accumulate changes: item -> new net
554 std::unordered_map<BOARD_CONNECTED_ITEM*, int> itemNewNets;
555 std::vector<PAD*> nonSelectedPadsToChange;
556
557 // Selected pads in the swap (for suppressing re-adding in connected pad handling)
558 std::unordered_set<PAD*> swapPads;
559
560 for( const auto& v : unitPads )
561 swapPads.insert( v.begin(), v.end() );
562
563 // Schedule net swaps for connectivity-attached items
564 auto scheduleForPad = [&]( PAD* pad, int fromNet, int toNet )
565 {
566 for( BOARD_CONNECTED_ITEM* ci : connectivity->GetConnectedItems( pad, 0 ) )
567 {
568 switch( ci->Type() )
569 {
570 case PCB_TRACE_T:
571 case PCB_ARC_T:
572 case PCB_VIA_T:
573 case PCB_PAD_T:
574 break;
575
576 default:
577 continue;
578 }
579
580 if( ci->GetNetCode() != fromNet )
581 continue;
582
583 itemNewNets[ ci ] = toNet;
584
585 if( ci->Type() == PCB_PAD_T )
586 {
587 PAD* other = static_cast<PAD*>( ci );
588
589 if( !swapPads.count( other ) )
590 nonSelectedPadsToChange.push_back( other );
591 }
592 }
593 };
594
595 // For each position, rotate nets among units forward
596 for( size_t pi = 0; pi < pinCount; ++pi )
597 {
598 for( size_t ui = 0; ui < unitCount; ++ui )
599 {
600 size_t fromIdx = ui;
601 size_t toIdx = ( ui + 1 ) % unitCount;
602
603 PAD* padFrom = unitPads[fromIdx][pi];
604 int fromNet = unitNets[fromIdx][pi];
605 int toNet = unitNets[toIdx][pi];
606
607 scheduleForPad( padFrom, fromNet, toNet );
608 }
609 }
610
611 bool includeConnectedPads = true;
612
613 if( !PromptConnectedPadDecision( frame(), nonSelectedPadsToChange, _( "Swap Gate Nets" ), includeConnectedPads ) )
614 {
615 return 0;
616 }
617
618 // Apply pad net swaps: rotate per position
619 for( size_t pi = 0; pi < pinCount; ++pi )
620 {
621 // First write back nets for each unit's pad at this position
622 for( size_t ui = 0; ui < unitCount; ++ui )
623 {
624 size_t toIdx = ( ui + 1 ) % unitCount;
625 PAD* pad = unitPads[ui][pi];
626 int newNet = unitNets[toIdx][pi];
627
628 commit->Modify( pad );
629 pad->SetNetCode( newNet );
630 }
631 }
632
633 // Apply connected items
634 for( const auto& kv : itemNewNets )
635 {
636 BOARD_CONNECTED_ITEM* item = kv.first;
637 int newNet = kv.second;
638
639 if( item->Type() == PCB_PAD_T )
640 {
641 PAD* p = static_cast<PAD*>( item );
642
643 if( swapPads.count( p ) )
644 continue;
645
646 if( !includeConnectedPads )
647 continue;
648 }
649
650 commit->Modify( item );
651 item->SetNetCode( newNet );
652 }
653
654 if( !localCommit.Empty() )
655 localCommit.Push( _( "Swap Gate Nets" ) );
656
659
660 return 0;
661}
662
663
665{
666 if( isRouterActive() || m_dragging )
667 {
668 wxBell();
669 return 0;
670 }
671
672 BOARD_COMMIT commit( this );
673 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
674 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
675 {
676 sTool->FilterCollectorForMarkers( aCollector );
677 sTool->FilterCollectorForHierarchy( aCollector, true );
678 sTool->FilterCollectorForFreePads( aCollector, true );
679
680 // Iterate from the back so we don't have to worry about removals.
681 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
682 {
683 BOARD_ITEM* item = aCollector[i];
684
685 if( !dynamic_cast<FOOTPRINT*>( item ) )
686 aCollector.Remove( item );
687 }
688
689 sTool->FilterCollectorForLockedItems( aCollector );
690 } );
691
692 std::vector<FOOTPRINT*> footprintsToPack;
693
694 for( EDA_ITEM* item : selection )
695 footprintsToPack.push_back( static_cast<FOOTPRINT*>( item ) );
696
697 if( footprintsToPack.empty() )
698 return 0;
699
700 BOX2I footprintsBbox;
701
702 for( FOOTPRINT* fp : footprintsToPack )
703 {
704 commit.Modify( fp );
705 fp->SetFlags( IS_MOVING );
706 footprintsBbox.Merge( fp->GetBoundingBox( false ) );
707 }
708
709 SpreadFootprints( &footprintsToPack, footprintsBbox.Normalize().GetOrigin(), false );
710
711 if( doMoveSelection( aEvent, &commit, true ) )
712 commit.Push( _( "Pack Footprints" ) );
713 else
714 commit.Revert();
715
716 return 0;
717}
718
719
720int EDIT_TOOL::Move( const TOOL_EVENT& aEvent )
721{
722 if( isRouterActive() || m_dragging )
723 {
724 wxBell();
725 return 0;
726 }
727
728 if( BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() ) )
729 {
730 // Most moves will be synchronous unless they are coming from the API
731 if( aEvent.SynchronousState() )
732 aEvent.SynchronousState()->store( STS_RUNNING );
733
734 if( doMoveSelection( aEvent, commit, true ) )
735 {
736 if( aEvent.SynchronousState() )
737 aEvent.SynchronousState()->store( STS_FINISHED );
738 }
739 else if( aEvent.SynchronousState() )
740 {
741 aEvent.SynchronousState()->store( STS_CANCELLED );
742 }
743 }
744 else
745 {
746 BOARD_COMMIT localCommit( this );
747
748 if( doMoveSelection( aEvent, &localCommit, false ) )
749 localCommit.Push( _( "Move" ) );
750 else
751 localCommit.Revert();
752 }
753
754 // Notify point editor. (While doMoveSelection() will re-select the items and post this
755 // event, it's done before the edit flags are cleared in BOARD_COMMIT::Push() so the point
756 // editor doesn't fire up.)
757 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
758
759 return 0;
760}
761
762
763VECTOR2I EDIT_TOOL::getSafeMovement( const VECTOR2I& aMovement, const BOX2I& aSourceBBox,
764 const VECTOR2D& aBBoxOffset )
765{
766 typedef std::numeric_limits<int> coord_limits;
767
768 static const double max = coord_limits::max() - (int) COORDS_PADDING;
769 static const double min = -max;
770
771 BOX2D testBox( aSourceBBox.GetPosition(), aSourceBBox.GetSize() );
772 testBox.Offset( aBBoxOffset );
773
774 // Do not restrict movement if bounding box is already out of bounds
775 if( testBox.GetLeft() < min || testBox.GetTop() < min || testBox.GetRight() > max
776 || testBox.GetBottom() > max )
777 {
778 return aMovement;
779 }
780
781 testBox.Offset( aMovement );
782
783 if( testBox.GetLeft() < min )
784 testBox.Offset( min - testBox.GetLeft(), 0 );
785
786 if( max < testBox.GetRight() )
787 testBox.Offset( -( testBox.GetRight() - max ), 0 );
788
789 if( testBox.GetTop() < min )
790 testBox.Offset( 0, min - testBox.GetTop() );
791
792 if( max < testBox.GetBottom() )
793 testBox.Offset( 0, -( testBox.GetBottom() - max ) );
794
795 return KiROUND( testBox.GetPosition() - aBBoxOffset - aSourceBBox.GetPosition() );
796}
797
798
799bool EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, BOARD_COMMIT* aCommit, bool aAutoStart )
800{
801 const bool moveWithReference = aEvent.IsAction( &PCB_ACTIONS::moveWithReference );
802 const bool moveIndividually = aEvent.IsAction( &PCB_ACTIONS::moveIndividually );
803
805 PCBNEW_SETTINGS* cfg = editFrame->GetPcbNewSettings();
806 BOARD* board = editFrame->GetBoard();
808 VECTOR2I originalCursorPos = controls->GetCursorPosition();
809 VECTOR2I originalMousePos = controls->GetMousePosition();
810 std::unique_ptr<STATUS_TEXT_POPUP> statusPopup;
811 size_t itemIdx = 0;
812
813 // Be sure that there is at least one item that we can modify. If nothing was selected before,
814 // try looking for the stuff under mouse cursor (i.e. KiCad old-style hover selection)
815 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
816 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
817 {
818 sTool->FilterCollectorForMarkers( aCollector );
819 sTool->FilterCollectorForHierarchy( aCollector, true );
820 sTool->FilterCollectorForFreePads( aCollector );
821 sTool->FilterCollectorForTableCells( aCollector );
822 sTool->FilterCollectorForLockedItems( aCollector );
823 } );
824
825 if( m_dragging || selection.Empty() )
826 return false;
827
828 TOOL_EVENT pushedEvent = aEvent;
829 editFrame->PushTool( aEvent );
830 Activate();
831
832 // Must be done after Activate() so that it gets set into the correct context
833 controls->ShowCursor( true );
834 controls->SetAutoPan( true );
835 controls->ForceCursorPosition( false );
836
837 auto displayConstraintsMessage =
838 [editFrame]( LEADER_MODE aMode )
839 {
840 wxString msg;
841
842 switch( aMode )
843 {
845 msg = _( "Angle snap lines: 45°" );
846 break;
847
849 msg = _( "Angle snap lines: 90°" );
850 break;
851
852 default:
853 msg.clear();
854 break;
855 }
856
857 editFrame->DisplayConstraintsMsg( msg );
858 };
859
860 auto updateStatusPopup =
861 [&]( EDA_ITEM* item, size_t ii, size_t count )
862 {
863 wxString popuptext = _( "Click to place %s (item %zu of %zu)\n"
864 "Press <esc> to cancel all; double-click to finish" );
865 wxString msg;
866
867 if( item->Type() == PCB_FOOTPRINT_T )
868 {
869 FOOTPRINT* fp = static_cast<FOOTPRINT*>( item );
870 msg = fp->GetReference();
871 }
872 else if( item->Type() == PCB_PAD_T )
873 {
874 PAD* pad = static_cast<PAD*>( item );
875 FOOTPRINT* fp = pad->GetParentFootprint();
876 msg = wxString::Format( _( "%s pad %s" ), fp->GetReference(), pad->GetNumber() );
877 }
878 else
879 {
880 msg = item->GetTypeDesc().Lower();
881 }
882
883 if( !statusPopup )
884 statusPopup = std::make_unique<STATUS_TEXT_POPUP>( frame() );
885
886 statusPopup->SetText( wxString::Format( popuptext, msg, ii, count ) );
887 };
888
889 std::vector<BOARD_ITEM*> sel_items; // All the items operated on by the move below
890 std::vector<BOARD_ITEM*> orig_items; // All the original items in the selection
891
892 for( EDA_ITEM* item : selection )
893 {
894 if( item->IsBOARD_ITEM() )
895 {
896 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
897
898 if( !selection.IsHover() )
899 orig_items.push_back( boardItem );
900
901 sel_items.push_back( boardItem );
902 }
903
904 if( item->Type() == PCB_FOOTPRINT_T )
905 {
906 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
907
908 for( PAD* pad : footprint->Pads() )
909 sel_items.push_back( pad );
910
911 // Clear this flag here; it will be set by the netlist updater if the footprint is new
912 // so that it was skipped in the initial connectivity update in OnNetlistChanged
913 footprint->SetAttributes( footprint->GetAttributes() & ~FP_JUST_ADDED );
914 }
915 }
916
917 VECTOR2I pickedReferencePoint;
918
919 if( moveWithReference && !pickReferencePoint( _( "Select reference point for move..." ), "", "",
920 pickedReferencePoint ) )
921 {
922 if( selection.IsHover() )
924
925 editFrame->PopTool( pushedEvent );
926 return false;
927 }
928
929 if( moveIndividually )
930 {
931 orig_items.clear();
932
933 for( EDA_ITEM* item : selection.GetItemsSortedBySelectionOrder() )
934 {
935 if( item->IsBOARD_ITEM() )
936 orig_items.push_back( static_cast<BOARD_ITEM*>( item ) );
937 }
938
939 updateStatusPopup( orig_items[ itemIdx ], itemIdx + 1, orig_items.size() );
940 statusPopup->Popup();
941 statusPopup->Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, 20 ) );
942 canvas()->SetStatusPopup( statusPopup->GetPanel() );
943
944 m_selectionTool->ClearSelection();
945 m_selectionTool->AddItemToSel( orig_items[ itemIdx ] );
946
947 sel_items.clear();
948 sel_items.push_back( orig_items[ itemIdx ] );
949 }
950
951 bool restore_state = false;
952 VECTOR2I originalPos = originalCursorPos; // Initialize to current cursor position
953 VECTOR2D bboxMovement;
954 BOX2I originalBBox;
955 bool updateBBox = true;
956 LSET layers( { editFrame->GetActiveLayer() } );
958 TOOL_EVENT copy = aEvent;
959 TOOL_EVENT* evt = &copy;
960 VECTOR2I prevPos;
961 bool enableLocalRatsnest = true;
962
963 LEADER_MODE angleSnapMode = GetAngleSnapMode();
964 bool eatFirstMouseUp = true;
965 bool allowRedraw3D = cfg->m_Display.m_Live3DRefresh;
966 bool showCourtyardConflicts = !m_isFootprintEditor && cfg->m_ShowCourtyardCollisions;
967
968 // Axis locking for arrow key movement
969 enum class AXIS_LOCK { NONE, HORIZONTAL, VERTICAL };
970 AXIS_LOCK axisLock = AXIS_LOCK::NONE;
971 long lastArrowKeyAction = 0;
972
973 // Used to test courtyard overlaps
974 std::unique_ptr<DRC_INTERACTIVE_COURTYARD_CLEARANCE> drc_on_move = nullptr;
975
976 if( showCourtyardConflicts )
977 {
978 std::shared_ptr<DRC_ENGINE> drcEngine = m_toolMgr->GetTool<DRC_TOOL>()->GetDRCEngine();
979 drc_on_move.reset( new DRC_INTERACTIVE_COURTYARD_CLEARANCE( drcEngine ) );
980 drc_on_move->Init( board );
981 }
982
983 auto configureAngleSnap =
984 [&]( LEADER_MODE aMode )
985 {
986 std::vector<VECTOR2I> directions;
987
988 switch( aMode )
989 {
991 directions = { VECTOR2I( 1, 0 ), VECTOR2I( 0, 1 ), VECTOR2I( 1, 1 ), VECTOR2I( 1, -1 ) };
992 break;
993
995 directions = { VECTOR2I( 1, 0 ), VECTOR2I( 0, 1 ) };
996 break;
997
998 default:
999 break;
1000 }
1001
1002 grid.SetSnapLineDirections( directions );
1003
1004 if( directions.empty() )
1005 {
1006 grid.ClearSnapLine();
1007 }
1008 else
1009 {
1010 grid.SetSnapLineOrigin( originalPos );
1011 }
1012 };
1013
1014 configureAngleSnap( angleSnapMode );
1015 displayConstraintsMessage( angleSnapMode );
1016
1017 // Prime the pump
1018 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1019
1020 // Main loop: keep receiving events
1021 do
1022 {
1023 VECTOR2I movement;
1025 grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
1026 grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
1027
1028 bool isSkip = evt->IsAction( &PCB_ACTIONS::skip ) && moveIndividually;
1029
1030 if( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) )
1031 eatFirstMouseUp = false;
1032
1033 if( evt->IsAction( &PCB_ACTIONS::move )
1034 || evt->IsMotion()
1035 || evt->IsDrag( BUT_LEFT )
1039 {
1040 if( m_dragging && ( evt->IsMotion()
1041 || evt->IsDrag( BUT_LEFT )
1042 || evt->IsAction( &ACTIONS::refreshPreview ) ) )
1043 {
1044 bool redraw3D = false;
1045
1046 GRID_HELPER_GRIDS selectionGrid = grid.GetSelectionGrid( selection );
1047
1048 // We need to bypass refreshPreview action here because it is triggered by the move,
1049 // so we were getting double-key events that toggled the axis locking if you
1050 // pressed them in a certain order.
1051 if( controls->GetSettings().m_lastKeyboardCursorPositionValid && ! evt->IsAction( &ACTIONS::refreshPreview ) )
1052 {
1053 VECTOR2I keyboardPos( controls->GetSettings().m_lastKeyboardCursorPosition );
1054 long action = controls->GetSettings().m_lastKeyboardCursorCommand;
1055
1056 grid.SetSnap( false );
1057 m_cursor = grid.Align( keyboardPos, selectionGrid );
1058
1059 // Update axis lock based on arrow key press
1060 if( action == ACTIONS::CURSOR_LEFT || action == ACTIONS::CURSOR_RIGHT )
1061 {
1062 if( axisLock == AXIS_LOCK::HORIZONTAL )
1063 {
1064 // Check if opposite horizontal key pressed to unlock
1065 if( ( lastArrowKeyAction == ACTIONS::CURSOR_LEFT && action == ACTIONS::CURSOR_RIGHT ) ||
1066 ( lastArrowKeyAction == ACTIONS::CURSOR_RIGHT && action == ACTIONS::CURSOR_LEFT ) )
1067 {
1068 axisLock = AXIS_LOCK::NONE;
1069 }
1070 // Same direction axis, keep locked
1071 }
1072 else
1073 {
1074 axisLock = AXIS_LOCK::HORIZONTAL;
1075 }
1076 }
1077 else if( action == ACTIONS::CURSOR_UP || action == ACTIONS::CURSOR_DOWN )
1078 {
1079 if( axisLock == AXIS_LOCK::VERTICAL )
1080 {
1081 // Check if opposite vertical key pressed to unlock
1082 if( ( lastArrowKeyAction == ACTIONS::CURSOR_UP && action == ACTIONS::CURSOR_DOWN ) ||
1083 ( lastArrowKeyAction == ACTIONS::CURSOR_DOWN && action == ACTIONS::CURSOR_UP ) )
1084 {
1085 axisLock = AXIS_LOCK::NONE;
1086 }
1087 // Same direction axis, keep locked
1088 }
1089 else
1090 {
1091 axisLock = AXIS_LOCK::VERTICAL;
1092 }
1093 }
1094
1095 lastArrowKeyAction = action;
1096 }
1097 else
1098 {
1099 VECTOR2I mousePos( controls->GetMousePosition() );
1100
1101 m_cursor = grid.BestSnapAnchor( mousePos, layers, selectionGrid, sel_items );
1102 }
1103
1104 if( axisLock == AXIS_LOCK::HORIZONTAL )
1105 m_cursor.y = prevPos.y;
1106 else if( axisLock == AXIS_LOCK::VERTICAL )
1107 m_cursor.x = prevPos.x;
1108
1109 if( !selection.HasReferencePoint() )
1110 originalPos = m_cursor;
1111
1112 if( updateBBox )
1113 {
1114 originalBBox = BOX2I();
1115 bboxMovement = VECTOR2D();
1116
1117 for( EDA_ITEM* item : sel_items )
1118 originalBBox.Merge( item->ViewBBox() );
1119
1120 updateBBox = false;
1121 }
1122
1123 // Constrain selection bounding box to coordinates limits
1124 movement = getSafeMovement( m_cursor - prevPos, originalBBox, bboxMovement );
1125
1126 // Apply constrained movement
1127 m_cursor = prevPos + movement;
1128
1129 controls->ForceCursorPosition( true, m_cursor );
1130 selection.SetReferencePoint( m_cursor );
1131
1132 prevPos = m_cursor;
1133 bboxMovement += movement;
1134
1135 // Drag items to the current cursor position
1136 for( BOARD_ITEM* item : sel_items )
1137 {
1138 // Don't double move child items.
1139 if( !item->GetParent() || !item->GetParent()->IsSelected() )
1140 item->Move( movement );
1141
1142 if( item->Type() == PCB_GENERATOR_T && sel_items.size() == 1 )
1143 {
1144 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genUpdateEdit, aCommit,
1145 static_cast<PCB_GENERATOR*>( item ) );
1146 }
1147
1148 if( item->Type() == PCB_FOOTPRINT_T )
1149 redraw3D = true;
1150 }
1151
1152 if( redraw3D && allowRedraw3D )
1153 editFrame->Update3DView( false, true );
1154
1155 if( showCourtyardConflicts && drc_on_move->m_FpInMove.size() )
1156 {
1157 drc_on_move->Run();
1158 drc_on_move->UpdateConflicts( m_toolMgr->GetView(), true );
1159 }
1160
1162 }
1163 else if( !m_dragging && ( aAutoStart || !evt->IsAction( &ACTIONS::refreshPreview ) ) )
1164 {
1165 // Prepare to start dragging
1166 editFrame->HideSolderMask();
1167
1168 m_dragging = true;
1169
1170 for( BOARD_ITEM* item : sel_items )
1171 {
1172 if( item->GetParent() && item->GetParent()->IsSelected() )
1173 continue;
1174
1175 if( !item->IsNew() && !item->IsMoving() )
1176 {
1177 if( item->Type() == PCB_GENERATOR_T && sel_items.size() == 1 )
1178 {
1179 enableLocalRatsnest = false;
1180
1181 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genStartEdit, aCommit,
1182 static_cast<PCB_GENERATOR*>( item ) );
1183 }
1184 else
1185 {
1186 aCommit->Modify( item, nullptr, RECURSE_MODE::RECURSE );
1187 }
1188
1189 item->SetFlags( IS_MOVING );
1190
1191 if( item->Type() == PCB_SHAPE_T )
1192 static_cast<PCB_SHAPE*>( item )->UpdateHatching();
1193
1194 item->RunOnChildren(
1195 [&]( BOARD_ITEM* child )
1196 {
1197 child->SetFlags( IS_MOVING );
1198
1199 if( child->Type() == PCB_SHAPE_T )
1200 static_cast<PCB_SHAPE*>( child )->UpdateHatching();
1201 },
1203 }
1204 }
1205
1206 m_cursor = controls->GetCursorPosition();
1207
1208 if( selection.HasReferencePoint() )
1209 {
1210 // start moving with the reference point attached to the cursor
1211 grid.SetAuxAxes( false );
1212
1213 movement = m_cursor - selection.GetReferencePoint();
1214
1215 // Drag items to the current cursor position
1216 for( EDA_ITEM* item : selection )
1217 {
1218 if( !item->IsBOARD_ITEM() )
1219 continue;
1220
1221 // Don't double move footprint pads, fields, etc.
1222 if( item->GetParent() && item->GetParent()->IsSelected() )
1223 continue;
1224
1225 static_cast<BOARD_ITEM*>( item )->Move( movement );
1226 }
1227
1228 selection.SetReferencePoint( m_cursor );
1229 }
1230 else
1231 {
1232 if( showCourtyardConflicts )
1233 {
1234 std::vector<FOOTPRINT*>& FPs = drc_on_move->m_FpInMove;
1235
1236 for( BOARD_ITEM* item : sel_items )
1237 {
1238 if( item->Type() == PCB_FOOTPRINT_T )
1239 FPs.push_back( static_cast<FOOTPRINT*>( item ) );
1240
1241 item->RunOnChildren(
1242 [&]( BOARD_ITEM* child )
1243 {
1244 if( child->Type() == PCB_FOOTPRINT_T )
1245 FPs.push_back( static_cast<FOOTPRINT*>( child ) );
1246 },
1248 }
1249 }
1250
1251 // Use the mouse position over cursor, as otherwise large grids will allow only
1252 // snapping to items that are closest to grid points
1253 m_cursor = grid.BestDragOrigin( originalMousePos, sel_items, grid.GetSelectionGrid( selection ),
1254 &m_selectionTool->GetFilter() );
1255
1256 // Set the current cursor position to the first dragged item origin, so the
1257 // movement vector could be computed later
1258 if( moveWithReference )
1259 {
1260 selection.SetReferencePoint( pickedReferencePoint );
1261
1262 if( angleSnapMode != LEADER_MODE::DIRECT )
1263 grid.SetSnapLineOrigin( selection.GetReferencePoint() );
1264
1265 controls->ForceCursorPosition( true, pickedReferencePoint );
1266 m_cursor = pickedReferencePoint;
1267 }
1268 else
1269 {
1270 // Don't snap the items on the initial drag start - this would warp
1271 // the object position before the mouse moves. Instead, set up construction
1272 // lines at the current object position and let the user move from there.
1273
1274 // Get the best drag origin (where an item anchor is)
1275 VECTOR2I dragOrigin = m_cursor;
1276
1277 // Set the reference point to the drag origin (actual item position)
1278 selection.SetReferencePoint( dragOrigin );
1279
1280 // Set up construction/snap lines at the CURRENT position, not a snapped position
1281 if( angleSnapMode != LEADER_MODE::DIRECT )
1282 grid.SetSnapLineOrigin( dragOrigin );
1283
1284 grid.SetAuxAxes( true, dragOrigin );
1285
1286 // Use the original cursor position if not warping
1287 if( !editFrame->GetMoveWarpsCursor() )
1288 m_cursor = originalCursorPos;
1289 else
1290 {
1291 // Even when warping is enabled, stay at the drag origin initially
1292 // to prevent immediate object movement
1293 m_cursor = dragOrigin;
1294 }
1295 }
1296
1297 originalPos = selection.GetReferencePoint();
1298 }
1299
1300 // Update variables for bounding box collision calculations
1301 updateBBox = true;
1302
1303 controls->SetCursorPosition( m_cursor, false );
1304
1305 prevPos = m_cursor;
1306 controls->SetAutoPan( true );
1308 }
1309
1310 if( statusPopup )
1311 statusPopup->Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, 20 ) );
1312
1313 if( enableLocalRatsnest )
1314 m_toolMgr->PostAction( PCB_ACTIONS::updateLocalRatsnest, movement );
1315 }
1316 else if( evt->IsCancelInteractive() || evt->IsActivate() )
1317 {
1318 if( m_dragging && evt->IsCancelInteractive() )
1319 evt->SetPassEvent( false );
1320
1321 restore_state = true; // Canceling the tool means that items have to be restored
1322 break; // Finish
1323 }
1324 else if( evt->IsClick( BUT_RIGHT ) )
1325 {
1326 m_selectionTool->GetToolMenu().ShowContextMenu( selection );
1327 }
1328 else if( evt->IsAction( &ACTIONS::undo ) )
1329 {
1330 restore_state = true; // Perform undo locally
1331 break; // Finish
1332 }
1333 else if( evt->IsAction( &ACTIONS::doDelete ) )
1334 {
1335 evt->SetPassEvent();
1336 // Exit on a delete; there will no longer be anything to drag.
1337 break;
1338 }
1339 else if( evt->IsAction( &ACTIONS::duplicate ) && evt != &copy )
1340 {
1341 wxBell();
1342 }
1343 else if( evt->IsAction( &ACTIONS::cut ) )
1344 {
1345 wxBell();
1346 }
1347 else if( evt->IsAction( &PCB_ACTIONS::rotateCw )
1349 || evt->IsAction( &PCB_ACTIONS::flip )
1350 || evt->IsAction( &PCB_ACTIONS::mirrorH )
1351 || evt->IsAction( &PCB_ACTIONS::mirrorV ) )
1352 {
1353 updateBBox = true;
1354 eatFirstMouseUp = false;
1355 evt->SetPassEvent();
1356 }
1357 else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) || isSkip )
1358 {
1359 // Eat mouse-up/-click events that leaked through from the lock dialog
1360 if( eatFirstMouseUp && !evt->IsAction( &ACTIONS::cursorClick ) )
1361 {
1362 eatFirstMouseUp = false;
1363 continue;
1364 }
1365 else if( moveIndividually && m_dragging )
1366 {
1367 // Put skipped items back where they started
1368 if( isSkip )
1369 orig_items[itemIdx]->SetPosition( originalPos );
1370
1371 view()->Update( orig_items[itemIdx] );
1373
1374 if( ++itemIdx < orig_items.size() )
1375 {
1376 BOARD_ITEM* nextItem = orig_items[itemIdx];
1377
1378 m_selectionTool->ClearSelection();
1379
1380 originalPos = nextItem->GetPosition();
1381 m_selectionTool->AddItemToSel( nextItem );
1382 selection.SetReferencePoint( originalPos );
1383 if( angleSnapMode != LEADER_MODE::DIRECT )
1384 grid.SetSnapLineOrigin( selection.GetReferencePoint() );
1385
1386 sel_items.clear();
1387 sel_items.push_back( nextItem );
1388 updateStatusPopup( nextItem, itemIdx + 1, orig_items.size() );
1389
1390 // Pick up new item
1391 aCommit->Modify( nextItem, nullptr, RECURSE_MODE::RECURSE );
1392 nextItem->Move( controls->GetCursorPosition( true ) - nextItem->GetPosition() );
1393
1394 continue;
1395 }
1396 }
1397
1398 break; // finish
1399 }
1400 else if( evt->IsDblClick( BUT_LEFT ) )
1401 {
1402 // The first click will move the new item, so put it back
1403 if( moveIndividually )
1404 orig_items[itemIdx]->SetPosition( originalPos );
1405
1406 break; // finish
1407 }
1409 {
1410 angleSnapMode = GetAngleSnapMode();
1411 configureAngleSnap( angleSnapMode );
1412 displayConstraintsMessage( angleSnapMode );
1413 evt->SetPassEvent( true );
1414 }
1415 else if( evt->IsAction( &ACTIONS::increment ) )
1416 {
1417 if( evt->HasParameter() )
1418 m_toolMgr->RunSynchronousAction( ACTIONS::increment, aCommit, evt->Parameter<ACTIONS::INCREMENT>() );
1419 else
1420 m_toolMgr->RunSynchronousAction( ACTIONS::increment, aCommit, ACTIONS::INCREMENT { 1, 0 } );
1421 }
1428 || evt->IsAction( &ACTIONS::redo ) )
1429 {
1430 wxBell();
1431 }
1432 else
1433 {
1434 evt->SetPassEvent();
1435 }
1436
1437 } while( ( evt = Wait() ) ); // Assignment (instead of equality test) is intentional
1438
1439 // Clear temporary COURTYARD_CONFLICT flag and ensure the conflict shadow is cleared
1440 if( showCourtyardConflicts )
1441 drc_on_move->ClearConflicts( m_toolMgr->GetView() );
1442
1443 controls->ForceCursorPosition( false );
1444 controls->ShowCursor( false );
1445 controls->SetAutoPan( false );
1446
1447 m_dragging = false;
1448
1449 // Discard reference point when selection is "dropped" onto the board
1450 selection.ClearReferencePoint();
1451
1452 // Unselect all items to clear selection flags and then re-select the originally selected
1453 // items.
1454 m_toolMgr->RunAction( ACTIONS::selectionClear );
1455
1456 if( restore_state )
1457 {
1458 if( sel_items.size() == 1 && sel_items.back()->Type() == PCB_GENERATOR_T )
1459 {
1460 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genCancelEdit, aCommit,
1461 static_cast<PCB_GENERATOR*>( sel_items.back() ) );
1462 }
1463 }
1464 else
1465 {
1466 if( sel_items.size() == 1 && sel_items.back()->Type() == PCB_GENERATOR_T )
1467 {
1468 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genFinishEdit, aCommit,
1469 static_cast<PCB_GENERATOR*>( sel_items.back() ) );
1470 }
1471
1472 EDA_ITEMS oItems( orig_items.begin(), orig_items.end() );
1473 m_toolMgr->RunAction<EDA_ITEMS*>( ACTIONS::selectItems, &oItems );
1474 }
1475
1476 // Remove the dynamic ratsnest from the screen
1478
1479 editFrame->PopTool( pushedEvent );
1481
1482 return !restore_state;
1483}
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
BOX2< VECTOR2D > BOX2D
Definition box2.h:923
@ CURSOR_RIGHT
Definition actions.h:311
@ CURSOR_LEFT
Definition actions.h:309
@ CURSOR_UP
Definition actions.h:305
@ CURSOR_DOWN
Definition actions.h:307
static TOOL_ACTION undo
Definition actions.h:75
static TOOL_ACTION duplicate
Definition actions.h:84
static TOOL_ACTION doDelete
Definition actions.h:85
static TOOL_ACTION cursorClick
Definition actions.h:180
static TOOL_ACTION redo
Definition actions.h:76
static TOOL_ACTION increment
Definition actions.h:94
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:224
static TOOL_ACTION cut
Definition actions.h:77
static TOOL_ACTION refreshPreview
Definition actions.h:159
static TOOL_ACTION selectItems
Select a list of items (specified as the event parameter)
Definition actions.h:232
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
virtual void Revert() override
Revert the commit by restoring the modified items state.
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
bool SetNetCode(int aNetCode, bool aNoAssert)
Set net using a net code.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:83
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition board_item.h:236
virtual void Move(const VECTOR2I &aMoveVector)
Move this object.
Definition board_item.h:343
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:284
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
constexpr const Vec & GetPosition() const
Definition box2.h:211
constexpr BOX2< Vec > & Normalize()
Ensure that the height and width are positive.
Definition box2.h:146
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:658
constexpr coord_type GetLeft() const
Definition box2.h:228
constexpr const Vec & GetOrigin() const
Definition box2.h:210
constexpr coord_type GetRight() const
Definition box2.h:217
constexpr const SizeVec & GetSize() const
Definition box2.h:206
constexpr coord_type GetTop() const
Definition box2.h:229
constexpr void Offset(coord_type dx, coord_type dy)
Definition box2.h:259
constexpr coord_type GetBottom() const
Definition box2.h:222
int GetCount() const
Return the number of objects in the list.
Definition collector.h:83
void Remove(int aIndex)
Remove the item at aIndex (first position is 0).
Definition collector.h:111
bool Empty() const
Definition commit.h:137
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:106
void DisplayConstraintsMsg(const wxString &msg)
void SetCurrentCursor(KICURSOR aCursor)
Set the current cursor shape for this panel.
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
virtual VECTOR2I GetPosition() const
Definition eda_item.h:278
virtual void SetPosition(const VECTOR2I &aPos)
Definition eda_item.h:279
wxString GetTypeDesc() const
Return a translated description of the type for this EDA_ITEM for display in user facing messages.
Definition eda_item.cpp:392
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:148
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
bool IsSelected() const
Definition eda_item.h:127
EDA_ITEM * GetParent() const
Definition eda_item.h:112
virtual const BOX2I ViewBBox() const override
Return the bounding box of the item covering all its layers.
Definition eda_item.cpp:345
bool IsMoving() const
Definition eda_item.h:125
bool IsNew() const
Definition eda_item.h:124
bool isRouterActive() const
int SwapGateNets(const TOOL_EVENT &aEvent)
bool doMoveSelection(const TOOL_EVENT &aEvent, BOARD_COMMIT *aCommit, bool aAutoStart)
Rebuilds the ratsnest for operations that require it outside the commit rebuild.
int Swap(const TOOL_EVENT &aEvent)
Swap currently selected items' positions.
int PackAndMoveFootprints(const TOOL_EVENT &aEvent)
Try to fit selected footprints inside a minimal area and start movement.
bool pickReferencePoint(const wxString &aTooltip, const wxString &aSuccessMessage, const wxString &aCanceledMessage, VECTOR2I &aReferencePoint)
bool m_dragging
Definition edit_tool.h:242
int Move(const TOOL_EVENT &aEvent)
Main loop in which events are handled.
static const unsigned int COORDS_PADDING
Definition edit_tool.h:247
VECTOR2I getSafeMovement(const VECTOR2I &aMovement, const BOX2I &aSourceBBox, const VECTOR2D &aBBoxOffset)
int SwapPadNets(const TOOL_EVENT &aEvent)
Swap nets between selected pads and propagate to connected copper items (tracks, arcs,...
static void PadFilter(const VECTOR2I &, GENERAL_COLLECTOR &aCollector, PCB_SELECTION_TOOL *sTool)
A selection filter which prunes the selection to contain only items of type PCB_PAD_T.
VECTOR2I m_cursor
Definition edit_tool.h:243
void rebuildConnectivity()
PCB_SELECTION_TOOL * m_selectionTool
Definition edit_tool.h:241
static const TOOL_EVENT SelectedEvent
Definition actions.h:345
static const TOOL_EVENT SelectedItemsModified
Selected items were moved, this can be very high frequency on the canvas, use with care.
Definition actions.h:352
static const TOOL_EVENT SelectedItemsMoved
Used to inform tools that the selection should temporarily be non-editable.
Definition actions.h:355
EDA_ANGLE GetOrientation() const
Definition footprint.h:328
void SetOrientation(const EDA_ANGLE &aNewAngle)
const std::vector< FP_UNIT_INFO > & GetUnitInfo() const
Definition footprint.h:840
bool IsFlipped() const
Definition footprint.h:514
void Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection) override
Flip this object, i.e.
const wxString & GetReference() const
Definition footprint.h:741
PAD * FindPadByNumber(const wxString &aPadNumber, PAD *aSearchAfterMe=nullptr) const
Return a PAD with a matching number.
Used when the right click button is pressed, or when the select tool is in effect.
Definition collectors.h:207
An interface for classes handling user events controlling the view behavior such as zooming,...
bool IsBOARD_ITEM() const
Definition view_item.h:102
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
Definition pad.h:55
DISPLAY_OPTIONS m_Display
static TOOL_ACTION mirrorH
Mirroring of selected items.
static TOOL_ACTION genFinishEdit
static TOOL_ACTION hideLocalRatsnest
static TOOL_ACTION genStartEdit
static TOOL_ACTION moveWithReference
move with a reference point
static TOOL_ACTION angleSnapModeChanged
Notification event when angle mode changes.
static TOOL_ACTION moveExact
Activation of the exact move tool.
static TOOL_ACTION copyWithReference
copy command with manual reference point selection
static TOOL_ACTION genCancelEdit
static TOOL_ACTION genUpdateEdit
static TOOL_ACTION updateLocalRatsnest
static TOOL_ACTION moveIndividually
move items one-by-one
static TOOL_ACTION interactiveOffsetTool
static TOOL_ACTION positionRelative
static TOOL_ACTION skip
static TOOL_ACTION move
move or drag an item
static TOOL_ACTION mirrorV
static TOOL_ACTION flip
Flipping of selected objects.
static TOOL_ACTION rotateCw
Rotation of selected objects.
static TOOL_ACTION rotateCcw
Common, abstract interface for edit frames.
PCBNEW_SETTINGS * GetPcbNewSettings() const
virtual PCB_LAYER_ID GetActiveLayer() const
virtual MAGNETIC_SETTINGS * GetMagneticItemsSettings()
PCB_DRAW_PANEL_GAL * GetCanvas() const override
Return a pointer to GAL-based canvas of given EDA draw frame.
BOARD * GetBoard() const
virtual void Update3DView(bool aMarkDirty, bool aRefresh, const wxString *aTitle=nullptr)
Update the 3D view, if the viewer is opened by this frame.
The selection tool: currently supports:
void FilterCollectorForMarkers(GENERAL_COLLECTOR &aCollector) const
Drop any PCB_MARKERs from the collector.
void FilterCollectorForFreePads(GENERAL_COLLECTOR &aCollector, bool aForcePromotion=false) const
Check the "allow free pads" setting and if disabled, replace any pads in the collector with their par...
void FilterCollectorForHierarchy(GENERAL_COLLECTOR &aCollector, bool aMultiselect) const
In general we don't want to select both a parent and any of it's children.
void FilterCollectorForLockedItems(GENERAL_COLLECTOR &aCollector)
In the PCB editor strip out any locked items unless the OverrideLocks checkbox is set.
void FilterCollectorForTableCells(GENERAL_COLLECTOR &aCollector) const
Promote any table cell selections to the whole table.
T * frame() const
KIGFX::PCB_VIEW * view() const
LEADER_MODE GetAngleSnapMode() const
Get the current angle snapping mode.
KIGFX::VIEW_CONTROLS * controls() const
BOARD * board() const
PCB_DRAW_PANEL_GAL * canvas() const
const PCB_SELECTION & selection() const
FOOTPRINT * footprint() const
virtual void PopTool(const TOOL_EVENT &aEvent)
Pops a tool from the stack.
bool GetMoveWarpsCursor() const
Indicate that a move operation should warp the mouse pointer to the origin of the move object.
virtual void PushTool(const TOOL_EVENT &aEvent)
NB: the definition of "tool" is different at the user level.
T * getEditFrame() const
Return the application window object, casted to requested user type.
Definition tool_base.h:186
KIGFX::VIEW_CONTROLS * getViewControls() const
Return the instance of VIEW_CONTROLS object used in the application.
Definition tool_base.cpp:44
TOOL_MANAGER * m_toolMgr
Definition tool_base.h:220
KIGFX::VIEW * getView() const
Returns the instance of #VIEW object used in the application.
Definition tool_base.cpp:38
Generic, UI-independent tool event.
Definition tool_event.h:171
bool DisableGridSnapping() const
Definition tool_event.h:371
bool HasParameter() const
Definition tool_event.h:464
bool IsCancelInteractive() const
Indicate the event should restart/end an ongoing interactive tool's event loop (eg esc key,...
bool IsActivate() const
Definition tool_event.h:345
COMMIT * Commit() const
Definition tool_event.h:283
bool IsClick(int aButtonMask=BUT_ANY) const
bool IsDrag(int aButtonMask=BUT_ANY) const
Definition tool_event.h:315
int Modifier(int aMask=MD_MODIFIER_MASK) const
Return information about key modifiers state (Ctrl, Alt, etc.).
Definition tool_event.h:366
bool IsAction(const TOOL_ACTION *aAction) const
Test if the event contains an action issued upon activation of the given TOOL_ACTION.
T Parameter() const
Return a parameter assigned to the event.
Definition tool_event.h:473
bool IsDblClick(int aButtonMask=BUT_ANY) const
std::atomic< SYNCRONOUS_TOOL_STATE > * SynchronousState() const
Definition tool_event.h:280
void SetPassEvent(bool aPass=true)
Definition tool_event.h:256
bool IsMouseUp(int aButtonMask=BUT_ANY) const
Definition tool_event.h:325
bool IsMotion() const
Definition tool_event.h:330
TOOL_EVENT * Wait(const TOOL_EVENT_LIST &aEventList=TOOL_EVENT(TC_ANY, TA_ANY))
Suspend execution of the tool until an event specified in aEventList arrives.
void Activate()
Run the tool.
static bool IsZoneFillAction(const TOOL_EVENT *aEvent)
@ MOVING
Definition cursors.h:48
@ ARROW
Definition cursors.h:46
#define _(s)
@ RECURSE
Definition eda_item.h:51
std::vector< EDA_ITEM * > EDA_ITEMS
Define list of drawing items for screens.
Definition eda_item.h:575
#define IS_MOVING
Item being moved.
@ NONE
Definition eda_shape.h:69
static bool PromptConnectedPadDecision(PCB_BASE_EDIT_FRAME *aFrame, const std::vector< PAD * > &aPads, const wxString &aDialogTitle, bool &aIncludeConnectedPads)
@ FP_JUST_ADDED
Definition footprint.h:87
a few functions useful in geometry calculations.
LEADER_MODE
The kind of the leader line.
@ DEG45
45 Degree only
@ DIRECT
Unconstrained point-to-point.
@ DEG90
90 Degree only
GRID_HELPER_GRIDS
Definition grid_helper.h:44
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ TOP_BOTTOM
Flip top to bottom (around the X axis)
Definition mirror.h:29
wxPoint GetMousePosition()
Returns the mouse position in screen coordinates.
Definition wxgtk/ui.cpp:689
Class to handle a set of BOARD_ITEMs.
void SpreadFootprints(std::vector< FOOTPRINT * > *aFootprints, VECTOR2I aTargetBoxPosition, bool aGroupBySheet, int aComponentGap, int aGroupGap)
Footprints (after loaded by reading a netlist for instance) are moved to be in a small free area (out...
@ STS_CANCELLED
Definition tool_event.h:164
@ STS_FINISHED
Definition tool_event.h:163
@ STS_RUNNING
Definition tool_event.h:162
@ MD_SHIFT
Definition tool_event.h:143
@ BUT_LEFT
Definition tool_event.h:132
@ BUT_RIGHT
Definition tool_event.h:133
#define kv
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:88
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:91
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:111
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:86
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:87
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:98
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:96
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694