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