mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 18:23:15 +02:00
Unfortunately, Windows headers define a lot of macros for common words, so we had to rename some enums to not collide. We also fix some of the many bugs related to the new simulation architecture and the Spice Model Editor dialog.
1913 lines
55 KiB
C++
1913 lines
55 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2016 CERN
|
|
* Copyright (C) 2016-2021 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>
|
|
#include <wx/stc/stc.h>
|
|
|
|
#include <project/project_file.h>
|
|
#include <sch_edit_frame.h>
|
|
#include <eeschema_id.h>
|
|
#include <kiway.h>
|
|
#include <confirm.h>
|
|
#include <bitmaps.h>
|
|
#include <wildcards_and_files_ext.h>
|
|
#include <widgets/tuner_slider.h>
|
|
#include <dialogs/dialog_signal_list.h>
|
|
#include "string_utils.h"
|
|
#include "ngspice_helpers.h"
|
|
#include <pgm_base.h>
|
|
#include "ngspice.h"
|
|
#include "sim_plot_colors.h"
|
|
#include "sim_plot_frame.h"
|
|
#include "sim_plot_panel.h"
|
|
#include "spice_simulator.h"
|
|
#include "spice_reporter.h"
|
|
#include <menus_helpers.h>
|
|
#include <tool/tool_manager.h>
|
|
#include <tools/ee_actions.h>
|
|
#include <eeschema_settings.h>
|
|
#include <wx/ffile.h>
|
|
#include <wx/filedlg.h>
|
|
#include <dialog_shim.h>
|
|
|
|
|
|
SIM_PLOT_TYPE operator|( SIM_PLOT_TYPE aFirst, SIM_PLOT_TYPE aSecond )
|
|
{
|
|
int res = (int) aFirst | (int) aSecond;
|
|
|
|
return (SIM_PLOT_TYPE) res;
|
|
}
|
|
|
|
|
|
class SIM_THREAD_REPORTER : public SPICE_REPORTER
|
|
{
|
|
public:
|
|
SIM_THREAD_REPORTER( SIM_PLOT_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( SPICE_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:
|
|
SIM_PLOT_FRAME* m_parent;
|
|
};
|
|
|
|
|
|
SIM_PLOT_FRAME::SIM_PLOT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
|
|
SIM_PLOT_FRAME_BASE( aParent ),
|
|
m_lastSimPlot( nullptr ),
|
|
m_plotNumber( 0 ),
|
|
m_simFinished( false )
|
|
{
|
|
SetKiway( this, aKiway );
|
|
m_signalsIconColorList = nullptr;
|
|
|
|
m_schematicFrame = (SCH_EDIT_FRAME*) Kiway().Player( FRAME_SCH, false );
|
|
|
|
if( m_schematicFrame == nullptr )
|
|
throw std::runtime_error( "There is no schematic window" );
|
|
|
|
// Give an icon
|
|
wxIcon icon;
|
|
icon.CopyFromBitmap( KiBitmap( BITMAPS::simulator ) );
|
|
SetIcon( icon );
|
|
|
|
m_simulator = SIMULATOR::CreateInstance( "ngspice" );
|
|
|
|
if( !m_simulator )
|
|
{
|
|
throw std::runtime_error( "Could not create simulator instance" );
|
|
return;
|
|
}
|
|
|
|
// Get the previous size and position of windows:
|
|
LoadSettings( config() );
|
|
|
|
// Prepare the color list to plot traces
|
|
SIM_PLOT_COLORS::FillDefaultColorList( GetPlotBgOpt() );
|
|
|
|
// Give icons to menuitems
|
|
setIconsForMenuItems();
|
|
|
|
m_simulator->Init();
|
|
|
|
m_reporter = new SIM_THREAD_REPORTER( this );
|
|
m_simulator->SetReporter( m_reporter );
|
|
|
|
// the settings dialog will be created later, on demand.
|
|
// if created in the ctor, for some obscure reason, there is an issue
|
|
// on Windows: when open it, the simulator frame is sent to the background.
|
|
// instead of being behind the dialog frame (as it does)
|
|
m_settingsDlg = nullptr;
|
|
|
|
m_circuitModel.reset( new NGSPICE_CIRCUIT_MODEL( &m_schematicFrame->Schematic() ) );
|
|
|
|
Bind( EVT_SIM_UPDATE, &SIM_PLOT_FRAME::onSimUpdate, this );
|
|
Bind( EVT_SIM_REPORT, &SIM_PLOT_FRAME::onSimReport, this );
|
|
Bind( EVT_SIM_STARTED, &SIM_PLOT_FRAME::onSimStarted, this );
|
|
Bind( EVT_SIM_FINISHED, &SIM_PLOT_FRAME::onSimFinished, this );
|
|
Bind( EVT_SIM_CURSOR_UPDATE, &SIM_PLOT_FRAME::onCursorUpdate, this );
|
|
|
|
// Toolbar buttons
|
|
m_toolSimulate = m_toolBar->AddTool( ID_SIM_RUN, _( "Run/Stop Simulation" ),
|
|
KiBitmap( BITMAPS::sim_run ), _( "Run Simulation" ), wxITEM_NORMAL );
|
|
m_toolAddSignals = m_toolBar->AddTool( ID_SIM_ADD_SIGNALS, _( "Add Signals" ),
|
|
KiBitmap( BITMAPS::sim_add_signal ), _( "Add signals to plot" ), wxITEM_NORMAL );
|
|
m_toolProbe = m_toolBar->AddTool( ID_SIM_PROBE, _( "Probe" ),
|
|
KiBitmap( BITMAPS::sim_probe ), _( "Probe signals on the schematic" ), wxITEM_NORMAL );
|
|
m_toolTune = m_toolBar->AddTool( ID_SIM_TUNE, _( "Tune" ),
|
|
KiBitmap( BITMAPS::sim_tune ), _( "Tune component values" ), wxITEM_NORMAL );
|
|
m_toolSettings = m_toolBar->AddTool( wxID_ANY, _( "Sim Parameters" ),
|
|
KiBitmap( BITMAPS::config ), _( "Simulation parameters and settings" ), wxITEM_NORMAL );
|
|
|
|
// Start all toolbar buttons except settings as disabled
|
|
m_toolSimulate->Enable( false );
|
|
m_toolAddSignals->Enable( false );
|
|
m_toolProbe->Enable( false );
|
|
m_toolTune->Enable( false );
|
|
m_toolSettings->Enable( true );
|
|
|
|
Bind( wxEVT_UPDATE_UI, &SIM_PLOT_FRAME::menuSimulateUpdate, this, m_toolSimulate->GetId() );
|
|
Bind( wxEVT_UPDATE_UI, &SIM_PLOT_FRAME::menuAddSignalsUpdate, this,
|
|
m_toolAddSignals->GetId() );
|
|
Bind( wxEVT_UPDATE_UI, &SIM_PLOT_FRAME::menuProbeUpdate, this, m_toolProbe->GetId() );
|
|
Bind( wxEVT_UPDATE_UI, &SIM_PLOT_FRAME::menuTuneUpdate, this, m_toolTune->GetId() );
|
|
|
|
Bind( wxEVT_COMMAND_TOOL_CLICKED, &SIM_PLOT_FRAME::onSimulate, this, m_toolSimulate->GetId() );
|
|
Bind( wxEVT_COMMAND_TOOL_CLICKED, &SIM_PLOT_FRAME::onAddSignal, this,
|
|
m_toolAddSignals->GetId() );
|
|
Bind( wxEVT_COMMAND_TOOL_CLICKED, &SIM_PLOT_FRAME::onProbe, this, m_toolProbe->GetId() );
|
|
Bind( wxEVT_COMMAND_TOOL_CLICKED, &SIM_PLOT_FRAME::onTune, this, m_toolTune->GetId() );
|
|
Bind( wxEVT_COMMAND_TOOL_CLICKED, &SIM_PLOT_FRAME::onSettings, this, m_toolSettings->GetId() );
|
|
|
|
Bind( EVT_WORKBOOK_MODIFIED, &SIM_PLOT_FRAME::onWorkbookModified, this );
|
|
Bind( EVT_WORKBOOK_CLR_MODIFIED, &SIM_PLOT_FRAME::onWorkbookClrModified, this );
|
|
|
|
// Bind toolbar buttons event to existing menu event handlers, so they behave the same
|
|
Bind( wxEVT_COMMAND_MENU_SELECTED, &SIM_PLOT_FRAME::onSimulate, this,
|
|
m_runSimulation->GetId() );
|
|
Bind( wxEVT_COMMAND_MENU_SELECTED, &SIM_PLOT_FRAME::onAddSignal, this, m_addSignals->GetId() );
|
|
Bind( wxEVT_COMMAND_MENU_SELECTED, &SIM_PLOT_FRAME::onProbe, this, m_probeSignals->GetId() );
|
|
Bind( wxEVT_COMMAND_MENU_SELECTED, &SIM_PLOT_FRAME::onTune, this, m_tuneValue->GetId() );
|
|
Bind( wxEVT_COMMAND_MENU_SELECTED, &SIM_PLOT_FRAME::onShowNetlist, this,
|
|
m_showNetlist->GetId() );
|
|
Bind( wxEVT_COMMAND_MENU_SELECTED, &SIM_PLOT_FRAME::onSettings, this,
|
|
m_boardAdapter->GetId() );
|
|
|
|
m_toolBar->Realize();
|
|
|
|
#ifndef wxHAS_NATIVE_TABART
|
|
// Non-native default tab art has ugly gradients we don't want
|
|
m_workbook->SetArtProvider( new wxAuiSimpleTabArt() );
|
|
#endif
|
|
|
|
// 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();
|
|
setSubWindowsSashSize();
|
|
|
|
// Ensure the window is on top
|
|
Raise();
|
|
|
|
initWorkbook();
|
|
updateTitle();
|
|
}
|
|
|
|
|
|
SIM_PLOT_FRAME::~SIM_PLOT_FRAME()
|
|
{
|
|
m_simulator->Attach( nullptr );
|
|
m_simulator->SetReporter( nullptr );
|
|
delete m_reporter;
|
|
delete m_signalsIconColorList;
|
|
|
|
if( m_settingsDlg )
|
|
m_settingsDlg->Destroy();
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::LoadSettings( APP_SETTINGS_BASE* aCfg )
|
|
{
|
|
EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( aCfg );
|
|
wxASSERT( cfg );
|
|
|
|
if( cfg )
|
|
{
|
|
EDA_BASE_FRAME::LoadSettings( cfg );
|
|
|
|
// Read subwindows sizes (should be > 0 )
|
|
m_splitterLeftRightSashPosition = cfg->m_Simulator.plot_panel_width;
|
|
m_splitterPlotAndConsoleSashPosition = cfg->m_Simulator.plot_panel_height;
|
|
m_splitterSignalsSashPosition = cfg->m_Simulator.signal_panel_height;
|
|
m_splitterTuneValuesSashPosition = cfg->m_Simulator.cursors_panel_height;
|
|
m_plotUseWhiteBg = cfg->m_Simulator.white_background;
|
|
}
|
|
|
|
PROJECT_FILE& project = Prj().GetProjectFile();
|
|
|
|
NGSPICE* currentSim = dynamic_cast<NGSPICE*>( m_simulator.get() );
|
|
|
|
if( currentSim )
|
|
m_simulator->Settings() = project.m_SchematicSettings->m_NgspiceSimulatorSettings;
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg )
|
|
{
|
|
EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( aCfg );
|
|
wxASSERT( cfg );
|
|
|
|
if( cfg )
|
|
{
|
|
EDA_BASE_FRAME::SaveSettings( cfg );
|
|
|
|
cfg->m_Simulator.plot_panel_width = m_splitterLeftRight->GetSashPosition();
|
|
cfg->m_Simulator.plot_panel_height = m_splitterPlotAndConsole->GetSashPosition();
|
|
cfg->m_Simulator.signal_panel_height = m_splitterSignals->GetSashPosition();
|
|
cfg->m_Simulator.cursors_panel_height = m_splitterTuneValues->GetSashPosition();
|
|
cfg->m_Simulator.white_background = m_plotUseWhiteBg;
|
|
}
|
|
|
|
if( !m_isNonUserClose ) // If we're exiting the project has already been released.
|
|
{
|
|
PROJECT_FILE& project = Prj().GetProjectFile();
|
|
|
|
if( project.m_SchematicSettings )
|
|
project.m_SchematicSettings->m_NgspiceSimulatorSettings->SaveToFile();
|
|
|
|
if( m_schematicFrame )
|
|
m_schematicFrame->SaveProjectSettings();
|
|
}
|
|
}
|
|
|
|
|
|
WINDOW_SETTINGS* SIM_PLOT_FRAME::GetWindowSettings( APP_SETTINGS_BASE* aCfg )
|
|
{
|
|
EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( aCfg );
|
|
wxASSERT( cfg );
|
|
|
|
return cfg ? &cfg->m_Simulator.window : nullptr;
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::initWorkbook()
|
|
{
|
|
// Removed for the time being. We cannot run the simulation on simulator launch, as it may
|
|
// take a lot of time, confusing the user.
|
|
// TODO: Change workbook loading routines so that they don't run the simulation until the user
|
|
// initiates it.
|
|
|
|
/*if( !m_simulator->Settings()->GetWorkbookFilename().IsEmpty() )
|
|
{
|
|
wxFileName filename = m_simulator->Settings()->GetWorkbookFilename();
|
|
filename.SetPath( Prj().GetProjectPath() );
|
|
|
|
if( !loadWorkbook( filename.GetFullPath() ) )
|
|
m_simulator->Settings()->SetWorkbookFilename( "" );
|
|
}*/
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::updateTitle()
|
|
{
|
|
wxFileName filename = Prj().AbsolutePath( m_simulator->Settings()->GetWorkbookFilename() );
|
|
|
|
bool readOnly = false;
|
|
bool unsaved = false;
|
|
|
|
if( filename.IsOk() && filename.FileExists() )
|
|
readOnly = !filename.IsFileWritable();
|
|
else
|
|
unsaved = true;
|
|
|
|
wxString title;
|
|
|
|
if( m_workbook->IsModified() )
|
|
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 );
|
|
}
|
|
|
|
|
|
// A small helper struct to handle bitmaps initialization in menus
|
|
struct BM_MENU_INIT_ITEM
|
|
{
|
|
int m_MenuId;
|
|
BITMAPS m_Bitmap;
|
|
};
|
|
|
|
|
|
void SIM_PLOT_FRAME::setIconsForMenuItems()
|
|
{
|
|
// Give icons to menuitems of the main menubar
|
|
BM_MENU_INIT_ITEM bm_list[]
|
|
{
|
|
// File menu:
|
|
{ wxID_NEW, BITMAPS::simulator },
|
|
{ wxID_OPEN, BITMAPS::directory_open },
|
|
{ wxID_SAVE, BITMAPS::save },
|
|
{ ID_SAVE_AS_IMAGE, BITMAPS::export_file },
|
|
{ ID_SAVE_AS_CSV, BITMAPS::export_file },
|
|
{ wxID_CLOSE, BITMAPS::exit },
|
|
|
|
// simulator menu:
|
|
{ ID_MENU_RUN_SIM, BITMAPS::sim_run },
|
|
{ ID_MENU_ADD_SIGNAL, BITMAPS::sim_add_signal },
|
|
{ ID_MENU_PROBE_SIGNALS, BITMAPS::sim_probe },
|
|
{ ID_MENU_TUNE_SIGNALS, BITMAPS::sim_tune },
|
|
{ ID_MENU_SHOW_NETLIST, BITMAPS::netlist },
|
|
{ ID_MENU_SET_SIMUL, BITMAPS::config },
|
|
|
|
// View menu
|
|
{ wxID_ZOOM_IN, BITMAPS::zoom_in },
|
|
{ wxID_ZOOM_OUT, BITMAPS::zoom_out },
|
|
{ wxID_ZOOM_FIT, BITMAPS::zoom_fit_in_page },
|
|
{ ID_MENU_SHOW_GRID, BITMAPS::grid },
|
|
{ ID_MENU_SHOW_LEGEND, BITMAPS::text },
|
|
{ ID_MENU_DOTTED, BITMAPS::add_dashed_line },
|
|
{ ID_MENU_WHITE_BG, BITMAPS::swap_layer },
|
|
|
|
{ 0, BITMAPS::INVALID_BITMAP } // Sentinel
|
|
};
|
|
|
|
// wxMenuItems are already created and attached to the m_mainMenu wxMenuBar.
|
|
// A problem is the fact setting bitmaps in wxMenuItems after they are attached
|
|
// to a wxMenu do not work in all cases.
|
|
// So the trick is:
|
|
// Remove the wxMenuItem from its wxMenu
|
|
// Set the bitmap
|
|
// Insert the modified wxMenuItem to its previous place
|
|
for( int ii = 0; bm_list[ii].m_MenuId; ++ii )
|
|
{
|
|
wxMenuItem* item = m_mainMenu->FindItem( bm_list[ii].m_MenuId );
|
|
|
|
if( !item || ( bm_list[ii].m_Bitmap == BITMAPS::INVALID_BITMAP ) )
|
|
continue;
|
|
|
|
wxMenu* menu = item->GetMenu();
|
|
|
|
// Calculate the initial index of item inside the wxMenu parent.
|
|
wxMenuItemList& mlist = menu->GetMenuItems();
|
|
int mpos = mlist.IndexOf( item );
|
|
|
|
if( mpos >= 0 ) // Should be always the case
|
|
{
|
|
// Modify the bitmap
|
|
menu->Remove( item );
|
|
AddBitmapToMenuItem( item, KiBitmap( bm_list[ii].m_Bitmap ) );
|
|
|
|
// Insert item to its the initial index
|
|
menu->Insert( mpos, item );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::setSubWindowsSashSize()
|
|
{
|
|
if( m_splitterLeftRightSashPosition > 0 )
|
|
m_splitterLeftRight->SetSashPosition( m_splitterLeftRightSashPosition );
|
|
|
|
if( m_splitterPlotAndConsoleSashPosition > 0 )
|
|
m_splitterPlotAndConsole->SetSashPosition( m_splitterPlotAndConsoleSashPosition );
|
|
|
|
if( m_splitterSignalsSashPosition > 0 )
|
|
m_splitterSignals->SetSashPosition( m_splitterSignalsSashPosition );
|
|
|
|
if( m_splitterTuneValuesSashPosition > 0 )
|
|
m_splitterTuneValues->SetSashPosition( m_splitterTuneValuesSashPosition );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::StartSimulation( const wxString& aSimCommand )
|
|
{
|
|
wxCHECK_RET( m_circuitModel->CommandToSimType( getCurrentSimCommand() ) != ST_UNKNOWN,
|
|
"Unknown simulation type" );
|
|
|
|
if( !m_settingsDlg )
|
|
m_settingsDlg = new DIALOG_SIM_SETTINGS( this, m_circuitModel, m_simulator->Settings() );
|
|
|
|
m_simConsole->Clear();
|
|
|
|
if( aSimCommand != "" )
|
|
m_circuitModel->SetSimCommand( aSimCommand );
|
|
|
|
// Make .save all and .probe alli permanent for now.
|
|
m_circuitModel->SetOptions( m_settingsDlg->GetNetlistOptions()
|
|
| NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_VOLTAGES
|
|
| NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_CURRENTS );
|
|
|
|
if( !m_simulator->Attach( m_circuitModel ) )
|
|
{
|
|
DisplayErrorMessage( this, _( "There were errors during netlist export, aborted." ) );
|
|
return;
|
|
}
|
|
|
|
std::unique_lock<std::mutex> simulatorLock( m_simulator->GetMutex(), std::try_to_lock );
|
|
|
|
if( simulatorLock.owns_lock() )
|
|
{
|
|
wxBusyCursor toggle;
|
|
|
|
updateTuners();
|
|
applyTuners();
|
|
// Prevents memory leak on succeding simulations by deleting old vectors
|
|
m_simulator->Clean();
|
|
m_simulator->Run();
|
|
}
|
|
else
|
|
DisplayErrorMessage( this, _( "Another simulation is already running." ) );
|
|
}
|
|
|
|
|
|
SIM_PANEL_BASE* SIM_PLOT_FRAME::NewPlotPanel( wxString aSimCommand )
|
|
{
|
|
SIM_PANEL_BASE* plotPanel = nullptr;
|
|
SIM_TYPE simType = NGSPICE_CIRCUIT_MODEL::CommandToSimType( aSimCommand );
|
|
|
|
if( SIM_PANEL_BASE::IsPlottable( simType ) )
|
|
{
|
|
SIM_PLOT_PANEL* panel;
|
|
panel = new SIM_PLOT_PANEL( aSimCommand, m_workbook, this, wxID_ANY );
|
|
|
|
panel->GetPlotWin()->EnableMouseWheelPan(
|
|
Pgm().GetCommonSettings()->m_Input.scroll_modifier_zoom != 0 );
|
|
|
|
plotPanel = dynamic_cast<SIM_PANEL_BASE*>( panel );
|
|
}
|
|
else
|
|
{
|
|
SIM_NOPLOT_PANEL* panel;
|
|
panel = new SIM_NOPLOT_PANEL( aSimCommand, m_workbook, wxID_ANY );
|
|
plotPanel = dynamic_cast<SIM_PANEL_BASE*>( panel );
|
|
}
|
|
|
|
wxString pageTitle( m_simulator->TypeToName( simType, true ) );
|
|
pageTitle.Prepend( wxString::Format( _( "Plot%u - " ), (unsigned int) ++m_plotNumber ) );
|
|
|
|
m_workbook->AddPage( dynamic_cast<wxWindow*>( plotPanel ), pageTitle, true );
|
|
|
|
return plotPanel;
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::AddVoltagePlot( const wxString& aNetName )
|
|
{
|
|
addPlot( aNetName, SPT_VOLTAGE );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::AddCurrentPlot( const wxString& aDeviceName )
|
|
{
|
|
addPlot( aDeviceName, SPT_CURRENT );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::AddTuner( SCH_SYMBOL* aSymbol )
|
|
{
|
|
SIM_PANEL_BASE* plotPanel = getCurrentPlotWindow();
|
|
|
|
if( !plotPanel )
|
|
return;
|
|
|
|
SIM_MODEL::TYPE type = SIM_MODEL::ReadTypeFromFields( aSymbol->GetFields() );
|
|
SIM_MODEL::DEVICE_TYPE_ deviceType = SIM_MODEL::TypeInfo( type ).deviceType;
|
|
|
|
switch( deviceType )
|
|
{
|
|
case SIM_MODEL::DEVICE_TYPE_::R:
|
|
case SIM_MODEL::DEVICE_TYPE_::C:
|
|
case SIM_MODEL::DEVICE_TYPE_::L:
|
|
case SIM_MODEL::DEVICE_TYPE_::XSPICE:
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
const wxString componentName = aSymbol->GetField( REFERENCE_FIELD )->GetText();
|
|
|
|
// Do not add multiple instances for the same component
|
|
auto tunerIt = std::find_if( m_tuners.begin(), m_tuners.end(), [&]( const TUNER_SLIDER* t )
|
|
{
|
|
return t->GetComponentName() == componentName;
|
|
}
|
|
);
|
|
|
|
if( tunerIt != m_tuners.end() )
|
|
return; // We already have it
|
|
|
|
try
|
|
{
|
|
TUNER_SLIDER* tuner = new TUNER_SLIDER( this, m_tunePanel, aSymbol );
|
|
m_tuneSizer->Add( tuner );
|
|
m_tuners.push_back( tuner );
|
|
m_tunePanel->Layout();
|
|
}
|
|
catch( const KI_PARAM_ERROR& e )
|
|
{
|
|
// Sorry, no bonus
|
|
DisplayErrorMessage( nullptr, e.What() );
|
|
}
|
|
}
|
|
|
|
void SIM_PLOT_FRAME::UpdateTunerValue( SCH_SYMBOL* aSymbol, int aId, const wxString& aValue )
|
|
{
|
|
for( auto& item : m_schematicFrame->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
|
|
{
|
|
if( item == aSymbol )
|
|
{
|
|
SCH_FIELD* field = aSymbol->GetFieldById( aId );
|
|
|
|
if( !field )
|
|
break;
|
|
|
|
field->SetText( aValue );
|
|
|
|
m_schematicFrame->UpdateItem( aSymbol, false, true );
|
|
m_schematicFrame->OnModify();
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::RemoveTuner( TUNER_SLIDER* aTuner, bool aErase )
|
|
{
|
|
if( aErase )
|
|
m_tuners.remove( aTuner );
|
|
|
|
aTuner->Destroy();
|
|
m_tunePanel->Layout();
|
|
}
|
|
|
|
|
|
SIM_PLOT_PANEL* SIM_PLOT_FRAME::GetCurrentPlot() const
|
|
{
|
|
SIM_PANEL_BASE* curPage = getCurrentPlotWindow();
|
|
|
|
return ( ( !curPage || curPage->GetType() == ST_UNKNOWN ) ?
|
|
nullptr :
|
|
dynamic_cast<SIM_PLOT_PANEL*>( curPage ) );
|
|
}
|
|
|
|
|
|
const NGSPICE_CIRCUIT_MODEL* SIM_PLOT_FRAME::GetExporter() const
|
|
{
|
|
return m_circuitModel.get();
|
|
}
|
|
|
|
|
|
std::shared_ptr<SPICE_SIMULATOR_SETTINGS>& SIM_PLOT_FRAME::GetSimulatorSettings()
|
|
{
|
|
wxASSERT( m_simulator->Settings() );
|
|
|
|
return m_simulator->Settings();
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::addPlot( const wxString& aName, SIM_PLOT_TYPE aType )
|
|
{
|
|
SIM_TYPE simType = m_circuitModel->GetSimType();
|
|
|
|
if( simType == ST_UNKNOWN )
|
|
{
|
|
m_simConsole->AppendText( _( "Error: simulation type not defined!\n" ) );
|
|
m_simConsole->SetInsertionPointEnd();
|
|
return;
|
|
}
|
|
else if( !SIM_PANEL_BASE::IsPlottable( simType ) )
|
|
{
|
|
m_simConsole->AppendText( _( "Error: simulation type doesn't support plotting!\n" ) );
|
|
m_simConsole->SetInsertionPointEnd();
|
|
return;
|
|
}
|
|
|
|
// Create a new plot if the current one displays a different type
|
|
SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
|
|
|
|
if( !plotPanel || plotPanel->GetType() != simType )
|
|
{
|
|
plotPanel =
|
|
dynamic_cast<SIM_PLOT_PANEL*>( NewPlotPanel( m_circuitModel->GetSimCommand() ) );
|
|
}
|
|
|
|
wxASSERT( plotPanel );
|
|
|
|
if( !plotPanel ) // Something is wrong
|
|
return;
|
|
|
|
bool updated = false;
|
|
SIM_PLOT_TYPE xAxisType = getXAxisType( simType );
|
|
|
|
if( xAxisType == SPT_LIN_FREQUENCY || xAxisType == SPT_LOG_FREQUENCY )
|
|
{
|
|
int baseType = aType & ~( SPT_AC_MAG | SPT_AC_PHASE );
|
|
|
|
// Add two plots: magnitude & phase
|
|
updated |= updatePlot( aName, ( SIM_PLOT_TYPE )( baseType | SPT_AC_MAG ), plotPanel );
|
|
updated |= updatePlot( aName, ( SIM_PLOT_TYPE )( baseType | SPT_AC_PHASE ), plotPanel );
|
|
}
|
|
else
|
|
{
|
|
updated = updatePlot( aName, aType, plotPanel );
|
|
}
|
|
|
|
if( updated )
|
|
{
|
|
updateSignalList();
|
|
}
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::removePlot( const wxString& aPlotName )
|
|
{
|
|
SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
|
|
|
|
if( !plotPanel )
|
|
return;
|
|
|
|
wxASSERT( plotPanel->TraceShown( aPlotName ) );
|
|
m_workbook->DeleteTrace( plotPanel, aPlotName );
|
|
plotPanel->GetPlotWin()->Fit();
|
|
|
|
updateSignalList();
|
|
wxCommandEvent dummy;
|
|
onCursorUpdate( dummy );
|
|
}
|
|
|
|
|
|
bool SIM_PLOT_FRAME::updatePlot( const wxString& aName, SIM_PLOT_TYPE aType,
|
|
SIM_PLOT_PANEL* aPlotPanel )
|
|
{
|
|
SIM_TYPE simType = m_circuitModel->GetSimType();
|
|
|
|
wxString plotTitle = aName;
|
|
if( aType & SPT_AC_MAG )
|
|
plotTitle += " (mag)";
|
|
else if( aType & SPT_AC_PHASE )
|
|
plotTitle += " (phase)";
|
|
|
|
if( !SIM_PANEL_BASE::IsPlottable( simType ) )
|
|
{
|
|
// There is no plot to be shown
|
|
m_simulator->Command( wxString::Format( "print %s", aName ).ToStdString() );
|
|
|
|
return false;
|
|
}
|
|
|
|
// First, handle the x axis
|
|
wxString xAxisName( m_simulator->GetXAxis( simType ) );
|
|
|
|
if( xAxisName.IsEmpty() )
|
|
return false;
|
|
|
|
auto data_x = m_simulator->GetMagPlot( (const char*) xAxisName.c_str() );
|
|
unsigned int size = data_x.size();
|
|
|
|
if( data_x.empty() )
|
|
return false;
|
|
|
|
std::vector<double> data_y;
|
|
|
|
// Now, Y axis data
|
|
switch( m_circuitModel->GetSimType() )
|
|
{
|
|
case ST_AC:
|
|
wxASSERT_MSG( !( ( aType & SPT_AC_MAG ) && ( aType & SPT_AC_PHASE ) ),
|
|
"Cannot set both AC_PHASE and AC_MAG bits" );
|
|
|
|
if( aType & SPT_AC_MAG )
|
|
data_y = m_simulator->GetMagPlot( (const char*) aName.c_str() );
|
|
else if( aType & SPT_AC_PHASE )
|
|
data_y = m_simulator->GetPhasePlot( (const char*) aName.c_str() );
|
|
else
|
|
wxASSERT_MSG( false, "Plot type missing AC_PHASE or AC_MAG bit" );
|
|
|
|
break;
|
|
|
|
case ST_NOISE:
|
|
case ST_DC:
|
|
case ST_TRANSIENT:
|
|
data_y = m_simulator->GetMagPlot( (const char*) aName.c_str() );
|
|
break;
|
|
|
|
default:
|
|
wxASSERT_MSG( false, "Unhandled plot type" );
|
|
return false;
|
|
}
|
|
|
|
if( data_y.size() != size )
|
|
return false;
|
|
|
|
// If we did a two-source DC analysis, we need to split the resulting vector and add traces
|
|
// for each input step
|
|
SPICE_DC_PARAMS source1, source2;
|
|
|
|
if( m_circuitModel->GetSimType() == ST_DC
|
|
&& m_circuitModel->ParseDCCommand( m_circuitModel->GetSimCommand(), &source1, &source2 ) )
|
|
{
|
|
if( !source2.m_source.IsEmpty() )
|
|
{
|
|
// Source 1 is the inner loop, so lets add traces for each Source 2 (outer loop) step
|
|
SPICE_VALUE v = source2.m_vstart;
|
|
wxString name;
|
|
|
|
size_t offset = 0;
|
|
size_t outer = ( size_t )( ( source2.m_vend - v ) / source2.m_vincrement ).ToDouble();
|
|
size_t inner = data_x.size() / ( outer + 1 );
|
|
|
|
wxASSERT( data_x.size() % ( outer + 1 ) == 0 );
|
|
|
|
for( size_t idx = 0; idx <= outer; idx++ )
|
|
{
|
|
name = wxString::Format( "%s (%s = %s V)", plotTitle, source2.m_source,
|
|
v.ToString() );
|
|
|
|
std::vector<double> sub_x( data_x.begin() + offset,
|
|
data_x.begin() + offset + inner );
|
|
std::vector<double> sub_y( data_y.begin() + offset,
|
|
data_y.begin() + offset + inner );
|
|
|
|
m_workbook->AddTrace( aPlotPanel, name, aName, inner, sub_x.data(), sub_y.data(),
|
|
aType );
|
|
|
|
v = v + source2.m_vincrement;
|
|
offset += inner;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
m_workbook->AddTrace( aPlotPanel, plotTitle, aName, size, data_x.data(), data_y.data(), aType );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::updateSignalList()
|
|
{
|
|
m_signals->ClearAll();
|
|
|
|
SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
|
|
|
|
if( !plotPanel )
|
|
return;
|
|
|
|
wxSize size = m_signals->GetClientSize();
|
|
m_signals->AppendColumn( _( "Signal" ), wxLIST_FORMAT_LEFT, size.x );
|
|
|
|
// Build an image list, to show the color of the corresponding trace
|
|
// in the plot panel
|
|
// This image list is used for trace and cursor lists
|
|
wxMemoryDC bmDC;
|
|
const int isize = bmDC.GetCharHeight();
|
|
|
|
if( m_signalsIconColorList == nullptr )
|
|
m_signalsIconColorList = new wxImageList( isize, isize, false );
|
|
else
|
|
m_signalsIconColorList->RemoveAll();
|
|
|
|
for( const auto& trace : GetCurrentPlot()->GetTraces() )
|
|
{
|
|
wxBitmap bitmap( isize, isize );
|
|
bmDC.SelectObject( bitmap );
|
|
wxColour tcolor = trace.second->GetPen().GetColour();
|
|
|
|
wxColour bgColor = m_signals->wxWindow::GetBackgroundColour();
|
|
bmDC.SetPen( wxPen( bgColor ) );
|
|
bmDC.SetBrush( wxBrush( bgColor ) );
|
|
bmDC.DrawRectangle( 0, 0, isize, isize ); // because bmDC.Clear() does not work in wxGTK
|
|
|
|
bmDC.SetPen( wxPen( tcolor ) );
|
|
bmDC.SetBrush( wxBrush( tcolor ) );
|
|
bmDC.DrawRectangle( 0, isize / 4 + 1, isize, isize / 2 );
|
|
|
|
bmDC.SelectObject( wxNullBitmap ); // Needed to initialize bitmap
|
|
|
|
bitmap.SetMask( new wxMask( bitmap, *wxBLACK ) );
|
|
m_signalsIconColorList->Add( bitmap );
|
|
}
|
|
|
|
if( bmDC.IsOk() )
|
|
{
|
|
bmDC.SetBrush( wxNullBrush );
|
|
bmDC.SetPen( wxNullPen );
|
|
}
|
|
|
|
m_signals->SetImageList( m_signalsIconColorList, wxIMAGE_LIST_SMALL );
|
|
|
|
// Fill the signals listctrl. Keep the order of names and
|
|
// the order of icon color identical, because the icons
|
|
// are also used in cursor list, and the color index is
|
|
// calculated from the trace name index
|
|
int imgidx = 0;
|
|
|
|
for( const auto& trace : plotPanel->GetTraces() )
|
|
{
|
|
m_signals->InsertItem( imgidx, trace.first, imgidx );
|
|
imgidx++;
|
|
}
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::updateTuners()
|
|
{
|
|
const auto& spiceItems = m_circuitModel->GetItems();
|
|
|
|
for( auto it = m_tuners.begin(); it != m_tuners.end(); /* iteration inside the loop */ )
|
|
{
|
|
const wxString& ref = (*it)->GetComponentName();
|
|
|
|
if( std::find_if( spiceItems.begin(), spiceItems.end(), [&]( const SPICE_ITEM& item )
|
|
{
|
|
return item.refName == ref;
|
|
}) == spiceItems.end() )
|
|
{
|
|
// The component does not exist anymore, remove the associated tuner
|
|
TUNER_SLIDER* tuner = *it;
|
|
it = m_tuners.erase( it );
|
|
RemoveTuner( tuner, false );
|
|
}
|
|
else
|
|
{
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::applyTuners()
|
|
{
|
|
for( auto& tuner : m_tuners )
|
|
{
|
|
std::pair<wxString, bool> command = tuner->GetSpiceTuningCommand();
|
|
const SPICE_VALUE& value = tuner->GetValue();
|
|
|
|
// 0 < value < 1 for model parameter to avoid division by zero, etc.
|
|
command.first += command.second
|
|
? wxString::FromCDouble( Clamp( 1e-9, value.ToDouble() / 100.0, 1-1e-9 ), 9 )
|
|
: value.ToSpiceString();
|
|
|
|
m_simulator->Command( command.first.ToStdString() );
|
|
}
|
|
}
|
|
|
|
|
|
bool SIM_PLOT_FRAME::loadWorkbook( const wxString& aPath )
|
|
{
|
|
m_workbook->DeleteAllPages();
|
|
|
|
wxTextFile file( aPath );
|
|
|
|
#define DISPLAY_LOAD_ERROR( fmt ) DisplayErrorMessage( this, wxString::Format( _( fmt ), \
|
|
file.GetCurrentLine()+1 ) )
|
|
|
|
if( !file.Open() )
|
|
return false;
|
|
|
|
long plotsCount;
|
|
|
|
if( !file.GetFirstLine().ToLong( &plotsCount ) ) // GetFirstLine instead of GetNextLine
|
|
{
|
|
DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is not an integer." );
|
|
file.Close();
|
|
|
|
return false;
|
|
}
|
|
|
|
for( long i = 0; i < plotsCount; ++i )
|
|
{
|
|
long plotType, tracesCount;
|
|
|
|
if( !file.GetNextLine().ToLong( &plotType ) )
|
|
{
|
|
DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is not an integer." );
|
|
file.Close();
|
|
|
|
return false;
|
|
}
|
|
|
|
wxString simCommand = UnescapeString( file.GetNextLine() );
|
|
NewPlotPanel( simCommand );
|
|
StartSimulation( simCommand );
|
|
|
|
// Perform simulation, so plots can be added with values
|
|
do
|
|
{
|
|
wxThread::This()->Sleep( 50 );
|
|
}
|
|
while( m_simulator->IsRunning() );
|
|
|
|
if( !file.GetNextLine().ToLong( &tracesCount ) )
|
|
{
|
|
DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is not an integer." );
|
|
file.Close();
|
|
|
|
return false;
|
|
}
|
|
|
|
for( long j = 0; j < tracesCount; ++j )
|
|
{
|
|
long traceType;
|
|
wxString name, param;
|
|
|
|
if( !file.GetNextLine().ToLong( &traceType ) )
|
|
{
|
|
DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is not an integer." );
|
|
file.Close();
|
|
|
|
return false;
|
|
}
|
|
|
|
name = file.GetNextLine();
|
|
|
|
if( name.IsEmpty() )
|
|
{
|
|
DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is empty." );
|
|
file.Close();
|
|
|
|
return false;
|
|
}
|
|
|
|
param = file.GetNextLine();
|
|
|
|
if( param.IsEmpty() )
|
|
{
|
|
DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is empty." );
|
|
file.Close();
|
|
|
|
return false;
|
|
}
|
|
|
|
addPlot( name, (SIM_PLOT_TYPE) traceType );
|
|
}
|
|
}
|
|
|
|
file.Close();
|
|
|
|
wxFileName filename( aPath );
|
|
filename.MakeRelativeTo( Prj().GetProjectPath() );
|
|
|
|
// Remember the loaded workbook filename.
|
|
m_simulator->Settings()->SetWorkbookFilename( filename.GetFullPath() );
|
|
|
|
// Successfully loading a workbook does not count as modifying it.
|
|
m_workbook->ClrModified();
|
|
return true;
|
|
}
|
|
|
|
|
|
bool SIM_PLOT_FRAME::saveWorkbook( const wxString& aPath )
|
|
{
|
|
wxFileName filename = aPath;
|
|
filename.SetExt( WorkbookFileExtension );
|
|
|
|
wxTextFile file( filename.GetFullPath() );
|
|
|
|
if( file.Exists() )
|
|
{
|
|
if( !file.Open() )
|
|
return false;
|
|
|
|
file.Clear();
|
|
}
|
|
else
|
|
{
|
|
file.Create();
|
|
}
|
|
|
|
file.AddLine( wxString::Format( "%llu", m_workbook->GetPageCount() ) );
|
|
|
|
for( size_t i = 0; i < m_workbook->GetPageCount(); i++ )
|
|
{
|
|
const SIM_PANEL_BASE* basePanel = dynamic_cast<const SIM_PANEL_BASE*>( m_workbook->GetPage( i ) );
|
|
|
|
if( !basePanel )
|
|
{
|
|
file.AddLine( wxString::Format( "%llu", 0ull ) );
|
|
continue;
|
|
}
|
|
|
|
file.AddLine( wxString::Format( "%d", basePanel->GetType() ) );
|
|
file.AddLine( EscapeString( m_workbook->GetSimCommand( basePanel ), CTX_LINE ) );
|
|
|
|
const SIM_PLOT_PANEL* plotPanel = dynamic_cast<const SIM_PLOT_PANEL*>( basePanel );
|
|
|
|
if( !plotPanel )
|
|
{
|
|
file.AddLine( wxString::Format( "%llu", 0ull ) );
|
|
continue;
|
|
}
|
|
|
|
file.AddLine( wxString::Format( "%llu", plotPanel->GetTraces().size() ) );
|
|
|
|
for( const auto& trace : plotPanel->GetTraces() )
|
|
{
|
|
file.AddLine( wxString::Format( "%d", trace.second->GetType() ) );
|
|
file.AddLine( trace.second->GetName() );
|
|
file.AddLine( trace.second->GetParam() );
|
|
}
|
|
}
|
|
|
|
bool res = file.Write();
|
|
file.Close();
|
|
|
|
// Store the filename of the last saved workbook.
|
|
if( res )
|
|
{
|
|
filename.MakeRelativeTo( Prj().GetProjectPath() );
|
|
m_simulator->Settings()->SetWorkbookFilename( filename.GetFullPath() );
|
|
}
|
|
|
|
m_workbook->ClrModified();
|
|
return res;
|
|
}
|
|
|
|
|
|
wxString SIM_PLOT_FRAME::getDefaultFilename()
|
|
{
|
|
wxFileName filename = m_simulator->Settings()->GetWorkbookFilename();
|
|
|
|
if( filename.GetName().IsEmpty() )
|
|
{
|
|
if( Prj().GetProjectName().IsEmpty() )
|
|
{
|
|
filename.SetName( _( "noname" ) );
|
|
filename.SetExt( WorkbookFileExtension );
|
|
}
|
|
else
|
|
{
|
|
filename.SetName( Prj().GetProjectName() );
|
|
filename.SetExt( WorkbookFileExtension );
|
|
}
|
|
}
|
|
|
|
return filename.GetFullName();
|
|
}
|
|
|
|
|
|
wxString SIM_PLOT_FRAME::getDefaultPath()
|
|
{
|
|
wxFileName path = m_simulator->Settings()->GetWorkbookFilename();
|
|
|
|
path.Normalize( wxPATH_NORM_ALL, Prj().GetProjectPath() );
|
|
return path.GetPath();
|
|
}
|
|
|
|
|
|
SIM_PLOT_TYPE SIM_PLOT_FRAME::getXAxisType( SIM_TYPE aType ) const
|
|
{
|
|
switch( aType )
|
|
{
|
|
/// @todo SPT_LOG_FREQUENCY
|
|
case ST_AC: return SPT_LIN_FREQUENCY;
|
|
case ST_DC: return SPT_SWEEP;
|
|
case ST_TRANSIENT: return SPT_TIME;
|
|
default:
|
|
wxASSERT_MSG( false, "Unhandled simulation type" );
|
|
return (SIM_PLOT_TYPE) 0;
|
|
}
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuNewPlot( wxCommandEvent& aEvent )
|
|
{
|
|
SIM_TYPE type = m_circuitModel->GetSimType();
|
|
|
|
if( SIM_PANEL_BASE::IsPlottable( type ) )
|
|
NewPlotPanel( m_circuitModel->GetSimCommand() );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuOpenWorkbook( wxCommandEvent& event )
|
|
{
|
|
wxFileDialog openDlg( this, _( "Open simulation workbook" ), getDefaultPath(), "",
|
|
WorkbookFileWildcard(), wxFD_OPEN | wxFD_FILE_MUST_EXIST );
|
|
|
|
if( openDlg.ShowModal() == wxID_CANCEL )
|
|
return;
|
|
|
|
loadWorkbook( openDlg.GetPath() );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuSaveWorkbook( wxCommandEvent& event )
|
|
{
|
|
if( !m_workbook->IsModified() )
|
|
return;
|
|
|
|
wxString filename = m_simulator->Settings()->GetWorkbookFilename();
|
|
|
|
if( filename.IsEmpty() )
|
|
{
|
|
menuSaveWorkbookAs( event );
|
|
return;
|
|
}
|
|
|
|
saveWorkbook( Prj().AbsolutePath( m_simulator->Settings()->GetWorkbookFilename() ) );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuSaveWorkbookAs( wxCommandEvent& event )
|
|
{
|
|
wxFileDialog saveAsDlg( this, _( "Save Simulation Workbook As" ), getDefaultPath(),
|
|
getDefaultFilename(), WorkbookFileWildcard(),
|
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
|
|
|
|
if( saveAsDlg.ShowModal() == wxID_CANCEL )
|
|
return;
|
|
|
|
saveWorkbook( Prj().AbsolutePath( saveAsDlg.GetPath() ) );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuSaveImage( wxCommandEvent& event )
|
|
{
|
|
if( !GetCurrentPlot() )
|
|
return;
|
|
|
|
wxFileDialog saveDlg( this, _( "Save Plot as Image" ), "", "", PngFileWildcard(),
|
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
|
|
|
|
if( saveDlg.ShowModal() == wxID_CANCEL )
|
|
return;
|
|
|
|
GetCurrentPlot()->GetPlotWin()->SaveScreenshot( saveDlg.GetPath(), wxBITMAP_TYPE_PNG );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuSaveCsv( wxCommandEvent& event )
|
|
{
|
|
if( !GetCurrentPlot() )
|
|
return;
|
|
|
|
const wxChar SEPARATOR = ';';
|
|
|
|
wxFileDialog saveDlg( this, _( "Save Plot Data" ), "", "", CsvFileWildcard(),
|
|
wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
|
|
|
|
if( saveDlg.ShowModal() == wxID_CANCEL )
|
|
return;
|
|
|
|
wxFFile out( saveDlg.GetPath(), "wb" );
|
|
|
|
std::map<wxString, TRACE *> traces = GetCurrentPlot()->GetTraces();
|
|
|
|
if( traces.size() == 0 )
|
|
return;
|
|
|
|
SIM_TYPE simType = m_circuitModel->GetSimType();
|
|
|
|
std::size_t rowCount = traces.begin()->second->GetDataX().size();
|
|
|
|
// write column header names on the first row
|
|
wxString xAxisName( m_simulator->GetXAxis( simType ) );
|
|
out.Write( wxString::Format( "%s%c", xAxisName, SEPARATOR ) );
|
|
|
|
for( const auto& trace : traces )
|
|
{
|
|
wxString yAxisName = trace.first;
|
|
out.Write( wxString::Format( "%s%c", yAxisName, SEPARATOR ) );
|
|
}
|
|
|
|
out.Write( "\r\n" );
|
|
|
|
// write each row's numerical value
|
|
for ( std::size_t curRow=0; curRow < rowCount; curRow++ )
|
|
{
|
|
double xAxisValue = traces.begin()->second->GetDataX().at( curRow );
|
|
out.Write( wxString::Format( "%g%c", xAxisValue, SEPARATOR ) );
|
|
|
|
for( const auto& trace : traces )
|
|
{
|
|
double yAxisValue = trace.second->GetDataY().at( curRow );
|
|
out.Write( wxString::Format( "%g%c", yAxisValue, SEPARATOR ) );
|
|
}
|
|
|
|
out.Write( "\r\n" );
|
|
}
|
|
|
|
out.Close();
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuZoomIn( wxCommandEvent& event )
|
|
{
|
|
if( GetCurrentPlot() )
|
|
GetCurrentPlot()->GetPlotWin()->ZoomIn();
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuZoomOut( wxCommandEvent& event )
|
|
{
|
|
if( GetCurrentPlot() )
|
|
GetCurrentPlot()->GetPlotWin()->ZoomOut();
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuZoomFit( wxCommandEvent& event )
|
|
{
|
|
if( GetCurrentPlot() )
|
|
GetCurrentPlot()->GetPlotWin()->Fit();
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuShowGrid( wxCommandEvent& event )
|
|
{
|
|
SIM_PLOT_PANEL* plot = GetCurrentPlot();
|
|
|
|
if( plot )
|
|
plot->ShowGrid( !plot->IsGridShown() );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuShowGridUpdate( wxUpdateUIEvent& event )
|
|
{
|
|
SIM_PLOT_PANEL* plot = GetCurrentPlot();
|
|
|
|
event.Check( plot ? plot->IsGridShown() : false );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuShowLegend( wxCommandEvent& event )
|
|
{
|
|
SIM_PLOT_PANEL* plot = GetCurrentPlot();
|
|
|
|
if( plot )
|
|
plot->ShowLegend( !plot->IsLegendShown() );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuShowLegendUpdate( wxUpdateUIEvent& event )
|
|
{
|
|
SIM_PLOT_PANEL* plot = GetCurrentPlot();
|
|
event.Check( plot ? plot->IsLegendShown() : false );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuShowDotted( wxCommandEvent& event )
|
|
{
|
|
SIM_PLOT_PANEL* plot = GetCurrentPlot();
|
|
|
|
if( plot )
|
|
plot->SetDottedCurrentPhase( !plot->GetDottedCurrentPhase() );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuShowDottedUpdate( wxUpdateUIEvent& event )
|
|
{
|
|
SIM_PLOT_PANEL* plot = GetCurrentPlot();
|
|
|
|
event.Check( plot ? plot->GetDottedCurrentPhase() : false );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuWhiteBackground( wxCommandEvent& event )
|
|
{
|
|
m_plotUseWhiteBg = not m_plotUseWhiteBg;
|
|
|
|
// Rebuild the color list to plot traces
|
|
SIM_PLOT_COLORS::FillDefaultColorList( GetPlotBgOpt() );
|
|
|
|
// Now send changes to all SIM_PLOT_PANEL
|
|
for( size_t page = 0; page < m_workbook->GetPageCount(); page++ )
|
|
{
|
|
wxWindow* curPage = m_workbook->GetPage( page );
|
|
|
|
// ensure it is truly a plot panel and not the (zero plots) placeholder
|
|
// which is only SIM_PLOT_PANEL_BASE
|
|
SIM_PLOT_PANEL* panel = dynamic_cast<SIM_PLOT_PANEL*>( curPage );
|
|
|
|
if( panel )
|
|
panel->UpdatePlotColors();
|
|
}
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuSimulateUpdate( wxUpdateUIEvent& event )
|
|
{
|
|
event.Enable( m_circuitModel->CommandToSimType( getCurrentSimCommand() ) != ST_UNKNOWN );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuAddSignalsUpdate( wxUpdateUIEvent& event )
|
|
{
|
|
event.Enable( m_simFinished );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuProbeUpdate( wxUpdateUIEvent& event )
|
|
{
|
|
event.Enable( m_simFinished );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::menuTuneUpdate( wxUpdateUIEvent& event )
|
|
{
|
|
event.Enable( m_simFinished );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onPlotClose( wxAuiNotebookEvent& event )
|
|
{
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onPlotClosed( wxAuiNotebookEvent& event )
|
|
{
|
|
if( m_workbook->GetPageCount() == 0 )
|
|
{
|
|
m_signals->ClearAll();
|
|
m_cursors->ClearAll();
|
|
}
|
|
else
|
|
{
|
|
updateSignalList();
|
|
wxCommandEvent dummy;
|
|
onCursorUpdate( dummy );
|
|
}
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onPlotChanged( wxAuiNotebookEvent& event )
|
|
{
|
|
updateSignalList();
|
|
wxCommandEvent dummy;
|
|
onCursorUpdate( dummy );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onPlotDragged( wxAuiNotebookEvent& event )
|
|
{
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onSignalDblClick( wxMouseEvent& event )
|
|
{
|
|
// Remove signal from the plot panel when double clicked
|
|
long idx = m_signals->GetFocusedItem();
|
|
|
|
if( idx != wxNOT_FOUND )
|
|
removePlot( m_signals->GetItemText( idx, 0 ) );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onSignalRClick( wxListEvent& event )
|
|
{
|
|
int idx = event.GetIndex();
|
|
|
|
if( idx != wxNOT_FOUND )
|
|
m_signals->Select( idx );
|
|
|
|
idx = m_signals->GetFirstSelected();
|
|
|
|
if( idx != wxNOT_FOUND )
|
|
{
|
|
const wxString& netName = m_signals->GetItemText( idx, 0 );
|
|
SIGNAL_CONTEXT_MENU ctxMenu( netName, this );
|
|
m_signals->PopupMenu( &ctxMenu );
|
|
}
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onWorkbookModified( wxCommandEvent& event )
|
|
{
|
|
updateTitle();
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onWorkbookClrModified( wxCommandEvent& event )
|
|
{
|
|
updateTitle();
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onSimulate( wxCommandEvent& event )
|
|
{
|
|
if( m_simulator->IsRunning() )
|
|
m_simulator->Stop();
|
|
else
|
|
StartSimulation();
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onSettings( wxCommandEvent& event )
|
|
{
|
|
SIM_PANEL_BASE* plotPanelWindow = getCurrentPlotWindow();
|
|
|
|
if( !m_settingsDlg )
|
|
m_settingsDlg = new DIALOG_SIM_SETTINGS( this, m_circuitModel, m_simulator->Settings() );
|
|
|
|
if( !m_circuitModel->ReadSchematicAndLibraries( NETLIST_EXPORTER_SPICE::OPTION_ALL_FLAGS ) )
|
|
{
|
|
DisplayErrorMessage( this, _( "There were errors during netlist export, aborted." ) );
|
|
return;
|
|
}
|
|
|
|
if( m_workbook->GetPageIndex( plotPanelWindow ) != wxNOT_FOUND )
|
|
m_settingsDlg->SetSimCommand( m_workbook->GetSimCommand( plotPanelWindow ) );
|
|
|
|
if( m_settingsDlg->ShowModal() == wxID_OK )
|
|
{
|
|
wxString oldCommand;
|
|
|
|
if( m_workbook->GetPageIndex( plotPanelWindow ) != wxNOT_FOUND )
|
|
oldCommand = m_workbook->GetSimCommand( plotPanelWindow );
|
|
else
|
|
oldCommand = wxString();
|
|
|
|
wxString newCommand = m_settingsDlg->GetSimCommand();
|
|
SIM_TYPE newSimType = NGSPICE_CIRCUIT_MODEL::CommandToSimType( newCommand );
|
|
|
|
// If it is a new simulation type, open a new plot
|
|
// For the DC sim, check if sweep source type has changed (char 4 will contain 'v',
|
|
// 'i', 'r' or 't'.
|
|
if( !plotPanelWindow
|
|
|| ( plotPanelWindow && plotPanelWindow->GetType() != newSimType )
|
|
|| ( newSimType == ST_DC
|
|
&& oldCommand.Lower().GetChar( 4 ) != newCommand.Lower().GetChar( 4 ) ) )
|
|
{
|
|
plotPanelWindow = NewPlotPanel( newCommand );
|
|
}
|
|
else
|
|
{
|
|
// Update simulation command in the current plot
|
|
m_workbook->SetSimCommand( plotPanelWindow, newCommand );
|
|
}
|
|
|
|
m_simulator->Init();
|
|
}
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onAddSignal( wxCommandEvent& event )
|
|
{
|
|
wxCHECK_RET( m_simFinished, "No simulation results available" );
|
|
|
|
SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
|
|
|
|
if( !plotPanel || !m_circuitModel || plotPanel->GetType() != m_circuitModel->GetSimType() )
|
|
{
|
|
DisplayInfoMessage( this, _( "You need to run plot-providing simulation first." ) );
|
|
return;
|
|
}
|
|
|
|
DIALOG_SIGNAL_LIST dialog( this, m_circuitModel.get() );
|
|
dialog.ShowModal();
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onProbe( wxCommandEvent& event )
|
|
{
|
|
wxCHECK_RET( m_simFinished, "No simulation results available" );
|
|
|
|
if( m_schematicFrame == nullptr )
|
|
return;
|
|
|
|
wxWindow* blocking_dialog = m_schematicFrame->Kiway().GetBlockingDialog();
|
|
|
|
if( blocking_dialog )
|
|
blocking_dialog->Close( true );
|
|
|
|
m_schematicFrame->GetToolManager()->RunAction( EE_ACTIONS::simProbe );
|
|
m_schematicFrame->Raise();
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onTune( wxCommandEvent& event )
|
|
{
|
|
wxCHECK_RET( m_simFinished, "No simulation results available" );
|
|
|
|
if( m_schematicFrame == nullptr )
|
|
return;
|
|
|
|
wxWindow* blocking_dialog = m_schematicFrame->Kiway().GetBlockingDialog();
|
|
|
|
if( blocking_dialog )
|
|
blocking_dialog->Close( true );
|
|
|
|
m_schematicFrame->GetToolManager()->RunAction( EE_ACTIONS::simTune );
|
|
m_schematicFrame->Raise();
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onShowNetlist( wxCommandEvent& event )
|
|
{
|
|
class NETLIST_VIEW_DIALOG : public DIALOG_SHIM
|
|
{
|
|
public:
|
|
enum
|
|
{
|
|
MARGIN_LINE_NUMBERS
|
|
};
|
|
|
|
void onClose( wxCloseEvent& evt )
|
|
{
|
|
wxPostEvent( this, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_CANCEL ) );
|
|
}
|
|
|
|
NETLIST_VIEW_DIALOG( wxWindow* parent, wxString source) :
|
|
DIALOG_SHIM( parent, wxID_ANY, "SPICE Netlist",
|
|
wxDefaultPosition, wxDefaultSize,
|
|
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER )
|
|
{
|
|
wxStyledTextCtrl* text = new wxStyledTextCtrl( this, wxID_ANY );
|
|
text->SetMinSize( wxSize( 600, 400 ) );
|
|
|
|
text->SetMarginWidth( MARGIN_LINE_NUMBERS, 50 );
|
|
text->StyleSetForeground( wxSTC_STYLE_LINENUMBER, wxColour( 75, 75, 75 ) );
|
|
text->StyleSetBackground( wxSTC_STYLE_LINENUMBER, wxColour( 220, 220, 220 ) );
|
|
text->SetMarginType( MARGIN_LINE_NUMBERS, wxSTC_MARGIN_NUMBER );
|
|
|
|
wxFont fixedFont = KIUI::GetMonospacedUIFont();
|
|
|
|
for( size_t i = 0; i < wxSTC_STYLE_MAX; ++i )
|
|
text->StyleSetFont( i, fixedFont );
|
|
|
|
text->StyleClearAll(); // Addresses a bug in wx3.0 where styles are not correctly set
|
|
|
|
text->SetWrapMode( wxSTC_WRAP_WORD );
|
|
|
|
text->SetText( source );
|
|
|
|
text->SetLexer( wxSTC_LEX_SPICE );
|
|
|
|
wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
|
|
sizer->Add( text, 1, wxEXPAND );
|
|
SetSizer( sizer );
|
|
|
|
Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( NETLIST_VIEW_DIALOG::onClose ),
|
|
nullptr, this );
|
|
|
|
finishDialogSettings();
|
|
}
|
|
};
|
|
|
|
if( m_schematicFrame == nullptr || m_simulator == nullptr )
|
|
return;
|
|
|
|
NETLIST_VIEW_DIALOG dlg( this, m_simulator->GetNetlist() );
|
|
dlg.ShowModal();
|
|
}
|
|
|
|
|
|
bool SIM_PLOT_FRAME::canCloseWindow( wxCloseEvent& aEvent )
|
|
{
|
|
if( m_workbook->IsModified() )
|
|
{
|
|
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" ) );
|
|
}
|
|
|
|
wxString fullFilename = filename.GetFullName();
|
|
wxString msg = _( "Save changes to '%s' before closing?" );
|
|
|
|
return HandleUnsavedChanges( this, wxString::Format( msg, fullFilename ),
|
|
[&]() -> bool
|
|
{
|
|
return saveWorkbook( Prj().AbsolutePath( fullFilename ) );
|
|
} );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void SIM_PLOT_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()->RunAction( ACTIONS::cancelInteractive );
|
|
|
|
SaveSettings( config() );
|
|
Destroy();
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onCursorUpdate( wxCommandEvent& event )
|
|
{
|
|
wxSize size = m_cursors->GetClientSize();
|
|
SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
|
|
m_cursors->ClearAll();
|
|
|
|
if( !plotPanel )
|
|
return;
|
|
|
|
if( m_signalsIconColorList )
|
|
m_cursors->SetImageList(m_signalsIconColorList, wxIMAGE_LIST_SMALL);
|
|
|
|
// Fill the signals listctrl
|
|
m_cursors->AppendColumn( _( "Signal" ), wxLIST_FORMAT_LEFT, size.x / 2 );
|
|
const long X_COL = m_cursors->AppendColumn( plotPanel->GetLabelX(), wxLIST_FORMAT_LEFT,
|
|
size.x / 4 );
|
|
|
|
wxString labelY1 = plotPanel->GetLabelY1();
|
|
wxString labelY2 = plotPanel->GetLabelY2();
|
|
wxString labelY;
|
|
|
|
if( !labelY2.IsEmpty() )
|
|
labelY = labelY1 + " / " + labelY2;
|
|
else
|
|
labelY = labelY1;
|
|
|
|
const long Y_COL = m_cursors->AppendColumn( labelY, wxLIST_FORMAT_LEFT, size.x / 4 );
|
|
|
|
// Update cursor values
|
|
int itemidx = 0;
|
|
|
|
for( const auto& trace : plotPanel->GetTraces() )
|
|
{
|
|
if( CURSOR* cursor = trace.second->GetCursor() )
|
|
{
|
|
// Find the right icon color in list.
|
|
// It is the icon used in m_signals list for the same trace
|
|
long iconColor = m_signals->FindItem( -1, trace.first );
|
|
|
|
const wxRealPoint coords = cursor->GetCoords();
|
|
long idx = m_cursors->InsertItem( itemidx++, trace.first, iconColor );
|
|
m_cursors->SetItem( idx, X_COL, SPICE_VALUE( coords.x ).ToSpiceString() );
|
|
m_cursors->SetItem( idx, Y_COL, SPICE_VALUE( coords.y ).ToSpiceString() );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onSimStarted( wxCommandEvent& aEvent )
|
|
{
|
|
m_toolBar->SetToolNormalBitmap( ID_SIM_RUN, KiBitmap( BITMAPS::sim_stop ) );
|
|
SetCursor( wxCURSOR_ARROWWAIT );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onSimFinished( wxCommandEvent& aEvent )
|
|
{
|
|
m_toolBar->SetToolNormalBitmap( ID_SIM_RUN, KiBitmap( BITMAPS::sim_run ) );
|
|
SetCursor( wxCURSOR_ARROW );
|
|
|
|
SIM_TYPE simType = m_circuitModel->GetSimType();
|
|
|
|
if( simType == ST_UNKNOWN )
|
|
return;
|
|
|
|
SIM_PANEL_BASE* plotPanelWindow = getCurrentPlotWindow();
|
|
|
|
if( !plotPanelWindow || plotPanelWindow->GetType() != simType )
|
|
plotPanelWindow = NewPlotPanel( m_circuitModel->GetSimCommand() );
|
|
|
|
if( m_simulator->IsRunning() )
|
|
return;
|
|
|
|
// If there are any signals plotted, update them
|
|
if( SIM_PANEL_BASE::IsPlottable( simType ) )
|
|
{
|
|
SIM_PLOT_PANEL* plotPanel = dynamic_cast<SIM_PLOT_PANEL*>( plotPanelWindow );
|
|
wxCHECK_RET( plotPanel, "not a SIM_PLOT_PANEL" );
|
|
|
|
struct TRACE_DESC
|
|
{
|
|
wxString m_name; ///< Name of the measured net/device
|
|
SIM_PLOT_TYPE m_type; ///< Type of the signal
|
|
};
|
|
|
|
std::vector<struct TRACE_DESC> traceInfo;
|
|
|
|
// Get information about all the traces on the plot, remove and add again
|
|
for( auto& trace : plotPanel->GetTraces() )
|
|
{
|
|
struct TRACE_DESC placeholder;
|
|
placeholder.m_name = trace.second->GetName();
|
|
placeholder.m_type = trace.second->GetType();
|
|
|
|
traceInfo.push_back( placeholder );
|
|
}
|
|
|
|
for( auto& trace : traceInfo )
|
|
{
|
|
if( !updatePlot( trace.m_name, trace.m_type, plotPanel ) )
|
|
removePlot( trace.m_name );
|
|
}
|
|
|
|
updateSignalList();
|
|
plotPanel->GetPlotWin()->UpdateAll();
|
|
plotPanel->ResetScales();
|
|
}
|
|
else if( simType == ST_OP )
|
|
{
|
|
m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
|
|
m_simConsole->SetInsertionPointEnd();
|
|
|
|
for( const auto& vec : m_simulator->AllPlots() )
|
|
{
|
|
std::vector<double> val_list = m_simulator->GetRealPlot( vec, 1 );
|
|
|
|
if( val_list.size() == 0 ) // The list of values can be empty!
|
|
continue;
|
|
|
|
double val = val_list.at( 0 );
|
|
wxString outLine, signal;
|
|
SIM_PLOT_TYPE type = m_circuitModel->VectorToSignal( vec, signal );
|
|
|
|
const size_t tab = 25; //characters
|
|
size_t padding = ( signal.length() < tab ) ? ( tab - signal.length() ) : 1;
|
|
|
|
outLine.Printf( wxT( "%s%s" ),
|
|
( signal + wxT( ":" ) ).Pad( padding, wxUniChar( ' ' ) ),
|
|
SPICE_VALUE( val ).ToSpiceString() );
|
|
|
|
outLine.Append( type == SPT_CURRENT ? "A\n" : "V\n" );
|
|
|
|
m_simConsole->AppendText( outLine );
|
|
m_simConsole->SetInsertionPointEnd();
|
|
|
|
// @todo display calculated values on the schematic
|
|
}
|
|
}
|
|
|
|
m_lastSimPlot = plotPanelWindow;
|
|
m_simFinished = true;
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onSimUpdate( 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();
|
|
|
|
if( getCurrentPlotWindow() != m_lastSimPlot )
|
|
{
|
|
// We need to rerun simulation, as the simulator currently stores
|
|
// results for another plot
|
|
StartSimulation();
|
|
}
|
|
else
|
|
{
|
|
std::unique_lock<std::mutex> simulatorLock( m_simulator->GetMutex(), std::try_to_lock );
|
|
|
|
if( simulatorLock.owns_lock() )
|
|
{
|
|
// Incremental update
|
|
m_simConsole->Clear();
|
|
|
|
// Do not export netlist, it is already stored in the simulator
|
|
applyTuners();
|
|
|
|
m_simulator->Run();
|
|
}
|
|
else
|
|
DisplayErrorMessage( this, _( "Another simulation is already running." ) );
|
|
}
|
|
updateInProgress = false;
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::onSimReport( wxCommandEvent& aEvent )
|
|
{
|
|
m_simConsole->AppendText( aEvent.GetString() + "\n" );
|
|
m_simConsole->SetInsertionPointEnd();
|
|
}
|
|
|
|
|
|
SIM_PLOT_FRAME::SIGNAL_CONTEXT_MENU::SIGNAL_CONTEXT_MENU( const wxString& aSignal,
|
|
SIM_PLOT_FRAME* aPlotFrame ) :
|
|
m_signal( aSignal ),
|
|
m_plotFrame( aPlotFrame )
|
|
{
|
|
SIM_PLOT_PANEL* plot = m_plotFrame->GetCurrentPlot();
|
|
|
|
AddMenuItem( this, HIDE_SIGNAL, _( "Hide Signal" ), _( "Erase the signal from plot screen" ),
|
|
KiBitmap( BITMAPS::trash ) );
|
|
|
|
TRACE* trace = plot->GetTrace( m_signal );
|
|
|
|
if( trace->HasCursor() )
|
|
AddMenuItem( this, HIDE_CURSOR, _( "Hide Cursor" ), KiBitmap( BITMAPS::pcb_target ) );
|
|
else
|
|
AddMenuItem( this, SHOW_CURSOR, _( "Show Cursor" ), KiBitmap( BITMAPS::pcb_target ) );
|
|
|
|
Connect( wxEVT_COMMAND_MENU_SELECTED, wxMenuEventHandler( SIGNAL_CONTEXT_MENU::onMenuEvent ),
|
|
nullptr, this );
|
|
}
|
|
|
|
|
|
void SIM_PLOT_FRAME::SIGNAL_CONTEXT_MENU::onMenuEvent( wxMenuEvent& aEvent )
|
|
{
|
|
SIM_PLOT_PANEL* plot = m_plotFrame->GetCurrentPlot();
|
|
|
|
switch( aEvent.GetId() )
|
|
{
|
|
case HIDE_SIGNAL:
|
|
m_plotFrame->removePlot( m_signal );
|
|
break;
|
|
|
|
case SHOW_CURSOR:
|
|
plot->EnableCursor( m_signal, true );
|
|
break;
|
|
|
|
case HIDE_CURSOR:
|
|
plot->EnableCursor( m_signal, false );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
wxDEFINE_EVENT( EVT_SIM_UPDATE, wxCommandEvent );
|
|
wxDEFINE_EVENT( EVT_SIM_REPORT, wxCommandEvent );
|
|
|
|
wxDEFINE_EVENT( EVT_SIM_STARTED, wxCommandEvent );
|
|
wxDEFINE_EVENT( EVT_SIM_FINISHED, wxCommandEvent );
|