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.

3582 lines
123 KiB

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