diff --git a/eeschema/sim/sim_plot_frame.cpp b/eeschema/sim/sim_plot_frame.cpp index 4ca86031e8..a9bcafc64e 100644 --- a/eeschema/sim/sim_plot_frame.cpp +++ b/eeschema/sim/sim_plot_frame.cpp @@ -83,8 +83,6 @@ SIM_PLOT_FRAME::SIM_PLOT_FRAME( KIWAY* aKiway, wxWindow* aParent ) Connect( EVT_SIM_STARTED, wxCommandEventHandler( SIM_PLOT_FRAME::onSimStarted ), NULL, this ); Connect( EVT_SIM_FINISHED, wxCommandEventHandler( SIM_PLOT_FRAME::onSimFinished ), NULL, this ); Connect( EVT_SIM_CURSOR_UPDATE, wxCommandEventHandler( SIM_PLOT_FRAME::onCursorUpdate ), NULL, this ); - - NewPlotPanel(); } @@ -121,9 +119,9 @@ void SIM_PLOT_FRAME::StopSimulation() } -void SIM_PLOT_FRAME::NewPlotPanel() +void SIM_PLOT_FRAME::NewPlotPanel( SIM_TYPE aSimType ) { - SIM_PLOT_PANEL* plot = new SIM_PLOT_PANEL( m_plotNotebook, wxID_ANY ); + SIM_PLOT_PANEL* plot = new SIM_PLOT_PANEL( aSimType, m_plotNotebook, wxID_ANY ); m_plotNotebook->AddPage( plot, wxString::Format( wxT( "Plot%lu" ), m_plotNotebook->GetPageCount() + 1 ), true ); @@ -140,6 +138,7 @@ void SIM_PLOT_FRAME::AddVoltagePlot( const wxString& aNetName ) } } + SIM_PLOT_PANEL* SIM_PLOT_FRAME::CurrentPlot() const { return static_cast( m_plotNotebook->GetCurrentPage() ); @@ -161,13 +160,71 @@ void SIM_PLOT_FRAME::updateNetlistExporter() void SIM_PLOT_FRAME::updatePlot( const wxString& aSpiceName, const wxString& aName, SIM_PLOT_PANEL* aPanel ) { - auto data_y = m_simulator->GetPlot( (const char*) aSpiceName.c_str() ); - auto data_t = m_simulator->GetPlot( "time" ); + // First, handle the x axis + wxString xAxisName; + SIM_TYPE simType = m_exporter->GetSimType(); - if( data_y.empty() || data_t.empty() ) + if( !SIM_PLOT_PANEL::IsPlottable( simType ) ) + { + // There is no plot to be shown + m_simulator->Command( wxString::Format( "print %s", aSpiceName ).ToStdString() ); + return; + } + + switch( simType ) + { + /// @todo x axis names should be moved to simulator iface, so they are not hardcoded for ngspice + case ST_AC: + case ST_NOISE: + xAxisName = "frequency"; + break; + + case ST_DC: + xAxisName = "v-sweep"; + break; + + case ST_TRANSIENT: + xAxisName = "time"; + break; + + case ST_OP: + break; + + default: + break; + } + + auto data_x = m_simulator->GetMagPlot( (const char*) xAxisName.c_str() ); + int size = data_x.size(); + + if( data_x.empty() ) return; - aPanel->AddTrace( aSpiceName, aName, data_t.size(), data_t.data(), data_y.data(), 0 ); + // Now, Y axis data + switch( m_exporter->GetSimType() ) + { + /// @todo x axis names should be moved to simulator iface + case ST_AC: + { + auto data_mag = m_simulator->GetMagPlot( (const char*) aSpiceName.c_str() ); + auto data_phase = m_simulator->GetPhasePlot( (const char*) aSpiceName.c_str() ); + aPanel->AddTrace( aSpiceName, aName + " (mag)", size, data_x.data(), data_mag.data(), 0 ); + aPanel->AddTrace( aSpiceName, aName + " (phase)", size, data_x.data(), data_phase.data(), 0 ); + } + break; + + case ST_NOISE: + case ST_DC: + case ST_TRANSIENT: + { + auto data_y = m_simulator->GetMagPlot( (const char*) aSpiceName.c_str() ); + aPanel->AddTrace( aSpiceName, aName, size, data_x.data(), data_y.data(), 0 ); + } + break; + + default: + return; + } } @@ -186,10 +243,22 @@ int SIM_PLOT_FRAME::getNodeNumber( const wxString& aNetName ) } +void SIM_PLOT_FRAME::menuNewPlot( wxCommandEvent& aEvent ) +{ + if( m_exporter ) + { + SIM_TYPE type = m_exporter->GetSimType(); + + if( SIM_PLOT_PANEL::IsPlottable( type ) ) + NewPlotPanel( type ); + } +} + + void SIM_PLOT_FRAME::menuSaveImage( wxCommandEvent& event ) { wxFileDialog saveDlg( this, wxT( "Save plot as image" ), "", "", - "PNG file (*.png)|*.png", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + "PNG file (*.png)|*.png", wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); if( saveDlg.ShowModal() == wxID_CANCEL ) return; @@ -203,7 +272,7 @@ void SIM_PLOT_FRAME::menuSaveCsv( wxCommandEvent& event ) const wxChar SEPARATOR = ';'; wxFileDialog saveDlg( this, wxT( "Save plot data" ), "", "", - "CSV file (*.csv)|*.csv", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + "CSV file (*.csv)|*.csv", wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); if( saveDlg.ShowModal() == wxID_CANCEL ) return; @@ -308,6 +377,14 @@ void SIM_PLOT_FRAME::onSignalDblClick( wxCommandEvent& event ) { int idx = m_signals->GetSelection(); SIM_PLOT_PANEL* plot = CurrentPlot(); + SIM_TYPE simType = m_exporter->GetSimType(); + + // Create a new plot if the current one displays a different type + if( SIM_PLOT_PANEL::IsPlottable( simType ) && ( plot == nullptr || plot->GetType() != simType ) ) + { + NewPlotPanel( simType ); + plot = CurrentPlot(); + } if( idx != wxNOT_FOUND ) { @@ -388,7 +465,7 @@ void SIM_PLOT_FRAME::onCursorUpdate( wxCommandEvent& event ) const long SIGNAL_COL = m_cursors->AppendColumn( wxT( "Signal" ), wxLIST_FORMAT_LEFT, size.x / 2 ); const long X_COL = m_cursors->AppendColumn( CurrentPlot()->GetLabelX(), wxLIST_FORMAT_LEFT, size.x / 4 ); - const long Y_COL = m_cursors->AppendColumn( CurrentPlot()->GetLabelY(), wxLIST_FORMAT_LEFT, size.x / 4 ); + const long Y_COL = m_cursors->AppendColumn( CurrentPlot()->GetLabelY1(), wxLIST_FORMAT_LEFT, size.x / 4 ); // Update cursor values for( const auto& trace : CurrentPlot()->GetTraces() ) @@ -416,6 +493,8 @@ void SIM_PLOT_FRAME::onSimFinished( wxCommandEvent& aEvent ) m_simulateBtn->SetLabel( wxT( "Simulate" ) ); SetCursor( wxCURSOR_ARROW ); + SIM_TYPE simType = m_exporter->GetSimType(); + // Fill the signals listbox m_signals->Clear(); @@ -426,12 +505,28 @@ void SIM_PLOT_FRAME::onSimFinished( wxCommandEvent& aEvent ) } // If there are any signals plotted, update them - SIM_PLOT_PANEL* plotPanel = CurrentPlot(); + if( SIM_PLOT_PANEL::IsPlottable( simType ) ) + { + SIM_PLOT_PANEL* plotPanel = CurrentPlot(); - for( const auto& trace : plotPanel->GetTraces() ) - updatePlot( trace.second->GetSpiceName(), trace.second->GetName(), plotPanel ); + if( plotPanel == nullptr ) + return; - plotPanel->UpdateAll(); + for( const auto& trace : plotPanel->GetTraces() ) + updatePlot( trace.second->GetSpiceName(), trace.second->GetName(), plotPanel ); + + plotPanel->UpdateAll(); + } + else + { + /// @todo do not make it hardcoded for ngspice + for( const auto& net : m_exporter->GetNetIndexMap() ) + { + int node = net.second; + if( node > 0 ) + m_simulator->Command( wxString::Format( "print v(%d)", node ).ToStdString() ); + } + } } diff --git a/eeschema/sim/sim_plot_frame.h b/eeschema/sim/sim_plot_frame.h index cf3f5f08f4..0c12db8562 100644 --- a/eeschema/sim/sim_plot_frame.h +++ b/eeschema/sim/sim_plot_frame.h @@ -30,6 +30,7 @@ @file Subclass of SIM_PLOT_FRAME_BASE, which is generated by wxFormBuilder. */ #include "sim_plot_frame_base.h" +#include "sim_types.h" #include "kiway_player.h" #include #include @@ -56,7 +57,7 @@ class SIM_PLOT_FRAME : public SIM_PLOT_FRAME_BASE void StartSimulation(); void StopSimulation(); - void NewPlotPanel(); + void NewPlotPanel( SIM_TYPE aSimType ); void AddVoltagePlot( const wxString& aNetName ); SIM_PLOT_PANEL* CurrentPlot() const; @@ -84,17 +85,13 @@ class SIM_PLOT_FRAME : public SIM_PLOT_FRAME_BASE int getNodeNumber( const wxString& aNetName ); // Menu handlers - void menuNewPlot( wxCommandEvent& aEvent ) override - { - NewPlotPanel(); - } + void menuNewPlot( wxCommandEvent& aEvent ) override; void menuExit( wxCommandEvent& event ) override { Close(); } - // Event handlers void menuSaveImage( wxCommandEvent& event ) override; void menuSaveCsv( wxCommandEvent& event ) override; void menuZoomIn( wxCommandEvent& event ) override; @@ -107,6 +104,7 @@ class SIM_PLOT_FRAME : public SIM_PLOT_FRAME_BASE void menuShowCoords( wxCommandEvent& event ) override; void menuShowCoordsUpdate( wxUpdateUIEvent& event ) override; + // Event handlers void onPlotChanged( wxNotebookEvent& event ) override; void onSignalDblClick( wxCommandEvent& event ) override; diff --git a/eeschema/sim/sim_plot_panel.cpp b/eeschema/sim/sim_plot_panel.cpp index 61a6fe1454..43a3dacb97 100644 --- a/eeschema/sim/sim_plot_panel.cpp +++ b/eeschema/sim/sim_plot_panel.cpp @@ -95,17 +95,58 @@ void CURSOR::Plot( wxDC& aDC, mpWindow& aWindow ) } -SIM_PLOT_PANEL::SIM_PLOT_PANEL( wxWindow* parent, wxWindowID id, const wxPoint& pos, +SIM_PLOT_PANEL::SIM_PLOT_PANEL( SIM_TYPE aType, wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) - : mpWindow( parent, id, pos, size, style ), m_colorIdx( 0 ) + : mpWindow( parent, id, pos, size, style ), m_colorIdx( 0 ), + m_axis_x( nullptr ), m_axis_y1( nullptr ), m_axis_y2( nullptr ), m_type( aType ) { - m_axis_x = new mpScaleX( wxT( "T [s]" ) ); - m_axis_x->SetTicks( false ); - AddLayer( m_axis_x ); + SetMargins( 10, 10, 10, 10 ); - m_axis_y = new mpScaleY( wxT( "U [V]" ) ); - m_axis_y->SetTicks( false ); - AddLayer( m_axis_y ); + switch( m_type ) + { + case ST_AC: + m_axis_x = new mpScaleX( wxT( "frequency [Hz]" ), mpALIGN_BORDER_BOTTOM ); + m_axis_y1 = new mpScaleY( wxT( "magnitude [V]" ), mpALIGN_BORDER_LEFT ); + m_axis_y2 = new mpScaleY( wxT( "phase [rad]" ), mpALIGN_BORDER_RIGHT ); + break; + + case ST_DC: + m_axis_x = new mpScaleX( wxT( "voltage [V]" ), mpALIGN_BORDER_BOTTOM ); + m_axis_y1 = new mpScaleY( wxT( "voltage [V]" ), mpALIGN_BORDER_LEFT ); + break; + + case ST_NOISE: + m_axis_x = new mpScaleX( wxT( "frequency [Hz]" ), mpALIGN_BORDER_BOTTOM ); + m_axis_y1 = new mpScaleY( wxT( "noise [(V or A)^2/Hz]" ), mpALIGN_BORDER_LEFT ); + break; + + case ST_TRANSIENT: + m_axis_x = new mpScaleX( wxT( "time [s]" ), mpALIGN_BORDER_BOTTOM ); + m_axis_y1 = new mpScaleY( wxT( "voltage [V]" ), mpALIGN_BORDER_LEFT ); + break; + + default: + // suppress warnings + break; + } + + if( m_axis_x ) + { + m_axis_x->SetTicks( false ); + AddLayer( m_axis_x ); + } + + if( m_axis_y1 ) + { + m_axis_y1->SetTicks( false ); + AddLayer( m_axis_y1 ); + } + + if( m_axis_y2 ) + { + m_axis_y2->SetTicks( false ); + AddLayer( m_axis_y2 ); + } m_coords = new mpInfoCoords( wxRect( 0, 0, 100, 40 ), wxWHITE_BRUSH ); AddLayer( m_coords ); @@ -123,6 +164,21 @@ SIM_PLOT_PANEL::~SIM_PLOT_PANEL() } +bool SIM_PLOT_PANEL::IsPlottable( SIM_TYPE aSimType ) +{ + switch( aSimType ) + { + case ST_AC: + case ST_DC: + case ST_TRANSIENT: + return true; + + default: + return false; + } +} + + bool SIM_PLOT_PANEL::AddTrace( const wxString& aSpiceName, const wxString& aName, int aPoints, const double* aT, const double* aY, int aFlags ) { diff --git a/eeschema/sim/sim_plot_panel.h b/eeschema/sim/sim_plot_panel.h index 9ba09a32c4..1d87471386 100644 --- a/eeschema/sim/sim_plot_panel.h +++ b/eeschema/sim/sim_plot_panel.h @@ -28,6 +28,7 @@ #include #include +#include "sim_types.h" class TRACE; @@ -143,19 +144,31 @@ private: class SIM_PLOT_PANEL : public mpWindow { public: - SIM_PLOT_PANEL( wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, + SIM_PLOT_PANEL( SIM_TYPE aType, wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0, const wxString& name = wxPanelNameStr ); ~SIM_PLOT_PANEL(); - const wxString& GetLabelX() const + SIM_TYPE GetType() const { - return m_axis_x->GetName(); + return m_type; } - const wxString& GetLabelY() const + static bool IsPlottable( SIM_TYPE aSimType ); + + wxString GetLabelX() const { - return m_axis_y->GetName(); + return m_axis_x ? m_axis_x->GetName() : ""; + } + + wxString GetLabelY1() const + { + return m_axis_y1 ? m_axis_y1->GetName() : ""; + } + + wxString GetLabelY2() const + { + return m_axis_y2 ? m_axis_y2->GetName() : ""; } bool AddTrace( const wxString& aSpiceName, const wxString& aName, int aPoints, @@ -185,13 +198,14 @@ public: void ShowGrid( bool aEnable ) { m_axis_x->SetTicks( !aEnable ); - m_axis_y->SetTicks( !aEnable ); + m_axis_y1->SetTicks( !aEnable ); + m_axis_y2->SetTicks( !aEnable ); UpdateAll(); } bool IsGridShown() const { - assert( m_axis_x->GetTicks() == m_axis_y->GetTicks() ); + assert( m_axis_x->GetTicks() == m_axis_y1->GetTicks() ); return !m_axis_x->GetTicks(); } @@ -230,11 +244,14 @@ private: std::map m_traces; mpScaleX* m_axis_x; - mpScaleY* m_axis_y; + mpScaleY* m_axis_y1; + mpScaleY* m_axis_y2; mpInfoLegend* m_legend; mpInfoCoords* m_coords; std::vector m_topLevel; + + const SIM_TYPE m_type; }; wxDECLARE_EVENT( EVT_SIM_CURSOR_UPDATE, wxCommandEvent );