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