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.

942 lines
31 KiB

4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, you may find one here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include "pad_tool.h"
  24. #include "pcb_painter.h"
  25. #include <kiplatform/ui.h>
  26. #include <macros.h>
  27. #include <class_draw_panel_gal.h>
  28. #include <view/view_controls.h>
  29. #include <tool/tool_manager.h>
  30. #include <board_design_settings.h>
  31. #include <board_item.h>
  32. #include <footprint.h>
  33. #include <pcb_shape.h>
  34. #include <pad.h>
  35. #include <pcbnew_settings.h>
  36. #include <board_commit.h>
  37. #include <dialogs/dialog_push_pad_properties.h>
  38. #include <tools/pcb_actions.h>
  39. #include <tools/pcb_grid_helper.h>
  40. #include <tools/pcb_selection_tool.h>
  41. #include <tools/pcb_selection_conditions.h>
  42. #include <tools/edit_tool.h>
  43. #include <dialogs/dialog_enum_pads.h>
  44. #include <widgets/wx_infobar.h>
  45. using KIGFX::PCB_RENDER_SETTINGS;
  46. PAD_TOOL::PAD_TOOL() :
  47. PCB_TOOL_BASE( "pcbnew.PadTool" ),
  48. m_previousHighContrastMode( HIGH_CONTRAST_MODE::NORMAL ),
  49. m_editPad( niluuid )
  50. {}
  51. PAD_TOOL::~PAD_TOOL()
  52. {}
  53. void PAD_TOOL::Reset( RESET_REASON aReason )
  54. {
  55. if( aReason == MODEL_RELOAD )
  56. m_lastPadNumber = wxT( "1" );
  57. if( board() && board()->GetItem( m_editPad ) == DELETED_BOARD_ITEM::GetInstance() )
  58. {
  59. PCB_DISPLAY_OPTIONS opts = frame()->GetDisplayOptions();
  60. if( m_previousHighContrastMode != opts.m_ContrastModeDisplay )
  61. {
  62. opts.m_ContrastModeDisplay = m_previousHighContrastMode;
  63. frame()->SetDisplayOptions( opts );
  64. }
  65. frame()->GetInfoBar()->Dismiss();
  66. m_editPad = niluuid;
  67. }
  68. }
  69. bool PAD_TOOL::Init()
  70. {
  71. static const std::vector<KICAD_T> padTypes = { PCB_PAD_T };
  72. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  73. if( selTool )
  74. {
  75. // Add context menu entries that are displayed when selection tool is active
  76. CONDITIONAL_MENU& menu = selTool->GetToolMenu().GetMenu();
  77. SELECTION_CONDITION padSel = SELECTION_CONDITIONS::HasType( PCB_PAD_T );
  78. SELECTION_CONDITION singlePadSel = SELECTION_CONDITIONS::Count( 1 ) &&
  79. SELECTION_CONDITIONS::OnlyTypes( padTypes );
  80. auto explodeCondition =
  81. [&]( const SELECTION& aSel )
  82. {
  83. return m_editPad == niluuid && aSel.Size() == 1 && aSel[0]->Type() == PCB_PAD_T;
  84. };
  85. auto recombineCondition =
  86. [&]( const SELECTION& aSel )
  87. {
  88. return m_editPad != niluuid;
  89. };
  90. menu.AddSeparator( 400 );
  91. if( m_isFootprintEditor )
  92. {
  93. menu.AddItem( PCB_ACTIONS::enumeratePads, SELECTION_CONDITIONS::ShowAlways, 400 );
  94. menu.AddItem( PCB_ACTIONS::recombinePad, recombineCondition, 400 );
  95. menu.AddItem( PCB_ACTIONS::explodePad, explodeCondition, 400 );
  96. }
  97. menu.AddItem( PCB_ACTIONS::copyPadSettings, singlePadSel, 400 );
  98. menu.AddItem( PCB_ACTIONS::applyPadSettings, padSel, 400 );
  99. menu.AddItem( PCB_ACTIONS::pushPadSettings, singlePadSel, 400 );
  100. }
  101. auto& ctxMenu = m_menu->GetMenu();
  102. // cancel current tool goes in main context menu at the top if present
  103. ctxMenu.AddItem( ACTIONS::cancelInteractive, SELECTION_CONDITIONS::ShowAlways, 1 );
  104. ctxMenu.AddSeparator( 1 );
  105. ctxMenu.AddItem( PCB_ACTIONS::rotateCcw, SELECTION_CONDITIONS::ShowAlways );
  106. ctxMenu.AddItem( PCB_ACTIONS::rotateCw, SELECTION_CONDITIONS::ShowAlways );
  107. ctxMenu.AddItem( PCB_ACTIONS::flip, SELECTION_CONDITIONS::ShowAlways );
  108. ctxMenu.AddItem( PCB_ACTIONS::mirrorH, SELECTION_CONDITIONS::ShowAlways );
  109. ctxMenu.AddItem( PCB_ACTIONS::mirrorV, SELECTION_CONDITIONS::ShowAlways );
  110. ctxMenu.AddItem( PCB_ACTIONS::properties, SELECTION_CONDITIONS::ShowAlways );
  111. // Finally, add the standard zoom/grid items
  112. getEditFrame<PCB_BASE_FRAME>()->AddStandardSubMenus( *m_menu.get() );
  113. return true;
  114. }
  115. int PAD_TOOL::pastePadProperties( const TOOL_EVENT& aEvent )
  116. {
  117. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  118. const PCB_SELECTION& selection = selTool->GetSelection();
  119. const PAD* masterPad = frame()->GetDesignSettings().m_Pad_Master.get();
  120. BOARD_COMMIT commit( frame() );
  121. // for every selected pad, paste global settings
  122. for( EDA_ITEM* item : selection )
  123. {
  124. if( item->Type() == PCB_PAD_T )
  125. {
  126. commit.Modify( item );
  127. static_cast<PAD&>( *item ).ImportSettingsFrom( *masterPad );
  128. }
  129. }
  130. commit.Push( _( "Paste Pad Properties" ) );
  131. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  132. frame()->Refresh();
  133. return 0;
  134. }
  135. int PAD_TOOL::copyPadSettings( const TOOL_EVENT& aEvent )
  136. {
  137. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  138. const PCB_SELECTION& selection = selTool->GetSelection();
  139. // can only copy from a single pad
  140. if( selection.Size() == 1 )
  141. {
  142. EDA_ITEM* item = selection[0];
  143. if( item->Type() == PCB_PAD_T )
  144. {
  145. const PAD& selPad = static_cast<const PAD&>( *item );
  146. frame()->GetDesignSettings().m_Pad_Master->ImportSettingsFrom( selPad );
  147. }
  148. }
  149. return 0;
  150. }
  151. static void doPushPadProperties( BOARD& board, const PAD& aSrcPad, BOARD_COMMIT& commit,
  152. bool aSameFootprints, bool aPadShapeFilter, bool aPadOrientFilter,
  153. bool aPadLayerFilter, bool aPadTypeFilter )
  154. {
  155. const FOOTPRINT* refFootprint = aSrcPad.GetParentFootprint();
  156. EDA_ANGLE srcPadAngle = aSrcPad.GetOrientation() - refFootprint->GetOrientation();
  157. for( FOOTPRINT* footprint : board.Footprints() )
  158. {
  159. if( !aSameFootprints && ( footprint != refFootprint ) )
  160. continue;
  161. if( footprint->GetFPID() != refFootprint->GetFPID() )
  162. continue;
  163. for( PAD* pad : footprint->Pads() )
  164. {
  165. // TODO(JE) padstacks
  166. if( aPadShapeFilter && ( pad->GetShape( PADSTACK::ALL_LAYERS ) != aSrcPad.GetShape( PADSTACK::ALL_LAYERS ) ) )
  167. continue;
  168. EDA_ANGLE padAngle = pad->GetOrientation() - footprint->GetOrientation();
  169. if( aPadOrientFilter && ( padAngle != srcPadAngle ) )
  170. continue;
  171. if( aPadLayerFilter && ( pad->GetLayerSet() != aSrcPad.GetLayerSet() ) )
  172. continue;
  173. if( aPadTypeFilter && ( pad->GetAttribute() != aSrcPad.GetAttribute() ) )
  174. continue;
  175. // Special-case for aperture pads
  176. if( aPadTypeFilter && pad->GetAttribute() == PAD_ATTRIB::CONN )
  177. {
  178. if( pad->IsAperturePad() != aSrcPad.IsAperturePad() )
  179. continue;
  180. }
  181. commit.Modify( pad );
  182. // Apply source pad settings to this pad
  183. pad->ImportSettingsFrom( aSrcPad );
  184. }
  185. }
  186. }
  187. int PAD_TOOL::pushPadSettings( const TOOL_EVENT& aEvent )
  188. {
  189. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  190. const PCB_SELECTION& selection = selTool->GetSelection();
  191. if( selection.Size() == 1 && selection[0]->Type() == PCB_PAD_T )
  192. {
  193. PAD* srcPad = static_cast<PAD*>( selection[0] );
  194. if( FOOTPRINT* footprint = srcPad->GetParentFootprint() )
  195. {
  196. frame()->SetMsgPanel( footprint );
  197. DIALOG_PUSH_PAD_PROPERTIES dlg( frame() );
  198. int dialogRet = dlg.ShowModal();
  199. if( dialogRet == wxID_CANCEL )
  200. return 0;
  201. const bool edit_Same_Modules = (dialogRet == 1);
  202. BOARD_COMMIT commit( frame() );
  203. doPushPadProperties( *getModel<BOARD>(), *srcPad, commit, edit_Same_Modules,
  204. DIALOG_PUSH_PAD_PROPERTIES::m_Pad_Shape_Filter,
  205. DIALOG_PUSH_PAD_PROPERTIES::m_Pad_Orient_Filter,
  206. DIALOG_PUSH_PAD_PROPERTIES::m_Pad_Layer_Filter,
  207. DIALOG_PUSH_PAD_PROPERTIES::m_Pad_Type_Filter );
  208. commit.Push( _( "Push Pad Settings" ) );
  209. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  210. frame()->Refresh();
  211. }
  212. }
  213. return 0;
  214. }
  215. /**
  216. * @brief Prompts the user for parameters for sequential pad numbering
  217. *
  218. * @param aFrame The parent window for the dialog
  219. * @return The parameters, or nullopt if no parameters, e.g. user cancelled the dialog
  220. */
  221. static std::optional<SEQUENTIAL_PAD_ENUMERATION_PARAMS>
  222. GetSequentialPadNumberingParams( wxWindow* aFrame )
  223. {
  224. // Persistent settings for the pad enumeration dialog.
  225. static SEQUENTIAL_PAD_ENUMERATION_PARAMS s_lastUsedParams;
  226. DIALOG_ENUM_PADS settingsDlg( aFrame, s_lastUsedParams );
  227. if( settingsDlg.ShowModal() != wxID_OK )
  228. return std::nullopt;
  229. return s_lastUsedParams;
  230. }
  231. int PAD_TOOL::EnumeratePads( const TOOL_EVENT& aEvent )
  232. {
  233. if( !m_isFootprintEditor )
  234. return 0;
  235. if( !board()->GetFirstFootprint() || board()->GetFirstFootprint()->Pads().empty() )
  236. return 0;
  237. GENERAL_COLLECTOR collector;
  238. GENERAL_COLLECTORS_GUIDE guide = frame()->GetCollectorsGuide();
  239. guide.SetIgnoreFPTextOnBack( true );
  240. guide.SetIgnoreFPTextOnFront( true );
  241. guide.SetIgnoreFPValues( true );
  242. guide.SetIgnoreFPReferences( true );
  243. const std::optional<SEQUENTIAL_PAD_ENUMERATION_PARAMS> params =
  244. GetSequentialPadNumberingParams( frame() );
  245. // Cancelled or otherwise failed to get any useful parameters
  246. if( !params )
  247. return 0;
  248. int seqPadNum = params->m_start_number;
  249. std::deque<int> storedPadNumbers;
  250. std::map<wxString, std::pair<int, wxString>> oldNumbers;
  251. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  252. frame()->PushTool( aEvent );
  253. VECTOR2I oldMousePos; // store the previous mouse cursor position, during mouse drag
  254. std::list<PAD*> selectedPads;
  255. BOARD_COMMIT commit( frame() );
  256. bool isFirstPoint = true; // make sure oldMousePos is initialized at least once
  257. std::deque<PAD*> pads = board()->GetFirstFootprint()->Pads();
  258. MAGNETIC_SETTINGS mag_settings;
  259. mag_settings.graphics = false;
  260. mag_settings.tracks = MAGNETIC_OPTIONS::NO_EFFECT;
  261. mag_settings.pads = MAGNETIC_OPTIONS::CAPTURE_ALWAYS;
  262. PCB_GRID_HELPER grid( m_toolMgr, &mag_settings );
  263. grid.SetSnap( true );
  264. grid.SetUseGrid( false );
  265. auto setCursor =
  266. [&]()
  267. {
  268. canvas()->SetCurrentCursor( KICURSOR::BULLSEYE );
  269. };
  270. KIGFX::VIEW* view = m_toolMgr->GetView();
  271. RENDER_SETTINGS* settings = view->GetPainter()->GetSettings();
  272. const std::set<int>& activeLayers = settings->GetHighContrastLayers();
  273. bool isHighContrast = settings->GetHighContrast();
  274. auto checkVisibility =
  275. [&]( BOARD_ITEM* item )
  276. {
  277. if( !view->IsVisible( item ) )
  278. return false;
  279. for( PCB_LAYER_ID layer : item->GetLayerSet().Seq() )
  280. {
  281. if( ( isHighContrast && activeLayers.count( layer ) )
  282. || view->IsLayerVisible( layer ) )
  283. {
  284. if( item->ViewGetLOD( layer, view ) < view->GetScale() )
  285. return true;
  286. }
  287. }
  288. return false;
  289. };
  290. for( PAD* pad : board()->GetFirstFootprint()->Pads() )
  291. {
  292. if( checkVisibility( pad ) )
  293. pads.push_back( pad );
  294. }
  295. Activate();
  296. // Must be done after Activate() so that it gets set into the correct context
  297. getViewControls()->ShowCursor( true );
  298. getViewControls()->ForceCursorPosition( false );
  299. // Set initial cursor
  300. setCursor();
  301. STATUS_TEXT_POPUP statusPopup( frame() );
  302. // Callable lambda to construct the pad number string for the given value
  303. const auto constructPadNumber =
  304. [&]( int aValue )
  305. {
  306. return wxString::Format( wxT( "%s%d" ), params->m_prefix.value_or( "" ), aValue );
  307. };
  308. // Callable lambda to set the popup text for the given pad value
  309. const auto setPopupTextForValue =
  310. [&]( int aValue )
  311. {
  312. const wxString msg = _( "Click on pad %s\n"
  313. "Press <esc> to cancel all; double-click to finish" );
  314. statusPopup.SetText( wxString::Format( msg, constructPadNumber( aValue ) ) );
  315. };
  316. setPopupTextForValue( seqPadNum );
  317. statusPopup.Popup();
  318. statusPopup.Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, 20 ) );
  319. canvas()->SetStatusPopup( statusPopup.GetPanel() );
  320. while( TOOL_EVENT* evt = Wait() )
  321. {
  322. setCursor();
  323. VECTOR2I mousePos = getViewControls()->GetMousePosition();
  324. VECTOR2I cursorPos = grid.SnapToPad( mousePos, pads );
  325. getViewControls()->ForceCursorPosition( true, cursorPos );
  326. if( evt->IsCancelInteractive() )
  327. {
  328. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  329. commit.Revert();
  330. frame()->PopTool( aEvent );
  331. break;
  332. }
  333. else if( evt->IsActivate() )
  334. {
  335. commit.Push( _( "Renumber Pads" ) );
  336. frame()->PopTool( aEvent );
  337. break;
  338. }
  339. else if( evt->IsDrag( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) )
  340. {
  341. selectedPads.clear();
  342. // Be sure the old cursor mouse position was initialized:
  343. if( isFirstPoint )
  344. {
  345. oldMousePos = mousePos;
  346. isFirstPoint = false;
  347. }
  348. // wxWidgets deliver mouse move events not frequently enough, resulting in skipping
  349. // pads if the user moves cursor too fast. To solve it, create a line that approximates
  350. // the mouse move and search pads that are on the line.
  351. int distance = ( mousePos - oldMousePos ).EuclideanNorm();
  352. // Search will be made every 0.1 mm:
  353. int segments = distance / int( 0.1 * pcbIUScale.IU_PER_MM ) + 1;
  354. const VECTOR2I line_step( ( mousePos - oldMousePos ) / segments );
  355. collector.Empty();
  356. for( int j = 0; j < segments; ++j )
  357. {
  358. VECTOR2I testpoint( mousePos.x - j * line_step.x, mousePos.y - j * line_step.y );
  359. collector.Collect( board(), { PCB_PAD_T }, testpoint, guide );
  360. for( int i = 0; i < collector.GetCount(); ++i )
  361. {
  362. PAD* pad = static_cast<PAD*>( collector[i] );
  363. if( !pad->IsAperturePad() && checkVisibility( pad ) )
  364. selectedPads.push_back( pad );
  365. }
  366. }
  367. selectedPads.unique();
  368. for( PAD* pad : selectedPads )
  369. {
  370. // If pad was not selected, then enumerate it
  371. if( !pad->IsSelected() )
  372. {
  373. commit.Modify( pad );
  374. // Rename pad and store the old name
  375. int newval;
  376. if( storedPadNumbers.size() > 0 )
  377. {
  378. newval = storedPadNumbers.front();
  379. storedPadNumbers.pop_front();
  380. }
  381. else
  382. {
  383. newval = seqPadNum;
  384. seqPadNum += params->m_step;
  385. }
  386. const wxString newNumber = constructPadNumber( newval );
  387. oldNumbers[newNumber] = { newval, pad->GetNumber() };
  388. pad->SetNumber( newNumber );
  389. SetLastPadNumber( newNumber );
  390. pad->SetSelected();
  391. getView()->Update( pad );
  392. // Ensure the popup text shows the correct next value
  393. if( storedPadNumbers.size() > 0 )
  394. newval = storedPadNumbers.front();
  395. else
  396. newval = seqPadNum;
  397. setPopupTextForValue( newval );
  398. }
  399. // ... or restore the old name if it was enumerated and clicked again
  400. else if( pad->IsSelected() && evt->IsClick( BUT_LEFT ) )
  401. {
  402. auto it = oldNumbers.find( pad->GetNumber() );
  403. wxASSERT( it != oldNumbers.end() );
  404. if( it != oldNumbers.end() )
  405. {
  406. storedPadNumbers.push_back( it->second.first );
  407. pad->SetNumber( it->second.second );
  408. SetLastPadNumber( it->second.second );
  409. oldNumbers.erase( it );
  410. const int newval = storedPadNumbers.front();
  411. setPopupTextForValue( newval );
  412. }
  413. pad->ClearSelected();
  414. getView()->Update( pad );
  415. }
  416. }
  417. }
  418. else if( evt->IsDblClick( BUT_LEFT ) )
  419. {
  420. commit.Push( _( "Renumber Pads" ) );
  421. frame()->PopTool( aEvent );
  422. break;
  423. }
  424. else if( evt->IsClick( BUT_RIGHT ) )
  425. {
  426. m_menu->ShowContextMenu( selection() );
  427. }
  428. else
  429. {
  430. evt->SetPassEvent();
  431. }
  432. // Prepare the next loop by updating the old cursor mouse position
  433. // to this last mouse cursor position
  434. oldMousePos = mousePos;
  435. statusPopup.Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, 20 ) );
  436. }
  437. for( PAD* p : board()->GetFirstFootprint()->Pads() )
  438. {
  439. p->ClearSelected();
  440. getView()->Update( p );
  441. }
  442. canvas()->SetStatusPopup( nullptr );
  443. statusPopup.Hide();
  444. canvas()->SetCurrentCursor( KICURSOR::ARROW );
  445. getViewControls()->ForceCursorPosition( false );
  446. return 0;
  447. }
  448. int PAD_TOOL::PlacePad( const TOOL_EVENT& aEvent )
  449. {
  450. // When creating a new pad (in FP editor) we can use a new pad number
  451. // or the last entered pad number
  452. // neednewPadNumber = true to create a new pad number, false to use the last
  453. // entered pad number
  454. static bool neednewPadNumber;
  455. if( !m_isFootprintEditor )
  456. return 0;
  457. if( !board()->GetFirstFootprint() )
  458. return 0;
  459. struct PAD_PLACER : public INTERACTIVE_PLACER_BASE
  460. {
  461. PAD_PLACER( PAD_TOOL* aPadTool, PCB_BASE_EDIT_FRAME* aFrame ) :
  462. m_padTool( aPadTool ),
  463. m_frame( aFrame ),
  464. m_gridHelper( aPadTool->GetManager(), aFrame->GetMagneticItemsSettings() )
  465. {
  466. neednewPadNumber = true; // Use a new pad number when creatin a pad by default
  467. }
  468. virtual ~PAD_PLACER()
  469. {
  470. }
  471. std::unique_ptr<BOARD_ITEM> CreateItem() override
  472. {
  473. // TODO(JE) padstacks
  474. PAD* pad = new PAD( m_board->GetFirstFootprint() );
  475. PAD* master = m_frame->GetDesignSettings().m_Pad_Master.get();
  476. pad->ImportSettingsFrom( *master );
  477. // If the footprint type and master pad type directly conflict then make some
  478. // adjustments. Otherwise assume the user set what they wanted.
  479. // Note also a HEATSINK pad (thermal via) is allowed in SMD footprint
  480. if( ( m_board->GetFirstFootprint()->GetAttributes() & FP_SMD )
  481. && master->GetAttribute() == PAD_ATTRIB::PTH )
  482. {
  483. if( pad->GetProperty() != PAD_PROP::HEATSINK )
  484. {
  485. pad->SetAttribute( PAD_ATTRIB::SMD );
  486. pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::ROUNDRECT );
  487. pad->SetSizeX( 1.5 * pad->GetSizeY() );
  488. pad->SetLayerSet( PAD::SMDMask() );
  489. }
  490. }
  491. else if( ( m_board->GetFirstFootprint()->GetAttributes() & FP_THROUGH_HOLE )
  492. && master->GetAttribute() == PAD_ATTRIB::SMD )
  493. {
  494. pad->SetAttribute( PAD_ATTRIB::PTH );
  495. pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
  496. pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( pad->GetSizeX(), pad->GetSizeX() ) );
  497. // Gives an acceptable drill size: it cannot be 0, but from pad master
  498. // it is currently 0, therefore change it:
  499. pad->SetDrillShape( PAD_DRILL_SHAPE::CIRCLE );
  500. int hole_size = pad->GetSizeX() / 2;
  501. pad->SetDrillSize( VECTOR2I( hole_size, hole_size ) );
  502. pad->SetLayerSet( PAD::PTHMask() );
  503. }
  504. if( pad->CanHaveNumber() )
  505. {
  506. wxString padNumber = m_padTool->GetLastPadNumber();
  507. // Use the last entered pad number when recreating a pad without using the
  508. // previously created pad, and a new number when creating a really new pad
  509. if( neednewPadNumber )
  510. padNumber = m_board->GetFirstFootprint()->GetNextPadNumber( padNumber );
  511. pad->SetNumber( padNumber );
  512. m_padTool->SetLastPadNumber( padNumber );
  513. // If a pad is recreated and the previously created was not placed, use
  514. // the last entered pad number
  515. neednewPadNumber = false;
  516. }
  517. return std::unique_ptr<BOARD_ITEM>( pad );
  518. }
  519. bool PlaceItem( BOARD_ITEM *aItem, BOARD_COMMIT& aCommit ) override
  520. {
  521. PAD* pad = dynamic_cast<PAD*>( aItem );
  522. // We are using this pad number.
  523. // therefore use a new pad number for a newly created pad
  524. neednewPadNumber = true;
  525. if( pad )
  526. {
  527. m_frame->GetDesignSettings().m_Pad_Master->ImportSettingsFrom( *pad );
  528. aCommit.Add( aItem );
  529. return true;
  530. }
  531. return false;
  532. }
  533. void SnapItem( BOARD_ITEM *aItem ) override
  534. {
  535. m_gridHelper.SetSnap( !( m_modifiers & MD_SHIFT ) );
  536. m_gridHelper.SetUseGrid( !( m_modifiers & MD_CTRL ) );
  537. if( !m_gridHelper.GetSnap() )
  538. return;
  539. MAGNETIC_SETTINGS* settings = m_frame->GetMagneticItemsSettings();
  540. PAD* pad = static_cast<PAD*>( aItem );
  541. VECTOR2I position = m_padTool->getViewControls()->GetMousePosition();
  542. KIGFX::VIEW_CONTROLS* viewControls = m_padTool->getViewControls();
  543. std::vector<BOARD_ITEM*> ignored_items( 1, pad );
  544. if( settings->pads == MAGNETIC_OPTIONS::NO_EFFECT )
  545. {
  546. PADS& pads = m_board->GetFirstFootprint()->Pads();
  547. ignored_items.insert( ignored_items.end(), pads.begin(), pads.end() );
  548. }
  549. if( !settings->graphics )
  550. {
  551. DRAWINGS& graphics = m_board->GetFirstFootprint()->GraphicalItems();
  552. ignored_items.insert( ignored_items.end(), graphics.begin(), graphics.end() );
  553. }
  554. VECTOR2I cursorPos = m_gridHelper.BestSnapAnchor( position, LSET::AllLayersMask(), GRID_CURRENT, ignored_items );
  555. viewControls->ForceCursorPosition( true, cursorPos );
  556. aItem->SetPosition( cursorPos );
  557. }
  558. PAD_TOOL* m_padTool;
  559. PCB_BASE_EDIT_FRAME* m_frame;
  560. PCB_GRID_HELPER m_gridHelper;
  561. };
  562. PAD_PLACER placer( this, frame() );
  563. doInteractiveItemPlacement( aEvent, &placer, _( "Place pad" ),
  564. IPO_REPEAT | IPO_SINGLE_CLICK | IPO_ROTATE | IPO_FLIP );
  565. return 0;
  566. }
  567. int PAD_TOOL::EditPad( const TOOL_EVENT& aEvent )
  568. {
  569. if( !m_isFootprintEditor )
  570. return 0;
  571. Activate();
  572. KIGFX::PCB_PAINTER* painter = static_cast<KIGFX::PCB_PAINTER*>( view()->GetPainter() );
  573. PCB_RENDER_SETTINGS* settings = painter->GetSettings();
  574. PCB_SELECTION& selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
  575. if( m_editPad != niluuid )
  576. {
  577. PAD* pad = dynamic_cast<PAD*>( frame()->GetItem( m_editPad ) );
  578. if( pad )
  579. {
  580. BOARD_COMMIT commit( frame() );
  581. commit.Modify( pad );
  582. std::vector<PCB_SHAPE*> mergedShapes = RecombinePad( pad, false );
  583. for( PCB_SHAPE* shape : mergedShapes )
  584. commit.Remove( shape );
  585. commit.Push( _( "Edit Pad" ) );
  586. }
  587. m_editPad = niluuid;
  588. }
  589. else if( selection.Size() == 1 && selection[0]->Type() == PCB_PAD_T )
  590. {
  591. PCB_LAYER_ID layer;
  592. PAD* pad = static_cast<PAD*>( selection[0] );
  593. BOARD_COMMIT commit( frame() );
  594. commit.Modify( pad );
  595. explodePad( pad, &layer, commit );
  596. commit.Push( _( "Edit Pad" ) );
  597. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  598. frame()->SetActiveLayer( layer );
  599. settings->m_PadEditModePad = pad;
  600. enterPadEditMode();
  601. }
  602. if( m_editPad == niluuid )
  603. ExitPadEditMode();
  604. return 0;
  605. }
  606. int PAD_TOOL::OnUndoRedo( const TOOL_EVENT& aEvent )
  607. {
  608. PAD* flaggedPad = nullptr;
  609. KIID flaggedPadId = niluuid;
  610. for( FOOTPRINT* fp : board()->Footprints() )
  611. {
  612. for( PAD* pad : fp->Pads() )
  613. {
  614. if( pad->IsEntered() )
  615. {
  616. flaggedPad = pad;
  617. flaggedPadId = pad->m_Uuid;
  618. break;
  619. }
  620. }
  621. }
  622. if( flaggedPadId != m_editPad )
  623. {
  624. KIGFX::PCB_PAINTER* painter = static_cast<KIGFX::PCB_PAINTER*>( view()->GetPainter() );
  625. PCB_RENDER_SETTINGS* settings = painter->GetSettings();
  626. m_editPad = flaggedPadId;
  627. settings->m_PadEditModePad = flaggedPad;
  628. if( flaggedPad )
  629. enterPadEditMode();
  630. else
  631. ExitPadEditMode();
  632. }
  633. return 0;
  634. }
  635. void PAD_TOOL::enterPadEditMode()
  636. {
  637. PCB_DISPLAY_OPTIONS opts = frame()->GetDisplayOptions();
  638. WX_INFOBAR* infoBar = frame()->GetInfoBar();
  639. wxString msg;
  640. canvas()->GetView()->UpdateAllItemsConditionally( KIGFX::REPAINT,
  641. [&]( KIGFX::VIEW_ITEM* aItem ) -> bool
  642. {
  643. return dynamic_cast<PAD*>( aItem ) != nullptr;
  644. } );
  645. m_previousHighContrastMode = opts.m_ContrastModeDisplay;
  646. if( opts.m_ContrastModeDisplay == HIGH_CONTRAST_MODE::NORMAL )
  647. {
  648. opts.m_ContrastModeDisplay = HIGH_CONTRAST_MODE::DIMMED;
  649. frame()->SetDisplayOptions( opts );
  650. }
  651. if( PCB_ACTIONS::explodePad.GetHotKey() == PCB_ACTIONS::recombinePad.GetHotKey() )
  652. {
  653. msg.Printf( _( "Pad Edit Mode. Press %s again to exit." ),
  654. KeyNameFromKeyCode( PCB_ACTIONS::recombinePad.GetHotKey() ) );
  655. }
  656. else
  657. {
  658. msg.Printf( _( "Pad Edit Mode. Press %s to exit." ),
  659. KeyNameFromKeyCode( PCB_ACTIONS::recombinePad.GetHotKey() ) );
  660. }
  661. infoBar->RemoveAllButtons();
  662. infoBar->ShowMessage( msg, wxICON_INFORMATION );
  663. }
  664. void PAD_TOOL::ExitPadEditMode()
  665. {
  666. KIGFX::PCB_PAINTER* painter = static_cast<KIGFX::PCB_PAINTER*>( view()->GetPainter() );
  667. PCB_RENDER_SETTINGS* settings = painter->GetSettings();
  668. PCB_DISPLAY_OPTIONS opts = frame()->GetDisplayOptions();
  669. settings->m_PadEditModePad = nullptr;
  670. if( m_previousHighContrastMode != opts.m_ContrastModeDisplay )
  671. {
  672. opts.m_ContrastModeDisplay = m_previousHighContrastMode;
  673. frame()->SetDisplayOptions( opts );
  674. }
  675. // Note: KIGFX::REPAINT isn't enough for things that go from invisible to visible as
  676. // they won't be found in the view layer's itemset for re-painting.
  677. canvas()->GetView()->UpdateAllItemsConditionally( KIGFX::ALL,
  678. [&]( KIGFX::VIEW_ITEM* aItem ) -> bool
  679. {
  680. return dynamic_cast<PAD*>( aItem ) != nullptr;
  681. } );
  682. // Refresh now (otherwise there's an uncomfortably long pause while the infoBar
  683. // closes before refresh).
  684. canvas()->ForceRefresh();
  685. frame()->GetInfoBar()->Dismiss();
  686. }
  687. void PAD_TOOL::explodePad( PAD* aPad, PCB_LAYER_ID* aLayer, BOARD_COMMIT& aCommit )
  688. {
  689. if( aPad->IsOnLayer( F_Cu ) )
  690. *aLayer = F_Cu;
  691. else if( aPad->IsOnLayer( B_Cu ) )
  692. *aLayer = B_Cu;
  693. else
  694. *aLayer = aPad->GetLayerSet().UIOrder().front();
  695. // TODO(JE) padstacks
  696. if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM )
  697. {
  698. for( const std::shared_ptr<PCB_SHAPE>& primitive : aPad->GetPrimitives( PADSTACK::ALL_LAYERS ) )
  699. {
  700. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( primitive->Duplicate() );
  701. shape->SetParent( board()->GetFirstFootprint() );
  702. shape->Rotate( VECTOR2I( 0, 0 ), aPad->GetOrientation() );
  703. shape->Move( aPad->ShapePos( PADSTACK::ALL_LAYERS ) );
  704. shape->SetLayer( *aLayer );
  705. if( shape->IsProxyItem() && shape->GetShape() == SHAPE_T::SEGMENT )
  706. {
  707. if( aPad->GetLocalThermalSpokeWidthOverride().has_value() )
  708. shape->SetWidth( aPad->GetLocalThermalSpokeWidthOverride().value() );
  709. else
  710. shape->SetWidth( pcbIUScale.mmToIU( ZONE_THERMAL_RELIEF_COPPER_WIDTH_MM ) );
  711. }
  712. aCommit.Add( shape );
  713. }
  714. // TODO(JE) padstacks
  715. aPad->SetShape( PADSTACK::ALL_LAYERS, aPad->GetAnchorPadShape( PADSTACK::ALL_LAYERS ) );
  716. aPad->DeletePrimitivesList();
  717. }
  718. aPad->SetFlags( ENTERED );
  719. m_editPad = aPad->m_Uuid;
  720. }
  721. std::vector<PCB_SHAPE*> PAD_TOOL::RecombinePad( PAD* aPad, bool aIsDryRun )
  722. {
  723. int maxError = board()->GetDesignSettings().m_MaxError;
  724. // Don't leave an object in the point editor that might no longer exist after recombining.
  725. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  726. return aPad->Recombine( aIsDryRun, maxError );
  727. }
  728. void PAD_TOOL::setTransitions()
  729. {
  730. Go( &PAD_TOOL::pastePadProperties, PCB_ACTIONS::applyPadSettings.MakeEvent() );
  731. Go( &PAD_TOOL::copyPadSettings, PCB_ACTIONS::copyPadSettings.MakeEvent() );
  732. Go( &PAD_TOOL::pushPadSettings, PCB_ACTIONS::pushPadSettings.MakeEvent() );
  733. Go( &PAD_TOOL::PlacePad, PCB_ACTIONS::placePad.MakeEvent() );
  734. Go( &PAD_TOOL::EnumeratePads, PCB_ACTIONS::enumeratePads.MakeEvent() );
  735. Go( &PAD_TOOL::EditPad, PCB_ACTIONS::explodePad.MakeEvent() );
  736. Go( &PAD_TOOL::EditPad, PCB_ACTIONS::recombinePad.MakeEvent() );
  737. Go( &PAD_TOOL::OnUndoRedo, EVENTS::UndoRedoPostEvent );
  738. }