848 lines
24 KiB
C++
848 lines
24 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2016-2023 CERN
|
|
* Copyright (C) 2016-2024 KiCad Developers, see AUTHORS.txt for contributors.
|
|
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
|
* @author Maciej Suminski <maciej.suminski@cern.ch>
|
|
*
|
|
* 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, you may find one here:
|
|
* https://www.gnu.org/licenses/gpl-3.0.html
|
|
* or you may search the http://www.gnu.org website for the version 3 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <wx/debug.h>
|
|
|
|
// For some obscure reason, needed on msys2 with some wxWidgets versions (3.0) to avoid
|
|
// undefined symbol at link stage (due to use of #include <pegtl.hpp>)
|
|
// Should not create issues on other platforms
|
|
#include <wx/menu.h>
|
|
|
|
#include <project/project_file.h>
|
|
#include <sch_edit_frame.h>
|
|
#include <kiway.h>
|
|
#include <confirm.h>
|
|
#include <bitmaps.h>
|
|
#include <wildcards_and_files_ext.h>
|
|
#include <widgets/tuner_slider.h>
|
|
#include <widgets/grid_color_swatch_helpers.h>
|
|
#include <widgets/wx_grid.h>
|
|
#include <tool/tool_manager.h>
|
|
#include <tool/tool_dispatcher.h>
|
|
#include <tool/action_manager.h>
|
|
#include <tool/action_toolbar.h>
|
|
#include <tool/common_control.h>
|
|
#include <tools/simulator_control.h>
|
|
#include <tools/ee_actions.h>
|
|
#include <string_utils.h>
|
|
#include <pgm_base.h>
|
|
#include "ngspice.h"
|
|
#include <sim/simulator_frame.h>
|
|
#include <sim/simulator_frame_ui.h>
|
|
#include <sim/sim_plot_tab.h>
|
|
#include <sim/spice_simulator.h>
|
|
#include <sim/simulator_reporter.h>
|
|
#include <eeschema_settings.h>
|
|
#include <advanced_config.h>
|
|
|
|
#include <memory>
|
|
|
|
|
|
// Reporter is stored by pointer in KIBIS, so keep this here to avoid crashes
|
|
static wxString s_errors;
|
|
static WX_STRING_REPORTER s_reporter( &s_errors );
|
|
|
|
|
|
class SIM_THREAD_REPORTER : public SIMULATOR_REPORTER
|
|
{
|
|
public:
|
|
SIM_THREAD_REPORTER( SIMULATOR_FRAME* aParent ) :
|
|
m_parent( aParent )
|
|
{
|
|
}
|
|
|
|
REPORTER& Report( const wxString& aText, SEVERITY aSeverity = RPT_SEVERITY_UNDEFINED ) override
|
|
{
|
|
wxCommandEvent* event = new wxCommandEvent( EVT_SIM_REPORT );
|
|
event->SetString( aText );
|
|
wxQueueEvent( m_parent, event );
|
|
return *this;
|
|
}
|
|
|
|
bool HasMessage() const override
|
|
{
|
|
return false; // Technically "indeterminate" rather than false.
|
|
}
|
|
|
|
void OnSimStateChange( SIMULATOR* aObject, SIM_STATE aNewState ) override
|
|
{
|
|
wxCommandEvent* event = nullptr;
|
|
|
|
switch( aNewState )
|
|
{
|
|
case SIM_IDLE: event = new wxCommandEvent( EVT_SIM_FINISHED ); break;
|
|
case SIM_RUNNING: event = new wxCommandEvent( EVT_SIM_STARTED ); break;
|
|
default: wxFAIL; return;
|
|
}
|
|
|
|
wxQueueEvent( m_parent, event );
|
|
}
|
|
|
|
private:
|
|
SIMULATOR_FRAME* m_parent;
|
|
};
|
|
|
|
|
|
BEGIN_EVENT_TABLE( SIMULATOR_FRAME, KIWAY_PLAYER )
|
|
EVT_MENU( wxID_EXIT, SIMULATOR_FRAME::onExit )
|
|
EVT_MENU( wxID_CLOSE, SIMULATOR_FRAME::onExit )
|
|
END_EVENT_TABLE()
|
|
|
|
|
|
SIMULATOR_FRAME::SIMULATOR_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
|
|
KIWAY_PLAYER( aKiway, aParent, FRAME_SIMULATOR, _( "Simulator" ), wxDefaultPosition,
|
|
wxDefaultSize, wxDEFAULT_FRAME_STYLE, wxT( "simulator" ), unityScale ),
|
|
m_schematicFrame( nullptr ),
|
|
m_toolBar( nullptr ),
|
|
m_ui( nullptr ),
|
|
m_simFinished( false ),
|
|
m_workbookModified( false )
|
|
{
|
|
m_schematicFrame = (SCH_EDIT_FRAME*) Kiway().Player( FRAME_SCH, false );
|
|
wxASSERT( m_schematicFrame );
|
|
|
|
// Give an icon
|
|
wxIcon icon;
|
|
icon.CopyFromBitmap( KiBitmap( BITMAPS::simulator ) );
|
|
SetIcon( icon );
|
|
|
|
wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
|
|
SetSizer( mainSizer );
|
|
|
|
m_infoBar = new WX_INFOBAR( this );
|
|
mainSizer->Add( m_infoBar, 0, wxEXPAND, 0 );
|
|
|
|
m_toolBar = new ACTION_TOOLBAR( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
|
|
wxAUI_TB_DEFAULT_STYLE|wxAUI_TB_HORZ_LAYOUT|wxAUI_TB_PLAIN_BACKGROUND );
|
|
m_toolBar->Realize();
|
|
mainSizer->Add( m_toolBar, 0, wxEXPAND, 5 );
|
|
|
|
m_ui = new SIMULATOR_FRAME_UI( this, m_schematicFrame );
|
|
mainSizer->Add( m_ui, 1, wxEXPAND, 5 );
|
|
|
|
m_simulator = SIMULATOR::CreateInstance( "ngspice" );
|
|
wxASSERT( m_simulator );
|
|
|
|
LoadSettings( config() );
|
|
|
|
NGSPICE_SETTINGS* settings = dynamic_cast<NGSPICE_SETTINGS*>( m_simulator->Settings().get() );
|
|
|
|
wxCHECK2( settings, /* do nothing in release builds*/ );
|
|
|
|
if( settings && settings->GetWorkbookFilename().IsEmpty() )
|
|
settings->SetCompatibilityMode( NGSPICE_COMPATIBILITY_MODE::LT_PSPICE );
|
|
|
|
m_simulator->Init();
|
|
|
|
m_reporter = new SIM_THREAD_REPORTER( this );
|
|
m_simulator->SetReporter( m_reporter );
|
|
|
|
m_circuitModel = std::make_shared<SPICE_CIRCUIT_MODEL>( &m_schematicFrame->Schematic(), this );
|
|
|
|
setupTools();
|
|
setupUIConditions();
|
|
|
|
ReCreateHToolbar();
|
|
ReCreateMenuBar();
|
|
|
|
Bind( wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( SIMULATOR_FRAME::onExit ), this,
|
|
wxID_EXIT );
|
|
|
|
Bind( EVT_SIM_UPDATE, &SIMULATOR_FRAME::onUpdateSim, this );
|
|
Bind( EVT_SIM_REPORT, &SIMULATOR_FRAME::onSimReport, this );
|
|
Bind( EVT_SIM_STARTED, &SIMULATOR_FRAME::onSimStarted, this );
|
|
Bind( EVT_SIM_FINISHED, &SIMULATOR_FRAME::onSimFinished, this );
|
|
|
|
// Ensure new items are taken in account by sizers:
|
|
Layout();
|
|
|
|
// resize the subwindows size. At least on Windows, calling wxSafeYield before
|
|
// resizing the subwindows forces the wxSplitWindows size events automatically generated
|
|
// by wxWidgets to be executed before our resize code.
|
|
// Otherwise, the changes made by setSubWindowsSashSize are overwritten by one these
|
|
// events
|
|
wxSafeYield();
|
|
m_ui->SetSubWindowsSashSize();
|
|
|
|
// Ensure the window is on top
|
|
Raise();
|
|
|
|
m_ui->InitWorkbook();
|
|
UpdateTitle();
|
|
}
|
|
|
|
|
|
SIMULATOR_FRAME::~SIMULATOR_FRAME()
|
|
{
|
|
NULL_REPORTER devnull;
|
|
|
|
m_simulator->Attach( nullptr, wxEmptyString, 0, wxEmptyString, devnull );
|
|
m_simulator->SetReporter( nullptr );
|
|
delete m_reporter;
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::setupTools()
|
|
{
|
|
// Create the manager
|
|
m_toolManager = new TOOL_MANAGER;
|
|
m_toolManager->SetEnvironment( nullptr, nullptr, nullptr, config(), this );
|
|
|
|
m_toolDispatcher = new TOOL_DISPATCHER( m_toolManager );
|
|
|
|
// Attach the events to the tool dispatcher
|
|
Bind( wxEVT_CHAR, &TOOL_DISPATCHER::DispatchWxEvent, m_toolDispatcher );
|
|
Bind( wxEVT_CHAR_HOOK, &TOOL_DISPATCHER::DispatchWxEvent, m_toolDispatcher );
|
|
|
|
// Register tools
|
|
m_toolManager->RegisterTool( new COMMON_CONTROL );
|
|
m_toolManager->RegisterTool( new SIMULATOR_CONTROL );
|
|
m_toolManager->InitTools();
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::ShowChangedLanguage()
|
|
{
|
|
EDA_BASE_FRAME::ShowChangedLanguage();
|
|
|
|
UpdateTitle();
|
|
|
|
m_ui->ShowChangedLanguage();
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::LoadSettings( APP_SETTINGS_BASE* aCfg )
|
|
{
|
|
EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( aCfg );
|
|
wxASSERT( cfg );
|
|
|
|
if( cfg )
|
|
{
|
|
EDA_BASE_FRAME::LoadSettings( cfg );
|
|
m_ui->LoadSettings( cfg );
|
|
}
|
|
|
|
PROJECT_FILE& project = Prj().GetProjectFile();
|
|
|
|
NGSPICE* currentSim = dynamic_cast<NGSPICE*>( m_simulator.get() );
|
|
|
|
if( currentSim )
|
|
m_simulator->Settings() = project.m_SchematicSettings->m_NgspiceSettings;
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg )
|
|
{
|
|
EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( aCfg );
|
|
wxASSERT( cfg );
|
|
|
|
if( cfg )
|
|
{
|
|
EDA_BASE_FRAME::SaveSettings( cfg );
|
|
m_ui->SaveSettings( cfg );
|
|
}
|
|
|
|
PROJECT_FILE& project = Prj().GetProjectFile();
|
|
|
|
if( project.m_SchematicSettings )
|
|
{
|
|
bool modified = project.m_SchematicSettings->m_NgspiceSettings->SaveToFile();
|
|
|
|
if( m_schematicFrame && modified )
|
|
m_schematicFrame->OnModify();
|
|
}
|
|
}
|
|
|
|
|
|
WINDOW_SETTINGS* SIMULATOR_FRAME::GetWindowSettings( APP_SETTINGS_BASE* aCfg )
|
|
{
|
|
EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( aCfg );
|
|
wxASSERT( cfg );
|
|
|
|
return cfg ? &cfg->m_Simulator.window : nullptr;
|
|
}
|
|
|
|
|
|
wxString SIMULATOR_FRAME::GetCurrentSimCommand() const
|
|
{
|
|
if( m_ui->GetCurrentSimTab() )
|
|
return m_ui->GetCurrentSimTab()->GetSimCommand();
|
|
else
|
|
return m_circuitModel->GetSchTextSimCommand();
|
|
}
|
|
|
|
|
|
SIM_TYPE SIMULATOR_FRAME::GetCurrentSimType() const
|
|
{
|
|
return SPICE_CIRCUIT_MODEL::CommandToSimType( GetCurrentSimCommand() );
|
|
}
|
|
|
|
|
|
int SIMULATOR_FRAME::GetCurrentOptions() const
|
|
{
|
|
if( SIM_TAB* simTab = m_ui->GetCurrentSimTab() )
|
|
return simTab->GetSimOptions();
|
|
else
|
|
return NETLIST_EXPORTER_SPICE::OPTION_DEFAULT_FLAGS;
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::UpdateTitle()
|
|
{
|
|
bool unsaved = true;
|
|
bool readOnly = false;
|
|
wxString title;
|
|
wxFileName filename = Prj().AbsolutePath( m_simulator->Settings()->GetWorkbookFilename() );
|
|
|
|
if( filename.IsOk() && filename.FileExists() )
|
|
{
|
|
unsaved = false;
|
|
readOnly = !filename.IsFileWritable();
|
|
}
|
|
|
|
if( m_workbookModified )
|
|
title = wxT( "*" ) + filename.GetName();
|
|
else
|
|
title = filename.GetName();
|
|
|
|
if( readOnly )
|
|
title += wxS( " " ) + _( "[Read Only]" );
|
|
|
|
if( unsaved )
|
|
title += wxS( " " ) + _( "[Unsaved]" );
|
|
|
|
title += wxT( " \u2014 " ) + _( "Spice Simulator" );
|
|
|
|
SetTitle( title );
|
|
}
|
|
|
|
|
|
// Don't let the dialog grow too tall: you may not be able to get to the OK button
|
|
#define MAX_MESSAGES 20
|
|
|
|
void SIMULATOR_FRAME::showNetlistErrors( const wxString& aErrors )
|
|
{
|
|
wxArrayString lines = wxSplit( aErrors, '\n' );
|
|
|
|
if( lines.size() > MAX_MESSAGES )
|
|
{
|
|
lines.RemoveAt( MAX_MESSAGES, lines.size() - MAX_MESSAGES );
|
|
lines.Add( wxS( "..." ) );
|
|
}
|
|
|
|
DisplayErrorMessage( this, _( "Errors during netlist generation." ), wxJoin( lines, '\n' ) );
|
|
}
|
|
|
|
|
|
bool SIMULATOR_FRAME::LoadSimulator( const wxString& aSimCommand, unsigned aSimOptions )
|
|
{
|
|
s_errors.clear();
|
|
|
|
if( !m_schematicFrame->ReadyToNetlist( _( "Simulator requires a fully annotated schematic." ) ) )
|
|
return false;
|
|
|
|
// If we are using the new connectivity, make sure that we do a full-rebuild
|
|
if( ADVANCED_CFG::GetCfg().m_IncrementalConnectivity )
|
|
m_schematicFrame->RecalculateConnections( nullptr, GLOBAL_CLEANUP );
|
|
|
|
if( !m_simulator->Attach( m_circuitModel, aSimCommand, aSimOptions, Prj().GetProjectPath(),
|
|
s_reporter ) )
|
|
{
|
|
showNetlistErrors( s_errors );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::ReloadSimulator( const wxString& aSimCommand, unsigned aSimOptions )
|
|
{
|
|
s_errors.clear();
|
|
|
|
if( !m_simulator->Attach( m_circuitModel, aSimCommand, aSimOptions, Prj().GetProjectPath(),
|
|
s_reporter ) )
|
|
{
|
|
showNetlistErrors( s_errors );
|
|
}
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::StartSimulation()
|
|
{
|
|
SIM_TAB* simTab = m_ui->GetCurrentSimTab();
|
|
|
|
if( !simTab )
|
|
return;
|
|
|
|
if( simTab->GetSimCommand().Upper().StartsWith( wxT( "FFT" ) )
|
|
|| simTab->GetSimCommand().Upper().Contains( wxT( "\nFFT" ) ) )
|
|
{
|
|
wxString tranSpicePlot;
|
|
|
|
if( SIM_TAB* tranPlotTab = m_ui->GetSimTab( ST_TRAN ) )
|
|
tranSpicePlot = tranPlotTab->GetSpicePlotName();
|
|
|
|
if( tranSpicePlot.IsEmpty() )
|
|
{
|
|
DisplayErrorMessage( this, _( "You must run a TRAN simulation first; its results"
|
|
"will be used for the fast Fourier transform." ) );
|
|
}
|
|
else
|
|
{
|
|
m_simulator->Command( "setplot " + tranSpicePlot.ToStdString() );
|
|
|
|
wxArrayString commands = wxSplit( simTab->GetSimCommand(), '\n' );
|
|
|
|
for( const wxString& command : commands )
|
|
{
|
|
wxBusyCursor wait;
|
|
m_simulator->Command( command.ToStdString() );
|
|
}
|
|
|
|
simTab->SetSpicePlotName( m_simulator->CurrentPlotName() );
|
|
m_ui->OnSimRefresh( true );
|
|
|
|
#if 0
|
|
m_simulator->Command( "setplot" ); // Print available plots to console
|
|
m_simulator->Command( "display" ); // Print vectors in current plot to console
|
|
#endif
|
|
}
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if( m_ui->GetSimTabIndex( simTab ) == 0
|
|
&& m_circuitModel->GetSchTextSimCommand() != simTab->GetLastSchTextSimCommand() )
|
|
{
|
|
if( simTab->GetLastSchTextSimCommand().IsEmpty()
|
|
|| IsOK( this, _( "Schematic sheet simulation command directive has changed. "
|
|
"Do you wish to update the Simulation Command?" ) ) )
|
|
{
|
|
simTab->SetSimCommand( m_circuitModel->GetSchTextSimCommand() );
|
|
simTab->SetLastSchTextSimCommand( simTab->GetSimCommand() );
|
|
OnModify();
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !LoadSimulator( simTab->GetSimCommand(), simTab->GetSimOptions() ) )
|
|
return;
|
|
|
|
std::unique_lock<std::mutex> simulatorLock( m_simulator->GetMutex(), std::try_to_lock );
|
|
|
|
if( simulatorLock.owns_lock() )
|
|
{
|
|
m_ui->OnSimUpdate();
|
|
m_simulator->Run();
|
|
|
|
// Netlist from schematic may have changed; update signals list, measurements list,
|
|
// etc.
|
|
m_ui->OnPlotSettingsChanged();
|
|
}
|
|
else
|
|
{
|
|
DisplayErrorMessage( this, _( "Another simulation is already running." ) );
|
|
}
|
|
}
|
|
|
|
|
|
SIM_TAB* SIMULATOR_FRAME::NewSimTab( const wxString& aSimCommand )
|
|
{
|
|
return m_ui->NewSimTab( aSimCommand );
|
|
}
|
|
|
|
|
|
const std::vector<wxString> SIMULATOR_FRAME::SimPlotVectors()
|
|
{
|
|
return m_ui->SimPlotVectors();
|
|
}
|
|
|
|
|
|
const std::vector<wxString> SIMULATOR_FRAME::Signals()
|
|
{
|
|
return m_ui->Signals();
|
|
}
|
|
|
|
|
|
const std::map<int, wxString>& SIMULATOR_FRAME::UserDefinedSignals()
|
|
{
|
|
return m_ui->UserDefinedSignals();
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::SetUserDefinedSignals( const std::map<int, wxString>& aSignals )
|
|
{
|
|
m_ui->SetUserDefinedSignals( aSignals );
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::AddVoltageTrace( const wxString& aNetName )
|
|
{
|
|
m_ui->AddTrace( aNetName, SPT_VOLTAGE );
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::AddCurrentTrace( const wxString& aDeviceName )
|
|
{
|
|
m_ui->AddTrace( aDeviceName, SPT_CURRENT );
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::AddTuner( const SCH_SHEET_PATH& aSheetPath, SCH_SYMBOL* aSymbol )
|
|
{
|
|
m_ui->AddTuner( aSheetPath, aSymbol );
|
|
}
|
|
|
|
|
|
SIM_TAB* SIMULATOR_FRAME::GetCurrentSimTab() const
|
|
{
|
|
return m_ui->GetCurrentSimTab();
|
|
}
|
|
|
|
|
|
bool SIMULATOR_FRAME::LoadWorkbook( const wxString& aPath )
|
|
{
|
|
if( m_ui->LoadWorkbook( aPath ) )
|
|
{
|
|
UpdateTitle();
|
|
|
|
// Successfully loading a workbook does not count as modifying it. Clear the modified
|
|
// flag after all the EVT_WORKBOOK_MODIFIED events have been processed.
|
|
CallAfter( [=]()
|
|
{
|
|
m_workbookModified = false;
|
|
} );
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool SIMULATOR_FRAME::SaveWorkbook( const wxString& aPath )
|
|
{
|
|
if( m_ui->SaveWorkbook( aPath ) )
|
|
{
|
|
m_workbookModified = false;
|
|
UpdateTitle();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::ToggleDarkModePlots()
|
|
{
|
|
m_ui->ToggleDarkModePlots();
|
|
}
|
|
|
|
|
|
bool SIMULATOR_FRAME::EditAnalysis()
|
|
{
|
|
SIM_TAB* simTab = m_ui->GetCurrentSimTab();
|
|
DIALOG_SIM_COMMAND dlg( this, m_circuitModel, m_simulator->Settings() );
|
|
|
|
s_errors.clear();
|
|
|
|
if( !simTab )
|
|
return false;
|
|
|
|
if( !m_circuitModel->ReadSchematicAndLibraries( NETLIST_EXPORTER_SPICE::OPTION_DEFAULT_FLAGS,
|
|
s_reporter ) )
|
|
{
|
|
showNetlistErrors( s_errors );
|
|
}
|
|
|
|
dlg.SetSimCommand( simTab->GetSimCommand() );
|
|
dlg.SetSimOptions( simTab->GetSimOptions() );
|
|
dlg.SetPlotSettings( simTab );
|
|
|
|
if( dlg.ShowModal() == wxID_OK )
|
|
{
|
|
simTab->SetSimCommand( dlg.GetSimCommand() );
|
|
dlg.ApplySettings( simTab );
|
|
m_ui->OnPlotSettingsChanged();
|
|
OnModify();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool SIMULATOR_FRAME::canCloseWindow( wxCloseEvent& aEvent )
|
|
{
|
|
if( m_workbookModified )
|
|
{
|
|
wxFileName filename = m_simulator->Settings()->GetWorkbookFilename();
|
|
|
|
if( filename.GetName().IsEmpty() )
|
|
{
|
|
if( Prj().GetProjectName().IsEmpty() )
|
|
filename.SetFullName( wxT( "noname.wbk" ) );
|
|
else
|
|
filename.SetFullName( Prj().GetProjectName() + wxT( ".wbk" ) );
|
|
}
|
|
|
|
return HandleUnsavedChanges( this, _( "Save changes to workbook?" ),
|
|
[&]() -> bool
|
|
{
|
|
return SaveWorkbook( Prj().AbsolutePath( filename.GetFullName() ) );
|
|
} );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::doCloseWindow()
|
|
{
|
|
if( m_simulator->IsRunning() )
|
|
m_simulator->Stop();
|
|
|
|
// Prevent memory leak on exit by deleting all simulation vectors
|
|
m_simulator->Clean();
|
|
|
|
// Cancel a running simProbe or simTune tool
|
|
m_schematicFrame->GetToolManager()->PostAction( ACTIONS::cancelInteractive );
|
|
|
|
SaveSettings( config() );
|
|
|
|
m_simulator->Settings() = nullptr;
|
|
|
|
Destroy();
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::setupUIConditions()
|
|
{
|
|
EDA_BASE_FRAME::setupUIConditions();
|
|
|
|
ACTION_MANAGER* mgr = m_toolManager->GetActionManager();
|
|
wxASSERT( mgr );
|
|
|
|
auto showGridCondition =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
|
|
return plotTab && plotTab->IsGridShown();
|
|
};
|
|
|
|
auto showLegendCondition =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
|
|
return plotTab && plotTab->IsLegendShown();
|
|
};
|
|
|
|
auto showDottedCondition =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
|
|
return plotTab && plotTab->GetDottedSecondary();
|
|
};
|
|
|
|
auto darkModePlotCondition =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
return m_ui->DarkModePlots();
|
|
};
|
|
|
|
auto simRunning =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
return m_simulator && m_simulator->IsRunning();
|
|
};
|
|
|
|
auto simFinished =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
return m_simFinished;
|
|
};
|
|
|
|
auto haveSim =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
return GetCurrentSimTab() != nullptr;
|
|
};
|
|
|
|
auto havePlot =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
return dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) != nullptr;
|
|
};
|
|
|
|
auto haveZoomUndo =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
|
|
return plotTab && plotTab->GetPlotWin()->UndoZoomStackSize() > 0;
|
|
};
|
|
|
|
auto haveZoomRedo =
|
|
[this]( const SELECTION& aSel )
|
|
{
|
|
SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
|
|
return plotTab && plotTab->GetPlotWin()->RedoZoomStackSize() > 0;
|
|
};
|
|
|
|
#define ENABLE( x ) ACTION_CONDITIONS().Enable( x )
|
|
#define CHECK( x ) ACTION_CONDITIONS().Check( x )
|
|
|
|
mgr->SetConditions( EE_ACTIONS::openWorkbook, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
|
|
mgr->SetConditions( EE_ACTIONS::saveWorkbook, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
|
|
mgr->SetConditions( EE_ACTIONS::saveWorkbookAs, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
|
|
|
|
mgr->SetConditions( EE_ACTIONS::exportPlotAsPNG, ENABLE( havePlot ) );
|
|
mgr->SetConditions( EE_ACTIONS::exportPlotAsCSV, ENABLE( havePlot ) );
|
|
|
|
mgr->SetConditions( ACTIONS::zoomUndo, ENABLE( haveZoomUndo ) );
|
|
mgr->SetConditions( ACTIONS::zoomRedo, ENABLE( haveZoomRedo ) );
|
|
mgr->SetConditions( EE_ACTIONS::toggleGrid, CHECK( showGridCondition ) );
|
|
mgr->SetConditions( EE_ACTIONS::toggleLegend, CHECK( showLegendCondition ) );
|
|
mgr->SetConditions( EE_ACTIONS::toggleDottedSecondary, CHECK( showDottedCondition ) );
|
|
mgr->SetConditions( EE_ACTIONS::toggleDarkModePlots, CHECK( darkModePlotCondition ) );
|
|
|
|
mgr->SetConditions( EE_ACTIONS::newAnalysisTab, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
|
|
mgr->SetConditions( EE_ACTIONS::simAnalysisProperties, ENABLE( haveSim ) );
|
|
mgr->SetConditions( EE_ACTIONS::runSimulation, ENABLE( !simRunning ) );
|
|
mgr->SetConditions( EE_ACTIONS::stopSimulation, ENABLE( simRunning ) );
|
|
mgr->SetConditions( EE_ACTIONS::simProbe, ENABLE( simFinished ) );
|
|
mgr->SetConditions( EE_ACTIONS::simTune, ENABLE( simFinished ) );
|
|
mgr->SetConditions( EE_ACTIONS::showNetlist, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
|
|
|
|
#undef CHECK
|
|
#undef ENABLE
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::onSimStarted( wxCommandEvent& aEvent )
|
|
{
|
|
SetCursor( wxCURSOR_ARROWWAIT );
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::onSimFinished( wxCommandEvent& aEvent )
|
|
{
|
|
// Sometimes (for instance with a directive like wrdata my_file.csv "my_signal")
|
|
// the simulator is in idle state (simulation is finished), but still running, during
|
|
// the time the file is written. So gives a slice of time to fully finish the work:
|
|
if( m_simulator->IsRunning() )
|
|
{
|
|
int max_time = 40; // For a max timeout = 2s
|
|
|
|
do
|
|
{
|
|
wxMilliSleep( 50 );
|
|
wxYield();
|
|
|
|
if( max_time )
|
|
max_time--;
|
|
|
|
} while( max_time && m_simulator->IsRunning() );
|
|
}
|
|
|
|
// ensure the shown cursor is the default cursor, not the wxCURSOR_ARROWWAIT set when
|
|
// staring the simulator in onSimStarted:
|
|
SetCursor( wxNullCursor );
|
|
|
|
// Is a warning message useful if the simulatior is still running?
|
|
SCHEMATIC& schematic = m_schematicFrame->Schematic();
|
|
schematic.ClearOperatingPoints();
|
|
|
|
m_simFinished = true;
|
|
|
|
m_ui->OnSimRefresh( true );
|
|
|
|
m_schematicFrame->RefreshOperatingPointDisplay();
|
|
m_schematicFrame->GetCanvas()->Refresh();
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::onUpdateSim( wxCommandEvent& aEvent )
|
|
{
|
|
static bool updateInProgress = false;
|
|
|
|
// skip update when events are triggered too often and previous call didn't end yet
|
|
if( updateInProgress )
|
|
return;
|
|
|
|
updateInProgress = true;
|
|
|
|
if( m_simulator->IsRunning() )
|
|
m_simulator->Stop();
|
|
|
|
std::unique_lock<std::mutex> simulatorLock( m_simulator->GetMutex(), std::try_to_lock );
|
|
|
|
if( simulatorLock.owns_lock() )
|
|
{
|
|
m_ui->OnSimUpdate();
|
|
m_simulator->Run();
|
|
}
|
|
else
|
|
{
|
|
DisplayErrorMessage( this, _( "Another simulation is already running." ) );
|
|
}
|
|
|
|
updateInProgress = false;
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::onSimReport( wxCommandEvent& aEvent )
|
|
{
|
|
m_ui->OnSimReport( aEvent.GetString() );
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::onExit( wxCommandEvent& aEvent )
|
|
{
|
|
if( aEvent.GetId() == wxID_EXIT )
|
|
Kiway().OnKiCadExit();
|
|
|
|
if( aEvent.GetId() == wxID_CLOSE )
|
|
Close( false );
|
|
}
|
|
|
|
|
|
void SIMULATOR_FRAME::OnModify()
|
|
{
|
|
KIWAY_PLAYER::OnModify();
|
|
m_workbookModified = true;
|
|
UpdateTitle();
|
|
}
|
|
|
|
|
|
wxDEFINE_EVENT( EVT_SIM_UPDATE, wxCommandEvent );
|
|
wxDEFINE_EVENT( EVT_SIM_REPORT, wxCommandEvent );
|
|
|
|
wxDEFINE_EVENT( EVT_SIM_STARTED, wxCommandEvent );
|
|
wxDEFINE_EVENT( EVT_SIM_FINISHED, wxCommandEvent );
|