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 The 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, see <https://www.gnu.org/licenses/>.
21 */
22
23#include <algorithm>
24#include <memory>
25#include <type_traits>
26
27#include <wx/event.h>
28#include <wx/settings.h>
29#include <fmt/format.h>
30#include <wx/wfstream.h>
31#include <wx/stdstream.h>
32#include <wx/debug.h>
33#include <wx/clipbrd.h>
34#include <wx/log.h>
35#include <wx/tokenzr.h>
36
38#include <sch_edit_frame.h>
39#include <confirm.h>
45#include <bitmaps.h>
46#include <widgets/wx_grid.h>
47#include <grid_tricks.h>
48#include <eda_pattern_match.h>
49#include <string_utils.h>
50#include <pgm_base.h>
52#include <sim/simulator_frame.h>
53#include <sim/sim_plot_tab.h>
54#include <sim/spice_simulator.h>
57#include <eeschema_settings.h>
58#include <advanced_config.h>
59#include <magic_enum.hpp>
60#include <widgets/wx_infobar.h>
61
62
64{
65 int res = static_cast<int>( aFirst ) | static_cast<int>( aSecond );
66
67 return static_cast<SIM_TRACE_TYPE>( res );
68}
69
70
80
81
99
100
102
103
104static bool smithColumnsShown( const WX_GRID* aGrid )
105{
106 return aGrid->GetNumberCols() > COL_CURSOR_SMITH_FIRST;
107}
108
109
116
117
118enum
119{
129
132};
133
134
136{
137public:
139 GRID_TRICKS( aGrid ),
140 m_parent( aParent ),
141 m_menuRow( 0 ),
142 m_menuCol( 0 )
143 {
144 }
145
146protected:
147 void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
148 void doPopupSelection( wxCommandEvent& event ) override;
149
150protected:
154};
155
156
157void SIGNALS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
158{
159 SIM_TAB* panel = m_parent->GetCurrentSimTab();
160
161 if( !panel )
162 return;
163
164 m_menuRow = aEvent.GetRow();
165 m_menuCol = aEvent.GetCol();
166
168 {
169 if( !( m_grid->IsInSelection( m_menuRow, m_menuCol ) ) )
170 m_grid->ClearSelection();
171
172 m_grid->SetGridCursor( m_menuRow, m_menuCol );
173
174 if( panel->GetSimType() == ST_TRAN || panel->GetSimType() == ST_AC || panel->GetSimType() == ST_DC
175 || panel->GetSimType() == ST_SP )
176 {
177 menu.Append( MYID_MEASURE_MIN, _( "Measure Min" ) );
178 menu.Append( MYID_MEASURE_MAX, _( "Measure Max" ) );
179 menu.Append( MYID_MEASURE_AVG, _( "Measure Average" ) );
180 menu.Append( MYID_MEASURE_RMS, _( "Measure RMS" ) );
181 menu.Append( MYID_MEASURE_PP, _( "Measure Peak-to-peak" ) );
182
183 if( panel->GetSimType() == ST_AC || panel->GetSimType() == ST_SP )
184 {
185 menu.Append( MYID_MEASURE_MIN_AT, _( "Measure Frequency of Min" ) );
186 menu.Append( MYID_MEASURE_MAX_AT, _( "Measure Frequency of Max" ) );
187 }
188 else
189 {
190 menu.Append( MYID_MEASURE_MIN_AT, _( "Measure Time of Min" ) );
191 menu.Append( MYID_MEASURE_MAX_AT, _( "Measure Time of Max" ) );
192 }
193
194 menu.Append( MYID_MEASURE_INTEGRAL, _( "Measure Integral" ) );
195
196 if( panel->GetSimType() == ST_TRAN )
197 {
198 menu.AppendSeparator();
199 menu.Append( MYID_FOURIER, _( "Perform Fourier Analysis..." ) );
200 }
201
202 menu.AppendSeparator();
203 menu.Append( GRIDTRICKS_ID_COPY, _( "Copy Signal Name" ) + "\tCtrl+C" );
204
205 menu.AppendSeparator();
206 menu.Append( GRIDTRICKS_ID_SELECT, _( "Create new cursor..." ) );
207
208 m_grid->PopupMenu( &menu );
209 }
210 }
211 else if( m_menuCol > static_cast<int>( COL_CURSOR_2 ) )
212 {
213 menu.Append( GRIDTRICKS_ID_SELECT, _( "Create new cursor..." ) );
214
215 menu.AppendSeparator();
216
217 wxString msg = m_grid->GetColLabelValue( m_grid->GetNumberCols() - 1 );
218
219 menu.AppendSeparator();
220 menu.Append( GRIDTRICKS_ID_DELETE, wxString::Format( _( "Delete %s..." ), msg ) );
221
222 m_grid->PopupMenu( &menu );
223 }
224 else
225 {
226 menu.Append( GRIDTRICKS_ID_SELECT, _( "Create new cursor..." ) );
227
228 m_grid->PopupMenu( &menu );
229 }
230}
231
232
233void SIGNALS_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
234{
235 std::vector<wxString> signals;
236
237 wxGridCellCoordsArray cells1 = m_grid->GetSelectionBlockTopLeft();
238 wxGridCellCoordsArray cells2 = m_grid->GetSelectionBlockBottomRight();
239
240 for( size_t i = 0; i < cells1.Count(); i++ )
241 {
242 if( cells1[i].GetCol() == COL_SIGNAL_NAME )
243 {
244 for( int j = cells1[i].GetRow(); j < cells2[i].GetRow() + 1; j++ )
245 {
246 signals.push_back( m_grid->GetCellValue( j, cells1[i].GetCol() ) );
247 }
248 }
249 }
250
251 wxGridCellCoordsArray cells3 = m_grid->GetSelectedCells();
252
253 for( size_t i = 0; i < cells3.Count(); i++ )
254 {
255 if( cells3[i].GetCol() == COL_SIGNAL_NAME )
256 signals.push_back( m_grid->GetCellValue( cells3[i].GetRow(), cells3[i].GetCol() ) );
257 }
258
259 if( signals.size() < 1 )
260 signals.push_back( m_grid->GetCellValue( m_menuRow, m_menuCol ) );
261
262 auto addMeasurement =
263 [this]( const wxString& cmd, wxString signal )
264 {
265 if( signal.EndsWith( _( " (phase)" ) ) )
266 return;
267
268 if( signal.EndsWith( _( " (gain)" ) ) || signal.EndsWith( _( " (amplitude)" ) ) )
269 {
270 signal = signal.Left( signal.length() - 7 );
271
272 if( signal.Upper().StartsWith( wxS( "V(" ) ) )
273 signal = wxS( "vdb" ) + signal.Mid( 1 );
274 }
275
276 m_parent->AddMeasurement( cmd + wxS( " " ) + signal );
277 };
278
279 if( event.GetId() == MYID_MEASURE_MIN )
280 {
281 for( const wxString& signal : signals )
282 addMeasurement( wxS( "MIN" ), signal );
283 }
284 else if( event.GetId() == MYID_MEASURE_MAX )
285 {
286 for( const wxString& signal : signals )
287 addMeasurement( wxS( "MAX" ), signal );
288 }
289 else if( event.GetId() == MYID_MEASURE_AVG )
290 {
291 for( const wxString& signal : signals )
292 addMeasurement( wxS( "AVG" ), signal );
293 }
294 else if( event.GetId() == MYID_MEASURE_RMS )
295 {
296 for( const wxString& signal : signals )
297 addMeasurement( wxS( "RMS" ), signal );
298 }
299 else if( event.GetId() == MYID_MEASURE_PP )
300 {
301 for( const wxString& signal : signals )
302 addMeasurement( wxS( "PP" ), signal );
303 }
304 else if( event.GetId() == MYID_MEASURE_MIN_AT )
305 {
306 for( const wxString& signal : signals )
307 addMeasurement( wxS( "MIN_AT" ), signal );
308 }
309 else if( event.GetId() == MYID_MEASURE_MAX_AT )
310 {
311 for( const wxString& signal : signals )
312 addMeasurement( wxS( "MAX_AT" ), signal );
313 }
314 else if( event.GetId() == MYID_MEASURE_INTEGRAL )
315 {
316 for( const wxString& signal : signals )
317 addMeasurement( wxS( "INTEG" ), signal );
318 }
319 else if( event.GetId() == MYID_FOURIER )
320 {
321 wxString title;
322 wxString fundamental = wxT( "1K" );
323
324 if( signals.size() == 1 )
325 title.Printf( _( "Fourier Analysis of %s" ), signals[0] );
326 else
327 title = _( "Fourier Analyses of Multiple Signals" );
328
329 WX_TEXT_ENTRY_DIALOG dlg( m_parent, _( "Fundamental frequency:" ), title, fundamental );
330
331 if( dlg.ShowModal() != wxID_OK )
332 return;
333
334 if( !dlg.GetValue().IsEmpty() )
335 fundamental = dlg.GetValue();
336
337 for( const wxString& signal : signals )
338 m_parent->DoFourier( signal, fundamental );
339 }
340 else if( event.GetId() == GRIDTRICKS_ID_COPY )
341 {
342 wxLogNull doNotLog; // disable logging of failed clipboard actions
343 wxString txt;
344
345 for( const wxString& signal : signals )
346 {
347 if( !txt.IsEmpty() )
348 txt += '\r';
349
350 txt += signal;
351 }
352
353 if( wxTheClipboard->Open() )
354 {
355 wxTheClipboard->SetData( new wxTextDataObject( txt ) );
356 wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
357 wxTheClipboard->Close();
358 }
359 }
360 else if( event.GetId() == GRIDTRICKS_ID_SELECT )
361 {
362 m_parent->CreateNewCursor();
363 }
364 else if( event.GetId() == GRIDTRICKS_ID_DELETE )
365 {
366 m_parent->DeleteCursor();
367 }
368}
369
370
372{
373public:
375 GRID_TRICKS( aGrid ),
376 m_parent( aParent ),
377 m_menuRow( 0 ),
378 m_menuCol( 0 )
379 {
380 }
381
382protected:
383 void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
384 void doPopupSelection( wxCommandEvent& event ) override;
385
386protected:
390};
391
392
393void CURSORS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
394{
395 m_menuRow = aEvent.GetRow();
396 m_menuCol = aEvent.GetCol();
397
399 {
400 wxString msg = m_grid->GetColLabelValue( m_menuCol );
401
402 menu.Append( MYID_FORMAT_VALUE, wxString::Format( _( "Format %s..." ), msg ) );
403 menu.AppendSeparator();
404 }
405
406 GRID_TRICKS::showPopupMenu( menu, aEvent );
407}
408
409
410void CURSORS_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
411{
412 auto getSignalName =
413 [this]( int row ) -> wxString
414 {
415 wxString signal = m_grid->GetCellValue( row, COL_CURSOR_SIGNAL );
416
417 if( signal.EndsWith( "[2 - 1]" ) )
418 signal = signal.Left( signal.length() - 7 );
419
420 return signal;
421 };
422
423 if( event.GetId() == MYID_FORMAT_VALUE )
424 {
425 int axis = m_menuCol - COL_CURSOR_X;
426 SPICE_VALUE_FORMAT format = m_parent->GetCursorFormat( m_menuRow, axis );
427 DIALOG_SIM_FORMAT_VALUE formatDialog( m_parent, &format );
428
429 if( formatDialog.ShowModal() == wxID_OK )
430 {
431 for( int row = 0; row < m_grid->GetNumberRows(); ++row )
432 {
433 if( getSignalName( row ) == getSignalName( m_menuRow ) )
434 m_parent->SetCursorFormat( row, axis, format );
435 }
436 }
437 }
438 else
439 {
441 }
442}
443
444
446{
447public:
449 GRID_TRICKS( aGrid ),
450 m_parent( aParent ),
451 m_menuRow( 0 ),
452 m_menuCol( 0 )
453 {
454 }
455
456protected:
457 void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
458 void doPopupSelection( wxCommandEvent& event ) override;
459
460protected:
464};
465
466
467void MEASUREMENTS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
468{
469 m_menuRow = aEvent.GetRow();
470 m_menuCol = aEvent.GetCol();
471
472 if( !( m_grid->IsInSelection( m_menuRow, m_menuCol ) ) )
473 m_grid->ClearSelection();
474
475 m_grid->SetGridCursor( m_menuRow, m_menuCol );
476
478 menu.Append( MYID_FORMAT_VALUE, _( "Format Value..." ) );
479
480 if( m_menuRow < ( m_grid->GetNumberRows() - 1 ) )
481 menu.Append( MYID_DELETE_MEASUREMENT, _( "Delete Measurement" ) );
482
483 menu.AppendSeparator();
484
485 GRID_TRICKS::showPopupMenu( menu, aEvent );
486}
487
488
490{
491 if( event.GetId() == MYID_FORMAT_VALUE )
492 {
493 SPICE_VALUE_FORMAT format = m_parent->GetMeasureFormat( m_menuRow );
494 DIALOG_SIM_FORMAT_VALUE formatDialog( m_parent, &format );
495
496 if( formatDialog.ShowModal() == wxID_OK )
497 {
498 m_parent->SetMeasureFormat( m_menuRow, format );
499 m_parent->UpdateMeasurement( m_menuRow );
500 m_parent->OnModify();
501 }
502 }
503 else if( event.GetId() == MYID_DELETE_MEASUREMENT )
504 {
505 std::vector<int> measurements;
506
507 wxGridCellCoordsArray cells1 = m_grid->GetSelectionBlockTopLeft();
508 wxGridCellCoordsArray cells2 = m_grid->GetSelectionBlockBottomRight();
509
510 for( size_t i = 0; i < cells1.Count(); i++ )
511 {
512 if( cells1[i].GetCol() == COL_MEASUREMENT )
513 {
514 for( int j = cells1[i].GetRow(); j < cells2[i].GetRow() + 1; j++ )
515 measurements.push_back( j );
516 }
517 }
518
519 wxGridCellCoordsArray cells3 = m_grid->GetSelectedCells();
520
521 for( size_t i = 0; i < cells3.Count(); i++ )
522 {
523 if( cells3[i].GetCol() == COL_MEASUREMENT )
524 measurements.push_back( cells3[i].GetRow() );
525 }
526
527 if( measurements.size() < 1 )
528 measurements.push_back( m_menuRow );
529
530 // When deleting a row, we'll change the indexes.
531 // To avoid problems, we can start with the highest indexes.
532 sort( measurements.begin(), measurements.end(), std::greater<>() );
533
534 for( int row : measurements )
535 m_parent->DeleteMeasurement( row );
536
537 m_grid->ClearSelection();
538
539 m_parent->OnModify();
540 }
541 else
542 {
544 }
545}
546
547
549{
550public:
552 m_frame( aFrame )
553 {
554 m_frame->m_SuppressGridEvents++;
555 }
556
558 {
559 m_frame->m_SuppressGridEvents--;
560 }
561
562private:
564};
565
566
567#define ID_SIM_REFRESH 10207
568#define REFRESH_INTERVAL 50 // 20 frames/second.
569
570
572 SIMULATOR_FRAME_UI_BASE( aSimulatorFrame ),
574 m_simulatorFrame( aSimulatorFrame ),
575 m_schematicFrame( aSchematicFrame ),
576 m_darkMode( true ),
577 m_plotNumber( 0 ),
579{
580 // Get the previous size and position of windows:
581 LoadSettings( m_schematicFrame->eeconfig() );
582
583 m_filter->SetHint( _( "Filter" ) );
584
585 Bind( EVT_SIM_VIEWS_CHANGED,
586 [&]( wxCommandEvent& aEvent )
587 {
588 rebuildSignalsGrid( m_filter->GetValue() );
590 OnModify();
591 } );
592
593 m_signalsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
594 m_cursorsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
595 m_measurementsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
596
597 m_signalsGrid->PushEventHandler( new SIGNALS_GRID_TRICKS( this, m_signalsGrid ) );
598 m_cursorsGrid->PushEventHandler( new CURSORS_GRID_TRICKS( this, m_cursorsGrid ) );
599 m_measurementsGrid->PushEventHandler( new MEASUREMENTS_GRID_TRICKS( this, m_measurementsGrid ) );
600
601 wxGridCellAttr* attr = new wxGridCellAttr;
602 attr->SetReadOnly();
603 m_signalsGrid->SetColAttr( COL_SIGNAL_NAME, attr );
604
605 attr = new wxGridCellAttr;
606 attr->SetReadOnly();
607 m_cursorsGrid->SetColAttr( COL_CURSOR_NAME, attr );
608
609 attr = new wxGridCellAttr;
610 attr->SetReadOnly();
611 m_cursorsGrid->SetColAttr( COL_CURSOR_SIGNAL, attr );
612
613 attr = new wxGridCellAttr;
614 attr->SetReadOnly();
615 m_cursorsGrid->SetColAttr( COL_CURSOR_Y, attr );
616
618
619 attr = new wxGridCellAttr;
620 attr->SetReadOnly();
621 m_measurementsGrid->SetColAttr( COL_MEASUREMENT_VALUE, attr );
622
623 // Prepare the color list to plot traces
625
626 Bind( EVT_SIM_CURSOR_UPDATE, &SIMULATOR_FRAME_UI::onPlotCursorUpdate, this );
627
628 Bind( wxEVT_TIMER,
629 [&]( wxTimerEvent& aEvent )
630 {
631 OnSimRefresh( false );
632
633 if( m_simulatorFrame->GetSimulator()->IsRunning() )
634 m_refreshTimer.Start( REFRESH_INTERVAL, wxTIMER_ONE_SHOT );
635 },
636 m_refreshTimer.GetId() );
637
638#ifndef wxHAS_NATIVE_TABART
639 // Default non-native tab art has ugly gradients we don't want
640 m_plotNotebook->SetArtProvider( new wxAuiSimpleTabArt() );
641#endif
642}
643
644
646{
647 // Delete the GRID_TRICKS.
648 m_signalsGrid->PopEventHandler( true );
649 m_cursorsGrid->PopEventHandler( true );
650 m_measurementsGrid->PopEventHandler( true );
651}
652
653
655{
656 for( auto& m_cursorFormat : m_cursorFormats )
657 {
658 m_cursorFormat[0] = { 3, wxS( "~s" ) };
659 m_cursorFormat[1] = { 3, wxS( "~V" ) };
660 }
661
662 // proper init and transfer/copy m_cursorFormats
663 // we work on m_cursorFormatsDyn from now on.
664 // TODO: rework +- LOC when m_cursorFormatsDyn and m_cursorFormats get merged.
665 m_cursorFormatsDyn.clear();
666 m_cursorFormatsDyn.resize( std::size( m_cursorFormats ) );
667
668 for( size_t index = 0; index < std::size( m_cursorFormats ); index++ )
669 {
670 for( size_t index2 = 0; index2 < std::size( m_cursorFormats[0] ); index2++ )
671 m_cursorFormatsDyn[index].push_back( m_cursorFormats[index][index2] );
672 }
673
674 // Dump string helper, tries to get the current higher cursor name to form the next one.
675 // Based on the column labeling
676 // TODO: "Cursor n" may translate as "n Cursor" in other languages
677 // TBD how to handle; just forbid for now.
678 int nameMax = 0;
679
680 for( int i = 0; i < m_signalsGrid->GetNumberCols(); i++ )
681 {
682 wxString maxCursor = m_signalsGrid->GetColLabelValue( i );
683
684 maxCursor.Replace( _( "Cursor " ), "" );
685
686 int tmpMax = wxAtoi( maxCursor );
687
688 if( nameMax < tmpMax )
689 nameMax = tmpMax;
690 }
691
692 m_customCursorsCnt = nameMax + 1; // Init with a +1 on top of current cursor 2, defaults to 3
693}
694
695
697{
698 std::vector<SPICE_VALUE_FORMAT> tmp;
699 // m_cursorFormatsDyn should be equal with m_cursorFormats on first entry here.
700 m_cursorFormatsDyn.emplace_back( tmp );
701
702 m_cursorFormatsDyn[m_customCursorsCnt].push_back( { 3, wxS( "~s" ) } );
703 m_cursorFormatsDyn[m_customCursorsCnt].push_back( { 3, wxS( "~V" ) } );
704
705 wxString cursor_name = wxString( _( "Cursor " ) ) << m_customCursorsCnt;
706
707 m_signalsGrid->InsertCols( m_signalsGrid->GetNumberCols(), 1, true );
708 m_signalsGrid->SetColLabelValue( m_signalsGrid->GetNumberCols() - 1, cursor_name );
709
710 wxGridCellAttr* attr = new wxGridCellAttr;
711 m_signalsGrid->SetColAttr( COL_CURSOR_2 + m_customCursorsCnt, attr );
712
714
717 OnModify();
718}
719
720
722{
723 int col = m_signalsGrid->GetNumberCols();
724 int rows = m_signalsGrid->GetNumberRows();
725
726 if( col > COL_CURSOR_2 )
727 {
728 // Now we need to find the active cursor and deactivate before removing the column,
729 // Send the dummy event to update the UI
730 for( int i = 0; i < rows; i++ )
731 {
732 if( m_signalsGrid->GetCellValue( i, col - 1 ) == wxS( "1" ) )
733 {
734 m_signalsGrid->SetCellValue( i, col - 1, wxEmptyString );
735 wxGridEvent aDummy( wxID_ANY, wxEVT_GRID_CELL_CHANGED, m_signalsGrid, i, col - 1 );
736 onSignalsGridCellChanged( aDummy );
737 break;
738 }
739 }
740
741 m_signalsGrid->DeleteCols( col - 1, 1, false );
742 m_cursorFormatsDyn.pop_back();
744 m_plotNotebook->Refresh();
747 OnModify();
748 }
749}
750
751
753{
754 for( int ii = 0; ii < static_cast<int>( m_plotNotebook->GetPageCount() ); ++ii )
755 {
756 if( SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( ii ) ) )
757 {
758 simTab->OnLanguageChanged();
759
760 wxString pageTitle( simulator()->TypeToName( simTab->GetSimType(), true ) );
761 pageTitle.Prepend( wxString::Format( _( "Analysis %u - " ), ii + 1 /* 1-based */ ) );
762
763 m_plotNotebook->SetPageText( ii, pageTitle );
764 }
765 }
766
767 m_filter->SetHint( _( "Filter" ) );
768
769 m_signalsGrid->SetColLabelValue( COL_SIGNAL_NAME, _( "Signal" ) );
770 m_signalsGrid->SetColLabelValue( COL_SIGNAL_SHOW, _( "Plot" ) );
771 m_signalsGrid->SetColLabelValue( COL_Y_SCALE, _( "Y Scale" ) );
772 m_signalsGrid->SetColLabelValue( COL_SIGNAL_COLOR, _( "Color" ) );
773 m_signalsGrid->SetColLabelValue( COL_CURSOR_1, _( "Cursor 1" ) );
774 m_signalsGrid->SetColLabelValue( COL_CURSOR_2, _( "Cursor 2" ) );
775
776 m_cursorsGrid->SetColLabelValue( COL_CURSOR_NAME, _( "Cursor" ) );
777 m_cursorsGrid->SetColLabelValue( COL_CURSOR_SIGNAL, _( "Signal" ) );
778 m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, _( "Time" ) );
779 m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, _( "Value" ) );
782
783 for( TUNER_SLIDER* tuner : m_tuners )
784 tuner->ShowChangedLanguage();
785}
786
787
803
804
806{
808
809 settings.view.plot_panel_width = m_splitterLeftRight->GetSashPosition();
810 settings.view.plot_panel_height = m_splitterPlotAndConsole->GetSashPosition();
811 settings.view.signal_panel_height = m_splitterSignals->GetSashPosition();
812 settings.view.cursors_panel_height = m_splitterCursors->GetSashPosition();
813 settings.view.measurements_panel_height = m_splitterMeasurements->GetSashPosition();
814 settings.view.white_background = !m_darkMode;
815
818
820}
821
822
824{
825 m_preferences = aPrefs;
826
827 for( std::size_t i = 0; i < m_plotNotebook->GetPageCount(); ++i )
828 {
829 if( SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( i ) ) )
830 simTab->ApplyPreferences( aPrefs );
831 }
832}
833
834
836{
837 wxString workbookFilename = simulator()->Settings()->GetWorkbookFilename();
838 bool loadFromSchematic = false;
839
840 if( !workbookFilename.IsEmpty() )
841 {
842 wxFileName filename = workbookFilename;
843 filename.SetPath( m_schematicFrame->Prj().GetProjectPath() );
844
845 if( !filename.FileExists() )
846 {
847 WX_INFOBAR* infobar = m_simulatorFrame->GetInfoBar();
848 wxString msg = wxString::Format( _( "Workbook file '%s' not found. Loading simulation settings from "
849 "schematic." ),
850 filename.GetFullPath() );
851 infobar->ShowMessageFor( msg, 8000, wxICON_WARNING );
852
853 simulator()->Settings()->SetWorkbookFilename( wxEmptyString );
854 loadFromSchematic = true;
855 }
856 else if( !LoadWorkbook( filename.GetFullPath() ) )
857 {
858 simulator()->Settings()->SetWorkbookFilename( wxEmptyString );
859 }
860 }
861 else
862 {
863 loadFromSchematic = true;
864 }
865
866 if( loadFromSchematic && m_simulatorFrame->LoadSimulator( wxEmptyString, 0 ) )
867 {
868 wxString schTextSimCommand = circuitModel()->GetSchTextSimCommand();
869
870 if( !schTextSimCommand.IsEmpty() )
871 {
872 SIM_TAB* simTab = NewSimTab( schTextSimCommand );
874 }
875
877 rebuildSignalsGrid( m_filter->GetValue() );
878 }
879}
880
881
899
900
901void sortSignals( std::vector<wxString>& signals )
902{
903 std::sort( signals.begin(), signals.end(),
904 []( const wxString& lhs, const wxString& rhs )
905 {
906 // Sort voltages first
907 if( lhs.Upper().StartsWith( 'V' ) && !rhs.Upper().StartsWith( 'V' ) )
908 return true;
909 else if( !lhs.Upper().StartsWith( 'V' ) && rhs.Upper().StartsWith( 'V' ) )
910 return false;
911
912 return StrNumCmp( lhs, rhs, true /* ignore case */ ) < 0;
913 } );
914}
915
916
918{
919 SUPPRESS_GRID_CELL_EVENTS raii( this );
920
921 m_signalsGrid->ClearRows();
922
923 SIM_PLOT_TAB* plotPanel = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
924
925 if( !plotPanel )
926 return;
927
928 SIM_TYPE simType = plotPanel->GetSimType();
929 std::vector<wxString> signals;
930
931 if( plotPanel->GetSimType() == ST_FFT )
932 {
933 wxStringTokenizer tokenizer( plotPanel->GetSimCommand(), " \t\r\n", wxTOKEN_STRTOK );
934
935 while( tokenizer.HasMoreTokens() && tokenizer.GetNextToken().Lower() != wxT( "fft" ) )
936 {
937 };
938
939 while( tokenizer.HasMoreTokens() )
940 signals.emplace_back( tokenizer.GetNextToken() );
941 }
942 else
943 {
944 // NB: m_signals are already broken out into gain/phase, but m_userDefinedSignals are
945 // as the user typed them
946
947 for( const wxString& signal : m_signals )
948 signals.push_back( signal );
949
950 for( const auto& [id, signal] : m_userDefinedSignals )
951 {
952 if( simType == ST_AC )
953 {
954 signals.push_back( signal + _( " (gain)" ) );
955 signals.push_back( signal + _( " (phase)" ) );
956 }
957 else if( simType == ST_SP )
958 {
959 if( plotPanel->IsSmithMode() )
960 {
961 signals.push_back( signal );
962 }
963 else
964 {
965 signals.push_back( signal + _( " (amplitude)" ) );
966 signals.push_back( signal + _( " (phase)" ) );
967 }
968 }
969 else
970 {
971 signals.push_back( signal );
972 }
973 }
974
975 sortSignals( signals );
976 }
977
978 if( aFilter.IsEmpty() )
979 aFilter = wxS( "*" );
980
981 EDA_COMBINED_MATCHER matcher( aFilter.Upper(), CTX_SIGNAL );
982 int row = 0;
983
984 for( const wxString& signal : signals )
985 {
986 if( matcher.Find( signal.Upper() ) )
987 {
988 int traceType = SPT_UNKNOWN;
989 wxString vectorName = vectorNameFromSignalName( plotPanel, signal, &traceType );
990 TRACE* trace = plotPanel->GetTrace( vectorName, traceType );
991
992 // A trace can exist but not be plotted on any view (e.g. after its view was removed);
993 // such an orphan is shown the same as a never-plotted signal.
994 bool plotted = trace && trace->GetView();
995
996 m_signalsGrid->AppendRows( 1 );
997 m_signalsGrid->SetCellValue( row, COL_SIGNAL_NAME, signal );
998
999 wxGridCellAttr* attr = new wxGridCellAttr;
1000 attr->SetEditor( new GRID_CELL_COMBOBOX( getViewChoices( plotPanel ) ) );
1001 attr->SetRenderer( new GRID_CELL_COMBOBOX_RENDERER() );
1002 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
1003
1004 if( !plotted )
1005 attr->SetTextColour( *wxLIGHT_GREY );
1006
1007 m_signalsGrid->SetAttr( row, COL_SIGNAL_SHOW, attr );
1008 m_signalsGrid->SetCellValue( row, COL_SIGNAL_SHOW, getViewLabel( plotPanel, trace ? trace->GetView()
1009 : nullptr ) );
1010
1011 if( !plotted )
1012 {
1013 attr = new wxGridCellAttr;
1014 attr->SetReadOnly();
1015 m_signalsGrid->SetAttr( row, COL_Y_SCALE, attr );
1016 m_signalsGrid->SetCellValue( row, COL_Y_SCALE, wxEmptyString );
1017
1018 attr = new wxGridCellAttr;
1019 attr->SetReadOnly();
1020 m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
1021 m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, wxEmptyString );
1022
1023 attr = new wxGridCellAttr;
1024 attr->SetReadOnly();
1025 m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
1026
1027 attr = new wxGridCellAttr;
1028 attr->SetReadOnly();
1029 m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
1030
1031 if( m_customCursorsCnt > 3 )
1032 {
1033 for( int i = 1; i <= m_customCursorsCnt - 3; i++ )
1034 {
1035 attr = new wxGridCellAttr;
1036 attr->SetReadOnly();
1037 m_signalsGrid->SetAttr( row, COL_CURSOR_2 + i, attr );
1038 }
1039 }
1040 }
1041 else
1042 {
1043 attr = new wxGridCellAttr;
1044 attr->SetEditor( new GRID_CELL_COMBOBOX( getYScaleChoices( plotPanel, trace->GetView() ) ) );
1045 attr->SetRenderer( new GRID_CELL_COMBOBOX_RENDERER() );
1046 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
1047 m_signalsGrid->SetAttr( row, COL_Y_SCALE, attr );
1048 m_signalsGrid->SetCellValue( row, COL_Y_SCALE, getYScaleLabel( plotPanel, trace ) );
1049
1050 attr = new wxGridCellAttr;
1051 attr->SetRenderer( new GRID_CELL_COLOR_RENDERER( this ) );
1052 attr->SetEditor( new GRID_CELL_COLOR_SELECTOR( this, m_signalsGrid ) );
1053 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
1054 m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
1055 KIGFX::COLOR4D color( trace->GetPen().GetColour() );
1056 m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, color.ToCSSString() );
1057
1058 attr = new wxGridCellAttr;
1059 attr->SetRenderer( new wxGridCellBoolRenderer() );
1060 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
1061 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
1062 m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
1063 m_signalsGrid->SetCellValue( row, COL_CURSOR_1, trace->GetCursor( 1 ) ? "1" : "0" );
1064
1065 attr = new wxGridCellAttr;
1066 attr->SetRenderer( new wxGridCellBoolRenderer() );
1067 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
1068 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
1069 m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
1070 m_signalsGrid->SetCellValue( row, COL_CURSOR_2, trace->GetCursor( 2 ) ? "1" : "0" );
1071
1072 if( m_customCursorsCnt > 3 )
1073 {
1074 for( int i = 1; i <= m_customCursorsCnt - 3; i++ )
1075 {
1076 attr = new wxGridCellAttr;
1077 attr->SetRenderer( new wxGridCellBoolRenderer() );
1078 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
1079 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
1080 m_signalsGrid->SetAttr( row, COL_CURSOR_2 + i, attr );
1081 m_signalsGrid->SetCellValue( row, COL_CURSOR_2 + i, trace->GetCursor( i ) ? "1" : "0" );
1082 }
1083 }
1084 }
1085 row++;
1086 }
1087 }
1088
1093
1094 for( int col = COL_CURSOR_1; col < m_signalsGrid->GetNumberCols(); ++col )
1096}
1097
1098
1099void SIMULATOR_FRAME_UI::autoSizeGridColumn( WX_GRID* aGrid, int aCol, int aMinWidth, int aExtraPadding )
1100{
1101 wxGridUpdateLocker deferRepaintsTillLeavingScope( aGrid );
1102
1103 aGrid->AutoSizeColumn( aCol );
1104 aGrid->AutoSizeColLabelSize( aCol ); // Also grow (never shrink) to fit the header label
1105
1106 if( aExtraPadding > 0 )
1107 aGrid->SetColSize( aCol, aGrid->GetColSize( aCol ) + aExtraPadding );
1108
1109 if( aGrid->GetColSize( aCol ) < aMinWidth )
1110 aGrid->SetColSize( aCol, aMinWidth );
1111}
1112
1113
1114#define UNPLOTTED _( "none" )
1115
1116wxArrayString SIMULATOR_FRAME_UI::getViewChoices( SIM_PLOT_TAB* aPlotTab ) const
1117{
1118 wxArrayString choices;
1119 choices.Add( UNPLOTTED );
1120
1121 if( aPlotTab )
1122 {
1123 for( int ii = 0; ii < aPlotTab->GetViewCount(); ++ii )
1124 choices.Add( wxString::Format( _( "View %d" ), ii + 1 ) );
1125 }
1126
1127 return choices;
1128}
1129
1130
1131wxString SIMULATOR_FRAME_UI::getViewLabel( SIM_PLOT_TAB* aPlotTab, SIM_VIEW* aView ) const
1132{
1133 if( !aView || !aPlotTab )
1134 return UNPLOTTED;
1135
1136 int index = aPlotTab->GetViewIndex( aView );
1137
1138 return index >= 0 ? wxString::Format( _( "View %d" ), index + 1 ) : UNPLOTTED;
1139}
1140
1141
1142std::vector<SIM_VIEW*> SIMULATOR_FRAME_UI::getYScaleTargetViews( SIM_PLOT_TAB* aPlotTab, SIM_VIEW* aOwnView ) const
1143{
1144 std::vector<SIM_VIEW*> views;
1145
1146 if( aPlotTab )
1147 {
1148 for( SIM_VIEW* view : aPlotTab->GetViews() )
1149 {
1150 if( view != aOwnView )
1151 views.push_back( view );
1152 }
1153 }
1154
1155 return views;
1156}
1157
1158
1159wxArrayString SIMULATOR_FRAME_UI::getYScaleChoices( SIM_PLOT_TAB* aPlotTab, SIM_VIEW* aOwnView ) const
1160{
1161 wxArrayString choices;
1162 choices.Add( _( "Default" ) );
1163
1164 for( SIM_VIEW* view : getYScaleTargetViews( aPlotTab, aOwnView ) )
1165 choices.Add( getViewLabel( aPlotTab, view ) );
1166
1167 return choices;
1168}
1169
1170
1171wxString SIMULATOR_FRAME_UI::getYScaleLabel( SIM_PLOT_TAB* aPlotTab, TRACE* aTrace ) const
1172{
1173 if( !aTrace || aTrace->IsYScaleDefault() )
1174 return _( "Default" );
1175
1176 return getViewLabel( aPlotTab, aTrace->GetYScaleView() );
1177}
1178
1179
1181{
1182 m_signals.clear();
1183
1184 int options = m_simulatorFrame->GetCurrentOptions();
1185 SIM_TYPE simType = m_simulatorFrame->GetCurrentSimType();
1186 wxString unconnected = wxString( wxS( "unconnected-(" ) );
1187
1188 if( simType == ST_UNKNOWN )
1189 simType = ST_TRAN;
1190
1191 unconnected.Replace( '(', '_' ); // Convert to SPICE markup
1192
1193 SIM_PLOT_TAB* curPlotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1194 bool smithMode = curPlotTab && curPlotTab->GetSimType() == ST_SP && curPlotTab->IsSmithMode();
1195
1196 auto addSignal =
1197 [&]( const wxString& aSignalName )
1198 {
1199 if( simType == ST_AC )
1200 {
1201 m_signals.push_back( aSignalName + _( " (gain)" ) );
1202 m_signals.push_back( aSignalName + _( " (phase)" ) );
1203 }
1204 else if( simType == ST_SP )
1205 {
1206 if( smithMode )
1207 {
1208 m_signals.push_back( aSignalName );
1209 }
1210 else
1211 {
1212 m_signals.push_back( aSignalName + _( " (amplitude)" ) );
1213 m_signals.push_back( aSignalName + _( " (phase)" ) );
1214 }
1215 }
1216 else
1217 {
1218 m_signals.push_back( aSignalName );
1219 }
1220 };
1221
1223 && ( simType == ST_TRAN || simType == ST_DC || simType == ST_AC || simType == ST_FFT ) )
1224 {
1225 for( const wxString& netname : circuitModel()->GetNets() )
1226 {
1227 if( netname.IsSameAs( wxS( "GND" ), false ) || netname == "0" || netname.StartsWith( unconnected ) )
1228 continue;
1229
1230 m_netnames.emplace_back( netname );
1231 addSignal( wxString::Format( wxS( "V(%s)" ), netname ) );
1232 }
1233 }
1234
1236 && ( simType == ST_TRAN || simType == ST_DC || simType == ST_AC ) )
1237 {
1238 for( const SPICE_ITEM& item : circuitModel()->GetItems() )
1239 {
1240 // Add all possible currents for the device.
1241 for( const std::string& name : item.model->SpiceGenerator().CurrentNames( item ) )
1242 addSignal( name );
1243 }
1244 }
1245
1247 && ( simType == ST_TRAN || simType == ST_DC ) )
1248 {
1249 for( const SPICE_ITEM& item : circuitModel()->GetItems() )
1250 {
1251 if( item.model->GetPinCount() >= 2 )
1252 {
1253 wxString name = item.model->SpiceGenerator().ItemName( item );
1254 addSignal( wxString::Format( wxS( "P(%s)" ), name ) );
1255 }
1256 }
1257 }
1258
1259 if( simType == ST_NOISE )
1260 {
1261 addSignal( wxS( "inoise_spectrum" ) );
1262 addSignal( wxS( "onoise_spectrum" ) );
1263 }
1264
1265 if( simType == ST_SP )
1266 {
1267 std::vector<std::string> portnums;
1268
1269 for( const SPICE_ITEM& item : circuitModel()->GetItems() )
1270 {
1271 wxString name = item.model->SpiceGenerator().ItemName( item );
1272
1273 // We are only looking for voltage sources in .SP mode
1274 if( !name.StartsWith( "V" ) )
1275 continue;
1276
1277 std::string portnum = "";
1278
1279 if( const SIM_MODEL::PARAM* portnum_param = item.model->FindParam( "portnum" ) )
1280 portnum = SIM_VALUE::ToSpice( portnum_param->value );
1281
1282 if( portnum != "" )
1283 portnums.push_back( portnum );
1284 }
1285
1286 for( const std::string& portnum1 : portnums )
1287 {
1288 for( const std::string& portnum2 : portnums )
1289 {
1290 // the Smith chart only makes sense for reflection parameters (S_i_i), the
1291 // transmission ones are not impedances
1292 if( smithMode && portnum1 != portnum2 )
1293 continue;
1294
1295 addSignal( wxString::Format( wxS( "S_%s_%s" ), portnum1, portnum2 ) );
1296 }
1297 }
1298 }
1299
1300 // Add .SAVE and .PROBE directives
1301 for( const wxString& directive : circuitModel()->GetDirectives() )
1302 {
1303 wxStringTokenizer directivesTokenizer( directive, "\r\n", wxTOKEN_STRTOK );
1304
1305 while( directivesTokenizer.HasMoreTokens() )
1306 {
1307 wxString line = directivesTokenizer.GetNextToken().Upper();
1308 wxString directiveParams;
1309
1310 if( line.StartsWith( wxS( ".SAVE" ), &directiveParams )
1311 || line.StartsWith( wxS( ".PROBE" ), &directiveParams ) )
1312 {
1313 wxStringTokenizer paramsTokenizer( directiveParams, " \t", wxTOKEN_STRTOK );
1314
1315 while( paramsTokenizer.HasMoreTokens() )
1316 addSignal( paramsTokenizer.GetNextToken() );
1317 }
1318 }
1319 }
1320}
1321
1322
1323SIM_TAB* SIMULATOR_FRAME_UI::NewSimTab( const wxString& aSimCommand )
1324{
1325 SIM_TAB* simTab = nullptr;
1326 SIM_TYPE simType = SPICE_CIRCUIT_MODEL::CommandToSimType( aSimCommand );
1327
1328 if( SIM_TAB::IsPlottable( simType ) )
1329 {
1330 SIM_PLOT_TAB* panel = new SIM_PLOT_TAB( aSimCommand, m_plotNotebook );
1331 simTab = panel;
1333 }
1334 else
1335 {
1336 simTab = new SIM_NOPLOT_TAB( aSimCommand, m_plotNotebook );
1337 }
1338
1339 wxString pageTitle( simulator()->TypeToName( simType, true ) );
1340 pageTitle.Prepend( wxString::Format( _( "Analysis %u - " ), static_cast<unsigned int>( ++m_plotNumber ) ) );
1341
1342 m_plotNotebook->AddPage( simTab, pageTitle, true );
1343
1344 return simTab;
1345}
1346
1347
1348void SIMULATOR_FRAME_UI::OnFilterText( wxCommandEvent& aEvent )
1349{
1350 rebuildSignalsGrid( m_filter->GetValue() );
1351}
1352
1353
1354void SIMULATOR_FRAME_UI::OnFilterMouseMoved( wxMouseEvent& aEvent )
1355{
1356#if defined( __WXOSX__ ) // Doesn't work properly on other ports
1357 wxPoint pos = aEvent.GetPosition();
1358 wxRect ctrlRect = m_filter->GetScreenRect();
1359 int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
1360
1361 if( m_filter->IsSearchButtonVisible() && pos.x < buttonWidth )
1362 SetCursor( wxCURSOR_ARROW );
1363 else if( m_filter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
1364 SetCursor( wxCURSOR_ARROW );
1365 else
1366 SetCursor( wxCURSOR_IBEAM );
1367#endif
1368}
1369
1370
1371wxString vectorNameFromSignalId( int aUserDefinedSignalId )
1372{
1373 return wxString::Format( wxS( "user%d" ), aUserDefinedSignalId );
1374}
1375
1376
1381wxString SIMULATOR_FRAME_UI::vectorNameFromSignalName( SIM_PLOT_TAB* aPlotTab, const wxString& aSignalName,
1382 int* aTraceType )
1383{
1384 auto looksLikePower = []( const wxString& aExpression ) -> bool
1385 {
1386 wxString exprUpper = aExpression.Upper();
1387
1388 if( exprUpper.Contains( wxS( ":POWER" ) ) )
1389 return true;
1390
1391 if( exprUpper.Find( '*' ) == wxNOT_FOUND )
1392 return false;
1393
1394 if( !exprUpper.Contains( wxS( "V(" ) ) )
1395 return false;
1396
1397 if( !exprUpper.Contains( wxS( "I(" ) ) )
1398 return false;
1399
1400 return true;
1401 };
1402
1403 std::map<wxString, int> suffixes;
1404 suffixes[_( " (amplitude)" )] = SPT_SP_AMP;
1405 suffixes[_( " (gain)" )] = SPT_AC_GAIN;
1406 suffixes[_( " (phase)" )] = SPT_AC_PHASE;
1407
1408 if( aTraceType )
1409 {
1410 if( aPlotTab && aPlotTab->GetSimType() == ST_NOISE )
1411 {
1412 if( getNoiseSource().Upper().StartsWith( 'I' ) )
1413 *aTraceType = SPT_CURRENT;
1414 else
1415 *aTraceType = SPT_VOLTAGE;
1416 }
1417 else
1418 {
1419 wxUniChar firstChar = aSignalName.Upper()[0];
1420
1421 if( firstChar == 'V' )
1422 *aTraceType = SPT_VOLTAGE;
1423 else if( firstChar == 'I' )
1424 *aTraceType = SPT_CURRENT;
1425 else if( firstChar == 'P' )
1426 *aTraceType = SPT_POWER;
1427 }
1428 }
1429
1430 wxString name = aSignalName;
1431
1432 for( const auto& [candidate, type] : suffixes )
1433 {
1434 if( name.EndsWith( candidate ) )
1435 {
1436 name = name.Left( name.Length() - candidate.Length() );
1437
1438 if( aTraceType )
1439 *aTraceType |= type;
1440
1441 break;
1442 }
1443 }
1444
1445 // smith mode rows carry no suffix, the trace holds the complex reflection coefficient
1446 if( aTraceType && aPlotTab && aPlotTab->GetSimType() == ST_SP && aPlotTab->IsSmithMode()
1447 && !( *aTraceType & ( SPT_SP_AMP | SPT_AC_GAIN | SPT_AC_PHASE ) ) )
1448 {
1449 *aTraceType |= SPT_SP_SMITH;
1450 }
1451
1452 for( const auto& [ id, signal ] : m_userDefinedSignals )
1453 {
1454 if( name == signal )
1455 {
1456 if( aTraceType && looksLikePower( signal ) )
1457 {
1458 int suffixBits = *aTraceType & ( SPT_SP_MASK | SPT_AC_GAIN );
1459 *aTraceType = suffixBits | SPT_POWER;
1460 }
1461
1462 return vectorNameFromSignalId( id );
1463 }
1464 }
1465
1466 return name;
1467};
1468
1469
1471{
1472 if( m_SuppressGridEvents > 0 )
1473 return;
1474
1475 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1476
1477 if( !plotTab )
1478 return;
1479
1480 int row = aEvent.GetRow();
1481 int col = aEvent.GetCol();
1482 wxString text = m_signalsGrid->GetCellValue( row, col );
1483 wxString signalName = m_signalsGrid->GetCellValue( row, COL_SIGNAL_NAME );
1484 int traceType = SPT_UNKNOWN;
1485 wxString vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
1486
1487 if( col == COL_SIGNAL_SHOW )
1488 {
1489 wxArrayString choices = getViewChoices( plotTab );
1490 int choiceIndex = choices.Index( text );
1491
1492 if( choiceIndex <= 0 )
1493 {
1494 plotTab->DeleteTrace( vectorName, traceType );
1495 }
1496 else if( SIM_VIEW* targetView = plotTab->GetView( choiceIndex - 1 ) )
1497 {
1498 TRACE* existing = plotTab->GetTrace( vectorName, traceType );
1499
1500 if( existing && existing->GetView() != targetView )
1501 plotTab->DeleteTrace( existing );
1502
1503 updateTrace( vectorName, traceType, plotTab, nullptr, false, targetView );
1504 }
1505
1506 for( SIM_VIEW* view : plotTab->GetViews() )
1507 view->UpdateAll();
1508
1509 // Update enabled/visible states of other controls
1512 OnModify();
1513 }
1514 else if( col == COL_Y_SCALE )
1515 {
1516 TRACE* trace = plotTab->GetTrace( vectorName, traceType );
1517
1518 if( trace )
1519 {
1520 std::vector<SIM_VIEW*> targetViews = getYScaleTargetViews( plotTab, trace->GetView() );
1521 wxArrayString choices = getYScaleChoices( plotTab, trace->GetView() );
1522 int choiceIndex = choices.Index( text );
1523
1524 if( choiceIndex == 0 )
1525 {
1526 trace->SetYScaleView( trace->GetView() );
1527 }
1528 else if( choiceIndex > 0 && choiceIndex - 1 < (int) targetViews.size() )
1529 {
1530 trace->SetYScaleView( targetViews[choiceIndex - 1] );
1531 }
1532
1533 plotTab->ResetScales( false );
1534
1535 for( SIM_VIEW* view : plotTab->GetViews() )
1536 view->UpdateAll();
1537
1538 OnModify();
1539 }
1540 }
1541 else if( col == COL_SIGNAL_COLOR )
1542 {
1543 KIGFX::COLOR4D color( m_signalsGrid->GetCellValue( row, COL_SIGNAL_COLOR ) );
1544 TRACE* trace = plotTab->GetTrace( vectorName, traceType );
1545
1546 if( trace )
1547 {
1548 trace->SetTraceColour( color.ToColour() );
1549 plotTab->UpdateTraceStyle( trace );
1550 plotTab->UpdatePlotColors();
1551 OnModify();
1552 }
1553 }
1554 else if( col == COL_CURSOR_1 || col == COL_CURSOR_2
1555 || ( std::size( m_cursorFormatsDyn ) > std::size( m_cursorFormats ) && col > COL_CURSOR_2 ) )
1556 {
1557 int id = col == COL_CURSOR_1 ? 1 : 2;
1558
1559 if( col > COL_CURSOR_2 ) // TODO: clean up logic
1560 {
1561 id = col - 3; // enum SIGNALS_GRID_COLUMNS offset for Cursor n (COL_CURSOR_1 - 1)
1562 }
1563
1564 TRACE* activeTrace = nullptr;
1565
1566 if( text == wxS( "1" ) )
1567 {
1568 signalName = m_signalsGrid->GetCellValue( row, COL_SIGNAL_NAME );
1569 vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
1570 activeTrace = plotTab->GetTrace( vectorName, traceType );
1571
1572 if( activeTrace )
1573 plotTab->EnableCursor( activeTrace, id, signalName );
1574
1575 OnModify();
1576 }
1577
1578 // Turn off cursor on other signals.
1579 for( const auto& [name, trace] : plotTab->GetTraces() )
1580 {
1581 if( trace != activeTrace && trace->HasCursor( id ) )
1582 {
1583 plotTab->DisableCursor( trace, id );
1584 OnModify();
1585 }
1586 }
1587
1588 // Update cursor checkboxes (which are really radio buttons)
1590 }
1591}
1592
1593
1595{
1596 if( m_SuppressGridEvents > 0 )
1597 return;
1598
1599 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1600
1601 if( !plotTab )
1602 return;
1603
1604 int row = aEvent.GetRow();
1605 int col = aEvent.GetCol();
1606 wxString text = m_cursorsGrid->GetCellValue( row, col );
1607 wxString cursorName = m_cursorsGrid->GetCellValue( row, COL_CURSOR_NAME );
1608
1609 double value = SPICE_VALUE( text ).ToDouble();
1610
1611 if( col == COL_CURSOR_X )
1612 {
1613 CURSOR* cursor1 = nullptr;
1614 CURSOR* cursor2 = nullptr;
1615
1616 std::vector<CURSOR*> cursorsVec;
1617 cursorsVec.clear();
1618
1619 for( const auto& [name, trace] : plotTab->GetTraces() )
1620 {
1621 if( CURSOR* cursor = trace->GetCursor( 1 ) )
1622 cursor1 = cursor;
1623
1624 if( CURSOR* cursor = trace->GetCursor( 2 ) )
1625 cursor2 = cursor;
1626
1627 int tmp = 3;
1628
1629 if( !cursor1 )
1630 tmp--;
1631 if( !cursor2 )
1632 tmp--;
1633
1634 for( int i = tmp; i < m_customCursorsCnt; i++ )
1635 {
1636 if( CURSOR* cursor = trace->GetCursor( i ) )
1637 {
1638 cursorsVec.emplace_back( cursor );
1639
1640 if( cursorName == ( wxString( "" ) << i ) && cursor )
1641 cursor->SetCoordX( value );
1642 }
1643 }
1644 }
1645
1646 //double value = SPICE_VALUE( text ).ToDouble();
1647
1648 if( cursorName == wxS( "1" ) && cursor1 )
1649 cursor1->SetCoordX( value );
1650 else if( cursorName == wxS( "2" ) && cursor2 )
1651 cursor2->SetCoordX( value );
1652 else if( cursorName == _( "Diff" ) && cursor1 && cursor2 )
1653 cursor2->SetCoordX( cursor1->GetCoords().x + value );
1654
1656 OnModify();
1657 }
1658 else
1659 {
1660 wxFAIL_MSG( wxT( "All other columns are supposed to be read-only!" ) );
1661 }
1662}
1663
1664
1666{
1668 result.FromString( m_measurementsGrid->GetCellValue( aRow, COL_MEASUREMENT_FORMAT ) );
1669 return result;
1670}
1671
1672
1674{
1675 m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_FORMAT, aFormat.ToString() );
1676}
1677
1678
1680{
1681 if( aRow < ( m_measurementsGrid->GetNumberRows() - 1 ) )
1682 m_measurementsGrid->DeleteRows( aRow, 1 );
1683}
1684
1685
1687{
1688 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1689
1690 if( !plotTab )
1691 return;
1692
1693 int row = aEvent.GetRow();
1694 int col = aEvent.GetCol();
1695
1696 if( col == COL_MEASUREMENT )
1697 {
1698 UpdateMeasurement( row );
1700 OnModify();
1701 }
1702 else
1703 {
1704 wxFAIL_MSG( wxT( "All other columns are supposed to be read-only!" ) );
1705 }
1706
1707 // Always leave a single empty row for type-in
1708
1709 int rowCount = static_cast<int>( m_measurementsGrid->GetNumberRows() );
1710 int emptyRows = 0;
1711
1712 for( row = rowCount - 1; row >= 0; row-- )
1713 {
1714 if( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1715 emptyRows++;
1716 else
1717 break;
1718 }
1719
1720 if( emptyRows > 1 )
1721 {
1722 int killRows = emptyRows - 1;
1723 m_measurementsGrid->DeleteRows( rowCount - killRows, killRows );
1724 }
1725 else if( emptyRows == 0 )
1726 {
1727 m_measurementsGrid->AppendRows( 1 );
1728 }
1729}
1730
1731
1732void SIMULATOR_FRAME_UI::OnUpdateUI( wxUpdateUIEvent& event )
1733{
1734 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
1735 {
1736 if( plotTab->GetLegendPosition() != plotTab->m_LastLegendPosition )
1737 {
1738 plotTab->m_LastLegendPosition = plotTab->GetLegendPosition();
1739 OnModify();
1740 }
1741 }
1742}
1743
1744
1760{
1761 static wxRegEx measureParamsRegEx( wxT( "^"
1762 " *"
1763 "([a-zA-Z_]+)"
1764 " +"
1765 "([a-zA-Z]*)\\(([^\\)]+)\\)" ) );
1766
1767 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1768
1769 if( !plotTab )
1770 return;
1771
1772 wxString text = m_measurementsGrid->GetCellValue( aRow, COL_MEASUREMENT );
1773
1774 if( text.IsEmpty() )
1775 {
1776 m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_VALUE, wxEmptyString );
1777 return;
1778 }
1779
1780 wxString simType = simulator()->TypeToName( plotTab->GetSimType(), true );
1781 wxString resultName = wxString::Format( wxS( "meas_result_%u" ), aRow );
1782 wxString result = wxS( "?" );
1783
1784 if( measureParamsRegEx.Matches( text ) )
1785 {
1786 wxString func = measureParamsRegEx.GetMatch( text, 1 ).Upper();
1787 wxString signalType = measureParamsRegEx.GetMatch( text, 2 ).Upper();
1788 wxString deviceName = measureParamsRegEx.GetMatch( text, 3 );
1789 wxString units;
1791
1792 if( signalType.EndsWith( wxS( "DB" ) ) )
1793 {
1794 units = wxS( "dB" );
1795 }
1796 else if( signalType.StartsWith( 'I' ) )
1797 {
1798 units = wxS( "A" );
1799 }
1800 else if( signalType.StartsWith( 'P' ) )
1801 {
1802 units = wxS( "W" );
1803 // Our syntax is different from ngspice for power signals
1804 text = func + " " + deviceName + ":power";
1805 }
1806 else
1807 {
1808 units = wxS( "V" );
1809 }
1810
1811 if( func.EndsWith( wxS( "_AT" ) ) )
1812 {
1813 if( plotTab->GetSimType() == ST_AC || plotTab->GetSimType() == ST_SP )
1814 units = wxS( "Hz" );
1815 else
1816 units = wxS( "s" );
1817 }
1818 else if( func.StartsWith( wxS( "INTEG" ) ) )
1819 {
1820 switch( plotTab->GetSimType() )
1821 {
1822 case ST_TRAN:
1823 if ( signalType.StartsWith( 'P' ) )
1824 units = wxS( "J" );
1825 else
1826 units += wxS( ".s" );
1827
1828 break;
1829
1830 case ST_AC:
1831 case ST_SP:
1832 case ST_DISTO:
1833 case ST_NOISE:
1834 case ST_FFT:
1835 case ST_SENS: // If there is a vector, it is frequency
1836 units += wxS( "·Hz" );
1837 break;
1838
1839 case ST_DC: // Could be a lot of things : V, A, deg C, ohm, ...
1840 case ST_OP: // There is no vector for integration
1841 case ST_PZ: // There is no vector for integration
1842 case ST_TF: // There is no vector for integration
1843 default:
1844 units += wxS( "·?" );
1845 break;
1846 }
1847 }
1848
1849 fmt.UpdateUnits( units );
1850 SetMeasureFormat( aRow, fmt );
1851
1853 }
1854
1855 if( m_simulatorFrame->SimFinished() )
1856 {
1857 wxString cmd = wxString::Format( wxS( "meas %s %s %s" ), simType, resultName, text );
1858 simulator()->Command( "echo " + cmd.ToStdString() );
1859 simulator()->Command( cmd.ToStdString() );
1860
1861 std::vector<double> resultVec = simulator()->GetGainVector( resultName.ToStdString() );
1862
1863 if( resultVec.size() > 0 )
1864 result = SPICE_VALUE( resultVec[0] ).ToString( GetMeasureFormat( aRow ) );
1865 }
1866
1867 m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_VALUE, result );
1868}
1869
1870
1871void SIMULATOR_FRAME_UI::AddTuner( const SCH_SHEET_PATH& aSheetPath, SCH_SYMBOL* aSymbol )
1872{
1873 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1874
1875 if( !plotTab )
1876 {
1877 DisplayErrorMessage( nullptr, _( "The current analysis must have a plot in order to tune "
1878 "the value of a passive R, L, C model or voltage or "
1879 "current source." ) );
1880 return;
1881 }
1882
1883 wxString ref = aSymbol->GetRef( &aSheetPath );
1884
1885 // Do not add multiple instances for the same component.
1886 for( TUNER_SLIDER* tuner : m_tuners )
1887 {
1888 if( tuner->GetSymbolRef() == ref )
1889 return;
1890 }
1891
1892 if( [[maybe_unused]] const SPICE_ITEM* item = GetExporter()->FindItem( ref ) )
1893 {
1894 try
1895 {
1896 TUNER_SLIDER* tuner = new TUNER_SLIDER( this, m_panelTuners, aSheetPath, aSymbol );
1897 m_sizerTuners->Add( tuner );
1898 m_tuners.push_back( tuner );
1899 m_panelTuners->Layout();
1900 OnModify();
1901 }
1902 catch( const KI_PARAM_ERROR& e )
1903 {
1904 DisplayErrorMessage( nullptr, e.What() );
1905 }
1906 }
1907}
1908
1909
1910void SIMULATOR_FRAME_UI::UpdateTunerValue( const SCH_SHEET_PATH& aSheetPath, const KIID& aSymbol, const wxString& aRef,
1911 const wxString& aValue )
1912{
1913 SCHEMATIC& schematic = m_schematicFrame->Schematic();
1914 wxString variant = schematic.GetCurrentVariant();
1915 SCH_ITEM* item = aSheetPath.ResolveItem( aSymbol );
1916 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( item );
1917
1918 if( !symbol )
1919 {
1920 DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( " " )
1921 + wxString::Format( _( "%s not found" ), aRef ) );
1922 return;
1923 }
1924
1925 NULL_REPORTER devnull;
1926 SIM_LIB_MGR mgr( &m_schematicFrame->Prj() );
1927
1928 std::vector<EMBEDDED_FILES*> embeddedFilesStack;
1929 embeddedFilesStack.push_back( m_schematicFrame->Schematic().GetEmbeddedFiles() );
1930
1931 if( EMBEDDED_FILES* symbolEmbeddedFiles = symbol->GetEmbeddedFiles() )
1932 {
1933 embeddedFilesStack.push_back( symbolEmbeddedFiles );
1934 symbol->GetLibSymbolRef()->AppendParentEmbeddedFiles( embeddedFilesStack );
1935 }
1936
1937 mgr.SetFilesStack( std::move( embeddedFilesStack ) );
1938
1939 SIM_MODEL& model = mgr.CreateModel( &aSheetPath, *symbol, true, 0, variant, devnull ).model;
1940
1941 const SIM_MODEL::PARAM* tunerParam = model.GetTunerParam();
1942
1943 if( !tunerParam )
1944 {
1945 DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( " " )
1946 + wxString::Format( _( "%s is not tunable" ), aRef ) );
1947 return;
1948 }
1949
1950 model.SetParamValue( tunerParam->info.name, std::string( aValue.ToUTF8() ) );
1951 model.WriteFields( symbol->GetFields(), &aSheetPath, variant );
1952
1953 m_schematicFrame->UpdateItem( symbol, false, true );
1954 m_schematicFrame->OnModify();
1955}
1956
1957
1959{
1960 m_tuners.remove( aTuner );
1961
1962 if( std::find( m_multiRunState.tuners.begin(), m_multiRunState.tuners.end(), aTuner )
1963 != m_multiRunState.tuners.end() )
1964 {
1965 clearMultiRunState( true );
1966 }
1967
1968 m_tunerOverrides.erase( aTuner );
1969
1970 aTuner->Destroy();
1971 m_panelTuners->Layout();
1972 OnModify();
1973}
1974
1975
1976void SIMULATOR_FRAME_UI::AddMeasurement( const wxString& aCmd )
1977{
1978 // -1 because the last one is for user input
1979 for( int i = 0; i < m_measurementsGrid->GetNumberRows(); i++ )
1980 {
1981 if( m_measurementsGrid->GetCellValue( i, COL_MEASUREMENT ) == aCmd )
1982 return; // Don't create duplicates
1983 }
1984
1985 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1986
1987 if( !plotTab )
1988 return;
1989
1990 int row;
1991
1992 for( row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
1993 {
1994 if( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1995 break;
1996 }
1997
1998 if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1999 {
2000 m_measurementsGrid->AppendRows( 1 );
2001 row = m_measurementsGrid->GetNumberRows() - 1;
2002 }
2003
2004 m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT, aCmd );
2005
2006 UpdateMeasurement( row );
2008 OnModify();
2009
2010 // Always leave at least one empty row for type-in:
2011 row = m_measurementsGrid->GetNumberRows() - 1;
2012
2013 if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
2014 m_measurementsGrid->AppendRows( 1 );
2015}
2016
2017
2018void SIMULATOR_FRAME_UI::DoFourier( const wxString& aSignal, const wxString& aFundamental )
2019{
2020 wxString cmd = wxString::Format( wxS( "fourier %s %s" ),
2021 SPICE_VALUE( aFundamental ).ToSpiceString(),
2022 aSignal );
2023
2024 simulator()->Command( cmd.ToStdString() );
2025}
2026
2027
2029{
2030 return circuitModel().get();
2031}
2032
2033
2034void SIMULATOR_FRAME_UI::AddTrace( const wxString& aName, SIM_TRACE_TYPE aType )
2035{
2036 if( !GetCurrentSimTab() )
2037 {
2038 m_simConsole->AppendText( _( "Error: no current simulation.\n" ) );
2039 m_simConsole->SetInsertionPointEnd();
2040 return;
2041 }
2042
2043 SIM_TYPE simType = SPICE_CIRCUIT_MODEL::CommandToSimType( GetCurrentSimTab()->GetSimCommand() );
2044
2045 if( simType == ST_UNKNOWN )
2046 {
2047 m_simConsole->AppendText( _( "Error: simulation type not defined.\n" ) );
2048 m_simConsole->SetInsertionPointEnd();
2049 return;
2050 }
2051 else if( !SIM_TAB::IsPlottable( simType ) )
2052 {
2053 m_simConsole->AppendText( _( "Error: simulation type doesn't support plotting.\n" ) );
2054 m_simConsole->SetInsertionPointEnd();
2055 return;
2056 }
2057
2058 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
2059 {
2060 if( simType == ST_AC )
2061 {
2062 updateTrace( aName, aType | SPT_AC_GAIN, plotTab );
2063 updateTrace( aName, aType | SPT_AC_PHASE, plotTab );
2064 }
2065 else if( simType == ST_SP )
2066 {
2067 if( plotTab->IsSmithMode() )
2068 {
2069 updateTrace( aName, aType | SPT_SP_SMITH, plotTab );
2070 }
2071 else
2072 {
2073 updateTrace( aName, aType | SPT_SP_AMP, plotTab );
2074 updateTrace( aName, aType | SPT_AC_PHASE, plotTab );
2075 }
2076 }
2077 else
2078 {
2079 updateTrace( aName, aType, plotTab );
2080 }
2081
2082 for( SIM_VIEW* view : plotTab->GetViews() )
2083 view->UpdateAll();
2084 }
2085
2087 OnModify();
2088}
2089
2090
2091void SIMULATOR_FRAME_UI::SetUserDefinedSignals( const std::map<int, wxString>& aNewSignals )
2092{
2093 for( size_t ii = 0; ii < m_plotNotebook->GetPageCount(); ++ii )
2094 {
2095 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( m_plotNotebook->GetPage( ii ) );
2096
2097 if( !plotTab )
2098 continue;
2099
2100 for( const auto& [id, existingSignal] : m_userDefinedSignals )
2101 {
2102 int traceType = SPT_UNKNOWN;
2103 wxString vectorName = vectorNameFromSignalName( plotTab, existingSignal, &traceType );
2104
2105 if( aNewSignals.count( id ) == 0 )
2106 {
2107 if( plotTab->GetSimType() == ST_AC )
2108 {
2109 for( int subType : { SPT_AC_GAIN, SPT_AC_PHASE } )
2110 plotTab->DeleteTrace( vectorName, traceType | subType );
2111 }
2112 else if( plotTab->GetSimType() == ST_SP )
2113 {
2114 for( int subType : { SPT_SP_AMP, SPT_AC_PHASE, SPT_SP_SMITH } )
2115 plotTab->DeleteTrace( vectorName, traceType | subType );
2116 }
2117 else
2118 {
2119 plotTab->DeleteTrace( vectorName, traceType );
2120 }
2121 }
2122 else
2123 {
2124 if( plotTab->GetSimType() == ST_AC )
2125 {
2126 for( int subType : { SPT_AC_GAIN, SPT_AC_PHASE } )
2127 {
2128 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType | subType ) )
2129 trace->SetName( aNewSignals.at( id ) );
2130 }
2131 }
2132 else if( plotTab->GetSimType() == ST_SP )
2133 {
2134 for( int subType : { SPT_SP_AMP, SPT_AC_PHASE, SPT_SP_SMITH } )
2135 {
2136 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType | subType ) )
2137 trace->SetName( aNewSignals.at( id ) );
2138 }
2139 }
2140 else
2141 {
2142 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
2143 trace->SetName( aNewSignals.at( id ) );
2144 }
2145 }
2146 }
2147 }
2148
2149 m_userDefinedSignals = aNewSignals;
2150
2151 if( m_simulatorFrame->SimFinished() )
2153
2155 rebuildSignalsGrid( m_filter->GetValue() );
2158 OnModify();
2159}
2160
2161
2162double SIMULATOR_FRAME_UI::getSmithPortImpedance( const wxString& aVectorName )
2163{
2164 long responsePort, drivePort;
2165
2166 // normalize to the response port z0
2167 if( !SMITH_MATH::ParseSParamPorts( aVectorName, &responsePort, &drivePort ) )
2168 return 0.0;
2169
2170 for( const SPICE_ITEM& item : circuitModel()->GetItems() )
2171 {
2172 if( !item.model )
2173 continue;
2174
2175 const SIM_MODEL::PARAM* portnumParam = item.model->FindParam( "portnum" );
2176 long portnum = 0;
2177
2178 if( !portnumParam
2179 || !wxString( SIM_VALUE::ToSpice( portnumParam->value ) ).ToLong( &portnum )
2180 || portnum != responsePort )
2181 {
2182 continue;
2183 }
2184
2185 if( const SIM_MODEL::PARAM* z0Param = item.model->FindParam( "z0" ) )
2186 {
2187 double z0 = SIM_VALUE::ToDouble( z0Param->value, 50.0 );
2188
2189 if( z0 > 0 )
2190 return z0;
2191 }
2192
2193 // this item has no usable z0, another item may still carry it for this port
2194 }
2195
2196 return 0.0;
2197}
2198
2199
2200void SIMULATOR_FRAME_UI::updateTrace( const wxString& aVectorName, int aTraceType, SIM_PLOT_TAB* aPlotTab,
2201 std::vector<double>* aDataX, bool aClearData, SIM_VIEW* aView )
2202{
2203 if( !aView )
2204 {
2205 TRACE* existing = aPlotTab->GetTrace( aVectorName, aTraceType );
2206 aView = existing && existing->GetView() ? existing->GetView() : aPlotTab->GetDefaultView();
2207 }
2208
2209 if( !m_simulatorFrame->SimFinished() && !simulator()->IsRunning() )
2210 {
2211 aPlotTab->GetOrAddTrace( aVectorName, aTraceType, aView );
2212 return;
2213 }
2214
2216
2217 aTraceType &= aTraceType & SPT_Y_AXIS_MASK;
2218 aTraceType |= getXAxisType( simType );
2219
2220 wxString simVectorName = aVectorName;
2221
2222 if( aTraceType & SPT_POWER )
2223 simVectorName = simVectorName.AfterFirst( '(' ).BeforeLast( ')' ) + wxS( ":power" );
2224
2225 if( !SIM_TAB::IsPlottable( simType ) )
2226 {
2227 // There is no plot to be shown
2228 simulator()->Command( wxString::Format( wxT( "print %s" ), aVectorName ).ToStdString() );
2229
2230 return;
2231 }
2232
2233 std::vector<double> data_x;
2234 std::vector<double> data_y;
2235 std::vector<double> frequencies;
2236
2237 if( !aDataX || aClearData )
2238 aDataX = &data_x;
2239
2240 // First, handle the x axis
2241 if( aDataX->empty() && !aClearData )
2242 {
2243 wxString xAxisName( simulator()->GetXAxis( simType ) );
2244
2245 if( xAxisName.IsEmpty() )
2246 return;
2247
2248 *aDataX = simulator()->GetGainVector( (const char*) xAxisName.c_str() );
2249 }
2250
2251 unsigned int size = aDataX->size();
2252
2253 switch( simType )
2254 {
2255 case ST_AC:
2256 if( aTraceType & SPT_AC_GAIN )
2257 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
2258 else if( aTraceType & SPT_AC_PHASE )
2259 data_y = simulator()->GetPhaseVector( (const char*) simVectorName.c_str(), size );
2260 else
2261 wxFAIL_MSG( wxT( "Plot type missing AC_PHASE or AC_MAG bit" ) );
2262
2263 break;
2264 case ST_SP:
2265 if( aTraceType & SPT_SP_SMITH )
2266 {
2267 // reflection coefficient locus, Re on X and Im on Y, frequency kept aside
2268 frequencies = *aDataX;
2269 data_y = simulator()->GetImaginaryVector( (const char*) simVectorName.c_str(), size );
2270 *aDataX = simulator()->GetRealVector( (const char*) simVectorName.c_str(), size );
2271 }
2272 else if( aTraceType & SPT_SP_AMP )
2273 {
2274 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
2275 }
2276 else if( aTraceType & SPT_AC_PHASE )
2277 {
2278 data_y = simulator()->GetPhaseVector( (const char*) simVectorName.c_str(), size );
2279 }
2280 else
2281 {
2282 wxFAIL_MSG( wxT( "Plot type missing AC_PHASE, SPT_SP_AMP or SPT_SP_SMITH bit" ) );
2283 }
2284
2285 break;
2286
2287 case ST_DC:
2288 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), -1 );
2289 break;
2290
2291 case ST_NOISE:
2292 case ST_TRAN:
2293 case ST_FFT:
2294 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
2295 break;
2296
2297 default:
2298 wxFAIL_MSG( wxT( "Unhandled plot type" ) );
2299 }
2300
2301 SPICE_DC_PARAMS source1, source2;
2302 int sweepCount = 1;
2303 size_t sweepSize = std::numeric_limits<size_t>::max();
2304
2305 if( simType == ST_DC
2306 && circuitModel()->ParseDCCommand( aPlotTab->GetSimCommand(), &source1, &source2 )
2307 && !source2.m_source.IsEmpty() )
2308 {
2309 SPICE_VALUE v = ( source2.m_vend - source2.m_vstart ) / source2.m_vincrement;
2310
2311 sweepCount = KiROUND( v.ToDouble() ) + 1;
2312 sweepSize = aDataX->size() / sweepCount;
2313 }
2314
2315 bool smithTrace = ( aTraceType & SPT_SP_SMITH ) > 0;
2316
2317 if( m_multiRunState.storePending )
2318 recordMultiRunData( aVectorName, aTraceType, *aDataX, data_y );
2319
2320 if( hasMultiRunTrace( aVectorName, aTraceType ) )
2321 {
2322 const std::string key = multiRunTraceKey( aVectorName, aTraceType );
2323 const auto traceIt = m_multiRunState.traces.find( key );
2324
2325 if( traceIt != m_multiRunState.traces.end() )
2326 {
2327 const MULTI_RUN_TRACE& traceData = traceIt->second;
2328
2329 if( !traceData.xValues.empty() && !traceData.yValues.empty() )
2330 {
2331 size_t sweepSizeMulti = traceData.xValues.size();
2332 size_t runCount = traceData.yValues.size();
2333
2334 if( sweepSizeMulti > 0 && runCount > 0 )
2335 {
2336 std::vector<double> combinedX;
2337 std::vector<double> combinedY;
2338 std::vector<double> combinedFreq;
2339
2340 combinedX.reserve( sweepSizeMulti * runCount );
2341 combinedY.reserve( sweepSizeMulti * runCount );
2342
2343 for( size_t run = 0; run < traceData.yValues.size(); run++ )
2344 {
2345 const std::vector<double>& runY = traceData.yValues[run];
2346
2347 if( runY.size() != sweepSizeMulti )
2348 continue;
2349
2350 if( smithTrace )
2351 {
2352 // per-run x, Re(gamma) differs between runs
2353 if( run >= traceData.xRuns.size() || traceData.xRuns[run].size() != sweepSizeMulti )
2354 continue;
2355
2356 combinedX.insert( combinedX.end(), traceData.xRuns[run].begin(),
2357 traceData.xRuns[run].end() );
2358 }
2359 else
2360 {
2361 combinedX.insert( combinedX.end(), traceData.xValues.begin(), traceData.xValues.end() );
2362 }
2363
2364 combinedY.insert( combinedY.end(), runY.begin(), runY.end() );
2365
2366 // the sweep frequencies are the same every run, repeat them per run
2367 if( frequencies.size() == sweepSizeMulti )
2368 combinedFreq.insert( combinedFreq.end(), frequencies.begin(), frequencies.end() );
2369 }
2370
2371 if( TRACE* trace = aPlotTab->GetOrAddTrace( aVectorName, aTraceType, aView ) )
2372 {
2373 if( SMITH_TRACE* smith = dynamic_cast<SMITH_TRACE*>( trace ) )
2374 {
2375 smith->SetReferenceImpedance( getSmithPortImpedance( aVectorName ) );
2377
2378 if( combinedFreq.size() == combinedX.size() )
2379 smith->SetFrequencies( combinedFreq );
2380 }
2381
2382 if( combinedY.size() >= combinedX.size() && sweepSizeMulti > 0 )
2383 {
2384 int sweepCountCombined =
2385 combinedX.empty() ? 0 : static_cast<int>( combinedY.size() / sweepSizeMulti );
2386
2387 if( sweepCountCombined > 0 )
2388 {
2389 // Generate labels for each run based on tuner values
2390 std::vector<wxString> labels;
2391 labels.reserve( sweepCountCombined );
2392
2393 for( int i = 0; i < sweepCountCombined && i < (int) m_multiRunState.steps.size(); ++i )
2394 {
2395 const MULTI_RUN_STEP& step = m_multiRunState.steps[i];
2396 wxString label;
2397
2398 for( auto it = step.overrides.begin(); it != step.overrides.end(); ++it )
2399 {
2400 if( it != step.overrides.begin() )
2401 label += wxS( ", " );
2402
2403 const TUNER_SLIDER* tuner = it->first;
2404 double value = it->second;
2405
2406 SPICE_VALUE spiceVal( value );
2407 label += tuner->GetSymbolRef() + wxS( "=" ) + spiceVal.ToSpiceString();
2408 }
2409
2410 labels.push_back( label );
2411 }
2412
2413 aPlotTab->SetTraceData( trace, combinedX, combinedY, sweepCountCombined, sweepSizeMulti,
2414 true, labels );
2415 }
2416 }
2417 }
2418
2419 return;
2420 }
2421 }
2422 }
2423 }
2424
2425 if( TRACE* trace = aPlotTab->GetOrAddTrace( aVectorName, aTraceType, aView ) )
2426 {
2427 if( data_y.size() >= size )
2428 {
2429 if( SMITH_TRACE* smith = dynamic_cast<SMITH_TRACE*>( trace ) )
2430 {
2431 smith->SetFrequencies( frequencies );
2432 smith->SetReferenceImpedance( getSmithPortImpedance( aVectorName ) );
2434 }
2435
2436 aPlotTab->SetTraceData( trace, *aDataX, data_y, sweepCount, sweepSize );
2437 }
2438 }
2439}
2440
2441
2442// TODO make sure where to instantiate and how to style correct
2443// Better ask someone..
2444template
2446
2447template <typename T, typename U, typename R>
2449 T t, U u, R r ) // t=cursor type/signals' grid col, u=cursor number/cursor "id", r=table's row
2450{
2451 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
2452 wxString signalName = m_signalsGrid->GetCellValue( r, COL_SIGNAL_NAME );
2453 int traceType = SPT_UNKNOWN;
2454 wxString vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
2455
2456 wxGridCellAttrPtr attr = m_signalsGrid->GetOrCreateCellAttrPtr( r, static_cast<int>( t ) );
2457
2458 TRACE* trace = plotTab ? plotTab->GetTrace( vectorName, traceType ) : nullptr;
2459
2460 // A trace with no view is an orphan (its view was removed); treat it as not plotted so it
2461 // shows the same disabled styling as a never-plotted signal.
2462 bool plotted = trace && trace->GetView();
2463
2464 if( plotted )
2465 {
2466 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
2467
2469 {
2470 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
2471 }
2472
2473 if constexpr( std::is_enum<T>::value )
2474 {
2476 {
2477 if( !attr->HasEditor() )
2478 attr->SetEditor( new GRID_CELL_COMBOBOX( getViewChoices( plotTab ) ) );
2479
2480 if( !attr->HasRenderer() )
2481 attr->SetRenderer( new GRID_CELL_COMBOBOX_RENDERER() );
2482
2483 attr->SetReadOnly( false );
2484 attr->SetTextColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT ) );
2485 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), getViewLabel( plotTab, trace->GetView() ) );
2486 }
2487 else if( t == SIGNALS_GRID_COLUMNS::COL_Y_SCALE )
2488 {
2489 if( !attr->HasEditor() )
2490 attr->SetEditor( new GRID_CELL_COMBOBOX( getYScaleChoices( plotTab, trace->GetView() ) ) );
2491
2492 if( !attr->HasRenderer() )
2493 attr->SetRenderer( new GRID_CELL_COMBOBOX_RENDERER() );
2494
2495 attr->SetReadOnly( false );
2496 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), getYScaleLabel( plotTab, trace ) );
2497 }
2499 {
2500 if( !attr->HasRenderer() )
2501 attr->SetRenderer( new GRID_CELL_COLOR_RENDERER( this ) );
2502
2503 if( !attr->HasEditor() )
2504 attr->SetEditor( new GRID_CELL_COLOR_SELECTOR( this, m_signalsGrid ) );
2505
2506 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
2507 attr->SetReadOnly( false );
2508
2509 KIGFX::COLOR4D color( trace->GetPen().GetColour() );
2510 m_signalsGrid->SetCellValue( r, COL_SIGNAL_COLOR, color.ToCSSString() );
2511 }
2515 {
2516 if( !attr->HasRenderer() )
2517 attr->SetRenderer( new wxGridCellBoolRenderer() );
2518
2519 if( u > 0 && trace->HasCursor( u ) )
2520 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), wxS( "1" ) );
2521 else
2522 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), wxEmptyString );
2523 }
2524 }
2525 }
2526 else
2527 {
2528 if constexpr( std::is_enum<T>::value )
2529 {
2531 {
2532 if( !attr->HasEditor() )
2533 attr->SetEditor( new GRID_CELL_COMBOBOX( getViewChoices( plotTab ) ) );
2534
2535 if( !attr->HasRenderer() )
2536 attr->SetRenderer( new GRID_CELL_COMBOBOX_RENDERER() );
2537
2538 attr->SetReadOnly( false );
2539 attr->SetTextColour( *wxLIGHT_GREY );
2540 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), getViewLabel( plotTab, nullptr ) );
2541 }
2547 {
2548 attr->SetEditor( nullptr );
2549 attr->SetRenderer( nullptr );
2550 attr->SetReadOnly();
2551 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), wxEmptyString );
2552 }
2553 }
2554 }
2555}
2556
2557
2559{
2560 for( int row = 0; row < m_signalsGrid->GetNumberRows(); ++row )
2561 {
2567
2568 if( ( m_signalsGrid->GetNumberCols() - 1 ) > COL_CURSOR_2 )
2569 {
2570 for( int i = 3; i < m_customCursorsCnt; i++ )
2571 {
2572 int tm = i + 3;
2573 signalsGridCursorUpdate( static_cast<SIGNALS_GRID_COLUMNS>( tm ), i, row );
2574 }
2575 }
2576 }
2577 m_signalsGrid->Refresh();
2578}
2579
2580
2582{
2583 auto quoteNetNames =
2584 [&]( wxString aExpression ) -> wxString
2585 {
2586 std::vector<bool> mask( aExpression.length(), false );
2587
2588 auto isNetnameChar =
2589 []( wxUniChar aChar ) -> bool
2590 {
2591 wxUint32 value = aChar.GetValue();
2592
2593 if( ( value >= '0' && value <= '9' ) || ( value >= 'A' && value <= 'Z' )
2594 || ( value >= 'a' && value <= 'z' ) )
2595 {
2596 return true;
2597 }
2598
2599 switch( value )
2600 {
2601 case '_':
2602 case '/':
2603 case '+':
2604 case '-':
2605 case '~':
2606 case '.':
2607 return true;
2608 default:
2609 break;
2610 }
2611
2612 return false;
2613 };
2614
2615 for( const wxString& netname : m_netnames )
2616 {
2617 size_t pos = aExpression.find( netname );
2618
2619 while( pos != wxString::npos )
2620 {
2621 for( size_t i = 0; i < netname.length(); ++i )
2622 mask[pos + i] = true; // Mark the positions of the netname
2623
2624 pos = aExpression.find( netname, pos + 1 ); // Find the next occurrence
2625 }
2626 }
2627
2628 for( size_t i = 0; i < aExpression.length(); ++i )
2629 {
2630 if( !mask[i] || ( i > 0 && mask[i - 1] ) )
2631 continue;
2632
2633 size_t j = i + 1;
2634
2635 while( j < aExpression.length() )
2636 {
2637 if( mask[j] )
2638 {
2639 ++j;
2640 continue;
2641 }
2642
2643 if( isNetnameChar( aExpression[j] ) )
2644 {
2645 mask[j] = true;
2646 ++j;
2647 }
2648 else
2649 {
2650 break;
2651 }
2652 }
2653 }
2654
2655 wxString quotedNetnames = "";
2656 bool startQuote = true;
2657
2658 // put quotes around all the positions that were found above
2659 for( size_t i = 0; i < aExpression.length(); i++ )
2660 {
2661 if( mask[i] && startQuote )
2662 {
2663 quotedNetnames = quotedNetnames + "\"";
2664 startQuote = false;
2665 }
2666 else if( !mask[i] && !startQuote )
2667 {
2668 quotedNetnames = quotedNetnames + "\"";
2669 startQuote = true;
2670 }
2671
2672 wxString ch = aExpression[i];
2673 quotedNetnames = quotedNetnames + ch;
2674 }
2675
2676 if( !startQuote )
2677 quotedNetnames = quotedNetnames + "\"";
2678
2679 return quotedNetnames;
2680 };
2681
2682 for( const auto& [id, signal] : m_userDefinedSignals )
2683 {
2684 constexpr const char* cmd = "let user{} = {}";
2685
2686 simulator()->Command( "echo " + fmt::format( cmd, id, signal.ToStdString() ) );
2687 simulator()->Command( fmt::format( cmd, id, quoteNetNames( signal ).ToStdString() ) );
2688 }
2689}
2690
2691
2693{
2695
2696 for( const TUNER_SLIDER* tuner : m_tuners )
2697 {
2698 SCH_SHEET_PATH sheetPath;
2699 wxString ref = tuner->GetSymbolRef();
2700 KIID symbolId = tuner->GetSymbol( &sheetPath );
2701 SCH_ITEM* schItem = sheetPath.ResolveItem( symbolId );
2702 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( schItem );
2703
2704 if( !symbol )
2705 {
2706 reporter.Report( wxString::Format( _( "%s not found" ), ref ) );
2707 continue;
2708 }
2709
2710 const SPICE_ITEM* item = GetExporter()->FindItem( tuner->GetSymbolRef() );
2711
2712 if( !item || !item->model->GetTunerParam() )
2713 {
2714 reporter.Report( wxString::Format( _( "%s is not tunable" ), ref ) );
2715 continue;
2716 }
2717
2718 double floatVal;
2719
2720 auto overrideIt = m_tunerOverrides.find( tuner );
2721
2722 if( overrideIt != m_tunerOverrides.end() )
2723 floatVal = overrideIt->second;
2724 else
2725 floatVal = tuner->GetValue().ToDouble();
2726
2727 simulator()->Command( item->model->SpiceGenerator().TunerCommand( *item, floatVal ) );
2728 }
2729
2730 if( reporter.HasMessage() )
2731 DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( "\n" ) + reporter.GetMessages() );
2732}
2733
2734bool SIMULATOR_FRAME_UI::LoadWorkbook( const wxString& aPath )
2735{
2736 wxTextFile file( aPath );
2737
2738 if( !file.Open() )
2739 return false;
2740
2741 wxString firstLine = file.GetFirstLine();
2742 long dummy;
2743 bool legacy = firstLine.StartsWith( wxT( "version " ) ) || firstLine.ToLong( &dummy );
2744
2745 file.Close();
2746
2747 m_plotNotebook->DeleteAllPages();
2748 m_userDefinedSignals.clear();
2749
2750 if( legacy )
2751 {
2752 if( !loadLegacyWorkbook( aPath ) )
2753 return false;
2754 }
2755 else
2756 {
2757 if( !loadJsonWorkbook( aPath ) )
2758 return false;
2759 }
2760
2762
2763 rebuildSignalsGrid( m_filter->GetValue() );
2767
2768 wxFileName filename( aPath );
2769 filename.MakeRelativeTo( m_schematicFrame->Prj().GetProjectPath() );
2770
2771 // Remember the loaded workbook filename.
2772 simulator()->Settings()->SetWorkbookFilename( filename.GetFullPath() );
2773
2774 return true;
2775}
2776
2777
2778bool SIMULATOR_FRAME_UI::loadJsonWorkbook( const wxString& aPath )
2779{
2780 wxFFileInputStream fp( aPath, wxT( "rt" ) );
2781 wxStdInputStream fstream( fp );
2782
2783 if( !fp.IsOk() )
2784 return false;
2785
2786 try
2787 {
2788 nlohmann::json js = nlohmann::json::parse( fstream, nullptr, true, true );
2789
2790 std::map<SIM_PLOT_TAB*, nlohmann::json> traceInfo;
2791
2792 for( const nlohmann::json& tab_js : js["tabs"] )
2793 {
2794 wxString simCommand;
2797
2798 if( !tab_js.contains( "commands" ) || !tab_js["commands"].is_array() )
2799 continue;
2800
2801 for( const nlohmann::json& cmd : tab_js[ "commands" ] )
2802 {
2803 if( cmd == ".kicad adjustpaths" )
2805 else if( cmd == ".save all" )
2807 else if( cmd == ".probe alli" )
2809 else if( cmd == ".probe allp" )
2811 else if( cmd == ".kicad esavenone" )
2812 simOptions &= ~NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_EVENTS;
2813 else
2814 simCommand += wxString( cmd.get<wxString>() ).Trim();
2815 }
2816
2817 SIM_TAB* simTab = NewSimTab( simCommand );
2818 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab );
2819
2820 simTab->SetSimOptions( simOptions );
2821
2822 if( plotTab )
2823 {
2824 int viewCount = tab_js.contains( "viewCount" ) ? (int) tab_js["viewCount"] : 1;
2825
2826 for( int ii = 1; ii < viewCount; ++ii )
2827 plotTab->AddView();
2828
2829 if( tab_js.contains( "traces" ) )
2830 traceInfo[plotTab] = tab_js["traces"];
2831
2832 if( tab_js.contains( "measurements" ) && tab_js["measurements"].is_array() )
2833 {
2834 for( const nlohmann::json& m_js : tab_js[ "measurements" ] )
2835 {
2836 plotTab->Measurements().emplace_back( wxString( m_js.value( "expr", "" ) ),
2837 wxString( m_js.value( "format", "" ) ) );
2838 }
2839 }
2840
2841 plotTab->SetDottedSecondary( tab_js.value( "dottedSecondary", false ) );
2842 plotTab->ShowGrid( tab_js.value( "showGrid", true ) );
2843
2844 if( tab_js.value( "smithMode", false ) )
2845 {
2846 plotTab->SetSmithMode( true );
2847 plotTab->SetSmithView( tab_js.value( "smithZoom", 1.0 ),
2848 tab_js.value( "smithPanX", 0.0 ),
2849 tab_js.value( "smithPanY", 0.0 ) );
2850
2851 if( tab_js.contains( "smithStashedTraces" ) && tab_js["smithStashedTraces"].is_array() )
2852 {
2853 for( const nlohmann::json& stash_js : tab_js["smithStashedTraces"] )
2854 {
2855 wxString vector( stash_js.value( "vector", "" ) );
2856 wxString name( stash_js.value( "name", "" ) );
2857
2858 if( vector.IsEmpty() )
2859 continue;
2860
2861 plotTab->SmithStashedTraces().push_back( { vector,
2862 name.IsEmpty() ? vector : name,
2863 stash_js.value( "base_type", 0 ) } );
2864 }
2865 }
2866
2867 if( tab_js.contains( "smithStashedCursors" ) && tab_js["smithStashedCursors"].is_array() )
2868 {
2869 for( const nlohmann::json& stash_js : tab_js["smithStashedCursors"] )
2870 {
2871 wxString vector( stash_js.value( "vector", "" ) );
2872 int id = stash_js.value( "id", 0 );
2873
2874 if( vector.IsEmpty() || ( id < 1 && id != -1 ) )
2875 continue;
2876
2877 plotTab->SmithStashedCursors().push_back( { id,
2878 vector,
2879 stash_js.value( "base_type", 0 ),
2880 stash_js.value( "sub_type", 0 ),
2881 stash_js.value( "frequency",
2882 std::nan( "" ) ) } );
2883 }
2884 }
2885 }
2886
2887 auto loadScale =
2888 [&]( const char* aKey, auto&& aSetter )
2889 {
2890 if( !tab_js.contains( aKey ) )
2891 return;
2892
2893 // older workbooks can hold null here (non-finite bounds saved as json null)
2894 nlohmann::json min_js = tab_js[aKey].value( "min", nlohmann::json() );
2895 nlohmann::json max_js = tab_js[aKey].value( "max", nlohmann::json() );
2896
2897 if( !min_js.is_number() || !max_js.is_number() )
2898 return;
2899
2900 double min = min_js.get<double>();
2901 double max = max_js.get<double>();
2902
2903 if( min < max )
2904 {
2905 aSetter( min, max );
2906 plotTab->GetPlotWin()->LockY( true );
2907 }
2908 };
2909
2910 loadScale( "fixedY1scale",
2911 [&]( double min, double max )
2912 {
2913 plotTab->SetY1Scale( true, min, max );
2914 } );
2915
2916 loadScale( "fixedY2scale",
2917 [&]( double min, double max )
2918 {
2919 plotTab->SetY2Scale( true, min, max );
2920 } );
2921
2922 loadScale( "fixedY3scale",
2923 [&]( double min, double max )
2924 {
2925 plotTab->EnsureThirdYAxisExists();
2926 plotTab->SetY3Scale( true, min, max );
2927 } );
2928
2929 if( tab_js.contains( "legend" ) )
2930 {
2931 const nlohmann::json& legend_js = tab_js[ "legend" ];
2932 plotTab->SetLegendPosition( wxPoint( legend_js.value( "x", 0 ), legend_js.value( "y", 0 ) ) );
2933 plotTab->ShowLegend( true );
2934 }
2935
2936 if( tab_js.contains( "margins" ) )
2937 {
2938 const nlohmann::json& margins_js = tab_js[ "margins" ];
2939 plotTab->GetPlotWin()->SetMargins( margins_js.value( "top", 30 ),
2940 margins_js.value( "right", 70 ),
2941 margins_js.value( "bottom", 45 ),
2942 margins_js.value( "left", 70 ) );
2943 }
2944 }
2945 }
2946
2947 int ii = 0;
2948
2949 if( js.contains( "user_defined_signals" ) )
2950 {
2951 for( const nlohmann::json& signal_js : js["user_defined_signals"] )
2952 m_userDefinedSignals[ii++] = wxString( signal_js.get<wxString>() );
2953 }
2954
2955 if( SIM_TAB* simTab = GetCurrentSimTab() )
2956 {
2957 m_simulatorFrame->LoadSimulator( simTab->GetSimCommand(), simTab->GetSimOptions() );
2958
2959 if( SIM_TAB* firstTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( 0 ) ) )
2960 firstTab->SetLastSchTextSimCommand( wxString( js.value( "last_sch_text_sim_command", "" ) ) );
2961 }
2962
2963 // 2 is kind of virtual, for the initial loading of the new setting
2964 int tempCustomCursorsCnt = js.value( "custom_cursors", 2 );
2965
2966 if( ( tempCustomCursorsCnt > m_customCursorsCnt ) && m_customCursorsCnt > 2 )
2967 tempCustomCursorsCnt = 2 * tempCustomCursorsCnt - m_customCursorsCnt;
2968
2969 for( int yy = 0; yy <= ( tempCustomCursorsCnt - m_customCursorsCnt ); yy++ )
2971
2972 auto addCursor =
2973 [=, this]( SIM_PLOT_TAB* aPlotTab, TRACE* aTrace, const wxString& aSignalName,
2974 int aCursorId, const nlohmann::json& aCursor_js )
2975 {
2976 if( aCursorId >= 1 )
2977 {
2978 aPlotTab->EnableCursor( aTrace, aCursorId, aSignalName );
2979
2980 // tolerate a missing or null position from an older workbook saved
2981 // with a non-finite cursor coordinate
2982 nlohmann::json position = aCursor_js.value( "position", nlohmann::json() );
2983
2984 if( position.is_number() )
2985 {
2986 if( CURSOR* cursor = aTrace->GetCursor( aCursorId ) )
2987 cursor->SetCoordX( position.get<double>() );
2988 }
2989 }
2990
2991 wxString xFormat( aCursor_js.value( "x_format", "" ) );
2992 wxString yFormat( aCursor_js.value( "y_format", "" ) );
2993 int formatSlot;
2994
2995 if( aCursorId == -1 )
2996 formatSlot = 2; // we are a "cursorD"
2997 else if( aCursorId < 3 )
2998 formatSlot = aCursorId - 1;
2999 else
3000 formatSlot = aCursorId;
3001
3002 if( !xFormat.IsEmpty() )
3003 m_cursorFormatsDyn[formatSlot][0].FromString( xFormat );
3004
3005 if( !yFormat.IsEmpty() )
3006 m_cursorFormatsDyn[formatSlot][1].FromString( yFormat );
3007 };
3008
3009 for( const auto& [plotTab, traces_js] : traceInfo )
3010 {
3011 for( const nlohmann::json& trace_js : traces_js )
3012 {
3013 int traceType = trace_js.value( "trace_type", (int) SPT_UNKNOWN );
3014 wxString signalName( trace_js.value( "signal", "" ) );
3015
3016 if( signalName.IsEmpty() || traceType == SPT_UNKNOWN )
3017 continue;
3018
3019 wxString vectorName = vectorNameFromSignalName( plotTab, signalName, nullptr );
3020
3021 int viewIndex = trace_js.contains( "view" ) ? (int) trace_js["view"] : 0;
3022 SIM_VIEW* view = plotTab->GetView( viewIndex );
3023
3024 if( !view )
3025 view = plotTab->GetDefaultView();
3026
3027 TRACE* trace = plotTab->GetOrAddTrace( vectorName, traceType, view );
3028
3029 if( trace )
3030 {
3031 int yScaleIndex = trace_js.contains( "yScaleView" ) ? (int) trace_js["yScaleView"]
3032 : viewIndex;
3033
3034 if( SIM_VIEW* yScaleView = plotTab->GetView( yScaleIndex ) )
3035 trace->SetYScaleView( yScaleView );
3036
3037 if( trace_js.contains( "cursorD" ) )
3038 addCursor( plotTab, trace, signalName, -1, trace_js["cursorD"] );
3039
3040 std::vector<const char*> aVec;
3041 aVec.clear();
3042
3043 for( int i = 1; i <= tempCustomCursorsCnt; i++ )
3044 {
3045 wxString str = "cursor" + std::to_string( i );
3046 aVec.emplace_back( str.c_str() );
3047
3048 if( trace_js.contains( aVec[i - 1] ) )
3049 addCursor( plotTab, trace, signalName, i, trace_js[aVec[i - 1]] );
3050 }
3051
3052 if( trace_js.contains( "color" ) )
3053 {
3054 wxColour color;
3055 color.Set( wxString( trace_js["color"].get<wxString>() ) );
3056 trace->SetTraceColour( color );
3057 plotTab->UpdateTraceStyle( trace );
3058 }
3059 }
3060 }
3061
3062 plotTab->UpdatePlotColors();
3063 }
3064 }
3065 catch( nlohmann::json::parse_error& error )
3066 {
3067 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ), aPath, error.what() );
3068
3069 return false;
3070 }
3071 catch( nlohmann::json::type_error& error )
3072 {
3073 wxLogTrace( traceSettings, wxT( "Json type error reading %s: %s" ), aPath, error.what() );
3074
3075 return false;
3076 }
3077 catch( nlohmann::json::invalid_iterator& error )
3078 {
3079 wxLogTrace( traceSettings, wxT( "Json invalid_iterator error reading %s: %s" ), aPath, error.what() );
3080
3081 return false;
3082 }
3083 catch( nlohmann::json::out_of_range& error )
3084 {
3085 wxLogTrace( traceSettings, wxT( "Json out_of_range error reading %s: %s" ), aPath, error.what() );
3086
3087 return false;
3088 }
3089 catch( ... )
3090 {
3091 wxLogTrace( traceSettings, wxT( "Error reading %s" ), aPath );
3092 return false;
3093 }
3094
3095 return true;
3096}
3097
3098void SIMULATOR_FRAME_UI::SaveCursorToWorkbook( nlohmann::json& aTraceJs, TRACE* aTrace, int aCursorId )
3099{
3100 int cursorIdAfterD = aCursorId;
3101
3102 if( aCursorId > 3 )
3103 cursorIdAfterD = cursorIdAfterD - 1;
3104
3105
3106 if( CURSOR* cursor = aTrace->GetCursor( aCursorId ) )
3107 {
3108 nlohmann::json cursor_js =
3109 nlohmann::json( { { "x_format", m_cursorFormatsDyn[cursorIdAfterD][0].ToString() },
3110 { "y_format", m_cursorFormatsDyn[cursorIdAfterD][1].ToString() } } );
3111
3112 // json serializes a non-finite double as null, which would poison the load,
3113 // leave the position out instead and the loader keeps its default placement
3114 double position = cursor->GetCoords().x;
3115
3116 if( std::isfinite( position ) )
3117 cursor_js["position"] = position;
3118
3119 aTraceJs["cursor" + wxString( "" ) << aCursorId] = cursor_js;
3120 }
3121
3122 if( cursorIdAfterD < 3 && ( aTrace->GetCursor( 1 ) || aTrace->GetCursor( 2 ) ) )
3123 {
3124 aTraceJs["cursorD"] = nlohmann::json( { { "x_format", m_cursorFormatsDyn[2][0].ToString() },
3125 { "y_format", m_cursorFormatsDyn[2][1].ToString() } } );
3126 }
3127}
3128
3129
3130bool SIMULATOR_FRAME_UI::SaveWorkbook( const wxString& aPath )
3131{
3133
3134 wxFileName filename = aPath;
3135 filename.SetExt( FILEEXT::WorkbookFileExtension );
3136
3137 wxFile file;
3138
3139 file.Create( filename.GetFullPath(), true /* overwrite */ );
3140
3141 if( !file.IsOpened() )
3142 return false;
3143
3144 nlohmann::json tabs_js = nlohmann::json::array();
3145
3146 for( size_t i = 0; i < m_plotNotebook->GetPageCount(); i++ )
3147 {
3148 SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( i ) );
3149
3150 if( !simTab )
3151 continue;
3152
3153 SIM_TYPE simType = simTab->GetSimType();
3154
3155 nlohmann::json commands_js = nlohmann::json::array();
3156
3157 commands_js.push_back( simTab->GetSimCommand() );
3158
3159 int options = simTab->GetSimOptions();
3160
3162 commands_js.push_back( ".kicad adjustpaths" );
3163
3165 commands_js.push_back( ".save all" );
3166
3168 commands_js.push_back( ".probe alli" );
3169
3171 commands_js.push_back( ".probe allp" );
3172
3174 commands_js.push_back( ".kicad esavenone" );
3175
3176 nlohmann::json tab_js = nlohmann::json( { { "analysis", SPICE_SIMULATOR::TypeToName( simType, true ) },
3177 { "commands", commands_js } } );
3178
3179 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab ) )
3180 {
3181 nlohmann::json traces_js = nlohmann::json::array();
3182
3183 auto findSignalName =
3184 [&]( const wxString& aVectorName ) -> wxString
3185 {
3186 wxString vectorName;
3187 wxString suffix;
3188
3189 if( aVectorName.EndsWith( _( " (phase)" ) ) )
3190 suffix = _( " (phase)" );
3191 else if( aVectorName.EndsWith( _( " (gain)" ) ) )
3192 suffix = _( " (gain)" );
3193
3194 vectorName = aVectorName.Left( aVectorName.Length() - suffix.Length() );
3195
3196 for( const auto& [id, signal] : m_userDefinedSignals )
3197 {
3198 if( vectorName == vectorNameFromSignalId( id ) )
3199 return signal + suffix;
3200 }
3201
3202 return aVectorName;
3203 };
3204
3205 for( const auto& [name, trace] : plotTab->GetTraces() )
3206 {
3207 nlohmann::json trace_js =
3208 nlohmann::json( { { "trace_type", (int) trace->GetType() },
3209 { "signal", findSignalName( trace->GetDisplayName() ) },
3210 { "color", COLOR4D( trace->GetTraceColour() ).ToCSSString() },
3211 { "view", plotTab->GetViewIndex( trace->GetView() ) },
3212 { "yScaleView", plotTab->GetViewIndex( trace->GetYScaleView() ) } } );
3213
3214 for( int ii = 1; ii <= m_customCursorsCnt; ii++ )
3215 SaveCursorToWorkbook( trace_js, trace, ii );
3216
3217 if( trace->GetCursor( 1 ) || trace->GetCursor( 2 ) )
3218 {
3219 trace_js["cursorD"] = nlohmann::json( { { "x_format", m_cursorFormatsDyn[2][0].ToString() },
3220 { "y_format", m_cursorFormatsDyn[2][1].ToString() } } );
3221 }
3222
3223 traces_js.push_back( trace_js );
3224 }
3225
3226 nlohmann::json measurements_js = nlohmann::json::array();
3227
3228 for( const auto& [measurement, format] : plotTab->Measurements() )
3229 {
3230 measurements_js.push_back( nlohmann::json( { { "expr", measurement },
3231 { "format", format } } ) );
3232 }
3233
3234 tab_js[ "traces" ] = traces_js;
3235 tab_js[ "measurements" ] = measurements_js;
3236 tab_js[ "dottedSecondary" ] = plotTab->GetDottedSecondary();
3237 tab_js[ "showGrid" ] = plotTab->IsGridShown();
3238 tab_js[ "smithMode" ] = plotTab->IsSmithMode();
3239
3240 if( plotTab->IsSmithMode() )
3241 {
3242 tab_js["smithZoom"] = plotTab->GetSmithZoom();
3243 tab_js["smithPanX"] = plotTab->GetSmithPan().x;
3244 tab_js["smithPanY"] = plotTab->GetSmithPan().y;
3245
3246 // traces and cursors the smith toggle set aside, saved so a workbook
3247 // written in smith mode does not lose them across a reload
3248 nlohmann::json stashedTraces_js = nlohmann::json::array();
3249
3250 for( const SMITH_STASHED_TRACE& stashed : plotTab->SmithStashedTraces() )
3251 {
3252 stashedTraces_js.push_back( nlohmann::json( { { "vector", stashed.vectorName },
3253 { "name", stashed.displayName },
3254 { "base_type", stashed.baseType } } ) );
3255 }
3256
3257 if( !stashedTraces_js.empty() )
3258 tab_js["smithStashedTraces"] = stashedTraces_js;
3259
3260 nlohmann::json stashedCursors_js = nlohmann::json::array();
3261
3262 for( const SMITH_STASHED_CURSOR& stashed : plotTab->SmithStashedCursors() )
3263 {
3264 nlohmann::json cursor_js = nlohmann::json( { { "id", stashed.id },
3265 { "vector", stashed.vectorName },
3266 { "base_type", stashed.baseType },
3267 { "sub_type", stashed.subType } } );
3268
3269 if( std::isfinite( stashed.frequency ) )
3270 cursor_js["frequency"] = stashed.frequency;
3271
3272 stashedCursors_js.push_back( cursor_js );
3273 }
3274
3275 if( !stashedCursors_js.empty() )
3276 tab_js["smithStashedCursors"] = stashedCursors_js;
3277 }
3278
3279 tab_js["viewCount"] = plotTab->GetViewCount();
3280
3281 double min, max;
3282
3283 // json serializes a non-finite double as null, which would poison the load
3284 auto saveScale =
3285 [&]( const char* aKey, double aMin, double aMax )
3286 {
3287 if( std::isfinite( aMin ) && std::isfinite( aMax ) )
3288 tab_js[aKey] = nlohmann::json( { { "min", aMin }, { "max", aMax } } );
3289 };
3290
3291 if( plotTab->GetY1Scale( &min, &max ) )
3292 saveScale( "fixedY1scale", min, max );
3293
3294 if( plotTab->GetY2Scale( &min, &max ) )
3295 saveScale( "fixedY2scale", min, max );
3296
3297 if( plotTab->GetY3Scale( &min, &max ) )
3298 saveScale( "fixedY3scale", min, max );
3299
3300 if( plotTab->IsLegendShown() )
3301 {
3302 tab_js["legend"] = nlohmann::json( { { "x", plotTab->GetLegendPosition().x },
3303 { "y", plotTab->GetLegendPosition().y } } );
3304 }
3305
3306 mpWindow* plotWin = plotTab->GetPlotWin();
3307
3308 tab_js["margins"] = nlohmann::json( { { "left", plotWin->GetMarginLeft() },
3309 { "right", plotWin->GetMarginRight() },
3310 { "top", plotWin->GetMarginTop() },
3311 { "bottom", plotWin->GetMarginBottom() } } );
3312 }
3313
3314 tabs_js.push_back( tab_js );
3315 }
3316
3317 nlohmann::json userDefinedSignals_js = nlohmann::json::array();
3318
3319 for( const auto& [id, signal] : m_userDefinedSignals )
3320 userDefinedSignals_js.push_back( signal );
3321
3322 // clang-format off
3323 nlohmann::json js = nlohmann::json( { { "version", 8 },
3324 { "tabs", tabs_js },
3325 { "user_defined_signals", userDefinedSignals_js },
3326 { "custom_cursors", m_customCursorsCnt - 1 } } ); // Since we start +1 on init
3327 // clang-format on
3328
3329 // Store the value of any simulation command found on the schematic sheet in a SCH_TEXT
3330 // object. If this changes we want to warn the user and ask them if they want to update
3331 // the corresponding panel's sim command.
3332 if( m_plotNotebook->GetPageCount() > 0 )
3333 {
3334 SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( 0 ) );
3335 js["last_sch_text_sim_command"] = simTab->GetLastSchTextSimCommand();
3336 }
3337
3338 std::stringstream buffer;
3339 buffer << std::setw( 2 ) << js << std::endl;
3340
3341 bool res = file.Write( buffer.str() );
3342 file.Close();
3343
3344 // Store the filename of the last saved workbook.
3345 if( res )
3346 {
3347 filename.MakeRelativeTo( m_schematicFrame->Prj().GetProjectPath() );
3348 simulator()->Settings()->SetWorkbookFilename( filename.GetFullPath() );
3349 }
3350
3351 return res;
3352}
3353
3354
3356{
3357 switch( aType )
3358 {
3360 case ST_AC: return SPT_LIN_FREQUENCY;
3361 case ST_SP: return SPT_LIN_FREQUENCY;
3362 case ST_FFT: return SPT_LIN_FREQUENCY;
3363 case ST_DC: return SPT_SWEEP;
3364 case ST_TRAN: return SPT_TIME;
3365 case ST_NOISE: return SPT_LIN_FREQUENCY;
3366
3367 default:
3368 wxFAIL_MSG( wxString::Format( wxS( "Unhandled simulation type: %d" ), (int) aType ) );
3369 return SPT_UNKNOWN;
3370 }
3371}
3372
3373
3375{
3376 wxString output;
3377 wxString ref;
3378 wxString source;
3379 wxString scale;
3380 SPICE_VALUE pts;
3381 SPICE_VALUE fStart;
3382 SPICE_VALUE fStop;
3383 bool saveAll;
3384
3385 if( GetCurrentSimTab() )
3386 {
3387 circuitModel()->ParseNoiseCommand( GetCurrentSimTab()->GetSimCommand(), &output, &ref, &source, &scale, &pts,
3388 &fStart, &fStop, &saveAll );
3389 }
3390
3391 return source;
3392}
3393
3394
3395void SIMULATOR_FRAME_UI::TogglePanel( wxPanel* aPanel, wxSplitterWindow* aSplitterWindow, int& aSashPosition )
3396{
3397 bool isShown = aPanel->IsShown();
3398
3399 if( isShown )
3400 aSashPosition = aSplitterWindow->GetSashPosition();
3401
3402 aPanel->Show( !isShown );
3403
3404 aSplitterWindow->SetSashInvisible( isShown );
3405 aSplitterWindow->SetSashPosition( isShown ? -1 : aSashPosition, true );
3406
3407 aSplitterWindow->UpdateSize();
3408 m_parent->Refresh();
3409 m_parent->Layout();
3410}
3411
3412
3414{
3415 return m_panelConsole->IsShown();
3416}
3417
3418
3423
3424
3426{
3427 return m_sidePanel->IsShown();
3428}
3429
3430
3435
3436
3438{
3440
3441 // Rebuild the color list to plot traces
3443
3444 // Now send changes to all SIM_PLOT_TAB
3445 for( size_t page = 0; page < m_plotNotebook->GetPageCount(); page++ )
3446 {
3447 wxWindow* curPage = m_plotNotebook->GetPage( page );
3448
3449 // ensure it is truly a plot plotTab and not the (zero plots) placeholder
3450 // which is only SIM_TAB
3451 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( curPage );
3452
3453 if( plotTab )
3454 plotTab->UpdatePlotColors();
3455 }
3456}
3457
3458
3460{
3461 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
3462
3463 if( !plotTab || plotTab->GetSimType() != ST_SP )
3464 return;
3465
3466 bool smithMode = !plotTab->IsSmithMode();
3467
3468 // collect the shown signals, then rebuild their traces in the new coordinate system,
3469 // keyed by vector name since user-defined traces are renamed to their display text
3470 struct SHOWN_VECTOR
3471 {
3472 wxString vectorName;
3473 wxString displayName;
3474 int baseType;
3475 SIM_VIEW* view; // the view it was plotted on, so the rebuild stays where it was
3476 };
3477
3478 std::vector<SHOWN_VECTOR> shownVectors;
3479
3480 // remember cursors so they survive the rebuild
3481 struct SAVED_CURSOR
3482 {
3483 int id;
3484 wxString vectorName;
3485 int baseType;
3486 int subType; // the SP subtype the cursor lived on
3487 double coordX;
3488 };
3489
3490 std::vector<SAVED_CURSOR> savedCursors;
3491
3492 for( const auto& [id, trace] : plotTab->GetTraces() )
3493 {
3494 int baseType = SPT_UNKNOWN;
3495 wxString vectorName = vectorNameFromSignalName( plotTab, trace->GetName(), &baseType );
3496 bool seen = false;
3497 int subType = trace->GetType() & ( SPT_SP_AMP | SPT_AC_PHASE | SPT_SP_SMITH );
3498
3499 baseType &= ~( SPT_SP_MASK | SPT_AC_GAIN );
3500
3501 for( const SHOWN_VECTOR& sv : shownVectors )
3502 seen |= sv.vectorName == vectorName;
3503
3504 if( !seen )
3505 shownVectors.push_back( { vectorName, trace->GetName(), baseType, trace->GetView() } );
3506
3507 for( const auto& [cursorId, cursor] : trace->GetCursors() )
3508 {
3509 if( cursor )
3510 savedCursors.push_back( { cursorId, vectorName, baseType, subType, cursor->GetCoords().x } );
3511 }
3512 }
3513
3514 // a transmission S-parameter (S_i_j, i != j) is not an impedance, drop it from the chart
3515 auto isReflection =
3516 []( const wxString& aName ) -> bool
3517 {
3518 long response, drive;
3519
3520 if( SMITH_MATH::ParseSParamPorts( aName, &response, &drive ) )
3521 return response == drive;
3522
3523 return true;
3524 };
3525
3526 std::vector<SMITH_STASHED_TRACE>& stashedTraces = plotTab->SmithStashedTraces();
3527 std::vector<SMITH_STASHED_CURSOR>& stashedCursors = plotTab->SmithStashedCursors();
3528
3529 if( smithMode )
3530 {
3531 // set aside what the Smith view cannot show, so leaving the mode restores it
3532 stashedTraces.clear();
3533 stashedCursors.clear();
3534
3535 for( const SHOWN_VECTOR& sv : shownVectors )
3536 {
3537 if( !isReflection( sv.vectorName ) )
3538 stashedTraces.push_back( { sv.vectorName, sv.displayName, sv.baseType } );
3539 }
3540
3541 for( const SAVED_CURSOR& saved : savedCursors )
3542 stashedCursors.push_back( { saved.id, saved.vectorName, saved.baseType, saved.subType, saved.coordX } );
3543 }
3544 else
3545 {
3546 // bring the stashed transmission traces back into the rebuild list
3547 for( const SMITH_STASHED_TRACE& stashed : stashedTraces )
3548 {
3549 bool seen = false;
3550
3551 for( const SHOWN_VECTOR& sv : shownVectors )
3552 seen |= sv.vectorName == stashed.vectorName;
3553
3554 if( !seen )
3555 {
3556 shownVectors.push_back( { stashed.vectorName, stashed.displayName, stashed.baseType,
3557 plotTab->GetDefaultView() } );
3558 }
3559 }
3560 }
3561
3562 for( const SHOWN_VECTOR& sv : shownVectors )
3563 {
3564 for( int subType : { SPT_SP_AMP, SPT_AC_PHASE, SPT_SP_SMITH } )
3565 plotTab->DeleteTrace( sv.vectorName, sv.baseType | subType );
3566 }
3567
3568 plotTab->SetSmithMode( smithMode );
3569
3570 for( const SHOWN_VECTOR& sv : shownVectors )
3571 {
3572 std::vector<int> subTypes;
3573
3574 if( smithMode )
3575 {
3576 if( !isReflection( sv.vectorName ) )
3577 continue;
3578
3579 subTypes = { SPT_SP_SMITH };
3580 }
3581 else
3582 {
3583 subTypes = { SPT_SP_AMP, SPT_AC_PHASE };
3584 }
3585
3586 for( int subType : subTypes )
3587 {
3588 updateTrace( sv.vectorName, sv.baseType | subType, plotTab, nullptr, false, sv.view );
3589
3590 if( TRACE* trace = plotTab->GetTrace( sv.vectorName, sv.baseType | subType ) )
3591 trace->SetName( sv.displayName );
3592 }
3593 }
3594
3595 // restore cursors on the rebuilt traces at the same frequency
3596 if( smithMode )
3597 {
3598 for( const SAVED_CURSOR& saved : savedCursors )
3599 {
3600 TRACE* trace = plotTab->GetTrace( saved.vectorName, saved.baseType | SPT_SP_SMITH );
3601
3602 // amplitude and phase cursors with the same id collapse onto one locus marker,
3603 // keep the first, the stash remembers both for the way back
3604 if( !trace || trace->GetCursor( saved.id ) )
3605 continue;
3606
3607 plotTab->EnableCursor( trace, saved.id, trace->GetName() );
3608
3609 if( CURSOR* cursor = trace->GetCursor( saved.id ) )
3610 {
3611 if( std::isfinite( saved.coordX ) )
3612 cursor->SetCoordX( saved.coordX );
3613 }
3614 }
3615 }
3616 else
3617 {
3618 auto findStashed =
3619 [&]( int aId, const wxString& aVectorName ) -> const SMITH_STASHED_CURSOR*
3620 {
3621 for( const SMITH_STASHED_CURSOR& stashed : stashedCursors )
3622 {
3623 if( stashed.id == aId && stashed.vectorName == aVectorName )
3624 return &stashed;
3625 }
3626
3627 return nullptr;
3628 };
3629
3630 auto restoreCursor =
3631 [&]( int aId, const wxString& aVectorName, int aBaseType, int aSubType, double aFreq ) -> bool
3632 {
3633 int wantSubType = aSubType == SPT_AC_PHASE ? SPT_AC_PHASE : SPT_SP_AMP;
3634
3635 if( TRACE* trace = plotTab->GetTrace( aVectorName, aBaseType | wantSubType ) )
3636 {
3637 plotTab->EnableCursor( trace, aId, trace->GetName() );
3638
3639 // a stash entry without a usable frequency keeps the default placement
3640 if( std::isfinite( aFreq ) )
3641 {
3642 if( CURSOR* cursor = trace->GetCursor( aId ) )
3643 cursor->SetCoordX( aFreq );
3644 }
3645
3646 return true;
3647 }
3648
3649 return false;
3650 };
3651
3652 std::vector<int> restoredIds;
3653
3654 // live smith cursors go back to the subtype they came from, at their current frequency
3655 for( const SAVED_CURSOR& saved : savedCursors )
3656 {
3657 const SMITH_STASHED_CURSOR* stashed = findStashed( saved.id, saved.vectorName );
3658
3659 if( restoreCursor( saved.id, saved.vectorName, saved.baseType,
3660 stashed ? stashed->subType : SPT_SP_AMP, saved.coordX ) )
3661 {
3662 restoredIds.push_back( saved.id );
3663 }
3664 }
3665
3666 // cursors whose trace could not exist in Smith mode come back from the stash alone,
3667 // unless the same cursor id was moved to another trace while in smith mode
3668 for( const SMITH_STASHED_CURSOR& stashed : stashedCursors )
3669 {
3670 if( isReflection( stashed.vectorName ) )
3671 continue;
3672
3673 if( std::find( restoredIds.begin(), restoredIds.end(), stashed.id ) != restoredIds.end() )
3674 continue;
3675
3676 restoreCursor( stashed.id, stashed.vectorName, stashed.baseType, stashed.subType, stashed.frequency );
3677 }
3678
3679 stashedTraces.clear();
3680 stashedCursors.clear();
3681 }
3682
3683 for( SIM_VIEW* view : plotTab->GetViews() )
3684 view->UpdateAll();
3685
3687 rebuildSignalsGrid( m_filter->GetValue() );
3690 OnModify();
3691}
3692
3693
3694void SIMULATOR_FRAME_UI::onPlotClose( wxAuiNotebookEvent& event )
3695{
3696 OnModify();
3697}
3698
3699
3700void SIMULATOR_FRAME_UI::onPlotClosed( wxAuiNotebookEvent& event )
3701{
3702 CallAfter( [this]()
3703 {
3705 rebuildSignalsGrid( m_filter->GetValue() );
3707
3708 //To avoid a current side effect in dynamic cursors while closing one out of many sim tabs
3710
3711 SIM_TAB* panel = GetCurrentSimTab();
3712
3713 if( !panel || panel->GetSimType() != ST_OP )
3714 {
3715 SCHEMATIC& schematic = m_schematicFrame->Schematic();
3716 schematic.ClearOperatingPoints();
3717 m_schematicFrame->RefreshOperatingPointDisplay();
3718 m_schematicFrame->GetCanvas()->Refresh();
3719 }
3720 } );
3721}
3722
3723
3725{
3726 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
3727 {
3728 std::vector<std::pair<wxString, wxString>>& measurements = plotTab->Measurements();
3729
3730 measurements.clear();
3731
3732 for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
3733 {
3734 if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
3735 {
3736 measurements.emplace_back( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ),
3737 m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT_FORMAT ) );
3738 }
3739 }
3740 }
3741}
3742
3743
3744void SIMULATOR_FRAME_UI::onPlotChanging( wxAuiNotebookEvent& event )
3745{
3746 m_measurementsGrid->ClearRows();
3747
3748 event.Skip();
3749}
3750
3751
3753{
3755 rebuildSignalsGrid( m_filter->GetValue() );
3757
3759
3760 for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
3761 UpdateMeasurement( row );
3762}
3763
3764
3765void SIMULATOR_FRAME_UI::onPlotChanged( wxAuiNotebookEvent& event )
3766{
3767 if( SIM_TAB* simTab = GetCurrentSimTab() )
3768 simulator()->Command( "setplot " + simTab->GetSpicePlotName().ToStdString() );
3769
3771
3772 //To avoid a current side effect in dynamic cursors while switching sim tabs
3774
3775 event.Skip();
3776}
3777
3778
3780{
3781 m_measurementsGrid->ClearRows();
3782
3783 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
3784 {
3785 for( const auto& [measurement, format] : plotTab->Measurements() )
3786 {
3787 int row = m_measurementsGrid->GetNumberRows();
3788 m_measurementsGrid->AppendRows();
3789 m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT, measurement );
3790 m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT_FORMAT, format );
3791 }
3792
3793 if( plotTab->GetSimType() == ST_TRAN || plotTab->GetSimType() == ST_AC || plotTab->GetSimType() == ST_DC
3794 || plotTab->GetSimType() == ST_SP )
3795 {
3796 m_measurementsGrid->AppendRows(); // Empty row at end
3797 }
3798 }
3799
3802}
3803
3804
3805void SIMULATOR_FRAME_UI::onPlotDragged( wxAuiNotebookEvent& event )
3806{
3807}
3808
3809
3810std::shared_ptr<SPICE_SIMULATOR> SIMULATOR_FRAME_UI::simulator() const
3811{
3812 return m_simulatorFrame->GetSimulator();
3813}
3814
3815
3816std::shared_ptr<SPICE_CIRCUIT_MODEL> SIMULATOR_FRAME_UI::circuitModel() const
3817{
3818 return m_simulatorFrame->GetCircuitModel();
3819}
3820
3821
3823{
3824 if( aSmithMode == smithColumnsShown( m_cursorsGrid ) )
3825 return;
3826
3827 if( aSmithMode )
3828 {
3829 m_cursorsGrid->AppendCols( SMITH_CURSOR_COLS );
3830
3831 for( int col = COL_CURSOR_SMITH_FIRST; col < COL_CURSOR_SMITH_END; ++col )
3832 {
3833 wxGridCellAttr* attr = new wxGridCellAttr;
3834 attr->SetReadOnly();
3835 m_cursorsGrid->SetColAttr( col, attr );
3836 }
3837
3838 // the panel is narrow, so keep the added columns off the horizontal scrollbar
3839 m_cursorsGrid->SetColSize( COL_CURSOR_R, 80 );
3840 m_cursorsGrid->SetColSize( COL_CURSOR_REACTANCE, 80 );
3841 m_cursorsGrid->SetColSize( COL_CURSOR_RL, 85 );
3842 m_cursorsGrid->SetColSize( COL_CURSOR_VSWR, 55 );
3843
3844 // a hidden column reports zero, so only a width the user really dragged overrides
3845 for( size_t ii = 0; ii < m_smithCursorWidths.size(); ++ii )
3846 {
3847 if( m_smithCursorWidths[ii] > 0 )
3848 m_cursorsGrid->SetColSize( COL_CURSOR_SMITH_FIRST + (int) ii, m_smithCursorWidths[ii] );
3849 }
3850
3853 }
3854 else
3855 {
3857
3858 // DeleteCols would otherwise throw away anything the user dragged
3859 m_smithCursorWidths.clear();
3860
3861 for( int col = COL_CURSOR_SMITH_FIRST; col < COL_CURSOR_SMITH_END; ++col )
3862 m_smithCursorWidths.push_back( m_cursorsGrid->GetColSize( col ) );
3863
3865 }
3866}
3867
3868
3870{
3872 return;
3873
3874 m_cursorsGrid->SetColLabelValue( COL_CURSOR_R, wxS( "R" ) );
3875 m_cursorsGrid->SetColLabelValue( COL_CURSOR_REACTANCE, wxS( "X" ) );
3876 m_cursorsGrid->SetColLabelValue( COL_CURSOR_RL, _( "Return loss" ) );
3877 m_cursorsGrid->SetColLabelValue( COL_CURSOR_VSWR, wxS( "VSWR" ) );
3878}
3879
3880
3882{
3883 // only the Smith columns, so hiding one cannot follow the user onto a non-Smith tab
3884 m_smithCursorColumns.Empty();
3885
3886 for( int col = COL_CURSOR_SMITH_FIRST; col < COL_CURSOR_SMITH_END; ++col )
3887 {
3888 if( !m_cursorsGrid->IsColShown( col ) )
3890 }
3891}
3892
3893
3895{
3896 wxStringTokenizer tokens( m_smithCursorColumns, wxS( " " ), wxTOKEN_STRTOK );
3897
3898 while( tokens.HasMoreTokens() )
3899 {
3900 long offset;
3901
3902 if( tokens.GetNextToken().ToLong( &offset ) && offset >= 0 && offset < SMITH_CURSOR_COLS )
3903 m_cursorsGrid->HideCol( COL_CURSOR_SMITH_FIRST + (int) offset );
3904 }
3905}
3906
3907
3908void SIMULATOR_FRAME_UI::fillSmithCursorRow( int aRow, CURSOR* aCursor, TRACE* aTrace )
3909{
3910 SMITH_CURSOR* smithCursor = dynamic_cast<SMITH_CURSOR*>( aCursor );
3911 SMITH_TRACE* smithTrace = dynamic_cast<SMITH_TRACE*>( aTrace );
3912
3913 if( !smithCursor || !smithTrace )
3914 return;
3915
3916 for( int col = COL_CURSOR_SMITH_FIRST; col < COL_CURSOR_SMITH_END; ++col )
3917 m_cursorsGrid->SetCellValue( aRow, col, wxS( "--" ) );
3918
3919 // gamma sits at the origin until the trace has a locus to snap to, which would read as a
3920 // perfect match rather than as no answer
3921 if( smithTrace->GetDataX().empty() )
3922 return;
3923
3924 const wxRealPoint& gamma = smithCursor->GetGamma();
3925 double z0 = smithTrace->GetReferenceImpedance();
3926 double zr, zi;
3927 const SPICE_VALUE_FORMAT ohms = { 3, wxS( "~Ω" ) };
3928
3929 // ohms need the port impedance, the match figures only need the magnitude
3930 if( z0 > 0.0 && SMITH_MATH::GammaToImpedance( gamma.x, gamma.y, z0, zr, zi ) )
3931 {
3932 m_cursorsGrid->SetCellValue( aRow, COL_CURSOR_R, SPICE_VALUE( zr ).ToString( ohms ) );
3933 m_cursorsGrid->SetCellValue( aRow, COL_CURSOR_REACTANCE, SPICE_VALUE( zi ).ToString( ohms ) );
3934 }
3935
3936 double gm = std::hypot( gamma.x, gamma.y );
3937 double rl = SMITH_MATH::ReturnLoss( gm );
3938 double vswr = SMITH_MATH::VSWR( gm );
3939
3940 if( std::isfinite( rl ) )
3941 m_cursorsGrid->SetCellValue( aRow, COL_CURSOR_RL, wxString::Format( wxS( "%.1f dB" ), rl ) );
3942 else if( std::isinf( rl ) )
3943 m_cursorsGrid->SetCellValue( aRow, COL_CURSOR_RL, wxS( "inf dB" ) );
3944
3945 if( std::isfinite( vswr ) )
3946 m_cursorsGrid->SetCellValue( aRow, COL_CURSOR_VSWR, wxString::Format( wxS( "%.2f" ), vswr ) );
3947 else if( std::isinf( vswr ) )
3948 m_cursorsGrid->SetCellValue( aRow, COL_CURSOR_VSWR, wxS( "inf" ) );
3949}
3950
3951
3953{
3954 SUPPRESS_GRID_CELL_EVENTS raii( this );
3955
3956 m_cursorsGrid->ClearRows();
3957
3958 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
3959
3960 if( !plotTab )
3961 {
3962 updateSmithCursorColumns( false );
3963 return;
3964 }
3965
3966 updateSmithCursorColumns( plotTab->GetSimType() == ST_SP && plotTab->IsSmithMode() );
3967
3968 // Update cursor values
3969 CURSOR* cursor1 = nullptr;
3970 wxString cursor1Name;
3971 wxString cursor1Units;
3972 CURSOR* cursor2 = nullptr;
3973 wxString cursor2Name;
3974 wxString cursor2Units;
3975
3976 auto getUnitsY =
3977 [&]( TRACE* aTrace ) -> wxString
3978 {
3979 // a smith cursor's y is the reflection coefficient magnitude, unitless
3980 if( aTrace->GetType() & SPT_SP_SMITH )
3981 return wxString();
3982
3983 if( plotTab->GetSimType() == ST_AC )
3984 {
3985 if( aTrace->GetType() & SPT_AC_PHASE )
3986 return plotTab->GetUnitsY2();
3987 else
3988 return plotTab->GetUnitsY1();
3989 }
3990 else
3991 {
3992 if( aTrace->GetType() & SPT_POWER )
3993 return plotTab->GetUnitsY3();
3994 else if( aTrace->GetType() & SPT_CURRENT )
3995 return plotTab->GetUnitsY2();
3996 else
3997 return plotTab->GetUnitsY1();
3998 }
3999 };
4000
4001 auto getNameY =
4002 [&]( TRACE* aTrace ) -> wxString
4003 {
4004 if( aTrace->GetType() & SPT_SP_SMITH )
4005 return _( "Refl. Coeff." );
4006
4007 if( plotTab->GetSimType() == ST_AC )
4008 {
4009 if( aTrace->GetType() & SPT_AC_PHASE )
4010 return plotTab->GetLabelY2();
4011 else
4012 return plotTab->GetLabelY1();
4013 }
4014 else
4015 {
4016 if( aTrace->GetType() & SPT_POWER )
4017 return plotTab->GetLabelY3();
4018 else if( aTrace->GetType() & SPT_CURRENT )
4019 return plotTab->GetLabelY2();
4020 else
4021 return plotTab->GetLabelY1();
4022 }
4023 };
4024
4025 auto formatValue =
4026 [this]( double aValue, int aCursorId, int aCol ) -> wxString
4027 {
4028 if( ( !m_simulatorFrame->SimFinished() && aCol == 1 ) || std::isnan( aValue ) )
4029 return wxS( "--" );
4030 else
4031 return SPICE_VALUE( aValue ).ToString( m_cursorFormatsDyn[aCursorId][aCol] );
4032 };
4033
4034 for( const auto& [name, trace] : plotTab->GetTraces() )
4035 {
4036 if( CURSOR* cursor = trace->GetCursor( 1 ) )
4037 {
4038 cursor1 = cursor;
4039 cursor1Name = getNameY( trace );
4040 cursor1Units = getUnitsY( trace );
4041
4042 wxRealPoint coords = cursor->GetCoords();
4043 int row = m_cursorsGrid->GetNumberRows();
4044
4045 m_cursorFormatsDyn[0][0].UpdateUnits( plotTab->GetUnitsX() );
4046 m_cursorFormatsDyn[0][1].UpdateUnits( cursor1Units );
4047
4048 m_cursorsGrid->AppendRows( 1 );
4049 m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "1" ) );
4050 m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, cursor->GetName() );
4051 m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, 0, 0 ) );
4052 m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, 0, 1 ) );
4053 fillSmithCursorRow( row, cursor, trace );
4054 break;
4055 }
4056 }
4057
4058 for( const auto& [name, trace] : plotTab->GetTraces() )
4059 {
4060 if( CURSOR* cursor = trace->GetCursor( 2 ) )
4061 {
4062 cursor2 = cursor;
4063 cursor2Name = getNameY( trace );
4064 cursor2Units = getUnitsY( trace );
4065
4066 wxRealPoint coords = cursor->GetCoords();
4067 int row = m_cursorsGrid->GetNumberRows();
4068
4069 m_cursorFormatsDyn[1][0].UpdateUnits( plotTab->GetUnitsX() );
4070 m_cursorFormatsDyn[1][1].UpdateUnits( cursor2Units );
4071
4072 m_cursorsGrid->AppendRows( 1 );
4073 m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "2" ) );
4074 m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, cursor->GetName() );
4075 m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, 1, 0 ) );
4076 m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, 1, 1 ) );
4077 fillSmithCursorRow( row, cursor, trace );
4078 break;
4079 }
4080 }
4081
4082 if( cursor1 && cursor2 && cursor1Units == cursor2Units )
4083 {
4084 wxRealPoint coords = cursor2->GetCoords() - cursor1->GetCoords();
4085 wxString signal;
4086
4087 m_cursorFormatsDyn[2][0].UpdateUnits( plotTab->GetUnitsX() );
4088 m_cursorFormatsDyn[2][1].UpdateUnits( cursor1Units );
4089
4090 if( cursor1->GetName() == cursor2->GetName() )
4091 signal = wxString::Format( wxS( "%s[2 - 1]" ), cursor2->GetName() );
4092 else
4093 signal = wxString::Format( wxS( "%s - %s" ), cursor2->GetName(), cursor1->GetName() );
4094
4095 m_cursorsGrid->AppendRows( 1 );
4096 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_NAME, _( "Diff" ) );
4097 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_SIGNAL, signal );
4098 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_X, formatValue( coords.x, 2, 0 ) );
4099 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_Y, formatValue( coords.y, 2, 1 ) );
4100 }
4101 // Set up the labels
4102 m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, plotTab->GetLabelX() );
4103
4104 wxString valColName = _( "Value" );
4105
4106 if( !cursor1Name.IsEmpty() )
4107 {
4108 if( cursor2Name.IsEmpty() || cursor1Name == cursor2Name )
4109 valColName = cursor1Name;
4110 }
4111 else if( !cursor2Name.IsEmpty() )
4112 {
4113 valColName = cursor2Name;
4114 }
4115
4116 m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, valColName );
4117
4118 if( m_customCursorsCnt > 3 ) // 2 for the default hardocded cursors plus the initial + 1
4119 {
4120 for( int i = 3; i < m_customCursorsCnt; i++ )
4121 {
4122 for( const auto& [name, trace] : plotTab->GetTraces() )
4123 {
4124 if( CURSOR* cursor = trace->GetCursor( i ) )
4125 {
4126 CURSOR* curs = cursor;
4127 wxString cursName = getNameY( trace );
4128 wxString cursUnits = getUnitsY( trace );
4129
4130 wxRealPoint coords = cursor->GetCoords();
4131 int row = m_cursorsGrid->GetNumberRows();
4132
4133 m_cursorFormatsDyn[i][0].UpdateUnits( plotTab->GetUnitsX() );
4134 m_cursorFormatsDyn[i][1].UpdateUnits( cursUnits );
4135
4136 m_cursorsGrid->AppendRows( 1 );
4137 m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "" ) + wxString( "" ) << i );
4138 m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, curs->GetName() );
4139 m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, i, 0 ) );
4140 m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, i, 1 ) );
4141 fillSmithCursorRow( row, curs, trace );
4142
4143 // Set up the labels
4144 m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, plotTab->GetLabelX() );
4145
4146 valColName = _( "Value" );
4147
4148 if( !cursName.IsEmpty() && m_cursorsGrid->GetColLabelValue( COL_CURSOR_Y ) == cursName )
4149 valColName = cursName;
4150
4151 m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, valColName );
4152 break;
4153 }
4154 }
4155 }
4156 }
4157
4162}
4163
4164
4165void SIMULATOR_FRAME_UI::onPlotCursorUpdate( wxCommandEvent& aEvent )
4166{
4168 OnModify();
4169}
4170
4171
4173{
4174 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
4175 plotTab->ResetScales( true );
4176
4177 // Drop any buffered output from the previous run and clear the console widget.
4178 m_simulatorFrame->TakeSimReportMessages();
4179 m_simConsole->Clear();
4180
4182
4183 // Do not export netlist, it is already stored in the simulator
4184 applyTuners();
4185
4186 m_refreshTimer.Start( REFRESH_INTERVAL, wxTIMER_ONE_SHOT );
4187}
4188
4189
4191{
4192 // AppendText is slow on MSW, so we use the buffered report lines
4193 wxString messages = m_simulatorFrame->TakeSimReportMessages();
4194
4195 if( messages.IsEmpty() )
4196 return;
4197
4198 m_simConsole->AppendText( messages );
4199 m_simConsole->SetInsertionPointEnd();
4200}
4201
4202
4203std::vector<wxString> SIMULATOR_FRAME_UI::SimPlotVectors() const
4204{
4205 std::vector<wxString> signals;
4206
4207 for( const std::string& vec : simulator()->AllVectors() )
4208 signals.emplace_back( vec );
4209
4210 return signals;
4211}
4212
4213
4214std::vector<wxString> SIMULATOR_FRAME_UI::Signals() const
4215{
4216 std::vector<wxString> signals;
4217
4218 for( const wxString& signal : m_signals )
4219 signals.emplace_back( signal );
4220
4221 for( const auto& [id, signal] : m_userDefinedSignals )
4222 signals.emplace_back( signal );
4223
4224 sortSignals( signals );
4225
4226 return signals;
4227}
4228
4229
4231{
4233
4234 if( aFinal )
4235 m_refreshTimer.Stop();
4236
4237 SIM_TAB* simTab = GetCurrentSimTab();
4238
4239 if( !simTab )
4240 return;
4241
4242 bool storeMultiRun = false;
4243
4244 if( aFinal && m_multiRunState.active )
4245 {
4246 if( m_multiRunState.currentStep < m_multiRunState.steps.size() )
4247 {
4248 storeMultiRun = true;
4249 m_multiRunState.storePending = true;
4250 }
4251 }
4252 else
4253 {
4254 m_multiRunState.storePending = false;
4255 }
4256
4257 SIM_TYPE simType = simTab->GetSimType();
4258 wxString msg;
4259
4260 // FFT is run synchronously in StartSimulation(), which stores the new FFT plot name before
4261 // refreshing. Ensure ngspice is on that plot before accessing its vectors. Other simulation
4262 // refreshes must not run setplot here, or a rerun would switch ngspice back to a stale plot.
4263 if( aFinal && simType == ST_FFT )
4264 {
4265 const wxString spicePlotName = simTab->GetSpicePlotName();
4266
4267 if( !spicePlotName.IsEmpty() )
4268 simulator()->Command( "setplot " + spicePlotName.ToStdString() );
4269 }
4270
4271 if( aFinal )
4272 {
4275 }
4276
4277 // If there are any signals plotted, update them
4278 if( SIM_TAB::IsPlottable( simType ) )
4279 {
4280 simTab->SetSpicePlotName( simulator()->CurrentPlotName() );
4281
4282 if( simType == ST_NOISE && aFinal )
4283 {
4284 m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
4285 m_simConsole->SetInsertionPointEnd();
4286
4287 // The simulator will create noise1 & noise2 on the first run, noise3 and noise4
4288 // on the second, etc. The first plot for each run contains the spectral density
4289 // noise vectors and second contains the integrated noise.
4290 long number;
4291 simulator()->CurrentPlotName().Mid( 5 ).ToLong( &number );
4292
4293 for( const std::string& vec : simulator()->AllVectors() )
4294 {
4295 std::vector<double> val_list = simulator()->GetRealVector( vec, 1 );
4296 wxString value = SPICE_VALUE( val_list[0] ).ToSpiceString();
4297
4298 msg.Printf( wxS( "%s: %sV\n" ), vec, value );
4299
4300 m_simConsole->AppendText( msg );
4301 m_simConsole->SetInsertionPointEnd();
4302 }
4303
4304 simulator()->Command( fmt::format( "setplot noise{}", number - 1 ) );
4305 simTab->SetSpicePlotName( simulator()->CurrentPlotName() );
4306 }
4307
4308 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab );
4309 wxCHECK_RET( plotTab, wxString::Format( wxT( "No SIM_PLOT_TAB for: %s" ), magic_enum::enum_name( simType ) ) );
4310
4311 struct TRACE_INFO
4312 {
4313 wxString Vector;
4314 int TraceType;
4315 bool ClearData;
4316 };
4317
4318 std::map<TRACE*, TRACE_INFO> traceMap;
4319
4320 for( const auto& [name, trace] : plotTab->GetTraces() )
4321 traceMap[trace] = { wxEmptyString, SPT_UNKNOWN, false };
4322
4323 // NB: m_signals are already broken out into gain/phase, but m_userDefinedSignals are
4324 // as the user typed them
4325
4326 for( const wxString& signal : m_signals )
4327 {
4328 int traceType = SPT_UNKNOWN;
4329 wxString vectorName = vectorNameFromSignalName( plotTab, signal, &traceType );
4330
4331 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
4332 traceMap[trace] = { vectorName, traceType, false };
4333 }
4334
4335 for( const auto& [id, signal] : m_userDefinedSignals )
4336 {
4337 int traceType = SPT_UNKNOWN;
4338 wxString vectorName = vectorNameFromSignalName( plotTab, signal, &traceType );
4339
4340 if( simType == ST_AC )
4341 {
4342 int baseType = traceType &= ~( SPT_AC_GAIN | SPT_AC_PHASE );
4343
4344 for( int subType : { baseType | SPT_AC_GAIN, baseType | SPT_AC_PHASE } )
4345 {
4346 if( TRACE* trace = plotTab->GetTrace( vectorName, subType ) )
4347 traceMap[trace] = { vectorName, subType, !aFinal };
4348 }
4349 }
4350 else if( simType == ST_SP )
4351 {
4352 int baseType = traceType &= ~SPT_SP_MASK;
4353
4354 for( int subType : { baseType | SPT_SP_AMP, baseType | SPT_AC_PHASE, baseType | SPT_SP_SMITH } )
4355 {
4356 if( TRACE* trace = plotTab->GetTrace( vectorName, subType ) )
4357 traceMap[trace] = { vectorName, subType, !aFinal };
4358 }
4359 }
4360 else
4361 {
4362 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
4363 traceMap[trace] = { vectorName, traceType, !aFinal };
4364 }
4365 }
4366
4367 // Two passes so that DC-sweep sub-traces get deleted and re-created:
4368
4369 for( const auto& [trace, traceInfo] : traceMap )
4370 {
4371 if( traceInfo.Vector.IsEmpty() )
4372 plotTab->DeleteTrace( trace );
4373 }
4374
4375 for( const auto& [trace, info] : traceMap )
4376 {
4377 std::vector<double> data_x;
4378
4379 if( !info.Vector.IsEmpty() )
4380 updateTrace( info.Vector, info.TraceType, plotTab, &data_x, info.ClearData );
4381 }
4382
4383 for( SIM_VIEW* view : plotTab->GetViews() )
4384 view->UpdateAll();
4385
4386 if( aFinal )
4387 {
4388 for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
4389 UpdateMeasurement( row );
4390
4391 plotTab->ResetScales( true );
4392 }
4393
4394 for( SIM_VIEW* view : plotTab->GetViews() )
4395 view->Fit();
4396
4398 }
4399 else if( simType == ST_OP && aFinal )
4400 {
4401 m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
4402 m_simConsole->SetInsertionPointEnd();
4403
4404 for( const std::string& vec : simulator()->AllVectors() )
4405 {
4406 std::vector<double> val_list = simulator()->GetRealVector( vec, 1 );
4407
4408 if( val_list.empty() )
4409 continue;
4410
4411 wxString value = SPICE_VALUE( val_list[0] ).ToSpiceString();
4412 wxString signal;
4413 SIM_TRACE_TYPE type = circuitModel()->VectorToSignal( vec, signal );
4414
4415 const size_t tab = 25; //characters
4416 size_t padding = ( signal.length() < tab ) ? ( tab - signal.length() ) : 1;
4417
4418 switch( type )
4419 {
4420 case SPT_VOLTAGE: value.Append( wxS( "V" ) ); break;
4421 case SPT_CURRENT: value.Append( wxS( "A" ) ); break;
4422 case SPT_POWER: value.Append( wxS( "W" ) ); break;
4423 default: value.Append( wxS( "?" ) ); break;
4424 }
4425
4426 msg.Printf( wxT( "%s%s\n" ),
4427 ( signal + wxT( ":" ) ).Pad( padding, wxUniChar( ' ' ) ),
4428 value );
4429
4430 m_simConsole->AppendText( msg );
4431 m_simConsole->SetInsertionPointEnd();
4432
4433 if( type == SPT_VOLTAGE || type == SPT_CURRENT || type == SPT_POWER )
4434 signal = signal.SubString( 2, signal.Length() - 2 );
4435
4436 if( type == SPT_POWER )
4437 signal += wxS( ":power" );
4438
4439 m_schematicFrame->Schematic().SetOperatingPoint( signal, val_list.at( 0 ) );
4440 }
4441 }
4442 else if( simType == ST_PZ && aFinal )
4443 {
4444 m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
4445 m_simConsole->SetInsertionPointEnd();
4446 simulator()->Command( "print all" );
4447 }
4448
4449 // Non-plottable analyses (op, pz, tf, sens, disto) still create an ngspice plot; record its
4450 // name so a rerun can destroy it instead of leaking the vectors. Plottable tabs already
4451 // stored their (possibly noise-adjusted) plot name above. A shared/stale plot name is caught
4452 // when destroying, not here.
4453 if( aFinal && !SIM_TAB::IsPlottable( simType ) )
4454 simTab->SetSpicePlotName( simulator()->CurrentPlotName() );
4455
4456 if( storeMultiRun )
4457 {
4458 m_multiRunState.storePending = false;
4459 m_multiRunState.storedSteps = m_multiRunState.currentStep + 1;
4460 }
4461
4462 if( aFinal && m_multiRunState.active )
4463 {
4464 if( m_multiRunState.currentStep + 1 < m_multiRunState.steps.size() )
4465 {
4466 m_multiRunState.currentStep++;
4467
4468 wxQueueEvent( m_simulatorFrame, new wxCommandEvent( EVT_SIM_UPDATE ) );
4469 }
4470 else
4471 {
4472 m_multiRunState.active = false;
4473 m_multiRunState.steps.clear();
4474 m_multiRunState.currentStep = 0;
4475 m_multiRunState.storePending = false;
4476 m_tunerOverrides.clear();
4477
4478 if( !m_multiRunState.traces.empty() )
4479 {
4480 auto iter = m_multiRunState.traces.begin();
4481
4482 if( iter != m_multiRunState.traces.end() )
4483 m_multiRunState.storedSteps = iter->second.yValues.size();
4484 }
4485 }
4486 }
4487}
4488
4489
4491{
4492 m_multiRunState.active = false;
4493 m_multiRunState.tuners.clear();
4494 m_multiRunState.steps.clear();
4495 m_multiRunState.currentStep = 0;
4496 m_multiRunState.storePending = false;
4497
4498 if( aClearTraces )
4499 {
4500 m_multiRunState.traces.clear();
4501 m_multiRunState.storedSteps = 0;
4502 }
4503
4504 m_tunerOverrides.clear();
4505}
4506
4507
4509{
4510 m_tunerOverrides.clear();
4511
4512 std::vector<TUNER_SLIDER*> multiTuners;
4513
4514 for( TUNER_SLIDER* tuner : m_tuners )
4515 {
4516 if( tuner->GetRunMode() == TUNER_SLIDER::RUN_MODE::MULTI )
4517 multiTuners.push_back( tuner );
4518 }
4519
4520 if( multiTuners.empty() )
4521 {
4522 clearMultiRunState( true );
4523 return;
4524 }
4525
4526 bool tunersChanged = multiTuners != m_multiRunState.tuners;
4527
4528 if( m_multiRunState.active && tunersChanged )
4529 clearMultiRunState( true );
4530
4531 if( !m_multiRunState.active )
4532 {
4533 if( tunersChanged || m_multiRunState.storedSteps > 0 || !m_multiRunState.traces.empty() )
4534 clearMultiRunState( true );
4535
4536 m_multiRunState.tuners = multiTuners;
4537 m_multiRunState.steps = calculateMultiRunSteps( multiTuners );
4538 m_multiRunState.currentStep = 0;
4539 m_multiRunState.storePending = false;
4540
4541 if( m_multiRunState.steps.size() >= 2 )
4542 {
4543 m_multiRunState.active = true;
4544 m_multiRunState.storedSteps = 0;
4545 }
4546 else
4547 {
4548 m_multiRunState.steps.clear();
4549 return;
4550 }
4551 }
4552 else if( tunersChanged )
4553 {
4554 m_multiRunState.tuners = multiTuners;
4555 }
4556
4557 if( m_multiRunState.active && m_multiRunState.currentStep < m_multiRunState.steps.size() )
4558 {
4559 const MULTI_RUN_STEP& step = m_multiRunState.steps[m_multiRunState.currentStep];
4560
4561 for( const auto& entry : step.overrides )
4562 m_tunerOverrides[entry.first] = entry.second;
4563 }
4564}
4565
4566
4567std::vector<SIMULATOR_FRAME_UI::MULTI_RUN_STEP>
4568SIMULATOR_FRAME_UI::calculateMultiRunSteps( const std::vector<TUNER_SLIDER*>& aTuners ) const
4569{
4570 std::vector<MULTI_RUN_STEP> steps;
4571
4572 if( aTuners.empty() )
4573 return steps;
4574
4575 std::vector<std::vector<double>> tunerValues;
4576 tunerValues.reserve( aTuners.size() );
4577
4578 for( TUNER_SLIDER* tuner : aTuners )
4579 {
4580 if( !tuner )
4581 return steps;
4582
4583 double startValue = tuner->GetMin().ToDouble();
4584 double endValue = tuner->GetMax().ToDouble();
4585 int stepCount = std::max( 2, tuner->GetStepCount() );
4586
4587 if( stepCount < 2 )
4588 stepCount = 2;
4589
4590 double increment = ( endValue - startValue ) / static_cast<double>( stepCount - 1 );
4591
4592 std::vector<double> values;
4593 values.reserve( stepCount );
4594
4595 for( int ii = 0; ii < stepCount; ++ii )
4596 values.push_back( startValue + increment * ii );
4597
4598 tunerValues.push_back( std::move( values ) );
4599 }
4600
4602
4603 if( limit < 1 )
4604 limit = 1;
4605
4606 std::vector<double> currentValues( aTuners.size(), 0.0 );
4607
4608 auto generate =
4609 [&]( auto&& self, size_t depth ) -> void
4610 {
4611 if( steps.size() >= static_cast<size_t>( limit ) )
4612 return;
4613
4614 if( depth == aTuners.size() )
4615 {
4616 MULTI_RUN_STEP step;
4617
4618 for( size_t ii = 0; ii < aTuners.size(); ++ii )
4619 step.overrides.emplace( aTuners[ii], currentValues[ii] );
4620
4621 steps.push_back( std::move( step ) );
4622 return;
4623 }
4624
4625 for( double value : tunerValues[depth] )
4626 {
4627 currentValues[depth] = value;
4628 self( self, depth + 1 );
4629
4630 if( steps.size() >= static_cast<size_t>( limit ) )
4631 return;
4632 }
4633 };
4634
4635 generate( generate, 0 );
4636
4637 return steps;
4638}
4639
4640
4641std::string SIMULATOR_FRAME_UI::multiRunTraceKey( const wxString& aVectorName, int aTraceType ) const
4642{
4643 return fmt::format( "{}|{}", aVectorName.ToStdString(), aTraceType );
4644}
4645
4646
4647void SIMULATOR_FRAME_UI::recordMultiRunData( const wxString& aVectorName, int aTraceType, const std::vector<double>& aX,
4648 const std::vector<double>& aY )
4649{
4650 if( aX.empty() || aY.empty() )
4651 return;
4652
4653 std::string key = multiRunTraceKey( aVectorName, aTraceType );
4654 MULTI_RUN_TRACE& trace = m_multiRunState.traces[key];
4655
4656 trace.traceType = aTraceType;
4657
4658 if( trace.xValues.empty() )
4659 trace.xValues = aX;
4660
4661 if( trace.xValues.size() != aX.size() )
4662 return;
4663
4664 size_t index = m_multiRunState.currentStep;
4665
4666 if( trace.yValues.size() <= index )
4667 trace.yValues.resize( index + 1 );
4668
4669 trace.yValues[index] = aY;
4670
4671 if( aTraceType & SPT_SP_SMITH )
4672 {
4673 if( trace.xRuns.size() <= index )
4674 trace.xRuns.resize( index + 1 );
4675
4676 trace.xRuns[index] = aX;
4677 }
4678}
4679
4680
4681bool SIMULATOR_FRAME_UI::hasMultiRunTrace( const wxString& aVectorName, int aTraceType ) const
4682{
4683 std::string key = multiRunTraceKey( aVectorName, aTraceType );
4684 auto it = m_multiRunState.traces.find( key );
4685
4686 if( it == m_multiRunState.traces.end() )
4687 return false;
4688
4689 const MULTI_RUN_TRACE& trace = it->second;
4690
4691 return !trace.xValues.empty() && !trace.yValues.empty();
4692}
4693
4694
4696{
4697 m_simulatorFrame->OnModify();
4698}
int index
const char * name
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
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.
const wxRealPoint & GetCoords() const
virtual void SetCoordX(double aValue)
int ShowModal() override
bool Find(const wxString &aTerm, int &aMatchersTriggered, int &aPosition)
Look in all existing matchers, return the earliest match of any of the existing.
Renders a combobox cell's text with an always-visible dropdown arrow, so it reads as a dropdown even ...
GRID_TRICKS(WX_GRID *aGrid)
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.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
wxString ToCSSString() const
Definition color4d.cpp:146
wxColour ToColour() const
Definition color4d.cpp:221
Definition kiid.h:46
Hold a translatable error message and may be used when throwing exceptions containing a translated er...
const wxString What() const
void AppendParentEmbeddedFiles(std::vector< EMBEDDED_FILES * > &aStack) const
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 wxString &aRefName) const
Find and return the item corresponding to aRefName.
A singleton reporter that reports to nowhere.
Definition reporter.h:267
Holds all the data relating to one schematic.
Definition schematic.h:148
void ClearOperatingPoints()
Clear operating points from a .op simulation.
Definition schematic.h:413
wxString GetCurrentVariant() const
Return the current variant being edited.
Schematic editor (Eeschema) main window.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:165
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
SCH_ITEM * ResolveItem(const KIID &aID) const
Fetch a SCH_ITEM by ID.
Schematic symbol object.
Definition sch_symbol.h:75
EMBEDDED_FILES * GetEmbeddedFiles() override
SCH_SYMBOLs don't currently support embedded files, but their LIB_SYMBOL counterparts do.
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly) const override
Populate a std::vector with SCH_FIELDs, sorted in ordinal order.
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition sch_symbol.h:183
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
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)
wxSplitterWindow * m_splitterLeftRight
wxSplitterWindow * m_splitterMeasurements
wxSplitterWindow * m_splitterCursors
wxSplitterWindow * m_splitterPlotAndConsole
SIMULATOR_FRAME_UI_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxTAB_TRAVERSAL, const wxString &name=wxEmptyString)
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.
std::string multiRunTraceKey(const wxString &aVectorName, int aTraceType) const
std::vector< int > m_smithCursorWidths
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 CustomCursorsInit()
Init handler for custom cursors.
void recordMultiRunData(const wxString &aVectorName, int aTraceType, const std::vector< double > &aX, const std::vector< double > &aY)
std::vector< MULTI_RUN_STEP > calculateMultiRunSteps(const std::vector< TUNER_SLIDER * > &aTuners) const
void autoSizeGridColumn(WX_GRID *aGrid, int aCol, int aMinWidth, int aExtraPadding=0)
Grow (but never shrink below aMinWidth) a grid column to fit its current contents.
void TogglePanel(wxPanel *aPanel, wxSplitterWindow *aSplitterWindow, int &aSashPosition)
A common toggler for the two main wxSplitterWindow s.
void onPlotChanged(wxAuiNotebookEvent &event) override
void rebuildSignalsGrid(wxString aFilter)
Rebuild the filtered list of signals in the signals grid.
std::vector< std::vector< SPICE_VALUE_FORMAT > > m_cursorFormatsDyn
void DoFourier(const wxString &aSignal, const wxString &aFundamental)
void rebuildMeasurementsGrid()
Rebuild the measurements grid for the current plot.
std::list< TUNER_SLIDER * > m_tuners
void rebuildSignalsList()
Rebuild the list of signals available from the netlist.
bool loadLegacyWorkbook(const wxString &aPath)
MULTI_RUN_STATE m_multiRunState
SPICE expressions need quoted versions of the netnames since KiCad allows '-' and '/' in netnames.
wxArrayString getYScaleChoices(SIM_PLOT_TAB *aPlotTab, SIM_VIEW *aOwnView) const
The Signals grid's "Y Scale" column label for a trace ("Default" if not explicitly linked).
void UpdateMeasurement(int aRow)
Update a measurement in the measurements grid.
wxString getNoiseSource() const
std::vector< wxString > SimPlotVectors() const
void SetSubWindowsSashSize()
Adjust the sash dimension of splitter windows after reading the config settings must be called after ...
void applyUserDefinedSignals()
Apply user-defined signals to the SPICE session.
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.
bool hasMultiRunTrace(const wxString &aVectorName, int aTraceType) const
std::vector< wxString > m_signals
SIM_TAB * GetCurrentSimTab() const
Return the currently opened plot panel (or NULL if there is none).
wxString getViewLabel(SIM_PLOT_TAB *aPlotTab, SIM_VIEW *aView) const
The views a trace's Y-axis scale could be linked to: every view except its own (since linking to its ...
void updateSmithCursorColumns(bool aSmithMode)
Add or remove the Smith-only cursor columns, carrying their shown/hidden state across.
void SaveCursorToWorkbook(nlohmann::json &aTraceJs, TRACE *aTrace, int aCursorId)
bool LoadWorkbook(const wxString &aPath)
Load plot, signal, cursor, measurement, etc.
wxString getYScaleLabel(SIM_PLOT_TAB *aPlotTab, TRACE *aTrace) const
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 updateTrace(const wxString &aVectorName, int aTraceType, SIM_PLOT_TAB *aPlotTab, std::vector< double > *aDataX=nullptr, bool aClearData=false, SIM_VIEW *aView=nullptr)
Update a trace in a particular SIM_PLOT_TAB.
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 fillSmithCursorRow(int aRow, CURSOR *aCursor, TRACE *aTrace)
std::vector< wxString > m_netnames
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 DeleteCursor()
Deletes last m_signalsGrid "Cursor n" column, removes vector's m_cursorFormatsDyn last entry,...
void CreateNewCursor()
Creates a column at the end of m_signalsGrid named "Cursor n" ( n = m_customCursorsCnt ),...
void applyTuners()
Apply component values specified using tuner sliders to the current netlist.
bool loadJsonWorkbook(const wxString &aPath)
wxArrayString getViewChoices(SIM_PLOT_TAB *aPlotTab) const
The Signals grid's "Plot" column label for a given view ("Disabled" if null).
void clearMultiRunState(bool aClearTraces)
void OnFilterMouseMoved(wxMouseEvent &aEvent) override
void AddMeasurement(const wxString &aCmd)
Add a measurement to the measurements grid.
void onPlotChanging(wxAuiNotebookEvent &event) override
std::vector< SIM_VIEW * > getYScaleTargetViews(SIM_PLOT_TAB *aPlotTab, SIM_VIEW *aOwnView) const
The Signals grid's "Y Scale" column choices: "Default" plus one entry per eligible view.
std::map< const TUNER_SLIDER *, double > m_tunerOverrides
std::shared_ptr< SPICE_SIMULATOR > simulator() const
void onPlotCursorUpdate(wxCommandEvent &aEvent)
void onSignalsGridCellChanged(wxGridEvent &aEvent) override
void InitWorkbook()
Load the currently active workbook stored in the project settings.
void ToggleDarkModePlots()
Toggle the current S-parameter tab between Smith chart and amplitude/phase views.
SIM_TRACE_TYPE getXAxisType(SIM_TYPE aType) const
Return X axis for a given simulation type.
void signalsGridCursorUpdate(T t, U u, R r)
Updates m_signalsGrid cursor widget, column rendering and attributes.
void SetMeasureFormat(int aRow, const SPICE_VALUE_FORMAT &aFormat)
double getSmithPortImpedance(const wxString &aVectorName)
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.
void OnUpdateUI(wxUpdateUIEvent &event) override
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.
SIM_MODEL & CreateModel(SIM_MODEL::TYPE aType, std::span< const wxString > aPins, REPORTER &aReporter)
void SetFilesStack(std::vector< EMBEDDED_FILES * > aFilesStack)
Definition sim_lib_mgr.h:45
virtual const PARAM * GetTunerParam() const
Definition sim_model.h:491
const SPICE_GENERATOR & SpiceGenerator() const
Definition sim_model.h:429
static void FillDefaultColorList(bool aWhiteBg)
Fills m_colorList by a default set of colors.
bool DeleteTrace(const wxString &aVectorName, int aTraceType)
int GetViewCount() const
The view new traces default to, and legacy (single-view) settings apply to.
void UpdateSmithReferenceImpedance()
The saved/restored pan and zoom track the default view.
wxString GetLabelY1() const
mpWindow * GetPlotWin() const
Get (or create, on aView) the TRACE for a signal.
void SetSmithView(double aZoom, double aPanX, double aPanY)
SIM_VIEW * GetView(int aIndex) const
void ShowGrid(bool aEnable)
void SetTraceData(TRACE *aTrace, std::vector< double > &aX, std::vector< double > &aY, int aSweepCount, size_t aSweepSize, bool aIsMultiRun=false, const std::vector< wxString > &aMultiRunLabels={})
const std::vector< SIM_VIEW * > & GetViews() const
wxString GetUnitsY2() const
void SetY2Scale(bool aLock, double aMin, double aMax)
TRACE * GetTrace(const wxString &aVecName, int aType) const
void SetSmithMode(bool aEnable)
wxString GetLabelX() const
const std::map< wxString, TRACE * > & GetTraces() const
wxString GetLabelY3() const
void SetY1Scale(bool aLock, double aMin, double aMax)
void SetY3Scale(bool aLock, double aMin, double aMax)
SIM_VIEW * GetDefaultView() const
Add a new (empty) view, stacked below the existing ones.
SIM_VIEW * AddView()
Remove a view.
std::vector< std::pair< wxString, wxString > > & Measurements()
void UpdateTraceStyle(TRACE *trace)
Update plot colors.
void SetLegendPosition(const wxPoint &aPosition)
TRACE * GetOrAddTrace(const wxString &aVectorName, int aType, SIM_VIEW *aView)
void ResetScales(bool aIncludeX)
Update trace line style.
void UpdatePlotColors()
std::vector< SMITH_STASHED_TRACE > & SmithStashedTraces()
void ShowLegend(bool aEnable)
wxString GetLabelY2() const
void EnableCursor(TRACE *aTrace, int aCursorId, const wxString &aSignalName)
int GetViewIndex(SIM_VIEW *aView) const
bool IsSmithMode() const
Refresh the grid z0 from the shown Smith traces, on every view.
wxString GetUnitsX() const
Get the display units (e.g. "V", "A", "dB") for a given trace's own view.
void EnsureThirdYAxisExists()
std::vector< SMITH_STASHED_CURSOR > & SmithStashedCursors()
Turn on/off the cursor for a particular trace.
void SetDottedSecondary(bool aEnable)
Draw secondary signal traces (current or phase) with dotted lines.
void ApplyPreferences(const SIM_PREFERENCES &aPrefs) override
wxString GetUnitsY1() const
void DisableCursor(TRACE *aTrace, int aCursorId)
Reset scale ranges to fit the current traces, on every view.
wxString GetUnitsY3() const
int GetSimOptions() const
Definition sim_tab.h:51
const wxString & GetSpicePlotName() const
Definition sim_tab.h:57
SIM_TYPE GetSimType() const
Definition sim_tab.cpp:71
const wxString & GetSimCommand() const
Definition sim_tab.h:48
static bool IsPlottable(SIM_TYPE aSimType)
Definition sim_tab.cpp:49
void SetSimOptions(int aOptions)
Definition sim_tab.h:52
wxString GetLastSchTextSimCommand() const
Definition sim_tab.h:54
void SetSpicePlotName(const wxString &aPlotName)
Definition sim_tab.h:58
static double ToDouble(const std::string &aString, double aDefault=NAN)
static std::string ToSpice(const std::string &aString)
A single stacked plot area within a SIM_PLOT_TAB.
Trace hidden while the tab is in Smith mode, kept so leaving the mode restores it.
const wxRealPoint & GetGamma() const
Cursor that snaps along a Smith chart locus, keyed by frequency.
double GetReferenceImpedance() const
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.
bool ParseNoiseCommand(const wxString &aCmd, wxString *aOutput, wxString *aRef, wxString *aSource, wxString *aScale, SPICE_VALUE *aPts, SPICE_VALUE *aFStart, SPICE_VALUE *aFStop, bool *aSaveAll)
wxString GetSchTextSimCommand()
Return simulation command directives placed in schematic sheets (if any).
SIM_TRACE_TYPE VectorToSignal(const std::string &aVector, wxString &aSignal) const
Return name of Spice dataset for a specific trace.
virtual std::string TunerCommand(const SPICE_ITEM &aItem, double aValue) const
wxString GetWorkbookFilename() const
void SetWorkbookFilename(const wxString &aFilename)
virtual std::vector< double > GetImaginaryVector(const std::string &aName, int aMaxLen=-1)=0
Return a requested vector with imaginary values.
virtual bool Command(const std::string &aCmd)=0
Execute a Spice command as if it was typed into console.
static wxString TypeToName(SIM_TYPE aType, bool aShortName)
Return a string with simulation name based on enum.
std::shared_ptr< SPICE_SETTINGS > & Settings()
Return the simulator configuration settings.
virtual wxString CurrentPlotName() const =0
virtual std::vector< double > GetRealVector(const std::string &aName, int aMaxLen=-1)=0
Return a requested vector with real values.
virtual std::vector< double > GetGainVector(const std::string &aName, int aMaxLen=-1)=0
Return a requested vector with magnitude values.
virtual std::vector< double > GetPhaseVector(const std::string &aName, int aMaxLen=-1)=0
Return a requested vector with phase values.
Helper class to recognize Spice formatted values.
Definition spice_value.h:52
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)
Overlay layer drawing the Smith chart grid (constant resistance and reactance circles)
bool IsYScaleDefault() const
Clears an explicit Y-scale link if it pointed at a view that no longer exists.
void SetTraceColour(const wxColour &aColour)
SIM_VIEW * GetView() const
bool HasCursor(int aCursorId)
SIM_VIEW * GetYScaleView() const
const std::vector< double > & GetDataX() const
CURSOR * GetCursor(int aCursorId)
void SetYScaleView(SIM_VIEW *aView)
True if this trace's Y-axis scale hasn't been explicitly linked to another view (i....
Custom widget to handle quick component values modification and simulation on the fly.
wxString GetSymbolRef() const
A modified version of the wxInfoBar class that allows us to:
Definition wx_infobar.h:76
void ShowMessageFor(const wxString &aMessage, int aTime, int aFlags=wxICON_INFORMATION, MESSAGE_TYPE aType=WX_INFOBAR::MESSAGE_TYPE::GENERIC)
Show the infobar with the provided message and icon for a specific period of time.
A wrapper for reporting to a wxString object.
Definition reporter.h:242
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:249
const wxPen & GetPen() const
Get pen set for this layer.
Definition mathplot.h:280
Canvas for plotting mpLayer implementations.
Definition mathplot.h:953
int GetMarginLeft() const
Definition mathplot.h:1264
void SetMargins(int top, int right, int bottom, int left)
Set window margins, creating a blank area where some kinds of layers cannot draw.
int GetMarginTop() const
Definition mathplot.h:1258
int GetMarginRight() const
Definition mathplot.h:1260
int GetMarginBottom() const
Definition mathplot.h:1262
void LockY(bool aLock)
Definition mathplot.h:1308
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
This file is part of the common library.
static std::string ToStdString(const wxString &aStr)
#define _(s)
Abstract pattern-matching tool and implementations.
@ CTX_SIGNAL
@ GRIDTRICKS_ID_SELECT
Definition grid_tricks.h:42
@ GRIDTRICKS_ID_COPY
Definition grid_tricks.h:39
@ GRIDTRICKS_ID_DELETE
Definition grid_tricks.h:40
@ GRIDTRICKS_FIRST_CLIENT_ID
Definition grid_tricks.h:44
int m_SimulatorMultiRunCombinationLimit
Maximum number of tuner combinations simulated when using multi-run mode.
static const std::string WorkbookFileExtension
#define traceSettings
KICOMMON_API wxFont GetStatusFont(wxWindow *aWindow)
double VSWR(double aGammaMag)
Definition smith_math.h:78
bool ParseSParamPorts(const wxString &aVectorName, long *aResponsePort, long *aDrivePort)
Definition smith_math.h:117
bool GammaToImpedance(double aRe, double aIm, double aZ0, double &aResistance, double &aReactance)
< Impedance of a reflection coefficient, z = z0 ( 1 + gamma ) / ( 1 - gamma ), false at the gamma = 1...
Definition smith_math.h:65
double ReturnLoss(double aGammaMag)
Series equivalent element of a reactance at one frequency, henries for aReactance > 0,...
Definition smith_math.h:86
see class PGM_BASE
SIM_TRACE_TYPE
Definition sim_types.h:49
@ SPT_TIME
Definition sim_types.h:62
@ SPT_AC_PHASE
Definition sim_types.h:53
@ SPT_SWEEP
Definition sim_types.h:65
@ SPT_UNKNOWN
Definition sim_types.h:68
@ SPT_AC_GAIN
Definition sim_types.h:54
@ SPT_Y_AXIS_MASK
Definition sim_types.h:59
@ SPT_SP_AMP
Definition sim_types.h:56
@ SPT_VOLTAGE
Definition sim_types.h:51
@ SPT_POWER
Definition sim_types.h:55
@ SPT_CURRENT
Definition sim_types.h:52
@ SPT_SP_MASK
Definition sim_types.h:58
@ SPT_LIN_FREQUENCY
Definition sim_types.h:63
@ SPT_SP_SMITH
Definition sim_types.h:57
SIM_TYPE
< Possible simulation types
Definition sim_types.h:31
@ ST_SP
Definition sim_types.h:42
@ ST_TRAN
Definition sim_types.h:41
@ ST_UNKNOWN
Definition sim_types.h:32
@ ST_NOISE
Definition sim_types.h:36
@ ST_AC
Definition sim_types.h:33
@ ST_DISTO
Definition sim_types.h:35
@ ST_TF
Definition sim_types.h:40
@ ST_SENS
Definition sim_types.h:39
@ ST_DC
Definition sim_types.h:34
@ ST_OP
Definition sim_types.h:37
@ ST_FFT
Definition sim_types.h:43
@ ST_PZ
Definition sim_types.h:38
void sortSignals(std::vector< wxString > &signals)
static bool smithColumnsShown(const WX_GRID *aGrid)
wxString vectorNameFromSignalId(int aUserDefinedSignalId)
MEASUREMENTS_GIRD_COLUMNS
@ COL_MEASUREMENT_FORMAT
@ COL_MEASUREMENT_VALUE
@ COL_MEASUREMENT
CURSORS_GRID_COLUMNS
@ COL_CURSOR_SMITH_END
@ COL_CURSOR_REACTANCE
@ COL_CURSOR_NAME
@ COL_CURSOR_RL
@ COL_CURSOR_SIGNAL
@ COL_CURSOR_R
@ COL_CURSOR_X
@ COL_CURSOR_Y
@ COL_CURSOR_SMITH_FIRST
@ COL_CURSOR_VSWR
SIGNALS_GRID_COLUMNS
@ COL_SIGNAL_SHOW
@ COL_SIGNAL_NAME
@ COL_Y_SCALE
@ COL_CURSOR_1
@ COL_SIGNAL_COLOR
@ COL_CURSOR_2
#define ID_SIM_REFRESH
@ MYID_MEASURE_INTEGRAL
@ MYID_MEASURE_MAX_AT
@ MYID_MEASURE_AVG
@ MYID_MEASURE_MAX
@ MYID_FORMAT_VALUE
@ MYID_MEASURE_RMS
@ MYID_DELETE_MEASUREMENT
@ MYID_MEASURE_MIN
@ MYID_MEASURE_MIN_AT
@ MYID_MEASURE_PP
#define UNPLOTTED
static constexpr int SMITH_CURSOR_COLS
SIM_TRACE_TYPE operator|(SIM_TRACE_TYPE aFirst, SIM_TRACE_TYPE aSecond)
#define REFRESH_INTERVAL
const int scale
std::vector< FAB_LAYER_COLOR > dummy
std::map< const TUNER_SLIDER *, double > overrides
std::vector< std::vector< double > > xRuns
std::vector< std::vector< double > > yValues
std::string value
Definition sim_model.h:398
const INFO & info
Definition sim_model.h:399
Contains preferences pertaining to the simulator.
Cursor recorded when entering Smith mode, so leaving restores it to its original trace.
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:39
wxString ToString() const
void UpdateUnits(const wxString &aUnits)
IbisParser parser & reporter
KIBIS_MODEL * model
VECTOR3I res
wxString result
Test unit parsing edge cases and error handling.
Definition of file extensions used in Kicad.