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.

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