KiCad PCB EDA Suite
Loading...
Searching...
No Matches
simulator_frame_ui.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2016-2023 CERN
5 * Copyright (C) 2016-2023 KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Tomasz Wlostowski <[email protected]>
7 * @author Maciej Suminski <[email protected]>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 3
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, you may find one here:
21 * https://www.gnu.org/licenses/gpl-3.0.html
22 * or you may search the http://www.gnu.org website for the version 3 license,
23 * or you may write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 */
26
27#include <memory>
28
29#include <fmt/format.h>
30#include <wx/wfstream.h>
31#include <wx/stdstream.h>
32#include <wx/debug.h>
33
35#include <sch_edit_frame.h>
36#include <confirm.h>
40#include <widgets/wx_grid.h>
41#include <grid_tricks.h>
42#include <eda_pattern_match.h>
43#include <string_utils.h>
44#include <pgm_base.h>
46#include <sim/simulator_frame.h>
47#include <sim/sim_plot_tab.h>
48#include <sim/spice_simulator.h>
51#include <eeschema_settings.h>
52#include "kiplatform/app.h"
53
54
56{
57 int res = (int) aFirst | (int) aSecond;
58
59 return (SIM_TRACE_TYPE) res;
60}
61
62
64{
70};
71
72
74{
79};
80
81
83{
87};
88
89
90enum
91{
101
105
106
108{
109public:
111 GRID_TRICKS( aGrid ),
112 m_parent( aParent ),
113 m_menuRow( 0 ),
114 m_menuCol( 0 )
115 {}
116
117protected:
118 void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
119 void doPopupSelection( wxCommandEvent& event ) override;
120
121protected:
125};
126
127
128void SIGNALS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
129{
130 m_menuRow = aEvent.GetRow();
131 m_menuCol = aEvent.GetCol();
132
134 {
135 if( !( m_grid->IsInSelection( m_menuRow, m_menuCol ) ) )
136 m_grid->ClearSelection();
137
138 m_grid->SetGridCursor( m_menuRow, m_menuCol );
139
140 if( SIM_TAB* panel = m_parent->GetCurrentSimTab() )
141 {
142 if( panel->GetSimType() == ST_TRAN || panel->GetSimType() == ST_AC
143 || panel->GetSimType() == ST_DC || panel->GetSimType() == ST_SP )
144 {
145 menu.Append( MYID_MEASURE_MIN, _( "Measure Min" ) );
146 menu.Append( MYID_MEASURE_MAX, _( "Measure Max" ) );
147 menu.Append( MYID_MEASURE_AVG, _( "Measure Average" ) );
148 menu.Append( MYID_MEASURE_RMS, _( "Measure RMS" ) );
149 menu.Append( MYID_MEASURE_PP, _( "Measure Peak-to-peak" ) );
150
151 if( panel->GetSimType() == ST_AC || panel->GetSimType() == ST_SP )
152 {
153 menu.Append( MYID_MEASURE_MIN_AT, _( "Measure Frequency of Min" ) );
154 menu.Append( MYID_MEASURE_MAX_AT, _( "Measure Frequency of Max" ) );
155 }
156 else
157 {
158 menu.Append( MYID_MEASURE_MIN_AT, _( "Measure Time of Min" ) );
159 menu.Append( MYID_MEASURE_MAX_AT, _( "Measure Time of Max" ) );
160 }
161
162 menu.Append( MYID_MEASURE_INTEGRAL, _( "Measure Integral" ) );
163
164 if( panel->GetSimType() == ST_TRAN )
165 {
166 menu.AppendSeparator();
167 menu.Append( MYID_FOURIER, _( "Perform Fourier Analysis..." ) );
168 }
169
170 menu.AppendSeparator();
171 }
172 }
173 }
174
175 GRID_TRICKS::showPopupMenu( menu, aEvent );
176}
177
178
179void SIGNALS_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
180{
181 std::vector<wxString> signals;
182
183 wxGridCellCoordsArray cells1 = m_grid->GetSelectionBlockTopLeft();
184 wxGridCellCoordsArray cells2 = m_grid->GetSelectionBlockBottomRight();
185
186 for( size_t i = 0; i < cells1.Count(); i++ )
187 {
188 if( cells1[i].GetCol() == COL_SIGNAL_NAME )
189 {
190 for( int j = cells1[i].GetRow(); j < cells2[i].GetRow() + 1; j++ )
191 {
192 signals.push_back( m_grid->GetCellValue( j, cells1[i].GetCol() ) );
193 }
194 }
195 }
196
197 wxGridCellCoordsArray cells3 = m_grid->GetSelectedCells();
198
199 for( size_t i = 0; i < cells3.Count(); i++ )
200 {
201 if( cells3[i].GetCol() == COL_SIGNAL_NAME )
202 signals.push_back( m_grid->GetCellValue( cells3[i].GetRow(), cells3[i].GetCol() ) );
203 }
204
205 if( signals.size() < 1 )
206 signals.push_back( m_grid->GetCellValue( m_menuRow, m_menuCol ) );
207
208 auto addMeasurement =
209 [this]( const wxString& cmd, wxString signal )
210 {
211 if( signal.EndsWith( _( " (phase)" ) ) )
212 return;
213
214 if( signal.EndsWith( _( " (gain)" ) ) || signal.EndsWith( _( " (amplitude)" ) ) )
215 {
216 signal = signal.Left( signal.length() - 7 );
217
218 if( signal.Upper().StartsWith( wxS( "V(" ) ) )
219 signal = wxS( "vdb" ) + signal.Mid( 1 );
220 }
221
222 m_parent->AddMeasurement( cmd + wxS( " " ) + signal );
223 };
224
225 if( event.GetId() == MYID_MEASURE_MIN )
226 {
227 for( const wxString& signal : signals )
228 addMeasurement( wxS( "MIN" ), signal );
229 }
230 else if( event.GetId() == MYID_MEASURE_MAX )
231 {
232 for( const wxString& signal : signals )
233 addMeasurement( wxS( "MAX" ), signal );
234 }
235 else if( event.GetId() == MYID_MEASURE_AVG )
236 {
237 for( const wxString& signal : signals )
238 addMeasurement( wxS( "AVG" ), signal );
239 }
240 else if( event.GetId() == MYID_MEASURE_RMS )
241 {
242 for( const wxString& signal : signals )
243 addMeasurement( wxS( "RMS" ), signal );
244 }
245 else if( event.GetId() == MYID_MEASURE_PP )
246 {
247 for( const wxString& signal : signals )
248 addMeasurement( wxS( "PP" ), signal );
249 }
250 else if( event.GetId() == MYID_MEASURE_MIN_AT )
251 {
252 for( const wxString& signal : signals )
253 addMeasurement( wxS( "MIN_AT" ), signal );
254 }
255 else if( event.GetId() == MYID_MEASURE_MAX_AT )
256 {
257 for( const wxString& signal : signals )
258 addMeasurement( wxS( "MAX_AT" ), signal );
259 }
260 else if( event.GetId() == MYID_MEASURE_INTEGRAL )
261 {
262 for( const wxString& signal : signals )
263 addMeasurement( wxS( "INTEG" ), signal );
264 }
265 else if( event.GetId() == MYID_FOURIER )
266 {
267 wxString title;
268 wxString fundamental = wxT( "1K" );
269
270 if( signals.size() == 1 )
271 title.Printf( _( "Fourier Analysis of %s" ), signals[0] );
272 else
273 title = _( "Fourier Analyses of Multiple Signals" );
274
275 WX_TEXT_ENTRY_DIALOG dlg( m_parent, _( "Fundamental frequency:" ), title, fundamental );
276
277 if( dlg.ShowModal() != wxID_OK )
278 return;
279
280 if( !dlg.GetValue().IsEmpty() )
281 fundamental = dlg.GetValue();
282
283 for( const wxString& signal : signals )
284 m_parent->DoFourier( signal, fundamental );
285 }
286 else
287 {
289 }
290}
291
292
294{
295public:
297 GRID_TRICKS( aGrid ),
298 m_parent( aParent ),
299 m_menuRow( 0 ),
300 m_menuCol( 0 )
301 {}
302
303protected:
304 void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
305 void doPopupSelection( wxCommandEvent& event ) override;
306
307protected:
311};
312
313
314void CURSORS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
315{
316 m_menuRow = aEvent.GetRow();
317 m_menuCol = aEvent.GetCol();
318
320 {
321 wxString msg = m_grid->GetColLabelValue( m_menuCol );
322
323 menu.Append( MYID_FORMAT_VALUE, wxString::Format( _( "Format %s..." ), msg ) );
324 menu.AppendSeparator();
325 }
326
327 GRID_TRICKS::showPopupMenu( menu, aEvent );
328}
329
330
331void CURSORS_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
332{
333 auto getSignalName =
334 [this]( int row ) -> wxString
335 {
336 wxString signal = m_grid->GetCellValue( row, COL_CURSOR_SIGNAL );
337
338 if( signal.EndsWith( "[2 - 1]" ) )
339 signal = signal.Left( signal.length() - 7 );
340
341 return signal;
342 };
343
344 if( event.GetId() == MYID_FORMAT_VALUE )
345 {
346 int axis = m_menuCol - COL_CURSOR_X;
348 DIALOG_SIM_FORMAT_VALUE formatDialog( m_parent, &format );
349
350 if( formatDialog.ShowModal() == wxID_OK )
351 {
352 for( int row = 0; row < m_grid->GetNumberRows(); ++row )
353 {
354 if( getSignalName( row ) == getSignalName( m_menuRow ) )
355 m_parent->SetCursorFormat( row, axis, format );
356 }
357 }
358 }
359 else
360 {
362 }
363}
364
365
367{
368public:
370 GRID_TRICKS( aGrid ),
371 m_parent( aParent ),
372 m_menuRow( 0 ),
373 m_menuCol( 0 )
374 {}
375
376protected:
377 void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
378 void doPopupSelection( wxCommandEvent& event ) override;
379
380protected:
384};
385
386
387void MEASUREMENTS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
388{
389 m_menuRow = aEvent.GetRow();
390 m_menuCol = aEvent.GetCol();
391
392 if( !( m_grid->IsInSelection( m_menuRow, m_menuCol ) ) )
393 m_grid->ClearSelection();
394
395 m_grid->SetGridCursor( m_menuRow, m_menuCol );
396
398 menu.Append( MYID_FORMAT_VALUE, _( "Format Value..." ) );
399
400 if( m_menuRow < ( m_grid->GetNumberRows() - 1 ) )
401 menu.Append( MYID_DELETE_MEASUREMENT, _( "Delete Measurement" ) );
402
403 menu.AppendSeparator();
404
405 GRID_TRICKS::showPopupMenu( menu, aEvent );
406}
407
408
410{
411 if( event.GetId() == MYID_FORMAT_VALUE )
412 {
414 DIALOG_SIM_FORMAT_VALUE formatDialog( m_parent, &format );
415
416 if( formatDialog.ShowModal() == wxID_OK )
417 {
421 }
422 }
423 else if( event.GetId() == MYID_DELETE_MEASUREMENT )
424 {
425 std::vector<int> measurements;
426
427 wxGridCellCoordsArray cells1 = m_grid->GetSelectionBlockTopLeft();
428 wxGridCellCoordsArray cells2 = m_grid->GetSelectionBlockBottomRight();
429
430 for( size_t i = 0; i < cells1.Count(); i++ )
431 {
432 if( cells1[i].GetCol() == COL_MEASUREMENT )
433 {
434 for( int j = cells1[i].GetRow(); j < cells2[i].GetRow() + 1; j++ )
435 measurements.push_back( j );
436 }
437 }
438
439 wxGridCellCoordsArray cells3 = m_grid->GetSelectedCells();
440
441 for( size_t i = 0; i < cells3.Count(); i++ )
442 {
443 if( cells3[i].GetCol() == COL_MEASUREMENT )
444 measurements.push_back( cells3[i].GetRow() );
445 }
446
447 if( measurements.size() < 1 )
448 measurements.push_back( m_menuRow );
449
450 // When deleting a row, we'll change the indexes.
451 // To avoid problems, we can start with the highest indexes.
452 sort( measurements.begin(), measurements.end(), std::greater<>() );
453
454 for( int row : measurements )
456
457 m_grid->ClearSelection();
458
460 }
461 else
462 {
464 }
465}
466
467
469{
470public:
472 m_frame( aFrame )
473 {
475 }
476
478 {
480 }
481
482private:
484};
485
486
487#define ID_SIM_REFRESH 10207
488#define REFRESH_INTERVAL 50 // 20 frames/second.
489
490
492 SCH_EDIT_FRAME* aSchematicFrame ) :
493 SIMULATOR_FRAME_UI_BASE( aSimulatorFrame ),
494 m_SuppressGridEvents( 0 ),
495 m_simulatorFrame( aSimulatorFrame ),
496 m_schematicFrame( aSchematicFrame ),
497 m_darkMode( true ),
498 m_plotNumber( 0 ),
499 m_refreshTimer( this, ID_SIM_REFRESH )
500{
501 // Get the previous size and position of windows:
503
504 m_filter->SetHint( _( "Filter" ) );
505
506 m_signalsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
507 m_cursorsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
508 m_measurementsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
509
510 m_signalsGrid->PushEventHandler( new SIGNALS_GRID_TRICKS( this, m_signalsGrid ) );
511 m_cursorsGrid->PushEventHandler( new CURSORS_GRID_TRICKS( this, m_cursorsGrid ) );
512 m_measurementsGrid->PushEventHandler( new MEASUREMENTS_GRID_TRICKS( this, m_measurementsGrid ) );
513
514 wxGridCellAttr* attr = new wxGridCellAttr;
515 attr->SetReadOnly();
516 m_signalsGrid->SetColAttr( COL_SIGNAL_NAME, attr );
517
518 attr = new wxGridCellAttr;
519 attr->SetReadOnly();
520 m_cursorsGrid->SetColAttr( COL_CURSOR_NAME, attr );
521
522 attr = new wxGridCellAttr;
523 attr->SetReadOnly();
524 m_cursorsGrid->SetColAttr( COL_CURSOR_SIGNAL, attr );
525
526 attr = new wxGridCellAttr;
527 attr->SetReadOnly();
528 m_cursorsGrid->SetColAttr( COL_CURSOR_Y, attr );
529
530 for( int cursorId = 0; cursorId < 3; ++cursorId )
531 {
532 m_cursorFormats[ cursorId ][ 0 ] = { 3, wxS( "~s" ) };
533 m_cursorFormats[ cursorId ][ 1 ] = { 3, wxS( "~V" ) };
534 }
535
536 attr = new wxGridCellAttr;
537 attr->SetReadOnly();
538 m_measurementsGrid->SetColAttr( COL_MEASUREMENT_VALUE, attr );
539
540 // Prepare the color list to plot traces
542
543 Bind( EVT_SIM_CURSOR_UPDATE, &SIMULATOR_FRAME_UI::onPlotCursorUpdate, this );
544
545 Bind( wxEVT_TIMER,
546 [&]( wxTimerEvent& aEvent )
547 {
548 OnSimRefresh( false );
549
550 if( m_simulatorFrame->GetSimulator()->IsRunning() )
551 m_refreshTimer.Start( REFRESH_INTERVAL, wxTIMER_ONE_SHOT );
552 },
553 m_refreshTimer.GetId() );
554
555#ifndef wxHAS_NATIVE_TABART
556 // Default non-native tab art has ugly gradients we don't want
557 m_plotNotebook->SetArtProvider( new wxAuiSimpleTabArt() );
558#endif
559}
560
561
563{
564 // Delete the GRID_TRICKS.
565 m_signalsGrid->PopEventHandler( true );
566 m_cursorsGrid->PopEventHandler( true );
567 m_measurementsGrid->PopEventHandler( true );
568}
569
570
572{
573 for( int ii = 0; ii < (int) m_plotNotebook->GetPageCount(); ++ii )
574 {
575 SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( ii ) );
576
577 wxCHECK( simTab, /* void */ );
578
579 simTab->OnLanguageChanged();
580
581 wxString pageTitle( simulator()->TypeToName( simTab->GetSimType(), true ) );
582 pageTitle.Prepend( wxString::Format( _( "Analysis %u - " ), ii+1 /* 1-based */ ) );
583
584 m_plotNotebook->SetPageText( ii, pageTitle );
585 }
586
587 m_filter->SetHint( _( "Filter" ) );
588
589 m_signalsGrid->SetColLabelValue( COL_SIGNAL_NAME, _( "Signal" ) );
590 m_signalsGrid->SetColLabelValue( COL_SIGNAL_SHOW, _( "Plot" ) );
591 m_signalsGrid->SetColLabelValue( COL_SIGNAL_COLOR, _( "Color" ) );
592 m_signalsGrid->SetColLabelValue( COL_CURSOR_1, _( "Cursor 1" ) );
593 m_signalsGrid->SetColLabelValue( COL_CURSOR_2, _( "Cursor 2" ) );
594
595 m_cursorsGrid->SetColLabelValue( COL_CURSOR_NAME, _( "Cursor" ) );
596 m_cursorsGrid->SetColLabelValue( COL_CURSOR_SIGNAL, _( "Signal" ) );
597 m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, _( "Time" ) );
598 m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, _( "Value" ) );
600
601 for( TUNER_SLIDER* tuner : m_tuners )
602 tuner->ShowChangedLanguage();
603}
604
605
607{
608 // Read subwindows sizes (should be > 0 )
615}
616
617
619{
620 aCfg->m_Simulator.plot_panel_width = m_splitterLeftRight->GetSashPosition();
621 aCfg->m_Simulator.plot_panel_height = m_splitterPlotAndConsole->GetSashPosition();
622 aCfg->m_Simulator.signal_panel_height = m_splitterSignals->GetSashPosition();
623 aCfg->m_Simulator.cursors_panel_height = m_splitterCursors->GetSashPosition();
626}
627
628
630{
631 if( !simulator()->Settings()->GetWorkbookFilename().IsEmpty() )
632 {
633 wxFileName filename = simulator()->Settings()->GetWorkbookFilename();
634 filename.SetPath( m_schematicFrame->Prj().GetProjectPath() );
635
636 if( !LoadWorkbook( filename.GetFullPath() ) )
637 simulator()->Settings()->SetWorkbookFilename( "" );
638 }
639 else if( m_simulatorFrame->LoadSimulator( wxEmptyString, 0 ) )
640 {
641 wxString schTextSimCommand = circuitModel()->GetSchTextSimCommand();
642
643 if( !schTextSimCommand.IsEmpty() )
644 {
645 SIM_TAB* simTab = NewSimTab( schTextSimCommand );
647 }
648
650 rebuildSignalsGrid( m_filter->GetValue() );
651 }
652}
653
654
656{
659
662
665
668
671}
672
673
674void sortSignals( std::vector<wxString>& signals )
675{
676 std::sort( signals.begin(), signals.end(),
677 []( const wxString& lhs, const wxString& rhs )
678 {
679 // Sort voltages first
680 if( lhs.Upper().StartsWith( 'V' ) && !rhs.Upper().StartsWith( 'V' ) )
681 return true;
682 else if( !lhs.Upper().StartsWith( 'V' ) && rhs.Upper().StartsWith( 'V' ) )
683 return false;
684
685 return StrNumCmp( lhs, rhs, true /* ignore case */ ) < 0;
686 } );
687}
688
689
691{
692 SUPPRESS_GRID_CELL_EVENTS raii( this );
693
695
696 SIM_PLOT_TAB* plotPanel = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
697
698 if( !plotPanel )
699 return;
700
701 SIM_TYPE simType = plotPanel->GetSimType();
702 std::vector<wxString> signals;
703
704 if( plotPanel->GetSimType() == ST_FFT )
705 {
706 wxStringTokenizer tokenizer( plotPanel->GetSimCommand(), wxT( " \t\r\n" ), wxTOKEN_STRTOK );
707
708 while( tokenizer.HasMoreTokens() && tokenizer.GetNextToken().Lower() != wxT( "fft" ) )
709 {};
710
711 while( tokenizer.HasMoreTokens() )
712 signals.emplace_back( tokenizer.GetNextToken() );
713 }
714 else
715 {
716 // NB: m_signals are already broken out into gain/phase, but m_userDefinedSignals are
717 // as the user typed them
718
719 for( const wxString& signal : m_signals )
720 signals.push_back( signal );
721
722 for( const auto& [ id, signal ] : m_userDefinedSignals )
723 {
724 if( simType == ST_AC )
725 {
726 signals.push_back( signal + _( " (gain)" ) );
727 signals.push_back( signal + _( " (phase)" ) );
728 }
729 else if( simType == ST_SP )
730 {
731 signals.push_back( signal + _( " (amplitude)" ) );
732 signals.push_back( signal + _( " (phase)" ) );
733 }
734 else
735 {
736 signals.push_back( signal );
737 }
738 }
739
740 sortSignals( signals );
741 }
742
743 if( aFilter.IsEmpty() )
744 aFilter = wxS( "*" );
745
746 EDA_COMBINED_MATCHER matcher( aFilter.Upper(), CTX_SIGNAL );
747 int row = 0;
748
749 for( const wxString& signal : signals )
750 {
751 if( matcher.Find( signal.Upper() ) )
752 {
753 int traceType = SPT_UNKNOWN;
754 wxString vectorName = vectorNameFromSignalName( plotPanel, signal, &traceType );
755 TRACE* trace = plotPanel ? plotPanel->GetTrace( vectorName, traceType ) : nullptr;
756
757 m_signalsGrid->AppendRows( 1 );
758 m_signalsGrid->SetCellValue( row, COL_SIGNAL_NAME, signal );
759
760 if( !plotPanel )
761 {
762 wxGridCellAttr* attr = new wxGridCellAttr;
763 attr->SetReadOnly();
764 m_signalsGrid->SetAttr( row, COL_SIGNAL_SHOW, attr );
765 }
766 else
767 {
768 wxGridCellAttr* attr = new wxGridCellAttr;
769 attr->SetRenderer( new wxGridCellBoolRenderer() );
770 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
771 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
772 m_signalsGrid->SetAttr( row, COL_SIGNAL_SHOW, attr );
773 }
774
775 if( trace )
776 m_signalsGrid->SetCellValue( row, COL_SIGNAL_SHOW, wxS( "1" ) );
777
778 if( !plotPanel || !trace )
779 {
780 wxGridCellAttr* attr = new wxGridCellAttr;
781 attr->SetReadOnly();
782 m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
783 m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, wxEmptyString );
784
785 attr = new wxGridCellAttr;
786 attr->SetReadOnly();
787 m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
788
789 attr = new wxGridCellAttr;
790 attr->SetReadOnly();
791 m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
792 }
793 else
794 {
795 wxGridCellAttr* attr = new wxGridCellAttr;
796 attr = new wxGridCellAttr;
797 attr->SetRenderer( new GRID_CELL_COLOR_RENDERER( this ) );
798 attr->SetEditor( new GRID_CELL_COLOR_SELECTOR( this, m_signalsGrid ) );
799 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
800 m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
801 KIGFX::COLOR4D color( trace->GetPen().GetColour() );
802 m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, color.ToCSSString() );
803
804 attr = new wxGridCellAttr;
805 attr->SetRenderer( new wxGridCellBoolRenderer() );
806 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
807 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
808 m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
809
810 attr = new wxGridCellAttr;
811 attr->SetRenderer( new wxGridCellBoolRenderer() );
812 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
813 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
814 m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
815 }
816
817 row++;
818 }
819 }
820}
821
822
824{
825 m_signals.clear();
826
827 int options = m_simulatorFrame->GetCurrentOptions();
829 wxString unconnected = wxString( wxS( "unconnected-(" ) );
830
831 if( simType == ST_UNKNOWN )
832 simType = ST_TRAN;
833
834 unconnected.Replace( '(', '_' ); // Convert to SPICE markup
835
836 auto addSignal =
837 [&]( const wxString& aSignalName )
838 {
839 if( simType == ST_AC )
840 {
841 m_signals.push_back( aSignalName + _( " (gain)" ) );
842 m_signals.push_back( aSignalName + _( " (phase)" ) );
843 }
844 else if( simType == ST_SP )
845 {
846 m_signals.push_back( aSignalName + _( " (amplitude)" ) );
847 m_signals.push_back( aSignalName + _( " (phase)" ) );
848 }
849 else
850 {
851 m_signals.push_back( aSignalName );
852 }
853 };
854
856 && ( simType == ST_TRAN || simType == ST_DC || simType == ST_AC || simType == ST_FFT) )
857 {
858 for( const std::string& net : circuitModel()->GetNets() )
859 {
860 // netnames are escaped (can contain "{slash}" for '/') Unscape them:
861 wxString netname = UnescapeString( net );
862
863 if( netname == "GND" || netname == "0" || netname.StartsWith( unconnected ) )
864 continue;
865
866 m_quotedNetnames[ netname ] = wxString::Format( wxS( "\"%s\"" ), netname );
867 addSignal( wxString::Format( wxS( "V(%s)" ), netname ) );
868 }
869 }
870
872 && ( simType == ST_TRAN || simType == ST_DC || simType == ST_AC ) )
873 {
874 for( const SPICE_ITEM& item : circuitModel()->GetItems() )
875 {
876 // Add all possible currents for the device.
877 for( const std::string& name : item.model->SpiceGenerator().CurrentNames( item ) )
878 addSignal( name );
879 }
880 }
881
883 && ( simType == ST_TRAN || simType == ST_DC ) )
884 {
885 for( const SPICE_ITEM& item : circuitModel()->GetItems() )
886 {
887 if( item.model->GetPinCount() >= 2 )
888 {
889 wxString name = item.model->SpiceGenerator().ItemName( item );
890 addSignal( wxString::Format( wxS( "P(%s)" ), name ) );
891 }
892 }
893 }
894
895 if( simType == ST_NOISE )
896 {
897 addSignal( wxS( "inoise_spectrum" ) );
898 addSignal( wxS( "onoise_spectrum" ) );
899 }
900
901 if( simType == ST_SP )
902 {
903 std::vector<std::string> portnums;
904
905 for( const SPICE_ITEM& item : circuitModel()->GetItems() )
906 {
907 wxString name = item.model->SpiceGenerator().ItemName( item );
908
909 // We are only looking for voltage sources in .SP mode
910 if( !name.StartsWith( "V" ) )
911 continue;
912
913 const SIM_MODEL::PARAM* portNum = item.model->FindParam( "portnum" );
914
915 if( portNum )
916 portnums.push_back( SIM_VALUE::ToSpice( portNum->value ) );
917 }
918
919 for( const std::string& portnum1 : portnums )
920 {
921 for( const std::string& portnum2 : portnums )
922 {
923 addSignal( wxString::Format( wxS( "S_%s_%s" ), portnum1, portnum2 ) );
924 }
925 }
926 }
927
928 // Add .PROBE directives
929 for( const wxString& directive : circuitModel()->GetDirectives() )
930 {
931 wxStringTokenizer tokenizer( directive, wxT( "\r\n" ), wxTOKEN_STRTOK );
932
933 while( tokenizer.HasMoreTokens() )
934 {
935 wxString line = tokenizer.GetNextToken();
936 wxString directiveParams;
937
938 if( line.Upper().StartsWith( wxS( ".PROBE" ), &directiveParams ) )
939 addSignal( directiveParams.Trim( true ).Trim( false ) );
940 }
941 }
942}
943
944
945SIM_TAB* SIMULATOR_FRAME_UI::NewSimTab( const wxString& aSimCommand )
946{
947 SIM_TAB* simTab = nullptr;
948 SIM_TYPE simType = SPICE_CIRCUIT_MODEL::CommandToSimType( aSimCommand );
949
950 if( SIM_TAB::IsPlottable( simType ) )
951 {
952 SIM_PLOT_TAB* panel = new SIM_PLOT_TAB( aSimCommand, m_plotNotebook );
953 simTab = panel;
954
955 COMMON_SETTINGS::INPUT cfg = Pgm().GetCommonSettings()->m_Input;
957 }
958 else
959 {
960 simTab = new SIM_NOPLOT_TAB( aSimCommand, m_plotNotebook );
961 }
962
963 wxString pageTitle( simulator()->TypeToName( simType, true ) );
964 pageTitle.Prepend( wxString::Format( _( "Analysis %u - " ), (unsigned int) ++m_plotNumber ) );
965
966 m_plotNotebook->AddPage( simTab, pageTitle, true );
967
968 return simTab;
969}
970
971
972void SIMULATOR_FRAME_UI::OnFilterText( wxCommandEvent& aEvent )
973{
974 rebuildSignalsGrid( m_filter->GetValue() );
975}
976
977
978void SIMULATOR_FRAME_UI::OnFilterMouseMoved( wxMouseEvent& aEvent )
979{
980 wxPoint pos = aEvent.GetPosition();
981 wxRect ctrlRect = m_filter->GetScreenRect();
982 int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
983
984 if( m_filter->IsSearchButtonVisible() && pos.x < buttonWidth )
985 SetCursor( wxCURSOR_ARROW );
986 else if( m_filter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
987 SetCursor( wxCURSOR_ARROW );
988 else
989 SetCursor( wxCURSOR_IBEAM );
990}
991
992
993wxString vectorNameFromSignalId( int aUserDefinedSignalId )
994{
995 return wxString::Format( wxS( "user%d" ), aUserDefinedSignalId );
996}
997
998
1004 const wxString& aSignalName,
1005 int* aTraceType )
1006{
1007 std::map<wxString, int> suffixes;
1008 suffixes[ _( " (amplitude)" ) ] = SPT_SP_AMP;
1009 suffixes[ _( " (gain)" ) ] = SPT_AC_GAIN;
1010 suffixes[ _( " (phase)" ) ] = SPT_AC_PHASE;
1011
1012 if( aTraceType )
1013 {
1014 if( aPlotTab && aPlotTab->GetSimType() == ST_NOISE )
1015 {
1016 if( getNoiseSource().Upper().StartsWith( 'I' ) )
1017 *aTraceType = SPT_CURRENT;
1018 else
1019 *aTraceType = SPT_VOLTAGE;
1020 }
1021 else
1022 {
1023 wxUniChar firstChar = aSignalName.Upper()[0];
1024
1025 if( firstChar == 'V' )
1026 *aTraceType = SPT_VOLTAGE;
1027 else if( firstChar == 'I' )
1028 *aTraceType = SPT_CURRENT;
1029 else if( firstChar == 'P' )
1030 *aTraceType = SPT_POWER;
1031 }
1032 }
1033
1034 wxString suffix;
1035 wxString name = aSignalName;
1036
1037 for( const auto& [ candidate, type ] : suffixes )
1038 {
1039 if( name.EndsWith( candidate ) )
1040 {
1041 name = name.Left( name.Length() - candidate.Length() );
1042
1043 if( aTraceType )
1044 *aTraceType |= type;
1045
1046 break;
1047 }
1048 }
1049
1050 for( const auto& [ id, signal ] : m_userDefinedSignals )
1051 {
1052 if( name == signal )
1053 return vectorNameFromSignalId( id );
1054 }
1055
1056 return name;
1057};
1058
1059
1061{
1062 if( m_SuppressGridEvents > 0 )
1063 return;
1064
1065 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1066
1067 if( !plotTab )
1068 return;
1069
1070 int row = aEvent.GetRow();
1071 int col = aEvent.GetCol();
1072 wxString text = m_signalsGrid->GetCellValue( row, col );
1073 wxString signalName = m_signalsGrid->GetCellValue( row, COL_SIGNAL_NAME );
1074 int traceType = SPT_UNKNOWN;
1075 wxString vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
1076
1077 if( col == COL_SIGNAL_SHOW )
1078 {
1079 if( text == wxS( "1" ) )
1080 updateTrace( vectorName, traceType, plotTab );
1081 else
1082 plotTab->DeleteTrace( vectorName, traceType );
1083
1084 plotTab->GetPlotWin()->UpdateAll();
1085
1086 // Update enabled/visible states of other controls
1089 OnModify();
1090 }
1091 else if( col == COL_SIGNAL_COLOR )
1092 {
1093 KIGFX::COLOR4D color( m_signalsGrid->GetCellValue( row, COL_SIGNAL_COLOR ) );
1094 TRACE* trace = plotTab->GetTrace( vectorName, traceType );
1095
1096 if( trace )
1097 {
1098 trace->SetTraceColour( color.ToColour() );
1099 plotTab->UpdateTraceStyle( trace );
1100 plotTab->UpdatePlotColors();
1101 OnModify();
1102 }
1103 }
1104 else if( col == COL_CURSOR_1 || col == COL_CURSOR_2 )
1105 {
1106 for( int ii = 0; ii < m_signalsGrid->GetNumberRows(); ++ii )
1107 {
1108 signalName = m_signalsGrid->GetCellValue( ii, COL_SIGNAL_NAME );
1109 vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
1110
1111 int id = col == COL_CURSOR_1 ? 1 : 2;
1112 bool enable = ii == row && text == wxS( "1" );
1113
1114 plotTab->EnableCursor( vectorName, traceType, id, enable, signalName );
1115 OnModify();
1116 }
1117
1118 // Update cursor checkboxes (which are really radio buttons)
1120 }
1121}
1122
1123
1125{
1126 if( m_SuppressGridEvents > 0 )
1127 return;
1128
1129 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1130
1131 if( !plotTab )
1132 return;
1133
1134 int row = aEvent.GetRow();
1135 int col = aEvent.GetCol();
1136 wxString text = m_cursorsGrid->GetCellValue( row, col );
1137 wxString cursorName = m_cursorsGrid->GetCellValue( row, COL_CURSOR_NAME );
1138
1139 if( col == COL_CURSOR_X )
1140 {
1141 CURSOR* cursor1 = nullptr;
1142 CURSOR* cursor2 = nullptr;
1143
1144 for( const auto& [name, trace] : plotTab->GetTraces() )
1145 {
1146 if( CURSOR* cursor = trace->GetCursor( 1 ) )
1147 cursor1 = cursor;
1148
1149 if( CURSOR* cursor = trace->GetCursor( 2 ) )
1150 cursor2 = cursor;
1151 }
1152
1153 double value = SPICE_VALUE( text ).ToDouble();
1154
1155 if( cursorName == wxS( "1" ) && cursor1 )
1156 cursor1->SetCoordX( value );
1157 else if( cursorName == wxS( "2" ) && cursor2 )
1158 cursor2->SetCoordX( value );
1159 else if( cursorName == _( "Diff" ) && cursor1 && cursor2 )
1160 cursor2->SetCoordX( cursor1->GetCoords().x + value );
1161
1163 OnModify();
1164 }
1165 else
1166 {
1167 wxFAIL_MSG( wxT( "All other columns are supposed to be read-only!" ) );
1168 }
1169}
1170
1171
1173{
1174 SPICE_VALUE_FORMAT result;
1175 result.FromString( m_measurementsGrid->GetCellValue( aRow, COL_MEASUREMENT_FORMAT ) );
1176 return result;
1177}
1178
1179
1181{
1182 m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_FORMAT, aFormat.ToString() );
1183}
1184
1185
1187{
1188 if( aRow < ( m_measurementsGrid->GetNumberRows() - 1 ) )
1189 m_measurementsGrid->DeleteRows( aRow, 1 );
1190}
1191
1192
1194{
1195 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1196
1197 if( !plotTab )
1198 return;
1199
1200 int row = aEvent.GetRow();
1201 int col = aEvent.GetCol();
1202 wxString text = m_measurementsGrid->GetCellValue( row, col );
1203
1204 if( col == COL_MEASUREMENT )
1205 {
1206 UpdateMeasurement( row );
1207 OnModify();
1208 }
1209 else
1210 {
1211 wxFAIL_MSG( wxT( "All other columns are supposed to be read-only!" ) );
1212 }
1213
1214 // Always leave a single empty row for type-in
1215
1216 int rowCount = (int) m_measurementsGrid->GetNumberRows();
1217 int emptyRows = 0;
1218
1219 for( row = rowCount - 1; row >= 0; row-- )
1220 {
1221 if( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1222 emptyRows++;
1223 else
1224 break;
1225 }
1226
1227 if( emptyRows > 1 )
1228 {
1229 int killRows = emptyRows - 1;
1230 m_measurementsGrid->DeleteRows( rowCount - killRows, killRows );
1231 }
1232 else if( emptyRows == 0 )
1233 {
1234 m_measurementsGrid->AppendRows( 1 );
1235 }
1236}
1237
1238
1239void SIMULATOR_FRAME_UI::OnUpdateUI( wxUpdateUIEvent& event )
1240{
1241 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
1242 {
1243 if( plotTab->GetLegendPosition() != plotTab->m_LastLegendPosition )
1244 OnModify();
1245 }
1246}
1247
1248
1264{
1265 static wxRegEx measureParamsRegEx( wxT( "^"
1266 " *"
1267 "([a-zA-Z_]+)"
1268 " +"
1269 "([a-zA-Z]*)\\(([^\\)]+)\\)" ) );
1270
1271 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1272
1273 if( !plotTab )
1274 return;
1275
1276 wxString text = m_measurementsGrid->GetCellValue( aRow, COL_MEASUREMENT );
1277
1278 if( text.IsEmpty() )
1279 {
1280 m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_VALUE, wxEmptyString );
1281 return;
1282 }
1283
1284 wxString simType = simulator()->TypeToName( plotTab->GetSimType(), true );
1285 wxString resultName = wxString::Format( wxS( "meas_result_%u" ), aRow );
1286 wxString result = wxS( "?" );
1287
1288 if( measureParamsRegEx.Matches( text ) )
1289 {
1290 wxString func = measureParamsRegEx.GetMatch( text, 1 ).Upper();
1291 wxString signalType = measureParamsRegEx.GetMatch( text, 2 ).Upper();
1292 wxString deviceName = measureParamsRegEx.GetMatch( text, 3 );
1293 wxString units;
1295
1296 if( signalType.EndsWith( wxS( "DB" ) ) )
1297 {
1298 units = wxS( "dB" );
1299 }
1300 else if( signalType.StartsWith( 'I' ) )
1301 {
1302 units = wxS( "A" );
1303 }
1304 else if( signalType.StartsWith( 'P' ) )
1305 {
1306 units = wxS( "W" );
1307 // Our syntax is different from ngspice for power signals
1308 text = func + " " + deviceName + ":power";
1309 }
1310 else
1311 {
1312 units = wxS( "V" );
1313 }
1314
1315 if( func.EndsWith( wxS( "_AT" ) ) )
1316 {
1317 if( plotTab->GetSimType() == ST_AC || plotTab->GetSimType() == ST_SP )
1318 units = wxS( "Hz" );
1319 else
1320 units = wxS( "s" );
1321 }
1322 else if( func.StartsWith( wxS( "INTEG" ) ) )
1323 {
1324 switch( plotTab->GetSimType() )
1325 {
1326 case ST_TRAN:
1327 if ( signalType.StartsWith( 'P' ) )
1328 units = wxS( "J" );
1329 else
1330 units += wxS( ".s" );
1331
1332 break;
1333
1334 case ST_AC:
1335 case ST_SP:
1336 case ST_DISTO:
1337 case ST_NOISE:
1338 case ST_FFT:
1339 case ST_SENS: // If there is a vector, it is frequency
1340 units += wxS( "·Hz" );
1341 break;
1342
1343 case ST_DC: // Could be a lot of things : V, A, deg C, ohm, ...
1344 case ST_OP: // There is no vector for integration
1345 case ST_PZ: // There is no vector for integration
1346 case ST_TF: // There is no vector for integration
1347 default:
1348 units += wxS( "·?" );
1349 break;
1350 }
1351 }
1352
1353 fmt.UpdateUnits( units );
1354 SetMeasureFormat( aRow, fmt );
1355 }
1356
1358 {
1359 wxString cmd = wxString::Format( wxS( "meas %s %s %s" ), simType, resultName, text );
1360 simulator()->Command( "echo " + cmd.ToStdString() );
1361 simulator()->Command( cmd.ToStdString() );
1362
1363 std::vector<double> resultVec = simulator()->GetGainVector( resultName.ToStdString() );
1364
1365 if( resultVec.size() > 0 )
1366 result = SPICE_VALUE( resultVec[0] ).ToString( GetMeasureFormat( aRow ) );
1367 }
1368
1369 m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_VALUE, result );
1370}
1371
1372
1373void SIMULATOR_FRAME_UI::AddTuner( const SCH_SHEET_PATH& aSheetPath, SCH_SYMBOL* aSymbol )
1374{
1375 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1376
1377 if( !plotTab )
1378 return;
1379
1380 wxString ref = aSymbol->GetRef( &aSheetPath );
1381
1382 // Do not add multiple instances for the same component.
1383 for( TUNER_SLIDER* tuner : m_tuners )
1384 {
1385 if( tuner->GetSymbolRef() == ref )
1386 return;
1387 }
1388
1389 const SPICE_ITEM* item = GetExporter()->FindItem( std::string( ref.ToUTF8() ) );
1390
1391 // Do nothing if the symbol is not tunable.
1392 if( !item || !item->model->GetTunerParam() )
1393 return;
1394
1395 try
1396 {
1397 TUNER_SLIDER* tuner = new TUNER_SLIDER( this, m_panelTuners, aSheetPath, aSymbol );
1398 m_sizerTuners->Add( tuner );
1399 m_tuners.push_back( tuner );
1400 m_panelTuners->Layout();
1401 OnModify();
1402 }
1403 catch( const KI_PARAM_ERROR& e )
1404 {
1405 DisplayErrorMessage( nullptr, e.What() );
1406 }
1407}
1408
1409
1410void SIMULATOR_FRAME_UI::UpdateTunerValue( const SCH_SHEET_PATH& aSheetPath, const KIID& aSymbol,
1411 const wxString& aRef, const wxString& aValue )
1412{
1413 SCH_ITEM* item = aSheetPath.GetItem( aSymbol );
1414 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( item );
1415
1416 if( !symbol )
1417 {
1418 DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( " " )
1419 + wxString::Format( _( "%s not found" ), aRef ) );
1420 return;
1421 }
1422
1423 NULL_REPORTER devnull;
1424 SIM_LIB_MGR mgr( &m_schematicFrame->Prj() );
1425 SIM_MODEL& model = mgr.CreateModel( &aSheetPath, *symbol, devnull ).model;
1426
1427 const SIM_MODEL::PARAM* tunerParam = model.GetTunerParam();
1428
1429 if( !tunerParam )
1430 {
1431 DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( " " )
1432 + wxString::Format( _( "%s is not tunable" ), aRef ) );
1433 return;
1434 }
1435
1436 model.SetParamValue( tunerParam->info.name, std::string( aValue.ToUTF8() ) );
1437 model.WriteFields( symbol->GetFields() );
1438
1439 m_schematicFrame->UpdateItem( symbol, false, true );
1441}
1442
1443
1445{
1446 m_tuners.remove( aTuner );
1447 aTuner->Destroy();
1448 m_panelTuners->Layout();
1449 OnModify();
1450}
1451
1452
1453void SIMULATOR_FRAME_UI::AddMeasurement( const wxString& aCmd )
1454{
1455 // -1 because the last one is for user input
1456 for( int i = 0; i < m_measurementsGrid->GetNumberRows(); i++ )
1457 {
1458 if ( m_measurementsGrid->GetCellValue( i, COL_MEASUREMENT ) == aCmd )
1459 return; // Don't create duplicates
1460 }
1461
1462 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1463
1464 if( !plotTab )
1465 return;
1466
1467 wxString simType = simulator()->TypeToName( plotTab->GetSimType(), true );
1468 int row;
1469
1470 for( row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
1471 {
1472 if( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1473 break;
1474 }
1475
1476 if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1477 {
1478 m_measurementsGrid->AppendRows( 1 );
1479 row = m_measurementsGrid->GetNumberRows() - 1;
1480 }
1481
1482 m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT, aCmd );
1483 SetMeasureFormat( row, { 2, wxS( "~V" ) } );
1484
1485 UpdateMeasurement( row );
1486 OnModify();
1487
1488 // Always leave at least one empty row for type-in:
1489 row = m_measurementsGrid->GetNumberRows() - 1;
1490
1491 if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1492 m_measurementsGrid->AppendRows( 1 );
1493}
1494
1495
1496void SIMULATOR_FRAME_UI::DoFourier( const wxString& aSignal, const wxString& aFundamental )
1497{
1498 wxString cmd = wxString::Format( wxS( "fourier %s %s" ),
1499 SPICE_VALUE( aFundamental ).ToSpiceString(),
1500 aSignal );
1501
1502 simulator()->Command( cmd.ToStdString() );
1503}
1504
1505
1507{
1508 return circuitModel().get();
1509}
1510
1511
1512void SIMULATOR_FRAME_UI::AddTrace( const wxString& aName, SIM_TRACE_TYPE aType )
1513{
1514 if( !GetCurrentSimTab() )
1515 {
1516 m_simConsole->AppendText( _( "Error: no current simulation.\n" ) );
1517 m_simConsole->SetInsertionPointEnd();
1518 return;
1519 }
1520
1521 SIM_TYPE simType = SPICE_CIRCUIT_MODEL::CommandToSimType( GetCurrentSimTab()->GetSimCommand() );
1522
1523 if( simType == ST_UNKNOWN )
1524 {
1525 m_simConsole->AppendText( _( "Error: simulation type not defined.\n" ) );
1526 m_simConsole->SetInsertionPointEnd();
1527 return;
1528 }
1529 else if( !SIM_TAB::IsPlottable( simType ) )
1530 {
1531 m_simConsole->AppendText( _( "Error: simulation type doesn't support plotting.\n" ) );
1532 m_simConsole->SetInsertionPointEnd();
1533 return;
1534 }
1535
1536 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1537 wxCHECK( plotTab, /* void */ );
1538
1539 if( simType == ST_AC )
1540 {
1541 updateTrace( aName, aType | SPT_AC_GAIN, plotTab );
1542 updateTrace( aName, aType | SPT_AC_PHASE, plotTab );
1543 }
1544 else if( simType == ST_SP )
1545 {
1546 updateTrace( aName, aType | SPT_AC_GAIN, plotTab );
1547 updateTrace( aName, aType | SPT_AC_PHASE, plotTab );
1548 }
1549 else
1550 {
1551 updateTrace( aName, aType, plotTab );
1552 }
1553
1554 plotTab->GetPlotWin()->UpdateAll();
1555
1557 OnModify();
1558}
1559
1560
1561void SIMULATOR_FRAME_UI::SetUserDefinedSignals( const std::map<int, wxString>& aNewSignals )
1562{
1563 for( size_t ii = 0; ii < m_plotNotebook->GetPageCount(); ++ii )
1564 {
1565 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( m_plotNotebook->GetPage( ii ) );
1566
1567 if( !plotTab )
1568 continue;
1569
1570 for( const auto& [ id, existingSignal ] : m_userDefinedSignals )
1571 {
1572 int traceType = SPT_UNKNOWN;
1573 wxString vectorName = vectorNameFromSignalName( plotTab, existingSignal, &traceType );
1574
1575 if( aNewSignals.count( id ) == 0 )
1576 {
1577 if( plotTab->GetSimType() == ST_AC )
1578 {
1579 for( int subType : { SPT_AC_GAIN, SPT_AC_PHASE } )
1580 plotTab->DeleteTrace( vectorName, traceType | subType );
1581 }
1582 else if( plotTab->GetSimType() == ST_SP )
1583 {
1584 for( int subType : { SPT_SP_AMP, SPT_AC_PHASE } )
1585 plotTab->DeleteTrace( vectorName, traceType | subType );
1586 }
1587 else
1588 {
1589 plotTab->DeleteTrace( vectorName, traceType );
1590 }
1591 }
1592 else
1593 {
1594 if( plotTab->GetSimType() == ST_AC )
1595 {
1596 for( int subType : { SPT_AC_GAIN, SPT_AC_PHASE } )
1597 {
1598 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType | subType ) )
1599 trace->SetName( aNewSignals.at( id ) );
1600 }
1601 }
1602 else if( plotTab->GetSimType() == ST_SP )
1603 {
1604 for( int subType : { SPT_SP_AMP, SPT_AC_PHASE } )
1605 {
1606 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType | subType ) )
1607 trace->SetName( aNewSignals.at( id ) );
1608 }
1609 }
1610 else
1611 {
1612 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
1613 trace->SetName( aNewSignals.at( id ) );
1614 }
1615 }
1616 }
1617 }
1618
1619 m_userDefinedSignals = aNewSignals;
1620
1623
1625 rebuildSignalsGrid( m_filter->GetValue() );
1628 OnModify();
1629}
1630
1631
1632void SIMULATOR_FRAME_UI::updateTrace( const wxString& aVectorName, int aTraceType,
1633 SIM_PLOT_TAB* aPlotTab, std::vector<double>* aDataX,
1634 bool aClearData )
1635{
1637
1638 aTraceType &= aTraceType & SPT_Y_AXIS_MASK;
1639 aTraceType |= getXAxisType( simType );
1640
1641 wxString simVectorName = aVectorName;
1642
1643 if( aTraceType & SPT_POWER )
1644 simVectorName = simVectorName.AfterFirst( '(' ).BeforeLast( ')' ) + wxS( ":power" );
1645
1646 if( !SIM_TAB::IsPlottable( simType ) )
1647 {
1648 // There is no plot to be shown
1649 simulator()->Command( wxString::Format( wxT( "print %s" ), aVectorName ).ToStdString() );
1650
1651 return;
1652 }
1653
1654 std::vector<double> data_x;
1655 std::vector<double> data_y;
1656
1657 if( !aDataX || aClearData )
1658 aDataX = &data_x;
1659
1660 // First, handle the x axis
1661 if( aDataX->empty() && !aClearData )
1662 {
1663 wxString xAxisName( simulator()->GetXAxis( simType ) );
1664
1665 if( xAxisName.IsEmpty() )
1666 return;
1667
1668 *aDataX = simulator()->GetGainVector( (const char*) xAxisName.c_str() );
1669 }
1670
1671 unsigned int size = aDataX->size();
1672
1673 switch( simType )
1674 {
1675 case ST_AC:
1676 if( aTraceType & SPT_AC_GAIN )
1677 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
1678 else if( aTraceType & SPT_AC_PHASE )
1679 data_y = simulator()->GetPhaseVector( (const char*) simVectorName.c_str(), size );
1680 else
1681 wxFAIL_MSG( wxT( "Plot type missing AC_PHASE or AC_MAG bit" ) );
1682
1683 break;
1684 case ST_SP:
1685 if( aTraceType & SPT_SP_AMP )
1686 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
1687 else if( aTraceType & SPT_AC_PHASE )
1688 data_y = simulator()->GetPhaseVector( (const char*) simVectorName.c_str(), size );
1689 else
1690 wxFAIL_MSG( wxT( "Plot type missing AC_PHASE or SPT_SP_AMP bit" ) );
1691
1692 break;
1693
1694 case ST_DC:
1695 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), -1 );
1696 break;
1697
1698 case ST_NOISE:
1699 case ST_TRAN:
1700 case ST_FFT:
1701 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
1702 break;
1703
1704 default:
1705 wxFAIL_MSG( wxT( "Unhandled plot type" ) );
1706 }
1707
1708 // If we did a two-source DC analysis, we need to split the resulting vector and add traces
1709 // for each input step
1710 SPICE_DC_PARAMS source1, source2;
1711
1712 if( simType == ST_DC
1713 && circuitModel()->ParseDCCommand( aPlotTab->GetSimCommand(), &source1, &source2 )
1714 && !source2.m_source.IsEmpty() )
1715 {
1716 // Source 1 is the inner loop, so lets add traces for each Source 2 (outer loop) step
1717 SPICE_VALUE v = source2.m_vstart;
1718
1719 size_t offset = 0;
1720 size_t outer = ( size_t )( ( source2.m_vend - v ) / source2.m_vincrement ).ToDouble();
1721 size_t inner = aDataX->size() / ( outer + 1 );
1722
1723 wxASSERT( aDataX->size() % ( outer + 1 ) == 0 );
1724
1725 for( size_t idx = 0; idx <= outer; idx++ )
1726 {
1727 if( TRACE* trace = aPlotTab->GetOrAddTrace( aVectorName, aTraceType ) )
1728 {
1729 if( data_y.size() >= size )
1730 {
1731 std::vector<double> sub_x( aDataX->begin() + offset,
1732 aDataX->begin() + offset + inner );
1733 std::vector<double> sub_y( data_y.begin() + offset,
1734 data_y.begin() + offset + inner );
1735
1736 aPlotTab->SetTraceData( trace, sub_x, sub_y );
1737 }
1738 }
1739
1740 v = v + source2.m_vincrement;
1741 offset += inner;
1742 }
1743 }
1744 else if( TRACE* trace = aPlotTab->GetOrAddTrace( aVectorName, aTraceType ) )
1745 {
1746 if( data_y.size() >= size )
1747 aPlotTab->SetTraceData( trace, *aDataX, data_y );
1748 }
1749}
1750
1751
1753{
1754 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1755
1756 for( int row = 0; row < m_signalsGrid->GetNumberRows(); ++row )
1757 {
1758 wxString signalName = m_signalsGrid->GetCellValue( row, COL_SIGNAL_NAME );
1759 int traceType = SPT_UNKNOWN;
1760 wxString vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
1761
1762 if( TRACE* trace = plotTab ? plotTab->GetTrace( vectorName, traceType ) : nullptr )
1763 {
1764 m_signalsGrid->SetCellValue( row, COL_SIGNAL_SHOW, wxS( "1" ) );
1765
1766 wxGridCellAttr* attr = new wxGridCellAttr;
1767 attr->SetRenderer( new GRID_CELL_COLOR_RENDERER( this ) );
1768 attr->SetEditor( new GRID_CELL_COLOR_SELECTOR( this, m_signalsGrid ) );
1769 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
1770 m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
1771
1772 KIGFX::COLOR4D color( trace->GetPen().GetColour() );
1773 m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, color.ToCSSString() );
1774
1775 attr = new wxGridCellAttr;
1776 attr->SetRenderer( new wxGridCellBoolRenderer() );
1777 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
1778 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
1779 m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
1780
1781 attr = new wxGridCellAttr;
1782 attr->SetRenderer( new wxGridCellBoolRenderer() );
1783 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
1784 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
1785 m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
1786
1787 if( trace->HasCursor( 1 ) )
1788 m_signalsGrid->SetCellValue( row, COL_CURSOR_1, wxS( "1" ) );
1789 else
1790 m_signalsGrid->SetCellValue( row, COL_CURSOR_1, wxEmptyString );
1791
1792 if( trace->HasCursor( 2 ) )
1793 m_signalsGrid->SetCellValue( row, COL_CURSOR_2, wxS( "1" ) );
1794 else
1795 m_signalsGrid->SetCellValue( row, COL_CURSOR_2, wxEmptyString );
1796 }
1797 else
1798 {
1799 m_signalsGrid->SetCellValue( row, COL_SIGNAL_SHOW, wxEmptyString );
1800
1801 wxGridCellAttr* attr = new wxGridCellAttr;
1802 attr->SetReadOnly();
1803 m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
1804 m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, wxEmptyString );
1805
1806 attr = new wxGridCellAttr;
1807 attr->SetReadOnly();
1808 m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
1809 m_signalsGrid->SetCellValue( row, COL_CURSOR_1, wxEmptyString );
1810
1811 attr = new wxGridCellAttr;
1812 attr->SetReadOnly();
1813 m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
1814 m_signalsGrid->SetCellValue( row, COL_CURSOR_2, wxEmptyString );
1815 }
1816 }
1817}
1818
1819
1821{
1822 auto quoteNetNames =
1823 [&]( wxString aExpression ) -> wxString
1824 {
1825 for( const auto& [netname, quotedNetname] : m_quotedNetnames )
1826 aExpression.Replace( netname, quotedNetname );
1827
1828 return aExpression;
1829 };
1830
1831 for( const auto& [ id, signal ] : m_userDefinedSignals )
1832 {
1833 std::string cmd = "let user{} = {}";
1834
1835 simulator()->Command( "echo " + fmt::format( cmd, id, signal.ToStdString() ) );
1836 simulator()->Command( fmt::format( cmd, id, quoteNetNames( signal ).ToStdString() ) );
1837 }
1838}
1839
1840
1842{
1843 wxString errors;
1844 WX_STRING_REPORTER reporter( &errors );
1845
1846 for( const TUNER_SLIDER* tuner : m_tuners )
1847 {
1848 SCH_SHEET_PATH sheetPath;
1849 wxString ref = tuner->GetSymbolRef();
1850 KIID symbolId = tuner->GetSymbol( &sheetPath );
1851 SCH_ITEM* schItem = sheetPath.GetItem( symbolId );
1852 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( schItem );
1853
1854 if( !symbol )
1855 {
1856 reporter.Report( wxString::Format( _( "%s not found" ), ref ) );
1857 continue;
1858 }
1859
1860 const SPICE_ITEM* item = GetExporter()->FindItem( tuner->GetSymbolRef().ToStdString() );
1861
1862 if( !item || !item->model->GetTunerParam() )
1863 {
1864 reporter.Report( wxString::Format( _( "%s is not tunable" ), ref ) );
1865 continue;
1866 }
1867
1868 double floatVal = tuner->GetValue().ToDouble();
1869
1870 simulator()->Command( item->model->SpiceGenerator().TunerCommand( *item, floatVal ) );
1871 }
1872
1873 if( reporter.HasMessage() )
1874 DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( "\n" ) + errors );
1875}
1876
1877
1878bool SIMULATOR_FRAME_UI::LoadWorkbook( const wxString& aPath )
1879{
1880 wxTextFile file( aPath );
1881
1882 if( !file.Open() )
1883 return false;
1884
1885 wxString firstLine = file.GetFirstLine();
1886 long dummy;
1887 bool legacy = firstLine.StartsWith( wxT( "version " ) ) || firstLine.ToLong( &dummy );
1888
1889 file.Close();
1890
1891 m_plotNotebook->DeleteAllPages();
1892 m_userDefinedSignals.clear();
1893
1894 if( legacy )
1895 {
1896 if( !loadLegacyWorkbook( aPath ) )
1897 return false;
1898 }
1899 else
1900 {
1901 if( !loadJsonWorkbook( aPath ) )
1902 return false;
1903 }
1904
1906
1907 rebuildSignalsGrid( m_filter->GetValue() );
1911
1912 wxFileName filename( aPath );
1913 filename.MakeRelativeTo( m_schematicFrame->Prj().GetProjectPath() );
1914
1915 // Remember the loaded workbook filename.
1916 simulator()->Settings()->SetWorkbookFilename( filename.GetFullPath() );
1917
1918 return true;
1919}
1920
1921
1922bool SIMULATOR_FRAME_UI::loadJsonWorkbook( const wxString& aPath )
1923{
1924 wxFFileInputStream fp( aPath, wxT( "rt" ) );
1925 wxStdInputStream fstream( fp );
1926
1927 if( !fp.IsOk() )
1928 return false;
1929
1930 try
1931 {
1932 nlohmann::json js = nlohmann::json::parse( fstream, nullptr, true, true );
1933
1934 std::map<SIM_PLOT_TAB*, nlohmann::json> traceInfo;
1935
1936 for( const nlohmann::json& tab_js : js[ "tabs" ] )
1937 {
1938 wxString simCommand;
1940
1941 for( const nlohmann::json& cmd : tab_js[ "commands" ] )
1942 {
1943 if( cmd == ".kicad adjustpaths" )
1945 else if( cmd == ".save all" )
1947 else if( cmd == ".probe alli" )
1949 else if( cmd == ".probe allp" )
1951 else
1952 simCommand += wxString( cmd.get<wxString>() ).Trim();
1953 }
1954
1955 SIM_TAB* simTab = NewSimTab( simCommand );
1956 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab );
1957
1958 simTab->SetSimOptions( simOptions );
1959
1960 if( plotTab )
1961 {
1962 if( tab_js.contains( "traces" ) )
1963 traceInfo[plotTab] = tab_js[ "traces" ];
1964
1965 if( tab_js.contains( "measurements" ) )
1966 {
1967 for( const nlohmann::json& m_js : tab_js[ "measurements" ] )
1968 plotTab->Measurements().emplace_back( m_js[ "expr" ], m_js[ "format" ] );
1969 }
1970
1971 plotTab->SetDottedSecondary( tab_js[ "dottedSecondary" ] );
1972 plotTab->ShowGrid( tab_js[ "showGrid" ] );
1973
1974 if( tab_js.contains( "fixedY1scale" ) )
1975 {
1976 const nlohmann::json& scale_js = tab_js[ "fixedY1scale" ];
1977 plotTab->SetY1Scale( true, scale_js[ "min" ], scale_js[ "max" ] );
1978 plotTab->GetPlotWin()->LockY( true );
1979 }
1980
1981 if( tab_js.contains( "fixedY2scale" ) )
1982 {
1983 const nlohmann::json& scale_js = tab_js[ "fixedY2scale" ];
1984 plotTab->SetY2Scale( true, scale_js[ "min" ], scale_js[ "max" ] );
1985 plotTab->GetPlotWin()->LockY( true );
1986 }
1987
1988 if( tab_js.contains( "fixedY3scale" ) )
1989 {
1990 const nlohmann::json& scale_js = tab_js[ "fixedY3scale" ];
1991 plotTab->SetY3Scale( true, scale_js[ "min" ], scale_js[ "max" ] );
1992 plotTab->GetPlotWin()->LockY( true );
1993 }
1994
1995 if( tab_js.contains( "legend" ) )
1996 {
1997 const nlohmann::json& legend_js = tab_js[ "legend" ];
1998 plotTab->SetLegendPosition( wxPoint( legend_js[ "x" ], legend_js[ "y" ] ) );
1999 plotTab->ShowLegend( true );
2000 }
2001
2002 if( tab_js.contains( "margins" ) )
2003 {
2004 const nlohmann::json& margins_js = tab_js[ "margins" ];
2005 plotTab->GetPlotWin()->SetMargins( margins_js[ "top" ],
2006 margins_js[ "right" ],
2007 margins_js[ "bottom" ],
2008 margins_js[ "left" ] );
2009 }
2010 }
2011 }
2012
2013 int ii = 0;
2014
2015 if( js.contains( "user_defined_signals" ) )
2016 {
2017 for( const nlohmann::json& signal_js : js[ "user_defined_signals" ] )
2018 m_userDefinedSignals[ii++] = wxString( signal_js.get<wxString>() );
2019 }
2020
2021 auto addCursor =
2022 [this]( SIM_PLOT_TAB* aPlotTab, TRACE* aTrace, const wxString& aSignalName,
2023 int aCursorId, const nlohmann::json& aCursor_js )
2024 {
2025 if( aCursorId == 1 || aCursorId == 2 )
2026 {
2027 CURSOR* cursor = new CURSOR( aTrace, aPlotTab );
2028
2029 cursor->SetName( aSignalName );
2030 cursor->SetPen( wxPen( aTrace->GetTraceColour() ) );
2031 cursor->SetCoordX( aCursor_js[ "position" ] );
2032
2033 aTrace->SetCursor( aCursorId, cursor );
2034 aPlotTab->GetPlotWin()->AddLayer( cursor );
2035 }
2036
2037 m_cursorFormats[aCursorId-1][0].FromString( aCursor_js[ "x_format" ] );
2038 m_cursorFormats[aCursorId-1][1].FromString( aCursor_js[ "y_format" ] );
2039 };
2040
2041 for( const auto& [ plotTab, traces_js ] : traceInfo )
2042 {
2043 for( const nlohmann::json& trace_js : traces_js )
2044 {
2045 wxString signalName = trace_js[ "signal" ];
2046 wxString vectorName = vectorNameFromSignalName( plotTab, signalName, nullptr );
2047 TRACE* trace = plotTab->GetOrAddTrace( vectorName, trace_js[ "trace_type" ] );
2048
2049 if( trace )
2050 {
2051 if( trace_js.contains( "cursor1" ) )
2052 addCursor( plotTab, trace, signalName, 1, trace_js[ "cursor1" ] );
2053
2054 if( trace_js.contains( "cursor2" ) )
2055 addCursor( plotTab, trace, signalName, 2, trace_js[ "cursor2" ] );
2056
2057 if( trace_js.contains( "cursorD" ) )
2058 addCursor( plotTab, trace, signalName, 3, trace_js[ "cursorD" ] );
2059
2060 if( trace_js.contains( "color" ) )
2061 {
2062 wxColour color;
2063 color.Set( wxString( trace_js["color"].get<wxString>() ) );
2064 trace->SetTraceColour( color );
2065 plotTab->UpdateTraceStyle( trace );
2066 }
2067 }
2068 }
2069
2070 plotTab->UpdatePlotColors();
2071 }
2072
2073 if( SIM_TAB* simTab = GetCurrentSimTab() )
2074 {
2075 m_simulatorFrame->LoadSimulator( simTab->GetSimCommand(), simTab->GetSimOptions() );
2076
2077 simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( 0 ) );
2078
2079 wxCHECK( simTab, false );
2080
2081 simTab->SetLastSchTextSimCommand( js[ "last_sch_text_sim_command" ] );
2082 }
2083 }
2084 catch( nlohmann::json::parse_error& error )
2085 {
2086 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ), aPath, error.what() );
2087
2088 return false;
2089 }
2090
2091 return true;
2092}
2093
2094
2095bool SIMULATOR_FRAME_UI::SaveWorkbook( const wxString& aPath )
2096{
2098
2099 wxFileName filename = aPath;
2100 filename.SetExt( WorkbookFileExtension );
2101
2102 wxFile file;
2103
2104 file.Create( filename.GetFullPath(), true /* overwrite */ );
2105
2106 if( !file.IsOpened() )
2107 return false;
2108
2109 nlohmann::json tabs_js = nlohmann::json::array();
2110
2111 for( size_t i = 0; i < m_plotNotebook->GetPageCount(); i++ )
2112 {
2113 SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( i ) );
2114
2115 if( !simTab )
2116 continue;
2117
2118 SIM_TYPE simType = simTab->GetSimType();
2119
2120 nlohmann::json commands_js = nlohmann::json::array();
2121
2122 commands_js.push_back( simTab->GetSimCommand() );
2123
2124 int options = simTab->GetSimOptions();
2125
2127 commands_js.push_back( ".kicad adjustpaths" );
2128
2130 commands_js.push_back( ".save all" );
2131
2133 commands_js.push_back( ".probe alli" );
2134
2136 commands_js.push_back( ".probe allp" );
2137
2138 nlohmann::json tab_js = nlohmann::json(
2139 { { "analysis", SPICE_SIMULATOR::TypeToName( simType, true ) },
2140 { "commands", commands_js } } );
2141
2142 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab ) )
2143 {
2144 nlohmann::json traces_js = nlohmann::json::array();
2145
2146 auto findSignalName =
2147 [&]( const wxString& aVectorName ) -> wxString
2148 {
2149 for( const auto& [ id, signal ] : m_userDefinedSignals )
2150 {
2151 if( aVectorName == vectorNameFromSignalId( id ) )
2152 return signal;
2153 }
2154
2155 return aVectorName;
2156 };
2157
2158 for( const auto& [name, trace] : plotTab->GetTraces() )
2159 {
2160 nlohmann::json trace_js = nlohmann::json(
2161 { { "trace_type", (int) trace->GetType() },
2162 { "signal", findSignalName( trace->GetName() ) },
2163 { "color", COLOR4D( trace->GetTraceColour() ).ToCSSString() } } );
2164
2165 if( CURSOR* cursor = trace->GetCursor( 1 ) )
2166 {
2167 trace_js["cursor1"] = nlohmann::json(
2168 { { "position", cursor->GetCoords().x },
2169 { "x_format", m_cursorFormats[0][0].ToString() },
2170 { "y_format", m_cursorFormats[0][1].ToString() } } );
2171 }
2172
2173 if( CURSOR* cursor = trace->GetCursor( 2 ) )
2174 {
2175 trace_js["cursor2"] = nlohmann::json(
2176 { { "position", cursor->GetCoords().x },
2177 { "x_format", m_cursorFormats[1][0].ToString() },
2178 { "y_format", m_cursorFormats[1][1].ToString() } } );
2179 }
2180
2181 if( trace->GetCursor( 1 ) || trace->GetCursor( 2 ) )
2182 {
2183 trace_js["cursorD"] = nlohmann::json(
2184 { { "x_format", m_cursorFormats[2][0].ToString() },
2185 { "y_format", m_cursorFormats[2][1].ToString() } } );
2186 }
2187
2188 traces_js.push_back( trace_js );
2189 }
2190
2191 nlohmann::json measurements_js = nlohmann::json::array();
2192
2193 for( const auto& [ measurement, format ] : plotTab->Measurements() )
2194 {
2195 measurements_js.push_back( nlohmann::json( { { "expr", measurement },
2196 { "format", format } } ) );
2197 }
2198
2199 tab_js[ "traces" ] = traces_js;
2200 tab_js[ "measurements" ] = measurements_js;
2201 tab_js[ "dottedSecondary" ] = plotTab->GetDottedSecondary();
2202 tab_js[ "showGrid" ] = plotTab->IsGridShown();
2203
2204 double min, max;
2205
2206 if( plotTab->GetY1Scale( &min, &max ) )
2207 tab_js[ "fixedY1scale" ] = nlohmann::json( { { "min", min }, { "max", max } } );
2208
2209 if( plotTab->GetY2Scale( &min, &max ) )
2210 tab_js[ "fixedY2scale" ] = nlohmann::json( { { "min", min }, { "max", max } } );
2211
2212 if( plotTab->GetY3Scale( &min, &max ) )
2213 tab_js[ "fixedY3scale" ] = nlohmann::json( { { "min", min }, { "max", max } } );
2214
2215 if( plotTab->IsLegendShown() )
2216 {
2217 tab_js[ "legend" ] = nlohmann::json( { { "x", plotTab->GetLegendPosition().x },
2218 { "y", plotTab->GetLegendPosition().y } } );
2219 }
2220
2221 mpWindow* plotWin = plotTab->GetPlotWin();
2222
2223 tab_js[ "margins" ] = nlohmann::json( { { "left", plotWin->GetMarginLeft() },
2224 { "right", plotWin->GetMarginRight() },
2225 { "top", plotWin->GetMarginTop() },
2226 { "bottom", plotWin->GetMarginBottom() } } );
2227 }
2228
2229 tabs_js.push_back( tab_js );
2230 }
2231
2232 nlohmann::json userDefinedSignals_js = nlohmann::json::array();
2233
2234 for( const auto& [ id, signal ] : m_userDefinedSignals )
2235 userDefinedSignals_js.push_back( signal );
2236
2237 nlohmann::json js = nlohmann::json( { { "version", 6 },
2238 { "tabs", tabs_js },
2239 { "user_defined_signals", userDefinedSignals_js } } );
2240
2241 // Store the value of any simulation command found on the schematic sheet in a SCH_TEXT
2242 // object. If this changes we want to warn the user and ask them if they want to update
2243 // the corresponding panel's sim command.
2244 if( m_plotNotebook->GetPageCount() > 0 )
2245 {
2246 SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( 0 ) );
2247 js[ "last_sch_text_sim_command" ] = simTab->GetLastSchTextSimCommand();
2248 }
2249
2250 std::stringstream buffer;
2251 buffer << std::setw( 2 ) << js << std::endl;
2252
2253 bool res = file.Write( buffer.str() );
2254 file.Close();
2255
2256 // Store the filename of the last saved workbook.
2257 if( res )
2258 {
2259 filename.MakeRelativeTo( m_schematicFrame->Prj().GetProjectPath() );
2260 simulator()->Settings()->SetWorkbookFilename( filename.GetFullPath() );
2261 }
2262
2263 return res;
2264}
2265
2266
2268{
2269 switch( aType )
2270 {
2272 case ST_AC: return SPT_LIN_FREQUENCY;
2273 case ST_SP: return SPT_LIN_FREQUENCY;
2274 case ST_FFT: return SPT_LIN_FREQUENCY;
2275 case ST_DC: return SPT_SWEEP;
2276 case ST_TRAN: return SPT_TIME;
2277 case ST_NOISE: return SPT_LIN_FREQUENCY;
2278 default: wxFAIL_MSG( wxS( "Unhandled simulation type" ) ); return SPT_UNKNOWN;
2279 }
2280}
2281
2282
2284{
2285 wxString output;
2286 wxString ref;
2287 wxString source;
2288 wxString scale;
2289 SPICE_VALUE pts;
2290 SPICE_VALUE fStart;
2291 SPICE_VALUE fStop;
2292 bool saveAll;
2293
2294 if( GetCurrentSimTab() )
2295 {
2296 circuitModel()->ParseNoiseCommand( GetCurrentSimTab()->GetSimCommand(), &output, &ref,
2297 &source, &scale, &pts, &fStart, &fStop, &saveAll );
2298 }
2299
2300 return source;
2301}
2302
2303
2305{
2307
2308 // Rebuild the color list to plot traces
2310
2311 // Now send changes to all SIM_PLOT_TAB
2312 for( size_t page = 0; page < m_plotNotebook->GetPageCount(); page++ )
2313 {
2314 wxWindow* curPage = m_plotNotebook->GetPage( page );
2315
2316 // ensure it is truly a plot plotTab and not the (zero plots) placeholder
2317 // which is only SIM_TAB
2318 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( curPage );
2319
2320 if( plotTab )
2321 plotTab->UpdatePlotColors();
2322 }
2323}
2324
2325
2326void SIMULATOR_FRAME_UI::onPlotClose( wxAuiNotebookEvent& event )
2327{
2328 OnModify();
2329}
2330
2331
2332void SIMULATOR_FRAME_UI::onPlotClosed( wxAuiNotebookEvent& event )
2333{
2334 CallAfter( [this]()
2335 {
2337 rebuildSignalsGrid( m_filter->GetValue() );
2339
2340 SIM_TAB* panel = GetCurrentSimTab();
2341
2342 if( !panel || panel->GetSimType() != ST_OP )
2343 {
2344 SCHEMATIC& schematic = m_schematicFrame->Schematic();
2345 schematic.ClearOperatingPoints();
2348 }
2349 } );
2350}
2351
2352
2354{
2355 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
2356 {
2357 std::vector<std::pair<wxString, wxString>>& measurements = plotTab->Measurements();
2358
2359 measurements.clear();
2360
2361 for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
2362 {
2363 if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
2364 {
2365 measurements.emplace_back( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ),
2366 m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT_FORMAT ) );
2367 }
2368 }
2369 }
2370}
2371
2372
2373void SIMULATOR_FRAME_UI::onPlotChanging( wxAuiNotebookEvent& event )
2374{
2376
2377 event.Skip();
2378}
2379
2380
2382{
2384 rebuildSignalsGrid( m_filter->GetValue() );
2386
2388
2389 for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
2390 UpdateMeasurement( row );
2391}
2392
2393
2394void SIMULATOR_FRAME_UI::onPlotChanged( wxAuiNotebookEvent& event )
2395{
2396 if( SIM_TAB* simTab = GetCurrentSimTab() )
2397 simulator()->Command( "setplot " + simTab->GetSpicePlotName().ToStdString() );
2398
2400
2401 event.Skip();
2402}
2403
2404
2406{
2408
2409 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
2410 {
2411 for( const auto& [ measurement, format ] : plotTab->Measurements() )
2412 {
2413 int row = m_measurementsGrid->GetNumberRows();
2414 m_measurementsGrid->AppendRows();
2415 m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT, measurement );
2416 m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT_FORMAT, format );
2417 }
2418
2419 if( plotTab->GetSimType() == ST_TRAN || plotTab->GetSimType() == ST_AC
2420 || plotTab->GetSimType() == ST_DC || plotTab->GetSimType() == ST_SP )
2421 {
2422 m_measurementsGrid->AppendRows(); // Empty row at end
2423 }
2424 }
2425}
2426
2427
2428void SIMULATOR_FRAME_UI::onPlotDragged( wxAuiNotebookEvent& event )
2429{
2430}
2431
2432
2433std::shared_ptr<SPICE_SIMULATOR> SIMULATOR_FRAME_UI::simulator() const
2434{
2436}
2437
2438
2439std::shared_ptr<SPICE_CIRCUIT_MODEL> SIMULATOR_FRAME_UI::circuitModel() const
2440{
2442}
2443
2444
2446{
2447 SUPPRESS_GRID_CELL_EVENTS raii( this );
2448
2450
2451 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
2452
2453 if( !plotTab )
2454 return;
2455
2456 // Update cursor values
2457 CURSOR* cursor1 = nullptr;
2458 wxString cursor1Name;
2459 wxString cursor1Units;
2460 CURSOR* cursor2 = nullptr;
2461 wxString cursor2Name;
2462 wxString cursor2Units;
2463
2464 auto getUnitsY =
2465 [&]( TRACE* aTrace ) -> wxString
2466 {
2467 if( ( aTrace->GetType() & SPT_AC_PHASE ) || ( aTrace->GetType() & SPT_CURRENT ) )
2468 return plotTab->GetUnitsY2();
2469 else if( aTrace->GetType() & SPT_POWER )
2470 return plotTab->GetUnitsY3();
2471 else
2472 return plotTab->GetUnitsY1();
2473 };
2474
2475 auto getNameY =
2476 [&]( TRACE* aTrace ) -> wxString
2477 {
2478 if( ( aTrace->GetType() & SPT_AC_PHASE ) || ( aTrace->GetType() & SPT_CURRENT ) )
2479 return plotTab->GetLabelY2();
2480 else if( aTrace->GetType() & SPT_POWER )
2481 return plotTab->GetLabelY3();
2482 else
2483 return plotTab->GetLabelY1();
2484 };
2485
2486 auto formatValue =
2487 [this]( double aValue, int aCursorId, int aCol ) -> wxString
2488 {
2489 if( ( !m_simulatorFrame->SimFinished() && aCol == 1 ) || std::isnan( aValue ) )
2490 return wxS( "--" );
2491 else
2492 return SPICE_VALUE( aValue ).ToString( m_cursorFormats[ aCursorId ][ aCol ] );
2493 };
2494
2495 for( const auto& [name, trace] : plotTab->GetTraces() )
2496 {
2497 if( CURSOR* cursor = trace->GetCursor( 1 ) )
2498 {
2499 cursor1 = cursor;
2500 cursor1Name = getNameY( trace );
2501 cursor1Units = getUnitsY( trace );
2502
2503 wxRealPoint coords = cursor->GetCoords();
2504 int row = m_cursorsGrid->GetNumberRows();
2505
2506 m_cursorFormats[0][0].UpdateUnits( plotTab->GetUnitsX() );
2507 m_cursorFormats[0][1].UpdateUnits( cursor1Units );
2508
2509 m_cursorsGrid->AppendRows( 1 );
2510 m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "1" ) );
2511 m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, cursor->GetName() );
2512 m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, 0, 0 ) );
2513 m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, 0, 1 ) );
2514 break;
2515 }
2516 }
2517
2518 for( const auto& [name, trace] : plotTab->GetTraces() )
2519 {
2520 if( CURSOR* cursor = trace->GetCursor( 2 ) )
2521 {
2522 cursor2 = cursor;
2523 cursor2Name = getNameY( trace );
2524 cursor2Units = getUnitsY( trace );
2525
2526 wxRealPoint coords = cursor->GetCoords();
2527 int row = m_cursorsGrid->GetNumberRows();
2528
2529 m_cursorFormats[1][0].UpdateUnits( plotTab->GetUnitsX() );
2530 m_cursorFormats[1][1].UpdateUnits( cursor2Units );
2531
2532 m_cursorsGrid->AppendRows( 1 );
2533 m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "2" ) );
2534 m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, cursor->GetName() );
2535 m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, 1, 0 ) );
2536 m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, 1, 1 ) );
2537 break;
2538 }
2539 }
2540
2541 if( cursor1 && cursor2 && cursor1Units == cursor2Units )
2542 {
2543 wxRealPoint coords = cursor2->GetCoords() - cursor1->GetCoords();
2544 wxString signal;
2545
2546 m_cursorFormats[2][0].UpdateUnits( plotTab->GetUnitsX() );
2547 m_cursorFormats[2][1].UpdateUnits( cursor1Units );
2548
2549 if( cursor1->GetName() == cursor2->GetName() )
2550 signal = wxString::Format( wxS( "%s[2 - 1]" ), cursor2->GetName() );
2551 else
2552 signal = wxString::Format( wxS( "%s - %s" ), cursor2->GetName(), cursor1->GetName() );
2553
2554 m_cursorsGrid->AppendRows( 1 );
2555 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_NAME, _( "Diff" ) );
2556 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_SIGNAL, signal );
2557 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_X, formatValue( coords.x, 2, 0 ) );
2558 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_Y, formatValue( coords.y, 2, 1 ) );
2559 }
2560
2561 // Set up the labels
2562 m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, plotTab->GetLabelX() );
2563
2564 wxString valColName = _( "Value" );
2565
2566 if( !cursor1Name.IsEmpty() )
2567 {
2568 if( cursor2Name.IsEmpty() || cursor1Name == cursor2Name )
2569 valColName = cursor1Name;
2570 }
2571 else if( !cursor2Name.IsEmpty() )
2572 {
2573 valColName = cursor2Name;
2574 }
2575
2576 m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, valColName );
2577}
2578
2579
2580void SIMULATOR_FRAME_UI::onPlotCursorUpdate( wxCommandEvent& aEvent )
2581{
2583 OnModify();
2584}
2585
2586
2588{
2589 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
2590 plotTab->ResetScales( true );
2591
2592 m_simConsole->Clear();
2593
2594 // Do not export netlist, it is already stored in the simulator
2595 applyTuners();
2596
2597 m_refreshTimer.Start( REFRESH_INTERVAL, wxTIMER_ONE_SHOT );
2598}
2599
2600
2601void SIMULATOR_FRAME_UI::OnSimReport( const wxString& aMsg )
2602{
2603 m_simConsole->AppendText( aMsg + "\n" );
2604 m_simConsole->SetInsertionPointEnd();
2605}
2606
2607
2608std::vector<wxString> SIMULATOR_FRAME_UI::SimPlotVectors() const
2609{
2610 std::vector<wxString> signals;
2611
2612 for( const std::string& vec : simulator()->AllVectors() )
2613 signals.emplace_back( vec );
2614
2615 return signals;
2616}
2617
2618
2619std::vector<wxString> SIMULATOR_FRAME_UI::Signals() const
2620{
2621 std::vector<wxString> signals;
2622
2623 for( const wxString& signal : m_signals )
2624 signals.emplace_back( signal );
2625
2626 for( const auto& [ id, signal ] : m_userDefinedSignals )
2627 signals.emplace_back( signal );
2628
2629 sortSignals( signals );
2630
2631 return signals;
2632}
2633
2634
2636{
2637 if( aFinal )
2638 m_refreshTimer.Stop();
2639
2640 SIM_TAB* simTab = GetCurrentSimTab();
2641
2642 if( !simTab )
2643 return;
2644
2645 SIM_TYPE simType = simTab->GetSimType();
2646 wxString msg;
2647
2648 if( aFinal )
2650
2651 // If there are any signals plotted, update them
2652 if( SIM_TAB::IsPlottable( simType ) )
2653 {
2654 simTab->SetSpicePlotName( simulator()->CurrentPlotName() );
2655
2656 if( simType == ST_NOISE && aFinal )
2657 {
2658 m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
2659 m_simConsole->SetInsertionPointEnd();
2660
2661 // The simulator will create noise1 & noise2 on the first run, noise3 and noise4
2662 // on the second, etc. The first plot for each run contains the spectral density
2663 // noise vectors and second contains the integrated noise.
2664 long number;
2665 simulator()->CurrentPlotName().Mid( 5 ).ToLong( &number );
2666
2667 for( const std::string& vec : simulator()->AllVectors() )
2668 {
2669 std::vector<double> val_list = simulator()->GetRealVector( vec, 1 );
2670 wxString value = SPICE_VALUE( val_list[ 0 ] ).ToSpiceString();
2671
2672 msg.Printf( wxS( "%s: %sV\n" ), vec, value );
2673
2674 m_simConsole->AppendText( msg );
2675 m_simConsole->SetInsertionPointEnd();
2676 }
2677
2678 simulator()->Command( fmt::format( "setplot noise{}", number - 1 ) );
2679 simTab->SetSpicePlotName( simulator()->CurrentPlotName() );
2680 }
2681
2682 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab );
2683 wxCHECK_RET( plotTab, wxT( "not a SIM_PLOT_TAB" ) );
2684
2685 struct TRACE_INFO
2686 {
2687 wxString Vector;
2688 int TraceType;
2689 bool ClearData;
2690 };
2691
2692 std::map<TRACE*, TRACE_INFO> traceMap;
2693
2694 for( const auto& [ name, trace ] : plotTab->GetTraces() )
2695 traceMap[ trace ] = { wxEmptyString, SPT_UNKNOWN, false };
2696
2697 // NB: m_signals are already broken out into gain/phase, but m_userDefinedSignals are
2698 // as the user typed them
2699
2700 for( const wxString& signal : m_signals )
2701 {
2702 int traceType = SPT_UNKNOWN;
2703 wxString vectorName = vectorNameFromSignalName( plotTab, signal, &traceType );
2704
2705 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
2706 traceMap[ trace ] = { vectorName, traceType, false };
2707 }
2708
2709 for( const auto& [ id, signal ] : m_userDefinedSignals )
2710 {
2711 int traceType = SPT_UNKNOWN;
2712 wxString vectorName = vectorNameFromSignalName( plotTab, signal, &traceType );
2713
2714 if( simType == ST_AC )
2715 {
2716 int baseType = traceType &= ~( SPT_AC_GAIN | SPT_AC_PHASE );
2717
2718 for( int subType : { baseType | SPT_AC_GAIN, baseType | SPT_AC_PHASE } )
2719 {
2720 if( TRACE* trace = plotTab->GetTrace( vectorName, subType ) )
2721 traceMap[ trace ] = { vectorName, subType, !aFinal };
2722 }
2723 }
2724 else if( simType == ST_SP )
2725 {
2726 int baseType = traceType &= ~( SPT_SP_AMP | SPT_AC_PHASE );
2727
2728 for( int subType : { baseType | SPT_SP_AMP, baseType | SPT_AC_PHASE } )
2729 {
2730 if( TRACE* trace = plotTab->GetTrace( vectorName, subType ) )
2731 traceMap[trace] = { vectorName, subType, !aFinal };
2732 }
2733 }
2734 else
2735 {
2736 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
2737 traceMap[ trace ] = { vectorName, traceType, !aFinal };
2738 }
2739 }
2740
2741 // Two passes so that DC-sweep sub-traces get deleted and re-created:
2742
2743 for( const auto& [ trace, traceInfo ] : traceMap )
2744 {
2745 if( traceInfo.Vector.IsEmpty() )
2746 plotTab->DeleteTrace( trace );
2747 }
2748
2749 for( const auto& [ trace, info ] : traceMap )
2750 {
2751 std::vector<double> data_x;
2752
2753 if( !info.Vector.IsEmpty() )
2754 updateTrace( info.Vector, info.TraceType, plotTab, &data_x, info.ClearData );
2755 }
2756
2757 plotTab->GetPlotWin()->UpdateAll();
2758
2759 if( aFinal )
2760 {
2761 for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
2762 UpdateMeasurement( row );
2763
2764 plotTab->ResetScales( true );
2765 }
2766
2767 plotTab->GetPlotWin()->Fit();
2768
2770 }
2771 else if( simType == ST_OP && aFinal )
2772 {
2773 m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
2774 m_simConsole->SetInsertionPointEnd();
2775
2776 for( const std::string& vec : simulator()->AllVectors() )
2777 {
2778 std::vector<double> val_list = simulator()->GetRealVector( vec, 1 );
2779 wxString value = SPICE_VALUE( val_list[ 0 ] ).ToSpiceString();
2780 wxString signal;
2781 SIM_TRACE_TYPE type = circuitModel()->VectorToSignal( vec, signal );
2782
2783 const size_t tab = 25; //characters
2784 size_t padding = ( signal.length() < tab ) ? ( tab - signal.length() ) : 1;
2785
2786 switch( type )
2787 {
2788 case SPT_VOLTAGE: value.Append( wxS( "V" ) ); break;
2789 case SPT_CURRENT: value.Append( wxS( "A" ) ); break;
2790 case SPT_POWER: value.Append( wxS( "W" ) ); break;
2791 default: value.Append( wxS( "?" ) ); break;
2792 }
2793
2794 msg.Printf( wxT( "%s%s\n" ),
2795 ( signal + wxT( ":" ) ).Pad( padding, wxUniChar( ' ' ) ),
2796 value );
2797
2798 m_simConsole->AppendText( msg );
2799 m_simConsole->SetInsertionPointEnd();
2800
2801 if( signal.StartsWith( wxS( "V(" ) ) || signal.StartsWith( wxS( "I(" ) ) )
2802 signal = signal.SubString( 2, signal.Length() - 2 );
2803
2804 m_schematicFrame->Schematic().SetOperatingPoint( signal, val_list.at( 0 ) );
2805 }
2806 }
2807 else if( simType == ST_PZ && aFinal )
2808 {
2809 m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
2810 m_simConsole->SetInsertionPointEnd();
2811 simulator()->Command( "print all" );
2812 }
2813}
2814
2815
2817{
2819}
int color
Definition: DXF_plotter.cpp:58
const char * name
Definition: DXF_plotter.cpp:57
void showPopupMenu(wxMenu &menu, wxGridEvent &aEvent) override
void doPopupSelection(wxCommandEvent &event) override
SIMULATOR_FRAME_UI * m_parent
CURSORS_GRID_TRICKS(SIMULATOR_FRAME_UI *aParent, WX_GRID *aGrid)
The SIMULATOR_FRAME holds the main user-interface for running simulations.
Definition: sim_plot_tab.h:64
const wxRealPoint & GetCoords() const
Definition: sim_plot_tab.h:100
void SetCoordX(double aValue)
bool Find(const wxString &aTerm, int &aMatchersTriggered, int &aPosition)
virtual void Refresh(bool aEraseBackground=true, const wxRect *aRect=nullptr) override
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition: grid_tricks.h:61
virtual void doPopupSelection(wxCommandEvent &event)
virtual void showPopupMenu(wxMenu &menu, wxGridEvent &aEvent)
WX_GRID * m_grid
I don't own the grid, but he owns me.
Definition: grid_tricks.h:125
A color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:104
wxString ToCSSString() const
Definition: color4d.cpp:147
Definition: kiid.h:49
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
Hold a translatable error message and may be used when throwing exceptions containing a translated er...
Definition: ki_exception.h:46
const wxString What() const
Definition: ki_exception.h:58
void doPopupSelection(wxCommandEvent &event) override
void showPopupMenu(wxMenu &menu, wxGridEvent &aEvent) override
SIMULATOR_FRAME_UI * m_parent
MEASUREMENTS_GRID_TRICKS(SIMULATOR_FRAME_UI *aParent, WX_GRID *aGrid)
const SPICE_ITEM * FindItem(const std::string &aRefName) const
Find and return the item corresponding to aRefName.
A singleton reporter that reports to nowhere.
Definition: reporter.h:223
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition: project.cpp:143
Holds all the data relating to one schematic.
Definition: schematic.h:75
void SetOperatingPoint(const wxString &aSignal, double aValue)
Set operating points from a .op simulation.
Definition: schematic.h:232
void ClearOperatingPoints()
Clear operating points from a .op simulation.
Definition: schematic.h:224
SCH_DRAW_PANEL * GetCanvas() const override
Return a pointer to GAL-based canvas of given EDA draw frame.
EESCHEMA_SETTINGS * eeconfig() const
Schematic editor (Eeschema) main window.
void RefreshOperatingPointDisplay()
Refresh the display of any operaintg points.
void OnModify() override
Must be called after a schematic change in order to set the "modify" flag and update other data struc...
SCHEMATIC & Schematic() const
void UpdateItem(EDA_ITEM *aItem, bool isAddOrDelete=false, bool aUpdateRtree=false) override
Mark an item for refresh.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition: sch_item.h:151
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
SCH_ITEM * GetItem(const KIID &aID) const
Fetch a SCH_ITEM by ID.
Schematic symbol object.
Definition: sch_symbol.h:81
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const
Return the reference for the given sheet path.
Definition: sch_symbol.cpp:731
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly)
Populate a std::vector with SCH_FIELDs.
Definition: sch_symbol.cpp:981
void doPopupSelection(wxCommandEvent &event) override
void showPopupMenu(wxMenu &menu, wxGridEvent &aEvent) override
SIMULATOR_FRAME_UI * m_parent
SIGNALS_GRID_TRICKS(SIMULATOR_FRAME_UI *aParent, WX_GRID *aGrid)
Class SIMULATOR_FRAME_UI_BASE.
wxSplitterWindow * m_splitterLeftRight
wxSplitterWindow * m_splitterMeasurements
wxSplitterWindow * m_splitterCursors
wxSplitterWindow * m_splitterPlotAndConsole
wxSplitterWindow * m_splitterSignals
The SIMULATOR_FRAME_UI holds the main user-interface for running simulations.
SIM_TAB * NewSimTab(const wxString &aSimCommand)
Create a new simulation tab for a given simulation type.
void SetUserDefinedSignals(const std::map< int, wxString > &aSignals)
void updatePlotCursors()
Update the cursor values (in the grid) and graphics (in the plot window).
void OnSimRefresh(bool aFinal)
void onPlotClose(wxAuiNotebookEvent &event) override
void onPlotChanged(wxAuiNotebookEvent &event) override
void rebuildSignalsGrid(wxString aFilter)
Rebuild the filtered list of signals in the signals grid.
void DoFourier(const wxString &aSignal, const wxString &aFundamental)
void rebuildMeasurementsGrid()
Rebuild the measurements grid for the current plot.
std::list< TUNER_SLIDER * > m_tuners
SPICE expressions need quoted versions of the netnames since KiCad allows '-' and '/' in netnames.
void rebuildSignalsList()
Rebuild the list of signals available from the netlist.
bool loadLegacyWorkbook(const wxString &aPath)
void UpdateMeasurement(int aRow)
Update a measurement in the measurements grid.
wxString getNoiseSource() const
std::vector< wxString > SimPlotVectors() const
void applyUserDefinedSignals()
Apply user-defined signals to the SPICE session.
std::map< wxString, wxString > m_quotedNetnames
void DeleteMeasurement(int aRow)
Delete a row from the measurements grid.
SCH_EDIT_FRAME * m_schematicFrame
wxString vectorNameFromSignalName(SIM_PLOT_TAB *aPlotTab, const wxString &aSignalName, int *aTraceType)
Get the simulator output vector name for a given signal name and type.
void updateSignalsGrid()
Update the values in the signals grid.
void onCursorsGridCellChanged(wxGridEvent &aEvent) override
void onPlotDragged(wxAuiNotebookEvent &event) override
std::vector< wxString > Signals() const
bool SaveWorkbook(const wxString &aPath)
Save plot, signal, cursor, measurement, etc.
std::vector< wxString > m_signals
SIM_TAB * GetCurrentSimTab() const
Return the currently opened plot panel (or NULL if there is none).
bool LoadWorkbook(const wxString &aPath)
Load plot, signal, cursor, measurement, etc.
SPICE_VALUE_FORMAT GetMeasureFormat(int aRow) const
Get/Set the format of a value in the measurements grid.
std::map< int, wxString > m_userDefinedSignals
void UpdateTunerValue(const SCH_SHEET_PATH &aSheetPath, const KIID &aSymbol, const wxString &aRef, const wxString &aValue)
Safely update a field of the associated symbol without dereferencing the symbol.
void AddTrace(const wxString &aName, SIM_TRACE_TYPE aType)
Add a new trace to the current plot.
void onPlotClosed(wxAuiNotebookEvent &event) override
void RemoveTuner(TUNER_SLIDER *aTuner)
Remove an existing tuner.
void SaveSettings(EESCHEMA_SETTINGS *aCfg)
std::shared_ptr< SPICE_CIRCUIT_MODEL > circuitModel() const
void OnFilterText(wxCommandEvent &aEvent) override
void onMeasurementsGridCellChanged(wxGridEvent &aEvent) override
void applyTuners()
Apply component values specified using tuner sliders to the current netlist.
bool loadJsonWorkbook(const wxString &aPath)
void OnFilterMouseMoved(wxMouseEvent &aEvent) override
void AddMeasurement(const wxString &aCmd)
Add a measurement to the measurements grid.
void onPlotChanging(wxAuiNotebookEvent &event) override
std::shared_ptr< SPICE_SIMULATOR > simulator() const
void onPlotCursorUpdate(wxCommandEvent &aEvent)
void onSignalsGridCellChanged(wxGridEvent &aEvent) override
void SetCursorFormat(int aCursorId, int aValueCol, const SPICE_VALUE_FORMAT &aFormat)
void InitWorkbook()
Load the currently active workbook stored in the project settings.
SIM_TRACE_TYPE getXAxisType(SIM_TYPE aType) const
Return X axis for a given simulation type.
void SetMeasureFormat(int aRow, const SPICE_VALUE_FORMAT &aFormat)
void OnSimReport(const wxString &aMsg)
const SPICE_CIRCUIT_MODEL * GetExporter() const
Return the netlist exporter object used for simulations.
SIMULATOR_FRAME * m_simulatorFrame
void AddTuner(const SCH_SHEET_PATH &aSheetPath, SCH_SYMBOL *aSymbol)
Add a tuner for a symbol.
SPICE_VALUE_FORMAT GetCursorFormat(int aCursorId, int aValueCol) const
Get/Set the number of significant digits and the range for formatting a cursor value.
void OnUpdateUI(wxUpdateUIEvent &event) override
void updateTrace(const wxString &aVectorName, int aTraceType, SIM_PLOT_TAB *aPlotTab, std::vector< double > *aDataX=nullptr, bool aClearData=false)
Update a trace in a particular SIM_PLOT_TAB.
SIMULATOR_FRAME_UI(SIMULATOR_FRAME *aSimulatorFrame, SCH_EDIT_FRAME *aSchematicFrame)
SPICE_VALUE_FORMAT m_cursorFormats[3][2]
void LoadSettings(EESCHEMA_SETTINGS *aCfg)
The SIMULATOR_FRAME holds the main user-interface for running simulations.
bool LoadSimulator(const wxString &aSimCommand, unsigned aSimOptions)
Check and load the current netlist into the simulator.
std::shared_ptr< SPICE_CIRCUIT_MODEL > GetCircuitModel() const
SIM_TYPE GetCurrentSimType() const
void OnModify() override
Must be called after a model change in order to set the "modify" flag and do other frame-specific pro...
bool SimFinished() const
int GetCurrentOptions() const
std::shared_ptr< SPICE_SIMULATOR > GetSimulator() const
SIM_MODEL & CreateModel(SIM_MODEL::TYPE aType, const std::vector< LIB_PIN * > &aPins, REPORTER &aReporter)
virtual const PARAM * GetTunerParam() const
Definition: sim_model.h:489
const SPICE_GENERATOR & SpiceGenerator() const
Definition: sim_model.h:439
void SetParamValue(int aParamIndex, const std::string &aValue, SIM_VALUE::NOTATION aNotation=SIM_VALUE::NOTATION::SI)
Definition: sim_model.cpp:854
void WriteFields(std::vector< T > &aFields) const
static void FillDefaultColorList(bool aWhiteBg)
Fills m_colorList by a default set of colors.
bool DeleteTrace(const wxString &aVectorName, int aTraceType)
void EnableCursor(const wxString &aVectorName, int aType, int aCursorId, bool aEnable, const wxString &aSignalName)
Reset scale ranges to fit the current traces.
wxString GetLabelY1() const
Definition: sim_plot_tab.h:203
mpWindow * GetPlotWin() const
Definition: sim_plot_tab.h:345
void ShowGrid(bool aEnable)
Definition: sim_plot_tab.h:263
wxString GetUnitsY2() const
void SetTraceData(TRACE *aTrace, std::vector< double > &aX, std::vector< double > &aY)
void SetY2Scale(bool aLock, double aMin, double aMax)
TRACE * GetTrace(const wxString &aVecName, int aType) const
Definition: sim_plot_tab.h:256
wxString GetLabelX() const
Definition: sim_plot_tab.h:198
const std::map< wxString, TRACE * > & GetTraces() const
Definition: sim_plot_tab.h:251
wxString GetLabelY3() const
Definition: sim_plot_tab.h:213
void SetY1Scale(bool aLock, double aMin, double aMax)
void SetY3Scale(bool aLock, double aMin, double aMax)
std::vector< std::pair< wxString, wxString > > & Measurements()
Definition: sim_plot_tab.h:354
void UpdateTraceStyle(TRACE *trace)
Update plot colors.
void SetLegendPosition(const wxPoint &aPosition)
Definition: sim_plot_tab.h:304
void ResetScales(bool aIncludeX)
Update trace line style.
void UpdatePlotColors()
void ShowLegend(bool aEnable)
Definition: sim_plot_tab.h:288
wxString GetLabelY2() const
Definition: sim_plot_tab.h:208
wxString GetUnitsX() const
TRACE * GetOrAddTrace(const wxString &aVectorName, int aType)
void SetDottedSecondary(bool aEnable)
Draw secondary signal traces (current or phase) with dotted lines.
Definition: sim_plot_tab.h:314
wxString GetUnitsY1() const
wxString GetUnitsY3() const
int GetSimOptions() const
Definition: sim_tab.h:52
virtual void OnLanguageChanged()=0
SIM_TYPE GetSimType() const
Definition: sim_tab.cpp:71
const wxString & GetSimCommand() const
Definition: sim_tab.h:49
static bool IsPlottable(SIM_TYPE aSimType)
Definition: sim_tab.cpp:53
void SetSimOptions(int aOptions)
Definition: sim_tab.h:53
wxString GetLastSchTextSimCommand() const
Definition: sim_tab.h:55
void SetSpicePlotName(const wxString &aPlotName)
Definition: sim_tab.h:59
static std::string ToSpice(const std::string &aString)
Definition: sim_value.h:84
Special netlist exporter flavor that allows one to override simulation commands.
static SIM_TYPE CommandToSimType(const wxString &aCmd)
Return simulation type basing on a simulation command directive.
virtual std::string TunerCommand(const SPICE_ITEM &aItem, double aValue) const
static wxString TypeToName(SIM_TYPE aType, bool aShortName)
Return a string with simulation name based on enum.
Helper class to recognize Spice formatted values.
Definition: spice_value.h:56
wxString ToString() const
Return string value as when converting double to string (e.g.
wxString ToSpiceString() const
Return string value in Spice format (e.g.
double ToDouble() const
SUPPRESS_GRID_CELL_EVENTS(SIMULATOR_FRAME_UI *aFrame)
SIMULATOR_FRAME_UI * m_frame
void SetTraceColour(const wxColour &aColour)
Definition: sim_plot_tab.h:181
Custom widget to handle quick component values modification and simulation on the fly.
Definition: tuner_slider.h:44
void ClearRows()
wxWidgets recently added an ASSERT which fires if the position is greater than or equal to the number...
Definition: wx_grid.h:147
A wrapper for reporting to a wxString object.
Definition: reporter.h:164
bool HasMessage() const override
Returns true if the reporter client is non-empty.
Definition: reporter.cpp:71
REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) override
Report a string with a given severity.
Definition: reporter.cpp:61
A KICAD version of wxTextEntryDialog which supports the various improvements/work-arounds from DIALOG...
wxString GetValue() const
const wxString & GetName() const
Get layer name.
Definition: mathplot.h:239
const wxPen & GetPen() const
Get pen set for this layer.
Definition: mathplot.h:254
Canvas for plotting mpLayer implementations.
Definition: mathplot.h:906
int GetMarginLeft() const
Definition: mathplot.h:1184
void SetMargins(int top, int right, int bottom, int left)
Set window margins, creating a blank area where some kinds of layers cannot draw.
Definition: mathplot.cpp:2302
int GetMarginTop() const
Definition: mathplot.h:1178
void UpdateAll()
Refresh display.
Definition: mathplot.cpp:2203
int GetMarginRight() const
Definition: mathplot.h:1180
int GetMarginBottom() const
Definition: mathplot.h:1182
void EnableMouseWheelPan(bool enabled)
Enable/disable trackpad friendly panning (2-axis scroll wheel)
Definition: mathplot.h:1073
void LockY(bool aLock)
Definition: mathplot.h:1228
bool AddLayer(mpLayer *layer, bool refreshDisplay=true)
Add a plot layer to the canvas.
Definition: mathplot.cpp:2077
void Fit() override
Set view to fit global bounding box of all plot layers and refresh display.
Definition: mathplot.cpp:1710
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:305
This file is part of the common library.
#define _(s)
Abstract pattern-matching tool and implementations.
@ CTX_SIGNAL
@ GRIDTRICKS_FIRST_CLIENT_ID
Definition: grid_tricks.h:48
const std::string WorkbookFileExtension
const wxChar *const traceSettings
Flag to enable debug output of settings operations and management.
wxFont GetStatusFont(wxWindow *aWindow)
Definition: ui_common.cpp:127
see class PGM_BASE
SIM_TRACE_TYPE
Definition: sim_types.h:50
@ SPT_TIME
Definition: sim_types.h:61
@ SPT_AC_PHASE
Definition: sim_types.h:54
@ SPT_SWEEP
Definition: sim_types.h:64
@ SPT_UNKNOWN
Definition: sim_types.h:67
@ SPT_AC_GAIN
Definition: sim_types.h:55
@ SPT_Y_AXIS_MASK
Definition: sim_types.h:58
@ SPT_SP_AMP
Definition: sim_types.h:57
@ SPT_VOLTAGE
Definition: sim_types.h:52
@ SPT_POWER
Definition: sim_types.h:56
@ SPT_CURRENT
Definition: sim_types.h:53
@ SPT_LIN_FREQUENCY
Definition: sim_types.h:62
SIM_TYPE
< Possible simulation types
Definition: sim_types.h:32
@ ST_SP
Definition: sim_types.h:43
@ ST_TRAN
Definition: sim_types.h:42
@ ST_UNKNOWN
Definition: sim_types.h:33
@ ST_NOISE
Definition: sim_types.h:37
@ ST_AC
Definition: sim_types.h:34
@ ST_DISTO
Definition: sim_types.h:36
@ ST_TF
Definition: sim_types.h:41
@ ST_SENS
Definition: sim_types.h:40
@ ST_DC
Definition: sim_types.h:35
@ ST_OP
Definition: sim_types.h:38
@ ST_FFT
Definition: sim_types.h:44
@ ST_PZ
Definition: sim_types.h:39
void sortSignals(std::vector< wxString > &signals)
wxString vectorNameFromSignalId(int aUserDefinedSignalId)
MEASUREMENTS_GIRD_COLUMNS
@ COL_MEASUREMENT_FORMAT
@ COL_MEASUREMENT_VALUE
@ COL_MEASUREMENT
CURSORS_GRID_COLUMNS
@ COL_CURSOR_NAME
@ COL_CURSOR_SIGNAL
@ COL_CURSOR_X
@ COL_CURSOR_Y
SIGNALS_GRID_COLUMNS
@ COL_SIGNAL_SHOW
@ COL_SIGNAL_NAME
@ COL_CURSOR_1
@ COL_SIGNAL_COLOR
@ COL_CURSOR_2
#define ID_SIM_REFRESH
SIM_TRACE_TYPE operator|(SIM_TRACE_TYPE aFirst, SIM_TRACE_TYPE aSecond)
#define REFRESH_INTERVAL
@ MYID_MEASURE_INTEGRAL
@ MYID_MEASURE_MAX_AT
@ MYID_MEASURE_AVG
@ MYID_MEASURE_MAX
@ MYID_FOURIER
@ MYID_FORMAT_VALUE
@ MYID_MEASURE_RMS
@ MYID_DELETE_MEASUREMENT
@ MYID_MEASURE_MIN
@ MYID_MEASURE_MIN_AT
@ MYID_MEASURE_PP
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:119
const int scale
std::vector< FAB_LAYER_COLOR > dummy
wxString UnescapeString(const wxString &aSource)
std::string value
Definition: sim_model.h:400
const INFO & info
Definition: sim_model.h:401
SPICE_VALUE m_vincrement
const SIM_MODEL * model
A SPICE_VALUE_FORMAT holds precision and range info for formatting values.Helper class to handle Spic...
Definition: spice_value.h:43
wxString ToString() const
Definition: spice_value.cpp:49
void UpdateUnits(const wxString &aUnits)
Definition: spice_value.cpp:55
void FromString(const wxString &aString)
Definition: spice_value.cpp:40
VECTOR3I res
Definition of file extensions used in Kicad.