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