1- # BUILD VERSION: 1.0.0
21'''
32===============================================================================
43 PROGRAM: PICA Launcher
2423import multiprocessing
2524from multiprocessing import Process
2625
26+ # Ensure this import exists in your project structure
2727from pica .utils .GPIB_Instrument_Scanner_GUI import GPIB_Instrument_Scanner_GUI
2828
2929try :
3434
3535try :
3636 import pyvisa
37- import pyvisa .errors # Import pyvisa.errors
37+ import pyvisa .errors
3838 PYVISA_AVAILABLE = True
3939except ImportError :
4040 PYVISA_AVAILABLE = False
@@ -57,14 +57,10 @@ def run_script_process(script_path):
5757def launch_plotter_utility ():
5858 """
5959 Finds and launches the plotter utility script in a new process.
60- This function is designed to be imported and used by other frontends.
6160 """
6261 try :
63- # Assuming the plotter is in a standard location relative to other
64- # scripts
6562 script_dir = os .path .dirname (os .path .abspath (__file__ ))
66- plotter_path = os .path .join (
67- script_dir , "utils" , "PlotterUtil_GUI.py" )
63+ plotter_path = os .path .join (script_dir , "utils" , "PlotterUtil_GUI.py" )
6864 Process (target = run_script_process , args = (plotter_path ,)).start ()
6965 except Exception as e :
7066 print (f"Failed to launch plotter: { e } " )
@@ -73,8 +69,6 @@ def launch_plotter_utility():
7369def resource_path (relative_path ):
7470 """
7571 Get absolute path to resource, works for dev and for PyInstaller.
76- It checks for resources in the application's root directory when running
77- from source, and within the package directory when installed.
7872 """
7973 try :
8074 base_path = sys ._MEIPASS
@@ -107,14 +101,12 @@ class PICALauncherApp:
107101 FONT_SIZE_BASE = 12
108102 FONT_BASE = ('Segoe UI' , FONT_SIZE_BASE )
109103 FONT_TITLE = ('Segoe UI' , FONT_SIZE_BASE + 10 , 'bold' )
110- FONT_SUBTITLE = ('Segoe UI' , FONT_SIZE_BASE + 1 , 'bold' ) # Reduced size from +2
111- FONT_INSTITUTE = (
112- 'Segoe UI' ,
113- FONT_SIZE_BASE + 6 ,
114- 'bold' ) # New font for institute
104+ FONT_SUBTITLE = ('Segoe UI' , FONT_SIZE_BASE + 1 , 'bold' )
105+ FONT_INSTITUTE = ('Segoe UI' , FONT_SIZE_BASE + 6 , 'bold' )
115106 FONT_CONSOLE = ('Consolas' , 10 )
116107 FONT_INFO = ('Segoe UI' , FONT_SIZE_BASE )
117108 FONT_INFO_ITALIC = ('Segoe UI' , FONT_SIZE_BASE , 'italic' )
109+
118110 LOGO_FILE = resource_path ("assets/LOGO/UGC_DAE_CSR_NBG.jpeg" )
119111 MANUAL_FILE = resource_path ("docs/User_Manual.md" )
120112 README_FILE = resource_path ("README.md" )
@@ -127,92 +119,45 @@ class PICALauncherApp:
127119 # Based on Updates.md, using the latest versions of scripts.
128120 "Delta Mode I-V Sweep" : resource_path ("keithley/delta_mode/IV_K6221_DC_Sweep_GUI.py" ),
129121 "Delta Mode R-T" : resource_path ("keithley/delta_mode/Delta_RT_K6221_K2182_L350_T_Control_GUI.py" ),
130- "Delta Mode R-T (T_Sensing)" : resource_path (
131- "keithley/delta_mode/Delta_RT_K6221_K2182_L350_Sensing_GUI.py" ),
122+ "Delta Mode R-T (T_Sensing)" : resource_path ("keithley/delta_mode/Delta_RT_K6221_K2182_L350_Sensing_GUI.py" ),
132123 "K2400 I-V" : resource_path ("keithley/k2400/IV_K2400_GUI.py" ),
133124 "K2400 R-T" : resource_path ("keithley/k2400/RT_K2400_L350_T_Control_GUI.py" ),
134125 "K2400 R-T (T_Sensing)" : resource_path ("keithley/k2400/RT_K2400_L350_T_Sensing_GUI.py" ),
135126 "K2400_2182 I-V" : resource_path ("keithley/k2400_2182/IV_K2400_K2182_GUI.py" ),
136127 "K2400_2182 R-T" : resource_path ("keithley/k2400_2182/RT_K2400_K2182_T_Control_GUI.py" ),
137- "K2400_2182 R-T (T_Sensing)" : resource_path (
138- "keithley/k2400_2182/RT_K2400_K2182_L350_T_Sensing_GUI.py" ),
128+ "K2400_2182 R-T (T_Sensing)" : resource_path ("keithley/k2400_2182/RT_K2400_K2182_L350_T_Sensing_GUI.py" ),
139129 "K6517B I-V" : resource_path ("keithley/k6517b/High_Resistance/IV_K6517B_GUI.py" ),
140130 "K6517B R-T" : resource_path ("keithley/k6517b/High_Resistance/RT_K6517B_L350_T_Control_GUI.py" ),
141131 "K6517B R-T (T_Sensing)" : resource_path ("keithley/k6517b/High_Resistance/RT_K6517B_L350_T_Sensing_GUI.py" ),
142132 "Pyroelectric Current" : resource_path ("keithley/k6517b/Pyroelectricity/Pyroelectric_K6517B_L350_GUI.py" ),
143133 "Lakeshore Temp Control" : resource_path ("lakeshore/T_Control_L350_RangeControl_GUI.py" ),
144134 "Lakeshore Temp Monitor" : resource_path ("lakeshore/T_Sensing_L350_GUI.py" ),
145135 "LCR C-V Measurement" : resource_path ("keysight/CV_KE4980A_GUI.py" ),
146- "Plotter Utility" : resource_path ("utils/PlotterUtil_GUI.py" ),
147- "GPIB Scanner" : resource_path ("utils/GPIB_Instrument_Scanner_GUI.py" ),
148- "PICA Help" : resource_path ("README.md" ),
149- }
150-
151-
152- def launch_pica_script (script_key ):
153- """
154- Globally accessible function to launch PICA scripts by their key.
155- This uses the same resource_path and Process logic as PICALauncherApp
156- to ensure consistency and correct path resolution for installed packages.
157- """
158- if script_key not in PICALauncherApp .SCRIPT_PATHS :
159- print (f"ERROR: Script key '{ script_key } ' not found in PICA SCRIPT_PATHS." )
160- messagebox .showwarning (
161- "Script Not Found" ,
162- f"The script key '{ script_key } ' is not defined in the main application's script paths." )
163- return
164-
165- script_path = PICALauncherApp .SCRIPT_PATHS [script_key ]
166- print (f"Launching external script: { os .path .basename (script_path )} " )
167- abs_path = os .path .abspath (script_path )
168-
169- if not os .path .exists (abs_path ):
170- print (f"ERROR: Script not found at { abs_path } " )
171- messagebox .showerror (
172- "File Not Found" ,
173- f"Script not found:\n \n { abs_path } " )
174- return
175-
176- try :
177- proc = Process (target = run_script_process , args = (abs_path ,))
178- proc .start ()
179- print (f"Successfully launched '{ os .path .basename (script_path )} ' in a new process." )
180- except Exception as e :
181- print (f"ERROR: Failed to launch script '{ script_key } '. Reason: { e } " )
182- messagebox .showerror (
183- "Launch Error" ,
184- f"An error occurred while launching the script '{ script_key } ':\n \n { e } " )
185-
186-
187- class PICALauncherApp :
188-
189- PROGRAM_VERSION = "1.0.0"
190- CLR_BG_DARK = '#2B3D4F'
191- CLR_FRAME_BG = '#3A506B'
192- CLR_ACCENT_GOLD = '#FFC107'
193- CLR_ACCENT_GREEN = '#A7C957'
194- CLR_TEXT = '#EDF2F4'
195- CLR_TEXT_DARK = '#1A1A1A'
196- CLR_CONSOLE_BG = '#1E2B38'
197- CLR_LINK = '#87CEEB' # Sky Blue, for better contrast
198- FONT_SIZE_BASE = 12
199- FONT_BASE = ('Segoe UI' , FONT_SIZE_BASE )
200- FONT_TITLE = ('Segoe UI' , FONT_SIZE_BASE + 10 , 'bold' )
201- FONT_SUBTITLE = ('Segoe UI' , FONT_SIZE_BASE + 1 , 'bold' ) # Reduced size from +2
202- FONT_INSTITUTE = (
203- 'Segoe UI' ,
204- FONT_SIZE_BASE + 6 ,
205- 'bold' ) # New font for institute
206- FONT_CONSOLE = ('Consolas' , 10 )
207- FONT_INFO = ('Segoe UI' , FONT_SIZE_BASE )
208- FONT_INFO_ITALIC = ('Segoe UI' , FONT_SIZE_BASE , 'italic' )
209- LOGO_FILE = resource_path ("assets/LOGO/UGC_DAE_CSR_NBG.jpeg" )
210- MANUAL_FILE = resource_path ("docs/User_Manual.md" )
211- README_FILE = resource_path ("README.md" )
212- LICENSE_FILE = resource_path ("LICENSE" )
213- UPDATES_FILE = resource_path ("CHANGELOG.md" )
214- REPO_URL = "https://github.com/prathameshnium/PICA-Python-Instrument-Control-and-Automation/tree/main"
215- LOGO_SIZE = 140
136+ "Plotter Utility" : resource_path ("utils/PlotterUtil_GUI.py" ),
137+ "GPIB Scanner" : resource_path ("utils/GPIB_Instrument_Scanner_GUI.py" ),
138+ "PICA Help" : resource_path ("README.md" ),
139+ }
140+
141+ def __init__ (self , root ):
142+ """Constructor to initialize the main application window."""
143+ self .root = root
144+ self .root .title (f"PICA Launcher - v{ self .PROGRAM_VERSION } " )
145+ self .root .geometry ("1100x750" )
146+ self .root .configure (bg = self .CLR_BG_DARK )
147+
148+ # Initialize internal variables
149+ self .logo_image = None
150+ self .console_widget = None
151+ self ._md_cache = {}
152+
153+ # Setup UI
154+ self .setup_styles ()
155+ self .create_widgets ()
156+
157+ # Background tasks
158+ self ._pre_cache_markdown_files ()
159+ self .log (f"PICA Launcher initialized (v{ self .PROGRAM_VERSION } )" )
160+
216161 def setup_styles (self ):
217162 style = ttk .Style (self .root )
218163 style .theme_use ('clam' )
@@ -241,9 +186,7 @@ def setup_styles(self):
241186 style .configure (
242187 'App.TButton' ,
243188 font = self .FONT_BASE ,
244- padding = (
245- 10 ,
246- 5 ),
189+ padding = (10 , 5 ),
247190 foreground = self .CLR_ACCENT_GOLD ,
248191 background = self .CLR_FRAME_BG ,
249192 borderwidth = 0 ,
@@ -256,22 +199,16 @@ def setup_styles(self):
256199 style .configure (
257200 'Scan.TButton' ,
258201 font = self .FONT_BASE ,
259- padding = (
260- 10 ,
261- 9 ),
202+ padding = (10 , 9 ),
262203 foreground = self .CLR_TEXT_DARK ,
263204 background = self .CLR_ACCENT_GREEN )
264205 style .map (
265206 'Scan.TButton' , background = [
266207 ('active' , '#8AB845' ), ('hover' , '#8AB845' )])
267208 style .configure (
268209 'Icon.TButton' ,
269- font = (
270- 'Segoe UI' ,
271- 12 ),
272- padding = (
273- 5 ,
274- 9 ),
210+ font = ('Segoe UI' , 12 ),
211+ padding = (5 , 9 ),
275212 foreground = self .CLR_ACCENT_GOLD ,
276213 background = self .CLR_FRAME_BG ,
277214 borderwidth = 0 )
@@ -320,9 +257,7 @@ def create_resource_panel(self, parent):
320257 font = self .FONT_INSTITUTE ,
321258 justify = 'center' ,
322259 anchor = 'center' ).pack (
323- pady = (
324- 0 ,
325- 15 ))
260+ pady = (0 , 15 ))
326261
327262 ttk .Label (
328263 info_frame ,
@@ -331,9 +266,7 @@ def create_resource_panel(self, parent):
331266 foreground = self .CLR_ACCENT_GOLD ,
332267 justify = 'center' ,
333268 anchor = 'center' ).pack (
334- pady = (
335- 0 ,
336- 15 ))
269+ pady = (0 , 15 ))
337270
338271 desc_text = "A modular software suite for automating laboratory measurements in physics research."
339272 ttk .Label (
@@ -343,9 +276,7 @@ def create_resource_panel(self, parent):
343276 wraplength = 360 ,
344277 justify = 'center' ,
345278 anchor = 'center' ).pack (
346- pady = (
347- 0 ,
348- 10 ))
279+ pady = (0 , 10 ))
349280
350281 # --- Create a bold font for names ---
351282 bold_font = font .Font (
@@ -359,18 +290,14 @@ def create_resource_panel(self, parent):
359290 font = bold_font ,
360291 justify = 'center' ,
361292 anchor = 'center' ).pack (
362- pady = (
363- 5 ,
364- 0 ))
293+ pady = (5 , 0 ))
365294 ttk .Label (
366295 info_frame ,
367296 text = "Vision & Guidance by Dr. Sudip Mukherjee" ,
368297 font = bold_font ,
369298 justify = 'center' ,
370299 anchor = 'center' ).pack (
371- pady = (
372- 0 ,
373- 15 ))
300+ pady = (0 , 15 ))
374301
375302 ttk .Separator (info_frame , orient = 'horizontal' ).pack (fill = 'x' , pady = 10 )
376303 util_frame = ttk .Frame (info_frame )
@@ -527,13 +454,10 @@ def _on_mousewheel_linux_macos(event):
527454
528455 canvas .create_window ((0 , 0 ), window = scrollable_frame , anchor = "nw" )
529456 canvas .configure (yscrollcommand = scrollbar .set )
530- # Bind scrolling only to relevant widgets for better performance and to
531- # enable it
532- # For Windows and some Linux
457+
458+ # Bind scrolling
533459 canvas .bind ("<MouseWheel>" , _on_mousewheel_windows )
534- # For Linux and macOS
535460 canvas .bind ("<Button-4>" , _on_mousewheel_linux_macos )
536- # For Linux and macOS
537461 canvas .bind ("<Button-5>" , _on_mousewheel_linux_macos )
538462
539463 canvas .grid (row = 0 , column = 0 , sticky = 'nsew' , pady = 10 )
@@ -633,7 +557,6 @@ def _create_suite_frame(
633557 instruments_text ,
634558 buttons ):
635559 """Helper to create a measurement suite frame, reducing code duplication."""
636- # Reduce vertical padding to make the card smaller
637560 frame = ttk .LabelFrame (parent , text = title , style = 'TLabelframe' )
638561 frame .pack (fill = 'x' , expand = True , pady = 5 )
639562 frame .columnconfigure (0 , weight = 1 )
@@ -661,9 +584,6 @@ def _create_suite_frame(
661584 badge .pack (side = 'left' , anchor = 'w' )
662585
663586 if instruments_text :
664- # Use a Label that can wrap text to prevent truncation.
665- # The wraplength is an estimate; it will wrap if the text exceeds
666- # this width.
667587 instrument_label = ttk .Label (
668588 left_header ,
669589 text = instruments_text ,
@@ -728,19 +648,10 @@ def _open_path(self, path):
728648 messagebox .showerror (
729649 "Error" , f"Could not open path: { path } \n \n Error: { e } " )
730650
731- # =========================================================================
732- # === THIS FUNCTION IS NOW UPDATED WITH THE MARKDOWN PARSER ==============
733- # =========================================================================
734651 def _parse_markdown (self , content ):
735652 """
736653 Parses markdown content into a list of (text, tags) for rendering.
737- This is a placeholder for a more sophisticated parser if needed.
738- For now, we just split by lines as the original did.
739- The key is that this runs only once per file.
740654 """
741- # This is a placeholder for a more sophisticated parser if needed.
742- # For now, we just split by lines as the original did.
743- # The key is that this runs only once per file.
744655 return content .split ('\n ' )
745656
746657 def _show_file_in_window (self , file_path , title ):
@@ -842,8 +753,7 @@ def run_gpib_test(self):
842753
843754 def _pre_cache_markdown_files (self ):
844755 """
845- Reads and parses key markdown/text files in the background to make
846- the documentation windows open instantly.
756+ Reads and parses key markdown/text files in the background.
847757 """
848758 files_to_cache = [
849759 (self .README_FILE , "README" ),
@@ -972,8 +882,6 @@ def _render_markdown_content(self, text_area, lines):
972882 text_area .insert ('end' , '\n ' )
973883
974884
975-
976-
977885def main ():
978886 """Initializes and runs the main application."""
979887 root = tk .Tk ()
@@ -983,9 +891,6 @@ def main():
983891
984892if __name__ == '__main__' :
985893 # This is ESSENTIAL for multiprocessing to work in a bundled executable
986- # and ensures a consistent, stable process creation method across platforms.
987- # 'spawn' is the most robust method for GUI apps, though it is the default
988- # on Windows and macOS.
989894 multiprocessing .set_start_method ('spawn' , force = True )
990895 multiprocessing .freeze_support ()
991896
0 commit comments