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