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