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