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.

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