From 6e33a9d441b6e066be6802866c88244fe456df07 Mon Sep 17 00:00:00 2001 From: Kai Chappell Date: Sun, 13 Apr 2025 18:47:37 +0000 Subject: [PATCH] Put sidebar controls in fragment to prevent page blanking Both controls and display are now fragments, so slider changes don't trigger full page reruns. --- src/py_dvt_ate/app/dashboard/app.py | 54 +++++++++++++++-------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/py_dvt_ate/app/dashboard/app.py b/src/py_dvt_ate/app/dashboard/app.py index 493519f..1616950 100644 --- a/src/py_dvt_ate/app/dashboard/app.py +++ b/src/py_dvt_ate/app/dashboard/app.py @@ -16,9 +16,6 @@ from py_dvt_ate.simulation.physics.engine import PhysicsEngine # History buffer size for charts HISTORY_SIZE = 500 -# Simulation speed settings -DEFAULT_TIME_MULTIPLIER = 10.0 # 10x faster than real-time - @dataclass class SimulationHistory: @@ -48,17 +45,17 @@ def init_session_state() -> None: st.session_state.history = SimulationHistory() if "running" not in st.session_state: st.session_state.running = False - if "time_multiplier" not in st.session_state: - st.session_state.time_multiplier = DEFAULT_TIME_MULTIPLIER if "last_update" not in st.session_state: st.session_state.last_update = time.time() + # Note: time_multiplier, temp_setpoint, input_voltage, output_enabled, + # load_current are managed by their respective widgets via keys def step_simulation() -> None: """Advance the simulation based on elapsed real time and multiplier.""" engine: PhysicsEngine = st.session_state.engine history: SimulationHistory = st.session_state.history - multiplier: float = st.session_state.time_multiplier + multiplier: float = st.session_state.get("time_multiplier", 10) # Calculate how much simulation time to advance current_time = time.time() @@ -87,10 +84,18 @@ def step_simulation() -> None: history.power_dissipation.append(electrical.power_dissipation) -def display_controls() -> None: - """Display simulation control panel in sidebar.""" +def sync_engine_from_session_state() -> None: + """Sync engine parameters from session state (called by fragment).""" engine: PhysicsEngine = st.session_state.engine + engine.set_chamber_setpoint(st.session_state.get("temp_setpoint", 25.0)) + engine.set_input_voltage(st.session_state.get("input_voltage", 5.0)) + engine.set_output_enabled(st.session_state.get("output_enabled", False)) + engine.set_load_current(st.session_state.get("load_current", 100.0) / 1000.0) + +@st.fragment +def display_controls() -> None: + """Display simulation control panel in sidebar (as fragment to avoid full reruns).""" st.sidebar.header("Simulation Controls") # Start/Stop button @@ -99,14 +104,12 @@ def display_controls() -> None: "Stop Simulation", type="primary", use_container_width=True ): st.session_state.running = False - st.rerun() else: if st.sidebar.button( "Start Simulation", type="primary", use_container_width=True ): st.session_state.running = True st.session_state.last_update = time.time() - st.rerun() # Reset button if st.sidebar.button("Reset", use_container_width=True): @@ -114,27 +117,27 @@ def display_controls() -> None: st.session_state.history = SimulationHistory() st.session_state.running = False st.session_state.last_update = time.time() - st.rerun() st.sidebar.divider() # Time multiplier st.sidebar.subheader("Simulation Speed") - time_mult = st.sidebar.select_slider( + st.sidebar.select_slider( "Time Multiplier", options=[1, 2, 5, 10, 20, 50, 100], - value=int(st.session_state.time_multiplier), + value=10, format_func=lambda x: f"{x}x", - key="time_mult_slider", + key="time_multiplier", + ) + st.sidebar.caption( + f"1 real second = {st.session_state.get('time_multiplier', 10)} simulation seconds" ) - st.session_state.time_multiplier = float(time_mult) - st.sidebar.caption(f"1 real second = {time_mult} simulation seconds") st.sidebar.divider() # Temperature setpoint st.sidebar.subheader("Thermal Chamber") - temp_setpoint = st.sidebar.slider( + st.sidebar.slider( "Temperature Setpoint (C)", min_value=-40.0, max_value=125.0, @@ -142,13 +145,12 @@ def display_controls() -> None: step=5.0, key="temp_setpoint", ) - engine.set_chamber_setpoint(temp_setpoint) st.sidebar.divider() # Power supply controls st.sidebar.subheader("Power Supply") - input_voltage = st.sidebar.slider( + st.sidebar.slider( "Input Voltage (V)", min_value=0.0, max_value=12.0, @@ -156,20 +158,18 @@ def display_controls() -> None: step=0.1, key="input_voltage", ) - engine.set_input_voltage(input_voltage) - output_enabled = st.sidebar.toggle( + st.sidebar.toggle( "Output Enabled", - value=engine.is_output_enabled, + value=False, key="output_enabled", ) - engine.set_output_enabled(output_enabled) st.sidebar.divider() # Load controls st.sidebar.subheader("Electronic Load") - load_current_ma = st.sidebar.slider( + st.sidebar.slider( "Load Current (mA)", min_value=0.0, max_value=500.0, @@ -177,7 +177,6 @@ def display_controls() -> None: step=10.0, key="load_current", ) - engine.set_load_current(load_current_ma / 1000.0) @st.fragment(run_every=0.1) @@ -186,6 +185,9 @@ def simulation_display() -> None: engine: PhysicsEngine = st.session_state.engine history: SimulationHistory = st.session_state.history + # Sync engine parameters from UI controls + sync_engine_from_session_state() + # Step simulation if running if st.session_state.running: step_simulation() @@ -220,7 +222,7 @@ def simulation_display() -> None: st.metric( "Sim Time", f"{engine.simulation_time:.1f} s", - delta=f"{status} @ {st.session_state.time_multiplier:.0f}x", + delta=f"{status} @ {st.session_state.get('time_multiplier', 10):.0f}x", ) # Temperature chart