KiCad PCB EDA Suite
dialog_sim_settings.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) 2016 CERN
5  * @author Maciej Suminski <maciej.suminski@cern.ch>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 3
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * https://www.gnu.org/licenses/gpl-3.0.html
20  * or you may search the http://www.gnu.org website for the version 3 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
25 #include "dialog_sim_settings.h"
27 #include <confirm.h>
28 
29 #include <wx/tokenzr.h>
30 
31 #include <vector>
32 #include <utility>
33 
35 //so there are a few tabs missing (e.g. pole-zero, distortion, sensitivity)
36 
37 // Helper function to shorten conditions
38 static bool empty( const wxTextEntryBase* aCtrl )
39 {
40  return aCtrl->GetValue().IsEmpty();
41 }
42 
43 
45  : DIALOG_SIM_SETTINGS_BASE( aParent ), m_exporter( nullptr ), m_spiceEmptyValidator( true )
46 {
47  m_posIntValidator.SetMin( 1 );
48 
49  m_acPointsNumber->SetValidator( m_posIntValidator );
50  m_acFreqStart->SetValidator( m_spiceValidator );
51  m_acFreqStop->SetValidator( m_spiceValidator );
52 
53  m_dcStart1->SetValidator( m_spiceValidator );
54  m_dcStop1->SetValidator( m_spiceValidator );
55  m_dcIncr1->SetValidator( m_spiceValidator );
56 
57  m_dcStart2->SetValidator( m_spiceValidator );
58  m_dcStop2->SetValidator( m_spiceValidator );
59  m_dcIncr2->SetValidator( m_spiceValidator );
60 
61  m_noisePointsNumber->SetValidator( m_posIntValidator );
62  m_noiseFreqStart->SetValidator( m_spiceValidator );
63  m_noiseFreqStop->SetValidator( m_spiceValidator );
64 
65  m_transStep->SetValidator( m_spiceValidator );
66  m_transFinal->SetValidator( m_spiceValidator );
67  m_transInitial->SetValidator( m_spiceEmptyValidator );
68 
70 
71  // Hide pages that aren't fully implemented yet
72  // wxPanel::Hide() isn't enough on some platforms
73  m_simPages->RemovePage( m_simPages->FindPage( m_pgDistortion ) );
74  m_simPages->RemovePage( m_simPages->FindPage( m_pgNoise ) );
75  m_simPages->RemovePage( m_simPages->FindPage( m_pgPoleZero ) );
76  m_simPages->RemovePage( m_simPages->FindPage( m_pgSensitivity ) );
77  m_simPages->RemovePage( m_simPages->FindPage( m_pgTransferFunction ) );
78 
79  m_sdbSizerOK->SetDefault();
81 
82 }
83 
84 wxString DIALOG_SIM_SETTINGS::evaluateDCControls( wxChoice* aDcSource, wxTextCtrl* aDcStart,
85  wxTextCtrl* aDcStop, wxTextCtrl* aDcIncr )
86 {
87  wxString dcSource = aDcSource->GetString( aDcSource->GetSelection() );
88  wxWindow* ctrlWithError = nullptr;
89 
90  if( dcSource.IsEmpty() )
91  {
92  DisplayError( this, _( "You need to select DC source" ) );
93  ctrlWithError = aDcSource;
94  }
96  // hence try..catch below
97  else if( !aDcStart->Validate() )
98  ctrlWithError = aDcStart;
99  else if( !aDcStop->Validate() )
100  ctrlWithError = aDcStop;
101  else if( !aDcIncr->Validate() )
102  ctrlWithError = aDcIncr;
103 
104  if( ctrlWithError )
105  {
106  ctrlWithError->SetFocus();
107  return wxEmptyString;
108  }
109 
110  try
111  {
112  // pick device name from exporter when something different than temperature is selected
113  if( dcSource.Cmp( "TEMP" ) )
114  dcSource = m_exporter->GetSpiceDevice( dcSource );
115 
116  return wxString::Format( "%s %s %s %s", dcSource,
117  SPICE_VALUE( aDcStart->GetValue() ).ToSpiceString(),
118  SPICE_VALUE( aDcStop->GetValue() ).ToSpiceString(),
119  SPICE_VALUE( aDcIncr->GetValue() ).ToSpiceString() );
120  }
121  catch( std::exception& e )
122  {
123  DisplayError( this, e.what() );
124  return wxEmptyString;
125  }
126  catch( const KI_PARAM_ERROR& e )
127  {
128  DisplayError( this, e.What() );
129  return wxEmptyString;
130  }
131  catch( ... )
132  {
133  return wxEmptyString;
134  }
135 }
136 
137 
139 {
140  if( !wxDialog::TransferDataFromWindow() )
141  return false;
142 
143  wxWindow* page = m_simPages->GetCurrentPage();
144 
145  // AC analysis
146  if( page == m_pgAC )
147  {
148  if( !m_pgAC->Validate() )
149  return false;
150 
151  m_simCommand = wxString::Format( ".ac %s %s %s %s",
152  scaleToString( m_acScale->GetSelection() ),
153  m_acPointsNumber->GetValue(),
154  SPICE_VALUE( m_acFreqStart->GetValue() ).ToSpiceString(),
155  SPICE_VALUE( m_acFreqStop->GetValue() ).ToSpiceString() );
156  }
157 
158 
159  // DC transfer analysis
160  else if( page == m_pgDC )
161  {
162  wxString simCmd = wxString( ".dc " );
163 
165  if( src1.IsEmpty() )
166  return false;
167  else
168  simCmd += src1;
169 
170  if( m_dcEnable2->IsChecked() )
171  {
173  if( src2.IsEmpty() )
174  return false;
175  else
176  simCmd += " " + src2;
177 
178  if( m_dcSource1->GetSelection() == m_dcSource2->GetSelection() )
179  {
180  DisplayError( this, _( "Source 1 and Source 2 must be different" ) );
181  return false;
182  }
183  }
184 
185  m_simCommand = simCmd;
186  }
187 
188 
189  // Noise analysis
190  else if( page == m_pgNoise )
191  {
193 
196  return false;
197 
198  wxString ref = empty( m_noiseRef )
199  ? wxString() : wxString::Format( ", %d", netMap.at( m_noiseRef->GetValue() ) );
200 
201  wxString noiseSource = m_exporter->GetSpiceDevice( m_noiseSrc->GetValue() );
202 
203  // Add voltage source prefix if needed
204  if( noiseSource[0] != 'v' && noiseSource[0] != 'V' )
205  noiseSource += 'v' + noiseSource;
206 
207  m_simCommand = wxString::Format( ".noise v(%d%s) %s %s %s %s %s",
208  netMap.at( m_noiseMeas->GetValue() ), ref,
209  noiseSource, scaleToString( m_noiseScale->GetSelection() ),
210  m_noisePointsNumber->GetValue(),
211  SPICE_VALUE( m_noiseFreqStart->GetValue() ).ToSpiceString(),
212  SPICE_VALUE( m_noiseFreqStop->GetValue() ).ToSpiceString() );
213  }
214 
215 
216  // DC operating point analysis
217  else if( page == m_pgOP )
218  {
219  m_simCommand = wxString( ".op" );
220  }
221 
222 
223  // Transient analysis
224  else if( page == m_pgTransient )
225  {
226  if( !m_pgTransient->Validate() )
227  return false;
228 
229  wxString initial = empty( m_transInitial )
230  ? "" : SPICE_VALUE( m_transInitial->GetValue() ).ToSpiceString();
231 
232  m_simCommand = wxString::Format( ".tran %s %s %s",
233  SPICE_VALUE( m_transStep->GetValue() ).ToSpiceString(),
234  SPICE_VALUE( m_transFinal->GetValue() ).ToSpiceString(),
235  initial );
236  }
237 
238 
239  // Custom directives
240  else if( page == m_pgCustom )
241  {
242  m_simCommand = m_customTxt->GetValue();
243  }
244 
245  else
246  {
247  return false;
248  }
249 
250  m_simCommand.Trim();
252 
253  return true;
254 }
255 
256 
258 {
260  if( empty( m_customTxt ) )
261  loadDirectives();
262 
263  if( m_simCommand.IsEmpty() && !empty( m_customTxt ) )
264  return parseCommand( m_customTxt->GetValue() );
265 
266  return true;
267 }
268 
269 
271 {
272  // Fill out comboboxes that allows one to select nets
273  // Map comoboxes to their current values
274  std::map<wxComboBox*, wxString> cmbNet = {
275  { m_noiseMeas, m_noiseMeas->GetStringSelection() },
276  { m_noiseRef, m_noiseRef->GetStringSelection() }
277  };
278 
279  for( auto c : cmbNet )
280  c.first->Clear();
281 
282  for( const auto& net : m_exporter->GetNetIndexMap() )
283  {
284  for( auto c : cmbNet )
285  c.first->Append( net.first );
286  }
287 
288  // Try to restore the previous selection, if possible
289  for( auto c : cmbNet )
290  {
291  int idx = c.first->FindString( c.second );
292 
293  if( idx != wxNOT_FOUND )
294  c.first->SetSelection( idx );
295  }
296 
297  return DIALOG_SIM_SETTINGS_BASE::ShowModal();
298 }
299 
300 void DIALOG_SIM_SETTINGS::updateDCSources( wxChar aType, wxChoice* aSource )
301 {
302  wxString prevSelection = aSource->GetString( aSource->GetSelection() );
303  std::vector<wxString> sourcesList;
304  bool enableSrcSelection = true;
305 
306  if( aType != 'T' )
307  {
308  for( const auto& item : m_exporter->GetSpiceItems() )
309  {
310  if( item.m_primitive == aType )
311  {
312  sourcesList.push_back( item.m_refName );
313  }
314  }
315 
316  std::sort( sourcesList.begin(), sourcesList.end(),
317  [](wxString& a, wxString& b) -> bool
318  {
319  return a.Len() < b.Len() || b.Cmp( a ) > 0;
320  } );
321 
322  if( aSource == m_dcSource2 && !m_dcEnable2->IsChecked() )
323  enableSrcSelection = false;
324  }
325  else
326  {
327  prevSelection = wxT( "TEMP" );
328  sourcesList.push_back( prevSelection );
329  enableSrcSelection = false;
330  }
331 
332  aSource->Enable( enableSrcSelection );
333 
334  aSource->Clear();
335  for( auto& src : sourcesList )
336  aSource->Append( src );
337 
338  // Try to restore the previous selection, if possible
339  int idx = aSource->FindString( prevSelection );
340 
341  if( idx != wxNOT_FOUND )
342  aSource->SetSelection( idx );
343 }
344 
345 
346 bool DIALOG_SIM_SETTINGS::parseCommand( const wxString& aCommand )
347 {
348  if( aCommand.IsEmpty() )
349  return false;
350 
351  wxStringTokenizer tokenizer( aCommand, " " );
352  wxString tkn = tokenizer.GetNextToken().Lower();
353 
354  try {
355  if( tkn == ".ac" )
356  {
357  m_simPages->SetSelection( m_simPages->FindPage( m_pgAC ) );
358 
359  tkn = tokenizer.GetNextToken().Lower();
360 
361  if( tkn == "dec" )
362  m_acScale->SetSelection( 0 );
363  if( tkn == "oct" )
364  m_acScale->SetSelection( 1 );
365  if( tkn == "lin" )
366  m_acScale->SetSelection( 2 );
367  else
368  return false;
369 
370  // If the fields below are empty, it will be caught by the exception handler
371  m_acPointsNumber->SetValue( tokenizer.GetNextToken() );
372  m_acFreqStart->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
373  m_acFreqStop->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
374  }
375 
376  else if( tkn == ".dc" )
377  {
378  SPICE_DC_PARAMS src1, src2;
379  src2.m_vincrement = SPICE_VALUE( -1 );
380  if( !m_exporter->ParseDCCommand( aCommand, &src1, &src2 ) )
381  return false;
382 
383  m_simPages->SetSelection( m_simPages->FindPage( m_pgDC ) );
384 
385  if( !src1.m_source.IsSameAs( wxT( "TEMP" ), false ) == 0 )
386  m_dcSourceType1->SetSelection( m_dcSourceType1->FindString( src1.m_source ) );
387  else
388  m_dcSourceType1->SetSelection(
389  m_dcSourceType1->FindString( src1.m_source.GetChar( 0 ) ) );
390 
391  updateDCSources( src1.m_source.GetChar( 0 ), m_dcSource1 );
392  m_dcSource1->SetSelection( m_dcSource1->FindString( src1.m_source ) );
393  m_dcStart1->SetValue( src1.m_vstart.ToSpiceString() );
394  m_dcStop1->SetValue( src1.m_vend.ToSpiceString() );
395  m_dcIncr1->SetValue( src1.m_vincrement.ToSpiceString() );
396 
397  if( src2.m_vincrement.ToDouble() != -1 )
398  {
399  if( !src2.m_source.IsSameAs( wxT( "TEMP" ), false ) == 0 )
400  m_dcSourceType2->SetSelection( m_dcSourceType2->FindString( src2.m_source ) );
401  else
402  m_dcSourceType2->SetSelection(
403  m_dcSourceType2->FindString( src2.m_source.GetChar( 0 ) ) );
404 
405  updateDCSources( src2.m_source.GetChar( 0 ), m_dcSource2 );
406  m_dcSource2->SetSelection( m_dcSource2->FindString( src2.m_source ) );
407  m_dcStart2->SetValue( src2.m_vstart.ToSpiceString() );
408  m_dcStop2->SetValue( src2.m_vend.ToSpiceString() );
409  m_dcIncr2->SetValue( src2.m_vincrement.ToSpiceString() );
410 
411  m_dcEnable2->SetValue( true );
412  }
413 
415  }
416 
417  else if( tkn == ".tran" )
418  {
419  m_simPages->SetSelection( m_simPages->FindPage( m_pgTransient ) );
420 
421  // If the fields below are empty, it will be caught by the exception handler
422  m_transStep->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
423  m_transFinal->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
424 
425  // Initial time is an optional field
426  tkn = tokenizer.GetNextToken();
427 
428  if( !tkn.IsEmpty() )
429  m_transInitial->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
430  }
431 
432  else if( tkn == ".op" )
433  {
434  m_simPages->SetSelection( m_simPages->FindPage( m_pgOP ) );
435  }
436 
437  // Custom directives
438  else if( !empty( m_customTxt ) )
439  {
440  m_simPages->SetSelection( m_simPages->FindPage( m_pgCustom ) );
441  }
442  }
443  catch( ... )
444  {
445  // Nothing really bad has happened
446  return false;
447  }
448 
449  return true;
450 }
451 
452 
453 void DIALOG_SIM_SETTINGS::onSwapDCSources( wxCommandEvent& event )
454 {
455  std::vector<std::pair<wxTextEntry*, wxTextEntry*>> textCtrl = { { m_dcStart1, m_dcStart2 },
456  { m_dcStop1, m_dcStop2 },
457  { m_dcIncr1, m_dcIncr2 } };
458 
459  for( auto& couple : textCtrl )
460  {
461  wxString tmp = couple.first->GetValue();
462  couple.first->SetValue( couple.second->GetValue() );
463  couple.second->SetValue( tmp );
464  }
465 
466  int src1 = m_dcSource1->GetSelection();
467  int src2 = m_dcSource2->GetSelection();
468 
469  int sel = m_dcSourceType1->GetSelection();
470  m_dcSourceType1->SetSelection( m_dcSourceType2->GetSelection() );
471  m_dcSourceType2->SetSelection( sel );
472 
473  wxChar type1 =
474  m_dcSourceType1->GetString( m_dcSourceType1->GetSelection() ).Upper().GetChar( 0 );
475  updateDCSources( type1, m_dcSource1 );
476  wxChar type2 =
477  m_dcSourceType2->GetString( m_dcSourceType2->GetSelection() ).Upper().GetChar( 0 );
478  updateDCSources( type2, m_dcSource2 );
479 
480  m_dcSource1->SetSelection( src2 );
481  m_dcSource2->SetSelection( src1 );
482 
485 }
486 
487 
488 void DIALOG_SIM_SETTINGS::onDCEnableSecondSource( wxCommandEvent& event )
489 {
490  bool is2ndSrcEnabled = m_dcEnable2->IsChecked();
491  wxChar type =
492  m_dcSourceType2->GetString( m_dcSourceType2->GetSelection() ).Upper().GetChar( 0 );
493 
494  m_dcSourceType2->Enable( is2ndSrcEnabled );
495  m_dcSource2->Enable( is2ndSrcEnabled && type != 'T' );
496  m_dcStart2->Enable( is2ndSrcEnabled );
497  m_dcStop2->Enable( is2ndSrcEnabled );
498  m_dcIncr2->Enable( is2ndSrcEnabled );
499 }
500 
501 
502 void DIALOG_SIM_SETTINGS::updateDCUnits( wxChar aType, wxChoice* aSource,
503  wxStaticText* aStartValUnit, wxStaticText* aEndValUnit,
504  wxStaticText* aStepUnit )
505 {
506  wxString unit;
507 
508  switch( aType )
509  {
510  case 'V': unit = _( "Volts" ); break;
511  case 'I': unit = _( "Amperes" ); break;
512  case 'R': unit = _( "Ohms" ); break;
513  case 'T': unit = wxT( "\u00B0C" ); break;
514  }
515  aStartValUnit->SetLabel( unit );
516  aEndValUnit->SetLabel( unit );
517  aStepUnit->SetLabel( unit );
518 
519  m_pgDC->Fit();
520  m_pgDC->Refresh();
521 }
522 
523 
525 {
526  if( m_exporter )
527  m_customTxt->SetValue( m_exporter->GetSheetSimCommand() );
528 }
529 
530 
532 {
534 
535  if( !m_fixPassiveVals->IsChecked() )
537 
538  if( !m_fixIncludePaths->IsChecked() )
540 }
void DisplayError(wxWindow *aParent, const wxString &aText, int aDisplayTime)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:253
wxString ToSpiceString() const
Return string value in Spice format (e.g.
const SPICE_ITEM_LIST & GetSpiceItems() const
Return list of items representing schematic components in the Spice world.
This file is part of the common library.
double ToDouble() const
SPICE_VALIDATOR m_spiceEmptyValidator
wxString evaluateDCControls(wxChoice *aDcSource, wxTextCtrl *aDcStart, wxTextCtrl *aDcStop, wxTextCtrl *aDcIncr)
Reads values from one DC sweep source to form a part of sim command.
static wxString scaleToString(int aOption)
Class DIALOG_SIM_SETTINGS_BASE.
void onSwapDCSources(wxCommandEvent &event) override
SPICE_VALIDATOR m_spiceValidator
wxString GetSheetSimCommand()
Return simulation command directives placed in schematic sheets (if any).
void updateDCSources(wxChar aType, wxChoice *aSource)
Updates DC sweep source with components from schematic.
< Helper class to handle Spice way of expressing values (e.g. 10.5 Meg) Helper class to recognize Spi...
Definition: spice_value.h:34
bool parseCommand(const wxString &aCommand)
Parses a Spice directive.
const NET_INDEX_MAP & GetNetIndexMap() const
Return a map of circuit nodes to net names.
std::map< wxString, int > NET_INDEX_MAP
bool ParseDCCommand(const wxString &aCmd, SPICE_DC_PARAMS *aSource1, SPICE_DC_PARAMS *aSource2)
Parse a two-source .dc command directive into its components.
bool TransferDataFromWindow() override
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
const wxString What() const
Definition: ki_exception.h:57
NETLIST_EXPORTER_PSPICE_SIM * m_exporter
DIALOG_SIM_SETTINGS(wxWindow *aParent)
#define _(s)
Definition: 3d_actions.cpp:33
bool TransferDataToWindow() override
wxIntegerValidator< int > m_posIntValidator
static bool empty(const wxTextEntryBase *aCtrl)
void updateDCUnits(wxChar aType, wxChoice *aSource, wxStaticText *aStartValUnit, wxStaticText *aEndValUnit, wxStaticText *aStepUnit)
Updates units on labels depending on selected source.
wxString GetSpiceDevice(const wxString &aComponent) const
Return name of Spice device corresponding to a schematic component.
void refreshUIControls()
!> Generates events to update UI state
void onDCEnableSecondSource(wxCommandEvent &event) override
Hold a translatable error message and may be used when throwing exceptions containing a translated er...
Definition: ki_exception.h:44