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