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