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