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