You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

636 lines
21 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
9 years ago
  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 <maciej.suminski@cern.ch>
  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. #include <ratsnest/ratsnest_data.h>
  30. #include <tool/tool_manager.h>
  31. #include <board.h>
  32. #include <board_commit.h>
  33. #include <bitmaps.h>
  34. #include <pcb_edit_frame.h>
  35. #include <geometry/distribute.h>
  36. #include <view/view_controls.h>
  37. ALIGN_DISTRIBUTE_TOOL::ALIGN_DISTRIBUTE_TOOL() :
  38. TOOL_INTERACTIVE( "pcbnew.Placement" ),
  39. m_selectionTool( nullptr ),
  40. m_placementMenu( nullptr ),
  41. m_frame( nullptr )
  42. {
  43. }
  44. ALIGN_DISTRIBUTE_TOOL::~ALIGN_DISTRIBUTE_TOOL()
  45. {
  46. delete m_placementMenu;
  47. }
  48. bool ALIGN_DISTRIBUTE_TOOL::Init()
  49. {
  50. // Find the selection tool, so they can cooperate
  51. m_selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  52. m_frame = getEditFrame<PCB_BASE_FRAME>();
  53. // Create a context menu and make it available through selection tool
  54. m_placementMenu = new CONDITIONAL_MENU( this );
  55. m_placementMenu->SetIcon( BITMAPS::align_items );
  56. m_placementMenu->SetTitle( _( "Align/Distribute" ) );
  57. const auto canAlign = SELECTION_CONDITIONS::MoreThan( 1 );
  58. const auto canDistribute = SELECTION_CONDITIONS::MoreThan( 2 );
  59. // Add all align/distribute commands
  60. m_placementMenu->AddItem( PCB_ACTIONS::alignLeft, canAlign );
  61. m_placementMenu->AddItem( PCB_ACTIONS::alignCenterX, canAlign );
  62. m_placementMenu->AddItem( PCB_ACTIONS::alignRight, canAlign );
  63. m_placementMenu->AddSeparator( canAlign );
  64. m_placementMenu->AddItem( PCB_ACTIONS::alignTop, canAlign );
  65. m_placementMenu->AddItem( PCB_ACTIONS::alignCenterY, canAlign );
  66. m_placementMenu->AddItem( PCB_ACTIONS::alignBottom, canAlign );
  67. m_placementMenu->AddSeparator( canDistribute );
  68. m_placementMenu->AddItem( PCB_ACTIONS::distributeHorizontallyCenters, canDistribute );
  69. m_placementMenu->AddItem( PCB_ACTIONS::distributeHorizontallyGaps, canDistribute );
  70. m_placementMenu->AddItem( PCB_ACTIONS::distributeVerticallyCenters, canDistribute );
  71. m_placementMenu->AddItem( PCB_ACTIONS::distributeVerticallyGaps, canDistribute );
  72. CONDITIONAL_MENU& selToolMenu = m_selectionTool->GetToolMenu().GetMenu();
  73. selToolMenu.AddMenu( m_placementMenu, SELECTION_CONDITIONS::MoreThan( 1 ), 100 );
  74. return true;
  75. }
  76. template <class T>
  77. std::vector<std::pair<BOARD_ITEM*, BOX2I>> GetBoundingBoxes( const T& aItems )
  78. {
  79. std::vector<std::pair<BOARD_ITEM*, BOX2I>> rects;
  80. for( EDA_ITEM* item : aItems )
  81. {
  82. BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( item );
  83. wxCHECK2( boardItem, continue );
  84. if( item->Type() == PCB_FOOTPRINT_T )
  85. {
  86. FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
  87. rects.emplace_back( std::make_pair( footprint,
  88. footprint->GetBoundingBox( false, false ) ) );
  89. }
  90. else
  91. {
  92. rects.emplace_back( std::make_pair( boardItem, boardItem->GetBoundingBox() ) );
  93. }
  94. }
  95. return rects;
  96. }
  97. template< typename T >
  98. int ALIGN_DISTRIBUTE_TOOL::selectTarget( std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aItems,
  99. std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aLocked,
  100. T aGetValue )
  101. {
  102. VECTOR2I curPos = getViewControls()->GetCursorPosition();
  103. // Prefer locked items to unlocked items.
  104. // Secondly, prefer items under the cursor to other items.
  105. if( aLocked.size() >= 1 )
  106. {
  107. for( const std::pair<BOARD_ITEM*, BOX2I>& item : aLocked )
  108. {
  109. if( item.second.Contains( curPos ) )
  110. return aGetValue( item );
  111. }
  112. return aGetValue( aLocked.front() );
  113. }
  114. for( const std::pair<BOARD_ITEM*, BOX2I>& item : aItems )
  115. {
  116. if( item.second.Contains( curPos ) )
  117. return aGetValue( item );
  118. }
  119. return aGetValue( aItems.front() );
  120. }
  121. template< typename T >
  122. size_t ALIGN_DISTRIBUTE_TOOL::GetSelections( std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aItemsToAlign,
  123. std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aLockedItems,
  124. T aCompare )
  125. {
  126. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  127. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  128. {
  129. // Iterate from the back so we don't have to worry about removals.
  130. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  131. {
  132. BOARD_ITEM* item = aCollector[i];
  133. if( item->Type() == PCB_MARKER_T )
  134. aCollector.Remove( item );
  135. }
  136. } );
  137. std::vector<BOARD_ITEM*> lockedItems;
  138. std::vector<BOARD_ITEM*> itemsToAlign;
  139. for( EDA_ITEM* item : selection )
  140. {
  141. BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( item );
  142. wxCHECK2( boardItem, continue );
  143. // We do not lock items in the footprint editor
  144. if( boardItem->IsLocked() && m_frame->IsType( FRAME_PCB_EDITOR ) )
  145. {
  146. // Locking a pad but not the footprint means that we align the footprint using
  147. // the pad position. So we test for footprint locking here
  148. if( boardItem->Type() == PCB_PAD_T && !boardItem->GetParent()->IsLocked() )
  149. itemsToAlign.push_back( boardItem );
  150. else
  151. lockedItems.push_back( boardItem );
  152. }
  153. else
  154. itemsToAlign.push_back( boardItem );
  155. }
  156. aItemsToAlign = GetBoundingBoxes( itemsToAlign );
  157. aLockedItems = GetBoundingBoxes( lockedItems );
  158. std::sort( aItemsToAlign.begin(), aItemsToAlign.end(), aCompare );
  159. std::sort( aLockedItems.begin(), aLockedItems.end(), aCompare );
  160. return aItemsToAlign.size();
  161. }
  162. int ALIGN_DISTRIBUTE_TOOL::AlignTop( const TOOL_EVENT& aEvent )
  163. {
  164. std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
  165. std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
  166. if( !GetSelections( itemsToAlign, locked_items,
  167. []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
  168. {
  169. return ( lhs.second.GetTop() < rhs.second.GetTop() );
  170. } ) )
  171. {
  172. return 0;
  173. }
  174. BOARD_COMMIT commit( m_frame );
  175. int targetTop = selectTarget( itemsToAlign, locked_items,
  176. []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
  177. {
  178. return aVal.second.GetTop();
  179. } );
  180. // Move the selected items
  181. for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
  182. {
  183. BOARD_ITEM* item = i.first;
  184. int difference = targetTop - i.second.GetTop();
  185. if( item->GetParent() && item->GetParent()->IsSelected() )
  186. continue;
  187. // Don't move a pad by itself unless editing the footprint
  188. if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
  189. item = item->GetParent();
  190. commit.Stage( item, CHT_MODIFY );
  191. item->Move( VECTOR2I( 0, difference ) );
  192. }
  193. commit.Push( _( "Align to Top" ) );
  194. return 0;
  195. }
  196. int ALIGN_DISTRIBUTE_TOOL::AlignBottom( const TOOL_EVENT& aEvent )
  197. {
  198. std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
  199. std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
  200. if( !GetSelections( itemsToAlign, locked_items,
  201. []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs)
  202. {
  203. return ( lhs.second.GetBottom() > rhs.second.GetBottom() );
  204. } ) )
  205. {
  206. return 0;
  207. }
  208. BOARD_COMMIT commit( m_frame );
  209. int targetBottom = selectTarget( itemsToAlign, locked_items,
  210. []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
  211. {
  212. return aVal.second.GetBottom();
  213. } );
  214. // Move the selected items
  215. for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
  216. {
  217. int difference = targetBottom - i.second.GetBottom();
  218. BOARD_ITEM* item = i.first;
  219. if( item->GetParent() && item->GetParent()->IsSelected() )
  220. continue;
  221. // Don't move a pad by itself unless editing the footprint
  222. if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
  223. item = item->GetParent();
  224. commit.Stage( item, CHT_MODIFY );
  225. item->Move( VECTOR2I( 0, difference ) );
  226. }
  227. commit.Push( _( "Align to Bottom" ) );
  228. return 0;
  229. }
  230. int ALIGN_DISTRIBUTE_TOOL::AlignLeft( const TOOL_EVENT& aEvent )
  231. {
  232. // Because this tool uses bounding boxes and they aren't mirrored even when
  233. // the view is mirrored, we need to call the other one if mirrored.
  234. if( getView()->IsMirroredX() )
  235. return doAlignRight();
  236. else
  237. return doAlignLeft();
  238. }
  239. int ALIGN_DISTRIBUTE_TOOL::doAlignLeft()
  240. {
  241. std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
  242. std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
  243. if( !GetSelections( itemsToAlign, locked_items,
  244. []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
  245. {
  246. return ( lhs.second.GetLeft() < rhs.second.GetLeft() );
  247. } ) )
  248. {
  249. return 0;
  250. }
  251. BOARD_COMMIT commit( m_frame );
  252. int targetLeft = selectTarget( itemsToAlign, locked_items,
  253. []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
  254. {
  255. return aVal.second.GetLeft();
  256. } );
  257. // Move the selected items
  258. for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
  259. {
  260. int difference = targetLeft - i.second.GetLeft();
  261. BOARD_ITEM* item = i.first;
  262. if( item->GetParent() && item->GetParent()->IsSelected() )
  263. continue;
  264. // Don't move a pad by itself unless editing the footprint
  265. if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
  266. item = item->GetParent();
  267. commit.Stage( item, CHT_MODIFY );
  268. item->Move( VECTOR2I( difference, 0 ) );
  269. }
  270. commit.Push( _( "Align to Left" ) );
  271. return 0;
  272. }
  273. int ALIGN_DISTRIBUTE_TOOL::AlignRight( const TOOL_EVENT& aEvent )
  274. {
  275. // Because this tool uses bounding boxes and they aren't mirrored even when
  276. // the view is mirrored, we need to call the other one if mirrored.
  277. if( getView()->IsMirroredX() )
  278. return doAlignLeft();
  279. else
  280. return doAlignRight();
  281. }
  282. int ALIGN_DISTRIBUTE_TOOL::doAlignRight()
  283. {
  284. std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
  285. std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
  286. if( !GetSelections( itemsToAlign, locked_items,
  287. []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
  288. {
  289. return ( lhs.second.GetRight() > rhs.second.GetRight() );
  290. } ) )
  291. {
  292. return 0;
  293. }
  294. BOARD_COMMIT commit( m_frame );
  295. int targetRight = selectTarget( itemsToAlign, locked_items,
  296. []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
  297. {
  298. return aVal.second.GetRight();
  299. } );
  300. // Move the selected items
  301. for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
  302. {
  303. int difference = targetRight - i.second.GetRight();
  304. BOARD_ITEM* item = i.first;
  305. if( item->GetParent() && item->GetParent()->IsSelected() )
  306. continue;
  307. // Don't move a pad by itself unless editing the footprint
  308. if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
  309. item = item->GetParent();
  310. commit.Stage( item, CHT_MODIFY );
  311. item->Move( VECTOR2I( difference, 0 ) );
  312. }
  313. commit.Push( _( "Align to Right" ) );
  314. return 0;
  315. }
  316. int ALIGN_DISTRIBUTE_TOOL::AlignCenterX( const TOOL_EVENT& aEvent )
  317. {
  318. std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
  319. std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
  320. if( !GetSelections( itemsToAlign, locked_items,
  321. []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
  322. {
  323. return ( lhs.second.Centre().x < rhs.second.Centre().x );
  324. } ) )
  325. {
  326. return 0;
  327. }
  328. BOARD_COMMIT commit( m_frame );
  329. int targetX = selectTarget( itemsToAlign, locked_items,
  330. []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
  331. {
  332. return aVal.second.Centre().x;
  333. } );
  334. // Move the selected items
  335. for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
  336. {
  337. int difference = targetX - i.second.Centre().x;
  338. BOARD_ITEM* item = i.first;
  339. if( item->GetParent() && item->GetParent()->IsSelected() )
  340. continue;
  341. // Don't move a pad by itself unless editing the footprint
  342. if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
  343. item = item->GetParent();
  344. commit.Stage( item, CHT_MODIFY );
  345. item->Move( VECTOR2I( difference, 0 ) );
  346. }
  347. commit.Push( _( "Align to Middle" ) );
  348. return 0;
  349. }
  350. int ALIGN_DISTRIBUTE_TOOL::AlignCenterY( const TOOL_EVENT& aEvent )
  351. {
  352. std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToAlign;
  353. std::vector<std::pair<BOARD_ITEM*, BOX2I>> locked_items;
  354. if( !GetSelections( itemsToAlign, locked_items,
  355. []( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
  356. {
  357. return ( lhs.second.Centre().y < rhs.second.Centre().y );
  358. } ) )
  359. {
  360. return 0;
  361. }
  362. BOARD_COMMIT commit( m_frame );
  363. int targetY = selectTarget( itemsToAlign, locked_items,
  364. []( const std::pair<BOARD_ITEM*, BOX2I>& aVal )
  365. {
  366. return aVal.second.Centre().y;
  367. } );
  368. // Move the selected items
  369. for( const std::pair<BOARD_ITEM*, BOX2I>& i : itemsToAlign )
  370. {
  371. int difference = targetY - i.second.Centre().y;
  372. BOARD_ITEM* item = i.first;
  373. if( item->GetParent() && item->GetParent()->IsSelected() )
  374. continue;
  375. // Don't move a pad by itself unless editing the footprint
  376. if( item->Type() == PCB_PAD_T && m_frame->IsType( FRAME_PCB_EDITOR ) )
  377. item = item->GetParent();
  378. commit.Stage( item, CHT_MODIFY );
  379. item->Move( VECTOR2I( 0, difference ) );
  380. }
  381. commit.Push( _( "Align to Center" ) );
  382. return 0;
  383. }
  384. int ALIGN_DISTRIBUTE_TOOL::DistributeItems( const TOOL_EVENT& aEvent )
  385. {
  386. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  387. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector,
  388. PCB_SELECTION_TOOL* sTool )
  389. {
  390. sTool->FilterCollectorForMarkers( aCollector );
  391. sTool->FilterCollectorForHierarchy( aCollector, true );
  392. sTool->FilterCollectorForFreePads( aCollector );
  393. },
  394. m_frame->IsType( FRAME_PCB_EDITOR ) /* prompt user regarding locked items */
  395. );
  396. // Need at least 3 items to distribute - one at each end and at least on in the middle
  397. if( selection.Size() < 3 )
  398. return 0;
  399. BOARD_COMMIT commit( m_frame );
  400. wxString commitMsg;
  401. std::vector<std::pair<BOARD_ITEM*, BOX2I>> itemsToDistribute = GetBoundingBoxes( selection );
  402. if( aEvent.Matches( PCB_ACTIONS::distributeHorizontallyCenters.MakeEvent() ) )
  403. {
  404. doDistributeCenters( true, itemsToDistribute, commit );
  405. commitMsg = PCB_ACTIONS::distributeHorizontallyCenters.GetFriendlyName();
  406. }
  407. else if( aEvent.Matches( PCB_ACTIONS::distributeHorizontallyGaps.MakeEvent() ) )
  408. {
  409. doDistributeGaps( true, itemsToDistribute, commit );
  410. commitMsg = PCB_ACTIONS::distributeHorizontallyGaps.GetFriendlyName();
  411. }
  412. else if( aEvent.Matches( PCB_ACTIONS::distributeVerticallyCenters.MakeEvent() ) )
  413. {
  414. doDistributeCenters( false, itemsToDistribute, commit );
  415. commitMsg = PCB_ACTIONS::distributeVerticallyCenters.GetFriendlyName();
  416. }
  417. else
  418. {
  419. doDistributeGaps( false, itemsToDistribute, commit );
  420. commitMsg = PCB_ACTIONS::distributeVerticallyGaps.GetFriendlyName();
  421. }
  422. commit.Push( commitMsg );
  423. return 0;
  424. }
  425. void ALIGN_DISTRIBUTE_TOOL::doDistributeGaps( bool aIsXAxis,
  426. std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aItems,
  427. BOARD_COMMIT& aCommit ) const
  428. {
  429. // Sort by start position.
  430. // This is a simple way to get the items in a sensible order but it's not perfect.
  431. // It will fail if, say, there's a huge items that's bigger than the total span of
  432. // all the other items, but at that point a gap-equalising algorithm probably isn't
  433. // well-defined anyway.
  434. std::sort( aItems.begin(), aItems.end(),
  435. [&]( const std::pair<BOARD_ITEM*, BOX2I>& a, const std::pair<BOARD_ITEM*, BOX2I>& b )
  436. {
  437. return aIsXAxis ? a.second.GetLeft() < b.second.GetLeft()
  438. : a.second.GetTop() < b.second.GetTop();
  439. } );
  440. // Consruct list of item spans in the relevant axis
  441. std::vector<std::pair<int, int>> itemSpans;
  442. itemSpans.reserve( aItems.size() );
  443. for( const auto& [item, box] : aItems )
  444. {
  445. const int start = aIsXAxis ? box.GetLeft() : box.GetTop();
  446. const int end = aIsXAxis ? box.GetRight() : box.GetBottom();
  447. itemSpans.emplace_back( start, end );
  448. }
  449. // Get the deltas needed to distribute the items evenly
  450. const std::vector<int> deltas = GetDeltasForDistributeByGaps( itemSpans );
  451. // Apply the deltas to the items
  452. for( size_t i = 1; i < aItems.size() - 1; ++i )
  453. {
  454. const auto& [item, box] = aItems[i];
  455. const int delta = deltas[i];
  456. if( delta != 0 )
  457. {
  458. const VECTOR2I deltaVec = aIsXAxis ? VECTOR2I( delta, 0 ) : VECTOR2I( 0, delta );
  459. aCommit.Stage( item, CHT_MODIFY );
  460. item->Move( deltaVec );
  461. }
  462. }
  463. }
  464. void ALIGN_DISTRIBUTE_TOOL::doDistributeCenters( bool aIsXAxis,
  465. std::vector<std::pair<BOARD_ITEM*, BOX2I>>& aItems,
  466. BOARD_COMMIT& aCommit ) const
  467. {
  468. std::sort(
  469. aItems.begin(), aItems.end(),
  470. [&]( const std::pair<BOARD_ITEM*, BOX2I>& lhs, const std::pair<BOARD_ITEM*, BOX2I>& rhs )
  471. {
  472. const int lhsPos = aIsXAxis ? lhs.second.Centre().x : lhs.second.Centre().y;
  473. const int rhsPos = aIsXAxis ? rhs.second.Centre().x : rhs.second.Centre().y;
  474. return lhsPos < rhsPos;
  475. } );
  476. std::vector<int> itemCenters;
  477. itemCenters.reserve( aItems.size() );
  478. for( const auto& [item, box] : aItems )
  479. {
  480. itemCenters.push_back( aIsXAxis ? box.Centre().x : box.Centre().y );
  481. }
  482. const std::vector<int> deltas = GetDeltasForDistributeByPoints( itemCenters );
  483. // Apply the deltas to the items
  484. for( size_t i = 1; i < aItems.size() - 1; ++i )
  485. {
  486. const auto& [item, box] = aItems[i];
  487. const int delta = deltas[i];
  488. if ( delta != 0)
  489. {
  490. const VECTOR2I deltaVec = aIsXAxis ? VECTOR2I( delta, 0 ) : VECTOR2I( 0, delta );
  491. aCommit.Stage( item, CHT_MODIFY );
  492. item->Move( deltaVec );
  493. }
  494. }
  495. }
  496. void ALIGN_DISTRIBUTE_TOOL::setTransitions()
  497. {
  498. Go( &ALIGN_DISTRIBUTE_TOOL::AlignTop, PCB_ACTIONS::alignTop.MakeEvent() );
  499. Go( &ALIGN_DISTRIBUTE_TOOL::AlignBottom, PCB_ACTIONS::alignBottom.MakeEvent() );
  500. Go( &ALIGN_DISTRIBUTE_TOOL::AlignLeft, PCB_ACTIONS::alignLeft.MakeEvent() );
  501. Go( &ALIGN_DISTRIBUTE_TOOL::AlignRight, PCB_ACTIONS::alignRight.MakeEvent() );
  502. Go( &ALIGN_DISTRIBUTE_TOOL::AlignCenterX, PCB_ACTIONS::alignCenterX.MakeEvent() );
  503. Go( &ALIGN_DISTRIBUTE_TOOL::AlignCenterY, PCB_ACTIONS::alignCenterY.MakeEvent() );
  504. Go( &ALIGN_DISTRIBUTE_TOOL::DistributeItems,
  505. PCB_ACTIONS::distributeHorizontallyCenters.MakeEvent() );
  506. Go( &ALIGN_DISTRIBUTE_TOOL::DistributeItems,
  507. PCB_ACTIONS::distributeHorizontallyGaps.MakeEvent() );
  508. Go( &ALIGN_DISTRIBUTE_TOOL::DistributeItems,
  509. PCB_ACTIONS::distributeVerticallyCenters.MakeEvent() );
  510. Go( &ALIGN_DISTRIBUTE_TOOL::DistributeItems,
  511. PCB_ACTIONS::distributeVerticallyGaps.MakeEvent() );
  512. }