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