From 9b6cc6c505d5e5169cea18666fd8be975d32ae67 Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Mon, 24 Oct 2022 03:47:48 +0200 Subject: [PATCH] Sim: Fix tuners --- .../netlist_exporter_spice.cpp | 23 +- .../netlist_exporter_spice.h | 14 +- eeschema/sim/sim_model_ideal.cpp | 10 + eeschema/sim/sim_model_ideal.h | 2 + eeschema/sim/sim_model_r_pot.cpp | 9 + eeschema/sim/sim_model_r_pot.h | 1 + eeschema/sim/sim_plot_frame.cpp | 68 ++---- eeschema/sim/sim_plot_frame.h | 2 +- eeschema/sim/spice_generator.cpp | 5 +- eeschema/sim/spice_generator.h | 2 +- eeschema/widgets/tuner_slider.cpp | 219 +++--------------- eeschema/widgets/tuner_slider.h | 17 +- 12 files changed, 118 insertions(+), 254 deletions(-) diff --git a/eeschema/netlist_exporters/netlist_exporter_spice.cpp b/eeschema/netlist_exporters/netlist_exporter_spice.cpp index faf4cfa26e..09a3f3f70a 100644 --- a/eeschema/netlist_exporters/netlist_exporter_spice.cpp +++ b/eeschema/netlist_exporters/netlist_exporter_spice.cpp @@ -252,21 +252,30 @@ void NETLIST_EXPORTER_SPICE::ReplaceForbiddenChars( std::string& aNetName ) std::string NETLIST_EXPORTER_SPICE::GetItemName( const std::string& aRefName ) const +{ + const SPICE_ITEM* item = FindItem( aRefName ); + + if( !item ) + return ""; + + return item->model->SpiceGenerator().ItemName( *item ); +} + + +const SPICE_ITEM* NETLIST_EXPORTER_SPICE::FindItem( const std::string& aRefName ) const { const std::list& spiceItems = GetItems(); auto it = std::find_if( spiceItems.begin(), spiceItems.end(), - [aRefName]( const SPICE_ITEM& aItem ) + [aRefName]( const SPICE_ITEM& item ) { - return aItem.refName == aRefName; + return item.refName == aRefName; } ); - if( it == spiceItems.end() ) - return ""; + if( it != spiceItems.end() ) + return &*it; - SPICE_ITEM item; - item.refName = aRefName; - return it->model->SpiceGenerator().ItemName( item ); + return nullptr; } diff --git a/eeschema/netlist_exporters/netlist_exporter_spice.h b/eeschema/netlist_exporters/netlist_exporter_spice.h index 2a1804e51f..f1c009a47c 100644 --- a/eeschema/netlist_exporters/netlist_exporter_spice.h +++ b/eeschema/netlist_exporters/netlist_exporter_spice.h @@ -98,24 +98,28 @@ public: * Return the list of nets. */ std::set GetNets() const { return m_nets; } - /** * Return name of Spice device corresponding to a schematic symbol. * * @param aRefName is the component reference. * @return Spice device name or empty string if there is no such symbol in the netlist. - * Normally the name is either plain reference if the first character of reference corresponds - * to the assigned device model type or it is the reference prefixed with a character defining - * the device model type. + * Normally the name is either a plain reference if the first character of reference + * corresponds to the assigned device model type or a reference prefixed with a character + * defining the device model type. */ std::string GetItemName( const std::string& aRefName ) const; /** - * Return the list of items representing schematic components in the Spice world. + * Return the list of items representing schematic symbols in the Spice world. */ const std::list& GetItems() const { return m_items; } + /** + * Find and return the item corresponding to \a aRefName. + */ + const SPICE_ITEM* FindItem( const std::string& aRefName ) const; + const std::vector& GetDirectives() { return m_directives; } protected: diff --git a/eeschema/sim/sim_model_ideal.cpp b/eeschema/sim/sim_model_ideal.cpp index 516dfc00c4..7d489b957f 100644 --- a/eeschema/sim/sim_model_ideal.cpp +++ b/eeschema/sim/sim_model_ideal.cpp @@ -25,6 +25,7 @@ #include #include #include +#include std::string SPICE_GENERATOR_IDEAL::ModelLine( const SPICE_ITEM& aItem ) const @@ -45,6 +46,15 @@ std::string SPICE_GENERATOR_IDEAL::ItemLine( const SPICE_ITEM& aItem ) const } +std::string SPICE_GENERATOR_IDEAL::TunerCommand( const SPICE_ITEM& aItem, + const SIM_VALUE_FLOAT& aValue ) const +{ + return fmt::format( "alter @{}={}", + aItem.model->SpiceGenerator().ItemName( aItem ), + aValue.ToSpiceString() ); +} + + SIM_MODEL_IDEAL::SIM_MODEL_IDEAL( TYPE aType ) : SIM_MODEL( aType, std::make_unique( *this ) ) { diff --git a/eeschema/sim/sim_model_ideal.h b/eeschema/sim/sim_model_ideal.h index a344d0629a..84758476ac 100644 --- a/eeschema/sim/sim_model_ideal.h +++ b/eeschema/sim/sim_model_ideal.h @@ -36,6 +36,8 @@ public: std::string ModelLine( const SPICE_ITEM& aItem ) const override; std::string ItemLine( const SPICE_ITEM& aItem ) const override; + + std::string TunerCommand( const SPICE_ITEM& aItem, const SIM_VALUE_FLOAT& aValue ) const override; }; diff --git a/eeschema/sim/sim_model_r_pot.cpp b/eeschema/sim/sim_model_r_pot.cpp index c662ad66ec..f0afbc0746 100644 --- a/eeschema/sim/sim_model_r_pot.cpp +++ b/eeschema/sim/sim_model_r_pot.cpp @@ -38,6 +38,15 @@ std::string SPICE_GENERATOR_R_POT::ModelLine( const SPICE_ITEM& aItem ) const } +std::string SPICE_GENERATOR_R_POT::TunerCommand( const SPICE_ITEM& aItem, + const SIM_VALUE_FLOAT& aValue ) const +{ + return fmt::format( "altermod @{}[position]={}", + aItem.model->SpiceGenerator().ItemName( aItem ), + aValue.ToSpiceString() ); +} + + SIM_MODEL_R_POT::SIM_MODEL_R_POT() : SIM_MODEL( TYPE::R_POT, std::make_unique( *this ) ) { diff --git a/eeschema/sim/sim_model_r_pot.h b/eeschema/sim/sim_model_r_pot.h index 2a3a27dee8..4a71cde309 100644 --- a/eeschema/sim/sim_model_r_pot.h +++ b/eeschema/sim/sim_model_r_pot.h @@ -35,6 +35,7 @@ public: using SPICE_GENERATOR::SPICE_GENERATOR; std::string ModelLine( const SPICE_ITEM& aItem ) const override; + std::string TunerCommand( const SPICE_ITEM& aItem, const SIM_VALUE_FLOAT& aValue ) const override; }; diff --git a/eeschema/sim/sim_plot_frame.cpp b/eeschema/sim/sim_plot_frame.cpp index 60247b9e1f..346d440776 100644 --- a/eeschema/sim/sim_plot_frame.cpp +++ b/eeschema/sim/sim_plot_frame.cpp @@ -549,33 +549,24 @@ void SIM_PLOT_FRAME::AddTuner( SCH_SYMBOL* aSymbol ) if( !plotPanel ) return; - SIM_MODEL::TYPE type = SIM_MODEL::ReadTypeFromFields( aSymbol->GetFields(), - static_cast( aSymbol->GetLibPins().size() ) ); - SIM_MODEL::DEVICE_TYPE_ deviceType = SIM_MODEL::TypeInfo( type ).deviceType; + wxString ref = aSymbol->GetField( REFERENCE_FIELD )->GetShownText(); - switch( deviceType ) - { - case SIM_MODEL::DEVICE_TYPE_::R: - case SIM_MODEL::DEVICE_TYPE_::C: - case SIM_MODEL::DEVICE_TYPE_::L: - case SIM_MODEL::DEVICE_TYPE_::XSPICE: - break; - - default: - return; - } - - const wxString componentName = aSymbol->GetField( REFERENCE_FIELD )->GetText(); - - // Do not add multiple instances for the same component - auto tunerIt = std::find_if( m_tuners.begin(), m_tuners.end(), [&]( const TUNER_SLIDER* t ) - { - return t->GetComponentName() == componentName; - } - ); + // Do not add multiple instances for the same component. + auto tunerIt = std::find_if( m_tuners.begin(), m_tuners.end(), + [ref]( const TUNER_SLIDER* tuner ) + { + return tuner->GetComponentName() == ref; + } ); if( tunerIt != m_tuners.end() ) - return; // We already have it + return; // We already have it. + + const SPICE_ITEM* item = GetExporter()->FindItem( std::string( ref.ToUTF8() ) ); + const SIM_MODEL::PARAM* tunerParam = item->model->GetTunerParam(); + + // Do nothing if the symbol is not tunable. + if( !tunerParam ) + return; try { @@ -586,30 +577,31 @@ void SIM_PLOT_FRAME::AddTuner( SCH_SYMBOL* aSymbol ) } catch( const KI_PARAM_ERROR& e ) { - // Sorry, no bonus DisplayErrorMessage( nullptr, e.What() ); } } -void SIM_PLOT_FRAME::UpdateTunerValue( SCH_SYMBOL* aSymbol, int aId, const wxString& aValue ) +void SIM_PLOT_FRAME::UpdateTunerValue( SCH_SYMBOL* aSymbol, const wxString& aValue ) { for( auto& item : m_schematicFrame->GetScreen()->Items().OfType( SCH_SYMBOL_T ) ) { if( item == aSymbol ) { - SCH_FIELD* field = aSymbol->GetFieldById( aId ); + SIM_LIB_MGR mgr( Prj() ); + SIM_MODEL& model = mgr.CreateModel( *aSymbol ).model; - if( !field ) - break; + const SIM_MODEL::PARAM* tunerParam = model.GetTunerParam(); - field->SetText( aValue ); + if( !tunerParam ) + return; + + model.SetParamValue( tunerParam->info.name, std::string( aValue.ToUTF8() ) ); + model.WriteFields( aSymbol->GetFields() ); m_schematicFrame->UpdateItem( aSymbol, false, true ); m_schematicFrame->OnModify(); - break; } } - } @@ -923,17 +915,7 @@ void SIM_PLOT_FRAME::updateTuners() void SIM_PLOT_FRAME::applyTuners() { for( auto& tuner : m_tuners ) - { - std::pair command = tuner->GetSpiceTuningCommand(); - const SPICE_VALUE& value = tuner->GetValue(); - - // 0 < value < 1 for model parameter to avoid division by zero, etc. - command.first += command.second - ? wxString::FromCDouble( Clamp( 1e-9, value.ToDouble() / 100.0, 1-1e-9 ), 9 ) - : value.ToSpiceString(); - - m_simulator->Command( command.first.ToStdString() ); - } + m_simulator->Command( tuner->GetTunerCommand() ); } diff --git a/eeschema/sim/sim_plot_frame.h b/eeschema/sim/sim_plot_frame.h index 7c2675933a..60088c785c 100644 --- a/eeschema/sim/sim_plot_frame.h +++ b/eeschema/sim/sim_plot_frame.h @@ -116,7 +116,7 @@ public: * @param aId id of the symbol field * @param aValue new value of the symbol field */ - void UpdateTunerValue( SCH_SYMBOL* aSymbol, int aId, const wxString& aValue ); + void UpdateTunerValue( SCH_SYMBOL* aSymbol, const wxString& aValue ); /** * Return the currently opened plot panel (or NULL if there is none). diff --git a/eeschema/sim/spice_generator.cpp b/eeschema/sim/spice_generator.cpp index bc567f5328..db9056950a 100644 --- a/eeschema/sim/spice_generator.cpp +++ b/eeschema/sim/spice_generator.cpp @@ -156,9 +156,10 @@ std::string SPICE_GENERATOR::ItemParams() const } -std::string SPICE_GENERATOR::TuningLine( const SPICE_ITEM& aItem ) const +std::string SPICE_GENERATOR::TunerCommand( const SPICE_ITEM& aItem, + const SIM_VALUE_FLOAT& aValue ) const { - // TODO. + // No tuning available by default. return ""; } diff --git a/eeschema/sim/spice_generator.h b/eeschema/sim/spice_generator.h index 956dba6de8..fe9e38b661 100644 --- a/eeschema/sim/spice_generator.h +++ b/eeschema/sim/spice_generator.h @@ -58,7 +58,7 @@ public: virtual std::string ItemModelName( const SPICE_ITEM& aItem ) const; virtual std::string ItemParams() const; - virtual std::string TuningLine( const SPICE_ITEM& aItem ) const; + virtual std::string TunerCommand( const SPICE_ITEM& aItem, const SIM_VALUE_FLOAT& aValue ) const; virtual std::vector CurrentNames( const SPICE_ITEM& aItem ) const; diff --git a/eeschema/widgets/tuner_slider.cpp b/eeschema/widgets/tuner_slider.cpp index 9bf5c88e5b..b989882a8e 100644 --- a/eeschema/widgets/tuner_slider.cpp +++ b/eeschema/widgets/tuner_slider.cpp @@ -35,190 +35,49 @@ #include // norm -// characteristic curves (default: 0B Lin) -static const struct { double m_law, m_mid; } CURVES[] = -{ - // same order as choices in m_curve - { 1.00, 0.10 }, // 10A Log - { 1.00, 0.15 }, // 15A Log - { 0.90, 0.13 }, // 15A Log S - { 0.00, 0.10 }, // 10C Rev Log - { 0.00, 0.15 }, // 15C Rev Log - { 0.10, 0.13 }, // 15C Rev Log S - { 0.50, 0.50 }, // 0B Lin - { 0.50, 0.15 }, // 4B S-Curve - { 0.50, 0.10 }, // 5B S-Curve - { 0.50, 0.00 } // Switch -}; - -template -static inline int arraysize( const T ( &v )[S] ) { return S; } - - -/** - * Transform ratio according to linear, logarithmic or reverse logarithmic laws - * (characteristic curve or taper) of rotary or slide potentiometer (pot or fader). - * - * Parameters corresponding to *IEC 60393-1:2008* and *JIS C 5260-1* code letters: - * - * | @p aLaw | @p aMid | Code | Resistance Law (Taper) - * | ------: | ------: | ---: | :--------------------- - * | 1.00 | 0.10 | 10A | CW logarithmic - * | ^ | 0.15 | 15A | CW logarithmic (audio) - * | 0.90 | 0.13 | ^ | CW logarithmic (high-end audio) - * | 0.00 | 0.10 | 10C | CCW/reverse logarithmic - * | ^ | 0.15 | 15C | CCW/reverse logarithmic (reverse audio) - * | 0.10 | 0.13 | ^ | CCW/reverse logarithmic (high-end reverse audio) - * | 0.50 | 0.50 | 0B | (ideal) linear - * | ^ | 0.15 | 4B | symmetric (audio S-curve) - * | ^ | 0.10 | 5B | symmetric (S-curve) - * | ^ | 0.00 | - | switch - * - * Standards code letters cross-reference: - * - * | IEC 60393-1:2008 | IEC 60393-1:1989 | MIL-R-94 | Resistance Law - * | ----------------: | :--------------: | :------: | :------------- - * | 0B | A | A | linear - * | 10A | B | C | logarithmic - * | 15A | ^ | - | ^ - * | 10C | C | F | reverse logarithmic - * | 15C | ^ | - | ^ - * - * **Logarithmic Law** is for *levels* (logarithmic units) and is actually an exponential curve. - * **Reverse** refers to a reverse-mounted resistive element or shaft on a potentiometer - * (resulting in a reflected curve). An **S-curve** is a curve joined to its (scaled) reflection, - * and *may* be symmetric or linear. **Inverse** refers to the mathematical inverse of a function. - * - * @tparam F is a floating point type. - * @param aRatio is the input (travel) ratio or moving contact (wiper) position, from - * 0%/CCW/left (0) through 50%/mid-travel/center (1/2) to 100%/CW/right (1). - * @param aMid is the logarithmic laws' output ratio at 50%/mid-travel/center (1/2) - * input ratio. - * @param aLaw is the (resistance) law, interpolating from *reverse logarithmic* (0) - * through *symmetric/linear* (1/2) to *logarithmic* (1). - * @param aInverse swaps input and output ratios (inverse function, where possible), - * if @c true. - * @return the output (resistance or voltage) ratio in [0, 1]. - */ -template -static F taper( F aRatio, F aMid = 0.5, F aLaw = 1.0, bool aInverse = false ) -{ - static_assert( std::is_floating_point::value, "F must be floating point" ); - - // clamp to [0, 1] and short-cut - if( aRatio <= 0 ) - return 0; - if( aRatio >= 1 ) - return 1; - - // short-cut for ideal linear or at S-curve inflection point - if( aMid == 0.5 || aRatio == aLaw ) - return aRatio; - - F t = aRatio; - - // clamp to [0, 1] and short-cut at (non-invertible) limits - if( aMid <= 0 ) - { - t = aInverse ? 1 : 0; - } - else if( aMid >= 1 ) - { - t = aInverse ? 0 : 1; - } - else - { - // clamp, and reflect and/or scale for reverse...symmetric...normal laws - if( aLaw >= 1 ) - { - // Do nothing, leave t = t - } - else if( aLaw <= 0 ) - { - t = 1 - t; - } - else if( aRatio <= aLaw ) - { - t = t / aLaw; - } - else - { - t = ( 1 - t ) / ( 1 - aLaw ); - } - - // scaling factors for domain and range in [0, 1] - F a = std::norm( 1 - 1 / aMid ); - F b = std::log( a ); - F c = a - 1; - - // scaling: a = (1 - 1/m)^2 - // log law: (a^t - 1) / (a - 1) - // inverse: log_a(1 + t (a - 1)) - t = aInverse ? ( std::log1p( t * c ) / b ) : ( std::expm1( t * b ) / c ); - } - - // clamp, and scale and/or reflect for reverse...symmetric...normal laws - if( aLaw >= 1 ) - { - // Do nothing, leave t = t - } - else if( aLaw <= 0 ) - { - t = 1 - t; - } - else if( aRatio <= aLaw ) - { - t = t * aLaw; - } - else - { - t = 1 - t * ( 1-aLaw ); - } - - return t; -} - - TUNER_SLIDER::TUNER_SLIDER( SIM_PLOT_FRAME* aFrame, wxWindow* aParent, SCH_SYMBOL* aSymbol ) : TUNER_SLIDER_BASE( aParent ), m_symbol( aSymbol ), - m_fieldId( MANDATORY_FIELD_T::VALUE_FIELD ), m_min( 0.0 ), m_max( 0.0 ), m_value( 0.0 ), m_changed( false ), m_frame ( aFrame ) { - // TODO. + wxString ref = aSymbol->GetField( REFERENCE_FIELD )->GetShownText(); + m_item = aFrame->GetExporter()->FindItem( std::string( ref.ToUTF8() ) ); - /*const wxString compName = aSymbol->GetField( REFERENCE_FIELD )->GetText(); - m_name->SetLabel( compName ); - m_spiceTuningCommand = aFrame->GetExporter()->GetSpiceTuningCommand( compName ); + if( !m_item ) + throw KI_PARAM_ERROR( wxString::Format( _( "Could not find Spice item with reference '%s'" ), + ref ) ); - if( m_spiceTuningCommand.second ) + m_name->SetLabel( ref ); + + + const SIM_MODEL::PARAM* tunerParam = m_item->model->GetTunerParam(); + + if( !tunerParam ) + throw KI_PARAM_ERROR( wxString::Format( + _( "Symbol '%s' has simulation model of type '%s %s', which cannot be tuned" ), + ref, + m_item->model->GetDeviceTypeInfo().fieldValue, + m_item->model->GetTypeInfo().fieldValue ) ); + + + m_value = SPICE_VALUE( m_item->model->GetTunerParam()->value->ToSpiceString() ); + + // Special case for potentiometers because we don't have value ranges implemented yet. + if( m_item->model->GetType() == SIM_MODEL::TYPE::R_POT ) { - // model parameter, with fixed %-range and unknown initial value - m_min = 0; - m_max = 100; - m_value = ( m_max - m_min ) / 2.0; // midpoint - m_minText->Disable(); - m_maxText->Disable(); - m_saveBtn->Disable(); // not an instance parameter that could be updated (invalid m_fieldId) + m_min = SPICE_VALUE( 0 ); + m_max = SPICE_VALUE( 1 ); } else { - // instance parameter - if( aSymbol->FindField( netlist_exporter_spice::GetSpiceFieldName( SF_MODEL ) ) ) - m_fieldId = aSymbol->FindField( netlist_exporter_spice::GetSpiceFieldName( SF_MODEL ) )->GetId(); - else - m_fieldId = aSymbol->GetField( VALUE_FIELD )->GetId(); - - m_value = SPICE_VALUE( aSymbol->GetFieldById( m_fieldId )->GetText() ); - m_min = SPICE_VALUE( 0.5 ) * m_value; - m_max = SPICE_VALUE( 2.0 ) * m_value; + m_min = SPICE_VALUE( 0.5 ) * m_value; + m_max = SPICE_VALUE( 2.0 ) * m_value; } - // Call Set*() methods to update fields and slider m_minText->SetValue( m_min.ToOrigString() ); m_maxText->SetValue( m_max.ToOrigString() ); @@ -226,7 +85,14 @@ TUNER_SLIDER::TUNER_SLIDER( SIM_PLOT_FRAME* aFrame, wxWindow* aParent, SCH_SYMBO updateSlider(); m_simTimer.SetOwner( this ); - Connect( wxEVT_TIMER, wxTimerEventHandler( TUNER_SLIDER::onSimTimer ), nullptr, this );*/ + Connect( wxEVT_TIMER, wxTimerEventHandler( TUNER_SLIDER::onSimTimer ), nullptr, this ); +} + + +std::string TUNER_SLIDER::GetTunerCommand() const +{ + return m_item->model->SpiceGenerator().TunerCommand( *m_item, + SIM_VALUE_FLOAT( m_value.ToDouble() ) ); } @@ -292,13 +158,7 @@ void TUNER_SLIDER::updateComponentValue() void TUNER_SLIDER::updateSlider() { wxASSERT( m_max >= m_value && m_value >= m_min ); - - int choice = m_curve->GetSelection(); - wxCHECK( choice >= 0 && choice < arraysize( CURVES ), /*void*/ ); - - double ratio = ( ( m_value - m_min ) / ( m_max - m_min ) ).ToDouble(); - double travel = taper( ratio, CURVES[choice].m_mid, CURVES[choice].m_law, true ); - m_slider->SetValue( KiROUND( travel * 100.0 ) ); + m_slider->SetValue( ( ( m_value - m_min ) / ( m_max - m_min ) ).ToDouble() * 100 ); } @@ -363,18 +223,13 @@ void TUNER_SLIDER::onClose( wxCommandEvent& event ) void TUNER_SLIDER::onSave( wxCommandEvent& event ) { - m_frame->UpdateTunerValue( m_symbol, m_fieldId, m_value.ToOrigString() ); + m_frame->UpdateTunerValue( m_symbol, m_value.ToOrigString() ); } void TUNER_SLIDER::onSliderChanged( wxScrollEvent& event ) { - int choice = m_curve->GetSelection(); - wxCHECK( choice >= 0 && choice < arraysize( CURVES ), /*void*/ ); - - double travel = m_slider->GetValue() / 100.0; - double ratio = taper( travel, CURVES[choice].m_mid, CURVES[choice].m_law, false ); - m_value = m_min + ( m_max - m_min ) * SPICE_VALUE( ratio ); + m_value = m_min + ( m_max - m_min ) * SPICE_VALUE( m_slider->GetValue() / 100.0 ); updateValueText(); updateComponentValue(); m_changed = true; diff --git a/eeschema/widgets/tuner_slider.h b/eeschema/widgets/tuner_slider.h index 6d3f5e1501..a6564da728 100644 --- a/eeschema/widgets/tuner_slider.h +++ b/eeschema/widgets/tuner_slider.h @@ -30,6 +30,7 @@ #include "tuner_slider_base.h" #include +#include #include @@ -49,16 +50,6 @@ public: return m_name->GetLabel(); } - const std::pair& GetSpiceTuningCommand() const - { - return m_spiceTuningCommand; - } - - const SPICE_VALUE& GetValue() const - { - return m_value; - } - const SPICE_VALUE& GetMin() const { return m_min; @@ -69,6 +60,8 @@ public: return m_max; } + std::string GetTunerCommand() const; + bool SetValue( const SPICE_VALUE& aVal ); bool SetMin( const SPICE_VALUE& aVal ); bool SetMax( const SPICE_VALUE& aVal ); @@ -97,14 +90,12 @@ private: void onSimTimer( wxTimerEvent& event ); - std::pair m_spiceTuningCommand; - ///< Timer that restarts the simulation after the slider value has changed wxTimer m_simTimer; SCH_SYMBOL* m_symbol; + const SPICE_ITEM* m_item; - int m_fieldId; SPICE_VALUE m_min, m_max, m_value; bool m_changed;