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.

3209 lines
110 KiB

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