2586 lines
88 KiB
C++
2586 lines
88 KiB
C++
/*
|
|
* 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 <dick@softplc.com>
|
|
* Copyright (C) 2013 Wayne Stambaugh <stambaughw@gmail.com>
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <advanced_config.h>
|
|
#include <kiface_base.h>
|
|
#include <kiway.h>
|
|
#include <pgm_base.h>
|
|
#include <pcb_edit_frame.h>
|
|
#include <3d_viewer/eda_3d_viewer_frame.h>
|
|
#include <fp_lib_table.h>
|
|
#include <bitmaps.h>
|
|
#include <confirm.h>
|
|
#include <trace_helpers.h>
|
|
#include <pcbnew_id.h>
|
|
#include <pcbnew_settings.h>
|
|
#include <pcb_layer_box_selector.h>
|
|
#include <footprint_edit_frame.h>
|
|
#include <dialog_plot.h>
|
|
#include <dialog_find.h>
|
|
#include <dialog_footprint_properties.h>
|
|
#include <dialogs/dialog_exchange_footprints.h>
|
|
#include <dialogs/dialog_net_inspector.h>
|
|
#include <dialog_board_setup.h>
|
|
#include <invoke_pcb_dialog.h>
|
|
#include <gal/graphics_abstraction_layer.h>
|
|
#include <board.h>
|
|
#include <pcb_group.h>
|
|
#include <board_design_settings.h>
|
|
#include <footprint.h>
|
|
#include <drawing_sheet/ds_proxy_view_item.h>
|
|
#include <connectivity/connectivity_data.h>
|
|
#include <wildcards_and_files_ext.h>
|
|
#include <pcb_draw_panel_gal.h>
|
|
#include <functional>
|
|
#include <pcb_painter.h>
|
|
#include <project/project_file.h>
|
|
#include <project/project_local_settings.h>
|
|
#include <python_scripting.h>
|
|
#include <settings/common_settings.h>
|
|
#include <settings/settings_manager.h>
|
|
#include <tool/tool_manager.h>
|
|
#include <tool/tool_dispatcher.h>
|
|
#include <tool/action_toolbar.h>
|
|
#include <tool/common_control.h>
|
|
#include <tool/common_tools.h>
|
|
#include <tool/properties_tool.h>
|
|
#include <tool/selection.h>
|
|
#include <tool/zoom_tool.h>
|
|
#include <tools/pcb_selection_tool.h>
|
|
#include <tools/pcb_picker_tool.h>
|
|
#include <tools/pcb_point_editor.h>
|
|
#include <tools/edit_tool.h>
|
|
#include <tools/group_tool.h>
|
|
#include <tools/generator_tool.h>
|
|
#include <tools/drc_tool.h>
|
|
#include <tools/global_edit_tool.h>
|
|
#include <tools/convert_tool.h>
|
|
#include <tools/drawing_tool.h>
|
|
#include <tools/pcb_control.h>
|
|
#include <tools/board_editor_control.h>
|
|
#include <tools/board_inspection_tool.h>
|
|
#include <tools/pcb_editor_conditions.h>
|
|
#include <tools/pcb_viewer_tools.h>
|
|
#include <tools/board_reannotate_tool.h>
|
|
#include <tools/placement_tool.h>
|
|
#include <tools/pad_tool.h>
|
|
#include <microwave/microwave_tool.h>
|
|
#include <tools/position_relative_tool.h>
|
|
#include <tools/zone_filler_tool.h>
|
|
#include <tools/pcb_actions.h>
|
|
#include <router/router_tool.h>
|
|
#include <router/length_tuner_tool.h>
|
|
#include <autorouter/autoplace_tool.h>
|
|
#include <python/scripting/pcb_scripting_tool.h>
|
|
#include <gestfich.h>
|
|
#include <executable_names.h>
|
|
#include <netlist_reader/netlist_reader.h>
|
|
#include <wx/socket.h>
|
|
#include <wx/wupdlock.h>
|
|
#include <dialog_drc.h> // for DIALOG_DRC_WINDOW_NAME definition
|
|
#include <ratsnest/ratsnest_view_item.h>
|
|
#include <widgets/appearance_controls.h>
|
|
#include <widgets/pcb_search_pane.h>
|
|
#include <widgets/wx_infobar.h>
|
|
#include <widgets/panel_selection_filter.h>
|
|
#include <widgets/pcb_properties_panel.h>
|
|
#include <widgets/wx_aui_utils.h>
|
|
#include <kiplatform/app.h>
|
|
#include <core/profile.h>
|
|
#include <view/wx_view_controls.h>
|
|
#include <footprint_viewer_frame.h>
|
|
#include <footprint_chooser_frame.h>
|
|
|
|
#include <action_plugin.h>
|
|
#include <pcbnew_scripting_helpers.h>
|
|
#include "../scripting/python_scripting.h"
|
|
|
|
#include <wx/filedlg.h>
|
|
|
|
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_netInspectorDlg( 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;
|
|
|
|
// 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 ) );
|
|
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();
|
|
|
|
|
|
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_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( 180, -1 ).BestSize( 180, -1 ) );
|
|
|
|
m_auimgr.AddPane( m_selectionFilterPanel, EDA_PANE().Name( wxS( "SelectionFilter" ) )
|
|
.Right().Layer( 4 ).Position( 2 )
|
|
.Caption( _( "Selection Filter" ) ).PaneBorder( false )
|
|
.MinSize( 180, -1 ).BestSize( 180, -1 ) );
|
|
|
|
m_auimgr.AddPane( m_propertiesPanel, EDA_PANE().Name( PropertiesPaneName() )
|
|
.Left().Layer( 5 )
|
|
.Caption( _( "Properties" ) ).PaneBorder( false )
|
|
.MinSize( 240, -1 ).BestSize( 300, -1 ) );
|
|
|
|
// Center
|
|
m_auimgr.AddPane( GetCanvas(), EDA_PANE().Canvas().Name( wxS( "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 );
|
|
m_auimgr.GetPane( PropertiesPaneName() ).Show( GetPcbNewSettings()->m_AuiPanels.show_properties );
|
|
|
|
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<PCBNEW_SETTINGS*>( 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 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 );
|
|
|
|
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<KIGFX::WX_VIEW_CONTROLS*>( 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 );
|
|
Bind( EDA_EVT_CLOSE_NET_INSPECTOR_DIALOG, &PCB_EDIT_FRAME::onCloseNetInspectorDialog, this );
|
|
Bind( EDA_EVT_UNITS_CHANGED, &PCB_EDIT_FRAME::onUnitsChanged, this );
|
|
|
|
m_acceptedExts.emplace( KiCadPcbFileExtension, &PCB_ACTIONS::ddAppendBoard );
|
|
m_acceptedExts.emplace( 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;
|
|
}
|
|
|
|
// 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<PCBNEW_SETTINGS*>( 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<PCB_SELECTION_TOOL>()->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 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<PCB_TOOL_BASE*>( 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<DRAWING_TOOL>();
|
|
|
|
if( drawingTool && drawingTool->GetDrawingMode() != DRAWING_TOOL::MODE::NONE )
|
|
return true;
|
|
|
|
ROUTER_TOOL* routerTool = m_toolManager->GetTool<ROUTER_TOOL>();
|
|
|
|
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::MoreThan( 1 ) ) );
|
|
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 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<BOARD_INSPECTION_TOOL>();
|
|
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::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<ROUTER_TOOL>();
|
|
return tool && tool->GetRouterMode() == PNS::RM_MarkObstacles;
|
|
};
|
|
|
|
auto isShoveMode =
|
|
[this]( const SELECTION& )
|
|
{
|
|
ROUTER_TOOL* tool = m_toolManager->GetTool<ROUTER_TOOL>();
|
|
return tool && tool->GetRouterMode() == PNS::RM_Shove;
|
|
};
|
|
|
|
auto isWalkaroundMode =
|
|
[this]( const SELECTION& )
|
|
{
|
|
ROUTER_TOOL* tool = m_toolManager->GetTool<ROUTER_TOOL>();
|
|
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<BOARD_CONNECTED_ITEM*>( 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<DRC_TOOL>();
|
|
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::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::placeMeanders );
|
|
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( 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<ZONE_FILLER_TOOL>();
|
|
|
|
if( zoneFillerTool->IsBusy() )
|
|
{
|
|
wxBell();
|
|
|
|
if( wxWindow* reporter = dynamic_cast<wxWindow*>( 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 );
|
|
Unbind( EDA_EVT_CLOSE_NET_INSPECTOR_DIALOG, &PCB_EDIT_FRAME::onCloseNetInspectorDialog, this );
|
|
Unbind( EDA_EVT_UNITS_CHANGED, &PCB_EDIT_FRAME::onUnitsChanged, 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;
|
|
}
|
|
|
|
if( m_netInspectorDlg )
|
|
{
|
|
RemoveBoardChangeListener( m_netInspectorDlg );
|
|
m_netInspectorDlg->Destroy();
|
|
m_netInspectorDlg = nullptr;
|
|
}
|
|
|
|
// 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() ) )
|
|
{
|
|
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<std::mutex> 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 );
|
|
|
|
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<BOARD_ITEM*>( 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<EDA_TEXT*>( 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<PCBNEW_SETTINGS*>( aCfg );
|
|
wxASSERT( cfg );
|
|
|
|
if( cfg )
|
|
{
|
|
m_show_layer_manager_tools = cfg->m_AuiPanels.show_layer_manager;
|
|
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<PCBNEW_SETTINGS*>( 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_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<PCBNEW_SETTINGS*>( 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<BOARD_ITEM*>( 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<PCB_VIA*>( 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<PAD*>( 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<PCB_TRACK*>( 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()
|
|
{
|
|
// JEY TODO: move this global to the board
|
|
ENUM_MAP<PCB_LAYER_ID>& layerEnum = ENUM_MAP<PCB_LAYER_ID>::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<DRC_TOOL>();
|
|
|
|
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();
|
|
m_propertiesPanel->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();
|
|
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<PCB_LAYER_ID>& layerEnum = ENUM_MAP<PCB_LAYER_ID>::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<PCB_SELECTION_TOOL>(), _1 ) );
|
|
}
|
|
|
|
wxString findString;
|
|
|
|
PCB_SELECTION& selection = m_toolManager->GetTool<PCB_SELECTION_TOOL>()->GetSelection();
|
|
|
|
if( selection.Size() == 1 )
|
|
{
|
|
EDA_ITEM* front = selection.Front();
|
|
|
|
switch( front->Type() )
|
|
{
|
|
case PCB_FOOTPRINT_T:
|
|
findString = UnescapeString( static_cast<FOOTPRINT*>( front )->GetValue() );
|
|
break;
|
|
|
|
case PCB_FIELD_T:
|
|
case PCB_TEXT_T:
|
|
findString = UnescapeString( static_cast<PCB_TEXT*>( 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->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<wxString>( 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(),
|
|
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() );
|
|
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->IsShown() ) // the frame exists, (created by the dialog field editor)
|
|
// but no project loaded.
|
|
{
|
|
frame->OpenProjectFiles( std::vector<wxString>( 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<const wxString, ENV_VAR_ITEM>& 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<const wxString, ENV_VAR_ITEM>& 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();
|
|
}
|
|
|
|
|
|
/**
|
|
* 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<PCB_TEXT*> candidates;
|
|
|
|
for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
|
|
{
|
|
PCB_TEXT* candidate = dynamic_cast<PCB_TEXT*>( 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<PCB_TEXT*> 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<PCB_TEXT*> 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 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<PCB_TEXT*>( item );
|
|
|
|
if( srcItem )
|
|
{
|
|
PCB_TEXT* destItem = getMatchingTextItem( srcItem, aNew );
|
|
|
|
if( destItem )
|
|
{
|
|
processTextItem( *srcItem, *destItem, false, resetTextLayers, resetTextEffects,
|
|
aUpdated );
|
|
}
|
|
else if( !deleteExtraTexts )
|
|
{
|
|
aNew->Add( new PCB_TEXT( *srcItem ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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(),
|
|
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, false, 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<KIID&>( aNew->m_Uuid ) = aExisting->m_Uuid;
|
|
aNew->SetPath( aExisting->GetPath() );
|
|
|
|
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<DRC_TOOL>();
|
|
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<void( wxHyperlinkEvent& aEvent )>(
|
|
[&]( 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( wxS( "LayersManager" ) ).IsShown();
|
|
}
|
|
|
|
|
|
bool PCB_EDIT_FRAME::PropertiesShown()
|
|
{
|
|
return m_auimgr.GetPane( PropertiesPaneName() ).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 );
|
|
}
|
|
|
|
// 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,
|
|
_( "Diff 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;
|
|
}
|
|
}
|
|
|
|
|
|
DIALOG_NET_INSPECTOR* PCB_EDIT_FRAME::GetNetInspectorDialog()
|
|
{
|
|
if( !m_netInspectorDlg )
|
|
{
|
|
m_netInspectorDlg = new DIALOG_NET_INSPECTOR( this );
|
|
AddBoardChangeListener( m_netInspectorDlg );
|
|
}
|
|
|
|
return m_netInspectorDlg;
|
|
}
|
|
|
|
|
|
void PCB_EDIT_FRAME::onCloseNetInspectorDialog( wxCommandEvent& aEvent )
|
|
{
|
|
if( m_netInspectorDlg )
|
|
{
|
|
RemoveBoardChangeListener( m_netInspectorDlg );
|
|
m_netInspectorDlg->Destroy();
|
|
m_netInspectorDlg = nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
void PCB_EDIT_FRAME::onUnitsChanged( wxCommandEvent& aEvent )
|
|
{
|
|
wxCommandEvent evt( EDA_EVT_UNITS_CHANGED );
|
|
|
|
if( m_netInspectorDlg )
|
|
m_netInspectorDlg->HandleWindowEvent( evt );
|
|
}
|