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.

2966 lines
92 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
3 years ago
5 years ago
5 years ago
9 years ago
9 years ago
5 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. OnModify();
  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. OnModify();
  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. OnModify();
  866. return plotPanel;
  867. }
  868. void SIM_PLOT_FRAME::OnFilterText( wxCommandEvent& aEvent )
  869. {
  870. rebuildSignalsGrid( m_filter->GetValue() );
  871. }
  872. void SIM_PLOT_FRAME::OnFilterMouseMoved( wxMouseEvent& aEvent )
  873. {
  874. wxPoint pos = aEvent.GetPosition();
  875. wxRect ctrlRect = m_filter->GetScreenRect();
  876. int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
  877. if( m_filter->IsSearchButtonVisible() && pos.x < buttonWidth )
  878. SetCursor( wxCURSOR_ARROW );
  879. else if( m_filter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
  880. SetCursor( wxCURSOR_ARROW );
  881. else
  882. SetCursor( wxCURSOR_IBEAM );
  883. }
  884. /**
  885. * For user-defined signals we display the expression such as "V(out)-V(in)", but the SPICE
  886. * signal we actually have to plot will be "user0" or some-such.
  887. */
  888. wxString SIM_PLOT_FRAME::getTraceName( const wxString& aSignalName )
  889. {
  890. if( alg::contains( m_userDefinedSignals, aSignalName ) )
  891. return m_userDefinedSignalToSpiceVecName[ aSignalName ];
  892. return aSignalName;
  893. }
  894. wxString SIM_PLOT_FRAME::getTraceName( int aRow )
  895. {
  896. return getTraceName( m_signalsGrid->GetCellValue( aRow, COL_SIGNAL_NAME ) );
  897. }
  898. /**
  899. * AC-small-signal analyses have two traces per signal, so we suffix the names.
  900. */
  901. wxString SIM_PLOT_FRAME::getTraceTitle( const wxString& aName, SIM_TRACE_TYPE aTraceType )
  902. {
  903. if( aTraceType & SPT_AC_MAG )
  904. return aName + _( " (gain)" );
  905. else if( aTraceType & SPT_AC_PHASE )
  906. return aName + _( " (phase)" );
  907. else
  908. return aName;
  909. }
  910. void SIM_PLOT_FRAME::onSignalsGridCellChanged( wxGridEvent& aEvent )
  911. {
  912. if( m_SuppressGridEvents > 0 )
  913. return;
  914. int row = aEvent.GetRow();
  915. int col = aEvent.GetCol();
  916. wxString text = m_signalsGrid->GetCellValue( row, col );
  917. SIM_PLOT_PANEL* plot = GetCurrentPlot();
  918. if( col == COL_SIGNAL_SHOW )
  919. {
  920. if( text == wxS( "1" ) )
  921. addTrace( getTraceName( row ) );
  922. else
  923. removeTrace( getTraceName( row ) );
  924. // Update enabled/visible states of other controls
  925. updateSignalsGrid();
  926. OnModify();
  927. }
  928. else if( col == COL_SIGNAL_COLOR )
  929. {
  930. KIGFX::COLOR4D color( m_signalsGrid->GetCellValue( row, COL_SIGNAL_COLOR ) );
  931. TRACE* trace = plot->GetTrace( getTraceName( row ) );
  932. if( trace )
  933. {
  934. trace->SetTraceColour( color.ToColour() );
  935. plot->UpdateTraceStyle( trace );
  936. plot->UpdatePlotColors();
  937. OnModify();
  938. }
  939. }
  940. else if( col == COL_CURSOR_1 || col == COL_CURSOR_2 )
  941. {
  942. for( int ii = 0; ii < m_signalsGrid->GetNumberRows(); ++ii )
  943. {
  944. bool enable = ii == row && text == wxS( "1" );
  945. plot->EnableCursor( getTraceName( ii ), col == COL_CURSOR_1 ? 1 : 2, enable );
  946. OnModify();
  947. }
  948. // Update cursor checkboxes (which are really radio buttons)
  949. updateSignalsGrid();
  950. }
  951. }
  952. void SIM_PLOT_FRAME::onCursorsGridCellChanged( wxGridEvent& aEvent )
  953. {
  954. if( m_SuppressGridEvents > 0 )
  955. return;
  956. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  957. if( !plotPanel )
  958. return;
  959. int row = aEvent.GetRow();
  960. int col = aEvent.GetCol();
  961. wxString text = m_cursorsGrid->GetCellValue( row, col );
  962. wxString cursorName = m_cursorsGrid->GetCellValue( row, COL_CURSOR_NAME );
  963. if( col == COL_CURSOR_X )
  964. {
  965. CURSOR* cursor1 = nullptr;
  966. CURSOR* cursor2 = nullptr;
  967. for( const auto& [name, trace] : plotPanel->GetTraces() )
  968. {
  969. if( CURSOR* cursor = trace->GetCursor( 1 ) )
  970. cursor1 = cursor;
  971. if( CURSOR* cursor = trace->GetCursor( 2 ) )
  972. cursor2 = cursor;
  973. }
  974. double value = SPICE_VALUE( text ).ToDouble();
  975. if( cursorName == wxS( "1" ) && cursor1 )
  976. cursor1->SetCoordX( value );
  977. else if( cursorName == wxS( "2" ) && cursor2 )
  978. cursor2->SetCoordX( value );
  979. else if( cursorName == _( "Diff" ) && cursor1 && cursor2 )
  980. cursor2->SetCoordX( cursor1->GetCoords().x + value );
  981. updateCursors();
  982. OnModify();
  983. }
  984. else
  985. {
  986. wxFAIL_MSG( wxT( "All other columns are supposed to be read-only!" ) );
  987. }
  988. }
  989. SPICE_VALUE_FORMAT SIM_PLOT_FRAME::GetMeasureFormat( int aRow ) const
  990. {
  991. SPICE_VALUE_FORMAT result;
  992. result.FromString( m_measurementsGrid->GetCellValue( aRow, COL_MEASUREMENT_FORMAT ) );
  993. return result;
  994. }
  995. void SIM_PLOT_FRAME::SetMeasureFormat( int aRow, const SPICE_VALUE_FORMAT& aFormat )
  996. {
  997. m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_FORMAT, aFormat.ToString() );
  998. OnModify();
  999. }
  1000. void SIM_PLOT_FRAME::DeleteMeasurement( int aRow )
  1001. {
  1002. if( aRow < ( m_measurementsGrid->GetNumberRows() - 1 ) )
  1003. {
  1004. m_measurementsGrid->DeleteRows( aRow, 1 );
  1005. OnModify();
  1006. }
  1007. }
  1008. void SIM_PLOT_FRAME::onMeasurementsGridCellChanged( wxGridEvent& aEvent )
  1009. {
  1010. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  1011. if( !plotPanel )
  1012. return;
  1013. int row = aEvent.GetRow();
  1014. int col = aEvent.GetCol();
  1015. wxString text = m_measurementsGrid->GetCellValue( row, col );
  1016. if( col == COL_MEASUREMENT )
  1017. {
  1018. UpdateMeasurement( row );
  1019. OnModify();
  1020. }
  1021. else
  1022. {
  1023. wxFAIL_MSG( wxT( "All other columns are supposed to be read-only!" ) );
  1024. }
  1025. // Always leave a single empty row for type-in
  1026. int rowCount = (int) m_measurementsGrid->GetNumberRows();
  1027. int emptyRows = 0;
  1028. for( row = rowCount - 1; row >= 0; row-- )
  1029. {
  1030. if( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
  1031. emptyRows++;
  1032. else
  1033. break;
  1034. }
  1035. if( emptyRows > 1 )
  1036. {
  1037. int killRows = emptyRows - 1;
  1038. m_measurementsGrid->DeleteRows( rowCount - killRows, killRows );
  1039. }
  1040. else if( emptyRows == 0 )
  1041. {
  1042. m_measurementsGrid->AppendRows( 1 );
  1043. }
  1044. }
  1045. /**
  1046. * The user measurement looks something like:
  1047. * MAX V(out)
  1048. *
  1049. * We need to send ngspice a "MEAS" command with the analysis type, an output variable name,
  1050. * and the signal name. For our example above, this looks something like:
  1051. * MEAS TRAN meas_result_0 MAX V(out)
  1052. *
  1053. * This is also a good time to harvest the signal name prefix so we know what units to show on
  1054. * the result. For instance, for:
  1055. * MAX P(out)
  1056. * we want to show:
  1057. * 15W
  1058. */
  1059. void SIM_PLOT_FRAME::UpdateMeasurement( int aRow )
  1060. {
  1061. static wxRegEx measureParamsRegEx( wxT( "^"
  1062. " *"
  1063. "([a-zA-Z_]+)"
  1064. " +"
  1065. "([a-zA-Z])\\(([^\\)]+)\\)" ) );
  1066. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  1067. if( !plotPanel )
  1068. return;
  1069. wxString text = m_measurementsGrid->GetCellValue( aRow, COL_MEASUREMENT );
  1070. if( text.IsEmpty() )
  1071. {
  1072. m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_VALUE, wxEmptyString );
  1073. return;
  1074. }
  1075. wxString simType = m_simulator->TypeToName( plotPanel->GetType(), true );
  1076. wxString resultName = wxString::Format( wxS( "meas_result_%u" ), aRow );
  1077. wxString result = wxS( "?" );
  1078. if( measureParamsRegEx.Matches( text ) )
  1079. {
  1080. wxString func = measureParamsRegEx.GetMatch( text, 1 ).Upper();
  1081. wxUniChar signalType = measureParamsRegEx.GetMatch( text, 2 ).Upper()[0];
  1082. wxString deviceName = measureParamsRegEx.GetMatch( text, 3 );
  1083. wxString units;
  1084. SPICE_VALUE_FORMAT fmt = GetMeasureFormat( aRow );
  1085. if( signalType == 'I' )
  1086. units = wxS( "A" );
  1087. else if( signalType == 'P' )
  1088. {
  1089. units = wxS( "W" );
  1090. // Our syntax is different from ngspice for power signals
  1091. text = func + " " + deviceName + ":power";
  1092. }
  1093. else
  1094. units = wxS( "V" );
  1095. if( func.EndsWith( wxS( "_AT" ) ) )
  1096. units = wxS( "s" );
  1097. else if( func.StartsWith( wxS( "INTEG" ) ) )
  1098. {
  1099. switch( plotPanel->GetType() )
  1100. {
  1101. case SIM_TYPE::ST_TRANSIENT:
  1102. if ( signalType == 'P' )
  1103. units = wxS( "J" );
  1104. else
  1105. units += wxS( ".s" );
  1106. break;
  1107. case SIM_TYPE::ST_AC:
  1108. case SIM_TYPE::ST_DISTORTION:
  1109. case SIM_TYPE::ST_NOISE:
  1110. case SIM_TYPE::ST_SENSITIVITY: // If there is a vector, it is frequency
  1111. units += wxS( "·Hz" );
  1112. break;
  1113. case SIM_TYPE::ST_DC: // Could be a lot of things : V, A, deg C, ohm, ...
  1114. case SIM_TYPE::ST_OP: // There is no vector for integration
  1115. case SIM_TYPE::ST_POLE_ZERO: // There is no vector for integration
  1116. case SIM_TYPE::ST_TRANS_FUNC: // There is no vector for integration
  1117. default:
  1118. units += wxS( "·?" );
  1119. break;
  1120. }
  1121. }
  1122. fmt.UpdateUnits( units );
  1123. SetMeasureFormat( aRow, fmt );
  1124. }
  1125. if( m_simFinished )
  1126. {
  1127. wxString cmd = wxString::Format( wxS( "meas %s %s %s" ), simType, resultName, text );
  1128. m_simulator->Command( "echo " + cmd.ToStdString() );
  1129. m_simulator->Command( cmd.ToStdString() );
  1130. std::vector<double> resultVec = m_simulator->GetMagPlot( resultName.ToStdString() );
  1131. if( resultVec.size() > 0 )
  1132. result = SPICE_VALUE( resultVec[0] ).ToString( GetMeasureFormat( aRow ) );
  1133. }
  1134. m_measurementsGrid->SetCellValue( aRow, COL_MEASUREMENT_VALUE, result );
  1135. }
  1136. void SIM_PLOT_FRAME::AddVoltagePlot( const wxString& aNetName )
  1137. {
  1138. doAddPlot( aNetName, SPT_VOLTAGE );
  1139. }
  1140. void SIM_PLOT_FRAME::AddCurrentPlot( const wxString& aDeviceName )
  1141. {
  1142. doAddPlot( aDeviceName, SPT_CURRENT );
  1143. }
  1144. void SIM_PLOT_FRAME::AddTuner( const SCH_SHEET_PATH& aSheetPath, SCH_SYMBOL* aSymbol )
  1145. {
  1146. SIM_PANEL_BASE* plotPanel = getCurrentPlotWindow();
  1147. if( !plotPanel )
  1148. return;
  1149. wxString ref = aSymbol->GetRef( &aSheetPath );
  1150. // Do not add multiple instances for the same component.
  1151. for( TUNER_SLIDER* tuner : m_tuners )
  1152. {
  1153. if( tuner->GetSymbolRef() == ref )
  1154. return;
  1155. }
  1156. const SPICE_ITEM* item = GetExporter()->FindItem( std::string( ref.ToUTF8() ) );
  1157. // Do nothing if the symbol is not tunable.
  1158. if( !item || !item->model->GetTunerParam() )
  1159. return;
  1160. try
  1161. {
  1162. TUNER_SLIDER* tuner = new TUNER_SLIDER( this, m_panelTuners, aSheetPath, aSymbol );
  1163. m_sizerTuners->Add( tuner );
  1164. m_tuners.push_back( tuner );
  1165. m_panelTuners->Layout();
  1166. OnModify();
  1167. }
  1168. catch( const KI_PARAM_ERROR& e )
  1169. {
  1170. DisplayErrorMessage( nullptr, e.What() );
  1171. }
  1172. }
  1173. void SIM_PLOT_FRAME::UpdateTunerValue( const SCH_SHEET_PATH& aSheetPath, const KIID& aSymbol,
  1174. const wxString& aRef, const wxString& aValue )
  1175. {
  1176. SCH_ITEM* item = aSheetPath.GetItem( aSymbol );
  1177. SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( item );
  1178. if( !symbol )
  1179. {
  1180. DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( " " )
  1181. + wxString::Format( _( "%s not found" ), aRef ) );
  1182. return;
  1183. }
  1184. SIM_LIB_MGR mgr( &Prj() );
  1185. SIM_MODEL& model = mgr.CreateModel( &aSheetPath, *symbol ).model;
  1186. const SIM_MODEL::PARAM* tunerParam = model.GetTunerParam();
  1187. if( !tunerParam )
  1188. {
  1189. DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( " " )
  1190. + wxString::Format( _( "%s is not tunable" ), aRef ) );
  1191. return;
  1192. }
  1193. model.SetParamValue( tunerParam->info.name, std::string( aValue.ToUTF8() ) );
  1194. model.WriteFields( symbol->GetFields() );
  1195. m_schematicFrame->UpdateItem( symbol, false, true );
  1196. m_schematicFrame->OnModify();
  1197. }
  1198. void SIM_PLOT_FRAME::RemoveTuner( TUNER_SLIDER* aTuner, bool aErase )
  1199. {
  1200. if( aErase )
  1201. m_tuners.remove( aTuner );
  1202. aTuner->Destroy();
  1203. m_panelTuners->Layout();
  1204. OnModify();
  1205. }
  1206. void SIM_PLOT_FRAME::AddMeasurement( const wxString& aCmd )
  1207. {
  1208. // -1 because the last one is for user inpu
  1209. for ( int i = 0; i < m_measurementsGrid->GetNumberRows(); i++ )
  1210. {
  1211. if ( m_measurementsGrid->GetCellValue( i, COL_MEASUREMENT ) == aCmd )
  1212. return; // Don't create duplicates
  1213. }
  1214. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  1215. if( !plotPanel )
  1216. return;
  1217. wxString simType = m_simulator->TypeToName( plotPanel->GetType(), true );
  1218. int row;
  1219. for( row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
  1220. {
  1221. if( m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
  1222. break;
  1223. }
  1224. if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
  1225. {
  1226. m_measurementsGrid->AppendRows( 1 );
  1227. row = m_measurementsGrid->GetNumberRows() - 1;
  1228. }
  1229. m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT, aCmd );
  1230. SetMeasureFormat( row, { 3, wxS( "~V" ) } );
  1231. UpdateMeasurement( row );
  1232. OnModify();
  1233. // Always leave at least one empty row for type-in:
  1234. row = m_measurementsGrid->GetNumberRows() - 1;
  1235. if( !m_measurementsGrid->GetCellValue( row, COL_MEASUREMENT ).IsEmpty() )
  1236. m_measurementsGrid->AppendRows( 1 );
  1237. }
  1238. SIM_PLOT_PANEL* SIM_PLOT_FRAME::GetCurrentPlot() const
  1239. {
  1240. SIM_PANEL_BASE* curPage = getCurrentPlotWindow();
  1241. return !curPage || curPage->GetType() == ST_UNKNOWN ? nullptr
  1242. : dynamic_cast<SIM_PLOT_PANEL*>( curPage );
  1243. }
  1244. const NGSPICE_CIRCUIT_MODEL* SIM_PLOT_FRAME::GetExporter() const
  1245. {
  1246. return m_circuitModel.get();
  1247. }
  1248. void SIM_PLOT_FRAME::doAddPlot( const wxString& aName, SIM_TRACE_TYPE aType )
  1249. {
  1250. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  1251. if( !plotPanel )
  1252. {
  1253. m_simConsole->AppendText( _( "Error: no current simulation.\n" ) );
  1254. m_simConsole->SetInsertionPointEnd();
  1255. return;
  1256. }
  1257. SIM_TYPE simType = NGSPICE_CIRCUIT_MODEL::CommandToSimType( plotPanel->GetSimCommand() );
  1258. if( simType == ST_UNKNOWN )
  1259. {
  1260. m_simConsole->AppendText( _( "Error: simulation type not defined.\n" ) );
  1261. m_simConsole->SetInsertionPointEnd();
  1262. return;
  1263. }
  1264. else if( !SIM_PANEL_BASE::IsPlottable( simType ) )
  1265. {
  1266. m_simConsole->AppendText( _( "Error: simulation type doesn't support plotting.\n" ) );
  1267. m_simConsole->SetInsertionPointEnd();
  1268. return;
  1269. }
  1270. SIM_TRACE_TYPE xAxisType = getXAxisType( simType );
  1271. if( ( xAxisType == SPT_LIN_FREQUENCY || xAxisType == SPT_LOG_FREQUENCY )
  1272. && ( aType & ( SPT_AC_MAG | SPT_AC_PHASE ) ) == 0 )
  1273. {
  1274. // If magnitude or phase wasn't specified, then add both
  1275. updateTrace( aName, (SIM_TRACE_TYPE) ( aType | SPT_AC_MAG ), plotPanel );
  1276. updateTrace( aName, (SIM_TRACE_TYPE) ( aType | SPT_AC_PHASE ), plotPanel );
  1277. }
  1278. else
  1279. {
  1280. updateTrace( aName, aType, plotPanel );
  1281. }
  1282. updateSignalsGrid();
  1283. OnModify();
  1284. }
  1285. void SIM_PLOT_FRAME::SetUserDefinedSignals( const std::vector<wxString>& aNewSignals )
  1286. {
  1287. for( const wxString& signal : m_userDefinedSignals )
  1288. {
  1289. if( !alg::contains( aNewSignals, signal ) )
  1290. removeTrace( m_userDefinedSignalToSpiceVecName[ signal ] );
  1291. }
  1292. m_userDefinedSignals = aNewSignals;
  1293. if( m_simFinished )
  1294. applyUserDefinedSignals();
  1295. rebuildSignalsList();
  1296. rebuildSignalsGrid( m_filter->GetValue() );
  1297. updateSignalsGrid();
  1298. OnModify();
  1299. }
  1300. void SIM_PLOT_FRAME::addTrace( const wxString& aSignalName )
  1301. {
  1302. if( aSignalName.IsEmpty() )
  1303. return;
  1304. wxString baseSignal = aSignalName;
  1305. wxString gainSuffix = _( " (gain)" );
  1306. wxString phaseSuffix = _( " (phase)" );
  1307. wxUniChar firstChar = aSignalName.Upper()[0];
  1308. int traceType = SPT_UNKNOWN;
  1309. if( firstChar == 'V' )
  1310. traceType = SPT_VOLTAGE;
  1311. else if( firstChar == 'I' )
  1312. traceType = SPT_CURRENT;
  1313. else if( firstChar == 'P' )
  1314. traceType = SPT_POWER;
  1315. if( aSignalName.EndsWith( gainSuffix ) )
  1316. {
  1317. traceType |= SPT_AC_MAG;
  1318. baseSignal = aSignalName.Left( aSignalName.Length() - gainSuffix.Length() );
  1319. }
  1320. else if( aSignalName.EndsWith( phaseSuffix ) )
  1321. {
  1322. traceType |= SPT_AC_PHASE;
  1323. baseSignal = aSignalName.Left( aSignalName.Length() - phaseSuffix.Length() );
  1324. }
  1325. if( traceType != SPT_UNKNOWN )
  1326. {
  1327. if( SIM_PLOT_PANEL* plotPanel = GetCurrentPlot() )
  1328. updateTrace( baseSignal, (SIM_TRACE_TYPE) traceType, plotPanel );
  1329. }
  1330. }
  1331. void SIM_PLOT_FRAME::removeTrace( const wxString& aSignalName )
  1332. {
  1333. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  1334. if( !plotPanel )
  1335. return;
  1336. wxASSERT( plotPanel->TraceShown( aSignalName ) );
  1337. if( plotPanel->DeleteTrace( aSignalName ) )
  1338. OnModify();
  1339. plotPanel->GetPlotWin()->Fit();
  1340. updateSignalsGrid();
  1341. updateCursors();
  1342. }
  1343. void SIM_PLOT_FRAME::updateTrace( const wxString& aName, SIM_TRACE_TYPE aTraceType,
  1344. SIM_PLOT_PANEL* aPlotPanel )
  1345. {
  1346. SIM_TYPE simType = NGSPICE_CIRCUIT_MODEL::CommandToSimType( aPlotPanel->GetSimCommand() );
  1347. aTraceType = (SIM_TRACE_TYPE) ( aTraceType & SPT_Y_AXIS_MASK );
  1348. aTraceType = (SIM_TRACE_TYPE) ( aTraceType | getXAxisType( simType ) );
  1349. wxString traceTitle = getTraceTitle( aName, aTraceType );
  1350. wxString vectorName = aName;
  1351. if( aTraceType & SPT_POWER )
  1352. vectorName = vectorName.AfterFirst( '(' ).BeforeLast( ')' ) + wxS( ":power" );
  1353. if( !SIM_PANEL_BASE::IsPlottable( simType ) )
  1354. {
  1355. // There is no plot to be shown
  1356. m_simulator->Command( wxString::Format( wxT( "print %s" ), aName ).ToStdString() );
  1357. return;
  1358. }
  1359. // First, handle the x axis
  1360. wxString xAxisName( m_simulator->GetXAxis( simType ) );
  1361. if( xAxisName.IsEmpty() )
  1362. return;
  1363. std::vector<double> data_x;
  1364. std::vector<double> data_y;
  1365. if( m_simFinished )
  1366. {
  1367. data_x = m_simulator->GetMagPlot( (const char*) xAxisName.c_str() );
  1368. switch( simType )
  1369. {
  1370. case ST_AC:
  1371. if( aTraceType & SPT_AC_MAG )
  1372. data_y = m_simulator->GetMagPlot( (const char*) vectorName.c_str() );
  1373. else if( aTraceType & SPT_AC_PHASE )
  1374. data_y = m_simulator->GetPhasePlot( (const char*) vectorName.c_str() );
  1375. else
  1376. wxFAIL_MSG( wxT( "Plot type missing AC_PHASE or AC_MAG bit" ) );
  1377. break;
  1378. case ST_NOISE:
  1379. case ST_DC:
  1380. case ST_TRANSIENT:
  1381. data_y = m_simulator->GetMagPlot( (const char*) vectorName.c_str() );
  1382. break;
  1383. default:
  1384. wxFAIL_MSG( wxT( "Unhandled plot type" ) );
  1385. }
  1386. }
  1387. unsigned int size = data_x.size();
  1388. // If we did a two-source DC analysis, we need to split the resulting vector and add traces
  1389. // for each input step
  1390. SPICE_DC_PARAMS source1, source2;
  1391. if( simType == ST_DC
  1392. && m_circuitModel->ParseDCCommand( m_circuitModel->GetSimCommand(), &source1, &source2 )
  1393. && !source2.m_source.IsEmpty() )
  1394. {
  1395. // Source 1 is the inner loop, so lets add traces for each Source 2 (outer loop) step
  1396. SPICE_VALUE v = source2.m_vstart;
  1397. wxString name;
  1398. size_t offset = 0;
  1399. size_t outer = ( size_t )( ( source2.m_vend - v ) / source2.m_vincrement ).ToDouble();
  1400. size_t inner = data_x.size() / ( outer + 1 );
  1401. wxASSERT( data_x.size() % ( outer + 1 ) == 0 );
  1402. for( size_t idx = 0; idx <= outer; idx++ )
  1403. {
  1404. name = wxString::Format( wxT( "%s (%s = %s V)" ),
  1405. traceTitle,
  1406. source2.m_source,
  1407. v.ToString() );
  1408. if( TRACE* trace = aPlotPanel->AddTrace( name, aName, aTraceType ) )
  1409. {
  1410. if( data_y.size() >= size )
  1411. {
  1412. std::vector<double> sub_x( data_x.begin() + offset,
  1413. data_x.begin() + offset + inner );
  1414. std::vector<double> sub_y( data_y.begin() + offset,
  1415. data_y.begin() + offset + inner );
  1416. aPlotPanel->SetTraceData( trace, inner, sub_x.data(), sub_y.data() );
  1417. }
  1418. }
  1419. v = v + source2.m_vincrement;
  1420. offset += inner;
  1421. }
  1422. }
  1423. else if( TRACE* trace = aPlotPanel->AddTrace( traceTitle, aName, aTraceType ) )
  1424. {
  1425. if( data_y.size() >= size )
  1426. aPlotPanel->SetTraceData( trace, size, data_x.data(), data_y.data() );
  1427. }
  1428. }
  1429. void SIM_PLOT_FRAME::updateSignalsGrid()
  1430. {
  1431. SIM_PLOT_PANEL* plot = GetCurrentPlot();
  1432. for( int row = 0; row < m_signalsGrid->GetNumberRows(); ++row )
  1433. {
  1434. if( TRACE* trace = plot ? plot->GetTrace( getTraceName( row ) ) : nullptr )
  1435. {
  1436. m_signalsGrid->SetCellValue( row, COL_SIGNAL_SHOW, wxS( "1" ) );
  1437. wxGridCellAttr* attr = new wxGridCellAttr;
  1438. attr->SetRenderer( new GRID_CELL_COLOR_RENDERER( this ) );
  1439. attr->SetEditor( new GRID_CELL_COLOR_SELECTOR( this, m_signalsGrid ) );
  1440. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  1441. m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
  1442. KIGFX::COLOR4D color( trace->GetPen().GetColour() );
  1443. m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, color.ToCSSString() );
  1444. attr = new wxGridCellAttr;
  1445. attr->SetRenderer( new wxGridCellBoolRenderer() );
  1446. attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
  1447. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  1448. m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
  1449. attr = new wxGridCellAttr;
  1450. attr->SetRenderer( new wxGridCellBoolRenderer() );
  1451. attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
  1452. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  1453. m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
  1454. if( trace->HasCursor( 1 ) )
  1455. m_signalsGrid->SetCellValue( row, COL_CURSOR_1, wxS( "1" ) );
  1456. else
  1457. m_signalsGrid->SetCellValue( row, COL_CURSOR_1, wxEmptyString );
  1458. if( trace->HasCursor( 2 ) )
  1459. m_signalsGrid->SetCellValue( row, COL_CURSOR_2, wxS( "1" ) );
  1460. else
  1461. m_signalsGrid->SetCellValue( row, COL_CURSOR_2, wxEmptyString );
  1462. }
  1463. else
  1464. {
  1465. m_signalsGrid->SetCellValue( row, COL_SIGNAL_SHOW, wxEmptyString );
  1466. wxGridCellAttr* attr = new wxGridCellAttr;
  1467. attr->SetReadOnly();
  1468. m_signalsGrid->SetAttr( row, COL_SIGNAL_COLOR, attr );
  1469. m_signalsGrid->SetCellValue( row, COL_SIGNAL_COLOR, wxEmptyString );
  1470. attr = new wxGridCellAttr;
  1471. attr->SetReadOnly();
  1472. m_signalsGrid->SetAttr( row, COL_CURSOR_1, attr );
  1473. m_signalsGrid->SetCellValue( row, COL_CURSOR_1, wxEmptyString );
  1474. attr = new wxGridCellAttr;
  1475. attr->SetReadOnly();
  1476. m_signalsGrid->SetAttr( row, COL_CURSOR_2, attr );
  1477. m_signalsGrid->SetCellValue( row, COL_CURSOR_2, wxEmptyString );
  1478. }
  1479. }
  1480. }
  1481. void SIM_PLOT_FRAME::applyUserDefinedSignals()
  1482. {
  1483. auto quoteNetNames =
  1484. [&]( wxString aExpression ) -> wxString
  1485. {
  1486. for( const auto& [netname, quotedNetname] : m_quotedNetnames )
  1487. aExpression.Replace( netname, quotedNetname );
  1488. return aExpression;
  1489. };
  1490. for( int ii = 0; ii < (int) m_userDefinedSignals.size(); ++ii )
  1491. {
  1492. wxString signal = m_userDefinedSignals[ii];
  1493. std::string cmd = "let user{} = {}";
  1494. m_simulator->Command( "echo " + fmt::format(cmd, ii, signal.ToStdString() ) );
  1495. m_simulator->Command( fmt::format( cmd, ii, quoteNetNames( signal ).ToStdString() ) );
  1496. }
  1497. }
  1498. void SIM_PLOT_FRAME::applyTuners()
  1499. {
  1500. wxString errors;
  1501. WX_STRING_REPORTER reporter( &errors );
  1502. for( const TUNER_SLIDER* tuner : m_tuners )
  1503. {
  1504. SCH_SHEET_PATH sheetPath;
  1505. wxString ref = tuner->GetSymbolRef();
  1506. KIID symbolId = tuner->GetSymbol( &sheetPath );
  1507. SCH_ITEM* schItem = sheetPath.GetItem( symbolId );
  1508. SCH_SYMBOL* symbol = dynamic_cast<SCH_SYMBOL*>( schItem );
  1509. if( !symbol )
  1510. {
  1511. reporter.Report( wxString::Format( _( "%s not found" ), ref ) );
  1512. continue;
  1513. }
  1514. const SPICE_ITEM* item = GetExporter()->FindItem( tuner->GetSymbolRef().ToStdString() );
  1515. if( !item || !item->model->GetTunerParam() )
  1516. {
  1517. reporter.Report( wxString::Format( _( "%s is not tunable" ), ref ) );
  1518. continue;
  1519. }
  1520. double floatVal = tuner->GetValue().ToDouble();
  1521. m_simulator->Command( item->model->SpiceGenerator().TunerCommand( *item, floatVal ) );
  1522. }
  1523. if( reporter.HasMessage() )
  1524. DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( "\n" ) + errors );
  1525. }
  1526. bool SIM_PLOT_FRAME::LoadWorkbook( const wxString& aPath )
  1527. {
  1528. m_plotNotebook->DeleteAllPages();
  1529. wxTextFile file( aPath );
  1530. #define DISPLAY_LOAD_ERROR( fmt ) DisplayErrorMessage( this, wxString::Format( _( fmt ), \
  1531. file.GetCurrentLine()+1 ) )
  1532. if( !file.Open() )
  1533. return false;
  1534. long version = 1;
  1535. wxString firstLine = file.GetFirstLine();
  1536. wxString plotCountLine;
  1537. if( firstLine.StartsWith( wxT( "version " ) ) )
  1538. {
  1539. if( !firstLine.substr( 8 ).ToLong( &version ) )
  1540. {
  1541. DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is not an integer." );
  1542. file.Close();
  1543. return false;
  1544. }
  1545. plotCountLine = file.GetNextLine();
  1546. }
  1547. else
  1548. {
  1549. plotCountLine = firstLine;
  1550. }
  1551. long plotsCount;
  1552. if( !plotCountLine.ToLong( &plotsCount ) )
  1553. {
  1554. DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is not an integer." );
  1555. file.Close();
  1556. return false;
  1557. }
  1558. for( long i = 0; i < plotsCount; ++i )
  1559. {
  1560. long plotType, tracesCount;
  1561. if( !file.GetNextLine().ToLong( &plotType ) )
  1562. {
  1563. DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is not an integer." );
  1564. file.Close();
  1565. return false;
  1566. }
  1567. wxString command = UnescapeString( file.GetNextLine() );
  1568. wxString simCommand;
  1569. int simOptions = NETLIST_EXPORTER_SPICE::OPTION_DEFAULT_FLAGS;
  1570. wxStringTokenizer tokenizer( command, wxT( "\r\n" ), wxTOKEN_STRTOK );
  1571. if( version >= 2 )
  1572. {
  1573. simOptions &= ~NETLIST_EXPORTER_SPICE::OPTION_ADJUST_INCLUDE_PATHS;
  1574. simOptions &= ~NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_VOLTAGES;
  1575. simOptions &= ~NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_CURRENTS;
  1576. }
  1577. else if( version >= 3 )
  1578. {
  1579. simOptions &= ~NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_DISSIPATIONS;
  1580. }
  1581. while( tokenizer.HasMoreTokens() )
  1582. {
  1583. wxString line = tokenizer.GetNextToken();
  1584. if( line.StartsWith( wxT( ".kicad adjustpaths" ) ) )
  1585. simOptions |= NETLIST_EXPORTER_SPICE::OPTION_ADJUST_INCLUDE_PATHS;
  1586. else if( line.StartsWith( wxT( ".save all" ) ) )
  1587. simOptions |= NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_VOLTAGES;
  1588. else if( line.StartsWith( wxT( ".probe alli" ) ) )
  1589. simOptions |= NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_CURRENTS;
  1590. else if( line.StartsWith( wxT( ".probe allp" ) ) )
  1591. simOptions |= NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_DISSIPATIONS;
  1592. else
  1593. simCommand += line + wxT( "\n" );
  1594. }
  1595. NewPlotPanel( simCommand, simOptions );
  1596. if( !file.GetNextLine().ToLong( &tracesCount ) )
  1597. {
  1598. DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is not an integer." );
  1599. file.Close();
  1600. return false;
  1601. }
  1602. for( long j = 0; j < tracesCount; ++j )
  1603. {
  1604. long traceType;
  1605. wxString name, param;
  1606. if( !file.GetNextLine().ToLong( &traceType ) )
  1607. {
  1608. DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is not an integer." );
  1609. file.Close();
  1610. return false;
  1611. }
  1612. name = file.GetNextLine();
  1613. if( name.IsEmpty() )
  1614. {
  1615. DISPLAY_LOAD_ERROR( "Error loading workbook: Line %d is empty." );
  1616. file.Close();
  1617. return false;
  1618. }
  1619. param = file.GetNextLine();
  1620. addTrace( name );
  1621. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  1622. TRACE* trace = plotPanel ? plotPanel->GetTrace( name ) : nullptr;
  1623. if( version >= 4 && trace )
  1624. {
  1625. auto addCursor =
  1626. []( int aCursorId, SIM_PLOT_PANEL* aPlotPanel, TRACE* aTrace, double x )
  1627. {
  1628. CURSOR* cursor = new CURSOR( aTrace, aPlotPanel );
  1629. mpWindow* win = aPlotPanel->GetPlotWin();
  1630. cursor->SetName( aTrace->GetName() );
  1631. cursor->SetPen( wxPen( aTrace->GetTraceColour() ) );
  1632. cursor->SetCoordX( x );
  1633. aTrace->SetCursor( aCursorId, cursor );
  1634. win->AddLayer( cursor );
  1635. };
  1636. wxArrayString items = wxSplit( param, '|' );
  1637. for( const wxString& item : items )
  1638. {
  1639. if( item.StartsWith( wxS( "rgb" ) ) )
  1640. {
  1641. wxColour color;
  1642. color.Set( item );
  1643. trace->SetTraceColour( color );
  1644. plotPanel->UpdateTraceStyle( trace );
  1645. }
  1646. else if( item.StartsWith( wxS( "cursor1" ) ) )
  1647. {
  1648. wxArrayString parts = wxSplit( item, ':' );
  1649. double val;
  1650. if( parts.size() == 3 )
  1651. {
  1652. parts[0].AfterFirst( '=' ).ToDouble( &val );
  1653. m_cursorFormats[0][0].FromString( parts[1] );
  1654. m_cursorFormats[0][1].FromString( parts[2] );
  1655. addCursor( 1, plotPanel, trace, val );
  1656. }
  1657. }
  1658. else if( item.StartsWith( wxS( "cursor2" ) ) )
  1659. {
  1660. wxArrayString parts = wxSplit( item, ':' );
  1661. double val;
  1662. if( parts.size() == 3 )
  1663. {
  1664. parts[0].AfterFirst( '=' ).ToDouble( &val );
  1665. m_cursorFormats[1][0].FromString( parts[1] );
  1666. m_cursorFormats[1][1].FromString( parts[2] );
  1667. addCursor( 2, plotPanel, trace, val );
  1668. }
  1669. }
  1670. else if( item.StartsWith( wxS( "cursorD" ) ) )
  1671. {
  1672. wxArrayString parts = wxSplit( item, ':' );
  1673. if( parts.size() == 3 )
  1674. {
  1675. m_cursorFormats[2][0].FromString( parts[1] );
  1676. m_cursorFormats[2][1].FromString( parts[2] );
  1677. }
  1678. }
  1679. else if( item == wxS( "dottedSecondary" ) )
  1680. {
  1681. plotPanel->SetDottedSecondary( true );
  1682. }
  1683. else if( item == wxS( "showLegend" ) )
  1684. {
  1685. plotPanel->ShowLegend( true );
  1686. }
  1687. else if( item == wxS( "hideGrid" ) )
  1688. {
  1689. plotPanel->ShowGrid( false );
  1690. }
  1691. }
  1692. plotPanel->UpdatePlotColors();
  1693. }
  1694. }
  1695. }
  1696. long userDefinedSignalCount;
  1697. long measurementCount;
  1698. if( file.GetNextLine().ToLong( &userDefinedSignalCount ) )
  1699. {
  1700. for( long i = 0; i < userDefinedSignalCount; ++i )
  1701. m_userDefinedSignals.push_back( file.GetNextLine() );
  1702. file.GetNextLine().ToLong( &measurementCount );
  1703. m_measurementsGrid->ClearRows();
  1704. m_measurementsGrid->AppendRows( (int) measurementCount + 1 /* empty row at end */ );
  1705. for( int row = 0; row < (int) measurementCount; ++row )
  1706. {
  1707. m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT, file.GetNextLine() );
  1708. m_measurementsGrid->SetCellValue( row, COL_MEASUREMENT_FORMAT, file.GetNextLine() );
  1709. }
  1710. }
  1711. LoadSimulator();
  1712. rebuildSignalsList();
  1713. rebuildSignalsGrid( m_filter->GetValue() );
  1714. updateSignalsGrid();
  1715. updateCursors();
  1716. file.Close();
  1717. wxFileName filename( aPath );
  1718. filename.MakeRelativeTo( Prj().GetProjectPath() );
  1719. // Remember the loaded workbook filename.
  1720. m_simulator->Settings()->SetWorkbookFilename( filename.GetFullPath() );
  1721. updateTitle();
  1722. // Successfully loading a workbook does not count as modifying it. Clear the modified
  1723. // flag after all the EVT_WORKBOOK_MODIFIED events have been processed.
  1724. CallAfter( [=]()
  1725. {
  1726. m_workbookModified = false;
  1727. } );
  1728. return true;
  1729. }
  1730. bool SIM_PLOT_FRAME::SaveWorkbook( const wxString& aPath )
  1731. {
  1732. wxFileName filename = aPath;
  1733. filename.SetExt( WorkbookFileExtension );
  1734. wxTextFile file( filename.GetFullPath() );
  1735. if( file.Exists() )
  1736. {
  1737. if( !file.Open() )
  1738. return false;
  1739. file.Clear();
  1740. }
  1741. else
  1742. {
  1743. file.Create();
  1744. }
  1745. file.AddLine( wxT( "version 4" ) );
  1746. file.AddLine( wxString::Format( wxT( "%llu" ), m_plotNotebook->GetPageCount() ) );
  1747. for( size_t i = 0; i < m_plotNotebook->GetPageCount(); i++ )
  1748. {
  1749. const SIM_PANEL_BASE* basePanel = dynamic_cast<const SIM_PANEL_BASE*>( m_plotNotebook->GetPage( i ) );
  1750. if( !basePanel )
  1751. {
  1752. file.AddLine( wxString::Format( wxT( "%llu" ), 0ull ) );
  1753. continue;
  1754. }
  1755. file.AddLine( wxString::Format( wxT( "%d" ), basePanel->GetType() ) );
  1756. wxString command = basePanel->GetSimCommand();
  1757. int options = basePanel->GetSimOptions();
  1758. if( options & NETLIST_EXPORTER_SPICE::OPTION_ADJUST_INCLUDE_PATHS )
  1759. command += wxT( "\n.kicad adjustpaths" );
  1760. if( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_VOLTAGES )
  1761. command += wxT( "\n.save all" );
  1762. if( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_CURRENTS )
  1763. command += wxT( "\n.probe alli" );
  1764. if( options & NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_DISSIPATIONS )
  1765. command += wxT( "\n.probe allp" );
  1766. file.AddLine( EscapeString( command, CTX_LINE ) );
  1767. const SIM_PLOT_PANEL* plotPanel = dynamic_cast<const SIM_PLOT_PANEL*>( basePanel );
  1768. if( !plotPanel )
  1769. {
  1770. file.AddLine( wxString::Format( wxT( "%llu" ), 0ull ) );
  1771. continue;
  1772. }
  1773. file.AddLine( wxString::Format( wxT( "%llu" ), plotPanel->GetTraces().size() ) );
  1774. for( const auto& [name, trace] : plotPanel->GetTraces() )
  1775. {
  1776. file.AddLine( wxString::Format( wxT( "%d" ), trace->GetType() ) );
  1777. file.AddLine( getTraceTitle( trace->GetName(), trace->GetType() ) );
  1778. wxString msg = COLOR4D( trace->GetTraceColour() ).ToCSSString();
  1779. if( CURSOR* cursor = trace->GetCursor( 1 ) )
  1780. {
  1781. msg += wxString::Format( wxS( "|cursor1=%E:%s:%s" ),
  1782. cursor->GetCoords().x,
  1783. m_cursorFormats[0][0].ToString(),
  1784. m_cursorFormats[0][1].ToString() );
  1785. }
  1786. if( CURSOR* cursor = trace->GetCursor( 2 ) )
  1787. {
  1788. msg += wxString::Format( wxS( "|cursor2=%E:%s:%s" ),
  1789. cursor->GetCoords().x,
  1790. m_cursorFormats[1][0].ToString(),
  1791. m_cursorFormats[1][1].ToString() );
  1792. }
  1793. if( trace->GetCursor( 1 ) || trace->GetCursor( 2 ) )
  1794. {
  1795. msg += wxString::Format( wxS( "|cursorD:%s:%s" ),
  1796. m_cursorFormats[2][0].ToString(),
  1797. m_cursorFormats[2][1].ToString() );
  1798. }
  1799. if( plotPanel->GetDottedSecondary() )
  1800. msg += wxS( "|dottedSecondary" );
  1801. if( plotPanel->IsLegendShown() )
  1802. msg += wxS( "|showLegend" );
  1803. if( !plotPanel->IsGridShown() )
  1804. msg += wxS( "|hideGrid" );
  1805. file.AddLine( msg );
  1806. }
  1807. }
  1808. file.AddLine( wxString::Format( wxT( "%llu" ), m_userDefinedSignals.size() ) );
  1809. for( const wxString& signal : m_userDefinedSignals )
  1810. file.AddLine( signal );
  1811. std::vector<wxString> measurements;
  1812. std::vector<wxString> formats;
  1813. for( int i = 0; i < m_measurementsGrid->GetNumberRows(); ++i )
  1814. {
  1815. if( !m_measurementsGrid->GetCellValue( i, COL_MEASUREMENT ).IsEmpty() )
  1816. {
  1817. measurements.push_back( m_measurementsGrid->GetCellValue( i, COL_MEASUREMENT ) );
  1818. formats.push_back( m_measurementsGrid->GetCellValue( i, COL_MEASUREMENT_FORMAT ) );
  1819. }
  1820. }
  1821. file.AddLine( wxString::Format( wxT( "%llu" ), measurements.size() ) );
  1822. for( size_t i = 0; i < measurements.size(); i++ )
  1823. {
  1824. file.AddLine( measurements[i] );
  1825. file.AddLine( formats[i] );
  1826. }
  1827. bool res = file.Write();
  1828. file.Close();
  1829. // Store the filename of the last saved workbook.
  1830. if( res )
  1831. {
  1832. filename.MakeRelativeTo( Prj().GetProjectPath() );
  1833. m_simulator->Settings()->SetWorkbookFilename( filename.GetFullPath() );
  1834. }
  1835. m_workbookModified = false;
  1836. updateTitle();
  1837. return res;
  1838. }
  1839. SIM_TRACE_TYPE SIM_PLOT_FRAME::getXAxisType( SIM_TYPE aType ) const
  1840. {
  1841. switch( aType )
  1842. {
  1843. /// @todo SPT_LOG_FREQUENCY
  1844. case ST_AC: return SPT_LIN_FREQUENCY;
  1845. case ST_DC: return SPT_SWEEP;
  1846. case ST_TRANSIENT: return SPT_TIME;
  1847. default: wxFAIL_MSG( wxS( "Unhandled simulation type" ) ); return SPT_UNKNOWN;
  1848. }
  1849. }
  1850. void SIM_PLOT_FRAME::ToggleDarkModePlots()
  1851. {
  1852. m_darkMode = !m_darkMode;
  1853. // Rebuild the color list to plot traces
  1854. SIM_PLOT_COLORS::FillDefaultColorList( m_darkMode );
  1855. // Now send changes to all SIM_PLOT_PANEL
  1856. for( size_t page = 0; page < m_plotNotebook->GetPageCount(); page++ )
  1857. {
  1858. wxWindow* curPage = m_plotNotebook->GetPage( page );
  1859. // ensure it is truly a plot panel and not the (zero plots) placeholder
  1860. // which is only SIM_PLOT_PANEL_BASE
  1861. SIM_PLOT_PANEL* panel = dynamic_cast<SIM_PLOT_PANEL*>( curPage );
  1862. if( panel )
  1863. panel->UpdatePlotColors();
  1864. }
  1865. }
  1866. void SIM_PLOT_FRAME::onPlotClose( wxAuiNotebookEvent& event )
  1867. {
  1868. }
  1869. void SIM_PLOT_FRAME::onPlotClosed( wxAuiNotebookEvent& event )
  1870. {
  1871. CallAfter( [this]()
  1872. {
  1873. rebuildSignalsList();
  1874. rebuildSignalsGrid( m_filter->GetValue() );
  1875. updateCursors();
  1876. SIM_PANEL_BASE* panel = getCurrentPlotWindow();
  1877. if( !panel || panel->GetType() != ST_OP )
  1878. {
  1879. SCHEMATIC& schematic = m_schematicFrame->Schematic();
  1880. schematic.ClearOperatingPoints();
  1881. m_schematicFrame->RefreshOperatingPointDisplay();
  1882. }
  1883. } );
  1884. }
  1885. void SIM_PLOT_FRAME::onPlotChanged( wxAuiNotebookEvent& event )
  1886. {
  1887. rebuildSignalsList();
  1888. rebuildSignalsGrid( m_filter->GetValue() );
  1889. updateCursors();
  1890. }
  1891. void SIM_PLOT_FRAME::onPlotDragged( wxAuiNotebookEvent& event )
  1892. {
  1893. }
  1894. void SIM_PLOT_FRAME::onNotebookModified( wxCommandEvent& event )
  1895. {
  1896. OnModify();
  1897. updateTitle();
  1898. }
  1899. bool SIM_PLOT_FRAME::EditSimCommand()
  1900. {
  1901. SIM_PANEL_BASE* plotPanelWindow = getCurrentPlotWindow();
  1902. DIALOG_SIM_COMMAND dlg( this, m_circuitModel, m_simulator->Settings() );
  1903. wxString errors;
  1904. WX_STRING_REPORTER reporter( &errors );
  1905. if( !m_circuitModel->ReadSchematicAndLibraries( NETLIST_EXPORTER_SPICE::OPTION_DEFAULT_FLAGS,
  1906. reporter ) )
  1907. {
  1908. DisplayErrorMessage( this, _( "Errors during netlist generation; simulation aborted.\n\n" )
  1909. + errors );
  1910. return false;
  1911. }
  1912. if( m_plotNotebook->GetPageIndex( plotPanelWindow ) != wxNOT_FOUND )
  1913. {
  1914. dlg.SetSimCommand( plotPanelWindow->GetSimCommand() );
  1915. dlg.SetSimOptions( plotPanelWindow->GetSimOptions() );
  1916. }
  1917. else
  1918. {
  1919. dlg.SetSimOptions( NETLIST_EXPORTER_SPICE::OPTION_DEFAULT_FLAGS );
  1920. }
  1921. if( dlg.ShowModal() == wxID_OK )
  1922. {
  1923. wxString oldCommand;
  1924. if( m_plotNotebook->GetPageIndex( plotPanelWindow ) != wxNOT_FOUND )
  1925. oldCommand = plotPanelWindow->GetSimCommand();
  1926. else
  1927. oldCommand = wxString();
  1928. const wxString& newCommand = dlg.GetSimCommand();
  1929. int newOptions = dlg.GetSimOptions();
  1930. SIM_TYPE newSimType = NGSPICE_CIRCUIT_MODEL::CommandToSimType( newCommand );
  1931. if( !plotPanelWindow )
  1932. {
  1933. m_circuitModel->SetSimCommandOverride( newCommand );
  1934. m_circuitModel->SetSimOptions( newOptions );
  1935. plotPanelWindow = NewPlotPanel( newCommand, newOptions );
  1936. }
  1937. // If it is a new simulation type, open a new plot. For the DC sim, check if sweep
  1938. // source type has changed (char 4 will contain 'v', 'i', 'r' or 't'.
  1939. else if( plotPanelWindow->GetType() != newSimType
  1940. || ( newSimType == ST_DC
  1941. && oldCommand.Lower().GetChar( 4 ) != newCommand.Lower().GetChar( 4 ) ) )
  1942. {
  1943. plotPanelWindow = NewPlotPanel( newCommand, newOptions );
  1944. }
  1945. else
  1946. {
  1947. if( m_plotNotebook->GetPageIndex( plotPanelWindow ) == 0 )
  1948. m_circuitModel->SetSimCommandOverride( newCommand );
  1949. // Update simulation command in the current plot
  1950. plotPanelWindow->SetSimCommand( newCommand );
  1951. plotPanelWindow->SetSimOptions( newOptions );
  1952. }
  1953. OnModify();
  1954. m_simulator->Init();
  1955. return true;
  1956. }
  1957. return false;
  1958. }
  1959. bool SIM_PLOT_FRAME::canCloseWindow( wxCloseEvent& aEvent )
  1960. {
  1961. if( m_workbookModified )
  1962. {
  1963. wxFileName filename = m_simulator->Settings()->GetWorkbookFilename();
  1964. if( filename.GetName().IsEmpty() )
  1965. {
  1966. if( Prj().GetProjectName().IsEmpty() )
  1967. filename.SetFullName( wxT( "noname.wbk" ) );
  1968. else
  1969. filename.SetFullName( Prj().GetProjectName() + wxT( ".wbk" ) );
  1970. }
  1971. return HandleUnsavedChanges( this, _( "Save changes to workbook?" ),
  1972. [&]() -> bool
  1973. {
  1974. return SaveWorkbook( Prj().AbsolutePath( filename.GetFullName() ) );
  1975. } );
  1976. }
  1977. return true;
  1978. }
  1979. void SIM_PLOT_FRAME::doCloseWindow()
  1980. {
  1981. if( m_simulator->IsRunning() )
  1982. m_simulator->Stop();
  1983. // Prevent memory leak on exit by deleting all simulation vectors
  1984. m_simulator->Clean();
  1985. // Cancel a running simProbe or simTune tool
  1986. m_schematicFrame->GetToolManager()->RunAction( ACTIONS::cancelInteractive );
  1987. SaveSettings( config() );
  1988. m_simulator->Settings() = nullptr;
  1989. Destroy();
  1990. }
  1991. void SIM_PLOT_FRAME::updateCursors()
  1992. {
  1993. SUPPRESS_GRID_CELL_EVENTS raii( this );
  1994. m_cursorsGrid->ClearRows();
  1995. SIM_PLOT_PANEL* plotPanel = GetCurrentPlot();
  1996. if( !plotPanel )
  1997. return;
  1998. // Update cursor values
  1999. CURSOR* cursor1 = nullptr;
  2000. wxString cursor1Name;
  2001. wxString cursor1Units;
  2002. CURSOR* cursor2 = nullptr;
  2003. wxString cursor2Name;
  2004. wxString cursor2Units;
  2005. auto getUnitsY =
  2006. [&]( TRACE* aTrace ) -> wxString
  2007. {
  2008. if( ( aTrace->GetType() & SPT_AC_PHASE ) || ( aTrace->GetType() & SPT_CURRENT ) )
  2009. return plotPanel->GetUnitsY2();
  2010. else if( aTrace->GetType() & SPT_POWER )
  2011. return plotPanel->GetUnitsY3();
  2012. else
  2013. return plotPanel->GetUnitsY1();
  2014. };
  2015. auto getNameY =
  2016. [&]( TRACE* aTrace ) -> wxString
  2017. {
  2018. if( ( aTrace->GetType() & SPT_AC_PHASE ) || ( aTrace->GetType() & SPT_CURRENT ) )
  2019. return plotPanel->GetLabelY2();
  2020. else if( aTrace->GetType() & SPT_POWER )
  2021. return plotPanel->GetLabelY3();
  2022. else
  2023. return plotPanel->GetLabelY1();
  2024. };
  2025. auto formatValue =
  2026. [this]( double aValue, int aCursorId, int aCol ) -> wxString
  2027. {
  2028. return SPICE_VALUE( aValue ).ToString( m_cursorFormats[ aCursorId ][ aCol ] );
  2029. };
  2030. for( const auto& [name, trace] : plotPanel->GetTraces() )
  2031. {
  2032. if( CURSOR* cursor = trace->GetCursor( 1 ) )
  2033. {
  2034. cursor1 = cursor;
  2035. cursor1Name = getNameY( trace );
  2036. cursor1Units = getUnitsY( trace );
  2037. wxRealPoint coords = cursor->GetCoords();
  2038. int row = m_cursorsGrid->GetNumberRows();
  2039. m_cursorFormats[0][0].UpdateUnits( plotPanel->GetUnitsX() );
  2040. m_cursorFormats[0][1].UpdateUnits( cursor1Units );
  2041. m_cursorsGrid->AppendRows( 1 );
  2042. m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "1" ) );
  2043. m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, cursor->GetName() );
  2044. m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, 0, 0 ) );
  2045. m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, 0, 1 ) );
  2046. break;
  2047. }
  2048. }
  2049. for( const auto& [name, trace] : plotPanel->GetTraces() )
  2050. {
  2051. if( CURSOR* cursor = trace->GetCursor( 2 ) )
  2052. {
  2053. cursor2 = cursor;
  2054. cursor2Name = getNameY( trace );
  2055. cursor2Units = getUnitsY( trace );
  2056. wxRealPoint coords = cursor->GetCoords();
  2057. int row = m_cursorsGrid->GetNumberRows();
  2058. m_cursorFormats[1][0].UpdateUnits( plotPanel->GetUnitsX() );
  2059. m_cursorFormats[1][1].UpdateUnits( cursor2Units );
  2060. m_cursorsGrid->AppendRows( 1 );
  2061. m_cursorsGrid->SetCellValue( row, COL_CURSOR_NAME, wxS( "2" ) );
  2062. m_cursorsGrid->SetCellValue( row, COL_CURSOR_SIGNAL, cursor->GetName() );
  2063. m_cursorsGrid->SetCellValue( row, COL_CURSOR_X, formatValue( coords.x, 1, 0 ) );
  2064. m_cursorsGrid->SetCellValue( row, COL_CURSOR_Y, formatValue( coords.y, 1, 1 ) );
  2065. break;
  2066. }
  2067. }
  2068. if( cursor1 && cursor2 && cursor1Units == cursor2Units )
  2069. {
  2070. wxRealPoint coords = cursor2->GetCoords() - cursor1->GetCoords();
  2071. wxString signal;
  2072. m_cursorFormats[2][0].UpdateUnits( plotPanel->GetUnitsX() );
  2073. m_cursorFormats[2][1].UpdateUnits( cursor1Units );
  2074. if( cursor1->GetName() == cursor2->GetName() )
  2075. signal = wxString::Format( wxS( "%s[2 - 1]" ), cursor2->GetName() );
  2076. else
  2077. signal = wxString::Format( wxS( "%s - %s" ), cursor2->GetName(), cursor1->GetName() );
  2078. m_cursorsGrid->AppendRows( 1 );
  2079. m_cursorsGrid->SetCellValue( 2, COL_CURSOR_NAME, _( "Diff" ) );
  2080. m_cursorsGrid->SetCellValue( 2, COL_CURSOR_SIGNAL, signal );
  2081. m_cursorsGrid->SetCellValue( 2, COL_CURSOR_X, formatValue( coords.x, 2, 0 ) );
  2082. m_cursorsGrid->SetCellValue( 2, COL_CURSOR_Y, formatValue( coords.y, 2, 1 ) );
  2083. }
  2084. // Set up the labels
  2085. m_cursorsGrid->SetColLabelValue( COL_CURSOR_X, plotPanel->GetLabelX() );
  2086. wxString valColName = _( "Value" );
  2087. if( !cursor1Name.IsEmpty() )
  2088. {
  2089. if( cursor2Name.IsEmpty() || cursor1Name == cursor2Name )
  2090. valColName = cursor1Name;
  2091. }
  2092. else if( !cursor2Name.IsEmpty() )
  2093. {
  2094. valColName = cursor2Name;
  2095. }
  2096. m_cursorsGrid->SetColLabelValue( COL_CURSOR_Y, valColName );
  2097. }
  2098. void SIM_PLOT_FRAME::onCursorUpdate( wxCommandEvent& aEvent )
  2099. {
  2100. updateCursors();
  2101. OnModify();
  2102. }
  2103. void SIM_PLOT_FRAME::setupUIConditions()
  2104. {
  2105. EDA_BASE_FRAME::setupUIConditions();
  2106. ACTION_MANAGER* mgr = m_toolManager->GetActionManager();
  2107. wxASSERT( mgr );
  2108. auto showGridCondition =
  2109. [this]( const SELECTION& aSel )
  2110. {
  2111. SIM_PLOT_PANEL* plot = GetCurrentPlot();
  2112. return plot && plot->IsGridShown();
  2113. };
  2114. auto showLegendCondition =
  2115. [this]( const SELECTION& aSel )
  2116. {
  2117. SIM_PLOT_PANEL* plot = GetCurrentPlot();
  2118. return plot && plot->IsLegendShown();
  2119. };
  2120. auto showDottedCondition =
  2121. [this]( const SELECTION& aSel )
  2122. {
  2123. SIM_PLOT_PANEL* plot = GetCurrentPlot();
  2124. return plot && plot->GetDottedSecondary();
  2125. };
  2126. auto darkModePlotCondition =
  2127. [this]( const SELECTION& aSel )
  2128. {
  2129. return m_darkMode;
  2130. };
  2131. auto simRunning =
  2132. [this]( const SELECTION& aSel )
  2133. {
  2134. return m_simulator && m_simulator->IsRunning();
  2135. };
  2136. auto simFinished =
  2137. [this]( const SELECTION& aSel )
  2138. {
  2139. return m_simFinished;
  2140. };
  2141. auto havePlot =
  2142. [this]( const SELECTION& aSel )
  2143. {
  2144. return GetCurrentPlot() != nullptr;
  2145. };
  2146. #define ENABLE( x ) ACTION_CONDITIONS().Enable( x )
  2147. #define CHECK( x ) ACTION_CONDITIONS().Check( x )
  2148. mgr->SetConditions( EE_ACTIONS::openWorkbook, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
  2149. mgr->SetConditions( EE_ACTIONS::saveWorkbook, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
  2150. mgr->SetConditions( EE_ACTIONS::saveWorkbookAs, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
  2151. mgr->SetConditions( EE_ACTIONS::exportPlotAsPNG, ENABLE( havePlot ) );
  2152. mgr->SetConditions( EE_ACTIONS::exportPlotAsCSV, ENABLE( havePlot ) );
  2153. mgr->SetConditions( EE_ACTIONS::toggleGrid, CHECK( showGridCondition ) );
  2154. mgr->SetConditions( EE_ACTIONS::toggleLegend, CHECK( showLegendCondition ) );
  2155. mgr->SetConditions( EE_ACTIONS::toggleDottedSecondary, CHECK( showDottedCondition ) );
  2156. mgr->SetConditions( EE_ACTIONS::toggleDarkModePlots, CHECK( darkModePlotCondition ) );
  2157. mgr->SetConditions( EE_ACTIONS::simCommand, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
  2158. mgr->SetConditions( EE_ACTIONS::runSimulation, ENABLE( !simRunning ) );
  2159. mgr->SetConditions( EE_ACTIONS::stopSimulation, ENABLE( simRunning ) );
  2160. mgr->SetConditions( EE_ACTIONS::simProbe, ENABLE( simFinished ) );
  2161. mgr->SetConditions( EE_ACTIONS::simTune, ENABLE( simFinished ) );
  2162. mgr->SetConditions( EE_ACTIONS::showNetlist, ENABLE( SELECTION_CONDITIONS::ShowAlways ) );
  2163. #undef CHECK
  2164. #undef ENABLE
  2165. }
  2166. void SIM_PLOT_FRAME::onSimStarted( wxCommandEvent& aEvent )
  2167. {
  2168. SetCursor( wxCURSOR_ARROWWAIT );
  2169. }
  2170. void SIM_PLOT_FRAME::onSimFinished( wxCommandEvent& aEvent )
  2171. {
  2172. SetCursor( wxCURSOR_ARROW );
  2173. SIM_TYPE simType = m_circuitModel->GetSimType();
  2174. if( simType == ST_UNKNOWN )
  2175. return;
  2176. SIM_PANEL_BASE* plotPanelWindow = getCurrentPlotWindow();
  2177. if( !plotPanelWindow || plotPanelWindow->GetType() != simType )
  2178. {
  2179. plotPanelWindow = NewPlotPanel( m_circuitModel->GetSimCommand(),
  2180. m_circuitModel->GetSimOptions() );
  2181. }
  2182. // Sometimes (for instance with a directive like wrdata my_file.csv "my_signal")
  2183. // the simulator is in idle state (simulation is finished), but still running, during
  2184. // the time the file is written. So gives a slice of time to fully finish the work:
  2185. if( m_simulator->IsRunning() )
  2186. {
  2187. int max_time = 40; // For a max timeout = 2s
  2188. do
  2189. {
  2190. wxMilliSleep( 50 );
  2191. wxYield();
  2192. if( max_time )
  2193. max_time--;
  2194. } while( max_time && m_simulator->IsRunning() );
  2195. }
  2196. // Is a warning message useful if the simulatior is still running?
  2197. m_simFinished = true;
  2198. applyUserDefinedSignals();
  2199. rebuildSignalsList();
  2200. SCHEMATIC& schematic = m_schematicFrame->Schematic();
  2201. schematic.ClearOperatingPoints();
  2202. // If there are any signals plotted, update them
  2203. if( SIM_PANEL_BASE::IsPlottable( simType ) )
  2204. {
  2205. SIM_PLOT_PANEL* plotPanel = dynamic_cast<SIM_PLOT_PANEL*>( plotPanelWindow );
  2206. wxCHECK_RET( plotPanel, wxT( "not a SIM_PLOT_PANEL" ) );
  2207. struct TRACE_DESC
  2208. {
  2209. wxString m_name; ///< Name of the measured SPICE vector
  2210. wxString m_title; ///< User-friendly signal name
  2211. SIM_TRACE_TYPE m_type; ///< Type of the signal
  2212. bool m_current;
  2213. };
  2214. std::vector<struct TRACE_DESC> placeholders;
  2215. // Get information about all the traces on the plot; update those that are still in
  2216. // the signals list and remove any that aren't
  2217. for( const auto& [name, trace] : plotPanel->GetTraces() )
  2218. {
  2219. struct TRACE_DESC placeholder;
  2220. placeholder.m_name = trace->GetName();
  2221. placeholder.m_title = getTraceTitle( trace->GetName(), trace->GetType() );
  2222. placeholder.m_type = trace->GetType();
  2223. placeholder.m_current = false;
  2224. for( const wxString& signal : m_signals )
  2225. {
  2226. if( getTraceName( signal ) == placeholder.m_title )
  2227. {
  2228. placeholder.m_current = true;
  2229. break;
  2230. }
  2231. }
  2232. placeholders.push_back( placeholder );
  2233. }
  2234. for( const struct TRACE_DESC& placeholder : placeholders )
  2235. {
  2236. if( placeholder.m_current )
  2237. updateTrace( placeholder.m_name, placeholder.m_type, plotPanel );
  2238. else
  2239. removeTrace( placeholder.m_name );
  2240. }
  2241. rebuildSignalsGrid( m_filter->GetValue() );
  2242. updateSignalsGrid();
  2243. plotPanel->GetPlotWin()->UpdateAll();
  2244. plotPanel->ResetScales();
  2245. }
  2246. else if( simType == ST_OP )
  2247. {
  2248. m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
  2249. m_simConsole->SetInsertionPointEnd();
  2250. for( const std::string& vec : m_simulator->AllPlots() )
  2251. {
  2252. std::vector<double> val_list = m_simulator->GetRealPlot( vec, 1 );
  2253. if( val_list.size() == 0 ) // The list of values can be empty!
  2254. continue;
  2255. wxString value = SPICE_VALUE( val_list.at( 0 ) ).ToSpiceString();
  2256. wxString msg;
  2257. wxString signal;
  2258. SIM_TRACE_TYPE type = m_circuitModel->VectorToSignal( vec, signal );
  2259. const size_t tab = 25; //characters
  2260. size_t padding = ( signal.length() < tab ) ? ( tab - signal.length() ) : 1;
  2261. value.Append( type == SPT_CURRENT ? wxS( "A" ) : wxS( "V" ) );
  2262. msg.Printf( wxT( "%s%s\n" ),
  2263. ( signal + wxT( ":" ) ).Pad( padding, wxUniChar( ' ' ) ),
  2264. value );
  2265. m_simConsole->AppendText( msg );
  2266. m_simConsole->SetInsertionPointEnd();
  2267. if( signal.StartsWith( wxS( "V(" ) ) || signal.StartsWith( wxS( "I(" ) ) )
  2268. signal = signal.SubString( 2, signal.Length() - 2 );
  2269. schematic.SetOperatingPoint( signal, val_list.at( 0 ) );
  2270. }
  2271. }
  2272. m_schematicFrame->RefreshOperatingPointDisplay();
  2273. for( int row = 0; row < m_measurementsGrid->GetNumberRows(); ++row )
  2274. UpdateMeasurement( row );
  2275. m_lastSimPlot = plotPanelWindow;
  2276. }
  2277. void SIM_PLOT_FRAME::onSimUpdate( wxCommandEvent& aEvent )
  2278. {
  2279. static bool updateInProgress = false;
  2280. // skip update when events are triggered too often and previous call didn't end yet
  2281. if( updateInProgress )
  2282. return;
  2283. updateInProgress = true;
  2284. if( m_simulator->IsRunning() )
  2285. m_simulator->Stop();
  2286. if( getCurrentPlotWindow() != m_lastSimPlot )
  2287. {
  2288. // We need to rerun simulation, as the simulator currently stores
  2289. // results for another plot
  2290. StartSimulation();
  2291. }
  2292. else
  2293. {
  2294. std::unique_lock<std::mutex> simulatorLock( m_simulator->GetMutex(), std::try_to_lock );
  2295. if( simulatorLock.owns_lock() )
  2296. {
  2297. // Incremental update
  2298. m_simConsole->Clear();
  2299. // Do not export netlist, it is already stored in the simulator
  2300. applyTuners();
  2301. m_simulator->Run();
  2302. }
  2303. else
  2304. {
  2305. DisplayErrorMessage( this, _( "Another simulation is already running." ) );
  2306. }
  2307. }
  2308. updateInProgress = false;
  2309. }
  2310. void SIM_PLOT_FRAME::onSimReport( wxCommandEvent& aEvent )
  2311. {
  2312. m_simConsole->AppendText( aEvent.GetString() + "\n" );
  2313. m_simConsole->SetInsertionPointEnd();
  2314. }
  2315. void SIM_PLOT_FRAME::onExit( wxCommandEvent& event )
  2316. {
  2317. Kiway().OnKiCadExit();
  2318. }
  2319. void SIM_PLOT_FRAME::OnModify()
  2320. {
  2321. KIWAY_PLAYER::OnModify();
  2322. m_workbookModified = true;
  2323. }
  2324. wxDEFINE_EVENT( EVT_SIM_UPDATE, wxCommandEvent );
  2325. wxDEFINE_EVENT( EVT_SIM_REPORT, wxCommandEvent );
  2326. wxDEFINE_EVENT( EVT_SIM_STARTED, wxCommandEvent );
  2327. wxDEFINE_EVENT( EVT_SIM_FINISHED, wxCommandEvent );