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.

1595 lines
48 KiB

4 years ago
4 years ago
4 years ago
4 years ago
6 years ago
6 years ago
4 years ago
4 years ago
4 years ago
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
4 years ago
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
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
7 years ago
9 years ago
9 years ago
4 years ago
9 years ago
9 years ago
4 years ago
5 years ago
7 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2014 CERN
  5. * Copyright (C) 2014-2021 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 <functional>
  26. #include <memory>
  27. #include <advanced_config.h>
  28. #include "board_editor_control.h"
  29. #include <bitmaps.h>
  30. #include <board.h>
  31. #include <board_commit.h>
  32. #include <board_design_settings.h>
  33. #include <pcb_group.h>
  34. #include <footprint.h>
  35. #include <pad.h>
  36. #include <pcb_target.h>
  37. #include <pcb_track.h>
  38. #include <zone.h>
  39. #include <pcb_marker.h>
  40. #include <confirm.h>
  41. #include <dialogs/dialog_page_settings.h>
  42. #include <dialogs/dialog_update_pcb.h>
  43. #include <kiface_base.h>
  44. #include <kiway.h>
  45. #include <netlist_reader/pcb_netlist.h>
  46. #include <origin_viewitem.h>
  47. #include <pcb_edit_frame.h>
  48. #include <pcbnew_id.h>
  49. #include <pcbnew_settings.h>
  50. #include <project.h>
  51. #include <project/project_file.h> // LAST_PATH_TYPE
  52. #include <tool/tool_manager.h>
  53. #include <tool/tool_event.h>
  54. #include <tools/drawing_tool.h>
  55. #include <tools/pcb_actions.h>
  56. #include <tools/pcb_picker_tool.h>
  57. #include <tools/pcb_selection_tool.h>
  58. #include <tools/edit_tool.h>
  59. #include <tools/tool_event_utils.h>
  60. #include <router/router_tool.h>
  61. #include <view/view_controls.h>
  62. #include <view/view_group.h>
  63. #include <wildcards_and_files_ext.h>
  64. #include <drawing_sheet/ds_proxy_undo_item.h>
  65. #include <footprint_edit_frame.h>
  66. #include <wx/filedlg.h>
  67. #include <wx/log.h>
  68. using namespace std::placeholders;
  69. class ZONE_CONTEXT_MENU : public ACTION_MENU
  70. {
  71. public:
  72. ZONE_CONTEXT_MENU() :
  73. ACTION_MENU( true )
  74. {
  75. SetIcon( BITMAPS::add_zone );
  76. SetTitle( _( "Zones" ) );
  77. Add( PCB_ACTIONS::zoneFill );
  78. Add( PCB_ACTIONS::zoneFillAll );
  79. Add( PCB_ACTIONS::zoneUnfill );
  80. Add( PCB_ACTIONS::zoneUnfillAll );
  81. AppendSeparator();
  82. Add( PCB_ACTIONS::zoneMerge );
  83. Add( PCB_ACTIONS::zoneDuplicate );
  84. Add( PCB_ACTIONS::drawZoneCutout );
  85. Add( PCB_ACTIONS::drawSimilarZone );
  86. }
  87. protected:
  88. ACTION_MENU* create() const override
  89. {
  90. return new ZONE_CONTEXT_MENU();
  91. }
  92. };
  93. class LOCK_CONTEXT_MENU : public ACTION_MENU
  94. {
  95. public:
  96. LOCK_CONTEXT_MENU() :
  97. ACTION_MENU( true )
  98. {
  99. SetIcon( BITMAPS::locked );
  100. SetTitle( _( "Locking" ) );
  101. Add( PCB_ACTIONS::lock );
  102. Add( PCB_ACTIONS::unlock );
  103. Add( PCB_ACTIONS::toggleLock );
  104. }
  105. ACTION_MENU* create() const override
  106. {
  107. return new LOCK_CONTEXT_MENU();
  108. }
  109. };
  110. /**
  111. * Helper widget to add controls to a wxFileDialog to set netlist configuration options.
  112. */
  113. class NETLIST_OPTIONS_HELPER : public wxPanel
  114. {
  115. public:
  116. NETLIST_OPTIONS_HELPER( wxWindow* aParent )
  117. : wxPanel( aParent )
  118. {
  119. m_cbOmitExtras = new wxCheckBox( this, wxID_ANY, _( "Omit extra information" ) );
  120. m_cbOmitNets = new wxCheckBox( this, wxID_ANY, _( "Omit nets" ) );
  121. m_cbOmitFpUuids = new wxCheckBox( this, wxID_ANY,
  122. _( "Do not prefix path with footprint UUID." ) );
  123. wxBoxSizer* sizer = new wxBoxSizer( wxHORIZONTAL );
  124. sizer->Add( m_cbOmitExtras, 0, wxALL, 5 );
  125. sizer->Add( m_cbOmitNets, 0, wxALL, 5 );
  126. sizer->Add( m_cbOmitFpUuids, 0, wxALL, 5 );
  127. SetSizerAndFit( sizer );
  128. }
  129. int GetNetlistOptions() const
  130. {
  131. int options = 0;
  132. if( m_cbOmitExtras->GetValue() )
  133. options |= CTL_OMIT_EXTRA;
  134. if( m_cbOmitNets->GetValue() )
  135. options |= CTL_OMIT_NETS;
  136. if( m_cbOmitFpUuids->GetValue() )
  137. options |= CTL_OMIT_FP_UUID;
  138. return options;
  139. }
  140. static wxWindow* Create( wxWindow* aParent )
  141. {
  142. return new NETLIST_OPTIONS_HELPER( aParent );
  143. }
  144. protected:
  145. wxCheckBox* m_cbOmitExtras;
  146. wxCheckBox* m_cbOmitNets;
  147. wxCheckBox* m_cbOmitFpUuids;
  148. };
  149. BOARD_EDITOR_CONTROL::BOARD_EDITOR_CONTROL() :
  150. PCB_TOOL_BASE( "pcbnew.EditorControl" ),
  151. m_frame( nullptr ),
  152. m_inPlaceFootprint( false ),
  153. m_placingFootprint( false ),
  154. m_inPlaceTarget( false )
  155. {
  156. m_placeOrigin = std::make_unique<KIGFX::ORIGIN_VIEWITEM>( KIGFX::COLOR4D( 0.8, 0.0, 0.0, 1.0 ),
  157. KIGFX::ORIGIN_VIEWITEM::CIRCLE_CROSS );
  158. }
  159. BOARD_EDITOR_CONTROL::~BOARD_EDITOR_CONTROL()
  160. {
  161. }
  162. void BOARD_EDITOR_CONTROL::Reset( RESET_REASON aReason )
  163. {
  164. m_frame = getEditFrame<PCB_EDIT_FRAME>();
  165. if( aReason == MODEL_RELOAD || aReason == GAL_SWITCH )
  166. {
  167. m_placeOrigin->SetPosition( getModel<BOARD>()->GetDesignSettings().GetAuxOrigin() );
  168. getView()->Remove( m_placeOrigin.get() );
  169. getView()->Add( m_placeOrigin.get() );
  170. }
  171. }
  172. bool BOARD_EDITOR_CONTROL::Init()
  173. {
  174. auto activeToolCondition =
  175. [this]( const SELECTION& aSel )
  176. {
  177. return ( !m_frame->ToolStackIsEmpty() );
  178. };
  179. auto inactiveStateCondition =
  180. [this]( const SELECTION& aSel )
  181. {
  182. return ( m_frame->ToolStackIsEmpty() && aSel.Size() == 0 );
  183. };
  184. auto placeModuleCondition =
  185. [this]( const SELECTION& aSel )
  186. {
  187. return m_frame->IsCurrentTool( PCB_ACTIONS::placeFootprint ) && aSel.GetSize() == 0;
  188. };
  189. auto& ctxMenu = m_menu.GetMenu();
  190. // "Cancel" goes at the top of the context menu when a tool is active
  191. ctxMenu.AddItem( ACTIONS::cancelInteractive, activeToolCondition, 1 );
  192. ctxMenu.AddSeparator( 1 );
  193. // "Get and Place Footprint" should be available for Place Footprint tool
  194. ctxMenu.AddItem( PCB_ACTIONS::getAndPlace, placeModuleCondition, 1000 );
  195. ctxMenu.AddSeparator( 1000 );
  196. // Finally, add the standard zoom & grid items
  197. getEditFrame<PCB_BASE_FRAME>()->AddStandardSubMenus( m_menu );
  198. auto zoneMenu = std::make_shared<ZONE_CONTEXT_MENU>();
  199. zoneMenu->SetTool( this );
  200. auto lockMenu = std::make_shared<LOCK_CONTEXT_MENU>();
  201. lockMenu->SetTool( this );
  202. // Add the PCB control menus to relevant other tools
  203. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  204. if( selTool )
  205. {
  206. auto& toolMenu = selTool->GetToolMenu();
  207. auto& menu = toolMenu.GetMenu();
  208. // Add "Get and Place Footprint" when Selection tool is in an inactive state
  209. menu.AddItem( PCB_ACTIONS::getAndPlace, inactiveStateCondition );
  210. menu.AddSeparator();
  211. toolMenu.AddSubMenu( zoneMenu );
  212. toolMenu.AddSubMenu( lockMenu );
  213. menu.AddMenu( lockMenu.get(), SELECTION_CONDITIONS::NotEmpty, 100 );
  214. menu.AddMenu( zoneMenu.get(), SELECTION_CONDITIONS::OnlyType( PCB_ZONE_T ), 200 );
  215. }
  216. DRAWING_TOOL* drawingTool = m_toolMgr->GetTool<DRAWING_TOOL>();
  217. if( drawingTool )
  218. {
  219. auto& toolMenu = drawingTool->GetToolMenu();
  220. auto& menu = toolMenu.GetMenu();
  221. toolMenu.AddSubMenu( zoneMenu );
  222. // Functor to say if the PCB_EDIT_FRAME is in a given mode
  223. // Capture the tool pointer and tool mode by value
  224. auto toolActiveFunctor =
  225. [=]( DRAWING_TOOL::MODE aMode )
  226. {
  227. return [=]( const SELECTION& sel )
  228. {
  229. return drawingTool->GetDrawingMode() == aMode;
  230. };
  231. };
  232. menu.AddMenu( zoneMenu.get(), toolActiveFunctor( DRAWING_TOOL::MODE::ZONE ), 200 );
  233. }
  234. return true;
  235. }
  236. int BOARD_EDITOR_CONTROL::New( const TOOL_EVENT& aEvent )
  237. {
  238. m_frame->Files_io_from_id( ID_NEW_BOARD );
  239. return 0;
  240. }
  241. int BOARD_EDITOR_CONTROL::Open( const TOOL_EVENT& aEvent )
  242. {
  243. m_frame->Files_io_from_id( ID_LOAD_FILE );
  244. return 0;
  245. }
  246. int BOARD_EDITOR_CONTROL::Save( const TOOL_EVENT& aEvent )
  247. {
  248. m_frame->Files_io_from_id( ID_SAVE_BOARD );
  249. return 0;
  250. }
  251. int BOARD_EDITOR_CONTROL::SaveAs( const TOOL_EVENT& aEvent )
  252. {
  253. m_frame->Files_io_from_id( ID_SAVE_BOARD_AS );
  254. return 0;
  255. }
  256. int BOARD_EDITOR_CONTROL::SaveCopyAs( const TOOL_EVENT& aEvent )
  257. {
  258. m_frame->Files_io_from_id( ID_COPY_BOARD_AS );
  259. return 0;
  260. }
  261. int BOARD_EDITOR_CONTROL::PageSettings( const TOOL_EVENT& aEvent )
  262. {
  263. PICKED_ITEMS_LIST undoCmd;
  264. DS_PROXY_UNDO_ITEM* undoItem = new DS_PROXY_UNDO_ITEM( m_frame );
  265. ITEM_PICKER wrapper( nullptr, undoItem, UNDO_REDO::PAGESETTINGS );
  266. undoCmd.PushItem( wrapper );
  267. m_frame->SaveCopyInUndoList( undoCmd, UNDO_REDO::PAGESETTINGS );
  268. DIALOG_PAGES_SETTINGS dlg( m_frame, IU_PER_MILS, wxSize( MAX_PAGE_SIZE_PCBNEW_MILS,
  269. MAX_PAGE_SIZE_PCBNEW_MILS ) );
  270. dlg.SetWksFileName( BASE_SCREEN::m_DrawingSheetFileName );
  271. if( dlg.ShowModal() == wxID_OK )
  272. m_frame->OnModify();
  273. else
  274. m_frame->RollbackFromUndo();
  275. return 0;
  276. }
  277. int BOARD_EDITOR_CONTROL::Plot( const TOOL_EVENT& aEvent )
  278. {
  279. m_frame->ToPlotter( ID_GEN_PLOT );
  280. return 0;
  281. }
  282. int BOARD_EDITOR_CONTROL::Find( const TOOL_EVENT& aEvent )
  283. {
  284. m_frame->ShowFindDialog();
  285. return 0;
  286. }
  287. int BOARD_EDITOR_CONTROL::FindNext( const TOOL_EVENT& aEvent )
  288. {
  289. m_frame->FindNext();
  290. return 0;
  291. }
  292. int BOARD_EDITOR_CONTROL::BoardSetup( const TOOL_EVENT& aEvent )
  293. {
  294. getEditFrame<PCB_EDIT_FRAME>()->ShowBoardSetupDialog();
  295. return 0;
  296. }
  297. int BOARD_EDITOR_CONTROL::ImportNetlist( const TOOL_EVENT& aEvent )
  298. {
  299. getEditFrame<PCB_EDIT_FRAME>()->InstallNetlistFrame();
  300. return 0;
  301. }
  302. int BOARD_EDITOR_CONTROL::ImportSpecctraSession( const TOOL_EVENT& aEvent )
  303. {
  304. wxString fullFileName = frame()->GetBoard()->GetFileName();
  305. wxString path;
  306. wxString name;
  307. wxString ext;
  308. wxFileName::SplitPath( fullFileName, &path, &name, &ext );
  309. name += wxT( "." ) + SpecctraSessionFileExtension;
  310. fullFileName = wxFileSelector( _( "Specctra Session File" ), path, name,
  311. wxT( "." ) + SpecctraSessionFileExtension,
  312. SpecctraSessionFileWildcard(), wxFD_OPEN | wxFD_CHANGE_DIR,
  313. frame() );
  314. if( !fullFileName.IsEmpty() )
  315. getEditFrame<PCB_EDIT_FRAME>()->ImportSpecctraSession( fullFileName );
  316. return 0;
  317. }
  318. int BOARD_EDITOR_CONTROL::ExportSpecctraDSN( const TOOL_EVENT& aEvent )
  319. {
  320. wxString fullFileName = m_frame->GetLastPath( LAST_PATH_SPECCTRADSN );
  321. wxFileName fn;
  322. if( fullFileName.IsEmpty() )
  323. {
  324. fn = m_frame->GetBoard()->GetFileName();
  325. fn.SetExt( SpecctraDsnFileExtension );
  326. }
  327. else
  328. {
  329. fn = fullFileName;
  330. }
  331. fullFileName = wxFileSelector( _( "Specctra DSN File" ), fn.GetPath(), fn.GetFullName(),
  332. SpecctraDsnFileExtension, SpecctraDsnFileWildcard(),
  333. wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxFD_CHANGE_DIR, frame() );
  334. if( !fullFileName.IsEmpty() )
  335. {
  336. m_frame->SetLastPath( LAST_PATH_SPECCTRADSN, fullFileName );
  337. getEditFrame<PCB_EDIT_FRAME>()->ExportSpecctraFile( fullFileName );
  338. }
  339. return 0;
  340. }
  341. int BOARD_EDITOR_CONTROL::ExportNetlist( const TOOL_EVENT& aEvent )
  342. {
  343. wxCHECK( m_frame, 0 );
  344. wxFileName fn = m_frame->Prj().GetProjectFullName();
  345. // Use a different file extension for the board netlist so the schematic netlist file
  346. // is accidentally overwritten.
  347. fn.SetExt( "pcb_net" );
  348. wxFileDialog dlg( m_frame, _( "Export Board Netlist" ), fn.GetPath(), fn.GetFullName(),
  349. _( "KiCad board netlist files" ) + AddFileExtListToFilter( { "pcb_net" } ),
  350. wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
  351. dlg.SetExtraControlCreator( &NETLIST_OPTIONS_HELPER::Create );
  352. if( dlg.ShowModal() == wxID_CANCEL )
  353. return 0;
  354. fn = dlg.GetPath();
  355. if( !fn.IsDirWritable() )
  356. {
  357. wxString msg;
  358. msg.Printf( _( "Path `%s` is read only." ), fn.GetPath() );
  359. wxMessageDialog( m_frame, msg, _( "I/O Error" ), wxOK | wxCENTER | wxICON_EXCLAMATION );
  360. return 0;
  361. }
  362. const NETLIST_OPTIONS_HELPER* noh =
  363. dynamic_cast<const NETLIST_OPTIONS_HELPER*>( dlg.GetExtraControl() );
  364. wxCHECK( noh, 0 );
  365. NETLIST netlist;
  366. for( const FOOTPRINT* footprint : board()->Footprints() )
  367. {
  368. COMPONENT* component = new COMPONENT( footprint->GetFPID(), footprint->GetReference(),
  369. footprint->GetValue(), footprint->GetPath(),
  370. { footprint->m_Uuid } );
  371. for( const PAD* pad : footprint->Pads() )
  372. {
  373. const wxString& netname = pad->GetShortNetname();
  374. if( !netname.IsEmpty() )
  375. {
  376. component->AddNet( pad->GetNumber(), netname, pad->GetPinFunction(),
  377. pad->GetPinType() );
  378. }
  379. }
  380. netlist.AddComponent( component );
  381. }
  382. FILE_OUTPUTFORMATTER formatter( fn.GetFullPath() );
  383. netlist.Format( "pcb_netlist", &formatter, 0, noh->GetNetlistOptions() );
  384. return 0;
  385. }
  386. int BOARD_EDITOR_CONTROL::GenerateFabFiles( const TOOL_EVENT& aEvent )
  387. {
  388. wxCommandEvent dummy;
  389. if( aEvent.IsAction( &PCB_ACTIONS::generateGerbers ) )
  390. m_frame->ToPlotter( ID_GEN_PLOT_GERBER );
  391. else if( aEvent.IsAction( &PCB_ACTIONS::generateReportFile ) )
  392. m_frame->GenFootprintsReport( dummy );
  393. else if( aEvent.IsAction( &PCB_ACTIONS::generateD356File ) )
  394. m_frame->GenD356File( dummy );
  395. else if( aEvent.IsAction( &PCB_ACTIONS::generateBOM ) )
  396. m_frame->RecreateBOMFileFromBoard( dummy );
  397. else
  398. wxFAIL_MSG( "GenerateFabFiles(): unexpected request" );
  399. return 0;
  400. }
  401. int BOARD_EDITOR_CONTROL::RepairBoard( const TOOL_EVENT& aEvent )
  402. {
  403. int errors = 0;
  404. wxString details;
  405. bool quiet = aEvent.Parameter<bool>();
  406. // Repair duplicate IDs and missing nets.
  407. std::set<KIID> ids;
  408. int duplicates = 0;
  409. auto processItem =
  410. [&]( EDA_ITEM* aItem )
  411. {
  412. if( ids.count( aItem->m_Uuid ) )
  413. {
  414. duplicates++;
  415. const_cast<KIID&>( aItem->m_Uuid ) = KIID();
  416. }
  417. ids.insert( aItem->m_Uuid );
  418. BOARD_CONNECTED_ITEM* cItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( aItem );
  419. if( cItem && cItem->GetNetCode() )
  420. {
  421. NETINFO_ITEM* netinfo = cItem->GetNet();
  422. if( netinfo && !board()->FindNet( netinfo->GetNetname() ) )
  423. {
  424. board()->Add( netinfo );
  425. details += wxString::Format( _( "Orphaned net %s re-parented.\n" ),
  426. netinfo->GetNetname() );
  427. errors++;
  428. }
  429. }
  430. };
  431. // Footprint IDs are the most important, so give them the first crack at "claiming" a
  432. // particular KIID.
  433. for( FOOTPRINT* footprint : board()->Footprints() )
  434. processItem( footprint );
  435. // After that the principal use is for DRC marker pointers, which are most likely to pads
  436. // or tracks.
  437. for( FOOTPRINT* footprint : board()->Footprints() )
  438. {
  439. for( PAD* pad : footprint->Pads() )
  440. processItem( pad );
  441. }
  442. for( PCB_TRACK* track : board()->Tracks() )
  443. processItem( track );
  444. // From here out I don't think order matters much.
  445. for( FOOTPRINT* footprint : board()->Footprints() )
  446. {
  447. processItem( &footprint->Reference() );
  448. processItem( &footprint->Value() );
  449. for( BOARD_ITEM* item : footprint->GraphicalItems() )
  450. processItem( item );
  451. for( ZONE* zone : footprint->Zones() )
  452. processItem( zone );
  453. for( PCB_GROUP* group : footprint->Groups() )
  454. processItem( group );
  455. }
  456. for( BOARD_ITEM* drawing : board()->Drawings() )
  457. processItem( drawing );
  458. for( ZONE* zone : board()->Zones() )
  459. processItem( zone );
  460. for( PCB_MARKER* marker : board()->Markers() )
  461. processItem( marker );
  462. for( PCB_GROUP* group : board()->Groups() )
  463. processItem( group );
  464. if( duplicates )
  465. {
  466. errors += duplicates;
  467. details += wxString::Format( _( "%d duplicate IDs replaced.\n" ), duplicates );
  468. }
  469. /*******************************
  470. * Your test here
  471. */
  472. /*******************************
  473. * Inform the user
  474. */
  475. if( errors )
  476. {
  477. m_frame->OnModify();
  478. wxString msg = wxString::Format( _( "%d potential problems repaired." ), errors );
  479. if( !quiet )
  480. DisplayInfoMessage( m_frame, msg, details );
  481. }
  482. else if( !quiet )
  483. {
  484. DisplayInfoMessage( m_frame, _( "No board problems found." ) );
  485. }
  486. return 0;
  487. }
  488. int BOARD_EDITOR_CONTROL::UpdatePCBFromSchematic( const TOOL_EVENT& aEvent )
  489. {
  490. NETLIST netlist;
  491. if( m_frame->FetchNetlistFromSchematic( netlist, _( "Updating PCB requires a fully annotated "
  492. "schematic." ) ) )
  493. {
  494. DIALOG_UPDATE_PCB updateDialog( m_frame, &netlist );
  495. updateDialog.ShowModal();
  496. }
  497. return 0;
  498. }
  499. int BOARD_EDITOR_CONTROL::UpdateSchematicFromPCB( const TOOL_EVENT& aEvent )
  500. {
  501. if( Kiface().IsSingle() )
  502. {
  503. DisplayErrorMessage(
  504. m_frame, _( "Cannot update schematic because Pcbnew is opened in stand-alone "
  505. "mode. In order to create or update PCBs from schematics, you "
  506. "must launch the KiCad project manager and create a project." ) );
  507. return 0;
  508. }
  509. m_frame->RunEeschema();
  510. KIWAY_PLAYER* frame = m_frame->Kiway().Player( FRAME_SCH, false );
  511. if( frame )
  512. {
  513. std::string payload;
  514. m_frame->Kiway().ExpressMail( FRAME_SCH, MAIL_SCH_UPDATE, payload, m_frame );
  515. }
  516. return 0;
  517. }
  518. int BOARD_EDITOR_CONTROL::ShowEeschema( const TOOL_EVENT& aEvent )
  519. {
  520. m_frame->RunEeschema();
  521. return 0;
  522. }
  523. int BOARD_EDITOR_CONTROL::ToggleLayersManager( const TOOL_EVENT& aEvent )
  524. {
  525. getEditFrame<PCB_EDIT_FRAME>()->ToggleLayersManager();
  526. return 0;
  527. }
  528. int BOARD_EDITOR_CONTROL::TogglePythonConsole( const TOOL_EVENT& aEvent )
  529. {
  530. m_frame->ScriptingConsoleEnableDisable();
  531. return 0;
  532. }
  533. // Track & via size control
  534. int BOARD_EDITOR_CONTROL::TrackWidthInc( const TOOL_EVENT& aEvent )
  535. {
  536. BOARD_DESIGN_SETTINGS& designSettings = getModel<BOARD>()->GetDesignSettings();
  537. constexpr KICAD_T types[] = { PCB_TRACE_T, PCB_VIA_T, EOT };
  538. PCB_SELECTION& selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
  539. if( m_frame->ToolStackIsEmpty() && SELECTION_CONDITIONS::OnlyTypes( types )( selection ) )
  540. {
  541. BOARD_COMMIT commit( this );
  542. for( EDA_ITEM* item : selection )
  543. {
  544. if( item->Type() == PCB_TRACE_T )
  545. {
  546. PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
  547. // Note: skip first entry which is the current netclass value
  548. for( int i = 1; i < (int) designSettings.m_TrackWidthList.size(); ++i )
  549. {
  550. int candidate = designSettings.m_TrackWidthList[ i ];
  551. if( candidate > track->GetWidth() )
  552. {
  553. commit.Modify( track );
  554. track->SetWidth( candidate );
  555. break;
  556. }
  557. }
  558. }
  559. }
  560. commit.Push( "Increase Track Width" );
  561. return 0;
  562. }
  563. ROUTER_TOOL* routerTool = m_toolMgr->GetTool<ROUTER_TOOL>();
  564. if( routerTool && routerTool->IsToolActive()
  565. && routerTool->Router()->Mode() == PNS::PNS_MODE_ROUTE_DIFF_PAIR )
  566. {
  567. int widthIndex = designSettings.GetDiffPairIndex() + 1;
  568. // If we go past the last track width entry in the list, start over at the beginning
  569. if( widthIndex >= (int) designSettings.m_DiffPairDimensionsList.size() )
  570. widthIndex = 0;
  571. designSettings.SetDiffPairIndex( widthIndex );
  572. designSettings.UseCustomDiffPairDimensions( false );
  573. m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
  574. }
  575. else
  576. {
  577. int widthIndex = designSettings.GetTrackWidthIndex() + 1;
  578. // If we go past the last track width entry in the list, start over at the beginning
  579. if( widthIndex >= (int) designSettings.m_TrackWidthList.size() )
  580. widthIndex = 0;
  581. designSettings.SetTrackWidthIndex( widthIndex );
  582. designSettings.UseCustomTrackViaSize( false );
  583. m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
  584. }
  585. return 0;
  586. }
  587. int BOARD_EDITOR_CONTROL::TrackWidthDec( const TOOL_EVENT& aEvent )
  588. {
  589. BOARD_DESIGN_SETTINGS& designSettings = getModel<BOARD>()->GetDesignSettings();
  590. constexpr KICAD_T types[] = { PCB_TRACE_T, PCB_VIA_T, EOT };
  591. PCB_SELECTION& selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
  592. if( m_frame->ToolStackIsEmpty() && SELECTION_CONDITIONS::OnlyTypes( types )( selection ) )
  593. {
  594. BOARD_COMMIT commit( this );
  595. for( EDA_ITEM* item : selection )
  596. {
  597. if( item->Type() == PCB_TRACE_T )
  598. {
  599. PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
  600. // Note: skip first entry which is the current netclass value
  601. for( int i = designSettings.m_TrackWidthList.size() - 1; i >= 1; --i )
  602. {
  603. int candidate = designSettings.m_TrackWidthList[ i ];
  604. if( candidate < track->GetWidth() )
  605. {
  606. commit.Modify( track );
  607. track->SetWidth( candidate );
  608. break;
  609. }
  610. }
  611. }
  612. }
  613. commit.Push( "Decrease Track Width" );
  614. return 0;
  615. }
  616. ROUTER_TOOL* routerTool = m_toolMgr->GetTool<ROUTER_TOOL>();
  617. if( routerTool && routerTool->IsToolActive()
  618. && routerTool->Router()->Mode() == PNS::PNS_MODE_ROUTE_DIFF_PAIR )
  619. {
  620. int widthIndex = designSettings.GetDiffPairIndex() - 1;
  621. // If we get to the lowest entry start over at the highest
  622. if( widthIndex < 0 )
  623. widthIndex = designSettings.m_DiffPairDimensionsList.size() - 1;
  624. designSettings.SetDiffPairIndex( widthIndex );
  625. designSettings.UseCustomDiffPairDimensions( false );
  626. m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
  627. }
  628. else
  629. {
  630. int widthIndex = designSettings.GetTrackWidthIndex() - 1;
  631. // If we get to the lowest entry start over at the highest
  632. if( widthIndex < 0 )
  633. widthIndex = designSettings.m_TrackWidthList.size() - 1;
  634. designSettings.SetTrackWidthIndex( widthIndex );
  635. designSettings.UseCustomTrackViaSize( false );
  636. m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
  637. }
  638. return 0;
  639. }
  640. int BOARD_EDITOR_CONTROL::ViaSizeInc( const TOOL_EVENT& aEvent )
  641. {
  642. BOARD_DESIGN_SETTINGS& designSettings = getModel<BOARD>()->GetDesignSettings();
  643. constexpr KICAD_T types[] = { PCB_TRACE_T, PCB_VIA_T, EOT };
  644. PCB_SELECTION& selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
  645. if( m_frame->ToolStackIsEmpty() && SELECTION_CONDITIONS::OnlyTypes( types )( selection ) )
  646. {
  647. BOARD_COMMIT commit( this );
  648. for( EDA_ITEM* item : selection )
  649. {
  650. if( item->Type() == PCB_VIA_T )
  651. {
  652. PCB_VIA* via = static_cast<PCB_VIA*>( item );
  653. for( VIA_DIMENSION candidate : designSettings.m_ViasDimensionsList )
  654. {
  655. if( candidate.m_Diameter > via->GetWidth() )
  656. {
  657. commit.Modify( via );
  658. via->SetWidth( candidate.m_Diameter );
  659. via->SetDrill( candidate.m_Drill );
  660. break;
  661. }
  662. }
  663. }
  664. }
  665. commit.Push( "Increase Via Size" );
  666. }
  667. else
  668. {
  669. int sizeIndex = designSettings.GetViaSizeIndex() + 1;
  670. // If we go past the last via entry in the list, start over at the beginning
  671. if( sizeIndex >= (int) designSettings.m_ViasDimensionsList.size() )
  672. sizeIndex = 0;
  673. designSettings.SetViaSizeIndex( sizeIndex );
  674. designSettings.UseCustomTrackViaSize( false );
  675. m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
  676. }
  677. return 0;
  678. }
  679. int BOARD_EDITOR_CONTROL::ViaSizeDec( const TOOL_EVENT& aEvent )
  680. {
  681. BOARD_DESIGN_SETTINGS& designSettings = getModel<BOARD>()->GetDesignSettings();
  682. constexpr KICAD_T types[] = { PCB_TRACE_T, PCB_VIA_T, EOT };
  683. PCB_SELECTION& selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
  684. if( m_frame->ToolStackIsEmpty() && SELECTION_CONDITIONS::OnlyTypes( types )( selection ) )
  685. {
  686. BOARD_COMMIT commit( this );
  687. for( EDA_ITEM* item : selection )
  688. {
  689. if( item->Type() == PCB_VIA_T )
  690. {
  691. PCB_VIA* via = static_cast<PCB_VIA*>( item );
  692. for( int i = designSettings.m_ViasDimensionsList.size() - 1; i >= 0; --i )
  693. {
  694. VIA_DIMENSION candidate = designSettings.m_ViasDimensionsList[ i ];
  695. if( candidate.m_Diameter < via->GetWidth() )
  696. {
  697. commit.Modify( via );
  698. via->SetWidth( candidate.m_Diameter );
  699. via->SetDrill( candidate.m_Drill );
  700. break;
  701. }
  702. }
  703. }
  704. }
  705. commit.Push( "Decrease Via Size" );
  706. }
  707. else
  708. {
  709. int sizeIndex = 0; // Assume we only have a single via size entry
  710. // If there are more, cycle through them backwards
  711. if( designSettings.m_ViasDimensionsList.size() > 0 )
  712. {
  713. sizeIndex = designSettings.GetViaSizeIndex() - 1;
  714. // If we get to the lowest entry start over at the highest
  715. if( sizeIndex < 0 )
  716. sizeIndex = designSettings.m_ViasDimensionsList.size() - 1;
  717. }
  718. designSettings.SetViaSizeIndex( sizeIndex );
  719. designSettings.UseCustomTrackViaSize( false );
  720. m_toolMgr->RunAction( PCB_ACTIONS::trackViaSizeChanged, true );
  721. }
  722. return 0;
  723. }
  724. int BOARD_EDITOR_CONTROL::PlaceFootprint( const TOOL_EVENT& aEvent )
  725. {
  726. if( m_inPlaceFootprint )
  727. return 0;
  728. REENTRANCY_GUARD guard( &m_inPlaceFootprint );
  729. FOOTPRINT* fp = aEvent.Parameter<FOOTPRINT*>();
  730. KIGFX::VIEW_CONTROLS* controls = getViewControls();
  731. BOARD_COMMIT commit( m_frame );
  732. BOARD* board = getModel<BOARD>();
  733. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  734. std::string tool = aEvent.GetCommandStr().get();
  735. m_frame->PushTool( tool );
  736. auto setCursor =
  737. [&]()
  738. {
  739. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::PENCIL );
  740. };
  741. Activate();
  742. // Must be done after Activate() so that it gets set into the correct context
  743. controls->ShowCursor( true );
  744. // Set initial cursor
  745. setCursor();
  746. VECTOR2I cursorPos = controls->GetCursorPosition();
  747. bool reselect = false;
  748. bool fromOtherCommand = fp != nullptr;
  749. // Prime the pump
  750. if( fp )
  751. {
  752. m_placingFootprint = true;
  753. fp->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
  754. m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, fp );
  755. m_toolMgr->RunAction( ACTIONS::refreshPreview );
  756. }
  757. else if( !aEvent.IsReactivate() )
  758. {
  759. m_toolMgr->RunAction( PCB_ACTIONS::cursorClick );
  760. }
  761. // Main loop: keep receiving events
  762. while( TOOL_EVENT* evt = Wait() )
  763. {
  764. setCursor();
  765. cursorPos = controls->GetCursorPosition( !evt->DisableGridSnapping() );
  766. if( reselect && fp )
  767. m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, fp );
  768. auto cleanup =
  769. [&] ()
  770. {
  771. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  772. commit.Revert();
  773. if( fromOtherCommand )
  774. {
  775. PICKED_ITEMS_LIST* undo = m_frame->PopCommandFromUndoList();
  776. if( undo )
  777. {
  778. m_frame->PutDataInPreviousState( undo );
  779. undo->ClearListAndDeleteItems();
  780. delete undo;
  781. }
  782. }
  783. fp = nullptr;
  784. m_placingFootprint = false;
  785. };
  786. if( evt->IsCancelInteractive() )
  787. {
  788. if( fp )
  789. {
  790. cleanup();
  791. }
  792. else
  793. {
  794. m_frame->PopTool( tool );
  795. break;
  796. }
  797. }
  798. else if( evt->IsActivate() )
  799. {
  800. if( fp )
  801. cleanup();
  802. if( evt->IsMoveTool() )
  803. {
  804. // leave ourselves on the stack so we come back after the move
  805. break;
  806. }
  807. else
  808. {
  809. frame()->PopTool( tool );
  810. break;
  811. }
  812. }
  813. else if( evt->IsClick( BUT_LEFT ) )
  814. {
  815. if( !fp )
  816. {
  817. // Pick the footprint to be placed
  818. fp = m_frame->SelectFootprintFromLibTree();
  819. if( fp == nullptr )
  820. continue;
  821. m_placingFootprint = true;
  822. fp->SetLink( niluuid );
  823. fp->SetFlags(IS_NEW ); // whatever
  824. // Set parent so that clearance can be loaded
  825. fp->SetParent( board );
  826. for( PAD* pad : fp->Pads() )
  827. {
  828. pad->SetLocalRatsnestVisible( m_frame->GetDisplayOptions().m_ShowGlobalRatsnest );
  829. // Pads in the library all have orphaned nets. Replace with Default.
  830. pad->SetNetCode( 0 );
  831. }
  832. // Put it on FRONT layer,
  833. // (Can be stored flipped if the lib is an archive built from a board)
  834. if( fp->IsFlipped() )
  835. fp->Flip( fp->GetPosition(), m_frame->Settings().m_FlipLeftRight );
  836. fp->SetOrientation( 0 );
  837. fp->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
  838. commit.Add( fp );
  839. m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, fp );
  840. controls->SetCursorPosition( cursorPos, false );
  841. }
  842. else
  843. {
  844. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  845. commit.Push( _( "Place a footprint" ) );
  846. fp = nullptr; // to indicate that there is no footprint that we currently modify
  847. m_placingFootprint = false;
  848. }
  849. }
  850. else if( evt->IsClick( BUT_RIGHT ) )
  851. {
  852. m_menu.ShowContextMenu( selection() );
  853. }
  854. else if( fp && ( evt->IsMotion() || evt->IsAction( &ACTIONS::refreshPreview ) ) )
  855. {
  856. fp->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
  857. selection().SetReferencePoint( cursorPos );
  858. getView()->Update( &selection() );
  859. getView()->Update( fp );
  860. }
  861. else if( fp && evt->IsAction( &PCB_ACTIONS::properties ) )
  862. {
  863. // Calling 'Properties' action clears the selection, so we need to restore it
  864. reselect = true;
  865. }
  866. else
  867. {
  868. evt->SetPassEvent();
  869. }
  870. // Enable autopanning and cursor capture only when there is a footprint to be placed
  871. controls->SetAutoPan( fp != nullptr );
  872. controls->CaptureCursor( fp != nullptr );
  873. }
  874. controls->SetAutoPan( false );
  875. controls->CaptureCursor( false );
  876. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
  877. return 0;
  878. }
  879. int BOARD_EDITOR_CONTROL::ToggleLockSelected( const TOOL_EVENT& aEvent )
  880. {
  881. return modifyLockSelected( TOGGLE );
  882. }
  883. int BOARD_EDITOR_CONTROL::LockSelected( const TOOL_EVENT& aEvent )
  884. {
  885. return modifyLockSelected( ON );
  886. }
  887. int BOARD_EDITOR_CONTROL::UnlockSelected( const TOOL_EVENT& aEvent )
  888. {
  889. return modifyLockSelected( OFF );
  890. }
  891. int BOARD_EDITOR_CONTROL::modifyLockSelected( MODIFY_MODE aMode )
  892. {
  893. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  894. const PCB_SELECTION& selection = selTool->GetSelection();
  895. BOARD_COMMIT commit( m_frame );
  896. if( selection.Empty() )
  897. m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true );
  898. // Resolve TOGGLE mode
  899. if( aMode == TOGGLE )
  900. {
  901. aMode = ON;
  902. for( EDA_ITEM* item : selection )
  903. {
  904. BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
  905. if( board_item->IsLocked() )
  906. {
  907. aMode = OFF;
  908. break;
  909. }
  910. }
  911. }
  912. bool modified = false;
  913. for( EDA_ITEM* item : selection )
  914. {
  915. BOARD_ITEM* board_item = static_cast<BOARD_ITEM*>( item );
  916. commit.Modify( board_item );
  917. if( aMode == ON )
  918. {
  919. modified |= !board_item->IsLocked();
  920. board_item->SetLocked( true );
  921. }
  922. else
  923. {
  924. modified |= board_item->IsLocked();
  925. board_item->SetLocked( false );
  926. }
  927. }
  928. if( modified )
  929. {
  930. commit.Push( aMode == ON ? _( "Lock" ) : _( "Unlock" ) );
  931. m_toolMgr->PostEvent( EVENTS::SelectedEvent );
  932. m_frame->UpdateMsgPanel();
  933. m_frame->OnModify();
  934. }
  935. return 0;
  936. }
  937. int BOARD_EDITOR_CONTROL::PlaceTarget( const TOOL_EVENT& aEvent )
  938. {
  939. if( m_inPlaceTarget )
  940. return 0;
  941. REENTRANCY_GUARD guard( &m_inPlaceTarget );
  942. KIGFX::VIEW* view = getView();
  943. KIGFX::VIEW_CONTROLS* controls = getViewControls();
  944. BOARD* board = getModel<BOARD>();
  945. PCB_TARGET* target = new PCB_TARGET( board );
  946. // Init the new item attributes
  947. target->SetLayer( Edge_Cuts );
  948. target->SetWidth( board->GetDesignSettings().GetLineThickness( Edge_Cuts ) );
  949. target->SetSize( Millimeter2iu( 5 ) );
  950. VECTOR2I cursorPos = controls->GetCursorPosition();
  951. target->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
  952. // Add a VIEW_GROUP that serves as a preview for the new item
  953. KIGFX::VIEW_GROUP preview( view );
  954. preview.Add( target );
  955. view->Add( &preview );
  956. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  957. std::string tool = aEvent.GetCommandStr().get();
  958. m_frame->PushTool( tool );
  959. Activate();
  960. auto setCursor =
  961. [&]()
  962. {
  963. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
  964. };
  965. // Set initial cursor
  966. setCursor();
  967. // Main loop: keep receiving events
  968. while( TOOL_EVENT* evt = Wait() )
  969. {
  970. setCursor();
  971. cursorPos = controls->GetCursorPosition( !evt->DisableGridSnapping() );
  972. if( evt->IsCancelInteractive() )
  973. {
  974. frame()->PopTool( tool );
  975. break;
  976. }
  977. else if( evt->IsActivate() )
  978. {
  979. if( evt->IsMoveTool() )
  980. {
  981. // leave ourselves on the stack so we come back after the move
  982. break;
  983. }
  984. else
  985. {
  986. frame()->PopTool( tool );
  987. break;
  988. }
  989. }
  990. else if( evt->IsAction( &PCB_ACTIONS::incWidth ) )
  991. {
  992. target->SetWidth( target->GetWidth() + WIDTH_STEP );
  993. view->Update( &preview );
  994. }
  995. else if( evt->IsAction( &PCB_ACTIONS::decWidth ) )
  996. {
  997. int width = target->GetWidth();
  998. if( width > WIDTH_STEP )
  999. {
  1000. target->SetWidth( width - WIDTH_STEP );
  1001. view->Update( &preview );
  1002. }
  1003. }
  1004. else if( evt->IsClick( BUT_LEFT ) )
  1005. {
  1006. assert( target->GetSize() > 0 );
  1007. assert( target->GetWidth() > 0 );
  1008. BOARD_COMMIT commit( m_frame );
  1009. commit.Add( target );
  1010. commit.Push( "Place a layer alignment target" );
  1011. preview.Remove( target );
  1012. // Create next PCB_TARGET
  1013. target = new PCB_TARGET( *target );
  1014. preview.Add( target );
  1015. }
  1016. else if( evt->IsClick( BUT_RIGHT ) )
  1017. {
  1018. m_menu.ShowContextMenu( selection() );
  1019. }
  1020. else if( evt->IsMotion() )
  1021. {
  1022. target->SetPosition( wxPoint( cursorPos.x, cursorPos.y ) );
  1023. view->Update( &preview );
  1024. }
  1025. else
  1026. {
  1027. evt->SetPassEvent();
  1028. }
  1029. }
  1030. preview.Clear();
  1031. delete target;
  1032. view->Remove( &preview );
  1033. m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
  1034. return 0;
  1035. }
  1036. static bool mergeZones( BOARD_COMMIT& aCommit, std::vector<ZONE*>& aOriginZones,
  1037. std::vector<ZONE*>& aMergedZones )
  1038. {
  1039. aCommit.Modify( aOriginZones[0] );
  1040. for( unsigned int i = 1; i < aOriginZones.size(); i++ )
  1041. {
  1042. aOriginZones[0]->Outline()->BooleanAdd( *aOriginZones[i]->Outline(),
  1043. SHAPE_POLY_SET::PM_FAST );
  1044. }
  1045. aOriginZones[0]->Outline()->Simplify( SHAPE_POLY_SET::PM_FAST );
  1046. // We should have one polygon with hole
  1047. // We can have 2 polygons with hole, if the 2 initial polygons have only one common corner
  1048. // and therefore cannot be merged (they are detected as intersecting)
  1049. // but we should never have more than 2 polys
  1050. if( aOriginZones[0]->Outline()->OutlineCount() > 1 )
  1051. {
  1052. wxLogMessage( "BOARD::mergeZones error: more than 2 polys after merging" );
  1053. return false;
  1054. }
  1055. for( unsigned int i = 1; i < aOriginZones.size(); i++ )
  1056. aCommit.Remove( aOriginZones[i] );
  1057. aMergedZones.push_back( aOriginZones[0] );
  1058. aOriginZones[0]->SetLocalFlags( 1 );
  1059. aOriginZones[0]->HatchBorder();
  1060. aOriginZones[0]->CacheTriangulation();
  1061. return true;
  1062. }
  1063. int BOARD_EDITOR_CONTROL::ZoneMerge( const TOOL_EVENT& aEvent )
  1064. {
  1065. const PCB_SELECTION& selection = m_toolMgr->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
  1066. BOARD* board = getModel<BOARD>();
  1067. BOARD_COMMIT commit( m_frame );
  1068. if( selection.Size() < 2 )
  1069. return 0;
  1070. int netcode = -1;
  1071. ZONE* firstZone = nullptr;
  1072. std::vector<ZONE*> toMerge, merged;
  1073. for( EDA_ITEM* item : selection )
  1074. {
  1075. ZONE* curr_area = dynamic_cast<ZONE*>( item );
  1076. if( !curr_area )
  1077. continue;
  1078. if( !firstZone )
  1079. firstZone = curr_area;
  1080. netcode = curr_area->GetNetCode();
  1081. if( firstZone->GetNetCode() != netcode )
  1082. continue;
  1083. if( curr_area->GetPriority() != firstZone->GetPriority() )
  1084. continue;
  1085. if( curr_area->GetIsRuleArea() != firstZone->GetIsRuleArea() )
  1086. continue;
  1087. if( curr_area->GetLayer() != firstZone->GetLayer() )
  1088. continue;
  1089. if( !board->TestZoneIntersection( curr_area, firstZone ) )
  1090. continue;
  1091. toMerge.push_back( curr_area );
  1092. }
  1093. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  1094. if( mergeZones( commit, toMerge, merged ) )
  1095. {
  1096. commit.Push( "Merge zones" );
  1097. for( EDA_ITEM* item : merged )
  1098. m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, item );
  1099. }
  1100. return 0;
  1101. }
  1102. int BOARD_EDITOR_CONTROL::ZoneDuplicate( const TOOL_EVENT& aEvent )
  1103. {
  1104. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  1105. const PCB_SELECTION& selection = selTool->GetSelection();
  1106. // because this pops up the zone editor, it would be confusing to handle multiple zones,
  1107. // so just handle single selections containing exactly one zone
  1108. if( selection.Size() != 1 )
  1109. return 0;
  1110. ZONE* oldZone = dyn_cast<ZONE*>( selection[0] );
  1111. if( !oldZone )
  1112. return 0;
  1113. ZONE_SETTINGS zoneSettings;
  1114. zoneSettings << *oldZone;
  1115. int dialogResult;
  1116. if( oldZone->GetIsRuleArea() )
  1117. dialogResult = InvokeRuleAreaEditor( m_frame, &zoneSettings );
  1118. else if( oldZone->IsOnCopperLayer() )
  1119. dialogResult = InvokeCopperZonesEditor( m_frame, &zoneSettings );
  1120. else
  1121. dialogResult = InvokeNonCopperZonesEditor( m_frame, &zoneSettings );
  1122. if( dialogResult != wxID_OK )
  1123. return 0;
  1124. // duplicate the zone
  1125. BOARD_COMMIT commit( m_frame );
  1126. std::unique_ptr<ZONE> newZone = std::make_unique<ZONE>( *oldZone );
  1127. newZone->ClearSelected();
  1128. newZone->UnFill();
  1129. zoneSettings.ExportSetting( *newZone );
  1130. // If the new zone is on the same layer(s) as the initial zone,
  1131. // offset it a bit so it can more easily be picked.
  1132. if( oldZone->GetIsRuleArea() && ( oldZone->GetLayerSet() == zoneSettings.m_Layers ) )
  1133. newZone->Move( wxPoint( IU_PER_MM, IU_PER_MM ) );
  1134. else if( !oldZone->GetIsRuleArea() && zoneSettings.m_Layers.test( oldZone->GetLayer() ) )
  1135. newZone->Move( wxPoint( IU_PER_MM, IU_PER_MM ) );
  1136. commit.Add( newZone.release() );
  1137. commit.Push( _( "Duplicate zone" ) );
  1138. return 0;
  1139. }
  1140. int BOARD_EDITOR_CONTROL::EditFpInFpEditor( const TOOL_EVENT& aEvent )
  1141. {
  1142. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  1143. const PCB_SELECTION& selection = selTool->RequestSelection( EDIT_TOOL::FootprintFilter );
  1144. if( selection.Empty() )
  1145. return 0;
  1146. FOOTPRINT* fp = selection.FirstOfKind<FOOTPRINT>();
  1147. if( !fp )
  1148. return 0;
  1149. PCB_BASE_EDIT_FRAME* editFrame = getEditFrame<PCB_BASE_EDIT_FRAME>();
  1150. auto editor = (FOOTPRINT_EDIT_FRAME*) editFrame->Kiway().Player( FRAME_FOOTPRINT_EDITOR, true );
  1151. if( aEvent.IsAction( &PCB_ACTIONS::editFpInFpEditor ) )
  1152. editor->LoadFootprintFromBoard( fp );
  1153. else if( aEvent.IsAction( &PCB_ACTIONS::editLibFpInFpEditor ) )
  1154. editor->LoadFootprintFromLibrary( fp->GetFPID() );
  1155. editor->Show( true );
  1156. editor->Raise(); // Iconize( false );
  1157. if( selection.IsHover() )
  1158. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  1159. return 0;
  1160. }
  1161. void BOARD_EDITOR_CONTROL::DoSetDrillOrigin( KIGFX::VIEW* aView, PCB_BASE_FRAME* aFrame,
  1162. EDA_ITEM* originViewItem, const VECTOR2D& aPosition )
  1163. {
  1164. aFrame->GetDesignSettings().SetAuxOrigin( wxPoint( aPosition ) );
  1165. originViewItem->SetPosition( (wxPoint) aPosition );
  1166. aView->MarkDirty();
  1167. aFrame->OnModify();
  1168. }
  1169. int BOARD_EDITOR_CONTROL::DrillOrigin( const TOOL_EVENT& aEvent )
  1170. {
  1171. std::string tool = aEvent.GetCommandStr().get();
  1172. PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
  1173. // Deactivate other tools; particularly important if another PICKER is currently running
  1174. Activate();
  1175. picker->SetClickHandler(
  1176. [this] ( const VECTOR2D& pt ) -> bool
  1177. {
  1178. m_frame->SaveCopyInUndoList( m_placeOrigin.get(), UNDO_REDO::DRILLORIGIN );
  1179. DoSetDrillOrigin( getView(), m_frame, m_placeOrigin.get(), pt );
  1180. return false; // drill origin is a one-shot; don't continue with tool
  1181. } );
  1182. m_toolMgr->RunAction( ACTIONS::pickerTool, true, &tool );
  1183. return 0;
  1184. }
  1185. void BOARD_EDITOR_CONTROL::setTransitions()
  1186. {
  1187. Go( &BOARD_EDITOR_CONTROL::New, ACTIONS::doNew.MakeEvent() );
  1188. Go( &BOARD_EDITOR_CONTROL::Open, ACTIONS::open.MakeEvent() );
  1189. Go( &BOARD_EDITOR_CONTROL::Save, ACTIONS::save.MakeEvent() );
  1190. Go( &BOARD_EDITOR_CONTROL::SaveAs, ACTIONS::saveAs.MakeEvent() );
  1191. Go( &BOARD_EDITOR_CONTROL::SaveCopyAs, ACTIONS::saveCopyAs.MakeEvent() );
  1192. Go( &BOARD_EDITOR_CONTROL::PageSettings, ACTIONS::pageSettings.MakeEvent() );
  1193. Go( &BOARD_EDITOR_CONTROL::Plot, ACTIONS::plot.MakeEvent() );
  1194. Go( &BOARD_EDITOR_CONTROL::Find, ACTIONS::find.MakeEvent() );
  1195. Go( &BOARD_EDITOR_CONTROL::FindNext, ACTIONS::findNext.MakeEvent() );
  1196. Go( &BOARD_EDITOR_CONTROL::BoardSetup, PCB_ACTIONS::boardSetup.MakeEvent() );
  1197. Go( &BOARD_EDITOR_CONTROL::ImportNetlist, PCB_ACTIONS::importNetlist.MakeEvent() );
  1198. Go( &BOARD_EDITOR_CONTROL::ImportSpecctraSession,
  1199. PCB_ACTIONS::importSpecctraSession.MakeEvent() );
  1200. Go( &BOARD_EDITOR_CONTROL::ExportSpecctraDSN, PCB_ACTIONS::exportSpecctraDSN.MakeEvent() );
  1201. if( ADVANCED_CFG::GetCfg().m_ShowPcbnewExportNetlist && m_frame &&
  1202. m_frame->GetExportNetlistAction() )
  1203. Go( &BOARD_EDITOR_CONTROL::ExportNetlist, m_frame->GetExportNetlistAction()->MakeEvent() );
  1204. Go( &BOARD_EDITOR_CONTROL::GenerateDrillFiles,
  1205. PCB_ACTIONS::generateDrillFiles.MakeEvent() );
  1206. Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles, PCB_ACTIONS::generateGerbers.MakeEvent() );
  1207. Go( &BOARD_EDITOR_CONTROL::GeneratePosFile, PCB_ACTIONS::generatePosFile.MakeEvent() );
  1208. Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles,
  1209. PCB_ACTIONS::generateReportFile.MakeEvent() );
  1210. Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles, PCB_ACTIONS::generateD356File.MakeEvent() );
  1211. Go( &BOARD_EDITOR_CONTROL::GenerateFabFiles, PCB_ACTIONS::generateBOM.MakeEvent() );
  1212. // Track & via size control
  1213. Go( &BOARD_EDITOR_CONTROL::TrackWidthInc, PCB_ACTIONS::trackWidthInc.MakeEvent() );
  1214. Go( &BOARD_EDITOR_CONTROL::TrackWidthDec, PCB_ACTIONS::trackWidthDec.MakeEvent() );
  1215. Go( &BOARD_EDITOR_CONTROL::ViaSizeInc, PCB_ACTIONS::viaSizeInc.MakeEvent() );
  1216. Go( &BOARD_EDITOR_CONTROL::ViaSizeDec, PCB_ACTIONS::viaSizeDec.MakeEvent() );
  1217. // Zone actions
  1218. Go( &BOARD_EDITOR_CONTROL::ZoneMerge, PCB_ACTIONS::zoneMerge.MakeEvent() );
  1219. Go( &BOARD_EDITOR_CONTROL::ZoneDuplicate, PCB_ACTIONS::zoneDuplicate.MakeEvent() );
  1220. // Placing tools
  1221. Go( &BOARD_EDITOR_CONTROL::PlaceTarget, PCB_ACTIONS::placeTarget.MakeEvent() );
  1222. Go( &BOARD_EDITOR_CONTROL::PlaceFootprint, PCB_ACTIONS::placeFootprint.MakeEvent() );
  1223. Go( &BOARD_EDITOR_CONTROL::DrillOrigin, PCB_ACTIONS::drillOrigin.MakeEvent() );
  1224. Go( &BOARD_EDITOR_CONTROL::EditFpInFpEditor, PCB_ACTIONS::editFpInFpEditor.MakeEvent() );
  1225. Go( &BOARD_EDITOR_CONTROL::EditFpInFpEditor, PCB_ACTIONS::editLibFpInFpEditor.MakeEvent() );
  1226. // Other
  1227. Go( &BOARD_EDITOR_CONTROL::ToggleLockSelected, PCB_ACTIONS::toggleLock.MakeEvent() );
  1228. Go( &BOARD_EDITOR_CONTROL::LockSelected, PCB_ACTIONS::lock.MakeEvent() );
  1229. Go( &BOARD_EDITOR_CONTROL::UnlockSelected, PCB_ACTIONS::unlock.MakeEvent() );
  1230. Go( &BOARD_EDITOR_CONTROL::UpdatePCBFromSchematic,
  1231. ACTIONS::updatePcbFromSchematic.MakeEvent() );
  1232. Go( &BOARD_EDITOR_CONTROL::UpdateSchematicFromPCB,
  1233. ACTIONS::updateSchematicFromPcb.MakeEvent() );
  1234. Go( &BOARD_EDITOR_CONTROL::ShowEeschema, PCB_ACTIONS::showEeschema.MakeEvent() );
  1235. Go( &BOARD_EDITOR_CONTROL::ToggleLayersManager, PCB_ACTIONS::showLayersManager.MakeEvent() );
  1236. Go( &BOARD_EDITOR_CONTROL::TogglePythonConsole, PCB_ACTIONS::showPythonConsole.MakeEvent() );
  1237. Go( &BOARD_EDITOR_CONTROL::RepairBoard, PCB_ACTIONS::repairBoard.MakeEvent() );
  1238. }
  1239. const int BOARD_EDITOR_CONTROL::WIDTH_STEP = 100000;