Skip to content

Commit eea5845

Browse files
Temperatures can be scrolled
1 parent 4c63cfd commit eea5845

1 file changed

Lines changed: 62 additions & 36 deletions

File tree

pica/lakeshore/T_Control_L350_Step_GUI.py

Lines changed: 62 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
22
Module: 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

66
import tkinter as tk
@@ -136,7 +136,7 @@ def shutdown(self):
136136
# -------------------------------------------------------------------------------
137137

138138
class 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\nScientific 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\nMumbai 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

Comments
 (0)