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