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_grid_item.h>
38#include <pcb_edit_frame.h>
39#include <spread_footprints.h>
40#include <tool/tool_manager.h>
41#include <tools/pcb_actions.h>
44#include <tools/edit_tool.h>
46#include <tools/drc_tool.h>
49#include <router/router_tool.h>
51#include <zone_filler.h>
52#include <drc/drc_engine.h>
55#include <view/view_controls.h>
56
58#include <wx/richmsgdlg.h>
59#include <wx/choicdlg.h>
60#include <unordered_set>
61#include <unordered_map>
62
63
64static bool PromptConnectedPadDecision( PCB_BASE_EDIT_FRAME* aFrame, const std::vector<PAD*>& aPads,
65 const wxString& aDialogTitle, bool& aIncludeConnectedPads )
66{
67 if( aPads.empty() )
68 {
69 aIncludeConnectedPads = true;
70 return true;
71 }
72
73 std::unordered_set<PAD*> uniquePads( aPads.begin(), aPads.end() );
74
75 wxString msg;
76 msg.Printf( _( "%zu unselected pad(s) are connected to these nets. How do you want to proceed?" ),
77 uniquePads.size() );
78
79 wxString details;
80 details << _( "Connected tracks, vias, and other non-zone copper items will still swap nets"
81 " even if you ignore the unselected pads." )
82 << "\n \n" // Add space so GTK doesn't eat the newlines
83 << _( "Unselected pads:" ) << '\n';
84
85 for( PAD* pad : uniquePads )
86 {
87 const FOOTPRINT* fp = pad->GetParentFootprint();
88 details << wxS( " • " ) << ( fp ? fp->GetReference() : _( "<no reference designator>" ) ) << wxS( ":" )
89 << pad->GetNumber() << '\n';
90 }
91
92
93 wxRichMessageDialog dlg( aFrame, msg, aDialogTitle, wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_WARNING );
94 dlg.SetYesNoLabels( _( "Ignore Unselected Pads" ), _( "Swap All Connected Pads" ) );
95 dlg.SetExtendedMessage( details );
96
97 int ret = dlg.ShowModal();
98
99 if( ret == wxID_CANCEL )
100 return false;
101
102 aIncludeConnectedPads = ( ret == wxID_NO );
103 return true;
104}
105
106
107// Makes sure we don't try to assign two different new nets to an item. Assigning
108// the same net to the item multiples times is fine.
109static bool ScheduleNetSwap( BOARD_CONNECTED_ITEM* aItem, int aNewNet,
110 std::unordered_map<BOARD_CONNECTED_ITEM*, int>& aItemNewNets,
111 wxString& aError )
112{
113 auto [it, inserted] = aItemNewNets.emplace( aItem, aNewNet );
114
115 if( !inserted && it->second != aNewNet )
116 {
117 aError = _( "Cannot swap nets: connected items would receive conflicting nets." );
118 return false;
119 }
120
121 return true;
122}
123
124
125// Make sure everything we're trying to swap is unlocked or we're overriding
127 const std::unordered_map<BOARD_CONNECTED_ITEM*, int>& aItemNewNets,
128 const std::unordered_set<PAD*>& aSwapPads, bool aIncludeConnectedPads )
129{
130 if( aFrame->GetOverrideLocks() )
131 return true;
132
133 for( const auto& [item, newNet] : aItemNewNets )
134 {
135 if( item->GetNetCode() == newNet )
136 continue;
137
138 if( item->Type() == PCB_PAD_T && !aIncludeConnectedPads
139 && !aSwapPads.count( static_cast<PAD*>( item ) ) )
140 continue;
141
142 if( item->IsLocked() )
143 {
144 aFrame->ShowInfoBarError( _( "Cannot swap nets of locked pads or connected copper unless "
145 "Override Locks is enabled." ) );
146 return false;
147 }
148 }
149
150 return true;
151}
152
153
154int EDIT_TOOL::Swap( const TOOL_EVENT& aEvent )
155{
156 if( isRouterActive() )
157 {
158 wxBell();
159 return 0;
160 }
161
162 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
163 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
164 {
165 sTool->FilterCollectorForMarkers( aCollector );
166 sTool->FilterCollectorForHierarchy( aCollector, true );
167 sTool->FilterCollectorForFreePads( aCollector );
168
169 // Iterate from the back so we don't have to worry about removals.
170 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
171 {
172 BOARD_ITEM* item = aCollector[i];
173
174 if( item->Type() == PCB_TRACE_T )
175 aCollector.Remove( item );
176 }
177
178 sTool->FilterCollectorForLockedItems( aCollector );
179 } );
180
181 m_selectionTool->ReportFilteredLockedItems();
182
183 if( selection.Size() < 2 )
184 return 0;
185
186 BOARD_COMMIT localCommit( this );
187 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
188
189 if( !commit )
190 commit = &localCommit;
191
192 std::vector<EDA_ITEM*> sorted = selection.GetItemsSortedBySelectionOrder();
193
194 // Save items, so changes can be undone
195 for( EDA_ITEM* item : selection )
196 commit->Modify( item, nullptr, RECURSE_MODE::RECURSE );
197
198 for( size_t i = 0; i < sorted.size() - 1; i++ )
199 {
200 EDA_ITEM* edaItemA = sorted[i];
201 EDA_ITEM* edaItemB = sorted[( i + 1 ) % sorted.size()];
202
203 if( !edaItemA->IsBOARD_ITEM() || !edaItemB->IsBOARD_ITEM() )
204 continue;
205
206 BOARD_ITEM* a = static_cast<BOARD_ITEM*>( edaItemA );
207 BOARD_ITEM* b = static_cast<BOARD_ITEM*>( edaItemB );
208
209 // Pads may have a copper shape offset from the anchor/hole, so swap visible shape
210 // centers rather than anchor positions. See PAD::SwapShapePositions.
211 if( a->Type() == PCB_PAD_T && b->Type() == PCB_PAD_T )
212 {
213 PAD::SwapShapePositions( static_cast<PAD*>( a ), static_cast<PAD*>( b ) );
214
215 PCB_LAYER_ID aLayer = a->GetLayer(), bLayer = b->GetLayer();
216 std::swap( aLayer, bLayer );
217 a->SetLayer( aLayer );
218 b->SetLayer( bLayer );
219
220 continue;
221 }
222
223 // Swap X,Y position
224 VECTOR2I aPos = a->GetPosition(), bPos = b->GetPosition();
225 std::swap( aPos, bPos );
226 a->SetPosition( aPos );
227 b->SetPosition( bPos );
228
229 // Handle footprints specially. They can be flipped to the back of the board which
230 // requires a special transformation.
231 if( a->Type() == PCB_FOOTPRINT_T && b->Type() == PCB_FOOTPRINT_T )
232 {
233 FOOTPRINT* aFP = static_cast<FOOTPRINT*>( a );
234 FOOTPRINT* bFP = static_cast<FOOTPRINT*>( b );
235
236 // Store initial orientation of footprints, before flipping them.
237 EDA_ANGLE aAngle = aFP->GetOrientation();
238 EDA_ANGLE bAngle = bFP->GetOrientation();
239
240 // Flip both if needed
241 if( aFP->IsFlipped() != bFP->IsFlipped() )
242 {
243 aFP->Flip( aPos, FLIP_DIRECTION::TOP_BOTTOM );
244 bFP->Flip( bPos, FLIP_DIRECTION::TOP_BOTTOM );
245 }
246
247 // Set orientation
248 std::swap( aAngle, bAngle );
249 aFP->SetOrientation( aAngle );
250 bFP->SetOrientation( bAngle );
251 }
252 // We can also do a layer swap safely for two objects of the same type,
253 // except groups which don't support layer swaps.
254 else if( a->Type() == b->Type() && a->Type() != PCB_GROUP_T )
255 {
256 // Swap layers
257 PCB_LAYER_ID aLayer = a->GetLayer(), bLayer = b->GetLayer();
258 std::swap( aLayer, bLayer );
259 a->SetLayer( aLayer );
260 b->SetLayer( bLayer );
261 }
262 }
263
264 if( !localCommit.Empty() )
265 localCommit.Push( _( "Swap" ) );
266
268
269 return 0;
270}
271
272
274{
275 if( isRouterActive() )
276 {
277 wxBell();
278 return 0;
279 }
280
282
283 if( selection.Size() < 2 || !selection.OnlyContains( { PCB_PAD_T } ) )
284 return 0;
285
286 // Get selected pads in selection order, because swapping is cyclic and we let the user pick
287 // the rotation order
288 std::vector<EDA_ITEM*> orderedPads = selection.GetItemsSortedBySelectionOrder();
289 std::vector<PAD*> pads;
290 const size_t padsCount = orderedPads.size();
291
292 for( EDA_ITEM* it : orderedPads )
293 pads.push_back( static_cast<PAD*>( static_cast<BOARD_ITEM*>( it ) ) );
294
295 // Record original nets and build selected set for quick membership tests
296 std::vector<int> originalNets( padsCount );
297 std::unordered_set<PAD*> selectedPads;
298
299 for( size_t i = 0; i < padsCount; ++i )
300 {
301 originalNets[i] = pads[i]->GetNetCode();
302 selectedPads.insert( pads[i] );
303 }
304
305 if( std::ranges::all_of( originalNets,
306 [&]( int net )
307 {
308 return net == originalNets.front();
309 } ) )
310 return 0;
311
312 // Desired new nets are a cyclic rotation of original nets (like Swap positions)
313 auto newNetForIndex =
314 [&]( size_t i )
315 {
316 return originalNets[( i + 1 ) % padsCount];
317 };
318
319 // Take an event commit since we will eventually support this while actively routing the board
320 BOARD_COMMIT localCommit( this );
321 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
322
323 if( !commit )
324 commit = &localCommit;
325
326 // Connectivity to find items connected to each pad
327 std::shared_ptr<CONNECTIVITY_DATA> connectivity = board()->GetConnectivity();
328
329 // Accumulate changes: for each item, assign the resulting new net
330 std::unordered_map<BOARD_CONNECTED_ITEM*, int> itemNewNets;
331 std::vector<PAD*> nonSelectedPadsToChange;
332 wxString error;
333
334 // Include the direct pad changes so connectivity cannot assign them a conflicting net.
335 for( size_t i = 0; i < padsCount; ++i )
336 {
337 if( !ScheduleNetSwap( pads[i], newNetForIndex( i ), itemNewNets, error ) )
338 {
339 frame()->ShowInfoBarError( error );
340 return 0;
341 }
342 }
343
344 for( size_t i = 0; i < padsCount; ++i )
345 {
346 PAD* pad = pads[i];
347 int fromNet = originalNets[i];
348 int toNet = newNetForIndex( i );
349
350 // For each connected item, if it matches fromNet, schedule it for toNet
351 // Exclude zones, user probably doesn't want to change zone nets
352 for( BOARD_CONNECTED_ITEM* ci : connectivity->GetConnectedItems( pad, EXCLUDE_ZONES ) )
353 {
354 switch( ci->Type() )
355 {
356 case PCB_TRACE_T:
357 case PCB_ARC_T:
358 case PCB_VIA_T:
359 case PCB_PAD_T:
360 case PCB_SHAPE_T:
361 break;
362 default:
363 continue;
364 }
365
366 if( ci->GetNetCode() != fromNet )
367 continue;
368
369 if( !ScheduleNetSwap( ci, toNet, itemNewNets, error ) )
370 {
371 frame()->ShowInfoBarError( error );
372 return 0;
373 }
374
375 if( ci->Type() == PCB_PAD_T )
376 {
377 PAD* otherPad = static_cast<PAD*>( ci );
378
379 if( !selectedPads.count( otherPad ) )
380 nonSelectedPadsToChange.push_back( otherPad );
381 }
382 }
383 }
384
385 bool includeConnectedPads = true;
386
387 if( !PromptConnectedPadDecision( frame(), nonSelectedPadsToChange, _( "Swap Pad Nets" ), includeConnectedPads ) )
388 return 0;
389
390 if( !ValidateNetSwapLocks( frame(), itemNewNets, selectedPads, includeConnectedPads ) )
391 return 0;
392
393 // Apply changes
394 // 1) Selected pads get their new nets directly
395 for( size_t i = 0; i < padsCount; ++i )
396 {
397 if( pads[i]->GetNetCode() == newNetForIndex( i ) )
398 continue;
399
400 commit->Modify( pads[i] );
401 pads[i]->SetNetCode( newNetForIndex( i ) );
402 }
403
404 // 2) Connected items propagate, depending on user choice
405 for( const auto& itemNewNet : itemNewNets )
406 {
407 BOARD_CONNECTED_ITEM* item = itemNewNet.first;
408 int newNet = itemNewNet.second;
409
410 if( item->Type() == PCB_PAD_T )
411 {
412 PAD* p = static_cast<PAD*>( item );
413
414 if( selectedPads.count( p ) )
415 continue; // already changed above
416
417 if( !includeConnectedPads )
418 continue; // skip non-selected pads if requested
419 }
420
421 if( item->GetNetCode() == newNet )
422 continue;
423
424 commit->Modify( item );
425 item->SetNetCode( newNet );
426 }
427
428 if( !localCommit.Empty() )
429 localCommit.Push( _( "Swap Pad Nets" ) );
430
431 // Ensure connectivity visuals update
434
435 return 0;
436}
437
438
440{
441 if( isRouterActive() )
442 {
443 wxBell();
444 return 0;
445 }
446
447 auto showError =
448 [this]()
449 {
450 frame()->ShowInfoBarError( _( "Gate swapping must be performed on pads within one multi-gate "
451 "footprint." ) );
452 };
453
455
456 // Get our sanity checks out of the way to clean up later loops
457 FOOTPRINT* targetFp = nullptr;
458 bool fail = false;
459
460 for( EDA_ITEM* it : selection )
461 {
462 // This shouldn't happen due to the filter, but just in case
463 if( it->Type() != PCB_PAD_T )
464 {
465 fail = true;
466 break;
467 }
468
469 FOOTPRINT* fp = static_cast<PAD*>( static_cast<BOARD_ITEM*>( it ) )->GetParentFootprint();
470
471 if( !targetFp )
472 {
473 targetFp = fp;
474 }
475 else if( fp && targetFp != fp )
476 {
477 fail = true;
478 break;
479 }
480 }
481
482 if( fail || !targetFp || targetFp->GetUnitInfo().size() < 2 )
483 {
484 showError();
485 return 0;
486 }
487
488
489 const auto& units = targetFp->GetUnitInfo();
490
491 // Collect unit hits and ordered unit list based on selection order
492 std::vector<bool> unitHit( units.size(), false );
493 std::vector<int> unitOrder;
494
495 std::vector<EDA_ITEM*> orderedPads = selection.GetItemsSortedBySelectionOrder();
496
497 for( EDA_ITEM* it : orderedPads )
498 {
499 PAD* pad = static_cast<PAD*>( static_cast<BOARD_ITEM*>( it ) );
500
501 const wxString& padNum = pad->GetNumber();
502 int unitIdx = -1;
503
504 for( size_t i = 0; i < units.size(); ++i )
505 {
506 for( const auto& p : units[i].m_pins )
507 {
508 if( p == padNum )
509 {
510 unitIdx = static_cast<int>( i );
511
512 if( !unitHit[i] )
513 unitOrder.push_back( unitIdx );
514
515 unitHit[i] = true;
516 break;
517 }
518 }
519
520 if( unitIdx >= 0 )
521 break;
522 }
523 }
524
525 // Determine active units from selection order: 0 -> bail, 1 -> single-unit flow, 2+ -> cycle
526 std::vector<int> activeUnitIdx;
527 int sourceIdx = -1;
528
529 if( unitOrder.size() >= 2 )
530 {
531 activeUnitIdx = unitOrder;
532 sourceIdx = unitOrder.front();
533 }
534 // If we only have one gate selected, we must have a target unit name parameter to proceed
535 else if( unitOrder.size() == 1 && aEvent.HasParameter() )
536 {
537 sourceIdx = unitOrder.front();
538 wxString targetUnitByName = aEvent.Parameter<wxString>();
539
540 int targetIdx = -1;
541
542 for( size_t i = 0; i < units.size(); ++i )
543 {
544 if( static_cast<int>( i ) == sourceIdx )
545 continue;
546
547 if( units[i].m_pins.size() == units[sourceIdx].m_pins.size() && units[i].m_unitName == targetUnitByName )
548 targetIdx = static_cast<int>( i );
549 }
550
551 if( targetIdx < 0 )
552 {
553 showError();
554 return 0;
555 }
556
557 activeUnitIdx.push_back( sourceIdx );
558 activeUnitIdx.push_back( targetIdx );
559 }
560 else
561 {
562 showError();
563 return 0;
564 }
565
566 // Verify equal pin counts across all active units
567 const size_t pinCount = units[activeUnitIdx.front()].m_pins.size();
568
569 for( int idx : activeUnitIdx )
570 {
571 if( units[idx].m_pins.size() != pinCount )
572 {
573 frame()->ShowInfoBarError( _( "Gate swapping must be performed on gates with equal pin counts." ) );
574 return 0;
575 }
576 }
577
578 // Build per-unit pad arrays and net vectors
579 const size_t unitCount = activeUnitIdx.size();
580 std::vector<std::vector<PAD*>> unitPads( unitCount );
581 std::vector<std::vector<int>> unitNets( unitCount );
582
583 for( size_t ui = 0; ui < unitCount; ++ui )
584 {
585 int uidx = activeUnitIdx[ui];
586 const auto& pins = units[uidx].m_pins;
587
588 for( size_t pi = 0; pi < pinCount; ++pi )
589 {
590 PAD* p = targetFp->FindPadByNumber( pins[pi] );
591
592 if( !p )
593 {
594 frame()->ShowInfoBarError( _( "Gate swapping failed: pad in unit missing from footprint." ) );
595 return 0;
596 }
597
598 unitPads[ui].push_back( p );
599 unitNets[ui].push_back( p->GetNetCode() );
600 }
601 }
602
603 if( std::ranges::all_of( unitNets,
604 [&]( const auto& nets )
605 {
606 return nets == unitNets.front();
607 } ) )
608 {
609 frame()->ShowInfoBarError( _( "Gate swapping has no effect: all selected gates have identical nets." ) );
610 return 0;
611 }
612
613 // TODO: someday support swapping while routing and take that commit
614 BOARD_COMMIT localCommit( this );
615 BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
616
617 if( !commit )
618 commit = &localCommit;
619
620 std::shared_ptr<CONNECTIVITY_DATA> connectivity = board()->GetConnectivity();
621
622 // Accumulate changes: item -> new net
623 std::unordered_map<BOARD_CONNECTED_ITEM*, int> itemNewNets;
624 std::vector<PAD*> nonSelectedPadsToChange;
625 wxString error;
626
627 // Selected pads in the swap (for suppressing re-adding in connected pad handling)
628 std::unordered_set<PAD*> swapPads;
629
630 for( const auto& v : unitPads )
631 swapPads.insert( v.begin(), v.end() );
632
633 for( size_t pi = 0; pi < pinCount; ++pi )
634 {
635 for( size_t ui = 0; ui < unitCount; ++ui )
636 {
637 size_t toIdx = ( ui + 1 ) % unitCount;
638
639 if( !ScheduleNetSwap( unitPads[ui][pi], unitNets[toIdx][pi], itemNewNets, error ) )
640 {
641 frame()->ShowInfoBarError( error );
642 return 0;
643 }
644 }
645 }
646
647 // Schedule net swaps for connectivity-attached items
648 auto scheduleForPad = [&]( PAD* pad, int fromNet, int toNet ) -> bool
649 {
650 for( BOARD_CONNECTED_ITEM* ci : connectivity->GetConnectedItems( pad, EXCLUDE_ZONES ) )
651 {
652 switch( ci->Type() )
653 {
654 case PCB_TRACE_T:
655 case PCB_ARC_T:
656 case PCB_VIA_T:
657 case PCB_PAD_T:
658 case PCB_SHAPE_T:
659 break;
660
661 default:
662 continue;
663 }
664
665 if( ci->GetNetCode() != fromNet )
666 continue;
667
668 if( !ScheduleNetSwap( ci, toNet, itemNewNets, error ) )
669 return false;
670
671 if( ci->Type() == PCB_PAD_T )
672 {
673 PAD* other = static_cast<PAD*>( ci );
674
675 if( !swapPads.count( other ) )
676 nonSelectedPadsToChange.push_back( other );
677 }
678 }
679
680 return true;
681 };
682
683 // For each position, rotate nets among units forward
684 for( size_t pi = 0; pi < pinCount; ++pi )
685 {
686 for( size_t ui = 0; ui < unitCount; ++ui )
687 {
688 size_t fromIdx = ui;
689 size_t toIdx = ( ui + 1 ) % unitCount;
690
691 PAD* padFrom = unitPads[fromIdx][pi];
692 int fromNet = unitNets[fromIdx][pi];
693 int toNet = unitNets[toIdx][pi];
694
695 if( !scheduleForPad( padFrom, fromNet, toNet ) )
696 {
697 frame()->ShowInfoBarError( error );
698 return 0;
699 }
700 }
701 }
702
703 bool includeConnectedPads = true;
704
705 if( !PromptConnectedPadDecision( frame(), nonSelectedPadsToChange, _( "Swap Gate Nets" ), includeConnectedPads ) )
706 {
707 return 0;
708 }
709
710 if( !ValidateNetSwapLocks( frame(), itemNewNets, swapPads, includeConnectedPads ) )
711 return 0;
712
713 // Apply pad net swaps: rotate per position
714 for( size_t pi = 0; pi < pinCount; ++pi )
715 {
716 // First write back nets for each unit's pad at this position
717 for( size_t ui = 0; ui < unitCount; ++ui )
718 {
719 size_t toIdx = ( ui + 1 ) % unitCount;
720 PAD* pad = unitPads[ui][pi];
721 int newNet = unitNets[toIdx][pi];
722
723 if( pad->GetNetCode() == newNet )
724 continue;
725
726 commit->Modify( pad );
727 pad->SetNetCode( newNet );
728 }
729 }
730
731 // Apply connected items
732 for( const auto& kv : itemNewNets )
733 {
734 BOARD_CONNECTED_ITEM* item = kv.first;
735 int newNet = kv.second;
736
737 if( item->Type() == PCB_PAD_T )
738 {
739 PAD* p = static_cast<PAD*>( item );
740
741 if( swapPads.count( p ) )
742 continue;
743
744 if( !includeConnectedPads )
745 continue;
746 }
747
748 if( item->GetNetCode() == newNet )
749 continue;
750
751 commit->Modify( item );
752 item->SetNetCode( newNet );
753 }
754
755 if( !localCommit.Empty() )
756 localCommit.Push( _( "Swap Gate Nets" ) );
757
760
761 return 0;
762}
763
764
766{
767 if( isRouterActive() || m_dragging )
768 {
769 wxBell();
770 return 0;
771 }
772
773 BOARD_COMMIT commit( this );
774 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
775 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
776 {
777 sTool->FilterCollectorForMarkers( aCollector );
778 sTool->FilterCollectorForHierarchy( aCollector, true );
779 sTool->FilterCollectorForFreePads( aCollector, true );
780
781 // Iterate from the back so we don't have to worry about removals.
782 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
783 {
784 BOARD_ITEM* item = aCollector[i];
785
786 if( !dynamic_cast<FOOTPRINT*>( item ) )
787 aCollector.Remove( item );
788 }
789
790 sTool->FilterCollectorForLockedItems( aCollector );
791 } );
792
793 m_selectionTool->ReportFilteredLockedItems();
794
795 std::vector<FOOTPRINT*> footprintsToPack;
796
797 for( EDA_ITEM* item : selection )
798 footprintsToPack.push_back( static_cast<FOOTPRINT*>( item ) );
799
800 if( footprintsToPack.empty() )
801 return 0;
802
803 BOX2I footprintsBbox;
804
805 for( FOOTPRINT* fp : footprintsToPack )
806 {
807 commit.Modify( fp );
808 fp->SetFlags( IS_MOVING );
809 footprintsBbox.Merge( fp->GetBoundingBox( false ) );
810 }
811
812 SpreadFootprints( &footprintsToPack, footprintsBbox.Normalize().GetOrigin(), false );
813
814 if( doMoveSelection( aEvent, &commit, true ) )
815 commit.Push( _( "Pack Footprints" ) );
816 else
817 commit.Revert();
818
819 return 0;
820}
821
822
823int EDIT_TOOL::Move( const TOOL_EVENT& aEvent )
824{
825 if( isRouterActive() || m_dragging )
826 {
827 wxBell();
828 return 0;
829 }
830
831 if( BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() ) )
832 {
833 // Most moves will be synchronous unless they are coming from the API. Do not run the
834 // constraint solver here; this path contributes only the requested move to the
835 // caller-owned commit.
836 if( aEvent.SynchronousState() )
837 aEvent.SynchronousState()->store( STS_RUNNING );
838
839 if( doMoveSelection( aEvent, commit, true ) )
840 {
841 if( aEvent.SynchronousState() )
842 aEvent.SynchronousState()->store( STS_FINISHED );
843 }
844 else if( aEvent.SynchronousState() )
845 {
846 aEvent.SynchronousState()->store( STS_CANCELLED );
847 }
848 }
849 else
850 {
851 BOARD_COMMIT localCommit( this );
852
853 // doMoveSelection captures these from live selection before it is cleared
854 // so they stay valid even for a hover move whose selection does not survive the drag
855 std::vector<PCB_SHAPE*> constraintShapes;
856
857 if( doMoveSelection( aEvent, &localCommit, false, &constraintShapes ) )
858 {
859 // The last painted motion frame already contains the validated constrained cluster.
860 // Mouse-up must commit that exact state, including when a newer motion event is pending.
861 localCommit.Push( _( "Move" ) );
862
863 if( !constraintShapes.empty() && BoardHasConstraints( board() ) )
864 {
865 if( CONSTRAINT_EDIT_TOOL* constraintTool = m_toolMgr->GetTool<CONSTRAINT_EDIT_TOOL>() )
866 constraintTool->DiagnoseAfterMove( constraintShapes );
867 }
868 }
869 else
870 {
871 localCommit.Revert();
872 }
873 }
874
875 // Notify point editor. (While doMoveSelection() will re-select the items and post this
876 // event, it's done before the edit flags are cleared in BOARD_COMMIT::Push() so the point
877 // editor doesn't fire up.)
878 m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
879
880 return 0;
881}
882
883
884VECTOR2I EDIT_TOOL::getSafeMovement( const VECTOR2I& aMovement, const BOX2I& aSourceBBox,
885 const VECTOR2D& aBBoxOffset )
886{
887 typedef std::numeric_limits<int> coord_limits;
888
889 static const double max = coord_limits::max() - (int) COORDS_PADDING;
890 static const double min = -max;
891
892 BOX2D testBox( aSourceBBox.GetPosition(), aSourceBBox.GetSize() );
893 testBox.Offset( aBBoxOffset );
894
895 // Do not restrict movement if bounding box is already out of bounds
896 if( testBox.GetLeft() < min || testBox.GetTop() < min || testBox.GetRight() > max
897 || testBox.GetBottom() > max )
898 {
899 return aMovement;
900 }
901
902 testBox.Offset( aMovement );
903
904 if( testBox.GetLeft() < min )
905 testBox.Offset( min - testBox.GetLeft(), 0 );
906
907 if( max < testBox.GetRight() )
908 testBox.Offset( -( testBox.GetRight() - max ), 0 );
909
910 if( testBox.GetTop() < min )
911 testBox.Offset( 0, min - testBox.GetTop() );
912
913 if( max < testBox.GetBottom() )
914 testBox.Offset( 0, -( testBox.GetBottom() - max ) );
915
916 return KiROUND( testBox.GetPosition() - aBBoxOffset - aSourceBBox.GetPosition() );
917}
918
919
920bool EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, BOARD_COMMIT* aCommit, bool aAutoStart,
921 std::vector<PCB_SHAPE*>* aConstraintShapes )
922{
923 const bool moveWithReference = aEvent.IsAction( &PCB_ACTIONS::moveWithReference );
924 const bool moveIndividually = aEvent.IsAction( &PCB_ACTIONS::moveIndividually );
925
927 PCBNEW_SETTINGS* cfg = editFrame->GetPcbNewSettings();
928 BOARD* board = editFrame->GetBoard();
930 VECTOR2I originalCursorPos = controls->GetCursorPosition();
931 VECTOR2I originalMousePos = controls->GetMousePosition();
932 std::unique_ptr<STATUS_TEXT_POPUP> statusPopup;
933 size_t itemIdx = 0;
934
935 // Be sure that there is at least one item that we can modify. If nothing was selected before,
936 // try looking for the stuff under mouse cursor (i.e. KiCad old-style hover selection)
937 PCB_SELECTION& selection = m_selectionTool->RequestSelection(
938 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
939 {
940 sTool->FilterCollectorForMarkers( aCollector );
941 sTool->FilterCollectorForHierarchy( aCollector, true );
942 sTool->FilterCollectorForFreePads( aCollector );
943 sTool->FilterCollectorForTableCells( aCollector );
944 sTool->FilterCollectorForLockedItems( aCollector );
945 } );
946
947 if( m_dragging )
948 return false;
949
950 m_selectionTool->ReportFilteredLockedItems();
951
952 if( selection.Empty() )
953 return false;
954
955 TOOL_EVENT originalEvent = aEvent; // This can change out from under us when the event loop runs
956 SCOPED_TOOL_PUSHER raii( editFrame, originalEvent );
957 Activate();
958
959 // Must be done after Activate() so that it gets set into the correct context
960 controls->ShowCursor( true );
961 controls->SetAutoPan( true );
962 controls->ForceCursorPosition( false );
963
964 auto displayConstraintsMessage =
965 [editFrame]( LEADER_MODE aMode )
966 {
967 wxString msg;
968
969 switch( aMode )
970 {
972 msg = _( "Angle snap lines: 45°" );
973 break;
974
976 msg = _( "Angle snap lines: 90°" );
977 break;
978
979 default:
980 msg.clear();
981 break;
982 }
983
984 editFrame->DisplayConstraintsMsg( msg );
985 };
986
987 auto updateStatusPopup =
988 [&]( EDA_ITEM* item, size_t ii, size_t count )
989 {
990 wxString popuptext = _( "Click to place %s (item %zu of %zu)\n"
991 "Press <esc> to cancel all; double-click to finish" );
992 wxString msg;
993
994 if( item->Type() == PCB_FOOTPRINT_T )
995 {
996 FOOTPRINT* fp = static_cast<FOOTPRINT*>( item );
997 msg = fp->GetReference();
998 }
999 else if( item->Type() == PCB_PAD_T )
1000 {
1001 PAD* pad = static_cast<PAD*>( item );
1002 FOOTPRINT* fp = pad->GetParentFootprint();
1003 msg = wxString::Format( _( "%s pad %s" ), fp->GetReference(), pad->GetNumber() );
1004 }
1005 else
1006 {
1007 msg = item->GetTypeDesc().Lower();
1008 }
1009
1010 if( !statusPopup )
1011 statusPopup = std::make_unique<STATUS_TEXT_POPUP>( frame() );
1012
1013 statusPopup->SetText( wxString::Format( popuptext, msg, ii, count ) );
1014 };
1015
1016 std::vector<BOARD_ITEM*> sel_items; // All the items operated on by the move below
1017 std::vector<BOARD_ITEM*> orig_items; // All the original items in the selection
1018
1019 // Top-level items being moved. Used instead of selection flags, which can be cleared
1020 // mid-move by the find dialog (issue 24884).
1021 std::unordered_set<EDA_ITEM*> moved_items;
1022
1023 for( EDA_ITEM* item : selection )
1024 {
1025 if( item->IsBOARD_ITEM() )
1026 {
1027 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
1028
1029 if( !selection.IsHover() )
1030 orig_items.push_back( boardItem );
1031
1032 sel_items.push_back( boardItem );
1033 moved_items.insert( boardItem );
1034 }
1035
1036 if( item->Type() == PCB_FOOTPRINT_T )
1037 {
1038 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
1039
1040 for( PAD* pad : footprint->Pads() )
1041 sel_items.push_back( pad );
1042
1043 // Clear this flag here; it will be set by the netlist updater if the footprint is new
1044 // so that it was skipped in the initial connectivity update in OnNetlistChanged
1045 footprint->SetAttributes( footprint->GetAttributes() & ~FP_JUST_ADDED );
1046 }
1047 }
1048
1049 // Selection stays stable for whole drag so gather constrainable shapes once here and reuse them
1050 // each tick and for final settle solve hover moves clear selection before returning so capture now
1051 if( aConstraintShapes )
1052 collectConstraintShapes( selection, *aConstraintShapes );
1053
1054 VECTOR2I pickedReferencePoint;
1055
1056 if( moveWithReference && !pickReferencePoint( _( "Select reference point for move..." ), "", "",
1057 pickedReferencePoint ) )
1058 {
1059 if( selection.IsHover() )
1060 m_toolMgr->RunAction( ACTIONS::selectionClear );
1061
1062 return false;
1063 }
1064
1065 m_inMoveWithReference = moveWithReference;
1066
1067 if( moveIndividually )
1068 {
1069 orig_items.clear();
1070
1071 for( EDA_ITEM* item : selection.GetItemsSortedBySelectionOrder() )
1072 {
1073 if( item->IsBOARD_ITEM() )
1074 orig_items.push_back( static_cast<BOARD_ITEM*>( item ) );
1075 }
1076
1077 updateStatusPopup( orig_items[ itemIdx ], itemIdx + 1, orig_items.size() );
1078 statusPopup->Popup();
1079 statusPopup->Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, 20 ) );
1080 canvas()->SetStatusPopup( statusPopup->GetPanel() );
1081
1082 m_selectionTool->ClearSelection();
1083 m_selectionTool->AddItemToSel( orig_items[ itemIdx ] );
1084
1085 sel_items.clear();
1086 sel_items.push_back( orig_items[ itemIdx ] );
1087
1088 moved_items.clear();
1089 moved_items.insert( orig_items[itemIdx] );
1090 }
1091
1092 bool restore_state = false;
1093 VECTOR2I originalPos = originalCursorPos; // Initialize to current cursor position
1094 VECTOR2D bboxMovement;
1095 BOX2I originalBBox;
1096 bool updateBBox = true;
1097 LSET layers( { editFrame->GetActiveLayer() } );
1099 std::shared_ptr<BOARD_CONSTRAINT_MOVE_SESSION> constraintMoveSession;
1100
1101 // Scans every footprint, and cannot change for the duration of the move
1102 const bool boardHasConstraints = BoardHasConstraints( board );
1103
1104 TOOL_EVENT copy = aEvent;
1105 TOOL_EVENT* evt = &copy;
1106 VECTOR2I prevPos;
1107 bool enableLocalRatsnest = true;
1108
1109 // Frame-aware orientation tracking (mirrors BOARD_EDITOR_CONTROL::PlaceFootprint).
1110 // A single footprint (plus, at most, its own pads) rotates about its own position;
1111 // any other selection containing footprints rotates as a whole about the pick-up
1112 // point, just like manual rotation during a move.
1113 auto findSingleFp =
1114 [&]() -> FOOTPRINT*
1115 {
1116 FOOTPRINT* singleFp = nullptr;
1117
1118 for( BOARD_ITEM* it : sel_items )
1119 {
1120 if( it->Type() == PCB_FOOTPRINT_T )
1121 {
1122 if( singleFp )
1123 return nullptr; // more than one footprint
1124
1125 singleFp = static_cast<FOOTPRINT*>( it );
1126 }
1127 else if( it->Type() != PCB_PAD_T )
1128 {
1129 return nullptr; // mixed selection
1130 }
1131 }
1132
1133 for( BOARD_ITEM* it : sel_items )
1134 {
1135 if( it->Type() == PCB_PAD_T && it->GetParentFootprint() != singleFp )
1136 return nullptr; // free pad of another footprint
1137 }
1138
1139 return singleFp;
1140 };
1141
1142 auto selectionHasFp =
1143 [&]()
1144 {
1145 return std::any_of( sel_items.begin(), sel_items.end(),
1146 []( BOARD_ITEM* it )
1147 {
1148 return it->Type() == PCB_FOOTPRINT_T;
1149 } );
1150 };
1151
1152 // Capture the frame angle at the PICK-UP position: a footprint inside a rotated/polar
1153 // grid already carries that frame's orientation, so the first cursor move must
1154 // not rotate it again. frameFp/frameRotate are recomputed whenever sel_items
1155 // changes (moveIndividually item switch).
1156 EDA_ANGLE prevFrameAngle = ANGLE_0;
1157 FOOTPRINT* frameFp = findSingleFp();
1158 bool frameRotate = frameFp || selectionHasFp();
1159
1160 if( frameRotate )
1161 {
1162 prevFrameAngle = GridFrameAngleAt( *board, frameFp ? frameFp->GetPosition() : originalCursorPos,
1164 }
1165
1166 auto applyMoveFrameOrientation =
1167 [&]()
1168 {
1169 if( !frameRotate )
1170 return;
1171
1172 // m_cursor is the pick-up point dragged along with the selection.
1173 VECTOR2I pivot = frameFp ? frameFp->GetPosition() : m_cursor;
1175 EDA_ANGLE delta = GridFrameRotationDelta( prevFrameAngle, newAngle, editFrame->GetRotationAngle() );
1176
1177 prevFrameAngle = newAngle;
1178
1179 if( delta.IsZero() )
1180 return;
1181
1182 if( frameFp )
1183 {
1184 frameFp->Rotate( pivot, delta );
1185 }
1186 else
1187 {
1188 for( BOARD_ITEM* item : sel_items )
1189 {
1190 // Don't double rotate child items.
1191 if( !item->GetParent() || !moved_items.count( item->GetParent() ) )
1192 item->Rotate( pivot, delta );
1193 }
1194 }
1195 };
1196
1197 LEADER_MODE angleSnapMode = GetAngleSnapMode();
1198 bool eatFirstMouseUp = true;
1199 bool allowRedraw3D = cfg->m_Display.m_Live3DRefresh;
1200 bool showCourtyardConflicts = !m_isFootprintEditor && cfg->m_ShowCourtyardCollisions;
1201
1202 const auto buildConstraintMoveSession =
1203 [&]( const VECTOR2I& aReference )
1204 {
1205 grid.SetFeasibilityCallback( {} );
1206 constraintMoveSession.reset();
1207
1208 if( moveIndividually || !aConstraintShapes || aConstraintShapes->empty()
1209 || !boardHasConstraints )
1210 {
1211 return;
1212 }
1213
1214 auto session = std::make_shared<BOARD_CONSTRAINT_MOVE_SESSION>();
1215
1216 if( session->Build( board, *aConstraintShapes, aReference ) )
1217 {
1218 constraintMoveSession = session;
1219 grid.SetFeasibilityCallback(
1220 [session]( const SNAP_SOURCE_CONTEXT& aContext,
1221 const std::vector<SNAP_CANDIDATE>& aCandidates )
1222 {
1223 return session->ResolveCandidates( aContext, aCandidates );
1224 } );
1225 }
1226 };
1227
1228 // Axis locking for arrow key movement
1229 enum class AXIS_LOCK { NONE, HORIZONTAL, VERTICAL };
1230 AXIS_LOCK axisLock = AXIS_LOCK::NONE;
1231 long lastArrowKeyAction = 0;
1232
1233 // The footprint editor has no DRC_TOOL, so the engine may be unavailable.
1234 DRC_TOOL* drcTool = m_toolMgr->GetTool<DRC_TOOL>();
1235 std::shared_ptr<DRC_ENGINE> drcEngine = drcTool ? drcTool->GetDRCEngine() : nullptr;
1236
1237 // Used to test courtyard overlaps
1238 std::unique_ptr<DRC_INTERACTIVE_COURTYARD_CLEARANCE> drc_on_move = nullptr;
1239
1240 if( showCourtyardConflicts )
1241 {
1242 drc_on_move.reset( new DRC_INTERACTIVE_COURTYARD_CLEARANCE( drcEngine ) );
1243 drc_on_move->Init( board );
1244 }
1245
1246 // No-op unless RealtimeCreepage is set and the board has creepage constraints
1247 std::unique_ptr<CREEPAGE_OVERLAY> creepage_on_move = std::make_unique<CREEPAGE_OVERLAY>( board, drcEngine,
1248 m_toolMgr->GetView() );
1249
1250 auto configureAngleSnap =
1251 [&]( LEADER_MODE aMode )
1252 {
1253 std::vector<VECTOR2I> directions;
1254
1255 switch( aMode )
1256 {
1257 case LEADER_MODE::DEG45:
1258 directions = { VECTOR2I( 1, 0 ), VECTOR2I( 0, 1 ), VECTOR2I( 1, 1 ), VECTOR2I( 1, -1 ) };
1259 break;
1260
1261 case LEADER_MODE::DEG90:
1262 directions = { VECTOR2I( 1, 0 ), VECTOR2I( 0, 1 ) };
1263 break;
1264
1265 default:
1266 break;
1267 }
1268
1269 grid.SetSnapLineDirections( directions );
1270
1271 if( directions.empty() )
1272 grid.ClearSnapLine();
1273 else
1274 grid.SetSnapLineOrigin( originalPos );
1275 };
1276
1277 configureAngleSnap( angleSnapMode );
1278 displayConstraintsMessage( angleSnapMode );
1279
1280 // Prime the pump
1281 m_toolMgr->PostAction( ACTIONS::refreshPreview );
1282
1283 // Main loop: keep receiving events
1284 do
1285 {
1286 VECTOR2I movement;
1288 grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
1289 grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
1290
1291 bool isSkip = evt->IsAction( &PCB_ACTIONS::skip ) && moveIndividually;
1292
1293 if( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) )
1294 eatFirstMouseUp = false;
1295
1296 if( evt->IsAction( &PCB_ACTIONS::move )
1297 || evt->IsMotion()
1298 || evt->IsDrag( BUT_LEFT )
1302 {
1303 if( m_dragging && ( evt->IsMotion()
1304 || evt->IsDrag( BUT_LEFT )
1305 || evt->IsAction( &ACTIONS::refreshPreview ) ) )
1306 {
1307 bool redraw3D = false;
1308
1309 GRID_HELPER_GRIDS selectionGrid = grid.GetSelectionGrid( selection );
1310
1311 if( controls->GetSettings().m_lastKeyboardCursorPositionValid )
1312 {
1313 VECTOR2I keyboardPos( controls->GetSettings().m_lastKeyboardCursorPosition );
1314
1315 grid.SetSnap( false );
1316
1317 // Use the keyboard position directly without grid alignment. The position
1318 // was already calculated correctly in CursorControl by adding the grid step
1319 // to the current position. Aligning to grid here would snap to the nearest
1320 // grid point, which causes precision errors when the original position is
1321 // not on a grid point (issue #22805).
1322 m_cursor = keyboardPos;
1323
1324 // Update axis lock based on arrow key press, but skip on refreshPreview
1325 // to avoid double-processing when CursorControl posts refreshPreview after
1326 // handling the arrow key.
1327 if( !evt->IsAction( &ACTIONS::refreshPreview ) )
1328 {
1329 long action = controls->GetSettings().m_lastKeyboardCursorCommand;
1330
1331 if( action == ACTIONS::CURSOR_LEFT || action == ACTIONS::CURSOR_RIGHT )
1332 {
1333 if( axisLock == AXIS_LOCK::HORIZONTAL )
1334 {
1335 // Check if opposite horizontal key pressed to unlock
1336 if( ( lastArrowKeyAction == ACTIONS::CURSOR_LEFT && action == ACTIONS::CURSOR_RIGHT ) ||
1337 ( lastArrowKeyAction == ACTIONS::CURSOR_RIGHT && action == ACTIONS::CURSOR_LEFT ) )
1338 {
1339 axisLock = AXIS_LOCK::NONE;
1340 }
1341 // Same direction axis, keep locked
1342 }
1343 else
1344 {
1345 axisLock = AXIS_LOCK::HORIZONTAL;
1346 }
1347 }
1348 else if( action == ACTIONS::CURSOR_UP || action == ACTIONS::CURSOR_DOWN )
1349 {
1350 if( axisLock == AXIS_LOCK::VERTICAL )
1351 {
1352 // Check if opposite vertical key pressed to unlock
1353 if( ( lastArrowKeyAction == ACTIONS::CURSOR_UP && action == ACTIONS::CURSOR_DOWN ) ||
1354 ( lastArrowKeyAction == ACTIONS::CURSOR_DOWN && action == ACTIONS::CURSOR_UP ) )
1355 {
1356 axisLock = AXIS_LOCK::NONE;
1357 }
1358 // Same direction axis, keep locked
1359 }
1360 else
1361 {
1362 axisLock = AXIS_LOCK::VERTICAL;
1363 }
1364 }
1365
1366 lastArrowKeyAction = action;
1367 }
1368 }
1369 else
1370 {
1371 VECTOR2I mousePos( controls->GetMousePosition() );
1372
1373 m_cursor = grid.ResolveSnap( mousePos, layers, selectionGrid, sel_items, prevPos ).position;
1374 }
1375
1376 if( axisLock == AXIS_LOCK::HORIZONTAL )
1377 m_cursor.y = prevPos.y;
1378 else if( axisLock == AXIS_LOCK::VERTICAL )
1379 m_cursor.x = prevPos.x;
1380
1381 if( !selection.HasReferencePoint() )
1382 originalPos = m_cursor;
1383
1384 if( updateBBox )
1385 {
1386 originalBBox = BOX2I();
1387 bboxMovement = VECTOR2D();
1388
1389 for( EDA_ITEM* item : sel_items )
1390 originalBBox.Merge( item->ViewBBox() );
1391
1392 updateBBox = false;
1393 }
1394
1395 // Constrain selection bounding box to coordinates limits
1396 VECTOR2I previousCursor = prevPos;
1397 movement = getSafeMovement( m_cursor - prevPos, originalBBox, bboxMovement );
1398
1399 // Apply constrained movement
1400 m_cursor = prevPos + movement;
1401
1402 controls->ForceCursorPosition( true, m_cursor );
1403 selection.SetReferencePoint( m_cursor );
1404
1405 prevPos = m_cursor;
1406 bboxMovement += movement;
1407
1408 // Drag items to the current cursor position
1409 for( BOARD_ITEM* item : sel_items )
1410 {
1411 // Don't double move child items.
1412 if( !item->GetParent() || !moved_items.count( item->GetParent() ) )
1413 {
1414 item->Move( movement );
1415
1416 // Images and grid items are on non-cached layers and will not be updated automatically in
1417 // the overlay, so explicitly tell the view they've moved.
1418 if( item->Type() == PCB_REFERENCE_IMAGE_T || item->Type() == PCB_GRID_ITEM_T
1419 || item->Type() == PCB_DRILL_MAP_T )
1420 view()->Update( item, KIGFX::GEOMETRY );
1421 }
1422
1423 if( item->Type() == PCB_GENERATOR_T && sel_items.size() == 1 )
1424 {
1425 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genUpdateEdit, aCommit,
1426 static_cast<PCB_GENERATOR*>( item ) );
1427 }
1428
1429 if( item->Type() == PCB_FOOTPRINT_T )
1430 redraw3D = true;
1431 }
1432
1433 // Constrained neighbors sit unselected with no IS_MOVING flag outside the move overlay
1434 // only local commit drag previews them stage touched neighbors so cancel Revert restores them
1435 if( aConstraintShapes && !aConstraintShapes->empty() && movement != VECTOR2I()
1436 && boardHasConstraints )
1437 {
1438 std::vector<PCB_SHAPE*> solved;
1439 std::vector<BOARD_ITEM*> dimensions;
1440
1441 const auto beforeConstraintModify =
1442 [&]( BOARD_ITEM* aItem )
1443 {
1444 aCommit->Modify( aItem );
1445
1446 // A remeasured dimension is not returned in solved so refresh it here
1447 // or it looks frozen until the drag ends
1448 if( aItem->Type() != PCB_SHAPE_T )
1449 dimensions.push_back( aItem );
1450 };
1451
1452 bool solvedConstraints =
1453 constraintMoveSession
1454 ? constraintMoveSession->Solve( m_cursor, &solved, beforeConstraintModify )
1455 : ReSolveShapeClustersHoldingEdited( board, *aConstraintShapes, &solved,
1456 beforeConstraintModify );
1457
1458 if( solvedConstraints )
1459 {
1460 for( PCB_SHAPE* neighbor : solved )
1461 view()->Update( neighbor, KIGFX::GEOMETRY );
1462
1463 for( BOARD_ITEM* dimension : dimensions )
1464 view()->Update( dimension, KIGFX::GEOMETRY );
1465 }
1466 else
1467 {
1468 for( BOARD_ITEM* item : sel_items )
1469 {
1470 if( !item->GetParent() || !moved_items.count( item->GetParent() ) )
1471 item->Move( -movement );
1472 }
1473
1474 m_cursor = previousCursor;
1475 prevPos = previousCursor;
1476 bboxMovement -= movement;
1477 movement = VECTOR2I();
1478 selection.SetReferencePoint( m_cursor );
1479 controls->ForceCursorPosition( true, m_cursor );
1480 grid.ClearSnapFeedback();
1481 }
1482 }
1483
1484 applyMoveFrameOrientation();
1485
1486 if( redraw3D && allowRedraw3D )
1487 editFrame->Update3DView( false, true );
1488
1489 if( showCourtyardConflicts && drc_on_move->m_FpInMove.size() )
1490 {
1491 drc_on_move->Run();
1492 drc_on_move->UpdateConflicts( m_toolMgr->GetView(), true );
1493 }
1494
1495 creepage_on_move->Update();
1496
1498 }
1499 else if( !m_dragging && ( aAutoStart || !evt->IsAction( &ACTIONS::refreshPreview ) ) )
1500 {
1501 // Prepare to start dragging
1502 editFrame->HideSolderMask();
1503
1504 m_dragging = true;
1505
1506 for( BOARD_ITEM* item : sel_items )
1507 {
1508 if( item->GetParent() && moved_items.count( item->GetParent() ) )
1509 continue;
1510
1511 if( !item->IsNew() && !item->IsMoving() )
1512 {
1513 if( item->Type() == PCB_GENERATOR_T && sel_items.size() == 1 )
1514 {
1515 enableLocalRatsnest = false;
1516
1517 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genStartEdit, aCommit,
1518 static_cast<PCB_GENERATOR*>( item ) );
1519 }
1520 else
1521 {
1522 aCommit->Modify( item, nullptr, RECURSE_MODE::RECURSE );
1523 }
1524
1525 item->SetFlags( IS_MOVING );
1526
1527 if( item->Type() == PCB_SHAPE_T )
1528 static_cast<PCB_SHAPE*>( item )->UpdateHatching();
1529
1530 item->RunOnChildren(
1531 [&]( BOARD_ITEM* child )
1532 {
1533 child->SetFlags( IS_MOVING );
1534
1535 if( child->Type() == PCB_SHAPE_T )
1536 static_cast<PCB_SHAPE*>( child )->UpdateHatching();
1537 },
1539 }
1540 }
1541
1542 m_cursor = controls->GetCursorPosition();
1543
1544 if( selection.HasReferencePoint() )
1545 {
1546 // start moving with the reference point attached to the cursor
1547 grid.SetAuxAxes( false );
1548 buildConstraintMoveSession( selection.GetReferencePoint() );
1549
1550 movement = m_cursor - selection.GetReferencePoint();
1551
1552 // Drag items to the current cursor position
1553 for( EDA_ITEM* item : selection )
1554 {
1555 if( !item->IsBOARD_ITEM() )
1556 continue;
1557
1558 // Don't double move footprint pads, fields, etc.
1559 if( item->GetParent() && moved_items.count( item->GetParent() ) )
1560 continue;
1561
1562 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
1563 boardItem->Move( movement );
1564
1565 // Images are on non-cached layers and will not be updated automatically in the overlay, so
1566 // explicitly tell the view they've moved.
1567 if( boardItem->Type() == PCB_REFERENCE_IMAGE_T )
1568 view()->Update( boardItem, KIGFX::GEOMETRY );
1569 }
1570
1571 applyMoveFrameOrientation();
1572 selection.SetReferencePoint( m_cursor );
1573 }
1574 else
1575 {
1576 if( showCourtyardConflicts )
1577 {
1578 std::vector<FOOTPRINT*>& FPs = drc_on_move->m_FpInMove;
1579
1580 for( BOARD_ITEM* item : sel_items )
1581 {
1582 if( item->Type() == PCB_FOOTPRINT_T )
1583 FPs.push_back( static_cast<FOOTPRINT*>( item ) );
1584
1585 item->RunOnChildren(
1586 [&]( BOARD_ITEM* child )
1587 {
1588 if( child->Type() == PCB_FOOTPRINT_T )
1589 FPs.push_back( static_cast<FOOTPRINT*>( child ) );
1590 },
1592 }
1593 }
1594
1595 creepage_on_move->Start( sel_items );
1596
1597 // Use the mouse position over cursor, as otherwise large grids will allow only
1598 // snapping to items that are closest to grid points
1599 m_cursor = grid.BestDragOrigin( originalMousePos, sel_items, grid.GetSelectionGrid( selection ),
1600 &m_selectionTool->GetFilter() );
1601
1602 // Set the current cursor position to the first dragged item origin, so the
1603 // movement vector could be computed later
1604 if( moveWithReference )
1605 {
1606 selection.SetReferencePoint( pickedReferencePoint );
1607
1608 if( angleSnapMode != LEADER_MODE::DIRECT )
1609 grid.SetSnapLineOrigin( selection.GetReferencePoint() );
1610
1611 controls->ForceCursorPosition( true, pickedReferencePoint );
1612 m_cursor = pickedReferencePoint;
1613 }
1614 else
1615 {
1616 VECTOR2I dragOrigin = m_cursor;
1617
1618 selection.SetReferencePoint( dragOrigin );
1619
1620 if( angleSnapMode != LEADER_MODE::DIRECT )
1621 grid.SetSnapLineOrigin( dragOrigin );
1622
1623 grid.SetAuxAxes( true, dragOrigin );
1624
1625 if( !editFrame->GetMoveWarpsCursor() )
1626 m_cursor = originalCursorPos;
1627 else
1628 m_cursor = dragOrigin;
1629 }
1630
1631 originalPos = selection.GetReferencePoint();
1632 buildConstraintMoveSession( originalPos );
1633 }
1634
1635 // Update variables for bounding box collision calculations
1636 updateBBox = true;
1637
1638 controls->SetCursorPosition( m_cursor, false );
1639
1640 prevPos = m_cursor;
1641 controls->SetAutoPan( true );
1643 }
1644
1645 if( statusPopup )
1646 statusPopup->Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, 20 ) );
1647
1648 if( enableLocalRatsnest )
1649 m_toolMgr->PostAction( PCB_ACTIONS::updateLocalRatsnest, movement );
1650 }
1651 else if( evt->IsCancelInteractive() || evt->IsActivate() )
1652 {
1653 if( m_dragging && evt->IsCancelInteractive() )
1654 evt->SetPassEvent( false );
1655
1656 restore_state = true; // Canceling the tool means that items have to be restored
1657 break; // Finish
1658 }
1659 else if( evt->IsClick( BUT_RIGHT ) )
1660 {
1661 m_selectionTool->GetToolMenu().ShowContextMenu( selection );
1662 }
1663 else if( evt->IsAction( &ACTIONS::undo ) )
1664 {
1665 restore_state = true; // Perform undo locally
1666 break; // Finish
1667 }
1668 else if( evt->IsAction( &ACTIONS::doDelete ) )
1669 {
1670 evt->SetPassEvent();
1671 // Exit on a delete; there will no longer be anything to drag.
1672 break;
1673 }
1674 else if( evt->IsAction( &ACTIONS::duplicate ) && evt != &copy )
1675 {
1676 wxBell();
1677 }
1678 else if( evt->IsAction( &ACTIONS::cut ) )
1679 {
1680 wxBell();
1681 }
1682 else if( evt->IsAction( &PCB_ACTIONS::rotateCw )
1684 || evt->IsAction( &PCB_ACTIONS::flip )
1685 || evt->IsAction( &PCB_ACTIONS::mirrorH )
1686 || evt->IsAction( &PCB_ACTIONS::mirrorV ) )
1687 {
1688 updateBBox = true;
1689 eatFirstMouseUp = false;
1690 evt->SetPassEvent();
1691 }
1692 else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) || isSkip )
1693 {
1694 // Eat mouse-up/-click events that leaked through from the lock dialog
1695 if( eatFirstMouseUp && !evt->IsAction( &ACTIONS::cursorClick ) )
1696 {
1697 eatFirstMouseUp = false;
1698 continue;
1699 }
1700 else if( moveIndividually && m_dragging )
1701 {
1702 // Put skipped items back where they started
1703 if( isSkip )
1704 orig_items[itemIdx]->SetPosition( originalPos );
1705
1706 view()->Update( orig_items[itemIdx] );
1708
1709 if( ++itemIdx < orig_items.size() )
1710 {
1711 BOARD_ITEM* nextItem = orig_items[itemIdx];
1712
1713 m_selectionTool->ClearSelection();
1714
1715 originalPos = nextItem->GetPosition();
1716 m_selectionTool->AddItemToSel( nextItem );
1717 selection.SetReferencePoint( originalPos );
1718
1719 if( angleSnapMode != LEADER_MODE::DIRECT )
1720 grid.SetSnapLineOrigin( selection.GetReferencePoint() );
1721
1722 sel_items.clear();
1723 sel_items.push_back( nextItem );
1724
1725 moved_items.clear();
1726 moved_items.insert( nextItem );
1727 updateStatusPopup( nextItem, itemIdx + 1, orig_items.size() );
1728
1729 // Re-capture the frame angle at the new item's pick-up position.
1730 frameFp = findSingleFp();
1731 frameRotate = frameFp || selectionHasFp();
1732
1733 if( frameRotate )
1734 {
1735 prevFrameAngle = GridFrameAngleAt( *board, frameFp ? frameFp->GetPosition() : originalPos,
1737 }
1738
1739 // Pick up new item
1740 aCommit->Modify( nextItem, nullptr, RECURSE_MODE::RECURSE );
1741 nextItem->Move( controls->GetCursorPosition( true ) - nextItem->GetPosition() );
1742
1743 // Images are on non-cached layers and will not be updated automatically in the overlay, so
1744 // explicitly tell the view they've moved.
1745 if( nextItem->Type() == PCB_REFERENCE_IMAGE_T )
1746 view()->Update( nextItem, KIGFX::GEOMETRY );
1747
1748 continue;
1749 }
1750 }
1751
1752 break; // finish
1753 }
1754 else if( evt->IsDblClick( BUT_LEFT ) || evt->IsAction( &ACTIONS::cursorDblClick ) )
1755 {
1756 // The first click will move the new item, so put it back
1757 if( moveIndividually )
1758 orig_items[itemIdx]->SetPosition( originalPos );
1759
1760 break; // finish
1761 }
1763 {
1764 angleSnapMode = GetAngleSnapMode();
1765 configureAngleSnap( angleSnapMode );
1766 displayConstraintsMessage( angleSnapMode );
1767 evt->SetPassEvent( true );
1768 }
1769 else if( evt->IsAction( &ACTIONS::increment ) )
1770 {
1771 if( evt->HasParameter() )
1772 m_toolMgr->RunSynchronousAction( ACTIONS::increment, aCommit, evt->Parameter<ACTIONS::INCREMENT>() );
1773 else
1774 m_toolMgr->RunSynchronousAction( ACTIONS::increment, aCommit, ACTIONS::INCREMENT { 1, 0 } );
1775 }
1782 || evt->IsAction( &ACTIONS::find )
1783 || evt->IsAction( &ACTIONS::findNext )
1785 || evt->IsAction( &ACTIONS::redo ) )
1786 {
1787 wxBell();
1788 }
1789 else
1790 {
1791 evt->SetPassEvent();
1792 }
1793
1794 } while( ( evt = Wait() ) ); // Assignment (instead of equality test) is intentional
1795
1796 // Clear temporary COURTYARD_CONFLICT flag and ensure the conflict shadow is cleared
1797 if( showCourtyardConflicts )
1798 drc_on_move->ClearConflicts( m_toolMgr->GetView() );
1799
1800 creepage_on_move->Stop();
1801
1802 controls->ForceCursorPosition( false );
1803 controls->ShowCursor( false );
1804 controls->SetAutoPan( false );
1805
1806 m_dragging = false;
1807
1808 // Discard reference point when selection is "dropped" onto the board
1809 selection.ClearReferencePoint();
1810
1811 // Unselect all items to clear selection flags and then re-select the originally selected
1812 // items.
1813 m_toolMgr->RunAction( ACTIONS::selectionClear );
1814
1815 if( restore_state )
1816 {
1817 if( sel_items.size() == 1 && sel_items.back()->Type() == PCB_GENERATOR_T )
1818 {
1819 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genCancelEdit, aCommit,
1820 static_cast<PCB_GENERATOR*>( sel_items.back() ) );
1821 }
1822 }
1823 else
1824 {
1825 if( sel_items.size() == 1 && sel_items.back()->Type() == PCB_GENERATOR_T )
1826 {
1827 m_toolMgr->RunSynchronousAction( PCB_ACTIONS::genFinishEdit, aCommit,
1828 static_cast<PCB_GENERATOR*>( sel_items.back() ) );
1829 }
1830
1831 // If any moved item is the child of a generator that allows individual selection
1832 // (e.g. a via-stitch via), regenerate the parent so it can react to the new child
1833 // position (the via stitch generator infers its grid offset from the dragged via).
1834 std::set<PCB_GENERATOR*> regenParents;
1835
1836 for( BOARD_ITEM* item : sel_items )
1837 {
1838 EDA_GROUP* parent = item->GetParentGroup();
1839
1840 if( !parent )
1841 continue;
1842
1843 PCB_GENERATOR* gen = dynamic_cast<PCB_GENERATOR*>( parent->AsEdaItem() );
1844
1845 if( gen && gen->ChildrenAreIndividuallySelectable() )
1846 regenParents.insert( gen );
1847 }
1848
1849 if( !regenParents.empty() )
1850 {
1851 GENERATOR_TOOL* genTool = m_toolMgr->GetTool<GENERATOR_TOOL>();
1852
1853 for( PCB_GENERATOR* gen : regenParents )
1854 {
1855 gen->EditStart( genTool, board, aCommit );
1856 gen->Update( genTool, board, aCommit );
1857 gen->EditFinish( genTool, board, aCommit );
1858 }
1859 }
1860
1861 EDA_ITEMS oItems( orig_items.begin(), orig_items.end() );
1862 m_toolMgr->RunAction<EDA_ITEMS*>( ACTIONS::selectItems, &oItems );
1863 }
1864
1865 // Remove the dynamic ratsnest from the screen
1867
1869
1870 m_inMoveWithReference = false;
1871 return !restore_state;
1872}
bool BoardHasConstraints(BOARD *aBoard)
True if the board or any of its footprints carries at least one geometric constraint.
bool ReSolveShapeClustersHoldingEdited(BOARD *aBoard, const std::vector< PCB_SHAPE * > &aEditedShapes, std::vector< PCB_SHAPE * > *aModified, const std::function< void(BOARD_ITEM *)> &aBeforeModify)
Re-solve clusters whose new geometry is authoritative holding every edited shape fully fixed so only ...
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
BOX2< VECTOR2D > BOX2D
Definition box2.h:915
static TOOL_ACTION findPrevious
Definition actions.h:116
static TOOL_ACTION findNext
Definition actions.h:115
static TOOL_ACTION cursorDblClick
Definition actions.h:177
@ 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: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
static TOOL_ACTION find
Definition actions.h:113
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
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.
virtual void Move(const VECTOR2I &aMoveVector)
Move this object.
Definition board_item.h:436
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:374
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
constexpr const Vec & GetPosition() const
Definition box2.h:208
constexpr BOX2< Vec > & Normalize()
Ensure that the height and width are positive.
Definition box2.h:143
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:594
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr const Vec & GetOrigin() const
Definition box2.h:207
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr const SizeVec & GetSize() const
Definition box2.h:203
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr void Offset(coord_type dx, coord_type dy)
Definition box2.h:256
constexpr coord_type GetBottom() const
Definition box2.h:219
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:145
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
Interactive authoring of geometric constraints (issue #2329).
std::shared_ptr< DRC_ENGINE > GetDRCEngine()
Definition drc_tool.h:83
void ShowInfoBarError(const wxString &aErrorMsg, bool aShowCloseButton=false, INFOBAR_MESSAGE_TYPE aType=INFOBAR_MESSAGE_TYPE::GENERIC)
Show the WX_INFOBAR displayed on the top of the canvas with a message and an error icon on the left o...
void DisplayConstraintsMsg(const wxString &msg)
bool GetOverrideLocks() const
void SetCurrentCursor(KICURSOR aCursor)
Set the current cursor shape for this panel.
A set of EDA_ITEMs (i.e., without duplicates).
Definition eda_group.h:43
virtual EDA_ITEM * AsEdaItem()=0
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
virtual VECTOR2I GetPosition() const
Definition eda_item.h:348
virtual void SetPosition(const VECTOR2I &aPos)
Definition eda_item.h:349
wxString GetTypeDesc() const
Return a translated description of the type for this EDA_ITEM for display in user facing messages.
Definition eda_item.cpp:556
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:158
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:116
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
EDA_ITEM * GetParent() const
Definition eda_item.h:112
virtual const BOX2I ViewBBox() const override
Return the bounding box of the item covering all its layers.
Definition eda_item.cpp:509
bool IsMoving() const
Definition eda_item.h:132
bool IsNew() const
Definition eda_item.h:131
static void collectConstraintShapes(const SELECTION &aSelection, std::vector< PCB_SHAPE * > &aShapes)
Collect constrainable PCB_SHAPEs in aSelection recursing groups and footprints so contained shapes se...
bool isRouterActive() const
int SwapGateNets(const TOOL_EVENT &aEvent)
int Swap(const TOOL_EVENT &aEvent)
Swap currently selected items' positions.
bool m_inMoveWithReference
Definition edit_tool.h:252
int PackAndMoveFootprints(const TOOL_EVENT &aEvent)
Try to fit selected footprints inside a minimal area and start movement.
bool doMoveSelection(const TOOL_EVENT &aEvent, BOARD_COMMIT *aCommit, bool aAutoStart, std::vector< PCB_SHAPE * > *aConstraintShapes=nullptr)
Runs interactive move drag when aConstraintShapes is non null previews constraint solve live each tic...
bool pickReferencePoint(const wxString &aTooltip, const wxString &aSuccessMessage, const wxString &aCanceledMessage, VECTOR2I &aReferencePoint)
bool m_dragging
Definition edit_tool.h:251
int Move(const TOOL_EVENT &aEvent)
Main loop in which events are handled.
static const unsigned int COORDS_PADDING
Definition edit_tool.h:257
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:253
void rebuildConnectivity()
Rebuilds the ratsnest for operations that require it outside the commit rebuild.
PCB_SELECTION_TOOL * m_selectionTool
Definition edit_tool.h:250
static const TOOL_EVENT SelectedEvent
Definition actions.h:345
static const TOOL_EVENT SelectedItemsModified
Selected item had a property changed (except movement)
Definition actions.h:352
static const TOOL_EVENT SelectedItemsMoved
Selected items were moved, this can be very high frequency on the canvas, use with care.
Definition actions.h:355
EDA_ANGLE GetOrientation() const
Definition footprint.h:439
void SetOrientation(const EDA_ANGLE &aNewAngle)
const std::vector< FP_UNIT_INFO > & GetUnitInfo() const
Definition footprint.h:1025
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
bool IsFlipped() const
Definition footprint.h:662
void Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection) override
Flip this object, i.e.
const wxString & GetReference() const
Definition footprint.h:909
VECTOR2I GetPosition() const override
Definition footprint.h:436
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
Handle actions specific to filling copper zones.
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:1882
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.
virtual EDA_ANGLE GetRotationAngle() const
Return the angle used for rotate operations.
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.
virtual bool ChildrenAreIndividuallySelectable() const
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
bool GetMoveWarpsCursor() const
Indicate that a move operation should warp the mouse pointer to the origin of the move object.
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
Return the instance of KIGFX::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:484
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)
#define EXCLUDE_ZONES
@ MOVING
Definition cursors.h:44
@ ARROW
Definition cursors.h:42
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
@ NONE
Definition eda_fill.h:42
@ RECURSE
Definition eda_item.h:51
#define IS_MOVING
Item being moved.
static bool ScheduleNetSwap(BOARD_CONNECTED_ITEM *aItem, int aNewNet, std::unordered_map< BOARD_CONNECTED_ITEM *, int > &aItemNewNets, wxString &aError)
static bool PromptConnectedPadDecision(PCB_BASE_EDIT_FRAME *aFrame, const std::vector< PAD * > &aPads, const wxString &aDialogTitle, bool &aIncludeConnectedPads)
static bool ValidateNetSwapLocks(PCB_BASE_EDIT_FRAME *aFrame, const std::unordered_map< BOARD_CONNECTED_ITEM *, int > &aItemNewNets, const std::unordered_set< PAD * > &aSwapPads, bool aIncludeConnectedPads)
@ FP_JUST_ADDED
Definition footprint.h:91
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:55
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:851
@ VERTICAL
A segment (or two points) is vertical.
@ HORIZONTAL
A segment (or two points) is horizontal.
EDA_ANGLE GridFrameAngleAt(const BOARD &aBoard, const VECTOR2I &aPos, PCB_GRID_ROLE aRole)
World frame angle of the grid active for aRole at aPos; ANGLE_0 when no grid applies.
EDA_ANGLE GridFrameRotationDelta(const EDA_ANGLE &aFrom, const EDA_ANGLE &aTo, const EDA_ANGLE &aRotationStep)
Minimal rotation that re-aligns an item from frame angle aFrom to aTo, reduced modulo min( aRotationS...
Class to handle a set of BOARD_ITEMs.
std::vector< EDA_ITEM * > EDA_ITEMS
void SpreadFootprints(std::vector< FOOTPRINT * > *aFootprints, const 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...
Increment event parameters.
Definition actions.h:328
int delta
@ 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:80
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:83
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_DRILL_MAP_T
class PCB_DRILL_MAP, drill symbols drawn at the holes
Definition typeinfo.h:240
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:103
@ PCB_REFERENCE_IMAGE_T
class PCB_REFERENCE_IMAGE, bitmap on a layer
Definition typeinfo.h:81
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:78
@ PCB_GRID_ITEM_T
a subgrid placed on a board
Definition typeinfo.h:238
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:90
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707