thcal

that which calculates cutting parameters for CNC thread turning.
git clone https://git.sharifjon.com/thcal
Log | Files | Refs | README | LICENSE

main.py (34632B)


      1 import ctypes
      2 
      3 def enable_high_dpi_awareness():
      4     try:
      5         # Windows 8.1+
      6         ctypes.windll.shcore.SetProcessDpiAwareness(2)  # PROCESS_PER_MONITOR_DPI_AWARE
      7     except Exception:
      8         try:
      9             # Windows Vista–8.0
     10             ctypes.windll.user32.SetProcessDPIAware()
     11         except Exception:
     12             pass
     13 
     14 enable_high_dpi_awareness()
     15 
     16 
     17 import math
     18 from preamble import ThreadCalculator, ThreadDatabase, InputParser
     19 import tkinter as tk
     20 from tkinter import ttk
     21 import os
     22 import sys
     23 
     24 def resource_path(relative_path):
     25     """ Get absolute path to resource, works for dev and for PyInstaller """
     26     try:
     27         base_path = sys._MEIPASS
     28     except Exception:
     29         base_path = os.path.abspath(".")    
     30     return os.path.join(base_path, relative_path)
     31 
     32 
     33 
     34 class App(tk.Tk):
     35     def __init__(self):
     36         super().__init__()
     37 
     38         
     39         # Adjust scaling for high-DPI displays
     40 
     41         dpi = self.winfo_fpixels('1i')
     42         scale_factor = dpi / 72.0
     43 
     44         # Apply scaling to Tkinter
     45         self.tk.call('tk', 'scaling', scale_factor)
     46 
     47         # Optional: also set a nicer default font
     48         self.option_add("*Font", ("Segoe UI", 10))
     49 
     50     
     51         self.bind_enter_to_button()
     52         
     53 
     54 
     55         icon = tk.PhotoImage(file=resource_path("icon.png"))
     56         self.iconphoto(True, icon)
     57         
     58         self.title("Thread Calculator")
     59         self.minsize(950, 400)
     60         
     61 
     62         self.parser = InputParser()
     63         self.db_td = ThreadDatabase(resource_path("T_d.csv"))
     64         self.db_es = ThreadDatabase(resource_path("es.csv"))
     65 
     66         self.columnconfigure(0, weight=2)
     67         self.columnconfigure(1, weight=1)
     68         self.rowconfigure(0, weight=7)
     69         self.rowconfigure(1, weight=3)
     70 
     71         self.container = ttk.Frame(self)
     72         self.container.pack(expand=True, fill="both")
     73 
     74         self.main_menu = self.build_main_menu()
     75         self.ext_env = self.build_ext_env()
     76         self.int_env = self.build_int_env()
     77 
     78         self.show(self.main_menu)
     79 
     80     def bind_enter_to_button(self):
     81         def trigger_focused_button(event):
     82             widget = self.focus_get()
     83             if isinstance(widget, tk.Button) or isinstance(widget, ttk.Button):
     84                 widget.invoke()  # Trigger the button's command
     85         self.bind_all("<Return>", trigger_focused_button)
     86     
     87     def show(self, frame):
     88         frame.tkraise()
     89 
     90 
     91 
     92     # ----------------- MAIN MENU ------------------
     93     def build_main_menu(self):
     94         frame = ttk.Frame(self.container)
     95         frame.place(relwidth=1, relheight=1)
     96 
     97         frame_buttons = ttk.Frame(frame)
     98         frame_buttons.pack(pady=50)  
     99 
    100         # Buttons
    101         btn_ext = ttk.Button(frame_buttons, text="External Thread Calculator",
    102                          command=lambda: self.show(self.ext_env))
    103         btn_ext.pack(padx=10, pady=20)  
    104 
    105         btn_int = ttk.Button(frame_buttons, text="Internal Thread Calculator",
    106                          command=lambda: self.show(self.int_env))
    107         btn_int.pack(padx=10)
    108 
    109         return frame
    110     
    111     # ---------------- EXTERNAL THREAD ENVIRONMENT ------------------
    112     def build_ext_env(self):
    113         frame = ttk.Frame(self.container)
    114         frame.place(relwidth=1, relheight=1)
    115 
    116         ttk.Button(frame, text="← Back", command=lambda: self.show(self.main_menu)).pack(anchor="w", pady=5, padx=5)
    117 
    118         notebook = ttk.Notebook(frame)
    119         notebook.pack(expand=True, fill="both", padx=5, pady=5)
    120 
    121         # --- TAB 1: EXTERNAL THREAD WITH RUNOUT --- #
    122         tab_runout = ttk.Frame(notebook)
    123         tab_runout.columnconfigure(0, weight=1, uniform="half")
    124         tab_runout.columnconfigure(1, weight=1, uniform="half")
    125         tab_runout.rowconfigure(0, weight=1)
    126         
    127         notebook.add(tab_runout, text="With Runout")
    128 
    129         tab_runout_left = ttk.LabelFrame(tab_runout, text="Input")        
    130         tab_runout_left.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
    131         tab_runout_left.columnconfigure(0, weight=1)
    132         tab_runout_left.rowconfigure(1, weight=1)
    133         tab_runout_left.rowconfigure(0, weight=3)
    134 
    135 
    136         tab_runout_left_top = ttk.Frame(tab_runout_left)        
    137         tab_runout_left_top.grid(row=0, column=0, padx=5, pady=5, sticky="new")
    138         tab_runout_left_top.columnconfigure(1, weight=1, uniform="half")
    139         tab_runout_left_top.columnconfigure(0, weight=1, uniform="half")
    140         
    141         
    142 
    143         tab_runout_left_bottom = ttk.Frame(tab_runout_left)
    144         tab_runout_left_bottom.grid(row=1, column=0, padx=5, pady=5, sticky="sew")
    145         tab_runout_left_bottom.columnconfigure(0, weight=1)
    146         tab_runout_left_bottom.columnconfigure(1, weight=1)
    147 
    148     
    149 
    150         tab_runout_right = ttk.LabelFrame(tab_runout, text="Output")
    151         tab_runout_right.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
    152         tab_runout_right.columnconfigure(0, weight=1)
    153         tab_runout_right.rowconfigure(0, weight=1)    
    154 
    155         tk.Label(tab_runout_left_top, text="Designation:").grid(padx=5, pady=5, row=0, column=0, sticky="nse")
    156         runout_designation_entry = ttk.Entry(tab_runout_left_top)
    157         runout_designation_entry.grid(padx=5, pady=5,  row=0, column=1, sticky="nsew")
    158 
    159         tk.Label(tab_runout_left_top, text="Thread start point and its length:").grid(padx=5, pady=5, row=1, column=0, sticky="nse")
    160         runout_sl_entry = ttk.Entry(tab_runout_left_top)
    161         runout_sl_entry.grid(padx=5, pady=5,  row=1, column=1, sticky="nsew")
    162 
    163         # CALCULATIONS FOR EXTERNAL THREAD WITH RUNOUT
    164 
    165         def calc_runout():
    166             runout_output_box.config(state="normal")
    167             runout_output_box.delete("1.0", tk.END)
    168 
    169             designation = runout_designation_entry.get()
    170             designation_parsed = self.parser.parse_designation(designation)
    171             if isinstance(designation_parsed, str):
    172                 runout_output_box.insert(tk.END, designation_parsed + "\n")
    173                 runout_output_box.config(state="disabled")
    174                 return
    175             d, pitch, grade, class_letter = designation_parsed
    176 
    177             sl = runout_sl_entry.get()
    178             sl_parsed = self.parser.parse_sl(sl)
    179             if isinstance(sl_parsed, str):
    180                 runout_output_box.insert(tk.END, sl_parsed + "\n")
    181                 runout_output_box.config(state="disabled")
    182                 return
    183             s, l = sl_parsed
    184 
    185             T_d_value = self.db_td.lookup_value_T_d(diameter=d, pitch=pitch, grade=grade)
    186             if T_d_value is None:
    187                 runout_output_box.insert(tk.END, "No T_d-value found in database!\n")
    188                 runout_output_box.config(state="disabled")
    189                 return
    190             
    191             es_value = self.db_es.lookup_value_es(pitch=pitch, class_letter=class_letter)
    192             if es_value is None:
    193                 runout_output_box.insert(tk.END, "No es-value found in database!\n")
    194                 runout_output_box.config(state="disabled")
    195                 return
    196             
    197             
    198             depth = ThreadCalculator.calc_depth(pitch, es_value, T_d_value)
    199             fal = ThreadCalculator.cal_fal(pitch)
    200             noc = ThreadCalculator.calc_noc(pitch)
    201 
    202             # DISPLAY RESULTS            
    203             runout_output_box.insert(tk.END, f"SPL: {s}\n")
    204             runout_output_box.insert(tk.END, f"DM1: {d}\n")
    205             runout_output_box.insert(tk.END, f"FPL: {s - l - 2.5*pitch}\n")
    206             runout_output_box.insert(tk.END, f"APP: {pitch}\n")
    207             runout_output_box.insert(tk.END, f"ROP: {2.5*pitch}\n")
    208             runout_output_box.insert(tk.END, f"TDEP: {depth:.4f}\n")
    209             runout_output_box.insert(tk.END, f"FAL: {fal}\n")
    210             runout_output_box.insert(tk.END, f"IANG: 28\n")
    211             if noc is None:
    212                 runout_output_box.insert(tk.END, f"NRC: {noc}, choose different pitch (0.5-6)!\n")
    213             else:
    214                 runout_output_box.insert(tk.END, f"NRC: {noc-1}\n")
    215             runout_output_box.insert(tk.END, f"PIT: {pitch}\n")
    216             runout_output_box.insert(tk.END, f"VARI: 300103 (constant area!)\n")
    217 
    218             runout_output_box.config(state="disabled")
    219         
    220         def clear_runout_output():
    221             runout_output_box.config(state="normal")
    222             runout_output_box.delete("1.0", tk.END)
    223             runout_output_box.config(state="disabled")
    224 
    225         ttk.Button(tab_runout_left_bottom, text="Calculate", command=calc_runout).grid(padx=5, pady=5,  row=5, column=0, sticky="sew")
    226         
    227 
    228 
    229         ttk.Button(tab_runout_left_bottom, text="Clear Output", command=clear_runout_output).grid(padx=5, pady=5,  row=5, column=1, sticky="sew")
    230         
    231           
    232         
    233         runout_output_box = tk.Text(tab_runout_right)
    234         runout_output_box.grid(padx=5, pady=5,  row=0, column=0, sticky="nsew")
    235         runout_output_box.config(state="disabled")  # Read-only initially
    236 
    237         
    238             
    239 
    240         
    241 
    242 
    243         # --- TAB 2: EXTERNAL THREAD WITH UNDERCUT --- #
    244         tab_undercut = ttk.Frame(notebook)
    245         tab_undercut.columnconfigure(0, weight=1, uniform="half")
    246         tab_undercut.columnconfigure(1, weight=1, uniform="half")
    247         tab_undercut.rowconfigure(0, weight=1)
    248 
    249         notebook.add(tab_undercut, text="With Undercut")
    250 
    251         tab_undercut_left = ttk.LabelFrame(tab_undercut, text="Input")        
    252         tab_undercut_left.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
    253         tab_undercut_left.columnconfigure(0, weight=1)
    254         tab_undercut_left.rowconfigure(1, weight=1)
    255         tab_undercut_left.rowconfigure(0, weight=3)
    256         tab_undercut_left.rowconfigure(1, weight=1)
    257 
    258 
    259         tab_undercut_left_top = ttk.Frame(tab_undercut_left)
    260         tab_undercut_left_top.grid(row=0, column=0, padx=5, pady=5, sticky="new")
    261         tab_undercut_left_top.columnconfigure(1, weight=1, uniform="half")
    262         tab_undercut_left_top.columnconfigure(0, weight=1, uniform="half")
    263 
    264         tab_undercut_left_bottom = ttk.Frame(tab_undercut_left)
    265         tab_undercut_left_bottom.grid(row=1, column=0, padx=5, pady=5, sticky="sew")
    266         tab_undercut_left_bottom.columnconfigure(0, weight=1)
    267         tab_undercut_left_bottom.columnconfigure(1, weight=1)
    268 
    269         tab_undercut_right = ttk.LabelFrame(tab_undercut, text="Output")
    270         tab_undercut_right.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
    271         tab_undercut_right.columnconfigure(0, weight=1)
    272         tab_undercut_right.rowconfigure(0, weight=1)
    273 
    274         
    275 
    276         tk.Label(tab_undercut_left_top, text="Designation:").grid(padx=5, pady=5, row=0, column=0, sticky="nse")
    277         undercut_designation_entry = ttk.Entry(tab_undercut_left_top)
    278         undercut_designation_entry.grid(padx=5, pady=5,  row=0, column=1, sticky="nsew")
    279 
    280         tk.Label(tab_undercut_left_top, text="Thread start point and its length:").grid(padx=5, pady=5, row=1, column=0, sticky="nse")
    281         undercut_sl_entry = ttk.Entry(tab_undercut_left_top)
    282         undercut_sl_entry.grid(padx=5, pady=5,  row=1, column=1, sticky="nsew")
    283 
    284         tk.Label(tab_undercut_left_top, text="PDX-value of your insert:").grid(padx=5, pady=5, row=2, column=0, sticky="nse")
    285         pdx_entry = ttk.Entry(tab_undercut_left_top)
    286         pdx_entry.grid(padx=5, pady=5,  row=2, column=1, sticky="nsew")
    287 
    288         # CALCULATIONS FOR EXTERNAL THREAD WITH UNDERCUT
    289 
    290         def calc_undercut():
    291             undercut_output_box.config(state="normal")
    292             undercut_output_box.delete("1.0", tk.END)
    293 
    294             designation = undercut_designation_entry.get()
    295             designation_parsed = self.parser.parse_designation(designation)
    296             if isinstance(designation_parsed, str):
    297                 undercut_output_box.insert(tk.END, designation_parsed + "\n")
    298                 undercut_output_box.config(state="disabled")
    299                 return
    300             d, pitch, grade, class_letter = designation_parsed
    301 
    302             sl = undercut_sl_entry.get()
    303             sl_parsed = self.parser.parse_sl(sl)
    304             if isinstance(sl_parsed, str):
    305                 undercut_output_box.insert(tk.END, sl_parsed + "\n")
    306                 undercut_output_box.config(state="disabled")
    307                 return
    308             s, l = sl_parsed
    309 
    310             PDX = self.parser.validated(pdx_entry.get())
    311             if PDX is None:
    312                 undercut_output_box.insert(tk.END, "Invalid PDX-value!\n")
    313 
    314             T_d_value = self.db_td.lookup_value_T_d(diameter=d, pitch=pitch, grade=grade)
    315             if T_d_value is None:
    316                 undercut_output_box.insert(tk.END, "No T_d-value found in database!\n")
    317                 undercut_output_box.config(state="disabled")
    318                 return
    319             
    320             es_value = self.db_es.lookup_value_es(pitch=pitch, class_letter=class_letter)
    321             if es_value is None:
    322                 undercut_output_box.insert(tk.END, "No es-value found in database!\n")
    323                 undercut_output_box.config(state="disabled")
    324                 return
    325             
    326             
    327             depth = ThreadCalculator.calc_depth(pitch, es_value, T_d_value)
    328             fal = ThreadCalculator.cal_fal(pitch)
    329             noc = ThreadCalculator.calc_noc(pitch)
    330 
    331             # Display results
    332             undercut_output_box.insert(tk.END, f"SPL: {s}\n")
    333             undercut_output_box.insert(tk.END, f"DM1: {d}\n")
    334             if PDX is None:
    335                 undercut_output_box.insert(tk.END, "FPL: Not Calculated\n")
    336             else:
    337                 undercut_output_box.insert(tk.END, f"FPL: {s - l + 0.5 + PDX}\n")
    338             undercut_output_box.insert(tk.END, f"APP: {pitch}\n")
    339             if PDX is None:
    340                 undercut_output_box.insert(tk.END, "ROP: Not Calculated\n")
    341             else:
    342                 undercut_output_box.insert(tk.END, f"ROP: g1-({0.5+PDX})\n")
    343             undercut_output_box.insert(tk.END, f"TDEP: {depth:.4f}\n")
    344             if fal is None:
    345                 undercut_output_box.insert(tk.END, f"FAL: {fal}, choose different pitch (0.5-6)!\n")
    346             else:
    347                 undercut_output_box.insert(tk.END, f"FAL: {fal}\n")
    348             undercut_output_box.insert(tk.END, f"IANG: 28\n")
    349             if noc is None:
    350                 undercut_output_box.insert(tk.END, f"NRC: {noc}, choose different pitch (0.5-6)!\n")
    351             else:
    352                 undercut_output_box.insert(tk.END, f"NRC: {noc-1}\n")
    353             undercut_output_box.insert(tk.END, f"PIT: {pitch}\n")
    354             undercut_output_box.insert(tk.END, f"VARI: 300103 (constant area!)\n")
    355 
    356             undercut_output_box.config(state="disabled")
    357         
    358         def clear_undercut_output():
    359             undercut_output_box.config(state="normal")
    360             undercut_output_box.delete("1.0", tk.END)
    361             undercut_output_box.config(state="disabled")
    362         
    363         
    364 
    365         ttk.Button(tab_undercut_left_bottom, text="Calculate", command=calc_undercut).grid(padx=5, pady=5,  row=0, column=0, sticky="sew")
    366 
    367         ttk.Button(tab_undercut_left_bottom, text="Clear Output", command=clear_undercut_output).grid(padx=5, pady=5,  row=0, column=1, sticky="sew")
    368 
    369         undercut_output_box = tk.Text(tab_undercut_right, height=20, width=80)
    370         undercut_output_box.grid(padx=5, pady=5,  row=0, column=0, sticky="nsew")
    371         undercut_output_box.config(state="disabled")  # Read-only initially
    372 
    373         return frame
    374     
    375     
    376     
    377     
    378     
    379     
    380     # ---------------- INTERNAL THREAD ENVIRONMENT ------------------
    381     def build_int_env(self):
    382         frame = ttk.Frame(self.container)
    383         frame.place(relwidth=1, relheight=1)
    384 
    385         ttk.Button(frame, text="← Back", command=lambda: self.show(self.main_menu)).pack(anchor="w", pady=5, padx=5)
    386 
    387         notebook = ttk.Notebook(frame)
    388         notebook.pack(expand=True, fill="both", padx=5, pady=10)
    389 
    390 
    391         # --- TAB 1: CENTER DRILL --- #
    392         tab_center_drill = ttk.Frame(notebook)
    393         tab_center_drill.columnconfigure(0, weight=1, uniform="half")
    394         tab_center_drill.columnconfigure(1, weight=1, uniform="half")
    395         tab_center_drill.rowconfigure(0, weight=1)
    396 
    397         notebook.add(tab_center_drill, text="Center Drill")
    398 
    399         tab_center_drill_left = ttk.LabelFrame(tab_center_drill, text="Input")
    400         tab_center_drill_left.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
    401         tab_center_drill_left.columnconfigure(0, weight=1)
    402         tab_center_drill_left.rowconfigure(1, weight=1)
    403         tab_center_drill_left.rowconfigure(0, weight=1)
    404         
    405 
    406         tab_center_drill_left_top = ttk.Frame(tab_center_drill_left)
    407         tab_center_drill_left_top.grid(row=0, column=0, padx=5, pady=5, sticky="new")
    408         tab_center_drill_left_top.columnconfigure(1, weight=1, uniform="half")
    409         tab_center_drill_left_top.columnconfigure(0, weight=1, uniform="half")
    410         
    411 
    412 
    413         tab_center_drill_left_bottom = ttk.Frame(tab_center_drill_left)
    414         tab_center_drill_left_bottom.grid(row=1, column=0, padx=5, pady=5, sticky="sew")
    415         tab_center_drill_left_bottom.columnconfigure(0, weight=1)
    416         tab_center_drill_left_bottom.columnconfigure(1, weight=1)
    417 
    418 
    419         tab_center_drill_right = ttk.LabelFrame(tab_center_drill, text="Output")
    420         tab_center_drill_right.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
    421         tab_center_drill_right.columnconfigure(0, weight=1)
    422         tab_center_drill_right.rowconfigure(0, weight=1)
    423 
    424 
    425 
    426         tk.Label(tab_center_drill_left_top, text="Designation:").grid(padx=5, pady=5, row=0, column=0, sticky="nse")
    427         center_drill_designation_entry = ttk.Entry(tab_center_drill_left_top)
    428         center_drill_designation_entry.grid(padx=5, pady=5,  row=0, column=1, sticky="nsew")    
    429 
    430         tk.Label(tab_center_drill_left_top, text="Hole start point:").grid(padx=5, pady=5, row=1, column=0, sticky="nse")
    431         center_drill_s_entry = ttk.Entry(tab_center_drill_left_top)
    432         center_drill_s_entry.grid(padx=5, pady=5,  row=1, column=1, sticky="nsew")
    433 
    434         tk.Label(tab_center_drill_left_top, text="Center drill cone angle:").grid(padx=5, pady=5, row=2, column=0, sticky="nse")
    435         center_drill_angle_entry = ttk.Entry(tab_center_drill_left_top)
    436         center_drill_angle_entry.grid(padx=5, pady=5,  row=2, column=1, sticky="nsew")
    437 
    438         # CALCULATIONS FOR CENTER DRILL
    439 
    440         def calc_center_drill():
    441             center_drill_output_box.config(state="normal")
    442             center_drill_output_box.delete("1.0", tk.END)
    443 
    444             designation = center_drill_designation_entry.get()
    445             designation_parsed = self.parser.parse_designation(designation)
    446             if isinstance(designation_parsed, str):
    447                 center_drill_output_box.insert(tk.END, designation_parsed + "\n")
    448                 center_drill_output_box.config(state="disabled")
    449                 return
    450             d, pitch, grade, class_letter = designation_parsed
    451             
    452             d2 = d - pitch
    453             
    454             s = self.parser.validated(center_drill_s_entry.get())
    455             if s is None:
    456                 center_drill_output_box.insert(tk.END, "Invalid hole start point!\n")
    457                 center_drill_output_box.config(state="disabled")
    458                 return
    459 
    460 
    461             a1 = self.parser.validated(center_drill_angle_entry.get())
    462             if a1 is None:
    463                 center_drill_output_box.insert(tk.END, "Invalid center drill cone angle!\n")
    464                 center_drill_output_box.config(state="disabled")
    465                 return
    466             
    467             h1 = (0.65*d2)/(2*math.tan(math.radians(a1/2)))
    468 
    469             # DISPLAY RESULTS
    470 
    471             center_drill_output_box.insert(tk.END, f"RFP: {s}\n")
    472             center_drill_output_box.insert(tk.END, f"DP: {s-h1: .5f}\n")
    473             center_drill_output_box.config(state="disabled")
    474         
    475         def clear_center_drill_output():
    476             center_drill_output_box.config(state="normal")
    477             center_drill_output_box.delete("1.0", tk.END)
    478             center_drill_output_box.config(state="disabled")
    479 
    480 
    481         ttk.Button(tab_center_drill_left_bottom, text="Calculate", command=calc_center_drill).grid(padx=5, pady=5,  row=0, column=0, sticky="sew")
    482 
    483         ttk.Button(tab_center_drill_left_bottom, text="Clear Output", command=clear_center_drill_output).grid(padx=5, pady=5,  row=0, column=1, sticky="sew")   
    484 
    485         
    486         center_drill_output_box = tk.Text(tab_center_drill_right, height=20, width=80)
    487         center_drill_output_box.grid(padx=5, pady=5,  row=0, column=0, sticky="nsew")
    488         center_drill_output_box.config(state="disabled")  # Read-only initially
    489 
    490         
    491             
    492 
    493         # --- TAB 2: DEEP HOLE DRILL --- #
    494         tab_deep_hole_drill = ttk.Frame(notebook)
    495         tab_deep_hole_drill.columnconfigure(0, weight=1, uniform="half")
    496         tab_deep_hole_drill.columnconfigure(1, weight=1, uniform="half")
    497         tab_deep_hole_drill.rowconfigure(0, weight=1)
    498 
    499         notebook.add(tab_deep_hole_drill, text="Deep Hole Drill")
    500 
    501         tab_deep_hole_drill_left = ttk.LabelFrame(tab_deep_hole_drill, text="Input")
    502         tab_deep_hole_drill_left.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
    503         tab_deep_hole_drill_left.columnconfigure(0, weight=1)
    504         tab_deep_hole_drill_left.rowconfigure(1, weight=1)
    505         tab_deep_hole_drill_left.rowconfigure(0, weight=1)
    506         tab_deep_hole_drill_left.rowconfigure(1, weight=1)
    507 
    508         tab_deep_hole_drill_left_top = ttk.Frame(tab_deep_hole_drill_left)
    509         tab_deep_hole_drill_left_top.grid(row=0, column=0, padx=5, pady=5, sticky="new")
    510         tab_deep_hole_drill_left_top.columnconfigure(1, weight=1, uniform="half")
    511         tab_deep_hole_drill_left_top.columnconfigure(0, weight=1, uniform="half")
    512 
    513         tab_deep_hole_drill_left_bottom = ttk.Frame(tab_deep_hole_drill_left)
    514         tab_deep_hole_drill_left_bottom.grid(row=1, column=0, padx=5, pady=5, sticky="sew")
    515         tab_deep_hole_drill_left_bottom.columnconfigure(0, weight=1)
    516         tab_deep_hole_drill_left_bottom.columnconfigure(1, weight=1)
    517 
    518         tab_deep_hole_drill_right = ttk.LabelFrame(tab_deep_hole_drill, text="Output")
    519         tab_deep_hole_drill_right.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
    520         tab_deep_hole_drill_right.columnconfigure(0, weight=1)
    521         tab_deep_hole_drill_right.rowconfigure(0, weight=1)
    522 
    523 
    524 
    525         tk.Label(tab_deep_hole_drill_left_top, text="Designation:").grid(padx=5, pady=5, row=0, column=0, sticky="nse")
    526         deep_hole_drill_designation_entry = ttk.Entry(tab_deep_hole_drill_left_top)
    527         deep_hole_drill_designation_entry.grid(padx=5, pady=5,  row=0, column=1, sticky="nsew") 
    528 
    529         tk.Label(tab_deep_hole_drill_left_top, text="Tapping tool form (e.g., C):").grid(padx=5, pady=5, row=1, column=0, sticky="nse")
    530         deep_hole_drill_form_entry = ttk.Entry(tab_deep_hole_drill_left_top)
    531         deep_hole_drill_form_entry.grid(padx=5, pady=5,  row=1, column=1, sticky="nsew")    
    532 
    533         tk.Label(tab_deep_hole_drill_left_top, text="Hole start point and thread length:").grid(padx=5, pady=5, row=2, column=0, sticky="nse") 
    534         deep_hole_drill_sl_entry = ttk.Entry(tab_deep_hole_drill_left_top)
    535         deep_hole_drill_sl_entry.grid(padx=5, pady=5,  row=2, column=1, sticky="nsew")
    536 
    537         tk.Label(tab_deep_hole_drill_left_top, text="Deep hole drill cone angle:").grid(padx=5, pady=5, row=3, column=0, sticky="nse")
    538         deep_hole_drill_angle_entry = ttk.Entry(tab_deep_hole_drill_left_top)
    539         deep_hole_drill_angle_entry.grid(padx=5, pady=5,  row=3, column=1, sticky="nsew")
    540 
    541         # CALCULATIONS FOR DEEP HOLE DRILLING
    542         
    543         def calc_deep_hole_drill():
    544             deep_hole_drill_output_box.config(state="normal")
    545             deep_hole_drill_output_box.delete("1.0", tk.END)
    546 
    547             designation = deep_hole_drill_designation_entry.get()
    548             designation_parsed = self.parser.parse_designation(designation)
    549             if isinstance(designation_parsed, str):
    550                 deep_hole_drill_output_box.insert(tk.END, designation_parsed + "\n")
    551                 deep_hole_drill_output_box.config(state="disabled")
    552                 return
    553             d, pitch, grade, class_letter = designation_parsed
    554             
    555             d2 = d - pitch
    556             
    557             form = deep_hole_drill_form_entry.get().lower()
    558             form_range = {
    559                 "a": 8,
    560                 "b": 5,
    561                 "c": 3,
    562                 "d": 5,
    563                 "e": 2
    564             }
    565             if not isinstance(form, str):
    566                 deep_hole_drill_output_box.insert(tk.END, "Invalid type!\n")
    567                 deep_hole_drill_output_box.config(state="disabled")
    568                 return
    569             elif form not in form_range:
    570                 deep_hole_drill_output_box.insert(tk.END, "Entered tool form not found!\n")
    571                 deep_hole_drill_output_box.config(state="disabled")
    572                 return
    573             k = form_range.get(form)
    574 
    575             sl = deep_hole_drill_sl_entry.get()
    576             sl_parsed = self.parser.parse_sl(sl)
    577             if isinstance(sl_parsed, str):
    578                 deep_hole_drill_output_box.insert(tk.END, sl_parsed + "\n")
    579                 deep_hole_drill_output_box.config(state="disabled")
    580                 return
    581             s, l = sl_parsed
    582 
    583             a2 = self.parser.validated(deep_hole_drill_angle_entry.get())
    584             if a2 is None:
    585                 deep_hole_drill_output_box.insert(tk.END, "Invalid deep hole drill cone angle!\n")
    586                 deep_hole_drill_output_box.config(state="disabled")
    587                 return
    588             
    589             h_2 = ThreadCalculator.half_ceil(l + (k+2)*pitch + (d2)/(2*math.tan(math.radians(a2/2))))
    590             h2 = s - h_2
    591 
    592             deep_hole_drill_output_box.insert(tk.END, f"RFP: {s}\n")
    593             deep_hole_drill_output_box.insert(tk.END, f"DP: {h2: .3f}\n")
    594             fdep = ThreadCalculator.depth_per_storke(h_2)
    595             if fdep is None:
    596                 deep_hole_drill_output_box.insert(tk.END, "FDEP: Invalid depth!\n")
    597             else:
    598                 deep_hole_drill_output_box.insert(tk.END, f"FDEP: {s - fdep}\n")
    599             deep_hole_drill_output_box.config(state="disabled")
    600 
    601         def clear_deep_hole_drill_output():
    602             deep_hole_drill_output_box.config(state="normal")
    603             deep_hole_drill_output_box.delete("1.0", tk.END)
    604             deep_hole_drill_output_box.config(state="disabled")
    605 
    606         def deep_hole_drill_get_data():
    607             if center_drill_designation_entry.get():
    608                 deep_hole_drill_designation_entry.delete(0, tk.END)
    609                 deep_hole_drill_designation_entry.insert(0, center_drill_designation_entry.get())
    610             if center_drill_s_entry.get():
    611                 deep_hole_drill_sl_entry.delete(0, tk.END)
    612                 deep_hole_drill_sl_entry.insert(0, center_drill_s_entry.get()+",")
    613 
    614        
    615         ttk.Button(tab_deep_hole_drill_left_bottom, text="Get Data", command=deep_hole_drill_get_data).grid(padx=5, pady=5, row=0, column=0, sticky="sew")
    616 
    617         ttk.Button(tab_deep_hole_drill_left_bottom, text="Calculate", command=calc_deep_hole_drill).grid(padx=5, pady=5, row=1, column=0, sticky="sew")
    618         ttk.Button(tab_deep_hole_drill_left_bottom, text="Clear Output", command=clear_deep_hole_drill_output).grid(padx=5, pady=5, row=1, column=1, sticky="sew")
    619 
    620         
    621         deep_hole_drill_output_box = tk.Text(tab_deep_hole_drill_right, height=20, width=80)
    622         deep_hole_drill_output_box.grid(padx=5, pady=5,  row=0, column=0, sticky="nsew")
    623         deep_hole_drill_output_box.config(state="disabled")  # Read-only initially
    624 
    625         
    626 
    627         # --- TAB 3: TAPPING --- #
    628         tab_tapping = ttk.Frame(notebook)
    629         tab_tapping.columnconfigure(0, weight=1, uniform="half")
    630         tab_tapping.columnconfigure(1, weight=1, uniform="half")
    631         tab_tapping.rowconfigure(0, weight=1)
    632 
    633         notebook.add(tab_tapping, text="Tapping")
    634 
    635         tab_tapping_left = ttk.LabelFrame(tab_tapping, text="Input")
    636         tab_tapping_left.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
    637         tab_tapping_left.columnconfigure(0, weight=1)
    638         tab_tapping_left.rowconfigure(1, weight=1)
    639         tab_tapping_left.rowconfigure(0, weight=1)
    640         tab_tapping_left.rowconfigure(1, weight=1)
    641 
    642         tab_tapping_left_top = ttk.Frame(tab_tapping_left)
    643         tab_tapping_left_top.grid(row=0, column=0, padx=5, pady=5, sticky="new")
    644         tab_tapping_left_top.columnconfigure(1, weight=1, uniform="half")
    645         tab_tapping_left_top.columnconfigure(0, weight=1, uniform="half")
    646         
    647 
    648         tab_tapping_left_bottom = ttk.Frame(tab_tapping_left)
    649         tab_tapping_left_bottom.grid(row=1, column=0, padx=5, pady=5, sticky="sew")
    650         tab_tapping_left_bottom.columnconfigure(0, weight=1)
    651         tab_tapping_left_bottom.columnconfigure(1, weight=1)
    652 
    653         tab_tapping_right = ttk.LabelFrame(tab_tapping, text="Output")
    654         tab_tapping_right.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
    655         tab_tapping_right.columnconfigure(0, weight=1)
    656         tab_tapping_right.rowconfigure(0, weight=1)
    657 
    658 
    659         tk.Label(tab_tapping_left_top, text="Designation:").grid(padx=5, pady=5, row=0, column=0, sticky="nse")
    660         tapping_designation_entry = ttk.Entry(tab_tapping_left_top)
    661         tapping_designation_entry.grid(padx=5, pady=5, row=0, column=1, sticky="nsew")
    662 
    663         tk.Label(tab_tapping_left_top, text="Tapping tool form (e.g., C):").grid(padx=5, pady=5, row=1, column=0, sticky="nse")  
    664         tapping_form_entry = ttk.Entry(tab_tapping_left_top)
    665         tapping_form_entry.grid(padx=5, pady=5, row=1, column=1, sticky="nsew")
    666 
    667         tk.Label(tab_tapping_left_top, text="Hole start point and thread length:").grid(padx=5, pady=5, row=2, column=0, sticky="nse")
    668         tapping_sl_entry = ttk.Entry(tab_tapping_left_top)
    669         tapping_sl_entry.grid(padx=5, pady=5, row=2, column=1, sticky="nsew")
    670 
    671         # CALCULATIONS FOR TAPPING
    672 
    673         def calc_tapping():
    674             tapping_output_box.config(state="normal")
    675             tapping_output_box.delete("1.0", tk.END)
    676 
    677             designation = tapping_designation_entry.get()
    678             designation_parsed = self.parser.parse_designation(designation)
    679             if isinstance(designation_parsed, str):
    680                 tapping_output_box.insert(tk.END, designation_parsed + "\n")
    681                 tapping_output_box.config(state="disabled")
    682                 return
    683             d, pitch, grade, class_letter = designation_parsed
    684             
    685             form = tapping_form_entry.get().lower()
    686             form_range = {
    687                 "a": 8,
    688                 "b": 5,
    689                 "c": 3,
    690                 "d": 5,
    691                 "e": 2
    692             }
    693             if not isinstance(form, str):
    694                 tapping_output_box.insert(tk.END, "Invalid type!\n")
    695                 tapping_output_box.config(state="disabled")
    696                 return
    697             elif form not in form_range:
    698                 tapping_output_box.insert(tk.END, "Entered tool form not found!\n")
    699                 tapping_output_box.config(state="disabled")
    700                 return
    701             k = form_range.get(form)
    702 
    703             sl = tapping_sl_entry.get()
    704             sl_parsed = self.parser.parse_sl(sl)
    705             if isinstance(sl_parsed, str):
    706                 tapping_output_box.insert(tk.END, sl_parsed + "\n")
    707                 tapping_output_box.config(state="disabled")
    708                 return
    709             s, l = sl_parsed
    710 
    711             h4 = s - (l + k*pitch)
    712 
    713             tapping_output_box.insert(tk.END, f"RFP: {s}\n")
    714             tapping_output_box.insert(tk.END, f"DP: {h4}\n")
    715             tapping_output_box.insert(tk.END, "SDAC: 5\n")
    716             tapping_output_box.insert(tk.END, f"PIT: {pitch}\n")
    717 
    718 
    719             tapping_output_box.config(state="disabled")
    720         
    721         def clear_tapping_output():
    722             tapping_output_box.config(state="normal")
    723             tapping_output_box.delete("1.0", tk.END)
    724             tapping_output_box.config(state="disabled")
    725         
    726         def get_tapping_data():
    727             if deep_hole_drill_designation_entry.get():
    728                 tapping_designation_entry.delete(0, tk.END)
    729                 tapping_designation_entry.insert(0, deep_hole_drill_designation_entry.get())
    730             
    731             if (not deep_hole_drill_designation_entry.get()) and center_drill_designation_entry.get():
    732                 tapping_designation_entry.delete(0, tk.END)
    733                 tapping_designation_entry.insert(0, center_drill_designation_entry.get())
    734             
    735             if (not deep_hole_drill_sl_entry.get()) and center_drill_s_entry.get():
    736                 tapping_sl_entry.delete(0, tk.END)
    737                 tapping_sl_entry.insert(0, center_drill_s_entry.get()+",")
    738 
    739             if deep_hole_drill_form_entry.get():
    740                 tapping_form_entry.delete(0, tk.END)
    741                 tapping_form_entry.insert(0, deep_hole_drill_form_entry.get())
    742 
    743             if deep_hole_drill_sl_entry.get():
    744                 tapping_sl_entry.delete(0, tk.END)
    745                 tapping_sl_entry.insert(0, deep_hole_drill_sl_entry.get())
    746 
    747         ttk.Button(tab_tapping_left_bottom, text="Get Data", command=get_tapping_data).grid(padx=5, pady=5, row=0, column=0, sticky="sew")
    748 
    749         ttk.Button(tab_tapping_left_bottom, text="Calculate", command=calc_tapping).grid(padx=5, pady=5, row=1, column=0, sticky="sew") 
    750 
    751         ttk.Button(tab_tapping_left_bottom, text="Clear Output", command=clear_tapping_output).grid(padx=5, pady=5, row=1, column=1, sticky="sew")  
    752 
    753         
    754         tapping_output_box = tk.Text(tab_tapping_right)
    755         tapping_output_box.grid(padx=5, pady=5,  row=0, column=0, sticky="nsew")    
    756         tapping_output_box.config(state="disabled")  # Read-only initially
    757 
    758         
    759 
    760         return frame
    761         
    762 
    763 
    764         
    765 
    766 
    767 
    768     
    769 if __name__ == "__main__":
    770     app = App()
    771     app.mainloop()
    772