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