11"""
22Module: T_Control_L350_Step_GUI.py
3- Purpose: GUI module for T Control L350 Step Measurement GUI (Threaded).
3+ Purpose: GUI module for T Control L350 Step Measurement GUI (Threaded & Multi-plot ).
44"""
55
66import tkinter as tk
@@ -136,7 +136,7 @@ def shutdown(self):
136136# -------------------------------------------------------------------------------
137137
138138class TempControlGUI :
139- PROGRAM_VERSION = "9.1 -Step"
139+ PROGRAM_VERSION = "9.2 -Step"
140140 CLR_BG_DARK = '#B8A392'
141141 CLR_HEADER = '#E5DCD3'
142142 CLR_FG_LIGHT = '#2C2825'
@@ -167,7 +167,9 @@ def __init__(self, root):
167167
168168 self .logo_image = None
169169 self .backend = Lakeshore_Backend ()
170- self .data_storage = {'time' : [], 'temperature' : [], 'heater' : []}
170+
171+ # Added 'target' to data storage for plotting expectations
172+ self .data_storage = {'time' : [], 'temperature' : [], 'target' : [], 'heater' : []}
171173
172174 self .setup_styles ()
173175 self .create_widgets ()
@@ -209,7 +211,7 @@ def create_widgets(self):
209211 main_pane = ttk .PanedWindow (self .root , orient = 'horizontal' )
210212 main_pane .pack (fill = 'both' , expand = True , padx = 10 , pady = 10 )
211213
212- left_panel = ttk .Frame (main_pane , width = 420 )
214+ left_panel = ttk .Frame (main_pane , width = 440 )
213215 main_pane .add (left_panel , weight = 0 )
214216 right_panel = ttk .Frame (main_pane )
215217 main_pane .add (right_panel , weight = 1 )
@@ -245,20 +247,28 @@ def _create_info_panel(self, parent, grid_row):
245247 except Exception :
246248 pass
247249
250+ # Institute Name forced into exactly 2 lines
248251 institute_font = ('Segoe UI' , self .FONT_BASE [1 ] + 1 , 'bold' )
249- ttk .Label (frame , text = "UGC-DAE Consortium for\n Scientific Research" , font = institute_font , justify = 'left' ). grid ( row = 0 , column = 1 , padx = 5 , pady = ( 15 , 0 ), sticky = 'sw' )
250- ttk . Label ( frame , text = "Mumbai Centre" , font = institute_font ).grid (row = 1 , column = 1 , padx = 5 , sticky = 'nw ' )
252+ ttk .Label (frame , text = "UGC-DAE Consortium for Scientific Research\n Mumbai Centre " ,
253+ font = institute_font , justify = 'left' ).grid (row = 0 , column = 1 , rowspan = 2 , padx = 5 , pady = 10 , sticky = 'w ' )
251254
252255 def _create_sequence_panel (self , parent , grid_row ):
253256 frame = ttk .LabelFrame (parent , text = 'Measurement Sequence Builder' )
254257 frame .grid (row = grid_row , column = 0 , sticky = 'new' , pady = 5 , padx = 5 )
255- frame .grid_columnconfigure (1 , weight = 1 )
256- frame .grid_columnconfigure (2 , weight = 1 )
257- frame .grid_columnconfigure (3 , weight = 1 )
258+ for i in range (4 ): frame .grid_columnconfigure (i , weight = 1 )
258259
259- # Listbox for Temperatures
260- self .listbox = tk .Listbox (frame , height = 6 , font = self .FONT_BASE , bg = self .CLR_INPUT_BG , fg = self .CLR_TEXT_DARK )
261- self .listbox .grid (row = 0 , column = 0 , columnspan = 4 , sticky = 'ew' , padx = 10 , pady = 5 )
260+ # Listbox with Scrollbar
261+ list_frame = ttk .Frame (frame )
262+ list_frame .grid (row = 0 , column = 0 , columnspan = 4 , sticky = 'nsew' , padx = 10 , pady = 5 )
263+
264+ scrollbar = ttk .Scrollbar (list_frame , orient = "vertical" )
265+ self .listbox = tk .Listbox (list_frame , height = 6 , selectmode = tk .EXTENDED ,
266+ font = self .FONT_BASE , bg = self .CLR_INPUT_BG , fg = self .CLR_TEXT_DARK ,
267+ yscrollcommand = scrollbar .set )
268+ scrollbar .config (command = self .listbox .yview )
269+
270+ self .listbox .pack (side = "left" , fill = "both" , expand = True )
271+ scrollbar .pack (side = "right" , fill = "y" )
262272
263273 # Auto Generator
264274 ttk .Label (frame , text = "Start(K):" ).grid (row = 1 , column = 0 , sticky = 'e' , padx = 2 )
@@ -275,24 +285,31 @@ def _create_sequence_panel(self, parent, grid_row):
275285
276286 ttk .Button (frame , text = "Generate Steps" , command = self ._generate_steps ).grid (row = 2 , column = 2 , columnspan = 2 , sticky = 'ew' , padx = 5 , pady = 2 )
277287
278- # Sort Order & Clear All
279288 ttk .Separator (frame , orient = 'horizontal' ).grid (row = 3 , column = 0 , columnspan = 4 , sticky = 'ew' , pady = 5 , padx = 10 )
280289
290+ # Sort Order & List Size
281291 ttk .Label (frame , text = "Order:" ).grid (row = 4 , column = 0 , sticky = 'e' , padx = 2 )
282292 self .sort_var = tk .StringVar (value = 'Ascending' )
283- sort_cb = ttk .Combobox (frame , textvariable = self .sort_var , values = ['Ascending' , 'Descending' ], state = 'readonly' , width = 12 )
284- sort_cb .grid (row = 4 , column = 1 , columnspan = 2 , sticky = 'w' , padx = 2 )
293+ sort_cb = ttk .Combobox (frame , textvariable = self .sort_var , values = ['Ascending' , 'Descending' ], state = 'readonly' , width = 10 )
294+ sort_cb .grid (row = 4 , column = 1 , sticky = 'w' , padx = 2 )
285295 sort_cb .bind ('<<ComboboxSelected>>' , lambda e : self ._sort_listbox ())
286296
287- ttk .Button (frame , text = "Clear All" , command = self ._clear_listbox ).grid (row = 4 , column = 3 , sticky = 'ew' , padx = 2 )
297+ ttk .Label (frame , text = "Rows:" ).grid (row = 4 , column = 2 , sticky = 'e' , padx = 2 )
298+ self .list_size_var = tk .IntVar (value = 6 )
299+ size_spin = ttk .Spinbox (frame , from_ = 3 , to = 25 , textvariable = self .list_size_var , width = 5 , command = self ._update_list_size )
300+ size_spin .grid (row = 4 , column = 3 , sticky = 'w' , padx = 2 )
301+ size_spin .bind ('<Return>' , self ._update_list_size )
302+ size_spin .bind ('<FocusOut>' , self ._update_list_size )
288303
289- # Manual Addition
304+ # Manual Addition & Clear
290305 ttk .Label (frame , text = "Manual(K):" ).grid (row = 5 , column = 0 , sticky = 'e' , padx = 2 , pady = 5 )
291306 self .entry_manual = ttk .Entry (frame , width = 6 )
292307 self .entry_manual .grid (row = 5 , column = 1 , sticky = 'w' , padx = 2 , pady = 5 )
293308
294309 ttk .Button (frame , text = "Add" , command = self ._add_manual_step ).grid (row = 5 , column = 2 , sticky = 'ew' , padx = 2 , pady = 5 )
295310 ttk .Button (frame , text = "Remove" , command = self ._remove_step ).grid (row = 5 , column = 3 , sticky = 'ew' , padx = 2 , pady = 5 )
311+
312+ ttk .Button (frame , text = "Clear All" , command = self ._clear_listbox ).grid (row = 6 , column = 0 , columnspan = 4 , sticky = 'ew' , padx = 10 , pady = (0 , 5 ))
296313
297314 def _create_settings_panel (self , parent , grid_row ):
298315 frame = ttk .LabelFrame (parent , text = 'Instrument & Stability Settings' )
@@ -302,15 +319,11 @@ def _create_settings_panel(self, parent, grid_row):
302319
303320 self .entries = {}
304321
305- # Stability Settings
306322 self ._create_grid_entry (frame , "Tolerance (±K)" , "0.5" , 0 , 0 )
307323 self ._create_grid_entry (frame , "Soak Time (s)" , "60" , 0 , 2 )
308-
309- # Ramp Settings
310324 self ._create_grid_entry (frame , "Ramp Rate (K/min)" , "2" , 1 , 0 )
311325 self ._create_grid_entry (frame , "Poll Delay (s)" , "1" , 1 , 2 )
312326
313- # Dropdowns
314327 ttk .Label (frame , text = "Heater Range:" ).grid (row = 2 , column = 0 , sticky = 'w' , padx = 10 , pady = 5 )
315328 self .heater_range_var = tk .StringVar (value = 'High' )
316329 heater_cb = ttk .Combobox (frame , textvariable = self .heater_range_var , values = ['Off' , 'Low' , 'Medium' , 'High' ], state = 'readonly' , width = 10 )
@@ -320,7 +333,6 @@ def _create_settings_panel(self, parent, grid_row):
320333 self .ls_cb = ttk .Combobox (frame , state = 'readonly' , width = 15 )
321334 self .ls_cb .grid (row = 2 , column = 3 , sticky = 'ew' , padx = 5 )
322335
323- # Run Controls
324336 button_frame = ttk .Frame (frame )
325337 button_frame .grid (row = 3 , column = 0 , columnspan = 4 , sticky = 'ew' , pady = 10 , padx = 10 )
326338 button_frame .grid_columnconfigure ((0 , 1 , 2 ), weight = 1 )
@@ -368,12 +380,15 @@ def _populate_right_panel(self, panel):
368380 self .ax_temp = self .figure .add_subplot (211 )
369381 self .ax_heater = self .figure .add_subplot (212 , sharex = self .ax_temp )
370382
371- self .line_temp = self .ax_temp .plot ([], [], color = self .CLR_ACCENT_RED , marker = 'o' , markersize = 3 , linestyle = '-' )
383+ # Dual Plots for Temperature
384+ self .line_target = self .ax_temp .plot ([], [], color = self .CLR_ACCENT_GREEN , marker = '' , linestyle = '--' , label = 'Target Setpoint' )[0 ]
385+ self .line_temp = self .ax_temp .plot ([], [], color = self .CLR_ACCENT_RED , marker = 'o' , markersize = 3 , linestyle = '-' , label = 'Actual Temp' )[0 ]
372386 self .ax_temp .set_ylabel ("Temperature (K)" )
373387 self .ax_temp .grid (True , linestyle = '--' , alpha = 0.6 )
388+ self .ax_temp .legend (loc = 'best' , frameon = True , facecolor = self .CLR_GRAPH_BG )
374389 self .ax_temp .tick_params (axis = 'x' , which = 'both' , bottom = False , top = False , labelbottom = False )
375390
376- self .line_heater = self .ax_heater .plot ([], [], color = self .CLR_ACCENT_GOLD , marker = '.' , markersize = 3 , linestyle = '-' )
391+ self .line_heater = self .ax_heater .plot ([], [], color = self .CLR_ACCENT_GOLD , marker = '.' , markersize = 3 , linestyle = '-' )[ 0 ]
377392 self .ax_heater .set_xlabel ("Time (s)" )
378393 self .ax_heater .set_ylabel ("Heater Output (%)" )
379394 self .ax_heater .grid (True , linestyle = '--' , alpha = 0.6 )
@@ -390,8 +405,15 @@ def _create_grid_entry(self, parent, label_text, default_value, row, col):
390405 entry .insert (0 , default_value )
391406 self .entries [label_text ] = entry
392407
408+ def _update_list_size (self , event = None ):
409+ try :
410+ val = self .list_size_var .get ()
411+ if 3 <= val <= 25 :
412+ self .listbox .config (height = val )
413+ except Exception :
414+ pass
415+
393416 def _sort_listbox (self ):
394- """Sorts the current listbox contents based on the selected order."""
395417 items = list (self .listbox .get (0 , tk .END ))
396418 if not items : return
397419 try :
@@ -402,7 +424,7 @@ def _sort_listbox(self):
402424 for val in floats :
403425 self .listbox .insert (tk .END , f"{ val :.2f} " )
404426 except Exception :
405- pass # Ignore malformed data quietly
427+ pass
406428
407429 def _generate_steps (self ):
408430 try :
@@ -420,7 +442,7 @@ def _generate_steps(self):
420442 while current >= end :
421443 self .listbox .insert (tk .END , f"{ current :.2f} " )
422444 current -= step
423- self ._sort_listbox () # Auto-sort after generation
445+ self ._sort_listbox ()
424446 except ValueError :
425447 messagebox .showerror ("Input Error" , "Please enter valid numeric values for Start, End, and Step." )
426448
@@ -429,14 +451,15 @@ def _add_manual_step(self):
429451 val = float (self .entry_manual .get ())
430452 self .listbox .insert (tk .END , f"{ val :.2f} " )
431453 self .entry_manual .delete (0 , tk .END )
432- self ._sort_listbox () # Auto-sort upon addition
454+ self ._sort_listbox ()
433455 except ValueError :
434456 messagebox .showerror ("Input Error" , "Enter a valid numeric temperature." )
435457
436458 def _remove_step (self ):
459+ # Reverse iteration ensures index deletion doesn't shift remaining targets incorrectly
437460 selection = self .listbox .curselection ()
438- if selection :
439- self .listbox .delete (selection [ 0 ] )
461+ for index in reversed ( selection ) :
462+ self .listbox .delete (index )
440463
441464 def _clear_listbox (self ):
442465 self .listbox .delete (0 , tk .END )
@@ -452,7 +475,6 @@ def _update_status_ui(self, text, color):
452475 self .lbl_status .config (text = text , bg = color )
453476
454477 def _on_proceed (self ):
455- """User clicked proceed. Unblock the background thread."""
456478 self .log ("User confirmed measurement. Moving to next setpoint." )
457479 self .btn_proceed .config (state = 'disabled' )
458480 self ._update_status_ui ("INITIATING NEXT RAMP..." , self .CLR_HEADER )
@@ -477,8 +499,9 @@ def start_sequence(self):
477499
478500 for key in self .data_storage :
479501 self .data_storage [key ].clear ()
480- self .line_temp [0 ].set_data ([], [])
481- self .line_heater [0 ].set_data ([], [])
502+ self .line_target .set_data ([], [])
503+ self .line_temp .set_data ([], [])
504+ self .line_heater .set_data ([], [])
482505 self .canvas .draw ()
483506
484507 self .start_time = time .time ()
@@ -522,7 +545,7 @@ def set_ui_state(self, running: bool):
522545 self .entry_end .config (state = state )
523546 self .entry_step .config (state = state )
524547 self .entry_manual .config (state = state )
525- self .sort_var .set (self .sort_var .get ()) # Keeps combobox visible but we disable the widget below
548+ self .sort_var .set (self .sort_var .get ())
526549 self .ls_cb .config (state = state if state == 'normal' else 'readonly' )
527550 self .btn_proceed .config (state = 'disabled' )
528551
@@ -561,8 +584,9 @@ def _process_gui_queue(self):
561584 self ._update_status_ui (msg ['text' ], msg ['color' ])
562585
563586 elif msg_type == 'plot' :
564- self .line_temp [0 ].set_data (self .data_storage ['time' ], self .data_storage ['temperature' ])
565- self .line_heater [0 ].set_data (self .data_storage ['time' ], self .data_storage ['heater' ])
587+ self .line_target .set_data (self .data_storage ['time' ], self .data_storage ['target' ])
588+ self .line_temp .set_data (self .data_storage ['time' ], self .data_storage ['temperature' ])
589+ self .line_heater .set_data (self .data_storage ['time' ], self .data_storage ['heater' ])
566590 for ax in [self .ax_temp , self .ax_heater ]:
567591 ax .relim ()
568592 ax .autoscale_view ()
@@ -601,7 +625,9 @@ def _hardware_worker_loop(self):
601625 temp , htr = self .backend .get_status ()
602626 self .data_storage ['time' ].append (time .time () - self .start_time )
603627 self .data_storage ['temperature' ].append (temp )
628+ self .data_storage ['target' ].append (target ) # Track target for graph
604629 self .data_storage ['heater' ].append (htr )
630+
605631 self ._put_gui_msg ('plot' )
606632
607633 if abs (temp - target ) <= self .params ['tolerance' ]:
0 commit comments