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