/* * 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-2022 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 // for DIALOG_DRC_WINDOW_NAME definition #include #include #include #include #include #include #include #include #include #include #include #include #include "../scripting/python_scripting.h" #include using namespace std::placeholders; 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_RUN_TEARDROP_TOOL, PCB_EDIT_FRAME::OnRunTeardropTool ) EVT_MENU( ID_REMOVE_TEARDROP_TOOL, PCB_EDIT_FRAME::OnRemoveTeardropTool ) 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 Config EVT_MENU( ID_GRID_SETTINGS, PCB_EDIT_FRAME::OnGridSettings ) // 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_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_properties = true; m_show_search = 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 = _( "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 ) ); 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(); if( ADVANCED_CFG::GetCfg().m_ShowPropertiesPanel ) m_propertiesPanel = new PCB_PROPERTIES_PANEL( this, this ); m_selectionFilterPanel = new PANEL_SELECTION_FILTER( this ); m_appearancePanel = new APPEARANCE_CONTROLS( this, GetCanvas() ); m_searchPane = new PCB_SEARCH_PANE( 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( "MainToolbar" ) .Top().Layer( 6 ) ); m_auimgr.AddPane( m_auxiliaryToolBar, EDA_PANE().HToolbar().Name( "AuxToolbar" ) .Top().Layer( 5 ) ); m_auimgr.AddPane( m_messagePanel, EDA_PANE().Messages().Name( "MsgPanel" ) .Bottom().Layer( 6 ) ); // Columns; layers 1 - 3 m_auimgr.AddPane( m_optionsToolBar, EDA_PANE().VToolbar().Name( "OptToolbar" ) .Left().Layer( 3 ) ); m_auimgr.AddPane( m_drawToolBar, EDA_PANE().VToolbar().Name( "ToolsToolbar" ) .Right().Layer( 3 ) ); m_auimgr.AddPane( m_appearancePanel, EDA_PANE().Name( "LayersManager" ) .Right().Layer( 4 ) .Caption( _( "Appearance" ) ).PaneBorder( false ) .MinSize( 180, -1 ).BestSize( 180, -1 ) ); m_auimgr.AddPane( m_selectionFilterPanel, EDA_PANE().Name( "SelectionFilter" ) .Right().Layer( 4 ).Position( 2 ) .Caption( _( "Selection Filter" ) ).PaneBorder( false ) .MinSize( 180, -1 ).BestSize( 180, -1 ) ); if( ADVANCED_CFG::GetCfg().m_ShowPropertiesPanel ) { m_auimgr.AddPane( m_propertiesPanel, EDA_PANE().Name( "PropertiesManager" ) .Left().Layer( 5 ).Caption( _( "Properties" ) ) .PaneBorder( false ).MinSize( 240, -1 ).BestSize( 300, -1 ) ); } // Center m_auimgr.AddPane( GetCanvas(), EDA_PANE().Canvas().Name( "DrawFrame" ) .Center() ); m_auimgr.AddPane( m_searchPane, EDA_PANE() .Name( SearchPaneName() ) .Bottom() .Caption( _( "Search" ) ) .PaneBorder( false ) .MinSize( 180, -1 ) .BestSize( 180, -1 ) .FloatingSize( 480, 200 ) .CloseButton( true ) .DestroyOnClose( false ) ); m_auimgr.GetPane( "LayersManager" ).Show( m_show_layer_manager_tools ); m_auimgr.GetPane( "SelectionFilter" ).Show( m_show_layer_manager_tools ); bool showProperties = ADVANCED_CFG::GetCfg().m_ShowPropertiesPanel && m_show_properties; m_auimgr.GetPane( "PropertiesManager" ).Show( showProperties ); 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( "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( "PropertiesManager" ); SetAuiPaneSize( m_auimgr, propertiesPanel, settings->m_AuiPanels.properties_panel_width, -1 ); } if( settings->m_AuiPanels.search_panel_height > 0 ) { wxAuiPaneInfo& searchPane = m_auimgr.GetPane( SearchPaneName() ); SetAuiPaneSize( m_auimgr, searchPane, -1, settings->m_AuiPanels.search_panel_height ); } m_appearancePanel->SetTabIndex( settings->m_AuiPanels.appearance_panel_tab ); } GetToolManager()->RunAction( ACTIONS::zoomFitScreen, false ); // 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 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( PCB_ACTIONS::pluginsReload, true ); 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( [&]() { 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( [&]() { // 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 ); } m_acceptedExts.emplace( KiCadPcbFileExtension, &PCB_ACTIONS::ddAppendBoard ); m_acceptedExts.emplace( LegacyPcbFileExtension, &PCB_ACTIONS::ddAppendBoard ); DragAcceptFiles( true ); } PCB_EDIT_FRAME::~PCB_EDIT_FRAME() { 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; } // 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; } 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(); for( PCB_TRACK* track : GetBoard()->Tracks() ) { double lod = track->ViewGetLOD( GetNetnameLayer( track->GetLayer() ), view ); double scale = view->GetScale(); if( lod != track->GetCachedLOD() || scale != track->GetCachedScale() ) { if( lod < view->GetScale() ) { 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.IU_PER_MILS, &m_pcb->GetPageSettings(), m_pcb->GetProject(), &m_pcb->GetTitleBlock(), &m_pcb->GetProperties() ); drawingSheet->SetSheetName( std::string( GetScreenDesc().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 LENGTH_TUNER_TOOL ); m_toolManager->RegisterTool( new EDIT_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 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 ); 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( cond.UndoAvailable() ) ); mgr->SetConditions( ACTIONS::redo, ENABLE( cond.RedoAvailable() ) ); mgr->SetConditions( ACTIONS::toggleGrid, CHECK( cond.GridVisible() ) ); 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::doDelete, ENABLE( cond.HasItems() ) ); mgr->SetConditions( ACTIONS::duplicate, ENABLE( cond.HasItems() ) ); auto haveAtLeastOneGroupCond = []( const SELECTION& aSel ) { for( EDA_ITEM* item : aSel ) { if( item->Type() == PCB_GROUP_T ) return true; } return false; }; mgr->SetConditions( PCB_ACTIONS::group, ENABLE( SELECTION_CONDITIONS::MoreThan( 1 ) ) ); mgr->SetConditions( PCB_ACTIONS::ungroup, ENABLE( haveAtLeastOneGroupCond ) ); mgr->SetConditions( PCB_ACTIONS::lock, ENABLE( cond.HasItems() ) ); mgr->SetConditions( PCB_ACTIONS::unlock, ENABLE( cond.HasItems() ) ); 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 enableZoneControlConition = [this] ( const SELECTION& ) { return GetBoard()->GetVisibleElements().Contains( LAYER_ZONES ) && GetDisplayOptions().m_ZoneOpacity > 0.0; }; mgr->SetConditions( PCB_ACTIONS::zoneDisplayFilled, ENABLE( enableZoneControlConition ) .Check( cond.ZoneDisplayMode( ZONE_DISPLAY_MODE::SHOW_FILLED ) ) ); mgr->SetConditions( PCB_ACTIONS::zoneDisplayOutline, ENABLE( enableZoneControlConition ) .Check( cond.ZoneDisplayMode( ZONE_DISPLAY_MODE::SHOW_ZONE_OUTLINE ) ) ); mgr->SetConditions( PCB_ACTIONS::zoneDisplayFractured, ENABLE( enableZoneControlConition ) .Check( cond.ZoneDisplayMode( ZONE_DISPLAY_MODE::SHOW_FRACTURE_BORDERS ) ) ); mgr->SetConditions( PCB_ACTIONS::zoneDisplayTriangulated, ENABLE( enableZoneControlConition ) .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 enableBoardSetupCondition = [this] ( const SELECTION& ) { if( DRC_TOOL* tool = m_toolManager->GetTool() ) return !tool->IsDRCDialogShown(); return true; }; auto boardFlippedCond = [this]( const SELECTION& ) { return GetCanvas()->GetView()->IsMirroredX(); }; auto layerManagerCond = [this] ( const SELECTION& ) { return LayerManagerShown(); }; auto propertiesCond = [this] ( const SELECTION& ) { return PropertiesShown(); }; 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->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::boardSetup, ENABLE( enableBoardSetupCondition ) ); mgr->SetConditions( PCB_ACTIONS::showProperties, CHECK( propertiesCond ) ); mgr->SetConditions( PCB_ACTIONS::showSearch, CHECK( searchPaneCond ) ); auto isHighlightMode = [this]( const SELECTION& ) { ROUTER_TOOL* tool = m_toolManager->GetTool(); return tool->GetRouterMode() == PNS::RM_MarkObstacles; }; auto isShoveMode = [this]( const SELECTION& ) { ROUTER_TOOL* tool = m_toolManager->GetTool(); return tool->GetRouterMode() == PNS::RM_Shove; }; auto isWalkaroundMode = [this]( const SELECTION& ) { ROUTER_TOOL* tool = m_toolManager->GetTool(); return 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, PCB_FP_ZONE_T } ); SELECTION_CONDITION zoneMergeCond = SELECTION_CONDITIONS::MoreThan( 1 ) && SELECTION_CONDITIONS::OnlyTypes( { PCB_ZONE_T, PCB_FP_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::zoneFill, ENABLE( SELECTION_CONDITIONS::MoreThan( 0 ) ) ); mgr->SetConditions( PCB_ACTIONS::zoneUnfill, ENABLE( SELECTION_CONDITIONS::MoreThan( 0 ) ) ); 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 isDrcRunning = [this] ( const SELECTION& ) { DRC_TOOL* tool = m_toolManager->GetTool(); return !tool->IsDRCRunning(); }; #define CURRENT_EDIT_TOOL( action ) mgr->SetConditions( action, ACTION_CONDITIONS().Check( cond.CurrentTool( action ) ).Enable( isDrcRunning ) ) // 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::routerTuneDiffPair ); CURRENT_EDIT_TOOL( PCB_ACTIONS::routerTuneDiffPairSkew ); CURRENT_EDIT_TOOL( PCB_ACTIONS::routerTuneSingleTrace ); 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::placeImage ); CURRENT_EDIT_TOOL( PCB_ACTIONS::placeText ); CURRENT_EDIT_TOOL( PCB_ACTIONS::drawTextBox ); 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::RecordDRCExclusions() { BOARD_DESIGN_SETTINGS& bds = GetBoard()->GetDesignSettings(); bds.m_DrcExclusions.clear(); for( PCB_MARKER* marker : GetBoard()->Markers() ) { if( marker->IsExcluded() ) bds.m_DrcExclusions.insert( marker->Serialize() ); } } void PCB_EDIT_FRAME::ResolveDRCExclusions() { BOARD_COMMIT commit( this ); for( PCB_MARKER* marker : GetBoard()->ResolveDRCExclusions() ) { 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()->Remove( marker ); GetCanvas()->GetView()->Add( 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; } 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; fpViewer = (FOOTPRINT_VIEWER_FRAME*) Kiway().Player( FRAME_FOOTPRINT_VIEWER_MODAL, false ); if( fpViewer && !fpViewer->Close() ) // Can close modal footprint viewer? 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; } } // Close modeless dialogs. They're trouble when they get destroyed after the frame and/or // board. wxWindow* open_dlg = wxWindow::FindWindowByName( DIALOG_DRC_WINDOW_NAME ); if( open_dlg ) open_dlg->Close( true ); 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(); // Delete the auto save file if it exists. wxFileName fn = GetBoard()->GetFileName(); // Auto save file name is the normal file name prefixed with 'GetAutoSaveFilePrefix()'. fn.SetName( GetAutoSaveFilePrefix() + 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() ) ) { wxString msg = wxString::Format( _( "The auto save file '%s' could not be removed!" ), fn.GetFullPath() ); wxMessageBox( msg, Pgm().App().GetAppName(), wxOK | wxICON_ERROR, this ); } // Make sure local settings are persisted SaveProjectSettings(); // 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( "LayersManager" ).Show( false ); m_auimgr.GetPane( "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 ) { // Make sure everything's up-to-date GetBoard()->BuildListOfNets(); DIALOG_BOARD_SETUP dlg( this ); if( !aInitialPage.IsEmpty() ) dlg.SetInitialPage( aInitialPage, wxEmptyString ); if( dlg.ShowQuasiModal() == wxID_OK ) { GetBoard()->SynchronizeNetsAndNetClasses( true ); SaveProjectSettings(); Kiway().CommonSettingsChanged( false, true ); 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 { if( dynamic_cast( aItem ) ) { if( settings->m_Display.m_TrackClearance == SHOW_WITH_VIA_ALWAYS ) return KIGFX::REPAINT; } else if( dynamic_cast( aItem ) ) { if( settings->m_Display.m_PadClearance ) return KIGFX::REPAINT; if( ( GetBoard()->GetVisibleLayers() & maskAndPasteLayers ).any() ) return KIGFX::REPAINT; } EDA_TEXT* text = dynamic_cast( aItem ); if( text && text->HasTextVars() ) { text->ClearRenderCache(); text->ClearBoundingBoxCache(); return KIGFX::GEOMETRY | KIGFX::REPAINT; } return 0; } ); 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_properties = cfg->m_AuiPanels.show_properties; m_show_search = cfg->m_AuiPanels.show_search; } } 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; cfg->m_AuiPanels.right_panel_width = m_appearancePanel->GetSize().x; cfg->m_AuiPanels.appearance_panel_tab = m_appearancePanel->GetTabIndex(); if( m_propertiesPanel ) { cfg->m_AuiPanels.show_properties = m_show_properties; cfg->m_AuiPanels.properties_panel_width = m_propertiesPanel->GetSize().x; } // ensure m_show_search is up to date (the pane can be closed) m_show_search = m_auimgr.GetPane( SearchPaneName() ).IsShown(); cfg->m_AuiPanels.show_search = m_show_search; cfg->m_AuiPanels.search_panel_height = m_searchPane->GetSize().y; } } 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->RunAction( PCB_ACTIONS::layerChanged ); // notify other tools GetCanvas()->SetFocus(); // allow capture of hotkeys GetCanvas()->SetHighContrastLayer( aLayer ); GetCanvas()->GetView()->UpdateAllItemsConditionally( KIGFX::REPAINT, [&]( KIGFX::VIEW_ITEM* aItem ) -> bool { if( PCB_VIA* via = dynamic_cast( aItem ) ) { // Vias on a restricted layer set must be redrawn when the active layer // is changed return ( via->GetViaType() == VIATYPE::BLIND_BURIED || via->GetViaType() == VIATYPE::MICROVIA ); } else if( PAD* pad = dynamic_cast( aItem ) ) { // 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 true; if( ( oldLayer == B_Cu || aLayer == B_Cu ) && pad->IsOnLayer( B_Cu ) ) return true; } return true; } } else if( PCB_TRACK* track = dynamic_cast( aItem ) ) { // 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. return track->IsOnLayer( oldLayer ) || track->IsOnLayer( aLayer ); } } return false; } ); GetCanvas()->Refresh(); } void PCB_EDIT_FRAME::onBoardLoaded() { // JEY TODO: move this global to the board 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(); // 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.Update(); m_appearancePanel->OnLanguageChanged(); m_selectionFilterPanel->OnLanguageChanged(); 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(); SaveProjectSettings(); } } 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(); } 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 = static_cast( front )->GetValue(); break; case PCB_FP_TEXT_T: findString = static_cast( front )->GetShownText(); break; case PCB_TEXT_T: findString = static_cast( front )->GetShownText(); 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() { if( !m_findDialog ) ShowFindDialog(); m_findDialog->FindNext(); } 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->IsShown() ) { wxFileName fn( Prj().GetProjectPath(), Prj().GetProjectName(), 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( 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 ) { if( TestStandalone() == 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( TestStandalone() < 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(), KiCadSchematicFileExtension ); if( !schematic.FileExists() ) { wxFileName legacySchematic( Prj().GetProjectPath(), Prj().GetProjectName(), LegacySchematicFileExtension ); if( legacySchematic.FileExists() ) { schematic = legacySchematic; } else { msg.Printf( _( "Schematic file '%s' not found." ), schematic.GetFullPath() ); wxMessageBox( msg, _( "KiCad Error" ), wxOK | wxICON_ERROR, this ); 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 ) { wxMessageBox( _( "Eeschema failed to load." ) + wxS( "\n" ) + err.What(), _( "KiCad Error" ), wxOK | wxICON_ERROR, this ); return; } } // If Kiway() cannot create the eeschema frame, it shows a error message, and // frame is null if( !frame ) return; if( !frame->IsShown() ) // 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 ) { auto editor = (FOOTPRINT_EDIT_FRAME*) Kiway().Player( FRAME_FOOTPRINT_EDITOR, true ); editor->LoadFootprintFromBoard( aFootprint ); editor->Show( true ); editor->Raise(); // Iconize( false ); } else if( retvalue == DIALOG_FOOTPRINT_PROPERTIES::FP_PROPS_EDIT_LIBRARY_FP ) { auto editor = (FOOTPRINT_EDIT_FRAME*) Kiway().Player( FRAME_FOOTPRINT_EDITOR, true ); editor->LoadFootprintFromLibrary( aFootprint->GetFPID() ); editor->Show( true ); 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(); } void PCB_EDIT_FRAME::CommonSettingsChanged( bool aEnvVarsChanged, bool aTextVarsChanged ) { PCB_BASE_EDIT_FRAME::CommonSettingsChanged( aEnvVarsChanged, aTextVarsChanged ); GetAppearancePanel()->OnColorThemeChanged(); KIGFX::PCB_VIEW* view = GetCanvas()->GetView(); KIGFX::PCB_PAINTER* painter = static_cast( view->GetPainter() ); KIGFX::PCB_RENDER_SETTINGS* renderSettings = painter->GetSettings(); renderSettings->LoadDisplayOptions( GetDisplayOptions() ); 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 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( "LayersManager" ).IsShown(); } bool PCB_EDIT_FRAME::PropertiesShown() { return m_auimgr.GetPane( "PropertiesManager" ).IsShown(); } void PCB_EDIT_FRAME::onSize( wxSizeEvent& aEvent ) { if( IsShown() ) { // 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, true ); } // Skip() is called in the base class. EDA_DRAW_FRAME::OnSize( aEvent ); }