You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2990 lines
96 KiB

Horizontal/vertical zoom for Simulator plots ADDED: Horizontal/vertical zoom for simulator plots, via mouse wheel, toolbar buttons, menu commands, and hotkeys. ADDED: Simulator preferences panel, populated with mouse wheel and trackpad settings that control pan and zoom of simulator plots. ADDED: Zoom In/Out Horizontally/Vertically commands that can be bound to hotkeys. CHANGED: Simulator plot scroll wheel gestures are no longer hard-coded and can now be configured via the new Simulator preferences panel. Fixes https://gitlab.com/kicad/code/kicad/-/issues/16597 Other unreported bugs that were fixed: - Fixed wierd, jumpy simulator plot view limiting behavior. - Fixed Zoom In Center and Zoom Out Center commands not preserving the simulator plot center point. - Fixed simulator plot nudging when exported as PNGs. - Fixed rectangular selection zoom being able to exceed simulator plot view limits. Notes: - Provided new SIM_PREFERENCES struct to be used for future simulator preferences set via the simulator preferences dialog. - Bundled pre-existing EESCHEMA_SETTINGS::SIMULATOR settings into EESCHEMA_SETTINGS::SIMULATOR::VIEW. - Replaced mpWindow::EnableMouseWheelPan with more general SetMouseWheelActions. - Refactored and tidied up wxMathPlot's mpWindow code involved with fitting, zooming, and panning. - Consolidated long lists of duplicated member variable initializers to a new mpWindow private delegated constructor. - Provided provisional Zoom In/Out Horizontally/Vertically toolbar icons that need improvement by a graphics designer. - Provided gitignore entries for the Qt Creator IDE
2 years ago
Horizontal/vertical zoom for Simulator plots ADDED: Horizontal/vertical zoom for simulator plots, via mouse wheel, toolbar buttons, menu commands, and hotkeys. ADDED: Simulator preferences panel, populated with mouse wheel and trackpad settings that control pan and zoom of simulator plots. ADDED: Zoom In/Out Horizontally/Vertically commands that can be bound to hotkeys. CHANGED: Simulator plot scroll wheel gestures are no longer hard-coded and can now be configured via the new Simulator preferences panel. Fixes https://gitlab.com/kicad/code/kicad/-/issues/16597 Other unreported bugs that were fixed: - Fixed wierd, jumpy simulator plot view limiting behavior. - Fixed Zoom In Center and Zoom Out Center commands not preserving the simulator plot center point. - Fixed simulator plot nudging when exported as PNGs. - Fixed rectangular selection zoom being able to exceed simulator plot view limits. Notes: - Provided new SIM_PREFERENCES struct to be used for future simulator preferences set via the simulator preferences dialog. - Bundled pre-existing EESCHEMA_SETTINGS::SIMULATOR settings into EESCHEMA_SETTINGS::SIMULATOR::VIEW. - Replaced mpWindow::EnableMouseWheelPan with more general SetMouseWheelActions. - Refactored and tidied up wxMathPlot's mpWindow code involved with fitting, zooming, and panning. - Consolidated long lists of duplicated member variable initializers to a new mpWindow private delegated constructor. - Provided provisional Zoom In/Out Horizontally/Vertically toolbar icons that need improvement by a graphics designer. - Provided gitignore entries for the Qt Creator IDE
2 years ago
Horizontal/vertical zoom for Simulator plots ADDED: Horizontal/vertical zoom for simulator plots, via mouse wheel, toolbar buttons, menu commands, and hotkeys. ADDED: Simulator preferences panel, populated with mouse wheel and trackpad settings that control pan and zoom of simulator plots. ADDED: Zoom In/Out Horizontally/Vertically commands that can be bound to hotkeys. CHANGED: Simulator plot scroll wheel gestures are no longer hard-coded and can now be configured via the new Simulator preferences panel. Fixes https://gitlab.com/kicad/code/kicad/-/issues/16597 Other unreported bugs that were fixed: - Fixed wierd, jumpy simulator plot view limiting behavior. - Fixed Zoom In Center and Zoom Out Center commands not preserving the simulator plot center point. - Fixed simulator plot nudging when exported as PNGs. - Fixed rectangular selection zoom being able to exceed simulator plot view limits. Notes: - Provided new SIM_PREFERENCES struct to be used for future simulator preferences set via the simulator preferences dialog. - Bundled pre-existing EESCHEMA_SETTINGS::SIMULATOR settings into EESCHEMA_SETTINGS::SIMULATOR::VIEW. - Replaced mpWindow::EnableMouseWheelPan with more general SetMouseWheelActions. - Refactored and tidied up wxMathPlot's mpWindow code involved with fitting, zooming, and panning. - Consolidated long lists of duplicated member variable initializers to a new mpWindow private delegated constructor. - Provided provisional Zoom In/Out Horizontally/Vertically toolbar icons that need improvement by a graphics designer. - Provided gitignore entries for the Qt Creator IDE
2 years ago
Horizontal/vertical zoom for Simulator plots ADDED: Horizontal/vertical zoom for simulator plots, via mouse wheel, toolbar buttons, menu commands, and hotkeys. ADDED: Simulator preferences panel, populated with mouse wheel and trackpad settings that control pan and zoom of simulator plots. ADDED: Zoom In/Out Horizontally/Vertically commands that can be bound to hotkeys. CHANGED: Simulator plot scroll wheel gestures are no longer hard-coded and can now be configured via the new Simulator preferences panel. Fixes https://gitlab.com/kicad/code/kicad/-/issues/16597 Other unreported bugs that were fixed: - Fixed wierd, jumpy simulator plot view limiting behavior. - Fixed Zoom In Center and Zoom Out Center commands not preserving the simulator plot center point. - Fixed simulator plot nudging when exported as PNGs. - Fixed rectangular selection zoom being able to exceed simulator plot view limits. Notes: - Provided new SIM_PREFERENCES struct to be used for future simulator preferences set via the simulator preferences dialog. - Bundled pre-existing EESCHEMA_SETTINGS::SIMULATOR settings into EESCHEMA_SETTINGS::SIMULATOR::VIEW. - Replaced mpWindow::EnableMouseWheelPan with more general SetMouseWheelActions. - Refactored and tidied up wxMathPlot's mpWindow code involved with fitting, zooming, and panning. - Consolidated long lists of duplicated member variable initializers to a new mpWindow private delegated constructor. - Provided provisional Zoom In/Out Horizontally/Vertically toolbar icons that need improvement by a graphics designer. - Provided gitignore entries for the Qt Creator IDE
2 years ago
Horizontal/vertical zoom for Simulator plots ADDED: Horizontal/vertical zoom for simulator plots, via mouse wheel, toolbar buttons, menu commands, and hotkeys. ADDED: Simulator preferences panel, populated with mouse wheel and trackpad settings that control pan and zoom of simulator plots. ADDED: Zoom In/Out Horizontally/Vertically commands that can be bound to hotkeys. CHANGED: Simulator plot scroll wheel gestures are no longer hard-coded and can now be configured via the new Simulator preferences panel. Fixes https://gitlab.com/kicad/code/kicad/-/issues/16597 Other unreported bugs that were fixed: - Fixed wierd, jumpy simulator plot view limiting behavior. - Fixed Zoom In Center and Zoom Out Center commands not preserving the simulator plot center point. - Fixed simulator plot nudging when exported as PNGs. - Fixed rectangular selection zoom being able to exceed simulator plot view limits. Notes: - Provided new SIM_PREFERENCES struct to be used for future simulator preferences set via the simulator preferences dialog. - Bundled pre-existing EESCHEMA_SETTINGS::SIMULATOR settings into EESCHEMA_SETTINGS::SIMULATOR::VIEW. - Replaced mpWindow::EnableMouseWheelPan with more general SetMouseWheelActions. - Refactored and tidied up wxMathPlot's mpWindow code involved with fitting, zooming, and panning. - Consolidated long lists of duplicated member variable initializers to a new mpWindow private delegated constructor. - Provided provisional Zoom In/Out Horizontally/Vertically toolbar icons that need improvement by a graphics designer. - Provided gitignore entries for the Qt Creator IDE
2 years ago
Horizontal/vertical zoom for Simulator plots ADDED: Horizontal/vertical zoom for simulator plots, via mouse wheel, toolbar buttons, menu commands, and hotkeys. ADDED: Simulator preferences panel, populated with mouse wheel and trackpad settings that control pan and zoom of simulator plots. ADDED: Zoom In/Out Horizontally/Vertically commands that can be bound to hotkeys. CHANGED: Simulator plot scroll wheel gestures are no longer hard-coded and can now be configured via the new Simulator preferences panel. Fixes https://gitlab.com/kicad/code/kicad/-/issues/16597 Other unreported bugs that were fixed: - Fixed wierd, jumpy simulator plot view limiting behavior. - Fixed Zoom In Center and Zoom Out Center commands not preserving the simulator plot center point. - Fixed simulator plot nudging when exported as PNGs. - Fixed rectangular selection zoom being able to exceed simulator plot view limits. Notes: - Provided new SIM_PREFERENCES struct to be used for future simulator preferences set via the simulator preferences dialog. - Bundled pre-existing EESCHEMA_SETTINGS::SIMULATOR settings into EESCHEMA_SETTINGS::SIMULATOR::VIEW. - Replaced mpWindow::EnableMouseWheelPan with more general SetMouseWheelActions. - Refactored and tidied up wxMathPlot's mpWindow code involved with fitting, zooming, and panning. - Consolidated long lists of duplicated member variable initializers to a new mpWindow private delegated constructor. - Provided provisional Zoom In/Out Horizontally/Vertically toolbar icons that need improvement by a graphics designer. - Provided gitignore entries for the Qt Creator IDE
2 years ago
2 years ago
Horizontal/vertical zoom for Simulator plots ADDED: Horizontal/vertical zoom for simulator plots, via mouse wheel, toolbar buttons, menu commands, and hotkeys. ADDED: Simulator preferences panel, populated with mouse wheel and trackpad settings that control pan and zoom of simulator plots. ADDED: Zoom In/Out Horizontally/Vertically commands that can be bound to hotkeys. CHANGED: Simulator plot scroll wheel gestures are no longer hard-coded and can now be configured via the new Simulator preferences panel. Fixes https://gitlab.com/kicad/code/kicad/-/issues/16597 Other unreported bugs that were fixed: - Fixed wierd, jumpy simulator plot view limiting behavior. - Fixed Zoom In Center and Zoom Out Center commands not preserving the simulator plot center point. - Fixed simulator plot nudging when exported as PNGs. - Fixed rectangular selection zoom being able to exceed simulator plot view limits. Notes: - Provided new SIM_PREFERENCES struct to be used for future simulator preferences set via the simulator preferences dialog. - Bundled pre-existing EESCHEMA_SETTINGS::SIMULATOR settings into EESCHEMA_SETTINGS::SIMULATOR::VIEW. - Replaced mpWindow::EnableMouseWheelPan with more general SetMouseWheelActions. - Refactored and tidied up wxMathPlot's mpWindow code involved with fitting, zooming, and panning. - Consolidated long lists of duplicated member variable initializers to a new mpWindow private delegated constructor. - Provided provisional Zoom In/Out Horizontally/Vertically toolbar icons that need improvement by a graphics designer. - Provided gitignore entries for the Qt Creator IDE
2 years ago
11 months ago
2 years ago
  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 <tomasz.wlostowski@cern.ch>
  7. * @author Maciej Suminski <maciej.suminski@cern.ch>
  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. #include <memory>
  27. #include <fmt/format.h>
  28. #include <wx/wfstream.h>
  29. #include <wx/stdstream.h>
  30. #include <wx/debug.h>
  31. #include <wx/clipbrd.h>
  32. #include <wx/log.h>
  33. #include <project/project_file.h>
  34. #include <sch_edit_frame.h>
  35. #include <confirm.h>
  36. #include <wildcards_and_files_ext.h>
  37. #include <widgets/tuner_slider.h>
  38. #include <widgets/grid_color_swatch_helpers.h>
  39. #include <widgets/wx_grid.h>
  40. #include <grid_tricks.h>
  41. #include <eda_pattern_match.h>
  42. #include <string_utils.h>
  43. #include <pgm_base.h>
  44. #include <sim/simulator_frame_ui.h>
  45. #include <sim/simulator_frame.h>
  46. #include <sim/sim_plot_tab.h>
  47. #include <sim/spice_simulator.h>
  48. #include <dialogs/dialog_text_entry.h>
  49. #include <dialogs/dialog_sim_format_value.h>
  50. #include <eeschema_settings.h>
  51. #include "kiplatform/app.h"
  52. #include <magic_enum.hpp>
  53. SIM_TRACE_TYPE operator|( SIM_TRACE_TYPE aFirst, SIM_TRACE_TYPE aSecond )
  54. {
  55. int res = (int) aFirst | (int) aSecond;
  56. return (SIM_TRACE_TYPE) res;
  57. }
  58. enum SIGNALS_GRID_COLUMNS
  59. {
  60. COL_SIGNAL_NAME = 0,
  61. COL_SIGNAL_SHOW,
  62. COL_SIGNAL_COLOR,
  63. COL_CURSOR_1,
  64. COL_CURSOR_2
  65. };
  66. enum CURSORS_GRID_COLUMNS
  67. {
  68. COL_CURSOR_NAME = 0,
  69. COL_CURSOR_SIGNAL,
  70. COL_CURSOR_X,
  71. COL_CURSOR_Y
  72. };
  73. enum MEASUREMENTS_GIRD_COLUMNS
  74. {
  75. COL_MEASUREMENT = 0,
  76. COL_MEASUREMENT_VALUE,
  77. COL_MEASUREMENT_FORMAT
  78. };
  79. enum
  80. {
  81. MYID_MEASURE_MIN = GRIDTRICKS_FIRST_CLIENT_ID,
  82. MYID_MEASURE_MAX,
  83. MYID_MEASURE_AVG,
  84. MYID_MEASURE_RMS,
  85. MYID_MEASURE_PP,
  86. MYID_MEASURE_MIN_AT,
  87. MYID_MEASURE_MAX_AT,
  88. MYID_MEASURE_INTEGRAL,
  89. MYID_FOURIER,
  90. MYID_FORMAT_VALUE,
  91. MYID_DELETE_MEASUREMENT
  92. };
  93. class SIGNALS_GRID_TRICKS : public GRID_TRICKS
  94. {
  95. public:
  96. SIGNALS_GRID_TRICKS( SIMULATOR_FRAME_UI* aParent, WX_GRID* aGrid ) :
  97. GRID_TRICKS( aGrid ),
  98. m_parent( aParent ),
  99. m_menuRow( 0 ),
  100. m_menuCol( 0 )
  101. {}
  102. protected:
  103. void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
  104. void doPopupSelection( wxCommandEvent& event ) override;
  105. protected:
  106. SIMULATOR_FRAME_UI* m_parent;
  107. int m_menuRow;
  108. int m_menuCol;
  109. };
  110. void SIGNALS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
  111. {
  112. m_menuRow = aEvent.GetRow();
  113. m_menuCol = aEvent.GetCol();
  114. if( m_menuCol == COL_SIGNAL_NAME )
  115. {
  116. if( !( m_grid->IsInSelection( m_menuRow, m_menuCol ) ) )
  117. m_grid->ClearSelection();
  118. m_grid->SetGridCursor( m_menuRow, m_menuCol );
  119. if( SIM_TAB* panel = m_parent->GetCurrentSimTab() )
  120. {
  121. if( panel->GetSimType() == ST_TRAN || panel->GetSimType() == ST_AC
  122. || panel->GetSimType() == ST_DC || panel->GetSimType() == ST_SP )
  123. {
  124. menu.Append( MYID_MEASURE_MIN, _( "Measure Min" ) );
  125. menu.Append( MYID_MEASURE_MAX, _( "Measure Max" ) );
  126. menu.Append( MYID_MEASURE_AVG, _( "Measure Average" ) );
  127. menu.Append( MYID_MEASURE_RMS, _( "Measure RMS" ) );
  128. menu.Append( MYID_MEASURE_PP, _( "Measure Peak-to-peak" ) );
  129. if( panel->GetSimType() == ST_AC || panel->GetSimType() == ST_SP )
  130. {
  131. menu.Append( MYID_MEASURE_MIN_AT, _( "Measure Frequency of Min" ) );
  132. menu.Append( MYID_MEASURE_MAX_AT, _( "Measure Frequency of Max" ) );
  133. }
  134. else
  135. {
  136. menu.Append( MYID_MEASURE_MIN_AT, _( "Measure Time of Min" ) );
  137. menu.Append( MYID_MEASURE_MAX_AT, _( "Measure Time of Max" ) );
  138. }
  139. menu.Append( MYID_MEASURE_INTEGRAL, _( "Measure Integral" ) );
  140. if( panel->GetSimType() == ST_TRAN )
  141. {
  142. menu.AppendSeparator();
  143. menu.Append( MYID_FOURIER, _( "Perform Fourier Analysis..." ) );
  144. }
  145. menu.AppendSeparator();
  146. menu.Append( GRIDTRICKS_ID_COPY, _( "Copy Signal Name" ) + "\tCtrl+C" );
  147. m_grid->PopupMenu( &menu );
  148. }
  149. }
  150. }
  151. }
  152. void SIGNALS_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
  153. {
  154. std::vector<wxString> signals;
  155. wxGridCellCoordsArray cells1 = m_grid->GetSelectionBlockTopLeft();
  156. wxGridCellCoordsArray cells2 = m_grid->GetSelectionBlockBottomRight();
  157. for( size_t i = 0; i < cells1.Count(); i++ )
  158. {
  159. if( cells1[i].GetCol() == COL_SIGNAL_NAME )
  160. {
  161. for( int j = cells1[i].GetRow(); j < cells2[i].GetRow() + 1; j++ )
  162. {
  163. signals.push_back( m_grid->GetCellValue( j, cells1[i].GetCol() ) );
  164. }
  165. }
  166. }
  167. wxGridCellCoordsArray cells3 = m_grid->GetSelectedCells();
  168. for( size_t i = 0; i < cells3.Count(); i++ )
  169. {
  170. if( cells3[i].GetCol() == COL_SIGNAL_NAME )
  171. signals.push_back( m_grid->GetCellValue( cells3[i].GetRow(), cells3[i].GetCol() ) );
  172. }
  173. if( signals.size() < 1 )
  174. signals.push_back( m_grid->GetCellValue( m_menuRow, m_menuCol ) );
  175. auto addMeasurement =
  176. [this]( const wxString& cmd, wxString signal )
  177. {
  178. if( signal.EndsWith( _( " (phase)" ) ) )
  179. return;
  180. if( signal.EndsWith( _( " (gain)" ) ) || signal.EndsWith( _( " (amplitude)" ) ) )
  181. {
  182. signal = signal.Left( signal.length() - 7 );
  183. if( signal.Upper().StartsWith( wxS( "V(" ) ) )
  184. signal = wxS( "vdb" ) + signal.Mid( 1 );
  185. }
  186. m_parent->AddMeasurement( cmd + wxS( " " ) + signal );
  187. };
  188. if( event.GetId() == MYID_MEASURE_MIN )
  189. {
  190. for( const wxString& signal : signals )
  191. addMeasurement( wxS( "MIN" ), signal );
  192. }
  193. else if( event.GetId() == MYID_MEASURE_MAX )
  194. {
  195. for( const wxString& signal : signals )
  196. addMeasurement( wxS( "MAX" ), signal );
  197. }
  198. else if( event.GetId() == MYID_MEASURE_AVG )
  199. {
  200. for( const wxString& signal : signals )
  201. addMeasurement( wxS( "AVG" ), signal );
  202. }
  203. else if( event.GetId() == MYID_MEASURE_RMS )
  204. {
  205. for( const wxString& signal : signals )
  206. addMeasurement( wxS( "RMS" ), signal );
  207. }
  208. else if( event.GetId() == MYID_MEASURE_PP )
  209. {
  210. for( const wxString& signal : signals )
  211. addMeasurement( wxS( "PP" ), signal );
  212. }
  213. else if( event.GetId() == MYID_MEASURE_MIN_AT )
  214. {
  215. for( const wxString& signal : signals )
  216. addMeasurement( wxS( "MIN_AT" ), signal );
  217. }
  218. else if( event.GetId() == MYID_MEASURE_MAX_AT )
  219. {
  220. for( const wxString& signal : signals )
  221. addMeasurement( wxS( "MAX_AT" ), signal );
  222. }
  223. else if( event.GetId() == MYID_MEASURE_INTEGRAL )
  224. {
  225. for( const wxString& signal : signals )
  226. addMeasurement( wxS( "INTEG" ), signal );
  227. }
  228. else if( event.GetId() == MYID_FOURIER )
  229. {
  230. wxString title;
  231. wxString fundamental = wxT( "1K" );
  232. if( signals.size() == 1 )
  233. title.Printf( _( "Fourier Analysis of %s" ), signals[0] );
  234. else
  235. title = _( "Fourier Analyses of Multiple Signals" );
  236. WX_TEXT_ENTRY_DIALOG dlg( m_parent, _( "Fundamental frequency:" ), title, fundamental );
  237. if( dlg.ShowModal() != wxID_OK )
  238. return;
  239. if( !dlg.GetValue().IsEmpty() )
  240. fundamental = dlg.GetValue();
  241. for( const wxString& signal : signals )
  242. m_parent->DoFourier( signal, fundamental );
  243. }
  244. else if( event.GetId() == GRIDTRICKS_ID_COPY )
  245. {
  246. wxLogNull doNotLog; // disable logging of failed clipboard actions
  247. wxString txt;
  248. for( const wxString& signal : signals )
  249. {
  250. if( !txt.IsEmpty() )
  251. txt += '\r';
  252. txt += signal;
  253. }
  254. if( wxTheClipboard->Open() )
  255. {
  256. wxTheClipboard->SetData( new wxTextDataObject( txt ) );
  257. wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
  258. wxTheClipboard->Close();
  259. }
  260. }
  261. }
  262. class CURSORS_GRID_TRICKS : public GRID_TRICKS
  263. {
  264. public:
  265. CURSORS_GRID_TRICKS( SIMULATOR_FRAME_UI* aParent, WX_GRID* aGrid ) :
  266. GRID_TRICKS( aGrid ),
  267. m_parent( aParent ),
  268. m_menuRow( 0 ),
  269. m_menuCol( 0 )
  270. {}
  271. protected:
  272. void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
  273. void doPopupSelection( wxCommandEvent& event ) override;
  274. protected:
  275. SIMULATOR_FRAME_UI* m_parent;
  276. int m_menuRow;
  277. int m_menuCol;
  278. };
  279. void CURSORS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
  280. {
  281. m_menuRow = aEvent.GetRow();
  282. m_menuCol = aEvent.GetCol();
  283. if( m_menuCol == COL_CURSOR_X || m_menuCol == COL_CURSOR_Y )
  284. {
  285. wxString msg = m_grid->GetColLabelValue( m_menuCol );
  286. menu.Append( MYID_FORMAT_VALUE, wxString::Format( _( "Format %s..." ), msg ) );
  287. menu.AppendSeparator();
  288. }
  289. GRID_TRICKS::showPopupMenu( menu, aEvent );
  290. }
  291. void CURSORS_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
  292. {
  293. auto getSignalName =
  294. [this]( int row ) -> wxString
  295. {
  296. wxString signal = m_grid->GetCellValue( row, COL_CURSOR_SIGNAL );
  297. if( signal.EndsWith( "[2 - 1]" ) )
  298. signal = signal.Left( signal.length() - 7 );
  299. return signal;
  300. };
  301. if( event.GetId() == MYID_FORMAT_VALUE )
  302. {
  303. int axis = m_menuCol - COL_CURSOR_X;
  304. SPICE_VALUE_FORMAT format = m_parent->GetCursorFormat( m_menuRow, axis );
  305. DIALOG_SIM_FORMAT_VALUE formatDialog( m_parent, &format );
  306. if( formatDialog.ShowModal() == wxID_OK )
  307. {
  308. for( int row = 0; row < m_grid->GetNumberRows(); ++row )
  309. {
  310. if( getSignalName( row ) == getSignalName( m_menuRow ) )
  311. m_parent->SetCursorFormat( row, axis, format );
  312. }
  313. }
  314. }
  315. else
  316. {
  317. GRID_TRICKS::doPopupSelection( event );
  318. }
  319. }
  320. class MEASUREMENTS_GRID_TRICKS : public GRID_TRICKS
  321. {
  322. public:
  323. MEASUREMENTS_GRID_TRICKS( SIMULATOR_FRAME_UI* aParent, WX_GRID* aGrid ) :
  324. GRID_TRICKS( aGrid ),
  325. m_parent( aParent ),
  326. m_menuRow( 0 ),
  327. m_menuCol( 0 )
  328. {}
  329. protected:
  330. void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
  331. void doPopupSelection( wxCommandEvent& event ) override;
  332. protected:
  333. SIMULATOR_FRAME_UI* m_parent;
  334. int m_menuRow;
  335. int m_menuCol;
  336. };
  337. void MEASUREMENTS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
  338. {
  339. m_menuRow = aEvent.GetRow();
  340. m_menuCol = aEvent.GetCol();
  341. if( !( m_grid->IsInSelection( m_menuRow, m_menuCol ) ) )
  342. m_grid->ClearSelection();
  343. m_grid->SetGridCursor( m_menuRow, m_menuCol );
  344. if( m_menuCol == COL_MEASUREMENT_VALUE )
  345. menu.Append( MYID_FORMAT_VALUE, _( "Format Value..." ) );
  346. if( m_menuRow < ( m_grid->GetNumberRows() - 1 ) )
  347. menu.Append( MYID_DELETE_MEASUREMENT, _( "Delete Measurement" ) );
  348. menu.AppendSeparator();
  349. GRID_TRICKS::showPopupMenu( menu, aEvent );
  350. }
  351. void MEASUREMENTS_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
  352. {
  353. if( event.GetId() == MYID_FORMAT_VALUE )
  354. {
  355. SPICE_VALUE_FORMAT format = m_parent->GetMeasureFormat( m_menuRow );
  356. DIALOG_SIM_FORMAT_VALUE formatDialog( m_parent, &format );
  357. if( formatDialog.ShowModal() == wxID_OK )
  358. {
  359. m_parent->SetMeasureFormat( m_menuRow, format );
  360. m_parent->UpdateMeasurement( m_menuRow );
  361. m_parent->OnModify();
  362. }
  363. }
  364. else if( event.GetId() == MYID_DELETE_MEASUREMENT )
  365. {
  366. std::vector<int> measurements;
  367. wxGridCellCoordsArray cells1 = m_grid->GetSelectionBlockTopLeft();
  368. wxGridCellCoordsArray cells2 = m_grid->GetSelectionBlockBottomRight();
  369. for( size_t i = 0; i < cells1.Count(); i++ )
  370. {
  371. if( cells1[i].GetCol() == COL_MEASUREMENT )
  372. {
  373. for( int j = cells1[i].GetRow(); j < cells2[i].GetRow() + 1; j++ )
  374. measurements.push_back( j );
  375. }
  376. }
  377. wxGridCellCoordsArray cells3 = m_grid->GetSelectedCells();
  378. for( size_t i = 0; i < cells3.Count(); i++ )
  379. {
  380. if( cells3[i].GetCol() == COL_MEASUREMENT )
  381. measurements.push_back( cells3[i].GetRow() );
  382. }
  383. if( measurements.size() < 1 )
  384. measurements.push_back( m_menuRow );
  385. // When deleting a row, we'll change the indexes.
  386. // To avoid problems, we can start with the highest indexes.
  387. sort( measurements.begin(), measurements.end(), std::greater<>() );
  388. for( int row : measurements )
  389. m_parent->DeleteMeasurement( row );
  390. m_grid->ClearSelection();
  391. m_parent->OnModify();
  392. }
  393. else
  394. {
  395. GRID_TRICKS::doPopupSelection( event );
  396. }
  397. }
  398. class SUPPRESS_GRID_CELL_EVENTS
  399. {
  400. public:
  401. SUPPRESS_GRID_CELL_EVENTS( SIMULATOR_FRAME_UI* aFrame ) :
  402. m_frame( aFrame )
  403. {
  404. m_frame->m_SuppressGridEvents++;
  405. }
  406. ~SUPPRESS_GRID_CELL_EVENTS()
  407. {
  408. m_frame->m_SuppressGridEvents--;
  409. }
  410. private:
  411. SIMULATOR_FRAME_UI* m_frame;
  412. };
  413. #define ID_SIM_REFRESH 10207
  414. #define REFRESH_INTERVAL 50 // 20 frames/second.
  415. SIMULATOR_FRAME_UI::SIMULATOR_FRAME_UI( SIMULATOR_FRAME* aSimulatorFrame,
  416. SCH_EDIT_FRAME* aSchematicFrame ) :
  417. SIMULATOR_FRAME_UI_BASE( aSimulatorFrame ),
  418. m_SuppressGridEvents( 0 ),
  419. m_simulatorFrame( aSimulatorFrame ),
  420. m_schematicFrame( aSchematicFrame ),
  421. m_darkMode( true ),
  422. m_plotNumber( 0 ),
  423. m_refreshTimer( this, ID_SIM_REFRESH )
  424. {
  425. // Get the previous size and position of windows:
  426. LoadSettings( m_schematicFrame->eeconfig() );
  427. m_filter->SetHint( _( "Filter" ) );
  428. m_signalsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
  429. m_cursorsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
  430. m_measurementsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
  431. m_signalsGrid->PushEventHandler( new SIGNALS_GRID_TRICKS( this, m_signalsGrid ) );
  432. m_cursorsGrid->PushEventHandler( new CURSORS_GRID_TRICKS( this, m_cursorsGrid ) );
  433. m_measurementsGrid->PushEventHandler( new MEASUREMENTS_GRID_TRICKS( this, m_measurementsGrid ) );
  434. wxGridCellAttr* attr = new wxGridCellAttr;
  435. attr->SetReadOnly();
  436. m_signalsGrid->SetColAttr( COL_SIGNAL_NAME, attr );
  437. attr = new wxGridCellAttr;
  438. attr->SetReadOnly();
  439. m_cursorsGrid->SetColAttr( COL_CURSOR_NAME, attr );
  440. attr = new wxGridCellAttr;
  441. attr->SetReadOnly();
  442. m_cursorsGrid->SetColAttr( COL_CURSOR_SIGNAL, attr );
  443. attr = new wxGridCellAttr;
  444. attr->SetReadOnly();
  445. m_cursorsGrid->SetColAttr( COL_CURSOR_Y, attr );
  446. for( int cursorId = 0; cursorId < 3; ++cursorId )
  447. {
  448. m_cursorFormats[ cursorId ][ 0 ] = { 3, wxS( "~s" ) };
  449. m_cursorFormats[ cursorId ][ 1 ] = { 3, wxS( "~V" ) };
  450. }
  451. attr = new wxGridCellAttr;
  452. attr->SetReadOnly();
  453. m_measurementsGrid->SetColAttr( COL_MEASUREMENT_VALUE, attr );
  454. // Prepare the color list to plot traces
  455. SIM_PLOT_COLORS::FillDefaultColorList( m_darkMode );
  456. Bind( EVT_SIM_CURSOR_UPDATE, &SIMULATOR_FRAME_UI::onPlotCursorUpdate, this );
  457. Bind( wxEVT_TIMER,
  458. [&]( wxTimerEvent& aEvent )
  459. {
  460. OnSimRefresh( false );
  461. if( m_simulatorFrame->GetSimulator()->IsRunning() )
  462. m_refreshTimer.Start( REFRESH_INTERVAL, wxTIMER_ONE_SHOT );
  463. },
  464. m_refreshTimer.GetId() );
  465. #ifndef wxHAS_NATIVE_TABART
  466. // Default non-native tab art has ugly gradients we don't want
  467. m_plotNotebook->SetArtProvider( new wxAuiSimpleTabArt() );
  468. #endif
  469. }
  470. SIMULATOR_FRAME_UI::~SIMULATOR_FRAME_UI()
  471. {
  472. // Delete the GRID_TRICKS.
  473. m_signalsGrid->PopEventHandler( true );
  474. m_cursorsGrid->PopEventHandler( true );
  475. m_measurementsGrid->PopEventHandler( true );
  476. }
  477. void SIMULATOR_FRAME_UI::ShowChangedLanguage()
  478. {
  479. for( int ii = 0; ii < (int) m_plotNotebook->GetPageCount(); ++ii )
  480. {
  481. if( SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( ii ) ) )
  482. {
  483. simTab->OnLanguageChanged();
  484. wxString pageTitle( simulator()->TypeToName( simTab->GetSimType(), true ) );
  485. pageTitle.Prepend( wxString::Format( _( "Analysis %u - " ), ii+1 /* 1-based */ ) );
  486. m_plotNotebook->SetPageText( ii, pageTitle );
  487. }
  488. }
  489. m_filter->SetHint( _( "Filter" ) );
  490. m_signalsGrid->SetColLabelValue( COL_SIGNAL_NAME, _( "Signal" ) );
  491. m_signalsGrid->SetColLabelValue( COL_SIGNAL_SHOW, _( "Plot" ) );
  492. m_signalsGrid->SetColLabelValue( COL_SIGNAL_COLOR, _( "Color" ) );
  493. m_signalsGrid->SetColLabelValue( COL_CURSOR_1, _( "Cursor 1" ) );
  494. m_signalsGrid->SetColLabelValue( COL_CURSOR_2, _( "Cursor 2" ) );
  495. m_cursorsGrid->SetColLabelValue( COL_CURSOR_NAME, _( "Cursor" ) );
  496. m_cursorsGrid->SetColLabelValue( COL_CURSOR_SIGNAL, _( "Signal" ) );
  497. m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, _( "Time" ) );
  498. m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, _( "Value" ) );
  499. updatePlotCursors();
  500. for( TUNER_SLIDER* tuner : m_tuners )
  501. tuner->ShowChangedLanguage();
  502. }
  503. void SIMULATOR_FRAME_UI::LoadSettings( EESCHEMA_SETTINGS* aCfg )
  504. {
  505. const EESCHEMA_SETTINGS::SIMULATOR& settings = aCfg->m_Simulator;
  506. // Read subwindows sizes (should be > 0 )
  507. m_splitterLeftRightSashPosition = settings.view.plot_panel_width;
  508. m_splitterPlotAndConsoleSashPosition = settings.view.plot_panel_height;
  509. m_splitterSignalsSashPosition = settings.view.signal_panel_height;
  510. m_splitterCursorsSashPosition = settings.view.cursors_panel_height;
  511. m_splitterTuneValuesSashPosition = settings.view.measurements_panel_height;
  512. m_darkMode = !settings.view.white_background;
  513. m_preferences = settings.preferences;
  514. }
  515. void SIMULATOR_FRAME_UI::SaveSettings( EESCHEMA_SETTINGS* aCfg )
  516. {
  517. EESCHEMA_SETTINGS::SIMULATOR& settings = aCfg->m_Simulator;
  518. settings.view.plot_panel_width = m_splitterLeftRight->GetSashPosition();
  519. settings.view.plot_panel_height = m_splitterPlotAndConsole->GetSashPosition();
  520. settings.view.signal_panel_height = m_splitterSignals->GetSashPosition();
  521. settings.view.cursors_panel_height = m_splitterCursors->GetSashPosition();
  522. settings.view.measurements_panel_height = m_splitterMeasurements->GetSashPosition();
  523. settings.view.white_background = !m_darkMode;
  524. }
  525. void SIMULATOR_FRAME_UI::ApplyPreferences( const SIM_PREFERENCES& aPrefs )
  526. {
  527. m_preferences = aPrefs;
  528. const std::size_t pageCount = m_plotNotebook->GetPageCount();
  529. for( std::size_t i = 0; i < pageCount; ++i )
  530. {
  531. wxWindow* page = m_plotNotebook->GetPage( i );
  532. auto simTab = dynamic_cast<SIM_TAB*>( page );
  533. wxASSERT( simTab != nullptr );
  534. simTab->ApplyPreferences( aPrefs );
  535. }
  536. }
  537. void SIMULATOR_FRAME_UI::InitWorkbook()
  538. {
  539. if( !simulator()->Settings()->GetWorkbookFilename().IsEmpty() )
  540. {
  541. wxFileName filename = simulator()->Settings()->GetWorkbookFilename();
  542. filename.SetPath( m_schematicFrame->Prj().GetProjectPath() );
  543. if( !LoadWorkbook( filename.GetFullPath() ) )
  544. simulator()->Settings()->SetWorkbookFilename( "" );
  545. }
  546. else if( m_simulatorFrame->LoadSimulator( wxEmptyString, 0 ) )
  547. {
  548. wxString schTextSimCommand = circuitModel()->GetSchTextSimCommand();
  549. if( !schTextSimCommand.IsEmpty() )
  550. {
  551. SIM_TAB* simTab = NewSimTab( schTextSimCommand );
  552. simTab->SetSimOptions( NETLIST_EXPORTER_SPICE::OPTION_DEFAULT_FLAGS );
  553. }
  554. rebuildSignalsList();
  555. rebuildSignalsGrid( m_filter->GetValue() );
  556. }
  557. }
  558. void SIMULATOR_FRAME_UI::SetSubWindowsSashSize()
  559. {
  560. if( m_splitterLeftRightSashPosition > 0 )
  561. m_splitterLeftRight->SetSashPosition( m_splitterLeftRightSashPosition );
  562. if( m_splitterPlotAndConsoleSashPosition > 0 )
  563. m_splitterPlotAndConsole->SetSashPosition( m_splitterPlotAndConsoleSashPosition );
  564. if( m_splitterSignalsSashPosition > 0 )
  565. m_splitterSignals->SetSashPosition( m_splitterSignalsSashPosition );
  566. if( m_splitterCursorsSashPosition > 0 )
  567. m_splitterCursors->SetSashPosition( m_splitterCursorsSashPosition );
  568. if( m_splitterTuneValuesSashPosition > 0 )
  569. m_splitterMeasurements->SetSashPosition( m_splitterTuneValuesSashPosition );
  570. }
  571. void sortSignals( std::vector<wxString>& signals )
  572. {
  573. std::sort( signals.begin(), signals.end(),
  574. []( const wxString& lhs, const wxString& rhs )
  575. {
  576. // Sort voltages first
  577. if( lhs.Upper().StartsWith( 'V' ) && !rhs.Upper().StartsWith( 'V' ) )
  578. return true;
  579. else if( !lhs.Upper().StartsWith( 'V' ) && rhs.Upper().StartsWith( 'V' ) )
  580. return false;
  581. return StrNumCmp( lhs, rhs, true /* ignore case */ ) < 0;
  582. } );
  583. }
  584. void SIMULATOR_FRAME_UI::rebuildSignalsGrid( wxString aFilter )
  585. {
  586. SUPPRESS_GRID_CELL_EVENTS raii( this );
  587. m_signalsGrid->ClearRows();
  588. SIM_PLOT_TAB* plotPanel = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
  589. if( !plotPanel )
  590. return;
  591. SIM_TYPE simType = plotPanel->GetSimType();
  592. std::vector<wxString> signals;
  593. if( plotPanel->GetSimType() == ST_FFT )
  594. {
  595. wxStringTokenizer tokenizer( plotPanel->GetSimCommand(), wxT( " \t\r\n" ), wxTOKEN_STRTOK );
  596. while( tokenizer.HasMoreTokens() && tokenizer.GetNextToken().Lower() != wxT( "fft" ) )
  597. {};
  598. while( tokenizer.HasMoreTokens() )
  599. signals.emplace_back( tokenizer.GetNextToken() );
  600. }
  601. else
  602. {
  603. // NB: m_signals are already broken out into gain/phase, but m_userDefinedSignals are
  604. // as the user typed them
  605. for( const wxString& signal : m_signals )
  606. signals.push_back( signal );
  607. for( const auto& [ id, signal ] : m_userDefinedSignals )
  608. {
  609. if( simType == ST_AC )
  610. {
  611. signals.push_back( signal + _( " (gain)" ) );
  612. signals.push_back( signal + _( " (phase)" ) );
  613. }
  614. else if( simType == ST_SP )
  615. {
  616. signals.push_back( signal + _( " (amplitude)" ) );
  617. signals.push_back( signal + _( " (phase)" ) );
  618. }
  619. else
  620. {
  621. signals.push_back( signal );
  622. }
  623. }
  624. sortSignals( signals );
  625. }
  626. if( aFilter.IsEmpty() )
  627. aFilter = wxS( "*" );
  628. EDA_COMBINED_MATCHER matcher( aFilter.Upper(), CTX_SIGNAL );
  629. int row = 0;
  630. for( const wxString& signal : signals )
  631. {
  632. if( matcher.Find( signal.Upper() ) )
  633. {
  634. int traceType = SPT_UNKNOWN;
  635. wxString vectorName = vectorNameFromSignalName( plotPanel, signal, &traceType );
  636. TRACE* trace = plotPanel->GetTrace( vectorName, traceType );
  637. m_signalsGrid->AppendRows( 1 );
  638. m_signalsGrid->SetCellValue( row, COL_SIGNAL_NAME, signal );
  639. wxGridCellAttr* attr = new wxGridCellAttr;
  640. attr->SetRenderer( new wxGridCellBoolRenderer() );
  641. attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
  642. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  643. m_signalsGrid->SetAttr( row, COL_SIGNAL_SHOW, attr );
  644. if( !trace )
  645. {
  646. attr = new wxGridCellAttr;
  647. attr->SetReadOnly();
  648. m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
  649. m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, wxEmptyString );
  650. attr = new wxGridCellAttr;
  651. attr->SetReadOnly();
  652. m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
  653. attr = new wxGridCellAttr;
  654. attr->SetReadOnly();
  655. m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
  656. }
  657. else
  658. {
  659. m_signalsGrid->SetCellValue( row, COL_SIGNAL_SHOW, wxS( "1" ) );
  660. attr = new wxGridCellAttr;
  661. attr->SetRenderer( new GRID_CELL_COLOR_RENDERER( this ) );
  662. attr->SetEditor( new GRID_CELL_COLOR_SELECTOR( this, m_signalsGrid ) );
  663. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  664. m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
  665. KIGFX::COLOR4D color( trace->GetPen().GetColour() );
  666. m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, color.ToCSSString() );
  667. attr = new wxGridCellAttr;
  668. attr->SetRenderer( new wxGridCellBoolRenderer() );
  669. attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
  670. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  671. m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
  672. m_signalsGrid->SetCellValue( row, COL_CURSOR_1, trace->GetCursor( 1 ) ? "1" : "0" );
  673. attr = new wxGridCellAttr;
  674. attr->SetRenderer( new wxGridCellBoolRenderer() );
  675. attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
  676. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  677. m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
  678. m_signalsGrid->SetCellValue( row, COL_CURSOR_2, trace->GetCursor( 2 ) ? "1" : "0" );
  679. }
  680. row++;
  681. }
  682. }
  683. }
  684. void SIMULATOR_FRAME_UI::rebuildSignalsList()
  685. {
  686. m_signals.clear();
  687. int options = m_simulatorFrame->GetCurrentOptions();
  688. SIM_TYPE simType = m_simulatorFrame->GetCurrentSimType();
  689. wxString unconnected = wxString( wxS( "unconnected-(" ) );
  690. if( simType == ST_UNKNOWN )
  691. simType = ST_TRAN;
  692. unconnected.Replace( '(', '_' ); // Convert to SPICE markup
  693. auto addSignal =
  694. [&]( const wxString& aSignalName )
  695. {
  696. if( simType == ST_AC )
  697. {
  698. m_signals.push_back( aSignalName + _( " (gain)" ) );
  699. m_signals.push_back( aSignalName + _( " (phase)" ) );
  700. }
  701. else if( simType == ST_SP )
  702. {
  703. m_signals.push_back( aSignalName + _( " (amplitude)" ) );
  704. m_signals.push_back( aSignalName + _( " (phase)" ) );
  705. }
  706. else
  707. {
  708. m_signals.push_back( aSignalName );
  709. }
  710. };
  711. if( ( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_VOLTAGES )
  712. && ( simType == ST_TRAN || simType == ST_DC || simType == ST_AC || simType == ST_FFT) )
  713. {
  714. for( const wxString& net : circuitModel()->GetNets() )
  715. {
  716. // netnames are escaped (can contain "{slash}" for '/') Unscape them:
  717. wxString netname = UnescapeString( net );
  718. NETLIST_EXPORTER_SPICE::ConvertToSpiceMarkup( &netname );
  719. if( netname == "GND" || netname == "0" || netname.StartsWith( unconnected ) )
  720. continue;
  721. m_netnames.emplace_back( netname );
  722. addSignal( wxString::Format( wxS( "V(%s)" ), netname ) );
  723. }
  724. }
  725. if( ( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_CURRENTS )
  726. && ( simType == ST_TRAN || simType == ST_DC || simType == ST_AC ) )
  727. {
  728. for( const SPICE_ITEM& item : circuitModel()->GetItems() )
  729. {
  730. // Add all possible currents for the device.
  731. for( const std::string& name : item.model->SpiceGenerator().CurrentNames( item ) )
  732. addSignal( name );
  733. }
  734. }
  735. if( ( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_DISSIPATIONS )
  736. && ( simType == ST_TRAN || simType == ST_DC ) )
  737. {
  738. for( const SPICE_ITEM& item : circuitModel()->GetItems() )
  739. {
  740. if( item.model->GetPinCount() >= 2 )
  741. {
  742. wxString name = item.model->SpiceGenerator().ItemName( item );
  743. addSignal( wxString::Format( wxS( "P(%s)" ), name ) );
  744. }
  745. }
  746. }
  747. if( simType == ST_NOISE )
  748. {
  749. addSignal( wxS( "inoise_spectrum" ) );
  750. addSignal( wxS( "onoise_spectrum" ) );
  751. }
  752. if( simType == ST_SP )
  753. {
  754. std::vector<std::string> portnums;
  755. for( const SPICE_ITEM& item : circuitModel()->GetItems() )
  756. {
  757. wxString name = item.model->SpiceGenerator().ItemName( item );
  758. // We are only looking for voltage sources in .SP mode
  759. if( !name.StartsWith( "V" ) )
  760. continue;
  761. std::string portnum = "";
  762. if( const SIM_MODEL::PARAM* portnum_param = item.model->FindParam( "portnum" ) )
  763. portnum = SIM_VALUE::ToSpice( portnum_param->value );
  764. if( portnum != "" )
  765. portnums.push_back( portnum );
  766. }
  767. for( const std::string& portnum1 : portnums )
  768. {
  769. for( const std::string& portnum2 : portnums )
  770. {
  771. addSignal( wxString::Format( wxS( "S_%s_%s" ), portnum1, portnum2 ) );
  772. }
  773. }
  774. }
  775. // Add .SAVE and .PROBE directives
  776. for( const wxString& directive : circuitModel()->GetDirectives() )
  777. {
  778. wxStringTokenizer directivesTokenizer( directive, wxT( "\r\n" ), wxTOKEN_STRTOK );
  779. while( directivesTokenizer.HasMoreTokens() )
  780. {
  781. wxString line = directivesTokenizer.GetNextToken().Upper();
  782. wxString directiveParams;
  783. if( line.StartsWith( wxS( ".SAVE" ), &directiveParams )
  784. || line.StartsWith( wxS( ".PROBE" ), &directiveParams ) )
  785. {
  786. wxStringTokenizer paramsTokenizer( directiveParams, wxT( " \t" ), wxTOKEN_STRTOK );
  787. while( paramsTokenizer.HasMoreTokens() )
  788. addSignal( paramsTokenizer.GetNextToken() );
  789. }
  790. }
  791. }
  792. }
  793. SIM_TAB* SIMULATOR_FRAME_UI::NewSimTab( const wxString& aSimCommand )
  794. {
  795. SIM_TAB* simTab = nullptr;
  796. SIM_TYPE simType = SPICE_CIRCUIT_MODEL::CommandToSimType( aSimCommand );
  797. if( SIM_TAB::IsPlottable( simType ) )
  798. {
  799. SIM_PLOT_TAB* panel = new SIM_PLOT_TAB( aSimCommand, m_plotNotebook );
  800. simTab = panel;
  801. panel->ApplyPreferences( m_preferences );
  802. }
  803. else
  804. {
  805. simTab = new SIM_NOPLOT_TAB( aSimCommand, m_plotNotebook );
  806. }
  807. wxString pageTitle( simulator()->TypeToName( simType, true ) );
  808. pageTitle.Prepend( wxString::Format( _( "Analysis %u - " ), (unsigned int) ++m_plotNumber ) );
  809. m_plotNotebook->AddPage( simTab, pageTitle, true );
  810. return simTab;
  811. }
  812. void SIMULATOR_FRAME_UI::OnFilterText( wxCommandEvent& aEvent )
  813. {
  814. rebuildSignalsGrid( m_filter->GetValue() );
  815. }
  816. void SIMULATOR_FRAME_UI::OnFilterMouseMoved( wxMouseEvent& aEvent )
  817. {
  818. #if defined( __WXOSX__ ) || wxCHECK_VERSION( 3, 3, 0 ) // Doesn't work properly on other ports
  819. wxPoint pos = aEvent.GetPosition();
  820. wxRect ctrlRect = m_filter->GetScreenRect();
  821. int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
  822. if( m_filter->IsSearchButtonVisible() && pos.x < buttonWidth )
  823. SetCursor( wxCURSOR_ARROW );
  824. else if( m_filter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
  825. SetCursor( wxCURSOR_ARROW );
  826. else
  827. SetCursor( wxCURSOR_IBEAM );
  828. #endif
  829. }
  830. wxString vectorNameFromSignalId( int aUserDefinedSignalId )
  831. {
  832. return wxString::Format( wxS( "user%d" ), aUserDefinedSignalId );
  833. }
  834. /**
  835. * For user-defined signals we display the user-oriented signal name such as "V(out)-V(in)",
  836. * but the simulator vector we actually have to plot will be "user0" or some-such.
  837. */
  838. wxString SIMULATOR_FRAME_UI::vectorNameFromSignalName( SIM_PLOT_TAB* aPlotTab,
  839. const wxString& aSignalName,
  840. int* aTraceType )
  841. {
  842. std::map<wxString, int> suffixes;
  843. suffixes[ _( " (amplitude)" ) ] = SPT_SP_AMP;
  844. suffixes[ _( " (gain)" ) ] = SPT_AC_GAIN;
  845. suffixes[ _( " (phase)" ) ] = SPT_AC_PHASE;
  846. if( aTraceType )
  847. {
  848. if( aPlotTab && aPlotTab->GetSimType() == ST_NOISE )
  849. {
  850. if( getNoiseSource().Upper().StartsWith( 'I' ) )
  851. *aTraceType = SPT_CURRENT;
  852. else
  853. *aTraceType = SPT_VOLTAGE;
  854. }
  855. else
  856. {
  857. wxUniChar firstChar = aSignalName.Upper()[0];
  858. if( firstChar == 'V' )
  859. *aTraceType = SPT_VOLTAGE;
  860. else if( firstChar == 'I' )
  861. *aTraceType = SPT_CURRENT;
  862. else if( firstChar == 'P' )
  863. *aTraceType = SPT_POWER;
  864. }
  865. }
  866. wxString suffix;
  867. wxString name = aSignalName;
  868. for( const auto& [ candidate, type ] : suffixes )
  869. {
  870. if( name.EndsWith( candidate ) )
  871. {
  872. name = name.Left( name.Length() - candidate.Length() );
  873. if( aTraceType )
  874. *aTraceType |= type;
  875. break;
  876. }
  877. }
  878. for( const auto& [ id, signal ] : m_userDefinedSignals )
  879. {
  880. if( name == signal )
  881. return vectorNameFromSignalId( id );
  882. }
  883. return name;
  884. };
  885. void SIMULATOR_FRAME_UI::onSignalsGridCellChanged( wxGridEvent& aEvent )
  886. {
  887. if( m_SuppressGridEvents > 0 )
  888. return;
  889. SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
  890. if( !plotTab )
  891. return;
  892. int row = aEvent.GetRow();
  893. int col = aEvent.GetCol();
  894. wxString text = m_signalsGrid->GetCellValue( row, col );
  895. wxString signalName = m_signalsGrid->GetCellValue( row, COL_SIGNAL_NAME );
  896. int traceType = SPT_UNKNOWN;
  897. wxString vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
  898. if( col == COL_SIGNAL_SHOW )
  899. {
  900. if( text == wxS( "1" ) )
  901. updateTrace( vectorName, traceType, plotTab );
  902. else
  903. plotTab->DeleteTrace( vectorName, traceType );
  904. plotTab->GetPlotWin()->UpdateAll();
  905. // Update enabled/visible states of other controls
  906. updateSignalsGrid();
  907. updatePlotCursors();
  908. OnModify();
  909. }
  910. else if( col == COL_SIGNAL_COLOR )
  911. {
  912. KIGFX::COLOR4D color( m_signalsGrid->GetCellValue( row, COL_SIGNAL_COLOR ) );
  913. TRACE* trace = plotTab->GetTrace( vectorName, traceType );
  914. if( trace )
  915. {
  916. trace->SetTraceColour( color.ToColour() );
  917. plotTab->UpdateTraceStyle( trace );
  918. plotTab->UpdatePlotColors();
  919. OnModify();
  920. }
  921. }
  922. else if( col == COL_CURSOR_1 || col == COL_CURSOR_2 )
  923. {
  924. int id = col == COL_CURSOR_1 ? 1 : 2;
  925. TRACE* activeTrace = nullptr;
  926. if( text == wxS( "1" ) )
  927. {
  928. signalName = m_signalsGrid->GetCellValue( row, COL_SIGNAL_NAME );
  929. vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
  930. activeTrace = plotTab->GetTrace( vectorName, traceType );
  931. if( activeTrace )
  932. plotTab->EnableCursor( activeTrace, id, signalName );
  933. OnModify();
  934. }
  935. // Turn off cursor on other signals.
  936. for( const auto& [name, trace] : plotTab->GetTraces() )
  937. {
  938. if( trace != activeTrace && trace->HasCursor( id ) )
  939. {
  940. plotTab->DisableCursor( trace, id );
  941. OnModify();
  942. }
  943. }
  944. // Update cursor checkboxes (which are really radio buttons)
  945. updateSignalsGrid();
  946. }
  947. }
  948. void SIMULATOR_FRAME_UI::onCursorsGridCellChanged( wxGridEvent& aEvent )
  949. {
  950. if( m_SuppressGridEvents > 0 )
  951. return;
  952. SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
  953. if( !plotTab )
  954. return;
  955. int row = aEvent.GetRow();
  956. int col = aEvent.GetCol();
  957. wxString text = m_cursorsGrid->GetCellValue( row, col );
  958. wxString cursorName = m_cursorsGrid->GetCellValue( row, COL_CURSOR_NAME );
  959. if( col == COL_CURSOR_X )
  960. {
  961. CURSOR* cursor1 = nullptr;
  962. CURSOR* cursor2 = nullptr;
  963. for( const auto& [name, trace] : plotTab->GetTraces() )
  964. {
  965. if( CURSOR* cursor = trace->GetCursor( 1 ) )
  966. cursor1 = cursor;
  967. if( CURSOR* cursor = trace->GetCursor( 2 ) )
  968. cursor2 = cursor;
  969. }
  970. double value = SPICE_VALUE( text ).ToDouble();
  971. if( cursorName == wxS( "1" ) && cursor1 )
  972. cursor1->SetCoordX( value );
  973. else if( cursorName == wxS( "2" ) && cursor2 )
  974. cursor2->SetCoordX( value );
  975. else if( cursorName == _( "Diff" ) && cursor1 && cursor2 )
  976. cursor2->SetCoordX( cursor1->GetCoords().x + value );
  977. updatePlotCursors();
  978. OnModify();
  979. }
  980. else
  981. {
  982. wxFAIL_MSG( wxT( "All other columns are supposed to be read-only!" ) );
  983. }
  984. }
  985. SPICE_VALUE_FORMAT SIMULATOR_FRAME_UI::GetMeasureFormat( int aRow ) const
  986. {
  987. SPICE_VALUE_FORMAT result;
  988. result.FromString( m_measurementsGrid->GetCellValue( aRow, COL_MEASUREMENT_FORMAT ) );
  989. return result;
  990. }
  991. void SIMULATOR_FRAME_UI::SetMeasureFormat( int aRow, const SPICE_VALUE_FORMAT& aFormat )
  992. {
  993. m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_FORMAT, aFormat.ToString() );
  994. }
  995. void SIMULATOR_FRAME_UI::DeleteMeasurement( int aRow )
  996. {
  997. if( aRow < ( m_measurementsGrid->GetNumberRows() - 1 ) )
  998. m_measurementsGrid->DeleteRows( aRow, 1 );
  999. }
  1000. void SIMULATOR_FRAME_UI::onMeasurementsGridCellChanged( wxGridEvent& aEvent )
  1001. {
  1002. SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
  1003. if( !plotTab )
  1004. return;
  1005. int row = aEvent.GetRow();
  1006. int col = aEvent.GetCol();
  1007. wxString text = m_measurementsGrid->GetCellValue( row, col );
  1008. if( col == COL_MEASUREMENT )
  1009. {
  1010. UpdateMeasurement( row );
  1011. updateMeasurementsFromGrid();
  1012. OnModify();
  1013. }
  1014. else
  1015. {
  1016. wxFAIL_MSG( wxT( "All other columns are supposed to be read-only!" ) );
  1017. }
  1018. // Always leave a single empty row for type-in
  1019. int rowCount = (int) m_measurementsGrid->GetNumberRows();
  1020. int emptyRows = 0;
  1021. for( row = rowCount - 1; row >= 0; row-- )
  1022. {
  1023. if( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
  1024. emptyRows++;
  1025. else
  1026. break;
  1027. }
  1028. if( emptyRows > 1 )
  1029. {
  1030. int killRows = emptyRows - 1;
  1031. m_measurementsGrid->DeleteRows( rowCount - killRows, killRows );
  1032. }
  1033. else if( emptyRows == 0 )
  1034. {
  1035. m_measurementsGrid->AppendRows( 1 );
  1036. }
  1037. }
  1038. void SIMULATOR_FRAME_UI::OnUpdateUI( wxUpdateUIEvent& event )
  1039. {
  1040. if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
  1041. {
  1042. if( plotTab->GetLegendPosition() != plotTab->m_LastLegendPosition )
  1043. {
  1044. plotTab->m_LastLegendPosition = plotTab->GetLegendPosition();
  1045. OnModify();
  1046. }
  1047. }
  1048. }
  1049. /**
  1050. * The user measurement looks something like:
  1051. * MAX V(out)
  1052. *
  1053. * We need to send ngspice a "MEAS" command with the analysis type, an output variable name,
  1054. * and the signal name. For our example above, this looks something like:
  1055. * MEAS TRAN meas_result_0 MAX V(out)
  1056. *
  1057. * This is also a good time to harvest the signal name prefix so we know what units to show on
  1058. * the result. For instance, for:
  1059. * MAX P(out)
  1060. * we want to show:
  1061. * 15W
  1062. */
  1063. void SIMULATOR_FRAME_UI::UpdateMeasurement( int aRow )
  1064. {
  1065. static wxRegEx measureParamsRegEx( wxT( "^"
  1066. " *"
  1067. "([a-zA-Z_]+)"
  1068. " +"
  1069. "([a-zA-Z]*)\\(([^\\)]+)\\)" ) );
  1070. SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
  1071. if( !plotTab )
  1072. return;
  1073. wxString text = m_measurementsGrid->GetCellValue( aRow, COL_MEASUREMENT );
  1074. if( text.IsEmpty() )
  1075. {
  1076. m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_VALUE, wxEmptyString );
  1077. return;
  1078. }
  1079. wxString simType = simulator()->TypeToName( plotTab->GetSimType(), true );
  1080. wxString resultName = wxString::Format( wxS( "meas_result_%u" ), aRow );
  1081. wxString result = wxS( "?" );
  1082. if( measureParamsRegEx.Matches( text ) )
  1083. {
  1084. wxString func = measureParamsRegEx.GetMatch( text, 1 ).Upper();
  1085. wxString signalType = measureParamsRegEx.GetMatch( text, 2 ).Upper();
  1086. wxString deviceName = measureParamsRegEx.GetMatch( text, 3 );
  1087. wxString units;
  1088. SPICE_VALUE_FORMAT fmt = GetMeasureFormat( aRow );
  1089. if( signalType.EndsWith( wxS( "DB" ) ) )
  1090. {
  1091. units = wxS( "dB" );
  1092. }
  1093. else if( signalType.StartsWith( 'I' ) )
  1094. {
  1095. units = wxS( "A" );
  1096. }
  1097. else if( signalType.StartsWith( 'P' ) )
  1098. {
  1099. units = wxS( "W" );
  1100. // Our syntax is different from ngspice for power signals
  1101. text = func + " " + deviceName + ":power";
  1102. }
  1103. else
  1104. {
  1105. units = wxS( "V" );
  1106. }
  1107. if( func.EndsWith( wxS( "_AT" ) ) )
  1108. {
  1109. if( plotTab->GetSimType() == ST_AC || plotTab->GetSimType() == ST_SP )
  1110. units = wxS( "Hz" );
  1111. else
  1112. units = wxS( "s" );
  1113. }
  1114. else if( func.StartsWith( wxS( "INTEG" ) ) )
  1115. {
  1116. switch( plotTab->GetSimType() )
  1117. {
  1118. case ST_TRAN:
  1119. if ( signalType.StartsWith( 'P' ) )
  1120. units = wxS( "J" );
  1121. else
  1122. units += wxS( ".s" );
  1123. break;
  1124. case ST_AC:
  1125. case ST_SP:
  1126. case ST_DISTO:
  1127. case ST_NOISE:
  1128. case ST_FFT:
  1129. case ST_SENS: // If there is a vector, it is frequency
  1130. units += wxS( "·Hz" );
  1131. break;
  1132. case ST_DC: // Could be a lot of things : V, A, deg C, ohm, ...
  1133. case ST_OP: // There is no vector for integration
  1134. case ST_PZ: // There is no vector for integration
  1135. case ST_TF: // There is no vector for integration
  1136. default:
  1137. units += wxS( "·?" );
  1138. break;
  1139. }
  1140. }
  1141. fmt.UpdateUnits( units );
  1142. SetMeasureFormat( aRow, fmt );
  1143. updateMeasurementsFromGrid();
  1144. }
  1145. if( m_simulatorFrame->SimFinished() )
  1146. {
  1147. wxString cmd = wxString::Format( wxS( "meas %s %s %s" ), simType, resultName, text );
  1148. simulator()->Command( "echo " + cmd.ToStdString() );
  1149. simulator()->Command( cmd.ToStdString() );
  1150. std::vector<double> resultVec = simulator()->GetGainVector( resultName.ToStdString() );
  1151. if( resultVec.size() > 0 )
  1152. result = SPICE_VALUE( resultVec[0] ).ToString( GetMeasureFormat( aRow ) );
  1153. }
  1154. m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_VALUE, result );
  1155. }
  1156. void SIMULATOR_FRAME_UI::AddTuner( const SCH_SHEET_PATH& aSheetPath, SCH_SYMBOL* aSymbol )
  1157. {
  1158. SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
  1159. if( !plotTab )
  1160. return;
  1161. wxString ref = aSymbol->GetRef( &aSheetPath );
  1162. // Do not add multiple instances for the same component.
  1163. for( TUNER_SLIDER* tuner : m_tuners )
  1164. {
  1165. if( tuner->GetSymbolRef() == ref )
  1166. return;
  1167. }
  1168. const SPICE_ITEM* item = GetExporter()->FindItem( ref );
  1169. // Do nothing if the symbol is not tunable.
  1170. if( !item || !item->model->GetTunerParam() )
  1171. return;
  1172. try
  1173. {
  1174. TUNER_SLIDER* tuner = new TUNER_SLIDER( this, m_panelTuners, aSheetPath, aSymbol );
  1175. m_sizerTuners->Add( tuner );
  1176. m_tuners.push_back( tuner );
  1177. m_panelTuners->Layout();
  1178. OnModify();
  1179. }
  1180. catch( const KI_PARAM_ERROR& e )
  1181. {
  1182. DisplayErrorMessage( nullptr, e.What() );
  1183. }
  1184. }
  1185. void SIMULATOR_FRAME_UI::UpdateTunerValue( const SCH_SHEET_PATH& aSheetPath, const KIID& aSymbol,
  1186. const wxString& aRef, const wxString& aValue )
  1187. {
  1188. SCH_ITEM* item = aSheetPath.GetItem( aSymbol );
  1189. SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( item );
  1190. if( !symbol )
  1191. {
  1192. DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( " " )
  1193. + wxString::Format( _( "%s not found" ), aRef ) );
  1194. return;
  1195. }
  1196. NULL_REPORTER devnull;
  1197. SIM_LIB_MGR mgr( &m_schematicFrame->Prj() );
  1198. std::vector<EMBEDDED_FILES*> embeddedFilesStack;
  1199. embeddedFilesStack.push_back( m_schematicFrame->Schematic().GetEmbeddedFiles() );
  1200. if( EMBEDDED_FILES* symbolEmbeddedFiles = symbol->GetEmbeddedFiles() )
  1201. embeddedFilesStack.push_back( symbolEmbeddedFiles );
  1202. mgr.SetFilesStack( embeddedFilesStack );
  1203. SIM_MODEL& model = mgr.CreateModel( &aSheetPath, *symbol, true, 0, devnull ).model;
  1204. const SIM_MODEL::PARAM* tunerParam = model.GetTunerParam();
  1205. if( !tunerParam )
  1206. {
  1207. DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( " " )
  1208. + wxString::Format( _( "%s is not tunable" ), aRef ) );
  1209. return;
  1210. }
  1211. model.SetParamValue( tunerParam->info.name, std::string( aValue.ToUTF8() ) );
  1212. model.WriteFields( symbol->GetFields() );
  1213. m_schematicFrame->UpdateItem( symbol, false, true );
  1214. m_schematicFrame->OnModify();
  1215. }
  1216. void SIMULATOR_FRAME_UI::RemoveTuner( TUNER_SLIDER* aTuner )
  1217. {
  1218. m_tuners.remove( aTuner );
  1219. aTuner->Destroy();
  1220. m_panelTuners->Layout();
  1221. OnModify();
  1222. }
  1223. void SIMULATOR_FRAME_UI::AddMeasurement( const wxString& aCmd )
  1224. {
  1225. // -1 because the last one is for user input
  1226. for( int i = 0; i < m_measurementsGrid->GetNumberRows(); i++ )
  1227. {
  1228. if ( m_measurementsGrid->GetCellValue( i, COL_MEASUREMENT ) == aCmd )
  1229. return; // Don't create duplicates
  1230. }
  1231. SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
  1232. if( !plotTab )
  1233. return;
  1234. wxString simType = simulator()->TypeToName( plotTab->GetSimType(), true );
  1235. int row;
  1236. for( row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
  1237. {
  1238. if( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
  1239. break;
  1240. }
  1241. if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
  1242. {
  1243. m_measurementsGrid->AppendRows( 1 );
  1244. row = m_measurementsGrid->GetNumberRows() - 1;
  1245. }
  1246. m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT, aCmd );
  1247. UpdateMeasurement( row );
  1248. updateMeasurementsFromGrid();
  1249. OnModify();
  1250. // Always leave at least one empty row for type-in:
  1251. row = m_measurementsGrid->GetNumberRows() - 1;
  1252. if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
  1253. m_measurementsGrid->AppendRows( 1 );
  1254. }
  1255. void SIMULATOR_FRAME_UI::DoFourier( const wxString& aSignal, const wxString& aFundamental )
  1256. {
  1257. wxString cmd = wxString::Format( wxS( "fourier %s %s" ),
  1258. SPICE_VALUE( aFundamental ).ToSpiceString(),
  1259. aSignal );
  1260. simulator()->Command( cmd.ToStdString() );
  1261. }
  1262. const SPICE_CIRCUIT_MODEL* SIMULATOR_FRAME_UI::GetExporter() const
  1263. {
  1264. return circuitModel().get();
  1265. }
  1266. void SIMULATOR_FRAME_UI::AddTrace( const wxString& aName, SIM_TRACE_TYPE aType )
  1267. {
  1268. if( !GetCurrentSimTab() )
  1269. {
  1270. m_simConsole->AppendText( _( "Error: no current simulation.\n" ) );
  1271. m_simConsole->SetInsertionPointEnd();
  1272. return;
  1273. }
  1274. SIM_TYPE simType = SPICE_CIRCUIT_MODEL::CommandToSimType( GetCurrentSimTab()->GetSimCommand() );
  1275. if( simType == ST_UNKNOWN )
  1276. {
  1277. m_simConsole->AppendText( _( "Error: simulation type not defined.\n" ) );
  1278. m_simConsole->SetInsertionPointEnd();
  1279. return;
  1280. }
  1281. else if( !SIM_TAB::IsPlottable( simType ) )
  1282. {
  1283. m_simConsole->AppendText( _( "Error: simulation type doesn't support plotting.\n" ) );
  1284. m_simConsole->SetInsertionPointEnd();
  1285. return;
  1286. }
  1287. if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
  1288. {
  1289. if( simType == ST_AC )
  1290. {
  1291. updateTrace( aName, aType | SPT_AC_GAIN, plotTab );
  1292. updateTrace( aName, aType | SPT_AC_PHASE, plotTab );
  1293. }
  1294. else if( simType == ST_SP )
  1295. {
  1296. updateTrace( aName, aType | SPT_AC_GAIN, plotTab );
  1297. updateTrace( aName, aType | SPT_AC_PHASE, plotTab );
  1298. }
  1299. else
  1300. {
  1301. updateTrace( aName, aType, plotTab );
  1302. }
  1303. plotTab->GetPlotWin()->UpdateAll();
  1304. }
  1305. updateSignalsGrid();
  1306. OnModify();
  1307. }
  1308. void SIMULATOR_FRAME_UI::SetUserDefinedSignals( const std::map<int, wxString>& aNewSignals )
  1309. {
  1310. for( size_t ii = 0; ii < m_plotNotebook->GetPageCount(); ++ii )
  1311. {
  1312. SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( m_plotNotebook->GetPage( ii ) );
  1313. if( !plotTab )
  1314. continue;
  1315. for( const auto& [ id, existingSignal ] : m_userDefinedSignals )
  1316. {
  1317. int traceType = SPT_UNKNOWN;
  1318. wxString vectorName = vectorNameFromSignalName( plotTab, existingSignal, &traceType );
  1319. if( aNewSignals.count( id ) == 0 )
  1320. {
  1321. if( plotTab->GetSimType() == ST_AC )
  1322. {
  1323. for( int subType : { SPT_AC_GAIN, SPT_AC_PHASE } )
  1324. plotTab->DeleteTrace( vectorName, traceType | subType );
  1325. }
  1326. else if( plotTab->GetSimType() == ST_SP )
  1327. {
  1328. for( int subType : { SPT_SP_AMP, SPT_AC_PHASE } )
  1329. plotTab->DeleteTrace( vectorName, traceType | subType );
  1330. }
  1331. else
  1332. {
  1333. plotTab->DeleteTrace( vectorName, traceType );
  1334. }
  1335. }
  1336. else
  1337. {
  1338. if( plotTab->GetSimType() == ST_AC )
  1339. {
  1340. for( int subType : { SPT_AC_GAIN, SPT_AC_PHASE } )
  1341. {
  1342. if( TRACE* trace = plotTab->GetTrace( vectorName, traceType | subType ) )
  1343. trace->SetName( aNewSignals.at( id ) );
  1344. }
  1345. }
  1346. else if( plotTab->GetSimType() == ST_SP )
  1347. {
  1348. for( int subType : { SPT_SP_AMP, SPT_AC_PHASE } )
  1349. {
  1350. if( TRACE* trace = plotTab->GetTrace( vectorName, traceType | subType ) )
  1351. trace->SetName( aNewSignals.at( id ) );
  1352. }
  1353. }
  1354. else
  1355. {
  1356. if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
  1357. trace->SetName( aNewSignals.at( id ) );
  1358. }
  1359. }
  1360. }
  1361. }
  1362. m_userDefinedSignals = aNewSignals;
  1363. if( m_simulatorFrame->SimFinished() )
  1364. applyUserDefinedSignals();
  1365. rebuildSignalsList();
  1366. rebuildSignalsGrid( m_filter->GetValue() );
  1367. updateSignalsGrid();
  1368. updatePlotCursors();
  1369. OnModify();
  1370. }
  1371. void SIMULATOR_FRAME_UI::updateTrace( const wxString& aVectorName, int aTraceType,
  1372. SIM_PLOT_TAB* aPlotTab, std::vector<double>* aDataX,
  1373. bool aClearData )
  1374. {
  1375. if( !m_simulatorFrame->SimFinished() && !simulator()->IsRunning())
  1376. {
  1377. aPlotTab->GetOrAddTrace( aVectorName, aTraceType );
  1378. return;
  1379. }
  1380. SIM_TYPE simType = SPICE_CIRCUIT_MODEL::CommandToSimType( aPlotTab->GetSimCommand() );
  1381. aTraceType &= aTraceType & SPT_Y_AXIS_MASK;
  1382. aTraceType |= getXAxisType( simType );
  1383. wxString simVectorName = aVectorName;
  1384. if( aTraceType & SPT_POWER )
  1385. simVectorName = simVectorName.AfterFirst( '(' ).BeforeLast( ')' ) + wxS( ":power" );
  1386. if( !SIM_TAB::IsPlottable( simType ) )
  1387. {
  1388. // There is no plot to be shown
  1389. simulator()->Command( wxString::Format( wxT( "print %s" ), aVectorName ).ToStdString() );
  1390. return;
  1391. }
  1392. std::vector<double> data_x;
  1393. std::vector<double> data_y;
  1394. if( !aDataX || aClearData )
  1395. aDataX = &data_x;
  1396. // First, handle the x axis
  1397. if( aDataX->empty() && !aClearData )
  1398. {
  1399. wxString xAxisName( simulator()->GetXAxis( simType ) );
  1400. if( xAxisName.IsEmpty() )
  1401. return;
  1402. *aDataX = simulator()->GetGainVector( (const char*) xAxisName.c_str() );
  1403. }
  1404. unsigned int size = aDataX->size();
  1405. switch( simType )
  1406. {
  1407. case ST_AC:
  1408. if( aTraceType & SPT_AC_GAIN )
  1409. data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
  1410. else if( aTraceType & SPT_AC_PHASE )
  1411. data_y = simulator()->GetPhaseVector( (const char*) simVectorName.c_str(), size );
  1412. else
  1413. wxFAIL_MSG( wxT( "Plot type missing AC_PHASE or AC_MAG bit" ) );
  1414. break;
  1415. case ST_SP:
  1416. if( aTraceType & SPT_SP_AMP )
  1417. data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
  1418. else if( aTraceType & SPT_AC_PHASE )
  1419. data_y = simulator()->GetPhaseVector( (const char*) simVectorName.c_str(), size );
  1420. else
  1421. wxFAIL_MSG( wxT( "Plot type missing AC_PHASE or SPT_SP_AMP bit" ) );
  1422. break;
  1423. case ST_DC:
  1424. data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), -1 );
  1425. break;
  1426. case ST_NOISE:
  1427. case ST_TRAN:
  1428. case ST_FFT:
  1429. data_y = simulator()->GetGainVector( (const char*) simVectorName.c_str(), size );
  1430. break;
  1431. default:
  1432. wxFAIL_MSG( wxT( "Unhandled plot type" ) );
  1433. }
  1434. SPICE_DC_PARAMS source1, source2;
  1435. int sweepCount = 1;
  1436. size_t sweepSize = std::numeric_limits<size_t>::max();
  1437. if( simType == ST_DC
  1438. && circuitModel()->ParseDCCommand( aPlotTab->GetSimCommand(), &source1, &source2 )
  1439. && !source2.m_source.IsEmpty() )
  1440. {
  1441. SPICE_VALUE v = ( source2.m_vend - source2.m_vstart ) / source2.m_vincrement;
  1442. sweepCount = KiROUND( v.ToDouble() ) + 1;
  1443. sweepSize = aDataX->size() / sweepCount;
  1444. }
  1445. if( TRACE* trace = aPlotTab->GetOrAddTrace( aVectorName, aTraceType ) )
  1446. {
  1447. if( data_y.size() >= size )
  1448. aPlotTab->SetTraceData( trace, *aDataX, data_y, sweepCount, sweepSize );
  1449. }
  1450. }
  1451. void SIMULATOR_FRAME_UI::updateSignalsGrid()
  1452. {
  1453. SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
  1454. for( int row = 0; row < m_signalsGrid->GetNumberRows(); ++row )
  1455. {
  1456. wxString signalName = m_signalsGrid->GetCellValue( row, COL_SIGNAL_NAME );
  1457. int traceType = SPT_UNKNOWN;
  1458. wxString vectorName = vectorNameFromSignalName( plotTab, signalName, &traceType );
  1459. if( TRACE* trace = plotTab ? plotTab->GetTrace( vectorName, traceType ) : nullptr )
  1460. {
  1461. m_signalsGrid->SetCellValue( row, COL_SIGNAL_SHOW, wxS( "1" ) );
  1462. wxGridCellAttrPtr attr = m_signalsGrid->GetOrCreateCellAttrPtr( row, COL_SIGNAL_COLOR );
  1463. if( !attr->HasRenderer() )
  1464. attr->SetRenderer( new GRID_CELL_COLOR_RENDERER( this ) );
  1465. if( !attr->HasEditor() )
  1466. attr->SetEditor( new GRID_CELL_COLOR_SELECTOR( this, m_signalsGrid ) );
  1467. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  1468. attr->SetReadOnly( false );
  1469. KIGFX::COLOR4D color( trace->GetPen().GetColour() );
  1470. m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, color.ToCSSString() );
  1471. attr = m_signalsGrid->GetOrCreateCellAttrPtr( row, COL_CURSOR_1 );
  1472. if( !attr->HasRenderer() )
  1473. attr->SetRenderer( new wxGridCellBoolRenderer() );
  1474. attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
  1475. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  1476. attr = m_signalsGrid->GetOrCreateCellAttrPtr( row, COL_CURSOR_2 );
  1477. if( !attr->HasRenderer() )
  1478. attr->SetRenderer( new wxGridCellBoolRenderer() );
  1479. attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
  1480. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  1481. if( trace->HasCursor( 1 ) )
  1482. m_signalsGrid->SetCellValue( row, COL_CURSOR_1, wxS( "1" ) );
  1483. else
  1484. m_signalsGrid->SetCellValue( row, COL_CURSOR_1, wxEmptyString );
  1485. if( trace->HasCursor( 2 ) )
  1486. m_signalsGrid->SetCellValue( row, COL_CURSOR_2, wxS( "1" ) );
  1487. else
  1488. m_signalsGrid->SetCellValue( row, COL_CURSOR_2, wxEmptyString );
  1489. }
  1490. else
  1491. {
  1492. m_signalsGrid->SetCellValue( row, COL_SIGNAL_SHOW, wxEmptyString );
  1493. wxGridCellAttrPtr attr = m_signalsGrid->GetOrCreateCellAttrPtr( row, COL_SIGNAL_COLOR );
  1494. attr->SetEditor( nullptr );
  1495. attr->SetRenderer( nullptr );
  1496. attr->SetReadOnly();
  1497. m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, wxEmptyString );
  1498. attr = m_signalsGrid->GetOrCreateCellAttrPtr( row, COL_CURSOR_1 );
  1499. attr->SetEditor( nullptr );
  1500. attr->SetRenderer( nullptr );
  1501. attr->SetReadOnly();
  1502. m_signalsGrid->SetCellValue( row, COL_CURSOR_1, wxEmptyString );
  1503. attr = m_signalsGrid->GetOrCreateCellAttrPtr( row, COL_CURSOR_2 );
  1504. attr->SetEditor( nullptr );
  1505. attr->SetRenderer( nullptr );
  1506. attr->SetReadOnly();
  1507. m_signalsGrid->SetCellValue( row, COL_CURSOR_2, wxEmptyString );
  1508. }
  1509. }
  1510. }
  1511. void SIMULATOR_FRAME_UI::applyUserDefinedSignals()
  1512. {
  1513. auto quoteNetNames = [&]( wxString aExpression ) -> wxString
  1514. {
  1515. std::vector<bool> mask( aExpression.length(), false );
  1516. for( const auto& netname : m_netnames )
  1517. {
  1518. size_t pos = aExpression.find( netname );
  1519. while( pos != wxString::npos )
  1520. {
  1521. for( size_t i = 0; i < netname.length(); ++i )
  1522. {
  1523. mask[pos + i] = true; // Mark the positions of the netname
  1524. }
  1525. pos = aExpression.find( netname, pos + 1 ); // Find the next occurrence
  1526. }
  1527. }
  1528. wxString quotedNetnames = "";
  1529. bool startQuote = true;
  1530. // put quotes around all the positions that were found above
  1531. for( size_t i = 0; i < aExpression.length(); i++ )
  1532. {
  1533. if( mask[i] && startQuote )
  1534. {
  1535. quotedNetnames = quotedNetnames + "\"";
  1536. startQuote = false;
  1537. }
  1538. else if( !mask[i] && !startQuote )
  1539. {
  1540. quotedNetnames = quotedNetnames + "\"";
  1541. startQuote = true;
  1542. }
  1543. wxString ch = aExpression[i];
  1544. quotedNetnames = quotedNetnames + ch;
  1545. }
  1546. if( !startQuote )
  1547. {
  1548. quotedNetnames = quotedNetnames + "\"";
  1549. }
  1550. return quotedNetnames;
  1551. };
  1552. for( const auto& [ id, signal ] : m_userDefinedSignals )
  1553. {
  1554. constexpr const char* cmd = "let user{} = {}";
  1555. simulator()->Command( "echo " + fmt::format( cmd, id, signal.ToStdString() ) );
  1556. simulator()->Command( fmt::format( cmd, id, quoteNetNames( signal ).ToStdString() ) );
  1557. }
  1558. }
  1559. void SIMULATOR_FRAME_UI::applyTuners()
  1560. {
  1561. WX_STRING_REPORTER reporter;
  1562. for( const TUNER_SLIDER* tuner : m_tuners )
  1563. {
  1564. SCH_SHEET_PATH sheetPath;
  1565. wxString ref = tuner->GetSymbolRef();
  1566. KIID symbolId = tuner->GetSymbol( &sheetPath );
  1567. SCH_ITEM* schItem = sheetPath.GetItem( symbolId );
  1568. SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( schItem );
  1569. if( !symbol )
  1570. {
  1571. reporter.Report( wxString::Format( _( "%s not found" ), ref ) );
  1572. continue;
  1573. }
  1574. const SPICE_ITEM* item = GetExporter()->FindItem( tuner->GetSymbolRef() );
  1575. if( !item || !item->model->GetTunerParam() )
  1576. {
  1577. reporter.Report( wxString::Format( _( "%s is not tunable" ), ref ) );
  1578. continue;
  1579. }
  1580. double floatVal = tuner->GetValue().ToDouble();
  1581. simulator()->Command( item->model->SpiceGenerator().TunerCommand( *item, floatVal ) );
  1582. }
  1583. if( reporter.HasMessage() )
  1584. DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( "\n" )
  1585. + reporter.GetMessages() );
  1586. }
  1587. bool SIMULATOR_FRAME_UI::LoadWorkbook( const wxString& aPath )
  1588. {
  1589. wxTextFile file( aPath );
  1590. if( !file.Open() )
  1591. return false;
  1592. wxString firstLine = file.GetFirstLine();
  1593. long dummy;
  1594. bool legacy = firstLine.StartsWith( wxT( "version " ) ) || firstLine.ToLong( &dummy );
  1595. file.Close();
  1596. m_plotNotebook->DeleteAllPages();
  1597. m_userDefinedSignals.clear();
  1598. if( legacy )
  1599. {
  1600. if( !loadLegacyWorkbook( aPath ) )
  1601. return false;
  1602. }
  1603. else
  1604. {
  1605. if( !loadJsonWorkbook( aPath ) )
  1606. return false;
  1607. }
  1608. rebuildSignalsList();
  1609. rebuildSignalsGrid( m_filter->GetValue() );
  1610. updateSignalsGrid();
  1611. updatePlotCursors();
  1612. rebuildMeasurementsGrid();
  1613. wxFileName filename( aPath );
  1614. filename.MakeRelativeTo( m_schematicFrame->Prj().GetProjectPath() );
  1615. // Remember the loaded workbook filename.
  1616. simulator()->Settings()->SetWorkbookFilename( filename.GetFullPath() );
  1617. return true;
  1618. }
  1619. bool SIMULATOR_FRAME_UI::loadJsonWorkbook( const wxString& aPath )
  1620. {
  1621. wxFFileInputStream fp( aPath, wxT( "rt" ) );
  1622. wxStdInputStream fstream( fp );
  1623. if( !fp.IsOk() )
  1624. return false;
  1625. try
  1626. {
  1627. nlohmann::json js = nlohmann::json::parse( fstream, nullptr, true, true );
  1628. std::map<SIM_PLOT_TAB*, nlohmann::json> traceInfo;
  1629. for( const nlohmann::json& tab_js : js[ "tabs" ] )
  1630. {
  1631. wxString simCommand;
  1632. int simOptions = NETLIST_EXPORTER_SPICE::OPTION_ADJUST_PASSIVE_VALS
  1633. | NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_EVENTS;
  1634. for( const nlohmann::json& cmd : tab_js[ "commands" ] )
  1635. {
  1636. if( cmd == ".kicad adjustpaths" )
  1637. simOptions |= NETLIST_EXPORTER_SPICE::OPTION_ADJUST_INCLUDE_PATHS;
  1638. else if( cmd == ".save all" )
  1639. simOptions |= NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_VOLTAGES;
  1640. else if( cmd == ".probe alli" )
  1641. simOptions |= NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_CURRENTS;
  1642. else if( cmd == ".probe allp" )
  1643. simOptions |= NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_DISSIPATIONS;
  1644. else if( cmd == ".kicad esavenone" )
  1645. simOptions &= ~NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_EVENTS;
  1646. else
  1647. simCommand += wxString( cmd.get<wxString>() ).Trim();
  1648. }
  1649. SIM_TAB* simTab = NewSimTab( simCommand );
  1650. SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab );
  1651. simTab->SetSimOptions( simOptions );
  1652. if( plotTab )
  1653. {
  1654. if( tab_js.contains( "traces" ) )
  1655. traceInfo[plotTab] = tab_js[ "traces" ];
  1656. if( tab_js.contains( "measurements" ) )
  1657. {
  1658. for( const nlohmann::json& m_js : tab_js[ "measurements" ] )
  1659. plotTab->Measurements().emplace_back( m_js[ "expr" ], m_js[ "format" ] );
  1660. }
  1661. plotTab->SetDottedSecondary( tab_js[ "dottedSecondary" ] );
  1662. plotTab->ShowGrid( tab_js[ "showGrid" ] );
  1663. if( tab_js.contains( "fixedY1scale" ) )
  1664. {
  1665. const nlohmann::json& scale_js = tab_js[ "fixedY1scale" ];
  1666. plotTab->SetY1Scale( true, scale_js[ "min" ], scale_js[ "max" ] );
  1667. plotTab->GetPlotWin()->LockY( true );
  1668. }
  1669. if( tab_js.contains( "fixedY2scale" ) )
  1670. {
  1671. const nlohmann::json& scale_js = tab_js[ "fixedY2scale" ];
  1672. plotTab->SetY2Scale( true, scale_js[ "min" ], scale_js[ "max" ] );
  1673. plotTab->GetPlotWin()->LockY( true );
  1674. }
  1675. if( tab_js.contains( "fixedY3scale" ) )
  1676. {
  1677. plotTab->EnsureThirdYAxisExists();
  1678. const nlohmann::json& scale_js = tab_js[ "fixedY3scale" ];
  1679. plotTab->SetY3Scale( true, scale_js[ "min" ], scale_js[ "max" ] );
  1680. plotTab->GetPlotWin()->LockY( true );
  1681. }
  1682. if( tab_js.contains( "legend" ) )
  1683. {
  1684. const nlohmann::json& legend_js = tab_js[ "legend" ];
  1685. plotTab->SetLegendPosition( wxPoint( legend_js[ "x" ], legend_js[ "y" ] ) );
  1686. plotTab->ShowLegend( true );
  1687. }
  1688. if( tab_js.contains( "margins" ) )
  1689. {
  1690. const nlohmann::json& margins_js = tab_js[ "margins" ];
  1691. plotTab->GetPlotWin()->SetMargins( margins_js[ "top" ],
  1692. margins_js[ "right" ],
  1693. margins_js[ "bottom" ],
  1694. margins_js[ "left" ] );
  1695. }
  1696. }
  1697. }
  1698. int ii = 0;
  1699. if( js.contains( "user_defined_signals" ) )
  1700. {
  1701. for( const nlohmann::json& signal_js : js[ "user_defined_signals" ] )
  1702. m_userDefinedSignals[ii++] = wxString( signal_js.get<wxString>() );
  1703. }
  1704. auto addCursor =
  1705. [this]( SIM_PLOT_TAB* aPlotTab, TRACE* aTrace, const wxString& aSignalName,
  1706. int aCursorId, const nlohmann::json& aCursor_js )
  1707. {
  1708. if( aCursorId == 1 || aCursorId == 2 )
  1709. {
  1710. CURSOR* cursor = new CURSOR( aTrace, aPlotTab );
  1711. cursor->SetName( aSignalName );
  1712. cursor->SetCoordX( aCursor_js[ "position" ] );
  1713. aTrace->SetCursor( aCursorId, cursor );
  1714. aPlotTab->GetPlotWin()->AddLayer( cursor );
  1715. }
  1716. m_cursorFormats[aCursorId-1][0].FromString( aCursor_js[ "x_format" ] );
  1717. m_cursorFormats[aCursorId-1][1].FromString( aCursor_js[ "y_format" ] );
  1718. };
  1719. for( const auto& [ plotTab, traces_js ] : traceInfo )
  1720. {
  1721. for( const nlohmann::json& trace_js : traces_js )
  1722. {
  1723. wxString signalName = trace_js[ "signal" ];
  1724. wxString vectorName = vectorNameFromSignalName( plotTab, signalName, nullptr );
  1725. TRACE* trace = plotTab->GetOrAddTrace( vectorName, trace_js[ "trace_type" ] );
  1726. if( trace )
  1727. {
  1728. if( trace_js.contains( "cursor1" ) )
  1729. addCursor( plotTab, trace, signalName, 1, trace_js[ "cursor1" ] );
  1730. if( trace_js.contains( "cursor2" ) )
  1731. addCursor( plotTab, trace, signalName, 2, trace_js[ "cursor2" ] );
  1732. if( trace_js.contains( "cursorD" ) )
  1733. addCursor( plotTab, trace, signalName, 3, trace_js[ "cursorD" ] );
  1734. if( trace_js.contains( "color" ) )
  1735. {
  1736. wxColour color;
  1737. color.Set( wxString( trace_js["color"].get<wxString>() ) );
  1738. trace->SetTraceColour( color );
  1739. plotTab->UpdateTraceStyle( trace );
  1740. }
  1741. }
  1742. }
  1743. plotTab->UpdatePlotColors();
  1744. }
  1745. if( SIM_TAB* simTab = GetCurrentSimTab() )
  1746. {
  1747. m_simulatorFrame->LoadSimulator( simTab->GetSimCommand(), simTab->GetSimOptions() );
  1748. if( SIM_TAB* firstTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( 0 ) ) )
  1749. firstTab->SetLastSchTextSimCommand( js[ "last_sch_text_sim_command" ] );
  1750. }
  1751. }
  1752. catch( nlohmann::json::parse_error& error )
  1753. {
  1754. wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ), aPath, error.what() );
  1755. return false;
  1756. }
  1757. catch( nlohmann::json::type_error& error )
  1758. {
  1759. wxLogTrace( traceSettings, wxT( "Json type error reading %s: %s" ), aPath, error.what() );
  1760. return false;
  1761. }
  1762. catch( nlohmann::json::invalid_iterator& error )
  1763. {
  1764. wxLogTrace( traceSettings, wxT( "Json invalid_iterator error reading %s: %s" ), aPath, error.what() );
  1765. return false;
  1766. }
  1767. catch( nlohmann::json::out_of_range& error )
  1768. {
  1769. wxLogTrace( traceSettings, wxT( "Json out_of_range error reading %s: %s" ), aPath, error.what() );
  1770. return false;
  1771. }
  1772. catch( ... )
  1773. {
  1774. wxLogTrace( traceSettings, wxT( "Error reading %s" ), aPath );
  1775. return false;
  1776. }
  1777. return true;
  1778. }
  1779. bool SIMULATOR_FRAME_UI::SaveWorkbook( const wxString& aPath )
  1780. {
  1781. updateMeasurementsFromGrid();
  1782. wxFileName filename = aPath;
  1783. filename.SetExt( FILEEXT::WorkbookFileExtension );
  1784. wxFile file;
  1785. file.Create( filename.GetFullPath(), true /* overwrite */ );
  1786. if( !file.IsOpened() )
  1787. return false;
  1788. nlohmann::json tabs_js = nlohmann::json::array();
  1789. for( size_t i = 0; i < m_plotNotebook->GetPageCount(); i++ )
  1790. {
  1791. SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( i ) );
  1792. if( !simTab )
  1793. continue;
  1794. SIM_TYPE simType = simTab->GetSimType();
  1795. nlohmann::json commands_js = nlohmann::json::array();
  1796. commands_js.push_back( simTab->GetSimCommand() );
  1797. int options = simTab->GetSimOptions();
  1798. if( options & NETLIST_EXPORTER_SPICE::OPTION_ADJUST_INCLUDE_PATHS )
  1799. commands_js.push_back( ".kicad adjustpaths" );
  1800. if( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_VOLTAGES )
  1801. commands_js.push_back( ".save all" );
  1802. if( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_CURRENTS )
  1803. commands_js.push_back( ".probe alli" );
  1804. if( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_DISSIPATIONS )
  1805. commands_js.push_back( ".probe allp" );
  1806. if( !( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_EVENTS ) )
  1807. commands_js.push_back( ".kicad esavenone" );
  1808. nlohmann::json tab_js = nlohmann::json(
  1809. { { "analysis", SPICE_SIMULATOR::TypeToName( simType, true ) },
  1810. { "commands", commands_js } } );
  1811. if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab ) )
  1812. {
  1813. nlohmann::json traces_js = nlohmann::json::array();
  1814. auto findSignalName =
  1815. [&]( const wxString& aVectorName ) -> wxString
  1816. {
  1817. wxString vectorName;
  1818. wxString suffix;
  1819. if( aVectorName.EndsWith( _( " (phase)" ) ) )
  1820. suffix = _( " (phase)" );
  1821. else if( aVectorName.EndsWith( _( " (gain)" ) ) )
  1822. suffix = _( " (gain)" );
  1823. vectorName = aVectorName.Left( aVectorName.Length() - suffix.Length() );
  1824. for( const auto& [ id, signal ] : m_userDefinedSignals )
  1825. {
  1826. if( vectorName == vectorNameFromSignalId( id ) )
  1827. return signal + suffix;
  1828. }
  1829. return aVectorName;
  1830. };
  1831. for( const auto& [name, trace] : plotTab->GetTraces() )
  1832. {
  1833. nlohmann::json trace_js = nlohmann::json(
  1834. { { "trace_type", (int) trace->GetType() },
  1835. { "signal", findSignalName( trace->GetDisplayName() ) },
  1836. { "color", COLOR4D( trace->GetTraceColour() ).ToCSSString() } } );
  1837. if( CURSOR* cursor = trace->GetCursor( 1 ) )
  1838. {
  1839. trace_js["cursor1"] = nlohmann::json(
  1840. { { "position", cursor->GetCoords().x },
  1841. { "x_format", m_cursorFormats[0][0].ToString() },
  1842. { "y_format", m_cursorFormats[0][1].ToString() } } );
  1843. }
  1844. if( CURSOR* cursor = trace->GetCursor( 2 ) )
  1845. {
  1846. trace_js["cursor2"] = nlohmann::json(
  1847. { { "position", cursor->GetCoords().x },
  1848. { "x_format", m_cursorFormats[1][0].ToString() },
  1849. { "y_format", m_cursorFormats[1][1].ToString() } } );
  1850. }
  1851. if( trace->GetCursor( 1 ) || trace->GetCursor( 2 ) )
  1852. {
  1853. trace_js["cursorD"] = nlohmann::json(
  1854. { { "x_format", m_cursorFormats[2][0].ToString() },
  1855. { "y_format", m_cursorFormats[2][1].ToString() } } );
  1856. }
  1857. traces_js.push_back( trace_js );
  1858. }
  1859. nlohmann::json measurements_js = nlohmann::json::array();
  1860. for( const auto& [ measurement, format ] : plotTab->Measurements() )
  1861. {
  1862. measurements_js.push_back( nlohmann::json( { { "expr", measurement },
  1863. { "format", format } } ) );
  1864. }
  1865. tab_js[ "traces" ] = traces_js;
  1866. tab_js[ "measurements" ] = measurements_js;
  1867. tab_js[ "dottedSecondary" ] = plotTab->GetDottedSecondary();
  1868. tab_js[ "showGrid" ] = plotTab->IsGridShown();
  1869. double min, max;
  1870. if( plotTab->GetY1Scale( &min, &max ) )
  1871. tab_js[ "fixedY1scale" ] = nlohmann::json( { { "min", min }, { "max", max } } );
  1872. if( plotTab->GetY2Scale( &min, &max ) )
  1873. tab_js[ "fixedY2scale" ] = nlohmann::json( { { "min", min }, { "max", max } } );
  1874. if( plotTab->GetY3Scale( &min, &max ) )
  1875. tab_js[ "fixedY3scale" ] = nlohmann::json( { { "min", min }, { "max", max } } );
  1876. if( plotTab->IsLegendShown() )
  1877. {
  1878. tab_js[ "legend" ] = nlohmann::json( { { "x", plotTab->GetLegendPosition().x },
  1879. { "y", plotTab->GetLegendPosition().y } } );
  1880. }
  1881. mpWindow* plotWin = plotTab->GetPlotWin();
  1882. tab_js[ "margins" ] = nlohmann::json( { { "left", plotWin->GetMarginLeft() },
  1883. { "right", plotWin->GetMarginRight() },
  1884. { "top", plotWin->GetMarginTop() },
  1885. { "bottom", plotWin->GetMarginBottom() } } );
  1886. }
  1887. tabs_js.push_back( tab_js );
  1888. }
  1889. nlohmann::json userDefinedSignals_js = nlohmann::json::array();
  1890. for( const auto& [ id, signal ] : m_userDefinedSignals )
  1891. userDefinedSignals_js.push_back( signal );
  1892. nlohmann::json js = nlohmann::json( { { "version", 6 },
  1893. { "tabs", tabs_js },
  1894. { "user_defined_signals", userDefinedSignals_js } } );
  1895. // Store the value of any simulation command found on the schematic sheet in a SCH_TEXT
  1896. // object. If this changes we want to warn the user and ask them if they want to update
  1897. // the corresponding panel's sim command.
  1898. if( m_plotNotebook->GetPageCount() > 0 )
  1899. {
  1900. SIM_TAB* simTab = dynamic_cast<SIM_TAB*>( m_plotNotebook->GetPage( 0 ) );
  1901. js[ "last_sch_text_sim_command" ] = simTab->GetLastSchTextSimCommand();
  1902. }
  1903. std::stringstream buffer;
  1904. buffer << std::setw( 2 ) << js << std::endl;
  1905. bool res = file.Write( buffer.str() );
  1906. file.Close();
  1907. // Store the filename of the last saved workbook.
  1908. if( res )
  1909. {
  1910. filename.MakeRelativeTo( m_schematicFrame->Prj().GetProjectPath() );
  1911. simulator()->Settings()->SetWorkbookFilename( filename.GetFullPath() );
  1912. }
  1913. return res;
  1914. }
  1915. SIM_TRACE_TYPE SIMULATOR_FRAME_UI::getXAxisType( SIM_TYPE aType ) const
  1916. {
  1917. switch( aType )
  1918. {
  1919. /// @todo SPT_LOG_FREQUENCY
  1920. case ST_AC: return SPT_LIN_FREQUENCY;
  1921. case ST_SP: return SPT_LIN_FREQUENCY;
  1922. case ST_FFT: return SPT_LIN_FREQUENCY;
  1923. case ST_DC: return SPT_SWEEP;
  1924. case ST_TRAN: return SPT_TIME;
  1925. case ST_NOISE: return SPT_LIN_FREQUENCY;
  1926. default:
  1927. wxFAIL_MSG( wxString::Format( wxS( "Unhandled simulation type: %d" ), (int) aType ) );
  1928. return SPT_UNKNOWN;
  1929. }
  1930. }
  1931. wxString SIMULATOR_FRAME_UI::getNoiseSource() const
  1932. {
  1933. wxString output;
  1934. wxString ref;
  1935. wxString source;
  1936. wxString scale;
  1937. SPICE_VALUE pts;
  1938. SPICE_VALUE fStart;
  1939. SPICE_VALUE fStop;
  1940. bool saveAll;
  1941. if( GetCurrentSimTab() )
  1942. {
  1943. circuitModel()->ParseNoiseCommand( GetCurrentSimTab()->GetSimCommand(), &output, &ref,
  1944. &source, &scale, &pts, &fStart, &fStop, &saveAll );
  1945. }
  1946. return source;
  1947. }
  1948. void SIMULATOR_FRAME_UI::ToggleDarkModePlots()
  1949. {
  1950. m_darkMode = !m_darkMode;
  1951. // Rebuild the color list to plot traces
  1952. SIM_PLOT_COLORS::FillDefaultColorList( m_darkMode );
  1953. // Now send changes to all SIM_PLOT_TAB
  1954. for( size_t page = 0; page < m_plotNotebook->GetPageCount(); page++ )
  1955. {
  1956. wxWindow* curPage = m_plotNotebook->GetPage( page );
  1957. // ensure it is truly a plot plotTab and not the (zero plots) placeholder
  1958. // which is only SIM_TAB
  1959. SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( curPage );
  1960. if( plotTab )
  1961. plotTab->UpdatePlotColors();
  1962. }
  1963. }
  1964. void SIMULATOR_FRAME_UI::onPlotClose( wxAuiNotebookEvent& event )
  1965. {
  1966. OnModify();
  1967. }
  1968. void SIMULATOR_FRAME_UI::onPlotClosed( wxAuiNotebookEvent& event )
  1969. {
  1970. CallAfter( [this]()
  1971. {
  1972. rebuildSignalsList();
  1973. rebuildSignalsGrid( m_filter->GetValue() );
  1974. updatePlotCursors();
  1975. SIM_TAB* panel = GetCurrentSimTab();
  1976. if( !panel || panel->GetSimType() != ST_OP )
  1977. {
  1978. SCHEMATIC& schematic = m_schematicFrame->Schematic();
  1979. schematic.ClearOperatingPoints();
  1980. m_schematicFrame->RefreshOperatingPointDisplay();
  1981. m_schematicFrame->GetCanvas()->Refresh();
  1982. }
  1983. } );
  1984. }
  1985. void SIMULATOR_FRAME_UI::updateMeasurementsFromGrid()
  1986. {
  1987. if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
  1988. {
  1989. std::vector<std::pair<wxString, wxString>>& measurements = plotTab->Measurements();
  1990. measurements.clear();
  1991. for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
  1992. {
  1993. if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
  1994. {
  1995. measurements.emplace_back( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ),
  1996. m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT_FORMAT ) );
  1997. }
  1998. }
  1999. }
  2000. }
  2001. void SIMULATOR_FRAME_UI::onPlotChanging( wxAuiNotebookEvent& event )
  2002. {
  2003. m_measurementsGrid->ClearRows();
  2004. event.Skip();
  2005. }
  2006. void SIMULATOR_FRAME_UI::OnPlotSettingsChanged()
  2007. {
  2008. rebuildSignalsList();
  2009. rebuildSignalsGrid( m_filter->GetValue() );
  2010. updatePlotCursors();
  2011. rebuildMeasurementsGrid();
  2012. for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
  2013. UpdateMeasurement( row );
  2014. }
  2015. void SIMULATOR_FRAME_UI::onPlotChanged( wxAuiNotebookEvent& event )
  2016. {
  2017. if( SIM_TAB* simTab = GetCurrentSimTab() )
  2018. simulator()->Command( "setplot " + simTab->GetSpicePlotName().ToStdString() );
  2019. OnPlotSettingsChanged();
  2020. event.Skip();
  2021. }
  2022. void SIMULATOR_FRAME_UI::rebuildMeasurementsGrid()
  2023. {
  2024. m_measurementsGrid->ClearRows();
  2025. if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
  2026. {
  2027. for( const auto& [ measurement, format ] : plotTab->Measurements() )
  2028. {
  2029. int row = m_measurementsGrid->GetNumberRows();
  2030. m_measurementsGrid->AppendRows();
  2031. m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT, measurement );
  2032. m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT_FORMAT, format );
  2033. }
  2034. if( plotTab->GetSimType() == ST_TRAN || plotTab->GetSimType() == ST_AC
  2035. || plotTab->GetSimType() == ST_DC || plotTab->GetSimType() == ST_SP )
  2036. {
  2037. m_measurementsGrid->AppendRows(); // Empty row at end
  2038. }
  2039. }
  2040. }
  2041. void SIMULATOR_FRAME_UI::onPlotDragged( wxAuiNotebookEvent& event )
  2042. {
  2043. }
  2044. std::shared_ptr<SPICE_SIMULATOR> SIMULATOR_FRAME_UI::simulator() const
  2045. {
  2046. return m_simulatorFrame->GetSimulator();
  2047. }
  2048. std::shared_ptr<SPICE_CIRCUIT_MODEL> SIMULATOR_FRAME_UI::circuitModel() const
  2049. {
  2050. return m_simulatorFrame->GetCircuitModel();
  2051. }
  2052. void SIMULATOR_FRAME_UI::updatePlotCursors()
  2053. {
  2054. SUPPRESS_GRID_CELL_EVENTS raii( this );
  2055. m_cursorsGrid->ClearRows();
  2056. SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() );
  2057. if( !plotTab )
  2058. return;
  2059. // Update cursor values
  2060. CURSOR* cursor1 = nullptr;
  2061. wxString cursor1Name;
  2062. wxString cursor1Units;
  2063. CURSOR* cursor2 = nullptr;
  2064. wxString cursor2Name;
  2065. wxString cursor2Units;
  2066. auto getUnitsY =
  2067. [&]( TRACE* aTrace ) -> wxString
  2068. {
  2069. if( plotTab->GetSimType() == ST_AC )
  2070. {
  2071. if( aTrace->GetType() & SPT_AC_PHASE )
  2072. return plotTab->GetUnitsY2();
  2073. else
  2074. return plotTab->GetUnitsY1();
  2075. }
  2076. else
  2077. {
  2078. if( aTrace->GetType() & SPT_POWER )
  2079. return plotTab->GetUnitsY3();
  2080. else if( aTrace->GetType() & SPT_CURRENT )
  2081. return plotTab->GetUnitsY2();
  2082. else
  2083. return plotTab->GetUnitsY1();
  2084. }
  2085. };
  2086. auto getNameY =
  2087. [&]( TRACE* aTrace ) -> wxString
  2088. {
  2089. if( plotTab->GetSimType() == ST_AC )
  2090. {
  2091. if( aTrace->GetType() & SPT_AC_PHASE )
  2092. return plotTab->GetLabelY2();
  2093. else
  2094. return plotTab->GetLabelY1();
  2095. }
  2096. else
  2097. {
  2098. if( aTrace->GetType() & SPT_POWER )
  2099. return plotTab->GetLabelY3();
  2100. else if( aTrace->GetType() & SPT_CURRENT )
  2101. return plotTab->GetLabelY2();
  2102. else
  2103. return plotTab->GetLabelY1();
  2104. }
  2105. };
  2106. auto formatValue =
  2107. [this]( double aValue, int aCursorId, int aCol ) -> wxString
  2108. {
  2109. if( ( !m_simulatorFrame->SimFinished() && aCol == 1 ) || std::isnan( aValue ) )
  2110. return wxS( "--" );
  2111. else
  2112. return SPICE_VALUE( aValue ).ToString( m_cursorFormats[ aCursorId ][ aCol ] );
  2113. };
  2114. for( const auto& [name, trace] : plotTab->GetTraces() )
  2115. {
  2116. if( CURSOR* cursor = trace->GetCursor( 1 ) )
  2117. {
  2118. cursor1 = cursor;
  2119. cursor1Name = getNameY( trace );
  2120. cursor1Units = getUnitsY( trace );
  2121. wxRealPoint coords = cursor->GetCoords();
  2122. int row = m_cursorsGrid->GetNumberRows();
  2123. m_cursorFormats[0][0].UpdateUnits( plotTab->GetUnitsX() );
  2124. m_cursorFormats[0][1].UpdateUnits( cursor1Units );
  2125. m_cursorsGrid->AppendRows( 1 );
  2126. m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "1" ) );
  2127. m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, cursor->GetName() );
  2128. m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, 0, 0 ) );
  2129. m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, 0, 1 ) );
  2130. break;
  2131. }
  2132. }
  2133. for( const auto& [name, trace] : plotTab->GetTraces() )
  2134. {
  2135. if( CURSOR* cursor = trace->GetCursor( 2 ) )
  2136. {
  2137. cursor2 = cursor;
  2138. cursor2Name = getNameY( trace );
  2139. cursor2Units = getUnitsY( trace );
  2140. wxRealPoint coords = cursor->GetCoords();
  2141. int row = m_cursorsGrid->GetNumberRows();
  2142. m_cursorFormats[1][0].UpdateUnits( plotTab->GetUnitsX() );
  2143. m_cursorFormats[1][1].UpdateUnits( cursor2Units );
  2144. m_cursorsGrid->AppendRows( 1 );
  2145. m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "2" ) );
  2146. m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, cursor->GetName() );
  2147. m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, 1, 0 ) );
  2148. m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, 1, 1 ) );
  2149. break;
  2150. }
  2151. }
  2152. if( cursor1 && cursor2 && cursor1Units == cursor2Units )
  2153. {
  2154. wxRealPoint coords = cursor2->GetCoords() - cursor1->GetCoords();
  2155. wxString signal;
  2156. m_cursorFormats[2][0].UpdateUnits( plotTab->GetUnitsX() );
  2157. m_cursorFormats[2][1].UpdateUnits( cursor1Units );
  2158. if( cursor1->GetName() == cursor2->GetName() )
  2159. signal = wxString::Format( wxS( "%s[2 - 1]" ), cursor2->GetName() );
  2160. else
  2161. signal = wxString::Format( wxS( "%s - %s" ), cursor2->GetName(), cursor1->GetName() );
  2162. m_cursorsGrid->AppendRows( 1 );
  2163. m_cursorsGrid->SetCellValue( 2, COL_CURSOR_NAME, _( "Diff" ) );
  2164. m_cursorsGrid->SetCellValue( 2, COL_CURSOR_SIGNAL, signal );
  2165. m_cursorsGrid->SetCellValue( 2, COL_CURSOR_X, formatValue( coords.x, 2, 0 ) );
  2166. m_cursorsGrid->SetCellValue( 2, COL_CURSOR_Y, formatValue( coords.y, 2, 1 ) );
  2167. }
  2168. // Set up the labels
  2169. m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, plotTab->GetLabelX() );
  2170. wxString valColName = _( "Value" );
  2171. if( !cursor1Name.IsEmpty() )
  2172. {
  2173. if( cursor2Name.IsEmpty() || cursor1Name == cursor2Name )
  2174. valColName = cursor1Name;
  2175. }
  2176. else if( !cursor2Name.IsEmpty() )
  2177. {
  2178. valColName = cursor2Name;
  2179. }
  2180. m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, valColName );
  2181. }
  2182. void SIMULATOR_FRAME_UI::onPlotCursorUpdate( wxCommandEvent& aEvent )
  2183. {
  2184. updatePlotCursors();
  2185. OnModify();
  2186. }
  2187. void SIMULATOR_FRAME_UI::OnSimUpdate()
  2188. {
  2189. if( SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( GetCurrentSimTab() ) )
  2190. plotTab->ResetScales( true );
  2191. m_simConsole->Clear();
  2192. // Do not export netlist, it is already stored in the simulator
  2193. applyTuners();
  2194. m_refreshTimer.Start( REFRESH_INTERVAL, wxTIMER_ONE_SHOT );
  2195. }
  2196. void SIMULATOR_FRAME_UI::OnSimReport( const wxString& aMsg )
  2197. {
  2198. m_simConsole->AppendText( aMsg + "\n" );
  2199. m_simConsole->SetInsertionPointEnd();
  2200. }
  2201. std::vector<wxString> SIMULATOR_FRAME_UI::SimPlotVectors() const
  2202. {
  2203. std::vector<wxString> signals;
  2204. for( const std::string& vec : simulator()->AllVectors() )
  2205. signals.emplace_back( vec );
  2206. return signals;
  2207. }
  2208. std::vector<wxString> SIMULATOR_FRAME_UI::Signals() const
  2209. {
  2210. std::vector<wxString> signals;
  2211. for( const wxString& signal : m_signals )
  2212. signals.emplace_back( signal );
  2213. for( const auto& [ id, signal ] : m_userDefinedSignals )
  2214. signals.emplace_back( signal );
  2215. sortSignals( signals );
  2216. return signals;
  2217. }
  2218. void SIMULATOR_FRAME_UI::OnSimRefresh( bool aFinal )
  2219. {
  2220. if( aFinal )
  2221. m_refreshTimer.Stop();
  2222. SIM_TAB* simTab = GetCurrentSimTab();
  2223. if( !simTab )
  2224. return;
  2225. SIM_TYPE simType = simTab->GetSimType();
  2226. wxString msg;
  2227. if( aFinal )
  2228. {
  2229. applyUserDefinedSignals();
  2230. updateSignalsGrid();
  2231. }
  2232. // If there are any signals plotted, update them
  2233. if( SIM_TAB::IsPlottable( simType ) )
  2234. {
  2235. simTab->SetSpicePlotName( simulator()->CurrentPlotName() );
  2236. if( simType == ST_NOISE && aFinal )
  2237. {
  2238. m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
  2239. m_simConsole->SetInsertionPointEnd();
  2240. // The simulator will create noise1 & noise2 on the first run, noise3 and noise4
  2241. // on the second, etc. The first plot for each run contains the spectral density
  2242. // noise vectors and second contains the integrated noise.
  2243. long number;
  2244. simulator()->CurrentPlotName().Mid( 5 ).ToLong( &number );
  2245. for( const std::string& vec : simulator()->AllVectors() )
  2246. {
  2247. std::vector<double> val_list = simulator()->GetRealVector( vec, 1 );
  2248. wxString value = SPICE_VALUE( val_list[ 0 ] ).ToSpiceString();
  2249. msg.Printf( wxS( "%s: %sV\n" ), vec, value );
  2250. m_simConsole->AppendText( msg );
  2251. m_simConsole->SetInsertionPointEnd();
  2252. }
  2253. simulator()->Command( fmt::format( "setplot noise{}", number - 1 ) );
  2254. simTab->SetSpicePlotName( simulator()->CurrentPlotName() );
  2255. }
  2256. SIM_PLOT_TAB* plotTab = dynamic_cast<SIM_PLOT_TAB*>( simTab );
  2257. wxCHECK_RET( plotTab, wxString::Format( wxT( "No SIM_PLOT_TAB for: %s" ),
  2258. magic_enum::enum_name( simType ) ) );
  2259. struct TRACE_INFO
  2260. {
  2261. wxString Vector;
  2262. int TraceType;
  2263. bool ClearData;
  2264. };
  2265. std::map<TRACE*, TRACE_INFO> traceMap;
  2266. for( const auto& [ name, trace ] : plotTab->GetTraces() )
  2267. traceMap[ trace ] = { wxEmptyString, SPT_UNKNOWN, false };
  2268. // NB: m_signals are already broken out into gain/phase, but m_userDefinedSignals are
  2269. // as the user typed them
  2270. for( const wxString& signal : m_signals )
  2271. {
  2272. int traceType = SPT_UNKNOWN;
  2273. wxString vectorName = vectorNameFromSignalName( plotTab, signal, &traceType );
  2274. if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
  2275. traceMap[ trace ] = { vectorName, traceType, false };
  2276. }
  2277. for( const auto& [ id, signal ] : m_userDefinedSignals )
  2278. {
  2279. int traceType = SPT_UNKNOWN;
  2280. wxString vectorName = vectorNameFromSignalName( plotTab, signal, &traceType );
  2281. if( simType == ST_AC )
  2282. {
  2283. int baseType = traceType &= ~( SPT_AC_GAIN | SPT_AC_PHASE );
  2284. for( int subType : { baseType | SPT_AC_GAIN, baseType | SPT_AC_PHASE } )
  2285. {
  2286. if( TRACE* trace = plotTab->GetTrace( vectorName, subType ) )
  2287. traceMap[ trace ] = { vectorName, subType, !aFinal };
  2288. }
  2289. }
  2290. else if( simType == ST_SP )
  2291. {
  2292. int baseType = traceType &= ~( SPT_SP_AMP | SPT_AC_PHASE );
  2293. for( int subType : { baseType | SPT_SP_AMP, baseType | SPT_AC_PHASE } )
  2294. {
  2295. if( TRACE* trace = plotTab->GetTrace( vectorName, subType ) )
  2296. traceMap[trace] = { vectorName, subType, !aFinal };
  2297. }
  2298. }
  2299. else
  2300. {
  2301. if( TRACE* trace = plotTab->GetTrace( vectorName, traceType ) )
  2302. traceMap[ trace ] = { vectorName, traceType, !aFinal };
  2303. }
  2304. }
  2305. // Two passes so that DC-sweep sub-traces get deleted and re-created:
  2306. for( const auto& [ trace, traceInfo ] : traceMap )
  2307. {
  2308. if( traceInfo.Vector.IsEmpty() )
  2309. plotTab->DeleteTrace( trace );
  2310. }
  2311. for( const auto& [ trace, info ] : traceMap )
  2312. {
  2313. std::vector<double> data_x;
  2314. if( !info.Vector.IsEmpty() )
  2315. updateTrace( info.Vector, info.TraceType, plotTab, &data_x, info.ClearData );
  2316. }
  2317. plotTab->GetPlotWin()->UpdateAll();
  2318. if( aFinal )
  2319. {
  2320. for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
  2321. UpdateMeasurement( row );
  2322. plotTab->ResetScales( true );
  2323. }
  2324. plotTab->GetPlotWin()->Fit();
  2325. updatePlotCursors();
  2326. }
  2327. else if( simType == ST_OP && aFinal )
  2328. {
  2329. m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
  2330. m_simConsole->SetInsertionPointEnd();
  2331. for( const std::string& vec : simulator()->AllVectors() )
  2332. {
  2333. std::vector<double> val_list = simulator()->GetRealVector( vec, 1 );
  2334. if( val_list.empty() )
  2335. continue;
  2336. wxString value = SPICE_VALUE( val_list[ 0 ] ).ToSpiceString();
  2337. wxString signal;
  2338. SIM_TRACE_TYPE type = circuitModel()->VectorToSignal( vec, signal );
  2339. const size_t tab = 25; //characters
  2340. size_t padding = ( signal.length() < tab ) ? ( tab - signal.length() ) : 1;
  2341. switch( type )
  2342. {
  2343. case SPT_VOLTAGE: value.Append( wxS( "V" ) ); break;
  2344. case SPT_CURRENT: value.Append( wxS( "A" ) ); break;
  2345. case SPT_POWER: value.Append( wxS( "W" ) ); break;
  2346. default: value.Append( wxS( "?" ) ); break;
  2347. }
  2348. msg.Printf( wxT( "%s%s\n" ),
  2349. ( signal + wxT( ":" ) ).Pad( padding, wxUniChar( ' ' ) ),
  2350. value );
  2351. m_simConsole->AppendText( msg );
  2352. m_simConsole->SetInsertionPointEnd();
  2353. if( type == SPT_VOLTAGE || type == SPT_CURRENT || type == SPT_POWER )
  2354. signal = signal.SubString( 2, signal.Length() - 2 );
  2355. if( type == SPT_POWER )
  2356. signal += wxS( ":power" );
  2357. m_schematicFrame->Schematic().SetOperatingPoint( signal, val_list.at( 0 ) );
  2358. }
  2359. }
  2360. else if( simType == ST_PZ && aFinal )
  2361. {
  2362. m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
  2363. m_simConsole->SetInsertionPointEnd();
  2364. simulator()->Command( "print all" );
  2365. }
  2366. }
  2367. void SIMULATOR_FRAME_UI::OnModify()
  2368. {
  2369. m_simulatorFrame->OnModify();
  2370. }