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