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_schfields( aFields ),
97  m_useSchFields( true ),
98  m_spiceEmptyValidator( true ), m_notEmptyValidator( wxFILTER_EMPTY )
99 {
100  Init();
101 }
102 
103 
104 DIALOG_SPICE_MODEL::DIALOG_SPICE_MODEL( wxWindow* aParent, SCH_COMPONENT& aComponent, LIB_FIELDS* aFields )
105  : DIALOG_SPICE_MODEL_BASE( aParent ), m_component( aComponent ),
106  m_libfields( aFields ), m_useSchFields( false ),
107  m_spiceEmptyValidator( true ), m_notEmptyValidator( wxFILTER_EMPTY )
108 {
109  Init();
110 }
111 
112 
114 {
115  m_pasValue->SetValidator( m_spiceValidator );
116 
117  m_modelType->SetValidator( m_notEmptyValidator );
118  m_modelType->Clear();
119 
120  // Create a list of handled models
121  for( const auto& model : modelTypes )
122  m_modelType->Append( model.description );
123 
124  m_modelName->SetValidator( m_notEmptyValidator );
125 
126  m_genDc->SetValidator( m_spiceEmptyValidator );
127  m_genAcMag->SetValidator( m_spiceEmptyValidator );
128  m_genAcPhase->SetValidator( m_spiceEmptyValidator );
129 
130  m_pulseInit->SetValidator( m_spiceEmptyValidator );
131  m_pulseNominal->SetValidator( m_spiceEmptyValidator );
132  m_pulseDelay->SetValidator( m_spiceEmptyValidator );
133  m_pulseRise->SetValidator( m_spiceEmptyValidator );
134  m_pulseFall->SetValidator( m_spiceEmptyValidator );
135  m_pulseWidth->SetValidator( m_spiceEmptyValidator );
136  m_pulsePeriod->SetValidator( m_spiceEmptyValidator );
137 
138  m_sinOffset->SetValidator( m_spiceEmptyValidator );
139  m_sinAmplitude->SetValidator( m_spiceEmptyValidator );
140  m_sinFreq->SetValidator( m_spiceEmptyValidator );
141  m_sinDelay->SetValidator( m_spiceEmptyValidator );
142  m_sinDampFactor->SetValidator( m_spiceEmptyValidator );
143 
144  m_expInit->SetValidator( m_spiceEmptyValidator );
145  m_expPulsed->SetValidator( m_spiceEmptyValidator );
146  m_expRiseDelay->SetValidator( m_spiceEmptyValidator );
147  m_expRiseConst->SetValidator( m_spiceEmptyValidator );
148  m_expFallDelay->SetValidator( m_spiceEmptyValidator );
149  m_expFallConst->SetValidator( m_spiceEmptyValidator );
150 
151  m_pwlTimeCol = m_pwlValList->AppendColumn( "Time [s]", wxLIST_FORMAT_LEFT, 100 );
152  m_pwlValueCol = m_pwlValList->AppendColumn( "Value [V/A]", wxLIST_FORMAT_LEFT, 100 );
153 
154  m_sdbSizerOK->SetDefault();
155 }
156 
157 
159 {
160  if( !DIALOG_SPICE_MODEL_BASE::TransferDataFromWindow() )
161  return false;
162 
163  wxWindow* page = m_notebook->GetCurrentPage();
164 
165  // Passive
166  if( page == m_passive )
167  {
168  if( !m_passive->Validate() )
169  return false;
170 
171  switch( m_pasType->GetSelection() )
172  {
173  case 0: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_RESISTOR; break;
174  case 1: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_CAPACITOR; break;
175  case 2: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_INDUCTOR; break;
176 
177  default:
178  wxASSERT_MSG( false, "Unhandled passive type" );
179  return false;
180  break;
181  }
182 
183  m_fieldsTmp[SF_MODEL] = m_pasValue->GetValue();
184  }
185 
186 
187  // Model
188  else if( page == m_model )
189  {
190  if( !m_model->Validate() )
191  return false;
192 
193  int modelIdx = m_modelType->GetSelection();
194 
195  if( modelIdx > 0 && modelIdx < (int)modelTypes.size() )
196  m_fieldsTmp[SF_PRIMITIVE] = static_cast<char>( modelTypes[modelIdx].type );
197 
198  m_fieldsTmp[SF_MODEL] = m_modelName->GetValue();
199 
200  if( !empty( m_modelLibrary ) )
201  m_fieldsTmp[SF_LIB_FILE] = m_modelLibrary->GetValue();
202  }
203 
204  // Power source
205  else if( page == m_power )
206  {
207  wxString model;
208 
209  if( !generatePowerSource( model ) )
210  return false;
211 
212  m_fieldsTmp[SF_PRIMITIVE] = (char)( m_pwrType->GetSelection() ? SP_ISOURCE : SP_VSOURCE );
213  m_fieldsTmp[SF_MODEL] = model;
214  }
215 
216 
217  else
218  {
219  wxASSERT_MSG( false, "Unhandled model type" );
220  return false;
221  }
222 
223  m_fieldsTmp[SF_ENABLED] = !m_disabled->GetValue() ? "Y" : "N"; // note bool inversion
224  m_fieldsTmp[SF_NODE_SEQUENCE] = m_nodeSeqCheck->IsChecked() ? m_nodeSeqVal->GetValue() : "";
225 
226  // Apply the settings
227  for( int i = 0; i < SF_END; ++i )
228  {
229  if( m_fieldsTmp.count( (SPICE_FIELD) i ) > 0 && !m_fieldsTmp.at( i ).IsEmpty() )
230  {
231  if( m_useSchFields )
232  getSchField( i ).SetText( m_fieldsTmp[i] );
233  else
234  getLibField( i ).SetText( m_fieldsTmp[i] );
235  }
236  else
237  {
238  // Erase empty fields (having empty fields causes a warning in the properties dialog)
239  const wxString& spiceField = NETLIST_EXPORTER_PSPICE::GetSpiceFieldName( (SPICE_FIELD) i );
240 
241  if( m_useSchFields )
242  m_schfields->erase( std::remove_if( m_schfields->begin(), m_schfields->end(),
243  [&]( const SCH_FIELD& f ) { return f.GetName() == spiceField; } ), m_schfields->end() );
244  else
245  m_libfields->erase( std::remove_if( m_libfields->begin(), m_libfields->end(),
246  [&]( const LIB_FIELD& f ) { return f.GetName() == spiceField; } ), m_libfields->end() );
247  }
248  }
249 
250  return true;
251 }
252 
253 
255 {
256  const auto& spiceFields = NETLIST_EXPORTER_PSPICE::GetSpiceFields();
257 
258  // Fill out the working buffer
259  for( unsigned int idx = 0; idx < spiceFields.size(); ++idx )
260  {
261  const wxString& spiceField = spiceFields[idx];
262 
265 
266  // Do not modify the existing value, just add missing fields with default values
267  if( m_schfields )
268  {
269  for( auto field : *m_schfields )
270  {
271  if( field.GetName() == spiceField && !field.GetText().IsEmpty() )
272  {
273  m_fieldsTmp[idx] = field.GetText();
274  break;
275  }
276  }
277  }
278  else if( m_libfields)
279  {
280  // TODO: There must be a good way to template out these repetitive calls
281  for( auto field : *m_libfields )
282  {
283  if( field.GetName() == spiceField && !field.GetText().IsEmpty() )
284  {
285  m_fieldsTmp[idx] = field.GetText();
286  break;
287  }
288  }
289  }
290  }
291 
292  // Analyze the component fields to fill out the dialog
293  char primitive = toupper( m_fieldsTmp[SF_PRIMITIVE][0] );
294 
295  switch( primitive )
296  {
297  case SP_RESISTOR:
298  case SP_CAPACITOR:
299  case SP_INDUCTOR:
300  m_notebook->SetSelection( m_notebook->FindPage( m_passive ) );
301  m_pasType->SetSelection( primitive == SP_RESISTOR ? 0
302  : primitive == SP_CAPACITOR ? 1
303  : primitive == SP_INDUCTOR ? 2
304  : -1 );
305  m_pasValue->SetValue( m_fieldsTmp[SF_MODEL] );
306  break;
307 
308  case SP_DIODE:
309  case SP_BJT:
310  case SP_MOSFET:
311  case SP_JFET:
312  case SP_SUBCKT:
313  m_notebook->SetSelection( m_notebook->FindPage( m_model ) );
314  m_modelType->SetSelection( getModelTypeIdx( primitive ) );
315  m_modelName->SetValue( m_fieldsTmp[SF_MODEL] );
316  m_modelLibrary->SetValue( m_fieldsTmp[SF_LIB_FILE] );
317 
318  if( !empty( m_modelLibrary ) )
319  {
320  const wxString& libFile = m_modelLibrary->GetValue();
321  m_fieldsTmp[SF_LIB_FILE] = libFile;
322  loadLibrary( libFile );
323  }
324  break;
325 
326  case SP_VSOURCE:
327  case SP_ISOURCE:
328  if( !parsePowerSource( m_fieldsTmp[SF_MODEL] ) )
329  return false;
330 
331  m_notebook->SetSelection( m_notebook->FindPage( m_power ) );
332  m_pwrType->SetSelection( primitive == SP_ISOURCE ? 1 : 0 );
333  break;
334 
335  default:
336  //wxASSERT_MSG( false, "Unhandled Spice primitive type" );
337  break;
338  }
339 
341 
342  // Check if node sequence is different than the default one
345  {
346  m_nodeSeqCheck->SetValue( true );
348  }
349 
350  return DIALOG_SPICE_MODEL_BASE::TransferDataToWindow();
351 }
352 
353 
354 bool DIALOG_SPICE_MODEL::parsePowerSource( const wxString& aModel )
355 {
356  if( aModel.IsEmpty() )
357  return false;
358 
359  wxStringTokenizer tokenizer( aModel, " ()" );
360  wxString tkn = tokenizer.GetNextToken().Lower();
361 
362  while( tokenizer.HasMoreTokens() )
363  {
364  // Variables used for generic values processing (filling out wxTextCtrls in sequence)
365  bool genericProcessing = false;
366  unsigned int genericReqParamsCount = 0;
367  std::vector<wxTextCtrl*> genericControls;
368 
369  if( tkn == "dc" )
370  {
371  // There might be an optional "dc" or "trans" directive, skip it
372  if( tkn == "dc" || tkn == "trans" )
373  tkn = tokenizer.GetNextToken().Lower();
374 
375  // DC value
376  try
377  {
378  m_genDc->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
379  }
380  catch( ... )
381  {
382  return false;
383  }
384  }
385 
386 
387  else if( tkn == "ac" )
388  {
389  // AC magnitude
390  try
391  {
392  tkn = tokenizer.GetNextToken().Lower();
393  m_genAcMag->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
394  }
395  catch( ... )
396  {
397  return false;
398  }
399 
400  // AC phase (optional)
401  try
402  {
403  tkn = tokenizer.GetNextToken().Lower();
404  m_genAcPhase->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
405  }
406  catch( ... )
407  {
408  continue; // perhaps another directive
409  }
410  }
411 
412 
413  else if( tkn == "pulse" )
414  {
415  m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrPulse ) );
416 
417  genericProcessing = true;
418  genericReqParamsCount = 2;
419  genericControls = { m_pulseInit, m_pulseNominal, m_pulseDelay,
421  }
422 
423 
424  else if( tkn == "sin" )
425  {
426  m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrSin ) );
427 
428  genericProcessing = true;
429  genericReqParamsCount = 2;
431  }
432 
433 
434  else if( tkn == "exp" )
435  {
436  m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrExp ) );
437 
438  genericProcessing = true;
439  genericReqParamsCount = 2;
440  genericControls = { m_expInit, m_expPulsed,
442  }
443 
444 
445  else if( tkn == "pwl" )
446  {
447  m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrPwl ) );
448 
449  try
450  {
451  while( tokenizer.HasMoreTokens() )
452  {
453  tkn = tokenizer.GetNextToken();
454  SPICE_VALUE time( tkn );
455 
456  tkn = tokenizer.GetNextToken();
457  SPICE_VALUE value( tkn );
458 
459  addPwlValue( time.ToSpiceString(), value.ToSpiceString() );
460  }
461  }
462  catch( ... )
463  {
464  return false;
465  }
466  }
467 
468 
469  else
470  {
471  // Unhandled power source type
472  wxASSERT_MSG( false, "Unhandled power source type" );
473  return false;
474  }
475 
476 
477  if( genericProcessing )
478  {
479  try
480  {
481  for( unsigned int i = 0; i < genericControls.size(); ++i )
482  {
483  // If there are no more tokens, let's check if we got at least required fields
484  if( !tokenizer.HasMoreTokens() )
485  return ( i >= genericReqParamsCount );
486 
487  tkn = tokenizer.GetNextToken().Lower();
488  genericControls[i]->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
489  }
490  }
491  catch( ... )
492  {
493  return false;
494  }
495  }
496 
497  // Get the next token now, so if any of the branches catches an expection, try to
498  // process it in another branch
499  tkn = tokenizer.GetNextToken().Lower();
500  }
501 
502  return true;
503 }
504 
505 
506 bool DIALOG_SPICE_MODEL::generatePowerSource( wxString& aTarget ) const
507 {
508  wxString acdc, trans;
509  wxWindow* page = m_powerNotebook->GetCurrentPage();
510  bool useTrans = true; // shall we use the transient command part?
511 
512  // Variables for generic processing
513  bool genericProcessing = false;
514  unsigned int genericReqParamsCount = 0;
515  std::vector<wxTextCtrl*> genericControls;
516 
518  // If SPICE_VALUE can be properly constructed, then it is a valid value
519  try
520  {
521  if( !empty( m_genDc ) )
522  acdc += wxString::Format( "dc %s ", SPICE_VALUE( m_genDc->GetValue() ).ToSpiceString() );
523  }
524  catch( ... )
525  {
526  DisplayError( NULL, wxT( "Invalid DC value" ) );
527  return false;
528  }
529 
530  try
531  {
532  if( !empty( m_genAcMag ) )
533  {
534  acdc += wxString::Format( "ac %s ", SPICE_VALUE( m_genAcMag->GetValue() ).ToSpiceString() );
535 
536  if( !empty( m_genAcPhase ) )
537  acdc += wxString::Format( "%s ", SPICE_VALUE( m_genAcPhase->GetValue() ).ToSpiceString() );
538  }
539  }
540  catch( ... )
541  {
542  DisplayError( NULL, wxT( "Invalid AC magnitude or phase" ) );
543  return false;
544  }
545 
547  if( page == m_pwrPulse )
548  {
549  if( !m_pwrPulse->Validate() )
550  return false;
551 
552  genericProcessing = true;
553  trans += "pulse";
554  genericReqParamsCount = 2;
555  genericControls = { m_pulseInit, m_pulseNominal, m_pulseDelay,
557  }
558 
559 
560  else if( page == m_pwrSin )
561  {
562  if( !m_pwrSin->Validate() )
563  return false;
564 
565  genericProcessing = true;
566  trans += "sin";
567  genericReqParamsCount = 2;
569  }
570 
571 
572  else if( page == m_pwrExp )
573  {
574  if( !m_pwrExp->Validate() )
575  return false;
576 
577  genericProcessing = true;
578  trans += "exp";
579  genericReqParamsCount = 2;
580  genericControls = { m_expInit, m_expPulsed,
582  }
583 
584 
585  else if( page == m_pwrPwl )
586  {
587  if( m_pwlValList->GetItemCount() > 0 )
588  {
589  trans += "pwl(";
590 
591  for( int i = 0; i < m_pwlValList->GetItemCount(); ++i )
592  {
593  trans += wxString::Format( "%s %s ", m_pwlValList->GetItemText( i, m_pwlTimeCol ),
594  m_pwlValList->GetItemText( i, m_pwlValueCol ) );
595  }
596 
597  trans.Trim();
598  trans += ")";
599  }
600  }
601 
602  if( genericProcessing )
603  {
604  trans += "(";
605 
606  auto first_empty = std::find_if( genericControls.begin(), genericControls.end(), empty );
607  auto first_not_empty = std::find_if( genericControls.begin(), genericControls.end(),
608  []( const wxTextCtrl* c ){ return !empty( c ); } );
609 
610  if( std::distance( first_not_empty, genericControls.end() ) == 0 )
611  {
612  // all empty
613  useTrans = false;
614  }
615  else if( std::distance( genericControls.begin(), first_empty ) < (int)genericReqParamsCount )
616  {
617  DisplayError( nullptr,
618  wxString::Format( wxT( "You need to specify at least the "
619  "first %d parameters for the transient source" ),
620  genericReqParamsCount ) );
621 
622  return false;
623  }
624  else if( std::find_if_not( first_empty, genericControls.end(),
625  empty ) != genericControls.end() )
626  {
627  DisplayError( nullptr, wxT( "You cannot leave interleaved empty fields "
628  "when defining a transient source" ) );
629  return false;
630  }
631  else
632  {
633  std::for_each( genericControls.begin(), first_empty,
634  [&trans] ( wxTextCtrl* ctrl ) {
635  trans += wxString::Format( "%s ", ctrl->GetValue() );
636  } );
637  }
638 
639  trans.Trim();
640  trans += ")";
641  }
642 
643  aTarget = acdc;
644 
645  if( useTrans )
646  aTarget += trans;
647 
648  // Remove whitespaces from left and right side
649  aTarget.Trim( false );
650  aTarget.Trim( true );
651 
652  return true;
653 }
654 
655 
656 void DIALOG_SPICE_MODEL::loadLibrary( const wxString& aFilePath )
657 {
658  wxString curModel = m_modelName->GetValue();
659  m_models.clear();
660  wxFileName filePath( aFilePath );
661  bool in_subckt = false; // flag indicating that the parser is inside a .subckt section
662 
663  // Look for the file in the project path
664  if( !filePath.Exists() )
665  {
666  filePath.SetPath( Prj().GetProjectPath() + filePath.GetPath() );
667 
668  if( !filePath.Exists() )
669  return;
670  }
671 
672  // Display the library contents
673  wxWindowUpdateLocker updateLock( this );
674  m_libraryContents->Clear();
675  wxTextFile file;
676  file.Open( filePath.GetFullPath() );
677  int line_nr = 0;
678 
679  // Process the file, looking for components
680  while( !file.Eof() )
681  {
682  const wxString& line = line_nr == 0 ? file.GetFirstLine() : file.GetNextLine();
683  m_libraryContents->AppendText( line );
684  m_libraryContents->AppendText( "\n" );
685 
686  wxStringTokenizer tokenizer( line );
687 
688  while( tokenizer.HasMoreTokens() )
689  {
690  wxString token = tokenizer.GetNextToken().Lower();
691 
692  // some subckts contain .model clauses inside,
693  // skip them as they are a part of the subckt, not another model
694  if( token == ".model" && !in_subckt )
695  {
696  wxString name = tokenizer.GetNextToken();
697 
698  if( name.IsEmpty() )
699  break;
700 
701  token = tokenizer.GetNextToken();
702  SPICE_PRIMITIVE type = MODEL::parseModelType( token );
703 
704  if( type != SP_UNKNOWN )
705  m_models.emplace( name, MODEL( line_nr, type ) );
706  }
707 
708  else if( token == ".subckt" )
709  {
710  wxASSERT( !in_subckt );
711  in_subckt = true;
712 
713  wxString name = tokenizer.GetNextToken();
714 
715  if( name.IsEmpty() )
716  break;
717 
718  m_models.emplace( name, MODEL( line_nr, SP_SUBCKT ) );
719  }
720 
721  else if( token == ".ends" )
722  {
723  wxASSERT( in_subckt );
724  in_subckt = false;
725  }
726  }
727 
728  ++line_nr;
729  }
730 
731  wxArrayString modelsList;
732 
733  // Refresh the model name combobox values
734  m_modelName->Clear();
735 
736  for( const auto& model : m_models )
737  {
738  m_modelName->Append( model.first );
739  modelsList.Add( model.first );
740  }
741 
742  m_modelName->AutoComplete( modelsList );
743 
744  // Restore the previous value or if there is none - pick the first one from the loaded library
745  if( !curModel.IsEmpty() )
746  m_modelName->SetValue( curModel );
747  else if( m_modelName->GetCount() > 0 )
748  m_modelName->SetSelection( 0 );
749 }
750 
751 
753 {
754  const wxString& spiceField = NETLIST_EXPORTER_PSPICE::GetSpiceFieldName( (SPICE_FIELD) aFieldType );
755 
756  auto fieldIt = std::find_if( m_schfields->begin(), m_schfields->end(), [&]( const SCH_FIELD& f ) {
757  return f.GetName() == spiceField;
758  } );
759 
760  // Found one, so return it
761  if( fieldIt != m_schfields->end() )
762  return *fieldIt;
763 
764  // Create a new field with requested name
765  m_schfields->emplace_back( wxPoint(), m_schfields->size(), &m_component, spiceField );
766  return m_schfields->back();
767 }
768 
769 
771 {
772  const wxString& spiceField = NETLIST_EXPORTER_PSPICE::GetSpiceFieldName( (SPICE_FIELD) aFieldType );
773 
774  auto fieldIt = std::find_if( m_libfields->begin(), m_libfields->end(), [&]( const LIB_FIELD& f ) {
775  return f.GetName() == spiceField;
776  } );
777 
778  // Found one, so return it
779  if( fieldIt != m_libfields->end() )
780  return *fieldIt;
781 
782  // Create a new field with requested name
783  LIB_FIELD new_field( m_libfields->size() );
784  m_libfields->front().Copy( &new_field );
785  new_field.SetName( spiceField );
786 
787  m_libfields->push_back( new_field );
788  return m_libfields->back();
789 }
790 
791 
792 bool DIALOG_SPICE_MODEL::addPwlValue( const wxString& aTime, const wxString& aValue )
793 {
794  // TODO execute validators
795  if( aTime.IsEmpty() || aValue.IsEmpty() )
796  return false;
797 
798  long idx = m_pwlValList->InsertItem( m_pwlTimeCol, aTime );
799  m_pwlValList->SetItem( idx, m_pwlValueCol, aValue );
800 
801  // There is no wxString::ToFloat, but we need to guarantee it fits in 4 bytes
802  double timeD;
803  float timeF;
804  m_pwlTime->GetValue().ToDouble( &timeD );
805  timeF = timeD;
806 
807  // Store the time value, so the entries can be sorted
808  m_pwlValList->SetItemData( idx, *reinterpret_cast<long*>( &timeF ) );
809 
810  // Sort items by timestamp
811  m_pwlValList->SortItems( comparePwlValues, -1 );
812 
813  return true;
814 }
815 
816 
817 void DIALOG_SPICE_MODEL::onSelectLibrary( wxCommandEvent& event )
818 {
819  wxString searchPath = wxFileName( m_modelLibrary->GetValue() ).GetPath();
820 
821  if( searchPath.IsEmpty() )
822  searchPath = Prj().GetProjectPath();
823 
824  wxString wildcards = SpiceLibraryFileWildcard() + "|" + AllFilesWildcard();
825  wxFileDialog openDlg( this, _( "Select library" ), searchPath, "", wildcards,
826  wxFD_OPEN | wxFD_FILE_MUST_EXIST );
827 
828  if( openDlg.ShowModal() == wxID_CANCEL )
829  return;
830 
831  wxFileName libPath( openDlg.GetPath() );
832 
833  // Try to convert the path to relative to project
834  if( libPath.MakeRelativeTo( Prj().GetProjectPath() ) && !libPath.GetFullPath().StartsWith( ".." ) )
835  m_modelLibrary->SetValue( libPath.GetFullPath() );
836  else
837  m_modelLibrary->SetValue( openDlg.GetPath() );
838 
839  loadLibrary( openDlg.GetPath() );
840  m_modelName->Popup();
841 }
842 
843 
844 void DIALOG_SPICE_MODEL::onModelSelected( wxCommandEvent& event )
845 {
846  // autoselect the model type
847  auto it = m_models.find( m_modelName->GetValue() );
848 
849  if( it != m_models.end() )
850  {
851  m_modelType->SetSelection( getModelTypeIdx( it->second.model ) );
852 
853  // scroll to the bottom, so the model definition is shown in the first line
854  m_libraryContents->ShowPosition(
855  m_libraryContents->XYToPosition( 0, m_libraryContents->GetNumberOfLines() ) );
856  m_libraryContents->ShowPosition( m_libraryContents->XYToPosition( 0, it->second.line ) );
857  }
858  else
859  {
860  m_libraryContents->ShowPosition( 0 );
861  }
862 }
863 
864 
865 void DIALOG_SPICE_MODEL::onPwlAdd( wxCommandEvent& event )
866 {
867  addPwlValue( m_pwlTime->GetValue(), m_pwlValue->GetValue() );
868 }
869 
870 
871 void DIALOG_SPICE_MODEL::onPwlRemove( wxCommandEvent& event )
872 {
873  long idx = m_pwlValList->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
874  m_pwlValList->DeleteItem( idx );
875 }
876 
877 
879 {
880  wxCHECK( !aValue.IsEmpty(), SP_UNKNOWN );
881  const wxString val( aValue.Lower() );
882 
883  for( const auto& model : modelTypes )
884  {
885  for( const auto& keyword : model.keywords )
886  {
887  if( val.StartsWith( keyword ) )
888  return model.type;
889  }
890  }
891 
892  return SP_UNKNOWN;
893 }
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
void Init()
Initializes the internal settings.
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.
virtual bool TransferDataFromWindow() override
Field object used in symbol libraries.
Definition: lib_field.h:59
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
LIB_FIELD & getLibField(int aFieldType)
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 AllFilesWildcard()
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)
PROJECT & Prj() const
Function Prj returns a reference to the PROJECT "associated with" this KIWAY.
std::vector< std::string > keywords
Keywords indicating the model.
void SetText(const wxString &aText) override
Sets the field text to aText.
Definition: lib_field.cpp:518
SCH_FIELDS * m_schfields
Fields from the component properties dialog
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:54
SPICE_PRIMITIVE type
Character identifying the model.
Definition of file extensions used in Kicad.
SCH_FIELD & getSchField(int aFieldType)
Returns or creates a field in the edited schematic fields vector.
wxTextValidator m_notEmptyValidator
void onModelSelected(wxCommandEvent &event) override
std::map< int, wxString > m_fieldsTmp
Temporary field values
void onPwlRemove(wxCommandEvent &event) override
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
DIALOG_SPICE_MODEL(wxWindow *aParent, SCH_COMPONENT &aComponent, SCH_FIELDS *aSchFields)
SPICE_PRIMITIVE
Basic Spice component primitives
std::map< wxString, MODEL > m_models
Models available in the selected library file
std::vector< LIB_FIELD > LIB_FIELDS
Definition: lib_field.h:261
size_t i
Definition: json11.cpp:597
wxString SpiceLibraryFileWildcard()
Class SCH_COMPONENT describes a real schematic component.
Definition: sch_component.h:70
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