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) 2020-2021 KiCad Developers, see AUTHORS.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 #include <common.h>
33 
34 #include <wx/textfile.h>
35 #include <wx/tokenzr.h>
36 #include <wx/wupdlock.h>
37 #include <wx/filedlg.h>
38 
39 
40 #include <cctype>
41 #include <cstring>
42 
43 // Helper function to shorten conditions
44 static bool empty( const wxTextCtrl* aCtrl )
45 {
46  return aCtrl->GetValue().IsEmpty();
47 }
48 
49 
50 // Function to sort PWL values list
51 static int wxCALLBACK comparePwlValues( wxIntPtr aItem1, wxIntPtr aItem2,
52  wxIntPtr WXUNUSED( aSortData ) )
53 {
54  float* t1 = reinterpret_cast<float*>( &aItem1 );
55  float* t2 = reinterpret_cast<float*>( &aItem2 );
56 
57  if( *t1 > *t2 )
58  return 1;
59 
60  if( *t1 < *t2 )
61  return -1;
62 
63  return 0;
64 }
65 
66 
67 // Structure describing a type of Spice model
69 {
71  wxString description;
72  std::vector<std::string> keywords;
73 };
74 
75 
76 // Recognized model types
77 static const std::vector<SPICE_MODEL_INFO> modelTypes =
78 {
79  { SP_DIODE, _( "Diode" ), { "d" } },
80  { SP_BJT, _( "BJT" ), { "npn", "pnp" } },
81  { SP_MOSFET, _( "MOSFET" ), { "nmos", "pmos", "vdmos" } },
82  { SP_JFET, _( "JFET" ), { "njf", "pjf" } },
83  { SP_SUBCKT, _( "Subcircuit" ), {} },
84 };
85 
86 
88 {
93 };
94 
95 
96 // Returns index of an entry in modelTypes array (above) corresponding to a Spice primitive
97 static int getModelTypeIdx( char aPrimitive )
98 {
99  const char prim = std::toupper( aPrimitive );
100 
101  for( size_t i = 0; i < modelTypes.size(); ++i )
102  {
103  if( modelTypes[i].type == prim )
104  return i;
105  }
106 
107  return -1;
108 }
109 
110 
111 DIALOG_SPICE_MODEL::DIALOG_SPICE_MODEL( wxWindow* aParent, SCH_SYMBOL& aSymbol,
112  SCH_FIELDS* aFields )
113  : DIALOG_SPICE_MODEL_BASE( aParent ), m_symbol( aSymbol ), m_schfields( aFields ),
114  m_libfields( nullptr ), m_useSchFields( true ),
115  m_spiceEmptyValidator( true ), m_notEmptyValidator( wxFILTER_EMPTY )
116 {
117  Init();
118 }
119 
120 
122  std::vector<LIB_FIELD>* aFields ) :
123  DIALOG_SPICE_MODEL_BASE( aParent ),
124  m_symbol( aSymbol ),
125  m_schfields( nullptr ),
126  m_libfields( aFields ),
127  m_useSchFields( false ),
128  m_spiceEmptyValidator( true ),
129  m_notEmptyValidator( wxFILTER_EMPTY )
130 {
131  Init();
132 }
133 
134 
136 {
137  m_pasValue->SetValidator( m_spiceValidator );
138 
139  m_modelType->SetValidator( m_notEmptyValidator );
140  m_modelType->Clear();
141 
142  // Create a list of handled models
143  for( const auto& model : modelTypes )
144  m_modelType->Append( model.description );
145 
146  m_modelName->SetValidator( m_notEmptyValidator );
147 
148  m_genDc->SetValidator( m_spiceEmptyValidator );
149  m_genAcMag->SetValidator( m_spiceEmptyValidator );
150  m_genAcPhase->SetValidator( m_spiceEmptyValidator );
151 
152  m_pulseInit->SetValidator( m_spiceEmptyValidator );
153  m_pulseNominal->SetValidator( m_spiceEmptyValidator );
154  m_pulseDelay->SetValidator( m_spiceEmptyValidator );
155  m_pulseRise->SetValidator( m_spiceEmptyValidator );
156  m_pulseFall->SetValidator( m_spiceEmptyValidator );
157  m_pulseWidth->SetValidator( m_spiceEmptyValidator );
158  m_pulsePeriod->SetValidator( m_spiceEmptyValidator );
159 
160  m_sinOffset->SetValidator( m_spiceEmptyValidator );
161  m_sinAmplitude->SetValidator( m_spiceEmptyValidator );
162  m_sinFreq->SetValidator( m_spiceEmptyValidator );
163  m_sinDelay->SetValidator( m_spiceEmptyValidator );
164  m_sinDampFactor->SetValidator( m_spiceEmptyValidator );
165 
166  m_expInit->SetValidator( m_spiceEmptyValidator );
167  m_expPulsed->SetValidator( m_spiceEmptyValidator );
168  m_expRiseDelay->SetValidator( m_spiceEmptyValidator );
169  m_expRiseConst->SetValidator( m_spiceEmptyValidator );
170  m_expFallDelay->SetValidator( m_spiceEmptyValidator );
171  m_expFallConst->SetValidator( m_spiceEmptyValidator );
172 
173  m_fmOffset->SetValidator( m_spiceEmptyValidator );
174  m_fmAmplitude->SetValidator( m_spiceEmptyValidator );
175  m_fmFcarrier->SetValidator( m_spiceEmptyValidator );
176  m_fmModIndex->SetValidator( m_spiceEmptyValidator );
177  m_fmFsignal->SetValidator( m_spiceEmptyValidator );
178  m_fmPhaseC->SetValidator( m_spiceEmptyValidator );
179  m_fmPhaseS->SetValidator( m_spiceEmptyValidator );
180 
181  m_amAmplitude->SetValidator( m_spiceEmptyValidator );
182  m_amOffset->SetValidator( m_spiceEmptyValidator );
183  m_amModulatingFreq->SetValidator( m_spiceEmptyValidator );
184  m_amCarrierFreq->SetValidator( m_spiceEmptyValidator );
185  m_amSignalDelay->SetValidator( m_spiceEmptyValidator );
186  m_amPhase->SetValidator( m_spiceEmptyValidator );
187 
188  m_rnTS->SetValidator( m_spiceEmptyValidator );
189  m_rnTD->SetValidator( m_spiceEmptyValidator );
190  m_rnParam1->SetValidator( m_spiceEmptyValidator );
191  m_rnParam2->SetValidator( m_spiceEmptyValidator );
192 
193  m_pwlTimeCol = m_pwlValList->AppendColumn( "Time [s]", wxLIST_FORMAT_LEFT, 100 );
194  m_pwlValueCol = m_pwlValList->AppendColumn( "Value [V/A]", wxLIST_FORMAT_LEFT, 100 );
195 
196  m_sdbSizerOK->SetDefault();
197 
198  m_staticTextF1->SetLabel( wxS( "f" ) );
199  m_staticTextP1->SetLabel( wxS( "p" ) );
200  m_staticTextN1->SetLabel( wxS( "n" ) );
201  m_staticTextU1->SetLabel( wxS( "u" ) );
202  m_staticTextM1->SetLabel( wxS( "m" ) );
203  m_staticTextK1->SetLabel( wxS( "k" ) );
204  m_staticTextMeg1->SetLabel( wxS( "meg" ) );
205  m_staticTextG1->SetLabel( wxS( "g" ) );
206  m_staticTextT1->SetLabel( wxS( "t" ) );
207 
208  m_staticTextF2->SetLabel( wxS( "femto" ) );
209  m_staticTextP2->SetLabel( wxS( "pico" ) );
210  m_staticTextN2->SetLabel( wxS( "nano" ) );
211  m_staticTextU2->SetLabel( wxS( "micro" ) );
212  m_staticTextM2->SetLabel( wxS( "milli" ) );
213  m_staticTextK2->SetLabel( wxS( "kilo" ) );
214  m_staticTextMeg2->SetLabel( wxS( "mega" ) );
215  m_staticTextG2->SetLabel( wxS( "giga" ) );
216  m_staticTextT2->SetLabel( wxS( "terra" ) );
217 
218  m_staticTextF3->SetLabel( wxS( "1e-15" ) );
219  m_staticTextP3->SetLabel( wxS( "1e-12" ) );
220  m_staticTextN3->SetLabel( wxS( "1e-9" ) );
221  m_staticTextU3->SetLabel( wxS( "1e-6" ) );
222  m_staticTextM3->SetLabel( wxS( "1e-3" ) );
223  m_staticTextK3->SetLabel( wxS( "1e3" ) );
224  m_staticTextMeg3->SetLabel( wxS( "1e6" ) );
225  m_staticTextG3->SetLabel( wxS( "1e9" ) );
226  m_staticTextT3->SetLabel( wxS( "1e12" ) );
227 
228  // Hide pages that aren't fully implemented yet
229  // wxPanel::Hide() isn't enough on some platforms
230  m_powerNotebook->RemovePage( m_powerNotebook->FindPage( m_pwrTransNoise ) );
231  m_powerNotebook->RemovePage( m_powerNotebook->FindPage( m_pwrExtData ) );
232 }
233 
234 
236 {
237  if( !DIALOG_SPICE_MODEL_BASE::TransferDataFromWindow() )
238  return false;
239 
240  wxWindow* page = m_notebook->GetCurrentPage();
241 
242  // Passive
243  if( page == m_passive )
244  {
245  if( !m_disabled->GetValue() && !m_passive->Validate() )
246  return false;
247 
248  switch( m_pasType->GetSelection() )
249  {
250  case 0: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_RESISTOR; break;
251  case 1: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_CAPACITOR; break;
252  case 2: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_INDUCTOR; break;
253 
254  default:
255  wxASSERT_MSG( false, "Unhandled passive type" );
256  return false;
257  break;
258  }
259 
260  m_fieldsTmp[SF_MODEL] = m_pasValue->GetValue();
261  }
262  else if( page == m_model ) // Model
263  {
264  if( !m_model->Validate() )
265  return false;
266 
267  int modelIdx = m_modelType->GetSelection();
268 
269  if( modelIdx >= 0 && modelIdx < (int) modelTypes.size() )
270  m_fieldsTmp[SF_PRIMITIVE] = static_cast<char>( modelTypes[modelIdx].type );
271 
272  m_fieldsTmp[SF_MODEL] = m_modelName->GetValue();
273 
274  if( !empty( m_modelLibrary ) )
275  m_fieldsTmp[SF_LIB_FILE] = m_modelLibrary->GetValue();
276  }
277  else if( page == m_power ) // Power source
278  {
279  wxString model;
280 
281  if( !generatePowerSource( model ) )
282  return false;
283 
284  m_fieldsTmp[SF_PRIMITIVE] = (char)( m_pwrType->GetSelection() ? SP_ISOURCE : SP_VSOURCE );
285  m_fieldsTmp[SF_MODEL] = model;
286  }
287  else
288  {
289  wxASSERT_MSG( false, "Unhandled model type" );
290  return false;
291  }
292 
293  m_fieldsTmp[SF_ENABLED] = !m_disabled->GetValue() ? "Y" : "N"; // note bool inversion
294  m_fieldsTmp[SF_NODE_SEQUENCE] = m_nodeSeqCheck->IsChecked() ? m_nodeSeqVal->GetValue() : "";
295 
296  // Apply the settings
297  for( int i = 0; i < SF_END; ++i )
298  {
299  if( m_fieldsTmp.count( (SPICE_FIELD) i ) > 0 && !m_fieldsTmp.at( i ).IsEmpty() )
300  {
301  if( m_useSchFields )
302  getSchField( i ).SetText( m_fieldsTmp[i] );
303  else
304  getLibField( i ).SetText( m_fieldsTmp[i] );
305  }
306  else
307  {
308  // Erase empty fields (having empty fields causes a warning in the properties dialog)
309  const wxString& spiceField =
311 
312  if( m_useSchFields )
313  {
314  m_schfields->erase( std::remove_if( m_schfields->begin(), m_schfields->end(),
315  [&]( const SCH_FIELD& f )
316  {
317  return f.GetName() == spiceField;
318  } ),
319  m_schfields->end() );
320  }
321  else
322  {
323  m_libfields->erase( std::remove_if( m_libfields->begin(), m_libfields->end(),
324  [&]( const LIB_FIELD& f )
325  {
326  return f.GetName() == spiceField;
327  } ),
328  m_libfields->end() );
329  }
330  }
331  }
332 
333  return true;
334 }
335 
336 
338 {
339  const auto& spiceFields = NETLIST_EXPORTER_PSPICE::GetSpiceFields();
340 
341  // Fill out the working buffer
342  for( unsigned int idx = 0; idx < spiceFields.size(); ++idx )
343  {
344  const wxString& spiceField = spiceFields[idx];
345 
347  (SPICE_FIELD ) idx, &m_symbol,
349 
350  // Do not modify the existing value, just add missing fields with default values
351  if( m_useSchFields && m_schfields )
352  {
353  for( const auto& field : *m_schfields )
354  {
355  if( field.GetName() == spiceField && !field.GetText().IsEmpty() )
356  {
357  m_fieldsTmp[idx] = field.GetText();
358  break;
359  }
360  }
361  }
362  else if( m_libfields )
363  {
364  // TODO: There must be a good way to template out these repetitive calls
365  for( const LIB_FIELD& field : *m_libfields )
366  {
367  if( field.GetName() == spiceField && !field.GetText().IsEmpty() )
368  {
369  m_fieldsTmp[idx] = field.GetText();
370  break;
371  }
372  }
373  }
374  }
375 
376  // Analyze the symbol fields to fill out the dialog.
377  unsigned int primitive = toupper( m_fieldsTmp[SF_PRIMITIVE][0] );
378 
379  switch( primitive )
380  {
381  case SP_RESISTOR:
382  case SP_CAPACITOR:
383  case SP_INDUCTOR:
384  m_notebook->SetSelection( m_notebook->FindPage( m_passive ) );
385  m_pasType->SetSelection( primitive == SP_RESISTOR ? 0
386  : primitive == SP_CAPACITOR ? 1
387  : primitive == SP_INDUCTOR ? 2
388  : -1 );
389  m_pasValue->SetValue( m_fieldsTmp[SF_MODEL] );
390  break;
391 
392  case SP_DIODE:
393  case SP_BJT:
394  case SP_MOSFET:
395  case SP_JFET:
396  case SP_SUBCKT:
397  m_notebook->SetSelection( m_notebook->FindPage( m_model ) );
398  m_modelType->SetSelection( getModelTypeIdx( primitive ) );
399  m_modelName->SetValue( m_fieldsTmp[SF_MODEL] );
400  m_modelLibrary->SetValue( m_fieldsTmp[SF_LIB_FILE] );
401 
402  if( !empty( m_modelLibrary ) )
403  {
404  const wxString& libFile = m_modelLibrary->GetValue();
405  m_fieldsTmp[SF_LIB_FILE] = libFile;
406  loadLibrary( libFile );
407  }
408  break;
409 
410  case SP_VSOURCE:
411  case SP_ISOURCE:
413  return false;
414 
415  m_notebook->SetSelection( m_notebook->FindPage( m_power ) );
416  m_pwrType->SetSelection( primitive == SP_ISOURCE ? 1 : 0 );
417  break;
418 
419  default:
420  //wxASSERT_MSG( false, "Unhandled Spice primitive type" );
421  break;
422  }
423 
425 
426  // Check if node sequence is different than the default one
429  {
430  m_nodeSeqCheck->SetValue( true );
432  }
433 
434  showPinOrderNote( primitive );
435 
436  return DIALOG_SPICE_MODEL_BASE::TransferDataToWindow();
437 }
438 
439 
441 {
442  // Display a note info about pin order, according to aModelType
443  wxString msg;
444 
445  msg = _( "Symbol pin numbering don't always match the required SPICE pin order\n"
446  "Check the symbol and use \"Alternate node sequence\" to reorder the pins"
447  ", if necessary" );
448 
449  msg += '\n';
450 
451  switch( aModelType )
452  {
453  case SP_DIODE:
454  msg += _( "For a Diode, pin order is anode, cathode" );
455  break;
456 
457  case SP_BJT:
458  msg += _( "For a BJT, pin order is collector, base, emitter, substrate (optional)" );
459  break;
460 
461  case SP_MOSFET:
462  msg += _( "For a MOSFET, pin order is drain, gate, source" );
463  break;
464 
465  case SP_JFET:
466  msg += _( "For a JFET, pin order is drain, gate, source" );
467  break;
468 
469  default:
470  break;
471  }
472 
473  m_stInfoNote->SetLabel( msg );
474 }
475 
476 
477 bool DIALOG_SPICE_MODEL::parsePowerSource( const wxString& aModel )
478 {
479  if( aModel.IsEmpty() )
480  return false;
481 
482  wxStringTokenizer tokenizer( aModel, " ()" );
483  wxString tkn = tokenizer.GetNextToken().Lower();
484 
485  while( tokenizer.HasMoreTokens() )
486  {
487  // Variables used for generic values processing (filling out wxTextCtrls in sequence)
488  bool genericProcessing = false;
489  unsigned int genericReqParamsCount = 0;
490  std::vector<wxTextCtrl*> genericControls;
491 
492  if( tkn == "dc" )
493  {
494  // There might be an optional "dc" or "trans" directive, skip it
495  if( tkn == "dc" || tkn == "trans" )
496  tkn = tokenizer.GetNextToken().Lower();
497 
498  // DC value
499  try
500  {
501  m_genDc->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
502  }
503  catch( ... )
504  {
505  return false;
506  }
507  }
508  else if( tkn == "ac" )
509  {
510  // AC magnitude
511  try
512  {
513  tkn = tokenizer.GetNextToken().Lower();
514  m_genAcMag->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
515  }
516  catch( ... )
517  {
518  return false;
519  }
520 
521  // AC phase (optional)
522  try
523  {
524  tkn = tokenizer.GetNextToken().Lower();
525  m_genAcPhase->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
526  }
527  catch( ... )
528  {
529  continue; // perhaps another directive
530  }
531  }
532  else if( tkn == "pulse" )
533  {
534  m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrPulse ) );
535 
536  genericProcessing = true;
537  genericReqParamsCount = 2;
538  genericControls = { m_pulseInit, m_pulseNominal, m_pulseDelay,
540  }
541  else if( tkn == "sin" )
542  {
543  m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrSin ) );
544 
545  genericProcessing = true;
546  genericReqParamsCount = 2;
547  genericControls = { m_sinOffset, m_sinAmplitude, m_sinFreq, m_sinDelay,
548  m_sinDampFactor };
549  }
550  else if( tkn == "exp" )
551  {
552  m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrExp ) );
553 
554  genericProcessing = true;
555  genericReqParamsCount = 2;
556  genericControls = { m_expInit, m_expPulsed,
558  }
559  else if( tkn == "pwl" )
560  {
561  m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrPwl ) );
562 
563  try
564  {
565  while( tokenizer.HasMoreTokens() )
566  {
567  tkn = tokenizer.GetNextToken();
568  SPICE_VALUE time( tkn );
569 
570  tkn = tokenizer.GetNextToken();
571  SPICE_VALUE value( tkn );
572 
573  addPwlValue( time.ToSpiceString(), value.ToSpiceString() );
574  }
575  }
576  catch( ... )
577  {
578  return false;
579  }
580  }
581  else if( tkn == "sffm" )
582  {
583  m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrFm ) );
584 
585  genericProcessing = true;
586  genericReqParamsCount = 4;
589  }
590  else if( tkn == "am" )
591  {
592  m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrAm ) );
593 
594  genericProcessing = true;
595  genericReqParamsCount = 5;
598  }
599  else if( tkn == "trrandom" )
600  {
601  m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrRandom ) );
602 
603  // first token will configure drop-down list
604  if( !tokenizer.HasMoreTokens() )
605  return false;
606 
607  tkn = tokenizer.GetNextToken().Lower();
608  long type;
609  if( !tkn.ToLong( &type ) )
610  return false;
611 
612  m_rnType->SetSelection( type - 1 );
613  wxCommandEvent dummy;
615 
616  // remaining parameters can be handled in generic way
617  genericProcessing = true;
618  genericReqParamsCount = 4;
619  genericControls = { m_rnTS, m_rnTD, m_rnParam1, m_rnParam2 };
620  }
621  else
622  {
623  // Unhandled power source type
624  wxASSERT_MSG( false, "Unhandled power source type" );
625  return false;
626  }
627 
628  if( genericProcessing )
629  {
630  try
631  {
632  for( unsigned int i = 0; i < genericControls.size(); ++i )
633  {
634  // If there are no more tokens, let's check if we got at least required fields
635  if( !tokenizer.HasMoreTokens() )
636  return ( i >= genericReqParamsCount );
637 
638  tkn = tokenizer.GetNextToken().Lower();
639  genericControls[i]->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
640  }
641  }
642  catch( ... )
643  {
644  return false;
645  }
646  }
647 
648  // Get the next token now, so if any of the branches catches an exception, try to
649  // process it in another branch
650  tkn = tokenizer.GetNextToken().Lower();
651  }
652 
653  return true;
654 }
655 
656 
658 {
659  wxString acdc, trans;
660  wxWindow* page = m_powerNotebook->GetCurrentPage();
661  bool useTrans = true; // shall we use the transient command part?
662 
663  // Variables for generic processing
664  bool genericProcessing = false;
665  unsigned int genericReqParamsCount = 0;
666  std::vector<wxTextCtrl*> genericControls;
667 
669  // If SPICE_VALUE can be properly constructed, then it is a valid value
670  try
671  {
672  if( !empty( m_genDc ) )
673  acdc += wxString::Format( "dc %s ",
674  SPICE_VALUE( m_genDc->GetValue() ).ToSpiceString() );
675  }
676  catch( ... )
677  {
678  DisplayError( this, wxT( "Invalid DC value" ) );
679  return false;
680  }
681 
682  try
683  {
684  if( !empty( m_genAcMag ) )
685  {
686  acdc += wxString::Format( "ac %s ",
687  SPICE_VALUE( m_genAcMag->GetValue() ).ToSpiceString() );
688 
689  if( !empty( m_genAcPhase ) )
690  acdc += wxString::Format( "%s ",
691  SPICE_VALUE( m_genAcPhase->GetValue() ).ToSpiceString() );
692  }
693  }
694  catch( ... )
695  {
696  DisplayError( this, wxT( "Invalid AC magnitude or phase" ) );
697  return false;
698  }
699 
701  if( page == m_pwrPulse )
702  {
703  if( !m_pwrPulse->Validate() )
704  return false;
705 
706  genericProcessing = true;
707  trans += "pulse(";
708  genericReqParamsCount = 2;
709  genericControls = { m_pulseInit, m_pulseNominal, m_pulseDelay,
711  }
712  else if( page == m_pwrSin )
713  {
714  if( !m_pwrSin->Validate() )
715  return false;
716 
717  genericProcessing = true;
718  trans += "sin(";
719  genericReqParamsCount = 2;
721  }
722  else if( page == m_pwrExp )
723  {
724  if( !m_pwrExp->Validate() )
725  return false;
726 
727  genericProcessing = true;
728  trans += "exp(";
729  genericReqParamsCount = 2;
730  genericControls = { m_expInit, m_expPulsed,
732  }
733  else if( page == m_pwrPwl )
734  {
735  if( m_pwlValList->GetItemCount() > 0 )
736  {
737  trans += "pwl(";
738 
739  for( int i = 0; i < m_pwlValList->GetItemCount(); ++i )
740  {
741  trans += wxString::Format( "%s %s ", m_pwlValList->GetItemText( i, m_pwlTimeCol ),
742  m_pwlValList->GetItemText( i, m_pwlValueCol ) );
743  }
744 
745  trans.Trim();
746  trans += ")";
747  }
748  }
749  else if( page == m_pwrFm )
750  {
751  if( !m_pwrFm->Validate() )
752  return false;
753 
754  genericProcessing = true;
755  trans += "sffm(";
756  genericReqParamsCount = 4;
759  }
760  else if( page == m_pwrAm )
761  {
762  if( !m_pwrAm->Validate() )
763  return false;
764 
765  genericProcessing = true;
766  trans += "am(";
767  genericReqParamsCount = 5;
770  }
771  else if( page == m_pwrRandom )
772  {
773  if( !m_pwrRandom->Validate() )
774  return false;
775 
776  // first parameter must be retrieved from drop-down list selection
777  trans += "trrandom(";
778  trans.Append( wxString::Format( wxT( "%i " ), ( m_rnType->GetSelection() + 1 ) ) );
779 
780  genericProcessing = true;
781  genericReqParamsCount = 4;
782  genericControls = { m_rnTS, m_rnTD, m_rnParam1, m_rnParam2 };
783  }
784  if( genericProcessing )
785  {
786  auto first_empty = std::find_if( genericControls.begin(), genericControls.end(), empty );
787  auto first_not_empty = std::find_if( genericControls.begin(), genericControls.end(),
788  []( const wxTextCtrl* c ){ return !empty( c ); } );
789 
790  if( std::distance( first_not_empty, genericControls.end() ) == 0 )
791  {
792  // all empty
793  useTrans = false;
794  }
795  else if( std::distance( genericControls.begin(),
796  first_empty ) < (int)genericReqParamsCount )
797  {
798  DisplayError( nullptr,
799  wxString::Format( _( "You need to specify at least the "
800  "first %d parameters for the transient source" ),
801  genericReqParamsCount ) );
802 
803  return false;
804  }
805  else if( std::find_if_not( first_empty, genericControls.end(),
806  empty ) != genericControls.end() )
807  {
808  DisplayError( nullptr, _( "You cannot leave interleaved empty fields "
809  "when defining a transient source" ) );
810  return false;
811  }
812  else
813  {
814  std::for_each( genericControls.begin(), first_empty, [&trans] ( wxTextCtrl* ctrl ) {
815  trans += wxString::Format( "%s ", ctrl->GetValue() );
816  } );
817  }
818 
819  trans.Trim();
820  trans += ")";
821  }
822 
823  aTarget = acdc;
824 
825  if( useTrans )
826  aTarget += trans;
827 
828  // Remove whitespaces from left and right side
829  aTarget.Trim( false );
830  aTarget.Trim( true );
831 
832  return true;
833 }
834 
835 
836 void DIALOG_SPICE_MODEL::loadLibrary( const wxString& aFilePath )
837 {
838  //First, expand env vars, if any
839  wxString libname = ExpandEnvVarSubstitutions( aFilePath, &Prj() );
840 
841  // Make path absolute, especially if it is relative to the project path
842  libname = Prj().AbsolutePath( libname );
843 
844  wxString curModel = m_modelName->GetValue();
845  m_models.clear();
846  wxFileName filePath( libname );
847  bool in_subckt = false; // flag indicating that the parser is inside a .subckt section
848 
849  if( !filePath.Exists() )
850  return;
851 
852  // Display the library contents
853  wxWindowUpdateLocker updateLock( this );
854 
855  m_libraryContents->SetReadOnly( false );
856  m_libraryContents->Clear();
857  wxTextFile file;
858  file.Open( filePath.GetFullPath() );
859  int line_nr = 0;
860 
861  // Stores the library content. It will be displayed after reading the full library
862  wxString fullText;
863 
864  // Process the file, looking for symbols.
865  while( !file.Eof() )
866  {
867  const wxString& line = line_nr == 0 ? file.GetFirstLine() : file.GetNextLine();
868  fullText << line << '\n';
869 
870  wxStringTokenizer tokenizer( line );
871 
872  while( tokenizer.HasMoreTokens() )
873  {
874  wxString token = tokenizer.GetNextToken().Lower();
875 
876  // some subckts contain .model clauses inside,
877  // skip them as they are a part of the subckt, not another model
878  if( token == ".model" && !in_subckt )
879  {
880  wxString name = tokenizer.GetNextToken();
881 
882  if( name.IsEmpty() )
883  break;
884 
885  token = tokenizer.GetNextToken();
886  SPICE_PRIMITIVE type = MODEL::parseModelType( token );
887 
888  if( type != SP_UNKNOWN )
889  m_models.emplace( name, MODEL( line_nr, type ) );
890  }
891  else if( token == ".subckt" )
892  {
893  wxASSERT( !in_subckt );
894  in_subckt = true;
895 
896  wxString name = tokenizer.GetNextToken();
897 
898  if( name.IsEmpty() )
899  break;
900 
901  m_models.emplace( name, MODEL( line_nr, SP_SUBCKT ) );
902  }
903  else if( token == ".ends" )
904  {
905  wxASSERT( in_subckt );
906  in_subckt = false;
907  }
908  }
909 
910  ++line_nr;
911  }
912 
913  // display the full library content:
914  m_libraryContents->AppendText( fullText );
915  m_libraryContents->SetReadOnly( true );
916 
917  wxArrayString modelsList;
918 
919  // Refresh the model name combobox values
920  m_modelName->Clear();
921 
922  for( const auto& model : m_models )
923  {
924  m_modelName->Append( model.first );
925  modelsList.Add( model.first );
926  }
927 
928  m_modelName->AutoComplete( modelsList );
929 
930  // Restore the previous value or if there is none - pick the first one from the loaded library
931  if( !curModel.IsEmpty() )
932  m_modelName->SetValue( curModel );
933  else if( m_modelName->GetCount() > 0 )
934  m_modelName->SetSelection( 0 );
935 }
936 
937 
939 {
940  const wxString& spiceField =
942 
943  auto fieldIt = std::find_if( m_schfields->begin(), m_schfields->end(),
944  [&]( const SCH_FIELD& f )
945  {
946  return f.GetName() == spiceField;
947  } );
948 
949  // Found one, so return it
950  if( fieldIt != m_schfields->end() )
951  return *fieldIt;
952 
953  // Create a new field with requested name
954  m_schfields->emplace_back( wxPoint(), m_schfields->size(), &m_symbol, spiceField );
955  return m_schfields->back();
956 }
957 
958 
960 {
961  const wxString& spiceField =
963 
964  auto fieldIt = std::find_if( m_libfields->begin(), m_libfields->end(),
965  [&]( const LIB_FIELD& f )
966  {
967  return f.GetName() == spiceField;
968  } );
969 
970  // Found one, so return it
971  if( fieldIt != m_libfields->end() )
972  return *fieldIt;
973 
974  // Create a new field with requested name
975  LIB_FIELD new_field( m_libfields->size() );
976  m_libfields->front().Copy( &new_field );
977  new_field.SetName( spiceField );
978 
979  m_libfields->push_back( new_field );
980  return m_libfields->back();
981 }
982 
983 
984 bool DIALOG_SPICE_MODEL::addPwlValue( const wxString& aTime, const wxString& aValue )
985 {
986  // TODO execute validators
987  if( aTime.IsEmpty() || aValue.IsEmpty() )
988  return false;
989 
990  long idx = m_pwlValList->InsertItem( m_pwlTimeCol, aTime );
991  m_pwlValList->SetItem( idx, m_pwlValueCol, aValue );
992 
993  // There is no wxString::ToFloat, but we need to guarantee it fits in 4 bytes
994  double timeD;
995  float timeF;
996  m_pwlTime->GetValue().ToDouble( &timeD );
997  timeF = timeD;
998  long data;
999  std::memcpy( &data, &timeF, sizeof( timeF ) );
1000 
1001  // Store the time value, so the entries can be sorted
1002  m_pwlValList->SetItemData( idx, data );
1003 
1004  // Sort items by timestamp
1005  m_pwlValList->SortItems( comparePwlValues, -1 );
1006 
1007  return true;
1008 }
1009 
1010 
1011 void DIALOG_SPICE_MODEL::onSelectLibrary( wxCommandEvent& event )
1012 {
1013  //First, expand env vars, if any, in lib path
1014  wxString libname = ExpandEnvVarSubstitutions( m_modelLibrary->GetValue(), &Prj() );
1015 
1016  // Make path absolute, especially if it is relative to the project path
1017  libname = Prj().AbsolutePath( libname );
1018 
1019  wxString searchPath = wxFileName( libname ).GetPath();
1020 
1021  if( searchPath.IsEmpty() )
1022  searchPath = Prj().GetProjectPath();
1023 
1024  wxString wildcards = SpiceLibraryFileWildcard() + "|" + AllFilesWildcard();
1025  wxFileDialog openDlg( this, _( "Select library" ), searchPath, "", wildcards,
1026  wxFD_OPEN | wxFD_FILE_MUST_EXIST );
1027 
1028  if( openDlg.ShowModal() == wxID_CANCEL )
1029  return;
1030 
1031  wxFileName libPath( openDlg.GetPath() );
1032 
1033  // Try to convert the path to relative to project
1034  if( libPath.MakeRelativeTo( Prj().GetProjectPath() )
1035  && !libPath.GetFullPath().StartsWith( ".." ) )
1036  m_modelLibrary->SetValue( libPath.GetFullPath() );
1037  else
1038  m_modelLibrary->SetValue( openDlg.GetPath() );
1039 
1040  loadLibrary( openDlg.GetPath() );
1041  m_modelName->Popup();
1042 }
1043 
1044 
1045 void DIALOG_SPICE_MODEL::onModelSelected( wxCommandEvent& event )
1046 {
1047  // autoselect the model type
1048  auto it = m_models.find( m_modelName->GetValue() );
1049 
1050  if( it != m_models.end() )
1051  {
1052  m_modelType->SetSelection( getModelTypeIdx( it->second.model ) );
1053 
1054  // scroll to the bottom, so the model definition is shown in the first line
1055  m_libraryContents->ShowPosition(
1056  m_libraryContents->XYToPosition( 0, m_libraryContents->GetNumberOfLines() ) );
1057  m_libraryContents->ShowPosition( m_libraryContents->XYToPosition( 0, it->second.line ) );
1058  }
1059  else
1060  {
1061  m_libraryContents->ShowPosition( 0 );
1062  }
1063 }
1064 
1065 
1066 void DIALOG_SPICE_MODEL::onTypeSelected( wxCommandEvent& event )
1067 {
1068  int type = m_modelType->GetSelection();
1069  int primitive = type >= 0 ? modelTypes[type].type : SP_SUBCKT;
1070  showPinOrderNote( primitive );
1071 }
1072 
1073 
1074 void DIALOG_SPICE_MODEL::onPwlAdd( wxCommandEvent& event )
1075 {
1076  addPwlValue( m_pwlTime->GetValue(), m_pwlValue->GetValue() );
1077 }
1078 
1079 
1080 void DIALOG_SPICE_MODEL::onPwlRemove( wxCommandEvent& event )
1081 {
1082  long idx = m_pwlValList->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
1083  m_pwlValList->DeleteItem( idx );
1084 }
1085 
1086 
1087 void DIALOG_SPICE_MODEL::onRandomSourceType( wxCommandEvent& event )
1088 {
1089  switch( m_rnType->GetSelection() )
1090  {
1091  case TRRANDOM_UNIFORM:
1092  // uniform white noise
1093  m_rnParam1Text->SetLabel( _( "Range:" ) );
1094  m_rnParam2Text->SetLabel( _( "Offset:" ) );
1095  break;
1096 
1097  case TRRANDOM_GAUSSIAN:
1098  // Gaussian
1099  m_rnParam1Text->SetLabel( _( "Standard deviation:" ) );
1100  m_rnParam2Text->SetLabel( _( "Mean:" ) );
1101  break;
1102 
1103  case TRRANDOM_EXPONENTIAL:
1104  // exponential
1105  m_rnParam1Text->SetLabel( _( "Mean:" ) );
1106  m_rnParam2Text->SetLabel( _( "Offset:" ) );
1107  break;
1108 
1109  case TRRANDOM_POISSON:
1110  // Poisson
1111  m_rnParam1Text->SetLabel( _( "Lambda:" ) );
1112  m_rnParam2Text->SetLabel( _( "Offset:" ) );
1113  break;
1114 
1115  default:
1116  wxFAIL_MSG( _( "type of random generator for source is invalid" ) );
1117  break;
1118  }
1119 }
1120 
1121 
1123 {
1124  wxCHECK( !aValue.IsEmpty(), SP_UNKNOWN );
1125  const wxString val( aValue.Lower() );
1126 
1127  for( const auto& model : modelTypes )
1128  {
1129  for( const auto& keyword : model.keywords )
1130  {
1131  if( val.StartsWith( keyword ) )
1132  return model.type;
1133  }
1134  }
1135 
1136  return SP_UNKNOWN;
1137 }
void DisplayError(wxWindow *aParent, const wxString &aText, int aDisplayTime)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:271
Instances are attached to a symbol or sheet and provide a place for the symbol's value,...
Definition: sch_field.h:49
void onSelectLibrary(wxCommandEvent &event) override
void Init()
Initialize the internal settings.
wxString ToSpiceString() const
Return string value in Spice format (e.g.
static SPICE_PRIMITIVE parseModelType(const wxString &aValue)
SPICE_VALIDATOR m_spiceValidator
void onTypeSelected(wxCommandEvent &event) override
Edited symbol.
std::vector< LIB_FIELD > * m_libfields
This file is part of the common library.
virtual bool TransferDataFromWindow() override
bool m_useSchFields
Temporary field values.
Field object used in symbol libraries.
Definition: lib_field.h:59
static const wxString & GetSpiceFieldName(SPICE_FIELD aField)
Return a string used for a particular component field related to Spice simulation.
bool addPwlValue(const wxString &aTime, const wxString &aValue)
Add a value to the PWL values list.
LIB_FIELD & getLibField(int aFieldType)
std::vector< SCH_FIELD > SCH_FIELDS
A container for several SCH_FIELD items.
Definition: sch_symbol.h:63
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:279
static bool StringToBool(const wxString &aStr)
Convert 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.
DIALOG_SPICE_MODEL(wxWindow *aParent, SCH_SYMBOL &aSymbol, std::vector< SCH_FIELD > *aSchFields)
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition: project.cpp:122
wxString AllFilesWildcard()
wxString description
Human-readable description.
static int getModelTypeIdx(char aPrimitive)
virtual const wxString AbsolutePath(const wxString &aFileName) const
Fix up aFileName if it is relative to the project's directory to be an absolute path and filename.
Definition: project.cpp:269
SCH_SYMBOL & m_symbol
Fields from the symbol properties dialog.
static bool empty(const wxTextCtrl *aCtrl)
static LIB_SYMBOL * dummy()
Used to draw a dummy shape when a LIB_SYMBOL is not found in library.
Definition: sch_symbol.cpp:72
std::vector< std::string > keywords
Keywords indicating the model.
virtual void SetText(const wxString &aText)
Definition: eda_text.cpp:114
void showPinOrderNote(int aModelType)
Display a note info about pin order.
void onRandomSourceType(wxCommandEvent &event) override
< Helper class to handle Spice way of expressing values (e.g. 10.5 Meg) Helper class to recognize Spi...
Definition: spice_value.h:34
wxStyledTextCtrl * m_libraryContents
static wxString GetSpiceFieldDefVal(SPICE_FIELD aField, SCH_SYMBOL *aSymbol, unsigned aCtl)
Retrieve the default value for a given field.
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
SPICE_PRIMITIVE type
Character identifying the model.
Definition of file extensions used in Kicad.
SCH_FIELD & getSchField(int aFieldType)
Return or create a field in the edited schematic fields vector.
#define _(s)
wxTextValidator m_notEmptyValidator
bool generatePowerSource(wxString &aTarget)
Generate a string to describe power source parameters, basing on the current selection.
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
void onModelSelected(wxCommandEvent &event) override
std::map< int, wxString > m_fieldsTmp
void onPwlRemove(wxCommandEvent &event) override
bool Init()
Perform application-specific initialization tasks.
Definition: gtk/app.cpp:40
void onPwlAdd(wxCommandEvent &event) override
void loadLibrary(const wxString &aFilePath)
Load a list of components (.model and .subckt) from a spice library file and add them to a combo box.
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
SPICE_PRIMITIVE model
Convert string to model.
Class DIALOG_SPICE_MODEL_BASE.
Schematic symbol object.
Definition: sch_symbol.h:78
const char * name
Definition: DXF_plotter.cpp:56
std::map< wxString, MODEL > m_models
Column identifiers for PWL power source value list.
wxString SpiceLibraryFileWildcard()
static int wxCALLBACK comparePwlValues(wxIntPtr aItem1, wxIntPtr aItem2, wxIntPtr WXUNUSED(aSortData))
The common library.
static const std::vector< wxString > & GetSpiceFields()
Return a vector of component field names related to Spice simulation.
std::vector< SCH_FIELD > * m_schfields
SPICE_VALIDATOR m_spiceEmptyValidator
static const std::vector< SPICE_MODEL_INFO > modelTypes
virtual bool TransferDataToWindow() override