KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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
40 TOOL_INTERACTIVE( "pcbnew.Placement" ),
41 m_selectionTool( nullptr ),
42 m_placementMenu( nullptr ),
43 m_frame( nullptr )
44{
45}
46
48{
49 delete m_placementMenu;
50}
51
52
54{
55 // Find the selection tool, so they can cooperate
57 m_frame = getEditFrame<PCB_BASE_FRAME>();
58
59 // Create a context menu and make it available through selection tool
60 m_placementMenu = new ACTION_MENU( true, this );
61 m_placementMenu->SetIcon( BITMAPS::align_items );
62 m_placementMenu->SetTitle( _( "Align/Distribute" ) );
63
64 // Add all align/distribute commands
68
69 m_placementMenu->AppendSeparator();
73
74 m_placementMenu->AppendSeparator();
77
80
81 return true;
82}
83
84
85template <class T>
86std::vector<std::pair<BOARD_ITEM*, BOX2I>> GetBoundingBoxes( const T& aItems )
87{
88 std::vector<std::pair<BOARD_ITEM*, BOX2I>> rects;
89
90 for( EDA_ITEM* item : aItems )
91 {
92 BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( item );
93
94 wxCHECK2( boardItem, continue );
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 = dynamic_cast<BOARD_ITEM*>( item );
167 wxCHECK2( boardItem, continue );
168
169 // We do not lock items in the footprint editor
170 if( boardItem->IsLocked() && m_frame->IsType( FRAME_PCB_EDITOR ) )
171 {
172 // Locking a pad but not the footprint means that we align the footprint using
173 // the pad position. So we test for footprint locking here
174 if( boardItem->Type() == PCB_PAD_T && !boardItem->GetParent()->IsLocked() )
175 itemsToAlign.push_back( boardItem );
176 else
177 lockedItems.push_back( boardItem );
178 }
179 else
180 itemsToAlign.push_back( boardItem );
181 }
182
183 aItemsToAlign = GetBoundingBoxes( itemsToAlign );
184 aLockedItems = GetBoundingBoxes( lockedItems );
185 std::sort( aItemsToAlign.begin(), aItemsToAlign.end(), aCompare );
186 std::sort( aLockedItems.begin(), aLockedItems.end(), aCompare );
187
188 return aItemsToAlign.size();
189}
190
191
193{
194 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
195 std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
196
197 if( !GetSelections( itemsToAlign, locked_items,
198 []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
199 {
200 return ( lhs.second.GetTop() < rhs.second.GetTop() );
201 } ) )
202 {
203 return 0;
204 }
205
206 BOARD_COMMIT commit( m_frame );
207
208 int targetTop = selectTarget( itemsToAlign, locked_items,
209 []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
210 {
211 return aVal.second.GetTop();
212 } );
213
214 // Move the selected items
215 for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
216 {
217 BOARD_ITEM* item = i.first;
218 int difference = targetTop - i.second.GetTop();
219
220 if( item->GetParent() && item->GetParent()->IsSelected() )
221 continue;
222
223 // Don't move a pad by itself unless editing the footprint
224 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
225 item = item->GetParent();
226
227 commit.Stage( item, CHT_MODIFY );
228 item->Move( VECTOR2I( 0, difference ) );
229 }
230
231 commit.Push( _( "Align to Top" ) );
232 return 0;
233}
234
235
237{
238 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
239 std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
240
241 if( !GetSelections( itemsToAlign, locked_items,
242 []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs)
243 {
244 return ( lhs.second.GetBottom() > rhs.second.GetBottom() );
245 } ) )
246 {
247 return 0;
248 }
249
250 BOARD_COMMIT commit( m_frame );
251
252 int targetBottom = selectTarget( itemsToAlign, locked_items,
253 []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
254 {
255 return aVal.second.GetBottom();
256 } );
257
258 // Move the selected items
259 for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
260 {
261 int difference = targetBottom - i.second.GetBottom();
262 BOARD_ITEM* item = i.first;
263
264 if( item->GetParent() && item->GetParent()->IsSelected() )
265 continue;
266
267 // Don't move a pad by itself unless editing the footprint
268 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
269 item = item->GetParent();
270
271 commit.Stage( item, CHT_MODIFY );
272 item->Move( VECTOR2I( 0, difference ) );
273 }
274
275 commit.Push( _( "Align to Bottom" ) );
276 return 0;
277}
278
279
281{
282 // Because this tool uses bounding boxes and they aren't mirrored even when
283 // the view is mirrored, we need to call the other one if mirrored.
284 if( getView()->IsMirroredX() )
285 return doAlignRight();
286 else
287 return doAlignLeft();
288}
289
290
292{
293 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
294 std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
295
296 if( !GetSelections( itemsToAlign, locked_items,
297 []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
298 {
299 return ( lhs.second.GetLeft() < rhs.second.GetLeft() );
300 } ) )
301 {
302 return 0;
303 }
304
305 BOARD_COMMIT commit( m_frame );
306
307 int targetLeft = selectTarget( itemsToAlign, locked_items,
308 []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
309 {
310 return aVal.second.GetLeft();
311 } );
312
313 // Move the selected items
314 for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
315 {
316 int difference = targetLeft - i.second.GetLeft();
317 BOARD_ITEM* item = i.first;
318
319 if( item->GetParent() && item->GetParent()->IsSelected() )
320 continue;
321
322 // Don't move a pad by itself unless editing the footprint
323 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
324 item = item->GetParent();
325
326 commit.Stage( item, CHT_MODIFY );
327 item->Move( VECTOR2I( difference, 0 ) );
328 }
329
330 commit.Push( _( "Align to Left" ) );
331 return 0;
332}
333
334
336{
337 // Because this tool uses bounding boxes and they aren't mirrored even when
338 // the view is mirrored, we need to call the other one if mirrored.
339 if( getView()->IsMirroredX() )
340 return doAlignLeft();
341 else
342 return doAlignRight();
343}
344
345
347{
348 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
349 std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
350
351 if( !GetSelections( itemsToAlign, locked_items,
352 []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
353 {
354 return ( lhs.second.GetRight() > rhs.second.GetRight() );
355 } ) )
356 {
357 return 0;
358 }
359
360 BOARD_COMMIT commit( m_frame );
361
362 int targetRight = selectTarget( itemsToAlign, locked_items,
363 []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
364 {
365 return aVal.second.GetRight();
366 } );
367
368 // Move the selected items
369 for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
370 {
371 int difference = targetRight - i.second.GetRight();
372 BOARD_ITEM* item = i.first;
373
374 if( item->GetParent() && item->GetParent()->IsSelected() )
375 continue;
376
377 // Don't move a pad by itself unless editing the footprint
378 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
379 item = item->GetParent();
380
381 commit.Stage( item, CHT_MODIFY );
382 item->Move( VECTOR2I( difference, 0 ) );
383 }
384
385 commit.Push( _( "Align to Right" ) );
386 return 0;
387}
388
389
391{
392 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
393 std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
394
395 if( !GetSelections( itemsToAlign, locked_items,
396 []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
397 {
398 return ( lhs.second.Centre().x < rhs.second.Centre().x );
399 } ) )
400 {
401 return 0;
402 }
403
404 BOARD_COMMIT commit( m_frame );
405
406 int targetX = selectTarget( itemsToAlign, locked_items,
407 []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
408 {
409 return aVal.second.Centre().x;
410 } );
411
412 // Move the selected items
413 for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
414 {
415 int difference = targetX - i.second.Centre().x;
416 BOARD_ITEM* item = i.first;
417
418 if( item->GetParent() && item->GetParent()->IsSelected() )
419 continue;
420
421 // Don't move a pad by itself unless editing the footprint
422 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
423 item = item->GetParent();
424
425 commit.Stage( item, CHT_MODIFY );
426 item->Move( VECTOR2I( difference, 0 ) );
427 }
428
429 commit.Push( _( "Align to Middle" ) );
430 return 0;
431}
432
433
435{
436 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
437 std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
438
439 if( !GetSelections( itemsToAlign, locked_items,
440 []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
441 {
442 return ( lhs.second.Centre().y < rhs.second.Centre().y );
443 } ) )
444 {
445 return 0;
446 }
447
448 BOARD_COMMIT commit( m_frame );
449
450 int targetY = selectTarget( itemsToAlign, locked_items,
451 []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
452 {
453 return aVal.second.Centre().y;
454 } );
455
456 // Move the selected items
457 for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
458 {
459 int difference = targetY - i.second.Centre().y;
460 BOARD_ITEM* item = i.first;
461
462 if( item->GetParent() && item->GetParent()->IsSelected() )
463 continue;
464
465 // Don't move a pad by itself unless editing the footprint
466 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
467 item = item->GetParent();
468
469 commit.Stage( item, CHT_MODIFY );
470 item->Move( VECTOR2I( 0, difference ) );
471 }
472
473 commit.Push( _( "Align to Center" ) );
474 return 0;
475}
476
477
479{
481 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
482 {
483 // Iterate from the back so we don't have to worry about removals.
484 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
485 {
486 BOARD_ITEM* item = aCollector[i];
487
488 if( item->Type() == PCB_MARKER_T )
489 aCollector.Remove( item );
490 }
491 },
492 m_frame->IsType( FRAME_PCB_EDITOR ) /* prompt user regarding locked items */ );
493
494 if( selection.Size() <= 1 )
495 return 0;
496
497 BOARD_COMMIT commit( m_frame );
498 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToDistribute = GetBoundingBoxes( selection );
499
500 // find the last item by reverse sorting
501 std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
502 []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
503 {
504 return ( lhs.second.GetRight() > rhs.second.GetRight() );
505 } );
506
507 BOARD_ITEM* lastItem = itemsToDistribute.begin()->first;
508 const int maxRight = itemsToDistribute.begin()->second.GetRight();
509
510 // sort to get starting order
511 std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
512 []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
513 {
514 return ( lhs.second.GetX() < rhs.second.GetX() );
515 } );
516
517 const int minX = itemsToDistribute.begin()->second.GetX();
518 int totalGap = maxRight - minX;
519 int totalWidth = 0;
520
521 for( const auto& [ item, rect ] : itemsToDistribute )
522 totalWidth += rect.GetWidth();
523
524 if( totalGap < totalWidth )
525 {
526 // the width of the items exceeds the gap (overlapping items) -> use center point spacing
527 doDistributeCentersHorizontally( itemsToDistribute, commit );
528 }
529 else
530 {
531 totalGap -= totalWidth;
532 doDistributeGapsHorizontally( itemsToDistribute, commit, lastItem, totalGap );
533 }
534
535 commit.Push( _( "Distribute Horizontally" ) );
536 return 0;
537}
538
539
540void ALIGN_DISTRIBUTE_TOOL::doDistributeGapsHorizontally( std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aItems,
541 BOARD_COMMIT& aCommit,
542 const BOARD_ITEM* lastItem,
543 int totalGap ) const
544{
545 const int itemGap = totalGap / ( aItems.size() - 1 );
546 int targetX = aItems.begin()->second.GetX();
547
548 for( const std::pair<BOARD_ITEM*, BOX2I>& i : aItems )
549 {
550 BOARD_ITEM* item = i.first;
551
552 // cover the corner case where the last item is wider than the previous item and gap
553 if( lastItem == item )
554 continue;
555
556 if( item->GetParent() && item->GetParent()->IsSelected() )
557 continue;
558
559 // Don't move a pad by itself unless editing the footprint
560 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
561 item = item->GetParent();
562
563 int difference = targetX - i.second.GetX();
564 aCommit.Stage( item, CHT_MODIFY );
565 item->Move( VECTOR2I( difference, 0 ) );
566 targetX += ( i.second.GetWidth() + itemGap );
567 }
568}
569
570
571void ALIGN_DISTRIBUTE_TOOL::doDistributeCentersHorizontally( std::vector<std::pair<BOARD_ITEM*, BOX2I>> &aItems,
572 BOARD_COMMIT& aCommit ) const
573{
574 std::sort( aItems.begin(), aItems.end(),
575 []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
576 {
577 return ( lhs.second.Centre().x < rhs.second.Centre().x );
578 } );
579
580 const int totalGap = ( aItems.end()-1 )->second.Centre().x - aItems.begin()->second.Centre().x;
581 const int itemGap = totalGap / ( aItems.size() - 1 );
582 int targetX = aItems.begin()->second.Centre().x;
583
584 for( const std::pair<BOARD_ITEM*, BOX2I>& i : aItems )
585 {
586 BOARD_ITEM* item = i.first;
587
588 if( item->GetParent() && item->GetParent()->IsSelected() )
589 continue;
590
591 // Don't move a pad by itself unless editing the footprint
592 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
593 item = item->GetParent();
594
595 int difference = targetX - i.second.Centre().x;
596 aCommit.Stage( item, CHT_MODIFY );
597 item->Move( VECTOR2I( difference, 0 ) );
598 targetX += ( itemGap );
599 }
600}
601
602
604{
606 []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
607 {
608 // Iterate from the back so we don't have to worry about removals.
609 for( int i = aCollector.GetCount() - 1; i >= 0; --i )
610 {
611 BOARD_ITEM* item = aCollector[i];
612
613 if( item->Type() == PCB_MARKER_T )
614 aCollector.Remove( item );
615 }
616 },
617 m_frame->IsType( FRAME_PCB_EDITOR ) /* prompt user regarding locked items */ );
618
619 if( selection.Size() <= 1 )
620 return 0;
621
622 BOARD_COMMIT commit( m_frame );
623 std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToDistribute = GetBoundingBoxes( selection );
624
625 // find the last item by reverse sorting
626 std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
627 []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
628 {
629 return ( lhs.second.GetBottom() > rhs.second.GetBottom() );
630 } );
631
632 BOARD_ITEM* lastItem = itemsToDistribute.begin()->first;
633 const int maxBottom = itemsToDistribute.begin()->second.GetBottom();
634
635 // sort to get starting order
636 std::sort( itemsToDistribute.begin(), itemsToDistribute.end(),
637 []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
638 {
639 return ( lhs.second.Centre().y < rhs.second.Centre().y );
640 } );
641
642 int minY = itemsToDistribute.begin()->second.GetY();
643 int totalGap = maxBottom - minY;
644 int totalHeight = 0;
645
646 for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToDistribute )
647 totalHeight += i.second.GetHeight();
648
649 if( totalGap < totalHeight )
650 {
651 // the width of the items exceeds the gap (overlapping items) -> use center point spacing
652 doDistributeCentersVertically( itemsToDistribute, commit );
653 }
654 else
655 {
656 totalGap -= totalHeight;
657 doDistributeGapsVertically( itemsToDistribute, commit, lastItem, totalGap );
658 }
659
660 commit.Push( _( "Distribute Vertically" ) );
661 return 0;
662}
663
664
665void ALIGN_DISTRIBUTE_TOOL::doDistributeGapsVertically( std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aItems,
666 BOARD_COMMIT& aCommit,
667 const BOARD_ITEM* lastItem,
668 int totalGap ) const
669{
670 const int itemGap = totalGap / ( aItems.size() - 1 );
671 int targetY = aItems.begin()->second.GetY();
672
673 for( std::pair<BOARD_ITEM*, BOX2I>& i : aItems )
674 {
675 BOARD_ITEM* item = i.first;
676
677 // cover the corner case where the last item is wider than the previous item and gap
678 if( lastItem == item )
679 continue;
680
681 if( item->GetParent() && item->GetParent()->IsSelected() )
682 continue;
683
684 // Don't move a pad by itself unless editing the footprint
685 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
686 item = item->GetParent();
687
688 int difference = targetY - i.second.GetY();
689 aCommit.Stage( item, CHT_MODIFY );
690 item->Move( VECTOR2I( 0, difference ) );
691 targetY += ( i.second.GetHeight() + itemGap );
692 }
693}
694
695
696void ALIGN_DISTRIBUTE_TOOL::doDistributeCentersVertically( std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aItems,
697 BOARD_COMMIT& aCommit ) const
698{
699 std::sort( aItems.begin(), aItems.end(),
700 [] ( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs)
701 {
702 return ( lhs.second.Centre().y < rhs.second.Centre().y );
703 } );
704
705 const int totalGap = ( aItems.end() - 1 )->second.Centre().y
706 - aItems.begin()->second.Centre().y;
707 const int itemGap = totalGap / ( aItems.size() - 1 );
708 int targetY = aItems.begin()->second.Centre().y;
709
710 for( const std::pair<BOARD_ITEM*, BOX2I>& i : aItems )
711 {
712 BOARD_ITEM* item = i.first;
713
714 if( item->GetParent() && item->GetParent()->IsSelected() )
715 continue;
716
717 // Don't move a pad by itself unless editing the footprint
718 if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
719 item = item->GetParent();
720
721 int difference = targetY - i.second.Centre().y;
722 aCommit.Stage( item, CHT_MODIFY );
723 item->Move( VECTOR2I( 0, difference ) );
724 targetY += ( itemGap );
725 }
726}
727
728
730{
737
742}
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:92
void SetIcon(BITMAPS aIcon)
Assign an icon for the entry.
Definition: action_menu.cpp:78
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=wxEmptyString, int aCommitFlags=0) override
Revert the commit by restoring the modified items state.
COMMIT & Stage(EDA_ITEM *aItem, CHANGE_TYPE aChangeType, BASE_SCREEN *aScreen=nullptr) override
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:77
int GetY() const
Definition: board_item.h:101
int GetX() const
Definition: board_item.h:95
virtual void Move(const VECTOR2I &aMoveVector)
Move this object.
Definition: board_item.h:314
virtual bool IsLocked() const
Definition: board_item.cpp:74
BOARD_ITEM_CONTAINER * GetParent() const
Definition: board_item.h:204
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:88
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:100
bool IsSelected() const
Definition: eda_item.h:109
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Definition: footprint.cpp:1221
Used when the right click button is pressed, or when the select tool is in effect.
Definition: collectors.h:206
VECTOR2D GetCursorPosition() const
Return the current cursor position in world coordinates.
static TOOL_ACTION distributeVertically
Definition: pcb_actions.h:311
static TOOL_ACTION alignTop
Definition: pcb_actions.h:304
static TOOL_ACTION alignRight
Definition: pcb_actions.h:307
static TOOL_ACTION alignBottom
Definition: pcb_actions.h:305
static TOOL_ACTION alignLeft
Definition: pcb_actions.h:306
static TOOL_ACTION distributeHorizontally
Definition: pcb_actions.h:310
static TOOL_ACTION alignCenterX
Definition: pcb_actions.h:308
static TOOL_ACTION alignCenterY
Definition: pcb_actions.h:309
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:115
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:216
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:167
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:44
#define _(s)
@ FRAME_PCB_EDITOR
Definition: frame_type.h:42
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:588