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