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.

3546 lines
121 KiB

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