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.

2936 lines
91 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
3 years ago
3 years ago
3 years ago
5 years ago
5 years ago
9 years ago
3 years ago
9 years ago
9 years ago
9 years ago
9 years ago
3 years ago
9 years ago
9 years ago
3 years ago
3 years ago
9 years ago
9 years ago
9 years ago
9 years ago
3 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
4 years ago
4 years ago
9 years ago
9 years ago
4 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 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 (C) 2016-2023 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 <wx/debug.h>
  27. // For some obscure reason, needed on msys2 with some wxWidgets versions (3.0) to avoid
  28. // undefined symbol at link stage (due to use of #include <pegtl.hpp>)
  29. // Should not create issues on other platforms
  30. #include <wx/menu.h>
  31. #include <project/project_file.h>
  32. #include <sch_edit_frame.h>
  33. #include <kiway.h>
  34. #include <confirm.h>
  35. #include <bitmaps.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 <tool/tool_manager.h>
  43. #include <tool/tool_dispatcher.h>
  44. #include <tool/action_manager.h>
  45. #include <tool/action_toolbar.h>
  46. #include <tool/common_control.h>
  47. #include <tools/simulator_control.h>
  48. #include <tools/ee_actions.h>
  49. #include <string_utils.h>
  50. #include <pgm_base.h>
  51. #include "ngspice.h"
  52. #include "sim_plot_frame.h"
  53. #include "sim_plot_panel.h"
  54. #include "spice_simulator.h"
  55. #include "spice_reporter.h"
  56. #include "core/kicad_algo.h"
  57. #include "fmt/format.h"
  58. #include <dialog_sim_format_value.h>
  59. #include <eeschema_settings.h>
  60. #include <memory>
  61. SIM_TRACE_TYPE operator|( SIM_TRACE_TYPE aFirst, SIM_TRACE_TYPE aSecond )
  62. {
  63. int res = (int) aFirst | (int) aSecond;
  64. return (SIM_TRACE_TYPE) res;
  65. }
  66. class SIM_THREAD_REPORTER : public SPICE_REPORTER
  67. {
  68. public:
  69. SIM_THREAD_REPORTER( SIM_PLOT_FRAME* aParent ) :
  70. m_parent( aParent )
  71. {
  72. }
  73. REPORTER& Report( const wxString& aText, SEVERITY aSeverity = RPT_SEVERITY_UNDEFINED ) override
  74. {
  75. wxCommandEvent* event = new wxCommandEvent( EVT_SIM_REPORT );
  76. event->SetString( aText );
  77. wxQueueEvent( m_parent, event );
  78. return *this;
  79. }
  80. bool HasMessage() const override
  81. {
  82. return false; // Technically "indeterminate" rather than false.
  83. }
  84. void OnSimStateChange( SPICE_SIMULATOR* aObject, SIM_STATE aNewState ) override
  85. {
  86. wxCommandEvent* event = nullptr;
  87. switch( aNewState )
  88. {
  89. case SIM_IDLE: event = new wxCommandEvent( EVT_SIM_FINISHED ); break;
  90. case SIM_RUNNING: event = new wxCommandEvent( EVT_SIM_STARTED ); break;
  91. default: wxFAIL; return;
  92. }
  93. wxQueueEvent( m_parent, event );
  94. }
  95. private:
  96. SIM_PLOT_FRAME* m_parent;
  97. };
  98. enum SIGNALS_GRID_COLUMNS
  99. {
  100. COL_SIGNAL_NAME = 0,
  101. COL_SIGNAL_SHOW,
  102. COL_SIGNAL_COLOR,
  103. COL_CURSOR_1,
  104. COL_CURSOR_2
  105. };
  106. enum CURSORS_GRID_COLUMNS
  107. {
  108. COL_CURSOR_NAME = 0,
  109. COL_CURSOR_SIGNAL,
  110. COL_CURSOR_X,
  111. COL_CURSOR_Y
  112. };
  113. enum MEASUREMENTS_GIRD_COLUMNS
  114. {
  115. COL_MEASUREMENT = 0,
  116. COL_MEASUREMENT_VALUE,
  117. COL_MEASUREMENT_FORMAT
  118. };
  119. enum
  120. {
  121. MYID_MEASURE_MIN = GRIDTRICKS_FIRST_CLIENT_ID,
  122. MYID_MEASURE_MAX,
  123. MYID_MEASURE_AVG,
  124. MYID_MEASURE_RMS,
  125. MYID_MEASURE_PP,
  126. MYID_MEASURE_MIN_AT,
  127. MYID_MEASURE_MAX_AT,
  128. MYID_MEASURE_INTEGRAL,
  129. MYID_FORMAT_VALUE,
  130. MYID_DELETE_MEASUREMENT
  131. };
  132. class SIGNALS_GRID_TRICKS : public GRID_TRICKS
  133. {
  134. public:
  135. SIGNALS_GRID_TRICKS( SIM_PLOT_FRAME* aParent, WX_GRID* aGrid ) :
  136. GRID_TRICKS( aGrid ),
  137. m_parent( aParent ),
  138. m_menuRow( 0 ),
  139. m_menuCol( 0 )
  140. {}
  141. protected:
  142. void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
  143. void doPopupSelection( wxCommandEvent& event ) override;
  144. protected:
  145. SIM_PLOT_FRAME* m_parent;
  146. int m_menuRow;
  147. int m_menuCol;
  148. };
  149. void SIGNALS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
  150. {
  151. m_menuRow = aEvent.GetRow();
  152. m_menuCol = aEvent.GetCol();
  153. if( m_menuCol == COL_SIGNAL_NAME )
  154. {
  155. if( !( m_grid->IsInSelection( m_menuRow, m_menuCol ) ) )
  156. m_grid->ClearSelection();
  157. m_grid->SetGridCursor( m_menuRow, m_menuCol );
  158. wxString msg = m_grid->GetCellValue( m_menuRow, m_menuCol );
  159. menu.Append( MYID_MEASURE_MIN, _( "Measure Min" ) );
  160. menu.Append( MYID_MEASURE_MAX, _( "Measure Max" ) );
  161. menu.Append( MYID_MEASURE_AVG, _( "Measure Average" ) );
  162. menu.Append( MYID_MEASURE_RMS, _( "Measure RMS" ) );
  163. menu.Append( MYID_MEASURE_PP, _( "Measure Peak-to-peak" ) );
  164. menu.Append( MYID_MEASURE_MIN_AT, _( "Measure Time of Min" ) );
  165. menu.Append( MYID_MEASURE_MAX_AT, _( "Measure Time of Max" ) );
  166. menu.Append( MYID_MEASURE_INTEGRAL, _( "Measure Integral" ) );
  167. menu.AppendSeparator();
  168. }
  169. GRID_TRICKS::showPopupMenu( menu, aEvent );
  170. }
  171. void SIGNALS_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
  172. {
  173. std::vector<wxString> signals;
  174. wxGridCellCoordsArray cells1 = m_grid->GetSelectionBlockTopLeft();
  175. wxGridCellCoordsArray cells2 = m_grid->GetSelectionBlockBottomRight();
  176. for( size_t i = 0; i < cells1.Count(); i++ )
  177. {
  178. if( cells1[i].GetCol() == COL_SIGNAL_NAME )
  179. {
  180. for( int j = cells1[i].GetRow(); j < cells2[i].GetRow() + 1; j++ )
  181. {
  182. signals.push_back( m_grid->GetCellValue( j, cells1[i].GetCol() ) );
  183. }
  184. }
  185. }
  186. wxGridCellCoordsArray cells3 = m_grid->GetSelectedCells();
  187. for( size_t i = 0; i < cells3.Count(); i++ )
  188. {
  189. if( cells3[i].GetCol() == COL_SIGNAL_NAME )
  190. signals.push_back( m_grid->GetCellValue( cells3[i].GetRow(), cells3[i].GetCol() ) );
  191. }
  192. if( signals.size() < 1 )
  193. signals.push_back( m_grid->GetCellValue( m_menuRow, m_menuCol ) );
  194. if( event.GetId() == MYID_MEASURE_MIN )
  195. {
  196. for( wxString signal : signals )
  197. m_parent->AddMeasurement( wxString::Format( wxS( "MIN %s" ), signal ) );
  198. }
  199. else if( event.GetId() == MYID_MEASURE_MAX )
  200. {
  201. for( wxString signal : signals )
  202. m_parent->AddMeasurement( wxString::Format( wxS( "MAX %s" ), signal ) );
  203. }
  204. else if( event.GetId() == MYID_MEASURE_AVG )
  205. {
  206. for( wxString signal : signals )
  207. m_parent->AddMeasurement( wxString::Format( wxS( "AVG %s" ), signal ) );
  208. }
  209. else if( event.GetId() == MYID_MEASURE_RMS )
  210. {
  211. for( wxString signal : signals )
  212. m_parent->AddMeasurement( wxString::Format( wxS( "RMS %s" ), signal ) );
  213. }
  214. else if( event.GetId() == MYID_MEASURE_PP )
  215. {
  216. for( wxString signal : signals )
  217. m_parent->AddMeasurement( wxString::Format( wxS( "PP %s" ), signal ) );
  218. }
  219. else if( event.GetId() == MYID_MEASURE_MIN_AT )
  220. {
  221. for( wxString signal : signals )
  222. m_parent->AddMeasurement( wxString::Format( wxS( "MIN_AT %s" ), signal ) );
  223. }
  224. else if( event.GetId() == MYID_MEASURE_MAX_AT )
  225. {
  226. for( wxString signal : signals )
  227. m_parent->AddMeasurement( wxString::Format( wxS( "MAX_AT %s" ), signal ) );
  228. }
  229. else if( event.GetId() == MYID_MEASURE_INTEGRAL )
  230. {
  231. for( wxString signal : signals )
  232. m_parent->AddMeasurement( wxString::Format( wxS( "INTEG %s" ), signal ) );
  233. }
  234. else
  235. GRID_TRICKS::doPopupSelection( event );
  236. }
  237. class CURSORS_GRID_TRICKS : public GRID_TRICKS
  238. {
  239. public:
  240. CURSORS_GRID_TRICKS( SIM_PLOT_FRAME* aParent, WX_GRID* aGrid ) :
  241. GRID_TRICKS( aGrid ),
  242. m_parent( aParent ),
  243. m_menuRow( 0 ),
  244. m_menuCol( 0 )
  245. {}
  246. protected:
  247. void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
  248. void doPopupSelection( wxCommandEvent& event ) override;
  249. protected:
  250. SIM_PLOT_FRAME* m_parent;
  251. int m_menuRow;
  252. int m_menuCol;
  253. };
  254. void CURSORS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
  255. {
  256. m_menuRow = aEvent.GetRow();
  257. m_menuCol = aEvent.GetCol();
  258. if( m_menuCol == COL_CURSOR_X || m_menuCol == COL_CURSOR_Y )
  259. {
  260. wxString msg = m_grid->GetColLabelValue( m_menuCol );
  261. menu.Append( MYID_FORMAT_VALUE, wxString::Format( _( "Format %s..." ), msg ) );
  262. menu.AppendSeparator();
  263. }
  264. GRID_TRICKS::showPopupMenu( menu, aEvent );
  265. }
  266. void CURSORS_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
  267. {
  268. if( event.GetId() == MYID_FORMAT_VALUE )
  269. {
  270. int cursorId = m_menuRow;
  271. int cursorAxis = m_menuCol - COL_CURSOR_X;
  272. SPICE_VALUE_FORMAT format = m_parent->GetCursorFormat( cursorId, cursorAxis );
  273. DIALOG_SIM_FORMAT_VALUE formatDialog( m_parent, &format );
  274. if( formatDialog.ShowModal() == wxID_OK )
  275. m_parent->SetCursorFormat( cursorId, cursorAxis, format );
  276. }
  277. else
  278. {
  279. GRID_TRICKS::doPopupSelection( event );
  280. }
  281. }
  282. class MEASUREMENTS_GRID_TRICKS : public GRID_TRICKS
  283. {
  284. public:
  285. MEASUREMENTS_GRID_TRICKS( SIM_PLOT_FRAME* aParent, WX_GRID* aGrid ) :
  286. GRID_TRICKS( aGrid ),
  287. m_parent( aParent ),
  288. m_menuRow( 0 ),
  289. m_menuCol( 0 )
  290. {}
  291. protected:
  292. void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override;
  293. void doPopupSelection( wxCommandEvent& event ) override;
  294. protected:
  295. SIM_PLOT_FRAME* m_parent;
  296. int m_menuRow;
  297. int m_menuCol;
  298. };
  299. void MEASUREMENTS_GRID_TRICKS::showPopupMenu( wxMenu& menu, wxGridEvent& aEvent )
  300. {
  301. m_menuRow = aEvent.GetRow();
  302. m_menuCol = aEvent.GetCol();
  303. if( !( m_grid->IsInSelection( m_menuRow, m_menuCol ) ) )
  304. m_grid->ClearSelection();
  305. m_grid->SetGridCursor( m_menuRow, m_menuCol );
  306. if( m_menuCol == COL_MEASUREMENT_VALUE )
  307. menu.Append( MYID_FORMAT_VALUE, _( "Format Value..." ) );
  308. if( m_menuRow < ( m_grid->GetNumberRows() - 1 ) )
  309. menu.Append( MYID_DELETE_MEASUREMENT, _( "Delete Measurement" ) );
  310. menu.AppendSeparator();
  311. GRID_TRICKS::showPopupMenu( menu, aEvent );
  312. }
  313. void MEASUREMENTS_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
  314. {
  315. if( event.GetId() == MYID_FORMAT_VALUE )
  316. {
  317. SPICE_VALUE_FORMAT format = m_parent->GetMeasureFormat( m_menuRow );
  318. DIALOG_SIM_FORMAT_VALUE formatDialog( m_parent, &format );
  319. if( formatDialog.ShowModal() == wxID_OK )
  320. {
  321. m_parent->SetMeasureFormat( m_menuRow, format );
  322. m_parent->UpdateMeasurement( m_menuRow );
  323. }
  324. }
  325. else if( event.GetId() == MYID_DELETE_MEASUREMENT )
  326. {
  327. std::vector<int> measurements;
  328. wxGridCellCoordsArray cells1 = m_grid->GetSelectionBlockTopLeft();
  329. wxGridCellCoordsArray cells2 = m_grid->GetSelectionBlockBottomRight();
  330. for( size_t i = 0; i < cells1.Count(); i++ )
  331. {
  332. if( cells1[i].GetCol() == COL_MEASUREMENT )
  333. {
  334. for( int j = cells1[i].GetRow(); j < cells2[i].GetRow() + 1; j++ )
  335. {
  336. measurements.push_back( j );
  337. }
  338. }
  339. }
  340. wxGridCellCoordsArray cells3 = m_grid->GetSelectedCells();
  341. for( size_t i = 0; i < cells3.Count(); i++ )
  342. {
  343. if( cells3[i].GetCol() == COL_MEASUREMENT )
  344. measurements.push_back( cells3[i].GetRow() );
  345. }
  346. if( measurements.size() < 1 )
  347. measurements.push_back( m_menuRow );
  348. // When deleting a row, we'll change the indexes.
  349. // To avoid problems, we can start with the highest indexes.
  350. sort( measurements.begin(), measurements.end(), std::greater<>() );
  351. for( int row : measurements )
  352. m_parent->DeleteMeasurement( row );
  353. m_grid->ClearSelection();
  354. }
  355. else
  356. {
  357. GRID_TRICKS::doPopupSelection( event );
  358. }
  359. }
  360. class SUPPRESS_GRID_CELL_EVENTS
  361. {
  362. public:
  363. SUPPRESS_GRID_CELL_EVENTS( SIM_PLOT_FRAME* aFrame ) :
  364. m_frame( aFrame )
  365. {
  366. m_frame->m_SuppressGridEvents++;
  367. }
  368. ~SUPPRESS_GRID_CELL_EVENTS()
  369. {
  370. m_frame->m_SuppressGridEvents--;
  371. }
  372. private:
  373. SIM_PLOT_FRAME* m_frame;
  374. };
  375. SIM_PLOT_FRAME::SIM_PLOT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
  376. SIM_PLOT_FRAME_BASE( aParent ),
  377. m_SuppressGridEvents( 0 ),
  378. m_lastSimPlot( nullptr ),
  379. m_darkMode( true ),
  380. m_plotNumber( 0 ),
  381. m_simFinished( false ),
  382. m_workbookModified( false )
  383. {
  384. SetKiway( this, aKiway );
  385. m_schematicFrame = (SCH_EDIT_FRAME*) Kiway().Player( FRAME_SCH, false );
  386. wxASSERT( m_schematicFrame );
  387. // Give an icon
  388. wxIcon icon;
  389. icon.CopyFromBitmap( KiBitmap( BITMAPS::simulator ) );
  390. SetIcon( icon );
  391. m_simulator = SIMULATOR::CreateInstance( "ngspice" );
  392. wxASSERT( m_simulator );
  393. // Get the previous size and position of windows:
  394. LoadSettings( config() );
  395. m_filter->SetHint( _( "Filter" ) );
  396. m_signalsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
  397. m_cursorsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
  398. m_measurementsGrid->wxGrid::SetLabelFont( KIUI::GetStatusFont( this ) );
  399. m_signalsGrid->PushEventHandler( new SIGNALS_GRID_TRICKS( this, m_signalsGrid ) );
  400. m_cursorsGrid->PushEventHandler( new CURSORS_GRID_TRICKS( this, m_cursorsGrid ) );
  401. m_measurementsGrid->PushEventHandler( new MEASUREMENTS_GRID_TRICKS( this, m_measurementsGrid ) );
  402. wxGridCellAttr* attr = new wxGridCellAttr;
  403. attr->SetReadOnly();
  404. m_signalsGrid->SetColAttr( COL_SIGNAL_NAME, attr );
  405. attr = new wxGridCellAttr;
  406. attr->SetReadOnly();
  407. m_cursorsGrid->SetColAttr( COL_CURSOR_NAME, attr );
  408. attr = new wxGridCellAttr;
  409. attr->SetReadOnly();
  410. m_cursorsGrid->SetColAttr( COL_CURSOR_SIGNAL, attr );
  411. attr = new wxGridCellAttr;
  412. attr->SetReadOnly();
  413. m_cursorsGrid->SetColAttr( COL_CURSOR_Y, attr );
  414. for( int cursorId = 0; cursorId < 3; ++cursorId )
  415. {
  416. m_cursorFormats[ cursorId ][ 0 ] = { 3, wxS( "~s" ) };
  417. m_cursorFormats[ cursorId ][ 1 ] = { 3, wxS( "~V" ) };
  418. }
  419. attr = new wxGridCellAttr;
  420. attr->SetReadOnly();
  421. m_measurementsGrid->SetColAttr( COL_MEASUREMENT_VALUE, attr );
  422. // Prepare the color list to plot traces
  423. SIM_PLOT_COLORS::FillDefaultColorList( m_darkMode );
  424. m_simulator->Init();
  425. m_reporter = new SIM_THREAD_REPORTER( this );
  426. m_simulator->SetReporter( m_reporter );
  427. m_circuitModel = std::make_shared<NGSPICE_CIRCUIT_MODEL>( &m_schematicFrame->Schematic(), this );
  428. setupTools();
  429. setupUIConditions();
  430. ReCreateHToolbar();
  431. ReCreateMenuBar();
  432. Bind( wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( SIM_PLOT_FRAME::onExit ), this,
  433. wxID_EXIT );
  434. Bind( EVT_SIM_UPDATE, &SIM_PLOT_FRAME::onSimUpdate, this );
  435. Bind( EVT_SIM_REPORT, &SIM_PLOT_FRAME::onSimReport, this );
  436. Bind( EVT_SIM_STARTED, &SIM_PLOT_FRAME::onSimStarted, this );
  437. Bind( EVT_SIM_FINISHED, &SIM_PLOT_FRAME::onSimFinished, this );
  438. Bind( EVT_SIM_CURSOR_UPDATE, &SIM_PLOT_FRAME::onCursorUpdate, this );
  439. Bind( EVT_WORKBOOK_MODIFIED, &SIM_PLOT_FRAME::onNotebookModified, this );
  440. #ifndef wxHAS_NATIVE_TABART
  441. // Default non-native tab art has ugly gradients we don't want
  442. m_plotNotebook->SetArtProvider( new wxAuiSimpleTabArt() );
  443. #endif
  444. // Ensure new items are taken in account by sizers:
  445. Layout();
  446. // resize the subwindows size. At least on Windows, calling wxSafeYield before
  447. // resizing the subwindows forces the wxSplitWindows size events automatically generated
  448. // by wxWidgets to be executed before our resize code.
  449. // Otherwise, the changes made by setSubWindowsSashSize are overwritten by one these
  450. // events
  451. wxSafeYield();
  452. setSubWindowsSashSize();
  453. // Ensure the window is on top
  454. Raise();
  455. initWorkbook();
  456. updateTitle();
  457. }
  458. SIM_PLOT_FRAME::~SIM_PLOT_FRAME()
  459. {
  460. // Delete the GRID_TRICKS.
  461. m_signalsGrid->PopEventHandler( true );
  462. m_cursorsGrid->PopEventHandler( true );
  463. m_measurementsGrid->PopEventHandler( true );
  464. NULL_REPORTER devnull;
  465. m_simulator->Attach( nullptr, devnull );
  466. m_simulator->SetReporter( nullptr );
  467. delete m_reporter;
  468. }
  469. void SIM_PLOT_FRAME::setupTools()
  470. {
  471. // Create the manager
  472. m_toolManager = new TOOL_MANAGER;
  473. m_toolManager->SetEnvironment( nullptr, nullptr, nullptr, config(), this );
  474. m_toolDispatcher = new TOOL_DISPATCHER( m_toolManager );
  475. // Attach the events to the tool dispatcher
  476. Bind( wxEVT_CHAR, &TOOL_DISPATCHER::DispatchWxEvent, m_toolDispatcher );
  477. Bind( wxEVT_CHAR_HOOK, &TOOL_DISPATCHER::DispatchWxEvent, m_toolDispatcher );
  478. // Register tools
  479. m_toolManager->RegisterTool( new COMMON_CONTROL );
  480. m_toolManager->RegisterTool( new SIMULATOR_CONTROL );
  481. m_toolManager->InitTools();
  482. }
  483. void SIM_PLOT_FRAME::ShowChangedLanguage()
  484. {
  485. EDA_BASE_FRAME::ShowChangedLanguage();
  486. updateTitle();
  487. for( int ii = 0; ii < (int) m_plotNotebook->GetPageCount(); ++ii )
  488. {
  489. SIM_PANEL_BASE* plot = dynamic_cast<SIM_PLOT_PANEL*>( m_plotNotebook->GetPage( ii ) );
  490. plot->OnLanguageChanged();
  491. wxString pageTitle( m_simulator->TypeToName( plot->GetType(), true ) );
  492. pageTitle.Prepend( wxString::Format( _( "Plot%u - " ), ii+1 /* 1-based */ ) );
  493. m_plotNotebook->SetPageText( ii, pageTitle );
  494. }
  495. m_filter->SetHint( _( "Filter" ) );
  496. m_signalsGrid->SetColLabelValue( COL_SIGNAL_NAME, _( "Signal" ) );
  497. m_signalsGrid->SetColLabelValue( COL_SIGNAL_SHOW, _( "Plot" ) );
  498. m_signalsGrid->SetColLabelValue( COL_SIGNAL_COLOR, _( "Color" ) );
  499. m_signalsGrid->SetColLabelValue( COL_CURSOR_1, _( "Cursor 1" ) );
  500. m_signalsGrid->SetColLabelValue( COL_CURSOR_2, _( "Cursor 2" ) );
  501. m_cursorsGrid->SetColLabelValue( COL_CURSOR_NAME, _( "Cursor" ) );
  502. m_cursorsGrid->SetColLabelValue( COL_CURSOR_SIGNAL, _( "Signal" ) );
  503. m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, _( "Time" ) );
  504. m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, _( "Value" ) );
  505. updateCursors();
  506. for( TUNER_SLIDER* tuner : m_tuners )
  507. tuner->ShowChangedLanguage();
  508. }
  509. void SIM_PLOT_FRAME::LoadSettings( APP_SETTINGS_BASE* aCfg )
  510. {
  511. EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( aCfg );
  512. wxASSERT( cfg );
  513. if( cfg )
  514. {
  515. EDA_BASE_FRAME::LoadSettings( cfg );
  516. // Read subwindows sizes (should be > 0 )
  517. m_splitterLeftRightSashPosition = cfg->m_Simulator.plot_panel_width;
  518. m_splitterPlotAndConsoleSashPosition = cfg->m_Simulator.plot_panel_height;
  519. m_splitterSignalsSashPosition = cfg->m_Simulator.signal_panel_height;
  520. m_splitterCursorsSashPosition = cfg->m_Simulator.cursors_panel_height;
  521. m_splitterTuneValuesSashPosition = cfg->m_Simulator.measurements_panel_height;
  522. m_darkMode = !cfg->m_Simulator.white_background;
  523. }
  524. PROJECT_FILE& project = Prj().GetProjectFile();
  525. NGSPICE* currentSim = dynamic_cast<NGSPICE*>( m_simulator.get() );
  526. if( currentSim )
  527. m_simulator->Settings() = project.m_SchematicSettings->m_NgspiceSimulatorSettings;
  528. }
  529. void SIM_PLOT_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg )
  530. {
  531. EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( aCfg );
  532. wxASSERT( cfg );
  533. if( cfg )
  534. {
  535. EDA_BASE_FRAME::SaveSettings( cfg );
  536. cfg->m_Simulator.plot_panel_width = m_splitterLeftRight->GetSashPosition();
  537. cfg->m_Simulator.plot_panel_height = m_splitterPlotAndConsole->GetSashPosition();
  538. cfg->m_Simulator.signal_panel_height = m_splitterSignals->GetSashPosition();
  539. cfg->m_Simulator.cursors_panel_height = m_splitterCursors->GetSashPosition();
  540. cfg->m_Simulator.measurements_panel_height = m_splitterMeasurements->GetSashPosition();
  541. cfg->m_Simulator.white_background = !m_darkMode;
  542. }
  543. PROJECT_FILE& project = Prj().GetProjectFile();
  544. if( project.m_SchematicSettings )
  545. project.m_SchematicSettings->m_NgspiceSimulatorSettings->SaveToFile();
  546. if( m_schematicFrame )
  547. m_schematicFrame->SaveProjectSettings();
  548. }
  549. WINDOW_SETTINGS* SIM_PLOT_FRAME::GetWindowSettings( APP_SETTINGS_BASE* aCfg )
  550. {
  551. EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( aCfg );
  552. wxASSERT( cfg );
  553. return cfg ? &cfg->m_Simulator.window : nullptr;
  554. }
  555. void SIM_PLOT_FRAME::initWorkbook()
  556. {
  557. if( !m_simulator->Settings()->GetWorkbookFilename().IsEmpty() )
  558. {
  559. wxFileName filename = m_simulator->Settings()->GetWorkbookFilename();
  560. filename.SetPath( Prj().GetProjectPath() );
  561. if( !LoadWorkbook( filename.GetFullPath() ) )
  562. m_simulator->Settings()->SetWorkbookFilename( "" );
  563. }
  564. else if( LoadSimulator() )
  565. {
  566. if( !m_circuitModel->GetSchTextSimCommand().IsEmpty() )
  567. NewPlotPanel( m_circuitModel->GetSchTextSimCommand(), m_circuitModel->GetSimOptions() );
  568. rebuildSignalsList();
  569. rebuildSignalsGrid( m_filter->GetValue() );
  570. }
  571. }
  572. void SIM_PLOT_FRAME::updateTitle()
  573. {
  574. bool unsaved = true;
  575. bool readOnly = false;
  576. wxString title;
  577. if( m_simulator && m_simulator->Settings() )
  578. {
  579. wxFileName filename = Prj().AbsolutePath( m_simulator->Settings()->GetWorkbookFilename() );
  580. if( filename.IsOk() && filename.FileExists() )
  581. {
  582. unsaved = false;
  583. readOnly = !filename.IsFileWritable();
  584. }
  585. if( m_workbookModified )
  586. title = wxT( "*" ) + filename.GetName();
  587. else
  588. title = filename.GetName();
  589. }
  590. if( readOnly )
  591. title += wxS( " " ) + _( "[Read Only]" );
  592. if( unsaved )
  593. title += wxS( " " ) + _( "[Unsaved]" );
  594. title += wxT( " \u2014 " ) + _( "Spice Simulator" );
  595. SetTitle( title );
  596. }
  597. void SIM_PLOT_FRAME::setSubWindowsSashSize()
  598. {
  599. if( m_splitterLeftRightSashPosition > 0 )
  600. m_splitterLeftRight->SetSashPosition( m_splitterLeftRightSashPosition );
  601. if( m_splitterPlotAndConsoleSashPosition > 0 )
  602. m_splitterPlotAndConsole->SetSashPosition( m_splitterPlotAndConsoleSashPosition );
  603. if( m_splitterSignalsSashPosition > 0 )
  604. m_splitterSignals->SetSashPosition( m_splitterSignalsSashPosition );
  605. if( m_splitterCursorsSashPosition > 0 )
  606. m_splitterCursors->SetSashPosition( m_splitterCursorsSashPosition );
  607. if( m_splitterTuneValuesSashPosition > 0 )
  608. m_splitterMeasurements->SetSashPosition( m_splitterTuneValuesSashPosition );
  609. }
  610. void SIM_PLOT_FRAME::rebuildSignalsGrid( wxString aFilter )
  611. {
  612. SUPPRESS_GRID_CELL_EVENTS raii( this );
  613. m_signalsGrid->ClearRows();
  614. if( aFilter.IsEmpty() )
  615. aFilter = wxS( "*" );
  616. EDA_COMBINED_MATCHER matcher( aFilter, CTX_SIGNAL );
  617. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  618. int row = 0;
  619. for( const wxString& signal : m_signals )
  620. {
  621. int matches;
  622. int offset;
  623. if( matcher.Find( signal, matches, offset ) && offset == 0 )
  624. {
  625. TRACE* trace = plotPanel ? plotPanel->GetTrace( signal ) : nullptr;
  626. m_signalsGrid->AppendRows( 1 );
  627. m_signalsGrid->SetCellValue( row, COL_SIGNAL_NAME, signal );
  628. if( !plotPanel )
  629. {
  630. wxGridCellAttr* attr = new wxGridCellAttr;
  631. attr->SetReadOnly();
  632. m_signalsGrid->SetAttr( row, COL_SIGNAL_SHOW, attr );
  633. }
  634. else
  635. {
  636. wxGridCellAttr* attr = new wxGridCellAttr;
  637. attr->SetRenderer( new wxGridCellBoolRenderer() );
  638. attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
  639. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  640. m_signalsGrid->SetAttr( row, COL_SIGNAL_SHOW, attr );
  641. }
  642. if( trace )
  643. m_signalsGrid->SetCellValue( row, COL_SIGNAL_SHOW, wxS( "1" ) );
  644. if( !plotPanel || !trace )
  645. {
  646. wxGridCellAttr* 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. wxGridCellAttr* attr = new wxGridCellAttr;
  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. attr = new wxGridCellAttr;
  673. attr->SetRenderer( new wxGridCellBoolRenderer() );
  674. attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
  675. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  676. m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
  677. }
  678. row++;
  679. }
  680. }
  681. }
  682. void SIM_PLOT_FRAME::rebuildSignalsList()
  683. {
  684. m_signals.clear();
  685. int options = GetCurrentOptions();
  686. SIM_TYPE simType = NGSPICE_CIRCUIT_MODEL::CommandToSimType( GetCurrentSimCommand() );
  687. wxString unconnected = wxString( wxS( "unconnected-(" ) );
  688. if( simType == ST_UNKNOWN )
  689. simType = ST_TRANSIENT;
  690. unconnected.Replace( '(', '_' ); // Convert to SPICE markup
  691. auto addSignal =
  692. [&]( const wxString& aSignal, const wxString& aSpiceVecName = wxEmptyString )
  693. {
  694. if( simType == ST_AC )
  695. {
  696. wxString gain = _( " (gain)" );
  697. wxString phase = _( " (phase)" );
  698. m_signals.push_back( aSignal + gain );
  699. m_signals.push_back( aSignal + phase );
  700. if( !aSpiceVecName.IsEmpty() )
  701. {
  702. m_userDefinedSignalToSpiceVecName[ aSignal + gain ] = aSpiceVecName + gain;
  703. m_userDefinedSignalToSpiceVecName[ aSignal + phase ] = aSpiceVecName + phase;
  704. }
  705. }
  706. else
  707. {
  708. m_signals.push_back( aSignal );
  709. if( !aSpiceVecName.IsEmpty() )
  710. m_userDefinedSignalToSpiceVecName[ aSignal ] = aSpiceVecName;
  711. }
  712. };
  713. if( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_VOLTAGES )
  714. {
  715. for( const std::string& net : m_circuitModel->GetNets() )
  716. {
  717. // netnames are escaped (can contain "{slash}" for '/') Unscape them:
  718. wxString netname = UnescapeString( net );
  719. if( netname == "GND" || netname == "0" || netname.StartsWith( unconnected ) )
  720. continue;
  721. m_quotedNetnames[ netname ] = wxString::Format( wxS( "\"%s\"" ), netname );
  722. addSignal( wxString::Format( wxS( "V(%s)" ), netname ) );
  723. }
  724. }
  725. if( ( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_CURRENTS )
  726. && ( simType == ST_TRANSIENT || simType == ST_DC ) )
  727. {
  728. for( const SPICE_ITEM& item : m_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_TRANSIENT || simType == ST_DC ) )
  737. {
  738. for( const SPICE_ITEM& item : m_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. // Add .PROBE directives
  748. for( const wxString& directive : m_circuitModel->GetDirectives() )
  749. {
  750. wxStringTokenizer tokenizer( directive, wxT( "\r\n" ), wxTOKEN_STRTOK );
  751. while( tokenizer.HasMoreTokens() )
  752. {
  753. wxString line = tokenizer.GetNextToken();
  754. wxString directiveParams;
  755. if( line.Upper().StartsWith( wxS( ".PROBE" ), &directiveParams ) )
  756. addSignal( directiveParams.Trim( true ).Trim( false ) );
  757. }
  758. }
  759. // JEY TODO: find and add SPICE "LET" commands
  760. // Add user-defined signals
  761. for( int ii = 0; ii < (int) m_userDefinedSignals.size(); ++ii )
  762. {
  763. static wxRegEx regEx( wxS( "(^|[^a-z0-9_])([VIP])\\(" ), wxRE_ICASE );
  764. const wxString& signal = m_userDefinedSignals[ii];
  765. if( regEx.Matches( signal ) )
  766. {
  767. wxString vecType = regEx.GetMatch( signal, 2 );
  768. wxString spiceVecName = wxString::Format( wxS( "%s(user%d)" ), vecType, ii );
  769. addSignal( signal, spiceVecName );
  770. }
  771. }
  772. std::sort( m_signals.begin(), m_signals.end(),
  773. []( const wxString& lhs, const wxString& rhs )
  774. {
  775. // Sort voltages first
  776. if( lhs.Upper().StartsWith( 'V' ) && !rhs.Upper().StartsWith( 'V' ) )
  777. return true;
  778. else if( !lhs.Upper().StartsWith( 'V' ) && rhs.Upper().StartsWith( 'V' ) )
  779. return false;
  780. return StrNumCmp( lhs, rhs, true /* ignore case */ ) < 0;
  781. } );
  782. }
  783. bool SIM_PLOT_FRAME::LoadSimulator()
  784. {
  785. wxString errors;
  786. WX_STRING_REPORTER reporter( &errors );
  787. if( !m_schematicFrame->ReadyToNetlist( _( "Simulator requires a fully annotated schematic." ) ) )
  788. return false;
  789. if( !m_simulator->Attach( m_circuitModel, reporter ) )
  790. {
  791. DisplayErrorMessage( this, _( "Errors during netlist generation.\n\n" ) + errors );
  792. return false;
  793. }
  794. return true;
  795. }
  796. void SIM_PLOT_FRAME::StartSimulation()
  797. {
  798. if( m_circuitModel->CommandToSimType( GetCurrentSimCommand() ) == ST_UNKNOWN )
  799. {
  800. if( !EditSimCommand() )
  801. return;
  802. if( m_circuitModel->CommandToSimType( GetCurrentSimCommand() ) == ST_UNKNOWN )
  803. return;
  804. }
  805. wxString schTextSimCommand = m_circuitModel->GetSchTextSimCommand();
  806. SIM_TYPE schTextSimType = NGSPICE_CIRCUIT_MODEL::CommandToSimType( schTextSimCommand );
  807. SIM_PANEL_BASE* plotWindow = getCurrentPlotWindow();
  808. if( !plotWindow )
  809. {
  810. plotWindow = NewPlotPanel( schTextSimCommand, m_circuitModel->GetSimOptions() );
  811. m_workbookModified = true;
  812. }
  813. else
  814. {
  815. m_circuitModel->SetSimCommandOverride( plotWindow->GetSimCommand() );
  816. if( plotWindow->GetType() == schTextSimType
  817. && schTextSimCommand != m_circuitModel->GetLastSchTextSimCommand() )
  818. {
  819. if( IsOK( this, _( "Schematic sheet simulation command directive has changed. "
  820. "Do you wish to update the Simulation Command?" ) ) )
  821. {
  822. m_circuitModel->SetSimCommandOverride( wxEmptyString );
  823. plotWindow->SetSimCommand( schTextSimCommand );
  824. m_workbookModified = true;
  825. }
  826. }
  827. }
  828. m_circuitModel->SetSimOptions( GetCurrentOptions() );
  829. if( !LoadSimulator() )
  830. return;
  831. std::unique_lock<std::mutex> simulatorLock( m_simulator->GetMutex(), std::try_to_lock );
  832. if( simulatorLock.owns_lock() )
  833. {
  834. wxBusyCursor toggle;
  835. m_simConsole->Clear();
  836. applyTuners();
  837. // Prevents memory leak on succeding simulations by deleting old vectors
  838. m_simulator->Clean();
  839. m_simulator->Run();
  840. }
  841. else
  842. {
  843. DisplayErrorMessage( this, _( "Another simulation is already running." ) );
  844. }
  845. }
  846. SIM_PANEL_BASE* SIM_PLOT_FRAME::NewPlotPanel( const wxString& aSimCommand, int aOptions )
  847. {
  848. SIM_PANEL_BASE* plotPanel = nullptr;
  849. SIM_TYPE simType = NGSPICE_CIRCUIT_MODEL::CommandToSimType( aSimCommand );
  850. if( SIM_PANEL_BASE::IsPlottable( simType ) )
  851. {
  852. SIM_PLOT_PANEL* panel = new SIM_PLOT_PANEL( aSimCommand, aOptions, m_plotNotebook, wxID_ANY );
  853. plotPanel = dynamic_cast<SIM_PANEL_BASE*>( panel );
  854. COMMON_SETTINGS::INPUT cfg = Pgm().GetCommonSettings()->m_Input;
  855. panel->GetPlotWin()->EnableMouseWheelPan( cfg.scroll_modifier_zoom != 0 );
  856. }
  857. else
  858. {
  859. SIM_NOPLOT_PANEL* panel = new SIM_NOPLOT_PANEL( aSimCommand, aOptions, m_plotNotebook, wxID_ANY );
  860. plotPanel = dynamic_cast<SIM_PANEL_BASE*>( panel );
  861. }
  862. wxString pageTitle( m_simulator->TypeToName( simType, true ) );
  863. pageTitle.Prepend( wxString::Format( _( "Plot%u - " ), (unsigned int) ++m_plotNumber ) );
  864. m_plotNotebook->AddPage( dynamic_cast<wxWindow*>( plotPanel ), pageTitle, true );
  865. return plotPanel;
  866. }
  867. void SIM_PLOT_FRAME::OnFilterText( wxCommandEvent& aEvent )
  868. {
  869. rebuildSignalsGrid( m_filter->GetValue() );
  870. }
  871. void SIM_PLOT_FRAME::OnFilterMouseMoved( wxMouseEvent& aEvent )
  872. {
  873. wxPoint pos = aEvent.GetPosition();
  874. wxRect ctrlRect = m_filter->GetScreenRect();
  875. int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
  876. if( m_filter->IsSearchButtonVisible() && pos.x < buttonWidth )
  877. SetCursor( wxCURSOR_ARROW );
  878. else if( m_filter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
  879. SetCursor( wxCURSOR_ARROW );
  880. else
  881. SetCursor( wxCURSOR_IBEAM );
  882. }
  883. /**
  884. * For user-defined signals we display the expression such as "V(out)-V(in)", but the SPICE
  885. * signal we actually have to plot will be "user0" or some-such.
  886. */
  887. wxString SIM_PLOT_FRAME::getTraceName( const wxString& aSignalName )
  888. {
  889. if( alg::contains( m_userDefinedSignals, aSignalName ) )
  890. return m_userDefinedSignalToSpiceVecName[ aSignalName ];
  891. return aSignalName;
  892. }
  893. wxString SIM_PLOT_FRAME::getTraceName( int aRow )
  894. {
  895. return getTraceName( m_signalsGrid->GetCellValue( aRow, COL_SIGNAL_NAME ) );
  896. }
  897. /**
  898. * AC-small-signal analyses have two traces per signal, so we suffix the names.
  899. */
  900. wxString SIM_PLOT_FRAME::getTraceTitle( const wxString& aName, SIM_TRACE_TYPE aTraceType )
  901. {
  902. if( aTraceType & SPT_AC_MAG )
  903. return aName + _( " (gain)" );
  904. else if( aTraceType & SPT_AC_PHASE )
  905. return aName + _( " (phase)" );
  906. else
  907. return aName;
  908. }
  909. void SIM_PLOT_FRAME::onSignalsGridCellChanged( wxGridEvent& aEvent )
  910. {
  911. if( m_SuppressGridEvents > 0 )
  912. return;
  913. int row = aEvent.GetRow();
  914. int col = aEvent.GetCol();
  915. wxString text = m_signalsGrid->GetCellValue( row, col );
  916. SIM_PLOT_PANEL* plot = GetCurrentPlot();
  917. if( col == COL_SIGNAL_SHOW )
  918. {
  919. if( text == wxS( "1" ) )
  920. addTrace( getTraceName( row ) );
  921. else
  922. removeTrace( getTraceName( row ) );
  923. // Update enabled/visible states of other controls
  924. updateSignalsGrid();
  925. m_workbookModified = true;
  926. }
  927. else if( col == COL_SIGNAL_COLOR )
  928. {
  929. KIGFX::COLOR4D color( m_signalsGrid->GetCellValue( row, COL_SIGNAL_COLOR ) );
  930. TRACE* trace = plot->GetTrace( getTraceName( row ) );
  931. if( trace )
  932. {
  933. trace->SetTraceColour( color.ToColour() );
  934. plot->UpdateTraceStyle( trace );
  935. plot->UpdatePlotColors();
  936. m_workbookModified = true;
  937. }
  938. }
  939. else if( col == COL_CURSOR_1 || col == COL_CURSOR_2 )
  940. {
  941. for( int ii = 0; ii < m_signalsGrid->GetNumberRows(); ++ii )
  942. {
  943. bool enable = ii == row && text == wxS( "1" );
  944. plot->EnableCursor( getTraceName( ii ), col == COL_CURSOR_1 ? 1 : 2, enable );
  945. m_workbookModified = true;
  946. }
  947. // Update cursor checkboxes (which are really radio buttons)
  948. updateSignalsGrid();
  949. }
  950. }
  951. void SIM_PLOT_FRAME::onCursorsGridCellChanged( wxGridEvent& aEvent )
  952. {
  953. if( m_SuppressGridEvents > 0 )
  954. return;
  955. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  956. if( !plotPanel )
  957. return;
  958. int row = aEvent.GetRow();
  959. int col = aEvent.GetCol();
  960. wxString text = m_cursorsGrid->GetCellValue( row, col );
  961. wxString cursorName = m_cursorsGrid->GetCellValue( row, COL_CURSOR_NAME );
  962. if( col == COL_CURSOR_X )
  963. {
  964. CURSOR* cursor1 = nullptr;
  965. CURSOR* cursor2 = nullptr;
  966. for( const auto& [name, trace] : plotPanel->GetTraces() )
  967. {
  968. if( CURSOR* cursor = trace->GetCursor( 1 ) )
  969. cursor1 = cursor;
  970. if( CURSOR* cursor = trace->GetCursor( 2 ) )
  971. cursor2 = cursor;
  972. }
  973. double value = SPICE_VALUE( text ).ToDouble();
  974. if( cursorName == wxS( "1" ) && cursor1 )
  975. cursor1->SetCoordX( value );
  976. else if( cursorName == wxS( "2" ) && cursor2 )
  977. cursor2->SetCoordX( value );
  978. else if( cursorName == _( "Diff" ) && cursor1 && cursor2 )
  979. cursor2->SetCoordX( cursor1->GetCoords().x + value );
  980. updateCursors();
  981. m_workbookModified = true;
  982. }
  983. else
  984. {
  985. wxFAIL_MSG( wxT( "All other columns are supposed to be read-only!" ) );
  986. }
  987. }
  988. SPICE_VALUE_FORMAT SIM_PLOT_FRAME::GetMeasureFormat( int aRow ) const
  989. {
  990. SPICE_VALUE_FORMAT result;
  991. result.FromString( m_measurementsGrid->GetCellValue( aRow, COL_MEASUREMENT_FORMAT ) );
  992. return result;
  993. }
  994. void SIM_PLOT_FRAME::SetMeasureFormat( int aRow, const SPICE_VALUE_FORMAT& aFormat )
  995. {
  996. m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_FORMAT, aFormat.ToString() );
  997. m_workbookModified = true;
  998. }
  999. void SIM_PLOT_FRAME::DeleteMeasurement( int aRow )
  1000. {
  1001. if( aRow < ( m_measurementsGrid->GetNumberRows() - 1 ) )
  1002. {
  1003. m_measurementsGrid->DeleteRows( aRow, 1 );
  1004. m_workbookModified = true;
  1005. }
  1006. }
  1007. void SIM_PLOT_FRAME::onMeasurementsGridCellChanged( wxGridEvent& aEvent )
  1008. {
  1009. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  1010. if( !plotPanel )
  1011. return;
  1012. int row = aEvent.GetRow();
  1013. int col = aEvent.GetCol();
  1014. wxString text = m_measurementsGrid->GetCellValue( row, col );
  1015. if( col == COL_MEASUREMENT )
  1016. {
  1017. UpdateMeasurement( row );
  1018. m_workbookModified = true;
  1019. }
  1020. else
  1021. {
  1022. wxFAIL_MSG( wxT( "All other columns are supposed to be read-only!" ) );
  1023. }
  1024. // Always leave a single empty row for type-in
  1025. int rowCount = (int) m_measurementsGrid->GetNumberRows();
  1026. int emptyRows = 0;
  1027. for( row = rowCount - 1; row >= 0; row-- )
  1028. {
  1029. if( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
  1030. emptyRows++;
  1031. else
  1032. break;
  1033. }
  1034. if( emptyRows > 1 )
  1035. {
  1036. int killRows = emptyRows - 1;
  1037. m_measurementsGrid->DeleteRows( rowCount - killRows, killRows );
  1038. }
  1039. else if( emptyRows == 0 )
  1040. {
  1041. m_measurementsGrid->AppendRows( 1 );
  1042. }
  1043. }
  1044. /**
  1045. * The user measurement looks something like:
  1046. * MAX V(out)
  1047. *
  1048. * We need to send ngspice a "MEAS" command with the analysis type, an output variable name,
  1049. * and the signal name. For our example above, this looks something like:
  1050. * MEAS TRAN meas_result_0 MAX V(out)
  1051. *
  1052. * This is also a good time to harvest the signal name prefix so we know what units to show on
  1053. * the result. For instance, for:
  1054. * MAX P(out)
  1055. * we want to show:
  1056. * 15W
  1057. */
  1058. void SIM_PLOT_FRAME::UpdateMeasurement( int aRow )
  1059. {
  1060. static wxRegEx measureParamsRegEx( wxT( "^"
  1061. " *"
  1062. "([a-zA-Z_]+)"
  1063. " +"
  1064. "([a-zA-Z])\\(([^\\)]+)\\)" ) );
  1065. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  1066. if( !plotPanel )
  1067. return;
  1068. wxString text = m_measurementsGrid->GetCellValue( aRow, COL_MEASUREMENT );
  1069. if( text.IsEmpty() )
  1070. {
  1071. m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_VALUE, wxEmptyString );
  1072. return;
  1073. }
  1074. wxString simType = m_simulator->TypeToName( plotPanel->GetType(), true );
  1075. wxString resultName = wxString::Format( wxS( "meas_result_%u" ), aRow );
  1076. wxString result = wxS( "?" );
  1077. if( measureParamsRegEx.Matches( text ) )
  1078. {
  1079. wxString func = measureParamsRegEx.GetMatch( text, 1 ).Upper();
  1080. wxUniChar signalType = measureParamsRegEx.GetMatch( text, 2 ).Upper()[0];
  1081. wxString deviceName = measureParamsRegEx.GetMatch( text, 3 );
  1082. wxString units;
  1083. SPICE_VALUE_FORMAT fmt = GetMeasureFormat( aRow );
  1084. if( signalType == 'I' )
  1085. units = wxS( "A" );
  1086. else if( signalType == 'P' )
  1087. {
  1088. units = wxS( "W" );
  1089. // Our syntax is different from ngspice for power signals
  1090. text = func + " " + deviceName + ":power";
  1091. }
  1092. else
  1093. units = wxS( "V" );
  1094. if( func.EndsWith( wxS( "_AT" ) ) )
  1095. units = wxS( "s" );
  1096. else if( func.StartsWith( wxS( "INTEG" ) ) )
  1097. {
  1098. switch( plotPanel->GetType() )
  1099. {
  1100. case SIM_TYPE::ST_TRANSIENT:
  1101. if ( signalType == 'P' )
  1102. units = wxS( "J" );
  1103. else
  1104. units += wxS( ".s" );
  1105. break;
  1106. case SIM_TYPE::ST_AC:
  1107. case SIM_TYPE::ST_DISTORTION:
  1108. case SIM_TYPE::ST_NOISE:
  1109. case SIM_TYPE::ST_SENSITIVITY: // If there is a vector, it is frequency
  1110. units += wxS( "·Hz" );
  1111. break;
  1112. case SIM_TYPE::ST_DC: // Could be a lot of things : V, A, deg C, ohm, ...
  1113. case SIM_TYPE::ST_OP: // There is no vector for integration
  1114. case SIM_TYPE::ST_POLE_ZERO: // There is no vector for integration
  1115. case SIM_TYPE::ST_TRANS_FUNC: // There is no vector for integration
  1116. default:
  1117. units += wxS( "·?" );
  1118. break;
  1119. }
  1120. }
  1121. fmt.UpdateUnits( units );
  1122. SetMeasureFormat( aRow, fmt );
  1123. }
  1124. if( m_simFinished )
  1125. {
  1126. wxString cmd = wxString::Format( wxS( "meas %s %s %s" ), simType, resultName, text );
  1127. m_simulator->Command( "echo " + cmd.ToStdString() );
  1128. m_simulator->Command( cmd.ToStdString() );
  1129. std::vector<double> resultVec = m_simulator->GetMagPlot( resultName.ToStdString() );
  1130. if( resultVec.size() > 0 )
  1131. result = SPICE_VALUE( resultVec[0] ).ToString( GetMeasureFormat( aRow ) );
  1132. }
  1133. m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_VALUE, result );
  1134. }
  1135. void SIM_PLOT_FRAME::AddVoltagePlot( const wxString& aNetName )
  1136. {
  1137. doAddPlot( aNetName, SPT_VOLTAGE );
  1138. }
  1139. void SIM_PLOT_FRAME::AddCurrentPlot( const wxString& aDeviceName )
  1140. {
  1141. doAddPlot( aDeviceName, SPT_CURRENT );
  1142. }
  1143. void SIM_PLOT_FRAME::AddTuner( const SCH_SHEET_PATH& aSheetPath, SCH_SYMBOL* aSymbol )
  1144. {
  1145. SIM_PANEL_BASE* plotPanel = getCurrentPlotWindow();
  1146. if( !plotPanel )
  1147. return;
  1148. wxString ref = aSymbol->GetRef( &aSheetPath );
  1149. // Do not add multiple instances for the same component.
  1150. for( TUNER_SLIDER* tuner : m_tuners )
  1151. {
  1152. if( tuner->GetSymbolRef() == ref )
  1153. return;
  1154. }
  1155. const SPICE_ITEM* item = GetExporter()->FindItem( std::string( ref.ToUTF8() ) );
  1156. // Do nothing if the symbol is not tunable.
  1157. if( !item || !item->model->GetTunerParam() )
  1158. return;
  1159. try
  1160. {
  1161. TUNER_SLIDER* tuner = new TUNER_SLIDER( this, m_panelTuners, aSheetPath, aSymbol );
  1162. m_sizerTuners->Add( tuner );
  1163. m_tuners.push_back( tuner );
  1164. m_panelTuners->Layout();
  1165. m_workbookModified = true;
  1166. }
  1167. catch( const KI_PARAM_ERROR& e )
  1168. {
  1169. DisplayErrorMessage( nullptr, e.What() );
  1170. }
  1171. }
  1172. void SIM_PLOT_FRAME::UpdateTunerValue( const SCH_SHEET_PATH& aSheetPath, const KIID& aSymbol,
  1173. const wxString& aRef, const wxString& aValue )
  1174. {
  1175. SCH_ITEM* item = aSheetPath.GetItem( aSymbol );
  1176. SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( item );
  1177. if( !symbol )
  1178. {
  1179. DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( " " )
  1180. + wxString::Format( _( "%s not found" ), aRef ) );
  1181. return;
  1182. }
  1183. SIM_LIB_MGR mgr( &Prj() );
  1184. SIM_MODEL& model = mgr.CreateModel( &aSheetPath, *symbol ).model;
  1185. const SIM_MODEL::PARAM* tunerParam = model.GetTunerParam();
  1186. if( !tunerParam )
  1187. {
  1188. DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( " " )
  1189. + wxString::Format( _( "%s is not tunable" ), aRef ) );
  1190. return;
  1191. }
  1192. model.SetParamValue( tunerParam->info.name, std::string( aValue.ToUTF8() ) );
  1193. model.WriteFields( symbol->GetFields() );
  1194. m_schematicFrame->UpdateItem( symbol, false, true );
  1195. m_schematicFrame->OnModify();
  1196. }
  1197. void SIM_PLOT_FRAME::RemoveTuner( TUNER_SLIDER* aTuner, bool aErase )
  1198. {
  1199. if( aErase )
  1200. m_tuners.remove( aTuner );
  1201. aTuner->Destroy();
  1202. m_panelTuners->Layout();
  1203. m_workbookModified = true;
  1204. }
  1205. void SIM_PLOT_FRAME::AddMeasurement( const wxString& aCmd )
  1206. {
  1207. // -1 because the last one is for user inpu
  1208. for ( int i = 0; i < m_measurementsGrid->GetNumberRows(); i++ )
  1209. {
  1210. if ( m_measurementsGrid->GetCellValue( i, COL_MEASUREMENT ) == aCmd )
  1211. return; // Don't create duplicates
  1212. }
  1213. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  1214. if( !plotPanel )
  1215. return;
  1216. wxString simType = m_simulator->TypeToName( plotPanel->GetType(), true );
  1217. int row;
  1218. for( row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
  1219. {
  1220. if( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
  1221. break;
  1222. }
  1223. if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
  1224. {
  1225. m_measurementsGrid->AppendRows( 1 );
  1226. row = m_measurementsGrid->GetNumberRows() - 1;
  1227. }
  1228. m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT, aCmd );
  1229. SetMeasureFormat( row, { 3, wxS( "~V" ) } );
  1230. UpdateMeasurement( row );
  1231. m_workbookModified = true;
  1232. // Always leave at least one empty row for type-in:
  1233. row = m_measurementsGrid->GetNumberRows() - 1;
  1234. if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
  1235. m_measurementsGrid->AppendRows( 1 );
  1236. }
  1237. SIM_PLOT_PANEL* SIM_PLOT_FRAME::GetCurrentPlot() const
  1238. {
  1239. SIM_PANEL_BASE* curPage = getCurrentPlotWindow();
  1240. return !curPage || curPage->GetType() == ST_UNKNOWN ? nullptr
  1241. : dynamic_cast<SIM_PLOT_PANEL*>( curPage );
  1242. }
  1243. const NGSPICE_CIRCUIT_MODEL* SIM_PLOT_FRAME::GetExporter() const
  1244. {
  1245. return m_circuitModel.get();
  1246. }
  1247. void SIM_PLOT_FRAME::doAddPlot( const wxString& aName, SIM_TRACE_TYPE aType )
  1248. {
  1249. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  1250. if( !plotPanel )
  1251. {
  1252. m_simConsole->AppendText( _( "Error: no current simulation.\n" ) );
  1253. m_simConsole->SetInsertionPointEnd();
  1254. return;
  1255. }
  1256. SIM_TYPE simType = NGSPICE_CIRCUIT_MODEL::CommandToSimType( plotPanel->GetSimCommand() );
  1257. if( simType == ST_UNKNOWN )
  1258. {
  1259. m_simConsole->AppendText( _( "Error: simulation type not defined.\n" ) );
  1260. m_simConsole->SetInsertionPointEnd();
  1261. return;
  1262. }
  1263. else if( !SIM_PANEL_BASE::IsPlottable( simType ) )
  1264. {
  1265. m_simConsole->AppendText( _( "Error: simulation type doesn't support plotting.\n" ) );
  1266. m_simConsole->SetInsertionPointEnd();
  1267. return;
  1268. }
  1269. SIM_TRACE_TYPE xAxisType = getXAxisType( simType );
  1270. if( ( xAxisType == SPT_LIN_FREQUENCY || xAxisType == SPT_LOG_FREQUENCY )
  1271. && ( aType & ( SPT_AC_MAG | SPT_AC_PHASE ) ) == 0 )
  1272. {
  1273. // If magnitude or phase wasn't specified, then add both
  1274. updateTrace( aName, (SIM_TRACE_TYPE) ( aType | SPT_AC_MAG ), plotPanel );
  1275. updateTrace( aName, (SIM_TRACE_TYPE) ( aType | SPT_AC_PHASE ), plotPanel );
  1276. }
  1277. else
  1278. {
  1279. updateTrace( aName, aType, plotPanel );
  1280. }
  1281. updateSignalsGrid();
  1282. m_workbookModified = true;
  1283. }
  1284. void SIM_PLOT_FRAME::SetUserDefinedSignals( const std::vector<wxString>& aNewSignals )
  1285. {
  1286. for( const wxString& signal : m_userDefinedSignals )
  1287. {
  1288. if( !alg::contains( aNewSignals, signal ) )
  1289. removeTrace( m_userDefinedSignalToSpiceVecName[ signal ] );
  1290. }
  1291. m_userDefinedSignals = aNewSignals;
  1292. if( m_simFinished )
  1293. applyUserDefinedSignals();
  1294. rebuildSignalsList();
  1295. rebuildSignalsGrid( m_filter->GetValue() );
  1296. updateSignalsGrid();
  1297. m_workbookModified = true;
  1298. }
  1299. void SIM_PLOT_FRAME::addTrace( const wxString& aSignalName )
  1300. {
  1301. if( aSignalName.IsEmpty() )
  1302. return;
  1303. wxString baseSignal = aSignalName;
  1304. wxString gainSuffix = _( " (gain)" );
  1305. wxString phaseSuffix = _( " (phase)" );
  1306. wxUniChar firstChar = aSignalName.Upper()[0];
  1307. int traceType = SPT_UNKNOWN;
  1308. if( firstChar == 'V' )
  1309. traceType = SPT_VOLTAGE;
  1310. else if( firstChar == 'I' )
  1311. traceType = SPT_CURRENT;
  1312. else if( firstChar == 'P' )
  1313. traceType = SPT_POWER;
  1314. if( aSignalName.EndsWith( gainSuffix ) )
  1315. {
  1316. traceType |= SPT_AC_MAG;
  1317. baseSignal = aSignalName.Left( aSignalName.Length() - gainSuffix.Length() );
  1318. }
  1319. else if( aSignalName.EndsWith( phaseSuffix ) )
  1320. {
  1321. traceType |= SPT_AC_PHASE;
  1322. baseSignal = aSignalName.Left( aSignalName.Length() - phaseSuffix.Length() );
  1323. }
  1324. if( traceType != SPT_UNKNOWN )
  1325. {
  1326. if( SIM_PLOT_PANEL* plotPanel = GetCurrentPlot() )
  1327. updateTrace( baseSignal, (SIM_TRACE_TYPE) traceType, plotPanel );
  1328. }
  1329. }
  1330. void SIM_PLOT_FRAME::removeTrace( const wxString& aSignalName )
  1331. {
  1332. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  1333. if( !plotPanel )
  1334. return;
  1335. wxASSERT( plotPanel->TraceShown( aSignalName ) );
  1336. if( plotPanel->DeleteTrace( aSignalName ) )
  1337. m_workbookModified = true;
  1338. plotPanel->GetPlotWin()->Fit();
  1339. updateSignalsGrid();
  1340. updateCursors();
  1341. }
  1342. void SIM_PLOT_FRAME::updateTrace( const wxString& aName, SIM_TRACE_TYPE aTraceType,
  1343. SIM_PLOT_PANEL* aPlotPanel )
  1344. {
  1345. SIM_TYPE simType = NGSPICE_CIRCUIT_MODEL::CommandToSimType( aPlotPanel->GetSimCommand() );
  1346. aTraceType = (SIM_TRACE_TYPE) ( aTraceType & SPT_Y_AXIS_MASK );
  1347. aTraceType = (SIM_TRACE_TYPE) ( aTraceType | getXAxisType( simType ) );
  1348. wxString traceTitle = getTraceTitle( aName, aTraceType );
  1349. wxString vectorName = aName;
  1350. if( aTraceType & SPT_POWER )
  1351. vectorName = vectorName.AfterFirst( '(' ).BeforeLast( ')' ) + wxS( ":power" );
  1352. if( !SIM_PANEL_BASE::IsPlottable( simType ) )
  1353. {
  1354. // There is no plot to be shown
  1355. m_simulator->Command( wxString::Format( wxT( "print %s" ), aName ).ToStdString() );
  1356. return;
  1357. }
  1358. // First, handle the x axis
  1359. wxString xAxisName( m_simulator->GetXAxis( simType ) );
  1360. if( xAxisName.IsEmpty() )
  1361. return;
  1362. std::vector<double> data_x;
  1363. std::vector<double> data_y;
  1364. if( m_simFinished )
  1365. {
  1366. data_x = m_simulator->GetMagPlot( (const char*) xAxisName.c_str() );
  1367. switch( simType )
  1368. {
  1369. case ST_AC:
  1370. if( aTraceType & SPT_AC_MAG )
  1371. data_y = m_simulator->GetMagPlot( (const char*) vectorName.c_str() );
  1372. else if( aTraceType & SPT_AC_PHASE )
  1373. data_y = m_simulator->GetPhasePlot( (const char*) vectorName.c_str() );
  1374. else
  1375. wxFAIL_MSG( wxT( "Plot type missing AC_PHASE or AC_MAG bit" ) );
  1376. break;
  1377. case ST_NOISE:
  1378. case ST_DC:
  1379. case ST_TRANSIENT:
  1380. data_y = m_simulator->GetMagPlot( (const char*) vectorName.c_str() );
  1381. break;
  1382. default:
  1383. wxFAIL_MSG( wxT( "Unhandled plot type" ) );
  1384. }
  1385. }
  1386. unsigned int size = data_x.size();
  1387. // If we did a two-source DC analysis, we need to split the resulting vector and add traces
  1388. // for each input step
  1389. SPICE_DC_PARAMS source1, source2;
  1390. if( simType == ST_DC
  1391. && m_circuitModel->ParseDCCommand( m_circuitModel->GetSimCommand(), &source1, &source2 )
  1392. && !source2.m_source.IsEmpty() )
  1393. {
  1394. // Source 1 is the inner loop, so lets add traces for each Source 2 (outer loop) step
  1395. SPICE_VALUE v = source2.m_vstart;
  1396. wxString name;
  1397. size_t offset = 0;
  1398. size_t outer = ( size_t )( ( source2.m_vend - v ) / source2.m_vincrement ).ToDouble();
  1399. size_t inner = data_x.size() / ( outer + 1 );
  1400. wxASSERT( data_x.size() % ( outer + 1 ) == 0 );
  1401. for( size_t idx = 0; idx <= outer; idx++ )
  1402. {
  1403. name = wxString::Format( wxT( "%s (%s = %s V)" ),
  1404. traceTitle,
  1405. source2.m_source,
  1406. v.ToString() );
  1407. if( TRACE* trace = aPlotPanel->AddTrace( name, aName, aTraceType ) )
  1408. {
  1409. if( data_y.size() >= size )
  1410. {
  1411. std::vector<double> sub_x( data_x.begin() + offset,
  1412. data_x.begin() + offset + inner );
  1413. std::vector<double> sub_y( data_y.begin() + offset,
  1414. data_y.begin() + offset + inner );
  1415. aPlotPanel->SetTraceData( trace, inner, sub_x.data(), sub_y.data() );
  1416. }
  1417. }
  1418. v = v + source2.m_vincrement;
  1419. offset += inner;
  1420. }
  1421. }
  1422. else if( TRACE* trace = aPlotPanel->AddTrace( traceTitle, aName, aTraceType ) )
  1423. {
  1424. if( data_y.size() >= size )
  1425. aPlotPanel->SetTraceData( trace, size, data_x.data(), data_y.data() );
  1426. }
  1427. }
  1428. void SIM_PLOT_FRAME::updateSignalsGrid()
  1429. {
  1430. SIM_PLOT_PANEL* plot = GetCurrentPlot();
  1431. for( int row = 0; row < m_signalsGrid->GetNumberRows(); ++row )
  1432. {
  1433. if( TRACE* trace = plot->GetTrace( getTraceName( row ) ) )
  1434. {
  1435. m_signalsGrid->SetCellValue( row, COL_SIGNAL_SHOW, wxS( "1" ) );
  1436. wxGridCellAttr* attr = new wxGridCellAttr;
  1437. attr->SetRenderer( new GRID_CELL_COLOR_RENDERER( this ) );
  1438. attr->SetEditor( new GRID_CELL_COLOR_SELECTOR( this, m_signalsGrid ) );
  1439. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  1440. m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
  1441. KIGFX::COLOR4D color( trace->GetPen().GetColour() );
  1442. m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, color.ToCSSString() );
  1443. attr = new wxGridCellAttr;
  1444. attr->SetRenderer( new wxGridCellBoolRenderer() );
  1445. attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
  1446. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  1447. m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
  1448. attr = new wxGridCellAttr;
  1449. attr->SetRenderer( new wxGridCellBoolRenderer() );
  1450. attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
  1451. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  1452. m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
  1453. if( trace->HasCursor( 1 ) )
  1454. m_signalsGrid->SetCellValue( row, COL_CURSOR_1, wxS( "1" ) );
  1455. else
  1456. m_signalsGrid->SetCellValue( row, COL_CURSOR_1, wxEmptyString );
  1457. if( trace->HasCursor( 2 ) )
  1458. m_signalsGrid->SetCellValue( row, COL_CURSOR_2, wxS( "1" ) );
  1459. else
  1460. m_signalsGrid->SetCellValue( row, COL_CURSOR_2, wxEmptyString );
  1461. }
  1462. else
  1463. {
  1464. m_signalsGrid->SetCellValue( row, COL_SIGNAL_SHOW, wxEmptyString );
  1465. wxGridCellAttr* attr = new wxGridCellAttr;
  1466. attr->SetReadOnly();
  1467. m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
  1468. m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, wxEmptyString );
  1469. attr = new wxGridCellAttr;
  1470. attr->SetReadOnly();
  1471. m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
  1472. m_signalsGrid->SetCellValue( row, COL_CURSOR_1, wxEmptyString );
  1473. attr = new wxGridCellAttr;
  1474. attr->SetReadOnly();
  1475. m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
  1476. m_signalsGrid->SetCellValue( row, COL_CURSOR_2, wxEmptyString );
  1477. }
  1478. }
  1479. }
  1480. void SIM_PLOT_FRAME::applyUserDefinedSignals()
  1481. {
  1482. auto quoteNetNames =
  1483. [&]( wxString aExpression ) -> wxString
  1484. {
  1485. for( const auto& [netname, quotedNetname] : m_quotedNetnames )
  1486. aExpression.Replace( netname, quotedNetname );
  1487. return aExpression;
  1488. };
  1489. for( int ii = 0; ii < (int) m_userDefinedSignals.size(); ++ii )
  1490. {
  1491. wxString signal = m_userDefinedSignals[ii];
  1492. std::string cmd = "let user{} = {}";
  1493. m_simulator->Command( "echo " + fmt::format(cmd, ii, signal.ToStdString() ) );
  1494. m_simulator->Command( fmt::format( cmd, ii, quoteNetNames( signal ).ToStdString() ) );
  1495. }
  1496. }
  1497. void SIM_PLOT_FRAME::applyTuners()
  1498. {
  1499. wxString errors;
  1500. WX_STRING_REPORTER reporter( &errors );
  1501. for( const TUNER_SLIDER* tuner : m_tuners )
  1502. {
  1503. SCH_SHEET_PATH sheetPath;
  1504. wxString ref = tuner->GetSymbolRef();
  1505. KIID symbolId = tuner->GetSymbol( &sheetPath );
  1506. SCH_ITEM* schItem = sheetPath.GetItem( symbolId );
  1507. SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( schItem );
  1508. if( !symbol )
  1509. {
  1510. reporter.Report( wxString::Format( _( "%s not found" ), ref ) );
  1511. continue;
  1512. }
  1513. const SPICE_ITEM* item = GetExporter()->FindItem( tuner->GetSymbolRef().ToStdString() );
  1514. if( !item || !item->model->GetTunerParam() )
  1515. {
  1516. reporter.Report( wxString::Format( _( "%s is not tunable" ), ref ) );
  1517. continue;
  1518. }
  1519. double floatVal = tuner->GetValue().ToDouble();
  1520. m_simulator->Command( item->model->SpiceGenerator().TunerCommand( *item, floatVal ) );
  1521. }
  1522. if( reporter.HasMessage() )
  1523. DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( "\n" ) + errors );
  1524. }
  1525. bool SIM_PLOT_FRAME::LoadWorkbook( const wxString& aPath )
  1526. {
  1527. m_plotNotebook->DeleteAllPages();
  1528. wxTextFile file( aPath );
  1529. #define DISPLAY_LOAD_ERROR( fmt ) DisplayErrorMessage( this, wxString::Format( _( fmt ), \
  1530. file.GetCurrentLine()+1 ) )
  1531. if( !file.Open() )
  1532. return false;
  1533. long version = 1;
  1534. wxString firstLine = file.GetFirstLine();
  1535. wxString plotCountLine;
  1536. if( firstLine.StartsWith( wxT( "version " ) ) )
  1537. {
  1538. if( !firstLine.substr( 8 ).ToLong( &version ) )
  1539. {
  1540. DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is not an integer." );
  1541. file.Close();
  1542. return false;
  1543. }
  1544. plotCountLine = file.GetNextLine();
  1545. }
  1546. else
  1547. {
  1548. plotCountLine = firstLine;
  1549. }
  1550. long plotsCount;
  1551. if( !plotCountLine.ToLong( &plotsCount ) )
  1552. {
  1553. DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is not an integer." );
  1554. file.Close();
  1555. return false;
  1556. }
  1557. for( long i = 0; i < plotsCount; ++i )
  1558. {
  1559. long plotType, tracesCount;
  1560. if( !file.GetNextLine().ToLong( &plotType ) )
  1561. {
  1562. DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is not an integer." );
  1563. file.Close();
  1564. return false;
  1565. }
  1566. wxString command = UnescapeString( file.GetNextLine() );
  1567. wxString simCommand;
  1568. int simOptions = NETLIST_EXPORTER_SPICE::OPTION_DEFAULT_FLAGS;
  1569. wxStringTokenizer tokenizer( command, wxT( "\r\n" ), wxTOKEN_STRTOK );
  1570. if( version >= 2 )
  1571. {
  1572. simOptions &= ~NETLIST_EXPORTER_SPICE::OPTION_ADJUST_INCLUDE_PATHS;
  1573. simOptions &= ~NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_VOLTAGES;
  1574. simOptions &= ~NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_CURRENTS;
  1575. }
  1576. else if( version >= 3 )
  1577. {
  1578. simOptions &= ~NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_DISSIPATIONS;
  1579. }
  1580. while( tokenizer.HasMoreTokens() )
  1581. {
  1582. wxString line = tokenizer.GetNextToken();
  1583. if( line.StartsWith( wxT( ".kicad adjustpaths" ) ) )
  1584. simOptions |= NETLIST_EXPORTER_SPICE::OPTION_ADJUST_INCLUDE_PATHS;
  1585. else if( line.StartsWith( wxT( ".save all" ) ) )
  1586. simOptions |= NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_VOLTAGES;
  1587. else if( line.StartsWith( wxT( ".probe alli" ) ) )
  1588. simOptions |= NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_CURRENTS;
  1589. else if( line.StartsWith( wxT( ".probe allp" ) ) )
  1590. simOptions |= NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_DISSIPATIONS;
  1591. else
  1592. simCommand += line + wxT( "\n" );
  1593. }
  1594. NewPlotPanel( simCommand, simOptions );
  1595. if( !file.GetNextLine().ToLong( &tracesCount ) )
  1596. {
  1597. DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is not an integer." );
  1598. file.Close();
  1599. return false;
  1600. }
  1601. for( long j = 0; j < tracesCount; ++j )
  1602. {
  1603. long traceType;
  1604. wxString name, param;
  1605. if( !file.GetNextLine().ToLong( &traceType ) )
  1606. {
  1607. DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is not an integer." );
  1608. file.Close();
  1609. return false;
  1610. }
  1611. name = file.GetNextLine();
  1612. if( name.IsEmpty() )
  1613. {
  1614. DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is empty." );
  1615. file.Close();
  1616. return false;
  1617. }
  1618. param = file.GetNextLine();
  1619. addTrace( name );
  1620. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  1621. TRACE* trace = plotPanel ? plotPanel->GetTrace( name ) : nullptr;
  1622. if( version >= 4 && trace )
  1623. {
  1624. auto addCursor =
  1625. []( int aCursorId, SIM_PLOT_PANEL* aPlotPanel, TRACE* aTrace, double x )
  1626. {
  1627. CURSOR* cursor = new CURSOR( aTrace, aPlotPanel );
  1628. mpWindow* win = aPlotPanel->GetPlotWin();
  1629. cursor->SetName( aTrace->GetName() );
  1630. cursor->SetPen( wxPen( aTrace->GetTraceColour() ) );
  1631. cursor->SetCoordX( x );
  1632. aTrace->SetCursor( aCursorId, cursor );
  1633. win->AddLayer( cursor );
  1634. };
  1635. wxArrayString items = wxSplit( param, '|' );
  1636. for( const wxString& item : items )
  1637. {
  1638. if( item.StartsWith( wxS( "rgb" ) ) )
  1639. {
  1640. wxColour color;
  1641. color.Set( item );
  1642. trace->SetTraceColour( color );
  1643. plotPanel->UpdateTraceStyle( trace );
  1644. }
  1645. else if( item.StartsWith( wxS( "cursor1" ) ) )
  1646. {
  1647. wxArrayString parts = wxSplit( item, ':' );
  1648. double val;
  1649. if( parts.size() == 3 )
  1650. {
  1651. parts[0].AfterFirst( '=' ).ToDouble( &val );
  1652. m_cursorFormats[0][0].FromString( parts[1] );
  1653. m_cursorFormats[0][1].FromString( parts[2] );
  1654. addCursor( 1, plotPanel, trace, val );
  1655. }
  1656. }
  1657. else if( item.StartsWith( wxS( "cursor2" ) ) )
  1658. {
  1659. wxArrayString parts = wxSplit( item, ':' );
  1660. double val;
  1661. if( parts.size() == 3 )
  1662. {
  1663. parts[0].AfterFirst( '=' ).ToDouble( &val );
  1664. m_cursorFormats[1][0].FromString( parts[1] );
  1665. m_cursorFormats[1][1].FromString( parts[2] );
  1666. addCursor( 2, plotPanel, trace, val );
  1667. }
  1668. }
  1669. else if( item.StartsWith( wxS( "cursorD" ) ) )
  1670. {
  1671. wxArrayString parts = wxSplit( item, ':' );
  1672. if( parts.size() == 3 )
  1673. {
  1674. m_cursorFormats[2][0].FromString( parts[1] );
  1675. m_cursorFormats[2][1].FromString( parts[2] );
  1676. }
  1677. }
  1678. }
  1679. plotPanel->UpdatePlotColors();
  1680. }
  1681. }
  1682. }
  1683. long userDefinedSignalCount;
  1684. long measurementCount;
  1685. if( file.GetNextLine().ToLong( &userDefinedSignalCount ) )
  1686. {
  1687. for( long i = 0; i < userDefinedSignalCount; ++i )
  1688. m_userDefinedSignals.push_back( file.GetNextLine() );
  1689. file.GetNextLine().ToLong( &measurementCount );
  1690. m_measurementsGrid->ClearRows();
  1691. m_measurementsGrid->AppendRows( (int) measurementCount + 1 /* empty row at end */ );
  1692. for( int row = 0; row < (int) measurementCount; ++row )
  1693. {
  1694. m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT, file.GetNextLine() );
  1695. m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT_FORMAT, file.GetNextLine() );
  1696. }
  1697. }
  1698. LoadSimulator();
  1699. rebuildSignalsList();
  1700. rebuildSignalsGrid( m_filter->GetValue() );
  1701. updateSignalsGrid();
  1702. updateCursors();
  1703. file.Close();
  1704. wxFileName filename( aPath );
  1705. filename.MakeRelativeTo( Prj().GetProjectPath() );
  1706. // Remember the loaded workbook filename.
  1707. m_simulator->Settings()->SetWorkbookFilename( filename.GetFullPath() );
  1708. updateTitle();
  1709. // Successfully loading a workbook does not count as modifying it. Clear the modified
  1710. // flag after all the EVT_WORKBOOK_MODIFIED events have been processed.
  1711. CallAfter( [=]()
  1712. {
  1713. m_workbookModified = false;
  1714. } );
  1715. return true;
  1716. }
  1717. bool SIM_PLOT_FRAME::SaveWorkbook( const wxString& aPath )
  1718. {
  1719. wxFileName filename = aPath;
  1720. filename.SetExt( WorkbookFileExtension );
  1721. wxTextFile file( filename.GetFullPath() );
  1722. if( file.Exists() )
  1723. {
  1724. if( !file.Open() )
  1725. return false;
  1726. file.Clear();
  1727. }
  1728. else
  1729. {
  1730. file.Create();
  1731. }
  1732. file.AddLine( wxT( "version 4" ) );
  1733. file.AddLine( wxString::Format( wxT( "%llu" ), m_plotNotebook->GetPageCount() ) );
  1734. for( size_t i = 0; i < m_plotNotebook->GetPageCount(); i++ )
  1735. {
  1736. const SIM_PANEL_BASE* basePanel = dynamic_cast<const SIM_PANEL_BASE*>( m_plotNotebook->GetPage( i ) );
  1737. if( !basePanel )
  1738. {
  1739. file.AddLine( wxString::Format( wxT( "%llu" ), 0ull ) );
  1740. continue;
  1741. }
  1742. file.AddLine( wxString::Format( wxT( "%d" ), basePanel->GetType() ) );
  1743. wxString command = basePanel->GetSimCommand();
  1744. int options = basePanel->GetSimOptions();
  1745. if( options & NETLIST_EXPORTER_SPICE::OPTION_ADJUST_INCLUDE_PATHS )
  1746. command += wxT( "\n.kicad adjustpaths" );
  1747. if( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_VOLTAGES )
  1748. command += wxT( "\n.save all" );
  1749. if( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_CURRENTS )
  1750. command += wxT( "\n.probe alli" );
  1751. if( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_DISSIPATIONS )
  1752. command += wxT( "\n.probe allp" );
  1753. file.AddLine( EscapeString( command, CTX_LINE ) );
  1754. const SIM_PLOT_PANEL* plotPanel = dynamic_cast<const SIM_PLOT_PANEL*>( basePanel );
  1755. if( !plotPanel )
  1756. {
  1757. file.AddLine( wxString::Format( wxT( "%llu" ), 0ull ) );
  1758. continue;
  1759. }
  1760. file.AddLine( wxString::Format( wxT( "%llu" ), plotPanel->GetTraces().size() ) );
  1761. for( const auto& [name, trace] : plotPanel->GetTraces() )
  1762. {
  1763. file.AddLine( wxString::Format( wxT( "%d" ), trace->GetType() ) );
  1764. file.AddLine( getTraceTitle( trace->GetName(), trace->GetType() ) );
  1765. wxString msg = COLOR4D( trace->GetTraceColour() ).ToCSSString();
  1766. if( CURSOR* cursor = trace->GetCursor( 1 ) )
  1767. {
  1768. msg += wxString::Format( wxS( "|cursor1=%E:%s:%s" ),
  1769. cursor->GetCoords().x,
  1770. m_cursorFormats[0][0].ToString(),
  1771. m_cursorFormats[0][1].ToString() );
  1772. }
  1773. if( CURSOR* cursor = trace->GetCursor( 2 ) )
  1774. {
  1775. msg += wxString::Format( wxS( "|cursor2=%E:%s:%s" ),
  1776. cursor->GetCoords().x,
  1777. m_cursorFormats[1][0].ToString(),
  1778. m_cursorFormats[1][1].ToString() );
  1779. }
  1780. if( trace->GetCursor( 1 ) || trace->GetCursor( 2 ) )
  1781. {
  1782. msg += wxString::Format( wxS( "|cursorD:%s:%s" ),
  1783. m_cursorFormats[2][0].ToString(),
  1784. m_cursorFormats[2][1].ToString() );
  1785. }
  1786. file.AddLine( msg );
  1787. }
  1788. }
  1789. file.AddLine( wxString::Format( wxT( "%llu" ), m_userDefinedSignals.size() ) );
  1790. for( const wxString& signal : m_userDefinedSignals )
  1791. file.AddLine( signal );
  1792. std::vector<wxString> measurements;
  1793. std::vector<wxString> formats;
  1794. for( int i = 0; i < m_measurementsGrid->GetNumberRows(); ++i )
  1795. {
  1796. if( !m_measurementsGrid->GetCellValue( i, COL_MEASUREMENT ).IsEmpty() )
  1797. {
  1798. measurements.push_back( m_measurementsGrid->GetCellValue( i, COL_MEASUREMENT ) );
  1799. formats.push_back( m_measurementsGrid->GetCellValue( i, COL_MEASUREMENT_FORMAT ) );
  1800. }
  1801. }
  1802. file.AddLine( wxString::Format( wxT( "%llu" ), measurements.size() ) );
  1803. for( size_t i = 0; i < measurements.size(); i++ )
  1804. {
  1805. file.AddLine( measurements[i] );
  1806. file.AddLine( formats[i] );
  1807. }
  1808. bool res = file.Write();
  1809. file.Close();
  1810. // Store the filename of the last saved workbook.
  1811. if( res )
  1812. {
  1813. filename.MakeRelativeTo( Prj().GetProjectPath() );
  1814. m_simulator->Settings()->SetWorkbookFilename( filename.GetFullPath() );
  1815. }
  1816. m_workbookModified = false;
  1817. updateTitle();
  1818. return res;
  1819. }
  1820. SIM_TRACE_TYPE SIM_PLOT_FRAME::getXAxisType( SIM_TYPE aType ) const
  1821. {
  1822. switch( aType )
  1823. {
  1824. /// @todo SPT_LOG_FREQUENCY
  1825. case ST_AC: return SPT_LIN_FREQUENCY;
  1826. case ST_DC: return SPT_SWEEP;
  1827. case ST_TRANSIENT: return SPT_TIME;
  1828. default: wxFAIL_MSG( wxS( "Unhandled simulation type" ) ); return SPT_UNKNOWN;
  1829. }
  1830. }
  1831. void SIM_PLOT_FRAME::ToggleDarkModePlots()
  1832. {
  1833. m_darkMode = !m_darkMode;
  1834. // Rebuild the color list to plot traces
  1835. SIM_PLOT_COLORS::FillDefaultColorList( m_darkMode );
  1836. // Now send changes to all SIM_PLOT_PANEL
  1837. for( size_t page = 0; page < m_plotNotebook->GetPageCount(); page++ )
  1838. {
  1839. wxWindow* curPage = m_plotNotebook->GetPage( page );
  1840. // ensure it is truly a plot panel and not the (zero plots) placeholder
  1841. // which is only SIM_PLOT_PANEL_BASE
  1842. SIM_PLOT_PANEL* panel = dynamic_cast<SIM_PLOT_PANEL*>( curPage );
  1843. if( panel )
  1844. panel->UpdatePlotColors();
  1845. }
  1846. }
  1847. void SIM_PLOT_FRAME::onPlotClose( wxAuiNotebookEvent& event )
  1848. {
  1849. }
  1850. void SIM_PLOT_FRAME::onPlotClosed( wxAuiNotebookEvent& event )
  1851. {
  1852. CallAfter( [this]()
  1853. {
  1854. rebuildSignalsList();
  1855. rebuildSignalsGrid( m_filter->GetValue() );
  1856. updateCursors();
  1857. SIM_PANEL_BASE* panel = getCurrentPlotWindow();
  1858. if( !panel || panel->GetType() != ST_OP )
  1859. {
  1860. SCHEMATIC& schematic = m_schematicFrame->Schematic();
  1861. schematic.ClearOperatingPoints();
  1862. m_schematicFrame->RefreshOperatingPointDisplay();
  1863. }
  1864. } );
  1865. }
  1866. void SIM_PLOT_FRAME::onPlotChanged( wxAuiNotebookEvent& event )
  1867. {
  1868. rebuildSignalsGrid( m_filter->GetValue() );
  1869. updateCursors();
  1870. }
  1871. void SIM_PLOT_FRAME::onPlotDragged( wxAuiNotebookEvent& event )
  1872. {
  1873. }
  1874. void SIM_PLOT_FRAME::onNotebookModified( wxCommandEvent& event )
  1875. {
  1876. m_workbookModified = true;
  1877. updateTitle();
  1878. }
  1879. bool SIM_PLOT_FRAME::EditSimCommand()
  1880. {
  1881. SIM_PANEL_BASE* plotPanelWindow = getCurrentPlotWindow();
  1882. DIALOG_SIM_COMMAND dlg( this, m_circuitModel, m_simulator->Settings() );
  1883. wxString errors;
  1884. WX_STRING_REPORTER reporter( &errors );
  1885. if( !m_circuitModel->ReadSchematicAndLibraries( NETLIST_EXPORTER_SPICE::OPTION_DEFAULT_FLAGS,
  1886. reporter ) )
  1887. {
  1888. DisplayErrorMessage( this, _( "Errors during netlist generation; simulation aborted.\n\n" )
  1889. + errors );
  1890. return false;
  1891. }
  1892. if( m_plotNotebook->GetPageIndex( plotPanelWindow ) != wxNOT_FOUND )
  1893. {
  1894. dlg.SetSimCommand( plotPanelWindow->GetSimCommand() );
  1895. dlg.SetSimOptions( plotPanelWindow->GetSimOptions() );
  1896. }
  1897. else
  1898. {
  1899. dlg.SetSimOptions( NETLIST_EXPORTER_SPICE::OPTION_DEFAULT_FLAGS );
  1900. }
  1901. if( dlg.ShowModal() == wxID_OK )
  1902. {
  1903. wxString oldCommand;
  1904. if( m_plotNotebook->GetPageIndex( plotPanelWindow ) != wxNOT_FOUND )
  1905. oldCommand = plotPanelWindow->GetSimCommand();
  1906. else
  1907. oldCommand = wxString();
  1908. const wxString& newCommand = dlg.GetSimCommand();
  1909. int newOptions = dlg.GetSimOptions();
  1910. SIM_TYPE newSimType = NGSPICE_CIRCUIT_MODEL::CommandToSimType( newCommand );
  1911. if( !plotPanelWindow )
  1912. {
  1913. m_circuitModel->SetSimCommandOverride( newCommand );
  1914. m_circuitModel->SetSimOptions( newOptions );
  1915. plotPanelWindow = NewPlotPanel( newCommand, newOptions );
  1916. }
  1917. // If it is a new simulation type, open a new plot. For the DC sim, check if sweep
  1918. // source type has changed (char 4 will contain 'v', 'i', 'r' or 't'.
  1919. else if( plotPanelWindow->GetType() != newSimType
  1920. || ( newSimType == ST_DC
  1921. && oldCommand.Lower().GetChar( 4 ) != newCommand.Lower().GetChar( 4 ) ) )
  1922. {
  1923. plotPanelWindow = NewPlotPanel( newCommand, newOptions );
  1924. }
  1925. else
  1926. {
  1927. if( m_plotNotebook->GetPageIndex( plotPanelWindow ) == 0 )
  1928. m_circuitModel->SetSimCommandOverride( newCommand );
  1929. // Update simulation command in the current plot
  1930. plotPanelWindow->SetSimCommand( newCommand );
  1931. plotPanelWindow->SetSimOptions( newOptions );
  1932. }
  1933. m_workbookModified = true;
  1934. m_simulator->Init();
  1935. return true;
  1936. }
  1937. return false;
  1938. }
  1939. bool SIM_PLOT_FRAME::canCloseWindow( wxCloseEvent& aEvent )
  1940. {
  1941. if( m_workbookModified )
  1942. {
  1943. wxFileName filename = m_simulator->Settings()->GetWorkbookFilename();
  1944. if( filename.GetName().IsEmpty() )
  1945. {
  1946. if( Prj().GetProjectName().IsEmpty() )
  1947. filename.SetFullName( wxT( "noname.wbk" ) );
  1948. else
  1949. filename.SetFullName( Prj().GetProjectName() + wxT( ".wbk" ) );
  1950. }
  1951. return HandleUnsavedChanges( this, _( "Save changes to workbook?" ),
  1952. [&]() -> bool
  1953. {
  1954. return SaveWorkbook( Prj().AbsolutePath( filename.GetFullName() ) );
  1955. } );
  1956. }
  1957. return true;
  1958. }
  1959. void SIM_PLOT_FRAME::doCloseWindow()
  1960. {
  1961. if( m_simulator->IsRunning() )
  1962. m_simulator->Stop();
  1963. // Prevent memory leak on exit by deleting all simulation vectors
  1964. m_simulator->Clean();
  1965. // Cancel a running simProbe or simTune tool
  1966. m_schematicFrame->GetToolManager()->RunAction( ACTIONS::cancelInteractive );
  1967. SaveSettings( config() );
  1968. m_simulator->Settings() = nullptr;
  1969. Destroy();
  1970. }
  1971. void SIM_PLOT_FRAME::updateCursors()
  1972. {
  1973. SUPPRESS_GRID_CELL_EVENTS raii( this );
  1974. m_cursorsGrid->ClearRows();
  1975. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  1976. if( !plotPanel )
  1977. return;
  1978. // Update cursor values
  1979. CURSOR* cursor1 = nullptr;
  1980. wxString cursor1Name;
  1981. wxString cursor1Units;
  1982. CURSOR* cursor2 = nullptr;
  1983. wxString cursor2Name;
  1984. wxString cursor2Units;
  1985. auto getUnitsY =
  1986. [&]( TRACE* aTrace ) -> wxString
  1987. {
  1988. if( ( aTrace->GetType() & SPT_AC_PHASE ) || ( aTrace->GetType() & SPT_CURRENT ) )
  1989. return plotPanel->GetUnitsY2();
  1990. else if( aTrace->GetType() & SPT_POWER )
  1991. return plotPanel->GetUnitsY3();
  1992. else
  1993. return plotPanel->GetUnitsY1();
  1994. };
  1995. auto getNameY =
  1996. [&]( TRACE* aTrace ) -> wxString
  1997. {
  1998. if( ( aTrace->GetType() & SPT_AC_PHASE ) || ( aTrace->GetType() & SPT_CURRENT ) )
  1999. return plotPanel->GetLabelY2();
  2000. else if( aTrace->GetType() & SPT_POWER )
  2001. return plotPanel->GetLabelY3();
  2002. else
  2003. return plotPanel->GetLabelY1();
  2004. };
  2005. auto formatValue =
  2006. [this]( double aValue, int aCursorId, int aCol ) -> wxString
  2007. {
  2008. return SPICE_VALUE( aValue ).ToString( m_cursorFormats[ aCursorId ][ aCol ] );
  2009. };
  2010. for( const auto& [name, trace] : plotPanel->GetTraces() )
  2011. {
  2012. if( CURSOR* cursor = trace->GetCursor( 1 ) )
  2013. {
  2014. cursor1 = cursor;
  2015. cursor1Name = getNameY( trace );
  2016. cursor1Units = getUnitsY( trace );
  2017. wxRealPoint coords = cursor->GetCoords();
  2018. int row = m_cursorsGrid->GetNumberRows();
  2019. m_cursorFormats[0][0].UpdateUnits( plotPanel->GetUnitsX() );
  2020. m_cursorFormats[0][1].UpdateUnits( cursor1Units );
  2021. m_cursorsGrid->AppendRows( 1 );
  2022. m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "1" ) );
  2023. m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, cursor->GetName() );
  2024. m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, 0, 0 ) );
  2025. m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, 0, 1 ) );
  2026. break;
  2027. }
  2028. }
  2029. for( const auto& [name, trace] : plotPanel->GetTraces() )
  2030. {
  2031. if( CURSOR* cursor = trace->GetCursor( 2 ) )
  2032. {
  2033. cursor2 = cursor;
  2034. cursor2Name = getNameY( trace );
  2035. cursor2Units = getUnitsY( trace );
  2036. wxRealPoint coords = cursor->GetCoords();
  2037. int row = m_cursorsGrid->GetNumberRows();
  2038. m_cursorFormats[1][0].UpdateUnits( plotPanel->GetUnitsX() );
  2039. m_cursorFormats[1][1].UpdateUnits( cursor2Units );
  2040. m_cursorsGrid->AppendRows( 1 );
  2041. m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "2" ) );
  2042. m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, cursor->GetName() );
  2043. m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, 1, 0 ) );
  2044. m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, 1, 1 ) );
  2045. break;
  2046. }
  2047. }
  2048. if( cursor1 && cursor2 && cursor1Units == cursor2Units )
  2049. {
  2050. wxRealPoint coords = cursor2->GetCoords() - cursor1->GetCoords();
  2051. wxString signal;
  2052. m_cursorFormats[2][0].UpdateUnits( plotPanel->GetUnitsX() );
  2053. m_cursorFormats[2][1].UpdateUnits( cursor1Units );
  2054. if( cursor1->GetName() == cursor2->GetName() )
  2055. signal = wxString::Format( wxS( "%s[2 - 1]" ), cursor2->GetName() );
  2056. else
  2057. signal = wxString::Format( wxS( "%s - %s" ), cursor2->GetName(), cursor1->GetName() );
  2058. m_cursorsGrid->AppendRows( 1 );
  2059. m_cursorsGrid->SetCellValue( 2, COL_CURSOR_NAME, _( "Diff" ) );
  2060. m_cursorsGrid->SetCellValue( 2, COL_CURSOR_SIGNAL, signal );
  2061. m_cursorsGrid->SetCellValue( 2, COL_CURSOR_X, formatValue( coords.x, 2, 0 ) );
  2062. m_cursorsGrid->SetCellValue( 2, COL_CURSOR_Y, formatValue( coords.y, 2, 1 ) );
  2063. }
  2064. // Set up the labels
  2065. m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, plotPanel->GetLabelX() );
  2066. wxString valColName = _( "Value" );
  2067. if( !cursor1Name.IsEmpty() )
  2068. {
  2069. if( cursor2Name.IsEmpty() || cursor1Name == cursor2Name )
  2070. valColName = cursor1Name;
  2071. }
  2072. else if( !cursor2Name.IsEmpty() )
  2073. {
  2074. valColName = cursor2Name;
  2075. }
  2076. m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, valColName );
  2077. }
  2078. void SIM_PLOT_FRAME::onCursorUpdate( wxCommandEvent& aEvent )
  2079. {
  2080. updateCursors();
  2081. m_workbookModified = true;
  2082. }
  2083. void SIM_PLOT_FRAME::setupUIConditions()
  2084. {
  2085. EDA_BASE_FRAME::setupUIConditions();
  2086. ACTION_MANAGER* mgr = m_toolManager->GetActionManager();
  2087. wxASSERT( mgr );
  2088. auto showGridCondition =
  2089. [this]( const SELECTION& aSel )
  2090. {
  2091. SIM_PLOT_PANEL* plot = GetCurrentPlot();
  2092. return plot && plot->IsGridShown();
  2093. };
  2094. auto showLegendCondition =
  2095. [this]( const SELECTION& aSel )
  2096. {
  2097. SIM_PLOT_PANEL* plot = GetCurrentPlot();
  2098. return plot && plot->IsLegendShown();
  2099. };
  2100. auto showDottedCondition =
  2101. [this]( const SELECTION& aSel )
  2102. {
  2103. SIM_PLOT_PANEL* plot = GetCurrentPlot();
  2104. return plot && plot->GetDottedSecondary();
  2105. };
  2106. auto darkModePlotCondition =
  2107. [this]( const SELECTION& aSel )
  2108. {
  2109. return m_darkMode;
  2110. };
  2111. auto simRunning =
  2112. [this]( const SELECTION& aSel )
  2113. {
  2114. return m_simulator && m_simulator->IsRunning();
  2115. };
  2116. auto simFinished =
  2117. [this]( const SELECTION& aSel )
  2118. {
  2119. return m_simFinished;
  2120. };
  2121. auto havePlot =
  2122. [this]( const SELECTION& aSel )
  2123. {
  2124. return GetCurrentPlot() != nullptr;
  2125. };
  2126. #define ENABLE( x ) ACTION_CONDITIONS().Enable( x )
  2127. #define CHECK( x ) ACTION_CONDITIONS().Check( x )
  2128. mgr->SetConditions( EE_ACTIONS::openWorkbook, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
  2129. mgr->SetConditions( EE_ACTIONS::saveWorkbook, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
  2130. mgr->SetConditions( EE_ACTIONS::saveWorkbookAs, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
  2131. mgr->SetConditions( EE_ACTIONS::exportPlotAsPNG, ENABLE( havePlot ) );
  2132. mgr->SetConditions( EE_ACTIONS::exportPlotAsCSV, ENABLE( havePlot ) );
  2133. mgr->SetConditions( EE_ACTIONS::toggleGrid, CHECK( showGridCondition ) );
  2134. mgr->SetConditions( EE_ACTIONS::toggleLegend, CHECK( showLegendCondition ) );
  2135. mgr->SetConditions( EE_ACTIONS::toggleDottedSecondary, CHECK( showDottedCondition ) );
  2136. mgr->SetConditions( EE_ACTIONS::toggleDarkModePlots, CHECK( darkModePlotCondition ) );
  2137. mgr->SetConditions( EE_ACTIONS::simCommand, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
  2138. mgr->SetConditions( EE_ACTIONS::runSimulation, ENABLE( !simRunning ) );
  2139. mgr->SetConditions( EE_ACTIONS::stopSimulation, ENABLE( simRunning ) );
  2140. mgr->SetConditions( EE_ACTIONS::simProbe, ENABLE( simFinished ) );
  2141. mgr->SetConditions( EE_ACTIONS::simTune, ENABLE( simFinished ) );
  2142. mgr->SetConditions( EE_ACTIONS::showNetlist, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
  2143. #undef CHECK
  2144. #undef ENABLE
  2145. }
  2146. void SIM_PLOT_FRAME::onSimStarted( wxCommandEvent& aEvent )
  2147. {
  2148. SetCursor( wxCURSOR_ARROWWAIT );
  2149. }
  2150. void SIM_PLOT_FRAME::onSimFinished( wxCommandEvent& aEvent )
  2151. {
  2152. SetCursor( wxCURSOR_ARROW );
  2153. SIM_TYPE simType = m_circuitModel->GetSimType();
  2154. if( simType == ST_UNKNOWN )
  2155. return;
  2156. SIM_PANEL_BASE* plotPanelWindow = getCurrentPlotWindow();
  2157. if( !plotPanelWindow || plotPanelWindow->GetType() != simType )
  2158. {
  2159. plotPanelWindow = NewPlotPanel( m_circuitModel->GetSimCommand(),
  2160. m_circuitModel->GetSimOptions() );
  2161. }
  2162. // Sometimes (for instance with a directive like wrdata my_file.csv "my_signal")
  2163. // the simulator is in idle state (simulation is finished), but still running, during
  2164. // the time the file is written. So gives a slice of time to fully finish the work:
  2165. if( m_simulator->IsRunning() )
  2166. {
  2167. int max_time = 40; // For a max timeout = 2s
  2168. do
  2169. {
  2170. wxMilliSleep( 50 );
  2171. wxYield();
  2172. if( max_time )
  2173. max_time--;
  2174. } while( max_time && m_simulator->IsRunning() );
  2175. }
  2176. // Is a warning message useful if the simulatior is still running?
  2177. m_simFinished = true;
  2178. applyUserDefinedSignals();
  2179. rebuildSignalsList();
  2180. SCHEMATIC& schematic = m_schematicFrame->Schematic();
  2181. schematic.ClearOperatingPoints();
  2182. // If there are any signals plotted, update them
  2183. if( SIM_PANEL_BASE::IsPlottable( simType ) )
  2184. {
  2185. SIM_PLOT_PANEL* plotPanel = dynamic_cast<SIM_PLOT_PANEL*>( plotPanelWindow );
  2186. wxCHECK_RET( plotPanel, wxT( "not a SIM_PLOT_PANEL" ) );
  2187. struct TRACE_DESC
  2188. {
  2189. wxString m_name; ///< Name of the measured SPICE vector
  2190. wxString m_title; ///< User-friendly signal name
  2191. SIM_TRACE_TYPE m_type; ///< Type of the signal
  2192. bool m_current;
  2193. };
  2194. std::vector<struct TRACE_DESC> placeholders;
  2195. // Get information about all the traces on the plot; update those that are still in
  2196. // the signals list and remove any that aren't
  2197. for( const auto& [name, trace] : plotPanel->GetTraces() )
  2198. {
  2199. struct TRACE_DESC placeholder;
  2200. placeholder.m_name = trace->GetName();
  2201. placeholder.m_title = getTraceTitle( trace->GetName(), trace->GetType() );
  2202. placeholder.m_type = trace->GetType();
  2203. placeholder.m_current = false;
  2204. for( const wxString& signal : m_signals )
  2205. {
  2206. if( getTraceName( signal ) == placeholder.m_title )
  2207. {
  2208. placeholder.m_current = true;
  2209. break;
  2210. }
  2211. }
  2212. placeholders.push_back( placeholder );
  2213. }
  2214. for( const struct TRACE_DESC& placeholder : placeholders )
  2215. {
  2216. if( placeholder.m_current )
  2217. updateTrace( placeholder.m_name, placeholder.m_type, plotPanel );
  2218. else
  2219. removeTrace( placeholder.m_name );
  2220. }
  2221. rebuildSignalsGrid( m_filter->GetValue() );
  2222. updateSignalsGrid();
  2223. plotPanel->GetPlotWin()->UpdateAll();
  2224. plotPanel->ResetScales();
  2225. }
  2226. else if( simType == ST_OP )
  2227. {
  2228. m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
  2229. m_simConsole->SetInsertionPointEnd();
  2230. for( const std::string& vec : m_simulator->AllPlots() )
  2231. {
  2232. std::vector<double> val_list = m_simulator->GetRealPlot( vec, 1 );
  2233. if( val_list.size() == 0 ) // The list of values can be empty!
  2234. continue;
  2235. wxString value = SPICE_VALUE( val_list.at( 0 ) ).ToSpiceString();
  2236. wxString msg;
  2237. wxString signal;
  2238. SIM_TRACE_TYPE type = m_circuitModel->VectorToSignal( vec, signal );
  2239. const size_t tab = 25; //characters
  2240. size_t padding = ( signal.length() < tab ) ? ( tab - signal.length() ) : 1;
  2241. value.Append( type == SPT_CURRENT ? wxS( "A" ) : wxS( "V" ) );
  2242. msg.Printf( wxT( "%s%s\n" ),
  2243. ( signal + wxT( ":" ) ).Pad( padding, wxUniChar( ' ' ) ),
  2244. value );
  2245. m_simConsole->AppendText( msg );
  2246. m_simConsole->SetInsertionPointEnd();
  2247. if( signal.StartsWith( wxS( "V(" ) ) || signal.StartsWith( wxS( "I(" ) ) )
  2248. signal = signal.SubString( 2, signal.Length() - 2 );
  2249. schematic.SetOperatingPoint( signal, val_list.at( 0 ) );
  2250. }
  2251. }
  2252. m_schematicFrame->RefreshOperatingPointDisplay();
  2253. for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
  2254. UpdateMeasurement( row );
  2255. m_lastSimPlot = plotPanelWindow;
  2256. }
  2257. void SIM_PLOT_FRAME::onSimUpdate( wxCommandEvent& aEvent )
  2258. {
  2259. static bool updateInProgress = false;
  2260. // skip update when events are triggered too often and previous call didn't end yet
  2261. if( updateInProgress )
  2262. return;
  2263. updateInProgress = true;
  2264. if( m_simulator->IsRunning() )
  2265. m_simulator->Stop();
  2266. if( getCurrentPlotWindow() != m_lastSimPlot )
  2267. {
  2268. // We need to rerun simulation, as the simulator currently stores
  2269. // results for another plot
  2270. StartSimulation();
  2271. }
  2272. else
  2273. {
  2274. std::unique_lock<std::mutex> simulatorLock( m_simulator->GetMutex(), std::try_to_lock );
  2275. if( simulatorLock.owns_lock() )
  2276. {
  2277. // Incremental update
  2278. m_simConsole->Clear();
  2279. // Do not export netlist, it is already stored in the simulator
  2280. applyTuners();
  2281. m_simulator->Run();
  2282. }
  2283. else
  2284. {
  2285. DisplayErrorMessage( this, _( "Another simulation is already running." ) );
  2286. }
  2287. }
  2288. updateInProgress = false;
  2289. }
  2290. void SIM_PLOT_FRAME::onSimReport( wxCommandEvent& aEvent )
  2291. {
  2292. m_simConsole->AppendText( aEvent.GetString() + "\n" );
  2293. m_simConsole->SetInsertionPointEnd();
  2294. }
  2295. void SIM_PLOT_FRAME::onExit( wxCommandEvent& event )
  2296. {
  2297. Kiway().OnKiCadExit();
  2298. }
  2299. wxDEFINE_EVENT( EVT_SIM_UPDATE, wxCommandEvent );
  2300. wxDEFINE_EVENT( EVT_SIM_REPORT, wxCommandEvent );
  2301. wxDEFINE_EVENT( EVT_SIM_STARTED, wxCommandEvent );
  2302. wxDEFINE_EVENT( EVT_SIM_FINISHED, wxCommandEvent );