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