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