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.

957 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
5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2017-2023 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. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  72. if( selTool )
  73. {
  74. // Add context menu entries that are displayed when selection tool is active
  75. CONDITIONAL_MENU& menu = selTool->GetToolMenu().GetMenu();
  76. SELECTION_CONDITION padSel = SELECTION_CONDITIONS::HasType( PCB_PAD_T );
  77. SELECTION_CONDITION singlePadSel = SELECTION_CONDITIONS::Count( 1 ) &&
  78. SELECTION_CONDITIONS::OnlyTypes( { PCB_PAD_T } );
  79. auto explodeCondition =
  80. [&]( const SELECTION& aSel )
  81. {
  82. return m_editPad == niluuid && aSel.Size() == 1 && aSel[0]->Type() == PCB_PAD_T;
  83. };
  84. auto recombineCondition =
  85. [&]( const SELECTION& aSel )
  86. {
  87. return m_editPad != niluuid;
  88. };
  89. menu.AddSeparator( 400 );
  90. if( m_isFootprintEditor )
  91. {
  92. menu.AddItem( PCB_ACTIONS::enumeratePads, SELECTION_CONDITIONS::ShowAlways, 400 );
  93. menu.AddItem( PCB_ACTIONS::recombinePad, recombineCondition, 400 );
  94. menu.AddItem( PCB_ACTIONS::explodePad, explodeCondition, 400 );
  95. }
  96. menu.AddItem( PCB_ACTIONS::copyPadSettings, singlePadSel, 400 );
  97. menu.AddItem( PCB_ACTIONS::applyPadSettings, padSel, 400 );
  98. menu.AddItem( PCB_ACTIONS::pushPadSettings, singlePadSel, 400 );
  99. }
  100. auto& ctxMenu = m_menu.GetMenu();
  101. // cancel current tool goes in main context menu at the top if present
  102. ctxMenu.AddItem( ACTIONS::cancelInteractive, SELECTION_CONDITIONS::ShowAlways, 1 );
  103. ctxMenu.AddSeparator( 1 );
  104. ctxMenu.AddItem( PCB_ACTIONS::rotateCcw, SELECTION_CONDITIONS::ShowAlways );
  105. ctxMenu.AddItem( PCB_ACTIONS::rotateCw, SELECTION_CONDITIONS::ShowAlways );
  106. ctxMenu.AddItem( PCB_ACTIONS::flip, SELECTION_CONDITIONS::ShowAlways );
  107. ctxMenu.AddItem( PCB_ACTIONS::mirrorH, SELECTION_CONDITIONS::ShowAlways );
  108. ctxMenu.AddItem( PCB_ACTIONS::mirrorV, SELECTION_CONDITIONS::ShowAlways );
  109. ctxMenu.AddItem( PCB_ACTIONS::properties, SELECTION_CONDITIONS::ShowAlways );
  110. // Finally, add the standard zoom/grid items
  111. getEditFrame<PCB_BASE_FRAME>()->AddStandardSubMenus( m_menu );
  112. return true;
  113. }
  114. int PAD_TOOL::pastePadProperties( const TOOL_EVENT& aEvent )
  115. {
  116. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  117. const PCB_SELECTION& selection = selTool->GetSelection();
  118. const PAD* masterPad = frame()->GetDesignSettings().m_Pad_Master.get();
  119. BOARD_COMMIT commit( frame() );
  120. // for every selected pad, paste global settings
  121. for( EDA_ITEM* item : selection )
  122. {
  123. if( item->Type() == PCB_PAD_T )
  124. {
  125. commit.Modify( item );
  126. static_cast<PAD&>( *item ).ImportSettingsFrom( *masterPad );
  127. }
  128. }
  129. commit.Push( _( "Paste Pad Properties" ) );
  130. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  131. frame()->Refresh();
  132. return 0;
  133. }
  134. int PAD_TOOL::copyPadSettings( const TOOL_EVENT& aEvent )
  135. {
  136. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  137. const PCB_SELECTION& selection = selTool->GetSelection();
  138. // can only copy from a single pad
  139. if( selection.Size() == 1 )
  140. {
  141. EDA_ITEM* item = selection[0];
  142. if( item->Type() == PCB_PAD_T )
  143. {
  144. const PAD& selPad = static_cast<const PAD&>( *item );
  145. frame()->GetDesignSettings().m_Pad_Master->ImportSettingsFrom( selPad );
  146. }
  147. }
  148. return 0;
  149. }
  150. static void doPushPadProperties( BOARD& board, const PAD& aSrcPad, BOARD_COMMIT& commit,
  151. bool aSameFootprints, bool aPadShapeFilter, bool aPadOrientFilter,
  152. bool aPadLayerFilter, bool aPadTypeFilter )
  153. {
  154. const FOOTPRINT* refFootprint = aSrcPad.GetParentFootprint();
  155. EDA_ANGLE srcPadAngle = aSrcPad.GetOrientation() - refFootprint->GetOrientation();
  156. for( FOOTPRINT* footprint : board.Footprints() )
  157. {
  158. if( !aSameFootprints && ( footprint != refFootprint ) )
  159. continue;
  160. if( footprint->GetFPID() != refFootprint->GetFPID() )
  161. continue;
  162. for( PAD* pad : footprint->Pads() )
  163. {
  164. if( aPadShapeFilter && ( pad->GetShape() != aSrcPad.GetShape() ) )
  165. continue;
  166. EDA_ANGLE padAngle = pad->GetOrientation() - footprint->GetOrientation();
  167. if( aPadOrientFilter && ( padAngle != srcPadAngle ) )
  168. continue;
  169. if( aPadLayerFilter && ( pad->GetLayerSet() != aSrcPad.GetLayerSet() ) )
  170. continue;
  171. if( aPadTypeFilter && ( pad->GetAttribute() != aSrcPad.GetAttribute() ) )
  172. continue;
  173. // Special-case for aperture pads
  174. if( aPadTypeFilter && pad->GetAttribute() == PAD_ATTRIB::CONN )
  175. {
  176. if( pad->IsAperturePad() != aSrcPad.IsAperturePad() )
  177. continue;
  178. }
  179. commit.Modify( pad );
  180. // Apply source pad settings to this pad
  181. pad->ImportSettingsFrom( aSrcPad );
  182. }
  183. }
  184. }
  185. int PAD_TOOL::pushPadSettings( const TOOL_EVENT& aEvent )
  186. {
  187. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  188. const PCB_SELECTION& selection = selTool->GetSelection();
  189. if( selection.Size() == 1 && selection[0]->Type() == PCB_PAD_T )
  190. {
  191. PAD* srcPad = static_cast<PAD*>( selection[0] );
  192. if( FOOTPRINT* footprint = srcPad->GetParentFootprint() )
  193. {
  194. frame()->SetMsgPanel( footprint );
  195. DIALOG_PUSH_PAD_PROPERTIES dlg( frame() );
  196. int dialogRet = dlg.ShowModal();
  197. if( dialogRet == wxID_CANCEL )
  198. return 0;
  199. const bool edit_Same_Modules = (dialogRet == 1);
  200. BOARD_COMMIT commit( frame() );
  201. doPushPadProperties( *getModel<BOARD>(), *srcPad, commit, edit_Same_Modules,
  202. DIALOG_PUSH_PAD_PROPERTIES::m_Pad_Shape_Filter,
  203. DIALOG_PUSH_PAD_PROPERTIES::m_Pad_Orient_Filter,
  204. DIALOG_PUSH_PAD_PROPERTIES::m_Pad_Layer_Filter,
  205. DIALOG_PUSH_PAD_PROPERTIES::m_Pad_Type_Filter );
  206. commit.Push( _( "Push Pad Settings" ) );
  207. m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );
  208. frame()->Refresh();
  209. }
  210. }
  211. return 0;
  212. }
  213. /**
  214. * @brief Prompts the user for parameters for sequential pad numbering
  215. *
  216. * @param aFrame The parent window for the dialog
  217. * @return The parameters, or nullopt if no parameters, e.g. user cancelled the dialog
  218. */
  219. static std::optional<SEQUENTIAL_PAD_ENUMERATION_PARAMS>
  220. GetSequentialPadNumberingParams( wxWindow* aFrame )
  221. {
  222. // Persistent settings for the pad enumeration dialog.
  223. static SEQUENTIAL_PAD_ENUMERATION_PARAMS s_lastUsedParams;
  224. DIALOG_ENUM_PADS settingsDlg( aFrame, s_lastUsedParams );
  225. if( settingsDlg.ShowModal() != wxID_OK )
  226. return std::nullopt;
  227. return s_lastUsedParams;
  228. }
  229. int PAD_TOOL::EnumeratePads( const TOOL_EVENT& aEvent )
  230. {
  231. if( !m_isFootprintEditor )
  232. return 0;
  233. if( !board()->GetFirstFootprint() || board()->GetFirstFootprint()->Pads().empty() )
  234. return 0;
  235. GENERAL_COLLECTOR collector;
  236. GENERAL_COLLECTORS_GUIDE guide = frame()->GetCollectorsGuide();
  237. guide.SetIgnoreMTextsMarkedNoShow( true );
  238. guide.SetIgnoreMTextsOnBack( true );
  239. guide.SetIgnoreMTextsOnFront( true );
  240. guide.SetIgnoreModulesVals( true );
  241. guide.SetIgnoreModulesRefs( true );
  242. const std::optional<SEQUENTIAL_PAD_ENUMERATION_PARAMS> params =
  243. GetSequentialPadNumberingParams( frame() );
  244. // Cancelled or otherwise failed to get any useful parameters
  245. if( !params )
  246. return 0;
  247. int seqPadNum = params->m_start_number;
  248. std::deque<int> storedPadNumbers;
  249. std::map<wxString, std::pair<int, wxString>> oldNumbers;
  250. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  251. frame()->PushTool( aEvent );
  252. VECTOR2I oldCursorPos; // store the previous mouse cursor position, during mouse drag
  253. std::list<PAD*> selectedPads;
  254. BOARD_COMMIT commit( frame() );
  255. bool isFirstPoint = true; // make sure oldCursorPos is initialized at least once
  256. PADS pads = board()->GetFirstFootprint()->Pads();
  257. MAGNETIC_SETTINGS mag_settings;
  258. mag_settings.graphics = false;
  259. mag_settings.tracks = MAGNETIC_OPTIONS::NO_EFFECT;
  260. mag_settings.pads = MAGNETIC_OPTIONS::CAPTURE_ALWAYS;
  261. PCB_GRID_HELPER grid( m_toolMgr, &mag_settings );
  262. grid.SetSnap( true );
  263. grid.SetUseGrid( false );
  264. auto setCursor =
  265. [&]()
  266. {
  267. canvas()->SetCurrentCursor( KICURSOR::BULLSEYE );
  268. };
  269. Activate();
  270. // Must be done after Activate() so that it gets set into the correct context
  271. getViewControls()->ShowCursor( true );
  272. getViewControls()->ForceCursorPosition( false );
  273. // Set initial cursor
  274. setCursor();
  275. STATUS_TEXT_POPUP statusPopup( frame() );
  276. // Callable lambda to construct the pad number string for the given value
  277. const auto constructPadNumber = [&]( int aValue )
  278. {
  279. return wxString::Format( wxT( "%s%d" ), params->m_prefix.value_or( "" ), aValue );
  280. };
  281. // Callable lambda to set the popup text for the given pad value
  282. const auto setPopupTextForValue = [&]( int aValue )
  283. {
  284. const wxString msg =
  285. _( "Click on pad %s\nPress <esc> to cancel all; double-click to finish" );
  286. statusPopup.SetText( wxString::Format( msg, constructPadNumber( aValue ) ) );
  287. };
  288. setPopupTextForValue( seqPadNum );
  289. statusPopup.Popup();
  290. statusPopup.Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, 20 ) );
  291. canvas()->SetStatusPopup( statusPopup.GetPanel() );
  292. while( TOOL_EVENT* evt = Wait() )
  293. {
  294. setCursor();
  295. VECTOR2I cursorPos = grid.AlignToNearestPad( getViewControls()->GetMousePosition(), pads );
  296. getViewControls()->ForceCursorPosition( true, cursorPos );
  297. if( evt->IsCancelInteractive() )
  298. {
  299. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  300. commit.Revert();
  301. frame()->PopTool( aEvent );
  302. break;
  303. }
  304. else if( evt->IsActivate() )
  305. {
  306. commit.Push( _( "Renumber pads" ) );
  307. frame()->PopTool( aEvent );
  308. break;
  309. }
  310. else if( evt->IsDrag( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) )
  311. {
  312. selectedPads.clear();
  313. // Be sure the old cursor mouse position was initialized:
  314. if( isFirstPoint )
  315. {
  316. oldCursorPos = cursorPos;
  317. isFirstPoint = false;
  318. }
  319. // wxWidgets deliver mouse move events not frequently enough, resulting in skipping
  320. // pads if the user moves cursor too fast. To solve it, create a line that approximates
  321. // the mouse move and search pads that are on the line.
  322. int distance = ( cursorPos - oldCursorPos ).EuclideanNorm();
  323. // Search will be made every 0.1 mm:
  324. int segments = distance / int( 0.1 * pcbIUScale.IU_PER_MM ) + 1;
  325. const VECTOR2I line_step( ( cursorPos - oldCursorPos ) / segments );
  326. collector.Empty();
  327. for( int j = 0; j < segments; ++j )
  328. {
  329. VECTOR2I testpoint( cursorPos.x - j * line_step.x, cursorPos.y - j * line_step.y );
  330. collector.Collect( board(), { PCB_PAD_T }, testpoint, guide );
  331. for( int i = 0; i < collector.GetCount(); ++i )
  332. selectedPads.push_back( static_cast<PAD*>( collector[i] ) );
  333. }
  334. selectedPads.unique();
  335. for( PAD* pad : selectedPads )
  336. {
  337. // If pad was not selected, then enumerate it
  338. if( !pad->IsSelected() )
  339. {
  340. commit.Modify( pad );
  341. // Rename pad and store the old name
  342. int newval;
  343. if( storedPadNumbers.size() > 0 )
  344. {
  345. newval = storedPadNumbers.front();
  346. storedPadNumbers.pop_front();
  347. }
  348. else
  349. {
  350. newval = seqPadNum;
  351. seqPadNum += params->m_step;
  352. }
  353. const wxString newNumber = constructPadNumber( newval );
  354. oldNumbers[newNumber] = { newval, pad->GetNumber() };
  355. pad->SetNumber( newNumber );
  356. SetLastPadNumber( newNumber );
  357. pad->SetSelected();
  358. getView()->Update( pad );
  359. // Ensure the popup text shows the correct next value
  360. if( storedPadNumbers.size() > 0 )
  361. newval = storedPadNumbers.front();
  362. else
  363. newval = seqPadNum;
  364. setPopupTextForValue( newval );
  365. }
  366. // ... or restore the old name if it was enumerated and clicked again
  367. else if( pad->IsSelected() && evt->IsClick( BUT_LEFT ) )
  368. {
  369. auto it = oldNumbers.find( pad->GetNumber() );
  370. wxASSERT( it != oldNumbers.end() );
  371. if( it != oldNumbers.end() )
  372. {
  373. storedPadNumbers.push_back( it->second.first );
  374. pad->SetNumber( it->second.second );
  375. SetLastPadNumber( it->second.second );
  376. oldNumbers.erase( it );
  377. const int newval = storedPadNumbers.front();
  378. setPopupTextForValue( newval );
  379. }
  380. pad->ClearSelected();
  381. getView()->Update( pad );
  382. }
  383. }
  384. }
  385. else if( evt->IsDblClick( BUT_LEFT ) )
  386. {
  387. commit.Push( _( "Renumber pads" ) );
  388. frame()->PopTool( aEvent );
  389. break;
  390. }
  391. else if( evt->IsClick( BUT_RIGHT ) )
  392. {
  393. m_menu.ShowContextMenu( selection() );
  394. }
  395. else
  396. {
  397. evt->SetPassEvent();
  398. }
  399. // Prepare the next loop by updating the old cursor mouse position
  400. // to this last mouse cursor position
  401. oldCursorPos = getViewControls()->GetCursorPosition();
  402. statusPopup.Move( KIPLATFORM::UI::GetMousePosition() + wxPoint( 20, 20 ) );
  403. }
  404. for( PAD* p : board()->GetFirstFootprint()->Pads() )
  405. {
  406. p->ClearSelected();
  407. getView()->Update( p );
  408. }
  409. canvas()->SetStatusPopup( nullptr );
  410. statusPopup.Hide();
  411. canvas()->SetCurrentCursor( KICURSOR::ARROW );
  412. getViewControls()->ForceCursorPosition( false );
  413. return 0;
  414. }
  415. int PAD_TOOL::PlacePad( const TOOL_EVENT& aEvent )
  416. {
  417. if( !m_isFootprintEditor )
  418. return 0;
  419. if( !board()->GetFirstFootprint() )
  420. return 0;
  421. struct PAD_PLACER : public INTERACTIVE_PLACER_BASE
  422. {
  423. PAD_PLACER( PAD_TOOL* aPadTool )
  424. {
  425. m_padTool = aPadTool;
  426. }
  427. virtual ~PAD_PLACER()
  428. {
  429. }
  430. std::unique_ptr<BOARD_ITEM> CreateItem() override
  431. {
  432. PAD* pad = new PAD( m_board->GetFirstFootprint() );
  433. PAD* master = m_frame->GetDesignSettings().m_Pad_Master.get();
  434. pad->ImportSettingsFrom( *master );
  435. // If the footprint type and master pad type directly conflict then make some
  436. // adjustments. Otherwise assume the user set what they wanted.
  437. if( ( m_board->GetFirstFootprint()->GetAttributes() & FP_SMD )
  438. && master->GetAttribute() == PAD_ATTRIB::PTH )
  439. {
  440. pad->SetAttribute( PAD_ATTRIB::SMD );
  441. pad->SetShape( PAD_SHAPE::ROUNDRECT );
  442. pad->SetSizeX( 1.5 * pad->GetSizeY() );
  443. pad->SetLayerSet( PAD::SMDMask() );
  444. }
  445. else if( ( m_board->GetFirstFootprint()->GetAttributes() & FP_THROUGH_HOLE )
  446. && master->GetAttribute() == PAD_ATTRIB::SMD )
  447. {
  448. pad->SetAttribute( PAD_ATTRIB::PTH );
  449. pad->SetShape( PAD_SHAPE::CIRCLE );
  450. pad->SetSize( VECTOR2I( pad->GetSizeX(), pad->GetSizeX() ) );
  451. pad->SetLayerSet( PAD::PTHMask() );
  452. }
  453. if( pad->CanHaveNumber() )
  454. {
  455. wxString padNumber = m_padTool->GetLastPadNumber();
  456. padNumber = m_board->GetFirstFootprint()->GetNextPadNumber( padNumber );
  457. pad->SetNumber( padNumber );
  458. m_padTool->SetLastPadNumber( padNumber );
  459. }
  460. return std::unique_ptr<BOARD_ITEM>( pad );
  461. }
  462. bool PlaceItem( BOARD_ITEM *aItem, BOARD_COMMIT& aCommit ) override
  463. {
  464. PAD* pad = dynamic_cast<PAD*>( aItem );
  465. if( pad )
  466. {
  467. m_frame->GetDesignSettings().m_Pad_Master->ImportSettingsFrom( *pad );
  468. aCommit.Add( aItem );
  469. return true;
  470. }
  471. return false;
  472. }
  473. PAD_TOOL* m_padTool;
  474. };
  475. PAD_PLACER placer( this );
  476. doInteractiveItemPlacement( aEvent, &placer, _( "Place pad" ),
  477. IPO_REPEAT | IPO_SINGLE_CLICK | IPO_ROTATE | IPO_FLIP );
  478. return 0;
  479. }
  480. int PAD_TOOL::EditPad( const TOOL_EVENT& aEvent )
  481. {
  482. if( !m_isFootprintEditor )
  483. return 0;
  484. Activate();
  485. KIGFX::PCB_PAINTER* painter = static_cast<KIGFX::PCB_PAINTER*>( view()->GetPainter() );
  486. PCB_RENDER_SETTINGS* settings = painter->GetSettings();
  487. PCB_SELECTION& selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
  488. if( m_editPad != niluuid )
  489. {
  490. PAD* pad = dynamic_cast<PAD*>( frame()->GetItem( m_editPad ) );
  491. if( pad )
  492. {
  493. BOARD_COMMIT commit( frame() );
  494. commit.Modify( pad->GetParentFootprint() );
  495. RecombinePad( pad, false );
  496. commit.Push( _( "Edit Pad" ) );
  497. }
  498. m_editPad = niluuid;
  499. }
  500. else if( selection.Size() == 1 && selection[0]->Type() == PCB_PAD_T )
  501. {
  502. PCB_LAYER_ID layer;
  503. PAD* pad = static_cast<PAD*>( selection[0] );
  504. BOARD_COMMIT commit( frame() );
  505. commit.Modify( pad->GetParentFootprint() );
  506. explodePad( pad, &layer );
  507. commit.Push( _( "Edit Pad" ) );
  508. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  509. frame()->SetActiveLayer( layer );
  510. settings->m_PadEditModePad = pad;
  511. enterPadEditMode();
  512. }
  513. if( m_editPad == niluuid )
  514. {
  515. settings->m_PadEditModePad = nullptr;
  516. exitPadEditMode();
  517. }
  518. return 0;
  519. }
  520. int PAD_TOOL::OnUndoRedo( const TOOL_EVENT& aEvent )
  521. {
  522. PAD* flaggedPad = nullptr;
  523. KIID flaggedPadId = niluuid;
  524. for( FOOTPRINT* fp : board()->Footprints() )
  525. {
  526. for( PAD* pad : fp->Pads() )
  527. {
  528. if( pad->IsEntered() )
  529. {
  530. flaggedPad = pad;
  531. flaggedPadId = pad->m_Uuid;
  532. break;
  533. }
  534. }
  535. }
  536. if( flaggedPadId != m_editPad )
  537. {
  538. KIGFX::PCB_PAINTER* painter = static_cast<KIGFX::PCB_PAINTER*>( view()->GetPainter() );
  539. PCB_RENDER_SETTINGS* settings = painter->GetSettings();
  540. m_editPad = flaggedPadId;
  541. settings->m_PadEditModePad = flaggedPad;
  542. if( flaggedPad )
  543. enterPadEditMode();
  544. else
  545. exitPadEditMode();
  546. }
  547. return 0;
  548. }
  549. void PAD_TOOL::enterPadEditMode()
  550. {
  551. PCB_DISPLAY_OPTIONS opts = frame()->GetDisplayOptions();
  552. WX_INFOBAR* infoBar = frame()->GetInfoBar();
  553. wxString msg;
  554. canvas()->GetView()->UpdateAllItemsConditionally( KIGFX::REPAINT,
  555. [&]( KIGFX::VIEW_ITEM* aItem ) -> bool
  556. {
  557. return dynamic_cast<PAD*>( aItem ) != nullptr;
  558. } );
  559. m_previousHighContrastMode = opts.m_ContrastModeDisplay;
  560. if( opts.m_ContrastModeDisplay == HIGH_CONTRAST_MODE::NORMAL )
  561. {
  562. opts.m_ContrastModeDisplay = HIGH_CONTRAST_MODE::DIMMED;
  563. frame()->SetDisplayOptions( opts );
  564. }
  565. if( PCB_ACTIONS::explodePad.GetHotKey() == PCB_ACTIONS::recombinePad.GetHotKey() )
  566. {
  567. msg.Printf( _( "Pad Edit Mode. Press %s again to exit." ),
  568. KeyNameFromKeyCode( PCB_ACTIONS::recombinePad.GetHotKey() ) );
  569. }
  570. else
  571. {
  572. msg.Printf( _( "Pad Edit Mode. Press %s to exit." ),
  573. KeyNameFromKeyCode( PCB_ACTIONS::recombinePad.GetHotKey() ) );
  574. }
  575. infoBar->RemoveAllButtons();
  576. infoBar->ShowMessage( msg, wxICON_INFORMATION );
  577. }
  578. void PAD_TOOL::exitPadEditMode()
  579. {
  580. PCB_DISPLAY_OPTIONS opts = frame()->GetDisplayOptions();
  581. if( m_previousHighContrastMode != opts.m_ContrastModeDisplay )
  582. {
  583. opts.m_ContrastModeDisplay = m_previousHighContrastMode;
  584. frame()->SetDisplayOptions( opts );
  585. }
  586. // Note: KIGFX::REPAINT isn't enough for things that go from invisible to visible as
  587. // they won't be found in the view layer's itemset for re-painting.
  588. canvas()->GetView()->UpdateAllItemsConditionally( KIGFX::ALL,
  589. [&]( KIGFX::VIEW_ITEM* aItem ) -> bool
  590. {
  591. return dynamic_cast<PAD*>( aItem ) != nullptr;
  592. } );
  593. // Refresh now (otherwise there's an uncomfortably long pause while the infoBar
  594. // closes before refresh).
  595. canvas()->ForceRefresh();
  596. frame()->GetInfoBar()->Dismiss();
  597. }
  598. void PAD_TOOL::explodePad( PAD* aPad, PCB_LAYER_ID* aLayer )
  599. {
  600. if( aPad->IsOnLayer( F_Cu ) )
  601. *aLayer = F_Cu;
  602. else if( aPad->IsOnLayer( B_Cu ) )
  603. *aLayer = B_Cu;
  604. else
  605. *aLayer = *aPad->GetLayerSet().UIOrder();
  606. if( aPad->GetShape() == PAD_SHAPE::CUSTOM )
  607. {
  608. for( const std::shared_ptr<PCB_SHAPE>& primitive : aPad->GetPrimitives() )
  609. {
  610. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( primitive->Duplicate() );
  611. shape->SetParent( board()->GetFirstFootprint() );
  612. shape->Rotate( VECTOR2I( 0, 0 ), aPad->GetOrientation() );
  613. shape->Move( aPad->ShapePos() );
  614. shape->SetLayer( *aLayer );
  615. if( shape->IsProxyItem() && shape->GetShape() == SHAPE_T::SEGMENT )
  616. {
  617. if( aPad->GetThermalSpokeWidth() )
  618. shape->SetWidth( aPad->GetThermalSpokeWidth() );
  619. else
  620. shape->SetWidth( pcbIUScale.mmToIU( ZONE_THERMAL_RELIEF_COPPER_WIDTH_MM ) );
  621. }
  622. board()->GetFirstFootprint()->Add( shape );
  623. frame()->GetCanvas()->GetView()->Add( shape );
  624. }
  625. aPad->SetShape( aPad->GetAnchorPadShape() );
  626. aPad->DeletePrimitivesList();
  627. }
  628. aPad->SetFlags( ENTERED );
  629. m_editPad = aPad->m_Uuid;
  630. }
  631. std::vector<PCB_SHAPE*> PAD_TOOL::RecombinePad( PAD* aPad, bool aIsDryRun )
  632. {
  633. int maxError = board()->GetDesignSettings().m_MaxError;
  634. FOOTPRINT* footprint = aPad->GetParentFootprint();
  635. // Don't leave an object in the point editor that might no longer exist after recombining.
  636. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  637. for( BOARD_ITEM* item : footprint->GraphicalItems() )
  638. item->ClearFlags( SKIP_STRUCT );
  639. auto findNext =
  640. [&]( PCB_LAYER_ID aLayer ) -> PCB_SHAPE*
  641. {
  642. SHAPE_POLY_SET padPoly;
  643. aPad->TransformShapeToPolygon( padPoly, aLayer, 0, maxError, ERROR_INSIDE );
  644. for( BOARD_ITEM* item : footprint->GraphicalItems() )
  645. {
  646. PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( item );
  647. if( !shape || ( shape->GetFlags() & SKIP_STRUCT ) )
  648. continue;
  649. if( shape->GetLayer() != aLayer )
  650. continue;
  651. if( shape->IsProxyItem() ) // Pad number (and net name) box
  652. return shape;
  653. SHAPE_POLY_SET drawPoly;
  654. shape->TransformShapeToPolygon( drawPoly, aLayer, 0, maxError, ERROR_INSIDE );
  655. drawPoly.BooleanIntersection( padPoly, SHAPE_POLY_SET::PM_FAST );
  656. if( !drawPoly.IsEmpty() )
  657. return shape;
  658. }
  659. return nullptr;
  660. };
  661. auto findMatching =
  662. [&]( PCB_SHAPE* aShape ) -> std::vector<PCB_SHAPE*>
  663. {
  664. std::vector<PCB_SHAPE*> matching;
  665. for( BOARD_ITEM* item : footprint->GraphicalItems() )
  666. {
  667. PCB_SHAPE* other = dynamic_cast<PCB_SHAPE*>( item );
  668. if( !other || ( other->GetFlags() & SKIP_STRUCT ) )
  669. continue;
  670. if( aPad->GetLayerSet().test( other->GetLayer() )
  671. && aShape->Compare( other ) == 0 )
  672. {
  673. matching.push_back( other );
  674. }
  675. }
  676. return matching;
  677. };
  678. PCB_LAYER_ID layer;
  679. std::vector<PCB_SHAPE*> mergedShapes;
  680. if( aPad->IsOnLayer( F_Cu ) )
  681. layer = F_Cu;
  682. else if( aPad->IsOnLayer( B_Cu ) )
  683. layer = B_Cu;
  684. else
  685. layer = *aPad->GetLayerSet().UIOrder();
  686. // If there are intersecting items to combine, we need to first make sure the pad is a
  687. // custom-shape pad.
  688. if( !aIsDryRun && findNext( layer ) && aPad->GetShape() != PAD_SHAPE::CUSTOM )
  689. {
  690. if( aPad->GetShape() == PAD_SHAPE::CIRCLE || aPad->GetShape() == PAD_SHAPE::RECTANGLE )
  691. {
  692. // Use the existing pad as an anchor
  693. aPad->SetAnchorPadShape( aPad->GetShape() );
  694. aPad->SetShape( PAD_SHAPE::CUSTOM );
  695. }
  696. else
  697. {
  698. // Create a new circular anchor and convert existing pad to a polygon primitive
  699. SHAPE_POLY_SET existingOutline;
  700. aPad->TransformShapeToPolygon( existingOutline, layer, 0, maxError, ERROR_INSIDE );
  701. int minExtent = std::min( aPad->GetSize().x, aPad->GetSize().y );
  702. aPad->SetAnchorPadShape( PAD_SHAPE::CIRCLE );
  703. aPad->SetSize( VECTOR2I( minExtent, minExtent ) );
  704. aPad->SetShape( PAD_SHAPE::CUSTOM );
  705. PCB_SHAPE* shape = new PCB_SHAPE( nullptr, SHAPE_T::POLY );
  706. shape->SetFilled( true );
  707. shape->SetStroke( STROKE_PARAMS( 0, LINE_STYLE::SOLID ) );
  708. shape->SetPolyShape( existingOutline );
  709. shape->Rotate( VECTOR2I( 0, 0 ), - aPad->GetOrientation() );
  710. shape->Move( - aPad->ShapePos() );
  711. aPad->AddPrimitive( shape );
  712. }
  713. }
  714. while( PCB_SHAPE* fpShape = findNext( layer ) )
  715. {
  716. fpShape->SetFlags( SKIP_STRUCT );
  717. mergedShapes.push_back( fpShape );
  718. if( !aIsDryRun )
  719. {
  720. PCB_SHAPE* primitive = static_cast<PCB_SHAPE*>( fpShape->Duplicate() );
  721. primitive->SetParent( nullptr );
  722. primitive->Move( - aPad->ShapePos() );
  723. primitive->Rotate( VECTOR2I( 0, 0 ), - aPad->GetOrientation() );
  724. aPad->AddPrimitive( primitive );
  725. }
  726. // See if there are other shapes that match and mark them for delete. (KiCad won't
  727. // produce these, but old footprints from other vendors have them.)
  728. for( PCB_SHAPE* other : findMatching( fpShape ) )
  729. {
  730. other->SetFlags( SKIP_STRUCT );
  731. mergedShapes.push_back( other );
  732. }
  733. }
  734. for( BOARD_ITEM* item : footprint->GraphicalItems() )
  735. item->ClearFlags( SKIP_STRUCT );
  736. if( !aIsDryRun )
  737. aPad->ClearFlags( ENTERED );
  738. return mergedShapes;
  739. }
  740. void PAD_TOOL::setTransitions()
  741. {
  742. Go( &PAD_TOOL::pastePadProperties, PCB_ACTIONS::applyPadSettings.MakeEvent() );
  743. Go( &PAD_TOOL::copyPadSettings, PCB_ACTIONS::copyPadSettings.MakeEvent() );
  744. Go( &PAD_TOOL::pushPadSettings, PCB_ACTIONS::pushPadSettings.MakeEvent() );
  745. Go( &PAD_TOOL::PlacePad, PCB_ACTIONS::placePad.MakeEvent() );
  746. Go( &PAD_TOOL::EnumeratePads, PCB_ACTIONS::enumeratePads.MakeEvent() );
  747. Go( &PAD_TOOL::EditPad, PCB_ACTIONS::explodePad.MakeEvent() );
  748. Go( &PAD_TOOL::EditPad, PCB_ACTIONS::recombinePad.MakeEvent() );
  749. Go( &PAD_TOOL::OnUndoRedo, EVENTS::UndoRedoPostEvent );
  750. }