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.

1637 lines
47 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
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
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
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
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
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2016 CERN
  5. * Copyright (C) 2016-2021 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/stc/stc.h>
  27. #include <sch_edit_frame.h>
  28. #include <eeschema_id.h>
  29. #include <kiway.h>
  30. #include <confirm.h>
  31. #include <bitmaps.h>
  32. #include <wildcards_and_files_ext.h>
  33. #include <widgets/tuner_slider.h>
  34. #include <dialogs/dialog_signal_list.h>
  35. #include "netlist_exporter_pspice_sim.h"
  36. #include <pgm_base.h>
  37. #include "sim_plot_frame.h"
  38. #include "sim_plot_panel.h"
  39. #include "spice_simulator.h"
  40. #include "spice_reporter.h"
  41. #include <menus_helpers.h>
  42. #include <settings/common_settings.h>
  43. #include <tool/tool_manager.h>
  44. #include <tools/ee_actions.h>
  45. #include <eeschema_settings.h>
  46. #include <wx/ffile.h>
  47. #include <dialog_shim.h>
  48. SIM_PLOT_TYPE operator|( SIM_PLOT_TYPE aFirst, SIM_PLOT_TYPE aSecond )
  49. {
  50. int res = (int) aFirst | (int) aSecond;
  51. return (SIM_PLOT_TYPE) res;
  52. }
  53. class SIM_THREAD_REPORTER : public SPICE_REPORTER
  54. {
  55. public:
  56. SIM_THREAD_REPORTER( SIM_PLOT_FRAME* aParent )
  57. : m_parent( aParent )
  58. {
  59. }
  60. REPORTER& Report( const wxString& aText, SEVERITY aSeverity = RPT_SEVERITY_UNDEFINED ) override
  61. {
  62. wxCommandEvent* event = new wxCommandEvent( EVT_SIM_REPORT );
  63. event->SetString( aText );
  64. wxQueueEvent( m_parent, event );
  65. return *this;
  66. }
  67. bool HasMessage() const override
  68. {
  69. return false; // Technically "indeterminate" rather than false.
  70. }
  71. void OnSimStateChange( SPICE_SIMULATOR* aObject, SIM_STATE aNewState ) override
  72. {
  73. wxCommandEvent* event = NULL;
  74. switch( aNewState )
  75. {
  76. case SIM_IDLE:
  77. event = new wxCommandEvent( EVT_SIM_FINISHED );
  78. break;
  79. case SIM_RUNNING:
  80. event = new wxCommandEvent( EVT_SIM_STARTED );
  81. break;
  82. default:
  83. wxFAIL;
  84. return;
  85. }
  86. wxQueueEvent( m_parent, event );
  87. }
  88. private:
  89. SIM_PLOT_FRAME* m_parent;
  90. };
  91. TRACE_DESC::TRACE_DESC( const NETLIST_EXPORTER_PSPICE_SIM& aExporter, const wxString& aName,
  92. SIM_PLOT_TYPE aType, const wxString& aParam )
  93. : m_name( aName ), m_type( aType ), m_param( aParam )
  94. {
  95. // Title generation
  96. m_title = wxString::Format( "%s(%s)", aParam, aName );
  97. if( aType & SPT_AC_MAG )
  98. m_title += " (mag)";
  99. else if( aType & SPT_AC_PHASE )
  100. m_title += " (phase)";
  101. }
  102. // Store the path of saved workbooks during the session
  103. wxString SIM_PLOT_FRAME::m_savedWorkbooksPath;
  104. SIM_PLOT_FRAME::SIM_PLOT_FRAME( KIWAY* aKiway, wxWindow* aParent )
  105. : SIM_PLOT_FRAME_BASE( aParent ),
  106. m_lastSimPlot( nullptr ),
  107. m_welcomePanel( nullptr ),
  108. m_plotNumber( 0 )
  109. {
  110. SetKiway( this, aKiway );
  111. m_signalsIconColorList = NULL;
  112. m_schematicFrame = (SCH_EDIT_FRAME*) Kiway().Player( FRAME_SCH, false );
  113. if( m_schematicFrame == NULL )
  114. throw std::runtime_error( "There is no schematic window" );
  115. // Give an icon
  116. wxIcon icon;
  117. icon.CopyFromBitmap( KiBitmap( simulator_xpm ) );
  118. SetIcon( icon );
  119. // Get the previous size and position of windows:
  120. LoadSettings( config() );
  121. // Prepare the color list to plot traces
  122. fillDefaultColorList( GetPlotBgOpt() );
  123. // Give icons to menuitems
  124. setIconsForMenuItems();
  125. m_simulator = SPICE_SIMULATOR::CreateInstance( "ngspice" );
  126. if( !m_simulator )
  127. {
  128. throw std::runtime_error( "Could not create simulator instance" );
  129. return;
  130. }
  131. m_simulator->Init();
  132. if( m_savedWorkbooksPath.IsEmpty() )
  133. {
  134. m_savedWorkbooksPath = Prj().GetProjectPath();
  135. }
  136. m_reporter = new SIM_THREAD_REPORTER( this );
  137. m_simulator->SetReporter( m_reporter );
  138. updateNetlistExporter();
  139. Connect( EVT_SIM_UPDATE, wxCommandEventHandler( SIM_PLOT_FRAME::onSimUpdate ), NULL, this );
  140. Connect( EVT_SIM_REPORT, wxCommandEventHandler( SIM_PLOT_FRAME::onSimReport ), NULL, this );
  141. Connect( EVT_SIM_STARTED, wxCommandEventHandler( SIM_PLOT_FRAME::onSimStarted ), NULL, this );
  142. Connect( EVT_SIM_FINISHED, wxCommandEventHandler( SIM_PLOT_FRAME::onSimFinished ), NULL, this );
  143. Connect( EVT_SIM_CURSOR_UPDATE, wxCommandEventHandler( SIM_PLOT_FRAME::onCursorUpdate ), NULL, this );
  144. // Toolbar buttons
  145. m_toolSimulate = m_toolBar->AddTool( ID_SIM_RUN, _( "Run/Stop Simulation" ),
  146. KiBitmap( sim_run_xpm ), _( "Run Simulation" ), wxITEM_NORMAL );
  147. m_toolAddSignals = m_toolBar->AddTool( ID_SIM_ADD_SIGNALS, _( "Add Signals" ),
  148. KiBitmap( sim_add_signal_xpm ), _( "Add signals to plot" ), wxITEM_NORMAL );
  149. m_toolProbe = m_toolBar->AddTool( ID_SIM_PROBE, _( "Probe" ),
  150. KiBitmap( sim_probe_xpm ), _( "Probe signals on the schematic" ), wxITEM_NORMAL );
  151. m_toolTune = m_toolBar->AddTool( ID_SIM_TUNE, _( "Tune" ),
  152. KiBitmap( sim_tune_xpm ), _( "Tune component values" ), wxITEM_NORMAL );
  153. m_toolSettings = m_toolBar->AddTool( wxID_ANY, _( "Settings" ),
  154. KiBitmap( sim_settings_xpm ), _( "Simulation settings" ), wxITEM_NORMAL );
  155. Connect( m_toolSimulate->GetId(), wxEVT_COMMAND_TOOL_CLICKED,
  156. wxCommandEventHandler( SIM_PLOT_FRAME::onSimulate ), NULL, this );
  157. Connect( m_toolAddSignals->GetId(), wxEVT_COMMAND_TOOL_CLICKED,
  158. wxCommandEventHandler( SIM_PLOT_FRAME::onAddSignal ), NULL, this );
  159. Connect( m_toolProbe->GetId(), wxEVT_COMMAND_TOOL_CLICKED,
  160. wxCommandEventHandler( SIM_PLOT_FRAME::onProbe ), NULL, this );
  161. Connect( m_toolTune->GetId(), wxEVT_COMMAND_TOOL_CLICKED,
  162. wxCommandEventHandler( SIM_PLOT_FRAME::onTune ), NULL, this );
  163. Connect( m_toolSettings->GetId(), wxEVT_COMMAND_TOOL_CLICKED,
  164. wxCommandEventHandler( SIM_PLOT_FRAME::onSettings ), NULL, this );
  165. // Bind toolbar buttons event to existing menu event handlers, so they behave the same
  166. Bind( wxEVT_COMMAND_MENU_SELECTED, &SIM_PLOT_FRAME::onSimulate, this, m_runSimulation->GetId() );
  167. Bind( wxEVT_COMMAND_MENU_SELECTED, &SIM_PLOT_FRAME::onAddSignal, this, m_addSignals->GetId() );
  168. Bind( wxEVT_COMMAND_MENU_SELECTED, &SIM_PLOT_FRAME::onProbe, this, m_probeSignals->GetId() );
  169. Bind( wxEVT_COMMAND_MENU_SELECTED, &SIM_PLOT_FRAME::onTune, this, m_tuneValue->GetId() );
  170. Bind( wxEVT_COMMAND_MENU_SELECTED, &SIM_PLOT_FRAME::onShowNetlist, this, m_showNetlist->GetId() );
  171. Bind( wxEVT_COMMAND_MENU_SELECTED, &SIM_PLOT_FRAME::onSettings, this, m_settings->GetId() );
  172. m_toolBar->Realize();
  173. m_welcomePanel = new SIM_PANEL_BASE( ST_UNKNOWN, m_plotNotebook, wxID_ANY );
  174. m_plotNotebook->AddPage( m_welcomePanel, _( "Welcome!" ), 1, true );
  175. // the settings dialog will be created later, on demand.
  176. // if created in the ctor, for some obscure reason, there is an issue
  177. // on Windows: when open it, the simulator frame is sent to the background.
  178. // instead of being behind the dialog frame (as it does)
  179. m_settingsDlg = NULL;
  180. // resize the subwindows size. At least on Windows, calling wxSafeYield before
  181. // resizing the subwindows forces the wxSplitWindows size events automatically generated
  182. // by wxWidgets to be executed before our resize code.
  183. // Otherwise, the changes made by setSubWindowsSashSize are overwritten by one these
  184. // events
  185. wxSafeYield();
  186. setSubWindowsSashSize();
  187. // Ensure the window is on top
  188. Raise();
  189. }
  190. SIM_PLOT_FRAME::~SIM_PLOT_FRAME()
  191. {
  192. m_simulator->SetReporter( nullptr );
  193. delete m_reporter;
  194. delete m_signalsIconColorList;
  195. if( m_settingsDlg )
  196. m_settingsDlg->Destroy();
  197. }
  198. void SIM_PLOT_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg )
  199. {
  200. auto cfg = dynamic_cast<EESCHEMA_SETTINGS*>( aCfg );
  201. wxASSERT( cfg );
  202. if( cfg )
  203. {
  204. EDA_BASE_FRAME::SaveSettings( cfg );
  205. cfg->m_Simulator.plot_panel_width = m_splitterLeftRight->GetSashPosition();
  206. cfg->m_Simulator.plot_panel_height = m_splitterPlotAndConsole->GetSashPosition();
  207. cfg->m_Simulator.signal_panel_height = m_splitterSignals->GetSashPosition();
  208. cfg->m_Simulator.cursors_panel_height = m_splitterTuneValues->GetSashPosition();
  209. cfg->m_Simulator.white_background = m_plotUseWhiteBg;
  210. }
  211. }
  212. void SIM_PLOT_FRAME::LoadSettings( APP_SETTINGS_BASE* aCfg )
  213. {
  214. auto cfg = dynamic_cast<EESCHEMA_SETTINGS*>( aCfg );
  215. wxASSERT( cfg );
  216. if( cfg )
  217. {
  218. EDA_BASE_FRAME::LoadSettings( cfg );
  219. // Read subwindows sizes (should be > 0 )
  220. m_splitterLeftRightSashPosition = cfg->m_Simulator.plot_panel_width;
  221. m_splitterPlotAndConsoleSashPosition = cfg->m_Simulator.plot_panel_height;
  222. m_splitterSignalsSashPosition = cfg->m_Simulator.signal_panel_height;
  223. m_splitterTuneValuesSashPosition = cfg->m_Simulator.cursors_panel_height;
  224. m_plotUseWhiteBg = cfg->m_Simulator.white_background;
  225. }
  226. }
  227. WINDOW_SETTINGS* SIM_PLOT_FRAME::GetWindowSettings( APP_SETTINGS_BASE* aCfg )
  228. {
  229. auto cfg = dynamic_cast<EESCHEMA_SETTINGS*>( aCfg );
  230. wxASSERT( cfg );
  231. return cfg ? &cfg->m_Simulator.window : nullptr;
  232. }
  233. // A small helper struct to handle bitmaps initialisation in menus
  234. struct BM_MENU_INIT_ITEM
  235. {
  236. int m_MenuId;
  237. BITMAP_DEF m_Bitmap;
  238. };
  239. void SIM_PLOT_FRAME::setIconsForMenuItems()
  240. {
  241. // Give icons to menuitems of the main menubar
  242. BM_MENU_INIT_ITEM bm_list[]
  243. {
  244. // File menu:
  245. { wxID_NEW, simulator_xpm },
  246. { wxID_OPEN, directory_open_xpm },
  247. { wxID_SAVE, save_xpm},
  248. { ID_SAVE_AS_IMAGE, export_xpm},
  249. { ID_SAVE_AS_CSV, export_xpm},
  250. { wxID_CLOSE, exit_xpm},
  251. // simulator menu:
  252. { ID_MENU_RUN_SIM, sim_run_xpm},
  253. { ID_MENU_ADD_SIGNAL, sim_add_signal_xpm},
  254. { ID_MENU_PROBE_SIGNALS, sim_probe_xpm},
  255. { ID_MENU_TUNE_SIGNALS, sim_tune_xpm},
  256. { ID_MENU_SHOW_NETLIST, netlist_xpm},
  257. { ID_MENU_SET_SIMUL, sim_settings_xpm},
  258. // View menu
  259. { wxID_ZOOM_IN, zoom_in_xpm},
  260. { wxID_ZOOM_OUT, zoom_out_xpm},
  261. { wxID_ZOOM_FIT, zoom_fit_in_page_xpm},
  262. { ID_MENU_SHOW_GRID, grid_xpm},
  263. { ID_MENU_SHOW_LEGEND, text_xpm},
  264. { ID_MENU_DOTTED, add_dashed_line_xpm},
  265. { ID_MENU_WHITE_BG, swap_layer_xpm},
  266. { 0, nullptr } // Sentinel
  267. };
  268. // wxMenuItems are already created and attached to the m_mainMenu wxMenuBar.
  269. // A problem is the fact setting bitmaps in wxMenuItems after they are attached
  270. // to a wxMenu do not work in all cases.
  271. // So the trick is:
  272. // Remove the wxMenuItem from its wxMenu
  273. // Set the bitmap
  274. // Insert the modified wxMenuItem to its previous place
  275. for( int ii = 0; bm_list[ii].m_MenuId; ++ii )
  276. {
  277. wxMenuItem* item = m_mainMenu->FindItem( bm_list[ii].m_MenuId );
  278. if( !item || !bm_list[ii].m_Bitmap)
  279. continue;
  280. wxMenu* menu = item->GetMenu();
  281. // Calculate the initial index of item inside the wxMenu parent
  282. wxMenuItemList& mlist = menu->GetMenuItems();
  283. int mpos = mlist.IndexOf( item );
  284. if( mpos >= 0 ) // Should be always the case
  285. {
  286. // Modify the bitmap
  287. menu->Remove( item );
  288. AddBitmapToMenuItem( item, KiBitmap( bm_list[ii].m_Bitmap ) );
  289. // Insert item to its the initial index
  290. menu->Insert( mpos, item );
  291. }
  292. }
  293. }
  294. void SIM_PLOT_FRAME::setSubWindowsSashSize()
  295. {
  296. if( m_splitterLeftRightSashPosition > 0 )
  297. m_splitterLeftRight->SetSashPosition( m_splitterLeftRightSashPosition );
  298. if( m_splitterPlotAndConsoleSashPosition > 0 )
  299. m_splitterPlotAndConsole->SetSashPosition( m_splitterPlotAndConsoleSashPosition );
  300. if( m_splitterSignalsSashPosition > 0 )
  301. m_splitterSignals->SetSashPosition( m_splitterSignalsSashPosition );
  302. if( m_splitterTuneValuesSashPosition > 0 )
  303. m_splitterTuneValues->SetSashPosition( m_splitterTuneValuesSashPosition );
  304. }
  305. wxColor SIM_PLOT_FRAME::GetPlotColor( int aColorId )
  306. {
  307. // return the wxColor selected in color list or BLACK is not in list
  308. if( aColorId >= 0 && aColorId < (int)m_colorList.size() )
  309. return m_colorList[aColorId];
  310. return wxColor( 0, 0, 0 );
  311. }
  312. void SIM_PLOT_FRAME::fillDefaultColorList( bool aWhiteBg )
  313. {
  314. m_colorList.clear();
  315. if( aWhiteBg )
  316. {
  317. m_colorList.emplace_back( 255, 255, 255 ); // Bg color
  318. m_colorList.emplace_back( 0, 0, 0 ); // Fg color (texts)
  319. m_colorList.emplace_back( 130, 130, 130 ); // Axis color
  320. m_colorList.emplace_back( 0, 0, 0 ); // cursors color
  321. }
  322. else
  323. {
  324. m_colorList.emplace_back( 0, 0, 0 ); // Bg color
  325. m_colorList.emplace_back( 255, 255, 255 ); // Fg color (texts)
  326. m_colorList.emplace_back( 130, 130, 130 ); // Axis color
  327. m_colorList.emplace_back( 255, 255, 255 ); // cursors color
  328. }
  329. // Add a list of color for traces, starting at index SIM_TRACE_COLOR
  330. m_colorList.emplace_back( 0xE4, 0x1A, 0x1C );
  331. m_colorList.emplace_back( 0x37, 0x7E, 0xB8 );
  332. m_colorList.emplace_back( 0x4D, 0xAF, 0x4A );
  333. m_colorList.emplace_back( 0x98, 0x4E, 0xA3 );
  334. m_colorList.emplace_back( 0xFF, 0x7F, 0x00 );
  335. m_colorList.emplace_back( 0xFF, 0xFF, 0x33 );
  336. m_colorList.emplace_back( 0xA6, 0x56, 0x28 );
  337. m_colorList.emplace_back( 0xF7, 0x81, 0xBF );
  338. m_colorList.emplace_back( 0x66, 0xC2, 0xA5 );
  339. m_colorList.emplace_back( 0xFC, 0x8D, 0x62 );
  340. m_colorList.emplace_back( 0x8D, 0xA0, 0xCB );
  341. m_colorList.emplace_back( 0xE7, 0x8A, 0xC3 );
  342. m_colorList.emplace_back( 0xA6, 0xD8, 0x54 );
  343. m_colorList.emplace_back( 0xFF, 0xD9, 0x2F );
  344. m_colorList.emplace_back( 0xE5, 0xC4, 0x94 );
  345. m_colorList.emplace_back( 0xB3, 0xB3, 0xB3 );
  346. }
  347. void SIM_PLOT_FRAME::StartSimulation( const wxString& aSimCommand )
  348. {
  349. STRING_FORMATTER formatter;
  350. if( !m_settingsDlg )
  351. m_settingsDlg = new DIALOG_SIM_SETTINGS( this );
  352. m_simConsole->Clear();
  353. updateNetlistExporter();
  354. if( aSimCommand.IsEmpty() )
  355. {
  356. SIM_PANEL_BASE* plotPanel = currentPlotWindow();
  357. if( plotPanel )
  358. m_exporter->SetSimCommand( m_plots[plotPanel].m_simCommand );
  359. }
  360. else
  361. {
  362. m_exporter->SetSimCommand( aSimCommand );
  363. }
  364. if( !m_exporter->Format( &formatter, m_settingsDlg->GetNetlistOptions() ) )
  365. {
  366. DisplayError( this, _( "There were errors during netlist export, aborted." ) );
  367. return;
  368. }
  369. if( m_exporter->GetSimType() == ST_UNKNOWN )
  370. {
  371. DisplayInfoMessage( this, _( "You need to select the simulation settings first." ) );
  372. return;
  373. }
  374. m_simulator->LoadNetlist( formatter.GetString() );
  375. updateTuners();
  376. applyTuners();
  377. m_simulator->Run();
  378. }
  379. void SIM_PLOT_FRAME::StopSimulation()
  380. {
  381. m_simulator->Stop();
  382. }
  383. bool SIM_PLOT_FRAME::IsSimulationRunning()
  384. {
  385. return m_simulator ? m_simulator->IsRunning() : false;
  386. }
  387. SIM_PANEL_BASE* SIM_PLOT_FRAME::NewPlotPanel( SIM_TYPE aSimType )
  388. {
  389. SIM_PANEL_BASE* plotPanel;
  390. if( SIM_PANEL_BASE::IsPlottable( aSimType ) )
  391. {
  392. SIM_PLOT_PANEL* panel;
  393. panel = new SIM_PLOT_PANEL( aSimType, m_plotNotebook, this, wxID_ANY );
  394. panel->GetPlotWin()->EnableMouseWheelPan(
  395. Pgm().GetCommonSettings()->m_Input.scroll_modifier_zoom != 0 );
  396. plotPanel = dynamic_cast<SIM_PANEL_BASE*>( panel );
  397. }
  398. else
  399. {
  400. SIM_NOPLOT_PANEL* panel;
  401. panel = new SIM_NOPLOT_PANEL( aSimType, m_plotNotebook, wxID_ANY );
  402. plotPanel = dynamic_cast<SIM_PANEL_BASE*>( panel );
  403. }
  404. if( m_welcomePanel )
  405. {
  406. m_plotNotebook->DeletePage( 0 );
  407. m_welcomePanel = nullptr;
  408. }
  409. wxString pageTitle( m_simulator->TypeToName( aSimType, true ) );
  410. pageTitle.Prepend( wxString::Format( _( "Plot%u - " ), (unsigned int) ++m_plotNumber ) );
  411. m_plotNotebook->AddPage( dynamic_cast<wxWindow*>( plotPanel ), pageTitle, true );
  412. m_plots[plotPanel] = PLOT_INFO();
  413. return plotPanel;
  414. }
  415. void SIM_PLOT_FRAME::AddVoltagePlot( const wxString& aNetName )
  416. {
  417. addPlot( aNetName, SPT_VOLTAGE, "V" );
  418. }
  419. void SIM_PLOT_FRAME::AddCurrentPlot( const wxString& aDeviceName, const wxString& aParam )
  420. {
  421. addPlot( aDeviceName, SPT_CURRENT, aParam );
  422. }
  423. void SIM_PLOT_FRAME::AddTuner( SCH_COMPONENT* aComponent )
  424. {
  425. SIM_PANEL_BASE* plotPanel = currentPlotWindow();
  426. if( !plotPanel || plotPanel == m_welcomePanel )
  427. return;
  428. // For now limit the tuner tool to RLC components
  429. char primitiveType = NETLIST_EXPORTER_PSPICE::GetSpiceField( SF_PRIMITIVE, aComponent, 0 )[0];
  430. if( primitiveType != SP_RESISTOR && primitiveType != SP_CAPACITOR && primitiveType != SP_INDUCTOR )
  431. return;
  432. const wxString componentName = aComponent->GetField( REFERENCE_FIELD )->GetText();
  433. // Do not add multiple instances for the same component
  434. auto tunerIt = std::find_if( m_tuners.begin(), m_tuners.end(), [&]( const TUNER_SLIDER* t )
  435. {
  436. return t->GetComponentName() == componentName;
  437. }
  438. );
  439. if( tunerIt != m_tuners.end() )
  440. return; // We already have it
  441. try
  442. {
  443. TUNER_SLIDER* tuner = new TUNER_SLIDER( this, m_tunePanel, aComponent );
  444. m_tuneSizer->Add( tuner );
  445. m_tuners.push_back( tuner );
  446. m_tunePanel->Layout();
  447. }
  448. catch( const KI_PARAM_ERROR& e )
  449. {
  450. // Sorry, no bonus
  451. DisplayError( nullptr, e.What() );
  452. }
  453. }
  454. void SIM_PLOT_FRAME::RemoveTuner( TUNER_SLIDER* aTuner, bool aErase )
  455. {
  456. if( aErase )
  457. m_tuners.remove( aTuner );
  458. aTuner->Destroy();
  459. m_tunePanel->Layout();
  460. }
  461. SIM_PLOT_PANEL* SIM_PLOT_FRAME::CurrentPlot() const
  462. {
  463. SIM_PANEL_BASE* curPage = currentPlotWindow();
  464. return ( ( !curPage || curPage->GetType() == ST_UNKNOWN ) ?
  465. nullptr :
  466. dynamic_cast<SIM_PLOT_PANEL*>( curPage ) );
  467. }
  468. const NETLIST_EXPORTER_PSPICE_SIM* SIM_PLOT_FRAME::GetExporter() const
  469. {
  470. return m_exporter.get();
  471. }
  472. void SIM_PLOT_FRAME::addPlot( const wxString& aName, SIM_PLOT_TYPE aType, const wxString& aParam )
  473. {
  474. SIM_TYPE simType = m_exporter->GetSimType();
  475. if( simType == ST_UNKNOWN )
  476. {
  477. m_simConsole->AppendText( _( "Error: simulation type not defined!\n" ) );
  478. m_simConsole->SetInsertionPointEnd();
  479. return;
  480. }
  481. else if( !SIM_PANEL_BASE::IsPlottable( simType ) )
  482. {
  483. m_simConsole->AppendText( _( "Error: simulation type doesn't support plotting!\n" ) );
  484. m_simConsole->SetInsertionPointEnd();
  485. return;
  486. }
  487. // Create a new plot if the current one displays a different type
  488. SIM_PLOT_PANEL* plotPanel = CurrentPlot();
  489. if( !plotPanel || plotPanel->GetType() != simType )
  490. plotPanel = dynamic_cast<SIM_PLOT_PANEL*>( NewPlotPanel( simType ) );
  491. wxASSERT( plotPanel );
  492. if( !plotPanel ) // Something is wrong
  493. return;
  494. TRACE_DESC descriptor( *m_exporter, aName, aType, aParam );
  495. bool updated = false;
  496. SIM_PLOT_TYPE xAxisType = GetXAxisType( simType );
  497. if( xAxisType == SPT_LIN_FREQUENCY || xAxisType == SPT_LOG_FREQUENCY )
  498. {
  499. int baseType = descriptor.GetType() & ~( SPT_AC_MAG | SPT_AC_PHASE );
  500. // Add two plots: magnitude & phase
  501. TRACE_DESC mag_desc( *m_exporter, descriptor, (SIM_PLOT_TYPE)( baseType | SPT_AC_MAG ) );
  502. TRACE_DESC phase_desc( *m_exporter, descriptor, (SIM_PLOT_TYPE)( baseType | SPT_AC_PHASE ) );
  503. updated |= updatePlot( mag_desc, plotPanel );
  504. updated |= updatePlot( phase_desc, plotPanel );
  505. }
  506. else
  507. {
  508. updated = updatePlot( descriptor, plotPanel );
  509. }
  510. if( updated )
  511. {
  512. updateSignalList();
  513. }
  514. }
  515. void SIM_PLOT_FRAME::removePlot( const wxString& aPlotName, bool aErase )
  516. {
  517. SIM_PLOT_PANEL* plotPanel = CurrentPlot();
  518. if( !plotPanel )
  519. return;
  520. if( aErase )
  521. {
  522. auto& traceMap = m_plots[plotPanel].m_traces;
  523. auto traceIt = traceMap.find( aPlotName );
  524. wxASSERT( traceIt != traceMap.end() );
  525. traceMap.erase( traceIt );
  526. }
  527. wxASSERT( plotPanel->TraceShown( aPlotName ) );
  528. plotPanel->DeleteTrace( aPlotName );
  529. plotPanel->GetPlotWin()->Fit();
  530. updateSignalList();
  531. wxCommandEvent dummy;
  532. onCursorUpdate( dummy );
  533. }
  534. void SIM_PLOT_FRAME::updateNetlistExporter()
  535. {
  536. m_exporter.reset( new NETLIST_EXPORTER_PSPICE_SIM( &m_schematicFrame->Schematic() ) );
  537. }
  538. bool SIM_PLOT_FRAME::updatePlot( const TRACE_DESC& aDescriptor, SIM_PLOT_PANEL* aPanel )
  539. {
  540. SIM_TYPE simType = m_exporter->GetSimType();
  541. wxString spiceVector = m_exporter->ComponentToVector(
  542. aDescriptor.GetName(), aDescriptor.GetType(), aDescriptor.GetParam() );
  543. if( !SIM_PANEL_BASE::IsPlottable( simType ) )
  544. {
  545. // There is no plot to be shown
  546. m_simulator->Command( wxString::Format( "print %s", spiceVector ).ToStdString() );
  547. return false;
  548. }
  549. // First, handle the x axis
  550. wxString xAxisName( m_simulator->GetXAxis( simType ) );
  551. if( xAxisName.IsEmpty() )
  552. return false;
  553. auto data_x = m_simulator->GetMagPlot( (const char*) xAxisName.c_str() );
  554. unsigned int size = data_x.size();
  555. if( data_x.empty() )
  556. return false;
  557. SIM_PLOT_TYPE plotType = aDescriptor.GetType();
  558. std::vector<double> data_y;
  559. // Now, Y axis data
  560. switch( m_exporter->GetSimType() )
  561. {
  562. case ST_AC:
  563. {
  564. wxASSERT_MSG( !( ( plotType & SPT_AC_MAG ) && ( plotType & SPT_AC_PHASE ) ),
  565. "Cannot set both AC_PHASE and AC_MAG bits" );
  566. if( plotType & SPT_AC_MAG )
  567. data_y = m_simulator->GetMagPlot( (const char*) spiceVector.c_str() );
  568. else if( plotType & SPT_AC_PHASE )
  569. data_y = m_simulator->GetPhasePlot( (const char*) spiceVector.c_str() );
  570. else
  571. wxASSERT_MSG( false, "Plot type missing AC_PHASE or AC_MAG bit" );
  572. }
  573. break;
  574. case ST_NOISE:
  575. case ST_DC:
  576. case ST_TRANSIENT:
  577. {
  578. data_y = m_simulator->GetMagPlot( (const char*) spiceVector.c_str() );
  579. }
  580. break;
  581. default:
  582. wxASSERT_MSG( false, "Unhandled plot type" );
  583. return false;
  584. }
  585. if( data_y.size() != size )
  586. return false;
  587. // If we did a two-source DC analysis, we need to split the resulting vector and add traces
  588. // for each input step
  589. SPICE_DC_PARAMS source1, source2;
  590. if( m_exporter->GetSimType() == ST_DC &&
  591. m_exporter->ParseDCCommand( m_exporter->GetUsedSimCommand(), &source1, &source2 ) )
  592. {
  593. if( !source2.m_source.IsEmpty() )
  594. {
  595. // Source 1 is the inner loop, so lets add traces for each Source 2 (outer loop) step
  596. SPICE_VALUE v = source2.m_vstart;
  597. wxString name;
  598. size_t offset = 0;
  599. size_t outer = ( size_t )( ( source2.m_vend - v ) / source2.m_vincrement ).ToDouble();
  600. size_t inner = data_x.size() / ( outer + 1 );
  601. wxASSERT( data_x.size() % ( outer + 1 ) == 0 );
  602. for( size_t idx = 0; idx <= outer; idx++ )
  603. {
  604. name = wxString::Format( "%s (%s = %s V)", aDescriptor.GetTitle(),
  605. source2.m_source, v.ToString() );
  606. std::vector<double> sub_x( data_x.begin() + offset,
  607. data_x.begin() + offset + inner );
  608. std::vector<double> sub_y( data_y.begin() + offset,
  609. data_y.begin() + offset + inner );
  610. if( aPanel->AddTrace( name, inner,
  611. sub_x.data(), sub_y.data(), aDescriptor.GetType() ) )
  612. {
  613. m_plots[aPanel].m_traces.insert( std::make_pair( name, aDescriptor ) );
  614. }
  615. v = v + source2.m_vincrement;
  616. offset += inner;
  617. }
  618. return true;
  619. }
  620. }
  621. if( aPanel->AddTrace( aDescriptor.GetTitle(), size,
  622. data_x.data(), data_y.data(), aDescriptor.GetType() ) )
  623. {
  624. m_plots[aPanel].m_traces.insert( std::make_pair( aDescriptor.GetTitle(), aDescriptor ) );
  625. }
  626. return true;
  627. }
  628. void SIM_PLOT_FRAME::updateSignalList()
  629. {
  630. m_signals->ClearAll();
  631. SIM_PLOT_PANEL* plotPanel = CurrentPlot();
  632. if( !plotPanel )
  633. return;
  634. wxSize size = m_signals->GetClientSize();
  635. m_signals->AppendColumn( _( "Signal" ), wxLIST_FORMAT_LEFT, size.x );
  636. // Build an image list, to show the color of the corresponding trace
  637. // in the plot panel
  638. // This image list is used for trace and cursor lists
  639. wxMemoryDC bmDC;
  640. const int isize = bmDC.GetCharHeight();
  641. if( m_signalsIconColorList == NULL )
  642. m_signalsIconColorList = new wxImageList( isize, isize, false );
  643. else
  644. m_signalsIconColorList->RemoveAll();
  645. for( const auto& trace : CurrentPlot()->GetTraces() )
  646. {
  647. wxBitmap bitmap( isize, isize );
  648. bmDC.SelectObject( bitmap );
  649. wxColour tcolor = trace.second->GetTraceColour();
  650. wxColour bgColor = m_signals->wxWindow::GetBackgroundColour();
  651. bmDC.SetPen( wxPen( bgColor ) );
  652. bmDC.SetBrush( wxBrush( bgColor ) );
  653. bmDC.DrawRectangle( 0, 0, isize, isize ); // because bmDC.Clear() does not work in wxGTK
  654. bmDC.SetPen( wxPen( tcolor ) );
  655. bmDC.SetBrush( wxBrush( tcolor ) );
  656. bmDC.DrawRectangle( 0, isize / 4 + 1, isize, isize / 2 );
  657. bmDC.SelectObject( wxNullBitmap ); // Needed to initialize bitmap
  658. bitmap.SetMask( new wxMask( bitmap, *wxBLACK ) );
  659. m_signalsIconColorList->Add( bitmap );
  660. }
  661. if( bmDC.IsOk() )
  662. {
  663. bmDC.SetBrush( wxNullBrush );
  664. bmDC.SetPen( wxNullPen );
  665. }
  666. m_signals->SetImageList( m_signalsIconColorList, wxIMAGE_LIST_SMALL );
  667. // Fill the signals listctrl. Keep the order of names and
  668. // the order of icon color identical, because the icons
  669. // are also used in cursor list, and the color index is
  670. // calculated from the trace name index
  671. int imgidx = 0;
  672. for( const auto& trace : m_plots[plotPanel].m_traces )
  673. {
  674. m_signals->InsertItem( imgidx, trace.first, imgidx );
  675. imgidx++;
  676. }
  677. }
  678. void SIM_PLOT_FRAME::updateTuners()
  679. {
  680. const auto& spiceItems = m_exporter->GetSpiceItems();
  681. for( auto it = m_tuners.begin(); it != m_tuners.end(); /* iteration inside the loop */ )
  682. {
  683. const wxString& ref = (*it)->GetComponentName();
  684. if( std::find_if( spiceItems.begin(), spiceItems.end(), [&]( const SPICE_ITEM& item )
  685. {
  686. return item.m_refName == ref;
  687. }) == spiceItems.end() )
  688. {
  689. // The component does not exist anymore, remove the associated tuner
  690. TUNER_SLIDER* tuner = *it;
  691. it = m_tuners.erase( it );
  692. RemoveTuner( tuner, false );
  693. }
  694. else
  695. {
  696. ++it;
  697. }
  698. }
  699. }
  700. void SIM_PLOT_FRAME::applyTuners()
  701. {
  702. for( auto& tuner : m_tuners )
  703. {
  704. /// @todo no ngspice hardcoding
  705. std::string command( "alter @" + tuner->GetSpiceName()
  706. + "=" + tuner->GetValue().ToSpiceString() );
  707. m_simulator->Command( command );
  708. }
  709. }
  710. bool SIM_PLOT_FRAME::loadWorkbook( const wxString& aPath )
  711. {
  712. m_plots.clear();
  713. m_plotNotebook->DeleteAllPages();
  714. wxTextFile file( aPath );
  715. if( !file.Open() )
  716. return false;
  717. long plotsCount;
  718. if( !file.GetFirstLine().ToLong( &plotsCount ) ) // GetFirstLine instead of GetNextLine
  719. return false;
  720. for( long i = 0; i < plotsCount; ++i )
  721. {
  722. long plotType, tracesCount;
  723. if( !file.GetNextLine().ToLong( &plotType ) )
  724. return false;
  725. SIM_PANEL_BASE* plotPanel = NewPlotPanel( (SIM_TYPE) plotType );
  726. m_plots[plotPanel].m_simCommand = file.GetNextLine();
  727. StartSimulation( m_plots[plotPanel].m_simCommand );
  728. // Perform simulation, so plots can be added with values
  729. do
  730. {
  731. wxThread::This()->Sleep( 50 );
  732. }
  733. while( IsSimulationRunning() );
  734. if( !file.GetNextLine().ToLong( &tracesCount ) )
  735. return false;
  736. for( long j = 0; j < tracesCount; ++j )
  737. {
  738. long traceType;
  739. wxString name, param;
  740. if( !file.GetNextLine().ToLong( &traceType ) )
  741. return false;
  742. name = file.GetNextLine();
  743. param = file.GetNextLine();
  744. if( name.IsEmpty() || param.IsEmpty() )
  745. return false;
  746. addPlot( name, (SIM_PLOT_TYPE) traceType, param );
  747. }
  748. }
  749. return true;
  750. }
  751. bool SIM_PLOT_FRAME::saveWorkbook( const wxString& aPath )
  752. {
  753. wxString savePath = aPath;
  754. if( !savePath.Lower().EndsWith(".wbk") )
  755. savePath += ".wbk";
  756. wxTextFile file( savePath );
  757. if( file.Exists() )
  758. {
  759. if( !file.Open() )
  760. return false;
  761. file.Clear();
  762. }
  763. else
  764. {
  765. file.Create();
  766. }
  767. file.AddLine( wxString::Format( "%llu", m_plots.size() ) );
  768. for( const auto& plot : m_plots )
  769. {
  770. if( plot.first )
  771. {
  772. file.AddLine( wxString::Format( "%d", plot.first->GetType() ) );
  773. file.AddLine( plot.second.m_simCommand );
  774. file.AddLine( wxString::Format( "%llu", plot.second.m_traces.size() ) );
  775. for( const auto& trace : plot.second.m_traces )
  776. {
  777. file.AddLine( wxString::Format( "%d", trace.second.GetType() ) );
  778. file.AddLine( trace.second.GetName() );
  779. file.AddLine( trace.second.GetParam() );
  780. }
  781. }
  782. }
  783. bool res = file.Write();
  784. file.Close();
  785. return res;
  786. }
  787. SIM_PLOT_TYPE SIM_PLOT_FRAME::GetXAxisType( SIM_TYPE aType ) const
  788. {
  789. switch( aType )
  790. {
  791. case ST_AC:
  792. return SPT_LIN_FREQUENCY;
  793. /// @todo SPT_LOG_FREQUENCY
  794. case ST_DC:
  795. return SPT_SWEEP;
  796. case ST_TRANSIENT:
  797. return SPT_TIME;
  798. default:
  799. wxASSERT_MSG( false, "Unhandled simulation type" );
  800. return (SIM_PLOT_TYPE) 0;
  801. }
  802. }
  803. void SIM_PLOT_FRAME::menuNewPlot( wxCommandEvent& aEvent )
  804. {
  805. SIM_TYPE type = m_exporter->GetSimType();
  806. if( SIM_PANEL_BASE::IsPlottable( type ) )
  807. {
  808. SIM_PLOT_PANEL* prevPlot = CurrentPlot();
  809. SIM_PLOT_PANEL* newPlot = dynamic_cast<SIM_PLOT_PANEL*>( NewPlotPanel( type ) );
  810. // If the previous plot had the same type, copy the simulation command
  811. if( prevPlot )
  812. m_plots[newPlot].m_simCommand = m_plots[prevPlot].m_simCommand;
  813. }
  814. }
  815. void SIM_PLOT_FRAME::menuOpenWorkbook( wxCommandEvent& event )
  816. {
  817. wxFileDialog openDlg( this, _( "Open simulation workbook" ), m_savedWorkbooksPath, "",
  818. WorkbookFileWildcard(), wxFD_OPEN | wxFD_FILE_MUST_EXIST );
  819. if( openDlg.ShowModal() == wxID_CANCEL )
  820. return;
  821. m_savedWorkbooksPath = openDlg.GetDirectory();
  822. if( !loadWorkbook( openDlg.GetPath() ) )
  823. DisplayError( this, _( "There was an error while opening the workbook file" ) );
  824. }
  825. void SIM_PLOT_FRAME::menuSaveWorkbook( wxCommandEvent& event )
  826. {
  827. if( !CurrentPlot() )
  828. return;
  829. wxFileDialog saveDlg( this, _( "Save Simulation Workbook" ), m_savedWorkbooksPath, "",
  830. WorkbookFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
  831. if( saveDlg.ShowModal() == wxID_CANCEL )
  832. return;
  833. m_savedWorkbooksPath = saveDlg.GetDirectory();
  834. if( !saveWorkbook( saveDlg.GetPath() ) )
  835. DisplayError( this, _( "There was an error while saving the workbook file" ) );
  836. }
  837. void SIM_PLOT_FRAME::menuSaveImage( wxCommandEvent& event )
  838. {
  839. if( !CurrentPlot() )
  840. return;
  841. wxFileDialog saveDlg( this, _( "Save Plot as Image" ), "", "",
  842. PngFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
  843. if( saveDlg.ShowModal() == wxID_CANCEL )
  844. return;
  845. CurrentPlot()->GetPlotWin()->SaveScreenshot( saveDlg.GetPath(), wxBITMAP_TYPE_PNG );
  846. }
  847. void SIM_PLOT_FRAME::menuSaveCsv( wxCommandEvent& event )
  848. {
  849. if( !CurrentPlot() )
  850. return;
  851. const wxChar SEPARATOR = ';';
  852. wxFileDialog saveDlg( this, _( "Save Plot Data" ), "", "",
  853. CsvFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
  854. if( saveDlg.ShowModal() == wxID_CANCEL )
  855. return;
  856. wxFFile out( saveDlg.GetPath(), "wb" );
  857. bool timeWritten = false;
  858. for( const auto& t : CurrentPlot()->GetTraces() )
  859. {
  860. const TRACE* trace = t.second;
  861. if( !timeWritten )
  862. {
  863. out.Write( wxString::Format( "Time%c", SEPARATOR ) );
  864. for( double v : trace->GetDataX() )
  865. out.Write( wxString::Format( "%g%c", v, SEPARATOR ) );
  866. out.Write( "\r\n" );
  867. timeWritten = true;
  868. }
  869. out.Write( wxString::Format( "%s%c", t.first, SEPARATOR ) );
  870. for( double v : trace->GetDataY() )
  871. out.Write( wxString::Format( "%g%c", v, SEPARATOR ) );
  872. out.Write( "\r\n" );
  873. }
  874. out.Close();
  875. }
  876. void SIM_PLOT_FRAME::menuZoomIn( wxCommandEvent& event )
  877. {
  878. if( CurrentPlot() )
  879. CurrentPlot()->GetPlotWin()->ZoomIn();
  880. }
  881. void SIM_PLOT_FRAME::menuZoomOut( wxCommandEvent& event )
  882. {
  883. if( CurrentPlot() )
  884. CurrentPlot()->GetPlotWin()->ZoomOut();
  885. }
  886. void SIM_PLOT_FRAME::menuZoomFit( wxCommandEvent& event )
  887. {
  888. if( CurrentPlot() )
  889. CurrentPlot()->GetPlotWin()->Fit();
  890. }
  891. void SIM_PLOT_FRAME::menuShowGrid( wxCommandEvent& event )
  892. {
  893. SIM_PLOT_PANEL* plot = CurrentPlot();
  894. if( plot )
  895. plot->ShowGrid( !plot->IsGridShown() );
  896. }
  897. void SIM_PLOT_FRAME::menuShowGridUpdate( wxUpdateUIEvent& event )
  898. {
  899. SIM_PLOT_PANEL* plot = CurrentPlot();
  900. event.Check( plot ? plot->IsGridShown() : false );
  901. }
  902. void SIM_PLOT_FRAME::menuShowLegend( wxCommandEvent& event )
  903. {
  904. SIM_PLOT_PANEL* plot = CurrentPlot();
  905. if( plot )
  906. plot->ShowLegend( !plot->IsLegendShown() );
  907. }
  908. void SIM_PLOT_FRAME::menuShowLegendUpdate( wxUpdateUIEvent& event )
  909. {
  910. SIM_PLOT_PANEL* plot = CurrentPlot();
  911. event.Check( plot ? plot->IsLegendShown() : false );
  912. }
  913. void SIM_PLOT_FRAME::menuShowDotted( wxCommandEvent& event )
  914. {
  915. SIM_PLOT_PANEL* plot = CurrentPlot();
  916. if( plot )
  917. plot->SetDottedCurrentPhase( !plot->GetDottedCurrentPhase() );
  918. }
  919. void SIM_PLOT_FRAME::menuShowDottedUpdate( wxUpdateUIEvent& event )
  920. {
  921. SIM_PLOT_PANEL* plot = CurrentPlot();
  922. event.Check( plot ? plot->GetDottedCurrentPhase() : false );
  923. }
  924. void SIM_PLOT_FRAME::menuWhiteBackground( wxCommandEvent& event )
  925. {
  926. m_plotUseWhiteBg = not m_plotUseWhiteBg;
  927. // Rebuild the color list to plot traces
  928. fillDefaultColorList( GetPlotBgOpt() );
  929. // Now send changes to all SIM_PLOT_PANEL
  930. for( size_t page = 0; page < m_plotNotebook->GetPageCount(); page++ )
  931. {
  932. wxWindow* curPage = m_plotNotebook->GetPage( page );
  933. if( curPage == m_welcomePanel )
  934. continue;
  935. // ensure it is truely a plot panel and not the (zero plots) placeholder
  936. // which is only SIM_PLOT_PANEL_BASE
  937. SIM_PLOT_PANEL* panel = dynamic_cast<SIM_PLOT_PANEL*>( curPage );
  938. if( panel != nullptr )
  939. {
  940. panel->UpdatePlotColors();
  941. }
  942. }
  943. }
  944. void SIM_PLOT_FRAME::onPlotClose( wxAuiNotebookEvent& event )
  945. {
  946. int idx = event.GetSelection();
  947. if( idx == wxNOT_FOUND )
  948. return;
  949. SIM_PANEL_BASE* plotPanel =
  950. dynamic_cast<SIM_PANEL_BASE*>( m_plotNotebook->GetPage( idx ) );
  951. m_plots.erase( plotPanel );
  952. updateSignalList();
  953. wxCommandEvent dummy;
  954. onCursorUpdate( dummy );
  955. }
  956. void SIM_PLOT_FRAME::onPlotChanged( wxAuiNotebookEvent& event )
  957. {
  958. updateSignalList();
  959. wxCommandEvent dummy;
  960. onCursorUpdate( dummy );
  961. }
  962. void SIM_PLOT_FRAME::onSignalDblClick( wxMouseEvent& event )
  963. {
  964. // Remove signal from the plot panel when double clicked
  965. long idx = m_signals->GetFocusedItem();
  966. if( idx != wxNOT_FOUND )
  967. removePlot( m_signals->GetItemText( idx, 0 ) );
  968. }
  969. void SIM_PLOT_FRAME::onSignalRClick( wxListEvent& event )
  970. {
  971. int idx = event.GetIndex();
  972. if( idx != wxNOT_FOUND )
  973. m_signals->Select( idx );
  974. idx = m_signals->GetFirstSelected();
  975. if( idx != wxNOT_FOUND )
  976. {
  977. const wxString& netName = m_signals->GetItemText( idx, 0 );
  978. SIGNAL_CONTEXT_MENU ctxMenu( netName, this );
  979. m_signals->PopupMenu( &ctxMenu );
  980. }
  981. }
  982. void SIM_PLOT_FRAME::onSimulate( wxCommandEvent& event )
  983. {
  984. if( IsSimulationRunning() )
  985. StopSimulation();
  986. else
  987. StartSimulation();
  988. }
  989. void SIM_PLOT_FRAME::onSettings( wxCommandEvent& event )
  990. {
  991. SIM_PANEL_BASE* plotPanelWindow = currentPlotWindow();
  992. // Initial processing is required to e.g. display a list of power sources
  993. updateNetlistExporter();
  994. if( !m_exporter->ProcessNetlist( NET_ALL_FLAGS ) )
  995. {
  996. DisplayError( this, _( "There were errors during netlist export, aborted." ) );
  997. return;
  998. }
  999. if( !m_settingsDlg )
  1000. m_settingsDlg = new DIALOG_SIM_SETTINGS( this );
  1001. if( plotPanelWindow != m_welcomePanel )
  1002. m_settingsDlg->SetSimCommand( m_plots[plotPanelWindow].m_simCommand );
  1003. m_settingsDlg->SetNetlistExporter( m_exporter.get() );
  1004. if( m_settingsDlg->ShowModal() == wxID_OK )
  1005. {
  1006. wxString newCommand = m_settingsDlg->GetSimCommand();
  1007. SIM_TYPE newSimType = NETLIST_EXPORTER_PSPICE_SIM::CommandToSimType( newCommand );
  1008. // If it is a new simulation type, open a new plot
  1009. if( !plotPanelWindow || ( plotPanelWindow && plotPanelWindow->GetType() != newSimType ) )
  1010. {
  1011. plotPanelWindow = NewPlotPanel( newSimType );
  1012. }
  1013. m_plots[plotPanelWindow].m_simCommand = newCommand;
  1014. }
  1015. }
  1016. void SIM_PLOT_FRAME::onAddSignal( wxCommandEvent& event )
  1017. {
  1018. SIM_PLOT_PANEL* plotPanel = CurrentPlot();
  1019. if( !plotPanel || !m_exporter || plotPanel->GetType() != m_exporter->GetSimType() )
  1020. {
  1021. DisplayInfoMessage( this, _( "You need to run plot-providing simulation first." ) );
  1022. return;
  1023. }
  1024. DIALOG_SIGNAL_LIST dialog( this, m_exporter.get() );
  1025. dialog.ShowModal();
  1026. }
  1027. void SIM_PLOT_FRAME::onProbe( wxCommandEvent& event )
  1028. {
  1029. if( m_schematicFrame == NULL )
  1030. return;
  1031. m_schematicFrame->GetToolManager()->RunAction( EE_ACTIONS::simProbe );
  1032. m_schematicFrame->Raise();
  1033. }
  1034. void SIM_PLOT_FRAME::onTune( wxCommandEvent& event )
  1035. {
  1036. if( m_schematicFrame == NULL )
  1037. return;
  1038. m_schematicFrame->GetToolManager()->RunAction( EE_ACTIONS::simTune );
  1039. m_schematicFrame->Raise();
  1040. }
  1041. void SIM_PLOT_FRAME::onShowNetlist( wxCommandEvent& event )
  1042. {
  1043. class NETLIST_VIEW_DIALOG : public DIALOG_SHIM
  1044. {
  1045. public:
  1046. enum
  1047. {
  1048. MARGIN_LINE_NUMBERS
  1049. };
  1050. void onClose( wxCloseEvent& evt )
  1051. {
  1052. EndModal( GetReturnCode() );
  1053. }
  1054. NETLIST_VIEW_DIALOG( wxWindow* parent, wxString source) :
  1055. DIALOG_SHIM( parent, wxID_ANY, "SPICE Netlist",
  1056. wxDefaultPosition, wxDefaultSize,
  1057. wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER )
  1058. {
  1059. wxStyledTextCtrl* text = new wxStyledTextCtrl( this, wxID_ANY );
  1060. text->SetMinSize( wxSize( 600, 400 ) );
  1061. text->SetMarginWidth( MARGIN_LINE_NUMBERS, 50 );
  1062. text->StyleSetForeground( wxSTC_STYLE_LINENUMBER, wxColour( 75, 75, 75 ) );
  1063. text->StyleSetBackground( wxSTC_STYLE_LINENUMBER, wxColour( 220, 220, 220 ) );
  1064. text->SetMarginType( MARGIN_LINE_NUMBERS, wxSTC_MARGIN_NUMBER );
  1065. wxFont font = wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_TELETYPE,
  1066. wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString );
  1067. text->StyleSetFont( wxSTC_STYLE_DEFAULT, font );
  1068. text->SetWrapMode( wxSTC_WRAP_WORD );
  1069. text->SetText( source );
  1070. text->StyleClearAll();
  1071. text->SetLexer( wxSTC_LEX_SPICE );
  1072. wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
  1073. sizer->Add( text, 1, wxEXPAND );
  1074. SetSizer( sizer );
  1075. Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( NETLIST_VIEW_DIALOG::onClose ), NULL,
  1076. this );
  1077. finishDialogSettings();
  1078. }
  1079. };
  1080. if( m_schematicFrame == NULL || m_simulator == NULL )
  1081. return;
  1082. NETLIST_VIEW_DIALOG dlg( this, m_simulator->GetNetlist() );
  1083. dlg.ShowModal();
  1084. }
  1085. void SIM_PLOT_FRAME::doCloseWindow()
  1086. {
  1087. SaveSettings( config() );
  1088. if( IsSimulationRunning() )
  1089. m_simulator->Stop();
  1090. // Cancel a running simProbe or simTune tool
  1091. m_schematicFrame->GetToolManager()->RunAction( ACTIONS::cancelInteractive );
  1092. Destroy();
  1093. }
  1094. void SIM_PLOT_FRAME::onCursorUpdate( wxCommandEvent& event )
  1095. {
  1096. wxSize size = m_cursors->GetClientSize();
  1097. SIM_PLOT_PANEL* plotPanel = CurrentPlot();
  1098. m_cursors->ClearAll();
  1099. if( !plotPanel )
  1100. return;
  1101. if( m_signalsIconColorList )
  1102. m_cursors->SetImageList(m_signalsIconColorList, wxIMAGE_LIST_SMALL);
  1103. // Fill the signals listctrl
  1104. m_cursors->AppendColumn( _( "Signal" ), wxLIST_FORMAT_LEFT, size.x / 2 );
  1105. const long X_COL = m_cursors->AppendColumn( plotPanel->GetLabelX(), wxLIST_FORMAT_LEFT, size.x / 4 );
  1106. wxString labelY1 = plotPanel->GetLabelY1();
  1107. wxString labelY2 = plotPanel->GetLabelY2();
  1108. wxString labelY;
  1109. if( !labelY2.IsEmpty() )
  1110. labelY = labelY1 + " / " + labelY2;
  1111. else
  1112. labelY = labelY1;
  1113. const long Y_COL = m_cursors->AppendColumn( labelY, wxLIST_FORMAT_LEFT, size.x / 4 );
  1114. // Update cursor values
  1115. int itemidx = 0;
  1116. for( const auto& trace : plotPanel->GetTraces() )
  1117. {
  1118. if( CURSOR* cursor = trace.second->GetCursor() )
  1119. {
  1120. // Find the right icon color in list.
  1121. // It is the icon used in m_signals list for the same trace
  1122. long iconColor = m_signals->FindItem( -1, trace.first );
  1123. const wxRealPoint coords = cursor->GetCoords();
  1124. long idx = m_cursors->InsertItem( itemidx++, trace.first, iconColor );
  1125. m_cursors->SetItem( idx, X_COL, SPICE_VALUE( coords.x ).ToSpiceString() );
  1126. m_cursors->SetItem( idx, Y_COL, SPICE_VALUE( coords.y ).ToSpiceString() );
  1127. }
  1128. }
  1129. }
  1130. void SIM_PLOT_FRAME::onSimStarted( wxCommandEvent& aEvent )
  1131. {
  1132. m_toolBar->SetToolNormalBitmap( ID_SIM_RUN, KiBitmap( sim_stop_xpm ) );
  1133. SetCursor( wxCURSOR_ARROWWAIT );
  1134. }
  1135. void SIM_PLOT_FRAME::onSimFinished( wxCommandEvent& aEvent )
  1136. {
  1137. m_toolBar->SetToolNormalBitmap( ID_SIM_RUN, KiBitmap( sim_run_xpm ) );
  1138. SetCursor( wxCURSOR_ARROW );
  1139. SIM_TYPE simType = m_exporter->GetSimType();
  1140. if( simType == ST_UNKNOWN )
  1141. return;
  1142. SIM_PANEL_BASE* plotPanelWindow = currentPlotWindow();
  1143. if( !plotPanelWindow || plotPanelWindow->GetType() != simType )
  1144. plotPanelWindow = NewPlotPanel( simType );
  1145. if( IsSimulationRunning() )
  1146. return;
  1147. // If there are any signals plotted, update them
  1148. if( SIM_PANEL_BASE::IsPlottable( simType ) )
  1149. {
  1150. TRACE_MAP& traceMap = m_plots[plotPanelWindow].m_traces;
  1151. SIM_PLOT_PANEL* plotPanel = dynamic_cast<SIM_PLOT_PANEL*>( plotPanelWindow );
  1152. wxCHECK_RET( plotPanel, "not a SIM_PLOT_PANEL" );
  1153. for( auto it = traceMap.begin(); it != traceMap.end(); /* iteration occurs in the loop */)
  1154. {
  1155. if( !updatePlot( it->second, plotPanel ) )
  1156. {
  1157. removePlot( it->first, false );
  1158. it = traceMap.erase( it ); // remove a plot that does not exist anymore
  1159. }
  1160. else
  1161. {
  1162. ++it;
  1163. }
  1164. }
  1165. updateSignalList();
  1166. plotPanel->GetPlotWin()->UpdateAll();
  1167. plotPanel->ResetScales();
  1168. }
  1169. else if( simType == ST_OP )
  1170. {
  1171. m_simConsole->AppendText( _( "\n\nSimulation results:\n\n" ) );
  1172. m_simConsole->SetInsertionPointEnd();
  1173. for( const auto& vec : m_simulator->AllPlots() )
  1174. {
  1175. double val = m_simulator->GetRealPlot( vec, 1 ).at( 0 );
  1176. wxString outLine, signal;
  1177. SIM_PLOT_TYPE type = m_exporter->VectorToSignal( vec, signal );
  1178. const size_t tab = 25; //characters
  1179. size_t padding = ( signal.length() < tab ) ? ( tab - signal.length() ) : 1;
  1180. outLine.Printf( wxT( "%s%s" ), ( signal + wxT( ":" ) ).Pad( padding, wxUniChar( ' ' ) ),
  1181. SPICE_VALUE( val ).ToSpiceString() );
  1182. outLine.Append( type == SPT_CURRENT ? "A\n" : "V\n" );
  1183. m_simConsole->AppendText( outLine );
  1184. m_simConsole->SetInsertionPointEnd();
  1185. // @todo display calculated values on the schematic
  1186. }
  1187. }
  1188. }
  1189. void SIM_PLOT_FRAME::onSimUpdate( wxCommandEvent& aEvent )
  1190. {
  1191. if( IsSimulationRunning() )
  1192. StopSimulation();
  1193. if( CurrentPlot() != m_lastSimPlot )
  1194. {
  1195. // We need to rerun simulation, as the simulator currently stores
  1196. // results for another plot
  1197. StartSimulation();
  1198. }
  1199. else
  1200. {
  1201. // Incremental update
  1202. m_simConsole->Clear();
  1203. // Do not export netlist, it is already stored in the simulator
  1204. applyTuners();
  1205. m_simulator->Run();
  1206. }
  1207. }
  1208. void SIM_PLOT_FRAME::onSimReport( wxCommandEvent& aEvent )
  1209. {
  1210. m_simConsole->AppendText( aEvent.GetString() + "\n" );
  1211. m_simConsole->SetInsertionPointEnd();
  1212. }
  1213. SIM_PLOT_FRAME::SIGNAL_CONTEXT_MENU::SIGNAL_CONTEXT_MENU( const wxString& aSignal,
  1214. SIM_PLOT_FRAME* aPlotFrame )
  1215. : m_signal( aSignal ), m_plotFrame( aPlotFrame )
  1216. {
  1217. SIM_PLOT_PANEL* plot = m_plotFrame->CurrentPlot();
  1218. AddMenuItem( this, HIDE_SIGNAL, _( "Hide Signal" ),
  1219. _( "Erase the signal from plot screen" ),
  1220. KiBitmap( trash_xpm ) );
  1221. TRACE* trace = plot->GetTrace( m_signal );
  1222. if( trace->HasCursor() )
  1223. AddMenuItem( this, HIDE_CURSOR, _( "Hide Cursor" ), KiBitmap( pcb_target_xpm ) );
  1224. else
  1225. AddMenuItem( this, SHOW_CURSOR, _( "Show Cursor" ), KiBitmap( pcb_target_xpm ) );
  1226. Connect( wxEVT_COMMAND_MENU_SELECTED, wxMenuEventHandler( SIGNAL_CONTEXT_MENU::onMenuEvent ), NULL, this );
  1227. }
  1228. void SIM_PLOT_FRAME::SIGNAL_CONTEXT_MENU::onMenuEvent( wxMenuEvent& aEvent )
  1229. {
  1230. SIM_PLOT_PANEL* plot = m_plotFrame->CurrentPlot();
  1231. switch( aEvent.GetId() )
  1232. {
  1233. case HIDE_SIGNAL:
  1234. m_plotFrame->removePlot( m_signal );
  1235. break;
  1236. case SHOW_CURSOR:
  1237. plot->EnableCursor( m_signal, true );
  1238. break;
  1239. case HIDE_CURSOR:
  1240. plot->EnableCursor( m_signal, false );
  1241. break;
  1242. }
  1243. }
  1244. wxDEFINE_EVENT( EVT_SIM_UPDATE, wxCommandEvent );
  1245. wxDEFINE_EVENT( EVT_SIM_REPORT, wxCommandEvent );
  1246. wxDEFINE_EVENT( EVT_SIM_STARTED, wxCommandEvent );
  1247. wxDEFINE_EVENT( EVT_SIM_FINISHED, wxCommandEvent );