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.

894 lines
27 KiB

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