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.

2285 lines
76 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
2 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
9 months ago
2 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2014-2016 CERN
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. * @author Maciej Suminski <maciej.suminski@cern.ch>
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, you may find one here:
  20. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  21. * or you may search the http://www.gnu.org website for the version 2 license,
  22. * or you may write to the Free Software Foundation, Inc.,
  23. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  24. */
  25. #include "pcb_control.h"
  26. #include "convert_basic_shapes_to_polygon.h"
  27. #include <kiplatform/ui.h>
  28. #include <tools/edit_tool.h>
  29. #include <tools/board_inspection_tool.h>
  30. #include <router/router_tool.h>
  31. #include <pgm_base.h>
  32. #include <tools/pcb_actions.h>
  33. #include <tools/pcb_picker_tool.h>
  34. #include <tools/pcb_selection_tool.h>
  35. #include <tools/board_reannotate_tool.h>
  36. #include <3d_viewer/eda_3d_viewer_frame.h>
  37. #include <board_commit.h>
  38. #include <board.h>
  39. #include <board_design_settings.h>
  40. #include <board_item.h>
  41. #include <clipboard.h>
  42. #include <design_block.h>
  43. #include <dialogs/dialog_paste_special.h>
  44. #include <pcb_dimension.h>
  45. #include <gal/graphics_abstraction_layer.h>
  46. #include <footprint.h>
  47. #include <layer_pairs.h>
  48. #include <pcb_group.h>
  49. #include <pcb_layer_presentation.h>
  50. #include <pcb_reference_image.h>
  51. #include <pcb_textbox.h>
  52. #include <pcb_track.h>
  53. #include <pcb_generator.h>
  54. #include <wildcards_and_files_ext.h>
  55. #include <zone.h>
  56. #include <confirm.h>
  57. #include <kidialog.h>
  58. #include <connectivity/connectivity_data.h>
  59. #include <core/kicad_algo.h>
  60. #include <dialogs/hotkey_cycle_popup.h>
  61. #include <kicad_clipboard.h>
  62. #include <origin_viewitem.h>
  63. #include <pcb_edit_frame.h>
  64. #include <pcb_painter.h>
  65. #include <settings/color_settings.h>
  66. #include <string>
  67. #include <tool/tool_manager.h>
  68. #include <footprint_edit_frame.h>
  69. #include <footprint_editor_settings.h>
  70. #include <footprint_viewer_frame.h>
  71. #include <widgets/appearance_controls.h>
  72. #include <widgets/pcb_design_block_pane.h>
  73. #include <widgets/wx_progress_reporters.h>
  74. #include <widgets/wx_infobar.h>
  75. #include <wx/hyperlink.h>
  76. using namespace std::placeholders;
  77. // files.cpp
  78. extern bool AskLoadBoardFileName( PCB_EDIT_FRAME* aParent, wxString* aFileName, int aCtl = 0 );
  79. PCB_CONTROL::PCB_CONTROL() :
  80. PCB_TOOL_BASE( "pcbnew.Control" ),
  81. m_frame( nullptr ),
  82. m_pickerItem( nullptr )
  83. {
  84. m_gridOrigin.reset( new KIGFX::ORIGIN_VIEWITEM() );
  85. }
  86. PCB_CONTROL::~PCB_CONTROL()
  87. {
  88. }
  89. void PCB_CONTROL::Reset( RESET_REASON aReason )
  90. {
  91. m_frame = getEditFrame<PCB_BASE_FRAME>();
  92. if( aReason == MODEL_RELOAD || aReason == GAL_SWITCH || aReason == REDRAW )
  93. {
  94. m_gridOrigin->SetPosition( board()->GetDesignSettings().GetGridOrigin() );
  95. m_gridOrigin->SetColor( m_frame->GetGridColor() );
  96. getView()->Remove( m_gridOrigin.get() );
  97. getView()->Add( m_gridOrigin.get() );
  98. }
  99. }
  100. int PCB_CONTROL::AddLibrary( const TOOL_EVENT& aEvent )
  101. {
  102. if( m_frame->IsType( FRAME_FOOTPRINT_EDITOR ) || m_frame->IsType( FRAME_PCB_EDITOR ) )
  103. {
  104. if( aEvent.IsAction( &ACTIONS::newLibrary ) )
  105. static_cast<PCB_BASE_EDIT_FRAME*>( m_frame )->CreateNewLibrary();
  106. else if( aEvent.IsAction( &ACTIONS::addLibrary ) )
  107. static_cast<PCB_BASE_EDIT_FRAME*>( m_frame )->AddLibrary();
  108. }
  109. return 0;
  110. }
  111. int PCB_CONTROL::LoadFpFromBoard( const TOOL_EVENT& aEvent )
  112. {
  113. if( m_frame->IsType( FRAME_FOOTPRINT_EDITOR ) )
  114. static_cast<FOOTPRINT_EDIT_FRAME*>( m_frame )->LoadFootprintFromBoard( nullptr );
  115. return 0;
  116. }
  117. int PCB_CONTROL::SaveFpToBoard( const TOOL_EVENT& aEvent )
  118. {
  119. if( m_frame->IsType( FRAME_FOOTPRINT_EDITOR ) )
  120. static_cast<FOOTPRINT_EDIT_FRAME*>( m_frame )->SaveFootprintToBoard( true );
  121. else if( m_frame->IsType( FRAME_FOOTPRINT_VIEWER ) )
  122. static_cast<FOOTPRINT_VIEWER_FRAME*>( m_frame )->AddFootprintToPCB();
  123. return 0;
  124. }
  125. int PCB_CONTROL::DdAddLibrary( const TOOL_EVENT& aEvent )
  126. {
  127. const wxString fn = *aEvent.Parameter<wxString*>();
  128. static_cast<PCB_BASE_EDIT_FRAME*>( m_frame )->AddLibrary( fn );
  129. return 0;
  130. }
  131. int PCB_CONTROL::DdImportFootprint( const TOOL_EVENT& aEvent )
  132. {
  133. const wxString fn = *aEvent.Parameter<wxString*>();
  134. static_cast<FOOTPRINT_EDIT_FRAME*>( m_frame )->ImportFootprint( fn );
  135. m_frame->Zoom_Automatique( false );
  136. return 0;
  137. }
  138. int PCB_CONTROL::IterateFootprint( const TOOL_EVENT& aEvent )
  139. {
  140. if( m_frame->IsType( FRAME_FOOTPRINT_VIEWER ) )
  141. static_cast<FOOTPRINT_VIEWER_FRAME*>( m_frame )->SelectAndViewFootprint( aEvent.Parameter<FPVIEWER_CONSTANTS>() );
  142. return 0;
  143. }
  144. int PCB_CONTROL::Quit( const TOOL_EVENT& aEvent )
  145. {
  146. m_frame->Close( false );
  147. return 0;
  148. }
  149. template<class T> void Flip( T& aValue )
  150. {
  151. aValue = !aValue;
  152. }
  153. int PCB_CONTROL::TrackDisplayMode( const TOOL_EVENT& aEvent )
  154. {
  155. Flip( displayOptions().m_DisplayPcbTrackFill );
  156. for( PCB_TRACK* track : board()->Tracks() )
  157. {
  158. if( track->Type() == PCB_TRACE_T || track->Type() == PCB_ARC_T )
  159. view()->Update( track, KIGFX::REPAINT );
  160. }
  161. for( BOARD_ITEM* shape : board()->Drawings() )
  162. {
  163. if( shape->Type() == PCB_SHAPE_T && static_cast<PCB_SHAPE*>( shape )->IsOnCopperLayer() )
  164. view()->Update( shape, KIGFX::REPAINT );
  165. }
  166. canvas()->Refresh();
  167. return 0;
  168. }
  169. int PCB_CONTROL::ToggleRatsnest( const TOOL_EVENT& aEvent )
  170. {
  171. if( aEvent.IsAction( &PCB_ACTIONS::showRatsnest ) )
  172. {
  173. // N.B. Do not disable the Ratsnest layer here. We use it for local ratsnest
  174. Flip( displayOptions().m_ShowGlobalRatsnest );
  175. getEditFrame<PCB_EDIT_FRAME>()->SetElementVisibility( LAYER_RATSNEST,
  176. displayOptions().m_ShowGlobalRatsnest );
  177. }
  178. else if( aEvent.IsAction( &PCB_ACTIONS::ratsnestLineMode ) )
  179. {
  180. Flip( displayOptions().m_DisplayRatsnestLinesCurved );
  181. }
  182. frame()->OnDisplayOptionsChanged();
  183. canvas()->RedrawRatsnest();
  184. canvas()->Refresh();
  185. return 0;
  186. }
  187. int PCB_CONTROL::ViaDisplayMode( const TOOL_EVENT& aEvent )
  188. {
  189. Flip( displayOptions().m_DisplayViaFill );
  190. for( PCB_TRACK* track : board()->Tracks() )
  191. {
  192. if( track->Type() == PCB_VIA_T )
  193. view()->Update( track, KIGFX::REPAINT );
  194. }
  195. canvas()->Refresh();
  196. return 0;
  197. }
  198. /**
  199. * We have bug reports indicating that some new users confuse zone filling/unfilling with the
  200. * display modes. This will put up a warning if they show zone fills when one or more zones
  201. * are unfilled.
  202. */
  203. void PCB_CONTROL::unfilledZoneCheck()
  204. {
  205. if( Pgm().GetCommonSettings()->m_DoNotShowAgain.zone_fill_warning )
  206. return;
  207. bool unfilledZones = false;
  208. for( const ZONE* zone : board()->Zones() )
  209. {
  210. if( !zone->GetIsRuleArea() && !zone->IsFilled() )
  211. {
  212. unfilledZones = true;
  213. break;
  214. }
  215. }
  216. if( unfilledZones )
  217. {
  218. WX_INFOBAR* infobar = frame()->GetInfoBar();
  219. wxHyperlinkCtrl* button = new wxHyperlinkCtrl( infobar, wxID_ANY, _( "Don't show again" ),
  220. wxEmptyString );
  221. button->Bind( wxEVT_COMMAND_HYPERLINK, std::function<void( wxHyperlinkEvent& aEvent )>(
  222. [&]( wxHyperlinkEvent& aEvent )
  223. {
  224. Pgm().GetCommonSettings()->m_DoNotShowAgain.zone_fill_warning = true;
  225. frame()->GetInfoBar()->Dismiss();
  226. } ) );
  227. infobar->RemoveAllButtons();
  228. infobar->AddButton( button );
  229. wxString msg;
  230. msg.Printf( _( "Not all zones are filled. Use Edit > Fill All Zones (%s) "
  231. "if you wish to see all fills." ),
  232. KeyNameFromKeyCode( PCB_ACTIONS::zoneFillAll.GetHotKey() ) );
  233. infobar->ShowMessageFor( msg, 5000, wxICON_WARNING );
  234. }
  235. }
  236. int PCB_CONTROL::ZoneDisplayMode( const TOOL_EVENT& aEvent )
  237. {
  238. PCB_DISPLAY_OPTIONS opts = frame()->GetDisplayOptions();
  239. // Apply new display options to the GAL canvas
  240. if( aEvent.IsAction( &PCB_ACTIONS::zoneDisplayFilled ) )
  241. {
  242. unfilledZoneCheck();
  243. opts.m_ZoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_FILLED;
  244. }
  245. else if( aEvent.IsAction( &PCB_ACTIONS::zoneDisplayOutline ) )
  246. {
  247. opts.m_ZoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_ZONE_OUTLINE;
  248. }
  249. else if( aEvent.IsAction( &PCB_ACTIONS::zoneDisplayFractured ) )
  250. {
  251. opts.m_ZoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_FRACTURE_BORDERS;
  252. }
  253. else if( aEvent.IsAction( &PCB_ACTIONS::zoneDisplayTriangulated ) )
  254. {
  255. opts.m_ZoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_TRIANGULATION;
  256. }
  257. else if( aEvent.IsAction( &PCB_ACTIONS::zoneDisplayToggle ) )
  258. {
  259. if( opts.m_ZoneDisplayMode == ZONE_DISPLAY_MODE::SHOW_FILLED )
  260. opts.m_ZoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_ZONE_OUTLINE;
  261. else
  262. opts.m_ZoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_FILLED;
  263. }
  264. else
  265. {
  266. wxFAIL;
  267. }
  268. m_frame->SetDisplayOptions( opts );
  269. for( ZONE* zone : board()->Zones() )
  270. view()->Update( zone, KIGFX::REPAINT );
  271. canvas()->Refresh();
  272. return 0;
  273. }
  274. int PCB_CONTROL::HighContrastMode( const TOOL_EVENT& aEvent )
  275. {
  276. PCB_DISPLAY_OPTIONS opts = frame()->GetDisplayOptions();
  277. opts.m_ContrastModeDisplay = opts.m_ContrastModeDisplay == HIGH_CONTRAST_MODE::NORMAL
  278. ? HIGH_CONTRAST_MODE::DIMMED
  279. : HIGH_CONTRAST_MODE::NORMAL;
  280. m_frame->SetDisplayOptions( opts );
  281. return 0;
  282. }
  283. int PCB_CONTROL::HighContrastModeCycle( const TOOL_EVENT& aEvent )
  284. {
  285. PCB_DISPLAY_OPTIONS opts = frame()->GetDisplayOptions();
  286. switch( opts.m_ContrastModeDisplay )
  287. {
  288. case HIGH_CONTRAST_MODE::NORMAL: opts.m_ContrastModeDisplay = HIGH_CONTRAST_MODE::DIMMED; break;
  289. case HIGH_CONTRAST_MODE::DIMMED: opts.m_ContrastModeDisplay = HIGH_CONTRAST_MODE::HIDDEN; break;
  290. case HIGH_CONTRAST_MODE::HIDDEN: opts.m_ContrastModeDisplay = HIGH_CONTRAST_MODE::NORMAL; break;
  291. }
  292. m_frame->SetDisplayOptions( opts );
  293. m_toolMgr->PostEvent( EVENTS::ContrastModeChangedByKeyEvent );
  294. return 0;
  295. }
  296. int PCB_CONTROL::ContrastModeFeedback( const TOOL_EVENT& aEvent )
  297. {
  298. if( !Pgm().GetCommonSettings()->m_Input.hotkey_feedback )
  299. return 0;
  300. PCB_DISPLAY_OPTIONS opts = frame()->GetDisplayOptions();
  301. wxArrayString labels;
  302. labels.Add( _( "Normal" ) );
  303. labels.Add( _( "Dimmed" ) );
  304. labels.Add( _( "Hidden" ) );
  305. if( !m_frame->GetHotkeyPopup() )
  306. m_frame->CreateHotkeyPopup();
  307. HOTKEY_CYCLE_POPUP* popup = m_frame->GetHotkeyPopup();
  308. if( popup )
  309. {
  310. popup->Popup( _( "Inactive Layer Display" ), labels,
  311. static_cast<int>( opts.m_ContrastModeDisplay ) );
  312. }
  313. return 0;
  314. }
  315. int PCB_CONTROL::NetColorModeCycle( const TOOL_EVENT& aEvent )
  316. {
  317. PCB_DISPLAY_OPTIONS opts = frame()->GetDisplayOptions();
  318. switch( opts.m_NetColorMode )
  319. {
  320. case NET_COLOR_MODE::ALL: opts.m_NetColorMode = NET_COLOR_MODE::RATSNEST; break;
  321. case NET_COLOR_MODE::RATSNEST: opts.m_NetColorMode = NET_COLOR_MODE::OFF; break;
  322. case NET_COLOR_MODE::OFF: opts.m_NetColorMode = NET_COLOR_MODE::ALL; break;
  323. }
  324. m_frame->SetDisplayOptions( opts );
  325. return 0;
  326. }
  327. int PCB_CONTROL::RatsnestModeCycle( const TOOL_EVENT& aEvent )
  328. {
  329. if( !displayOptions().m_ShowGlobalRatsnest )
  330. {
  331. displayOptions().m_ShowGlobalRatsnest = true;
  332. displayOptions().m_RatsnestMode = RATSNEST_MODE::ALL;
  333. }
  334. else if( displayOptions().m_RatsnestMode == RATSNEST_MODE::ALL )
  335. {
  336. displayOptions().m_RatsnestMode = RATSNEST_MODE::VISIBLE;
  337. }
  338. else
  339. {
  340. displayOptions().m_ShowGlobalRatsnest = false;
  341. }
  342. getEditFrame<PCB_EDIT_FRAME>()->SetElementVisibility( LAYER_RATSNEST,
  343. displayOptions().m_ShowGlobalRatsnest );
  344. frame()->OnDisplayOptionsChanged();
  345. canvas()->RedrawRatsnest();
  346. canvas()->Refresh();
  347. return 0;
  348. }
  349. int PCB_CONTROL::LayerSwitch( const TOOL_EVENT& aEvent )
  350. {
  351. m_frame->SwitchLayer( aEvent.Parameter<PCB_LAYER_ID>() );
  352. return 0;
  353. }
  354. int PCB_CONTROL::LayerNext( const TOOL_EVENT& aEvent )
  355. {
  356. PCB_BASE_FRAME* editFrame = m_frame;
  357. BOARD* brd = board();
  358. PCB_LAYER_ID layer = editFrame->GetActiveLayer();
  359. bool wraparound = false;
  360. if( !IsCopperLayer( layer ) )
  361. {
  362. editFrame->SwitchLayer( B_Cu );
  363. return 0;
  364. }
  365. LSET cuMask = LSET::AllCuMask( brd->GetCopperLayerCount() );
  366. LSEQ layerStack = cuMask.UIOrder();
  367. int ii = 0;
  368. // Find the active layer in list
  369. for( ; ii < (int)layerStack.size(); ii++ )
  370. {
  371. if( layer == layerStack[ii] )
  372. break;
  373. }
  374. // Find the next visible layer in list
  375. for( ; ii < (int)layerStack.size(); ii++ )
  376. {
  377. int jj = ii+1;
  378. if( jj >= (int)layerStack.size() )
  379. jj = 0;
  380. layer = layerStack[jj];
  381. if( brd->IsLayerVisible( layer ) )
  382. break;
  383. if( jj == 0 ) // the end of list is reached. Try from the beginning
  384. {
  385. if( wraparound )
  386. {
  387. wxBell();
  388. return 0;
  389. }
  390. else
  391. {
  392. wraparound = true;
  393. ii = -1;
  394. }
  395. }
  396. }
  397. wxCHECK( IsCopperLayer( layer ), 0 );
  398. editFrame->SwitchLayer( layer );
  399. return 0;
  400. }
  401. int PCB_CONTROL::LayerPrev( const TOOL_EVENT& aEvent )
  402. {
  403. PCB_BASE_FRAME* editFrame = m_frame;
  404. BOARD* brd = board();
  405. PCB_LAYER_ID layer = editFrame->GetActiveLayer();
  406. bool wraparound = false;
  407. if( !IsCopperLayer( layer ) )
  408. {
  409. editFrame->SwitchLayer( F_Cu );
  410. return 0;
  411. }
  412. LSET cuMask = LSET::AllCuMask( brd->GetCopperLayerCount() );
  413. LSEQ layerStack = cuMask.UIOrder();
  414. int ii = 0;
  415. // Find the active layer in list
  416. for( ; ii < (int)layerStack.size(); ii++ )
  417. {
  418. if( layer == layerStack[ii] )
  419. break;
  420. }
  421. // Find the previous visible layer in list
  422. for( ; ii >= 0; ii-- )
  423. {
  424. int jj = ii - 1;
  425. if( jj < 0 )
  426. jj = (int)layerStack.size() - 1;
  427. layer = layerStack[jj];
  428. if( brd->IsLayerVisible( layer ) )
  429. break;
  430. if( ii == 0 ) // the start of list is reached. Try from the last
  431. {
  432. if( wraparound )
  433. {
  434. wxBell();
  435. return 0;
  436. }
  437. else
  438. {
  439. wraparound = true;
  440. ii = 1;
  441. }
  442. }
  443. }
  444. wxCHECK( IsCopperLayer( layer ), 0 );
  445. editFrame->SwitchLayer( layer );
  446. return 0;
  447. }
  448. int PCB_CONTROL::LayerToggle( const TOOL_EVENT& aEvent )
  449. {
  450. int currentLayer = m_frame->GetActiveLayer();
  451. PCB_SCREEN* screen = m_frame->GetScreen();
  452. if( currentLayer == screen->m_Route_Layer_TOP )
  453. m_frame->SwitchLayer( screen->m_Route_Layer_BOTTOM );
  454. else
  455. m_frame->SwitchLayer( screen->m_Route_Layer_TOP );
  456. return 0;
  457. }
  458. // It'd be nice to share the min/max with the DIALOG_COLOR_PICKER, but those are
  459. // set in wxFormBuilder.
  460. #define ALPHA_MIN 0.20
  461. #define ALPHA_MAX 1.00
  462. #define ALPHA_STEP 0.05
  463. int PCB_CONTROL::LayerAlphaInc( const TOOL_EVENT& aEvent )
  464. {
  465. COLOR_SETTINGS* settings = m_frame->GetColorSettings();
  466. int currentLayer = m_frame->GetActiveLayer();
  467. KIGFX::COLOR4D currentColor = settings->GetColor( currentLayer );
  468. if( currentColor.a <= ALPHA_MAX - ALPHA_STEP )
  469. {
  470. currentColor.a += ALPHA_STEP;
  471. settings->SetColor( currentLayer, currentColor );
  472. m_frame->GetCanvas()->UpdateColors();
  473. KIGFX::VIEW* view = m_frame->GetCanvas()->GetView();
  474. view->UpdateLayerColor( currentLayer );
  475. view->UpdateLayerColor( GetNetnameLayer( currentLayer ) );
  476. if( IsCopperLayer( currentLayer ) )
  477. view->UpdateLayerColor( ZONE_LAYER_FOR( currentLayer ) );
  478. m_frame->GetCanvas()->ForceRefresh();
  479. }
  480. else
  481. {
  482. wxBell();
  483. }
  484. return 0;
  485. }
  486. int PCB_CONTROL::LayerAlphaDec( const TOOL_EVENT& aEvent )
  487. {
  488. COLOR_SETTINGS* settings = m_frame->GetColorSettings();
  489. int currentLayer = m_frame->GetActiveLayer();
  490. KIGFX::COLOR4D currentColor = settings->GetColor( currentLayer );
  491. if( currentColor.a >= ALPHA_MIN + ALPHA_STEP )
  492. {
  493. currentColor.a -= ALPHA_STEP;
  494. settings->SetColor( currentLayer, currentColor );
  495. m_frame->GetCanvas()->UpdateColors();
  496. KIGFX::VIEW* view = m_frame->GetCanvas()->GetView();
  497. view->UpdateLayerColor( currentLayer );
  498. view->UpdateLayerColor( GetNetnameLayer( currentLayer ) );
  499. if( IsCopperLayer( currentLayer ) )
  500. view->UpdateLayerColor( ZONE_LAYER_FOR( currentLayer ) );
  501. m_frame->GetCanvas()->ForceRefresh();
  502. }
  503. else
  504. {
  505. wxBell();
  506. }
  507. return 0;
  508. }
  509. int PCB_CONTROL::CycleLayerPresets( const TOOL_EVENT& aEvent )
  510. {
  511. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
  512. if( !editFrame )
  513. return 0;
  514. LAYER_PAIR_SETTINGS* settings = editFrame->GetLayerPairSettings();
  515. if( !settings )
  516. return 0;
  517. int currentIndex;
  518. std::vector<LAYER_PAIR_INFO> presets = settings->GetEnabledLayerPairs( currentIndex );
  519. if( presets.size() < 2 )
  520. return 0;
  521. if( currentIndex < 0 )
  522. {
  523. wxASSERT_MSG( false, "Current layer pair not found in layer settings" );
  524. currentIndex = 0;
  525. }
  526. const int nextIndex = ( currentIndex + 1 ) % presets.size();
  527. const LAYER_PAIR& nextPair = presets[nextIndex].GetLayerPair();
  528. settings->SetCurrentLayerPair( nextPair );
  529. m_toolMgr->PostEvent( PCB_EVENTS::LayerPairPresetChangedByKeyEvent() );
  530. return 0;
  531. }
  532. int PCB_CONTROL::LayerPresetFeedback( const TOOL_EVENT& aEvent )
  533. {
  534. if( !Pgm().GetCommonSettings()->m_Input.hotkey_feedback )
  535. return 0;
  536. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
  537. if( !editFrame )
  538. return 0;
  539. LAYER_PAIR_SETTINGS* settings = editFrame->GetLayerPairSettings();
  540. if( !settings )
  541. return 0;
  542. PCB_LAYER_PRESENTATION layerPresentation( editFrame );
  543. int currentIndex;
  544. std::vector<LAYER_PAIR_INFO> presets = settings->GetEnabledLayerPairs( currentIndex );
  545. wxArrayString labels;
  546. for( const LAYER_PAIR_INFO& layerPairInfo : presets )
  547. {
  548. wxString label = layerPresentation.getLayerPairName( layerPairInfo.GetLayerPair() );
  549. if( layerPairInfo.GetName() )
  550. {
  551. label += wxT( " (" ) + *layerPairInfo.GetName() + wxT( ")" );
  552. }
  553. labels.Add( label );
  554. }
  555. if( !editFrame->GetHotkeyPopup() )
  556. editFrame->CreateHotkeyPopup();
  557. HOTKEY_CYCLE_POPUP* popup = editFrame->GetHotkeyPopup();
  558. if( popup )
  559. {
  560. int selection = currentIndex;
  561. popup->Popup( _( "Preset Layer Pairs" ), labels, selection );
  562. }
  563. return 0;
  564. }
  565. void PCB_CONTROL::DoSetGridOrigin( KIGFX::VIEW* aView, PCB_BASE_FRAME* aFrame,
  566. EDA_ITEM* originViewItem, const VECTOR2D& aPoint )
  567. {
  568. aFrame->GetDesignSettings().SetGridOrigin( VECTOR2I( aPoint ) );
  569. aView->GetGAL()->SetGridOrigin( aPoint );
  570. originViewItem->SetPosition( aPoint );
  571. aView->MarkDirty();
  572. aFrame->OnModify();
  573. }
  574. int PCB_CONTROL::GridPlaceOrigin( const TOOL_EVENT& aEvent )
  575. {
  576. VECTOR2D* origin = aEvent.Parameter<VECTOR2D*>();
  577. if( origin )
  578. {
  579. // We can't undo the other grid dialog settings, so no sense undoing just the origin
  580. DoSetGridOrigin( getView(), m_frame, m_gridOrigin.get(), *origin );
  581. delete origin;
  582. }
  583. else
  584. {
  585. if( m_isFootprintEditor && !getEditFrame<PCB_BASE_EDIT_FRAME>()->GetModel() )
  586. return 0;
  587. PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
  588. if( !picker ) // Happens in footprint wizard
  589. return 0;
  590. // Deactivate other tools; particularly important if another PICKER is currently running
  591. Activate();
  592. picker->SetClickHandler(
  593. [this]( const VECTOR2D& pt ) -> bool
  594. {
  595. m_frame->SaveCopyInUndoList( m_gridOrigin.get(), UNDO_REDO::GRIDORIGIN );
  596. DoSetGridOrigin( getView(), m_frame, m_gridOrigin.get(), pt );
  597. return false; // drill origin is a one-shot; don't continue with tool
  598. } );
  599. m_toolMgr->RunAction( ACTIONS::pickerTool, &aEvent );
  600. }
  601. return 0;
  602. }
  603. int PCB_CONTROL::GridResetOrigin( const TOOL_EVENT& aEvent )
  604. {
  605. m_frame->SaveCopyInUndoList( m_gridOrigin.get(), UNDO_REDO::GRIDORIGIN );
  606. DoSetGridOrigin( getView(), m_frame, m_gridOrigin.get(), VECTOR2D( 0, 0 ) );
  607. return 0;
  608. }
  609. #define HITTEST_THRESHOLD_PIXELS 5
  610. int PCB_CONTROL::InteractiveDelete( const TOOL_EVENT& aEvent )
  611. {
  612. if( m_isFootprintEditor && !m_frame->GetBoard()->GetFirstFootprint() )
  613. return 0;
  614. PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
  615. m_pickerItem = nullptr;
  616. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  617. // Deactivate other tools; particularly important if another PICKER is currently running
  618. Activate();
  619. picker->SetCursor( KICURSOR::REMOVE );
  620. picker->SetClickHandler(
  621. [this]( const VECTOR2D& aPosition ) -> bool
  622. {
  623. if( m_pickerItem )
  624. {
  625. if( m_pickerItem && m_pickerItem->IsLocked() )
  626. {
  627. m_statusPopup.reset( new STATUS_TEXT_POPUP( m_frame ) );
  628. m_statusPopup->SetText( _( "Item locked." ) );
  629. m_statusPopup->PopupFor( 2000 );
  630. m_statusPopup->Move( KIPLATFORM::UI::GetMousePosition()
  631. + wxPoint( 20, 20 ) );
  632. return true;
  633. }
  634. PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  635. selectionTool->UnbrightenItem( m_pickerItem );
  636. PCB_SELECTION items;
  637. items.Add( m_pickerItem );
  638. EDIT_TOOL* editTool = m_toolMgr->GetTool<EDIT_TOOL>();
  639. editTool->DeleteItems( items, false );
  640. m_pickerItem = nullptr;
  641. }
  642. return true;
  643. } );
  644. picker->SetMotionHandler(
  645. [this]( const VECTOR2D& aPos )
  646. {
  647. BOARD* board = m_frame->GetBoard();
  648. PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  649. GENERAL_COLLECTORS_GUIDE guide = m_frame->GetCollectorsGuide();
  650. GENERAL_COLLECTOR collector;
  651. collector.m_Threshold = KiROUND( getView()->ToWorld( HITTEST_THRESHOLD_PIXELS ) );
  652. if( m_isFootprintEditor )
  653. collector.Collect( board, GENERAL_COLLECTOR::FootprintItems, aPos, guide );
  654. else
  655. collector.Collect( board, GENERAL_COLLECTOR::BoardLevelItems, aPos, guide );
  656. // Remove unselectable items
  657. for( int i = collector.GetCount() - 1; i >= 0; --i )
  658. {
  659. if( !selectionTool->Selectable( collector[ i ] ) )
  660. collector.Remove( i );
  661. }
  662. selectionTool->FilterCollectorForHierarchy( collector, false );
  663. selectionTool->FilterCollectedItems( collector, false );
  664. if( collector.GetCount() > 1 )
  665. selectionTool->GuessSelectionCandidates( collector, aPos );
  666. BOARD_ITEM* item = collector.GetCount() == 1 ? collector[ 0 ] : nullptr;
  667. if( m_pickerItem != item )
  668. {
  669. if( m_pickerItem )
  670. selectionTool->UnbrightenItem( m_pickerItem );
  671. m_pickerItem = item;
  672. if( m_pickerItem )
  673. selectionTool->BrightenItem( m_pickerItem );
  674. }
  675. } );
  676. picker->SetFinalizeHandler(
  677. [this]( const int& aFinalState )
  678. {
  679. if( m_pickerItem )
  680. m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->UnbrightenItem( m_pickerItem );
  681. m_statusPopup.reset();
  682. // Ensure the cursor gets changed&updated
  683. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
  684. m_frame->GetCanvas()->Refresh();
  685. } );
  686. m_toolMgr->RunAction( ACTIONS::pickerTool, &aEvent );
  687. return 0;
  688. }
  689. static void pasteFootprintItemsToFootprintEditor( FOOTPRINT* aClipFootprint, BOARD* aBoard,
  690. std::vector<BOARD_ITEM*>& aPastedItems )
  691. {
  692. FOOTPRINT* editorFootprint = aBoard->GetFirstFootprint();
  693. aClipFootprint->SetParent( aBoard );
  694. for( PAD* pad : aClipFootprint->Pads() )
  695. {
  696. pad->SetParent( editorFootprint );
  697. aPastedItems.push_back( pad );
  698. }
  699. aClipFootprint->Pads().clear();
  700. // Not all items can be added to the current footprint: mandatory fields are already existing
  701. // in the current footprint.
  702. //
  703. for( PCB_FIELD* field : aClipFootprint->GetFields() )
  704. {
  705. if( field->IsMandatory() )
  706. {
  707. if( EDA_GROUP* parentGroup = field->GetParentGroup() )
  708. parentGroup->RemoveItem( field );
  709. }
  710. else
  711. {
  712. PCB_TEXT* text = static_cast<PCB_TEXT*>( field );
  713. text->SetTextAngle( text->GetTextAngle() - aClipFootprint->GetOrientation() );
  714. text->SetTextAngle( text->GetTextAngle() + editorFootprint->GetOrientation() );
  715. VECTOR2I pos = field->GetFPRelativePosition();
  716. field->SetParent( editorFootprint );
  717. field->SetFPRelativePosition( pos );
  718. aPastedItems.push_back( field );
  719. }
  720. }
  721. aClipFootprint->GetFields().clear();
  722. for( BOARD_ITEM* item : aClipFootprint->GraphicalItems() )
  723. {
  724. if( item->Type() == PCB_TEXT_T )
  725. {
  726. PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
  727. text->SetTextAngle( text->GetTextAngle() - aClipFootprint->GetOrientation() );
  728. text->SetTextAngle( text->GetTextAngle() + editorFootprint->GetOrientation() );
  729. }
  730. item->Rotate( item->GetPosition(), -aClipFootprint->GetOrientation() );
  731. item->Rotate( item->GetPosition(), editorFootprint->GetOrientation() );
  732. VECTOR2I pos = item->GetFPRelativePosition();
  733. item->SetParent( editorFootprint );
  734. item->SetFPRelativePosition( pos );
  735. aPastedItems.push_back( item );
  736. }
  737. aClipFootprint->GraphicalItems().clear();
  738. for( ZONE* zone : aClipFootprint->Zones() )
  739. {
  740. zone->SetParent( editorFootprint );
  741. aPastedItems.push_back( zone );
  742. }
  743. aClipFootprint->Zones().clear();
  744. for( PCB_GROUP* group : aClipFootprint->Groups() )
  745. {
  746. group->SetParent( editorFootprint );
  747. aPastedItems.push_back( group );
  748. }
  749. aClipFootprint->Groups().clear();
  750. }
  751. void PCB_CONTROL::pruneItemLayers( std::vector<BOARD_ITEM*>& aItems )
  752. {
  753. // Do not prune items or layers when copying to the FP editor, because all
  754. // layers are accepted, even if they are not enabled in the dummy board
  755. // This is mainly true for internal copper layers: all are allowed but only one
  756. // (In1.cu) is enabled for the GUI.
  757. if( m_isFootprintEditor || frame()->IsType( FRAME_FOOTPRINT_EDITOR ) )
  758. return;
  759. LSET enabledLayers = board()->GetEnabledLayers();
  760. std::vector<BOARD_ITEM*> returnItems;
  761. bool fpItemDeleted = false;
  762. for( BOARD_ITEM* item : aItems )
  763. {
  764. if( item->Type() == PCB_FOOTPRINT_T )
  765. {
  766. FOOTPRINT* fp = static_cast<FOOTPRINT*>( item );
  767. // Items living in a parent footprint are never removed, even if their
  768. // layer does not exist in the board editor
  769. // Otherwise the parent footprint could be seriously broken especially
  770. // if some layers are later re-enabled.
  771. // Moreover a fp lives in a fp library, that does not know the enabled
  772. // layers of a given board, so fp items are just ignored when on not
  773. // enabled layers in board editor
  774. returnItems.push_back( fp );
  775. }
  776. else if( item->Type() == PCB_GROUP_T || item->Type() == PCB_GENERATOR_T )
  777. {
  778. returnItems.push_back( item );
  779. }
  780. else
  781. {
  782. LSET allowed = item->GetLayerSet() & enabledLayers;
  783. bool item_valid = true;
  784. // Ensure, for vias, the top and bottom layers are compatible with
  785. // the current board copper layers.
  786. // Otherwise they must be skipped, even is one layer is valid
  787. if( item->Type() == PCB_VIA_T )
  788. item_valid = static_cast<PCB_VIA*>( item )->HasValidLayerPair(
  789. board()->GetCopperLayerCount() );
  790. if( allowed.any() && item_valid )
  791. {
  792. item->SetLayerSet( allowed );
  793. returnItems.push_back( item );
  794. }
  795. else
  796. {
  797. if( EDA_GROUP* parentGroup = item->GetParentGroup() )
  798. parentGroup->RemoveItem( item );
  799. }
  800. }
  801. }
  802. if( ( returnItems.size() < aItems.size() ) || fpItemDeleted )
  803. {
  804. DisplayError( m_frame, _( "Warning: some pasted items were on layers which are not "
  805. "present in the current board.\n"
  806. "These items could not be pasted.\n" ) );
  807. }
  808. aItems = returnItems;
  809. }
  810. int PCB_CONTROL::Paste( const TOOL_EVENT& aEvent )
  811. {
  812. // The viewer frames cannot paste
  813. if( !frame()->IsType( FRAME_FOOTPRINT_EDITOR ) && !frame()->IsType( FRAME_PCB_EDITOR ) )
  814. return 0;
  815. bool isFootprintEditor = m_isFootprintEditor || frame()->IsType( FRAME_FOOTPRINT_EDITOR );
  816. // The clipboard can contain two different things, an entire kicad_pcb or a single footprint
  817. if( isFootprintEditor && ( !board() || !footprint() ) )
  818. return 0;
  819. // We should never get here if a modal dialog is up... but we do on MacOS.
  820. // https://gitlab.com/kicad/code/kicad/-/issues/18912
  821. #ifdef __WXMAC__
  822. if( wxDialog::OSXHasModalDialogsOpen() )
  823. {
  824. wxBell();
  825. return 0;
  826. }
  827. #endif
  828. BOARD_COMMIT commit( frame() );
  829. CLIPBOARD_IO pi;
  830. BOARD_ITEM* clipItem = pi.Parse();
  831. if( !clipItem )
  832. {
  833. // When the clipboard doesn't parse, create a PCB item with the clipboard contents
  834. std::vector<BOARD_ITEM*> newItems;
  835. if( std::unique_ptr<wxImage> clipImg = GetImageFromClipboard() )
  836. {
  837. auto refImg = std::make_unique<PCB_REFERENCE_IMAGE>( m_frame->GetModel() );
  838. if( refImg->GetReferenceImage().SetImage( *clipImg ) )
  839. newItems.push_back( refImg.release() );
  840. }
  841. else
  842. {
  843. const wxString clipText = GetClipboardUTF8();
  844. if( clipText.empty() )
  845. return 0;
  846. std::unique_ptr<PCB_TEXT> item;
  847. item = std::make_unique<PCB_TEXT>( m_frame->GetModel() );
  848. item->SetText( clipText );
  849. newItems.push_back( item.release() );
  850. }
  851. bool cancelled = !placeBoardItems( &commit, newItems, true, false, false );
  852. if( cancelled )
  853. commit.Revert();
  854. else
  855. commit.Push( _( "Paste Text" ) );
  856. return 0;
  857. }
  858. // If we get here, we have a parsed board/FP to paste
  859. PASTE_MODE mode = PASTE_MODE::KEEP_ANNOTATIONS;
  860. bool clear_nets = false;
  861. const wxString defaultRef = wxT( "REF**" );
  862. if( aEvent.IsAction( &ACTIONS::pasteSpecial ) )
  863. {
  864. DIALOG_PASTE_SPECIAL dlg( m_frame, &mode, defaultRef );
  865. if( clipItem->Type() != PCB_T )
  866. dlg.HideClearNets();
  867. if( dlg.ShowModal() == wxID_CANCEL )
  868. return 0;
  869. clear_nets = dlg.GetClearNets();
  870. }
  871. if( clipItem->Type() == PCB_T )
  872. {
  873. BOARD* clipBoard = static_cast<BOARD*>( clipItem );
  874. if( isFootprintEditor || clear_nets )
  875. {
  876. for( BOARD_CONNECTED_ITEM* item : clipBoard->AllConnectedItems() )
  877. item->SetNet( NETINFO_LIST::OrphanedItem() );
  878. }
  879. else
  880. {
  881. clipBoard->MapNets( m_frame->GetBoard() );
  882. }
  883. }
  884. bool cancelled = false;
  885. switch( clipItem->Type() )
  886. {
  887. case PCB_T:
  888. {
  889. BOARD* clipBoard = static_cast<BOARD*>( clipItem );
  890. if( isFootprintEditor )
  891. {
  892. FOOTPRINT* editorFootprint = board()->GetFirstFootprint();
  893. std::vector<BOARD_ITEM*> pastedItems;
  894. for( PCB_GROUP* group : clipBoard->Groups() )
  895. {
  896. group->SetParent( editorFootprint );
  897. pastedItems.push_back( group );
  898. }
  899. clipBoard->RemoveAll( { PCB_GROUP_T } );
  900. for( FOOTPRINT* clipFootprint : clipBoard->Footprints() )
  901. pasteFootprintItemsToFootprintEditor( clipFootprint, board(), pastedItems );
  902. for( BOARD_ITEM* clipDrawItem : clipBoard->Drawings() )
  903. {
  904. switch( clipDrawItem->Type() )
  905. {
  906. case PCB_TEXT_T:
  907. case PCB_TEXTBOX_T:
  908. case PCB_TABLE_T:
  909. case PCB_SHAPE_T:
  910. case PCB_DIM_ALIGNED_T:
  911. case PCB_DIM_CENTER_T:
  912. case PCB_DIM_LEADER_T:
  913. case PCB_DIM_ORTHOGONAL_T:
  914. case PCB_DIM_RADIAL_T:
  915. clipDrawItem->SetParent( editorFootprint );
  916. pastedItems.push_back( clipDrawItem );
  917. break;
  918. default:
  919. // Everything we *didn't* put into pastedItems is going to get nuked, so
  920. // make sure it's not still included in its parent group.
  921. if( EDA_GROUP* parentGroup = clipDrawItem->GetParentGroup() )
  922. parentGroup->RemoveItem( clipDrawItem );
  923. break;
  924. }
  925. }
  926. // NB: PCB_SHAPE_T actually removes everything in Drawings() (including PCB_TEXTs,
  927. // PCB_TABLES, dimensions, etc.), not just PCB_SHAPEs.)
  928. clipBoard->RemoveAll( { PCB_SHAPE_T } );
  929. clipBoard->Visit(
  930. [&]( EDA_ITEM* item, void* testData )
  931. {
  932. // Anything still on the clipboard didn't get copied and needs to be
  933. // removed from the pasted groups.
  934. BOARD_ITEM* boardItem = static_cast<BOARD_ITEM*>( item );
  935. EDA_GROUP* parentGroup = boardItem->GetParentGroup();
  936. if( parentGroup )
  937. parentGroup->RemoveItem( boardItem );
  938. return INSPECT_RESULT::CONTINUE;
  939. },
  940. nullptr, GENERAL_COLLECTOR::AllBoardItems );
  941. delete clipBoard;
  942. pruneItemLayers( pastedItems );
  943. cancelled = !placeBoardItems( &commit, pastedItems, true, true,
  944. mode == PASTE_MODE::UNIQUE_ANNOTATIONS );
  945. }
  946. else // isBoardEditor
  947. {
  948. // Fixup footprint component classes
  949. for( FOOTPRINT* clipFootprint : clipBoard->Footprints() )
  950. {
  951. clipFootprint->ResolveComponentClassNames(
  952. board(), clipFootprint->GetTransientComponentClassNames() );
  953. clipFootprint->ClearTransientComponentClassNames();
  954. }
  955. if( mode == PASTE_MODE::REMOVE_ANNOTATIONS )
  956. {
  957. for( FOOTPRINT* clipFootprint : clipBoard->Footprints() )
  958. clipFootprint->SetReference( defaultRef );
  959. }
  960. cancelled = !placeBoardItems( &commit, clipBoard, true,
  961. mode == PASTE_MODE::UNIQUE_ANNOTATIONS );
  962. }
  963. break;
  964. }
  965. case PCB_FOOTPRINT_T:
  966. {
  967. FOOTPRINT* clipFootprint = static_cast<FOOTPRINT*>( clipItem );
  968. std::vector<BOARD_ITEM*> pastedItems;
  969. if( isFootprintEditor )
  970. {
  971. pasteFootprintItemsToFootprintEditor( clipFootprint, board(), pastedItems );
  972. delete clipFootprint;
  973. }
  974. else
  975. {
  976. if( mode == PASTE_MODE::REMOVE_ANNOTATIONS )
  977. clipFootprint->SetReference( defaultRef );
  978. clipFootprint->SetParent( board() );
  979. clipFootprint->ResolveComponentClassNames(
  980. board(), clipFootprint->GetTransientComponentClassNames() );
  981. clipFootprint->ClearTransientComponentClassNames();
  982. pastedItems.push_back( clipFootprint );
  983. }
  984. pruneItemLayers( pastedItems );
  985. cancelled = !placeBoardItems( &commit, pastedItems, true, true,
  986. mode == PASTE_MODE::UNIQUE_ANNOTATIONS );
  987. break;
  988. }
  989. default:
  990. m_frame->DisplayToolMsg( _( "Invalid clipboard contents" ) );
  991. break;
  992. }
  993. if( cancelled )
  994. commit.Revert();
  995. else
  996. commit.Push( _( "Paste" ) );
  997. return 1;
  998. }
  999. int PCB_CONTROL::AppendBoardFromFile( const TOOL_EVENT& aEvent )
  1000. {
  1001. wxString fileName;
  1002. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
  1003. if( !editFrame )
  1004. return 1;
  1005. // Pick a file to append
  1006. if( !AskLoadBoardFileName( editFrame, &fileName, KICTL_KICAD_ONLY ) )
  1007. return 1;
  1008. PCB_IO_MGR::PCB_FILE_T pluginType =
  1009. PCB_IO_MGR::FindPluginTypeFromBoardPath( fileName, KICTL_KICAD_ONLY );
  1010. IO_RELEASER<PCB_IO> pi( PCB_IO_MGR::PluginFind( pluginType ) );
  1011. if( !pi )
  1012. return 1;
  1013. return AppendBoard( *pi, fileName );
  1014. }
  1015. int PCB_CONTROL::AppendDesignBlock( const TOOL_EVENT& aEvent )
  1016. {
  1017. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
  1018. if( !editFrame )
  1019. return 1;
  1020. DESIGN_BLOCK* designBlock = nullptr;
  1021. if( !editFrame->GetDesignBlockPane()->GetSelectedLibId().IsValid() )
  1022. return 1;
  1023. designBlock = editFrame->GetDesignBlockPane()->GetDesignBlock( editFrame->GetDesignBlockPane()->GetSelectedLibId(),
  1024. true, true );
  1025. if( !designBlock || designBlock->GetBoardFile().IsEmpty() )
  1026. return 1;
  1027. PCB_IO_MGR::PCB_FILE_T pluginType = PCB_IO_MGR::KICAD_SEXP;
  1028. IO_RELEASER<PCB_IO> pi( PCB_IO_MGR::PluginFind( pluginType ) );
  1029. if( !pi )
  1030. return 1;
  1031. bool repeatPlacement = false;
  1032. if( APP_SETTINGS_BASE* cfg = editFrame->config() )
  1033. repeatPlacement = cfg->m_DesignBlockChooserPanel.repeated_placement;
  1034. int ret = 0;
  1035. do
  1036. {
  1037. ret = AppendBoard( *pi, designBlock->GetBoardFile() );
  1038. } while( repeatPlacement && ret == 0 );
  1039. return ret;
  1040. }
  1041. template<typename T>
  1042. static void moveUnflaggedItems( const std::deque<T>& aList, std::vector<BOARD_ITEM*>& aTarget,
  1043. bool aIsNew )
  1044. {
  1045. std::copy_if( aList.begin(), aList.end(), std::back_inserter( aTarget ),
  1046. [aIsNew]( T aItem )
  1047. {
  1048. bool doCopy = ( aItem->GetFlags() & SKIP_STRUCT ) == 0;
  1049. aItem->ClearFlags( SKIP_STRUCT );
  1050. aItem->SetFlags( aIsNew ? IS_NEW : 0 );
  1051. return doCopy;
  1052. } );
  1053. }
  1054. template<typename T>
  1055. static void moveUnflaggedItems( const std::vector<T>& aList, std::vector<BOARD_ITEM*>& aTarget,
  1056. bool aIsNew )
  1057. {
  1058. std::copy_if( aList.begin(), aList.end(), std::back_inserter( aTarget ),
  1059. [aIsNew]( T aItem )
  1060. {
  1061. bool doCopy = ( aItem->GetFlags() & SKIP_STRUCT ) == 0;
  1062. aItem->ClearFlags( SKIP_STRUCT );
  1063. aItem->SetFlags( aIsNew ? IS_NEW : 0 );
  1064. return doCopy;
  1065. } );
  1066. }
  1067. bool PCB_CONTROL::placeBoardItems( BOARD_COMMIT* aCommit, BOARD* aBoard, bool aAnchorAtOrigin,
  1068. bool aReannotateDuplicates )
  1069. {
  1070. // items are new if the current board is not the board source
  1071. bool isNew = board() != aBoard;
  1072. std::vector<BOARD_ITEM*> items;
  1073. moveUnflaggedItems( aBoard->Tracks(), items, isNew );
  1074. moveUnflaggedItems( aBoard->Footprints(), items, isNew );
  1075. moveUnflaggedItems( aBoard->Drawings(), items, isNew );
  1076. moveUnflaggedItems( aBoard->Zones(), items, isNew );
  1077. // Subtlety: When selecting a group via the mouse,
  1078. // PCB_SELECTION_TOOL::highlightInternal runs, which does a SetSelected() on all
  1079. // descendants. In PCB_CONTROL::placeBoardItems, below, we skip that and
  1080. // mark items non-recursively. That works because the saving of the
  1081. // selection created aBoard that has the group and all descendants in it.
  1082. moveUnflaggedItems( aBoard->Groups(), items, isNew );
  1083. moveUnflaggedItems( aBoard->Generators(), items, isNew );
  1084. if( isNew )
  1085. aBoard->RemoveAll();
  1086. // Reparent before calling pruneItemLayers, as SetLayer can have a dependence on the
  1087. // item's parent board being set correctly.
  1088. if( isNew )
  1089. {
  1090. for( BOARD_ITEM* item : items )
  1091. item->SetParent( board() );
  1092. }
  1093. pruneItemLayers( items );
  1094. return placeBoardItems( aCommit, items, isNew, aAnchorAtOrigin, aReannotateDuplicates );
  1095. }
  1096. bool PCB_CONTROL::placeBoardItems( BOARD_COMMIT* aCommit, std::vector<BOARD_ITEM*>& aItems,
  1097. bool aIsNew, bool aAnchorAtOrigin, bool aReannotateDuplicates )
  1098. {
  1099. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear );
  1100. PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  1101. std::vector<BOARD_ITEM*> itemsToSel;
  1102. itemsToSel.reserve( aItems.size() );
  1103. for( BOARD_ITEM* item : aItems )
  1104. {
  1105. if( aIsNew )
  1106. {
  1107. const_cast<KIID&>( item->m_Uuid ) = KIID();
  1108. item->RunOnChildren(
  1109. []( BOARD_ITEM* aChild )
  1110. {
  1111. const_cast<KIID&>( aChild->m_Uuid ) = KIID();
  1112. },
  1113. RECURSE_MODE::RECURSE );
  1114. // Even though BOARD_COMMIT::Push() will add any new items to the group, we're
  1115. // going to run PCB_ACTIONS::move first, and the move tool will throw out any
  1116. // items that aren't in the entered group.
  1117. if( selectionTool->GetEnteredGroup() && !item->GetParentGroup() )
  1118. selectionTool->GetEnteredGroup()->AddItem( item );
  1119. item->SetParent( board() );
  1120. }
  1121. // Update item attributes if needed
  1122. if( BaseType( item->Type() ) == PCB_DIMENSION_T )
  1123. {
  1124. static_cast<PCB_DIMENSION_BASE*>( item )->UpdateUnits();
  1125. }
  1126. else if( item->Type() == PCB_FOOTPRINT_T )
  1127. {
  1128. FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item );
  1129. // Update the footprint path with the new KIID path if the footprint is new
  1130. if( aIsNew )
  1131. footprint->SetPath( KIID_PATH() );
  1132. for( BOARD_ITEM* dwg : footprint->GraphicalItems() )
  1133. {
  1134. if( BaseType( dwg->Type() ) == PCB_DIMENSION_T )
  1135. static_cast<PCB_DIMENSION_BASE*>( dwg )->UpdateUnits();
  1136. }
  1137. }
  1138. // We only need to add the items that aren't inside a group currently selected
  1139. // to the selection. If an item is inside a group and that group is selected,
  1140. // then the selection tool will select it for us.
  1141. if( !item->GetParentGroup() || !alg::contains( aItems, item->GetParentGroup()->AsEdaItem() ) )
  1142. itemsToSel.push_back( item );
  1143. }
  1144. // Select the items that should be selected
  1145. EDA_ITEMS toSel( itemsToSel.begin(), itemsToSel.end() );
  1146. m_toolMgr->RunAction<EDA_ITEMS*>( PCB_ACTIONS::selectItems, &toSel );
  1147. // Reannotate duplicate footprints (make sense only in board editor )
  1148. if( aReannotateDuplicates && m_isBoardEditor )
  1149. m_toolMgr->GetTool<BOARD_REANNOTATE_TOOL>()->ReannotateDuplicatesInSelection();
  1150. for( BOARD_ITEM* item : aItems )
  1151. {
  1152. if( aIsNew )
  1153. aCommit->Add( item );
  1154. else
  1155. aCommit->Added( item );
  1156. }
  1157. PCB_SELECTION& selection = selectionTool->GetSelection();
  1158. if( selection.Size() > 0 )
  1159. {
  1160. if( aAnchorAtOrigin )
  1161. {
  1162. selection.SetReferencePoint( VECTOR2I( 0, 0 ) );
  1163. }
  1164. else
  1165. {
  1166. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.GetTopLeftItem() );
  1167. selection.SetReferencePoint( item->GetPosition() );
  1168. }
  1169. getViewControls()->SetCursorPosition( getViewControls()->GetMousePosition(), false );
  1170. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  1171. return m_toolMgr->RunSynchronousAction( PCB_ACTIONS::move, aCommit );
  1172. }
  1173. return true;
  1174. }
  1175. int PCB_CONTROL::AppendBoard( PCB_IO& pi, const wxString& fileName )
  1176. {
  1177. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
  1178. if( !editFrame )
  1179. return 1;
  1180. BOARD* brd = board();
  1181. if( !brd )
  1182. return 1;
  1183. BOARD_COMMIT commit( editFrame );
  1184. // Mark existing items, in order to know what are the new items so we can select only
  1185. // the new items after loading
  1186. for( PCB_TRACK* track : brd->Tracks() )
  1187. track->SetFlags( SKIP_STRUCT );
  1188. for( FOOTPRINT* footprint : brd->Footprints() )
  1189. footprint->SetFlags( SKIP_STRUCT );
  1190. for( PCB_GROUP* group : brd->Groups() )
  1191. group->SetFlags( SKIP_STRUCT );
  1192. for( BOARD_ITEM* drawing : brd->Drawings() )
  1193. drawing->SetFlags( SKIP_STRUCT );
  1194. for( ZONE* zone : brd->Zones() )
  1195. zone->SetFlags( SKIP_STRUCT );
  1196. for( PCB_GENERATOR* generator : brd->Generators() )
  1197. generator->SetFlags( SKIP_STRUCT );
  1198. std::map<wxString, wxString> oldProperties = brd->GetProperties();
  1199. std::map<wxString, wxString> newProperties;
  1200. PAGE_INFO oldPageInfo = brd->GetPageSettings();
  1201. TITLE_BLOCK oldTitleBlock = brd->GetTitleBlock();
  1202. // Keep also the count of copper layers, to adjust if necessary
  1203. int initialCopperLayerCount = brd->GetCopperLayerCount();
  1204. LSET initialEnabledLayers = brd->GetEnabledLayers();
  1205. // Load the data
  1206. try
  1207. {
  1208. std::map<std::string, UTF8> props;
  1209. // PCB_IO_EAGLE can use this info to center the BOARD, but it does not yet.
  1210. props["page_width"] = std::to_string( editFrame->GetPageSizeIU().x );
  1211. props["page_height"] = std::to_string( editFrame->GetPageSizeIU().y );
  1212. pi.SetQueryUserCallback(
  1213. [&]( wxString aTitle, int aIcon, wxString aMessage, wxString aAction ) -> bool
  1214. {
  1215. KIDIALOG dlg( editFrame, aMessage, aTitle, wxOK | wxCANCEL | aIcon );
  1216. if( !aAction.IsEmpty() )
  1217. dlg.SetOKLabel( aAction );
  1218. dlg.DoNotShowCheckbox( aMessage, 0 );
  1219. return dlg.ShowModal() == wxID_OK;
  1220. } );
  1221. WX_PROGRESS_REPORTER progressReporter( editFrame, _( "Loading PCB" ), 1 );
  1222. editFrame->GetDesignSettings().m_NetSettings->ClearNetclasses();
  1223. pi.SetProgressReporter( &progressReporter );
  1224. pi.LoadBoard( fileName, brd, &props, nullptr );
  1225. }
  1226. catch( const IO_ERROR& ioe )
  1227. {
  1228. DisplayErrorMessage( editFrame, _( "Error loading board." ), ioe.What() );
  1229. return 0;
  1230. }
  1231. newProperties = brd->GetProperties();
  1232. for( const std::pair<const wxString, wxString>& prop : oldProperties )
  1233. newProperties[ prop.first ] = prop.second;
  1234. brd->SetProperties( newProperties );
  1235. brd->SetPageSettings( oldPageInfo );
  1236. brd->SetTitleBlock( oldTitleBlock );
  1237. // rebuild nets and ratsnest before any use of nets
  1238. brd->BuildListOfNets();
  1239. brd->SynchronizeNetsAndNetClasses( true );
  1240. brd->BuildConnectivity();
  1241. // Synchronize layers
  1242. // we should not ask PLUGINs to do these items:
  1243. int copperLayerCount = brd->GetCopperLayerCount();
  1244. if( copperLayerCount > initialCopperLayerCount )
  1245. brd->SetCopperLayerCount( copperLayerCount );
  1246. // Enable all used layers, and make them visible:
  1247. LSET enabledLayers = brd->GetEnabledLayers();
  1248. enabledLayers |= initialEnabledLayers;
  1249. brd->SetEnabledLayers( enabledLayers );
  1250. brd->SetVisibleLayers( enabledLayers );
  1251. int ret = 0;
  1252. bool placeAsGroup = editFrame->config() ? editFrame->config()->m_DesignBlockChooserPanel.place_as_sheet : false;
  1253. if( placeBoardItems( &commit, brd, false, false /* Don't reannotate dupes on Append Board */ ) )
  1254. {
  1255. commit.Push( _( "Append Board" ) );
  1256. if( placeAsGroup )
  1257. {
  1258. BOARD_COMMIT grpCommit( m_toolMgr );
  1259. PCB_GROUP* group = new PCB_GROUP( brd );
  1260. // Get the selection tool selection
  1261. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  1262. PCB_SELECTION selection = selTool->GetSelection();
  1263. for( EDA_ITEM* eda_item : selection )
  1264. {
  1265. if( eda_item->IsBOARD_ITEM() )
  1266. {
  1267. if( static_cast<BOARD_ITEM*>( eda_item )->IsLocked() )
  1268. group->SetLocked( true );
  1269. }
  1270. }
  1271. grpCommit.Add( group );
  1272. for( EDA_ITEM* eda_item : selection )
  1273. {
  1274. if( eda_item->IsBOARD_ITEM() && !static_cast<BOARD_ITEM*>( eda_item )->GetParentFootprint() )
  1275. {
  1276. grpCommit.Stage( static_cast<BOARD_ITEM*>( eda_item ), CHT_GROUP );
  1277. }
  1278. }
  1279. grpCommit.Push( _( "Group Items" ), APPEND_UNDO );
  1280. selTool->ClearSelection();
  1281. selTool->select( group );
  1282. m_toolMgr->PostEvent( EVENTS::SelectedItemsModified );
  1283. m_frame->OnModify();
  1284. m_frame->Refresh();
  1285. }
  1286. ret = 0;
  1287. }
  1288. else
  1289. {
  1290. commit.Revert();
  1291. ret = 1;
  1292. }
  1293. // Refresh the UI for the updated board properties
  1294. editFrame->GetAppearancePanel()->OnBoardChanged();
  1295. return ret;
  1296. }
  1297. int PCB_CONTROL::Undo( const TOOL_EVENT& aEvent )
  1298. {
  1299. PCB_BASE_EDIT_FRAME* editFrame = dynamic_cast<PCB_BASE_EDIT_FRAME*>( m_frame );
  1300. wxCommandEvent dummy;
  1301. if( editFrame )
  1302. editFrame->RestoreCopyFromUndoList( dummy );
  1303. return 0;
  1304. }
  1305. int PCB_CONTROL::Redo( const TOOL_EVENT& aEvent )
  1306. {
  1307. PCB_BASE_EDIT_FRAME* editFrame = dynamic_cast<PCB_BASE_EDIT_FRAME*>( m_frame );
  1308. wxCommandEvent dummy;
  1309. if( editFrame )
  1310. editFrame->RestoreCopyFromRedoList( dummy );
  1311. return 0;
  1312. }
  1313. int PCB_CONTROL::SnapMode( const TOOL_EVENT& aEvent )
  1314. {
  1315. MAGNETIC_SETTINGS& settings = m_isFootprintEditor
  1316. ? m_frame->GetFootprintEditorSettings()->m_MagneticItems
  1317. : m_frame->GetPcbNewSettings()->m_MagneticItems;
  1318. bool& snapMode = settings.allLayers;
  1319. if( aEvent.IsAction( &PCB_ACTIONS::magneticSnapActiveLayer ) )
  1320. snapMode = false;
  1321. else if( aEvent.IsAction( &PCB_ACTIONS::magneticSnapAllLayers ) )
  1322. snapMode = true;
  1323. else
  1324. snapMode = !snapMode;
  1325. m_toolMgr->PostEvent( PCB_EVENTS::SnappingModeChangedByKeyEvent() );
  1326. return 0;
  1327. }
  1328. int PCB_CONTROL::SnapModeFeedback( const TOOL_EVENT& aEvent )
  1329. {
  1330. if( !Pgm().GetCommonSettings()->m_Input.hotkey_feedback )
  1331. return 0;
  1332. wxArrayString labels;
  1333. labels.Add( _( "Active Layer" ) );
  1334. labels.Add( _( "All Layers" ) );
  1335. if( !m_frame->GetHotkeyPopup() )
  1336. m_frame->CreateHotkeyPopup();
  1337. HOTKEY_CYCLE_POPUP* popup = m_frame->GetHotkeyPopup();
  1338. MAGNETIC_SETTINGS& settings = m_isFootprintEditor
  1339. ? m_frame->GetFootprintEditorSettings()->m_MagneticItems
  1340. : m_frame->GetPcbNewSettings()->m_MagneticItems;
  1341. if( popup )
  1342. popup->Popup( _( "Object Snapping" ), labels, static_cast<int>( settings.allLayers ) );
  1343. return 0;
  1344. }
  1345. int PCB_CONTROL::UpdateMessagePanel( const TOOL_EVENT& aEvent )
  1346. {
  1347. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  1348. ROUTER_TOOL* routerTool = m_toolMgr->GetTool<ROUTER_TOOL>();
  1349. PCB_SELECTION& selection = selTool->GetSelection();
  1350. PCB_EDIT_FRAME* pcbFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
  1351. std::shared_ptr<DRC_ENGINE> drcEngine = m_frame->GetBoard()->GetDesignSettings().m_DRCEngine;
  1352. DRC_CONSTRAINT constraint;
  1353. std::vector<MSG_PANEL_ITEM> msgItems;
  1354. if( routerTool && routerTool->RoutingInProgress() )
  1355. {
  1356. routerTool->UpdateMessagePanel();
  1357. return 0;
  1358. }
  1359. if( !pcbFrame && !m_frame->GetModel() )
  1360. return 0;
  1361. if( selection.Empty() )
  1362. {
  1363. if( !pcbFrame )
  1364. {
  1365. FOOTPRINT* fp = static_cast<FOOTPRINT*>( m_frame->GetModel() );
  1366. fp->GetMsgPanelInfo( m_frame, msgItems );
  1367. }
  1368. else
  1369. {
  1370. m_frame->SetMsgPanel( m_frame->GetBoard() );
  1371. }
  1372. }
  1373. else if( selection.GetSize() == 1 )
  1374. {
  1375. EDA_ITEM* item = selection.Front();
  1376. if( std::optional<wxString> uuid = GetMsgPanelDisplayUuid( item->m_Uuid ) )
  1377. msgItems.emplace_back( _( "UUID" ), *uuid );
  1378. item->GetMsgPanelInfo( m_frame, msgItems );
  1379. PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item );
  1380. NETINFO_ITEM* net = track ? track->GetNet() : nullptr;
  1381. NETINFO_ITEM* coupledNet = net ? m_frame->GetBoard()->DpCoupledNet( net ) : nullptr;
  1382. if( coupledNet )
  1383. {
  1384. SEG trackSeg( track->GetStart(), track->GetEnd() );
  1385. PCB_TRACK* coupledItem = nullptr;
  1386. SEG::ecoord closestDist_sq = VECTOR2I::ECOORD_MAX;
  1387. for( PCB_TRACK* candidate : m_frame->GetBoard()->Tracks() )
  1388. {
  1389. if( candidate->GetNet() != coupledNet )
  1390. continue;
  1391. SEG::ecoord dist_sq = trackSeg.SquaredDistance( SEG( candidate->GetStart(),
  1392. candidate->GetEnd() ) );
  1393. if( !coupledItem || dist_sq < closestDist_sq )
  1394. {
  1395. coupledItem = candidate;
  1396. closestDist_sq = dist_sq;
  1397. }
  1398. }
  1399. constraint = drcEngine->EvalRules( DIFF_PAIR_GAP_CONSTRAINT, track, coupledItem,
  1400. track->GetLayer() );
  1401. wxString msg = m_frame->MessageTextFromMinOptMax( constraint.Value() );
  1402. if( !msg.IsEmpty() )
  1403. {
  1404. msgItems.emplace_back( wxString::Format( _( "DP Gap Constraints: %s" ), msg ),
  1405. wxString::Format( _( "(from %s)" ), constraint.GetName() ) );
  1406. }
  1407. constraint = drcEngine->EvalRules( MAX_UNCOUPLED_CONSTRAINT, track,
  1408. coupledItem, track->GetLayer() );
  1409. if( constraint.Value().HasMax() )
  1410. {
  1411. msg = m_frame->MessageTextFromValue( constraint.Value().Max() );
  1412. msgItems.emplace_back( wxString::Format( _( "DP Max Uncoupled-length: %s" ), msg ),
  1413. wxString::Format( _( "(from %s)" ), constraint.GetName() ) );
  1414. }
  1415. }
  1416. }
  1417. else if( pcbFrame && selection.GetSize() == 2 )
  1418. {
  1419. // Pair selection broken into multiple, optional data, starting with the selected item
  1420. // names
  1421. BOARD_ITEM* a = static_cast<BOARD_ITEM*>( selection[0] );
  1422. BOARD_ITEM* b = static_cast<BOARD_ITEM*>( selection[1] );
  1423. msgItems.emplace_back( MSG_PANEL_ITEM( a->GetItemDescription( m_frame, false ),
  1424. b->GetItemDescription( m_frame, false ) ) );
  1425. BOARD_CONNECTED_ITEM* a_conn = dyn_cast<BOARD_CONNECTED_ITEM*>( a );
  1426. BOARD_CONNECTED_ITEM* b_conn = dyn_cast<BOARD_CONNECTED_ITEM*>( b );
  1427. if( a_conn && b_conn )
  1428. {
  1429. LSET overlap = a_conn->GetLayerSet() & b_conn->GetLayerSet() & LSET::AllCuMask();
  1430. int a_netcode = a_conn->GetNetCode();
  1431. int b_netcode = b_conn->GetNetCode();
  1432. if( overlap.count() > 0
  1433. && ( a_netcode != b_netcode || a_netcode < 0 || b_netcode < 0 ) )
  1434. {
  1435. PCB_LAYER_ID layer = overlap.CuStack().front();
  1436. constraint = drcEngine->EvalRules( CLEARANCE_CONSTRAINT, a, b, layer );
  1437. msgItems.emplace_back( _( "Resolved Clearance" ),
  1438. m_frame->MessageTextFromValue( constraint.m_Value.Min() ) );
  1439. std::shared_ptr<SHAPE> a_shape( a_conn->GetEffectiveShape( layer ) );
  1440. std::shared_ptr<SHAPE> b_shape( b_conn->GetEffectiveShape( layer ) );
  1441. int actual_clearance = a_shape->GetClearance( b_shape.get() );
  1442. if( actual_clearance > -1 && actual_clearance < std::numeric_limits<int>::max() )
  1443. {
  1444. msgItems.emplace_back( _( "Actual Clearance" ),
  1445. m_frame->MessageTextFromValue( actual_clearance ) );
  1446. }
  1447. }
  1448. }
  1449. if( ( a->HasHole() || b->HasHole() ) )
  1450. {
  1451. PCB_LAYER_ID active = m_frame->GetActiveLayer();
  1452. PCB_LAYER_ID layer = UNDEFINED_LAYER;
  1453. if( b->IsOnLayer( active ) && IsCopperLayer( active ) )
  1454. layer = active;
  1455. else if( b->HasHole() && a->IsOnLayer( active ) && IsCopperLayer( active ) )
  1456. layer = active;
  1457. else if( a->HasHole() && b->IsOnCopperLayer() )
  1458. layer = b->GetLayer();
  1459. else if( b->HasHole() && a->IsOnCopperLayer() )
  1460. layer = a->GetLayer();
  1461. if( IsCopperLayer( layer ) )
  1462. {
  1463. int actual = std::numeric_limits<int>::max();
  1464. if( a->HasHole() && b->IsOnCopperLayer() )
  1465. {
  1466. std::shared_ptr<SHAPE_SEGMENT> hole = a->GetEffectiveHoleShape();
  1467. std::shared_ptr<SHAPE> other( b->GetEffectiveShape( layer ) );
  1468. actual = std::min( actual, hole->GetClearance( other.get() ) );
  1469. }
  1470. if( b->HasHole() && a->IsOnCopperLayer() )
  1471. {
  1472. std::shared_ptr<SHAPE_SEGMENT> hole = b->GetEffectiveHoleShape();
  1473. std::shared_ptr<SHAPE> other( a->GetEffectiveShape( layer ) );
  1474. actual = std::min( actual, hole->GetClearance( other.get() ) );
  1475. }
  1476. if( actual < std::numeric_limits<int>::max() )
  1477. {
  1478. constraint = drcEngine->EvalRules( HOLE_CLEARANCE_CONSTRAINT, a, b, layer );
  1479. msgItems.emplace_back( _( "Resolved Hole Clearance" ),
  1480. m_frame->MessageTextFromValue( constraint.m_Value.Min() ) );
  1481. if( actual > -1 && actual < std::numeric_limits<int>::max() )
  1482. {
  1483. msgItems.emplace_back( _( "Actual Hole Clearance" ),
  1484. m_frame->MessageTextFromValue( actual ) );
  1485. }
  1486. }
  1487. }
  1488. }
  1489. for( PCB_LAYER_ID edgeLayer : { Edge_Cuts, Margin } )
  1490. {
  1491. PCB_LAYER_ID active = m_frame->GetActiveLayer();
  1492. PCB_LAYER_ID layer = UNDEFINED_LAYER;
  1493. if( a->IsOnLayer( edgeLayer ) && b->Type() != PCB_FOOTPRINT_T )
  1494. {
  1495. if( b->IsOnLayer( active ) && IsCopperLayer( active ) )
  1496. layer = active;
  1497. else if( IsCopperLayer( b->GetLayer() ) )
  1498. layer = b->GetLayer();
  1499. }
  1500. else if( b->IsOnLayer( edgeLayer ) && a->Type() != PCB_FOOTPRINT_T )
  1501. {
  1502. if( a->IsOnLayer( active ) && IsCopperLayer( active ) )
  1503. layer = active;
  1504. else if( IsCopperLayer( a->GetLayer() ) )
  1505. layer = a->GetLayer();
  1506. }
  1507. if( layer >= 0 )
  1508. {
  1509. constraint = drcEngine->EvalRules( EDGE_CLEARANCE_CONSTRAINT, a, b, layer );
  1510. if( edgeLayer == Edge_Cuts )
  1511. {
  1512. msgItems.emplace_back( _( "Resolved Edge Clearance" ),
  1513. m_frame->MessageTextFromValue( constraint.m_Value.Min() ) );
  1514. }
  1515. else
  1516. {
  1517. msgItems.emplace_back( _( "Resolved Margin Clearance" ),
  1518. m_frame->MessageTextFromValue( constraint.m_Value.Min() ) );
  1519. }
  1520. }
  1521. }
  1522. }
  1523. if( selection.GetSize() )
  1524. {
  1525. if( msgItems.empty() )
  1526. {
  1527. msgItems.emplace_back( _( "Selected Items" ),
  1528. wxString::Format( wxT( "%d" ), selection.GetSize() ) );
  1529. if( m_isBoardEditor )
  1530. {
  1531. std::set<wxString> netNames;
  1532. std::set<wxString> netClasses;
  1533. for( EDA_ITEM* item : selection )
  1534. {
  1535. if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
  1536. {
  1537. netNames.insert( UnescapeString( bci->GetNetname() ) );
  1538. netClasses.insert( UnescapeString( bci->GetEffectiveNetClass()->GetHumanReadableName() ) );
  1539. if( netNames.size() > 1 && netClasses.size() > 1 )
  1540. break;
  1541. }
  1542. }
  1543. if( netNames.size() == 1 )
  1544. msgItems.emplace_back( _( "Net" ), *netNames.begin() );
  1545. if( netClasses.size() == 1 )
  1546. msgItems.emplace_back( _( "Resolved Netclass" ), *netClasses.begin() );
  1547. }
  1548. }
  1549. if( selection.GetSize() >= 2 )
  1550. {
  1551. bool lengthValid = true;
  1552. double selectedLength = 0;
  1553. // Lambda to accumulate track length if item is a track or arc, otherwise mark invalid
  1554. std::function<void( EDA_ITEM* )> accumulateTrackLength;
  1555. accumulateTrackLength =
  1556. [&]( EDA_ITEM* aItem )
  1557. {
  1558. if( aItem->Type() == PCB_TRACE_T || aItem->Type() == PCB_ARC_T )
  1559. {
  1560. selectedLength += static_cast<PCB_TRACK*>( aItem )->GetLength();
  1561. }
  1562. else if( aItem->Type() == PCB_VIA_T )
  1563. {
  1564. // zero 2D length
  1565. }
  1566. else if( aItem->Type() == PCB_SHAPE_T )
  1567. {
  1568. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
  1569. if( shape->GetShape() == SHAPE_T::SEGMENT
  1570. || shape->GetShape() == SHAPE_T::ARC
  1571. || shape->GetShape() == SHAPE_T::BEZIER )
  1572. {
  1573. selectedLength += shape->GetLength();
  1574. }
  1575. else
  1576. {
  1577. lengthValid = false;
  1578. }
  1579. }
  1580. // Use dynamic_cast to include PCB_GENERATORs.
  1581. else if( PCB_GROUP* group = dynamic_cast<PCB_GROUP*>( aItem ) )
  1582. {
  1583. group->RunOnChildren( accumulateTrackLength, RECURSE_MODE::NO_RECURSE );
  1584. }
  1585. else
  1586. {
  1587. lengthValid = false;
  1588. }
  1589. };
  1590. for( EDA_ITEM* item : selection )
  1591. {
  1592. if( lengthValid )
  1593. accumulateTrackLength( item );
  1594. }
  1595. if( lengthValid )
  1596. {
  1597. msgItems.emplace_back( _( "Selected 2D Length" ),
  1598. m_frame->MessageTextFromValue( selectedLength ) );
  1599. }
  1600. }
  1601. if( selection.GetSize() >= 2 && selection.GetSize() < 100 )
  1602. {
  1603. LSET enabledCopper = LSET::AllCuMask( m_frame->GetBoard()->GetCopperLayerCount() );
  1604. bool areaValid = true;
  1605. std::map<PCB_LAYER_ID, SHAPE_POLY_SET> copperPolys;
  1606. SHAPE_POLY_SET holes;
  1607. std::function<void( EDA_ITEM* )> accumulateArea;
  1608. accumulateArea =
  1609. [&]( EDA_ITEM* aItem )
  1610. {
  1611. if( aItem->Type() == PCB_FOOTPRINT_T )
  1612. {
  1613. areaValid = false;
  1614. return;
  1615. }
  1616. if( BOARD_ITEM* boardItem = dynamic_cast<BOARD_ITEM*>( aItem ) )
  1617. {
  1618. boardItem->RunOnChildren( accumulateArea, RECURSE_MODE::NO_RECURSE );
  1619. for( PCB_LAYER_ID layer : LSET( boardItem->GetLayerSet() & enabledCopper ) )
  1620. {
  1621. boardItem->TransformShapeToPolySet( copperPolys[layer], layer, 0,
  1622. ARC_LOW_DEF, ERROR_INSIDE );
  1623. }
  1624. if( aItem->Type() == PCB_PAD_T && static_cast<PAD*>( aItem )->HasHole() )
  1625. {
  1626. static_cast<PAD*>( aItem )->TransformHoleToPolygon( holes, 0, ARC_LOW_DEF,
  1627. ERROR_OUTSIDE );
  1628. }
  1629. else if( aItem->Type() == PCB_VIA_T )
  1630. {
  1631. PCB_VIA* via = static_cast<PCB_VIA*>( aItem );
  1632. VECTOR2I center = via->GetPosition();
  1633. int R = via->GetDrillValue() / 2;
  1634. TransformCircleToPolygon( holes, center, R, ARC_LOW_DEF, ERROR_OUTSIDE );
  1635. }
  1636. }
  1637. };
  1638. for( EDA_ITEM* item : selection )
  1639. {
  1640. if( areaValid )
  1641. accumulateArea( item );
  1642. }
  1643. if( areaValid )
  1644. {
  1645. double area = 0.0;
  1646. for( auto& [layer, copperPoly] : copperPolys )
  1647. {
  1648. copperPoly.BooleanSubtract( holes );
  1649. area += copperPoly.Area();
  1650. }
  1651. msgItems.emplace_back( _( "Selected 2D Copper Area" ),
  1652. m_frame->MessageTextFromValue( area, true, EDA_DATA_TYPE::AREA ) );
  1653. }
  1654. }
  1655. }
  1656. else
  1657. {
  1658. m_frame->GetBoard()->GetMsgPanelInfo( m_frame, msgItems );
  1659. }
  1660. m_frame->SetMsgPanel( msgItems );
  1661. return 0;
  1662. }
  1663. int PCB_CONTROL::DdAppendBoard( const TOOL_EVENT& aEvent )
  1664. {
  1665. wxFileName fileName = wxFileName( *aEvent.Parameter<wxString*>() );
  1666. PCB_EDIT_FRAME* editFrame = dynamic_cast<PCB_EDIT_FRAME*>( m_frame );
  1667. if( !editFrame )
  1668. return 1;
  1669. wxString filePath = fileName.GetFullPath();
  1670. PCB_IO_MGR::PCB_FILE_T pluginType = PCB_IO_MGR::FindPluginTypeFromBoardPath( filePath );
  1671. IO_RELEASER<PCB_IO> pi( PCB_IO_MGR::PluginFind( pluginType ) );
  1672. if( !pi )
  1673. return 1;
  1674. return AppendBoard( *pi, filePath );
  1675. }
  1676. int PCB_CONTROL::FlipPcbView( const TOOL_EVENT& aEvent )
  1677. {
  1678. view()->SetMirror( !view()->IsMirroredX(), false );
  1679. view()->RecacheAllItems();
  1680. frame()->GetCanvas()->ForceRefresh();
  1681. frame()->OnDisplayOptionsChanged();
  1682. return 0;
  1683. }
  1684. // clang-format off
  1685. void PCB_CONTROL::setTransitions()
  1686. {
  1687. Go( &PCB_CONTROL::AddLibrary, ACTIONS::newLibrary.MakeEvent() );
  1688. Go( &PCB_CONTROL::AddLibrary, ACTIONS::addLibrary.MakeEvent() );
  1689. Go( &PCB_CONTROL::Print, ACTIONS::print.MakeEvent() );
  1690. Go( &PCB_CONTROL::Quit, ACTIONS::quit.MakeEvent() );
  1691. // Footprint library actions
  1692. Go( &PCB_CONTROL::SaveFpToBoard, PCB_ACTIONS::saveFpToBoard.MakeEvent() );
  1693. Go( &PCB_CONTROL::LoadFpFromBoard, PCB_ACTIONS::loadFpFromBoard.MakeEvent() );
  1694. Go( &PCB_CONTROL::IterateFootprint, PCB_ACTIONS::nextFootprint.MakeEvent() );
  1695. Go( &PCB_CONTROL::IterateFootprint, PCB_ACTIONS::previousFootprint.MakeEvent() );
  1696. // Display modes
  1697. Go( &PCB_CONTROL::TrackDisplayMode, PCB_ACTIONS::trackDisplayMode.MakeEvent() );
  1698. Go( &PCB_CONTROL::ToggleRatsnest, PCB_ACTIONS::showRatsnest.MakeEvent() );
  1699. Go( &PCB_CONTROL::ToggleRatsnest, PCB_ACTIONS::ratsnestLineMode.MakeEvent() );
  1700. Go( &PCB_CONTROL::ViaDisplayMode, PCB_ACTIONS::viaDisplayMode.MakeEvent() );
  1701. Go( &PCB_CONTROL::ZoneDisplayMode, PCB_ACTIONS::zoneDisplayFilled.MakeEvent() );
  1702. Go( &PCB_CONTROL::ZoneDisplayMode, PCB_ACTIONS::zoneDisplayOutline.MakeEvent() );
  1703. Go( &PCB_CONTROL::ZoneDisplayMode, PCB_ACTIONS::zoneDisplayFractured.MakeEvent() );
  1704. Go( &PCB_CONTROL::ZoneDisplayMode, PCB_ACTIONS::zoneDisplayTriangulated.MakeEvent() );
  1705. Go( &PCB_CONTROL::ZoneDisplayMode, PCB_ACTIONS::zoneDisplayToggle.MakeEvent() );
  1706. Go( &PCB_CONTROL::HighContrastMode, ACTIONS::highContrastMode.MakeEvent() );
  1707. Go( &PCB_CONTROL::HighContrastModeCycle, ACTIONS::highContrastModeCycle.MakeEvent() );
  1708. Go( &PCB_CONTROL::ContrastModeFeedback, EVENTS::ContrastModeChangedByKeyEvent );
  1709. Go( &PCB_CONTROL::NetColorModeCycle, PCB_ACTIONS::netColorModeCycle.MakeEvent() );
  1710. Go( &PCB_CONTROL::RatsnestModeCycle, PCB_ACTIONS::ratsnestModeCycle.MakeEvent() );
  1711. Go( &PCB_CONTROL::FlipPcbView, PCB_ACTIONS::flipBoard.MakeEvent() );
  1712. // Layer control
  1713. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerTop.MakeEvent() );
  1714. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner1.MakeEvent() );
  1715. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner2.MakeEvent() );
  1716. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner3.MakeEvent() );
  1717. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner4.MakeEvent() );
  1718. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner5.MakeEvent() );
  1719. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner6.MakeEvent() );
  1720. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner7.MakeEvent() );
  1721. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner8.MakeEvent() );
  1722. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner9.MakeEvent() );
  1723. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner10.MakeEvent() );
  1724. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner11.MakeEvent() );
  1725. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner12.MakeEvent() );
  1726. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner13.MakeEvent() );
  1727. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner14.MakeEvent() );
  1728. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner15.MakeEvent() );
  1729. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner16.MakeEvent() );
  1730. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner17.MakeEvent() );
  1731. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner18.MakeEvent() );
  1732. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner19.MakeEvent() );
  1733. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner20.MakeEvent() );
  1734. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner21.MakeEvent() );
  1735. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner22.MakeEvent() );
  1736. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner23.MakeEvent() );
  1737. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner24.MakeEvent() );
  1738. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner25.MakeEvent() );
  1739. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner26.MakeEvent() );
  1740. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner27.MakeEvent() );
  1741. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner28.MakeEvent() );
  1742. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner29.MakeEvent() );
  1743. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerInner30.MakeEvent() );
  1744. Go( &PCB_CONTROL::LayerSwitch, PCB_ACTIONS::layerBottom.MakeEvent() );
  1745. Go( &PCB_CONTROL::LayerNext, PCB_ACTIONS::layerNext.MakeEvent() );
  1746. Go( &PCB_CONTROL::LayerPrev, PCB_ACTIONS::layerPrev.MakeEvent() );
  1747. Go( &PCB_CONTROL::LayerToggle, PCB_ACTIONS::layerToggle.MakeEvent() );
  1748. Go( &PCB_CONTROL::LayerAlphaInc, PCB_ACTIONS::layerAlphaInc.MakeEvent() );
  1749. Go( &PCB_CONTROL::LayerAlphaDec, PCB_ACTIONS::layerAlphaDec.MakeEvent() );
  1750. Go( &PCB_CONTROL::CycleLayerPresets, PCB_ACTIONS::layerPairPresetsCycle.MakeEvent() );
  1751. Go( &PCB_CONTROL::LayerPresetFeedback, PCB_EVENTS::LayerPairPresetChangedByKeyEvent() );
  1752. // Grid control
  1753. Go( &PCB_CONTROL::GridPlaceOrigin, ACTIONS::gridSetOrigin.MakeEvent() );
  1754. Go( &PCB_CONTROL::GridResetOrigin, ACTIONS::gridResetOrigin.MakeEvent() );
  1755. Go( &PCB_CONTROL::Undo, ACTIONS::undo.MakeEvent() );
  1756. Go( &PCB_CONTROL::Redo, ACTIONS::redo.MakeEvent() );
  1757. // Snapping control
  1758. Go( &PCB_CONTROL::SnapMode, PCB_ACTIONS::magneticSnapActiveLayer.MakeEvent() );
  1759. Go( &PCB_CONTROL::SnapMode, PCB_ACTIONS::magneticSnapAllLayers.MakeEvent() );
  1760. Go( &PCB_CONTROL::SnapMode, PCB_ACTIONS::magneticSnapToggle.MakeEvent() );
  1761. Go( &PCB_CONTROL::SnapModeFeedback, PCB_EVENTS::SnappingModeChangedByKeyEvent() );
  1762. // Miscellaneous
  1763. Go( &PCB_CONTROL::InteractiveDelete, ACTIONS::deleteTool.MakeEvent() );
  1764. // Append control
  1765. Go( &PCB_CONTROL::AppendDesignBlock, PCB_ACTIONS::placeDesignBlock.MakeEvent() );
  1766. Go( &PCB_CONTROL::AppendBoardFromFile, PCB_ACTIONS::appendBoard.MakeEvent() );
  1767. Go( &PCB_CONTROL::DdAppendBoard, PCB_ACTIONS::ddAppendBoard.MakeEvent() );
  1768. Go( &PCB_CONTROL::Paste, ACTIONS::paste.MakeEvent() );
  1769. Go( &PCB_CONTROL::Paste, ACTIONS::pasteSpecial.MakeEvent() );
  1770. Go( &PCB_CONTROL::UpdateMessagePanel, EVENTS::PointSelectedEvent );
  1771. Go( &PCB_CONTROL::UpdateMessagePanel, EVENTS::SelectedEvent );
  1772. Go( &PCB_CONTROL::UpdateMessagePanel, EVENTS::UnselectedEvent );
  1773. Go( &PCB_CONTROL::UpdateMessagePanel, EVENTS::ClearedEvent );
  1774. Go( &PCB_CONTROL::UpdateMessagePanel, EVENTS::SelectedItemsModified );
  1775. Go( &PCB_CONTROL::UpdateMessagePanel, EVENTS::ConnectivityChangedEvent );
  1776. // Add library by dropping file
  1777. Go( &PCB_CONTROL::DdAddLibrary, ACTIONS::ddAddLibrary.MakeEvent() );
  1778. Go( &PCB_CONTROL::DdImportFootprint, PCB_ACTIONS::ddImportFootprint.MakeEvent() );
  1779. }
  1780. // clang-format on