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 SIMULATOR_FRAME_UI_BASE( aSimulatorFrame ),
549 m_simulatorFrame( aSimulatorFrame ),
550 m_schematicFrame( aSchematicFrame ),
551 m_darkMode( true ),
552 m_plotNumber( 0 ),
554{
555 // Get the previous size and position of windows:
556 LoadSettings( m_schematicFrame->eeconfig() );
557
558 m_filter->SetHint( _( "Filter" ) );
559
560 m_signalsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
561 m_cursorsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
562 m_measurementsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
563
564 m_signalsGrid->PushEventHandler( new SIGNALS_GRID_TRICKS( this, m_signalsGrid ) );
565 m_cursorsGrid->PushEventHandler( new CURSORS_GRID_TRICKS( this, m_cursorsGrid ) );
566 m_measurementsGrid->PushEventHandler( new MEASUREMENTS_GRID_TRICKS( this, m_measurementsGrid ) );
567
568 wxGridCellAttr* attr = new wxGridCellAttr;
569 attr->SetReadOnly();
570 m_signalsGrid->SetColAttr( COL_SIGNAL_NAME, attr );
571
572 attr = new wxGridCellAttr;
573 attr->SetReadOnly();
574 m_cursorsGrid->SetColAttr( COL_CURSOR_NAME, attr );
575
576 attr = new wxGridCellAttr;
577 attr->SetReadOnly();
578 m_cursorsGrid->SetColAttr( COL_CURSOR_SIGNAL, attr );
579
580 attr = new wxGridCellAttr;
581 attr->SetReadOnly();
582 m_cursorsGrid->SetColAttr( COL_CURSOR_Y, attr );
583
585
586 attr = new wxGridCellAttr;
587 attr->SetReadOnly();
588 m_measurementsGrid->SetColAttr( COL_MEASUREMENT_VALUE, attr );
589
590 // Prepare the color list to plot traces
592
593 Bind( EVT_SIM_CURSOR_UPDATE, &SIMULATOR_FRAME_UI::onPlotCursorUpdate, this );
594
595 Bind( wxEVT_TIMER,
596 [&]( wxTimerEvent& aEvent )
597 {
598 OnSimRefresh( false );
599
600 if( m_simulatorFrame->GetSimulator()->IsRunning() )
601 m_refreshTimer.Start( REFRESH_INTERVAL, wxTIMER_ONE_SHOT );
602 },
603 m_refreshTimer.GetId() );
604
605#ifndef wxHAS_NATIVE_TABART
606 // Default non-native tab art has ugly gradients we don't want
607 m_plotNotebook->SetArtProvider( new wxAuiSimpleTabArt() );
608#endif
609}
610
611
613{
614 // Delete the GRID_TRICKS.
615 m_signalsGrid->PopEventHandler( true );
616 m_cursorsGrid->PopEventHandler( true );
617 m_measurementsGrid->PopEventHandler( true );
618}
619
620
622{
623 for( auto& m_cursorFormat : m_cursorFormats )
624 {
625 m_cursorFormat[0] = { 3, wxS( "~s" ) };
626 m_cursorFormat[1] = { 3, wxS( "~V" ) };
627 }
628
629 // proper init and transfer/copy m_cursorFormats
630 // we work on m_cursorFormatsDyn from now on.
631 // TODO: rework +- LOC when m_cursorFormatsDyn and m_cursorFormats get merged.
632 m_cursorFormatsDyn.clear();
633 m_cursorFormatsDyn.resize( std::size( m_cursorFormats ) );
634
635 for( size_t index = 0; index < std::size( m_cursorFormats ); index++ )
636 {
637 for( size_t index2 = 0; index2 < std::size( m_cursorFormats[0] ); index2++ )
638 m_cursorFormatsDyn[index].push_back( m_cursorFormats[index][index2] );
639 }
640
641 // Dump string helper, tries to get the current higher cursor name to form the next one.
642 // Based on the column labeling
643 // TODO: "Cursor n" may translate as "n Cursor" in other languages
644 // TBD how to handle; just forbid for now.
645 int nameMax = 0;
646
647 for( int i = 0; i < m_signalsGrid->GetNumberCols(); i++ )
648 {
649 wxString maxCursor = m_signalsGrid->GetColLabelValue( i );
650
651 maxCursor.Replace( _( "Cursor " ), "" );
652
653 int tmpMax = wxAtoi( maxCursor );
654
655 if( nameMax < tmpMax )
656 nameMax = tmpMax;
657 }
658
659 m_customCursorsCnt = nameMax + 1; // Init with a +1 on top of current cursor 2, defaults to 3
660}
661
662
664{
665 std::vector<SPICE_VALUE_FORMAT> tmp;
666 // m_cursorFormatsDyn should be equal with m_cursorFormats on first entry here.
667 m_cursorFormatsDyn.emplace_back( tmp );
668
669 m_cursorFormatsDyn[m_customCursorsCnt].push_back( { 3, wxS( "~s" ) } );
670 m_cursorFormatsDyn[m_customCursorsCnt].push_back( { 3, wxS( "~V" ) } );
671
672 wxString cursor_name = wxString( _( "Cursor " ) ) << m_customCursorsCnt;
673
674 m_signalsGrid->InsertCols( m_signalsGrid->GetNumberCols() , 1, true );
675 m_signalsGrid->SetColLabelValue( m_signalsGrid->GetNumberCols() - 1, cursor_name );
676
677 wxGridCellAttr* attr = new wxGridCellAttr;
678 m_signalsGrid->SetColAttr( COL_CURSOR_2 + m_customCursorsCnt, attr );
679
681
684 OnModify();
685}
686
687
689{
690 int col = m_signalsGrid->GetNumberCols();
691 int rows = m_signalsGrid->GetNumberRows();
692
693 if( col > COL_CURSOR_2 )
694 {
695 // Now we need to find the active cursor and deactivate before removing the column,
696 // Send the dummy event to update the UI
697 for( int i = 0; i < rows; i++ )
698 {
699 if( m_signalsGrid->GetCellValue( i, col - 1 ) == wxS( "1" ) )
700 {
701 m_signalsGrid->SetCellValue( i, col - 1, wxEmptyString );
702 wxGridEvent aDummy( wxID_ANY, wxEVT_GRID_CELL_CHANGED, m_signalsGrid, i, col - 1 );
703 onSignalsGridCellChanged( aDummy );
704 break;
705 }
706
707 }
708
709 m_signalsGrid->DeleteCols( col - 1, 1, false );
710 m_cursorFormatsDyn.pop_back();
712 m_plotNotebook->Refresh();
715 OnModify();
716 }
717}
718
719
721{
722 for( int ii = 0; ii < static_cast<int>( m_plotNotebook->GetPageCount() ); ++ii )
723 {
724 if( SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( ii ) ) )
725 {
726 simTab->OnLanguageChanged();
727
728 wxString pageTitle( simulator()->TypeToName( simTab->GetSimType(), true ) );
729 pageTitle.Prepend( wxString::Format( _( "Analysis %u - " ), ii+1 /* 1-based */ ) );
730
731 m_plotNotebook->SetPageText( ii, pageTitle );
732 }
733 }
734
735 m_filter->SetHint( _( "Filter" ) );
736
737 m_signalsGrid->SetColLabelValue( COL_SIGNAL_NAME, _( "Signal" ) );
738 m_signalsGrid->SetColLabelValue( COL_SIGNAL_SHOW, _( "Plot" ) );
739 m_signalsGrid->SetColLabelValue( COL_SIGNAL_COLOR, _( "Color" ) );
740 m_signalsGrid->SetColLabelValue( COL_CURSOR_1, _( "Cursor 1" ) );
741 m_signalsGrid->SetColLabelValue( COL_CURSOR_2, _( "Cursor 2" ) );
742
743 m_cursorsGrid->SetColLabelValue( COL_CURSOR_NAME, _( "Cursor" ) );
744 m_cursorsGrid->SetColLabelValue( COL_CURSOR_SIGNAL, _( "Signal" ) );
745 m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, _( "Time" ) );
746 m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, _( "Value" ) );
748
749 for( TUNER_SLIDER* tuner : m_tuners )
750 tuner->ShowChangedLanguage();
751}
752
753
768
769
771{
773
774 settings.view.plot_panel_width = m_splitterLeftRight->GetSashPosition();
775 settings.view.plot_panel_height = m_splitterPlotAndConsole->GetSashPosition();
776 settings.view.signal_panel_height = m_splitterSignals->GetSashPosition();
777 settings.view.cursors_panel_height = m_splitterCursors->GetSashPosition();
778 settings.view.measurements_panel_height = m_splitterMeasurements->GetSashPosition();
779 settings.view.white_background = !m_darkMode;
780}
781
782
784{
785 m_preferences = aPrefs;
786
787 for( std::size_t i = 0; i < m_plotNotebook->GetPageCount(); ++i )
788 {
789 if( SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( i ) ) )
790 simTab->ApplyPreferences( aPrefs );
791 }
792}
793
794
796{
797 if( !simulator()->Settings()->GetWorkbookFilename().IsEmpty() )
798 {
799 wxFileName filename = simulator()->Settings()->GetWorkbookFilename();
800 filename.SetPath( m_schematicFrame->Prj().GetProjectPath() );
801
802 if( !LoadWorkbook( filename.GetFullPath() ) )
804 }
805 else if( m_simulatorFrame->LoadSimulator( wxEmptyString, 0 ) )
806 {
807 wxString schTextSimCommand = circuitModel()->GetSchTextSimCommand();
808
809 if( !schTextSimCommand.IsEmpty() )
810 {
811 SIM_TAB* simTab = NewSimTab( schTextSimCommand );
813 }
814
816 rebuildSignalsGrid( m_filter->GetValue() );
817 }
818}
819
820
838
839
840void sortSignals( std::vector<wxString>& signals )
841{
842 std::sort( signals.begin(), signals.end(),
843 []( const wxString& lhs, const wxString& rhs )
844 {
845 // Sort voltages first
846 if( lhs.Upper().StartsWith( 'V' ) && !rhs.Upper().StartsWith( 'V' ) )
847 return true;
848 else if( !lhs.Upper().StartsWith( 'V' ) && rhs.Upper().StartsWith( 'V' ) )
849 return false;
850
851 return StrNumCmp( lhs, rhs, true /* ignore case */ ) < 0;
852 } );
853}
854
855
857{
858 SUPPRESS_GRID_CELL_EVENTS raii( this );
859
860 m_signalsGrid->ClearRows();
861
862 SIM_PLOT_TAB* plotPanel = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
863
864 if( !plotPanel )
865 return;
866
867 SIM_TYPE simType = plotPanel->GetSimType();
868 std::vector<wxString> signals;
869
870 if( plotPanel->GetSimType() == ST_FFT )
871 {
872 wxStringTokenizer tokenizer( plotPanel->GetSimCommand(), " \t\r\n", wxTOKEN_STRTOK );
873
874 while( tokenizer.HasMoreTokens() && tokenizer.GetNextToken().Lower() != wxT( "fft" ) )
875 {};
876
877 while( tokenizer.HasMoreTokens() )
878 signals.emplace_back( tokenizer.GetNextToken() );
879 }
880 else
881 {
882 // NB: m_signals are already broken out into gain/phase, but m_userDefinedSignals are
883 // as the user typed them
884
885 for( const wxString& signal : m_signals )
886 signals.push_back( signal );
887
888 for( const auto& [ id, signal ] : m_userDefinedSignals )
889 {
890 if( simType == ST_AC )
891 {
892 signals.push_back( signal + _( " (gain)" ) );
893 signals.push_back( signal + _( " (phase)" ) );
894 }
895 else if( simType == ST_SP )
896 {
897 signals.push_back( signal + _( " (amplitude)" ) );
898 signals.push_back( signal + _( " (phase)" ) );
899 }
900 else
901 {
902 signals.push_back( signal );
903 }
904 }
905
906 sortSignals( signals );
907 }
908
909 if( aFilter.IsEmpty() )
910 aFilter = wxS( "*" );
911
912 EDA_COMBINED_MATCHER matcher( aFilter.Upper(), CTX_SIGNAL );
913 int row = 0;
914
915 for( const wxString& signal : signals )
916 {
917 if( matcher.Find( signal.Upper() ) )
918 {
919 int traceType = SPT_UNKNOWN;
920 wxString vectorName = vectorNameFromSignalName( plotPanel, signal, &traceType );
921 TRACE* trace = plotPanel->GetTrace( vectorName, traceType );
922
923 m_signalsGrid->AppendRows( 1 );
924 m_signalsGrid->SetCellValue( row, COL_SIGNAL_NAME, signal );
925
926 wxGridCellAttr* attr = new wxGridCellAttr;
927 attr->SetRenderer( new wxGridCellBoolRenderer() );
928 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
929 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
930 m_signalsGrid->SetAttr( row, COL_SIGNAL_SHOW, attr );
931
932 if( !trace )
933 {
934 attr = new wxGridCellAttr;
935 attr->SetReadOnly();
936 m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
937 m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, wxEmptyString );
938
939 attr = new wxGridCellAttr;
940 attr->SetReadOnly();
941 m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
942
943 attr = new wxGridCellAttr;
944 attr->SetReadOnly();
945 m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
946
947 if( m_customCursorsCnt > 3 )
948 {
949 for( int i = 1; i <= m_customCursorsCnt - 3; i++ )
950 {
951 attr = new wxGridCellAttr;
952 attr->SetReadOnly();
953 m_signalsGrid->SetAttr( row, COL_CURSOR_2 + i, attr );
954 }
955 }
956 }
957 else
958 {
959 m_signalsGrid->SetCellValue( row, COL_SIGNAL_SHOW, wxS( "1" ) );
960
961 attr = new wxGridCellAttr;
962 attr->SetRenderer( new GRID_CELL_COLOR_RENDERER( this ) );
963 attr->SetEditor( new GRID_CELL_COLOR_SELECTOR( this, m_signalsGrid ) );
964 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
965 m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
966 KIGFX::COLOR4D color( trace->GetPen().GetColour() );
967 m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, color.ToCSSString() );
968
969 attr = new wxGridCellAttr;
970 attr->SetRenderer( new wxGridCellBoolRenderer() );
971 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
972 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
973 m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
974 m_signalsGrid->SetCellValue( row, COL_CURSOR_1, trace->GetCursor( 1 ) ? "1" : "0" );
975
976 attr = new wxGridCellAttr;
977 attr->SetRenderer( new wxGridCellBoolRenderer() );
978 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
979 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
980 m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
981 m_signalsGrid->SetCellValue( row, COL_CURSOR_2, trace->GetCursor( 2 ) ? "1" : "0" );
982
983 if( m_customCursorsCnt > 3 )
984 {
985 for( int i = 1; i <= m_customCursorsCnt - 3; i++ )
986 {
987 attr = new wxGridCellAttr;
988 attr->SetRenderer( new wxGridCellBoolRenderer() );
989 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
990 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
991 m_signalsGrid->SetAttr( row, COL_CURSOR_2 + i, attr );
992 m_signalsGrid->SetCellValue( row, COL_CURSOR_2 + i, trace->GetCursor( i ) ? "1" : "0" );
993 }
994 }
995 }
996 row++;
997 }
998 }
999}
1000
1001
1003{
1004 m_signals.clear();
1005
1006 int options = m_simulatorFrame->GetCurrentOptions();
1007 SIM_TYPE simType = m_simulatorFrame->GetCurrentSimType();
1008 wxString unconnected = wxString( wxS( "unconnected-(" ) );
1009
1010 if( simType == ST_UNKNOWN )
1011 simType = ST_TRAN;
1012
1013 unconnected.Replace( '(', '_' ); // Convert to SPICE markup
1014
1015 auto addSignal =
1016 [&]( const wxString& aSignalName )
1017 {
1018 if( simType == ST_AC )
1019 {
1020 m_signals.push_back( aSignalName + _( " (gain)" ) );
1021 m_signals.push_back( aSignalName + _( " (phase)" ) );
1022 }
1023 else if( simType == ST_SP )
1024 {
1025 m_signals.push_back( aSignalName + _( " (amplitude)" ) );
1026 m_signals.push_back( aSignalName + _( " (phase)" ) );
1027 }
1028 else
1029 {
1030 m_signals.push_back( aSignalName );
1031 }
1032 };
1033
1035 && ( simType == ST_TRAN || simType == ST_DC || simType == ST_AC || simType == ST_FFT) )
1036 {
1037 for( const wxString& net : circuitModel()->GetNets() )
1038 {
1039 // netnames are escaped (can contain "{slash}" for '/') Unscape them:
1040 wxString netname = UnescapeString( net );
1042
1043 if( netname == "GND" || netname == "0" || netname.StartsWith( unconnected ) )
1044 continue;
1045
1046 m_netnames.emplace_back( netname );
1047 addSignal( wxString::Format( wxS( "V(%s)" ), netname ) );
1048 }
1049 }
1050
1052 && ( simType == ST_TRAN || simType == ST_DC || simType == ST_AC ) )
1053 {
1054 for( const SPICE_ITEM& item : circuitModel()->GetItems() )
1055 {
1056 // Add all possible currents for the device.
1057 for( const std::string& name : item.model->SpiceGenerator().CurrentNames( item ) )
1058 addSignal( name );
1059 }
1060 }
1061
1063 && ( simType == ST_TRAN || simType == ST_DC ) )
1064 {
1065 for( const SPICE_ITEM& item : circuitModel()->GetItems() )
1066 {
1067 if( item.model->GetPinCount() >= 2 )
1068 {
1069 wxString name = item.model->SpiceGenerator().ItemName( item );
1070 addSignal( wxString::Format( wxS( "P(%s)" ), name ) );
1071 }
1072 }
1073 }
1074
1075 if( simType == ST_NOISE )
1076 {
1077 addSignal( wxS( "inoise_spectrum" ) );
1078 addSignal( wxS( "onoise_spectrum" ) );
1079 }
1080
1081 if( simType == ST_SP )
1082 {
1083 std::vector<std::string> portnums;
1084
1085 for( const SPICE_ITEM& item : circuitModel()->GetItems() )
1086 {
1087 wxString name = item.model->SpiceGenerator().ItemName( item );
1088
1089 // We are only looking for voltage sources in .SP mode
1090 if( !name.StartsWith( "V" ) )
1091 continue;
1092
1093 std::string portnum = "";
1094
1095 if( const SIM_MODEL::PARAM* portnum_param = item.model->FindParam( "portnum" ) )
1096 portnum = SIM_VALUE::ToSpice( portnum_param->value );
1097
1098 if( portnum != "" )
1099 portnums.push_back( portnum );
1100 }
1101
1102 for( const std::string& portnum1 : portnums )
1103 {
1104 for( const std::string& portnum2 : portnums )
1105 addSignal( wxString::Format( wxS( "S_%s_%s" ), portnum1, portnum2 ) );
1106 }
1107 }
1108
1109 // Add .SAVE and .PROBE directives
1110 for( const wxString& directive : circuitModel()->GetDirectives() )
1111 {
1112 wxStringTokenizer directivesTokenizer( directive, "\r\n", wxTOKEN_STRTOK );
1113
1114 while( directivesTokenizer.HasMoreTokens() )
1115 {
1116 wxString line = directivesTokenizer.GetNextToken().Upper();
1117 wxString directiveParams;
1118
1119 if( line.StartsWith( wxS( ".SAVE" ), &directiveParams )
1120 || line.StartsWith( wxS( ".PROBE" ), &directiveParams ) )
1121 {
1122 wxStringTokenizer paramsTokenizer( directiveParams, " \t", wxTOKEN_STRTOK );
1123
1124 while( paramsTokenizer.HasMoreTokens() )
1125 addSignal( paramsTokenizer.GetNextToken() );
1126 }
1127 }
1128 }
1129}
1130
1131
1132SIM_TAB* SIMULATOR_FRAME_UI::NewSimTab( const wxString& aSimCommand )
1133{
1134 SIM_TAB* simTab = nullptr;
1135 SIM_TYPE simType = SPICE_CIRCUIT_MODEL::CommandToSimType( aSimCommand );
1136
1137 if( SIM_TAB::IsPlottable( simType ) )
1138 {
1139 SIM_PLOT_TAB* panel = new SIM_PLOT_TAB( aSimCommand, m_plotNotebook );
1140 simTab = panel;
1142 }
1143 else
1144 {
1145 simTab = new SIM_NOPLOT_TAB( aSimCommand, m_plotNotebook );
1146 }
1147
1148 wxString pageTitle( simulator()->TypeToName( simType, true ) );
1149 pageTitle.Prepend( wxString::Format( _( "Analysis %u - " ), static_cast<unsigned int>( ++m_plotNumber ) ) );
1150
1151 m_plotNotebook->AddPage( simTab, pageTitle, true );
1152
1153 return simTab;
1154}
1155
1156
1157void SIMULATOR_FRAME_UI::OnFilterText( wxCommandEvent& aEvent )
1158{
1159 rebuildSignalsGrid( m_filter->GetValue() );
1160}
1161
1162
1163void SIMULATOR_FRAME_UI::OnFilterMouseMoved( wxMouseEvent& aEvent )
1164{
1165#if defined( __WXOSX__ ) // Doesn't work properly on other ports
1166 wxPoint pos = aEvent.GetPosition();
1167 wxRect ctrlRect = m_filter->GetScreenRect();
1168 int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
1169
1170 if( m_filter->IsSearchButtonVisible() && pos.x < buttonWidth )
1171 SetCursor( wxCURSOR_ARROW );
1172 else if( m_filter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
1173 SetCursor( wxCURSOR_ARROW );
1174 else
1175 SetCursor( wxCURSOR_IBEAM );
1176#endif
1177}
1178
1179
1180wxString vectorNameFromSignalId( int aUserDefinedSignalId )
1181{
1182 return wxString::Format( wxS( "user%d" ), aUserDefinedSignalId );
1183}
1184
1185
1190wxString SIMULATOR_FRAME_UI::vectorNameFromSignalName( SIM_PLOT_TAB* aPlotTab, const wxString& aSignalName,
1191 int* aTraceType )
1192{
1193 auto looksLikePower = []( const wxString& aExpression ) -> bool
1194 {
1195 wxString exprUpper = aExpression.Upper();
1196
1197 if( exprUpper.Contains( wxS( ":POWER" ) ) )
1198 return true;
1199
1200 if( exprUpper.Find( '*' ) == wxNOT_FOUND )
1201 return false;
1202
1203 if( !exprUpper.Contains( wxS( "V(" ) ) )
1204 return false;
1205
1206 if( !exprUpper.Contains( wxS( "I(" ) ) )
1207 return false;
1208
1209 return true;
1210 };
1211
1212 std::map<wxString, int> suffixes;
1213 suffixes[ _( " (amplitude)" ) ] = SPT_SP_AMP;
1214 suffixes[ _( " (gain)" ) ] = SPT_AC_GAIN;
1215 suffixes[ _( " (phase)" ) ] = SPT_AC_PHASE;
1216
1217 if( aTraceType )
1218 {
1219 if( aPlotTab && aPlotTab->GetSimType() == ST_NOISE )
1220 {
1221 if( getNoiseSource().Upper().StartsWith( 'I' ) )
1222 *aTraceType = SPT_CURRENT;
1223 else
1224 *aTraceType = SPT_VOLTAGE;
1225 }
1226 else
1227 {
1228 wxUniChar firstChar = aSignalName.Upper()[0];
1229
1230 if( firstChar == 'V' )
1231 *aTraceType = SPT_VOLTAGE;
1232 else if( firstChar == 'I' )
1233 *aTraceType = SPT_CURRENT;
1234 else if( firstChar == 'P' )
1235 *aTraceType = SPT_POWER;
1236 }
1237 }
1238
1239 wxString name = aSignalName;
1240
1241 for( const auto& [ candidate, type ] : suffixes )
1242 {
1243 if( name.EndsWith( candidate ) )
1244 {
1245 name = name.Left( name.Length() - candidate.Length() );
1246
1247 if( aTraceType )
1248 *aTraceType |= type;
1249
1250 break;
1251 }
1252 }
1253
1254 for( const auto& [ id, signal ] : m_userDefinedSignals )
1255 {
1256 if( name == signal )
1257 {
1258 if( aTraceType && looksLikePower( signal ) )
1259 {
1260 int suffixBits = *aTraceType & ( SPT_AC_GAIN | SPT_AC_PHASE | SPT_SP_AMP );
1261 *aTraceType = suffixBits | SPT_POWER;
1262 }
1263
1264 return vectorNameFromSignalId( id );
1265 }
1266 }
1267
1268 return name;
1269};
1270
1271
1273{
1274 if( m_SuppressGridEvents > 0 )
1275 return;
1276
1277 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1278
1279 if( !plotTab )
1280 return;
1281
1282 int row = aEvent.GetRow();
1283 int col = aEvent.GetCol();
1284 wxString text = m_signalsGrid->GetCellValue( row, col );
1285 wxString signalName = m_signalsGrid->GetCellValue( row, COL_SIGNAL_NAME );
1286 int traceType = SPT_UNKNOWN;
1287 wxString vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
1288
1289 if( col == COL_SIGNAL_SHOW )
1290 {
1291 if( text == wxS( "1" ) )
1292 updateTrace( vectorName, traceType, plotTab );
1293 else
1294 plotTab->DeleteTrace( vectorName, traceType );
1295
1296 plotTab->GetPlotWin()->UpdateAll();
1297
1298 // Update enabled/visible states of other controls
1301 OnModify();
1302 }
1303 else if( col == COL_SIGNAL_COLOR )
1304 {
1305 KIGFX::COLOR4D color( m_signalsGrid->GetCellValue( row, COL_SIGNAL_COLOR ) );
1306 TRACE* trace = plotTab->GetTrace( vectorName, traceType );
1307
1308 if( trace )
1309 {
1310 trace->SetTraceColour( color.ToColour() );
1311 plotTab->UpdateTraceStyle( trace );
1312 plotTab->UpdatePlotColors();
1313 OnModify();
1314 }
1315 }
1316 else if( col == COL_CURSOR_1 || col == COL_CURSOR_2
1317 || ( std::size( m_cursorFormatsDyn ) > std::size( m_cursorFormats ) && col > COL_CURSOR_2 ) )
1318 {
1319 int id = col == COL_CURSOR_1 ? 1 : 2;
1320
1321 if( col > COL_CURSOR_2 ) // TODO: clean up logic
1322 {
1323 id = col - 2; // enum SIGNALS_GRID_COLUMNS offset for Cursor n
1324 }
1325
1326 TRACE* activeTrace = nullptr;
1327
1328 if( text == wxS( "1" ) )
1329 {
1330 signalName = m_signalsGrid->GetCellValue( row, COL_SIGNAL_NAME );
1331 vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
1332 activeTrace = plotTab->GetTrace( vectorName, traceType );
1333
1334 if( activeTrace )
1335 plotTab->EnableCursor( activeTrace, id, signalName );
1336
1337 OnModify();
1338 }
1339
1340 // Turn off cursor on other signals.
1341 for( const auto& [name, trace] : plotTab->GetTraces() )
1342 {
1343 if( trace != activeTrace && trace->HasCursor( id ) )
1344 {
1345 plotTab->DisableCursor( trace, id );
1346 OnModify();
1347 }
1348 }
1349
1350 // Update cursor checkboxes (which are really radio buttons)
1352 }
1353}
1354
1355
1357{
1358 if( m_SuppressGridEvents > 0 )
1359 return;
1360
1361 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1362
1363 if( !plotTab )
1364 return;
1365
1366 int row = aEvent.GetRow();
1367 int col = aEvent.GetCol();
1368 wxString text = m_cursorsGrid->GetCellValue( row, col );
1369 wxString cursorName = m_cursorsGrid->GetCellValue( row, COL_CURSOR_NAME );
1370
1371 double value = SPICE_VALUE( text ).ToDouble();
1372
1373 if( col == COL_CURSOR_X )
1374 {
1375 CURSOR* cursor1 = nullptr;
1376 CURSOR* cursor2 = nullptr;
1377
1378 std::vector<CURSOR*> cursorsVec;
1379 cursorsVec.clear();
1380
1381 for( const auto& [name, trace] : plotTab->GetTraces() )
1382 {
1383 if( CURSOR* cursor = trace->GetCursor( 1 ) )
1384 cursor1 = cursor;
1385
1386 if( CURSOR* cursor = trace->GetCursor( 2 ) )
1387 cursor2 = cursor;
1388
1389 int tmp = 3;
1390
1391 if( !cursor1 )
1392 tmp--;
1393 if( !cursor2 )
1394 tmp--;
1395
1396 for( int i = tmp; i < m_customCursorsCnt; i++ )
1397 {
1398 if( CURSOR* cursor = trace->GetCursor( i ) )
1399 {
1400 cursorsVec.emplace_back( cursor );
1401
1402 if( cursorName == ( wxString( "" ) << i ) && cursor )
1403 cursor->SetCoordX( value );
1404 }
1405 }
1406 }
1407
1408 //double value = SPICE_VALUE( text ).ToDouble();
1409
1410 if( cursorName == wxS( "1" ) && cursor1 )
1411 cursor1->SetCoordX( value );
1412 else if( cursorName == wxS( "2" ) && cursor2 )
1413 cursor2->SetCoordX( value );
1414 else if( cursorName == _( "Diff" ) && cursor1 && cursor2 )
1415 cursor2->SetCoordX( cursor1->GetCoords().x + value );
1416
1418 OnModify();
1419 }
1420 else
1421 {
1422 wxFAIL_MSG( wxT( "All other columns are supposed to be read-only!" ) );
1423 }
1424}
1425
1426
1428{
1430 result.FromString( m_measurementsGrid->GetCellValue( aRow, COL_MEASUREMENT_FORMAT ) );
1431 return result;
1432}
1433
1434
1436{
1437 m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_FORMAT, aFormat.ToString() );
1438}
1439
1440
1442{
1443 if( aRow < ( m_measurementsGrid->GetNumberRows() - 1 ) )
1444 m_measurementsGrid->DeleteRows( aRow, 1 );
1445}
1446
1447
1449{
1450 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1451
1452 if( !plotTab )
1453 return;
1454
1455 int row = aEvent.GetRow();
1456 int col = aEvent.GetCol();
1457
1458 if( col == COL_MEASUREMENT )
1459 {
1460 UpdateMeasurement( row );
1462 OnModify();
1463 }
1464 else
1465 {
1466 wxFAIL_MSG( wxT( "All other columns are supposed to be read-only!" ) );
1467 }
1468
1469 // Always leave a single empty row for type-in
1470
1471 int rowCount = static_cast<int>( m_measurementsGrid->GetNumberRows() );
1472 int emptyRows = 0;
1473
1474 for( row = rowCount - 1; row >= 0; row-- )
1475 {
1476 if( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1477 emptyRows++;
1478 else
1479 break;
1480 }
1481
1482 if( emptyRows > 1 )
1483 {
1484 int killRows = emptyRows - 1;
1485 m_measurementsGrid->DeleteRows( rowCount - killRows, killRows );
1486 }
1487 else if( emptyRows == 0 )
1488 {
1489 m_measurementsGrid->AppendRows( 1 );
1490 }
1491}
1492
1493
1494void SIMULATOR_FRAME_UI::OnUpdateUI( wxUpdateUIEvent& event )
1495{
1496 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
1497 {
1498 if( plotTab->GetLegendPosition() != plotTab->m_LastLegendPosition )
1499 {
1500 plotTab->m_LastLegendPosition = plotTab->GetLegendPosition();
1501 OnModify();
1502 }
1503 }
1504}
1505
1506
1522{
1523 static wxRegEx measureParamsRegEx( wxT( "^"
1524 " *"
1525 "([a-zA-Z_]+)"
1526 " +"
1527 "([a-zA-Z]*)\\(([^\\)]+)\\)" ) );
1528
1529 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1530
1531 if( !plotTab )
1532 return;
1533
1534 wxString text = m_measurementsGrid->GetCellValue( aRow, COL_MEASUREMENT );
1535
1536 if( text.IsEmpty() )
1537 {
1538 m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_VALUE, wxEmptyString );
1539 return;
1540 }
1541
1542 wxString simType = simulator()->TypeToName( plotTab->GetSimType(), true );
1543 wxString resultName = wxString::Format( wxS( "meas_result_%u" ), aRow );
1544 wxString result = wxS( "?" );
1545
1546 if( measureParamsRegEx.Matches( text ) )
1547 {
1548 wxString func = measureParamsRegEx.GetMatch( text, 1 ).Upper();
1549 wxString signalType = measureParamsRegEx.GetMatch( text, 2 ).Upper();
1550 wxString deviceName = measureParamsRegEx.GetMatch( text, 3 );
1551 wxString units;
1553
1554 if( signalType.EndsWith( wxS( "DB" ) ) )
1555 {
1556 units = wxS( "dB" );
1557 }
1558 else if( signalType.StartsWith( 'I' ) )
1559 {
1560 units = wxS( "A" );
1561 }
1562 else if( signalType.StartsWith( 'P' ) )
1563 {
1564 units = wxS( "W" );
1565 // Our syntax is different from ngspice for power signals
1566 text = func + " " + deviceName + ":power";
1567 }
1568 else
1569 {
1570 units = wxS( "V" );
1571 }
1572
1573 if( func.EndsWith( wxS( "_AT" ) ) )
1574 {
1575 if( plotTab->GetSimType() == ST_AC || plotTab->GetSimType() == ST_SP )
1576 units = wxS( "Hz" );
1577 else
1578 units = wxS( "s" );
1579 }
1580 else if( func.StartsWith( wxS( "INTEG" ) ) )
1581 {
1582 switch( plotTab->GetSimType() )
1583 {
1584 case ST_TRAN:
1585 if ( signalType.StartsWith( 'P' ) )
1586 units = wxS( "J" );
1587 else
1588 units += wxS( ".s" );
1589
1590 break;
1591
1592 case ST_AC:
1593 case ST_SP:
1594 case ST_DISTO:
1595 case ST_NOISE:
1596 case ST_FFT:
1597 case ST_SENS: // If there is a vector, it is frequency
1598 units += wxS( "·Hz" );
1599 break;
1600
1601 case ST_DC: // Could be a lot of things : V, A, deg C, ohm, ...
1602 case ST_OP: // There is no vector for integration
1603 case ST_PZ: // There is no vector for integration
1604 case ST_TF: // There is no vector for integration
1605 default:
1606 units += wxS( "·?" );
1607 break;
1608 }
1609 }
1610
1611 fmt.UpdateUnits( units );
1612 SetMeasureFormat( aRow, fmt );
1613
1615 }
1616
1617 if( m_simulatorFrame->SimFinished() )
1618 {
1619 wxString cmd = wxString::Format( wxS( "meas %s %s %s" ), simType, resultName, text );
1620 simulator()->Command( "echo " + cmd.ToStdString() );
1621 simulator()->Command( cmd.ToStdString() );
1622
1623 std::vector<double> resultVec = simulator()->GetGainVector( resultName.ToStdString() );
1624
1625 if( resultVec.size() > 0 )
1626 result = SPICE_VALUE( resultVec[0] ).ToString( GetMeasureFormat( aRow ) );
1627 }
1628
1629 m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_VALUE, result );
1630}
1631
1632
1633void SIMULATOR_FRAME_UI::AddTuner( const SCH_SHEET_PATH& aSheetPath, SCH_SYMBOL* aSymbol )
1634{
1635 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1636
1637 if( !plotTab )
1638 {
1639 DisplayErrorMessage( nullptr, _( "The current analysis must have a plot in order to tune "
1640 "the value of a passive R, L, C model or voltage or "
1641 "current source." ) );
1642 return;
1643 }
1644
1645 wxString ref = aSymbol->GetRef( &aSheetPath );
1646
1647 // Do not add multiple instances for the same component.
1648 for( TUNER_SLIDER* tuner : m_tuners )
1649 {
1650 if( tuner->GetSymbolRef() == ref )
1651 return;
1652 }
1653
1654 if( [[maybe_unused]] const SPICE_ITEM* item = GetExporter()->FindItem( ref ) )
1655 {
1656 try
1657 {
1658 TUNER_SLIDER* tuner = new TUNER_SLIDER( this, m_panelTuners, aSheetPath, aSymbol );
1659 m_sizerTuners->Add( tuner );
1660 m_tuners.push_back( tuner );
1661 m_panelTuners->Layout();
1662 OnModify();
1663 }
1664 catch( const KI_PARAM_ERROR& e )
1665 {
1666 DisplayErrorMessage( nullptr, e.What() );
1667 }
1668 }
1669}
1670
1671
1672void SIMULATOR_FRAME_UI::UpdateTunerValue( const SCH_SHEET_PATH& aSheetPath, const KIID& aSymbol,
1673 const wxString& aRef, const wxString& aValue )
1674{
1675 SCH_ITEM* item = aSheetPath.ResolveItem( aSymbol );
1676 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( item );
1677
1678 if( !symbol )
1679 {
1680 DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( " " )
1681 + wxString::Format( _( "%s not found" ), aRef ) );
1682 return;
1683 }
1684
1685 NULL_REPORTER devnull;
1686 SIM_LIB_MGR mgr( &m_schematicFrame->Prj() );
1687
1688 std::vector<EMBEDDED_FILES*> embeddedFilesStack;
1689 embeddedFilesStack.push_back( m_schematicFrame->Schematic().GetEmbeddedFiles() );
1690
1691 if( EMBEDDED_FILES* symbolEmbeddedFiles = symbol->GetEmbeddedFiles() )
1692 {
1693 embeddedFilesStack.push_back( symbolEmbeddedFiles );
1694 symbol->GetLibSymbolRef()->AppendParentEmbeddedFiles( embeddedFilesStack );
1695 }
1696
1697 mgr.SetFilesStack( std::move( embeddedFilesStack ) );
1698
1699 SIM_MODEL& model = mgr.CreateModel( &aSheetPath, *symbol, true, 0, devnull ).model;
1700
1701 const SIM_MODEL::PARAM* tunerParam = model.GetTunerParam();
1702
1703 if( !tunerParam )
1704 {
1705 DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( " " )
1706 + wxString::Format( _( "%s is not tunable" ), aRef ) );
1707 return;
1708 }
1709
1710 model.SetParamValue( tunerParam->info.name, std::string( aValue.ToUTF8() ) );
1711 model.WriteFields( symbol->GetFields() );
1712
1713 m_schematicFrame->UpdateItem( symbol, false, true );
1714 m_schematicFrame->OnModify();
1715}
1716
1717
1719{
1720 m_tuners.remove( aTuner );
1721
1722 if( std::find( m_multiRunState.tuners.begin(), m_multiRunState.tuners.end(), aTuner )
1723 != m_multiRunState.tuners.end() )
1724 {
1725 clearMultiRunState( true );
1726 }
1727
1728 m_tunerOverrides.erase( aTuner );
1729
1730 aTuner->Destroy();
1731 m_panelTuners->Layout();
1732 OnModify();
1733}
1734
1735
1736void SIMULATOR_FRAME_UI::AddMeasurement( const wxString& aCmd )
1737{
1738 // -1 because the last one is for user input
1739 for( int i = 0; i < m_measurementsGrid->GetNumberRows(); i++ )
1740 {
1741 if ( m_measurementsGrid->GetCellValue( i, COL_MEASUREMENT ) == aCmd )
1742 return; // Don't create duplicates
1743 }
1744
1745 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
1746
1747 if( !plotTab )
1748 return;
1749
1750 int row;
1751
1752 for( row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
1753 {
1754 if( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1755 break;
1756 }
1757
1758 if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1759 {
1760 m_measurementsGrid->AppendRows( 1 );
1761 row = m_measurementsGrid->GetNumberRows() - 1;
1762 }
1763
1764 m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT, aCmd );
1765
1766 UpdateMeasurement( row );
1768 OnModify();
1769
1770 // Always leave at least one empty row for type-in:
1771 row = m_measurementsGrid->GetNumberRows() - 1;
1772
1773 if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
1774 m_measurementsGrid->AppendRows( 1 );
1775}
1776
1777
1778void SIMULATOR_FRAME_UI::DoFourier( const wxString& aSignal, const wxString& aFundamental )
1779{
1780 wxString cmd = wxString::Format( wxS( "fourier %s %s" ),
1781 SPICE_VALUE( aFundamental ).ToSpiceString(),
1782 aSignal );
1783
1784 simulator()->Command( cmd.ToStdString() );
1785}
1786
1787
1789{
1790 return circuitModel().get();
1791}
1792
1793
1794void SIMULATOR_FRAME_UI::AddTrace( const wxString& aName, SIM_TRACE_TYPE aType )
1795{
1796 if( !GetCurrentSimTab() )
1797 {
1798 m_simConsole->AppendText( _( "Error: no current simulation.\n" ) );
1799 m_simConsole->SetInsertionPointEnd();
1800 return;
1801 }
1802
1803 SIM_TYPE simType = SPICE_CIRCUIT_MODEL::CommandToSimType( GetCurrentSimTab()->GetSimCommand() );
1804
1805 if( simType == ST_UNKNOWN )
1806 {
1807 m_simConsole->AppendText( _( "Error: simulation type not defined.\n" ) );
1808 m_simConsole->SetInsertionPointEnd();
1809 return;
1810 }
1811 else if( !SIM_TAB::IsPlottable( simType ) )
1812 {
1813 m_simConsole->AppendText( _( "Error: simulation type doesn't support plotting.\n" ) );
1814 m_simConsole->SetInsertionPointEnd();
1815 return;
1816 }
1817
1818 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
1819 {
1820 if( simType == ST_AC )
1821 {
1822 updateTrace( aName, aType | SPT_AC_GAIN, plotTab );
1823 updateTrace( aName, aType | SPT_AC_PHASE, plotTab );
1824 }
1825 else if( simType == ST_SP )
1826 {
1827 updateTrace( aName, aType | SPT_AC_GAIN, plotTab );
1828 updateTrace( aName, aType | SPT_AC_PHASE, plotTab );
1829 }
1830 else
1831 {
1832 updateTrace( aName, aType, plotTab );
1833 }
1834
1835 plotTab->GetPlotWin()->UpdateAll();
1836 }
1837
1839 OnModify();
1840}
1841
1842
1843void SIMULATOR_FRAME_UI::SetUserDefinedSignals( const std::map<int, wxString>& aNewSignals )
1844{
1845 for( size_t ii = 0; ii < m_plotNotebook->GetPageCount(); ++ii )
1846 {
1847 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( m_plotNotebook->GetPage( ii ) );
1848
1849 if( !plotTab )
1850 continue;
1851
1852 for( const auto& [ id, existingSignal ] : m_userDefinedSignals )
1853 {
1854 int traceType = SPT_UNKNOWN;
1855 wxString vectorName = vectorNameFromSignalName( plotTab, existingSignal, &traceType );
1856
1857 if( aNewSignals.count( id ) == 0 )
1858 {
1859 if( plotTab->GetSimType() == ST_AC )
1860 {
1861 for( int subType : { SPT_AC_GAIN, SPT_AC_PHASE } )
1862 plotTab->DeleteTrace( vectorName, traceType | subType );
1863 }
1864 else if( plotTab->GetSimType() == ST_SP )
1865 {
1866 for( int subType : { SPT_SP_AMP, SPT_AC_PHASE } )
1867 plotTab->DeleteTrace( vectorName, traceType | subType );
1868 }
1869 else
1870 {
1871 plotTab->DeleteTrace( vectorName, traceType );
1872 }
1873 }
1874 else
1875 {
1876 if( plotTab->GetSimType() == ST_AC )
1877 {
1878 for( int subType : { SPT_AC_GAIN, SPT_AC_PHASE } )
1879 {
1880 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType | subType ) )
1881 trace->SetName( aNewSignals.at( id ) );
1882 }
1883 }
1884 else if( plotTab->GetSimType() == ST_SP )
1885 {
1886 for( int subType : { SPT_SP_AMP, SPT_AC_PHASE } )
1887 {
1888 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType | subType ) )
1889 trace->SetName( aNewSignals.at( id ) );
1890 }
1891 }
1892 else
1893 {
1894 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
1895 trace->SetName( aNewSignals.at( id ) );
1896 }
1897 }
1898 }
1899 }
1900
1901 m_userDefinedSignals = aNewSignals;
1902
1903 if( m_simulatorFrame->SimFinished() )
1905
1907 rebuildSignalsGrid( m_filter->GetValue() );
1910 OnModify();
1911}
1912
1913
1914void SIMULATOR_FRAME_UI::updateTrace( const wxString& aVectorName, int aTraceType, SIM_PLOT_TAB* aPlotTab,
1915 std::vector<double>* aDataX, bool aClearData )
1916{
1917 if( !m_simulatorFrame->SimFinished() && !simulator()->IsRunning())
1918 {
1919 aPlotTab->GetOrAddTrace( aVectorName, aTraceType );
1920 return;
1921 }
1922
1924
1925 aTraceType &= aTraceType & SPT_Y_AXIS_MASK;
1926 aTraceType |= getXAxisType( simType );
1927
1928 wxString simVectorName = aVectorName;
1929
1930 if( aTraceType & SPT_POWER )
1931 simVectorName = simVectorName.AfterFirst( '(' ).BeforeLast( ')' ) + wxS( ":power" );
1932
1933 if( !SIM_TAB::IsPlottable( simType ) )
1934 {
1935 // There is no plot to be shown
1936 simulator()->Command( wxString::Format( wxT( "print %s" ), aVectorName ).ToStdString() );
1937
1938 return;
1939 }
1940
1941 std::vector<double> data_x;
1942 std::vector<double> data_y;
1943
1944 if( !aDataX || aClearData )
1945 aDataX = &data_x;
1946
1947 // First, handle the x axis
1948 if( aDataX->empty() && !aClearData )
1949 {
1950 wxString xAxisName( simulator()->GetXAxis( simType ) );
1951
1952 if( xAxisName.IsEmpty() )
1953 return;
1954
1955 *aDataX = simulator()->GetGainVector( (const char*) xAxisName.c_str() );
1956 }
1957
1958 unsigned int size = aDataX->size();
1959
1960 switch( simType )
1961 {
1962 case ST_AC:
1963 if( aTraceType & SPT_AC_GAIN )
1964 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
1965 else if( aTraceType & SPT_AC_PHASE )
1966 data_y = simulator()->GetPhaseVector( (const char*) simVectorName.c_str(), size );
1967 else
1968 wxFAIL_MSG( wxT( "Plot type missing AC_PHASE or AC_MAG bit" ) );
1969
1970 break;
1971 case ST_SP:
1972 if( aTraceType & SPT_SP_AMP )
1973 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
1974 else if( aTraceType & SPT_AC_PHASE )
1975 data_y = simulator()->GetPhaseVector( (const char*) simVectorName.c_str(), size );
1976 else
1977 wxFAIL_MSG( wxT( "Plot type missing AC_PHASE or SPT_SP_AMP bit" ) );
1978
1979 break;
1980
1981 case ST_DC:
1982 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), -1 );
1983 break;
1984
1985 case ST_NOISE:
1986 case ST_TRAN:
1987 case ST_FFT:
1988 data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
1989 break;
1990
1991 default:
1992 wxFAIL_MSG( wxT( "Unhandled plot type" ) );
1993 }
1994
1995 SPICE_DC_PARAMS source1, source2;
1996 int sweepCount = 1;
1997 size_t sweepSize = std::numeric_limits<size_t>::max();
1998
1999 if( simType == ST_DC
2000 && circuitModel()->ParseDCCommand( aPlotTab->GetSimCommand(), &source1, &source2 )
2001 && !source2.m_source.IsEmpty() )
2002 {
2003 SPICE_VALUE v = ( source2.m_vend - source2.m_vstart ) / source2.m_vincrement;
2004
2005 sweepCount = KiROUND( v.ToDouble() ) + 1;
2006 sweepSize = aDataX->size() / sweepCount;
2007 }
2008
2009 if( m_multiRunState.storePending )
2010 recordMultiRunData( aVectorName, aTraceType, *aDataX, data_y );
2011
2012 if( hasMultiRunTrace( aVectorName, aTraceType ) )
2013 {
2014 const std::string key = multiRunTraceKey( aVectorName, aTraceType );
2015 const auto traceIt = m_multiRunState.traces.find( key );
2016
2017 if( traceIt != m_multiRunState.traces.end() )
2018 {
2019 const MULTI_RUN_TRACE& traceData = traceIt->second;
2020
2021 if( !traceData.xValues.empty() && !traceData.yValues.empty() )
2022 {
2023 size_t sweepSizeMulti = traceData.xValues.size();
2024 size_t runCount = traceData.yValues.size();
2025
2026 if( sweepSizeMulti > 0 && runCount > 0 )
2027 {
2028 std::vector<double> combinedX;
2029 std::vector<double> combinedY;
2030
2031 combinedX.reserve( sweepSizeMulti * runCount );
2032 combinedY.reserve( sweepSizeMulti * runCount );
2033
2034 for( const std::vector<double>& runY : traceData.yValues )
2035 {
2036 if( runY.size() != sweepSizeMulti )
2037 continue;
2038
2039 combinedX.insert( combinedX.end(), traceData.xValues.begin(), traceData.xValues.end() );
2040 combinedY.insert( combinedY.end(), runY.begin(), runY.end() );
2041 }
2042
2043 if( TRACE* trace = aPlotTab->GetOrAddTrace( aVectorName, aTraceType ) )
2044 {
2045 if( combinedY.size() >= combinedX.size() && sweepSizeMulti > 0 )
2046 {
2047 int sweepCountCombined = combinedX.empty() ? 0 : static_cast<int>( combinedY.size() / sweepSizeMulti );
2048
2049 if( sweepCountCombined > 0 )
2050 {
2051 // Generate labels for each run based on tuner values
2052 std::vector<wxString> labels;
2053 labels.reserve( sweepCountCombined );
2054
2055 for( int i = 0; i < sweepCountCombined && i < (int)m_multiRunState.steps.size(); ++i )
2056 {
2057 const MULTI_RUN_STEP& step = m_multiRunState.steps[i];
2058 wxString label;
2059
2060 for( auto it = step.overrides.begin(); it != step.overrides.end(); ++it )
2061 {
2062 if( it != step.overrides.begin() )
2063 label += wxS( ", " );
2064
2065 const TUNER_SLIDER* tuner = it->first;
2066 double value = it->second;
2067
2068 SPICE_VALUE spiceVal( value );
2069 label += tuner->GetSymbolRef() + wxS( "=" ) + spiceVal.ToSpiceString();
2070 }
2071
2072 labels.push_back( label );
2073 }
2074
2075 aPlotTab->SetTraceData( trace, combinedX, combinedY, sweepCountCombined,
2076 sweepSizeMulti, true, labels );
2077 }
2078 }
2079 }
2080
2081 return;
2082 }
2083 }
2084 }
2085 }
2086
2087 if( TRACE* trace = aPlotTab->GetOrAddTrace( aVectorName, aTraceType ) )
2088 {
2089 if( data_y.size() >= size )
2090 aPlotTab->SetTraceData( trace, *aDataX, data_y, sweepCount, sweepSize );
2091 }
2092}
2093
2094
2095// TODO make sure where to instantiate and how to style correct
2096// Better ask someone..
2098 SIGNALS_GRID_COLUMNS, int, int );
2099
2100template <typename T, typename U, typename R>
2101void 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
2102{
2103 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
2104 wxString signalName = m_signalsGrid->GetCellValue( r, COL_SIGNAL_NAME );
2105 int traceType = SPT_UNKNOWN;
2106 wxString vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
2107
2108 wxGridCellAttrPtr attr = m_signalsGrid->GetOrCreateCellAttrPtr( r, static_cast<int>( t ) );
2109
2110 if( TRACE* trace = plotTab ? plotTab->GetTrace( vectorName, traceType ) : nullptr )
2111 {
2112 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
2113
2115 {
2116 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
2117 }
2118
2119 if constexpr ( std::is_enum<T>::value )
2120 {
2122 {
2123 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), wxS( "1" ) );
2124 }
2126 {
2127 if( !attr->HasRenderer() )
2128 attr->SetRenderer( new GRID_CELL_COLOR_RENDERER( this ) );
2129
2130 if( !attr->HasEditor() )
2131 attr->SetEditor( new GRID_CELL_COLOR_SELECTOR( this, m_signalsGrid ) );
2132
2133 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
2134 attr->SetReadOnly( false );
2135
2136 KIGFX::COLOR4D color( trace->GetPen().GetColour() );
2137 m_signalsGrid->SetCellValue( r, COL_SIGNAL_COLOR, color.ToCSSString() );
2138 }
2142 {
2143 if( !attr->HasRenderer() )
2144 attr->SetRenderer( new wxGridCellBoolRenderer() );
2145
2146 if( u > 0 && trace->HasCursor( u ) )
2147 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), wxS( "1" ) );
2148 else
2149 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), wxEmptyString );
2150 }
2151 }
2152 }
2153 else
2154 {
2155 if constexpr ( std::is_enum<T>::value )
2156 {
2158 {
2159 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), wxEmptyString );
2160 }
2165 {
2166 attr->SetEditor( nullptr );
2167 attr->SetRenderer( nullptr );
2168 attr->SetReadOnly();
2169 m_signalsGrid->SetCellValue( r, static_cast<int>( t ), wxEmptyString );
2170 }
2171 }
2172 }
2173}
2174
2175
2177{
2178 for( int row = 0; row < m_signalsGrid->GetNumberRows(); ++row )
2179 {
2184
2185 if( ( m_signalsGrid->GetNumberCols() - 1 ) > COL_CURSOR_2 )
2186 {
2187 for( int i = 3; i < m_customCursorsCnt; i++ )
2188 {
2189 int tm = i + 2;
2190 signalsGridCursorUpdate( static_cast<SIGNALS_GRID_COLUMNS>( tm ), i, row );
2191 }
2192 }
2193 }
2194 m_signalsGrid->Refresh();
2195}
2196
2197
2199{
2200 auto quoteNetNames =
2201 [&]( wxString aExpression ) -> wxString
2202 {
2203 std::vector<bool> mask( aExpression.length(), false );
2204
2205 auto isNetnameChar =
2206 []( wxUniChar aChar ) -> bool
2207 {
2208 wxUint32 value = aChar.GetValue();
2209
2210 if( ( value >= '0' && value <= '9' ) || ( value >= 'A' && value <= 'Z' )
2211 || ( value >= 'a' && value <= 'z' ) )
2212 {
2213 return true;
2214 }
2215
2216 switch( value )
2217 {
2218 case '_':
2219 case '/':
2220 case '+':
2221 case '-':
2222 case '~':
2223 case '.':
2224 return true;
2225 default:
2226 break;
2227 }
2228
2229 return false;
2230 };
2231
2232 for( const wxString& netname : m_netnames )
2233 {
2234 size_t pos = aExpression.find( netname );
2235
2236 while( pos != wxString::npos )
2237 {
2238 for( size_t i = 0; i < netname.length(); ++i )
2239 mask[pos + i] = true; // Mark the positions of the netname
2240
2241 pos = aExpression.find( netname, pos + 1 ); // Find the next occurrence
2242 }
2243 }
2244
2245 for( size_t i = 0; i < aExpression.length(); ++i )
2246 {
2247 if( !mask[i] || ( i > 0 && mask[i - 1] ) )
2248 continue;
2249
2250 size_t j = i + 1;
2251
2252 while( j < aExpression.length() )
2253 {
2254 if( mask[j] )
2255 {
2256 ++j;
2257 continue;
2258 }
2259
2260 if( isNetnameChar( aExpression[j] ) )
2261 {
2262 mask[j] = true;
2263 ++j;
2264 }
2265 else
2266 {
2267 break;
2268 }
2269 }
2270 }
2271
2272 wxString quotedNetnames = "";
2273 bool startQuote = true;
2274
2275 // put quotes around all the positions that were found above
2276 for( size_t i = 0; i < aExpression.length(); i++ )
2277 {
2278 if( mask[i] && startQuote )
2279 {
2280 quotedNetnames = quotedNetnames + "\"";
2281 startQuote = false;
2282 }
2283 else if( !mask[i] && !startQuote )
2284 {
2285 quotedNetnames = quotedNetnames + "\"";
2286 startQuote = true;
2287 }
2288
2289 wxString ch = aExpression[i];
2290 quotedNetnames = quotedNetnames + ch;
2291 }
2292
2293 if( !startQuote )
2294 quotedNetnames = quotedNetnames + "\"";
2295
2296 return quotedNetnames;
2297 };
2298
2299 for( const auto& [ id, signal ] : m_userDefinedSignals )
2300 {
2301 constexpr const char* cmd = "let user{} = {}";
2302
2303 simulator()->Command( "echo " + fmt::format( cmd, id, signal.ToStdString() ) );
2304 simulator()->Command( fmt::format( cmd, id, quoteNetNames( signal ).ToStdString() ) );
2305 }
2306}
2307
2308
2310{
2311 WX_STRING_REPORTER reporter;
2312
2313 for( const TUNER_SLIDER* tuner : m_tuners )
2314 {
2315 SCH_SHEET_PATH sheetPath;
2316 wxString ref = tuner->GetSymbolRef();
2317 KIID symbolId = tuner->GetSymbol( &sheetPath );
2318 SCH_ITEM* schItem = sheetPath.ResolveItem( symbolId );
2319 SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( schItem );
2320
2321 if( !symbol )
2322 {
2323 reporter.Report( wxString::Format( _( "%s not found" ), ref ) );
2324 continue;
2325 }
2326
2327 const SPICE_ITEM* item = GetExporter()->FindItem( tuner->GetSymbolRef() );
2328
2329 if( !item || !item->model->GetTunerParam() )
2330 {
2331 reporter.Report( wxString::Format( _( "%s is not tunable" ), ref ) );
2332 continue;
2333 }
2334
2335 double floatVal;
2336
2337 auto overrideIt = m_tunerOverrides.find( tuner );
2338
2339 if( overrideIt != m_tunerOverrides.end() )
2340 floatVal = overrideIt->second;
2341 else
2342 floatVal = tuner->GetValue().ToDouble();
2343
2344 simulator()->Command( item->model->SpiceGenerator().TunerCommand( *item, floatVal ) );
2345 }
2346
2347 if( reporter.HasMessage() )
2348 DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( "\n" ) + reporter.GetMessages() );
2349}
2350
2351bool SIMULATOR_FRAME_UI::LoadWorkbook( const wxString& aPath )
2352{
2353 wxTextFile file( aPath );
2354
2355 if( !file.Open() )
2356 return false;
2357
2358 wxString firstLine = file.GetFirstLine();
2359 long dummy;
2360 bool legacy = firstLine.StartsWith( wxT( "version " ) ) || firstLine.ToLong( &dummy );
2361
2362 file.Close();
2363
2364 m_plotNotebook->DeleteAllPages();
2365 m_userDefinedSignals.clear();
2366
2367 if( legacy )
2368 {
2369 if( !loadLegacyWorkbook( aPath ) )
2370 return false;
2371 }
2372 else
2373 {
2374 if( !loadJsonWorkbook( aPath ) )
2375 return false;
2376 }
2377
2379
2380 rebuildSignalsGrid( m_filter->GetValue() );
2384
2385 wxFileName filename( aPath );
2386 filename.MakeRelativeTo( m_schematicFrame->Prj().GetProjectPath() );
2387
2388 // Remember the loaded workbook filename.
2389 simulator()->Settings()->SetWorkbookFilename( filename.GetFullPath() );
2390
2391 return true;
2392}
2393
2394
2395bool SIMULATOR_FRAME_UI::loadJsonWorkbook( const wxString& aPath )
2396{
2397 wxFFileInputStream fp( aPath, wxT( "rt" ) );
2398 wxStdInputStream fstream( fp );
2399
2400 if( !fp.IsOk() )
2401 return false;
2402
2403 try
2404 {
2405 nlohmann::json js = nlohmann::json::parse( fstream, nullptr, true, true );
2406
2407 std::map<SIM_PLOT_TAB*, nlohmann::json> traceInfo;
2408
2409 for( const nlohmann::json& tab_js : js[ "tabs" ] )
2410 {
2411 wxString simCommand;
2414
2415 for( const nlohmann::json& cmd : tab_js[ "commands" ] )
2416 {
2417 if( cmd == ".kicad adjustpaths" )
2419 else if( cmd == ".save all" )
2421 else if( cmd == ".probe alli" )
2423 else if( cmd == ".probe allp" )
2425 else if( cmd == ".kicad esavenone" )
2426 simOptions &= ~NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_EVENTS;
2427 else
2428 simCommand += wxString( cmd.get<wxString>() ).Trim();
2429 }
2430
2431 SIM_TAB* simTab = NewSimTab( simCommand );
2432 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab );
2433
2434 simTab->SetSimOptions( simOptions );
2435
2436 if( plotTab )
2437 {
2438 if( tab_js.contains( "traces" ) )
2439 traceInfo[plotTab] = tab_js[ "traces" ];
2440
2441 if( tab_js.contains( "measurements" ) )
2442 {
2443 for( const nlohmann::json& m_js : tab_js[ "measurements" ] )
2444 plotTab->Measurements().emplace_back( m_js[ "expr" ], m_js[ "format" ] );
2445 }
2446
2447 plotTab->SetDottedSecondary( tab_js[ "dottedSecondary" ] );
2448 plotTab->ShowGrid( tab_js[ "showGrid" ] );
2449
2450 if( tab_js.contains( "fixedY1scale" ) )
2451 {
2452 const nlohmann::json& scale_js = tab_js[ "fixedY1scale" ];
2453 plotTab->SetY1Scale( true, scale_js[ "min" ], scale_js[ "max" ] );
2454 plotTab->GetPlotWin()->LockY( true );
2455 }
2456
2457 if( tab_js.contains( "fixedY2scale" ) )
2458 {
2459 const nlohmann::json& scale_js = tab_js[ "fixedY2scale" ];
2460 plotTab->SetY2Scale( true, scale_js[ "min" ], scale_js[ "max" ] );
2461 plotTab->GetPlotWin()->LockY( true );
2462 }
2463
2464 if( tab_js.contains( "fixedY3scale" ) )
2465 {
2466 plotTab->EnsureThirdYAxisExists();
2467 const nlohmann::json& scale_js = tab_js[ "fixedY3scale" ];
2468 plotTab->SetY3Scale( true, scale_js[ "min" ], scale_js[ "max" ] );
2469 plotTab->GetPlotWin()->LockY( true );
2470 }
2471
2472 if( tab_js.contains( "legend" ) )
2473 {
2474 const nlohmann::json& legend_js = tab_js[ "legend" ];
2475 plotTab->SetLegendPosition( wxPoint( legend_js[ "x" ], legend_js[ "y" ] ) );
2476 plotTab->ShowLegend( true );
2477 }
2478
2479 if( tab_js.contains( "margins" ) )
2480 {
2481 const nlohmann::json& margins_js = tab_js[ "margins" ];
2482 plotTab->GetPlotWin()->SetMargins( margins_js[ "top" ],
2483 margins_js[ "right" ],
2484 margins_js[ "bottom" ],
2485 margins_js[ "left" ] );
2486 }
2487 }
2488 }
2489
2490 int ii = 0;
2491
2492 if( js.contains( "user_defined_signals" ) )
2493 {
2494 for( const nlohmann::json& signal_js : js[ "user_defined_signals" ] )
2495 m_userDefinedSignals[ii++] = wxString( signal_js.get<wxString>() );
2496 }
2497
2498 if( SIM_TAB* simTab = GetCurrentSimTab() )
2499 {
2500 m_simulatorFrame->LoadSimulator( simTab->GetSimCommand(), simTab->GetSimOptions() );
2501
2502 if( SIM_TAB* firstTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( 0 ) ) )
2503 firstTab->SetLastSchTextSimCommand( js["last_sch_text_sim_command"] );
2504 }
2505
2506 int tempCustomCursorsCnt = 0;
2507
2508 if( js.contains( "custom_cursors" ) )
2509 tempCustomCursorsCnt = js["custom_cursors"];
2510 else
2511 tempCustomCursorsCnt = 2; // Kind of virtual, for the initial loading of the new setting
2512
2513 if( ( tempCustomCursorsCnt > m_customCursorsCnt ) && m_customCursorsCnt > 2 )
2514 tempCustomCursorsCnt = 2 * tempCustomCursorsCnt - m_customCursorsCnt;
2515
2516 for( int yy = 0; yy <= ( tempCustomCursorsCnt - m_customCursorsCnt ); yy++ )
2518
2519 auto addCursor =
2520 [=,this]( SIM_PLOT_TAB* aPlotTab, TRACE* aTrace, const wxString& aSignalName,
2521 int aCursorId, const nlohmann::json& aCursor_js )
2522 {
2523 if( aCursorId >= 1 )
2524 {
2525 CURSOR* cursor = new CURSOR( aTrace, aPlotTab );
2526
2527 cursor->SetName( aSignalName );
2528 cursor->SetCoordX( aCursor_js[ "position" ] );
2529
2530 aTrace->SetCursor( aCursorId, cursor );
2531 aPlotTab->GetPlotWin()->AddLayer( cursor );
2532 }
2533
2534 if( aCursorId == -1 )
2535 {
2536 // We are a "cursorD"
2537 m_cursorFormatsDyn[2][0].FromString( aCursor_js["x_format"] );
2538 m_cursorFormatsDyn[2][1].FromString( aCursor_js["y_format"] );
2539 }
2540 else
2541 {
2542 if( aCursorId < 3 )
2543 {
2544 m_cursorFormatsDyn[aCursorId - 1][0].FromString( aCursor_js["x_format"] );
2545 m_cursorFormatsDyn[aCursorId - 1][1].FromString( aCursor_js["y_format"] );
2546 }
2547 else
2548 {
2549 m_cursorFormatsDyn[aCursorId][0].FromString( aCursor_js["x_format"] );
2550 m_cursorFormatsDyn[aCursorId][1].FromString( aCursor_js["y_format"] );
2551 }
2552 }
2553 };
2554
2555 for( const auto& [ plotTab, traces_js ] : traceInfo )
2556 {
2557 for( const nlohmann::json& trace_js : traces_js )
2558 {
2559 wxString signalName = trace_js[ "signal" ];
2560 wxString vectorName = vectorNameFromSignalName( plotTab, signalName, nullptr );
2561 TRACE* trace = plotTab->GetOrAddTrace( vectorName, trace_js[ "trace_type" ] );
2562
2563 if( trace )
2564 {
2565 if( trace_js.contains( "cursorD" ) )
2566 addCursor( plotTab, trace, signalName, -1, trace_js[ "cursorD" ] );
2567
2568 std::vector<const char*> aVec;
2569 aVec.clear();
2570
2571 for( int i = 1; i <= tempCustomCursorsCnt; i++ )
2572 {
2573 wxString str = "cursor" + std::to_string( i );
2574 aVec.emplace_back( str.c_str() );
2575
2576 if( trace_js.contains( aVec[i - 1] ) )
2577 addCursor( plotTab, trace, signalName, i, trace_js[aVec[i - 1]] );
2578 }
2579
2580 if( trace_js.contains( "color" ) )
2581 {
2582 wxColour color;
2583 color.Set( wxString( trace_js["color"].get<wxString>() ) );
2584 trace->SetTraceColour( color );
2585 plotTab->UpdateTraceStyle( trace );
2586 }
2587 }
2588 }
2589
2590 plotTab->UpdatePlotColors();
2591 }
2592 }
2593 catch( nlohmann::json::parse_error& error )
2594 {
2595 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ), aPath, error.what() );
2596
2597 return false;
2598 }
2599 catch( nlohmann::json::type_error& error )
2600 {
2601 wxLogTrace( traceSettings, wxT( "Json type error reading %s: %s" ), aPath, error.what() );
2602
2603 return false;
2604 }
2605 catch( nlohmann::json::invalid_iterator& error )
2606 {
2607 wxLogTrace( traceSettings, wxT( "Json invalid_iterator error reading %s: %s" ), aPath, error.what() );
2608
2609 return false;
2610 }
2611 catch( nlohmann::json::out_of_range& error )
2612 {
2613 wxLogTrace( traceSettings, wxT( "Json out_of_range error reading %s: %s" ), aPath, error.what() );
2614
2615 return false;
2616 }
2617 catch( ... )
2618 {
2619 wxLogTrace( traceSettings, wxT( "Error reading %s" ), aPath );
2620 return false;
2621 }
2622
2623 return true;
2624}
2625
2626void SIMULATOR_FRAME_UI::SaveCursorToWorkbook( nlohmann::json& aTraceJs, TRACE* aTrace, int aCursorId )
2627{
2628 int cursorIdAfterD = aCursorId;
2629
2630 if( aCursorId > 3 )
2631 cursorIdAfterD = cursorIdAfterD - 1;
2632
2633
2634 if( CURSOR* cursor = aTrace->GetCursor( aCursorId ) )
2635 {
2636 aTraceJs["cursor" + wxString( "" ) << aCursorId] =
2637 nlohmann::json( { { "position", cursor->GetCoords().x },
2638 { "x_format", m_cursorFormatsDyn[cursorIdAfterD][0].ToString() },
2639 { "y_format", m_cursorFormatsDyn[cursorIdAfterD][1].ToString() } } );
2640 }
2641
2642 if( cursorIdAfterD < 3 && ( aTrace->GetCursor( 1 ) || aTrace->GetCursor( 2 ) ) )
2643 {
2644 aTraceJs["cursorD"] =
2645 nlohmann::json( { { "x_format", m_cursorFormatsDyn[2][0].ToString() },
2646 { "y_format", m_cursorFormatsDyn[2][1].ToString() } } );
2647 }
2648}
2649
2650
2651bool SIMULATOR_FRAME_UI::SaveWorkbook( const wxString& aPath )
2652{
2654
2655 wxFileName filename = aPath;
2656 filename.SetExt( FILEEXT::WorkbookFileExtension );
2657
2658 wxFile file;
2659
2660 file.Create( filename.GetFullPath(), true /* overwrite */ );
2661
2662 if( !file.IsOpened() )
2663 return false;
2664
2665 nlohmann::json tabs_js = nlohmann::json::array();
2666
2667 for( size_t i = 0; i < m_plotNotebook->GetPageCount(); i++ )
2668 {
2669 SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( i ) );
2670
2671 if( !simTab )
2672 continue;
2673
2674 SIM_TYPE simType = simTab->GetSimType();
2675
2676 nlohmann::json commands_js = nlohmann::json::array();
2677
2678 commands_js.push_back( simTab->GetSimCommand() );
2679
2680 int options = simTab->GetSimOptions();
2681
2683 commands_js.push_back( ".kicad adjustpaths" );
2684
2686 commands_js.push_back( ".save all" );
2687
2689 commands_js.push_back( ".probe alli" );
2690
2692 commands_js.push_back( ".probe allp" );
2693
2695 commands_js.push_back( ".kicad esavenone" );
2696
2697 nlohmann::json tab_js = nlohmann::json(
2698 { { "analysis", SPICE_SIMULATOR::TypeToName( simType, true ) },
2699 { "commands", commands_js } } );
2700
2701 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab ) )
2702 {
2703 nlohmann::json traces_js = nlohmann::json::array();
2704
2705 auto findSignalName =
2706 [&]( const wxString& aVectorName ) -> wxString
2707 {
2708 wxString vectorName;
2709 wxString suffix;
2710
2711 if( aVectorName.EndsWith( _( " (phase)" ) ) )
2712 suffix = _( " (phase)" );
2713 else if( aVectorName.EndsWith( _( " (gain)" ) ) )
2714 suffix = _( " (gain)" );
2715
2716 vectorName = aVectorName.Left( aVectorName.Length() - suffix.Length() );
2717
2718 for( const auto& [ id, signal ] : m_userDefinedSignals )
2719 {
2720 if( vectorName == vectorNameFromSignalId( id ) )
2721 return signal + suffix;
2722 }
2723
2724 return aVectorName;
2725 };
2726
2727 for( const auto& [name, trace] : plotTab->GetTraces() )
2728 {
2729 nlohmann::json trace_js = nlohmann::json(
2730 { { "trace_type", (int) trace->GetType() },
2731 { "signal", findSignalName( trace->GetDisplayName() ) },
2732 { "color", COLOR4D( trace->GetTraceColour() ).ToCSSString() } } );
2733
2734 for( int ii = 1; ii <= m_customCursorsCnt; ii++ )
2735 SaveCursorToWorkbook( trace_js, trace, ii );
2736
2737 if( trace->GetCursor( 1 ) || trace->GetCursor( 2 ) )
2738 {
2739 trace_js["cursorD"] = nlohmann::json(
2740 { { "x_format", m_cursorFormatsDyn[2][0].ToString() },
2741 { "y_format", m_cursorFormatsDyn[2][1].ToString() } } );
2742 }
2743
2744 traces_js.push_back( trace_js );
2745 }
2746
2747 nlohmann::json measurements_js = nlohmann::json::array();
2748
2749 for( const auto& [ measurement, format ] : plotTab->Measurements() )
2750 {
2751 measurements_js.push_back( nlohmann::json( { { "expr", measurement },
2752 { "format", format } } ) );
2753 }
2754
2755 tab_js[ "traces" ] = traces_js;
2756 tab_js[ "measurements" ] = measurements_js;
2757 tab_js[ "dottedSecondary" ] = plotTab->GetDottedSecondary();
2758 tab_js[ "showGrid" ] = plotTab->IsGridShown();
2759
2760 double min, max;
2761
2762 if( plotTab->GetY1Scale( &min, &max ) )
2763 tab_js[ "fixedY1scale" ] = nlohmann::json( { { "min", min }, { "max", max } } );
2764
2765 if( plotTab->GetY2Scale( &min, &max ) )
2766 tab_js[ "fixedY2scale" ] = nlohmann::json( { { "min", min }, { "max", max } } );
2767
2768 if( plotTab->GetY3Scale( &min, &max ) )
2769 tab_js[ "fixedY3scale" ] = nlohmann::json( { { "min", min }, { "max", max } } );
2770
2771 if( plotTab->IsLegendShown() )
2772 {
2773 tab_js[ "legend" ] = nlohmann::json( { { "x", plotTab->GetLegendPosition().x },
2774 { "y", plotTab->GetLegendPosition().y } } );
2775 }
2776
2777 mpWindow* plotWin = plotTab->GetPlotWin();
2778
2779 tab_js[ "margins" ] = nlohmann::json( { { "left", plotWin->GetMarginLeft() },
2780 { "right", plotWin->GetMarginRight() },
2781 { "top", plotWin->GetMarginTop() },
2782 { "bottom", plotWin->GetMarginBottom() } } );
2783 }
2784
2785 tabs_js.push_back( tab_js );
2786 }
2787
2788 nlohmann::json userDefinedSignals_js = nlohmann::json::array();
2789
2790 for( const auto& [ id, signal ] : m_userDefinedSignals )
2791 userDefinedSignals_js.push_back( signal );
2792
2793 // clang-format off
2794 nlohmann::json js = nlohmann::json( { { "version", 7 },
2795 { "tabs", tabs_js },
2796 { "user_defined_signals", userDefinedSignals_js },
2797 { "custom_cursors", m_customCursorsCnt - 1 } } ); // Since we start +1 on init
2798 // clang-format on
2799
2800 // Store the value of any simulation command found on the schematic sheet in a SCH_TEXT
2801 // object. If this changes we want to warn the user and ask them if they want to update
2802 // the corresponding panel's sim command.
2803 if( m_plotNotebook->GetPageCount() > 0 )
2804 {
2805 SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( 0 ) );
2806 js[ "last_sch_text_sim_command" ] = simTab->GetLastSchTextSimCommand();
2807 }
2808
2809 std::stringstream buffer;
2810 buffer << std::setw( 2 ) << js << std::endl;
2811
2812 bool res = file.Write( buffer.str() );
2813 file.Close();
2814
2815 // Store the filename of the last saved workbook.
2816 if( res )
2817 {
2818 filename.MakeRelativeTo( m_schematicFrame->Prj().GetProjectPath() );
2819 simulator()->Settings()->SetWorkbookFilename( filename.GetFullPath() );
2820 }
2821
2822 return res;
2823}
2824
2825
2827{
2828 switch( aType )
2829 {
2831 case ST_AC: return SPT_LIN_FREQUENCY;
2832 case ST_SP: return SPT_LIN_FREQUENCY;
2833 case ST_FFT: return SPT_LIN_FREQUENCY;
2834 case ST_DC: return SPT_SWEEP;
2835 case ST_TRAN: return SPT_TIME;
2836 case ST_NOISE: return SPT_LIN_FREQUENCY;
2837
2838 default:
2839 wxFAIL_MSG( wxString::Format( wxS( "Unhandled simulation type: %d" ), (int) aType ) );
2840 return SPT_UNKNOWN;
2841 }
2842}
2843
2844
2846{
2847 wxString output;
2848 wxString ref;
2849 wxString source;
2850 wxString scale;
2851 SPICE_VALUE pts;
2852 SPICE_VALUE fStart;
2853 SPICE_VALUE fStop;
2854 bool saveAll;
2855
2856 if( GetCurrentSimTab() )
2857 {
2858 circuitModel()->ParseNoiseCommand( GetCurrentSimTab()->GetSimCommand(), &output, &ref,
2859 &source, &scale, &pts, &fStart, &fStop, &saveAll );
2860 }
2861
2862 return source;
2863}
2864
2865
2866void SIMULATOR_FRAME_UI::TogglePanel( wxPanel* aPanel, wxSplitterWindow* aSplitterWindow,
2867 int& aSashPosition )
2868{
2869 bool isShown = aPanel->IsShown();
2870
2871 if( isShown )
2872 aSashPosition = aSplitterWindow->GetSashPosition();
2873
2874 aPanel->Show( !isShown );
2875
2876 aSplitterWindow->SetSashInvisible( isShown );
2877 aSplitterWindow->SetSashPosition( isShown ? -1 : aSashPosition, true );
2878
2879 aSplitterWindow->UpdateSize();
2880 m_parent->Refresh();
2881 m_parent->Layout();
2882}
2883
2884
2886{
2887 return m_panelConsole->IsShown();
2888}
2889
2890
2895
2896
2898{
2899 return m_sidePanel->IsShown();
2900}
2901
2902
2907
2908
2910{
2912
2913 // Rebuild the color list to plot traces
2915
2916 // Now send changes to all SIM_PLOT_TAB
2917 for( size_t page = 0; page < m_plotNotebook->GetPageCount(); page++ )
2918 {
2919 wxWindow* curPage = m_plotNotebook->GetPage( page );
2920
2921 // ensure it is truly a plot plotTab and not the (zero plots) placeholder
2922 // which is only SIM_TAB
2923 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( curPage );
2924
2925 if( plotTab )
2926 plotTab->UpdatePlotColors();
2927 }
2928}
2929
2930
2931void SIMULATOR_FRAME_UI::onPlotClose( wxAuiNotebookEvent& event )
2932{
2933 OnModify();
2934}
2935
2936
2937void SIMULATOR_FRAME_UI::onPlotClosed( wxAuiNotebookEvent& event )
2938{
2939 CallAfter( [this]()
2940 {
2942 rebuildSignalsGrid( m_filter->GetValue() );
2944
2945 //To avoid a current side effect in dynamic cursors while closing one out of many sim tabs
2947
2948 SIM_TAB* panel = GetCurrentSimTab();
2949
2950 if( !panel || panel->GetSimType() != ST_OP )
2951 {
2952 SCHEMATIC& schematic = m_schematicFrame->Schematic();
2953 schematic.ClearOperatingPoints();
2954 m_schematicFrame->RefreshOperatingPointDisplay();
2955 m_schematicFrame->GetCanvas()->Refresh();
2956 }
2957 } );
2958}
2959
2960
2962{
2963 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
2964 {
2965 std::vector<std::pair<wxString, wxString>>& measurements = plotTab->Measurements();
2966
2967 measurements.clear();
2968
2969 for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
2970 {
2971 if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
2972 {
2973 measurements.emplace_back( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ),
2974 m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT_FORMAT ) );
2975 }
2976 }
2977 }
2978}
2979
2980
2981void SIMULATOR_FRAME_UI::onPlotChanging( wxAuiNotebookEvent& event )
2982{
2983 m_measurementsGrid->ClearRows();
2984
2985 event.Skip();
2986}
2987
2988
2990{
2992 rebuildSignalsGrid( m_filter->GetValue() );
2994
2996
2997 for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
2998 UpdateMeasurement( row );
2999}
3000
3001
3002void SIMULATOR_FRAME_UI::onPlotChanged( wxAuiNotebookEvent& event )
3003{
3004 if( SIM_TAB* simTab = GetCurrentSimTab() )
3005 simulator()->Command( "setplot " + simTab->GetSpicePlotName().ToStdString() );
3006
3008
3009 //To avoid a current side effect in dynamic cursors while switching sim tabs
3011
3012 event.Skip();
3013}
3014
3015
3017{
3018 m_measurementsGrid->ClearRows();
3019
3020 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
3021 {
3022 for( const auto& [ measurement, format ] : plotTab->Measurements() )
3023 {
3024 int row = m_measurementsGrid->GetNumberRows();
3025 m_measurementsGrid->AppendRows();
3026 m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT, measurement );
3027 m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT_FORMAT, format );
3028 }
3029
3030 if( plotTab->GetSimType() == ST_TRAN || plotTab->GetSimType() == ST_AC
3031 || plotTab->GetSimType() == ST_DC || plotTab->GetSimType() == ST_SP )
3032 {
3033 m_measurementsGrid->AppendRows(); // Empty row at end
3034 }
3035 }
3036}
3037
3038
3039void SIMULATOR_FRAME_UI::onPlotDragged( wxAuiNotebookEvent& event )
3040{
3041}
3042
3043
3044std::shared_ptr<SPICE_SIMULATOR> SIMULATOR_FRAME_UI::simulator() const
3045{
3046 return m_simulatorFrame->GetSimulator();
3047}
3048
3049
3050std::shared_ptr<SPICE_CIRCUIT_MODEL> SIMULATOR_FRAME_UI::circuitModel() const
3051{
3052 return m_simulatorFrame->GetCircuitModel();
3053}
3054
3055
3057{
3058 SUPPRESS_GRID_CELL_EVENTS raii( this );
3059
3060 m_cursorsGrid->ClearRows();
3061
3062 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
3063
3064 if( !plotTab )
3065 return;
3066
3067 // Update cursor values
3068 CURSOR* cursor1 = nullptr;
3069 wxString cursor1Name;
3070 wxString cursor1Units;
3071 CURSOR* cursor2 = nullptr;
3072 wxString cursor2Name;
3073 wxString cursor2Units;
3074
3075 auto getUnitsY =
3076 [&]( TRACE* aTrace ) -> wxString
3077 {
3078 if( plotTab->GetSimType() == ST_AC )
3079 {
3080 if( aTrace->GetType() & SPT_AC_PHASE )
3081 return plotTab->GetUnitsY2();
3082 else
3083 return plotTab->GetUnitsY1();
3084 }
3085 else
3086 {
3087 if( aTrace->GetType() & SPT_POWER )
3088 return plotTab->GetUnitsY3();
3089 else if( aTrace->GetType() & SPT_CURRENT )
3090 return plotTab->GetUnitsY2();
3091 else
3092 return plotTab->GetUnitsY1();
3093 }
3094 };
3095
3096 auto getNameY =
3097 [&]( TRACE* aTrace ) -> wxString
3098 {
3099 if( plotTab->GetSimType() == ST_AC )
3100 {
3101 if( aTrace->GetType() & SPT_AC_PHASE )
3102 return plotTab->GetLabelY2();
3103 else
3104 return plotTab->GetLabelY1();
3105 }
3106 else
3107 {
3108 if( aTrace->GetType() & SPT_POWER )
3109 return plotTab->GetLabelY3();
3110 else if( aTrace->GetType() & SPT_CURRENT )
3111 return plotTab->GetLabelY2();
3112 else
3113 return plotTab->GetLabelY1();
3114 }
3115 };
3116
3117 auto formatValue =
3118 [this]( double aValue, int aCursorId, int aCol ) -> wxString
3119 {
3120 if( ( !m_simulatorFrame->SimFinished() && aCol == 1 ) || std::isnan( aValue ) )
3121 return wxS( "--" );
3122 else
3123 return SPICE_VALUE( aValue ).ToString( m_cursorFormatsDyn[ aCursorId ][ aCol ] );
3124 };
3125
3126 for( const auto& [name, trace] : plotTab->GetTraces() )
3127 {
3128 if( CURSOR* cursor = trace->GetCursor( 1 ) )
3129 {
3130 cursor1 = cursor;
3131 cursor1Name = getNameY( trace );
3132 cursor1Units = getUnitsY( trace );
3133
3134 wxRealPoint coords = cursor->GetCoords();
3135 int row = m_cursorsGrid->GetNumberRows();
3136
3137 m_cursorFormatsDyn[0][0].UpdateUnits( plotTab->GetUnitsX() );
3138 m_cursorFormatsDyn[0][1].UpdateUnits( cursor1Units );
3139
3140 m_cursorsGrid->AppendRows( 1 );
3141 m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "1" ) );
3142 m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, cursor->GetName() );
3143 m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, 0, 0 ) );
3144 m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, 0, 1 ) );
3145 break;
3146 }
3147 }
3148
3149 for( const auto& [name, trace] : plotTab->GetTraces() )
3150 {
3151 if( CURSOR* cursor = trace->GetCursor( 2 ) )
3152 {
3153 cursor2 = cursor;
3154 cursor2Name = getNameY( trace );
3155 cursor2Units = getUnitsY( trace );
3156
3157 wxRealPoint coords = cursor->GetCoords();
3158 int row = m_cursorsGrid->GetNumberRows();
3159
3160 m_cursorFormatsDyn[1][0].UpdateUnits( plotTab->GetUnitsX() );
3161 m_cursorFormatsDyn[1][1].UpdateUnits( cursor2Units );
3162
3163 m_cursorsGrid->AppendRows( 1 );
3164 m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "2" ) );
3165 m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, cursor->GetName() );
3166 m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, 1, 0 ) );
3167 m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, 1, 1 ) );
3168 break;
3169 }
3170 }
3171
3172 if( cursor1 && cursor2 && cursor1Units == cursor2Units )
3173 {
3174 wxRealPoint coords = cursor2->GetCoords() - cursor1->GetCoords();
3175 wxString signal;
3176
3177 m_cursorFormatsDyn[2][0].UpdateUnits( plotTab->GetUnitsX() );
3178 m_cursorFormatsDyn[2][1].UpdateUnits( cursor1Units );
3179
3180 if( cursor1->GetName() == cursor2->GetName() )
3181 signal = wxString::Format( wxS( "%s[2 - 1]" ), cursor2->GetName() );
3182 else
3183 signal = wxString::Format( wxS( "%s - %s" ), cursor2->GetName(), cursor1->GetName() );
3184
3185 m_cursorsGrid->AppendRows( 1 );
3186 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_NAME, _( "Diff" ) );
3187 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_SIGNAL, signal );
3188 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_X, formatValue( coords.x, 2, 0 ) );
3189 m_cursorsGrid->SetCellValue( 2, COL_CURSOR_Y, formatValue( coords.y, 2, 1 ) );
3190 }
3191 // Set up the labels
3192 m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, plotTab->GetLabelX() );
3193
3194 wxString valColName = _( "Value" );
3195
3196 if( !cursor1Name.IsEmpty() )
3197 {
3198 if( cursor2Name.IsEmpty() || cursor1Name == cursor2Name )
3199 valColName = cursor1Name;
3200 }
3201 else if( !cursor2Name.IsEmpty() )
3202 {
3203 valColName = cursor2Name;
3204 }
3205
3206 m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, valColName );
3207
3208 if( m_customCursorsCnt > 3 ) // 2 for the default hardocded cursors plus the initial + 1
3209 {
3210 for( int i = 3; i < m_customCursorsCnt; i++ )
3211 {
3212 for( const auto& [name, trace] : plotTab->GetTraces() )
3213 {
3214 if( CURSOR* cursor = trace->GetCursor( i ) )
3215 {
3216 CURSOR* curs = cursor;
3217 wxString cursName = getNameY( trace );
3218 wxString cursUnits = getUnitsY( trace );
3219
3220 wxRealPoint coords = cursor->GetCoords();
3221 int row = m_cursorsGrid->GetNumberRows();
3222
3223 m_cursorFormatsDyn[i][0].UpdateUnits( plotTab->GetUnitsX() );
3224 m_cursorFormatsDyn[i][1].UpdateUnits( cursUnits );
3225
3226 m_cursorsGrid->AppendRows( 1 );
3227 m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "" ) + wxString( "" ) << i );
3228 m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, curs->GetName() );
3229 m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, i, 0 ) );
3230 m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, i, 1 ) );
3231
3232 // Set up the labels
3233 m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, plotTab->GetLabelX() );
3234
3235 valColName = _( "Value" );
3236
3237 if( !cursName.IsEmpty() && m_cursorsGrid->GetColLabelValue( COL_CURSOR_Y ) == cursName )
3238 valColName = cursName;
3239
3240 m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, valColName );
3241 break;
3242 }
3243 }
3244 }
3245 }
3246}
3247
3248
3249void SIMULATOR_FRAME_UI::onPlotCursorUpdate( wxCommandEvent& aEvent )
3250{
3252 OnModify();
3253}
3254
3255
3257{
3258 if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
3259 plotTab->ResetScales( true );
3260
3261 m_simConsole->Clear();
3262
3264
3265 // Do not export netlist, it is already stored in the simulator
3266 applyTuners();
3267
3268 m_refreshTimer.Start( REFRESH_INTERVAL, wxTIMER_ONE_SHOT );
3269}
3270
3271
3272void SIMULATOR_FRAME_UI::OnSimReport( const wxString& aMsg )
3273{
3274 m_simConsole->AppendText( aMsg + "\n" );
3275 m_simConsole->SetInsertionPointEnd();
3276}
3277
3278
3279std::vector<wxString> SIMULATOR_FRAME_UI::SimPlotVectors() const
3280{
3281 std::vector<wxString> signals;
3282
3283 for( const std::string& vec : simulator()->AllVectors() )
3284 signals.emplace_back( vec );
3285
3286 return signals;
3287}
3288
3289
3290std::vector<wxString> SIMULATOR_FRAME_UI::Signals() const
3291{
3292 std::vector<wxString> signals;
3293
3294 for( const wxString& signal : m_signals )
3295 signals.emplace_back( signal );
3296
3297 for( const auto& [ id, signal ] : m_userDefinedSignals )
3298 signals.emplace_back( signal );
3299
3300 sortSignals( signals );
3301
3302 return signals;
3303}
3304
3305
3307{
3308 if( aFinal )
3309 m_refreshTimer.Stop();
3310
3311 SIM_TAB* simTab = GetCurrentSimTab();
3312
3313 if( !simTab )
3314 return;
3315
3316 bool storeMultiRun = false;
3317
3318 if( aFinal && m_multiRunState.active )
3319 {
3320 if( m_multiRunState.currentStep < m_multiRunState.steps.size() )
3321 {
3322 storeMultiRun = true;
3323 m_multiRunState.storePending = true;
3324 }
3325 }
3326 else
3327 {
3328 m_multiRunState.storePending = false;
3329 }
3330
3331 SIM_TYPE simType = simTab->GetSimType();
3332 wxString msg;
3333
3334 if( aFinal )
3335 {
3338 }
3339
3340 // If there are any signals plotted, update them
3341 if( SIM_TAB::IsPlottable( simType ) )
3342 {
3343 simTab->SetSpicePlotName( simulator()->CurrentPlotName() );
3344
3345 if( simType == ST_NOISE && aFinal )
3346 {
3347 m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
3348 m_simConsole->SetInsertionPointEnd();
3349
3350 // The simulator will create noise1 & noise2 on the first run, noise3 and noise4
3351 // on the second, etc. The first plot for each run contains the spectral density
3352 // noise vectors and second contains the integrated noise.
3353 long number;
3354 simulator()->CurrentPlotName().Mid( 5 ).ToLong( &number );
3355
3356 for( const std::string& vec : simulator()->AllVectors() )
3357 {
3358 std::vector<double> val_list = simulator()->GetRealVector( vec, 1 );
3359 wxString value = SPICE_VALUE( val_list[ 0 ] ).ToSpiceString();
3360
3361 msg.Printf( wxS( "%s: %sV\n" ), vec, value );
3362
3363 m_simConsole->AppendText( msg );
3364 m_simConsole->SetInsertionPointEnd();
3365 }
3366
3367 simulator()->Command( fmt::format( "setplot noise{}", number - 1 ) );
3368 simTab->SetSpicePlotName( simulator()->CurrentPlotName() );
3369 }
3370
3371 SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab );
3372 wxCHECK_RET( plotTab, wxString::Format( wxT( "No SIM_PLOT_TAB for: %s" ),
3373 magic_enum::enum_name( simType ) ) );
3374
3375 struct TRACE_INFO
3376 {
3377 wxString Vector;
3378 int TraceType;
3379 bool ClearData;
3380 };
3381
3382 std::map<TRACE*, TRACE_INFO> traceMap;
3383
3384 for( const auto& [ name, trace ] : plotTab->GetTraces() )
3385 traceMap[ trace ] = { wxEmptyString, SPT_UNKNOWN, false };
3386
3387 // NB: m_signals are already broken out into gain/phase, but m_userDefinedSignals are
3388 // as the user typed them
3389
3390 for( const wxString& signal : m_signals )
3391 {
3392 int traceType = SPT_UNKNOWN;
3393 wxString vectorName = vectorNameFromSignalName( plotTab, signal, &traceType );
3394
3395 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
3396 traceMap[ trace ] = { vectorName, traceType, false };
3397 }
3398
3399 for( const auto& [ id, signal ] : m_userDefinedSignals )
3400 {
3401 int traceType = SPT_UNKNOWN;
3402 wxString vectorName = vectorNameFromSignalName( plotTab, signal, &traceType );
3403
3404 if( simType == ST_AC )
3405 {
3406 int baseType = traceType &= ~( SPT_AC_GAIN | SPT_AC_PHASE );
3407
3408 for( int subType : { baseType | SPT_AC_GAIN, baseType | SPT_AC_PHASE } )
3409 {
3410 if( TRACE* trace = plotTab->GetTrace( vectorName, subType ) )
3411 traceMap[ trace ] = { vectorName, subType, !aFinal };
3412 }
3413 }
3414 else if( simType == ST_SP )
3415 {
3416 int baseType = traceType &= ~( SPT_SP_AMP | SPT_AC_PHASE );
3417
3418 for( int subType : { baseType | SPT_SP_AMP, baseType | SPT_AC_PHASE } )
3419 {
3420 if( TRACE* trace = plotTab->GetTrace( vectorName, subType ) )
3421 traceMap[trace] = { vectorName, subType, !aFinal };
3422 }
3423 }
3424 else
3425 {
3426 if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
3427 traceMap[ trace ] = { vectorName, traceType, !aFinal };
3428 }
3429 }
3430
3431 // Two passes so that DC-sweep sub-traces get deleted and re-created:
3432
3433 for( const auto& [ trace, traceInfo ] : traceMap )
3434 {
3435 if( traceInfo.Vector.IsEmpty() )
3436 plotTab->DeleteTrace( trace );
3437 }
3438
3439 for( const auto& [ trace, info ] : traceMap )
3440 {
3441 std::vector<double> data_x;
3442
3443 if( !info.Vector.IsEmpty() )
3444 updateTrace( info.Vector, info.TraceType, plotTab, &data_x, info.ClearData );
3445 }
3446
3447 plotTab->GetPlotWin()->UpdateAll();
3448
3449 if( aFinal )
3450 {
3451 for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
3452 UpdateMeasurement( row );
3453
3454 plotTab->ResetScales( true );
3455 }
3456
3457 plotTab->GetPlotWin()->Fit();
3458
3460 }
3461 else if( simType == ST_OP && aFinal )
3462 {
3463 m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
3464 m_simConsole->SetInsertionPointEnd();
3465
3466 for( const std::string& vec : simulator()->AllVectors() )
3467 {
3468 std::vector<double> val_list = simulator()->GetRealVector( vec, 1 );
3469
3470 if( val_list.empty() )
3471 continue;
3472
3473 wxString value = SPICE_VALUE( val_list[ 0 ] ).ToSpiceString();
3474 wxString signal;
3475 SIM_TRACE_TYPE type = circuitModel()->VectorToSignal( vec, signal );
3476
3477 const size_t tab = 25; //characters
3478 size_t padding = ( signal.length() < tab ) ? ( tab - signal.length() ) : 1;
3479
3480 switch( type )
3481 {
3482 case SPT_VOLTAGE: value.Append( wxS( "V" ) ); break;
3483 case SPT_CURRENT: value.Append( wxS( "A" ) ); break;
3484 case SPT_POWER: value.Append( wxS( "W" ) ); break;
3485 default: value.Append( wxS( "?" ) ); break;
3486 }
3487
3488 msg.Printf( wxT( "%s%s\n" ),
3489 ( signal + wxT( ":" ) ).Pad( padding, wxUniChar( ' ' ) ),
3490 value );
3491
3492 m_simConsole->AppendText( msg );
3493 m_simConsole->SetInsertionPointEnd();
3494
3495 if( type == SPT_VOLTAGE || type == SPT_CURRENT || type == SPT_POWER )
3496 signal = signal.SubString( 2, signal.Length() - 2 );
3497
3498 if( type == SPT_POWER )
3499 signal += wxS( ":power" );
3500
3501 m_schematicFrame->Schematic().SetOperatingPoint( signal, val_list.at( 0 ) );
3502 }
3503 }
3504 else if( simType == ST_PZ && aFinal )
3505 {
3506 m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
3507 m_simConsole->SetInsertionPointEnd();
3508 simulator()->Command( "print all" );
3509 }
3510
3511 if( storeMultiRun )
3512 {
3513 m_multiRunState.storePending = false;
3514 m_multiRunState.storedSteps = m_multiRunState.currentStep + 1;
3515 }
3516
3517 if( aFinal && m_multiRunState.active )
3518 {
3519 if( m_multiRunState.currentStep + 1 < m_multiRunState.steps.size() )
3520 {
3521 m_multiRunState.currentStep++;
3522
3523 wxQueueEvent( m_simulatorFrame, new wxCommandEvent( EVT_SIM_UPDATE ) );
3524 }
3525 else
3526 {
3527 m_multiRunState.active = false;
3528 m_multiRunState.steps.clear();
3529 m_multiRunState.currentStep = 0;
3530 m_multiRunState.storePending = false;
3531 m_tunerOverrides.clear();
3532
3533 if( !m_multiRunState.traces.empty() )
3534 {
3535 auto iter = m_multiRunState.traces.begin();
3536
3537 if( iter != m_multiRunState.traces.end() )
3538 m_multiRunState.storedSteps = iter->second.yValues.size();
3539 }
3540 }
3541 }
3542}
3543
3544
3546{
3547 m_multiRunState.active = false;
3548 m_multiRunState.tuners.clear();
3549 m_multiRunState.steps.clear();
3550 m_multiRunState.currentStep = 0;
3551 m_multiRunState.storePending = false;
3552
3553 if( aClearTraces )
3554 {
3555 m_multiRunState.traces.clear();
3556 m_multiRunState.storedSteps = 0;
3557 }
3558
3559 m_tunerOverrides.clear();
3560}
3561
3562
3564{
3565 m_tunerOverrides.clear();
3566
3567 std::vector<TUNER_SLIDER*> multiTuners;
3568
3569 for( TUNER_SLIDER* tuner : m_tuners )
3570 {
3571 if( tuner->GetRunMode() == TUNER_SLIDER::RUN_MODE::MULTI )
3572 multiTuners.push_back( tuner );
3573 }
3574
3575 if( multiTuners.empty() )
3576 {
3577 clearMultiRunState( true );
3578 return;
3579 }
3580
3581 bool tunersChanged = multiTuners != m_multiRunState.tuners;
3582
3583 if( m_multiRunState.active && tunersChanged )
3584 clearMultiRunState( true );
3585
3586 if( !m_multiRunState.active )
3587 {
3588 if( tunersChanged || m_multiRunState.storedSteps > 0 || !m_multiRunState.traces.empty() )
3589 clearMultiRunState( true );
3590
3591 m_multiRunState.tuners = multiTuners;
3592 m_multiRunState.steps = calculateMultiRunSteps( multiTuners );
3593 m_multiRunState.currentStep = 0;
3594 m_multiRunState.storePending = false;
3595
3596 if( m_multiRunState.steps.size() >= 2 )
3597 {
3598 m_multiRunState.active = true;
3599 m_multiRunState.storedSteps = 0;
3600 }
3601 else
3602 {
3603 m_multiRunState.steps.clear();
3604 return;
3605 }
3606 }
3607 else if( tunersChanged )
3608 {
3609 m_multiRunState.tuners = multiTuners;
3610 }
3611
3612 if( m_multiRunState.active && m_multiRunState.currentStep < m_multiRunState.steps.size() )
3613 {
3614 const MULTI_RUN_STEP& step = m_multiRunState.steps[m_multiRunState.currentStep];
3615
3616 for( const auto& entry : step.overrides )
3617 m_tunerOverrides[entry.first] = entry.second;
3618 }
3619}
3620
3621
3622std::vector<SIMULATOR_FRAME_UI::MULTI_RUN_STEP> SIMULATOR_FRAME_UI::calculateMultiRunSteps(
3623 const std::vector<TUNER_SLIDER*>& aTuners ) const
3624{
3625 std::vector<MULTI_RUN_STEP> steps;
3626
3627 if( aTuners.empty() )
3628 return steps;
3629
3630 std::vector<std::vector<double>> tunerValues;
3631 tunerValues.reserve( aTuners.size() );
3632
3633 for( TUNER_SLIDER* tuner : aTuners )
3634 {
3635 if( !tuner )
3636 return steps;
3637
3638 double startValue = tuner->GetMin().ToDouble();
3639 double endValue = tuner->GetMax().ToDouble();
3640 int stepCount = std::max( 2, tuner->GetStepCount() );
3641
3642 if( stepCount < 2 )
3643 stepCount = 2;
3644
3645 double increment = ( endValue - startValue ) / static_cast<double>( stepCount - 1 );
3646
3647 std::vector<double> values;
3648 values.reserve( stepCount );
3649
3650 for( int ii = 0; ii < stepCount; ++ii )
3651 values.push_back( startValue + increment * ii );
3652
3653 tunerValues.push_back( std::move( values ) );
3654 }
3655
3657
3658 if( limit < 1 )
3659 limit = 1;
3660
3661 std::vector<double> currentValues( aTuners.size(), 0.0 );
3662
3663 auto generate = [&]( auto&& self, size_t depth ) -> void
3664 {
3665 if( steps.size() >= static_cast<size_t>( limit ) )
3666 return;
3667
3668 if( depth == aTuners.size() )
3669 {
3670 MULTI_RUN_STEP step;
3671
3672 for( size_t ii = 0; ii < aTuners.size(); ++ii )
3673 step.overrides.emplace( aTuners[ii], currentValues[ii] );
3674
3675 steps.push_back( std::move( step ) );
3676 return;
3677 }
3678
3679 for( double value : tunerValues[depth] )
3680 {
3681 currentValues[depth] = value;
3682 self( self, depth + 1 );
3683
3684 if( steps.size() >= static_cast<size_t>( limit ) )
3685 return;
3686 }
3687 };
3688
3689 generate( generate, 0 );
3690
3691 return steps;
3692}
3693
3694
3695std::string SIMULATOR_FRAME_UI::multiRunTraceKey( const wxString& aVectorName, int aTraceType ) const
3696{
3697 return fmt::format( "{}|{}", aVectorName.ToStdString(), aTraceType );
3698}
3699
3700
3701void SIMULATOR_FRAME_UI::recordMultiRunData( const wxString& aVectorName, int aTraceType,
3702 const std::vector<double>& aX,
3703 const std::vector<double>& aY )
3704{
3705 if( aX.empty() || aY.empty() )
3706 return;
3707
3708 std::string key = multiRunTraceKey( aVectorName, aTraceType );
3709 MULTI_RUN_TRACE& trace = m_multiRunState.traces[key];
3710
3711 trace.traceType = aTraceType;
3712
3713 if( trace.xValues.empty() )
3714 trace.xValues = aX;
3715
3716 if( trace.xValues.size() != aX.size() )
3717 return;
3718
3719 size_t index = m_multiRunState.currentStep;
3720
3721 if( trace.yValues.size() <= index )
3722 trace.yValues.resize( index + 1 );
3723
3724 trace.yValues[index] = aY;
3725}
3726
3727
3728bool SIMULATOR_FRAME_UI::hasMultiRunTrace( const wxString& aVectorName, int aTraceType ) const
3729{
3730 std::string key = multiRunTraceKey( aVectorName, aTraceType );
3731 auto it = m_multiRunState.traces.find( key );
3732
3733 if( it == m_multiRunState.traces.end() )
3734 return false;
3735
3736 const MULTI_RUN_TRACE& trace = it->second;
3737
3738 return !trace.xValues.empty() && !trace.yValues.empty();
3739}
3740
3741
3743{
3744 m_simulatorFrame->OnModify();
3745}
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:150
wxColour ToColour() const
Definition color4d.cpp:225
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:290
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:69
const wxString & GetMessages() const
Definition reporter.cpp:78
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.