KiCad PCB EDA Suite
placement_tool.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) 2014-2016 CERN
5 * Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Maciej Suminski <[email protected]>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25#include "tool/selection.h"
26#include "placement_tool.h"
27#include "pcb_actions.h"
28#include "pcb_selection_tool.h"
29
31#include <tool/tool_manager.h>
32
33#include <pcb_edit_frame.h>
34#include <board.h>
35#include <board_commit.h>
36#include <bitmaps.h>
37
38#include <menus_helpers.h>
39
40
42 TOOL_INTERACTIVE( "pcbnew.Placement" ),
43 m_selectionTool( nullptr ),
44 m_placementMenu( nullptr ),
45 m_frame( nullptr )
46{
47}
48
50{
51 delete m_placementMenu;
52}
53
54
56{
57 // Find the selection tool, so they can cooperate
59 m_frame = getEditFrame<PCB_BASE_FRAME>();
60
61 // Create a context menu and make it available through selection tool
62 m_placementMenu = new ACTION_MENU( true, this );
64 m_placementMenu->SetTitle( _( "Align/Distribute" ) );
65
66 // Add all align/distribute commands
70
71 m_placementMenu->AppendSeparator();
75
76 m_placementMenu->AppendSeparator();
79
82
83 return true;
84}
85
86
87template <class T>
88std::vector<std::pair<BOARD_ITEM*, BOX2I>> GetBoundingBoxes( const T& aItems )
89{
90 std::vector<std::pair<BOARD_ITEM*, BOX2I>> rects;
91
92 for( EDA_ITEM* item : aItems )
93 {
94 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
95
96 if( item->Type() == PCB_FOOTPRINT_T )
97 {
98 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
99 rects.emplace_back( std::make_pair( footprint,
100 footprint->GetBoundingBox( false, false ) ) );
101 }
102 else
103 {
104 rects.emplace_back( std::make_pair( boardItem, boardItem->GetBoundingBox() ) );
105 }
106 }
107
108 return rects;
109}
110
111
112template< typename T >
113int ALIGN_DISTRIBUTE_TOOL::selectTarget( std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aItems,
114 std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aLocked,
115 T aGetValue )
116{
118
119 // Prefer locked items to unlocked items.
120 // Secondly, prefer items under the cursor to other items.
121
122 if( aLocked.size() >= 1 )
123 {
124 for( const std::pair<BOARD_ITEM*, BOX2I>& item : aLocked )
125 {
126 if( item.second.Contains( curPos ) )
127 return aGetValue( item );
128 }
129
130 return aGetValue( aLocked.front() );
131 }
132
133 for( const std::pair<BOARD_ITEM*, BOX2I>& item : aItems )
134 {
135 if( item.second.Contains( curPos ) )
136 return aGetValue( item );
137 }
138
139 return aGetValue( aItems.front() );
140}
141
142
143template< typename T >
144size_t ALIGN_DISTRIBUTE_TOOL::GetSelections( std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aItemsToAlign,
145 std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aLockedItems,
146 T aCompare )
147{
149 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
150 {
151 // Iterate from the back so we don't have to worry about removals.
152 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
153 {
154 BOARD_ITEM* item = aCollector[i];
155
156 if( item->Type() == PCB_MARKER_T )
157 aCollector.Remove( item );
158 }
159 } );
160
161 std::vector<BOARD_ITEM*> lockedItems;
162 std::vector<BOARD_ITEM*> itemsToAlign;
163
164 for( EDA_ITEM* item : selection )
165 {
166 BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
167
168 // We do not lock items in the footprint editor
169 if( boardItem->IsLocked() && m_frame->IsType( FRAME_PCB_EDITOR ) )
170 {
171 // Locking a pad but not the footprint means that we align the footprint using
172 // the pad position. So we test for footprint locking here
173 if( boardItem->Type() == PCB_PAD_T && !boardItem->GetParent()->IsLocked() )
174 {
175 itemsToAlign.push_back( boardItem );
176 }
177 else
178 {
179 lockedItems.push_back( boardItem );
180 }
181 }
182 else
183 itemsToAlign.push_back( boardItem );
184 }
185
186 aItemsToAlign = GetBoundingBoxes( itemsToAlign );
187 aLockedItems = GetBoundingBoxes( lockedItems );
188 std::sort( aItemsToAlign.begin(), aItemsToAlign.end(), aCompare );
189 std::sort( aLockedItems.begin(), aLockedItems.end(), aCompare );
190
191 return aItemsToAlign.size();
192}
193
194
196{
197 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
198 std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
199
200 if( !GetSelections( itemsToAlign, locked_items,
201 []( const std::pair<BOARD_ITEM*, BOX2I> left, const std::pair<BOARD_ITEM*, BOX2I> right)
202 {
203 return ( left.second.GetTop() < right.second.GetTop() );
204 } ) )
205 {
206 return 0;
207 }
208
209 BOARD_COMMIT commit( m_frame );
210
211 int targetTop = selectTarget( itemsToAlign, locked_items,
212 []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
213 {
214 return aVal.second.GetTop();
215 } );
216
217 // Move the selected items
218 for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
219 {
220 BOARD_ITEM* item = i.first;
221 int difference = targetTop - i.second.GetTop();
222
223 if( item->GetParent() && item->GetParent()->IsSelected() )
224 continue;
225
226 // Don't move a pad by itself unless editing the footprint
227 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
228 item = item->GetParent();
229
230 commit.Stage( item, CHT_MODIFY );
231 item->Move( VECTOR2I( 0, difference ) );
232 }
233
234 commit.Push( _( "Align to top" ) );
235
236 return 0;
237}
238
239
241{
242 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
243 std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
244
245 if( !GetSelections( itemsToAlign, locked_items,
246 []( const std::pair<BOARD_ITEM*, BOX2I> left, const std::pair<BOARD_ITEM*, BOX2I> right)
247 {
248 return ( left.second.GetBottom() < right.second.GetBottom() );
249 } ) )
250 {
251 return 0;
252 }
253
254 BOARD_COMMIT commit( m_frame );
255
256 int targetBottom = selectTarget( itemsToAlign, locked_items,
257 []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
258 {
259 return aVal.second.GetBottom();
260 } );
261
262 // Move the selected items
263 for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
264 {
265 int difference = targetBottom - i.second.GetBottom();
266 BOARD_ITEM* item = i.first;
267
268 if( item->GetParent() && item->GetParent()->IsSelected() )
269 continue;
270
271 // Don't move a pad by itself unless editing the footprint
272 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
273 item = item->GetParent();
274
275 commit.Stage( item, CHT_MODIFY );
276 item->Move( VECTOR2I( 0, difference ) );
277 }
278
279 commit.Push( _( "Align to bottom" ) );
280
281 return 0;
282}
283
284
286{
287 // Because this tool uses bounding boxes and they aren't mirrored even when
288 // the view is mirrored, we need to call the other one if mirrored.
289 if( getView()->IsMirroredX() )
290 {
291 return doAlignRight();
292 }
293 else
294 {
295 return doAlignLeft();
296 }
297}
298
299
301{
302 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
303 std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
304
305 if( !GetSelections( itemsToAlign, locked_items,
306 []( const std::pair<BOARD_ITEM*, BOX2I> left, const std::pair<BOARD_ITEM*, BOX2I> right)
307 {
308 return ( left.second.GetLeft() < right.second.GetLeft() );
309 } ) )
310 {
311 return 0;
312 }
313
314 BOARD_COMMIT commit( m_frame );
315
316 int targetLeft = selectTarget( itemsToAlign, locked_items,
317 []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
318 {
319 return aVal.second.GetLeft();
320 } );
321
322 // Move the selected items
323 for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
324 {
325 int difference = targetLeft - i.second.GetLeft();
326 BOARD_ITEM* item = i.first;
327
328 if( item->GetParent() && item->GetParent()->IsSelected() )
329 continue;
330
331 // Don't move a pad by itself unless editing the footprint
332 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
333 item = item->GetParent();
334
335 commit.Stage( item, CHT_MODIFY );
336 item->Move( VECTOR2I( difference, 0 ) );
337 }
338
339 commit.Push( _( "Align to left" ) );
340
341 return 0;
342}
343
344
346{
347 // Because this tool uses bounding boxes and they aren't mirrored even when
348 // the view is mirrored, we need to call the other one if mirrored.
349 if( getView()->IsMirroredX() )
350 {
351 return doAlignLeft();
352 }
353 else
354 {
355 return doAlignRight();
356 }
357}
358
359
361{
362 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
363 std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
364
365 if( !GetSelections( itemsToAlign, locked_items,
366 []( const std::pair<BOARD_ITEM*, BOX2I> left, const std::pair<BOARD_ITEM*, BOX2I> right)
367 {
368 return ( left.second.GetRight() < right.second.GetRight() );
369 } ) )
370 {
371 return 0;
372 }
373
374 BOARD_COMMIT commit( m_frame );
375
376 int targetRight = selectTarget( itemsToAlign, locked_items,
377 []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
378 {
379 return aVal.second.GetRight();
380 } );
381
382 // Move the selected items
383 for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
384 {
385 int difference = targetRight - i.second.GetRight();
386 BOARD_ITEM* item = i.first;
387
388 if( item->GetParent() && item->GetParent()->IsSelected() )
389 continue;
390
391 // Don't move a pad by itself unless editing the footprint
392 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
393 item = item->GetParent();
394
395 commit.Stage( item, CHT_MODIFY );
396 item->Move( VECTOR2I( difference, 0 ) );
397 }
398
399 commit.Push( _( "Align to right" ) );
400
401 return 0;
402}
403
404
406{
407 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
408 std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
409
410 if( !GetSelections( itemsToAlign, locked_items,
411 []( const std::pair<BOARD_ITEM*, BOX2I> left, const std::pair<BOARD_ITEM*, BOX2I> right)
412 {
413 return ( left.second.Centre().x < right.second.Centre().x );
414 } ) )
415 {
416 return 0;
417 }
418
419 BOARD_COMMIT commit( m_frame );
420
421 int targetX = selectTarget( itemsToAlign, locked_items,
422 []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
423 {
424 return aVal.second.Centre().x;
425 } );
426
427 // Move the selected items
428 for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
429 {
430 int difference = targetX - i.second.Centre().x;
431 BOARD_ITEM* item = i.first;
432
433 if( item->GetParent() && item->GetParent()->IsSelected() )
434 continue;
435
436 // Don't move a pad by itself unless editing the footprint
437 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
438 item = item->GetParent();
439
440 commit.Stage( item, CHT_MODIFY );
441 item->Move( VECTOR2I( difference, 0 ) );
442 }
443
444 commit.Push( _( "Align to middle" ) );
445
446 return 0;
447}
448
449
451{
452 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
453 std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
454
455 if( !GetSelections( itemsToAlign, locked_items,
456 []( const std::pair<BOARD_ITEM*, BOX2I> left, const std::pair<BOARD_ITEM*, BOX2I> right)
457 {
458 return ( left.second.Centre().y < right.second.Centre().y );
459 } ) )
460 {
461 return 0;
462 }
463
464 BOARD_COMMIT commit( m_frame );
465
466 int targetY = selectTarget( itemsToAlign, locked_items,
467 []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
468 {
469 return aVal.second.Centre().y;
470 } );
471
472 // Move the selected items
473 for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
474 {
475 int difference = targetY - i.second.Centre().y;
476 BOARD_ITEM* item = i.first;
477
478 if( item->GetParent() && item->GetParent()->IsSelected() )
479 continue;
480
481 // Don't move a pad by itself unless editing the footprint
482 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
483 item = item->GetParent();
484
485 commit.Stage( item, CHT_MODIFY );
486 item->Move( VECTOR2I( 0, difference ) );
487 }
488
489 commit.Push( _( "Align to center" ) );
490
491 return 0;
492}
493
494
496{
498 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
499 {
500 // Iterate from the back so we don't have to worry about removals.
501 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
502 {
503 BOARD_ITEM* item = aCollector[i];
504
505 if( item->Type() == PCB_MARKER_T )
506 aCollector.Remove( item );
507 }
508 },
509 m_frame->IsType( FRAME_PCB_EDITOR ) /* prompt user regarding locked items */ );
510
511 if( selection.Size() <= 1 )
512 return 0;
513
514 BOARD_COMMIT commit( m_frame );
515 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToDistribute = GetBoundingBoxes( selection );
516
517 // find the last item by reverse sorting
518 std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
519 [] ( const std::pair<BOARD_ITEM*, BOX2I> left,
520 const std::pair<BOARD_ITEM*, BOX2I> right)
521 {
522 return ( left.second.GetRight() > right.second.GetRight() );
523 } );
524
525 BOARD_ITEM* lastItem = itemsToDistribute.begin()->first;
526 const int maxRight = itemsToDistribute.begin()->second.GetRight();
527
528 // sort to get starting order
529 std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
530 [] ( const std::pair<BOARD_ITEM*, BOX2I> left,
531 const std::pair<BOARD_ITEM*, BOX2I> right)
532 {
533 return ( left.second.GetX() < right.second.GetX() );
534 } );
535
536 const int minX = itemsToDistribute.begin()->second.GetX();
537 int totalGap = maxRight - minX;
538 int totalWidth = 0;
539
540 for( const auto& [ item, rect ] : itemsToDistribute )
541 totalWidth += rect.GetWidth();
542
543 if( totalGap < totalWidth )
544 {
545 // the width of the items exceeds the gap (overlapping items) -> use center point spacing
546 doDistributeCentersHorizontally( itemsToDistribute, commit );
547 }
548 else
549 {
550 totalGap -= totalWidth;
551 doDistributeGapsHorizontally( itemsToDistribute, commit, lastItem, totalGap );
552 }
553
554 commit.Push( _( "Distribute horizontally" ) );
555
556 return 0;
557}
558
559
560void ALIGN_DISTRIBUTE_TOOL::doDistributeGapsHorizontally( std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aItems,
561 BOARD_COMMIT& aCommit,
562 const BOARD_ITEM* lastItem,
563 int totalGap ) const
564{
565 const int itemGap = totalGap / ( aItems.size() - 1 );
566 int targetX = aItems.begin()->second.GetX();
567
568 for( const std::pair<BOARD_ITEM*, BOX2I>& i : aItems )
569 {
570 BOARD_ITEM* item = i.first;
571
572 // cover the corner case where the last item is wider than the previous item and gap
573 if( lastItem == item )
574 continue;
575
576 if( item->GetParent() && item->GetParent()->IsSelected() )
577 continue;
578
579 // Don't move a pad by itself unless editing the footprint
580 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
581 item = item->GetParent();
582
583 int difference = targetX - i.second.GetX();
584 aCommit.Stage( item, CHT_MODIFY );
585 item->Move( VECTOR2I( difference, 0 ) );
586 targetX += ( i.second.GetWidth() + itemGap );
587 }
588}
589
590
591void ALIGN_DISTRIBUTE_TOOL::doDistributeCentersHorizontally( std::vector<std::pair<BOARD_ITEM*, BOX2I>> &aItems,
592 BOARD_COMMIT& aCommit ) const
593{
594 std::sort( aItems.begin(), aItems.end(),
595 [] ( const std::pair<BOARD_ITEM*, BOX2I> left, const std::pair<BOARD_ITEM*, BOX2I> right)
596 {
597 return ( left.second.Centre().x < right.second.Centre().x );
598 } );
599
600 const int totalGap = ( aItems.end() - 1 )->second.Centre().x
601 - aItems.begin()->second.Centre().x;
602 const int itemGap = totalGap / ( aItems.size() - 1 );
603 int targetX = aItems.begin()->second.Centre().x;
604
605 for( const std::pair<BOARD_ITEM*, BOX2I>& i : aItems )
606 {
607 BOARD_ITEM* item = i.first;
608
609 if( item->GetParent() && item->GetParent()->IsSelected() )
610 continue;
611
612 // Don't move a pad by itself unless editing the footprint
613 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
614 item = item->GetParent();
615
616 int difference = targetX - i.second.Centre().x;
617 aCommit.Stage( item, CHT_MODIFY );
618 item->Move( VECTOR2I( difference, 0 ) );
619 targetX += ( itemGap );
620 }
621}
622
623
625{
627 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
628 {
629 // Iterate from the back so we don't have to worry about removals.
630 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
631 {
632 BOARD_ITEM* item = aCollector[i];
633
634 if( item->Type() == PCB_MARKER_T )
635 aCollector.Remove( item );
636 }
637 },
638 m_frame->IsType( FRAME_PCB_EDITOR ) /* prompt user regarding locked items */ );
639
640 if( selection.Size() <= 1 )
641 return 0;
642
643 BOARD_COMMIT commit( m_frame );
644 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToDistribute = GetBoundingBoxes( selection );
645
646 // find the last item by reverse sorting
647 std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
648 [] ( const std::pair<BOARD_ITEM*, BOX2I> left, const std::pair<BOARD_ITEM*, BOX2I> right)
649 {
650 return ( left.second.GetBottom() > right.second.GetBottom() );
651 } );
652
653 BOARD_ITEM* lastItem = itemsToDistribute.begin()->first;
654 const int maxBottom = itemsToDistribute.begin()->second.GetBottom();
655
656 // sort to get starting order
657 std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
658 [] ( const std::pair<BOARD_ITEM*, BOX2I> left, const std::pair<BOARD_ITEM*, BOX2I> right)
659 {
660 return ( left.second.Centre().y < right.second.Centre().y );
661 } );
662
663 int minY = itemsToDistribute.begin()->second.GetY();
664 int totalGap = maxBottom - minY;
665 int totalHeight = 0;
666
667 for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToDistribute )
668 totalHeight += i.second.GetHeight();
669
670 if( totalGap < totalHeight )
671 {
672 // the width of the items exceeds the gap (overlapping items) -> use center point spacing
673 doDistributeCentersVertically( itemsToDistribute, commit );
674 }
675 else
676 {
677 totalGap -= totalHeight;
678 doDistributeGapsVertically( itemsToDistribute, commit, lastItem, totalGap );
679 }
680
681 commit.Push( _( "Distribute vertically" ) );
682
683 return 0;
684}
685
686
687void ALIGN_DISTRIBUTE_TOOL::doDistributeGapsVertically( std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aItems,
688 BOARD_COMMIT& aCommit,
689 const BOARD_ITEM* lastItem,
690 int totalGap ) const
691{
692 const int itemGap = totalGap / ( aItems.size() - 1 );
693 int targetY = aItems.begin()->second.GetY();
694
695 for( std::pair<BOARD_ITEM*, BOX2I>& i : aItems )
696 {
697 BOARD_ITEM* item = i.first;
698
699 // cover the corner case where the last item is wider than the previous item and gap
700 if( lastItem == item )
701 continue;
702
703 if( item->GetParent() && item->GetParent()->IsSelected() )
704 continue;
705
706 // Don't move a pad by itself unless editing the footprint
707 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
708 item = item->GetParent();
709
710 int difference = targetY - i.second.GetY();
711 aCommit.Stage( item, CHT_MODIFY );
712 item->Move( VECTOR2I( 0, difference ) );
713 targetY += ( i.second.GetHeight() + itemGap );
714 }
715}
716
717
718void ALIGN_DISTRIBUTE_TOOL::doDistributeCentersVertically( std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aItems,
719 BOARD_COMMIT& aCommit ) const
720{
721 std::sort( aItems.begin(), aItems.end(),
722 [] ( const std::pair<BOARD_ITEM*, BOX2I> left, const std::pair<BOARD_ITEM*, BOX2I> right)
723 {
724 return ( left.second.Centre().y < right.second.Centre().y );
725 } );
726
727 const int totalGap = ( aItems.end() - 1 )->second.Centre().y
728 - aItems.begin()->second.Centre().y;
729 const int itemGap = totalGap / ( aItems.size() - 1 );
730 int targetY = aItems.begin()->second.Centre().y;
731
732 for( const std::pair<BOARD_ITEM*, BOX2I>& i : aItems )
733 {
734 BOARD_ITEM* item = i.first;
735
736 if( item->GetParent() && item->GetParent()->IsSelected() )
737 continue;
738
739 // Don't move a pad by itself unless editing the footprint
740 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
741 item = item->GetParent();
742
743 int difference = targetY - i.second.Centre().y;
744 aCommit.Stage( item, CHT_MODIFY );
745 item->Move( VECTOR2I( 0, difference ) );
746 targetY += ( itemGap );
747 }
748}
749
750
752{
759
764}
Defines the structure of a menu based on ACTIONs.
Definition: action_menu.h:49
void SetTitle(const wxString &aTitle) override
Set title for the menu.
Definition: action_menu.cpp:87
void SetIcon(BITMAPS aIcon)
Assign an icon for the entry.
Definition: action_menu.cpp:73
wxMenuItem * Add(const wxString &aLabel, int aId, BITMAPS aIcon)
Add a wxWidgets-style entry to the menu.
void doDistributeGapsVertically(std::vector< std::pair< BOARD_ITEM *, BOX2I > > &aItems, BOARD_COMMIT &aCommit, const BOARD_ITEM *lastItem, int totalGap) const
Distributes selected items using an even spacing between their bounding boxes.
int AlignBottom(const TOOL_EVENT &aEvent)
Sets Y coordinate of the selected items to the value of the bottom-most selected item Y coordinate.
PCB_SELECTION_TOOL * m_selectionTool
size_t GetSelections(std::vector< std::pair< BOARD_ITEM *, BOX2I > > &aItemsToAlign, std::vector< std::pair< BOARD_ITEM *, BOX2I > > &aLockedItems, T aCompare)
Populate two vectors with the sorted selection and sorted locked items.
int AlignCenterX(const TOOL_EVENT &aEvent)
Set the x coordinate of the midpoint of each of the selected items to the value of the x coordinate o...
PCB_BASE_FRAME * m_frame
void doDistributeGapsHorizontally(std::vector< std::pair< BOARD_ITEM *, BOX2I > > &aItems, BOARD_COMMIT &aCommit, const BOARD_ITEM *lastItem, int totalGap) const
Distributes selected items using an even spacing between their bounding boxes.
int AlignTop(const TOOL_EVENT &aEvent)
Set Y coordinate of the selected items to the value of the top-most selected item Y coordinate.
int doAlignLeft()
Sets X coordinate of the selected items to the value of the left-most selected item X coordinate.
int selectTarget(std::vector< std::pair< BOARD_ITEM *, BOX2I > > &aItems, std::vector< std::pair< BOARD_ITEM *, BOX2I > > &aLocked, T aGetValue)
int AlignCenterY(const TOOL_EVENT &aEvent)
Set the y coordinate of the midpoint of each of the selected items to the value of the y coordinate o...
int AlignRight(const TOOL_EVENT &aEvent)
Sets X coordinate of the selected items to the value of the right-most selected item X coordinate.
virtual ~ALIGN_DISTRIBUTE_TOOL()
void doDistributeCentersHorizontally(std::vector< std::pair< BOARD_ITEM *, BOX2I > > &aItems, BOARD_COMMIT &aCommit) const
Distribute selected items using an even spacing between the centers of their bounding boxes.
int DistributeVertically(const TOOL_EVENT &aEvent)
Distribute the selected items along the Y axis.
bool Init() override
Init() is called once upon a registration of the tool.
int DistributeHorizontally(const TOOL_EVENT &aEvent)
Distribute the selected items along the X axis.
void setTransitions() override
This method is meant to be overridden in order to specify handlers for events.
void doDistributeCentersVertically(std::vector< std::pair< BOARD_ITEM *, BOX2I > > &aItems, BOARD_COMMIT &aCommit) const
Distribute selected items using an even spacing between the centers of their bounding boxes.
int doAlignRight()
Align selected items using the right edge of their bounding boxes to the right-most item.
int AlignLeft(const TOOL_EVENT &aEvent)
Sets X coordinate of the selected items to the value of the left-most selected item X coordinate.
ACTION_MENU * m_placementMenu
virtual void Push(const wxString &aMessage=wxT("A commit"), int aCommitFlags=0) override
Revert the commit by restoring the modified items state.
COMMIT & Stage(EDA_ITEM *aItem, CHANGE_TYPE aChangeType) override
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:50
int GetY() const
Definition: board_item.h:72
int GetX() const
Definition: board_item.h:66
virtual void Move(const VECTOR2I &aMoveVector)
Move this object.
Definition: board_item.h:253
virtual bool IsLocked() const
Definition: board_item.cpp:65
BOARD_ITEM_CONTAINER * GetParent() const
Definition: board_item.h:150
int GetCount() const
Return the number of objects in the list.
Definition: collector.h:81
void AddMenu(ACTION_MENU *aMenu, const SELECTION_CONDITION &aCondition=SELECTION_CONDITIONS::ShowAlways, int aOrder=ANY_ORDER)
Add a submenu to the menu.
bool IsType(FRAME_T aType) const
A base class for most all the KiCad significant classes used in schematics and boards.
Definition: eda_item.h:85
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition: eda_item.cpp:74
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:97
bool IsSelected() const
Definition: eda_item.h:107
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Definition: footprint.cpp:782
Used when the right click button is pressed, or when the select tool is in effect.
Definition: collectors.h:204
VECTOR2D GetCursorPosition() const
Return the current cursor position in world coordinates.
static TOOL_ACTION distributeVertically
Definition: pcb_actions.h:257
static TOOL_ACTION alignTop
Definition: pcb_actions.h:250
static TOOL_ACTION alignRight
Definition: pcb_actions.h:253
static TOOL_ACTION alignBottom
Definition: pcb_actions.h:251
static TOOL_ACTION alignLeft
Definition: pcb_actions.h:252
static TOOL_ACTION distributeHorizontally
Definition: pcb_actions.h:256
static TOOL_ACTION alignCenterX
Definition: pcb_actions.h:254
static TOOL_ACTION alignCenterY
Definition: pcb_actions.h:255
The selection tool: currently supports:
PCB_SELECTION & RequestSelection(CLIENT_SELECTION_FILTER aClientFilter, bool aConfirmLockedItems=false)
Return the current selection, filtered according to aClientFilter.
static SELECTION_CONDITION MoreThan(int aNumber)
Create a functor that tests if the number of selected items is greater than the value given as parame...
int Size() const
Returns the number of selected parts.
Definition: selection.h:113
KIGFX::VIEW_CONTROLS * getViewControls() const
Return the instance of VIEW_CONTROLS object used in the application.
Definition: tool_base.cpp:42
TOOL_MANAGER * m_toolMgr
Definition: tool_base.h:214
KIGFX::VIEW * getView() const
Returns the instance of #VIEW object used in the application.
Definition: tool_base.cpp:36
Generic, UI-independent tool event.
Definition: tool_event.h:156
void Go(int(T::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
Define which state (aStateFunc) to go when a certain event arrives (aConditions).
TOOL_MENU & GetToolMenu()
CONDITIONAL_MENU & GetMenu()
Definition: tool_menu.cpp:44
@ CHT_MODIFY
Definition: commit.h:42
#define _(s)
@ FRAME_PCB_EDITOR
Definition: frame_type.h:40
Macros and inline functions to create menus items in menubars or popup menus.
std::vector< std::pair< BOARD_ITEM *, BOX2I > > GetBoundingBoxes(const T &aItems)
Class that computes missing connections on a PCB.
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition: typeinfo.h:86
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition: typeinfo.h:87
VECTOR2< int > VECTOR2I
Definition: vector2d.h:618