/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck * Copyright (C) 2013 Wayne Stambaugh * Copyright (C) 2013-2023 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include #include #include #include #include #include <3d_viewer/eda_3d_viewer_frame.h> #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for DIALOG_DRC_WINDOW_NAME definition #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KICAD_IPC_API #include #include #endif #include #include #include "../scripting/python_scripting.h" #include using namespace std::placeholders; #define INSPECT_DRC_ERROR_DIALOG_NAME wxT( "InspectDrcErrorDialog" ) #define INSPECT_CLEARANCE_DIALOG_NAME wxT( "InspectClearanceDialog" ) #define INSPECT_CONSTRAINTS_DIALOG_NAME wxT( "InspectConstraintsDialog" ) #define FOOTPRINT_DIFF_DIALOG_NAME wxT( "FootprintDiffDialog" ) BEGIN_EVENT_TABLE( PCB_EDIT_FRAME, PCB_BASE_FRAME ) EVT_SOCKET( ID_EDA_SOCKET_EVENT_SERV, PCB_EDIT_FRAME::OnSockRequestServer ) EVT_SOCKET( ID_EDA_SOCKET_EVENT, PCB_EDIT_FRAME::OnSockRequest ) EVT_CHOICE( ID_ON_ZOOM_SELECT, PCB_EDIT_FRAME::OnSelectZoom ) EVT_CHOICE( ID_ON_GRID_SELECT, PCB_EDIT_FRAME::OnSelectGrid ) EVT_SIZE( PCB_EDIT_FRAME::OnSize ) EVT_TOOL( ID_MENU_RECOVER_BOARD_AUTOSAVE, PCB_EDIT_FRAME::Files_io ) // Menu Files: EVT_MENU( ID_MAIN_MENUBAR, PCB_EDIT_FRAME::Process_Special_Functions ) EVT_MENU( ID_IMPORT_NON_KICAD_BOARD, PCB_EDIT_FRAME::Files_io ) EVT_MENU_RANGE( ID_FILE1, ID_FILEMAX, PCB_EDIT_FRAME::OnFileHistory ) EVT_MENU( ID_FILE_LIST_CLEAR, PCB_EDIT_FRAME::OnClearFileHistory ) EVT_MENU( ID_GEN_EXPORT_FILE_GENCADFORMAT, PCB_EDIT_FRAME::ExportToGenCAD ) EVT_MENU( ID_GEN_EXPORT_FILE_VRML, PCB_EDIT_FRAME::OnExportVRML ) EVT_MENU( ID_GEN_EXPORT_FILE_IDF3, PCB_EDIT_FRAME::OnExportIDF3 ) EVT_MENU( ID_GEN_EXPORT_FILE_STEP, PCB_EDIT_FRAME::OnExportSTEP ) EVT_MENU( ID_GEN_EXPORT_FILE_HYPERLYNX, PCB_EDIT_FRAME::OnExportHyperlynx ) EVT_MENU( ID_MENU_EXPORT_FOOTPRINTS_TO_LIBRARY, PCB_EDIT_FRAME::Process_Special_Functions ) EVT_MENU( ID_MENU_EXPORT_FOOTPRINTS_TO_NEW_LIBRARY, PCB_EDIT_FRAME::Process_Special_Functions ) EVT_MENU( wxID_EXIT, PCB_EDIT_FRAME::OnQuit ) EVT_MENU( wxID_CLOSE, PCB_EDIT_FRAME::OnQuit ) // menu Postprocess EVT_MENU( ID_PCB_GEN_CMP_FILE, PCB_EDIT_FRAME::RecreateCmpFileFromBoard ) // Horizontal toolbar EVT_TOOL( ID_GEN_PLOT_SVG, PCB_EDIT_FRAME::ExportSVG ) EVT_TOOL( ID_AUX_TOOLBAR_PCB_SELECT_AUTO_WIDTH, PCB_EDIT_FRAME::Tracks_and_Vias_Size_Event ) EVT_COMBOBOX( ID_TOOLBARH_PCB_SELECT_LAYER, PCB_EDIT_FRAME::Process_Special_Functions ) EVT_CHOICE( ID_AUX_TOOLBAR_PCB_TRACK_WIDTH, PCB_EDIT_FRAME::Tracks_and_Vias_Size_Event ) EVT_CHOICE( ID_AUX_TOOLBAR_PCB_VIA_SIZE, PCB_EDIT_FRAME::Tracks_and_Vias_Size_Event ) // Tracks and vias sizes general options EVT_MENU_RANGE( ID_POPUP_PCB_SELECT_WIDTH_START_RANGE, ID_POPUP_PCB_SELECT_WIDTH_END_RANGE, PCB_EDIT_FRAME::Tracks_and_Vias_Size_Event ) // User interface update event handlers. EVT_UPDATE_UI( ID_TOOLBARH_PCB_SELECT_LAYER, PCB_EDIT_FRAME::OnUpdateLayerSelectBox ) EVT_UPDATE_UI( ID_AUX_TOOLBAR_PCB_TRACK_WIDTH, PCB_EDIT_FRAME::OnUpdateSelectTrackWidth ) EVT_UPDATE_UI( ID_AUX_TOOLBAR_PCB_VIA_SIZE, PCB_EDIT_FRAME::OnUpdateSelectViaSize ) EVT_UPDATE_UI( ID_AUX_TOOLBAR_PCB_SELECT_AUTO_WIDTH, PCB_EDIT_FRAME::OnUpdateSelectAutoWidth ) EVT_UPDATE_UI_RANGE( ID_POPUP_PCB_SELECT_WIDTH1, ID_POPUP_PCB_SELECT_WIDTH8, PCB_EDIT_FRAME::OnUpdateSelectTrackWidth ) EVT_UPDATE_UI_RANGE( ID_POPUP_PCB_SELECT_VIASIZE1, ID_POPUP_PCB_SELECT_VIASIZE8, PCB_EDIT_FRAME::OnUpdateSelectViaSize ) // Drop files event EVT_DROP_FILES( PCB_EDIT_FRAME::OnDropFiles ) END_EVENT_TABLE() PCB_EDIT_FRAME::PCB_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) : PCB_BASE_EDIT_FRAME( aKiway, aParent, FRAME_PCB_EDITOR, _( "PCB Editor" ), wxDefaultPosition, wxDefaultSize, KICAD_DEFAULT_DRAWFRAME_STYLE, PCB_EDIT_FRAME_NAME ), m_exportNetlistAction( nullptr ), m_findDialog( nullptr ), m_inspectDrcErrorDlg( nullptr ), m_inspectClearanceDlg( nullptr ), m_inspectConstraintsDlg( nullptr ), m_footprintDiffDlg( nullptr ), m_importProperties( nullptr ) { m_maximizeByDefault = true; m_showBorderAndTitleBlock = true; // true to display sheet references m_SelTrackWidthBox = nullptr; m_SelViaSizeBox = nullptr; m_SelLayerBox = nullptr; m_show_layer_manager_tools = true; m_supportsAutoSave = true; m_probingSchToPcb = false; m_show_search = false; m_show_net_inspector = false; // We don't know what state board was in when it was last saved, so we have to // assume dirty m_ZoneFillsDirty = true; m_aboutTitle = _HKI( "KiCad PCB Editor" ); // Must be created before the menus are created. if( ADVANCED_CFG::GetCfg().m_ShowPcbnewExportNetlist ) m_exportNetlistAction = new TOOL_ACTION( "pcbnew.EditorControl.exportNetlist", AS_GLOBAL, 0, "", _( "Netlist..." ), _( "Export netlist used to update schematics" ) ); // Create GAL canvas auto canvas = new PCB_DRAW_PANEL_GAL( this, -1, wxPoint( 0, 0 ), m_frameSize, GetGalDisplayOptions(), EDA_DRAW_PANEL_GAL::GAL_FALLBACK ); SetCanvas( canvas ); SetBoard( new BOARD() ); wxIcon icon; wxIconBundle icon_bundle; icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_pcbnew, 48 ) ); icon_bundle.AddIcon( icon ); icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_pcbnew, 128 ) ); icon_bundle.AddIcon( icon ); icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_pcbnew, 256 ) ); icon_bundle.AddIcon( icon ); icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_pcbnew_32 ) ); icon_bundle.AddIcon( icon ); icon.CopyFromBitmap( KiBitmap( BITMAPS::icon_pcbnew_16 ) ); icon_bundle.AddIcon( icon ); SetIcons( icon_bundle ); // LoadSettings() *after* creating m_LayersManager, because LoadSettings() // initialize parameters in m_LayersManager LoadSettings( config() ); SetScreen( new PCB_SCREEN( GetPageSettings().GetSizeIU( pcbIUScale.IU_PER_MILS ) ) ); // PCB drawings start in the upper left corner. GetScreen()->m_Center = false; setupTools(); setupUIConditions(); ReCreateMenuBar(); ReCreateHToolbar(); ReCreateAuxiliaryToolbar(); ReCreateVToolbar(); ReCreateOptToolbar(); #ifdef KICAD_IPC_API wxTheApp->Bind( EDA_EVT_PLUGIN_AVAILABILITY_CHANGED, &PCB_EDIT_FRAME::onPluginAvailabilityChanged, this ); #endif m_propertiesPanel = new PCB_PROPERTIES_PANEL( this, this ); float proportion = GetPcbNewSettings()->m_AuiPanels.properties_splitter; m_propertiesPanel->SetSplitterProportion( proportion ); m_selectionFilterPanel = new PANEL_SELECTION_FILTER( this ); m_appearancePanel = new APPEARANCE_CONTROLS( this, GetCanvas() ); m_searchPane = new PCB_SEARCH_PANE( this ); m_netInspectorPanel = new PCB_NET_INSPECTOR_PANEL( this, this ); m_auimgr.SetManagedWindow( this ); CreateInfoBar(); unsigned int auiFlags = wxAUI_MGR_DEFAULT; #if !defined( _WIN32 ) // Windows cannot redraw the UI fast enough during a live resize and may lead to all kinds // of graphical glitches. auiFlags |= wxAUI_MGR_LIVE_RESIZE; #endif m_auimgr.SetFlags( auiFlags ); // Rows; layers 4 - 6 m_auimgr.AddPane( m_mainToolBar, EDA_PANE().HToolbar().Name( wxS( "MainToolbar" ) ) .Top().Layer( 6 ) ); m_auimgr.AddPane( m_auxiliaryToolBar, EDA_PANE().HToolbar().Name( wxS( "AuxToolbar" ) ) .Top().Layer( 5 ) ); m_auimgr.AddPane( m_messagePanel, EDA_PANE().Messages().Name( wxS( "MsgPanel" ) ) .Bottom().Layer( 6 ) ); // Columns; layers 1 - 3 m_auimgr.AddPane( m_optionsToolBar, EDA_PANE().VToolbar().Name( wxS( "OptToolbar" ) ) .Left().Layer( 3 ) ); m_auimgr.AddPane( m_drawToolBar, EDA_PANE().VToolbar().Name( wxS( "ToolsToolbar" ) ) .Right().Layer( 3 ) ); m_auimgr.AddPane( m_appearancePanel, EDA_PANE().Name( wxS( "LayersManager" ) ) .Right().Layer( 4 ) .Caption( _( "Appearance" ) ).PaneBorder( false ) .MinSize( FromDIP( 180 ), -1 ).BestSize( FromDIP( 180 ), -1 ) ); m_auimgr.AddPane( m_selectionFilterPanel, EDA_PANE().Name( wxS( "SelectionFilter" ) ) .Right().Layer( 4 ).Position( 2 ) .Caption( _( "Selection Filter" ) ).PaneBorder( false ) .MinSize( FromDIP( 180 ), -1 ).BestSize( FromDIP( 180 ), -1 ) ); m_auimgr.AddPane( m_propertiesPanel, EDA_PANE().Name( PropertiesPaneName() ) .Left().Layer( 5 ) .Caption( _( "Properties" ) ).PaneBorder( false ) .MinSize( FromDIP( wxSize( 240, 60 ) ) ).BestSize( FromDIP( wxSize( 300, 200 ) ) ) ); // Center m_auimgr.AddPane( GetCanvas(), EDA_PANE().Canvas().Name( wxS( "DrawFrame" ) ) .Center() ); m_auimgr.AddPane( m_netInspectorPanel, EDA_PANE() .Name( NetInspectorPanelName() ) .Bottom() .Caption( _( "Net Inspector" ) ) .PaneBorder( false ) .MinSize( FromDIP( wxSize( 240, 60 ) ) ) .BestSize( FromDIP( wxSize( 300, 200 ) ) ) ); m_auimgr.AddPane( m_searchPane, EDA_PANE().Name( SearchPaneName() ) .Bottom() .Caption( _( "Search" ) ).PaneBorder( false ) .MinSize( FromDIP( wxSize ( 180, 60 ) ) ).BestSize( FromDIP( wxSize ( 180, 100 ) ) ) .FloatingSize( FromDIP( wxSize( 480, 200 ) ) ) .DestroyOnClose( false ) ); m_auimgr.GetPane( "LayersManager" ).Show( m_show_layer_manager_tools ); m_auimgr.GetPane( "SelectionFilter" ).Show( m_show_layer_manager_tools ); m_auimgr.GetPane( PropertiesPaneName() ).Show( GetPcbNewSettings()->m_AuiPanels.show_properties ); m_auimgr.GetPane( NetInspectorPanelName() ).Show( m_show_net_inspector ); m_auimgr.GetPane( SearchPaneName() ).Show( m_show_search ); // The selection filter doesn't need to grow in the vertical direction when docked m_auimgr.GetPane( "SelectionFilter" ).dock_proportion = 0; FinishAUIInitialization(); if( PCBNEW_SETTINGS* settings = dynamic_cast( config() ) ) { if( settings->m_AuiPanels.right_panel_width > 0 ) { wxAuiPaneInfo& layersManager = m_auimgr.GetPane( wxS( "LayersManager" ) ); SetAuiPaneSize( m_auimgr, layersManager, settings->m_AuiPanels.right_panel_width, -1 ); } if( settings->m_AuiPanels.properties_panel_width > 0 && m_propertiesPanel ) { wxAuiPaneInfo& propertiesPanel = m_auimgr.GetPane( PropertiesPaneName() ); SetAuiPaneSize( m_auimgr, propertiesPanel, settings->m_AuiPanels.properties_panel_width, -1 ); } if( settings->m_AuiPanels.search_panel_height > 0 && ( settings->m_AuiPanels.search_panel_dock_direction == wxAUI_DOCK_TOP || settings->m_AuiPanels.search_panel_dock_direction == wxAUI_DOCK_BOTTOM ) ) { wxAuiPaneInfo& searchPane = m_auimgr.GetPane( SearchPaneName() ); searchPane.Direction( settings->m_AuiPanels.search_panel_dock_direction ); SetAuiPaneSize( m_auimgr, searchPane, -1, settings->m_AuiPanels.search_panel_height ); } else if( settings->m_AuiPanels.search_panel_width > 0 && ( settings->m_AuiPanels.search_panel_dock_direction == wxAUI_DOCK_LEFT || settings->m_AuiPanels.search_panel_dock_direction == wxAUI_DOCK_RIGHT ) ) { wxAuiPaneInfo& searchPane = m_auimgr.GetPane( SearchPaneName() ); searchPane.Direction( settings->m_AuiPanels.search_panel_dock_direction ); SetAuiPaneSize( m_auimgr, searchPane, settings->m_AuiPanels.search_panel_width, -1 ); } m_appearancePanel->SetTabIndex( settings->m_AuiPanels.appearance_panel_tab ); } GetToolManager()->PostAction( ACTIONS::zoomFitScreen ); // This is used temporarily to fix a client size issue on GTK that causes zoom to fit // to calculate the wrong zoom size. See PCB_EDIT_FRAME::onSize(). Bind( wxEVT_SIZE, &PCB_EDIT_FRAME::onSize, this ); // Redraw netnames (so that they fall within the current viewport) after the viewport // has stopped changing. Redrawing them without the timer moves them smoothly with scrolling, // making it look like the tracks are being dragged -- which we don't want. m_redrawNetnamesTimer.SetOwner( this ); Connect( wxEVT_TIMER, wxTimerEventHandler( PCB_EDIT_FRAME::redrawNetnames ), nullptr, this ); Bind( wxEVT_IDLE, [this]( wxIdleEvent& aEvent ) { if( GetCanvas()->GetView()->GetViewport() != m_lastViewport ) { m_lastViewport = GetCanvas()->GetView()->GetViewport(); m_redrawNetnamesTimer.StartOnce( 500 ); } // Do not forget to pass the Idle event to other clients: aEvent.Skip(); } ); resolveCanvasType(); setupUnits( config() ); // Ensure the DRC engine is initialized so that constraints can be resolved even before a // board is loaded or saved try { m_toolManager->GetTool()->GetDRCEngine()->InitEngine( wxFileName() ); } catch( PARSE_ERROR& ) { } // Ensure the Python interpreter is up to date with its environment variables PythonSyncEnvironmentVariables(); PythonSyncProjectName(); // Sync action plugins in case they changed since the last time the frame opened GetToolManager()->RunAction( ACTIONS::pluginsReload ); #ifdef KICAD_IPC_API m_apiHandler = std::make_unique( this ); Pgm().GetApiServer().RegisterHandler( m_apiHandler.get() ); #endif GetCanvas()->SwitchBackend( m_canvasType ); ActivateGalCanvas(); // Default shutdown reason until a file is loaded KIPLATFORM::APP::SetShutdownBlockReason( this, _( "New PCB file is unsaved" ) ); // disable Export STEP item if kicad2step does not exist wxString strK2S = Pgm().GetExecutablePath(); #ifdef __WXMAC__ if( strK2S.Find( wxT( "pcbnew.app" ) ) != wxNOT_FOUND ) { // On macOS, we have standalone applications inside the main bundle, so we handle that here: strK2S += wxT( "../../" ); } strK2S += wxT( "Contents/MacOS/" ); #endif wxFileName appK2S( strK2S, wxT( "kicad2step" ) ); #ifdef _WIN32 appK2S.SetExt( wxT( "exe" ) ); #endif // Ensure the window is on top Raise(); // if( !appK2S.FileExists() ) // GetMenuBar()->FindItem( ID_GEN_EXPORT_FILE_STEP )->Enable( false ); // AUI doesn't refresh properly on wxMac after changes in eb7dc6dd, so force it to #ifdef __WXMAC__ if( Kiface().IsSingle() ) { CallAfter( [this]() { m_appearancePanel->OnBoardChanged(); } ); } #endif // Register a call to update the toolbar sizes. It can't be done immediately because // it seems to require some sizes calculated that aren't yet (at least on GTK). CallAfter( [this]() { // Ensure the controls on the toolbars all are correctly sized UpdateToolbarControlSizes(); } ); if( ADVANCED_CFG::GetCfg().m_ShowEventCounters ) { m_eventCounterTimer = new wxTimer( this ); Bind( wxEVT_TIMER, [&]( wxTimerEvent& aEvent ) { GetCanvas()->m_PaintEventCounter->Show(); GetCanvas()->m_PaintEventCounter->Reset(); KIGFX::WX_VIEW_CONTROLS* vc = static_cast( GetCanvas()->GetViewControls() ); vc->m_MotionEventCounter->Show(); vc->m_MotionEventCounter->Reset(); }, m_eventCounterTimer->GetId() ); m_eventCounterTimer->Start( 1000 ); } Bind( EDA_EVT_CLOSE_DIALOG_BOOK_REPORTER, &PCB_EDIT_FRAME::onCloseModelessBookReporterDialogs, this ); m_acceptedExts.emplace( FILEEXT::KiCadPcbFileExtension, &PCB_ACTIONS::ddAppendBoard ); m_acceptedExts.emplace( FILEEXT::LegacyPcbFileExtension, &PCB_ACTIONS::ddAppendBoard ); DragAcceptFiles( true ); } PCB_EDIT_FRAME::~PCB_EDIT_FRAME() { ScriptingOnDestructPcbEditFrame( this ); if( ADVANCED_CFG::GetCfg().m_ShowEventCounters ) { // Stop the timer during destruction early to avoid potential event race conditions (that // do happen on windows) m_eventCounterTimer->Stop(); delete m_eventCounterTimer; } #ifdef KICAD_IPC_API Pgm().GetApiServer().DeregisterHandler( m_apiHandler.get() ); wxTheApp->Unbind( EDA_EVT_PLUGIN_AVAILABILITY_CHANGED, &PCB_EDIT_FRAME::onPluginAvailabilityChanged, this ); #endif // Close modeless dialogs wxWindow* open_dlg = wxWindow::FindWindowByName( DIALOG_DRC_WINDOW_NAME ); if( open_dlg ) open_dlg->Close( true ); // Shutdown all running tools if( m_toolManager ) m_toolManager->ShutdownAllTools(); if( GetBoard() ) GetBoard()->RemoveAllListeners(); delete m_selectionFilterPanel; delete m_appearancePanel; delete m_exportNetlistAction; delete m_propertiesPanel; delete m_netInspectorPanel; } void PCB_EDIT_FRAME::SetBoard( BOARD* aBoard, bool aBuildConnectivity, PROGRESS_REPORTER* aReporter ) { if( m_pcb ) m_pcb->ClearProject(); PCB_BASE_EDIT_FRAME::SetBoard( aBoard, aReporter ); aBoard->SetProject( &Prj() ); if( aBuildConnectivity ) aBoard->BuildConnectivity(); // reload the drawing-sheet SetPageSettings( aBoard->GetPageSettings() ); } BOARD_ITEM_CONTAINER* PCB_EDIT_FRAME::GetModel() const { return m_pcb; } void PCB_EDIT_FRAME::redrawNetnames( wxTimerEvent& aEvent ) { bool needs_refresh = false; // Don't stomp on the auto-save timer event. if( aEvent.GetId() == ID_AUTO_SAVE_TIMER ) { aEvent.Skip(); return; } PCBNEW_SETTINGS* cfg = dynamic_cast( Kiface().KifaceSettings() ); if( !cfg || cfg->m_Display.m_NetNames < 2 ) return; KIGFX::VIEW* view = GetCanvas()->GetView(); double scale = view->GetScale(); for( PCB_TRACK* track : GetBoard()->Tracks() ) { double lod = track->ViewGetLOD( GetNetnameLayer( track->GetLayer() ), view ); if( lod < scale ) continue; if( lod != track->GetCachedLOD() || scale != track->GetCachedScale() ) { view->Update( track, KIGFX::REPAINT ); needs_refresh = true; track->SetCachedLOD( lod ); track->SetCachedScale( scale ); } } if( needs_refresh ) GetCanvas()->Refresh(); } void PCB_EDIT_FRAME::SetPageSettings( const PAGE_INFO& aPageSettings ) { PCB_BASE_FRAME::SetPageSettings( aPageSettings ); // Prepare drawing-sheet template DS_PROXY_VIEW_ITEM* drawingSheet = new DS_PROXY_VIEW_ITEM( pcbIUScale, &m_pcb->GetPageSettings(), m_pcb->GetProject(), &m_pcb->GetTitleBlock(), &m_pcb->GetProperties() ); drawingSheet->SetSheetName( std::string( GetScreenDesc().mb_str() ) ); drawingSheet->SetSheetPath( std::string( GetFullScreenDesc().mb_str() ) ); // A board is not like a schematic having a main page and sub sheets. // So for the drawing sheet, use only the first page option to display items drawingSheet->SetIsFirstPage( true ); BASE_SCREEN* screen = GetScreen(); if( screen != nullptr ) { drawingSheet->SetPageNumber(TO_UTF8( screen->GetPageNumber() ) ); drawingSheet->SetSheetCount( screen->GetPageCount() ); } if( BOARD* board = GetBoard() ) drawingSheet->SetFileName( TO_UTF8( board->GetFileName() ) ); // PCB_DRAW_PANEL_GAL takes ownership of the drawing-sheet GetCanvas()->SetDrawingSheet( drawingSheet ); } bool PCB_EDIT_FRAME::IsContentModified() const { return GetScreen() && GetScreen()->IsContentModified(); } SELECTION& PCB_EDIT_FRAME::GetCurrentSelection() { return m_toolManager->GetTool()->GetSelection(); } void PCB_EDIT_FRAME::setupTools() { // Create the manager and dispatcher & route draw panel events to the dispatcher m_toolManager = new TOOL_MANAGER; m_toolManager->SetEnvironment( m_pcb, GetCanvas()->GetView(), GetCanvas()->GetViewControls(), config(), this ); m_actions = new PCB_ACTIONS(); m_toolDispatcher = new TOOL_DISPATCHER( m_toolManager ); // Register tools m_toolManager->RegisterTool( new COMMON_CONTROL ); m_toolManager->RegisterTool( new COMMON_TOOLS ); m_toolManager->RegisterTool( new PCB_SELECTION_TOOL ); m_toolManager->RegisterTool( new ZOOM_TOOL ); m_toolManager->RegisterTool( new PCB_PICKER_TOOL ); m_toolManager->RegisterTool( new ROUTER_TOOL ); m_toolManager->RegisterTool( new EDIT_TOOL ); m_toolManager->RegisterTool( new PCB_EDIT_TABLE_TOOL ); m_toolManager->RegisterTool( new GLOBAL_EDIT_TOOL ); m_toolManager->RegisterTool( new PAD_TOOL ); m_toolManager->RegisterTool( new DRAWING_TOOL ); m_toolManager->RegisterTool( new PCB_POINT_EDITOR ); m_toolManager->RegisterTool( new PCB_CONTROL ); m_toolManager->RegisterTool( new BOARD_EDITOR_CONTROL ); m_toolManager->RegisterTool( new BOARD_INSPECTION_TOOL ); m_toolManager->RegisterTool( new BOARD_REANNOTATE_TOOL ); m_toolManager->RegisterTool( new ALIGN_DISTRIBUTE_TOOL ); m_toolManager->RegisterTool( new MICROWAVE_TOOL ); m_toolManager->RegisterTool( new POSITION_RELATIVE_TOOL ); m_toolManager->RegisterTool( new ZONE_FILLER_TOOL ); m_toolManager->RegisterTool( new AUTOPLACE_TOOL ); m_toolManager->RegisterTool( new DRC_TOOL ); m_toolManager->RegisterTool( new PCB_VIEWER_TOOLS ); m_toolManager->RegisterTool( new CONVERT_TOOL ); m_toolManager->RegisterTool( new GROUP_TOOL ); m_toolManager->RegisterTool( new GENERATOR_TOOL ); m_toolManager->RegisterTool( new SCRIPTING_TOOL ); m_toolManager->RegisterTool( new PROPERTIES_TOOL ); m_toolManager->InitTools(); for( TOOL_BASE* tool : m_toolManager->Tools() ) { if( PCB_TOOL_BASE* pcbTool = dynamic_cast( tool ) ) pcbTool->SetIsBoardEditor( true ); } // Run the selection tool, it is supposed to be always active m_toolManager->InvokeTool( "pcbnew.InteractiveSelection" ); } void PCB_EDIT_FRAME::setupUIConditions() { PCB_BASE_EDIT_FRAME::setupUIConditions(); ACTION_MANAGER* mgr = m_toolManager->GetActionManager(); PCB_EDITOR_CONDITIONS cond( this ); auto undoCond = [ this ] (const SELECTION& aSel ) { DRAWING_TOOL* drawingTool = m_toolManager->GetTool(); if( drawingTool && drawingTool->GetDrawingMode() != DRAWING_TOOL::MODE::NONE ) return true; ROUTER_TOOL* routerTool = m_toolManager->GetTool(); if( routerTool && routerTool->RoutingInProgress() ) return true; return GetUndoCommandCount() > 0; }; wxASSERT( mgr ); #define ENABLE( x ) ACTION_CONDITIONS().Enable( x ) #define CHECK( x ) ACTION_CONDITIONS().Check( x ) mgr->SetConditions( ACTIONS::save, ENABLE( SELECTION_CONDITIONS::ShowAlways ) ); mgr->SetConditions( ACTIONS::undo, ENABLE( undoCond ) ); mgr->SetConditions( ACTIONS::redo, ENABLE( cond.RedoAvailable() ) ); mgr->SetConditions( ACTIONS::toggleGrid, CHECK( cond.GridVisible() ) ); mgr->SetConditions( ACTIONS::toggleGridOverrides, CHECK( cond.GridOverrides() ) ); mgr->SetConditions( ACTIONS::toggleCursorStyle, CHECK( cond.FullscreenCursor() ) ); mgr->SetConditions( ACTIONS::togglePolarCoords, CHECK( cond.PolarCoordinates() ) ); mgr->SetConditions( ACTIONS::millimetersUnits, CHECK( cond.Units( EDA_UNITS::MILLIMETRES ) ) ); mgr->SetConditions( ACTIONS::inchesUnits, CHECK( cond.Units( EDA_UNITS::INCHES ) ) ); mgr->SetConditions( ACTIONS::milsUnits, CHECK( cond.Units( EDA_UNITS::MILS ) ) ); mgr->SetConditions( ACTIONS::cut, ENABLE( cond.HasItems() ) ); mgr->SetConditions( ACTIONS::copy, ENABLE( cond.HasItems() ) ); mgr->SetConditions( ACTIONS::paste, ENABLE( SELECTION_CONDITIONS::Idle && cond.NoActiveTool() ) ); mgr->SetConditions( ACTIONS::pasteSpecial, ENABLE( SELECTION_CONDITIONS::Idle && cond.NoActiveTool() ) ); mgr->SetConditions( ACTIONS::selectAll, ENABLE( cond.HasItems() ) ); mgr->SetConditions( ACTIONS::unselectAll, ENABLE( cond.HasItems() ) ); mgr->SetConditions( ACTIONS::doDelete, ENABLE( cond.HasItems() ) ); mgr->SetConditions( ACTIONS::duplicate, ENABLE( cond.HasItems() ) ); mgr->SetConditions( PCB_ACTIONS::group, ENABLE( SELECTION_CONDITIONS::NotEmpty ) ); mgr->SetConditions( PCB_ACTIONS::ungroup, ENABLE( SELECTION_CONDITIONS::HasTypes( { PCB_GROUP_T, PCB_GENERATOR_T } ) ) ); mgr->SetConditions( PCB_ACTIONS::lock, ENABLE( PCB_SELECTION_CONDITIONS::HasUnlockedItems ) ); mgr->SetConditions( PCB_ACTIONS::unlock, ENABLE( PCB_SELECTION_CONDITIONS::HasLockedItems ) ); mgr->SetConditions( PCB_ACTIONS::padDisplayMode, CHECK( !cond.PadFillDisplay() ) ); mgr->SetConditions( PCB_ACTIONS::viaDisplayMode, CHECK( !cond.ViaFillDisplay() ) ); mgr->SetConditions( PCB_ACTIONS::trackDisplayMode, CHECK( !cond.TrackFillDisplay() ) ); mgr->SetConditions( PCB_ACTIONS::graphicsOutlines, CHECK( !cond.GraphicsFillDisplay() ) ); mgr->SetConditions( PCB_ACTIONS::textOutlines, CHECK( !cond.TextFillDisplay() ) ); if( SCRIPTING::IsWxAvailable() ) mgr->SetConditions( PCB_ACTIONS::showPythonConsole, CHECK( cond.ScriptingConsoleVisible() ) ); auto enableZoneControlCondition = [this] ( const SELECTION& ) { return GetBoard() && GetBoard()->GetVisibleElements().Contains( LAYER_ZONES ) && GetDisplayOptions().m_ZoneOpacity > 0.0; }; mgr->SetConditions( PCB_ACTIONS::zoneDisplayFilled, ENABLE( enableZoneControlCondition ) .Check( cond.ZoneDisplayMode( ZONE_DISPLAY_MODE::SHOW_FILLED ) ) ); mgr->SetConditions( PCB_ACTIONS::zoneDisplayOutline, ENABLE( enableZoneControlCondition ) .Check( cond.ZoneDisplayMode( ZONE_DISPLAY_MODE::SHOW_ZONE_OUTLINE ) ) ); mgr->SetConditions( PCB_ACTIONS::zoneDisplayFractured, ENABLE( enableZoneControlCondition ) .Check( cond.ZoneDisplayMode( ZONE_DISPLAY_MODE::SHOW_FRACTURE_BORDERS ) ) ); mgr->SetConditions( PCB_ACTIONS::zoneDisplayTriangulated, ENABLE( enableZoneControlCondition ) .Check( cond.ZoneDisplayMode( ZONE_DISPLAY_MODE::SHOW_TRIANGULATION ) ) ); mgr->SetConditions( ACTIONS::toggleBoundingBoxes, CHECK( cond.BoundingBoxes() ) ); auto constrainedDrawingModeCond = [this]( const SELECTION& ) { return GetPcbNewSettings()->m_Use45DegreeLimit; }; auto boardFlippedCond = [this]( const SELECTION& ) { return GetCanvas() && GetCanvas()->GetView()->IsMirroredX(); }; auto layerManagerCond = [this] ( const SELECTION& ) { return LayerManagerShown(); }; auto propertiesCond = [this] ( const SELECTION& ) { return PropertiesShown(); }; auto netInspectorCond = [this] ( const SELECTION& ) { return NetInspectorShown(); }; auto searchPaneCond = [this] ( const SELECTION& ) { return m_auimgr.GetPane( SearchPaneName() ).IsShown(); }; auto highContrastCond = [this] ( const SELECTION& ) { return GetDisplayOptions().m_ContrastModeDisplay != HIGH_CONTRAST_MODE::NORMAL; }; auto globalRatsnestCond = [this] (const SELECTION& ) { return GetPcbNewSettings()->m_Display.m_ShowGlobalRatsnest; }; auto curvedRatsnestCond = [this] (const SELECTION& ) { return GetPcbNewSettings()->m_Display.m_DisplayRatsnestLinesCurved; }; auto netHighlightCond = [this]( const SELECTION& ) { KIGFX::RENDER_SETTINGS* settings = GetCanvas()->GetView()->GetPainter()->GetSettings(); return !settings->GetHighlightNetCodes().empty(); }; auto enableNetHighlightCond = [this]( const SELECTION& ) { BOARD_INSPECTION_TOOL* tool = m_toolManager->GetTool(); return tool && tool->IsNetHighlightSet(); }; mgr->SetConditions( PCB_ACTIONS::toggleHV45Mode, CHECK( constrainedDrawingModeCond ) ); mgr->SetConditions( ACTIONS::highContrastMode, CHECK( highContrastCond ) ); mgr->SetConditions( PCB_ACTIONS::flipBoard, CHECK( boardFlippedCond ) ); mgr->SetConditions( PCB_ACTIONS::showLayersManager, CHECK( layerManagerCond ) ); mgr->SetConditions( PCB_ACTIONS::showRatsnest, CHECK( globalRatsnestCond ) ); mgr->SetConditions( PCB_ACTIONS::ratsnestLineMode, CHECK( curvedRatsnestCond ) ); mgr->SetConditions( PCB_ACTIONS::toggleNetHighlight, CHECK( netHighlightCond ) .Enable( enableNetHighlightCond ) ); mgr->SetConditions( PCB_ACTIONS::showProperties, CHECK( propertiesCond ) ); mgr->SetConditions( PCB_ACTIONS::showNetInspector, CHECK( netInspectorCond ) ); mgr->SetConditions( PCB_ACTIONS::showSearch, CHECK( searchPaneCond ) ); auto isArcKeepCenterMode = [this]( const SELECTION& ) { return GetPcbNewSettings()->m_ArcEditMode == ARC_EDIT_MODE::KEEP_CENTER_ADJUST_ANGLE_RADIUS; }; auto isArcKeepEndpointMode = [this]( const SELECTION& ) { return GetPcbNewSettings()->m_ArcEditMode == ARC_EDIT_MODE::KEEP_ENDPOINTS_OR_START_DIRECTION; }; mgr->SetConditions( PCB_ACTIONS::pointEditorArcKeepCenter, CHECK( isArcKeepCenterMode ) ); mgr->SetConditions( PCB_ACTIONS::pointEditorArcKeepEndpoint, CHECK( isArcKeepEndpointMode ) ); auto isHighlightMode = [this]( const SELECTION& ) { ROUTER_TOOL* tool = m_toolManager->GetTool(); return tool && tool->GetRouterMode() == PNS::RM_MarkObstacles; }; auto isShoveMode = [this]( const SELECTION& ) { ROUTER_TOOL* tool = m_toolManager->GetTool(); return tool && tool->GetRouterMode() == PNS::RM_Shove; }; auto isWalkaroundMode = [this]( const SELECTION& ) { ROUTER_TOOL* tool = m_toolManager->GetTool(); return tool && tool->GetRouterMode() == PNS::RM_Walkaround; }; mgr->SetConditions( PCB_ACTIONS::routerHighlightMode, CHECK( isHighlightMode ) ); mgr->SetConditions( PCB_ACTIONS::routerShoveMode, CHECK( isShoveMode ) ); mgr->SetConditions( PCB_ACTIONS::routerWalkaroundMode, CHECK( isWalkaroundMode ) ); auto haveNetCond = [] ( const SELECTION& aSel ) { for( EDA_ITEM* item : aSel ) { if( BOARD_CONNECTED_ITEM* bci = dynamic_cast( item ) ) { if( bci->GetNetCode() > 0 ) return true; } } return false; }; mgr->SetConditions( PCB_ACTIONS::showNetInRatsnest, ENABLE( haveNetCond ) ); mgr->SetConditions( PCB_ACTIONS::hideNetInRatsnest, ENABLE( haveNetCond ) ); mgr->SetConditions( PCB_ACTIONS::highlightNet, ENABLE( SELECTION_CONDITIONS::ShowAlways ) ); mgr->SetConditions( PCB_ACTIONS::highlightNetSelection, ENABLE( SELECTION_CONDITIONS::ShowAlways ) ); mgr->SetConditions( PCB_ACTIONS::selectNet, ENABLE( SELECTION_CONDITIONS::OnlyTypes( { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T } ) ) ); mgr->SetConditions( PCB_ACTIONS::deselectNet, ENABLE( SELECTION_CONDITIONS::OnlyTypes( { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T } ) ) ); mgr->SetConditions( PCB_ACTIONS::selectUnconnected, ENABLE( SELECTION_CONDITIONS::OnlyTypes( { PCB_FOOTPRINT_T, PCB_PAD_T, PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T } ) ) ); mgr->SetConditions( PCB_ACTIONS::selectSameSheet, ENABLE( SELECTION_CONDITIONS::OnlyTypes( { PCB_FOOTPRINT_T } ) ) ); mgr->SetConditions( PCB_ACTIONS::selectOnSchematic, ENABLE( SELECTION_CONDITIONS::HasTypes( { PCB_PAD_T, PCB_FOOTPRINT_T, PCB_GROUP_T } ) ) ); SELECTION_CONDITION singleZoneCond = SELECTION_CONDITIONS::Count( 1 ) && SELECTION_CONDITIONS::OnlyTypes( { PCB_ZONE_T } ); SELECTION_CONDITION zoneMergeCond = SELECTION_CONDITIONS::MoreThan( 1 ) && SELECTION_CONDITIONS::OnlyTypes( { PCB_ZONE_T } ); mgr->SetConditions( PCB_ACTIONS::zoneDuplicate, ENABLE( singleZoneCond ) ); mgr->SetConditions( PCB_ACTIONS::drawZoneCutout, ENABLE( singleZoneCond ) ); mgr->SetConditions( PCB_ACTIONS::drawSimilarZone, ENABLE( singleZoneCond ) ); mgr->SetConditions( PCB_ACTIONS::zoneMerge, ENABLE( zoneMergeCond ) ); mgr->SetConditions( PCB_ACTIONS::toggleHV45Mode, CHECK( cond.Get45degMode() ) ); #define CURRENT_TOOL( action ) mgr->SetConditions( action, CHECK( cond.CurrentTool( action ) ) ) // These tools can be used at any time to inspect the board CURRENT_TOOL( ACTIONS::zoomTool ); CURRENT_TOOL( ACTIONS::measureTool ); CURRENT_TOOL( ACTIONS::selectionTool ); CURRENT_TOOL( PCB_ACTIONS::localRatsnestTool ); auto isDRCIdle = [this] ( const SELECTION& ) { DRC_TOOL* tool = m_toolManager->GetTool(); return !( tool && tool->IsDRCRunning() ); }; #define CURRENT_EDIT_TOOL( action ) \ mgr->SetConditions( action, ACTION_CONDITIONS().Check( cond.CurrentTool( action ) ) \ .Enable( isDRCIdle ) ) // These tools edit the board, so they must be disabled during some operations CURRENT_EDIT_TOOL( ACTIONS::deleteTool ); CURRENT_EDIT_TOOL( PCB_ACTIONS::placeFootprint ); CURRENT_EDIT_TOOL( PCB_ACTIONS::routeSingleTrack); CURRENT_EDIT_TOOL( PCB_ACTIONS::routeDiffPair ); CURRENT_EDIT_TOOL( PCB_ACTIONS::tuneSingleTrack); CURRENT_EDIT_TOOL( PCB_ACTIONS::tuneDiffPair ); CURRENT_EDIT_TOOL( PCB_ACTIONS::tuneSkew ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawVia ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawZone ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawRuleArea ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawLine ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawRectangle ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawCircle ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawArc ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawPolygon ); CURRENT_EDIT_TOOL( PCB_ACTIONS::placeReferenceImage ); CURRENT_EDIT_TOOL( PCB_ACTIONS::placeText ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawTextBox ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawTable ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawAlignedDimension ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawOrthogonalDimension ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawCenterDimension ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawRadialDimension ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawLeader ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drillOrigin ); CURRENT_EDIT_TOOL( PCB_ACTIONS::gridSetOrigin ); CURRENT_EDIT_TOOL( PCB_ACTIONS::microwaveCreateLine ); CURRENT_EDIT_TOOL( PCB_ACTIONS::microwaveCreateGap ); CURRENT_EDIT_TOOL( PCB_ACTIONS::microwaveCreateStub ); CURRENT_EDIT_TOOL( PCB_ACTIONS::microwaveCreateStubArc ); CURRENT_EDIT_TOOL( PCB_ACTIONS::microwaveCreateFunctionShape ); #undef CURRENT_TOOL #undef CURRENT_EDIT_TOOL #undef ENABLE #undef CHECK } void PCB_EDIT_FRAME::OnQuit( wxCommandEvent& event ) { if( event.GetId() == wxID_EXIT ) Kiway().OnKiCadExit(); if( event.GetId() == wxID_CLOSE || Kiface().IsSingle() ) Close( false ); } void PCB_EDIT_FRAME::ResolveDRCExclusions( bool aCreateMarkers ) { BOARD_COMMIT commit( this ); for( PCB_MARKER* marker : GetBoard()->ResolveDRCExclusions( aCreateMarkers ) ) { if( marker->GetMarkerType() == MARKER_BASE::MARKER_DRAWING_SHEET ) marker->GetRCItem()->SetItems( GetCanvas()->GetDrawingSheet() ); commit.Add( marker ); } commit.Push( wxEmptyString, SKIP_UNDO | SKIP_SET_DIRTY ); for( PCB_MARKER* marker : GetBoard()->Markers() ) { if( marker->GetSeverity() == RPT_SEVERITY_EXCLUSION ) GetCanvas()->GetView()->Update( marker ); } GetBoard()->UpdateRatsnestExclusions(); } bool PCB_EDIT_FRAME::canCloseWindow( wxCloseEvent& aEvent ) { // Shutdown blocks must be determined and vetoed as early as possible if( KIPLATFORM::APP::SupportsShutdownBlockReason() && aEvent.GetId() == wxEVT_QUERY_END_SESSION && IsContentModified() ) { return false; } ZONE_FILLER_TOOL* zoneFillerTool = m_toolManager->GetTool(); if( zoneFillerTool->IsBusy() ) { wxBell(); if( wxWindow* reporter = dynamic_cast( zoneFillerTool->GetProgressReporter() ) ) reporter->ShowWithEffect( wxSHOW_EFFECT_EXPAND ); return false; } if( Kiface().IsSingle() ) { auto* fpEditor = (FOOTPRINT_EDIT_FRAME*) Kiway().Player( FRAME_FOOTPRINT_EDITOR, false ); if( fpEditor && !fpEditor->Close() ) // Can close footprint editor? return false; auto* fpViewer = (FOOTPRINT_VIEWER_FRAME*) Kiway().Player( FRAME_FOOTPRINT_VIEWER, false ); if( fpViewer && !fpViewer->Close() ) // Can close footprint viewer? return false; // FOOTPRINT_CHOOSER_FRAME is always modal so this shouldn't come up, but better safe than // sorry. auto* chooser = (FOOTPRINT_CHOOSER_FRAME*) Kiway().Player( FRAME_FOOTPRINT_CHOOSER, false ); if( chooser && !chooser->Close() ) // Can close footprint chooser? return false; } else { auto* fpEditor = (FOOTPRINT_EDIT_FRAME*) Kiway().Player( FRAME_FOOTPRINT_EDITOR, false ); if( fpEditor && fpEditor->IsCurrentFPFromBoard() ) { if( !fpEditor->CanCloseFPFromBoard( true ) ) return false; } } if( IsContentModified() ) { wxFileName fileName = GetBoard()->GetFileName(); wxString msg = _( "Save changes to '%s' before closing?" ); if( !HandleUnsavedChanges( this, wxString::Format( msg, fileName.GetFullName() ), [&]() -> bool { return Files_io_from_id( ID_SAVE_BOARD ); } ) ) { return false; } } return PCB_BASE_EDIT_FRAME::canCloseWindow( aEvent ); } void PCB_EDIT_FRAME::doCloseWindow() { // On Windows 7 / 32 bits, on OpenGL mode only, Pcbnew crashes // when closing this frame if a footprint was selected, and the footprint editor called // to edit this footprint, and when closing pcbnew if this footprint is still selected // See https://bugs.launchpad.net/kicad/+bug/1655858 // I think this is certainly a OpenGL event fired after frame deletion, so this workaround // avoid the crash (JPC) GetCanvas()->SetEvtHandlerEnabled( false ); GetCanvas()->StopDrawing(); // Clean up mode-less dialogs. Unbind( EDA_EVT_CLOSE_DIALOG_BOOK_REPORTER, &PCB_EDIT_FRAME::onCloseModelessBookReporterDialogs, this ); wxWindow* open_dlg = wxWindow::FindWindowByName( DIALOG_DRC_WINDOW_NAME ); if( open_dlg ) open_dlg->Close( true ); if( m_findDialog ) { m_findDialog->Destroy(); m_findDialog = nullptr; } if( m_inspectDrcErrorDlg ) { m_inspectDrcErrorDlg->Destroy(); m_inspectDrcErrorDlg = nullptr; } if( m_inspectClearanceDlg ) { m_inspectClearanceDlg->Destroy(); m_inspectClearanceDlg = nullptr; } if( m_inspectConstraintsDlg ) { m_inspectConstraintsDlg->Destroy(); m_inspectConstraintsDlg = nullptr; } if( m_footprintDiffDlg ) { m_footprintDiffDlg->Destroy(); m_footprintDiffDlg = nullptr; } // Delete the auto save file if it exists. wxFileName fn = GetBoard()->GetFileName(); // Auto save file name is the normal file name prefixed with 'FILEEXT::AutoSaveFilePrefix'. fn.SetName( FILEEXT::AutoSaveFilePrefix + fn.GetName() ); // When the auto save feature does not have write access to the board file path, it falls // back to a platform specific user temporary file path. if( !fn.IsOk() || !fn.IsDirWritable() ) fn.SetPath( wxFileName::GetTempDir() ); wxLogTrace( traceAutoSave, wxT( "Deleting auto save file <" ) + fn.GetFullPath() + wxT( ">" ) ); // Remove the auto save file on a normal close of Pcbnew. if( fn.FileExists() && !wxRemoveFile( fn.GetFullPath() ) ) { wxLogTrace( traceAutoSave, wxT( "The auto save file could not be removed!" ) ); } // Make sure local settings are persisted SaveProjectLocalSettings(); // Do not show the layer manager during closing to avoid flicker // on some platforms (Windows) that generate useless redraw of items in // the Layer Manager if( m_show_layer_manager_tools ) { m_auimgr.GetPane( wxS( "LayersManager" ) ).Show( false ); m_auimgr.GetPane( wxS( "TabbedPanel" ) ).Show( false ); } // Unlink the old project if needed GetBoard()->ClearProject(); // Delete board structs and undo/redo lists, to avoid crash on exit // when deleting some structs (mainly in undo/redo lists) too late Clear_Pcb( false, true ); // do not show the window because ScreenPcb will be deleted and we do not // want any paint event Show( false ); PCB_BASE_EDIT_FRAME::doCloseWindow(); } void PCB_EDIT_FRAME::ActivateGalCanvas() { PCB_BASE_EDIT_FRAME::ActivateGalCanvas(); GetCanvas()->UpdateColors(); GetCanvas()->Refresh(); } void PCB_EDIT_FRAME::ShowBoardSetupDialog( const wxString& aInitialPage ) { std::unique_lock dialogLock( DIALOG_BOARD_SETUP::g_Mutex, std::try_to_lock ); // One DIALOG_BOARD_SETUP dialog at a time. if( !dialogLock.owns_lock() ) return; // Make sure everything's up-to-date GetBoard()->BuildListOfNets(); DIALOG_BOARD_SETUP dlg( this ); if( !aInitialPage.IsEmpty() ) dlg.SetInitialPage( aInitialPage, wxEmptyString ); // QuasiModal required for Scintilla auto-complete if( dlg.ShowQuasiModal() == wxID_OK ) { GetBoard()->SynchronizeNetsAndNetClasses( true ); // We don't know if anything was modified, so err on the side of requiring a save OnModify(); Kiway().CommonSettingsChanged( false, true ); Prj().IncrementTextVarsTicker(); Prj().IncrementNetclassesTicker(); PCBNEW_SETTINGS* settings = GetPcbNewSettings(); static LSET maskAndPasteLayers = LSET( 4, F_Mask, F_Paste, B_Mask, B_Paste ); GetCanvas()->GetView()->UpdateAllItemsConditionally( [&]( KIGFX::VIEW_ITEM* aItem ) -> int { BOARD_ITEM* item = dynamic_cast( aItem ); int flags = 0; if( !item ) return flags; if( item->Type() == PCB_VIA_T || item->Type() == PCB_PAD_T ) { // Note: KIGFX::REPAINT isn't enough for things that go from invisible // to visible as they won't be found in the view layer's itemset for // re-painting. if( ( GetBoard()->GetVisibleLayers() & maskAndPasteLayers ).any() ) flags |= KIGFX::ALL; } if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T || item->Type() == PCB_VIA_T ) { if( settings->m_Display.m_TrackClearance == SHOW_WITH_VIA_ALWAYS ) flags |= KIGFX::REPAINT; } if( item->Type() == PCB_PAD_T ) { if( settings->m_Display.m_PadClearance ) flags |= KIGFX::REPAINT; } EDA_TEXT* text = dynamic_cast( aItem ); if( text && text->HasTextVars() ) { text->ClearRenderCache(); text->ClearBoundingBoxCache(); flags |= KIGFX::GEOMETRY | KIGFX::REPAINT; } return flags; } ); GetCanvas()->Refresh(); UpdateUserInterface(); ReCreateAuxiliaryToolbar(); m_toolManager->ResetTools( TOOL_BASE::MODEL_RELOAD ); //this event causes the routing tool to reload its design rules information TOOL_EVENT toolEvent( TC_COMMAND, TA_MODEL_CHANGE, AS_ACTIVE ); toolEvent.SetHasPosition( false ); m_toolManager->ProcessEvent( toolEvent ); } GetCanvas()->SetFocus(); } void PCB_EDIT_FRAME::LoadSettings( APP_SETTINGS_BASE* aCfg ) { PCB_BASE_FRAME::LoadSettings( aCfg ); PCBNEW_SETTINGS* cfg = dynamic_cast( aCfg ); wxASSERT( cfg ); if( cfg ) { m_show_layer_manager_tools = cfg->m_AuiPanels.show_layer_manager; m_show_search = cfg->m_AuiPanels.show_search; m_show_net_inspector = cfg->m_AuiPanels.show_net_inspector; } } void PCB_EDIT_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg ) { PCB_BASE_FRAME::SaveSettings( aCfg ); PCBNEW_SETTINGS* cfg = dynamic_cast( aCfg ); wxASSERT( cfg ); if( cfg ) { cfg->m_AuiPanels.show_layer_manager = m_show_layer_manager_tools; if( m_propertiesPanel ) { cfg->m_AuiPanels.show_properties = m_propertiesPanel->IsShownOnScreen(); cfg->m_AuiPanels.properties_panel_width = m_propertiesPanel->GetSize().x; cfg->m_AuiPanels.properties_splitter = m_propertiesPanel->SplitterProportion(); } // ensure m_show_search is up to date (the pane can be closed) wxAuiPaneInfo& searchPaneInfo = m_auimgr.GetPane( SearchPaneName() ); m_show_search = searchPaneInfo.IsShown(); cfg->m_AuiPanels.show_search = m_show_search; cfg->m_AuiPanels.search_panel_height = m_searchPane->GetSize().y; cfg->m_AuiPanels.search_panel_width = m_searchPane->GetSize().x; cfg->m_AuiPanels.search_panel_dock_direction = searchPaneInfo.dock_direction; if( m_netInspectorPanel ) { wxAuiPaneInfo& netInspectorhPaneInfo = m_auimgr.GetPane( NetInspectorPanelName() ); m_show_net_inspector = netInspectorhPaneInfo.IsShown(); cfg->m_AuiPanels.show_net_inspector = m_show_net_inspector; } if( m_appearancePanel ) { cfg->m_AuiPanels.right_panel_width = m_appearancePanel->GetSize().x; cfg->m_AuiPanels.appearance_panel_tab = m_appearancePanel->GetTabIndex(); cfg->m_AuiPanels.appearance_expand_layer_display = m_appearancePanel->IsLayerOptionsExpanded(); cfg->m_AuiPanels.appearance_expand_net_display = m_appearancePanel->IsNetOptionsExpanded(); } } } EDA_ANGLE PCB_EDIT_FRAME::GetRotationAngle() const { PCBNEW_SETTINGS* cfg = dynamic_cast( config() ); return cfg ? cfg->m_RotationAngle : ANGLE_90; } COLOR4D PCB_EDIT_FRAME::GetGridColor() { return GetColorSettings()->GetColor( LAYER_GRID ); } void PCB_EDIT_FRAME::SetGridColor( const COLOR4D& aColor ) { GetColorSettings()->SetColor( LAYER_GRID, aColor ); GetCanvas()->GetGAL()->SetGridColor( aColor ); } void PCB_EDIT_FRAME::SetActiveLayer( PCB_LAYER_ID aLayer ) { PCB_LAYER_ID oldLayer = GetActiveLayer(); if( oldLayer == aLayer ) return; PCB_BASE_FRAME::SetActiveLayer( aLayer ); m_appearancePanel->OnLayerChanged(); m_toolManager->PostAction( PCB_ACTIONS::layerChanged ); // notify other tools GetCanvas()->SetFocus(); // allow capture of hotkeys GetCanvas()->SetHighContrastLayer( aLayer ); GetCanvas()->GetView()->UpdateAllItemsConditionally( [&]( KIGFX::VIEW_ITEM* aItem ) -> int { BOARD_ITEM* item = dynamic_cast( aItem ); if( !item ) return 0; // Note: KIGFX::REPAINT isn't enough for things that go from invisible to visible // as they won't be found in the view layer's itemset for re-painting. if( GetDisplayOptions().m_ContrastModeDisplay == HIGH_CONTRAST_MODE::HIDDEN ) { if( item->IsOnLayer( oldLayer ) || item->IsOnLayer( aLayer ) ) return KIGFX::ALL; } if( item->Type() == PCB_VIA_T ) { PCB_VIA* via = static_cast( item ); // Vias on a restricted layer set must be redrawn when the active layer // is changed if( via->GetViaType() == VIATYPE::BLIND_BURIED || via->GetViaType() == VIATYPE::MICROVIA ) { return KIGFX::REPAINT; } if( via->GetRemoveUnconnected() ) return KIGFX::ALL; // Clearances could be layer-dependent so redraw them when the active layer // is changed if( GetPcbNewSettings()->m_Display.m_TrackClearance == SHOW_WITH_VIA_ALWAYS ) return KIGFX::REPAINT; } else if( item->Type() == PCB_PAD_T ) { PAD* pad = static_cast( item ); if( pad->GetRemoveUnconnected() ) return KIGFX::ALL; // Clearances could be layer-dependent so redraw them when the active layer // is changed if( GetPcbNewSettings()->m_Display.m_PadClearance ) { // Round-corner rects are expensive to draw, but are mostly found on // SMD pads which only need redrawing on an active-to-not-active // switch. if( pad->GetAttribute() == PAD_ATTRIB::SMD ) { if( ( oldLayer == F_Cu || aLayer == F_Cu ) && pad->IsOnLayer( F_Cu ) ) return KIGFX::REPAINT; if( ( oldLayer == B_Cu || aLayer == B_Cu ) && pad->IsOnLayer( B_Cu ) ) return KIGFX::REPAINT; } else if( pad->IsOnLayer( oldLayer ) || pad->IsOnLayer( aLayer ) ) { return KIGFX::REPAINT; } } } else if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T ) { PCB_TRACK* track = static_cast( item ); // Clearances could be layer-dependent so redraw them when the active layer // is changed if( GetPcbNewSettings()->m_Display.m_TrackClearance ) { // Tracks aren't particularly expensive to draw, but it's an easy check. if( track->IsOnLayer( oldLayer ) || track->IsOnLayer( aLayer ) ) return KIGFX::REPAINT; } } return 0; } ); GetCanvas()->Refresh(); } void PCB_EDIT_FRAME::onBoardLoaded() { ENUM_MAP& layerEnum = ENUM_MAP::Instance(); layerEnum.Choices().Clear(); layerEnum.Undefined( UNDEFINED_LAYER ); for( LSEQ seq = LSET::AllLayersMask().Seq(); seq; ++seq ) { // Canonical name layerEnum.Map( *seq, LSET::Name( *seq ) ); // User name layerEnum.Map( *seq, GetBoard()->GetLayerName( *seq ) ); } DRC_TOOL* drcTool = m_toolManager->GetTool(); try { drcTool->GetDRCEngine()->InitEngine( GetDesignRulesPath() ); } catch( PARSE_ERROR& ) { // Not sure this is the best place to tell the user their rules are buggy, so // we'll stay quiet for now. Feel free to revisit this decision.... } UpdateTitle(); wxFileName fn = GetBoard()->GetFileName(); // Display a warning that the file is read only if( fn.FileExists() && !fn.IsFileWritable() ) { m_infoBar->RemoveAllButtons(); m_infoBar->AddCloseButton(); m_infoBar->ShowMessage( _( "Board file is read only." ), wxICON_WARNING, WX_INFOBAR::MESSAGE_TYPE::OUTDATED_SAVE ); } ReCreateLayerBox(); // Sync layer and item visibility GetCanvas()->SyncLayersVisibility( m_pcb ); SetElementVisibility( LAYER_RATSNEST, GetPcbNewSettings()->m_Display.m_ShowGlobalRatsnest ); m_appearancePanel->OnBoardChanged(); // Apply saved display state to the appearance panel after it has been set up PROJECT_LOCAL_SETTINGS& localSettings = Prj().GetLocalSettings(); m_appearancePanel->ApplyLayerPreset( localSettings.m_ActiveLayerPreset ); if( GetBoard()->GetDesignSettings().IsLayerEnabled( localSettings.m_ActiveLayer ) ) SetActiveLayer( localSettings.m_ActiveLayer ); // Updates any auto dimensions and the auxiliary toolbar tracks/via sizes unitsChangeRefresh(); // Sync the net inspector now we have connectivity calculated if( m_netInspectorPanel ) m_netInspectorPanel->OnBoardChanged(); // Display the loaded board: Zoom_Automatique( false ); // Invalidate painting as loading the DRC engine will cause clearances to become valid GetCanvas()->GetView()->UpdateAllItems( KIGFX::ALL ); Refresh(); SetMsgPanel( GetBoard() ); SetStatusText( wxEmptyString ); KIPLATFORM::APP::SetShutdownBlockReason( this, _( "PCB file changes are unsaved" ) ); } void PCB_EDIT_FRAME::OnDisplayOptionsChanged() { m_appearancePanel->UpdateDisplayOptions(); } bool PCB_EDIT_FRAME::IsElementVisible( GAL_LAYER_ID aElement ) const { return GetBoard()->IsElementVisible( aElement ); } void PCB_EDIT_FRAME::SetElementVisibility( GAL_LAYER_ID aElement, bool aNewState ) { // Force the RATSNEST visible if( aElement == LAYER_RATSNEST ) GetCanvas()->GetView()->SetLayerVisible( aElement, true ); else GetCanvas()->GetView()->SetLayerVisible( aElement , aNewState ); GetBoard()->SetElementVisibility( aElement, aNewState ); } void PCB_EDIT_FRAME::ShowChangedLanguage() { // call my base class PCB_BASE_EDIT_FRAME::ShowChangedLanguage(); m_auimgr.GetPane( m_appearancePanel ).Caption( _( "Appearance" ) ); m_auimgr.GetPane( m_selectionFilterPanel ).Caption( _( "Selection Filter" ) ); m_auimgr.GetPane( m_propertiesPanel ).Caption( _( "Properties" ) ); m_auimgr.GetPane( m_netInspectorPanel ).Caption( _( "Net Inspector" ) ); m_auimgr.Update(); UpdateTitle(); } wxString PCB_EDIT_FRAME::GetLastPath( LAST_PATH_TYPE aType ) { PROJECT_FILE& project = Prj().GetProjectFile(); if( project.m_PcbLastPath[ aType ].IsEmpty() ) return wxEmptyString; wxFileName absoluteFileName = project.m_PcbLastPath[ aType ]; wxFileName pcbFileName = GetBoard()->GetFileName(); absoluteFileName.MakeAbsolute( pcbFileName.GetPath() ); return absoluteFileName.GetFullPath(); } void PCB_EDIT_FRAME::SetLastPath( LAST_PATH_TYPE aType, const wxString& aLastPath ) { PROJECT_FILE& project = Prj().GetProjectFile(); wxFileName relativeFileName = aLastPath; wxFileName pcbFileName = GetBoard()->GetFileName(); relativeFileName.MakeRelativeTo( pcbFileName.GetPath() ); if( relativeFileName.GetFullPath() != project.m_PcbLastPath[ aType ] ) { project.m_PcbLastPath[ aType ] = relativeFileName.GetFullPath(); OnModify(); } } void PCB_EDIT_FRAME::OnModify() { PCB_BASE_FRAME::OnModify(); Update3DView( true, GetPcbNewSettings()->m_Display.m_Live3DRefresh ); if( !GetTitle().StartsWith( wxT( "*" ) ) ) UpdateTitle(); m_ZoneFillsDirty = true; } void PCB_EDIT_FRAME::HardRedraw() { Update3DView( true, true ); } void PCB_EDIT_FRAME::ExportSVG( wxCommandEvent& event ) { InvokeExportSVG( this, GetBoard() ); } void PCB_EDIT_FRAME::UpdateTitle() { wxFileName fn = GetBoard()->GetFileName(); bool readOnly = false; bool unsaved = false; if( fn.IsOk() && fn.FileExists() ) readOnly = !fn.IsFileWritable(); else unsaved = true; wxString title; if( IsContentModified() ) title = wxT( "*" ); title += fn.GetName(); if( readOnly ) title += wxS( " " ) + _( "[Read Only]" ); if( unsaved ) title += wxS( " " ) + _( "[Unsaved]" ); title += wxT( " \u2014 " ) + _( "PCB Editor" ); SetTitle( title ); } void PCB_EDIT_FRAME::UpdateUserInterface() { // Update the layer manager and other widgets from the board setup // (layer and items visibility, colors ...) // Rebuild list of nets (full ratsnest rebuild) GetBoard()->BuildConnectivity(); // Update info shown by the horizontal toolbars ReCreateLayerBox(); LSET activeLayers = GetBoard()->GetEnabledLayers(); if( !activeLayers.test( GetActiveLayer() ) ) SetActiveLayer( activeLayers.Seq().front() ); m_SelLayerBox->SetLayerSelection( GetActiveLayer() ); ENUM_MAP& layerEnum = ENUM_MAP::Instance(); layerEnum.Choices().Clear(); layerEnum.Undefined( UNDEFINED_LAYER ); for( LSEQ seq = LSET::AllLayersMask().Seq(); seq; ++seq ) { // Canonical name layerEnum.Map( *seq, LSET::Name( *seq ) ); // User name layerEnum.Map( *seq, GetBoard()->GetLayerName( *seq ) ); } // Sync visibility with canvas for( PCB_LAYER_ID layer : LSET::AllLayersMask().Seq() ) GetCanvas()->GetView()->SetLayerVisible( layer, GetBoard()->IsLayerVisible( layer ) ); // Stackup and/or color theme may have changed m_appearancePanel->OnBoardChanged(); m_netInspectorPanel->OnParentSetupChanged(); } void PCB_EDIT_FRAME::SwitchCanvas( EDA_DRAW_PANEL_GAL::GAL_TYPE aCanvasType ) { // switches currently used canvas (Cairo / OpenGL). PCB_BASE_FRAME::SwitchCanvas( aCanvasType ); } void PCB_EDIT_FRAME::ShowFindDialog() { if( !m_findDialog ) { m_findDialog = new DIALOG_FIND( this ); m_findDialog->SetCallback( std::bind( &PCB_SELECTION_TOOL::FindItem, m_toolManager->GetTool(), _1 ) ); } wxString findString; PCB_SELECTION& selection = m_toolManager->GetTool()->GetSelection(); if( selection.Size() == 1 ) { EDA_ITEM* front = selection.Front(); switch( front->Type() ) { case PCB_FOOTPRINT_T: findString = UnescapeString( static_cast( front )->GetValue() ); break; case PCB_FIELD_T: case PCB_TEXT_T: findString = UnescapeString( static_cast( front )->GetText() ); if( findString.Contains( wxT( "\n" ) ) ) findString = findString.Before( '\n' ); break; default: break; } } m_findDialog->Preload( findString ); m_findDialog->Show( true ); } void PCB_EDIT_FRAME::FindNext( bool reverse ) { if( !m_findDialog ) ShowFindDialog(); m_findDialog->FindNext( reverse ); } void PCB_EDIT_FRAME::ToPlotter( int aID ) { PCB_PLOT_PARAMS plotSettings = GetPlotSettings(); switch( aID ) { case ID_GEN_PLOT_GERBER: plotSettings.SetFormat( PLOT_FORMAT::GERBER ); break; case ID_GEN_PLOT_DXF: plotSettings.SetFormat( PLOT_FORMAT::DXF ); break; case ID_GEN_PLOT_HPGL: plotSettings.SetFormat( PLOT_FORMAT::HPGL ); break; case ID_GEN_PLOT_PDF: plotSettings.SetFormat( PLOT_FORMAT::PDF ); break; case ID_GEN_PLOT_PS: plotSettings.SetFormat( PLOT_FORMAT::POST ); break; case ID_GEN_PLOT: /* keep the previous setup */ break; default: wxFAIL_MSG( wxT( "ToPlotter(): unexpected plot type" ) ); break; break; } SetPlotSettings( plotSettings ); DIALOG_PLOT dlg( this ); dlg.ShowQuasiModal( ); } int PCB_EDIT_FRAME::TestStandalone() { if( Kiface().IsSingle() ) return 0; // Update PCB requires a netlist. Therefore the schematic editor must be running // If this is not the case, open the schematic editor KIWAY_PLAYER* frame = Kiway().Player( FRAME_SCH, true ); // If Kiway() cannot create the eeschema frame, it shows a error message, and // frame is null if( !frame ) return -1; if( !frame->IsShownOnScreen() ) { wxFileName fn( Prj().GetProjectPath(), Prj().GetProjectName(), FILEEXT::KiCadSchematicFileExtension ); // Maybe the file hasn't been converted to the new s-expression file format so // see if the legacy schematic file is still in play. if( !fn.FileExists() ) { fn.SetExt( FILEEXT::LegacySchematicFileExtension ); if( !fn.FileExists() ) { DisplayError( this, _( "The schematic for this board cannot be found." ) ); return -2; } } frame->OpenProjectFiles( std::vector( 1, fn.GetFullPath() ) ); // we show the schematic editor frame, because do not show is seen as // a not yet opened schematic by Kicad manager, which is not the case frame->Show( true ); // bring ourselves back to the front Raise(); } return 1; //Success! } bool PCB_EDIT_FRAME::FetchNetlistFromSchematic( NETLIST& aNetlist, const wxString& aAnnotateMessage ) { int standalone = TestStandalone(); if( standalone == 0 ) { DisplayErrorMessage( this, _( "Cannot update the PCB because PCB editor is opened in " "stand-alone mode. In order to create or update PCBs from " "schematics, you must launch the KiCad project manager and " "create a project." ) ); return false; // Not in standalone mode } if( standalone < 0 ) // Problem with Eeschema or the schematic return false; Raise(); // Show std::string payload( aAnnotateMessage ); Kiway().ExpressMail( FRAME_SCH, MAIL_SCH_GET_NETLIST, payload, this ); if( payload == aAnnotateMessage ) { Raise(); DisplayErrorMessage( this, aAnnotateMessage ); return false; } try { auto lineReader = new STRING_LINE_READER( payload, _( "Eeschema netlist" ) ); KICAD_NETLIST_READER netlistReader( lineReader, &aNetlist ); netlistReader.LoadNetlist(); } catch( const IO_ERROR& e ) { Raise(); // Do not translate extra_info strings. These are for developers wxString extra_info = e.Problem() + wxT( " : " ) + e.What() + wxT( " at " ) + e.Where(); DisplayErrorMessage( this, _( "Received an error while reading netlist. Please " "report this issue to the KiCad team using the menu " "Help->Report Bug."), extra_info ); return false; } return true; } void PCB_EDIT_FRAME::RunEeschema() { wxString msg; wxFileName schematic( Prj().GetProjectPath(), Prj().GetProjectName(), FILEEXT::KiCadSchematicFileExtension ); if( !schematic.FileExists() ) { wxFileName legacySchematic( Prj().GetProjectPath(), Prj().GetProjectName(), FILEEXT::LegacySchematicFileExtension ); if( legacySchematic.FileExists() ) { schematic = legacySchematic; } else { msg.Printf( _( "Schematic file '%s' not found." ), schematic.GetFullPath() ); DisplayErrorMessage( this, msg ); return; } } if( Kiface().IsSingle() ) { ExecuteFile( EESCHEMA_EXE, schematic.GetFullPath() ); } else { KIWAY_PLAYER* frame = Kiway().Player( FRAME_SCH, false ); // Please: note: DIALOG_EDIT_LIBENTRY_FIELDS_IN_LIB::initBuffers() calls // Kiway.Player( FRAME_SCH, true ) // therefore, the schematic editor is sometimes running, but the schematic project // is not loaded, if the library editor was called, and the dialog field editor was used. // On Linux, it happens the first time the schematic editor is launched, if // library editor was running, and the dialog field editor was open // On Windows, it happens always after the library editor was called, // and the dialog field editor was used if( !frame ) { try { frame = Kiway().Player( FRAME_SCH, true ); } catch( const IO_ERROR& err ) { DisplayErrorMessage( this, _( "Eeschema failed to load." ) + wxS( "\n" ) + err.What() ); return; } } // If Kiway() cannot create the eeschema frame, it shows a error message, and // frame is null if( !frame ) return; if( !frame->IsShownOnScreen() ) // the frame exists, (created by the dialog field editor) // but no project loaded. { frame->OpenProjectFiles( std::vector( 1, schematic.GetFullPath() ) ); frame->Show( true ); } // On Windows, Raise() does not bring the window on screen, when iconized or not shown // On Linux, Raise() brings the window on screen, but this code works fine if( frame->IsIconized() ) { frame->Iconize( false ); // If an iconized frame was created by Pcbnew, Iconize( false ) is not enough // to show the frame at its normal size: Maximize should be called. frame->Maximize( false ); } frame->Raise(); } } void PCB_EDIT_FRAME::PythonSyncEnvironmentVariables() { const ENV_VAR_MAP& vars = Pgm().GetLocalEnvVariables(); // Set the environment variables for python scripts // note: the string will be encoded UTF8 for python env for( const std::pair& var : vars ) UpdatePythonEnvVar( var.first, var.second.GetValue() ); // Because the env vars can be modified by the python scripts (rewritten in UTF8), // regenerate them (in Unicode) for our normal environment for( const std::pair& var : vars ) wxSetEnv( var.first, var.second.GetValue() ); } void PCB_EDIT_FRAME::PythonSyncProjectName() { wxString evValue; wxGetEnv( PROJECT_VAR_NAME, &evValue ); UpdatePythonEnvVar( wxString( PROJECT_VAR_NAME ).ToStdString(), evValue ); // Because PROJECT_VAR_NAME can be modified by the python scripts (rewritten in UTF8), // regenerate it (in Unicode) for our normal environment wxSetEnv( PROJECT_VAR_NAME, evValue ); } void PCB_EDIT_FRAME::ShowFootprintPropertiesDialog( FOOTPRINT* aFootprint ) { if( aFootprint == nullptr ) return; DIALOG_FOOTPRINT_PROPERTIES::FP_PROPS_RETVALUE retvalue; /* * Make sure dlg is destroyed before GetCanvas->Refresh is called * later or the refresh will try to modify its properties since * they share a GL context. */ { DIALOG_FOOTPRINT_PROPERTIES dlg( this, aFootprint ); dlg.ShowModal(); retvalue = dlg.GetReturnValue(); } /* * retvalue = * FP_PROPS_UPDATE_FP to show Update Footprints dialog * FP_PROPS_CHANGE_FP to show Change Footprints dialog * FP_PROPS_OK for normal edit * FP_PROPS_CANCEL if aborted * FP_PROPS_EDIT_BOARD_FP to load board footprint into Footprint Editor * FP_PROPS_EDIT_LIBRARY_FP to load library footprint into Footprint Editor */ if( retvalue == DIALOG_FOOTPRINT_PROPERTIES::FP_PROPS_OK ) { // If something edited, push a refresh request GetCanvas()->Refresh(); } else if( retvalue == DIALOG_FOOTPRINT_PROPERTIES::FP_PROPS_EDIT_BOARD_FP ) { if( KIWAY_PLAYER* frame = Kiway().Player( FRAME_FOOTPRINT_EDITOR, true ) ) { FOOTPRINT_EDIT_FRAME* fp_editor = static_cast( frame ); fp_editor->LoadFootprintFromBoard( aFootprint ); fp_editor->Show( true ); fp_editor->Raise(); // Iconize( false ); } } else if( retvalue == DIALOG_FOOTPRINT_PROPERTIES::FP_PROPS_EDIT_LIBRARY_FP ) { if( KIWAY_PLAYER* frame = Kiway().Player( FRAME_FOOTPRINT_EDITOR, true ) ) { FOOTPRINT_EDIT_FRAME* fp_editor = static_cast( frame ); fp_editor->LoadFootprintFromLibrary( aFootprint->GetFPID() ); fp_editor->Show( true ); fp_editor->Raise(); // Iconize( false ); } } else if( retvalue == DIALOG_FOOTPRINT_PROPERTIES::FP_PROPS_UPDATE_FP ) { ShowExchangeFootprintsDialog( aFootprint, true, true ); } else if( retvalue == DIALOG_FOOTPRINT_PROPERTIES::FP_PROPS_CHANGE_FP ) { ShowExchangeFootprintsDialog( aFootprint, false, true ); } } int PCB_EDIT_FRAME::ShowExchangeFootprintsDialog( FOOTPRINT* aFootprint, bool aUpdateMode, bool aSelectedMode ) { DIALOG_EXCHANGE_FOOTPRINTS dialog( this, aFootprint, aUpdateMode, aSelectedMode ); return dialog.ShowQuasiModal(); } /** * copy text settings from aSrc to aDest * @param aSrc is the PCB_TEXT source * @param aDest is the PCB_TEXT target * @param aResetText is true to keep the default target text (false to use the aSrc text) * @param aResetTextLayers is true to keep the default target layers setting * (false to use the aSrc setting) * @param aResetTextEffects is true to keep the default target text effects * (false to use the aSrc effect) * @param aUpdated is a refrence to a bool to keep trace of changes */ static void processTextItem( const PCB_TEXT& aSrc, PCB_TEXT& aDest, bool aResetText, bool aResetTextLayers, bool aResetTextEffects, bool* aUpdated ) { if( aResetText ) *aUpdated |= aSrc.GetText() != aDest.GetText(); else aDest.SetText( aSrc.GetText() ); if( aResetTextLayers ) { *aUpdated |= aSrc.GetLayer() != aDest.GetLayer(); *aUpdated |= aSrc.IsVisible() != aDest.IsVisible(); } else { aDest.SetLayer( aSrc.GetLayer() ); aDest.SetVisible( aSrc.IsVisible() ); } if( aResetTextEffects ) { *aUpdated |= aSrc.GetHorizJustify() != aDest.GetHorizJustify(); *aUpdated |= aSrc.GetVertJustify() != aDest.GetVertJustify(); *aUpdated |= aSrc.GetTextSize() != aDest.GetTextSize(); *aUpdated |= aSrc.GetTextThickness() != aDest.GetTextThickness(); *aUpdated |= aSrc.GetTextAngle() != aDest.GetTextAngle(); *aUpdated |= aSrc.GetFPRelativePosition() != aDest.GetFPRelativePosition(); } else { // Careful: the visible bit and position are also set by SetAttributes() bool visible = aDest.IsVisible(); aDest.SetAttributes( aSrc ); aDest.SetVisible( visible ); aDest.SetFPRelativePosition( aSrc.GetFPRelativePosition() ); } aDest.SetLocked( aSrc.IsLocked() ); } static PCB_TEXT* getMatchingTextItem( PCB_TEXT* aRefItem, FOOTPRINT* aFootprint ) { std::vector candidates; for( BOARD_ITEM* item : aFootprint->GraphicalItems() ) { PCB_TEXT* candidate = dynamic_cast( item ); if( candidate && candidate->GetText() == aRefItem->GetText() ) candidates.push_back( candidate ); } if( candidates.size() == 0 ) return nullptr; if( candidates.size() == 1 ) return candidates[0]; // Try refining the match by layer std::vector candidatesOnSameLayer; for( PCB_TEXT* candidate : candidates ) { if( candidate->GetLayer() == aRefItem->GetLayer() ) candidatesOnSameLayer.push_back( candidate ); } if( candidatesOnSameLayer.size() == 1 ) return candidatesOnSameLayer[0]; // Last ditch effort: refine by position std::vector candidatesAtSamePos; for( PCB_TEXT* candidate : candidatesOnSameLayer.size() ? candidatesOnSameLayer : candidates ) { if( candidate->GetFPRelativePosition() == aRefItem->GetFPRelativePosition() ) candidatesAtSamePos.push_back( candidate ); } if( candidatesAtSamePos.size() > 0 ) return candidatesAtSamePos[0]; else if( candidatesOnSameLayer.size() > 0 ) return candidatesOnSameLayer[0]; else return candidates[0]; } void PCB_EDIT_FRAME::ExchangeFootprint( FOOTPRINT* aExisting, FOOTPRINT* aNew, BOARD_COMMIT& aCommit, bool deleteExtraTexts, bool resetTextLayers, bool resetTextEffects, bool resetTextContent, bool resetFabricationAttrs, bool reset3DModels, bool* aUpdated ) { PCB_GROUP* parentGroup = aExisting->GetParentGroup(); bool dummyBool = false; if( !aUpdated ) aUpdated = &dummyBool; if( parentGroup ) { parentGroup->RemoveItem( aExisting ); parentGroup->AddItem( aNew ); } aNew->SetParent( GetBoard() ); PlaceFootprint( aNew, false ); // PlaceFootprint will move the footprint to the cursor position, which we don't want. Copy // the original position across. aNew->SetPosition( aExisting->GetPosition() ); if( aNew->GetLayer() != aExisting->GetLayer() ) aNew->Flip( aNew->GetPosition(), GetPcbNewSettings()->m_FlipLeftRight ); if( aNew->GetOrientation() != aExisting->GetOrientation() ) aNew->SetOrientation( aExisting->GetOrientation() ); aNew->SetLocked( aExisting->IsLocked() ); // Now transfer the net info from "old" pads to the new footprint for( PAD* pad : aNew->Pads() ) { PAD* pad_model = nullptr; // Pads with no copper are never connected to a net if( !pad->IsOnCopperLayer() ) { pad->SetNetCode( NETINFO_LIST::UNCONNECTED ); continue; } // Pads with no numbers are never connected to a net if( pad->GetNumber().IsEmpty() ) { pad->SetNetCode( NETINFO_LIST::UNCONNECTED ); continue; } // Search for a similar pad on a copper layer, to reuse net info PAD* last_pad = nullptr; while( true ) { pad_model = aExisting->FindPadByNumber( pad->GetNumber(), last_pad ); if( !pad_model ) break; if( pad_model->IsOnCopperLayer() ) // a candidate is found break; last_pad = pad_model; } if( pad_model ) { pad->SetLocalRatsnestVisible( pad_model->GetLocalRatsnestVisible() ); pad->SetPinFunction( pad_model->GetPinFunction() ); pad->SetPinType( pad_model->GetPinType() ); } pad->SetNetCode( pad_model ? pad_model->GetNetCode() : NETINFO_LIST::UNCONNECTED ); } for( BOARD_ITEM* item : aExisting->GraphicalItems() ) { PCB_TEXT* srcItem = dynamic_cast( item ); if( srcItem ) { // Dimensions have PCB_TEXT base but are not treated like texts in the updater if( dynamic_cast( srcItem ) ) continue; PCB_TEXT* destItem = getMatchingTextItem( srcItem, aNew ); if( destItem ) { processTextItem( *srcItem, *destItem, resetTextContent, resetTextLayers, resetTextEffects, aUpdated ); } else if( !deleteExtraTexts ) { aNew->Add( static_cast( srcItem->Clone() ) ); } } } // Copy reference. The initial text is always used, never resetted processTextItem( aExisting->Reference(), aNew->Reference(), false, resetTextLayers, resetTextEffects, aUpdated ); // Copy value processTextItem( aExisting->Value(), aNew->Value(), // reset value text only when it is a proxy for the footprint ID // (cf replacing value "MountingHole-2.5mm" with "MountingHole-4.0mm") aExisting->GetValue() == aExisting->GetFPID().GetLibItemName().wx_str(), resetTextLayers, resetTextEffects, aUpdated ); // Copy fields in accordance with the reset* flags for( PCB_FIELD* field : aExisting->GetFields() ) { // Reference and value are already handled if( field->IsReference() || field->IsValue() ) continue; PCB_FIELD* newField = aNew->GetFieldByName( field->GetName() ); if( !newField ) { newField = new PCB_FIELD( *field ); aNew->Add( newField ); processTextItem( *field, *newField, true, true, true, aUpdated ); } else { processTextItem( *field, *newField, resetTextContent, resetTextLayers, resetTextEffects, aUpdated ); } } if( resetFabricationAttrs ) { // We've replaced the existing footprint with the library one, so the fabrication attrs // are already reset. // // We only have to do anything if resetFabricationAttrs is *not* set.... } else { // Careful; allow-soldermask-bridges is in the m_attributes field but is not presented // as a fabrication attribute in the GUI.... int libraryFlagsToKeep = aNew->GetAttributes() & FP_ALLOW_SOLDERMASK_BRIDGES; int existingFlagsToKeep = aExisting->GetAttributes() & ~FP_ALLOW_SOLDERMASK_BRIDGES; aNew->SetAttributes( existingFlagsToKeep | libraryFlagsToKeep ); } if( reset3DModels ) { // We've replaced the existing footprint with the library one, so the 3D models are // already reset. // // We only have to do anything if reset3DModels is *not* set.... } else { aNew->Models() = aExisting->Models(); // Linked list of 3D models. } // Updating other parameters const_cast( aNew->m_Uuid ) = aExisting->m_Uuid; aNew->SetPath( aExisting->GetPath() ); aNew->SetSheetfile( aExisting->GetSheetfile() ); aNew->SetSheetname( aExisting->GetSheetname() ); aNew->SetFilters( aExisting->GetFilters() ); aCommit.Remove( aExisting ); aCommit.Add( aNew ); aNew->ClearFlags(); } void PCB_EDIT_FRAME::CommonSettingsChanged( bool aEnvVarsChanged, bool aTextVarsChanged ) { PCB_BASE_EDIT_FRAME::CommonSettingsChanged( aEnvVarsChanged, aTextVarsChanged ); GetAppearancePanel()->OnColorThemeChanged(); SetElementVisibility( LAYER_RATSNEST, GetPcbNewSettings()->m_Display.m_ShowGlobalRatsnest ); GetGalDisplayOptions().ReadWindowSettings( GetPcbNewSettings()->m_Window ); // Netclass definitions could have changed, either by us or by Eeschema, so we need to // recompile the implicit rules DRC_TOOL* drcTool = m_toolManager->GetTool(); WX_INFOBAR* infobar = GetInfoBar(); try { drcTool->GetDRCEngine()->InitEngine( GetDesignRulesPath() ); if( infobar->GetMessageType() == WX_INFOBAR::MESSAGE_TYPE::DRC_RULES_ERROR ) infobar->Dismiss(); } catch( PARSE_ERROR& ) { wxHyperlinkCtrl* button = new wxHyperlinkCtrl( infobar, wxID_ANY, _( "Edit design rules" ), wxEmptyString ); button->Bind( wxEVT_COMMAND_HYPERLINK, std::function( [&]( wxHyperlinkEvent& aEvent ) { ShowBoardSetupDialog( _( "Custom Rules" ) ); } ) ); infobar->RemoveAllButtons(); infobar->AddButton( button ); infobar->AddCloseButton(); infobar->ShowMessage( _( "Could not compile custom design rules." ), wxICON_ERROR, WX_INFOBAR::MESSAGE_TYPE::DRC_RULES_ERROR ); } GetCanvas()->GetView()->MarkTargetDirty( KIGFX::TARGET_NONCACHED ); GetCanvas()->ForceRefresh(); // Update the environment variables in the Python interpreter if( aEnvVarsChanged ) PythonSyncEnvironmentVariables(); Layout(); SendSizeEvent(); } void PCB_EDIT_FRAME::ThemeChanged() { PCB_BASE_EDIT_FRAME::ThemeChanged(); } void PCB_EDIT_FRAME::ProjectChanged() { PythonSyncProjectName(); } bool PCB_EDIT_FRAME::CanAcceptApiCommands() { // For now, be conservative: Don't allow any API use while the user is changing things if( GetToolManager()->GetCurrentTool() != GetToolManager()->GetTool() ) return false; ZONE_FILLER_TOOL* zoneFillerTool = m_toolManager->GetTool(); if( zoneFillerTool->IsBusy() ) return false; ROUTER_TOOL* routerTool = m_toolManager->GetTool(); if( routerTool->RoutingInProgress() ) return false; return EDA_BASE_FRAME::CanAcceptApiCommands(); } bool ExportBoardToHyperlynx( BOARD* aBoard, const wxFileName& aPath ); void PCB_EDIT_FRAME::OnExportHyperlynx( wxCommandEvent& event ) { wxString wildcard = wxT( "*.hyp" ); wxFileName fn = GetBoard()->GetFileName(); fn.SetExt( wxT("hyp") ); wxFileDialog dlg( this, _( "Export Hyperlynx Layout" ), fn.GetPath(), fn.GetFullName(), wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); if( dlg.ShowModal() != wxID_OK ) return; fn = dlg.GetPath(); // always enforce filename extension, user may not have entered it. fn.SetExt( wxT( "hyp" ) ); ExportBoardToHyperlynx( GetBoard(), fn ); } wxString PCB_EDIT_FRAME::GetCurrentFileName() const { return GetBoard()->GetFileName(); } bool PCB_EDIT_FRAME::LayerManagerShown() { return m_auimgr.GetPane( wxS( "LayersManager" ) ).IsShown(); } bool PCB_EDIT_FRAME::PropertiesShown() { return m_auimgr.GetPane( PropertiesPaneName() ).IsShown(); } bool PCB_EDIT_FRAME::NetInspectorShown() { return m_auimgr.GetPane( NetInspectorPanelName() ).IsShown(); } void PCB_EDIT_FRAME::onSize( wxSizeEvent& aEvent ) { if( IsShownOnScreen() ) { // We only need this until the frame is done resizing and the final client size is // established. Unbind( wxEVT_SIZE, &PCB_EDIT_FRAME::onSize, this ); GetToolManager()->RunAction( ACTIONS::zoomFitScreen ); } // Skip() is called in the base class. EDA_DRAW_FRAME::OnSize( aEvent ); } DIALOG_BOOK_REPORTER* PCB_EDIT_FRAME::GetInspectDrcErrorDialog() { if( !m_inspectDrcErrorDlg ) m_inspectDrcErrorDlg = new DIALOG_BOOK_REPORTER( this, INSPECT_DRC_ERROR_DIALOG_NAME, _( "Violation Report" ) ); return m_inspectDrcErrorDlg; } DIALOG_BOOK_REPORTER* PCB_EDIT_FRAME::GetInspectClearanceDialog() { if( !m_inspectClearanceDlg ) m_inspectClearanceDlg = new DIALOG_BOOK_REPORTER( this, INSPECT_CLEARANCE_DIALOG_NAME, _( "Clearance Report" ) ); return m_inspectClearanceDlg; } DIALOG_BOOK_REPORTER* PCB_EDIT_FRAME::GetInspectConstraintsDialog() { if( !m_inspectConstraintsDlg ) m_inspectConstraintsDlg = new DIALOG_BOOK_REPORTER( this, INSPECT_CONSTRAINTS_DIALOG_NAME, _( "Constraints Report" ) ); return m_inspectConstraintsDlg; } DIALOG_BOOK_REPORTER* PCB_EDIT_FRAME::GetFootprintDiffDialog() { if( !m_footprintDiffDlg ) m_footprintDiffDlg = new DIALOG_BOOK_REPORTER( this, FOOTPRINT_DIFF_DIALOG_NAME, _( "Compare Footprint with Library" ) ); return m_footprintDiffDlg; } void PCB_EDIT_FRAME::onCloseModelessBookReporterDialogs( wxCommandEvent& aEvent ) { if( m_inspectDrcErrorDlg && aEvent.GetString() == INSPECT_DRC_ERROR_DIALOG_NAME ) { m_inspectDrcErrorDlg->Destroy(); m_inspectDrcErrorDlg = nullptr; } else if( m_inspectClearanceDlg && aEvent.GetString() == INSPECT_CLEARANCE_DIALOG_NAME ) { m_inspectClearanceDlg->Destroy(); m_inspectClearanceDlg = nullptr; } else if( m_inspectConstraintsDlg && aEvent.GetString() == INSPECT_CONSTRAINTS_DIALOG_NAME ) { m_inspectConstraintsDlg->Destroy(); m_inspectConstraintsDlg = nullptr; } else if( m_footprintDiffDlg && aEvent.GetString() == INSPECT_CONSTRAINTS_DIALOG_NAME ) { m_footprintDiffDlg->Destroy(); m_footprintDiffDlg = nullptr; } } #ifdef KICAD_IPC_API void PCB_EDIT_FRAME::onPluginAvailabilityChanged( wxCommandEvent& aEvt ) { wxLogTrace( traceApi, "PCB frame: EDA_EVT_PLUGIN_AVAILABILITY_CHANGED" ); ReCreateHToolbar(); aEvt.Skip(); } #endif