commit 09aca4a1cc49bd10eea63eece3f451439ae3c20a
parent 158d5b85838be6d65f1681df4904e68b3ba956b2
Author: Sharifjon Sharipov <ss2393506@gmail.com>
Date: Sun, 15 Feb 2026 10:53:48 +0000
Add files via upload
Diffstat:
| A | T_d.csv | | | 49 | +++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | defaults.csv | | | 107 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | es.csv | | | 26 | ++++++++++++++++++++++++++ |
| A | flake.nix | | | 25 | +++++++++++++++++++++++++ |
| A | main.py | | | 772 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | preamble.py | | | 257 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
6 files changed, 1236 insertions(+), 0 deletions(-)
diff --git a/T_d.csv b/T_d.csv
@@ -0,0 +1,49 @@
+dmin,dmax,p,g3,g4,g5,g6,g7,g8,g9
+0.99,1.4,0.2,24,30,38,48,,,
+0.99,1.4,0.25,26,34,42,53,,,
+0.99,1.4,0.3,28,36,45,56,,,
+1.4,2.8,0.2,25,32,40,50,,,
+1.4,2.8,0.25,28,36,45,56,,,
+1.4,2.8,0.35,32,40,50,63,80,,
+1.4,2.8,0.4,34,42,53,67,85,,
+1.4,2.8,0.45,36,45,56,71,90,,
+2.8,5.6,0.35,34,42,53,67,85,,
+2.8,5.6,0.5,38,48,60,75,95,,
+2.8,5.6,0.6,42,53,67,85,106,,
+2.8,5.6,0.7,45,56,71,90,112,,
+2.8,5.6,0.75,45,56,71,90,112,,
+2.8,5.6,0.8,48,60,75,95,118,150,190
+5.6,11.2,0.75,50,63,80,100,125,,
+5.6,11.2,1,56,71,90,112,140,180,224
+5.6,11.2,1.25,60,75,95,118,150,190,236
+5.6,11.2,1.5,67,85,106,132,170,212,265
+11.2,22.4,1,60,75,95,118,150,190,236
+11.2,22.4,1.25,67,85,106,132,170,212,265
+11.2,22.4,1.5,71,90,112,140,180,224,280
+11.2,22.4,1.75,75,95,118,150,190,236,300
+11.2,22.4,2,80,100,125,160,200,250,315
+11.2,22.4,2.5,85,106,132,170,212,265,335
+22.4,45,1,63,80,100,125,160,200,250
+22.4,45,1.5,75,95,118,150,190,236,300
+22.4,45,2,85,106,132,170,212,265,335
+22.4,45,3,100,125,160,200,250,315,400
+22.4,45,3.5,106,132,170,212,265,335,425
+22.4,45,4,112,140,180,224,280,355,450
+22.4,45,4.5,118,150,190,236,300,375,475
+45,90,1.5,80,100,125,160,200,250,315
+45,90,2,90,112,140,180,224,280,355
+45,90,3,106,132,170,212,265,335,425
+45,90,4,118,150,190,236,300,375,475
+45,90,5,125,160,200,250,315,400,500
+45,90,5.5,132,170,212,265,335,425,530
+45,90,6,140,180,224,280,355,450,560
+90,180,2,95,118,150,190,236,300,375
+90,180,3,112,140,180,224,280,355,450
+90,180,4,125,160,200,250,315,400,500
+90,180,6,150,190,236,300,375,450,560
+90,180,8,170,212,265,335,425,530,670
+180,355,3,125,160,200,250,315,400,500
+180,355,4,140,180,224,280,355,450,560
+180,355,6,160,200,250,315,400,500,630
+180,355,8,180,224,280,355,450,560,710
+
diff --git a/defaults.csv b/defaults.csv
@@ -0,0 +1,107 @@
+d,p
+1,0.25
+1.1,0.25
+1.2,0.25
+1.4,0.3
+1.6,0.35
+1.8,0.35
+2,0.4
+2.2,0.45
+2.5,0.45
+3,0.5
+3.5,0.6
+4,0.7
+4.5,0.75
+5,0.8
+5.5,0.75
+6,1
+7,1
+8,1.25
+9,1.25
+10,1.5
+11,1.5
+12,1.75
+14,2
+15,1.5
+16,2
+17,1.5
+18,2.5
+20,2.5
+22,2.5
+24,3
+25,2
+26,1.5
+27,3
+28,2
+30,3.5
+32,2
+33,3.5
+35,1.5
+36,4
+38,2
+39,4
+40,3
+42,4.5
+45,4.5
+48,5
+50,3
+52,5
+55,5.5
+56,5.5
+58,4
+60,5.5
+62,4
+64,6
+65,4
+68,6
+70,4
+72,6
+75,7
+76,6
+78,6
+80,6
+82,6
+85,6
+90,6
+95,6
+100,6
+105,6
+110,6
+115,6
+120,6
+125,8
+130,8
+135,8
+140,8
+145,6
+150,8
+155,6
+160,8
+165,6
+170,8
+175,8
+180,8
+185,6
+190,8
+195,6
+200,8
+205,6
+210,8
+215,6
+220,8
+225,6
+230,8
+235,6
+240,8
+245,6
+250,8
+255,6
+260,8
+265,6
+270,8
+275,6
+280,8
+285,6
+290,8
+295,6
+300,8
diff --git a/es.csv b/es.csv
@@ -0,0 +1,26 @@
+a,b,c,d,e,f,g,h
+0.2,,,,,,,-17,0
+0.25,,,,,,,-18,0
+0.3,,,,,,,-18,0
+0.35,,,,,,-34.0,-19,0
+0.4,,,,,,-34.0,-19,0
+0.45,,,,,,-35.0,-20,0
+0.5,,,,,-50.0,-36.0,-20,0
+0.6,,,,,-53.0,-36.0,-21,0
+0.7,,,,,-56.0,-38.0,-22,0
+0.75,,,,,-56.0,-38.0,-22,0
+0.8,,,,,-60.0,-38.0,-24,0
+1.0,-290.0,-200.0,-130.0,-85.0,-60.0,-40.0,-26,0
+1.25,-295.0,-205.0,-135.0,-90.0,-63.0,-42.0,-28,0
+1.5,-300.0,-212.0,-140.0,-95.0,-67.0,-45.0,-32,0
+1.75,-310.0,-220.0,-145.0,-100.0,-71.0,-48.0,-34,0
+2.0,-315.0,-225.0,-150.0,-105.0,-71.0,-52.0,-38,0
+2.5,-325.0,-235.0,-160.0,-110.0,-80.0,-58.0,-42,0
+3.0,-335.0,-245.0,-170.0,-115.0,-85.0,-63.0,-48,0
+3.5,-345.0,-255.0,-180.0,-125.0,-90.0,-70.0,-53,0
+4.0,-355.0,-265.0,-190.0,-130.0,-95.0,-75.0,-60,0
+4.5,-365.0,-280.0,-200.0,-135.0,-100.0,-80.0,-63,0
+5.0,-375.0,-290.0,-212.0,-140.0,-106.0,-85.0,-71,0
+5.5,-385.0,-300.0,-224.0,-150.0,-112.0,-90.0,-75,0
+6.0,-395.0,-310.0,-236.0,-155.0,-118.0,-95.0,-80,0
+8.0,-425.0,-340.0,-265.0,-180.0,-140.0,-118.0,-100,0
diff --git a/flake.nix b/flake.nix
@@ -0,0 +1,25 @@
+{
+ description = "A very basic flake";
+
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
+ };
+
+ outputs = { self, nixpkgs }:
+ let
+ pkgs = nixpkgs.legacyPackages."x86_64-linux";
+ in
+ {
+
+ devShells."x86_64-linux".default = pkgs.mkShell {
+
+ packages = [ pkgs.python313Packages.tkinter ];
+
+ shellHook = ''
+ export PS1='\[\e[92m\](nix-shell)\[\e[0m\]\w '
+
+ '';
+ };
+ };
+}
+
diff --git a/main.py b/main.py
@@ -0,0 +1,772 @@
+import ctypes
+
+def enable_high_dpi_awareness():
+ try:
+ # Windows 8.1+
+ ctypes.windll.shcore.SetProcessDpiAwareness(2) # PROCESS_PER_MONITOR_DPI_AWARE
+ except Exception:
+ try:
+ # Windows Vista–8.0
+ ctypes.windll.user32.SetProcessDPIAware()
+ except Exception:
+ pass
+
+enable_high_dpi_awareness()
+
+
+import math
+from preamble import ThreadCalculator, ThreadDatabase, InputParser
+import tkinter as tk
+from tkinter import ttk
+import os
+import sys
+
+def resource_path(relative_path):
+ """ Get absolute path to resource, works for dev and for PyInstaller """
+ try:
+ base_path = sys._MEIPASS
+ except Exception:
+ base_path = os.path.abspath(".")
+ return os.path.join(base_path, relative_path)
+
+
+
+class App(tk.Tk):
+ def __init__(self):
+ super().__init__()
+
+
+ # Adjust scaling for high-DPI displays
+
+ dpi = self.winfo_fpixels('1i')
+ scale_factor = dpi / 72.0
+
+ # Apply scaling to Tkinter
+ self.tk.call('tk', 'scaling', scale_factor)
+
+ # Optional: also set a nicer default font
+ self.option_add("*Font", ("Segoe UI", 10))
+
+
+ self.bind_enter_to_button()
+
+
+
+ icon = tk.PhotoImage(file=resource_path("icon.png"))
+ self.iconphoto(True, icon)
+
+ self.title("Thread Calculator")
+ self.minsize(950, 400)
+
+
+ self.parser = InputParser()
+ self.db_td = ThreadDatabase(resource_path("T_d.csv"))
+ self.db_es = ThreadDatabase(resource_path("es.csv"))
+
+ self.columnconfigure(0, weight=2)
+ self.columnconfigure(1, weight=1)
+ self.rowconfigure(0, weight=7)
+ self.rowconfigure(1, weight=3)
+
+ self.container = ttk.Frame(self)
+ self.container.pack(expand=True, fill="both")
+
+ self.main_menu = self.build_main_menu()
+ self.ext_env = self.build_ext_env()
+ self.int_env = self.build_int_env()
+
+ self.show(self.main_menu)
+
+ def bind_enter_to_button(self):
+ def trigger_focused_button(event):
+ widget = self.focus_get()
+ if isinstance(widget, tk.Button) or isinstance(widget, ttk.Button):
+ widget.invoke() # Trigger the button's command
+ self.bind_all("<Return>", trigger_focused_button)
+
+ def show(self, frame):
+ frame.tkraise()
+
+
+
+ # ----------------- MAIN MENU ------------------
+ def build_main_menu(self):
+ frame = ttk.Frame(self.container)
+ frame.place(relwidth=1, relheight=1)
+
+ frame_buttons = ttk.Frame(frame)
+ frame_buttons.pack(pady=50)
+
+ # Buttons
+ btn_ext = ttk.Button(frame_buttons, text="External Thread Calculator",
+ command=lambda: self.show(self.ext_env))
+ btn_ext.pack(padx=10, pady=20)
+
+ btn_int = ttk.Button(frame_buttons, text="Internal Thread Calculator",
+ command=lambda: self.show(self.int_env))
+ btn_int.pack(padx=10)
+
+ return frame
+
+ # ---------------- EXTERNAL THREAD ENVIRONMENT ------------------
+ def build_ext_env(self):
+ frame = ttk.Frame(self.container)
+ frame.place(relwidth=1, relheight=1)
+
+ ttk.Button(frame, text="← Back", command=lambda: self.show(self.main_menu)).pack(anchor="w", pady=5, padx=5)
+
+ notebook = ttk.Notebook(frame)
+ notebook.pack(expand=True, fill="both", padx=5, pady=5)
+
+ # --- TAB 1: EXTERNAL THREAD WITH RUNOUT --- #
+ tab_runout = ttk.Frame(notebook)
+ tab_runout.columnconfigure(0, weight=1, uniform="half")
+ tab_runout.columnconfigure(1, weight=1, uniform="half")
+ tab_runout.rowconfigure(0, weight=1)
+
+ notebook.add(tab_runout, text="With Runout")
+
+ tab_runout_left = ttk.LabelFrame(tab_runout, text="Input")
+ tab_runout_left.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
+ tab_runout_left.columnconfigure(0, weight=1)
+ tab_runout_left.rowconfigure(1, weight=1)
+ tab_runout_left.rowconfigure(0, weight=3)
+
+
+ tab_runout_left_top = ttk.Frame(tab_runout_left)
+ tab_runout_left_top.grid(row=0, column=0, padx=5, pady=5, sticky="new")
+ tab_runout_left_top.columnconfigure(1, weight=1, uniform="half")
+ tab_runout_left_top.columnconfigure(0, weight=1, uniform="half")
+
+
+
+ tab_runout_left_bottom = ttk.Frame(tab_runout_left)
+ tab_runout_left_bottom.grid(row=1, column=0, padx=5, pady=5, sticky="sew")
+ tab_runout_left_bottom.columnconfigure(0, weight=1)
+ tab_runout_left_bottom.columnconfigure(1, weight=1)
+
+
+
+ tab_runout_right = ttk.LabelFrame(tab_runout, text="Output")
+ tab_runout_right.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
+ tab_runout_right.columnconfigure(0, weight=1)
+ tab_runout_right.rowconfigure(0, weight=1)
+
+ tk.Label(tab_runout_left_top, text="Designation:").grid(padx=5, pady=5, row=0, column=0, sticky="nse")
+ runout_designation_entry = ttk.Entry(tab_runout_left_top)
+ runout_designation_entry.grid(padx=5, pady=5, row=0, column=1, sticky="nsew")
+
+ tk.Label(tab_runout_left_top, text="Thread start point and its length:").grid(padx=5, pady=5, row=1, column=0, sticky="nse")
+ runout_sl_entry = ttk.Entry(tab_runout_left_top)
+ runout_sl_entry.grid(padx=5, pady=5, row=1, column=1, sticky="nsew")
+
+ # CALCULATIONS FOR EXTERNAL THREAD WITH RUNOUT
+
+ def calc_runout():
+ runout_output_box.config(state="normal")
+ runout_output_box.delete("1.0", tk.END)
+
+ designation = runout_designation_entry.get()
+ designation_parsed = self.parser.parse_designation(designation)
+ if isinstance(designation_parsed, str):
+ runout_output_box.insert(tk.END, designation_parsed + "\n")
+ runout_output_box.config(state="disabled")
+ return
+ d, pitch, grade, class_letter = designation_parsed
+
+ sl = runout_sl_entry.get()
+ sl_parsed = self.parser.parse_sl(sl)
+ if isinstance(sl_parsed, str):
+ runout_output_box.insert(tk.END, sl_parsed + "\n")
+ runout_output_box.config(state="disabled")
+ return
+ s, l = sl_parsed
+
+ T_d_value = self.db_td.lookup_value_T_d(diameter=d, pitch=pitch, grade=grade)
+ if T_d_value is None:
+ runout_output_box.insert(tk.END, "No T_d-value found in database!\n")
+ runout_output_box.config(state="disabled")
+ return
+
+ es_value = self.db_es.lookup_value_es(pitch=pitch, class_letter=class_letter)
+ if es_value is None:
+ runout_output_box.insert(tk.END, "No es-value found in database!\n")
+ runout_output_box.config(state="disabled")
+ return
+
+
+ depth = ThreadCalculator.calc_depth(pitch, es_value, T_d_value)
+ fal = ThreadCalculator.cal_fal(pitch)
+ noc = ThreadCalculator.calc_noc(pitch)
+
+ # DISPLAY RESULTS
+ runout_output_box.insert(tk.END, f"SPL: {s}\n")
+ runout_output_box.insert(tk.END, f"DM1: {d}\n")
+ runout_output_box.insert(tk.END, f"FPL: {s - l - 2.5*pitch}\n")
+ runout_output_box.insert(tk.END, f"APP: {pitch}\n")
+ runout_output_box.insert(tk.END, f"ROP: {2.5*pitch}\n")
+ runout_output_box.insert(tk.END, f"TDEP: {depth:.4f}\n")
+ runout_output_box.insert(tk.END, f"FAL: {fal}\n")
+ runout_output_box.insert(tk.END, f"IANG: 28\n")
+ if noc is None:
+ runout_output_box.insert(tk.END, f"NRC: {noc}, choose different pitch (0.5-6)!\n")
+ else:
+ runout_output_box.insert(tk.END, f"NRC: {noc-1}\n")
+ runout_output_box.insert(tk.END, f"PIT: {pitch}\n")
+ runout_output_box.insert(tk.END, f"VARI: 300103 (constant area!)\n")
+
+ runout_output_box.config(state="disabled")
+
+ def clear_runout_output():
+ runout_output_box.config(state="normal")
+ runout_output_box.delete("1.0", tk.END)
+ runout_output_box.config(state="disabled")
+
+ ttk.Button(tab_runout_left_bottom, text="Calculate", command=calc_runout).grid(padx=5, pady=5, row=5, column=0, sticky="sew")
+
+
+
+ ttk.Button(tab_runout_left_bottom, text="Clear Output", command=clear_runout_output).grid(padx=5, pady=5, row=5, column=1, sticky="sew")
+
+
+
+ runout_output_box = tk.Text(tab_runout_right)
+ runout_output_box.grid(padx=5, pady=5, row=0, column=0, sticky="nsew")
+ runout_output_box.config(state="disabled") # Read-only initially
+
+
+
+
+
+
+
+ # --- TAB 2: EXTERNAL THREAD WITH UNDERCUT --- #
+ tab_undercut = ttk.Frame(notebook)
+ tab_undercut.columnconfigure(0, weight=1, uniform="half")
+ tab_undercut.columnconfigure(1, weight=1, uniform="half")
+ tab_undercut.rowconfigure(0, weight=1)
+
+ notebook.add(tab_undercut, text="With Undercut")
+
+ tab_undercut_left = ttk.LabelFrame(tab_undercut, text="Input")
+ tab_undercut_left.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
+ tab_undercut_left.columnconfigure(0, weight=1)
+ tab_undercut_left.rowconfigure(1, weight=1)
+ tab_undercut_left.rowconfigure(0, weight=3)
+ tab_undercut_left.rowconfigure(1, weight=1)
+
+
+ tab_undercut_left_top = ttk.Frame(tab_undercut_left)
+ tab_undercut_left_top.grid(row=0, column=0, padx=5, pady=5, sticky="new")
+ tab_undercut_left_top.columnconfigure(1, weight=1, uniform="half")
+ tab_undercut_left_top.columnconfigure(0, weight=1, uniform="half")
+
+ tab_undercut_left_bottom = ttk.Frame(tab_undercut_left)
+ tab_undercut_left_bottom.grid(row=1, column=0, padx=5, pady=5, sticky="sew")
+ tab_undercut_left_bottom.columnconfigure(0, weight=1)
+ tab_undercut_left_bottom.columnconfigure(1, weight=1)
+
+ tab_undercut_right = ttk.LabelFrame(tab_undercut, text="Output")
+ tab_undercut_right.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
+ tab_undercut_right.columnconfigure(0, weight=1)
+ tab_undercut_right.rowconfigure(0, weight=1)
+
+
+
+ tk.Label(tab_undercut_left_top, text="Designation:").grid(padx=5, pady=5, row=0, column=0, sticky="nse")
+ undercut_designation_entry = ttk.Entry(tab_undercut_left_top)
+ undercut_designation_entry.grid(padx=5, pady=5, row=0, column=1, sticky="nsew")
+
+ tk.Label(tab_undercut_left_top, text="Thread start point and its length:").grid(padx=5, pady=5, row=1, column=0, sticky="nse")
+ undercut_sl_entry = ttk.Entry(tab_undercut_left_top)
+ undercut_sl_entry.grid(padx=5, pady=5, row=1, column=1, sticky="nsew")
+
+ tk.Label(tab_undercut_left_top, text="PDX-value of your insert:").grid(padx=5, pady=5, row=2, column=0, sticky="nse")
+ pdx_entry = ttk.Entry(tab_undercut_left_top)
+ pdx_entry.grid(padx=5, pady=5, row=2, column=1, sticky="nsew")
+
+ # CALCULATIONS FOR EXTERNAL THREAD WITH UNDERCUT
+
+ def calc_undercut():
+ undercut_output_box.config(state="normal")
+ undercut_output_box.delete("1.0", tk.END)
+
+ designation = undercut_designation_entry.get()
+ designation_parsed = self.parser.parse_designation(designation)
+ if isinstance(designation_parsed, str):
+ undercut_output_box.insert(tk.END, designation_parsed + "\n")
+ undercut_output_box.config(state="disabled")
+ return
+ d, pitch, grade, class_letter = designation_parsed
+
+ sl = undercut_sl_entry.get()
+ sl_parsed = self.parser.parse_sl(sl)
+ if isinstance(sl_parsed, str):
+ undercut_output_box.insert(tk.END, sl_parsed + "\n")
+ undercut_output_box.config(state="disabled")
+ return
+ s, l = sl_parsed
+
+ PDX = self.parser.validated(pdx_entry.get())
+ if PDX is None:
+ undercut_output_box.insert(tk.END, "Invalid PDX-value!\n")
+
+ T_d_value = self.db_td.lookup_value_T_d(diameter=d, pitch=pitch, grade=grade)
+ if T_d_value is None:
+ undercut_output_box.insert(tk.END, "No T_d-value found in database!\n")
+ undercut_output_box.config(state="disabled")
+ return
+
+ es_value = self.db_es.lookup_value_es(pitch=pitch, class_letter=class_letter)
+ if es_value is None:
+ undercut_output_box.insert(tk.END, "No es-value found in database!\n")
+ undercut_output_box.config(state="disabled")
+ return
+
+
+ depth = ThreadCalculator.calc_depth(pitch, es_value, T_d_value)
+ fal = ThreadCalculator.cal_fal(pitch)
+ noc = ThreadCalculator.calc_noc(pitch)
+
+ # Display results
+ undercut_output_box.insert(tk.END, f"SPL: {s}\n")
+ undercut_output_box.insert(tk.END, f"DM1: {d}\n")
+ if PDX is None:
+ undercut_output_box.insert(tk.END, "FPL: Not Calculated\n")
+ else:
+ undercut_output_box.insert(tk.END, f"FPL: {s - l + 0.5 + PDX}\n")
+ undercut_output_box.insert(tk.END, f"APP: {pitch}\n")
+ if PDX is None:
+ undercut_output_box.insert(tk.END, "ROP: Not Calculated\n")
+ else:
+ undercut_output_box.insert(tk.END, f"ROP: g1-({0.5+PDX})\n")
+ undercut_output_box.insert(tk.END, f"TDEP: {depth:.4f}\n")
+ if fal is None:
+ undercut_output_box.insert(tk.END, f"FAL: {fal}, choose different pitch (0.5-6)!\n")
+ else:
+ undercut_output_box.insert(tk.END, f"FAL: {fal}\n")
+ undercut_output_box.insert(tk.END, f"IANG: 28\n")
+ if noc is None:
+ undercut_output_box.insert(tk.END, f"NRC: {noc}, choose different pitch (0.5-6)!\n")
+ else:
+ undercut_output_box.insert(tk.END, f"NRC: {noc-1}\n")
+ undercut_output_box.insert(tk.END, f"PIT: {pitch}\n")
+ undercut_output_box.insert(tk.END, f"VARI: 300103 (constant area!)\n")
+
+ undercut_output_box.config(state="disabled")
+
+ def clear_undercut_output():
+ undercut_output_box.config(state="normal")
+ undercut_output_box.delete("1.0", tk.END)
+ undercut_output_box.config(state="disabled")
+
+
+
+ ttk.Button(tab_undercut_left_bottom, text="Calculate", command=calc_undercut).grid(padx=5, pady=5, row=0, column=0, sticky="sew")
+
+ ttk.Button(tab_undercut_left_bottom, text="Clear Output", command=clear_undercut_output).grid(padx=5, pady=5, row=0, column=1, sticky="sew")
+
+ undercut_output_box = tk.Text(tab_undercut_right, height=20, width=80)
+ undercut_output_box.grid(padx=5, pady=5, row=0, column=0, sticky="nsew")
+ undercut_output_box.config(state="disabled") # Read-only initially
+
+ return frame
+
+
+
+
+
+
+ # ---------------- INTERNAL THREAD ENVIRONMENT ------------------
+ def build_int_env(self):
+ frame = ttk.Frame(self.container)
+ frame.place(relwidth=1, relheight=1)
+
+ ttk.Button(frame, text="← Back", command=lambda: self.show(self.main_menu)).pack(anchor="w", pady=5, padx=5)
+
+ notebook = ttk.Notebook(frame)
+ notebook.pack(expand=True, fill="both", padx=5, pady=10)
+
+
+ # --- TAB 1: CENTER DRILL --- #
+ tab_center_drill = ttk.Frame(notebook)
+ tab_center_drill.columnconfigure(0, weight=1, uniform="half")
+ tab_center_drill.columnconfigure(1, weight=1, uniform="half")
+ tab_center_drill.rowconfigure(0, weight=1)
+
+ notebook.add(tab_center_drill, text="Center Drill")
+
+ tab_center_drill_left = ttk.LabelFrame(tab_center_drill, text="Input")
+ tab_center_drill_left.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
+ tab_center_drill_left.columnconfigure(0, weight=1)
+ tab_center_drill_left.rowconfigure(1, weight=1)
+ tab_center_drill_left.rowconfigure(0, weight=1)
+
+
+ tab_center_drill_left_top = ttk.Frame(tab_center_drill_left)
+ tab_center_drill_left_top.grid(row=0, column=0, padx=5, pady=5, sticky="new")
+ tab_center_drill_left_top.columnconfigure(1, weight=1, uniform="half")
+ tab_center_drill_left_top.columnconfigure(0, weight=1, uniform="half")
+
+
+
+ tab_center_drill_left_bottom = ttk.Frame(tab_center_drill_left)
+ tab_center_drill_left_bottom.grid(row=1, column=0, padx=5, pady=5, sticky="sew")
+ tab_center_drill_left_bottom.columnconfigure(0, weight=1)
+ tab_center_drill_left_bottom.columnconfigure(1, weight=1)
+
+
+ tab_center_drill_right = ttk.LabelFrame(tab_center_drill, text="Output")
+ tab_center_drill_right.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
+ tab_center_drill_right.columnconfigure(0, weight=1)
+ tab_center_drill_right.rowconfigure(0, weight=1)
+
+
+
+ tk.Label(tab_center_drill_left_top, text="Designation:").grid(padx=5, pady=5, row=0, column=0, sticky="nse")
+ center_drill_designation_entry = ttk.Entry(tab_center_drill_left_top)
+ center_drill_designation_entry.grid(padx=5, pady=5, row=0, column=1, sticky="nsew")
+
+ tk.Label(tab_center_drill_left_top, text="Hole start point:").grid(padx=5, pady=5, row=1, column=0, sticky="nse")
+ center_drill_s_entry = ttk.Entry(tab_center_drill_left_top)
+ center_drill_s_entry.grid(padx=5, pady=5, row=1, column=1, sticky="nsew")
+
+ tk.Label(tab_center_drill_left_top, text="Center drill cone angle:").grid(padx=5, pady=5, row=2, column=0, sticky="nse")
+ center_drill_angle_entry = ttk.Entry(tab_center_drill_left_top)
+ center_drill_angle_entry.grid(padx=5, pady=5, row=2, column=1, sticky="nsew")
+
+ # CALCULATIONS FOR CENTER DRILL
+
+ def calc_center_drill():
+ center_drill_output_box.config(state="normal")
+ center_drill_output_box.delete("1.0", tk.END)
+
+ designation = center_drill_designation_entry.get()
+ designation_parsed = self.parser.parse_designation(designation)
+ if isinstance(designation_parsed, str):
+ center_drill_output_box.insert(tk.END, designation_parsed + "\n")
+ center_drill_output_box.config(state="disabled")
+ return
+ d, pitch, grade, class_letter = designation_parsed
+
+ d2 = d - pitch
+
+ s = self.parser.validated(center_drill_s_entry.get())
+ if s is None:
+ center_drill_output_box.insert(tk.END, "Invalid hole start point!\n")
+ center_drill_output_box.config(state="disabled")
+ return
+
+
+ a1 = self.parser.validated(center_drill_angle_entry.get())
+ if a1 is None:
+ center_drill_output_box.insert(tk.END, "Invalid center drill cone angle!\n")
+ center_drill_output_box.config(state="disabled")
+ return
+
+ h1 = (0.65*d2)/(2*math.tan(math.radians(a1/2)))
+
+ # DISPLAY RESULTS
+
+ center_drill_output_box.insert(tk.END, f"RFP: {s}\n")
+ center_drill_output_box.insert(tk.END, f"DP: {s-h1: .5f}\n")
+ center_drill_output_box.config(state="disabled")
+
+ def clear_center_drill_output():
+ center_drill_output_box.config(state="normal")
+ center_drill_output_box.delete("1.0", tk.END)
+ center_drill_output_box.config(state="disabled")
+
+
+ ttk.Button(tab_center_drill_left_bottom, text="Calculate", command=calc_center_drill).grid(padx=5, pady=5, row=0, column=0, sticky="sew")
+
+ 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")
+
+
+ center_drill_output_box = tk.Text(tab_center_drill_right, height=20, width=80)
+ center_drill_output_box.grid(padx=5, pady=5, row=0, column=0, sticky="nsew")
+ center_drill_output_box.config(state="disabled") # Read-only initially
+
+
+
+
+ # --- TAB 2: DEEP HOLE DRILL --- #
+ tab_deep_hole_drill = ttk.Frame(notebook)
+ tab_deep_hole_drill.columnconfigure(0, weight=1, uniform="half")
+ tab_deep_hole_drill.columnconfigure(1, weight=1, uniform="half")
+ tab_deep_hole_drill.rowconfigure(0, weight=1)
+
+ notebook.add(tab_deep_hole_drill, text="Deep Hole Drill")
+
+ tab_deep_hole_drill_left = ttk.LabelFrame(tab_deep_hole_drill, text="Input")
+ tab_deep_hole_drill_left.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
+ tab_deep_hole_drill_left.columnconfigure(0, weight=1)
+ tab_deep_hole_drill_left.rowconfigure(1, weight=1)
+ tab_deep_hole_drill_left.rowconfigure(0, weight=1)
+ tab_deep_hole_drill_left.rowconfigure(1, weight=1)
+
+ tab_deep_hole_drill_left_top = ttk.Frame(tab_deep_hole_drill_left)
+ tab_deep_hole_drill_left_top.grid(row=0, column=0, padx=5, pady=5, sticky="new")
+ tab_deep_hole_drill_left_top.columnconfigure(1, weight=1, uniform="half")
+ tab_deep_hole_drill_left_top.columnconfigure(0, weight=1, uniform="half")
+
+ tab_deep_hole_drill_left_bottom = ttk.Frame(tab_deep_hole_drill_left)
+ tab_deep_hole_drill_left_bottom.grid(row=1, column=0, padx=5, pady=5, sticky="sew")
+ tab_deep_hole_drill_left_bottom.columnconfigure(0, weight=1)
+ tab_deep_hole_drill_left_bottom.columnconfigure(1, weight=1)
+
+ tab_deep_hole_drill_right = ttk.LabelFrame(tab_deep_hole_drill, text="Output")
+ tab_deep_hole_drill_right.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
+ tab_deep_hole_drill_right.columnconfigure(0, weight=1)
+ tab_deep_hole_drill_right.rowconfigure(0, weight=1)
+
+
+
+ tk.Label(tab_deep_hole_drill_left_top, text="Designation:").grid(padx=5, pady=5, row=0, column=0, sticky="nse")
+ deep_hole_drill_designation_entry = ttk.Entry(tab_deep_hole_drill_left_top)
+ deep_hole_drill_designation_entry.grid(padx=5, pady=5, row=0, column=1, sticky="nsew")
+
+ 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")
+ deep_hole_drill_form_entry = ttk.Entry(tab_deep_hole_drill_left_top)
+ deep_hole_drill_form_entry.grid(padx=5, pady=5, row=1, column=1, sticky="nsew")
+
+ 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")
+ deep_hole_drill_sl_entry = ttk.Entry(tab_deep_hole_drill_left_top)
+ deep_hole_drill_sl_entry.grid(padx=5, pady=5, row=2, column=1, sticky="nsew")
+
+ 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")
+ deep_hole_drill_angle_entry = ttk.Entry(tab_deep_hole_drill_left_top)
+ deep_hole_drill_angle_entry.grid(padx=5, pady=5, row=3, column=1, sticky="nsew")
+
+ # CALCULATIONS FOR DEEP HOLE DRILLING
+
+ def calc_deep_hole_drill():
+ deep_hole_drill_output_box.config(state="normal")
+ deep_hole_drill_output_box.delete("1.0", tk.END)
+
+ designation = deep_hole_drill_designation_entry.get()
+ designation_parsed = self.parser.parse_designation(designation)
+ if isinstance(designation_parsed, str):
+ deep_hole_drill_output_box.insert(tk.END, designation_parsed + "\n")
+ deep_hole_drill_output_box.config(state="disabled")
+ return
+ d, pitch, grade, class_letter = designation_parsed
+
+ d2 = d - pitch
+
+ form = deep_hole_drill_form_entry.get().lower()
+ form_range = {
+ "a": 8,
+ "b": 5,
+ "c": 3,
+ "d": 5,
+ "e": 2
+ }
+ if not isinstance(form, str):
+ deep_hole_drill_output_box.insert(tk.END, "Invalid type!\n")
+ deep_hole_drill_output_box.config(state="disabled")
+ return
+ elif form not in form_range:
+ deep_hole_drill_output_box.insert(tk.END, "Entered tool form not found!\n")
+ deep_hole_drill_output_box.config(state="disabled")
+ return
+ k = form_range.get(form)
+
+ sl = deep_hole_drill_sl_entry.get()
+ sl_parsed = self.parser.parse_sl(sl)
+ if isinstance(sl_parsed, str):
+ deep_hole_drill_output_box.insert(tk.END, sl_parsed + "\n")
+ deep_hole_drill_output_box.config(state="disabled")
+ return
+ s, l = sl_parsed
+
+ a2 = self.parser.validated(deep_hole_drill_angle_entry.get())
+ if a2 is None:
+ deep_hole_drill_output_box.insert(tk.END, "Invalid deep hole drill cone angle!\n")
+ deep_hole_drill_output_box.config(state="disabled")
+ return
+
+ h_2 = ThreadCalculator.half_ceil(l + (k+2)*pitch + (d2)/(2*math.tan(math.radians(a2/2))))
+ h2 = s - h_2
+
+ deep_hole_drill_output_box.insert(tk.END, f"RFP: {s}\n")
+ deep_hole_drill_output_box.insert(tk.END, f"DP: {h2: .3f}\n")
+ fdep = ThreadCalculator.depth_per_storke(h_2)
+ if fdep is None:
+ deep_hole_drill_output_box.insert(tk.END, "FDEP: Invalid depth!\n")
+ else:
+ deep_hole_drill_output_box.insert(tk.END, f"FDEP: {s - fdep}\n")
+ deep_hole_drill_output_box.config(state="disabled")
+
+ def clear_deep_hole_drill_output():
+ deep_hole_drill_output_box.config(state="normal")
+ deep_hole_drill_output_box.delete("1.0", tk.END)
+ deep_hole_drill_output_box.config(state="disabled")
+
+ def deep_hole_drill_get_data():
+ if center_drill_designation_entry.get():
+ deep_hole_drill_designation_entry.delete(0, tk.END)
+ deep_hole_drill_designation_entry.insert(0, center_drill_designation_entry.get())
+ if center_drill_s_entry.get():
+ deep_hole_drill_sl_entry.delete(0, tk.END)
+ deep_hole_drill_sl_entry.insert(0, center_drill_s_entry.get()+",")
+
+
+ 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")
+
+ 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")
+ 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")
+
+
+ deep_hole_drill_output_box = tk.Text(tab_deep_hole_drill_right, height=20, width=80)
+ deep_hole_drill_output_box.grid(padx=5, pady=5, row=0, column=0, sticky="nsew")
+ deep_hole_drill_output_box.config(state="disabled") # Read-only initially
+
+
+
+ # --- TAB 3: TAPPING --- #
+ tab_tapping = ttk.Frame(notebook)
+ tab_tapping.columnconfigure(0, weight=1, uniform="half")
+ tab_tapping.columnconfigure(1, weight=1, uniform="half")
+ tab_tapping.rowconfigure(0, weight=1)
+
+ notebook.add(tab_tapping, text="Tapping")
+
+ tab_tapping_left = ttk.LabelFrame(tab_tapping, text="Input")
+ tab_tapping_left.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")
+ tab_tapping_left.columnconfigure(0, weight=1)
+ tab_tapping_left.rowconfigure(1, weight=1)
+ tab_tapping_left.rowconfigure(0, weight=1)
+ tab_tapping_left.rowconfigure(1, weight=1)
+
+ tab_tapping_left_top = ttk.Frame(tab_tapping_left)
+ tab_tapping_left_top.grid(row=0, column=0, padx=5, pady=5, sticky="new")
+ tab_tapping_left_top.columnconfigure(1, weight=1, uniform="half")
+ tab_tapping_left_top.columnconfigure(0, weight=1, uniform="half")
+
+
+ tab_tapping_left_bottom = ttk.Frame(tab_tapping_left)
+ tab_tapping_left_bottom.grid(row=1, column=0, padx=5, pady=5, sticky="sew")
+ tab_tapping_left_bottom.columnconfigure(0, weight=1)
+ tab_tapping_left_bottom.columnconfigure(1, weight=1)
+
+ tab_tapping_right = ttk.LabelFrame(tab_tapping, text="Output")
+ tab_tapping_right.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
+ tab_tapping_right.columnconfigure(0, weight=1)
+ tab_tapping_right.rowconfigure(0, weight=1)
+
+
+ tk.Label(tab_tapping_left_top, text="Designation:").grid(padx=5, pady=5, row=0, column=0, sticky="nse")
+ tapping_designation_entry = ttk.Entry(tab_tapping_left_top)
+ tapping_designation_entry.grid(padx=5, pady=5, row=0, column=1, sticky="nsew")
+
+ tk.Label(tab_tapping_left_top, text="Tapping tool form (e.g., C):").grid(padx=5, pady=5, row=1, column=0, sticky="nse")
+ tapping_form_entry = ttk.Entry(tab_tapping_left_top)
+ tapping_form_entry.grid(padx=5, pady=5, row=1, column=1, sticky="nsew")
+
+ tk.Label(tab_tapping_left_top, text="Hole start point and thread length:").grid(padx=5, pady=5, row=2, column=0, sticky="nse")
+ tapping_sl_entry = ttk.Entry(tab_tapping_left_top)
+ tapping_sl_entry.grid(padx=5, pady=5, row=2, column=1, sticky="nsew")
+
+ # CALCULATIONS FOR TAPPING
+
+ def calc_tapping():
+ tapping_output_box.config(state="normal")
+ tapping_output_box.delete("1.0", tk.END)
+
+ designation = tapping_designation_entry.get()
+ designation_parsed = self.parser.parse_designation(designation)
+ if isinstance(designation_parsed, str):
+ tapping_output_box.insert(tk.END, designation_parsed + "\n")
+ tapping_output_box.config(state="disabled")
+ return
+ d, pitch, grade, class_letter = designation_parsed
+
+ form = tapping_form_entry.get().lower()
+ form_range = {
+ "a": 8,
+ "b": 5,
+ "c": 3,
+ "d": 5,
+ "e": 2
+ }
+ if not isinstance(form, str):
+ tapping_output_box.insert(tk.END, "Invalid type!\n")
+ tapping_output_box.config(state="disabled")
+ return
+ elif form not in form_range:
+ tapping_output_box.insert(tk.END, "Entered tool form not found!\n")
+ tapping_output_box.config(state="disabled")
+ return
+ k = form_range.get(form)
+
+ sl = tapping_sl_entry.get()
+ sl_parsed = self.parser.parse_sl(sl)
+ if isinstance(sl_parsed, str):
+ tapping_output_box.insert(tk.END, sl_parsed + "\n")
+ tapping_output_box.config(state="disabled")
+ return
+ s, l = sl_parsed
+
+ h4 = s - (l + k*pitch)
+
+ tapping_output_box.insert(tk.END, f"RFP: {s}\n")
+ tapping_output_box.insert(tk.END, f"DP: {h4}\n")
+ tapping_output_box.insert(tk.END, "SDAC: 5\n")
+ tapping_output_box.insert(tk.END, f"PIT: {pitch}\n")
+
+
+ tapping_output_box.config(state="disabled")
+
+ def clear_tapping_output():
+ tapping_output_box.config(state="normal")
+ tapping_output_box.delete("1.0", tk.END)
+ tapping_output_box.config(state="disabled")
+
+ def get_tapping_data():
+ if deep_hole_drill_designation_entry.get():
+ tapping_designation_entry.delete(0, tk.END)
+ tapping_designation_entry.insert(0, deep_hole_drill_designation_entry.get())
+
+ if (not deep_hole_drill_designation_entry.get()) and center_drill_designation_entry.get():
+ tapping_designation_entry.delete(0, tk.END)
+ tapping_designation_entry.insert(0, center_drill_designation_entry.get())
+
+ if (not deep_hole_drill_sl_entry.get()) and center_drill_s_entry.get():
+ tapping_sl_entry.delete(0, tk.END)
+ tapping_sl_entry.insert(0, center_drill_s_entry.get()+",")
+
+ if deep_hole_drill_form_entry.get():
+ tapping_form_entry.delete(0, tk.END)
+ tapping_form_entry.insert(0, deep_hole_drill_form_entry.get())
+
+ if deep_hole_drill_sl_entry.get():
+ tapping_sl_entry.delete(0, tk.END)
+ tapping_sl_entry.insert(0, deep_hole_drill_sl_entry.get())
+
+ ttk.Button(tab_tapping_left_bottom, text="Get Data", command=get_tapping_data).grid(padx=5, pady=5, row=0, column=0, sticky="sew")
+
+ ttk.Button(tab_tapping_left_bottom, text="Calculate", command=calc_tapping).grid(padx=5, pady=5, row=1, column=0, sticky="sew")
+
+ ttk.Button(tab_tapping_left_bottom, text="Clear Output", command=clear_tapping_output).grid(padx=5, pady=5, row=1, column=1, sticky="sew")
+
+
+ tapping_output_box = tk.Text(tab_tapping_right)
+ tapping_output_box.grid(padx=5, pady=5, row=0, column=0, sticky="nsew")
+ tapping_output_box.config(state="disabled") # Read-only initially
+
+
+
+ return frame
+
+
+
+
+
+
+
+
+if __name__ == "__main__":
+ app = App()
+ app.mainloop()
+
diff --git a/preamble.py b/preamble.py
@@ -0,0 +1,257 @@
+import pandas as pd
+import math
+
+
+import os
+import sys
+
+def resource_path(relative_path):
+ """ Get absolute path to resource, works for dev and for PyInstaller """
+ try:
+
+ base_path = sys._MEIPASS
+ except Exception:
+
+ base_path = os.path.abspath(".")
+
+ return os.path.join(base_path, relative_path)
+
+class ThreadDatabase:
+ def __init__(self, csv_path):
+ full_path = resource_path(csv_path)
+ self.df = pd.read_csv(full_path)
+
+ def lookup_value_T_d(self, diameter, pitch, grade):
+
+ row = self.df[( self.df["dmin"] < diameter) & ( diameter <= self.df["dmax"] ) & (self.df["p"] == pitch)]
+
+ if row.empty:
+ return None
+
+ value = 0.001*row.iat[0, grade]
+
+ if pd.isna(value):
+ return None
+
+ return float(value)
+
+ def lookup_value_es(self, pitch, class_letter):
+
+ value1=-0.001*self.df.loc[pitch, class_letter]
+
+ if pd.isna(value1):
+ return None
+
+ return float(value1)
+
+
+class InputParser:
+ def __init__(self):
+ self.defaults = pd.read_csv(resource_path("defaults.csv"))
+
+ def validated(self, value):
+ try:
+ value_float = float(value)
+ return value_float
+ except ValueError:
+ return None
+
+ def parse_designation(self, designation):
+ """Return (diameter and pitch) with defaults if needed."""
+
+ designation = designation.lower().replace("x", "×")
+
+ # Minimal format (e.g. M10)
+ if ("×" not in designation) and ("-" not in designation):
+ d = self.validated(designation.replace("m", ""))
+ # check format
+ if d is None:
+ return "Invalid diameter format!"
+
+ #check availability
+ elif not self.defaults["d"].eq(d).any():
+ return "Diameter not available!"
+
+ else:
+ row=self.defaults[self.defaults["d"]==d]
+ pitch = row.iat[0, 1]
+
+ grade = 6 # default tolerance grade
+ if d <= 1.4:
+ class_letter = "h" # default class up to and including 1.4 nominal diameter
+ else:
+ class_letter = "g" # default class for larger diameters
+ return d, pitch, grade, class_letter
+
+
+ # Semi-full format 1 (e.g. M10×1.5)
+
+ elif ("×" in designation) and ("-" not in designation):
+ d = self.validated(designation.replace("m", "").split("×")[0])
+
+ # check format
+ if d is None:
+ return "Invalid diameter format!"
+
+ # check availability
+ elif not self.defaults["d"].eq(d).any():
+ return "Diameter not available!"
+
+ pitch = self.validated(designation.split("×")[1])
+
+ # check format
+ if pitch is None:
+ return "Invalid pitch format!"
+
+ # check availability
+ elif not self.defaults["p"].eq(pitch).any():
+ return "Pitch not available!"
+
+ # check compatibility
+ elif pitch > self.defaults[self.defaults["d"]==d].iat[0, 1]:
+ return "Incompatible pitch for given diameter!"
+
+ grade = 6 # default tolerance grade
+ if d <= 1.4:
+ class_letter = "h" # default class up to and including 1.4 nominal diameter
+ else:
+ class_letter = "g" # default class for larger diameters
+ return d, pitch, grade, class_letter
+
+ # Semi-full format 2 (e.g. M10-6g)
+ elif ("×" not in designation) and ("-" in designation):
+ try:
+ before_dash, after_dash = designation.split("-")
+ d = self.validated(before_dash.replace("m", ""))
+ if d is None:
+ return "Invalid diameter format!"
+ elif not self.defaults["d"].eq(d).any():
+ return "Diameter not available!"
+ else:
+ row=self.defaults[self.defaults["d"]==d]
+ pitch = row.iat[0, 1]
+ try:
+ grade = int(''.join(filter(str.isdigit, after_dash)))
+ if (grade < 3) or (grade > 9):
+ return "Invalid tolerance grade!"
+ except ValueError:
+ return "Invalid tolerance grade!"
+
+ try:
+ class_letter = ''.join(filter(str.isalpha, after_dash))
+ if len(class_letter) != 1:
+ return "Invalid class letter!"
+ except ValueError:
+ return "Invalid class letter!"
+ return d, pitch, grade, class_letter
+ except ValueError:
+ print("Error")
+ return None
+
+ # Full format (e.g. M10×1.5-6g)
+ else:
+ try:
+ before_dash, after_dash = designation.split("-")
+ d = self.validated(before_dash.replace("m", "").split("×")[0])
+ if d is None:
+ return "Invalid diameter format!"
+ elif not self.defaults["d"].eq(d).any():
+ return "Diameter not available!"
+ pitch = self.validated(before_dash.split("×")[1])
+ if pitch is None:
+ return "Invalid pitch format!"
+ elif not self.defaults["p"].eq(pitch).any():
+ return "Pitch not available!"
+ elif pitch > self.defaults[self.defaults["d"]==d].iat[0, 1]:
+ return "Incompatible pitch for given diameter!"
+ grade = int(''.join(filter(str.isdigit, after_dash)))
+ if (grade < 3) or (grade > 9):
+ return "Invalid tolerance grade!"
+ class_letter = ''.join(filter(str.isalpha, after_dash))
+ if len(class_letter) > 1:
+ return "Invalid class letter!"
+ return d, pitch, grade, class_letter
+ except Exception:
+ print("ERROR: invalid thread format.")
+ return None
+
+
+
+ def parse_sl(self, sl): # sl = "start,length"
+ """Return (start and thread length)."""
+
+ sl = sl.replace(" ", "")
+
+ start = self.validated(sl.split(",")[0])
+ if start is None:
+ return "Invalid start point"
+
+ length = self.validated(sl.split(",")[1])
+ if length is None:
+ return "Invalid length"
+ return start, length
+
+
+
+
+
+
+
+
+class ThreadCalculator:
+
+ # Functions for external thread
+ @staticmethod
+ def calc_depth(pitch, es, T_d):
+ return 0.6134 * pitch + 0.5*es + 0.25*T_d
+
+
+ ranges_fal = [
+ ((0.5, 0.5), 0.06),
+ ((0.75, 0.75), 0.07),
+ ((0.8, 4), 0.08),
+ ((4.5, 6), 0.1)
+ ]
+
+ @staticmethod
+ def cal_fal(pitch):
+ for (pmin, pmax), value in ThreadCalculator.ranges_fal:
+ if pmin <= pitch <= pmax:
+ return value
+ return None
+
+
+ ranges_noc = [
+ ((0.5, 0.8), 4),
+ ((1, 1), 5),
+ ((1.25, 1.5), 6),
+ ((1.75, 2), 8),
+ ((2.5, 2.5), 10),
+ ((3, 3.5), 12),
+ ((4, 5), 14),
+ ((5.5, 6), 16)
+ ]
+
+ @staticmethod
+ def calc_noc(pitch):
+ for (pmin, pmax), value in ThreadCalculator.ranges_noc:
+ if pmin <= pitch <= pmax:
+ return value
+ return None
+
+
+ # Functions for internal thread
+
+ @staticmethod
+ def half_ceil(x):
+ return math.ceil(2*x)/2
+
+ @staticmethod
+ def depth_per_storke(depth):
+ if (depth > 0) and (depth <= 6):
+ return depth
+ elif depth > 6:
+ strokes = math.ceil(depth/6)
+ return math.ceil(depth/strokes)
+ else:
+ return None