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