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