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.

806 lines
24 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2016-2017 CERN
  5. * @author Maciej Suminski <maciej.suminski@cern.ch>
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 3
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * https://www.gnu.org/licenses/gpl-3.0.html
  20. * or you may search the http://www.gnu.org website for the version 3 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include "wildcards_and_files_ext.h"
  25. #include "dialog_spice_model.h"
  26. #include <netlist_exporters/netlist_exporter_pspice.h>
  27. #include <sim/spice_value.h>
  28. #include <confirm.h>
  29. #include <project.h>
  30. #include <wx/tokenzr.h>
  31. #include <wx/wupdlock.h>
  32. // Helper function to shorten conditions
  33. static bool empty( const wxTextCtrl* aCtrl )
  34. {
  35. return aCtrl->GetValue().IsEmpty();
  36. }
  37. // Function to sort PWL values list
  38. static int wxCALLBACK comparePwlValues( wxIntPtr aItem1, wxIntPtr aItem2, wxIntPtr WXUNUSED( aSortData ) )
  39. {
  40. float* t1 = reinterpret_cast<float*>( &aItem1 );
  41. float* t2 = reinterpret_cast<float*>( &aItem2 );
  42. if( *t1 > *t2 )
  43. return 1;
  44. if( *t1 < *t2 )
  45. return -1;
  46. return 0;
  47. }
  48. DIALOG_SPICE_MODEL::DIALOG_SPICE_MODEL( wxWindow* aParent, SCH_COMPONENT& aComponent, SCH_FIELDS& aFields )
  49. : DIALOG_SPICE_MODEL_BASE( aParent ), m_component( aComponent ), m_fields( aFields ),
  50. m_spiceEmptyValidator( true ), m_notEmptyValidator( wxFILTER_EMPTY )
  51. {
  52. m_pasValue->SetValidator( m_spiceValidator );
  53. m_modelType->SetValidator( m_notEmptyValidator );
  54. m_modelName->SetValidator( m_notEmptyValidator );
  55. m_genDc->SetValidator( m_spiceEmptyValidator );
  56. m_genAcMag->SetValidator( m_spiceEmptyValidator );
  57. m_genAcPhase->SetValidator( m_spiceEmptyValidator );
  58. m_pulseInit->SetValidator( m_spiceEmptyValidator );
  59. m_pulseNominal->SetValidator( m_spiceEmptyValidator );
  60. m_pulseDelay->SetValidator( m_spiceEmptyValidator );
  61. m_pulseRise->SetValidator( m_spiceEmptyValidator );
  62. m_pulseFall->SetValidator( m_spiceEmptyValidator );
  63. m_pulseWidth->SetValidator( m_spiceEmptyValidator );
  64. m_pulsePeriod->SetValidator( m_spiceEmptyValidator );
  65. m_sinOffset->SetValidator( m_spiceEmptyValidator );
  66. m_sinAmplitude->SetValidator( m_spiceEmptyValidator );
  67. m_sinFreq->SetValidator( m_spiceEmptyValidator );
  68. m_sinDelay->SetValidator( m_spiceEmptyValidator );
  69. m_sinDampFactor->SetValidator( m_spiceEmptyValidator );
  70. m_expInit->SetValidator( m_spiceEmptyValidator );
  71. m_expPulsed->SetValidator( m_spiceEmptyValidator );
  72. m_expRiseDelay->SetValidator( m_spiceEmptyValidator );
  73. m_expRiseConst->SetValidator( m_spiceEmptyValidator );
  74. m_expFallDelay->SetValidator( m_spiceEmptyValidator );
  75. m_expFallConst->SetValidator( m_spiceEmptyValidator );
  76. m_pwlTimeCol = m_pwlValList->AppendColumn( "Time [s]", wxLIST_FORMAT_LEFT, 100 );
  77. m_pwlValueCol = m_pwlValList->AppendColumn( "Value [V/A]", wxLIST_FORMAT_LEFT, 100 );
  78. m_sdbSizerOK->SetDefault();
  79. }
  80. bool DIALOG_SPICE_MODEL::TransferDataFromWindow()
  81. {
  82. if( !DIALOG_SPICE_MODEL_BASE::TransferDataFromWindow() )
  83. return false;
  84. wxWindow* page = m_notebook->GetCurrentPage();
  85. // Passive
  86. if( page == m_passive )
  87. {
  88. if( !m_passive->Validate() )
  89. return false;
  90. switch( m_pasType->GetSelection() )
  91. {
  92. case 0: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_RESISTOR; break;
  93. case 1: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_CAPACITOR; break;
  94. case 2: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_INDUCTOR; break;
  95. default:
  96. wxASSERT_MSG( false, "Unhandled passive type" );
  97. return false;
  98. break;
  99. }
  100. m_fieldsTmp[SF_MODEL] = m_pasValue->GetValue();
  101. }
  102. // Model
  103. else if( page == m_model )
  104. {
  105. if( !m_model->Validate() )
  106. return false;
  107. switch( m_modelType->GetSelection() )
  108. {
  109. case 0: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_SUBCKT; break;
  110. case 1: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_BJT; break;
  111. case 2: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_MOSFET; break;
  112. case 3: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_DIODE; break;
  113. default:
  114. wxASSERT_MSG( false, "Unhandled semiconductor type" );
  115. return false;
  116. break;
  117. }
  118. m_fieldsTmp[SF_MODEL] = m_modelName->GetValue();
  119. if( !empty( m_modelLibrary ) )
  120. m_fieldsTmp[SF_LIB_FILE] = m_modelLibrary->GetValue();
  121. }
  122. // Power source
  123. else if( page == m_power )
  124. {
  125. wxString model;
  126. if( !generatePowerSource( model ) )
  127. return false;
  128. m_fieldsTmp[SF_PRIMITIVE] = (char)( m_pwrType->GetSelection() ? SP_ISOURCE : SP_VSOURCE );
  129. m_fieldsTmp[SF_MODEL] = model;
  130. }
  131. else
  132. {
  133. wxASSERT_MSG( false, "Unhandled model type" );
  134. return false;
  135. }
  136. m_fieldsTmp[SF_ENABLED] = !m_disabled->GetValue() ? "Y" : "N"; // note bool inversion
  137. m_fieldsTmp[SF_NODE_SEQUENCE] = m_nodeSeqCheck->IsChecked() ? m_nodeSeqVal->GetValue() : "";
  138. // Apply the settings
  139. for( int i = 0; i < SF_END; ++i )
  140. {
  141. if( m_fieldsTmp.count( (SPICE_FIELD) i ) > 0 && !m_fieldsTmp.at( i ).IsEmpty() )
  142. {
  143. getField( i ).SetText( m_fieldsTmp[i] );
  144. }
  145. else
  146. {
  147. // Erase empty fields (having empty fields causes a warning in the properties dialog)
  148. const wxString& spiceField = NETLIST_EXPORTER_PSPICE::GetSpiceFieldName( (SPICE_FIELD) i );
  149. auto fieldIt = std::find_if( m_fields.begin(), m_fields.end(), [&]( const SCH_FIELD& f ) {
  150. return f.GetName() == spiceField;
  151. } );
  152. if( fieldIt != m_fields.end() )
  153. m_fields.erase( fieldIt );
  154. }
  155. }
  156. return true;
  157. }
  158. bool DIALOG_SPICE_MODEL::TransferDataToWindow()
  159. {
  160. const auto& spiceFields = NETLIST_EXPORTER_PSPICE::GetSpiceFields();
  161. // Fill out the working buffer
  162. for( unsigned int idx = 0; idx < spiceFields.size(); ++idx )
  163. {
  164. const wxString& spiceField = spiceFields[idx];
  165. auto fieldIt = std::find_if( m_fields.begin(), m_fields.end(), [&]( const SCH_FIELD& f ) {
  166. return f.GetName() == spiceField;
  167. } );
  168. // Do not modify the existing value, just add missing fields with default values
  169. if( fieldIt != m_fields.end() && !fieldIt->GetText().IsEmpty() )
  170. m_fieldsTmp[idx] = fieldIt->GetText();
  171. else
  172. m_fieldsTmp[idx] = NETLIST_EXPORTER_PSPICE::GetSpiceFieldDefVal( (SPICE_FIELD) idx, &m_component,
  173. NET_ADJUST_INCLUDE_PATHS | NET_ADJUST_PASSIVE_VALS );
  174. }
  175. // Analyze the component fields to fill out the dialog
  176. char primitive = toupper( m_fieldsTmp[SF_PRIMITIVE][0] );
  177. switch( primitive )
  178. {
  179. case SP_RESISTOR:
  180. case SP_CAPACITOR:
  181. case SP_INDUCTOR:
  182. m_notebook->SetSelection( m_notebook->FindPage( m_passive ) );
  183. m_pasType->SetSelection( primitive == SP_RESISTOR ? 0
  184. : primitive == SP_CAPACITOR ? 1
  185. : primitive == SP_INDUCTOR ? 2
  186. : -1 );
  187. m_pasValue->SetValue( m_fieldsTmp[SF_MODEL] );
  188. break;
  189. case SP_SUBCKT:
  190. case SP_DIODE:
  191. case SP_BJT:
  192. case SP_MOSFET:
  193. m_notebook->SetSelection( m_notebook->FindPage( m_model ) );
  194. m_modelType->SetSelection( primitive == SP_SUBCKT ? 0
  195. : primitive == SP_BJT ? 1
  196. : primitive == SP_MOSFET ? 2
  197. : primitive == SP_DIODE ? 3
  198. : -1 );
  199. m_modelName->SetValue( m_fieldsTmp[SF_MODEL] );
  200. m_modelLibrary->SetValue( m_fieldsTmp[SF_LIB_FILE] );
  201. if( !empty( m_modelLibrary ) )
  202. {
  203. const wxString& libFile = m_modelLibrary->GetValue();
  204. m_fieldsTmp[SF_LIB_FILE] = libFile;
  205. loadLibrary( libFile );
  206. }
  207. break;
  208. case SP_VSOURCE:
  209. case SP_ISOURCE:
  210. if( !parsePowerSource( m_fieldsTmp[SF_MODEL] ) )
  211. return false;
  212. m_notebook->SetSelection( m_notebook->FindPage( m_power ) );
  213. m_pwrType->SetSelection( primitive == SP_ISOURCE ? 1 : 0 );
  214. break;
  215. default:
  216. //wxASSERT_MSG( false, "Unhandled Spice primitive type" );
  217. break;
  218. }
  219. m_disabled->SetValue( !NETLIST_EXPORTER_PSPICE::StringToBool( m_fieldsTmp[SF_ENABLED] ) );
  220. // Check if node sequence is different than the default one
  221. if( m_fieldsTmp[SF_NODE_SEQUENCE]
  222. != NETLIST_EXPORTER_PSPICE::GetSpiceFieldDefVal( SF_NODE_SEQUENCE, &m_component, 0 ) )
  223. {
  224. m_nodeSeqCheck->SetValue( true );
  225. m_nodeSeqVal->SetValue( m_fieldsTmp[SF_NODE_SEQUENCE] );
  226. }
  227. return DIALOG_SPICE_MODEL_BASE::TransferDataToWindow();
  228. }
  229. bool DIALOG_SPICE_MODEL::parsePowerSource( const wxString& aModel )
  230. {
  231. if( aModel.IsEmpty() )
  232. return false;
  233. wxStringTokenizer tokenizer( aModel, " ()" );
  234. wxString tkn = tokenizer.GetNextToken().Lower();
  235. while( tokenizer.HasMoreTokens() )
  236. {
  237. // Variables used for generic values processing (filling out wxTextCtrls in sequence)
  238. bool genericProcessing = false;
  239. unsigned int genericReqParamsCount = 0;
  240. std::vector<wxTextCtrl*> genericControls;
  241. if( tkn == "dc" )
  242. {
  243. // There might be an optional "dc" or "trans" directive, skip it
  244. if( tkn == "dc" || tkn == "trans" )
  245. tkn = tokenizer.GetNextToken().Lower();
  246. // DC value
  247. try
  248. {
  249. m_genDc->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
  250. }
  251. catch( ... )
  252. {
  253. return false;
  254. }
  255. }
  256. else if( tkn == "ac" )
  257. {
  258. // AC magnitude
  259. try
  260. {
  261. tkn = tokenizer.GetNextToken().Lower();
  262. m_genAcMag->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
  263. }
  264. catch( ... )
  265. {
  266. return false;
  267. }
  268. // AC phase (optional)
  269. try
  270. {
  271. tkn = tokenizer.GetNextToken().Lower();
  272. m_genAcPhase->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
  273. }
  274. catch( ... )
  275. {
  276. continue; // perhaps another directive
  277. }
  278. }
  279. else if( tkn == "pulse" )
  280. {
  281. m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrPulse ) );
  282. genericProcessing = true;
  283. genericReqParamsCount = 2;
  284. genericControls = { m_pulseInit, m_pulseNominal, m_pulseDelay,
  285. m_pulseRise, m_pulseFall, m_pulseWidth, m_pulsePeriod };
  286. }
  287. else if( tkn == "sin" )
  288. {
  289. m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrSin ) );
  290. genericProcessing = true;
  291. genericReqParamsCount = 2;
  292. genericControls = { m_sinOffset, m_sinAmplitude, m_sinFreq, m_sinDelay, m_sinDampFactor };
  293. }
  294. else if( tkn == "exp" )
  295. {
  296. m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrExp ) );
  297. genericProcessing = true;
  298. genericReqParamsCount = 2;
  299. genericControls = { m_expInit, m_expPulsed,
  300. m_expRiseDelay, m_expRiseConst, m_expFallDelay, m_expFallConst };
  301. }
  302. else if( tkn == "pwl" )
  303. {
  304. m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrPwl ) );
  305. try
  306. {
  307. while( tokenizer.HasMoreTokens() )
  308. {
  309. tkn = tokenizer.GetNextToken();
  310. SPICE_VALUE time( tkn );
  311. tkn = tokenizer.GetNextToken();
  312. SPICE_VALUE value( tkn );
  313. addPwlValue( time.ToSpiceString(), value.ToSpiceString() );
  314. }
  315. }
  316. catch( ... )
  317. {
  318. return false;
  319. }
  320. }
  321. else
  322. {
  323. // Unhandled power source type
  324. wxASSERT_MSG( false, "Unhandled power source type" );
  325. return false;
  326. }
  327. if( genericProcessing )
  328. {
  329. try
  330. {
  331. for( unsigned int i = 0; i < genericControls.size(); ++i )
  332. {
  333. // If there are no more tokens, let's check if we got at least required fields
  334. if( !tokenizer.HasMoreTokens() )
  335. return ( i >= genericReqParamsCount );
  336. tkn = tokenizer.GetNextToken().Lower();
  337. genericControls[i]->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
  338. }
  339. }
  340. catch( ... )
  341. {
  342. return false;
  343. }
  344. }
  345. // Get the next token now, so if any of the branches catches an expection, try to
  346. // process it in another branch
  347. tkn = tokenizer.GetNextToken().Lower();
  348. }
  349. return true;
  350. }
  351. bool DIALOG_SPICE_MODEL::generatePowerSource( wxString& aTarget ) const
  352. {
  353. wxString acdc, trans;
  354. wxWindow* page = m_powerNotebook->GetCurrentPage();
  355. bool useTrans = true; // shall we use the transient command part?
  356. // Variables for generic processing
  357. bool genericProcessing = false;
  358. unsigned int genericReqParamsCount = 0;
  359. std::vector<wxTextCtrl*> genericControls;
  360. /// DC / AC section
  361. // If SPICE_VALUE can be properly constructed, then it is a valid value
  362. try
  363. {
  364. if( !empty( m_genDc ) )
  365. acdc += wxString::Format( "dc %s ", SPICE_VALUE( m_genDc->GetValue() ).ToSpiceString() );
  366. }
  367. catch( ... )
  368. {
  369. DisplayError( NULL, wxT( "Invalid DC value" ) );
  370. return false;
  371. }
  372. try
  373. {
  374. if( !empty( m_genAcMag ) )
  375. {
  376. acdc += wxString::Format( "ac %s ", SPICE_VALUE( m_genAcMag->GetValue() ).ToSpiceString() );
  377. if( !empty( m_genAcPhase ) )
  378. acdc += wxString::Format( "%s ", SPICE_VALUE( m_genAcPhase->GetValue() ).ToSpiceString() );
  379. }
  380. }
  381. catch( ... )
  382. {
  383. DisplayError( NULL, wxT( "Invalid AC magnitude or phase" ) );
  384. return false;
  385. }
  386. /// Transient section
  387. if( page == m_pwrPulse )
  388. {
  389. if( !m_pwrPulse->Validate() )
  390. return false;
  391. genericProcessing = true;
  392. trans += "pulse";
  393. genericReqParamsCount = 2;
  394. genericControls = { m_pulseInit, m_pulseNominal, m_pulseDelay,
  395. m_pulseRise, m_pulseFall, m_pulseWidth, m_pulsePeriod };
  396. }
  397. else if( page == m_pwrSin )
  398. {
  399. if( !m_pwrSin->Validate() )
  400. return false;
  401. genericProcessing = true;
  402. trans += "sin";
  403. genericReqParamsCount = 2;
  404. genericControls = { m_sinOffset, m_sinAmplitude, m_sinFreq, m_sinDelay, m_sinDampFactor };
  405. }
  406. else if( page == m_pwrExp )
  407. {
  408. if( !m_pwrExp->Validate() )
  409. return false;
  410. genericProcessing = true;
  411. trans += "exp";
  412. genericReqParamsCount = 2;
  413. genericControls = { m_expInit, m_expPulsed,
  414. m_expRiseDelay, m_expRiseConst, m_expFallDelay, m_expFallConst };
  415. }
  416. else if( page == m_pwrPwl )
  417. {
  418. if( m_pwlValList->GetItemCount() > 0 )
  419. {
  420. trans += "pwl(";
  421. for( int i = 0; i < m_pwlValList->GetItemCount(); ++i )
  422. {
  423. trans += wxString::Format( "%s %s ", m_pwlValList->GetItemText( i, m_pwlTimeCol ),
  424. m_pwlValList->GetItemText( i, m_pwlValueCol ) );
  425. }
  426. trans.Trim();
  427. trans += ")";
  428. }
  429. }
  430. if( genericProcessing )
  431. {
  432. trans += "(";
  433. auto first_empty = std::find_if( genericControls.begin(), genericControls.end(), empty );
  434. auto first_not_empty = std::find_if( genericControls.begin(), genericControls.end(),
  435. []( const wxTextCtrl* c ){ return !empty( c ); } );
  436. if( std::distance( first_not_empty, genericControls.end() ) == 0 )
  437. {
  438. // all empty
  439. useTrans = false;
  440. }
  441. else if( std::distance( genericControls.begin(), first_empty ) < (int)genericReqParamsCount )
  442. {
  443. DisplayError( nullptr,
  444. wxString::Format( wxT( "You need to specify at least the "
  445. "first %d parameters for the transient source" ),
  446. genericReqParamsCount ) );
  447. return false;
  448. }
  449. else if( std::find_if_not( first_empty, genericControls.end(),
  450. empty ) != genericControls.end() )
  451. {
  452. DisplayError( nullptr, wxT( "You cannot leave interleaved empty fields "
  453. "when defining a transient source" ) );
  454. return false;
  455. }
  456. else
  457. {
  458. std::for_each( genericControls.begin(), first_empty,
  459. [&trans] ( wxTextCtrl* ctrl ) {
  460. trans += wxString::Format( "%s ", ctrl->GetValue() );
  461. } );
  462. }
  463. trans.Trim();
  464. trans += ")";
  465. }
  466. aTarget = acdc;
  467. if( useTrans )
  468. aTarget += trans;
  469. // Remove whitespaces from left and right side
  470. aTarget.Trim( false );
  471. aTarget.Trim( true );
  472. return true;
  473. }
  474. void DIALOG_SPICE_MODEL::loadLibrary( const wxString& aFilePath )
  475. {
  476. wxString curModel = m_modelName->GetValue();
  477. m_models.clear();
  478. wxFileName filePath( aFilePath );
  479. bool in_subckt = false; // flag indicating that the parser is inside a .subckt section
  480. // Look for the file in the project path
  481. if( !filePath.Exists() )
  482. {
  483. filePath.SetPath( Prj().GetProjectPath() + filePath.GetPath() );
  484. if( !filePath.Exists() )
  485. return;
  486. }
  487. // Display the library contents
  488. wxWindowUpdateLocker updateLock( this );
  489. m_libraryContents->Clear();
  490. wxTextFile file;
  491. file.Open( filePath.GetFullPath() );
  492. int line_nr = 0;
  493. // Process the file, looking for components
  494. while( !file.Eof() )
  495. {
  496. const wxString& line = line_nr == 0 ? file.GetFirstLine() : file.GetNextLine();
  497. m_libraryContents->AppendText( line );
  498. m_libraryContents->AppendText( "\n" );
  499. wxStringTokenizer tokenizer( line );
  500. while( tokenizer.HasMoreTokens() )
  501. {
  502. wxString token = tokenizer.GetNextToken().Lower();
  503. // some subckts contain .model clauses inside,
  504. // skip them as they are a part of the subckt, not another model
  505. if( token == ".model" && !in_subckt )
  506. {
  507. wxString name = tokenizer.GetNextToken();
  508. if( name.IsEmpty() )
  509. break;
  510. token = tokenizer.GetNextToken();
  511. MODEL::TYPE type = MODEL::parseModelType( token );
  512. if( type != MODEL::UNKNOWN )
  513. m_models.emplace( name, MODEL( line_nr, type ) );
  514. }
  515. else if( token == ".subckt" )
  516. {
  517. wxASSERT( !in_subckt );
  518. in_subckt = true;
  519. wxString name = tokenizer.GetNextToken();
  520. if( name.IsEmpty() )
  521. break;
  522. m_models.emplace( name, MODEL( line_nr, MODEL::SUBCKT ) );
  523. }
  524. else if( token == ".ends" )
  525. {
  526. wxASSERT( in_subckt );
  527. in_subckt = false;
  528. }
  529. }
  530. ++line_nr;
  531. }
  532. wxArrayString models;
  533. // Refresh the model name combobox values
  534. m_modelName->Clear();
  535. for( const auto& model : m_models )
  536. {
  537. m_modelName->Append( model.first );
  538. models.Add( model.first );
  539. }
  540. m_modelName->AutoComplete( models );
  541. // Restore the previous value or if there is none - pick the first one from the loaded library
  542. if( !curModel.IsEmpty() )
  543. m_modelName->SetValue( curModel );
  544. else if( m_modelName->GetCount() > 0 )
  545. m_modelName->SetSelection( 0 );
  546. }
  547. SCH_FIELD& DIALOG_SPICE_MODEL::getField( int aFieldType )
  548. {
  549. const wxString& spiceField = NETLIST_EXPORTER_PSPICE::GetSpiceFieldName( (SPICE_FIELD) aFieldType );
  550. auto fieldIt = std::find_if( m_fields.begin(), m_fields.end(), [&]( const SCH_FIELD& f ) {
  551. return f.GetName() == spiceField;
  552. } );
  553. // Found one, so return it
  554. if( fieldIt != m_fields.end() )
  555. return *fieldIt;
  556. // Create a new field with requested name
  557. m_fields.emplace_back( wxPoint(), m_fields.size(), &m_component, spiceField );
  558. return m_fields.back();
  559. }
  560. bool DIALOG_SPICE_MODEL::addPwlValue( const wxString& aTime, const wxString& aValue )
  561. {
  562. // TODO execute validators
  563. if( aTime.IsEmpty() || aValue.IsEmpty() )
  564. return false;
  565. long idx = m_pwlValList->InsertItem( m_pwlTimeCol, aTime );
  566. m_pwlValList->SetItem( idx, m_pwlValueCol, aValue );
  567. // There is no wxString::ToFloat, but we need to guarantee it fits in 4 bytes
  568. double timeD;
  569. float timeF;
  570. m_pwlTime->GetValue().ToDouble( &timeD );
  571. timeF = timeD;
  572. // Store the time value, so the entries can be sorted
  573. m_pwlValList->SetItemData( idx, *reinterpret_cast<long*>( &timeF ) );
  574. // Sort items by timestamp
  575. m_pwlValList->SortItems( comparePwlValues, -1 );
  576. return true;
  577. }
  578. void DIALOG_SPICE_MODEL::onSelectLibrary( wxCommandEvent& event )
  579. {
  580. wxString searchPath = wxFileName( m_modelLibrary->GetValue() ).GetPath();
  581. if( searchPath.IsEmpty() )
  582. searchPath = Prj().GetProjectPath();
  583. wxString wildcards = SpiceLibraryFileWildcard() + "|" + AllFilesWildcard;
  584. wxFileDialog openDlg( this, _( "Select library" ), searchPath, "", wildcards,
  585. wxFD_OPEN | wxFD_FILE_MUST_EXIST );
  586. if( openDlg.ShowModal() == wxID_CANCEL )
  587. return;
  588. wxFileName libPath( openDlg.GetPath() );
  589. // Try to convert the path to relative to project
  590. if( libPath.MakeRelativeTo( Prj().GetProjectPath() ) && !libPath.GetFullPath().StartsWith( ".." ) )
  591. m_modelLibrary->SetValue( libPath.GetFullPath() );
  592. else
  593. m_modelLibrary->SetValue( openDlg.GetPath() );
  594. loadLibrary( openDlg.GetPath() );
  595. m_modelName->Popup();
  596. }
  597. void DIALOG_SPICE_MODEL::onModelSelected( wxCommandEvent& event )
  598. {
  599. // autoselect the model type
  600. auto it = m_models.find( m_modelName->GetValue() );
  601. if( it != m_models.end() )
  602. {
  603. m_modelType->SetSelection( (int) it->second.model );
  604. // scroll to the bottom, so the model definition is shown in the first line
  605. m_libraryContents->ShowPosition(
  606. m_libraryContents->XYToPosition( 0, m_libraryContents->GetNumberOfLines() ) );
  607. m_libraryContents->ShowPosition( m_libraryContents->XYToPosition( 0, it->second.line ) );
  608. }
  609. else
  610. {
  611. m_libraryContents->ShowPosition( 0 );
  612. }
  613. }
  614. void DIALOG_SPICE_MODEL::onPwlAdd( wxCommandEvent& event )
  615. {
  616. addPwlValue( m_pwlTime->GetValue(), m_pwlValue->GetValue() );
  617. }
  618. void DIALOG_SPICE_MODEL::onPwlRemove( wxCommandEvent& event )
  619. {
  620. long idx = m_pwlValList->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
  621. m_pwlValList->DeleteItem( idx );
  622. }
  623. DIALOG_SPICE_MODEL::MODEL::TYPE DIALOG_SPICE_MODEL::MODEL::parseModelType( const wxString& aValue )
  624. {
  625. if( aValue.IsEmpty() )
  626. return UNKNOWN;
  627. const wxString val( aValue.Lower() );
  628. if( val.StartsWith( "npn" ) || val.StartsWith( "pnp" ) )
  629. return BJT;
  630. else if( val.StartsWith( "nmos" ) || val.StartsWith( "pmos" ) )
  631. return MOSFET;
  632. else if( val[0] == 'd' )
  633. return DIODE;
  634. return UNKNOWN;
  635. }