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, you may find one here:
21 * https://www.gnu.org/licenses/gpl-3.0.html
22 * or you may search the http://www.gnu.org website for the version 3 license,
23 * or you may write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 */
26
27#include <algorithm>
28#include <memory>
29#include <type_traits>
30
31#include <wx/event.h>
32#include <fmt/format.h>
33#include <wx/wfstream.h>
34#include <wx/stdstream.h>
35#include <wx/debug.h>
36#include <wx/clipbrd.h>
37#include <wx/log.h>
38
40#include <sch_edit_frame.h>
41#include <confirm.h>
45#include <widgets/wx_grid.h>
46#include <grid_tricks.h>
47#include <eda_pattern_match.h>
48#include <string_utils.h>
49#include <pgm_base.h>
51#include <sim/simulator_frame.h>
52#include <sim/sim_plot_tab.h>
53#include <sim/spice_simulator.h>
56#include <eeschema_settings.h>
57#include <advanced_config.h>
58#include <magic_enum.hpp>
59
60
62{
63 int res = static_cast<int>( aFirst ) | static_cast<int>( aSecond);
64
65 return static_cast<SIM_TRACE_TYPE>( res );
66}
67
68
77
78
86
87
94
95
96enum
97{
107
110};
111
112
114{
115public:
117 GRID_TRICKS( aGrid ),
118 m_parent( aParent ),
119 m_menuRow( 0 ),
120 m_menuCol( 0 )
121 {}
122
123protected:
124 void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
125 void doPopupSelection( wxCommandEvent& event ) override;
126
127protected:
131};
132
133
134void SIGNALS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
135{
136 SIM_TAB* panel = m_parent->GetCurrentSimTab();
137
138 if( !panel )
139 return;
140
141 m_menuRow = aEvent.GetRow();
142 m_menuCol = aEvent.GetCol();
143
145 {
146 if( !( m_grid->IsInSelection( m_menuRow, m_menuCol ) ) )
147 m_grid->ClearSelection();
148
149 m_grid->SetGridCursor( m_menuRow, m_menuCol );
150
151 if( panel->GetSimType() == ST_TRAN || panel->GetSimType() == ST_AC
152 || panel->GetSimType() == ST_DC || panel->GetSimType() == ST_SP )
153 {
154 menu.Append( MYID_MEASURE_MIN, _( "Measure Min" ) );
155 menu.Append( MYID_MEASURE_MAX, _( "Measure Max" ) );
156 menu.Append( MYID_MEASURE_AVG, _( "Measure Average" ) );
157 menu.Append( MYID_MEASURE_RMS, _( "Measure RMS" ) );
158 menu.Append( MYID_MEASURE_PP, _( "Measure Peak-to-peak" ) );
159
160 if( panel->GetSimType() == ST_AC || panel->GetSimType() == ST_SP )
161 {
162 menu.Append( MYID_MEASURE_MIN_AT, _( "Measure Frequency of Min" ) );
163 menu.Append( MYID_MEASURE_MAX_AT, _( "Measure Frequency of Max" ) );
164 }
165 else
166 {
167 menu.Append( MYID_MEASURE_MIN_AT, _( "Measure Time of Min" ) );
168 menu.Append( MYID_MEASURE_MAX_AT, _( "Measure Time of Max" ) );
169 }
170
171 menu.Append( MYID_MEASURE_INTEGRAL, _( "Measure Integral" ) );
172
173 if( panel->GetSimType() == ST_TRAN )
174 {
175 menu.AppendSeparator();
176 menu.Append( MYID_FOURIER, _( "Perform Fourier Analysis..." ) );
177 }
178
179 menu.AppendSeparator();
180 menu.Append( GRIDTRICKS_ID_COPY, _( "Copy Signal Name" ) + "\tCtrl+C" );
181
182 menu.AppendSeparator();
183 menu.Append( GRIDTRICKS_ID_SELECT, _( "Create new cursor..." ) );
184
185 m_grid->PopupMenu( &menu );
186 }
187 }
188 else if( m_menuCol > static_cast<int>( COL_CURSOR_2 ) )
189 {
190 menu.Append( GRIDTRICKS_ID_SELECT, _( "Create new cursor..." ) );
191
192 menu.AppendSeparator();
193
194 wxString msg = m_grid->GetColLabelValue( m_grid->GetNumberCols() - 1 );
195
196 menu.AppendSeparator();
197 menu.Append( GRIDTRICKS_ID_DELETE, wxString::Format( _( "Delete %s..." ), msg ) );
198
199 m_grid->PopupMenu( &menu );
200 }
201 else
202 {
203 menu.Append( GRIDTRICKS_ID_SELECT, _( "Create new cursor..." ) );
204
205 m_grid->PopupMenu( &menu );
206 }
207}
208
209
210void SIGNALS_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
211{
212 std::vector<wxString> signals;
213
214 wxGridCellCoordsArray cells1 = m_grid->GetSelectionBlockTopLeft();
215 wxGridCellCoordsArray cells2 = m_grid->GetSelectionBlockBottomRight();
216
217 for( size_t i = 0; i < cells1.Count(); i++ )
218 {
219 if( cells1[i].GetCol() == COL_SIGNAL_NAME )
220 {
221 for( int j = cells1[i].GetRow(); j < cells2[i].GetRow() + 1; j++ )
222 {
223 signals.push_back( m_grid->GetCellValue( j, cells1[i].GetCol() ) );
224 }
225 }
226 }
227
228 wxGridCellCoordsArray cells3 = m_grid->GetSelectedCells();
229
230 for( size_t i = 0; i < cells3.Count(); i++ )
231 {
232 if( cells3[i].GetCol() == COL_SIGNAL_NAME )
233 signals.push_back( m_grid->GetCellValue( cells3[i].GetRow(), cells3[i].GetCol() ) );
234 }
235
236 if( signals.size() < 1 )
237 signals.push_back( m_grid->GetCellValue( m_menuRow, m_menuCol ) );
238
239 auto addMeasurement =
240 [this]( const wxString& cmd, wxString signal )
241 {
242 if( signal.EndsWith( _( " (phase)" ) ) )
243 return;
244
245 if( signal.EndsWith( _( " (gain)" ) ) || signal.EndsWith( _( " (amplitude)" ) ) )
246 {
247 signal = signal.Left( signal.length() - 7 );
248
249 if( signal.Upper().StartsWith( wxS( "V(" ) ) )
250 signal = wxS( "vdb" ) + signal.Mid( 1 );
251 }
252
253 m_parent->AddMeasurement( cmd + wxS( " " ) + signal );
254 };
255
256 if( event.GetId() == MYID_MEASURE_MIN )
257 {
258 for( const wxString& signal : signals )
259 addMeasurement( wxS( "MIN" ), signal );
260 }
261 else if( event.GetId() == MYID_MEASURE_MAX )
262 {
263 for( const wxString& signal : signals )
264 addMeasurement( wxS( "MAX" ), signal );
265 }
266 else if( event.GetId() == MYID_MEASURE_AVG )
267 {
268 for( const wxString& signal : signals )
269 addMeasurement( wxS( "AVG" ), signal );
270 }
271 else if( event.GetId() == MYID_MEASURE_RMS )
272 {
273 for( const wxString& signal : signals )
274 addMeasurement( wxS( "RMS" ), signal );
275 }
276 else if( event.GetId() == MYID_MEASURE_PP )
277 {
278 for( const wxString& signal : signals )
279 addMeasurement( wxS( "PP" ), signal );
280 }
281 else if( event.GetId() == MYID_MEASURE_MIN_AT )
282 {
283 for( const wxString& signal : signals )
284 addMeasurement( wxS( "MIN_AT" ), signal );
285 }
286 else if( event.GetId() == MYID_MEASURE_MAX_AT )
287 {
288 for( const wxString& signal : signals )
289 addMeasurement( wxS( "MAX_AT" ), signal );
290 }
291 else if( event.GetId() == MYID_MEASURE_INTEGRAL )
292 {
293 for( const wxString& signal : signals )
294 addMeasurement( wxS( "INTEG" ), signal );
295 }
296 else if( event.GetId() == MYID_FOURIER )
297 {
298 wxString title;
299 wxString fundamental = wxT( "1K" );
300
301 if( signals.size() == 1 )
302 title.Printf( _( "Fourier Analysis of %s" ), signals[0] );
303 else
304 title = _( "Fourier Analyses of Multiple Signals" );
305
306 WX_TEXT_ENTRY_DIALOG dlg( m_parent, _( "Fundamental frequency:" ), title, fundamental );
307
308 if( dlg.ShowModal() != wxID_OK )
309 return;
310
311 if( !dlg.GetValue().IsEmpty() )
312 fundamental = dlg.GetValue();
313
314 for( const wxString& signal : signals )
315 m_parent->DoFourier( signal, fundamental );
316 }
317 else if( event.GetId() == GRIDTRICKS_ID_COPY )
318 {
319 wxLogNull doNotLog; // disable logging of failed clipboard actions
320 wxString txt;
321
322 for( const wxString& signal : signals )
323 {
324 if( !txt.IsEmpty() )
325 txt += '\r';
326
327 txt += signal;
328 }
329
330 if( wxTheClipboard->Open() )
331 {
332 wxTheClipboard->SetData( new wxTextDataObject( txt ) );
333 wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
334 wxTheClipboard->Close();
335 }
336 }
337 else if( event.GetId() == GRIDTRICKS_ID_SELECT )
338 {
339 m_parent->CreateNewCursor();
340 }
341 else if( event.GetId() == GRIDTRICKS_ID_DELETE )
342 {
343 m_parent->DeleteCursor();
344 }
345}
346
347
349{
350public:
352 GRID_TRICKS( aGrid ),
353 m_parent( aParent ),
354 m_menuRow( 0 ),
355 m_menuCol( 0 )
356 {}
357
358protected:
359 void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
360 void doPopupSelection( wxCommandEvent& event ) override;
361
362protected:
366};
367
368
369void CURSORS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
370{
371 m_menuRow = aEvent.GetRow();
372 m_menuCol = aEvent.GetCol();
373
375 {
376 wxString msg = m_grid->GetColLabelValue( m_menuCol );
377
378 menu.Append( MYID_FORMAT_VALUE, wxString::Format( _( "Format %s..." ), msg ) );
379 menu.AppendSeparator();
380 }
381
382 GRID_TRICKS::showPopupMenu( menu, aEvent );
383}
384
385
386void CURSORS_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
387{
388 auto getSignalName =
389 [this]( int row ) -> wxString
390 {
391 wxString signal = m_grid->GetCellValue( row, COL_CURSOR_SIGNAL );
392
393 if( signal.EndsWith( "[2 - 1]" ) )
394 signal = signal.Left( signal.length() - 7 );
395
396 return signal;
397 };
398
399 if( event.GetId() == MYID_FORMAT_VALUE )
400 {
401 int axis = m_menuCol - COL_CURSOR_X;
402 SPICE_VALUE_FORMAT format = m_parent->GetCursorFormat( m_menuRow, axis );
403 DIALOG_SIM_FORMAT_VALUE formatDialog( m_parent, &format );
404
405 if( formatDialog.ShowModal() == wxID_OK )
406 {
407 for( int row = 0; row < m_grid->GetNumberRows(); ++row )
408 {
409 if( getSignalName( row ) == getSignalName( m_menuRow ) )
410 m_parent->SetCursorFormat( row, axis, format );
411 }
412 }
413 }
414 else
415 {
417 }
418}
419
420
422{
423public:
425 GRID_TRICKS( aGrid ),
426 m_parent( aParent ),
427 m_menuRow( 0 ),
428 m_menuCol( 0 )
429 {}
430
431protected:
432 void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
433 void doPopupSelection( wxCommandEvent& event ) override;
434
435protected:
439};
440
441
442void MEASUREMENTS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
443{
444 m_menuRow = aEvent.GetRow();
445 m_menuCol = aEvent.GetCol();
446
447 if( !( m_grid->IsInSelection( m_menuRow, m_menuCol ) ) )
448 m_grid->ClearSelection();
449
450 m_grid->SetGridCursor( m_menuRow, m_menuCol );
451
453 menu.Append( MYID_FORMAT_VALUE, _( "Format Value..." ) );
454
455 if( m_menuRow < ( m_grid->GetNumberRows() - 1 ) )
456 menu.Append( MYID_DELETE_MEASUREMENT, _( "Delete Measurement" ) );
457
458 menu.AppendSeparator();
459
460 GRID_TRICKS::showPopupMenu( menu, aEvent );
461}
462
463
465{
466 if( event.GetId() == MYID_FORMAT_VALUE )
467 {
468 SPICE_VALUE_FORMAT format = m_parent->GetMeasureFormat( m_menuRow );
469 DIALOG_SIM_FORMAT_VALUE formatDialog( m_parent, &format );
470
471 if( formatDialog.ShowModal() == wxID_OK )
472 {
473 m_parent->SetMeasureFormat( m_menuRow, format );
474 m_parent->UpdateMeasurement( m_menuRow );
475 m_parent->OnModify();
476 }
477 }
478 else if( event.GetId() == MYID_DELETE_MEASUREMENT )
479 {
480 std::vector<int> measurements;
481
482 wxGridCellCoordsArray cells1 = m_grid->GetSelectionBlockTopLeft();
483 wxGridCellCoordsArray cells2 = m_grid->GetSelectionBlockBottomRight();
484
485 for( size_t i = 0; i < cells1.Count(); i++ )
486 {
487 if( cells1[i].GetCol() == COL_MEASUREMENT )
488 {
489 for( int j = cells1[i].GetRow(); j < cells2[i].GetRow() + 1; j++ )
490 measurements.push_back( j );
491 }
492 }
493
494 wxGridCellCoordsArray cells3 = m_grid->GetSelectedCells();
495
496 for( size_t i = 0; i < cells3.Count(); i++ )
497 {
498 if( cells3[i].GetCol() == COL_MEASUREMENT )
499 measurements.push_back( cells3[i].GetRow() );
500 }
501
502 if( measurements.size() < 1 )
503 measurements.push_back( m_menuRow );
504
505 // When deleting a row, we'll change the indexes.
506 // To avoid problems, we can start with the highest indexes.
507 sort( measurements.begin(), measurements.end(), std::greater<>() );
508
509 for( int row : measurements )
510 m_parent->DeleteMeasurement( row );
511
512 m_grid->ClearSelection();
513
514 m_parent->OnModify();
515 }
516 else
517 {
519 }
520}
521
522
524{
525public:
527 m_frame( aFrame )
528 {
529 m_frame->m_SuppressGridEvents++;
530 }
531
533 {
534 m_frame->m_SuppressGridEvents--;
535 }
536
537private:
539};
540
541
542#define ID_SIM_REFRESH 10207
543#define REFRESH_INTERVAL 50 // 20 frames/second.
544
545
547 SCH_EDIT_FRAME* aSchematicFrame ) :
548 SIMULATOR_FRAME_UI_BASE( aSimulatorFrame ),
550 m_simulatorFrame( aSimulatorFrame ),
551 m_schematicFrame( aSchematicFrame ),
552 m_darkMode( true ),
553 m_plotNumber( 0 ),
555{
556 // Get the previous size and position of windows:
557 LoadSettings( m_schematicFrame->eeconfig() );
558
559 m_filter->SetHint( _( "Filter" ) );
560
561 m_signalsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
562 m_cursorsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
563 m_measurementsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
564
565 m_signalsGrid->PushEventHandler( new SIGNALS_GRID_TRICKS( this, m_signalsGrid ) );
566 m_cursorsGrid->PushEventHandler( new CURSORS_GRID_TRICKS( this, m_cursorsGrid ) );
567 m_measurementsGrid->PushEventHandler( new MEASUREMENTS_GRID_TRICKS( this, m_measurementsGrid ) );
568
569 wxGridCellAttr* attr = new wxGridCellAttr;
570 attr->SetReadOnly();
571 m_signalsGrid->SetColAttr( COL_SIGNAL_NAME, attr );
572
573 attr = new wxGridCellAttr;
574 attr->SetReadOnly();
575 m_cursorsGrid->SetColAttr( COL_CURSOR_NAME, attr );
576
577 attr = new wxGridCellAttr;
578 attr->SetReadOnly();
579 m_cursorsGrid->SetColAttr( COL_CURSOR_SIGNAL, attr );
580
581 attr = new wxGridCellAttr;
582 attr->SetReadOnly();
583 m_cursorsGrid->SetColAttr( COL_CURSOR_Y, attr );
584
586
587 attr = new wxGridCellAttr;
588 attr->SetReadOnly();
589 m_measurementsGrid->SetColAttr( COL_MEASUREMENT_VALUE, attr );
590
591 // Prepare the color list to plot traces
593
594 Bind( EVT_SIM_CURSOR_UPDATE, &SIMULATOR_FRAME_UI::onPlotCursorUpdate, this );
595
596 Bind( wxEVT_TIMER,
597 [&]( wxTimerEvent& aEvent )
598 {
599 OnSimRefresh( false );
600
601 if( m_simulatorFrame->GetSimulator()->IsRunning() )
602 m_refreshTimer.Start( REFRESH_INTERVAL, wxTIMER_ONE_SHOT );
603 },
604 m_refreshTimer.GetId() );
605
606#ifndef wxHAS_NATIVE_TABART
607 // Default non-native tab art has ugly gradients we don't want
608 m_plotNotebook->SetArtProvider( new wxAuiSimpleTabArt() );
609#endif
610}
611
612
614{
615 // Delete the GRID_TRICKS.
616 m_signalsGrid->PopEventHandler( true );
617 m_cursorsGrid->PopEventHandler( true );
618 m_measurementsGrid->PopEventHandler( true );
619}
620
621
623{
624 for( auto& m_cursorFormat : m_cursorFormats )
625 {
626 m_cursorFormat[0] = { 3, wxS( "~s" ) };
627 m_cursorFormat[1] = { 3, wxS( "~V" ) };
628 }
629
630 // proper init and transfer/copy m_cursorFormats
631 // we work on m_cursorFormatsDyn from now on.
632 // TODO: rework +- LOC when m_cursorFormatsDyn and m_cursorFormats get merged.
633 m_cursorFormatsDyn.clear();
634 m_cursorFormatsDyn.resize( std::size( m_cursorFormats ) );
635
636 for( size_t index = 0; index < std::size( m_cursorFormats ); index++ )
637 {
638 for( size_t index2 = 0; index2 < std::size( m_cursorFormats[0] ); index2++ )
639 {
640 m_cursorFormatsDyn[index].push_back( m_cursorFormats[index][index2] );
641 }
642 }
643
644 // Dump string helper, tries to get the current higher cursor name to form the next one.
645 // Based on the column labeling
646 // TODO: "Cursor n" may translate as "n Cursor" in other languages
647 // TBD how to handle; just forbid for now.
648 int nameMax = 0;
649
650 for( int i = 0; i < m_signalsGrid->GetNumberCols(); i++ )
651 {
652 wxString maxCursor = m_signalsGrid->GetColLabelValue( i );
653
654 maxCursor.Replace( _( "Cursor " ), "" );
655
656 int tmpMax = wxAtoi( maxCursor );
657
658 if( nameMax < tmpMax )
659 nameMax = tmpMax;
660 }
661
662 m_customCursorsCnt = nameMax + 1; // Init with a +1 on top of current cursor 2, defaults to 3
663}
664
665
667{
668 std::vector<SPICE_VALUE_FORMAT> tmp;
669 // m_cursorFormatsDyn should be equal with m_cursorFormats on first entry here.
670 m_cursorFormatsDyn.emplace_back( tmp );
671
672 m_cursorFormatsDyn[m_customCursorsCnt].push_back( { 3, wxS( "~s" ) } );
673 m_cursorFormatsDyn[m_customCursorsCnt].push_back( { 3, wxS( "~V" ) } );
674
675 wxString cursor_name = wxString( _( "Cursor " ) ) << m_customCursorsCnt;
676
677 m_signalsGrid->InsertCols( m_signalsGrid->GetNumberCols() , 1, true );
678 m_signalsGrid->SetColLabelValue( m_signalsGrid->GetNumberCols() - 1, cursor_name );
679
680 wxGridCellAttr* attr = new wxGridCellAttr;
681 m_signalsGrid->SetColAttr( COL_CURSOR_2 + m_customCursorsCnt, attr );
682
684
687 OnModify();
688}
689
690
692{
693 int col = m_signalsGrid->GetNumberCols();
694 int rows = m_signalsGrid->GetNumberRows();
695
696 if( col > COL_CURSOR_2 )
697 {
698 // Now we need to find the active cursor and deactivate before removing the column,
699 // Send the dummy event to update the UI
700 for( int i = 0; i < rows; i++ )
701 {
702 if( m_signalsGrid->GetCellValue( i, col - 1 ) == wxS( "1" ) )
703 {
704 m_signalsGrid->SetCellValue( i, col - 1, wxEmptyString );
705 wxGridEvent aDummy( wxID_ANY, wxEVT_GRID_CELL_CHANGED, m_signalsGrid, i, col - 1 );
706 onSignalsGridCellChanged( aDummy );
707 break;
708 }
709
710 }
711
712 m_signalsGrid->DeleteCols( col - 1, 1, false );
713 m_cursorFormatsDyn.pop_back();
715 m_plotNotebook->Refresh();
718 OnModify();
719 }
720}
721
722
724{
725 for( int ii = 0; ii < static_cast<int>( m_plotNotebook->GetPageCount() ); ++ii )
726 {
727 if( SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( ii ) ) )
728 {
729 simTab->OnLanguageChanged();
730
731 wxString pageTitle( simulator()->TypeToName( simTab->GetSimType(), true ) );
732 pageTitle.Prepend( wxString::Format( _( "Analysis %u - " ), ii+1 /* 1-based */ ) );
733
734 m_plotNotebook->SetPageText( ii, pageTitle );
735 }
736 }
737
738 m_filter->SetHint( _( "Filter" ) );
739
740 m_signalsGrid->SetColLabelValue( COL_SIGNAL_NAME, _( "Signal" ) );
741 m_signalsGrid->SetColLabelValue( COL_SIGNAL_SHOW, _( "Plot" ) );
742 m_signalsGrid->SetColLabelValue( COL_SIGNAL_COLOR, _( "Color" ) );
743 m_signalsGrid->SetColLabelValue( COL_CURSOR_1, _( "Cursor 1" ) );
744 m_signalsGrid->SetColLabelValue( COL_CURSOR_2, _( "Cursor 2" ) );
745
746 m_cursorsGrid->SetColLabelValue( COL_CURSOR_NAME, _( "Cursor" ) );
747 m_cursorsGrid->SetColLabelValue( COL_CURSOR_SIGNAL, _( "Signal" ) );
748 m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, _( "Time" ) );
749 m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, _( "Value" ) );
751
752 for( TUNER_SLIDER* tuner : m_tuners )
753 tuner->ShowChangedLanguage();
754}
755
756
771
772
774{
776
777 settings.view.plot_panel_width = m_splitterLeftRight->GetSashPosition();
778 settings.view.plot_panel_height = m_splitterPlotAndConsole->GetSashPosition();
779 settings.view.signal_panel_height = m_splitterSignals->GetSashPosition();
780 settings.view.cursors_panel_height = m_splitterCursors->GetSashPosition();
781 settings.view.measurements_panel_height = m_splitterMeasurements->GetSashPosition();
782 settings.view.white_background = !m_darkMode;
783}
784
785
787{
788 m_preferences = aPrefs;
789
790 for( std::size_t i = 0; i < m_plotNotebook->GetPageCount(); ++i )
791 {
792 if( SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( i ) ) )
793 simTab->ApplyPreferences( aPrefs );
794 }
795}
796
797
799{
800 if( !simulator()->Settings()->GetWorkbookFilename().IsEmpty() )
801 {
802 wxFileName filename = simulator()->Settings()->GetWorkbookFilename();
803 filename.SetPath( m_schematicFrame->Prj().GetProjectPath() );
804
805 if( !LoadWorkbook( filename.GetFullPath() ) )
807 }
808 else if( m_simulatorFrame->LoadSimulator( wxEmptyString, 0 ) )
809 {
810 wxString schTextSimCommand = circuitModel()->GetSchTextSimCommand();
811
812 if( !schTextSimCommand.IsEmpty() )
813 {
814 SIM_TAB* simTab = NewSimTab( schTextSimCommand );
816 }
817
819 rebuildSignalsGrid( m_filter->GetValue() );
820 }
821}
822
823
841
842
843void sortSignals( std::vector<wxString>& signals )
844{
845 std::sort( signals.begin(), signals.end(),
846 []( const wxString& lhs, const wxString& rhs )
847 {
848 // Sort voltages first
849 if( lhs.Upper().StartsWith( 'V' ) && !rhs.Upper().StartsWith( 'V' ) )
850 return true;
851 else if( !lhs.Upper().StartsWith( 'V' ) && rhs.Upper().StartsWith( 'V' ) )
852 return false;
853
854 return StrNumCmp( lhs, rhs, true /* ignore case */ ) < 0;
855 } );
856}
857
858
860{
861 SUPPRESS_GRID_CELL_EVENTS raii( this );
862
863 m_signalsGrid->ClearRows();
864
865 SIM_PLOT_TAB* plotPanel = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
866
867 if( !plotPanel )
868 return;
869
870 SIM_TYPE simType = plotPanel->GetSimType();
871 std::vector<wxString> signals;
872
873 if( plotPanel->GetSimType() == ST_FFT )
874 {
875 wxStringTokenizer tokenizer( plotPanel->GetSimCommand(), " \t\r\n", wxTOKEN_STRTOK );
876
877 while( tokenizer.HasMoreTokens() && tokenizer.GetNextToken().Lower() != wxT( "fft" ) )
878 {};
879
880 while( tokenizer.HasMoreTokens() )
881 signals.emplace_back( tokenizer.GetNextToken() );
882 }
883 else
884 {
885 // NB: m_signals are already broken out into gain/phase, but m_userDefinedSignals are
886 // as the user typed them
887
888 for( const wxString& signal : m_signals )
889 signals.push_back( signal );
890
891 for( const auto& [ id, signal ] : m_userDefinedSignals )
892 {
893 if( simType == ST_AC )
894 {
895 signals.push_back( signal + _( " (gain)" ) );
896 signals.push_back( signal + _( " (phase)" ) );
897 }
898 else if( simType == ST_SP )
899 {
900 signals.push_back( signal + _( " (amplitude)" ) );
901 signals.push_back( signal + _( " (phase)" ) );
902 }
903 else
904 {
905 signals.push_back( signal );
906 }
907 }
908
909 sortSignals( signals );
910 }
911
912 if( aFilter.IsEmpty() )
913 aFilter = wxS( "*" );
914
915 EDA_COMBINED_MATCHER matcher( aFilter.Upper(), CTX_SIGNAL );
916 int row = 0;
917
918 for( const wxString& signal : signals )
919 {
920 if( matcher.Find( signal.Upper() ) )
921 {
922 int traceType = SPT_UNKNOWN;
923 wxString vectorName = vectorNameFromSignalName( plotPanel, signal, &traceType );
924 TRACE* trace = plotPanel->GetTrace( vectorName, traceType );
925
926 m_signalsGrid->AppendRows( 1 );
927 m_signalsGrid->SetCellValue( row, COL_SIGNAL_NAME, signal );
928
929 wxGridCellAttr* attr = new wxGridCellAttr;
930 attr->SetRenderer( new wxGridCellBoolRenderer() );
931 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
932 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
933 m_signalsGrid->SetAttr( row, COL_SIGNAL_SHOW, attr );
934
935 if( !trace )
936 {
937 attr = new wxGridCellAttr;
938 attr->SetReadOnly();
939 m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
940 m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, wxEmptyString );
941
942 attr = new wxGridCellAttr;
943 attr->SetReadOnly();
944 m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
945
946 attr = new wxGridCellAttr;
947 attr->SetReadOnly();
948 m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
949
950 if( m_customCursorsCnt > 3 )
951 {
952 for( int i = 1; i <= m_customCursorsCnt - 3; i++ )
953 {
954 attr = new wxGridCellAttr;
955 attr->SetReadOnly();
956 m_signalsGrid->SetAttr( row, COL_CURSOR_2 + i, attr );
957 }
958 }
959 }
960 else
961 {
962 m_signalsGrid->SetCellValue( row, COL_SIGNAL_SHOW, wxS( "1" ) );
963
964 attr = new wxGridCellAttr;
965 attr->SetRenderer( new GRID_CELL_COLOR_RENDERER( this ) );
966 attr->SetEditor( new GRID_CELL_COLOR_SELECTOR( this, m_signalsGrid ) );
967 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
968 m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
969 KIGFX::COLOR4D color( trace->GetPen().GetColour() );
970 m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, color.ToCSSString() );
971
972 attr = new wxGridCellAttr;
973 attr->SetRenderer( new wxGridCellBoolRenderer() );
974 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
975 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
976 m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
977 m_signalsGrid->SetCellValue( row, COL_CURSOR_1, trace->GetCursor( 1 ) ? "1" : "0" );
978
979 attr = new wxGridCellAttr;
980 attr->SetRenderer( new wxGridCellBoolRenderer() );
981 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
982 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
983 m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
984 m_signalsGrid->SetCellValue( row, COL_CURSOR_2, trace->GetCursor( 2 ) ? "1" : "0" );
985
986 if( m_customCursorsCnt > 3 )
987 {
988 for( int i = 1; i <= m_customCursorsCnt - 3; i++ )
989 {
990 attr = new wxGridCellAttr;
991 attr->SetRenderer( new wxGridCellBoolRenderer() );
992 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
993 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
994 m_signalsGrid->SetAttr( row, COL_CURSOR_2 + i, attr );
995 m_signalsGrid->SetCellValue( row, COL_CURSOR_2 + i,
996 trace->GetCursor( i ) ? "1" : "0" );
997 }
998 }
999 }
1000 row++;
1001 }
1002 }
1003}
1004
1005
1007{
1008 m_signals.clear();
1009
1010 int options = m_simulatorFrame->GetCurrentOptions();
1011 SIM_TYPE simType = m_simulatorFrame->GetCurrentSimType();
1012 wxString unconnected = wxString( wxS( "unconnected-(" ) );
1013
1014 if( simType == ST_UNKNOWN )
1015 simType = ST_TRAN;
1016
1017 unconnected.Replace( '(', '_' ); // Convert to SPICE markup
1018
1019 auto addSignal =
1020 [&]( const wxString& aSignalName )
1021 {
1022 if( simType == ST_AC )
1023 {
1024 m_signals.push_back( aSignalName + _( " (gain)" ) );
1025 m_signals.push_back( aSignalName + _( " (phase)" ) );
1026 }
1027 else if( simType == ST_SP )
1028 {
1029 m_signals.push_back( aSignalName + _( " (amplitude)" ) );
1030 m_signals.push_back( aSignalName + _( " (phase)" ) );
1031 }
1032 else
1033 {
1034 m_signals.push_back( aSignalName );
1035 }
1036 };
1037
1039 && ( simType == ST_TRAN || simType == ST_DC || simType == ST_AC || simType == ST_FFT) )
1040 {
1041 for( const wxString& net : circuitModel()->GetNets() )
1042 {
1043 // netnames are escaped (can contain "{slash}" for '/') Unscape them:
1044 wxString netname = UnescapeString( net );
1046
1047 if( netname == "GND" || netname == "0" || netname.StartsWith( unconnected ) )
1048 continue;
1049
1050 m_netnames.emplace_back( netname );
1051 addSignal( wxString::Format( wxS( "V(%s)" ), netname ) );
1052 }
1053 }
1054
1056 && ( simType == ST_TRAN || simType == ST_DC || simType == ST_AC ) )
1057 {
1058 for( const SPICE_ITEM& item : circuitModel()->GetItems() )
1059 {
1060 // Add all possible currents for the device.
1061 for( const std::string& name : item.model->SpiceGenerator().CurrentNames( item ) )
1062 addSignal( name );
1063 }
1064 }
1065
1067 && ( simType == ST_TRAN || simType == ST_DC ) )
1068 {
1069 for( const SPICE_ITEM& item : circuitModel()->GetItems() )
1070 {
1071 if( item.model->GetPinCount() >= 2 )
1072 {
1073 wxString name = item.model->SpiceGenerator().ItemName( item );
1074 addSignal( wxString::Format( wxS( "P(%s)" ), name ) );
1075 }
1076 }
1077 }
1078
1079 if( simType == ST_NOISE )
1080 {
1081 addSignal( wxS( "inoise_spectrum" ) );
1082 addSignal( wxS( "onoise_spectrum" ) );
1083 }
1084
1085 if( simType == ST_SP )
1086 {
1087 std::vector<std::string> portnums;
1088
1089 for( const SPICE_ITEM& item : circuitModel()->GetItems() )
1090 {
1091 wxString name = item.model->SpiceGenerator().ItemName( item );
1092
1093 // We are only looking for voltage sources in .SP mode
1094 if( !name.StartsWith( "V" ) )
1095 continue;
1096
1097 std::string portnum = "";
1098
1099 if( const SIM_MODEL::PARAM* portnum_param = item.model->FindParam( "portnum" ) )
1100 portnum = SIM_VALUE::ToSpice( portnum_param->value );
1101
1102 if( portnum != "" )
1103 portnums.push_back( portnum );
1104 }
1105
1106 for( const std::string& portnum1 : portnums )
1107 {
1108 for( const std::string& portnum2 : portnums )
1109 {
1110 addSignal( wxString::Format( wxS( "S_%s_%s" ), portnum1, portnum2 ) );
1111 }
1112 }
1113 }
1114
1115 // Add .SAVE and .PROBE directives
1116 for( const wxString& directive : circuitModel()->GetDirectives() )
1117 {
1118 wxStringTokenizer directivesTokenizer( directive, "\r\n", wxTOKEN_STRTOK );
1119
1120 while( directivesTokenizer.HasMoreTokens() )
1121 {
1122 wxString line = directivesTokenizer.GetNextToken().Upper();
1123 wxString directiveParams;
1124
1125 if( line.StartsWith( wxS( ".SAVE" ), &directiveParams )
1126 || line.StartsWith( wxS( ".PROBE" ), &directiveParams ) )
1127 {
1128 wxStringTokenizer paramsTokenizer( directiveParams, " \t", wxTOKEN_STRTOK );
1129
1130 while( paramsTokenizer.HasMoreTokens() )
1131 addSignal( paramsTokenizer.GetNextToken() );
1132 }
1133 }
1134 }
1135}
1136
1137
1138SIM_TAB* SIMULATOR_FRAME_UI::NewSimTab( const wxString& aSimCommand )
1139{
1140 SIM_TAB* simTab = nullptr;
1141 SIM_TYPE simType = SPICE_CIRCUIT_MODEL::CommandToSimType( aSimCommand );
1142
1143 if( SIM_TAB::IsPlottable( simType ) )
1144 {
1145 SIM_PLOT_TAB* panel = new SIM_PLOT_TAB( aSimCommand, m_plotNotebook );
1146 simTab = panel;
1148 }
1149 else
1150 {
1151 simTab = new SIM_NOPLOT_TAB( aSimCommand, m_plotNotebook );
1152 }
1153
1154 wxString pageTitle( simulator()->TypeToName( simType, true ) );
1155 pageTitle.Prepend( wxString::Format( _( "Analysis %u - " ), static_cast<unsigned int>( ++m_plotNumber ) ) );
1156
1157 m_plotNotebook->AddPage( simTab, pageTitle, true );
1158
1159 return simTab;
1160}
1161
1162
1163void SIMULATOR_FRAME_UI::OnFilterText( wxCommandEvent& aEvent )
1164{
1165 rebuildSignalsGrid( m_filter->GetValue() );
1166}
1167
1168
1169void SIMULATOR_FRAME_UI::OnFilterMouseMoved( wxMouseEvent& aEvent )
1170{
1171#if defined( __WXOSX__ ) || wxCHECK_VERSION( 3, 3, 0 ) // Doesn't work properly on other ports
1172 wxPoint pos = aEvent.GetPosition();
1173 wxRect ctrlRect = m_filter->GetScreenRect();
1174 int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
1175
1176 if( m_filter->IsSearchButtonVisible() && pos.x < buttonWidth )
1177 SetCursor( wxCURSOR_ARROW );
1178 else if( m_filter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
1179 SetCursor( wxCURSOR_ARROW );
1180 else
1181 SetCursor( wxCURSOR_IBEAM );
1182#endif
1183}
1184
1185
1186wxString vectorNameFromSignalId( int aUserDefinedSignalId )
1187{
1188 return wxString::Format( wxS( "user%d" ), aUserDefinedSignalId );
1189}
1190
1191
1197 const wxString& aSignalName,
1198 int* aTraceType )
1199{
1200 auto looksLikePower = []( const wxString& aExpression ) -> bool
1201 {
1202 wxString exprUpper = aExpression.Upper();
1203
1204 if( exprUpper.Contains( wxS( ":POWER" ) ) )
1205 return true;
1206
1207 if( exprUpper.Find( '*' ) == wxNOT_FOUND )
1208 return false;
1209
1210 if( !exprUpper.Contains( wxS( "V(" ) ) )
1211 return false;
1212
1213 if( !exprUpper.Contains( wxS( "I(" ) ) )
1214 return false;
1215
1216 return true;
1217 };
1218
1219 std::map<wxString, int> suffixes;
1220 suffixes[ _( " (amplitude)" ) ] = SPT_SP_AMP;
1221 suffixes[ _( " (gain)" ) ] = SPT_AC_GAIN;
1222 suffixes[ _( " (phase)" ) ] = SPT_AC_PHASE;
1223
1224 if( aTraceType )
1225 {
1226 if( aPlotTab && aPlotTab->GetSimType() == ST_NOISE )
1227 {
1228 if( getNoiseSource().Upper().StartsWith( 'I' ) )
1229 *aTraceType = SPT_CURRENT;
1230 else
1231 *aTraceType = SPT_VOLTAGE;
1232 }
1233 else
1234 {
1235 wxUniChar firstChar = aSignalName.Upper()[0];
1236
1237 if( firstChar == 'V' )
1238 *aTraceType = SPT_VOLTAGE;
1239 else if( firstChar == 'I' )
1240 *aTraceType = SPT_CURRENT;
1241 else if( firstChar == 'P' )
1242 *aTraceType = SPT_POWER;
1243 }
1244 }
1245
1246 wxString name = aSignalName;
1247
1248 for( const auto& [ candidate, type ] : suffixes )
1249 {
1250 if( name.EndsWith( candidate ) )
1251 {
1252 name = name.Left( name.Length() - candidate.Length() );
1253
1254 if( aTraceType )
1255 *aTraceType |= type;
1256
1257 break;
1258 }
1259 }
1260
1261 for( const auto& [ id, signal ] : m_userDefinedSignals )
1262 {
1263 if( name == signal )
1264 {
1265 if( aTraceType && looksLikePower( signal ) )
1266 {
1267 int suffixBits = *aTraceType & ( SPT_AC_GAIN | SPT_AC_PHASE | SPT_SP_AMP );
1268 *aTraceType = suffixBits | SPT_POWER;
1269 }
1270
1271 return vectorNameFromSignalId( id );
1272 }
1273 }
1274
1275 return name;
1276};
1277
1278
1280{
1281 if( m_SuppressGridEvents > 0 )
1282 return;
1283
1284 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1285
1286 if( !plotTab )
1287 return;
1288
1289 int row = aEvent.GetRow();
1290 int col = aEvent.GetCol();
1291 wxString text = m_signalsGrid->GetCellValue( row, col );
1292 wxString signalName = m_signalsGrid->GetCellValue( row, COL_SIGNAL_NAME );
1293 int traceType = SPT_UNKNOWN;
1294 wxString vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
1295
1296 if( col == COL_SIGNAL_SHOW )
1297 {
1298 if( text == wxS( "1" ) )
1299 updateTrace( vectorName, traceType, plotTab );
1300 else
1301 plotTab->DeleteTrace( vectorName, traceType );
1302
1303 plotTab->GetPlotWin()->UpdateAll();
1304
1305 // Update enabled/visible states of other controls
1308 OnModify();
1309 }
1310 else if( col == COL_SIGNAL_COLOR )
1311 {
1312 KIGFX::COLOR4D color( m_signalsGrid->GetCellValue( row, COL_SIGNAL_COLOR ) );
1313 TRACE* trace = plotTab->GetTrace( vectorName, traceType );
1314
1315 if( trace )
1316 {
1317 trace->SetTraceColour( color.ToColour() );
1318 plotTab->UpdateTraceStyle( trace );
1319 plotTab->UpdatePlotColors();
1320 OnModify();
1321 }
1322 }
1323 else if( col == COL_CURSOR_1 || col == COL_CURSOR_2
1324 || ( ( std::size( m_cursorFormatsDyn ) > std::size( m_cursorFormats ) )
1325 && col > COL_CURSOR_2 ) )
1326 {
1327 int id = col == COL_CURSOR_1 ? 1 : 2;
1328
1329 if( col > COL_CURSOR_2 ) // TODO: clean up logic
1330 {
1331 id = col - 2; // enum SIGNALS_GRID_COLUMNS offset for Cursor n
1332 }
1333
1334 TRACE* activeTrace = nullptr;
1335
1336 if( text == wxS( "1" ) )
1337 {
1338 signalName = m_signalsGrid->GetCellValue( row, COL_SIGNAL_NAME );
1339 vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
1340 activeTrace = plotTab->GetTrace( vectorName, traceType );
1341
1342 if( activeTrace )
1343 plotTab->EnableCursor( activeTrace, id, signalName );
1344
1345 OnModify();
1346 }
1347
1348 // Turn off cursor on other signals.
1349 for( const auto& [name, trace] : plotTab->GetTraces() )
1350 {
1351 if( trace != activeTrace && trace->HasCursor( id ) )
1352 {
1353 plotTab->DisableCursor( trace, id );
1354 OnModify();
1355 }
1356 }
1357
1358 // Update cursor checkboxes (which are really radio buttons)
1360 }
1361}
1362
1363
1365{
1366 if( m_SuppressGridEvents > 0 )
1367 return;
1368
1369 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1370
1371 if( !plotTab )
1372 return;
1373
1374 int row = aEvent.GetRow();
1375 int col = aEvent.GetCol();
1376 wxString text = m_cursorsGrid->GetCellValue( row, col );
1377 wxString cursorName = m_cursorsGrid->GetCellValue( row, COL_CURSOR_NAME );
1378
1379 double value = SPICE_VALUE( text ).ToDouble();
1380
1381 if( col == COL_CURSOR_X )
1382 {
1383 CURSOR* cursor1 = nullptr;
1384 CURSOR* cursor2 = nullptr;
1385
1386 std::vector<CURSOR*> cursorsVec;
1387 cursorsVec.clear();
1388
1389 for( const auto& [name, trace] : plotTab->GetTraces() )
1390 {
1391 if( CURSOR* cursor = trace->GetCursor( 1 ) )
1392 cursor1 = cursor;
1393
1394 if( CURSOR* cursor = trace->GetCursor( 2 ) )
1395 cursor2 = cursor;
1396
1397 int tmp = 3;
1398
1399 if( !cursor1 )
1400 tmp--;
1401 if( !cursor2 )
1402 tmp--;
1403
1404 for( int i = tmp; i < m_customCursorsCnt; i++ )
1405 {
1406 if( CURSOR* cursor = trace->GetCursor( i ) )
1407 {
1408 cursorsVec.emplace_back( cursor );
1409
1410 if( cursorName == ( wxString( "" ) << i ) && cursor )
1411 cursor->SetCoordX( value );
1412 }
1413 }
1414 }
1415
1416 //double value = SPICE_VALUE( text ).ToDouble();
1417
1418 if( cursorName == wxS( "1" ) && cursor1 )
1419 cursor1->SetCoordX( value );
1420 else if( cursorName == wxS( "2" ) && cursor2 )
1421 cursor2->SetCoordX( value );
1422 else if( cursorName == _( "Diff" ) && cursor1 && cursor2 )
1423 cursor2->SetCoordX( cursor1->GetCoords().x + value );
1424
1426 OnModify();
1427 }
1428 else
1429 {
1430 wxFAIL_MSG( wxT( "All other columns are supposed to be read-only!" ) );
1431 }
1432}
1433
1434
1436{
1438 result.FromString( m_measurementsGrid->GetCellValue( aRow, COL_MEASUREMENT_FORMAT ) );
1439 return result;
1440}
1441
1442
1444{
1445 m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_FORMAT, aFormat.ToString() );
1446}
1447
1448
1450{
1451 if( aRow < ( m_measurementsGrid->GetNumberRows() - 1 ) )
1452 m_measurementsGrid->DeleteRows( aRow, 1 );
1453}
1454
1455
1457{
1458 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1459
1460 if( !plotTab )
1461 return;
1462
1463 int row = aEvent.GetRow();
1464 int col = aEvent.GetCol();
1465
1466 if( col == COL_MEASUREMENT )
1467 {
1468 UpdateMeasurement( row );
1470 OnModify();
1471 }
1472 else
1473 {
1474 wxFAIL_MSG( wxT( "All other columns are supposed to be read-only!" ) );
1475 }
1476
1477 // Always leave a single empty row for type-in
1478
1479 int rowCount = static_cast<int>( m_measurementsGrid->GetNumberRows() );
1480 int emptyRows = 0;
1481
1482 for( row = rowCount - 1; row >= 0; row-- )
1483 {
1484 if( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1485 emptyRows++;
1486 else
1487 break;
1488 }
1489
1490 if( emptyRows > 1 )
1491 {
1492 int killRows = emptyRows - 1;
1493 m_measurementsGrid->DeleteRows( rowCount - killRows, killRows );
1494 }
1495 else if( emptyRows == 0 )
1496 {
1497 m_measurementsGrid->AppendRows( 1 );
1498 }
1499}
1500
1501
1502void SIMULATOR_FRAME_UI::OnUpdateUI( wxUpdateUIEvent& event )
1503{
1504 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
1505 {
1506 if( plotTab->GetLegendPosition() != plotTab->m_LastLegendPosition )
1507 {
1508 plotTab->m_LastLegendPosition = plotTab->GetLegendPosition();
1509 OnModify();
1510 }
1511 }
1512}
1513
1514
1530{
1531 static wxRegEx measureParamsRegEx( wxT( "^"
1532 " *"
1533 "([a-zA-Z_]+)"
1534 " +"
1535 "([a-zA-Z]*)\\(([^\\)]+)\\)" ) );
1536
1537 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1538
1539 if( !plotTab )
1540 return;
1541
1542 wxString text = m_measurementsGrid->GetCellValue( aRow, COL_MEASUREMENT );
1543
1544 if( text.IsEmpty() )
1545 {
1546 m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_VALUE, wxEmptyString );
1547 return;
1548 }
1549
1550 wxString simType = simulator()->TypeToName( plotTab->GetSimType(), true );
1551 wxString resultName = wxString::Format( wxS( "meas_result_%u" ), aRow );
1552 wxString result = wxS( "?" );
1553
1554 if( measureParamsRegEx.Matches( text ) )
1555 {
1556 wxString func = measureParamsRegEx.GetMatch( text, 1 ).Upper();
1557 wxString signalType = measureParamsRegEx.GetMatch( text, 2 ).Upper();
1558 wxString deviceName = measureParamsRegEx.GetMatch( text, 3 );
1559 wxString units;
1561
1562 if( signalType.EndsWith( wxS( "DB" ) ) )
1563 {
1564 units = wxS( "dB" );
1565 }
1566 else if( signalType.StartsWith( 'I' ) )
1567 {
1568 units = wxS( "A" );
1569 }
1570 else if( signalType.StartsWith( 'P' ) )
1571 {
1572 units = wxS( "W" );
1573 // Our syntax is different from ngspice for power signals
1574 text = func + " " + deviceName + ":power";
1575 }
1576 else
1577 {
1578 units = wxS( "V" );
1579 }
1580
1581 if( func.EndsWith( wxS( "_AT" ) ) )
1582 {
1583 if( plotTab->GetSimType() == ST_AC || plotTab->GetSimType() == ST_SP )
1584 units = wxS( "Hz" );
1585 else
1586 units = wxS( "s" );
1587 }
1588 else if( func.StartsWith( wxS( "INTEG" ) ) )
1589 {
1590 switch( plotTab->GetSimType() )
1591 {
1592 case ST_TRAN:
1593 if ( signalType.StartsWith( 'P' ) )
1594 units = wxS( "J" );
1595 else
1596 units += wxS( ".s" );
1597
1598 break;
1599
1600 case ST_AC:
1601 case ST_SP:
1602 case ST_DISTO:
1603 case ST_NOISE:
1604 case ST_FFT:
1605 case ST_SENS: // If there is a vector, it is frequency
1606 units += wxS( "·Hz" );
1607 break;
1608
1609 case ST_DC: // Could be a lot of things : V, A, deg C, ohm, ...
1610 case ST_OP: // There is no vector for integration
1611 case ST_PZ: // There is no vector for integration
1612 case ST_TF: // There is no vector for integration
1613 default:
1614 units += wxS( "·?" );
1615 break;
1616 }
1617 }
1618
1619 fmt.UpdateUnits( units );
1620 SetMeasureFormat( aRow, fmt );
1621
1623 }
1624
1625 if( m_simulatorFrame->SimFinished() )
1626 {
1627 wxString cmd = wxString::Format( wxS( "meas %s %s %s" ), simType, resultName, text );
1628 simulator()->Command( "echo " + cmd.ToStdString() );
1629 simulator()->Command( cmd.ToStdString() );
1630
1631 std::vector<double> resultVec = simulator()->GetGainVector( resultName.ToStdString() );
1632
1633 if( resultVec.size() > 0 )
1634 result = SPICE_VALUE( resultVec[0] ).ToString( GetMeasureFormat( aRow ) );
1635 }
1636
1637 m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_VALUE, result );
1638}
1639
1640
1641void SIMULATOR_FRAME_UI::AddTuner( const SCH_SHEET_PATH& aSheetPath, SCH_SYMBOL* aSymbol )
1642{
1643 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1644
1645 if( !plotTab )
1646 {
1647 DisplayErrorMessage( nullptr, _( "The current analysis must have a plot in order to tune "
1648 "the value of a passive R, L, C model or voltage or "
1649 "current source." ) );
1650 return;
1651 }
1652
1653 wxString ref = aSymbol->GetRef( &aSheetPath );
1654
1655 // Do not add multiple instances for the same component.
1656 for( TUNER_SLIDER* tuner : m_tuners )
1657 {
1658 if( tuner->GetSymbolRef() == ref )
1659 return;
1660 }
1661
1662 if( [[maybe_unused]] const SPICE_ITEM* item = GetExporter()->FindItem( ref ) )
1663 {
1664 try
1665 {
1666 TUNER_SLIDER* tuner = new TUNER_SLIDER( this, m_panelTuners, aSheetPath, aSymbol );
1667 m_sizerTuners->Add( tuner );
1668 m_tuners.push_back( tuner );
1669 m_panelTuners->Layout();
1670 OnModify();
1671 }
1672 catch( const KI_PARAM_ERROR& e )
1673 {
1674 DisplayErrorMessage( nullptr, e.What() );
1675 }
1676 }
1677}
1678
1679
1680void SIMULATOR_FRAME_UI::UpdateTunerValue( const SCH_SHEET_PATH& aSheetPath, const KIID& aSymbol,
1681 const wxString& aRef, const wxString& aValue )
1682{
1683 SCH_ITEM* item = aSheetPath.ResolveItem( aSymbol );
1684 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( item );
1685
1686 if( !symbol )
1687 {
1688 DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( " " )
1689 + wxString::Format( _( "%s not found" ), aRef ) );
1690 return;
1691 }
1692
1693 NULL_REPORTER devnull;
1694 SIM_LIB_MGR mgr( &m_schematicFrame->Prj() );
1695
1696 std::vector<EMBEDDED_FILES*> embeddedFilesStack;
1697 embeddedFilesStack.push_back( m_schematicFrame->Schematic().GetEmbeddedFiles() );
1698
1699 if( EMBEDDED_FILES* symbolEmbeddedFiles = symbol->GetEmbeddedFiles() )
1700 {
1701 embeddedFilesStack.push_back( symbolEmbeddedFiles );
1702 symbol->GetLibSymbolRef()->AppendParentEmbeddedFiles( embeddedFilesStack );
1703 }
1704
1705 mgr.SetFilesStack( std::move( embeddedFilesStack ) );
1706
1707 SIM_MODEL& model = mgr.CreateModel( &aSheetPath, *symbol, true, 0, devnull ).model;
1708
1709 const SIM_MODEL::PARAM* tunerParam = model.GetTunerParam();
1710
1711 if( !tunerParam )
1712 {
1713 DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( " " )
1714 + wxString::Format( _( "%s is not tunable" ), aRef ) );
1715 return;
1716 }
1717
1718 model.SetParamValue( tunerParam->info.name, std::string( aValue.ToUTF8() ) );
1719 model.WriteFields( symbol->GetFields() );
1720
1721 m_schematicFrame->UpdateItem( symbol, false, true );
1722 m_schematicFrame->OnModify();
1723}
1724
1725
1727{
1728 m_tuners.remove( aTuner );
1729
1730 if( std::find( m_multiRunState.tuners.begin(), m_multiRunState.tuners.end(), aTuner )
1731 != m_multiRunState.tuners.end() )
1732 clearMultiRunState( true );
1733
1734 m_tunerOverrides.erase( aTuner );
1735
1736 aTuner->Destroy();
1737 m_panelTuners->Layout();
1738 OnModify();
1739}
1740
1741
1742void SIMULATOR_FRAME_UI::AddMeasurement( const wxString& aCmd )
1743{
1744 // -1 because the last one is for user input
1745 for( int i = 0; i < m_measurementsGrid->GetNumberRows(); i++ )
1746 {
1747 if ( m_measurementsGrid->GetCellValue( i, COL_MEASUREMENT ) == aCmd )
1748 return; // Don't create duplicates
1749 }
1750
1751 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1752
1753 if( !plotTab )
1754 return;
1755
1756 int row;
1757
1758 for( row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
1759 {
1760 if( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1761 break;
1762 }
1763
1764 if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1765 {
1766 m_measurementsGrid->AppendRows( 1 );
1767 row = m_measurementsGrid->GetNumberRows() - 1;
1768 }
1769
1770 m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT, aCmd );
1771
1772 UpdateMeasurement( row );
1774 OnModify();
1775
1776 // Always leave at least one empty row for type-in:
1777 row = m_measurementsGrid->GetNumberRows() - 1;
1778
1779 if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1780 m_measurementsGrid->AppendRows( 1 );
1781}
1782
1783
1784void SIMULATOR_FRAME_UI::DoFourier( const wxString& aSignal, const wxString& aFundamental )
1785{
1786 wxString cmd = wxString::Format( wxS( "fourier %s %s" ),
1787 SPICE_VALUE( aFundamental ).ToSpiceString(),
1788 aSignal );
1789
1790 simulator()->Command( cmd.ToStdString() );
1791}
1792
1793
1795{
1796 return circuitModel().get();
1797}
1798
1799
1800void SIMULATOR_FRAME_UI::AddTrace( const wxString& aName, SIM_TRACE_TYPE aType )
1801{
1802 if( !GetCurrentSimTab() )
1803 {
1804 m_simConsole->AppendText( _( "Error: no current simulation.\n" ) );
1805 m_simConsole->SetInsertionPointEnd();
1806 return;
1807 }
1808
1809 SIM_TYPE simType = SPICE_CIRCUIT_MODEL::CommandToSimType( GetCurrentSimTab()->GetSimCommand() );
1810
1811 if( simType == ST_UNKNOWN )
1812 {
1813 m_simConsole->AppendText( _( "Error: simulation type not defined.\n" ) );
1814 m_simConsole->SetInsertionPointEnd();
1815 return;
1816 }
1817 else if( !SIM_TAB::IsPlottable( simType ) )
1818 {
1819 m_simConsole->AppendText( _( "Error: simulation type doesn't support plotting.\n" ) );
1820 m_simConsole->SetInsertionPointEnd();
1821 return;
1822 }
1823
1824 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
1825 {
1826 if( simType == ST_AC )
1827 {
1828 updateTrace( aName, aType | SPT_AC_GAIN, plotTab );
1829 updateTrace( aName, aType | SPT_AC_PHASE, plotTab );
1830 }
1831 else if( simType == ST_SP )
1832 {
1833 updateTrace( aName, aType | SPT_AC_GAIN, plotTab );
1834 updateTrace( aName, aType | SPT_AC_PHASE, plotTab );
1835 }
1836 else
1837 {
1838 updateTrace( aName, aType, plotTab );
1839 }
1840
1841 plotTab->GetPlotWin()->UpdateAll();
1842 }
1843
1845 OnModify();
1846}
1847
1848
1849void SIMULATOR_FRAME_UI::SetUserDefinedSignals( const std::map<int, wxString>& aNewSignals )
1850{
1851 for( size_t ii = 0; ii < m_plotNotebook->GetPageCount(); ++ii )
1852 {
1853 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( m_plotNotebook->GetPage( ii ) );
1854
1855 if( !plotTab )
1856 continue;
1857
1858 for( const auto& [ id, existingSignal ] : m_userDefinedSignals )
1859 {
1860 int traceType = SPT_UNKNOWN;
1861 wxString vectorName = vectorNameFromSignalName( plotTab, existingSignal, &traceType );
1862
1863 if( aNewSignals.count( id ) == 0 )
1864 {
1865 if( plotTab->GetSimType() == ST_AC )
1866 {
1867 for( int subType : { SPT_AC_GAIN, SPT_AC_PHASE } )
1868 plotTab->DeleteTrace( vectorName, traceType | subType );
1869 }
1870 else if( plotTab->GetSimType() == ST_SP )
1871 {
1872 for( int subType : { SPT_SP_AMP, SPT_AC_PHASE } )
1873 plotTab->DeleteTrace( vectorName, traceType | subType );
1874 }
1875 else
1876 {
1877 plotTab->DeleteTrace( vectorName, traceType );
1878 }
1879 }
1880 else
1881 {
1882 if( plotTab->GetSimType() == ST_AC )
1883 {
1884 for( int subType : { SPT_AC_GAIN, SPT_AC_PHASE } )
1885 {
1886 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType | subType ) )
1887 trace->SetName( aNewSignals.at( id ) );
1888 }
1889 }
1890 else if( plotTab->GetSimType() == ST_SP )
1891 {
1892 for( int subType : { SPT_SP_AMP, SPT_AC_PHASE } )
1893 {
1894 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType | subType ) )
1895 trace->SetName( aNewSignals.at( id ) );
1896 }
1897 }
1898 else
1899 {
1900 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
1901 trace->SetName( aNewSignals.at( id ) );
1902 }
1903 }
1904 }
1905 }
1906
1907 m_userDefinedSignals = aNewSignals;
1908
1909 if( m_simulatorFrame->SimFinished() )
1911
1913 rebuildSignalsGrid( m_filter->GetValue() );
1916 OnModify();
1917}
1918
1919
1920void SIMULATOR_FRAME_UI::updateTrace( const wxString& aVectorName, int aTraceType,
1921 SIM_PLOT_TAB* aPlotTab, std::vector<double>* aDataX,
1922 bool aClearData )
1923{
1924 if( !m_simulatorFrame->SimFinished() && !simulator()->IsRunning())
1925 {
1926 aPlotTab->GetOrAddTrace( aVectorName, aTraceType );
1927 return;
1928 }
1929
1931
1932 aTraceType &= aTraceType & SPT_Y_AXIS_MASK;
1933 aTraceType |= getXAxisType( simType );
1934
1935 wxString simVectorName = aVectorName;
1936
1937 if( aTraceType & SPT_POWER )
1938 simVectorName = simVectorName.AfterFirst( '(' ).BeforeLast( ')' ) + wxS( ":power" );
1939
1940 if( !SIM_TAB::IsPlottable( simType ) )
1941 {
1942 // There is no plot to be shown
1943 simulator()->Command( wxString::Format( wxT( "print %s" ), aVectorName ).ToStdString() );
1944
1945 return;
1946 }
1947
1948 std::vector<double> data_x;
1949 std::vector<double> data_y;
1950
1951 if( !aDataX || aClearData )
1952 aDataX = &data_x;
1953
1954 // First, handle the x axis
1955 if( aDataX->empty() && !aClearData )
1956 {
1957 wxString xAxisName( simulator()->GetXAxis( simType ) );
1958
1959 if( xAxisName.IsEmpty() )
1960 return;
1961
1962 *aDataX = simulator()->GetGainVector( (const char*) xAxisName.c_str() );
1963 }
1964
1965 unsigned int size = aDataX->size();
1966
1967 switch( simType )
1968 {
1969 case ST_AC:
1970 if( aTraceType & SPT_AC_GAIN )
1971 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
1972 else if( aTraceType & SPT_AC_PHASE )
1973 data_y = simulator()->GetPhaseVector( (const char*) simVectorName.c_str(), size );
1974 else
1975 wxFAIL_MSG( wxT( "Plot type missing AC_PHASE or AC_MAG bit" ) );
1976
1977 break;
1978 case ST_SP:
1979 if( aTraceType & SPT_SP_AMP )
1980 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
1981 else if( aTraceType & SPT_AC_PHASE )
1982 data_y = simulator()->GetPhaseVector( (const char*) simVectorName.c_str(), size );
1983 else
1984 wxFAIL_MSG( wxT( "Plot type missing AC_PHASE or SPT_SP_AMP bit" ) );
1985
1986 break;
1987
1988 case ST_DC:
1989 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), -1 );
1990 break;
1991
1992 case ST_NOISE:
1993 case ST_TRAN:
1994 case ST_FFT:
1995 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
1996 break;
1997
1998 default:
1999 wxFAIL_MSG( wxT( "Unhandled plot type" ) );
2000 }
2001
2002 SPICE_DC_PARAMS source1, source2;
2003 int sweepCount = 1;
2004 size_t sweepSize = std::numeric_limits<size_t>::max();
2005
2006 if( simType == ST_DC
2007 && circuitModel()->ParseDCCommand( aPlotTab->GetSimCommand(), &source1, &source2 )
2008 && !source2.m_source.IsEmpty() )
2009 {
2010 SPICE_VALUE v = ( source2.m_vend - source2.m_vstart ) / source2.m_vincrement;
2011
2012 sweepCount = KiROUND( v.ToDouble() ) + 1;
2013 sweepSize = aDataX->size() / sweepCount;
2014 }
2015
2016 if( m_multiRunState.storePending )
2017 recordMultiRunData( aVectorName, aTraceType, *aDataX, data_y );
2018
2019 if( hasMultiRunTrace( aVectorName, aTraceType ) )
2020 {
2021 const std::string key = multiRunTraceKey( aVectorName, aTraceType );
2022 const auto traceIt = m_multiRunState.traces.find( key );
2023
2024 if( traceIt != m_multiRunState.traces.end() )
2025 {
2026 const MULTI_RUN_TRACE& traceData = traceIt->second;
2027
2028 if( !traceData.xValues.empty() && !traceData.yValues.empty() )
2029 {
2030 size_t sweepSizeMulti = traceData.xValues.size();
2031 size_t runCount = traceData.yValues.size();
2032
2033 if( sweepSizeMulti > 0 && runCount > 0 )
2034 {
2035 std::vector<double> combinedX;
2036 std::vector<double> combinedY;
2037
2038 combinedX.reserve( sweepSizeMulti * runCount );
2039 combinedY.reserve( sweepSizeMulti * runCount );
2040
2041 for( const std::vector<double>& runY : traceData.yValues )
2042 {
2043 if( runY.size() != sweepSizeMulti )
2044 continue;
2045
2046 combinedX.insert( combinedX.end(), traceData.xValues.begin(), traceData.xValues.end() );
2047 combinedY.insert( combinedY.end(), runY.begin(), runY.end() );
2048 }
2049
2050 if( TRACE* trace = aPlotTab->GetOrAddTrace( aVectorName, aTraceType ) )
2051 {
2052 if( combinedY.size() >= combinedX.size() && sweepSizeMulti > 0 )
2053 {
2054 int sweepCountCombined = combinedX.empty() ? 0 : static_cast<int>( combinedY.size() / sweepSizeMulti );
2055
2056 if( sweepCountCombined > 0 )
2057 {
2058 // Generate labels for each run based on tuner values
2059 std::vector<wxString> labels;
2060 labels.reserve( sweepCountCombined );
2061
2062 for( int i = 0; i < sweepCountCombined && i < (int)m_multiRunState.steps.size(); ++i )
2063 {
2064 const MULTI_RUN_STEP& step = m_multiRunState.steps[i];
2065 wxString label;
2066
2067 for( auto it = step.overrides.begin(); it != step.overrides.end(); ++it )
2068 {
2069 if( it != step.overrides.begin() )
2070 label += wxS( ", " );
2071
2072 const TUNER_SLIDER* tuner = it->first;
2073 double value = it->second;
2074
2075 SPICE_VALUE spiceVal( value );
2076 label += tuner->GetSymbolRef() + wxS( "=" ) + spiceVal.ToSpiceString();
2077 }
2078
2079 labels.push_back( label );
2080 }
2081
2082 aPlotTab->SetTraceData( trace, combinedX, combinedY, sweepCountCombined,
2083 sweepSizeMulti, true, labels );
2084 }
2085 }
2086 }
2087
2088 return;
2089 }
2090 }
2091 }
2092 }
2093
2094 if( TRACE* trace = aPlotTab->GetOrAddTrace( aVectorName, aTraceType ) )
2095 {
2096 if( data_y.size() >= size )
2097 aPlotTab->SetTraceData( trace, *aDataX, data_y, sweepCount, sweepSize );
2098 }
2099}
2100
2101
2102// TODO make sure where to instantiate and how to style correct
2103// Better ask someone..
2105 SIGNALS_GRID_COLUMNS, int, int );
2106
2107template <typename T, typename U, typename R>
2108void 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
2109{
2110 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
2111
2112 wxString signalName = m_signalsGrid->GetCellValue( r, COL_SIGNAL_NAME );
2113 int traceType = SPT_UNKNOWN;
2114 wxString vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
2115
2116 wxGridCellAttrPtr attr = m_signalsGrid->GetOrCreateCellAttrPtr( r, static_cast<int>( t ) );
2117
2118 if( TRACE* trace = plotTab ? plotTab->GetTrace( vectorName, traceType ) : nullptr )
2119 {
2120 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
2121
2123 {
2124 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
2125 }
2126
2127 if constexpr ( std::is_enum<T>::value )
2128 {
2130 {
2131 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), wxS( "1" ) );
2132 }
2134 {
2135 if( !attr->HasRenderer() )
2136 attr->SetRenderer( new GRID_CELL_COLOR_RENDERER( this ) );
2137
2138 if( !attr->HasEditor() )
2139 attr->SetEditor( new GRID_CELL_COLOR_SELECTOR( this, m_signalsGrid ) );
2140
2141 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
2142 attr->SetReadOnly( false );
2143
2144 KIGFX::COLOR4D color( trace->GetPen().GetColour() );
2145 m_signalsGrid->SetCellValue( r, COL_SIGNAL_COLOR, color.ToCSSString() );
2146 }
2150 {
2151 if( !attr->HasRenderer() )
2152 attr->SetRenderer( new wxGridCellBoolRenderer() );
2153
2154 if( u > 0 && trace->HasCursor( u ) )
2155 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), wxS( "1" ) );
2156 else
2157 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), wxEmptyString );
2158 }
2159 }
2160 }
2161 else
2162 {
2163 if constexpr ( std::is_enum<T>::value )
2164 {
2166 {
2167 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), wxEmptyString );
2168 }
2173 {
2174 attr->SetEditor( nullptr );
2175 attr->SetRenderer( nullptr );
2176 attr->SetReadOnly();
2177 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), wxEmptyString );
2178 }
2179 }
2180 }
2181}
2182
2183
2185{
2186 for( int row = 0; row < m_signalsGrid->GetNumberRows(); ++row )
2187 {
2192
2193 if( ( m_signalsGrid->GetNumberCols() - 1 ) > COL_CURSOR_2 )
2194 {
2195 for( int i = 3; i < m_customCursorsCnt; i++ )
2196 {
2197 int tm = i + 2;
2199 static_cast<SIGNALS_GRID_COLUMNS>( tm ),
2200 i,
2201 row );
2202 }
2203 }
2204 }
2205 m_signalsGrid->Refresh();
2206}
2207
2208
2210{
2211 auto quoteNetNames = [&]( wxString aExpression ) -> wxString
2212 {
2213 std::vector<bool> mask( aExpression.length(), false );
2214
2215 auto isNetnameChar = []( wxUniChar aChar ) -> bool
2216 {
2217 wxUint32 value = aChar.GetValue();
2218
2219 if( ( value >= '0' && value <= '9' ) || ( value >= 'A' && value <= 'Z' )
2220 || ( value >= 'a' && value <= 'z' ) )
2221 {
2222 return true;
2223 }
2224
2225 switch( value )
2226 {
2227 case '_':
2228 case '/':
2229 case '+':
2230 case '-':
2231 case '~':
2232 case '.':
2233 return true;
2234 default:
2235 break;
2236 }
2237
2238 return false;
2239 };
2240
2241 for( const auto& netname : m_netnames )
2242 {
2243 size_t pos = aExpression.find( netname );
2244
2245 while( pos != wxString::npos )
2246 {
2247 for( size_t i = 0; i < netname.length(); ++i )
2248 {
2249 mask[pos + i] = true; // Mark the positions of the netname
2250 }
2251 pos = aExpression.find( netname, pos + 1 ); // Find the next occurrence
2252 }
2253 }
2254
2255 for( size_t i = 0; i < aExpression.length(); ++i )
2256 {
2257 if( !mask[i] || ( i > 0 && mask[i - 1] ) )
2258 continue;
2259
2260 size_t j = i + 1;
2261
2262 while( j < aExpression.length() )
2263 {
2264 if( mask[j] )
2265 {
2266 ++j;
2267 continue;
2268 }
2269
2270 if( isNetnameChar( aExpression[j] ) )
2271 {
2272 mask[j] = true;
2273 ++j;
2274 }
2275 else
2276 {
2277 break;
2278 }
2279 }
2280 }
2281
2282 wxString quotedNetnames = "";
2283 bool startQuote = true;
2284
2285 // put quotes around all the positions that were found above
2286 for( size_t i = 0; i < aExpression.length(); i++ )
2287 {
2288 if( mask[i] && startQuote )
2289 {
2290 quotedNetnames = quotedNetnames + "\"";
2291 startQuote = false;
2292 }
2293 else if( !mask[i] && !startQuote )
2294 {
2295 quotedNetnames = quotedNetnames + "\"";
2296 startQuote = true;
2297 }
2298 wxString ch = aExpression[i];
2299 quotedNetnames = quotedNetnames + ch;
2300 }
2301
2302 if( !startQuote )
2303 {
2304 quotedNetnames = quotedNetnames + "\"";
2305 }
2306 return quotedNetnames;
2307 };
2308
2309 for( const auto& [ id, signal ] : m_userDefinedSignals )
2310 {
2311 constexpr const char* cmd = "let user{} = {}";
2312
2313 simulator()->Command( "echo " + fmt::format( cmd, id, signal.ToStdString() ) );
2314 simulator()->Command( fmt::format( cmd, id, quoteNetNames( signal ).ToStdString() ) );
2315 }
2316}
2317
2318
2320{
2321 WX_STRING_REPORTER reporter;
2322
2323 for( const TUNER_SLIDER* tuner : m_tuners )
2324 {
2325 SCH_SHEET_PATH sheetPath;
2326 wxString ref = tuner->GetSymbolRef();
2327 KIID symbolId = tuner->GetSymbol( &sheetPath );
2328 SCH_ITEM* schItem = sheetPath.ResolveItem( symbolId );
2329 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( schItem );
2330
2331 if( !symbol )
2332 {
2333 reporter.Report( wxString::Format( _( "%s not found" ), ref ) );
2334 continue;
2335 }
2336
2337 const SPICE_ITEM* item = GetExporter()->FindItem( tuner->GetSymbolRef() );
2338
2339 if( !item || !item->model->GetTunerParam() )
2340 {
2341 reporter.Report( wxString::Format( _( "%s is not tunable" ), ref ) );
2342 continue;
2343 }
2344
2345 double floatVal;
2346
2347 auto overrideIt = m_tunerOverrides.find( tuner );
2348
2349 if( overrideIt != m_tunerOverrides.end() )
2350 floatVal = overrideIt->second;
2351 else
2352 floatVal = tuner->GetValue().ToDouble();
2353
2354 simulator()->Command( item->model->SpiceGenerator().TunerCommand( *item, floatVal ) );
2355 }
2356
2357 if( reporter.HasMessage() )
2358 DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( "\n" )
2359 + reporter.GetMessages() );
2360}
2361
2362bool SIMULATOR_FRAME_UI::LoadWorkbook( const wxString& aPath )
2363{
2364 wxTextFile file( aPath );
2365
2366 if( !file.Open() )
2367 return false;
2368
2369 wxString firstLine = file.GetFirstLine();
2370 long dummy;
2371 bool legacy = firstLine.StartsWith( wxT( "version " ) ) || firstLine.ToLong( &dummy );
2372
2373 file.Close();
2374
2375 m_plotNotebook->DeleteAllPages();
2376 m_userDefinedSignals.clear();
2377
2378 if( legacy )
2379 {
2380 if( !loadLegacyWorkbook( aPath ) )
2381 return false;
2382 }
2383 else
2384 {
2385 if( !loadJsonWorkbook( aPath ) )
2386 return false;
2387 }
2388
2390
2391 rebuildSignalsGrid( m_filter->GetValue() );
2395
2396 wxFileName filename( aPath );
2397 filename.MakeRelativeTo( m_schematicFrame->Prj().GetProjectPath() );
2398
2399 // Remember the loaded workbook filename.
2400 simulator()->Settings()->SetWorkbookFilename( filename.GetFullPath() );
2401
2402 return true;
2403}
2404
2405
2406bool SIMULATOR_FRAME_UI::loadJsonWorkbook( const wxString& aPath )
2407{
2408 wxFFileInputStream fp( aPath, wxT( "rt" ) );
2409 wxStdInputStream fstream( fp );
2410
2411 if( !fp.IsOk() )
2412 return false;
2413
2414 try
2415 {
2416 nlohmann::json js = nlohmann::json::parse( fstream, nullptr, true, true );
2417
2418 std::map<SIM_PLOT_TAB*, nlohmann::json> traceInfo;
2419
2420 for( const nlohmann::json& tab_js : js[ "tabs" ] )
2421 {
2422 wxString simCommand;
2425
2426 for( const nlohmann::json& cmd : tab_js[ "commands" ] )
2427 {
2428 if( cmd == ".kicad adjustpaths" )
2430 else if( cmd == ".save all" )
2432 else if( cmd == ".probe alli" )
2434 else if( cmd == ".probe allp" )
2436 else if( cmd == ".kicad esavenone" )
2437 simOptions &= ~NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_EVENTS;
2438 else
2439 simCommand += wxString( cmd.get<wxString>() ).Trim();
2440 }
2441
2442 SIM_TAB* simTab = NewSimTab( simCommand );
2443 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab );
2444
2445 simTab->SetSimOptions( simOptions );
2446
2447 if( plotTab )
2448 {
2449 if( tab_js.contains( "traces" ) )
2450 traceInfo[plotTab] = tab_js[ "traces" ];
2451
2452 if( tab_js.contains( "measurements" ) )
2453 {
2454 for( const nlohmann::json& m_js : tab_js[ "measurements" ] )
2455 plotTab->Measurements().emplace_back( m_js[ "expr" ], m_js[ "format" ] );
2456 }
2457
2458 plotTab->SetDottedSecondary( tab_js[ "dottedSecondary" ] );
2459 plotTab->ShowGrid( tab_js[ "showGrid" ] );
2460
2461 if( tab_js.contains( "fixedY1scale" ) )
2462 {
2463 const nlohmann::json& scale_js = tab_js[ "fixedY1scale" ];
2464 plotTab->SetY1Scale( true, scale_js[ "min" ], scale_js[ "max" ] );
2465 plotTab->GetPlotWin()->LockY( true );
2466 }
2467
2468 if( tab_js.contains( "fixedY2scale" ) )
2469 {
2470 const nlohmann::json& scale_js = tab_js[ "fixedY2scale" ];
2471 plotTab->SetY2Scale( true, scale_js[ "min" ], scale_js[ "max" ] );
2472 plotTab->GetPlotWin()->LockY( true );
2473 }
2474
2475 if( tab_js.contains( "fixedY3scale" ) )
2476 {
2477 plotTab->EnsureThirdYAxisExists();
2478 const nlohmann::json& scale_js = tab_js[ "fixedY3scale" ];
2479 plotTab->SetY3Scale( true, scale_js[ "min" ], scale_js[ "max" ] );
2480 plotTab->GetPlotWin()->LockY( true );
2481 }
2482
2483 if( tab_js.contains( "legend" ) )
2484 {
2485 const nlohmann::json& legend_js = tab_js[ "legend" ];
2486 plotTab->SetLegendPosition( wxPoint( legend_js[ "x" ], legend_js[ "y" ] ) );
2487 plotTab->ShowLegend( true );
2488 }
2489
2490 if( tab_js.contains( "margins" ) )
2491 {
2492 const nlohmann::json& margins_js = tab_js[ "margins" ];
2493 plotTab->GetPlotWin()->SetMargins( margins_js[ "top" ],
2494 margins_js[ "right" ],
2495 margins_js[ "bottom" ],
2496 margins_js[ "left" ] );
2497 }
2498 }
2499 }
2500
2501 int ii = 0;
2502
2503 if( js.contains( "user_defined_signals" ) )
2504 {
2505 for( const nlohmann::json& signal_js : js[ "user_defined_signals" ] )
2506 m_userDefinedSignals[ii++] = wxString( signal_js.get<wxString>() );
2507 }
2508
2509 if( SIM_TAB* simTab = GetCurrentSimTab() )
2510 {
2511 m_simulatorFrame->LoadSimulator( simTab->GetSimCommand(), simTab->GetSimOptions() );
2512
2513 if( SIM_TAB* firstTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( 0 ) ) )
2514 firstTab->SetLastSchTextSimCommand( js["last_sch_text_sim_command"] );
2515 }
2516
2517 int tempCustomCursorsCnt = 0;
2518
2519 if( js.contains( "custom_cursors" ) )
2520 tempCustomCursorsCnt = js["custom_cursors"];
2521 else
2522 tempCustomCursorsCnt = 2; // Kind of virtual, for the initial loading of the new setting
2523
2524 if( ( tempCustomCursorsCnt > m_customCursorsCnt ) && m_customCursorsCnt > 2 )
2525 tempCustomCursorsCnt = 2 * tempCustomCursorsCnt - m_customCursorsCnt;
2526
2527 for( int yy = 0; yy <= ( tempCustomCursorsCnt - m_customCursorsCnt ); yy++ )
2529
2530 auto addCursor =
2531 [=,this]( SIM_PLOT_TAB* aPlotTab, TRACE* aTrace, const wxString& aSignalName,
2532 int aCursorId, const nlohmann::json& aCursor_js )
2533 {
2534 if( aCursorId >= 1 )
2535 {
2536 CURSOR* cursor = new CURSOR( aTrace, aPlotTab );
2537
2538 cursor->SetName( aSignalName );
2539 cursor->SetCoordX( aCursor_js[ "position" ] );
2540
2541 aTrace->SetCursor( aCursorId, cursor );
2542 aPlotTab->GetPlotWin()->AddLayer( cursor );
2543 }
2544
2545 if( aCursorId == -1 )
2546 {
2547 // We are a "cursorD"
2548 m_cursorFormatsDyn[2][0].FromString( aCursor_js["x_format"] );
2549 m_cursorFormatsDyn[2][1].FromString( aCursor_js["y_format"] );
2550 }
2551 else
2552 {
2553 if( aCursorId < 3 )
2554 {
2555 m_cursorFormatsDyn[aCursorId - 1][0].FromString(
2556 aCursor_js["x_format"] );
2557 m_cursorFormatsDyn[aCursorId - 1][1].FromString(
2558 aCursor_js["y_format"] );
2559 }
2560 else
2561 {
2562 m_cursorFormatsDyn[aCursorId][0].FromString( aCursor_js["x_format"] );
2563 m_cursorFormatsDyn[aCursorId][1].FromString( aCursor_js["y_format"] );
2564 }
2565 }
2566 };
2567
2568 for( const auto& [ plotTab, traces_js ] : traceInfo )
2569 {
2570 for( const nlohmann::json& trace_js : traces_js )
2571 {
2572 wxString signalName = trace_js[ "signal" ];
2573 wxString vectorName = vectorNameFromSignalName( plotTab, signalName, nullptr );
2574 TRACE* trace = plotTab->GetOrAddTrace( vectorName, trace_js[ "trace_type" ] );
2575
2576 if( trace )
2577 {
2578 if( trace_js.contains( "cursorD" ) )
2579 addCursor( plotTab, trace, signalName, -1, trace_js[ "cursorD" ] );
2580
2581 std::vector<const char*> aVec;
2582 aVec.clear();
2583
2584 for( int i = 1; i <= tempCustomCursorsCnt; i++ )
2585 {
2586 wxString str = "cursor" + std::to_string( i );
2587 aVec.emplace_back( str.c_str() );
2588
2589 if( trace_js.contains( aVec[i - 1] ) )
2590 addCursor( plotTab, trace, signalName, i, trace_js[aVec[i - 1]] );
2591 }
2592
2593 if( trace_js.contains( "color" ) )
2594 {
2595 wxColour color;
2596 color.Set( wxString( trace_js["color"].get<wxString>() ) );
2597 trace->SetTraceColour( color );
2598 plotTab->UpdateTraceStyle( trace );
2599 }
2600 }
2601 }
2602
2603 plotTab->UpdatePlotColors();
2604 }
2605 }
2606 catch( nlohmann::json::parse_error& error )
2607 {
2608 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ), aPath, error.what() );
2609
2610 return false;
2611 }
2612 catch( nlohmann::json::type_error& error )
2613 {
2614 wxLogTrace( traceSettings, wxT( "Json type error reading %s: %s" ), aPath, error.what() );
2615
2616 return false;
2617 }
2618 catch( nlohmann::json::invalid_iterator& error )
2619 {
2620 wxLogTrace( traceSettings, wxT( "Json invalid_iterator error reading %s: %s" ), aPath, error.what() );
2621
2622 return false;
2623 }
2624 catch( nlohmann::json::out_of_range& error )
2625 {
2626 wxLogTrace( traceSettings, wxT( "Json out_of_range error reading %s: %s" ), aPath, error.what() );
2627
2628 return false;
2629 }
2630 catch( ... )
2631 {
2632 wxLogTrace( traceSettings, wxT( "Error reading %s" ), aPath );
2633 return false;
2634 }
2635
2636 return true;
2637}
2638
2639void SIMULATOR_FRAME_UI::SaveCursorToWorkbook( nlohmann::json& aTraceJs, TRACE* aTrace, int aCursorId )
2640{
2641 int cursorIdAfterD = aCursorId;
2642
2643 if( aCursorId > 3 )
2644 cursorIdAfterD = cursorIdAfterD - 1;
2645
2646
2647 if( CURSOR* cursor = aTrace->GetCursor( aCursorId ) )
2648 {
2649 aTraceJs["cursor" + wxString( "" ) << aCursorId] =
2650 nlohmann::json( { { "position", cursor->GetCoords().x },
2651 { "x_format", m_cursorFormatsDyn[cursorIdAfterD][0].ToString() },
2652 { "y_format", m_cursorFormatsDyn[cursorIdAfterD][1].ToString() } } );
2653 }
2654
2655 if( cursorIdAfterD < 3 && ( aTrace->GetCursor( 1 ) || aTrace->GetCursor( 2 ) ) )
2656 {
2657 aTraceJs["cursorD"] =
2658 nlohmann::json( { { "x_format", m_cursorFormatsDyn[2][0].ToString() },
2659 { "y_format", m_cursorFormatsDyn[2][1].ToString() } } );
2660 }
2661}
2662
2663
2664bool SIMULATOR_FRAME_UI::SaveWorkbook( const wxString& aPath )
2665{
2667
2668 wxFileName filename = aPath;
2669 filename.SetExt( FILEEXT::WorkbookFileExtension );
2670
2671 wxFile file;
2672
2673 file.Create( filename.GetFullPath(), true /* overwrite */ );
2674
2675 if( !file.IsOpened() )
2676 return false;
2677
2678 nlohmann::json tabs_js = nlohmann::json::array();
2679
2680 for( size_t i = 0; i < m_plotNotebook->GetPageCount(); i++ )
2681 {
2682 SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( i ) );
2683
2684 if( !simTab )
2685 continue;
2686
2687 SIM_TYPE simType = simTab->GetSimType();
2688
2689 nlohmann::json commands_js = nlohmann::json::array();
2690
2691 commands_js.push_back( simTab->GetSimCommand() );
2692
2693 int options = simTab->GetSimOptions();
2694
2696 commands_js.push_back( ".kicad adjustpaths" );
2697
2699 commands_js.push_back( ".save all" );
2700
2702 commands_js.push_back( ".probe alli" );
2703
2705 commands_js.push_back( ".probe allp" );
2706
2708 commands_js.push_back( ".kicad esavenone" );
2709
2710 nlohmann::json tab_js = nlohmann::json(
2711 { { "analysis", SPICE_SIMULATOR::TypeToName( simType, true ) },
2712 { "commands", commands_js } } );
2713
2714 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab ) )
2715 {
2716 nlohmann::json traces_js = nlohmann::json::array();
2717
2718 auto findSignalName =
2719 [&]( const wxString& aVectorName ) -> wxString
2720 {
2721 wxString vectorName;
2722 wxString suffix;
2723
2724 if( aVectorName.EndsWith( _( " (phase)" ) ) )
2725 suffix = _( " (phase)" );
2726 else if( aVectorName.EndsWith( _( " (gain)" ) ) )
2727 suffix = _( " (gain)" );
2728
2729 vectorName = aVectorName.Left( aVectorName.Length() - suffix.Length() );
2730
2731 for( const auto& [ id, signal ] : m_userDefinedSignals )
2732 {
2733 if( vectorName == vectorNameFromSignalId( id ) )
2734 return signal + suffix;
2735 }
2736
2737 return aVectorName;
2738 };
2739
2740 for( const auto& [name, trace] : plotTab->GetTraces() )
2741 {
2742 nlohmann::json trace_js = nlohmann::json(
2743 { { "trace_type", (int) trace->GetType() },
2744 { "signal", findSignalName( trace->GetDisplayName() ) },
2745 { "color", COLOR4D( trace->GetTraceColour() ).ToCSSString() } } );
2746
2747 for( int ii = 1; ii <= m_customCursorsCnt; ii++ )
2748 SaveCursorToWorkbook( trace_js, trace, ii );
2749
2750 if( trace->GetCursor( 1 ) || trace->GetCursor( 2 ) )
2751 {
2752 trace_js["cursorD"] = nlohmann::json(
2753 { { "x_format", m_cursorFormatsDyn[2][0].ToString() },
2754 { "y_format", m_cursorFormatsDyn[2][1].ToString() } } );
2755 }
2756
2757 traces_js.push_back( trace_js );
2758 }
2759
2760 nlohmann::json measurements_js = nlohmann::json::array();
2761
2762 for( const auto& [ measurement, format ] : plotTab->Measurements() )
2763 {
2764 measurements_js.push_back( nlohmann::json( { { "expr", measurement },
2765 { "format", format } } ) );
2766 }
2767
2768 tab_js[ "traces" ] = traces_js;
2769 tab_js[ "measurements" ] = measurements_js;
2770 tab_js[ "dottedSecondary" ] = plotTab->GetDottedSecondary();
2771 tab_js[ "showGrid" ] = plotTab->IsGridShown();
2772
2773 double min, max;
2774
2775 if( plotTab->GetY1Scale( &min, &max ) )
2776 tab_js[ "fixedY1scale" ] = nlohmann::json( { { "min", min }, { "max", max } } );
2777
2778 if( plotTab->GetY2Scale( &min, &max ) )
2779 tab_js[ "fixedY2scale" ] = nlohmann::json( { { "min", min }, { "max", max } } );
2780
2781 if( plotTab->GetY3Scale( &min, &max ) )
2782 tab_js[ "fixedY3scale" ] = nlohmann::json( { { "min", min }, { "max", max } } );
2783
2784 if( plotTab->IsLegendShown() )
2785 {
2786 tab_js[ "legend" ] = nlohmann::json( { { "x", plotTab->GetLegendPosition().x },
2787 { "y", plotTab->GetLegendPosition().y } } );
2788 }
2789
2790 mpWindow* plotWin = plotTab->GetPlotWin();
2791
2792 tab_js[ "margins" ] = nlohmann::json( { { "left", plotWin->GetMarginLeft() },
2793 { "right", plotWin->GetMarginRight() },
2794 { "top", plotWin->GetMarginTop() },
2795 { "bottom", plotWin->GetMarginBottom() } } );
2796 }
2797
2798 tabs_js.push_back( tab_js );
2799 }
2800
2801 nlohmann::json userDefinedSignals_js = nlohmann::json::array();
2802
2803 for( const auto& [ id, signal ] : m_userDefinedSignals )
2804 userDefinedSignals_js.push_back( signal );
2805
2806 // clang-format off
2807 nlohmann::json js = nlohmann::json( { { "version", 7 },
2808 { "tabs", tabs_js },
2809 { "user_defined_signals", userDefinedSignals_js },
2810 { "custom_cursors", m_customCursorsCnt - 1 } } ); // Since we start +1 on init
2811 // clang-format on
2812
2813 // Store the value of any simulation command found on the schematic sheet in a SCH_TEXT
2814 // object. If this changes we want to warn the user and ask them if they want to update
2815 // the corresponding panel's sim command.
2816 if( m_plotNotebook->GetPageCount() > 0 )
2817 {
2818 SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( 0 ) );
2819 js[ "last_sch_text_sim_command" ] = simTab->GetLastSchTextSimCommand();
2820 }
2821
2822 std::stringstream buffer;
2823 buffer << std::setw( 2 ) << js << std::endl;
2824
2825 bool res = file.Write( buffer.str() );
2826 file.Close();
2827
2828 // Store the filename of the last saved workbook.
2829 if( res )
2830 {
2831 filename.MakeRelativeTo( m_schematicFrame->Prj().GetProjectPath() );
2832 simulator()->Settings()->SetWorkbookFilename( filename.GetFullPath() );
2833 }
2834
2835 return res;
2836}
2837
2838
2840{
2841 switch( aType )
2842 {
2844 case ST_AC: return SPT_LIN_FREQUENCY;
2845 case ST_SP: return SPT_LIN_FREQUENCY;
2846 case ST_FFT: return SPT_LIN_FREQUENCY;
2847 case ST_DC: return SPT_SWEEP;
2848 case ST_TRAN: return SPT_TIME;
2849 case ST_NOISE: return SPT_LIN_FREQUENCY;
2850
2851 default:
2852 wxFAIL_MSG( wxString::Format( wxS( "Unhandled simulation type: %d" ), (int) aType ) );
2853 return SPT_UNKNOWN;
2854 }
2855}
2856
2857
2859{
2860 wxString output;
2861 wxString ref;
2862 wxString source;
2863 wxString scale;
2864 SPICE_VALUE pts;
2865 SPICE_VALUE fStart;
2866 SPICE_VALUE fStop;
2867 bool saveAll;
2868
2869 if( GetCurrentSimTab() )
2870 {
2871 circuitModel()->ParseNoiseCommand( GetCurrentSimTab()->GetSimCommand(), &output, &ref,
2872 &source, &scale, &pts, &fStart, &fStop, &saveAll );
2873 }
2874
2875 return source;
2876}
2877
2878
2879void SIMULATOR_FRAME_UI::TogglePanel( wxPanel* aPanel, wxSplitterWindow* aSplitterWindow,
2880 int& aSashPosition )
2881{
2882 bool isShown = aPanel->IsShown();
2883
2884 if( isShown )
2885 aSashPosition = aSplitterWindow->GetSashPosition();
2886
2887 aPanel->Show( !isShown );
2888
2889 aSplitterWindow->SetSashInvisible( isShown );
2890 aSplitterWindow->SetSashPosition( isShown ? -1 : aSashPosition, true );
2891
2892 aSplitterWindow->UpdateSize();
2893 m_parent->Refresh();
2894 m_parent->Layout();
2895}
2896
2897
2899{
2900 return m_panelConsole->IsShown();
2901}
2902
2903
2908
2909
2911{
2912 return m_sidePanel->IsShown();
2913}
2914
2915
2920
2921
2923{
2925
2926 // Rebuild the color list to plot traces
2928
2929 // Now send changes to all SIM_PLOT_TAB
2930 for( size_t page = 0; page < m_plotNotebook->GetPageCount(); page++ )
2931 {
2932 wxWindow* curPage = m_plotNotebook->GetPage( page );
2933
2934 // ensure it is truly a plot plotTab and not the (zero plots) placeholder
2935 // which is only SIM_TAB
2936 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( curPage );
2937
2938 if( plotTab )
2939 plotTab->UpdatePlotColors();
2940 }
2941}
2942
2943
2944void SIMULATOR_FRAME_UI::onPlotClose( wxAuiNotebookEvent& event )
2945{
2946 OnModify();
2947}
2948
2949
2950void SIMULATOR_FRAME_UI::onPlotClosed( wxAuiNotebookEvent& event )
2951{
2952 CallAfter( [this]()
2953 {
2955 rebuildSignalsGrid( m_filter->GetValue() );
2957
2958 //To avoid a current side effect in dynamic cursors while closing one out of many sim tabs
2960
2961 SIM_TAB* panel = GetCurrentSimTab();
2962
2963 if( !panel || panel->GetSimType() != ST_OP )
2964 {
2965 SCHEMATIC& schematic = m_schematicFrame->Schematic();
2966 schematic.ClearOperatingPoints();
2967 m_schematicFrame->RefreshOperatingPointDisplay();
2968 m_schematicFrame->GetCanvas()->Refresh();
2969 }
2970 } );
2971}
2972
2973
2975{
2976 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
2977 {
2978 std::vector<std::pair<wxString, wxString>>& measurements = plotTab->Measurements();
2979
2980 measurements.clear();
2981
2982 for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
2983 {
2984 if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
2985 {
2986 measurements.emplace_back( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ),
2987 m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT_FORMAT ) );
2988 }
2989 }
2990 }
2991}
2992
2993
2994void SIMULATOR_FRAME_UI::onPlotChanging( wxAuiNotebookEvent& event )
2995{
2996 m_measurementsGrid->ClearRows();
2997
2998 event.Skip();
2999}
3000
3001
3003{
3005 rebuildSignalsGrid( m_filter->GetValue() );
3007
3009
3010 for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
3011 UpdateMeasurement( row );
3012}
3013
3014
3015void SIMULATOR_FRAME_UI::onPlotChanged( wxAuiNotebookEvent& event )
3016{
3017 if( SIM_TAB* simTab = GetCurrentSimTab() )
3018 simulator()->Command( "setplot " + simTab->GetSpicePlotName().ToStdString() );
3019
3021
3022 //To avoid a current side effect in dynamic cursors while switching sim tabs
3024
3025 event.Skip();
3026}
3027
3028
3030{
3031 m_measurementsGrid->ClearRows();
3032
3033 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
3034 {
3035 for( const auto& [ measurement, format ] : plotTab->Measurements() )
3036 {
3037 int row = m_measurementsGrid->GetNumberRows();
3038 m_measurementsGrid->AppendRows();
3039 m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT, measurement );
3040 m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT_FORMAT, format );
3041 }
3042
3043 if( plotTab->GetSimType() == ST_TRAN || plotTab->GetSimType() == ST_AC
3044 || plotTab->GetSimType() == ST_DC || plotTab->GetSimType() == ST_SP )
3045 {
3046 m_measurementsGrid->AppendRows(); // Empty row at end
3047 }
3048 }
3049}
3050
3051
3052void SIMULATOR_FRAME_UI::onPlotDragged( wxAuiNotebookEvent& event )
3053{
3054}
3055
3056
3057std::shared_ptr<SPICE_SIMULATOR> SIMULATOR_FRAME_UI::simulator() const
3058{
3059 return m_simulatorFrame->GetSimulator();
3060}
3061
3062
3063std::shared_ptr<SPICE_CIRCUIT_MODEL> SIMULATOR_FRAME_UI::circuitModel() const
3064{
3065 return m_simulatorFrame->GetCircuitModel();
3066}
3067
3068
3070{
3071 SUPPRESS_GRID_CELL_EVENTS raii( this );
3072
3073 m_cursorsGrid->ClearRows();
3074
3075 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
3076
3077 if( !plotTab )
3078 return;
3079
3080 // Update cursor values
3081 CURSOR* cursor1 = nullptr;
3082 wxString cursor1Name;
3083 wxString cursor1Units;
3084 CURSOR* cursor2 = nullptr;
3085 wxString cursor2Name;
3086 wxString cursor2Units;
3087
3088 auto getUnitsY =
3089 [&]( TRACE* aTrace ) -> wxString
3090 {
3091 if( plotTab->GetSimType() == ST_AC )
3092 {
3093 if( aTrace->GetType() & SPT_AC_PHASE )
3094 return plotTab->GetUnitsY2();
3095 else
3096 return plotTab->GetUnitsY1();
3097 }
3098 else
3099 {
3100 if( aTrace->GetType() & SPT_POWER )
3101 return plotTab->GetUnitsY3();
3102 else if( aTrace->GetType() & SPT_CURRENT )
3103 return plotTab->GetUnitsY2();
3104 else
3105 return plotTab->GetUnitsY1();
3106 }
3107 };
3108
3109 auto getNameY =
3110 [&]( TRACE* aTrace ) -> wxString
3111 {
3112 if( plotTab->GetSimType() == ST_AC )
3113 {
3114 if( aTrace->GetType() & SPT_AC_PHASE )
3115 return plotTab->GetLabelY2();
3116 else
3117 return plotTab->GetLabelY1();
3118 }
3119 else
3120 {
3121 if( aTrace->GetType() & SPT_POWER )
3122 return plotTab->GetLabelY3();
3123 else if( aTrace->GetType() & SPT_CURRENT )
3124 return plotTab->GetLabelY2();
3125 else
3126 return plotTab->GetLabelY1();
3127 }
3128 };
3129
3130 auto formatValue =
3131 [this]( double aValue, int aCursorId, int aCol ) -> wxString
3132 {
3133 if( ( !m_simulatorFrame->SimFinished() && aCol == 1 ) || std::isnan( aValue ) )
3134 return wxS( "--" );
3135 else
3136 return SPICE_VALUE( aValue ).ToString( m_cursorFormatsDyn[ aCursorId ][ aCol ] );
3137 };
3138
3139 for( const auto& [name, trace] : plotTab->GetTraces() )
3140 {
3141 if( CURSOR* cursor = trace->GetCursor( 1 ) )
3142 {
3143 cursor1 = cursor;
3144 cursor1Name = getNameY( trace );
3145 cursor1Units = getUnitsY( trace );
3146
3147 wxRealPoint coords = cursor->GetCoords();
3148 int row = m_cursorsGrid->GetNumberRows();
3149
3150 m_cursorFormatsDyn[0][0].UpdateUnits( plotTab->GetUnitsX() );
3151 m_cursorFormatsDyn[0][1].UpdateUnits( cursor1Units );
3152
3153 m_cursorsGrid->AppendRows( 1 );
3154 m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "1" ) );
3155 m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, cursor->GetName() );
3156 m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, 0, 0 ) );
3157 m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, 0, 1 ) );
3158 break;
3159 }
3160 }
3161
3162 for( const auto& [name, trace] : plotTab->GetTraces() )
3163 {
3164 if( CURSOR* cursor = trace->GetCursor( 2 ) )
3165 {
3166 cursor2 = cursor;
3167 cursor2Name = getNameY( trace );
3168 cursor2Units = getUnitsY( trace );
3169
3170 wxRealPoint coords = cursor->GetCoords();
3171 int row = m_cursorsGrid->GetNumberRows();
3172
3173 m_cursorFormatsDyn[1][0].UpdateUnits( plotTab->GetUnitsX() );
3174 m_cursorFormatsDyn[1][1].UpdateUnits( cursor2Units );
3175
3176 m_cursorsGrid->AppendRows( 1 );
3177 m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "2" ) );
3178 m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, cursor->GetName() );
3179 m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, 1, 0 ) );
3180 m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, 1, 1 ) );
3181 break;
3182 }
3183 }
3184
3185 if( cursor1 && cursor2 && cursor1Units == cursor2Units )
3186 {
3187 wxRealPoint coords = cursor2->GetCoords() - cursor1->GetCoords();
3188 wxString signal;
3189
3190 m_cursorFormatsDyn[2][0].UpdateUnits( plotTab->GetUnitsX() );
3191 m_cursorFormatsDyn[2][1].UpdateUnits( cursor1Units );
3192
3193 if( cursor1->GetName() == cursor2->GetName() )
3194 signal = wxString::Format( wxS( "%s[2 - 1]" ), cursor2->GetName() );
3195 else
3196 signal = wxString::Format( wxS( "%s - %s" ), cursor2->GetName(), cursor1->GetName() );
3197
3198 m_cursorsGrid->AppendRows( 1 );
3199 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_NAME, _( "Diff" ) );
3200 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_SIGNAL, signal );
3201 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_X, formatValue( coords.x, 2, 0 ) );
3202 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_Y, formatValue( coords.y, 2, 1 ) );
3203 }
3204 // Set up the labels
3205 m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, plotTab->GetLabelX() );
3206
3207 wxString valColName = _( "Value" );
3208
3209 if( !cursor1Name.IsEmpty() )
3210 {
3211 if( cursor2Name.IsEmpty() || cursor1Name == cursor2Name )
3212 valColName = cursor1Name;
3213 }
3214 else if( !cursor2Name.IsEmpty() )
3215 {
3216 valColName = cursor2Name;
3217 }
3218
3219 m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, valColName );
3220
3221 if( m_customCursorsCnt > 3 ) // 2 for the default hardocded cursors plus the initial + 1
3222 {
3223 for( int i = 3; i < m_customCursorsCnt; i++ )
3224 {
3225 for( const auto& [name, trace] : plotTab->GetTraces() )
3226 {
3227 if( CURSOR* cursor = trace->GetCursor( i ) )
3228 {
3229 CURSOR* curs = cursor;
3230 wxString cursName = getNameY( trace );
3231 wxString cursUnits = getUnitsY( trace );
3232
3233 wxRealPoint coords = cursor->GetCoords();
3234 int row = m_cursorsGrid->GetNumberRows();
3235
3236 m_cursorFormatsDyn[i][0].UpdateUnits( plotTab->GetUnitsX() );
3237 m_cursorFormatsDyn[i][1].UpdateUnits( cursUnits );
3238
3239 m_cursorsGrid->AppendRows( 1 );
3240 m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "" ) + wxString( "" ) << i );
3241 m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, curs->GetName() );
3242 m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, i, 0 ) );
3243 m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, i, 1 ) );
3244
3245 // Set up the labels
3246 m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, plotTab->GetLabelX() );
3247
3248 valColName = _( "Value" );
3249
3250 if( !cursName.IsEmpty()
3251 && ( m_cursorsGrid->GetColLabelValue( COL_CURSOR_Y ) == cursName ) )
3252 {
3253 valColName = cursName;
3254 }
3255 m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, valColName );
3256 break;
3257 }
3258 }
3259 }
3260 }
3261}
3262
3263
3264void SIMULATOR_FRAME_UI::onPlotCursorUpdate( wxCommandEvent& aEvent )
3265{
3267 OnModify();
3268}
3269
3270
3272{
3273 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
3274 plotTab->ResetScales( true );
3275
3276 m_simConsole->Clear();
3277
3279
3280 // Do not export netlist, it is already stored in the simulator
3281 applyTuners();
3282
3283 m_refreshTimer.Start( REFRESH_INTERVAL, wxTIMER_ONE_SHOT );
3284}
3285
3286
3287void SIMULATOR_FRAME_UI::OnSimReport( const wxString& aMsg )
3288{
3289 m_simConsole->AppendText( aMsg + "\n" );
3290 m_simConsole->SetInsertionPointEnd();
3291}
3292
3293
3294std::vector<wxString> SIMULATOR_FRAME_UI::SimPlotVectors() const
3295{
3296 std::vector<wxString> signals;
3297
3298 for( const std::string& vec : simulator()->AllVectors() )
3299 signals.emplace_back( vec );
3300
3301 return signals;
3302}
3303
3304
3305std::vector<wxString> SIMULATOR_FRAME_UI::Signals() const
3306{
3307 std::vector<wxString> signals;
3308
3309 for( const wxString& signal : m_signals )
3310 signals.emplace_back( signal );
3311
3312 for( const auto& [ id, signal ] : m_userDefinedSignals )
3313 signals.emplace_back( signal );
3314
3315 sortSignals( signals );
3316
3317 return signals;
3318}
3319
3320
3322{
3323 if( aFinal )
3324 m_refreshTimer.Stop();
3325
3326 SIM_TAB* simTab = GetCurrentSimTab();
3327
3328 if( !simTab )
3329 return;
3330
3331 bool storeMultiRun = false;
3332
3333 if( aFinal && m_multiRunState.active )
3334 {
3335 if( m_multiRunState.currentStep < m_multiRunState.steps.size() )
3336 {
3337 storeMultiRun = true;
3338 m_multiRunState.storePending = true;
3339 }
3340 }
3341 else
3342 {
3343 m_multiRunState.storePending = false;
3344 }
3345
3346 SIM_TYPE simType = simTab->GetSimType();
3347 wxString msg;
3348
3349 if( aFinal )
3350 {
3353 }
3354
3355 // If there are any signals plotted, update them
3356 if( SIM_TAB::IsPlottable( simType ) )
3357 {
3358 simTab->SetSpicePlotName( simulator()->CurrentPlotName() );
3359
3360 if( simType == ST_NOISE && aFinal )
3361 {
3362 m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
3363 m_simConsole->SetInsertionPointEnd();
3364
3365 // The simulator will create noise1 & noise2 on the first run, noise3 and noise4
3366 // on the second, etc. The first plot for each run contains the spectral density
3367 // noise vectors and second contains the integrated noise.
3368 long number;
3369 simulator()->CurrentPlotName().Mid( 5 ).ToLong( &number );
3370
3371 for( const std::string& vec : simulator()->AllVectors() )
3372 {
3373 std::vector<double> val_list = simulator()->GetRealVector( vec, 1 );
3374 wxString value = SPICE_VALUE( val_list[ 0 ] ).ToSpiceString();
3375
3376 msg.Printf( wxS( "%s: %sV\n" ), vec, value );
3377
3378 m_simConsole->AppendText( msg );
3379 m_simConsole->SetInsertionPointEnd();
3380 }
3381
3382 simulator()->Command( fmt::format( "setplot noise{}", number - 1 ) );
3383 simTab->SetSpicePlotName( simulator()->CurrentPlotName() );
3384 }
3385
3386 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab );
3387 wxCHECK_RET( plotTab, wxString::Format( wxT( "No SIM_PLOT_TAB for: %s" ),
3388 magic_enum::enum_name( simType ) ) );
3389
3390 struct TRACE_INFO
3391 {
3392 wxString Vector;
3393 int TraceType;
3394 bool ClearData;
3395 };
3396
3397 std::map<TRACE*, TRACE_INFO> traceMap;
3398
3399 for( const auto& [ name, trace ] : plotTab->GetTraces() )
3400 traceMap[ trace ] = { wxEmptyString, SPT_UNKNOWN, false };
3401
3402 // NB: m_signals are already broken out into gain/phase, but m_userDefinedSignals are
3403 // as the user typed them
3404
3405 for( const wxString& signal : m_signals )
3406 {
3407 int traceType = SPT_UNKNOWN;
3408 wxString vectorName = vectorNameFromSignalName( plotTab, signal, &traceType );
3409
3410 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
3411 traceMap[ trace ] = { vectorName, traceType, false };
3412 }
3413
3414 for( const auto& [ id, signal ] : m_userDefinedSignals )
3415 {
3416 int traceType = SPT_UNKNOWN;
3417 wxString vectorName = vectorNameFromSignalName( plotTab, signal, &traceType );
3418
3419 if( simType == ST_AC )
3420 {
3421 int baseType = traceType &= ~( SPT_AC_GAIN | SPT_AC_PHASE );
3422
3423 for( int subType : { baseType | SPT_AC_GAIN, baseType | SPT_AC_PHASE } )
3424 {
3425 if( TRACE* trace = plotTab->GetTrace( vectorName, subType ) )
3426 traceMap[ trace ] = { vectorName, subType, !aFinal };
3427 }
3428 }
3429 else if( simType == ST_SP )
3430 {
3431 int baseType = traceType &= ~( SPT_SP_AMP | SPT_AC_PHASE );
3432
3433 for( int subType : { baseType | SPT_SP_AMP, baseType | SPT_AC_PHASE } )
3434 {
3435 if( TRACE* trace = plotTab->GetTrace( vectorName, subType ) )
3436 traceMap[trace] = { vectorName, subType, !aFinal };
3437 }
3438 }
3439 else
3440 {
3441 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
3442 traceMap[ trace ] = { vectorName, traceType, !aFinal };
3443 }
3444 }
3445
3446 // Two passes so that DC-sweep sub-traces get deleted and re-created:
3447
3448 for( const auto& [ trace, traceInfo ] : traceMap )
3449 {
3450 if( traceInfo.Vector.IsEmpty() )
3451 plotTab->DeleteTrace( trace );
3452 }
3453
3454 for( const auto& [ trace, info ] : traceMap )
3455 {
3456 std::vector<double> data_x;
3457
3458 if( !info.Vector.IsEmpty() )
3459 updateTrace( info.Vector, info.TraceType, plotTab, &data_x, info.ClearData );
3460 }
3461
3462 plotTab->GetPlotWin()->UpdateAll();
3463
3464 if( aFinal )
3465 {
3466 for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
3467 UpdateMeasurement( row );
3468
3469 plotTab->ResetScales( true );
3470 }
3471
3472 plotTab->GetPlotWin()->Fit();
3473
3475 }
3476 else if( simType == ST_OP && aFinal )
3477 {
3478 m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
3479 m_simConsole->SetInsertionPointEnd();
3480
3481 for( const std::string& vec : simulator()->AllVectors() )
3482 {
3483 std::vector<double> val_list = simulator()->GetRealVector( vec, 1 );
3484
3485 if( val_list.empty() )
3486 continue;
3487
3488 wxString value = SPICE_VALUE( val_list[ 0 ] ).ToSpiceString();
3489 wxString signal;
3490 SIM_TRACE_TYPE type = circuitModel()->VectorToSignal( vec, signal );
3491
3492 const size_t tab = 25; //characters
3493 size_t padding = ( signal.length() < tab ) ? ( tab - signal.length() ) : 1;
3494
3495 switch( type )
3496 {
3497 case SPT_VOLTAGE: value.Append( wxS( "V" ) ); break;
3498 case SPT_CURRENT: value.Append( wxS( "A" ) ); break;
3499 case SPT_POWER: value.Append( wxS( "W" ) ); break;
3500 default: value.Append( wxS( "?" ) ); break;
3501 }
3502
3503 msg.Printf( wxT( "%s%s\n" ),
3504 ( signal + wxT( ":" ) ).Pad( padding, wxUniChar( ' ' ) ),
3505 value );
3506
3507 m_simConsole->AppendText( msg );
3508 m_simConsole->SetInsertionPointEnd();
3509
3510 if( type == SPT_VOLTAGE || type == SPT_CURRENT || type == SPT_POWER )
3511 signal = signal.SubString( 2, signal.Length() - 2 );
3512
3513 if( type == SPT_POWER )
3514 signal += wxS( ":power" );
3515
3516 m_schematicFrame->Schematic().SetOperatingPoint( signal, val_list.at( 0 ) );
3517 }
3518 }
3519 else if( simType == ST_PZ && aFinal )
3520 {
3521 m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
3522 m_simConsole->SetInsertionPointEnd();
3523 simulator()->Command( "print all" );
3524 }
3525
3526 if( storeMultiRun )
3527 {
3528 m_multiRunState.storePending = false;
3529 m_multiRunState.storedSteps = m_multiRunState.currentStep + 1;
3530 }
3531
3532 if( aFinal && m_multiRunState.active )
3533 {
3534 if( m_multiRunState.currentStep + 1 < m_multiRunState.steps.size() )
3535 {
3536 m_multiRunState.currentStep++;
3537
3538 wxQueueEvent( m_simulatorFrame, new wxCommandEvent( EVT_SIM_UPDATE ) );
3539 }
3540 else
3541 {
3542 m_multiRunState.active = false;
3543 m_multiRunState.steps.clear();
3544 m_multiRunState.currentStep = 0;
3545 m_multiRunState.storePending = false;
3546 m_tunerOverrides.clear();
3547
3548 if( !m_multiRunState.traces.empty() )
3549 {
3550 auto iter = m_multiRunState.traces.begin();
3551
3552 if( iter != m_multiRunState.traces.end() )
3553 m_multiRunState.storedSteps = iter->second.yValues.size();
3554 }
3555 }
3556 }
3557}
3558
3559
3561{
3562 m_multiRunState.active = false;
3563 m_multiRunState.tuners.clear();
3564 m_multiRunState.steps.clear();
3565 m_multiRunState.currentStep = 0;
3566 m_multiRunState.storePending = false;
3567
3568 if( aClearTraces )
3569 {
3570 m_multiRunState.traces.clear();
3571 m_multiRunState.storedSteps = 0;
3572 }
3573
3574 m_tunerOverrides.clear();
3575}
3576
3577
3579{
3580 m_tunerOverrides.clear();
3581
3582 std::vector<TUNER_SLIDER*> multiTuners;
3583
3584 for( TUNER_SLIDER* tuner : m_tuners )
3585 {
3586 if( tuner->GetRunMode() == TUNER_SLIDER::RUN_MODE::MULTI )
3587 multiTuners.push_back( tuner );
3588 }
3589
3590 if( multiTuners.empty() )
3591 {
3592 clearMultiRunState( true );
3593 return;
3594 }
3595
3596 bool tunersChanged = multiTuners != m_multiRunState.tuners;
3597
3598 if( m_multiRunState.active && tunersChanged )
3599 clearMultiRunState( true );
3600
3601 if( !m_multiRunState.active )
3602 {
3603 if( tunersChanged || m_multiRunState.storedSteps > 0 || !m_multiRunState.traces.empty() )
3604 {
3605 clearMultiRunState( true );
3606 }
3607
3608 m_multiRunState.tuners = multiTuners;
3609 m_multiRunState.steps = calculateMultiRunSteps( multiTuners );
3610 m_multiRunState.currentStep = 0;
3611 m_multiRunState.storePending = false;
3612
3613 if( m_multiRunState.steps.size() >= 2 )
3614 {
3615 m_multiRunState.active = true;
3616 m_multiRunState.storedSteps = 0;
3617 }
3618 else
3619 {
3620 m_multiRunState.steps.clear();
3621 return;
3622 }
3623 }
3624 else if( tunersChanged )
3625 {
3626 m_multiRunState.tuners = multiTuners;
3627 }
3628
3629 if( m_multiRunState.active && m_multiRunState.currentStep < m_multiRunState.steps.size() )
3630 {
3631 const MULTI_RUN_STEP& step = m_multiRunState.steps[m_multiRunState.currentStep];
3632
3633 for( const auto& entry : step.overrides )
3634 m_tunerOverrides[entry.first] = entry.second;
3635 }
3636}
3637
3638
3639std::vector<SIMULATOR_FRAME_UI::MULTI_RUN_STEP> SIMULATOR_FRAME_UI::calculateMultiRunSteps(
3640 const std::vector<TUNER_SLIDER*>& aTuners ) const
3641{
3642 std::vector<MULTI_RUN_STEP> steps;
3643
3644 if( aTuners.empty() )
3645 return steps;
3646
3647 std::vector<std::vector<double>> tunerValues;
3648 tunerValues.reserve( aTuners.size() );
3649
3650 for( TUNER_SLIDER* tuner : aTuners )
3651 {
3652 if( !tuner )
3653 return steps;
3654
3655 double startValue = tuner->GetMin().ToDouble();
3656 double endValue = tuner->GetMax().ToDouble();
3657 int stepCount = std::max( 2, tuner->GetStepCount() );
3658
3659 if( stepCount < 2 )
3660 stepCount = 2;
3661
3662 double increment = ( endValue - startValue ) / static_cast<double>( stepCount - 1 );
3663
3664 std::vector<double> values;
3665 values.reserve( stepCount );
3666
3667 for( int ii = 0; ii < stepCount; ++ii )
3668 values.push_back( startValue + increment * ii );
3669
3670 tunerValues.push_back( std::move( values ) );
3671 }
3672
3674
3675 if( limit < 1 )
3676 limit = 1;
3677
3678 std::vector<double> currentValues( aTuners.size(), 0.0 );
3679
3680 auto generate = [&]( auto&& self, size_t depth ) -> void
3681 {
3682 if( steps.size() >= static_cast<size_t>( limit ) )
3683 return;
3684
3685 if( depth == aTuners.size() )
3686 {
3687 MULTI_RUN_STEP step;
3688
3689 for( size_t ii = 0; ii < aTuners.size(); ++ii )
3690 step.overrides.emplace( aTuners[ii], currentValues[ii] );
3691
3692 steps.push_back( std::move( step ) );
3693 return;
3694 }
3695
3696 for( double value : tunerValues[depth] )
3697 {
3698 currentValues[depth] = value;
3699 self( self, depth + 1 );
3700
3701 if( steps.size() >= static_cast<size_t>( limit ) )
3702 return;
3703 }
3704 };
3705
3706 generate( generate, 0 );
3707
3708 return steps;
3709}
3710
3711
3712std::string SIMULATOR_FRAME_UI::multiRunTraceKey( const wxString& aVectorName, int aTraceType ) const
3713{
3714 return fmt::format( "{}|{}", aVectorName.ToStdString(), aTraceType );
3715}
3716
3717
3718void SIMULATOR_FRAME_UI::recordMultiRunData( const wxString& aVectorName, int aTraceType,
3719 const std::vector<double>& aX,
3720 const std::vector<double>& aY )
3721{
3722 if( aX.empty() || aY.empty() )
3723 return;
3724
3725 std::string key = multiRunTraceKey( aVectorName, aTraceType );
3726 MULTI_RUN_TRACE& trace = m_multiRunState.traces[key];
3727
3728 trace.traceType = aTraceType;
3729
3730 if( trace.xValues.empty() )
3731 trace.xValues = aX;
3732
3733 if( trace.xValues.size() != aX.size() )
3734 return;
3735
3736 size_t index = m_multiRunState.currentStep;
3737
3738 if( trace.yValues.size() <= index )
3739 trace.yValues.resize( index + 1 );
3740
3741 trace.yValues[index] = aY;
3742}
3743
3744
3745bool SIMULATOR_FRAME_UI::hasMultiRunTrace( const wxString& aVectorName, int aTraceType ) const
3746{
3747 std::string key = multiRunTraceKey( aVectorName, aTraceType );
3748 auto it = m_multiRunState.traces.find( key );
3749
3750 if( it == m_multiRunState.traces.end() )
3751 return false;
3752
3753 const MULTI_RUN_TRACE& trace = it->second;
3754
3755 return !trace.xValues.empty() && !trace.yValues.empty();
3756}
3757
3758
3760{
3761 m_simulatorFrame->OnModify();
3762}
int index
const char * name
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
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
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:105
wxString ToCSSString() const
Definition color4d.cpp:147
wxColour ToColour() const
Definition color4d.cpp:220
Definition kiid.h:49
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:216
virtual bool HasMessage() const
Returns true if any messages were reported.
Definition reporter.h:134
Holds all the data relating to one schematic.
Definition schematic.h:88
void ClearOperatingPoints()
Clear operating points from a .op simulation.
Definition schematic.h:289
Schematic editor (Eeschema) main window.
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:167
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:76
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:184
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.
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)
void OnSimReport(const wxString &aMsg)
void ApplyPreferences(const SIM_PREFERENCES &aPrefs)
Called when settings are changed via the common Preferences dialog.
const SPICE_CIRCUIT_MODEL * GetExporter() const
Return the netlist exporter object used for simulations.
SIMULATOR_FRAME * m_simulatorFrame
void AddTuner(const SCH_SHEET_PATH &aSheetPath, SCH_SYMBOL *aSymbol)
Add a tuner for a symbol.
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:48
virtual const PARAM * GetTunerParam() const
Definition sim_model.h:480
const SPICE_GENERATOR & SpiceGenerator() const
Definition sim_model.h:431
static void FillDefaultColorList(bool aWhiteBg)
Fills m_colorList by a default set of colors.
bool DeleteTrace(const wxString &aVectorName, int aTraceType)
wxString GetLabelY1() const
mpWindow * GetPlotWin() const
void ShowGrid(bool aEnable)
void SetTraceData(TRACE *aTrace, std::vector< double > &aX, std::vector< double > &aY, int aSweepCount, size_t aSweepSize, bool aIsMultiRun=false, const std::vector< wxString > &aMultiRunLabels={})
wxString GetUnitsY2() const
void SetY2Scale(bool aLock, double aMin, double aMax)
TRACE * GetTrace(const wxString &aVecName, int aType) const
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()
void ShowLegend(bool aEnable)
wxString GetLabelY2() const
void EnableCursor(TRACE *aTrace, int aCursorId, const wxString &aSignalName)
wxString GetUnitsX() const
void EnsureThirdYAxisExists()
TRACE * GetOrAddTrace(const wxString &aVectorName, int aType)
void SetDottedSecondary(bool aEnable)
Draw secondary signal traces (current or phase) with dotted lines.
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:55
SIM_TYPE GetSimType() const
Definition sim_tab.cpp:75
const wxString & GetSimCommand() const
Definition sim_tab.h:52
static bool IsPlottable(SIM_TYPE aSimType)
Definition sim_tab.cpp:53
void SetSimOptions(int aOptions)
Definition sim_tab.h:56
wxString GetLastSchTextSimCommand() const
Definition sim_tab.h:58
void SetSpicePlotName(const wxString &aPlotName)
Definition sim_tab.h:62
static std::string ToSpice(const std::string &aString)
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 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:56
wxString ToString() const
Return string value as when converting double to string (e.g.
wxString ToSpiceString() const
Return string value in Spice format (e.g.
double ToDouble() const
SUPPRESS_GRID_CELL_EVENTS(SIMULATOR_FRAME_UI *aFrame)
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:191
REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED) override
Report a string with a given severity.
Definition reporter.cpp:68
const wxString & GetMessages() const
Definition reporter.cpp:77
A KICAD version of wxTextEntryDialog which supports the various improvements/work-arounds from DIALOG...
wxString GetValue() const
const wxString & GetName() const
Get layer name.
Definition mathplot.h:239
const wxPen & GetPen() const
Get pen set for this layer.
Definition mathplot.h:254
Canvas for plotting mpLayer implementations.
Definition mathplot.h:910
int GetMarginLeft() const
Definition mathplot.h:1221
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:1215
void UpdateAll()
Refresh display.
int GetMarginRight() const
Definition mathplot.h:1217
int GetMarginBottom() const
Definition mathplot.h:1219
void LockY(bool aLock)
Definition mathplot.h:1265
bool AddLayer(mpLayer *layer, bool refreshDisplay=true)
Add a plot layer to the canvas.
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:202
This file is part of the common library.
#define _(s)
Abstract pattern-matching tool and implementations.
@ CTX_SIGNAL
@ GRIDTRICKS_ID_SELECT
Definition grid_tricks.h:46
@ GRIDTRICKS_ID_COPY
Definition grid_tricks.h:43
@ GRIDTRICKS_ID_DELETE
Definition grid_tricks.h:44
@ GRIDTRICKS_FIRST_CLIENT_ID
Definition grid_tricks.h:48
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)
see class PGM_BASE
SIM_TRACE_TYPE
Definition sim_types.h:50
@ SPT_TIME
Definition sim_types.h:61
@ SPT_AC_PHASE
Definition sim_types.h:54
@ SPT_SWEEP
Definition sim_types.h:64
@ SPT_UNKNOWN
Definition sim_types.h:67
@ SPT_AC_GAIN
Definition sim_types.h:55
@ SPT_Y_AXIS_MASK
Definition sim_types.h:58
@ SPT_SP_AMP
Definition sim_types.h:57
@ SPT_VOLTAGE
Definition sim_types.h:52
@ SPT_POWER
Definition sim_types.h:56
@ SPT_CURRENT
Definition sim_types.h:53
@ SPT_LIN_FREQUENCY
Definition sim_types.h:62
SIM_TYPE
< Possible simulation types
Definition sim_types.h:32
@ ST_SP
Definition sim_types.h:43
@ ST_TRAN
Definition sim_types.h:42
@ ST_UNKNOWN
Definition sim_types.h:33
@ ST_NOISE
Definition sim_types.h:37
@ ST_AC
Definition sim_types.h:34
@ ST_DISTO
Definition sim_types.h:36
@ ST_TF
Definition sim_types.h:41
@ ST_SENS
Definition sim_types.h:40
@ ST_DC
Definition sim_types.h:35
@ ST_OP
Definition sim_types.h:38
@ ST_FFT
Definition sim_types.h:44
@ ST_PZ
Definition sim_types.h:39
void sortSignals(std::vector< wxString > &signals)
wxString vectorNameFromSignalId(int aUserDefinedSignalId)
MEASUREMENTS_GIRD_COLUMNS
@ COL_MEASUREMENT_FORMAT
@ COL_MEASUREMENT_VALUE
@ COL_MEASUREMENT
CURSORS_GRID_COLUMNS
@ COL_CURSOR_NAME
@ COL_CURSOR_SIGNAL
@ COL_CURSOR_X
@ COL_CURSOR_Y
SIGNALS_GRID_COLUMNS
@ COL_SIGNAL_SHOW
@ COL_SIGNAL_NAME
@ COL_CURSOR_1
@ COL_SIGNAL_COLOR
@ COL_CURSOR_2
#define ID_SIM_REFRESH
@ 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 > > yValues
const INFO & info
Definition sim_model.h:401
Contains preferences pertaining to the simulator.
const SIM_MODEL * model
A SPICE_VALUE_FORMAT holds precision and range info for formatting values.Helper class to handle Spic...
Definition spice_value.h:43
wxString ToString() const
void UpdateUnits(const wxString &aUnits)
KIBIS_MODEL * model
VECTOR3I res
wxString result
Test unit parsing edge cases and error handling.
Definition of file extensions used in Kicad.