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.

3201 lines
110 KiB

3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
7 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
4 years ago
12 years ago
2 years ago
4 years ago
10 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2013-2023 CERN
  5. * Copyright (C) 2017-2024 KiCad Developers, see AUTHORS.txt for contributors.
  6. * @author Maciej Suminski <maciej.suminski@cern.ch>
  7. * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
  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, you may find one here:
  21. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  22. * or you may search the http://www.gnu.org website for the version 2 license,
  23. * or you may write to the Free Software Foundation, Inc.,
  24. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  25. */
  26. #include <macros.h>
  27. #include <advanced_config.h>
  28. #include <limits>
  29. #include <kiplatform/ui.h>
  30. #include <gal/graphics_abstraction_layer.h>
  31. #include <board.h>
  32. #include <board_design_settings.h>
  33. #include <footprint.h>
  34. #include <pcb_shape.h>
  35. #include <pcb_group.h>
  36. #include <pcb_target.h>
  37. #include <pcb_textbox.h>
  38. #include <pcb_table.h>
  39. #include <pcb_generator.h>
  40. #include <pcb_edit_frame.h>
  41. #include <drawing_sheet/ds_proxy_view_item.h>
  42. #include <kiway.h>
  43. #include <array_creator.h>
  44. #include <status_popup.h>
  45. #include <tool/selection_conditions.h>
  46. #include <tool/tool_manager.h>
  47. #include <tools/pcb_actions.h>
  48. #include <tools/pcb_selection_tool.h>
  49. #include <tools/edit_tool.h>
  50. #include <tools/item_modification_routine.h>
  51. #include <tools/pcb_picker_tool.h>
  52. #include <tools/pcb_point_editor.h>
  53. #include <tools/tool_event_utils.h>
  54. #include <tools/pcb_grid_helper.h>
  55. #include <tools/pad_tool.h>
  56. #include <view/view_controls.h>
  57. #include <connectivity/connectivity_algo.h>
  58. #include <core/kicad_algo.h>
  59. #include <fix_board_shape.h>
  60. #include <bitmaps.h>
  61. #include <functional>
  62. using namespace std::placeholders;
  63. #include "kicad_clipboard.h"
  64. #include <wx/hyperlink.h>
  65. #include <router/router_tool.h>
  66. #include <dialogs/dialog_move_exact.h>
  67. #include <dialogs/dialog_track_via_properties.h>
  68. #include <dialogs/dialog_tablecell_properties.h>
  69. #include <dialogs/dialog_table_properties.h>
  70. #include <dialogs/dialog_unit_entry.h>
  71. #include <pcb_reference_image.h>
  72. const unsigned int EDIT_TOOL::COORDS_PADDING = pcbIUScale.mmToIU( 20 );
  73. static const std::vector<KICAD_T> padTypes = { PCB_PAD_T };
  74. static const std::vector<KICAD_T> footprintTypes = { PCB_FOOTPRINT_T };
  75. static const std::vector<KICAD_T> groupTypes = { PCB_GROUP_T };
  76. static const std::vector<KICAD_T> trackTypes = { PCB_TRACE_T,
  77. PCB_ARC_T,
  78. PCB_VIA_T };
  79. static const std::vector<KICAD_T> baseConnectedTypes = { PCB_PAD_T,
  80. PCB_VIA_T,
  81. PCB_TRACE_T,
  82. PCB_ARC_T };
  83. static const std::vector<KICAD_T> connectedTypes = { PCB_TRACE_T,
  84. PCB_ARC_T,
  85. PCB_VIA_T,
  86. PCB_PAD_T,
  87. PCB_ZONE_T };
  88. static const std::vector<KICAD_T> unroutableTypes = { PCB_TRACE_T,
  89. PCB_ARC_T,
  90. PCB_VIA_T,
  91. PCB_PAD_T,
  92. PCB_FOOTPRINT_T };
  93. EDIT_TOOL::EDIT_TOOL() :
  94. PCB_TOOL_BASE( "pcbnew.InteractiveEdit" ),
  95. m_selectionTool( nullptr ),
  96. m_dragging( false )
  97. {
  98. }
  99. void EDIT_TOOL::Reset( RESET_REASON aReason )
  100. {
  101. m_dragging = false;
  102. m_statusPopup = std::make_unique<STATUS_TEXT_POPUP>( getEditFrame<PCB_BASE_EDIT_FRAME>() );
  103. }
  104. static std::shared_ptr<CONDITIONAL_MENU> makePositioningToolsMenu( TOOL_INTERACTIVE* aTool )
  105. {
  106. auto menu = std::make_shared<CONDITIONAL_MENU>( aTool );
  107. menu->SetIcon( BITMAPS::special_tools );
  108. menu->SetTitle( _( "Positioning Tools" ) );
  109. auto notMovingCondition = []( const SELECTION& aSelection )
  110. {
  111. return aSelection.Empty() || !aSelection.Front()->IsMoving();
  112. };
  113. // clang-format off
  114. menu->AddItem( PCB_ACTIONS::moveExact, SELECTION_CONDITIONS::NotEmpty && notMovingCondition );
  115. menu->AddItem( PCB_ACTIONS::moveWithReference, SELECTION_CONDITIONS::NotEmpty && notMovingCondition );
  116. menu->AddItem( PCB_ACTIONS::copyWithReference, SELECTION_CONDITIONS::NotEmpty && notMovingCondition );
  117. menu->AddItem( PCB_ACTIONS::positionRelative, SELECTION_CONDITIONS::NotEmpty && notMovingCondition );
  118. // clang-format on
  119. return menu;
  120. };
  121. static std::shared_ptr<CONDITIONAL_MENU> makeShapeModificationMenu( TOOL_INTERACTIVE* aTool )
  122. {
  123. auto menu = std::make_shared<CONDITIONAL_MENU>( aTool );
  124. menu->SetTitle( _( "Shape Modification" ) );
  125. static const std::vector<KICAD_T> filletChamferTypes = { PCB_SHAPE_LOCATE_POLY_T,
  126. PCB_SHAPE_LOCATE_RECT_T,
  127. PCB_SHAPE_LOCATE_SEGMENT_T };
  128. static const std::vector<KICAD_T> healShapesTypes = { PCB_SHAPE_LOCATE_SEGMENT_T,
  129. PCB_SHAPE_LOCATE_ARC_T,
  130. PCB_SHAPE_LOCATE_BEZIER_T };
  131. static const std::vector<KICAD_T> lineExtendTypes = { PCB_SHAPE_LOCATE_SEGMENT_T };
  132. static const std::vector<KICAD_T> polygonBooleanTypes = { PCB_SHAPE_LOCATE_RECT_T,
  133. PCB_SHAPE_LOCATE_POLY_T };
  134. static const std::vector<KICAD_T> polygonSimplifyTypes = { PCB_SHAPE_LOCATE_POLY_T,
  135. PCB_ZONE_T };
  136. auto hasCornerCondition =
  137. [aTool]( const SELECTION& aSelection )
  138. {
  139. PCB_POINT_EDITOR *pt_tool = aTool->GetManager()->GetTool<PCB_POINT_EDITOR>();
  140. return pt_tool && pt_tool->HasCorner();
  141. };
  142. auto hasMidpointCondition =
  143. [aTool]( const SELECTION& aSelection )
  144. {
  145. PCB_POINT_EDITOR *pt_tool = aTool->GetManager()->GetTool<PCB_POINT_EDITOR>();
  146. return pt_tool && pt_tool->HasMidpoint();
  147. };
  148. // clang-format off
  149. menu->AddItem( PCB_ACTIONS::healShapes, SELECTION_CONDITIONS::HasTypes( healShapesTypes ) );
  150. menu->AddItem( PCB_ACTIONS::simplifyPolygons, SELECTION_CONDITIONS::HasTypes( polygonSimplifyTypes ) );
  151. menu->AddItem( PCB_ACTIONS::filletLines, SELECTION_CONDITIONS::OnlyTypes( filletChamferTypes ) );
  152. menu->AddItem( PCB_ACTIONS::chamferLines, SELECTION_CONDITIONS::OnlyTypes( filletChamferTypes ) );
  153. menu->AddItem( PCB_ACTIONS::extendLines, SELECTION_CONDITIONS::OnlyTypes( lineExtendTypes )
  154. && SELECTION_CONDITIONS::Count( 2 ) );
  155. menu->AddItem( PCB_ACTIONS::pointEditorMoveCorner, hasCornerCondition );
  156. menu->AddItem( PCB_ACTIONS::pointEditorMoveMidpoint, hasMidpointCondition );
  157. menu->AddSeparator( SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
  158. && SELECTION_CONDITIONS::MoreThan( 1 ) );
  159. menu->AddItem( PCB_ACTIONS::mergePolygons, SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
  160. && SELECTION_CONDITIONS::MoreThan( 1 ) );
  161. menu->AddItem( PCB_ACTIONS::subtractPolygons, SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
  162. && SELECTION_CONDITIONS::MoreThan( 1 ) );
  163. menu->AddItem( PCB_ACTIONS::intersectPolygons, SELECTION_CONDITIONS::OnlyTypes( polygonBooleanTypes )
  164. && SELECTION_CONDITIONS::MoreThan( 1 ) );
  165. // clang-format on
  166. return menu;
  167. };
  168. bool EDIT_TOOL::Init()
  169. {
  170. // Find the selection tool, so they can cooperate
  171. m_selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  172. std::shared_ptr<CONDITIONAL_MENU> positioningToolsSubMenu = makePositioningToolsMenu( this );
  173. m_selectionTool->GetToolMenu().RegisterSubMenu( positioningToolsSubMenu );
  174. std::shared_ptr<CONDITIONAL_MENU> shapeModificationSubMenu = makeShapeModificationMenu( this );
  175. m_selectionTool->GetToolMenu().RegisterSubMenu( shapeModificationSubMenu );
  176. auto positioningToolsCondition =
  177. [&]( const SELECTION& aSel )
  178. {
  179. std::shared_ptr<CONDITIONAL_MENU> subMenu = makePositioningToolsMenu( this );
  180. subMenu->Evaluate( aSel );
  181. return subMenu->GetMenuItemCount() > 0;
  182. };
  183. auto shapeModificationCondition =
  184. [&]( const SELECTION& aSel )
  185. {
  186. std::shared_ptr<CONDITIONAL_MENU> subMenu = makeShapeModificationMenu( this );
  187. subMenu->Evaluate( aSel );
  188. return subMenu->GetMenuItemCount() > 0;
  189. };
  190. auto propertiesCondition =
  191. [&]( const SELECTION& aSel )
  192. {
  193. if( aSel.GetSize() == 0 )
  194. {
  195. if( getView()->IsLayerVisible( LAYER_SCHEMATIC_DRAWINGSHEET ) )
  196. {
  197. DS_PROXY_VIEW_ITEM* ds = canvas()->GetDrawingSheet();
  198. VECTOR2D cursor = getViewControls()->GetCursorPosition( false );
  199. if( ds && ds->HitTestDrawingSheetItems( getView(), cursor ) )
  200. return true;
  201. }
  202. return false;
  203. }
  204. if( aSel.GetSize() == 1 )
  205. return true;
  206. for( EDA_ITEM* item : aSel )
  207. {
  208. if( !dynamic_cast<PCB_TRACK*>( item ) )
  209. return false;
  210. }
  211. return true;
  212. };
  213. auto inFootprintEditor =
  214. [ this ]( const SELECTION& aSelection )
  215. {
  216. return m_isFootprintEditor;
  217. };
  218. auto canMirror =
  219. [ this ]( const SELECTION& aSelection )
  220. {
  221. if( !m_isFootprintEditor
  222. && SELECTION_CONDITIONS::OnlyTypes( padTypes )( aSelection ) )
  223. {
  224. return false;
  225. }
  226. if( SELECTION_CONDITIONS::HasTypes( groupTypes )( aSelection ) )
  227. return true;
  228. return SELECTION_CONDITIONS::HasTypes( EDIT_TOOL::MirrorableItems )( aSelection );
  229. };
  230. auto singleFootprintCondition = SELECTION_CONDITIONS::OnlyTypes( footprintTypes )
  231. && SELECTION_CONDITIONS::Count( 1 );
  232. auto multipleFootprintsCondition =
  233. []( const SELECTION& aSelection )
  234. {
  235. bool foundFirst = false;
  236. for( EDA_ITEM* item : aSelection )
  237. {
  238. if( item->Type() == PCB_FOOTPRINT_T )
  239. {
  240. if( foundFirst )
  241. return true;
  242. else
  243. foundFirst = true;
  244. }
  245. }
  246. return false;
  247. };
  248. auto noActiveToolCondition =
  249. [ this ]( const SELECTION& aSelection )
  250. {
  251. return frame()->ToolStackIsEmpty();
  252. };
  253. auto notMovingCondition =
  254. []( const SELECTION& aSelection )
  255. {
  256. return aSelection.Empty() || !aSelection.Front()->IsMoving();
  257. };
  258. auto noItemsCondition =
  259. [ this ]( const SELECTION& aSelections ) -> bool
  260. {
  261. return frame()->GetBoard() && !frame()->GetBoard()->IsEmpty();
  262. };
  263. auto isSkippable =
  264. [ this ]( const SELECTION& aSelection )
  265. {
  266. return frame()->IsCurrentTool( PCB_ACTIONS::moveIndividually );
  267. };
  268. // Add context menu entries that are displayed when selection tool is active
  269. CONDITIONAL_MENU& menu = m_selectionTool->GetToolMenu().GetMenu();
  270. // clang-format off
  271. menu.AddItem( PCB_ACTIONS::move, SELECTION_CONDITIONS::NotEmpty
  272. && notMovingCondition );
  273. menu.AddItem( PCB_ACTIONS::unrouteSelected, SELECTION_CONDITIONS::NotEmpty
  274. && SELECTION_CONDITIONS::OnlyTypes( unroutableTypes )
  275. && notMovingCondition
  276. && !inFootprintEditor );
  277. menu.AddItem( PCB_ACTIONS::moveIndividually, SELECTION_CONDITIONS::MoreThan( 1 )
  278. && notMovingCondition );
  279. menu.AddItem( PCB_ACTIONS::skip, isSkippable );
  280. menu.AddItem( PCB_ACTIONS::breakTrack, SELECTION_CONDITIONS::Count( 1 )
  281. && SELECTION_CONDITIONS::OnlyTypes( trackTypes ) );
  282. menu.AddItem( PCB_ACTIONS::drag45Degree, SELECTION_CONDITIONS::Count( 1 )
  283. && SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::DraggableItems ) );
  284. menu.AddItem( PCB_ACTIONS::dragFreeAngle, SELECTION_CONDITIONS::Count( 1 )
  285. && SELECTION_CONDITIONS::OnlyTypes( GENERAL_COLLECTOR::DraggableItems )
  286. && !SELECTION_CONDITIONS::OnlyTypes( footprintTypes ) );
  287. menu.AddItem( PCB_ACTIONS::filletTracks, SELECTION_CONDITIONS::OnlyTypes( trackTypes ) );
  288. menu.AddItem( PCB_ACTIONS::rotateCcw, SELECTION_CONDITIONS::NotEmpty );
  289. menu.AddItem( PCB_ACTIONS::rotateCw, SELECTION_CONDITIONS::NotEmpty );
  290. menu.AddItem( PCB_ACTIONS::flip, SELECTION_CONDITIONS::NotEmpty );
  291. menu.AddItem( PCB_ACTIONS::mirrorH, canMirror );
  292. menu.AddItem( PCB_ACTIONS::mirrorV, canMirror );
  293. menu.AddItem( PCB_ACTIONS::swap, SELECTION_CONDITIONS::MoreThan( 1 ) );
  294. menu.AddItem( PCB_ACTIONS::packAndMoveFootprints, SELECTION_CONDITIONS::MoreThan( 1 )
  295. && SELECTION_CONDITIONS::HasType( PCB_FOOTPRINT_T ) );
  296. menu.AddItem( PCB_ACTIONS::properties, propertiesCondition );
  297. menu.AddItem( PCB_ACTIONS::assignNetClass, SELECTION_CONDITIONS::OnlyTypes( connectedTypes )
  298. && !inFootprintEditor );
  299. menu.AddItem( PCB_ACTIONS::inspectClearance, SELECTION_CONDITIONS::Count( 2 ) );
  300. // Footprint actions
  301. menu.AddSeparator();
  302. menu.AddItem( PCB_ACTIONS::editFpInFpEditor, singleFootprintCondition );
  303. menu.AddItem( PCB_ACTIONS::updateFootprint, singleFootprintCondition );
  304. menu.AddItem( PCB_ACTIONS::updateFootprints, multipleFootprintsCondition );
  305. menu.AddItem( PCB_ACTIONS::changeFootprint, singleFootprintCondition );
  306. menu.AddItem( PCB_ACTIONS::changeFootprints, multipleFootprintsCondition );
  307. // Add the submenu for the special tools: modfiers and positioning tools
  308. menu.AddSeparator( 100 );
  309. menu.AddMenu( shapeModificationSubMenu.get(), shapeModificationCondition, 100 );
  310. menu.AddMenu( positioningToolsSubMenu.get(), positioningToolsCondition, 100 );
  311. menu.AddSeparator( 150 );
  312. menu.AddItem( ACTIONS::cut, SELECTION_CONDITIONS::NotEmpty, 150 );
  313. menu.AddItem( ACTIONS::copy, SELECTION_CONDITIONS::NotEmpty, 150 );
  314. // Selection tool handles the context menu for some other tools, such as the Picker.
  315. // Don't add things like Paste when another tool is active.
  316. menu.AddItem( ACTIONS::paste, noActiveToolCondition, 150 );
  317. menu.AddItem( ACTIONS::pasteSpecial, noActiveToolCondition && !inFootprintEditor, 150 );
  318. menu.AddItem( ACTIONS::duplicate, SELECTION_CONDITIONS::NotEmpty, 150 );
  319. menu.AddItem( ACTIONS::doDelete, SELECTION_CONDITIONS::NotEmpty, 150 );
  320. menu.AddSeparator( 150 );
  321. menu.AddItem( ACTIONS::selectAll, noItemsCondition, 150 );
  322. menu.AddItem( ACTIONS::unselectAll, noItemsCondition, 150 );
  323. // clang-format on
  324. return true;
  325. }
  326. int EDIT_TOOL::GetAndPlace( const TOOL_EVENT& aEvent )
  327. {
  328. // GetAndPlace makes sense only in board editor, although it is also called
  329. // in fpeditor, that shares the same EDIT_TOOL list
  330. if( !getEditFrame<PCB_BASE_FRAME>()->IsType( FRAME_PCB_EDITOR ) )
  331. return 0;
  332. PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  333. FOOTPRINT* fp = getEditFrame<PCB_BASE_FRAME>()->GetFootprintFromBoardByReference();
  334. if( fp )
  335. {
  336. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  337. m_toolMgr->RunAction<EDA_ITEM*>( PCB_ACTIONS::selectItem, fp );
  338. selectionTool->GetSelection().SetReferencePoint( fp->GetPosition() );
  339. m_toolMgr->PostAction( PCB_ACTIONS::move );
  340. }
  341. return 0;
  342. }
  343. bool EDIT_TOOL::invokeInlineRouter( int aDragMode )
  344. {
  345. ROUTER_TOOL* theRouter = m_toolMgr->GetTool<ROUTER_TOOL>();
  346. if( !theRouter )
  347. return false;
  348. // don't allow switch from moving to dragging
  349. if( m_dragging )
  350. {
  351. wxBell();
  352. return false;
  353. }
  354. // make sure we don't accidentally invoke inline routing mode while the router is already
  355. // active!
  356. if( theRouter->IsToolActive() )
  357. return false;
  358. if( theRouter->CanInlineDrag( aDragMode ) )
  359. {
  360. m_toolMgr->RunAction( PCB_ACTIONS::routerInlineDrag, aDragMode );
  361. return true;
  362. }
  363. return false;
  364. }
  365. bool EDIT_TOOL::isRouterActive() const
  366. {
  367. ROUTER_TOOL* router = m_toolMgr->GetTool<ROUTER_TOOL>();
  368. return router && router->RoutingInProgress();
  369. }
  370. int EDIT_TOOL::Drag( const TOOL_EVENT& aEvent )
  371. {
  372. if( !m_toolMgr->GetTool<ROUTER_TOOL>() )
  373. {
  374. wxBell();
  375. return false; // don't drag when no router tool (i.e. fp editor)
  376. }
  377. if( m_toolMgr->GetTool<ROUTER_TOOL>()->IsToolActive() )
  378. {
  379. wxBell();
  380. return false; // don't drag when router is already active
  381. }
  382. if( m_dragging )
  383. {
  384. wxBell();
  385. return false; // don't do a router drag when already in an EDIT_TOOL drag
  386. }
  387. int mode = PNS::DM_ANY;
  388. if( aEvent.IsAction( &PCB_ACTIONS::dragFreeAngle ) )
  389. mode |= PNS::DM_FREE_ANGLE;
  390. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  391. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  392. {
  393. sTool->FilterCollectorForFreePads( aCollector );
  394. sTool->FilterCollectorForHierarchy( aCollector, true );
  395. std::vector<PCB_TRACK*> tracks;
  396. std::vector<PCB_TRACK*> vias;
  397. std::vector<FOOTPRINT*> footprints;
  398. for( EDA_ITEM* item : aCollector )
  399. {
  400. if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item ) )
  401. {
  402. if( track->Type() == PCB_VIA_T )
  403. vias.push_back( track );
  404. else
  405. tracks.push_back( track );
  406. }
  407. else if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( item ) )
  408. {
  409. footprints.push_back( footprint );
  410. }
  411. }
  412. if( footprints.size() == (unsigned) aCollector.GetCount() )
  413. {
  414. // We can drag multiple footprints; no need to whittle down selection.
  415. }
  416. else if( tracks.size() || vias.size() )
  417. {
  418. /*
  419. * First trim down selection to active layer, tracks vs zones, etc.
  420. */
  421. if( aCollector.GetCount() > 1 )
  422. sTool->GuessSelectionCandidates( aCollector, aPt );
  423. /*
  424. * If we have a knee between two tracks, or a via attached to two tracks,
  425. * then drop the selection to a single item. We don't want a selection
  426. * disambiguation menu when it doesn't matter which items is picked.
  427. */
  428. auto connected = []( PCB_TRACK* track, const VECTOR2I& pt )
  429. {
  430. return track->GetStart() == pt || track->GetEnd() == pt;
  431. };
  432. if( tracks.size() == 2 && vias.size() == 0 )
  433. {
  434. if( connected( tracks[0], tracks[1]->GetStart() )
  435. || connected( tracks[0], tracks[1]->GetEnd() ) )
  436. {
  437. aCollector.Remove( tracks[1] );
  438. }
  439. }
  440. else if( tracks.size() == 2 && vias.size() == 1 )
  441. {
  442. if( connected( tracks[0], vias[0]->GetPosition() )
  443. && connected( tracks[1], vias[0]->GetPosition() ) )
  444. {
  445. aCollector.Remove( tracks[0] );
  446. aCollector.Remove( tracks[1] );
  447. }
  448. }
  449. }
  450. },
  451. true /* prompt user regarding locked items */ );
  452. if( selection.Empty() )
  453. return 0;
  454. if( selection.Size() == 1 && selection.Front()->Type() == PCB_ARC_T )
  455. {
  456. // TODO: This really should be done in PNS to ensure DRC is maintained, but for now
  457. // it allows interactive editing of an arc track
  458. return DragArcTrack( aEvent );
  459. }
  460. else
  461. {
  462. invokeInlineRouter( mode );
  463. }
  464. return 0;
  465. }
  466. int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent )
  467. {
  468. PCB_SELECTION& selection = m_selectionTool->GetSelection();
  469. if( selection.Size() != 1 || selection.Front()->Type() != PCB_ARC_T )
  470. return 0;
  471. PCB_ARC* theArc = static_cast<PCB_ARC*>( selection.Front() );
  472. EDA_ANGLE maxTangentDeviation( ADVANCED_CFG::GetCfg().m_MaxTangentAngleDeviation, DEGREES_T );
  473. if( theArc->GetAngle() + maxTangentDeviation >= ANGLE_180 )
  474. {
  475. wxString msg = wxString::Format( _( "Unable to resize arc tracks of %s or greater." ),
  476. EDA_UNIT_UTILS::UI::MessageTextFromValue( ANGLE_180 - maxTangentDeviation ) );
  477. frame()->ShowInfoBarError( msg );
  478. return 0; // don't bother with > 180 degree arcs
  479. }
  480. KIGFX::VIEW_CONTROLS* controls = getViewControls();
  481. Activate();
  482. // Must be done after Activate() so that it gets set into the correct context
  483. controls->ShowCursor( true );
  484. controls->SetAutoPan( true );
  485. BOARD_COMMIT commit( this );
  486. bool restore_state = false;
  487. commit.Modify( theArc );
  488. VECTOR2I arcCenter = theArc->GetCenter();
  489. SEG tanStart = SEG( arcCenter, theArc->GetStart() ).PerpendicularSeg( theArc->GetStart() );
  490. SEG tanEnd = SEG( arcCenter, theArc->GetEnd() ).PerpendicularSeg( theArc->GetEnd() );
  491. //Ensure the tangent segments are in the correct orientation
  492. OPT_VECTOR2I tanIntersect = tanStart.IntersectLines( tanEnd );
  493. if( !tanIntersect )
  494. return 0;
  495. tanStart.A = *tanIntersect;
  496. tanStart.B = theArc->GetStart();
  497. tanEnd.A = *tanIntersect;
  498. tanEnd.B = theArc->GetEnd();
  499. std::set<PCB_TRACK*> addedTracks;
  500. auto getUniqueTrackAtAnchorCollinear =
  501. [&]( const VECTOR2I& aAnchor, const SEG& aCollinearSeg ) -> PCB_TRACK*
  502. {
  503. std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
  504. // Allow items at a distance within the width of the arc track
  505. int allowedDeviation = theArc->GetWidth();
  506. std::vector<BOARD_CONNECTED_ITEM*> itemsOnAnchor;
  507. for( int i = 0; i < 3; i++ )
  508. {
  509. itemsOnAnchor = conn->GetConnectedItemsAtAnchor( theArc, aAnchor,
  510. baseConnectedTypes,
  511. allowedDeviation );
  512. allowedDeviation /= 2;
  513. if( itemsOnAnchor.size() == 1 )
  514. break;
  515. }
  516. PCB_TRACK* track = nullptr;
  517. if( itemsOnAnchor.size() == 1 && itemsOnAnchor.front()->Type() == PCB_TRACE_T )
  518. {
  519. track = static_cast<PCB_TRACK*>( itemsOnAnchor.front() );
  520. commit.Modify( track );
  521. SEG trackSeg( track->GetStart(), track->GetEnd() );
  522. // Allow deviations in colinearity as defined in ADVANCED_CFG
  523. if( trackSeg.Angle( aCollinearSeg ) > maxTangentDeviation )
  524. track = nullptr;
  525. }
  526. if( !track )
  527. {
  528. track = new PCB_TRACK( theArc->GetParent() );
  529. track->SetStart( aAnchor );
  530. track->SetEnd( aAnchor );
  531. track->SetNet( theArc->GetNet() );
  532. track->SetLayer( theArc->GetLayer() );
  533. track->SetWidth( theArc->GetWidth() );
  534. track->SetLocked( theArc->IsLocked() );
  535. track->SetFlags( IS_NEW );
  536. getView()->Add( track );
  537. addedTracks.insert( track );
  538. }
  539. return track;
  540. };
  541. PCB_TRACK* trackOnStart = getUniqueTrackAtAnchorCollinear( theArc->GetStart(), tanStart);
  542. PCB_TRACK* trackOnEnd = getUniqueTrackAtAnchorCollinear( theArc->GetEnd(), tanEnd );
  543. if( trackOnStart->GetLength() != 0 )
  544. {
  545. tanStart.A = trackOnStart->GetStart();
  546. tanStart.B = trackOnStart->GetEnd();
  547. }
  548. if( trackOnEnd->GetLength() != 0 )
  549. {
  550. tanEnd.A = trackOnEnd->GetStart();
  551. tanEnd.B = trackOnEnd->GetEnd();
  552. }
  553. // Recalculate intersection point
  554. if( tanIntersect = tanStart.IntersectLines( tanEnd ); !tanIntersect )
  555. return 0;
  556. auto isTrackStartClosestToArcStart =
  557. [&]( PCB_TRACK* aTrack ) -> bool
  558. {
  559. double trackStartToArcStart = aTrack->GetStart().Distance( theArc->GetStart() );
  560. double trackEndToArcStart = aTrack->GetEnd().Distance( theArc->GetStart() );
  561. return trackStartToArcStart < trackEndToArcStart;
  562. };
  563. bool isStartTrackOnStartPt = isTrackStartClosestToArcStart( trackOnStart );
  564. bool isEndTrackOnStartPt = isTrackStartClosestToArcStart( trackOnEnd );
  565. // Calculate constraints
  566. //======================
  567. // maxTanCircle is the circle with maximum radius that is tangent to the two adjacent straight
  568. // tracks and whose tangent points are constrained within the original tracks and their
  569. // projected intersection points.
  570. //
  571. // The cursor will be constrained first within the isosceles triangle formed by the segments
  572. // cSegTanStart, cSegTanEnd and cSegChord. After that it will be constrained to be outside
  573. // maxTanCircle.
  574. //
  575. //
  576. // ____________ <-cSegTanStart
  577. // / * . ' *
  578. // cSegTanEnd-> / * . ' *
  579. // /* . ' <-cSegChord *
  580. // /. '
  581. // /* *
  582. //
  583. // * c * <-maxTanCircle
  584. //
  585. // * *
  586. //
  587. // * *
  588. // * *
  589. // * *
  590. //
  591. auto getFurthestPointToTanInterstect =
  592. [&]( VECTOR2I& aPointA, VECTOR2I& aPointB ) -> VECTOR2I
  593. {
  594. if( ( aPointA - *tanIntersect ).EuclideanNorm()
  595. > ( aPointB - *tanIntersect ).EuclideanNorm() )
  596. {
  597. return aPointA;
  598. }
  599. else
  600. {
  601. return aPointB;
  602. }
  603. };
  604. CIRCLE maxTanCircle;
  605. VECTOR2I tanStartPoint = getFurthestPointToTanInterstect( tanStart.A, tanStart.B );
  606. VECTOR2I tanEndPoint = getFurthestPointToTanInterstect( tanEnd.A, tanEnd.B );
  607. VECTOR2I tempTangentPoint = tanEndPoint;
  608. if( getFurthestPointToTanInterstect( tanStartPoint, tanEndPoint ) == tanEndPoint )
  609. tempTangentPoint = tanStartPoint;
  610. maxTanCircle.ConstructFromTanTanPt( tanStart, tanEnd, tempTangentPoint );
  611. VECTOR2I maxTanPtStart = tanStart.LineProject( maxTanCircle.Center );
  612. VECTOR2I maxTanPtEnd = tanEnd.LineProject( maxTanCircle.Center );
  613. SEG cSegTanStart( maxTanPtStart, *tanIntersect );
  614. SEG cSegTanEnd( maxTanPtEnd, *tanIntersect );
  615. SEG cSegChord( maxTanPtStart, maxTanPtEnd );
  616. int cSegTanStartSide = cSegTanStart.Side( theArc->GetMid() );
  617. int cSegTanEndSide = cSegTanEnd.Side( theArc->GetMid() );
  618. int cSegChordSide = cSegChord.Side( theArc->GetMid() );
  619. bool eatFirstMouseUp = true;
  620. // Start the tool loop
  621. while( TOOL_EVENT* evt = Wait() )
  622. {
  623. m_cursor = controls->GetMousePosition();
  624. // Constrain cursor within the isosceles triangle
  625. if( cSegTanStartSide != cSegTanStart.Side( m_cursor )
  626. || cSegTanEndSide != cSegTanEnd.Side( m_cursor )
  627. || cSegChordSide != cSegChord.Side( m_cursor ) )
  628. {
  629. std::vector<VECTOR2I> possiblePoints;
  630. possiblePoints.push_back( cSegTanEnd.NearestPoint( m_cursor ) );
  631. possiblePoints.push_back( cSegChord.NearestPoint( m_cursor ) );
  632. VECTOR2I closest = cSegTanStart.NearestPoint( m_cursor );
  633. for( const VECTOR2I& candidate : possiblePoints )
  634. {
  635. if( ( candidate - m_cursor ).SquaredEuclideanNorm()
  636. < ( closest - m_cursor ).SquaredEuclideanNorm() )
  637. {
  638. closest = candidate;
  639. }
  640. }
  641. m_cursor = closest;
  642. }
  643. // Constrain cursor to be outside maxTanCircle
  644. if( ( m_cursor - maxTanCircle.Center ).EuclideanNorm() < maxTanCircle.Radius )
  645. m_cursor = maxTanCircle.NearestPoint( m_cursor );
  646. controls->ForceCursorPosition( true, m_cursor );
  647. // Calculate resulting object coordinates
  648. CIRCLE circlehelper;
  649. circlehelper.ConstructFromTanTanPt( cSegTanStart, cSegTanEnd, m_cursor );
  650. VECTOR2I newCenter = circlehelper.Center;
  651. VECTOR2I newStart = cSegTanStart.LineProject( newCenter );
  652. VECTOR2I newEnd = cSegTanEnd.LineProject( newCenter );
  653. VECTOR2I newMid = CalcArcMid( newStart, newEnd, newCenter );
  654. // Update objects
  655. theArc->SetStart( newStart );
  656. theArc->SetEnd( newEnd );
  657. theArc->SetMid( newMid );
  658. if( isStartTrackOnStartPt )
  659. trackOnStart->SetStart( newStart );
  660. else
  661. trackOnStart->SetEnd( newStart );
  662. if( isEndTrackOnStartPt )
  663. trackOnEnd->SetStart( newEnd );
  664. else
  665. trackOnEnd->SetEnd( newEnd );
  666. // Update view
  667. getView()->Update( trackOnStart );
  668. getView()->Update( trackOnEnd );
  669. getView()->Update( theArc );
  670. // Handle events
  671. if( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) )
  672. {
  673. eatFirstMouseUp = false;
  674. }
  675. else if( evt->IsCancelInteractive() || evt->IsActivate() )
  676. {
  677. restore_state = true; // Canceling the tool means that items have to be restored
  678. break; // Finish
  679. }
  680. else if( evt->IsAction( &ACTIONS::undo ) )
  681. {
  682. restore_state = true; // Perform undo locally
  683. break; // Finish
  684. }
  685. else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT )
  686. || evt->IsDblClick( BUT_LEFT ) )
  687. {
  688. // Eat mouse-up/-click events that leaked through from the lock dialog
  689. if( eatFirstMouseUp && !evt->IsAction( &ACTIONS::cursorClick ) )
  690. {
  691. eatFirstMouseUp = false;
  692. continue;
  693. }
  694. break; // Finish
  695. }
  696. }
  697. // Amend the end points of the arc if we delete the joining tracks
  698. VECTOR2I newStart = trackOnStart->GetStart();
  699. VECTOR2I newEnd = trackOnEnd->GetStart();
  700. if( isStartTrackOnStartPt )
  701. newStart = trackOnStart->GetEnd();
  702. if( isEndTrackOnStartPt )
  703. newEnd = trackOnEnd->GetEnd();
  704. int maxLengthIU =
  705. KiROUND( ADVANCED_CFG::GetCfg().m_MaxTrackLengthToKeep * pcbIUScale.IU_PER_MM );
  706. if( trackOnStart->GetLength() <= maxLengthIU )
  707. {
  708. if( addedTracks.count( trackOnStart ) )
  709. {
  710. getView()->Remove( trackOnStart );
  711. addedTracks.erase( trackOnStart );
  712. delete trackOnStart;
  713. }
  714. else
  715. {
  716. commit.Remove( trackOnStart );
  717. }
  718. theArc->SetStart( newStart );
  719. }
  720. if( trackOnEnd->GetLength() <= maxLengthIU )
  721. {
  722. if( addedTracks.count( trackOnEnd ) )
  723. {
  724. getView()->Remove( trackOnEnd );
  725. addedTracks.erase( trackOnEnd );
  726. delete trackOnEnd;
  727. }
  728. else
  729. {
  730. commit.Remove( trackOnEnd );
  731. }
  732. theArc->SetEnd( newEnd );
  733. }
  734. if( theArc->GetLength() <= 0 )
  735. commit.Remove( theArc );
  736. for( PCB_TRACK* added : addedTracks )
  737. {
  738. getView()->Remove( added );
  739. commit.Add( added );
  740. }
  741. // Should we commit?
  742. if( restore_state )
  743. commit.Revert();
  744. else
  745. commit.Push( _( "Drag Arc Track" ) );
  746. return 0;
  747. }
  748. int EDIT_TOOL::ChangeTrackWidth( const TOOL_EVENT& aEvent )
  749. {
  750. const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  751. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  752. {
  753. // Iterate from the back so we don't have to worry about removals.
  754. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  755. {
  756. BOARD_ITEM* item = aCollector[ i ];
  757. if( !dynamic_cast<PCB_TRACK*>( item ) )
  758. aCollector.Remove( item );
  759. }
  760. },
  761. true /* prompt user regarding locked items */ );
  762. BOARD_COMMIT commit( this );
  763. for( EDA_ITEM* item : selection )
  764. {
  765. if( item->Type() == PCB_VIA_T )
  766. {
  767. PCB_VIA* via = static_cast<PCB_VIA*>( item );
  768. commit.Modify( via );
  769. int new_width;
  770. int new_drill;
  771. if( via->GetViaType() == VIATYPE::MICROVIA )
  772. {
  773. NETCLASS* netClass = via->GetEffectiveNetClass();
  774. new_width = netClass->GetuViaDiameter();
  775. new_drill = netClass->GetuViaDrill();
  776. }
  777. else
  778. {
  779. new_width = board()->GetDesignSettings().GetCurrentViaSize();
  780. new_drill = board()->GetDesignSettings().GetCurrentViaDrill();
  781. }
  782. via->SetDrill( new_drill );
  783. via->SetWidth( new_width );
  784. }
  785. else if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T )
  786. {
  787. PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item );
  788. wxCHECK( track, 0 );
  789. commit.Modify( track );
  790. int new_width = board()->GetDesignSettings().GetCurrentTrackWidth();
  791. track->SetWidth( new_width );
  792. }
  793. }
  794. commit.Push( _( "Edit Track Width/Via Size" ) );
  795. if( selection.IsHover() )
  796. {
  797. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  798. // Notify other tools of the changes -- This updates the visual ratsnest
  799. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  800. }
  801. return 0;
  802. }
  803. int EDIT_TOOL::FilletTracks( const TOOL_EVENT& aEvent )
  804. {
  805. // Store last used fillet radius to allow pressing "enter" if repeat fillet is required
  806. static int filletRadius = 0;
  807. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  808. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  809. {
  810. // Iterate from the back so we don't have to worry about removals.
  811. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  812. {
  813. BOARD_ITEM* item = aCollector[i];
  814. if( !dynamic_cast<PCB_TRACK*>( item ) )
  815. aCollector.Remove( item );
  816. }
  817. },
  818. true /* prompt user regarding locked items */ );
  819. if( selection.Size() < 2 )
  820. {
  821. frame()->ShowInfoBarMsg( _( "At least two straight track segments must be selected." ) );
  822. return 0;
  823. }
  824. WX_UNIT_ENTRY_DIALOG dlg( frame(), _( "Fillet Tracks" ), _( "Radius:" ), filletRadius );
  825. if( dlg.ShowModal() == wxID_CANCEL || dlg.GetValue() == 0 )
  826. return 0;
  827. filletRadius = dlg.GetValue();
  828. struct FILLET_OP
  829. {
  830. PCB_TRACK* t1;
  831. PCB_TRACK* t2;
  832. // Start point of track is modified after PCB_ARC is added, otherwise the end point:
  833. bool t1Start = true;
  834. bool t2Start = true;
  835. };
  836. std::vector<FILLET_OP> filletOperations;
  837. bool operationPerformedOnAtLeastOne = false;
  838. bool didOneAttemptFail = false;
  839. std::set<PCB_TRACK*> processedTracks;
  840. auto processFilletOp =
  841. [&]( PCB_TRACK* aTrack, bool aStartPoint )
  842. {
  843. std::shared_ptr<CONNECTIVITY_DATA> c = board()->GetConnectivity();
  844. VECTOR2I anchor = aStartPoint ? aTrack->GetStart() : aTrack->GetEnd();
  845. std::vector<BOARD_CONNECTED_ITEM*> itemsOnAnchor;
  846. itemsOnAnchor = c->GetConnectedItemsAtAnchor( aTrack, anchor, baseConnectedTypes );
  847. if( itemsOnAnchor.size() > 0
  848. && selection.Contains( itemsOnAnchor.at( 0 ) )
  849. && itemsOnAnchor.at( 0 )->Type() == PCB_TRACE_T )
  850. {
  851. PCB_TRACK* trackOther = static_cast<PCB_TRACK*>( itemsOnAnchor.at( 0 ) );
  852. // Make sure we don't fillet the same pair of tracks twice
  853. if( processedTracks.find( trackOther ) == processedTracks.end() )
  854. {
  855. if( itemsOnAnchor.size() == 1 )
  856. {
  857. FILLET_OP filletOp;
  858. filletOp.t1 = aTrack;
  859. filletOp.t2 = trackOther;
  860. filletOp.t1Start = aStartPoint;
  861. filletOp.t2Start = aTrack->IsPointOnEnds( filletOp.t2->GetStart() );
  862. filletOperations.push_back( filletOp );
  863. }
  864. else
  865. {
  866. // User requested to fillet these two tracks but not possible as
  867. // there are other elements connected at that point
  868. didOneAttemptFail = true;
  869. }
  870. }
  871. }
  872. };
  873. for( EDA_ITEM* item : selection )
  874. {
  875. if( item->Type() == PCB_TRACE_T )
  876. {
  877. PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
  878. if( track->GetLength() > 0 )
  879. {
  880. processFilletOp( track, true ); // on the start point of track
  881. processFilletOp( track, false ); // on the end point of track
  882. processedTracks.insert( track );
  883. }
  884. }
  885. }
  886. BOARD_COMMIT commit( this );
  887. std::vector<BOARD_ITEM*> itemsToAddToSelection;
  888. for( FILLET_OP filletOp : filletOperations )
  889. {
  890. PCB_TRACK* track1 = filletOp.t1;
  891. PCB_TRACK* track2 = filletOp.t2;
  892. bool trackOnStart = track1->IsPointOnEnds( track2->GetStart() );
  893. bool trackOnEnd = track1->IsPointOnEnds( track2->GetEnd() );
  894. if( trackOnStart && trackOnEnd )
  895. continue; // Ignore duplicate tracks
  896. if( ( trackOnStart || trackOnEnd ) && track1->GetLayer() == track2->GetLayer() )
  897. {
  898. SEG t1Seg( track1->GetStart(), track1->GetEnd() );
  899. SEG t2Seg( track2->GetStart(), track2->GetEnd() );
  900. if( t1Seg.ApproxCollinear( t2Seg ) )
  901. continue;
  902. SHAPE_ARC sArc( t1Seg, t2Seg, filletRadius );
  903. VECTOR2I t1newPoint, t2newPoint;
  904. auto setIfPointOnSeg =
  905. []( VECTOR2I& aPointToSet, const SEG& aSegment, const VECTOR2I& aVecToTest )
  906. {
  907. VECTOR2I segToVec = aSegment.NearestPoint( aVecToTest ) - aVecToTest;
  908. // Find out if we are on the segment (minimum precision)
  909. if( segToVec.EuclideanNorm() < SHAPE_ARC::MIN_PRECISION_IU )
  910. {
  911. aPointToSet.x = aVecToTest.x;
  912. aPointToSet.y = aVecToTest.y;
  913. return true;
  914. }
  915. return false;
  916. };
  917. //Do not draw a fillet if the end points of the arc are not within the track segments
  918. if( !setIfPointOnSeg( t1newPoint, t1Seg, sArc.GetP0() )
  919. && !setIfPointOnSeg( t2newPoint, t2Seg, sArc.GetP0() ) )
  920. {
  921. didOneAttemptFail = true;
  922. continue;
  923. }
  924. if( !setIfPointOnSeg( t1newPoint, t1Seg, sArc.GetP1() )
  925. && !setIfPointOnSeg( t2newPoint, t2Seg, sArc.GetP1() ) )
  926. {
  927. didOneAttemptFail = true;
  928. continue;
  929. }
  930. PCB_ARC* tArc = new PCB_ARC( frame()->GetBoard(), &sArc );
  931. tArc->SetLayer( track1->GetLayer() );
  932. tArc->SetWidth( track1->GetWidth() );
  933. tArc->SetNet( track1->GetNet() );
  934. tArc->SetLocked( track1->IsLocked() );
  935. commit.Add( tArc );
  936. itemsToAddToSelection.push_back( tArc );
  937. commit.Modify( track1 );
  938. commit.Modify( track2 );
  939. if( filletOp.t1Start )
  940. track1->SetStart( t1newPoint );
  941. else
  942. track1->SetEnd( t1newPoint );
  943. if( filletOp.t2Start )
  944. track2->SetStart( t2newPoint );
  945. else
  946. track2->SetEnd( t2newPoint );
  947. operationPerformedOnAtLeastOne = true;
  948. }
  949. }
  950. commit.Push( _( "Fillet Tracks" ) );
  951. //select the newly created arcs
  952. for( BOARD_ITEM* item : itemsToAddToSelection )
  953. m_selectionTool->AddItemToSel( item );
  954. if( !operationPerformedOnAtLeastOne )
  955. frame()->ShowInfoBarMsg( _( "Unable to fillet the selected track segments." ) );
  956. else if( didOneAttemptFail )
  957. frame()->ShowInfoBarMsg( _( "Some of the track segments could not be filleted." ) );
  958. return 0;
  959. }
  960. /**
  961. * Prompt the user for the fillet radius and return it.
  962. *
  963. * @param aFrame
  964. * @param aErrorMsg filled with an error message if the parameter is invalid somehow
  965. * @return std::optional<int> the fillet radius or std::nullopt if no
  966. * valid fillet specified
  967. */
  968. static std::optional<int> GetFilletParams( PCB_BASE_EDIT_FRAME& aFrame )
  969. {
  970. // Store last used fillet radius to allow pressing "enter" if repeat fillet is required
  971. static int filletRadius = 0;
  972. WX_UNIT_ENTRY_DIALOG dlg( &aFrame, _( "Fillet Lines" ), _( "Radius:" ), filletRadius );
  973. if( dlg.ShowModal() == wxID_CANCEL || dlg.GetValue() == 0 )
  974. return std::nullopt;
  975. filletRadius = dlg.GetValue();
  976. return filletRadius;
  977. }
  978. /**
  979. * Prompt the user for chamfer parameters
  980. *
  981. * @param aFrame
  982. * @param aErrorMsg filled with an error message if the parameter is invalid somehow
  983. * @return std::optional<int> the chamfer parameters or std::nullopt if no
  984. * valid fillet specified
  985. */
  986. static std::optional<CHAMFER_PARAMS> GetChamferParams( PCB_BASE_EDIT_FRAME& aFrame )
  987. {
  988. // Non-zero and the KLC default for Fab layer chamfers
  989. const int default_setback = pcbIUScale.mmToIU( 1 );
  990. // Store last used setback to allow pressing "enter" if repeat chamfer is required
  991. static CHAMFER_PARAMS params{ default_setback, default_setback };
  992. WX_UNIT_ENTRY_DIALOG dlg( &aFrame, _( "Chamfer Lines" ), _( "Chamfer setback:" ),
  993. params.m_chamfer_setback_a );
  994. if( dlg.ShowModal() == wxID_CANCEL || dlg.GetValue() == 0 )
  995. return std::nullopt;
  996. params.m_chamfer_setback_a = dlg.GetValue();
  997. // It's hard to easily specify an asymmetric chamfer (which line gets the longer setback?),
  998. // so we just use the same setback for each
  999. params.m_chamfer_setback_b = params.m_chamfer_setback_a;
  1000. return params;
  1001. }
  1002. int EDIT_TOOL::ModifyLines( const TOOL_EVENT& aEvent )
  1003. {
  1004. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  1005. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  1006. {
  1007. std::vector<VECTOR2I> pts;
  1008. // Iterate from the back so we don't have to worry about removals.
  1009. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  1010. {
  1011. BOARD_ITEM* item = aCollector[i];
  1012. // We've converted the polygon and rectangle to segments, so drop everything
  1013. // that isn't a segment at this point
  1014. if( !item->IsType( { PCB_SHAPE_LOCATE_SEGMENT_T,
  1015. PCB_SHAPE_LOCATE_POLY_T,
  1016. PCB_SHAPE_LOCATE_RECT_T } ) )
  1017. {
  1018. aCollector.Remove( item );
  1019. }
  1020. }
  1021. },
  1022. true /* prompt user regarding locked items */ );
  1023. std::set<PCB_SHAPE*> lines_to_add;
  1024. std::vector<PCB_SHAPE*> items_to_remove;
  1025. for( EDA_ITEM* item : selection )
  1026. {
  1027. std::vector<VECTOR2I> pts;
  1028. PCB_SHAPE *graphic = static_cast<PCB_SHAPE*>( item );
  1029. PCB_LAYER_ID layer = graphic->GetLayer();
  1030. int width = graphic->GetWidth();
  1031. if( graphic->GetShape() == SHAPE_T::RECTANGLE )
  1032. {
  1033. items_to_remove.push_back( graphic );
  1034. VECTOR2I start( graphic->GetStart() );
  1035. VECTOR2I end( graphic->GetEnd() );
  1036. pts.emplace_back( start );
  1037. pts.emplace_back( VECTOR2I( end.x, start.y ) );
  1038. pts.emplace_back( end );
  1039. pts.emplace_back( VECTOR2I( start.x, end.y ) );
  1040. }
  1041. if( graphic->GetShape() == SHAPE_T::POLY )
  1042. {
  1043. items_to_remove.push_back( graphic );
  1044. for( int jj = 0; jj < graphic->GetPolyShape().VertexCount(); ++jj )
  1045. pts.emplace_back( graphic->GetPolyShape().CVertex( jj ) );
  1046. }
  1047. for( size_t jj = 1; jj < pts.size(); ++jj )
  1048. {
  1049. PCB_SHAPE *line = new PCB_SHAPE( frame()->GetModel(), SHAPE_T::SEGMENT );
  1050. line->SetStart( pts[jj - 1] );
  1051. line->SetEnd( pts[jj] );
  1052. line->SetWidth( width );
  1053. line->SetLayer( layer );
  1054. lines_to_add.insert( line );
  1055. }
  1056. if( pts.size() > 1 )
  1057. {
  1058. PCB_SHAPE *line = new PCB_SHAPE( frame()->GetModel(), SHAPE_T::SEGMENT );
  1059. line->SetStart( pts.back() );
  1060. line->SetEnd( pts.front() );
  1061. line->SetWidth( width );
  1062. line->SetLayer( layer );
  1063. lines_to_add.insert( line );
  1064. }
  1065. }
  1066. int segmentCount = selection.CountType( PCB_SHAPE_LOCATE_SEGMENT_T ) + lines_to_add.size();
  1067. if( aEvent.IsAction( &PCB_ACTIONS::extendLines ) && segmentCount != 2 )
  1068. {
  1069. frame()->ShowInfoBarMsg( _( "Exactly two lines must be selected to extend them." ) );
  1070. for( PCB_SHAPE* line : lines_to_add )
  1071. delete line;
  1072. return 0;
  1073. }
  1074. else if( segmentCount < 2 )
  1075. {
  1076. frame()->ShowInfoBarMsg( _( "A shape with at least two lines must be selected." ) );
  1077. for( PCB_SHAPE* line : lines_to_add )
  1078. delete line;
  1079. return 0;
  1080. }
  1081. BOARD_COMMIT commit( this );
  1082. // Items created like lines from a rectangle
  1083. for( PCB_SHAPE* item : lines_to_add )
  1084. {
  1085. commit.Add( item );
  1086. selection.Add( item );
  1087. }
  1088. // Remove items like rectangles that we decomposed into lines
  1089. for( PCB_SHAPE* item : items_to_remove )
  1090. {
  1091. selection.Remove( item );
  1092. commit.Remove( item );
  1093. }
  1094. for( EDA_ITEM* item : selection )
  1095. item->ClearFlags( STRUCT_DELETED );
  1096. // List of thing to select at the end of the operation
  1097. // (doing it as we go will invalidate the iterator)
  1098. std::vector<PCB_SHAPE*> items_to_select_on_success;
  1099. // And same for items to deselect
  1100. std::vector<PCB_SHAPE*> items_to_deselect_on_success;
  1101. // Handle modifications to existing items by the routine
  1102. // How to deal with this depends on whether we're in the footprint editor or not
  1103. // and whether the item was conjured up by decomposing a polygon or rectangle
  1104. auto item_modification_handler =
  1105. [&]( PCB_SHAPE& aItem )
  1106. {
  1107. // If the item was "conjured up" it will be added later separately
  1108. if( !alg::contains( lines_to_add, &aItem ) )
  1109. {
  1110. commit.Modify( &aItem );
  1111. items_to_select_on_success.push_back( &aItem );
  1112. }
  1113. };
  1114. bool any_items_created = !lines_to_add.empty();
  1115. auto item_creation_handler =
  1116. [&]( std::unique_ptr<PCB_SHAPE> aItem )
  1117. {
  1118. any_items_created = true;
  1119. items_to_select_on_success.push_back( aItem.get() );
  1120. commit.Add( aItem.release() );
  1121. };
  1122. bool any_items_removed = !items_to_remove.empty();
  1123. auto item_removal_handler =
  1124. [&]( PCB_SHAPE& aItem )
  1125. {
  1126. aItem.SetFlags( STRUCT_DELETED );
  1127. any_items_removed = true;
  1128. items_to_deselect_on_success.push_back( &aItem );
  1129. commit.Remove( &aItem );
  1130. };
  1131. // Combine these callbacks into a CHANGE_HANDLER to inject in the ROUTINE
  1132. ITEM_MODIFICATION_ROUTINE::CALLABLE_BASED_HANDLER change_handler(
  1133. item_creation_handler, item_modification_handler, item_removal_handler );
  1134. // Construct an appropriate tool
  1135. std::unique_ptr<PAIRWISE_LINE_ROUTINE> pairwise_line_routine;
  1136. if( aEvent.IsAction( &PCB_ACTIONS::filletLines ) )
  1137. {
  1138. std::optional<int> filletRadiusIU = GetFilletParams( *frame() );
  1139. if( filletRadiusIU.has_value() )
  1140. {
  1141. pairwise_line_routine = std::make_unique<LINE_FILLET_ROUTINE>( frame()->GetModel(),
  1142. change_handler,
  1143. *filletRadiusIU );
  1144. }
  1145. }
  1146. else if( aEvent.IsAction( &PCB_ACTIONS::chamferLines ) )
  1147. {
  1148. std::optional<CHAMFER_PARAMS> chamfer_params = GetChamferParams( *frame() );
  1149. if( chamfer_params.has_value() )
  1150. {
  1151. pairwise_line_routine = std::make_unique<LINE_CHAMFER_ROUTINE>( frame()->GetModel(),
  1152. change_handler,
  1153. *chamfer_params );
  1154. }
  1155. }
  1156. else if( aEvent.IsAction( &PCB_ACTIONS::extendLines ) )
  1157. {
  1158. pairwise_line_routine = std::make_unique<LINE_EXTENSION_ROUTINE>( frame()->GetModel(),
  1159. change_handler );
  1160. }
  1161. if( !pairwise_line_routine )
  1162. {
  1163. // Didn't construct any mofication routine - user must have cancelled
  1164. commit.Revert();
  1165. return 0;
  1166. }
  1167. // Apply the tool to every line pair
  1168. alg::for_all_pairs( selection.begin(), selection.end(),
  1169. [&]( EDA_ITEM* a, EDA_ITEM* b )
  1170. {
  1171. if( ( a->GetFlags() & STRUCT_DELETED ) == 0
  1172. && ( b->GetFlags() & STRUCT_DELETED ) == 0 )
  1173. {
  1174. PCB_SHAPE* line_a = static_cast<PCB_SHAPE*>( a );
  1175. PCB_SHAPE* line_b = static_cast<PCB_SHAPE*>( b );
  1176. pairwise_line_routine->ProcessLinePair( *line_a, *line_b );
  1177. }
  1178. } );
  1179. // Select added and modified items
  1180. for( PCB_SHAPE* item : items_to_select_on_success )
  1181. m_selectionTool->AddItemToSel( item, true );
  1182. // Deselect removed items
  1183. for( PCB_SHAPE* item : items_to_deselect_on_success )
  1184. m_selectionTool->RemoveItemFromSel( item, true );
  1185. if( any_items_removed )
  1186. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  1187. if( any_items_created )
  1188. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1189. // Notify other tools of the changes
  1190. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  1191. commit.Push( pairwise_line_routine->GetCommitDescription() );
  1192. if( const std::optional<wxString> msg = pairwise_line_routine->GetStatusMessage() )
  1193. frame()->ShowInfoBarMsg( *msg );
  1194. return 0;
  1195. }
  1196. int EDIT_TOOL::SimplifyPolygons( const TOOL_EVENT& aEvent )
  1197. {
  1198. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  1199. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  1200. {
  1201. std::vector<VECTOR2I> pts;
  1202. // Iterate from the back so we don't have to worry about removals.
  1203. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  1204. {
  1205. BOARD_ITEM* item = aCollector[i];
  1206. if( !item->IsType( { PCB_SHAPE_LOCATE_POLY_T, PCB_ZONE_T } ) )
  1207. aCollector.Remove( item );
  1208. if( ZONE* zone = dyn_cast<ZONE*>( item ) )
  1209. {
  1210. if( zone->IsTeardropArea() )
  1211. aCollector.Remove( item );
  1212. }
  1213. }
  1214. },
  1215. true /* prompt user regarding locked items */ );
  1216. // Store last used value
  1217. static int s_toleranceValue = pcbIUScale.mmToIU( 3 );
  1218. WX_UNIT_ENTRY_DIALOG dlg( frame(), _( "Simplify Shapes" ), _( "Tolerance value:" ),
  1219. s_toleranceValue );
  1220. if( dlg.ShowModal() == wxID_CANCEL )
  1221. return 0;
  1222. s_toleranceValue = dlg.GetValue();
  1223. if( s_toleranceValue <= 0 )
  1224. return 0;
  1225. BOARD_COMMIT commit{ this };
  1226. std::vector<PCB_SHAPE*> shapeList;
  1227. for( EDA_ITEM* item : selection )
  1228. {
  1229. commit.Modify( item );
  1230. if( PCB_SHAPE* shape = dyn_cast<PCB_SHAPE*>( item ) )
  1231. {
  1232. SHAPE_POLY_SET& poly = shape->GetPolyShape();
  1233. poly.SimplifyOutlines( s_toleranceValue );
  1234. }
  1235. if( ZONE* zone = dyn_cast<ZONE*>( item ) )
  1236. {
  1237. SHAPE_POLY_SET* poly = zone->Outline();
  1238. poly->SimplifyOutlines( s_toleranceValue );
  1239. }
  1240. }
  1241. commit.Push( _( "Simplify Polygons" ) );
  1242. // Notify other tools of the changes
  1243. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  1244. return 0;
  1245. }
  1246. int EDIT_TOOL::HealShapes( const TOOL_EVENT& aEvent )
  1247. {
  1248. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  1249. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  1250. {
  1251. std::vector<VECTOR2I> pts;
  1252. // Iterate from the back so we don't have to worry about removals.
  1253. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  1254. {
  1255. BOARD_ITEM* item = aCollector[i];
  1256. // We've converted the polygon and rectangle to segments, so drop everything
  1257. // that isn't a segment at this point
  1258. if( !item->IsType( { PCB_SHAPE_LOCATE_SEGMENT_T, PCB_SHAPE_LOCATE_ARC_T,
  1259. PCB_SHAPE_LOCATE_BEZIER_T } ) )
  1260. {
  1261. aCollector.Remove( item );
  1262. }
  1263. }
  1264. },
  1265. true /* prompt user regarding locked items */ );
  1266. // Store last used value
  1267. static int s_toleranceValue = pcbIUScale.mmToIU( 3 );
  1268. WX_UNIT_ENTRY_DIALOG dlg( frame(), _( "Heal Shapes" ), _( "Tolerance value:" ),
  1269. s_toleranceValue );
  1270. if( dlg.ShowModal() == wxID_CANCEL )
  1271. return 0;
  1272. s_toleranceValue = dlg.GetValue();
  1273. if( s_toleranceValue <= 0 )
  1274. return 0;
  1275. BOARD_COMMIT commit{ this };
  1276. std::vector<PCB_SHAPE*> shapeList;
  1277. std::vector<std::unique_ptr<PCB_SHAPE>> newShapes;
  1278. for( EDA_ITEM* item : selection )
  1279. {
  1280. if( PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item ) )
  1281. {
  1282. shapeList.push_back( shape );
  1283. commit.Modify( shape );
  1284. }
  1285. }
  1286. ConnectBoardShapes( shapeList, newShapes, s_toleranceValue );
  1287. std::vector<PCB_SHAPE*> items_to_select;
  1288. for( std::unique_ptr<PCB_SHAPE>& ptr : newShapes )
  1289. {
  1290. PCB_SHAPE* shape = ptr.release();
  1291. commit.Add( shape );
  1292. items_to_select.push_back( shape );
  1293. }
  1294. commit.Push( _( "Heal Shapes" ) );
  1295. // Select added items
  1296. for( PCB_SHAPE* item : items_to_select )
  1297. m_selectionTool->AddItemToSel( item, true );
  1298. if( items_to_select.size() > 0 )
  1299. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1300. // Notify other tools of the changes
  1301. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  1302. return 0;
  1303. }
  1304. int EDIT_TOOL::BooleanPolygons( const TOOL_EVENT& aEvent )
  1305. {
  1306. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  1307. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  1308. {
  1309. // Iterate from the back so we don't have to worry about removals.
  1310. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  1311. {
  1312. BOARD_ITEM* item = aCollector[i];
  1313. if( !item->IsType( {
  1314. PCB_SHAPE_LOCATE_POLY_T,
  1315. PCB_SHAPE_LOCATE_RECT_T,
  1316. } ) )
  1317. {
  1318. aCollector.Remove( item );
  1319. }
  1320. }
  1321. },
  1322. true /* prompt user regarding locked items */ );
  1323. const EDA_ITEM* const last_item = selection.GetLastAddedItem();
  1324. // Gather or construct polygon source shapes to merge
  1325. std::vector<PCB_SHAPE*> items_to_process;
  1326. for( EDA_ITEM* item : selection )
  1327. {
  1328. items_to_process.push_back( static_cast<PCB_SHAPE*>( item ) );
  1329. // put the last one in the selection at the front of the vector
  1330. // so it can be used as the property donor and as the basis for the
  1331. // boolean operation
  1332. if( item == last_item )
  1333. std::swap( items_to_process.back(), items_to_process.front() );
  1334. }
  1335. BOARD_COMMIT commit{ this };
  1336. // Handle modifications to existing items by the routine
  1337. auto item_modification_handler =
  1338. [&]( PCB_SHAPE& aItem )
  1339. {
  1340. commit.Modify( &aItem );
  1341. };
  1342. std::vector<PCB_SHAPE*> items_to_select_on_success;
  1343. auto item_creation_handler =
  1344. [&]( std::unique_ptr<PCB_SHAPE> aItem )
  1345. {
  1346. items_to_select_on_success.push_back( aItem.get() );
  1347. commit.Add( aItem.release() );
  1348. };
  1349. auto item_removal_handler =
  1350. [&]( PCB_SHAPE& aItem )
  1351. {
  1352. commit.Remove( &aItem );
  1353. };
  1354. // Combine these callbacks into a CHANGE_HANDLER to inject in the ROUTINE
  1355. ITEM_MODIFICATION_ROUTINE::CALLABLE_BASED_HANDLER change_handler(
  1356. item_creation_handler, item_modification_handler, item_removal_handler );
  1357. // Construct an appropriate routine
  1358. std::unique_ptr<POLYGON_BOOLEAN_ROUTINE> boolean_routine;
  1359. if( aEvent.IsAction( &PCB_ACTIONS::mergePolygons ) )
  1360. {
  1361. boolean_routine = std::make_unique<POLYGON_MERGE_ROUTINE>( frame()->GetModel(),
  1362. change_handler );
  1363. }
  1364. else if( aEvent.IsAction( &PCB_ACTIONS::subtractPolygons ) )
  1365. {
  1366. boolean_routine = std::make_unique<POLYGON_SUBTRACT_ROUTINE>( frame()->GetModel(),
  1367. change_handler );
  1368. }
  1369. else if( aEvent.IsAction( &PCB_ACTIONS::intersectPolygons ) )
  1370. {
  1371. boolean_routine = std::make_unique<POLYGON_INTERSECT_ROUTINE>( frame()->GetModel(),
  1372. change_handler );
  1373. }
  1374. else
  1375. {
  1376. wxASSERT_MSG( false, "Could not find a polygon routine for this action" );
  1377. return 0;
  1378. }
  1379. // Perform the operation on each polygon
  1380. for( PCB_SHAPE* shape : items_to_process )
  1381. boolean_routine->ProcessShape( *shape );
  1382. // Select new items
  1383. for( PCB_SHAPE* item : items_to_select_on_success )
  1384. m_selectionTool->AddItemToSel( item, true );
  1385. // Notify other tools of the changes
  1386. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  1387. commit.Push( boolean_routine->GetCommitDescription() );
  1388. if( const std::optional<wxString> msg = boolean_routine->GetStatusMessage() )
  1389. frame()->ShowInfoBarMsg( *msg );
  1390. return 0;
  1391. }
  1392. int EDIT_TOOL::Properties( const TOOL_EVENT& aEvent )
  1393. {
  1394. PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  1395. const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  1396. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  1397. {
  1398. } );
  1399. // Tracks & vias are treated in a special way:
  1400. if( ( SELECTION_CONDITIONS::OnlyTypes( { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T } ) )( selection ) )
  1401. {
  1402. DIALOG_TRACK_VIA_PROPERTIES dlg( editFrame, selection );
  1403. dlg.ShowQuasiModal(); // QuasiModal required for NET_SELECTOR
  1404. }
  1405. else if( ( SELECTION_CONDITIONS::OnlyTypes( { PCB_TABLECELL_T } ) )( selection ) )
  1406. {
  1407. std::vector<PCB_TABLECELL*> cells;
  1408. for( EDA_ITEM* item : selection.Items() )
  1409. cells.push_back( static_cast<PCB_TABLECELL*>( item ) );
  1410. DIALOG_TABLECELL_PROPERTIES dlg( editFrame, cells );
  1411. dlg.ShowModal();
  1412. if( dlg.GetReturnValue() == DIALOG_TABLECELL_PROPERTIES::TABLECELL_PROPS_EDIT_TABLE )
  1413. {
  1414. PCB_TABLE* table = static_cast<PCB_TABLE*>( cells[0]->GetParent() );
  1415. DIALOG_TABLE_PROPERTIES tableDlg( frame(), table );
  1416. tableDlg.ShowQuasiModal(); // Scintilla's auto-complete requires quasiModal
  1417. }
  1418. }
  1419. else if( selection.Size() == 1 )
  1420. {
  1421. // Display properties dialog
  1422. if( BOARD_ITEM* item = dynamic_cast<BOARD_ITEM*>( selection.Front() ) )
  1423. {
  1424. // Do not handle undo buffer, it is done by the properties dialogs
  1425. editFrame->OnEditItemRequest( item );
  1426. // Notify other tools of the changes
  1427. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  1428. }
  1429. }
  1430. else if( selection.Size() == 0 && getView()->IsLayerVisible( LAYER_DRAWINGSHEET ) )
  1431. {
  1432. DS_PROXY_VIEW_ITEM* ds = editFrame->GetCanvas()->GetDrawingSheet();
  1433. VECTOR2D cursorPos = getViewControls()->GetCursorPosition( false );
  1434. if( ds && ds->HitTestDrawingSheetItems( getView(), cursorPos ) )
  1435. m_toolMgr->PostAction( ACTIONS::pageSettings );
  1436. else
  1437. m_toolMgr->RunAction( PCB_ACTIONS::footprintProperties );
  1438. }
  1439. if( selection.IsHover() )
  1440. {
  1441. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  1442. }
  1443. else
  1444. {
  1445. // Check for items becoming invisible and drop them from the selection.
  1446. PCB_SELECTION selCopy = selection;
  1447. LSET visible = editFrame->GetBoard()->GetVisibleLayers();
  1448. for( EDA_ITEM* eda_item : selCopy )
  1449. {
  1450. if( BOARD_ITEM* item = dynamic_cast<BOARD_ITEM*>( eda_item ) )
  1451. {
  1452. if( !( item->GetLayerSet() & visible ).any() )
  1453. m_selectionTool->RemoveItemFromSel( item );
  1454. }
  1455. }
  1456. }
  1457. return 0;
  1458. }
  1459. int EDIT_TOOL::Rotate( const TOOL_EVENT& aEvent )
  1460. {
  1461. if( isRouterActive() )
  1462. {
  1463. wxBell();
  1464. return 0;
  1465. }
  1466. PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  1467. BOARD_COMMIT localCommit( this );
  1468. BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
  1469. if( !commit )
  1470. commit = &localCommit;
  1471. // Be sure that there is at least one item that we can modify. If nothing was selected before,
  1472. // try looking for the stuff under mouse cursor (i.e. KiCad old-style hover selection)
  1473. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  1474. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  1475. {
  1476. sTool->FilterCollectorForHierarchy( aCollector, true );
  1477. sTool->FilterCollectorForMarkers( aCollector );
  1478. sTool->FilterCollectorForTableCells( aCollector );
  1479. },
  1480. // Prompt user regarding locked items if in board editor and in free-pad-mode (if
  1481. // we're not in free-pad mode we delay this until the second RequestSelection()).
  1482. frame()->GetPcbNewSettings()->m_AllowFreePads && !m_isFootprintEditor );
  1483. if( selection.Empty() )
  1484. return 0;
  1485. std::optional<VECTOR2I> oldRefPt;
  1486. bool is_hover = selection.IsHover(); // N.B. This must be saved before the second
  1487. // call to RequestSelection() below
  1488. if( selection.HasReferencePoint() )
  1489. oldRefPt = selection.GetReferencePoint();
  1490. // Now filter out pads if not in free pads mode. We cannot do this in the first
  1491. // RequestSelection() as we need the reference point when a pad is the selection front.
  1492. if( !m_isFootprintEditor && !frame()->GetPcbNewSettings()->m_AllowFreePads )
  1493. {
  1494. selection = m_selectionTool->RequestSelection(
  1495. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  1496. {
  1497. sTool->FilterCollectorForMarkers( aCollector );
  1498. sTool->FilterCollectorForHierarchy( aCollector, true );
  1499. sTool->FilterCollectorForFreePads( aCollector );
  1500. sTool->FilterCollectorForTableCells( aCollector );
  1501. },
  1502. true /* prompt user regarding locked items */ );
  1503. }
  1504. // Did we filter everything out? If so, don't try to operate further
  1505. if( selection.Empty() )
  1506. return 0;
  1507. // A single textbox "walks" if we rotate it around its position, as we keep resetting which
  1508. // corner is the origin.
  1509. if( selection.Size() == 1 && dynamic_cast<PCB_TEXTBOX*>( selection.Front() ) )
  1510. {
  1511. selection.SetReferencePoint( static_cast<PCB_TEXTBOX*>( selection.Front() )->GetCenter() );
  1512. }
  1513. else
  1514. {
  1515. updateModificationPoint( selection );
  1516. }
  1517. VECTOR2I refPt = selection.GetReferencePoint();
  1518. EDA_ANGLE rotateAngle = TOOL_EVT_UTILS::GetEventRotationAngle( *editFrame, aEvent );
  1519. if( frame()->GetCanvas()->GetView()->GetGAL()->IsFlippedX() )
  1520. rotateAngle = -rotateAngle;
  1521. // Calculate view bounding box
  1522. BOX2I viewBBox = selection.Front()->ViewBBox();
  1523. for( EDA_ITEM* item : selection )
  1524. viewBBox.Merge( item->ViewBBox() );
  1525. // Check if the view bounding box will go out of bounds
  1526. VECTOR2D rotPos = viewBBox.GetPosition();
  1527. VECTOR2D rotEnd = viewBBox.GetEnd();
  1528. RotatePoint( &rotPos.x, &rotPos.y, refPt.x, refPt.y, rotateAngle );
  1529. RotatePoint( &rotEnd.x, &rotEnd.y, refPt.x, refPt.y, rotateAngle );
  1530. typedef std::numeric_limits<int> coord_limits;
  1531. int max = coord_limits::max() - COORDS_PADDING;
  1532. int min = -max;
  1533. bool outOfBounds = rotPos.x < min || rotPos.x > max || rotPos.y < min || rotPos.y > max
  1534. || rotEnd.x < min || rotEnd.x > max || rotEnd.y < min || rotEnd.y > max;
  1535. if( !outOfBounds )
  1536. {
  1537. for( EDA_ITEM* item : selection )
  1538. {
  1539. if( !item->IsNew() && !item->IsMoving() )
  1540. commit->Modify( item );
  1541. if( BOARD_ITEM* board_item = dynamic_cast<BOARD_ITEM*>( item ) )
  1542. {
  1543. board_item->Rotate( refPt, rotateAngle );
  1544. board_item->Normalize();
  1545. }
  1546. }
  1547. if( !localCommit.Empty() )
  1548. localCommit.Push( _( "Rotate" ) );
  1549. if( is_hover && !m_dragging )
  1550. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  1551. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  1552. if( m_dragging )
  1553. m_toolMgr->PostAction( PCB_ACTIONS::updateLocalRatsnest, VECTOR2I() );
  1554. }
  1555. // Restore the old reference so any mouse dragging that occurs doesn't make the selection jump
  1556. // to this now invalid reference
  1557. if( oldRefPt )
  1558. selection.SetReferencePoint( *oldRefPt );
  1559. else
  1560. selection.ClearReferencePoint();
  1561. return 0;
  1562. }
  1563. /**
  1564. * Mirror a point about the vertical axis passing through another point.
  1565. */
  1566. static VECTOR2I mirrorPointX( const VECTOR2I& aPoint, const VECTOR2I& aMirrorPoint )
  1567. {
  1568. VECTOR2I mirrored = aPoint;
  1569. mirrored.x -= aMirrorPoint.x;
  1570. mirrored.x = -mirrored.x;
  1571. mirrored.x += aMirrorPoint.x;
  1572. return mirrored;
  1573. }
  1574. /**
  1575. * Mirror a point about the vertical axis passing through another point.
  1576. */
  1577. static VECTOR2I mirrorPointY( const VECTOR2I& aPoint, const VECTOR2I& aMirrorPoint )
  1578. {
  1579. VECTOR2I mirrored = aPoint;
  1580. mirrored.y -= aMirrorPoint.y;
  1581. mirrored.y = -mirrored.y;
  1582. mirrored.y += aMirrorPoint.y;
  1583. return mirrored;
  1584. }
  1585. /**
  1586. * Mirror a pad in the vertical axis passing through a point (mirror left to right).
  1587. */
  1588. static void mirrorPadX( PAD& aPad, const VECTOR2I& aMirrorPoint )
  1589. {
  1590. if( aPad.GetShape() == PAD_SHAPE::CUSTOM )
  1591. aPad.FlipPrimitives( true ); // mirror primitives left to right
  1592. VECTOR2I tmpPt = mirrorPointX( aPad.GetPosition(), aMirrorPoint );
  1593. aPad.SetPosition( tmpPt );
  1594. tmpPt = aPad.GetOffset();
  1595. tmpPt.x = -tmpPt.x;
  1596. aPad.SetOffset( tmpPt );
  1597. auto tmpz = aPad.GetDelta();
  1598. tmpz.x = -tmpz.x;
  1599. aPad.SetDelta( tmpz );
  1600. aPad.SetOrientation( -aPad.GetOrientation() );
  1601. }
  1602. /**
  1603. * Mirror a pad in the vertical axis passing through a point (mirror left to right).
  1604. */
  1605. static void mirrorPadY( PAD& aPad, const VECTOR2I& aMirrorPoint )
  1606. {
  1607. if( aPad.GetShape() == PAD_SHAPE::CUSTOM )
  1608. aPad.FlipPrimitives( false ); // mirror primitives top to bottom
  1609. VECTOR2I tmpPt = mirrorPointY( aPad.GetPosition(), aMirrorPoint );
  1610. aPad.SetPosition( tmpPt );
  1611. tmpPt = aPad.GetOffset();
  1612. tmpPt.y = -tmpPt.y;
  1613. aPad.SetOffset( tmpPt );
  1614. auto tmpz = aPad.GetDelta();
  1615. tmpz.y = -tmpz.y;
  1616. aPad.SetDelta( tmpz );
  1617. aPad.SetOrientation( -aPad.GetOrientation() );
  1618. }
  1619. const std::vector<KICAD_T> EDIT_TOOL::MirrorableItems = {
  1620. PCB_SHAPE_T,
  1621. PCB_FIELD_T,
  1622. PCB_TEXT_T,
  1623. PCB_TEXTBOX_T,
  1624. PCB_ZONE_T,
  1625. PCB_PAD_T,
  1626. PCB_TRACE_T,
  1627. PCB_ARC_T,
  1628. PCB_VIA_T,
  1629. };
  1630. int EDIT_TOOL::Mirror( const TOOL_EVENT& aEvent )
  1631. {
  1632. if( isRouterActive() )
  1633. {
  1634. wxBell();
  1635. return 0;
  1636. }
  1637. BOARD_COMMIT localCommit( this );
  1638. BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
  1639. if( !commit )
  1640. commit = &localCommit;
  1641. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  1642. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  1643. {
  1644. sTool->FilterCollectorForMarkers( aCollector );
  1645. sTool->FilterCollectorForHierarchy( aCollector, true );
  1646. sTool->FilterCollectorForFreePads( aCollector );
  1647. },
  1648. !m_dragging /* prompt user regarding locked items */ );
  1649. if( selection.Empty() )
  1650. return 0;
  1651. updateModificationPoint( selection );
  1652. VECTOR2I mirrorPoint = selection.GetReferencePoint();
  1653. // Set the mirroring options.
  1654. // Unfortunately, the mirror function do not have the same parameter for all items
  1655. // So we need these 2 parameters to avoid mistakes
  1656. bool mirrorLeftRight = true;
  1657. bool mirrorAroundXaxis = false;
  1658. if( aEvent.IsAction( &PCB_ACTIONS::mirrorV ) )
  1659. {
  1660. mirrorLeftRight = false;
  1661. mirrorAroundXaxis = true;
  1662. }
  1663. std::vector<EDA_ITEM*> items;
  1664. for( EDA_ITEM* item : selection )
  1665. {
  1666. if( item->Type() == PCB_GROUP_T )
  1667. {
  1668. static_cast<PCB_GROUP*>( item )->RunOnDescendants(
  1669. [&]( BOARD_ITEM* descendant )
  1670. {
  1671. items.push_back( descendant );
  1672. } );
  1673. }
  1674. else
  1675. {
  1676. items.push_back( item );
  1677. }
  1678. }
  1679. for( EDA_ITEM* item : items )
  1680. {
  1681. if( !item->IsType( MirrorableItems ) )
  1682. continue;
  1683. if( !item->IsNew() && !item->IsMoving() )
  1684. commit->Modify( item );
  1685. // modify each object as necessary
  1686. switch( item->Type() )
  1687. {
  1688. case PCB_SHAPE_T:
  1689. static_cast<PCB_SHAPE*>( item )->Mirror( mirrorPoint, mirrorAroundXaxis );
  1690. break;
  1691. case PCB_ZONE_T:
  1692. static_cast<ZONE*>( item )->Mirror( mirrorPoint, mirrorLeftRight );
  1693. break;
  1694. case PCB_FIELD_T:
  1695. case PCB_TEXT_T:
  1696. static_cast<PCB_TEXT*>( item )->Mirror( mirrorPoint, mirrorAroundXaxis );
  1697. break;
  1698. case PCB_TEXTBOX_T:
  1699. static_cast<PCB_TEXTBOX*>( item )->Mirror( mirrorPoint, mirrorAroundXaxis );
  1700. break;
  1701. case PCB_TABLE_T:
  1702. // JEY TODO: tables
  1703. break;
  1704. case PCB_PAD_T:
  1705. if( mirrorLeftRight )
  1706. mirrorPadX( *static_cast<PAD*>( item ), mirrorPoint );
  1707. else
  1708. mirrorPadY( *static_cast<PAD*>( item ), mirrorPoint );
  1709. break;
  1710. case PCB_TRACE_T:
  1711. case PCB_ARC_T:
  1712. case PCB_VIA_T:
  1713. static_cast<PCB_TRACK*>( item )->Mirror( mirrorPoint, mirrorAroundXaxis );
  1714. break;
  1715. default:
  1716. // it's likely the commit object is wrong if you get here
  1717. UNIMPLEMENTED_FOR( item->GetClass() );
  1718. }
  1719. }
  1720. if( !localCommit.Empty() )
  1721. localCommit.Push( _( "Mirror" ) );
  1722. if( selection.IsHover() && !m_dragging )
  1723. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  1724. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  1725. if( m_dragging )
  1726. m_toolMgr->PostAction( PCB_ACTIONS::updateLocalRatsnest, VECTOR2I() );
  1727. return 0;
  1728. }
  1729. int EDIT_TOOL::JustifyText( const TOOL_EVENT& aEvent )
  1730. {
  1731. if( isRouterActive() )
  1732. {
  1733. wxBell();
  1734. return 0;
  1735. }
  1736. BOARD_COMMIT localCommit( this );
  1737. BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
  1738. if( !commit )
  1739. commit = &localCommit;
  1740. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  1741. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  1742. {
  1743. sTool->FilterCollectorForHierarchy( aCollector, true );
  1744. },
  1745. !m_dragging /* prompt user regarding locked items */ );
  1746. if( selection.Empty() )
  1747. return 0;
  1748. auto setJustify =
  1749. [&]( EDA_TEXT* aTextItem )
  1750. {
  1751. if( aEvent.Matches( ACTIONS::leftJustify.MakeEvent() ) )
  1752. aTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
  1753. else if( aEvent.Matches( ACTIONS::centerJustify.MakeEvent() ) )
  1754. aTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
  1755. else
  1756. aTextItem->SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT );
  1757. };
  1758. for( EDA_ITEM* item : selection )
  1759. {
  1760. if( item->Type() == PCB_FIELD_T || item->Type() == PCB_TEXT_T )
  1761. {
  1762. if( !item->IsNew() && !item->IsMoving() )
  1763. commit->Modify( item );
  1764. setJustify( static_cast<PCB_TEXT*>( item ) );
  1765. }
  1766. else if( item->Type() == PCB_TEXTBOX_T )
  1767. {
  1768. if( !item->IsNew() && !item->IsMoving() )
  1769. commit->Modify( item );
  1770. setJustify( static_cast<PCB_TEXTBOX*>( item ) );
  1771. }
  1772. }
  1773. if( !localCommit.Empty() )
  1774. {
  1775. if( aEvent.Matches( ACTIONS::leftJustify.MakeEvent() ) )
  1776. localCommit.Push( _( "Left Justify" ) );
  1777. else if( aEvent.Matches( ACTIONS::centerJustify.MakeEvent() ) )
  1778. localCommit.Push( _( "Center Justify" ) );
  1779. else
  1780. localCommit.Push( _( "Right Justify" ) );
  1781. }
  1782. if( selection.IsHover() && !m_dragging )
  1783. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  1784. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  1785. if( m_dragging )
  1786. m_toolMgr->PostAction( PCB_ACTIONS::updateLocalRatsnest, VECTOR2I() );
  1787. return 0;
  1788. }
  1789. int EDIT_TOOL::Flip( const TOOL_EVENT& aEvent )
  1790. {
  1791. if( isRouterActive() )
  1792. {
  1793. wxBell();
  1794. return 0;
  1795. }
  1796. BOARD_COMMIT localCommit( this );
  1797. BOARD_COMMIT* commit = dynamic_cast<BOARD_COMMIT*>( aEvent.Commit() );
  1798. if( !commit )
  1799. commit = &localCommit;
  1800. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  1801. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  1802. {
  1803. sTool->FilterCollectorForMarkers( aCollector );
  1804. sTool->FilterCollectorForHierarchy( aCollector, true );
  1805. sTool->FilterCollectorForFreePads( aCollector );
  1806. sTool->FilterCollectorForTableCells( aCollector );
  1807. },
  1808. !m_dragging /* prompt user regarding locked items */ );
  1809. if( selection.Empty() )
  1810. return 0;
  1811. std::optional<VECTOR2I> oldRefPt;
  1812. if( selection.HasReferencePoint() )
  1813. oldRefPt = selection.GetReferencePoint();
  1814. updateModificationPoint( selection );
  1815. // Flip around the anchor for footprints, and the bounding box center for board items
  1816. VECTOR2I refPt = IsFootprintEditor() ? VECTOR2I( 0, 0 ) : selection.GetCenter();
  1817. // If only one item selected, flip around the selection or item anchor point (instead
  1818. // of the bounding box center) to avoid moving the item anchor
  1819. if( selection.GetSize() == 1 )
  1820. refPt = selection.GetReferencePoint();
  1821. bool leftRight = frame()->GetPcbNewSettings()->m_FlipLeftRight;
  1822. for( EDA_ITEM* item : selection )
  1823. {
  1824. if( BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( item ) )
  1825. {
  1826. if( !boardItem->IsNew() && !boardItem->IsMoving() )
  1827. commit->Modify( boardItem );
  1828. boardItem->Flip( refPt, leftRight );
  1829. boardItem->Normalize();
  1830. }
  1831. }
  1832. if( !localCommit.Empty() )
  1833. localCommit.Push( _( "Change Side / Flip" ) );
  1834. if( selection.IsHover() && !m_dragging )
  1835. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  1836. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  1837. if( m_dragging )
  1838. m_toolMgr->PostAction( PCB_ACTIONS::updateLocalRatsnest, VECTOR2I() );
  1839. // Restore the old reference so any mouse dragging that occurs doesn't make the selection jump
  1840. // to this now invalid reference
  1841. if( oldRefPt )
  1842. selection.SetReferencePoint( *oldRefPt );
  1843. else
  1844. selection.ClearReferencePoint();
  1845. return 0;
  1846. }
  1847. void EDIT_TOOL::getChildItemsOfGroupsAndGenerators( EDA_ITEM* item,
  1848. std::unordered_set<BOARD_ITEM*>& children )
  1849. {
  1850. wxASSERT( item->Type() == PCB_GROUP_T || item->Type() == PCB_GENERATOR_T );
  1851. std::unordered_set<BOARD_ITEM*>& childItems = static_cast<PCB_GROUP*>( item )->GetItems();
  1852. for( BOARD_ITEM* childItem : childItems )
  1853. {
  1854. children.insert( childItem );
  1855. if( childItem->Type() == PCB_GROUP_T || childItem->Type() == PCB_GENERATOR_T )
  1856. getChildItemsOfGroupsAndGenerators( childItem, children );
  1857. }
  1858. }
  1859. void EDIT_TOOL::removeNonRootItems( std::unordered_set<EDA_ITEM*>& items )
  1860. {
  1861. auto itr = items.begin();
  1862. while( itr != items.end() )
  1863. {
  1864. BOARD_ITEM* curItem = dynamic_cast<BOARD_ITEM*>( *itr );
  1865. if( curItem )
  1866. {
  1867. if( curItem->Type() == PCB_GROUP_T || curItem->Type() == PCB_GENERATOR_T )
  1868. {
  1869. std::unordered_set<BOARD_ITEM*> childItems;
  1870. getChildItemsOfGroupsAndGenerators( curItem, childItems );
  1871. std::for_each( childItems.begin(), childItems.end(),
  1872. [&]( auto eraseItem )
  1873. {
  1874. items.erase( eraseItem );
  1875. } );
  1876. }
  1877. }
  1878. ++itr;
  1879. }
  1880. }
  1881. void EDIT_TOOL::DeleteItems( const PCB_SELECTION& aItems, bool aIsCut )
  1882. {
  1883. PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  1884. BOARD_COMMIT commit( this );
  1885. // As we are about to remove items, they have to be removed from the selection first
  1886. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  1887. // Only delete items that are the root of a selected set (e.g. only delete grouped / generated
  1888. // items from the parent item, not individually) - issue #17527
  1889. std::unordered_set<EDA_ITEM*> rootItems( aItems.begin(), aItems.end() );
  1890. removeNonRootItems( rootItems );
  1891. int itemsDeleted = 0;
  1892. int fieldsHidden = 0;
  1893. int fieldsAlreadyHidden = 0;
  1894. for( EDA_ITEM* item : rootItems )
  1895. {
  1896. BOARD_ITEM* board_item = dynamic_cast<BOARD_ITEM*>( item );
  1897. wxCHECK2( board_item, continue );
  1898. FOOTPRINT* parentFP = board_item->GetParentFootprint();
  1899. if( board_item->GetParentGroup() )
  1900. commit.Stage( board_item, CHT_UNGROUP );
  1901. switch( item->Type() )
  1902. {
  1903. case PCB_FIELD_T:
  1904. {
  1905. PCB_FIELD* field = static_cast<PCB_FIELD*>( board_item );
  1906. wxASSERT( parentFP );
  1907. commit.Modify( parentFP );
  1908. if( field->IsVisible() )
  1909. {
  1910. field->SetVisible( false );
  1911. fieldsHidden++;
  1912. }
  1913. else
  1914. {
  1915. fieldsAlreadyHidden++;
  1916. }
  1917. getView()->Update( board_item );
  1918. break;
  1919. }
  1920. case PCB_TEXT_T:
  1921. case PCB_SHAPE_T:
  1922. case PCB_TEXTBOX_T:
  1923. case PCB_TABLE_T:
  1924. case PCB_REFERENCE_IMAGE_T:
  1925. case PCB_DIMENSION_T:
  1926. case PCB_DIM_ALIGNED_T:
  1927. case PCB_DIM_LEADER_T:
  1928. case PCB_DIM_CENTER_T:
  1929. case PCB_DIM_RADIAL_T:
  1930. case PCB_DIM_ORTHOGONAL_T:
  1931. commit.Remove( board_item );
  1932. itemsDeleted++;
  1933. break;
  1934. case PCB_TABLECELL_T:
  1935. // Clear contents of table cell
  1936. commit.Modify( board_item );
  1937. static_cast<PCB_TABLECELL*>( board_item )->SetText( wxEmptyString );
  1938. itemsDeleted++;
  1939. break;
  1940. case PCB_GROUP_T:
  1941. board_item->RunOnDescendants(
  1942. [&commit]( BOARD_ITEM* aItem )
  1943. {
  1944. commit.Stage( aItem, CHT_UNGROUP );
  1945. } );
  1946. board_item->RunOnDescendants(
  1947. [&commit]( BOARD_ITEM* aItem )
  1948. {
  1949. commit.Remove( aItem );
  1950. } );
  1951. commit.Remove( board_item );
  1952. itemsDeleted++;
  1953. break;
  1954. case PCB_PAD_T:
  1955. if( IsFootprintEditor() || frame()->GetPcbNewSettings()->m_AllowFreePads )
  1956. {
  1957. commit.Modify( parentFP );
  1958. getView()->Remove( board_item );
  1959. parentFP->Remove( board_item );
  1960. itemsDeleted++;
  1961. }
  1962. break;
  1963. case PCB_ZONE_T:
  1964. // We process the zones special so that cutouts can be deleted when the delete
  1965. // tool is called from inside a cutout when the zone is selected.
  1966. // Only interact with cutouts when deleting and a single item is selected
  1967. if( !aIsCut && aItems.GetSize() == 1 )
  1968. {
  1969. VECTOR2I curPos = getViewControls()->GetCursorPosition();
  1970. ZONE* zone = static_cast<ZONE*>( board_item );
  1971. int outlineIdx, holeIdx;
  1972. if( zone->HitTestCutout( curPos, &outlineIdx, &holeIdx ) )
  1973. {
  1974. // Remove the cutout
  1975. commit.Modify( zone );
  1976. zone->RemoveCutout( outlineIdx, holeIdx );
  1977. zone->UnFill();
  1978. // TODO Refill zone when KiCad supports auto re-fill
  1979. // Update the display
  1980. zone->HatchBorder();
  1981. canvas()->Refresh();
  1982. // Restore the selection on the original zone
  1983. m_toolMgr->RunAction<EDA_ITEM*>( PCB_ACTIONS::selectItem, zone );
  1984. break;
  1985. }
  1986. }
  1987. // Remove the entire zone otherwise
  1988. commit.Remove( board_item );
  1989. itemsDeleted++;
  1990. break;
  1991. case PCB_GENERATOR_T:
  1992. if( rootItems.size() == 1 )
  1993. {
  1994. PCB_GENERATOR* generator = static_cast<PCB_GENERATOR*>( board_item );
  1995. m_toolMgr->RunSynchronousAction<PCB_GENERATOR*>( PCB_ACTIONS::genRemove, &commit,
  1996. generator );
  1997. }
  1998. else
  1999. {
  2000. PCB_GENERATOR* generator = static_cast<PCB_GENERATOR*>( board_item );
  2001. for( BOARD_ITEM* member : generator->GetItems() )
  2002. commit.Stage( member, CHT_UNGROUP );
  2003. for( BOARD_ITEM* member : generator->GetItems() )
  2004. commit.Remove( member );
  2005. commit.Remove( board_item );
  2006. }
  2007. itemsDeleted++;
  2008. break;
  2009. default:
  2010. wxASSERT_MSG( parentFP == nullptr, wxT( "Try to delete an item living in a footprint" ) );
  2011. commit.Remove( board_item );
  2012. itemsDeleted++;
  2013. break;
  2014. }
  2015. }
  2016. // If the entered group has been emptied then leave it.
  2017. PCB_GROUP* enteredGroup = m_selectionTool->GetEnteredGroup();
  2018. if( enteredGroup && enteredGroup->GetItems().empty() )
  2019. m_selectionTool->ExitGroup();
  2020. if( aIsCut )
  2021. {
  2022. commit.Push( _( "Cut" ) );
  2023. }
  2024. else if( itemsDeleted == 0 )
  2025. {
  2026. if( fieldsHidden == 1 )
  2027. commit.Push( _( "Hide Field" ) );
  2028. else if( fieldsHidden > 1 )
  2029. commit.Push( _( "Hide Fields" ) );
  2030. else if( fieldsAlreadyHidden > 0 )
  2031. editFrame->ShowInfoBarError( _( "Use the Footprint Properties dialog to remove fields." ) );
  2032. }
  2033. else
  2034. {
  2035. commit.Push( _( "Delete" ) );
  2036. }
  2037. }
  2038. int EDIT_TOOL::Remove( const TOOL_EVENT& aEvent )
  2039. {
  2040. PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  2041. editFrame->PushTool( aEvent );
  2042. std::vector<BOARD_ITEM*> lockedItems;
  2043. Activate();
  2044. // get a copy instead of reference (as we're going to clear the selection before removing items)
  2045. PCB_SELECTION selectionCopy;
  2046. bool isCut = aEvent.Parameter<PCB_ACTIONS::REMOVE_FLAGS>() == PCB_ACTIONS::REMOVE_FLAGS::CUT;
  2047. bool isAlt = aEvent.Parameter<PCB_ACTIONS::REMOVE_FLAGS>() == PCB_ACTIONS::REMOVE_FLAGS::ALT;
  2048. // If we are in a "Cut" operation, then the copied selection exists already and we want to
  2049. // delete exactly that; no more, no fewer. Any filtering for locked items must be done in
  2050. // the copyToClipboard() routine.
  2051. if( isCut )
  2052. {
  2053. selectionCopy = m_selectionTool->GetSelection();
  2054. }
  2055. else
  2056. {
  2057. // When not in free-pad mode we normally auto-promote selected pads to their parent
  2058. // footprints. But this is probably a little too dangerous for a destructive operation,
  2059. // so we just do the promotion but not the deletion (allowing for a second delete to do
  2060. // it if that's what the user wanted).
  2061. selectionCopy = m_selectionTool->RequestSelection(
  2062. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  2063. {
  2064. } );
  2065. size_t beforeFPCount = selectionCopy.CountType( PCB_FOOTPRINT_T );
  2066. m_selectionTool->RequestSelection(
  2067. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  2068. {
  2069. sTool->FilterCollectorForFreePads( aCollector );
  2070. } );
  2071. if( !selectionCopy.IsHover()
  2072. && m_selectionTool->GetSelection().CountType( PCB_FOOTPRINT_T ) > beforeFPCount )
  2073. {
  2074. wxBell();
  2075. canvas()->Refresh();
  2076. editFrame->PopTool( aEvent );
  2077. return 0;
  2078. }
  2079. // In "alternative" mode, we expand selected track items to their full connection.
  2080. if( isAlt && ( selectionCopy.HasType( PCB_TRACE_T ) || selectionCopy.HasType( PCB_VIA_T ) ) )
  2081. {
  2082. m_toolMgr->RunAction( PCB_ACTIONS::selectConnection );
  2083. }
  2084. // Finally run RequestSelection() one more time to find out what user wants to do about
  2085. // locked objects.
  2086. selectionCopy = m_selectionTool->RequestSelection(
  2087. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  2088. {
  2089. sTool->FilterCollectorForFreePads( aCollector );
  2090. },
  2091. true /* prompt user regarding locked items */ );
  2092. }
  2093. DeleteItems( selectionCopy, isCut );
  2094. canvas()->Refresh();
  2095. editFrame->PopTool( aEvent );
  2096. return 0;
  2097. }
  2098. int EDIT_TOOL::MoveExact( const TOOL_EVENT& aEvent )
  2099. {
  2100. if( isRouterActive() )
  2101. {
  2102. wxBell();
  2103. return 0;
  2104. }
  2105. const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  2106. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  2107. {
  2108. sTool->FilterCollectorForMarkers( aCollector );
  2109. sTool->FilterCollectorForHierarchy( aCollector, true );
  2110. sTool->FilterCollectorForTableCells( aCollector );
  2111. },
  2112. true /* prompt user regarding locked items */ );
  2113. if( selection.Empty() )
  2114. return 0;
  2115. VECTOR2I translation;
  2116. EDA_ANGLE rotation;
  2117. ROTATION_ANCHOR rotationAnchor = selection.Size() > 1 ? ROTATE_AROUND_SEL_CENTER
  2118. : ROTATE_AROUND_ITEM_ANCHOR;
  2119. // TODO: Implement a visible bounding border at the edge
  2120. BOX2I sel_box = selection.GetBoundingBox();
  2121. DIALOG_MOVE_EXACT dialog( frame(), translation, rotation, rotationAnchor, sel_box );
  2122. int ret = dialog.ShowModal();
  2123. if( ret == wxID_OK )
  2124. {
  2125. BOARD_COMMIT commit( this );
  2126. EDA_ANGLE angle = rotation;
  2127. VECTOR2I rp = selection.GetCenter();
  2128. VECTOR2I selCenter( rp.x, rp.y );
  2129. // Make sure the rotation is from the right reference point
  2130. selCenter += translation;
  2131. if( !frame()->GetPcbNewSettings()->m_Display.m_DisplayInvertYAxis )
  2132. rotation = -rotation;
  2133. for( EDA_ITEM* item : selection )
  2134. {
  2135. BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( item );
  2136. wxCHECK2( boardItem, continue );
  2137. if( !boardItem->IsNew() )
  2138. commit.Modify( boardItem );
  2139. if( !boardItem->GetParent() || !boardItem->GetParent()->IsSelected() )
  2140. boardItem->Move( translation );
  2141. switch( rotationAnchor )
  2142. {
  2143. case ROTATE_AROUND_ITEM_ANCHOR:
  2144. boardItem->Rotate( boardItem->GetPosition(), angle );
  2145. break;
  2146. case ROTATE_AROUND_SEL_CENTER:
  2147. boardItem->Rotate( selCenter, angle );
  2148. break;
  2149. case ROTATE_AROUND_USER_ORIGIN:
  2150. boardItem->Rotate( frame()->GetScreen()->m_LocalOrigin, angle );
  2151. break;
  2152. case ROTATE_AROUND_AUX_ORIGIN:
  2153. boardItem->Rotate( board()->GetDesignSettings().GetAuxOrigin(), angle );
  2154. break;
  2155. }
  2156. if( !m_dragging )
  2157. getView()->Update( boardItem );
  2158. }
  2159. commit.Push( _( "Move Exactly" ) );
  2160. if( selection.IsHover() )
  2161. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  2162. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  2163. if( m_dragging )
  2164. m_toolMgr->PostAction( PCB_ACTIONS::updateLocalRatsnest, VECTOR2I() );
  2165. }
  2166. return 0;
  2167. }
  2168. int EDIT_TOOL::Duplicate( const TOOL_EVENT& aEvent )
  2169. {
  2170. if( isRouterActive() )
  2171. {
  2172. wxBell();
  2173. return 0;
  2174. }
  2175. bool increment = aEvent.IsAction( &PCB_ACTIONS::duplicateIncrement );
  2176. // Be sure that there is at least one item that we can modify
  2177. const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  2178. []( const VECTOR2I&, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  2179. {
  2180. sTool->FilterCollectorForFreePads( aCollector, true );
  2181. sTool->FilterCollectorForMarkers( aCollector );
  2182. sTool->FilterCollectorForHierarchy( aCollector, true );
  2183. sTool->FilterCollectorForTableCells( aCollector );
  2184. } );
  2185. if( selection.Empty() )
  2186. return 0;
  2187. // Duplicating tuhing patterns alone is not supported
  2188. if( selection.Size() == 1 && selection.CountType( PCB_GENERATOR_T ) )
  2189. return 0;
  2190. // we have a selection to work on now, so start the tool process
  2191. PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  2192. BOARD_COMMIT commit( this );
  2193. // If the selection was given a hover, we do not keep the selection after completion
  2194. bool is_hover = selection.IsHover();
  2195. std::vector<BOARD_ITEM*> new_items;
  2196. new_items.reserve( selection.Size() );
  2197. // Each selected item is duplicated and pushed to new_items list
  2198. // Old selection is cleared, and new items are then selected.
  2199. for( EDA_ITEM* item : selection )
  2200. {
  2201. BOARD_ITEM* dupe_item = nullptr;
  2202. BOARD_ITEM* orig_item = dynamic_cast<BOARD_ITEM*>( item );
  2203. wxCHECK2( orig_item, continue );
  2204. if( m_isFootprintEditor )
  2205. {
  2206. FOOTPRINT* parentFootprint = editFrame->GetBoard()->GetFirstFootprint();
  2207. // PCB_FIELD items are specific items (not only graphic, but are properies )
  2208. // and cannot be duplicated like other footprint items. So skip it:
  2209. if( orig_item->Type() == PCB_FIELD_T )
  2210. {
  2211. orig_item->ClearSelected();
  2212. continue;
  2213. }
  2214. dupe_item = parentFootprint->DuplicateItem( orig_item );
  2215. if( increment && dupe_item->Type() == PCB_PAD_T
  2216. && static_cast<PAD*>( dupe_item )->CanHaveNumber() )
  2217. {
  2218. PAD_TOOL* padTool = m_toolMgr->GetTool<PAD_TOOL>();
  2219. wxString padNumber = padTool->GetLastPadNumber();
  2220. padNumber = parentFootprint->GetNextPadNumber( padNumber );
  2221. padTool->SetLastPadNumber( padNumber );
  2222. static_cast<PAD*>( dupe_item )->SetNumber( padNumber );
  2223. }
  2224. dupe_item->ClearSelected();
  2225. new_items.push_back( dupe_item );
  2226. commit.Add( dupe_item );
  2227. }
  2228. else if( /*FOOTPRINT* parentFootprint =*/ orig_item->GetParentFootprint() )
  2229. {
  2230. // No sub-footprint modifications allowed outside of footprint editor
  2231. // and parentFootprint is not (yet?) used
  2232. }
  2233. else
  2234. {
  2235. switch( orig_item->Type() )
  2236. {
  2237. case PCB_FOOTPRINT_T:
  2238. case PCB_TEXT_T:
  2239. case PCB_TEXTBOX_T:
  2240. case PCB_REFERENCE_IMAGE_T:
  2241. case PCB_SHAPE_T:
  2242. case PCB_TRACE_T:
  2243. case PCB_ARC_T:
  2244. case PCB_VIA_T:
  2245. case PCB_ZONE_T:
  2246. case PCB_TARGET_T:
  2247. case PCB_DIM_ALIGNED_T:
  2248. case PCB_DIM_CENTER_T:
  2249. case PCB_DIM_RADIAL_T:
  2250. case PCB_DIM_ORTHOGONAL_T:
  2251. case PCB_DIM_LEADER_T:
  2252. dupe_item = orig_item->Duplicate();
  2253. // Clear the selection flag here, otherwise the PCB_SELECTION_TOOL
  2254. // will not properly select it later on
  2255. dupe_item->ClearSelected();
  2256. new_items.push_back( dupe_item );
  2257. commit.Add( dupe_item );
  2258. break;
  2259. case PCB_TABLE_T:
  2260. // JEY TODO: tables
  2261. break;
  2262. case PCB_GENERATOR_T:
  2263. case PCB_GROUP_T:
  2264. dupe_item = static_cast<PCB_GROUP*>( orig_item )->DeepDuplicate();
  2265. dupe_item->RunOnDescendants(
  2266. [&]( BOARD_ITEM* aItem )
  2267. {
  2268. aItem->ClearSelected();
  2269. new_items.push_back( aItem );
  2270. commit.Add( aItem );
  2271. } );
  2272. dupe_item->ClearSelected();
  2273. new_items.push_back( dupe_item );
  2274. commit.Add( dupe_item );
  2275. break;
  2276. default:
  2277. wxASSERT_MSG( false, wxString::Format( wxT( "Unhandled item type %d" ),
  2278. orig_item->Type() ) );
  2279. break;
  2280. }
  2281. }
  2282. }
  2283. // Clear the old selection first
  2284. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  2285. // Select the new items
  2286. EDA_ITEMS nItems( new_items.begin(), new_items.end() );
  2287. m_toolMgr->RunAction<EDA_ITEMS*>( PCB_ACTIONS::selectItems, &nItems );
  2288. // record the new items as added
  2289. if( !selection.Empty() )
  2290. {
  2291. editFrame->DisplayToolMsg( wxString::Format( _( "Duplicated %d item(s)" ),
  2292. (int) new_items.size() ) );
  2293. // If items were duplicated, pick them up
  2294. if( doMoveSelection( aEvent, &commit, true ) )
  2295. commit.Push( _( "Duplicate" ) );
  2296. else
  2297. commit.Revert();
  2298. // Deselect the duplicated item if we originally started as a hover selection
  2299. if( is_hover )
  2300. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  2301. }
  2302. return 0;
  2303. }
  2304. int EDIT_TOOL::CreateArray( const TOOL_EVENT& aEvent )
  2305. {
  2306. if( isRouterActive() )
  2307. {
  2308. wxBell();
  2309. return 0;
  2310. }
  2311. // Be sure that there is at least one item that we can modify
  2312. const PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  2313. []( const VECTOR2I&, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  2314. {
  2315. sTool->FilterCollectorForMarkers( aCollector );
  2316. sTool->FilterCollectorForHierarchy( aCollector, true );
  2317. } );
  2318. if( selection.Empty() )
  2319. return 0;
  2320. // we have a selection to work on now, so start the tool process
  2321. PCB_BASE_FRAME* editFrame = getEditFrame<PCB_BASE_FRAME>();
  2322. ARRAY_CREATOR array_creator( *editFrame, m_isFootprintEditor, selection, m_toolMgr );
  2323. array_creator.Invoke();
  2324. return 0;
  2325. }
  2326. void EDIT_TOOL::PadFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector,
  2327. PCB_SELECTION_TOOL* sTool )
  2328. {
  2329. for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
  2330. {
  2331. if( aCollector[i]->Type() != PCB_PAD_T )
  2332. aCollector.Remove( i );
  2333. }
  2334. }
  2335. void EDIT_TOOL::FootprintFilter( const VECTOR2I&, GENERAL_COLLECTOR& aCollector,
  2336. PCB_SELECTION_TOOL* sTool )
  2337. {
  2338. for( int i = aCollector.GetCount() - 1; i >= 0; i-- )
  2339. {
  2340. if( aCollector[i]->Type() != PCB_FOOTPRINT_T )
  2341. aCollector.Remove( i );
  2342. }
  2343. }
  2344. bool EDIT_TOOL::updateModificationPoint( PCB_SELECTION& aSelection )
  2345. {
  2346. // Can't modify an empty group
  2347. if( aSelection.Empty() )
  2348. return false;
  2349. if( ( m_dragging || aSelection[0]->IsMoving() ) && aSelection.HasReferencePoint() )
  2350. return false;
  2351. // When there is only one item selected, the reference point is its position...
  2352. if( aSelection.Size() == 1 && aSelection.Front()->Type() != PCB_TABLE_T )
  2353. {
  2354. if( BOARD_ITEM* item = dynamic_cast<BOARD_ITEM*>( aSelection.Front() ) )
  2355. aSelection.SetReferencePoint( item->GetPosition() );
  2356. }
  2357. // ...otherwise modify items with regard to the grid-snapped center position
  2358. else
  2359. {
  2360. PCB_GRID_HELPER grid( m_toolMgr, frame()->GetMagneticItemsSettings() );
  2361. VECTOR2I refPt = aSelection.GetCenter();
  2362. // Exclude text in the footprint editor if there's anything else selected
  2363. if( m_isFootprintEditor )
  2364. {
  2365. BOX2I nonFieldsBBox;
  2366. for( EDA_ITEM* item : aSelection.Items() )
  2367. {
  2368. if( !item->IsType( { PCB_TEXT_T, PCB_FIELD_T } ) )
  2369. nonFieldsBBox.Merge( item->GetBoundingBox() );
  2370. }
  2371. if( nonFieldsBBox.IsValid() )
  2372. refPt = nonFieldsBBox.GetCenter();
  2373. }
  2374. aSelection.SetReferencePoint( grid.BestSnapAnchor( refPt, nullptr ) );
  2375. }
  2376. return true;
  2377. }
  2378. bool EDIT_TOOL::pickReferencePoint( const wxString& aTooltip, const wxString& aSuccessMessage,
  2379. const wxString& aCanceledMessage, VECTOR2I& aReferencePoint )
  2380. {
  2381. PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
  2382. std::optional<VECTOR2I> pickedPoint;
  2383. bool done = false;
  2384. m_statusPopup->SetText( aTooltip );
  2385. /// This allow the option of snapping in the tool
  2386. picker->SetSnapping( true );
  2387. picker->SetClickHandler(
  2388. [&]( const VECTOR2D& aPoint ) -> bool
  2389. {
  2390. pickedPoint = aPoint;
  2391. if( !aSuccessMessage.empty() )
  2392. {
  2393. m_statusPopup->SetText( aSuccessMessage );
  2394. m_statusPopup->Expire( 800 );
  2395. }
  2396. else
  2397. {
  2398. m_statusPopup->Hide();
  2399. }
  2400. return false; // we don't need any more points
  2401. } );
  2402. picker->SetMotionHandler(
  2403. [&]( const VECTOR2D& aPos )
  2404. {
  2405. m_statusPopup->Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, -50 ) );
  2406. } );
  2407. picker->SetCancelHandler(
  2408. [&]()
  2409. {
  2410. if( !aCanceledMessage.empty() )
  2411. {
  2412. m_statusPopup->SetText( aCanceledMessage );
  2413. m_statusPopup->Expire( 800 );
  2414. }
  2415. else
  2416. {
  2417. m_statusPopup->Hide();
  2418. }
  2419. } );
  2420. picker->SetFinalizeHandler(
  2421. [&]( const int& aFinalState )
  2422. {
  2423. done = true;
  2424. } );
  2425. m_statusPopup->Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, -50 ) );
  2426. m_statusPopup->Popup();
  2427. canvas()->SetStatusPopup( m_statusPopup->GetPanel() );
  2428. m_toolMgr->RunAction( ACTIONS::pickerSubTool );
  2429. while( !done )
  2430. {
  2431. // Pass events unless we receive a null event, then we must shut down
  2432. if( TOOL_EVENT* evt = Wait() )
  2433. evt->SetPassEvent();
  2434. else
  2435. break;
  2436. }
  2437. // Ensure statusPopup is hidden after use and before deleting it:
  2438. canvas()->SetStatusPopup( nullptr );
  2439. m_statusPopup->Hide();
  2440. if( pickedPoint )
  2441. aReferencePoint = *pickedPoint;
  2442. return pickedPoint.has_value();
  2443. }
  2444. int EDIT_TOOL::copyToClipboard( const TOOL_EVENT& aEvent )
  2445. {
  2446. CLIPBOARD_IO io;
  2447. PCB_GRID_HELPER grid( m_toolMgr,
  2448. getEditFrame<PCB_BASE_EDIT_FRAME>()->GetMagneticItemsSettings() );
  2449. TOOL_EVENT selectReferencePoint( aEvent.Category(), aEvent.Action(),
  2450. "pcbnew.InteractiveEdit.selectReferencePoint",
  2451. TOOL_ACTION_SCOPE::AS_GLOBAL );
  2452. frame()->PushTool( selectReferencePoint );
  2453. Activate();
  2454. PCB_SELECTION& selection = m_selectionTool->RequestSelection(
  2455. []( const VECTOR2I& aPt, GENERAL_COLLECTOR& aCollector, PCB_SELECTION_TOOL* sTool )
  2456. {
  2457. for( int i = aCollector.GetCount() - 1; i >= 0; --i )
  2458. {
  2459. BOARD_ITEM* item = aCollector[i];
  2460. // We can't copy both a footprint and its text in the same operation, so if
  2461. // both are selected, remove the text
  2462. if( ( item->Type() == PCB_FIELD_T || item->Type() == PCB_TEXT_T )
  2463. && aCollector.HasItem( item->GetParentFootprint() ) )
  2464. {
  2465. aCollector.Remove( item );
  2466. }
  2467. else if( item->Type() == PCB_MARKER_T )
  2468. {
  2469. // Don't allow copying marker objects
  2470. aCollector.Remove( item );
  2471. }
  2472. }
  2473. },
  2474. // Prompt user regarding locked items.
  2475. aEvent.IsAction( &ACTIONS::cut ) && !m_isFootprintEditor );
  2476. if( !selection.Empty() )
  2477. {
  2478. std::vector<BOARD_ITEM*> items;
  2479. for( EDA_ITEM* item : selection )
  2480. {
  2481. if( BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( item ) )
  2482. items.push_back( boardItem );
  2483. }
  2484. VECTOR2I refPoint;
  2485. if( aEvent.IsAction( &PCB_ACTIONS::copyWithReference ) )
  2486. {
  2487. if( !pickReferencePoint( _( "Select reference point for the copy..." ),
  2488. _( "Selection copied" ),
  2489. _( "Copy canceled" ),
  2490. refPoint ) )
  2491. {
  2492. frame()->PopTool( selectReferencePoint );
  2493. return 0;
  2494. }
  2495. }
  2496. else
  2497. {
  2498. refPoint = grid.BestDragOrigin( getViewControls()->GetCursorPosition(), items );
  2499. }
  2500. selection.SetReferencePoint( refPoint );
  2501. io.SetBoard( board() );
  2502. io.SaveSelection( selection, m_isFootprintEditor );
  2503. frame()->SetStatusText( _( "Selection copied" ) );
  2504. }
  2505. frame()->PopTool( selectReferencePoint );
  2506. if( selection.IsHover() )
  2507. m_selectionTool->ClearSelection();
  2508. return 0;
  2509. }
  2510. int EDIT_TOOL::cutToClipboard( const TOOL_EVENT& aEvent )
  2511. {
  2512. if( !copyToClipboard( aEvent ) )
  2513. {
  2514. // N.B. Setting the CUT flag prevents lock filtering as we only want to delete the items
  2515. // that were copied to the clipboard, no more, no fewer. Filtering for locked item, if
  2516. // any will be done in the copyToClipboard() routine
  2517. TOOL_EVENT evt = aEvent;
  2518. evt.SetParameter( PCB_ACTIONS::REMOVE_FLAGS::CUT );
  2519. Remove( evt );
  2520. }
  2521. return 0;
  2522. }
  2523. void EDIT_TOOL::rebuildConnectivity()
  2524. {
  2525. board()->BuildConnectivity();
  2526. m_toolMgr->PostEvent( EVENTS::ConnectivityChangedEvent );
  2527. canvas()->RedrawRatsnest();
  2528. }
  2529. void EDIT_TOOL::setTransitions()
  2530. {
  2531. Go( &EDIT_TOOL::GetAndPlace, PCB_ACTIONS::getAndPlace.MakeEvent() );
  2532. Go( &EDIT_TOOL::Move, PCB_ACTIONS::move.MakeEvent() );
  2533. Go( &EDIT_TOOL::Move, PCB_ACTIONS::moveIndividually.MakeEvent() );
  2534. Go( &EDIT_TOOL::Drag, PCB_ACTIONS::drag45Degree.MakeEvent() );
  2535. Go( &EDIT_TOOL::Drag, PCB_ACTIONS::dragFreeAngle.MakeEvent() );
  2536. Go( &EDIT_TOOL::Rotate, PCB_ACTIONS::rotateCw.MakeEvent() );
  2537. Go( &EDIT_TOOL::Rotate, PCB_ACTIONS::rotateCcw.MakeEvent() );
  2538. Go( &EDIT_TOOL::Flip, PCB_ACTIONS::flip.MakeEvent() );
  2539. Go( &EDIT_TOOL::Remove, ACTIONS::doDelete.MakeEvent() );
  2540. Go( &EDIT_TOOL::Remove, PCB_ACTIONS::deleteFull.MakeEvent() );
  2541. Go( &EDIT_TOOL::Properties, PCB_ACTIONS::properties.MakeEvent() );
  2542. Go( &EDIT_TOOL::MoveExact, PCB_ACTIONS::moveExact.MakeEvent() );
  2543. Go( &EDIT_TOOL::Move, PCB_ACTIONS::moveWithReference.MakeEvent() );
  2544. Go( &EDIT_TOOL::Duplicate, ACTIONS::duplicate.MakeEvent() );
  2545. Go( &EDIT_TOOL::Duplicate, PCB_ACTIONS::duplicateIncrement.MakeEvent() );
  2546. Go( &EDIT_TOOL::CreateArray, PCB_ACTIONS::createArray.MakeEvent() );
  2547. Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirrorH.MakeEvent() );
  2548. Go( &EDIT_TOOL::Mirror, PCB_ACTIONS::mirrorV.MakeEvent() );
  2549. Go( &EDIT_TOOL::Swap, PCB_ACTIONS::swap.MakeEvent() );
  2550. Go( &EDIT_TOOL::PackAndMoveFootprints, PCB_ACTIONS::packAndMoveFootprints.MakeEvent() );
  2551. Go( &EDIT_TOOL::ChangeTrackWidth, PCB_ACTIONS::changeTrackWidth.MakeEvent() );
  2552. Go( &EDIT_TOOL::FilletTracks, PCB_ACTIONS::filletTracks.MakeEvent() );
  2553. Go( &EDIT_TOOL::ModifyLines, PCB_ACTIONS::filletLines.MakeEvent() );
  2554. Go( &EDIT_TOOL::ModifyLines, PCB_ACTIONS::chamferLines.MakeEvent() );
  2555. Go( &EDIT_TOOL::SimplifyPolygons, PCB_ACTIONS::simplifyPolygons.MakeEvent() );
  2556. Go( &EDIT_TOOL::HealShapes, PCB_ACTIONS::healShapes.MakeEvent() );
  2557. Go( &EDIT_TOOL::ModifyLines, PCB_ACTIONS::extendLines.MakeEvent() );
  2558. Go( &EDIT_TOOL::BooleanPolygons, PCB_ACTIONS::mergePolygons.MakeEvent() );
  2559. Go( &EDIT_TOOL::BooleanPolygons, PCB_ACTIONS::subtractPolygons.MakeEvent() );
  2560. Go( &EDIT_TOOL::BooleanPolygons, PCB_ACTIONS::intersectPolygons.MakeEvent() );
  2561. Go( &EDIT_TOOL::JustifyText, ACTIONS::leftJustify.MakeEvent() );
  2562. Go( &EDIT_TOOL::JustifyText, ACTIONS::centerJustify.MakeEvent() );
  2563. Go( &EDIT_TOOL::JustifyText, ACTIONS::rightJustify.MakeEvent() );
  2564. Go( &EDIT_TOOL::copyToClipboard, ACTIONS::copy.MakeEvent() );
  2565. Go( &EDIT_TOOL::copyToClipboard, PCB_ACTIONS::copyWithReference.MakeEvent() );
  2566. Go( &EDIT_TOOL::cutToClipboard, ACTIONS::cut.MakeEvent() );
  2567. }