thcal

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

preamble.py (8450B)


      1 import pandas as pd
      2 import math
      3 
      4 
      5 import os
      6 import sys
      7 
      8 def resource_path(relative_path):
      9     """ Get absolute path to resource, works for dev and for PyInstaller """
     10     try:
     11         
     12         base_path = sys._MEIPASS
     13     except Exception:
     14         
     15         base_path = os.path.abspath(".")
     16     
     17     return os.path.join(base_path, relative_path)
     18 
     19 class ThreadDatabase:
     20     def __init__(self, csv_path):
     21         full_path = resource_path(csv_path)         
     22         self.df = pd.read_csv(full_path)
     23 
     24     def lookup_value_T_d(self, diameter, pitch, grade):
     25 
     26         row = self.df[( self.df["dmin"] < diameter) & ( diameter <= self.df["dmax"] ) & (self.df["p"] == pitch)]
     27 
     28         if row.empty:
     29             return None
     30 
     31         value = 0.001*row.iat[0, grade]
     32 
     33         if pd.isna(value):
     34             return None
     35 
     36         return float(value)
     37         
     38     def lookup_value_es(self, pitch, class_letter):
     39         
     40         value1=-0.001*self.df.loc[pitch, class_letter]
     41         
     42         if pd.isna(value1):
     43             return None
     44             
     45         return float(value1)
     46 
     47 
     48 class InputParser:
     49     def __init__(self):
     50         self.defaults = pd.read_csv(resource_path("defaults.csv"))
     51 
     52     def validated(self, value):
     53         try:
     54              value_float = float(value)
     55              return value_float
     56         except ValueError:
     57              return None
     58          
     59     def parse_designation(self, designation):
     60         """Return (diameter and pitch) with defaults if needed."""
     61 
     62         designation = designation.lower().replace("x", "×")
     63 
     64         # Minimal format (e.g. M10)
     65         if ("×" not in designation) and ("-" not in designation):
     66             d = self.validated(designation.replace("m", ""))
     67             # check format
     68             if d is None:
     69                 return "Invalid diameter format!" 
     70                 
     71             #check availability
     72             elif not self.defaults["d"].eq(d).any():
     73                 return "Diameter not available!" 
     74                 
     75             else:
     76                 row=self.defaults[self.defaults["d"]==d]
     77                 pitch = row.iat[0, 1]
     78             
     79             grade = 6          # default tolerance grade
     80             if d <= 1.4:
     81                 class_letter = "h" # default class up to and including 1.4 nominal diameter
     82             else:
     83                 class_letter = "g" # default class for larger diameters
     84             return d, pitch, grade, class_letter
     85                     
     86         
     87         # Semi-full format 1 (e.g. M10×1.5)   
     88             
     89         elif ("×" in designation) and ("-" not in designation):
     90             d = self.validated(designation.replace("m", "").split("×")[0])
     91             
     92             # check format
     93             if d is None:
     94                 return "Invalid diameter format!" 
     95                 
     96             # check availability
     97             elif not self.defaults["d"].eq(d).any():
     98                 return "Diameter not available!" 
     99                 
    100             pitch = self.validated(designation.split("×")[1])
    101 
    102             # check format
    103             if pitch is None:
    104                 return "Invalid pitch format!" 
    105                 
    106             # check availability
    107             elif not self.defaults["p"].eq(pitch).any():
    108                 return "Pitch not available!" 
    109 
    110             # check compatibility
    111             elif pitch > self.defaults[self.defaults["d"]==d].iat[0, 1]:
    112                 return "Incompatible pitch for given diameter!" 
    113             
    114             grade = 6          # default tolerance grade
    115             if d <= 1.4:
    116                 class_letter = "h" # default class up to and including 1.4 nominal diameter
    117             else:
    118                 class_letter = "g" # default class for larger diameters
    119             return d, pitch, grade, class_letter
    120         
    121         # Semi-full format 2 (e.g. M10-6g)
    122         elif ("×" not in designation) and ("-" in designation):
    123             try:
    124                 before_dash, after_dash = designation.split("-")
    125                 d = self.validated(before_dash.replace("m", ""))
    126                 if d is None:
    127                     return "Invalid diameter format!" 
    128                 elif not self.defaults["d"].eq(d).any():
    129                     return "Diameter not available!" 
    130                 else:
    131                     row=self.defaults[self.defaults["d"]==d]
    132                     pitch = row.iat[0, 1]
    133                 try:
    134                     grade = int(''.join(filter(str.isdigit, after_dash)))
    135                     if (grade < 3) or (grade > 9):
    136                         return "Invalid tolerance grade!" 
    137                 except ValueError:
    138                     return "Invalid tolerance grade!" 
    139             
    140                 try:
    141                     class_letter = ''.join(filter(str.isalpha, after_dash))
    142                     if len(class_letter) != 1:
    143                         return "Invalid class letter!" 
    144                 except ValueError:
    145                     return "Invalid class letter!" 
    146                 return d, pitch, grade, class_letter
    147             except ValueError:
    148                 print("Error")
    149                 return None
    150         
    151         # Full format (e.g. M10×1.5-6g)
    152         else:
    153             try:
    154                 before_dash, after_dash = designation.split("-")
    155                 d = self.validated(before_dash.replace("m", "").split("×")[0])
    156                 if d is None:
    157                     return "Invalid diameter format!" 
    158                 elif not self.defaults["d"].eq(d).any():
    159                     return "Diameter not available!" 
    160                 pitch = self.validated(before_dash.split("×")[1])
    161                 if pitch is None:
    162                     return "Invalid pitch format!" 
    163                 elif not self.defaults["p"].eq(pitch).any():
    164                     return "Pitch not available!" 
    165                 elif pitch > self.defaults[self.defaults["d"]==d].iat[0, 1]:
    166                     return "Incompatible pitch for given diameter!" 
    167                 grade = int(''.join(filter(str.isdigit, after_dash)))
    168                 if (grade < 3) or (grade > 9):
    169                     return "Invalid tolerance grade!" 
    170                 class_letter = ''.join(filter(str.isalpha, after_dash))
    171                 if len(class_letter) > 1:
    172                     return "Invalid class letter!"  
    173                 return d, pitch, grade, class_letter
    174             except Exception:
    175                 print("ERROR: invalid thread format.")
    176                 return None
    177 
    178 
    179             
    180     def parse_sl(self, sl): # sl = "start,length"
    181         """Return (start and thread length)."""
    182         
    183         sl = sl.replace(" ", "")
    184     
    185         start = self.validated(sl.split(",")[0])
    186         if start is None:
    187             return "Invalid start point" 
    188         
    189         length = self.validated(sl.split(",")[1])
    190         if length is None:
    191             return "Invalid length" 
    192         return start, length
    193         
    194        
    195             
    196         
    197             
    198 
    199 
    200 
    201 class ThreadCalculator:
    202 
    203     # Functions for external thread
    204     @staticmethod
    205     def calc_depth(pitch, es, T_d):
    206         return 0.6134 * pitch + 0.5*es + 0.25*T_d
    207 
    208 
    209     ranges_fal = [
    210         ((0.5, 0.5), 0.06),
    211         ((0.75, 0.75), 0.07),
    212         ((0.8, 4), 0.08),
    213         ((4.5, 6), 0.1)
    214     ]
    215     
    216     @staticmethod
    217     def cal_fal(pitch):
    218         for (pmin, pmax), value in ThreadCalculator.ranges_fal:
    219             if pmin <= pitch <= pmax:
    220                 return value
    221         return None
    222 
    223 
    224     ranges_noc = [
    225         ((0.5, 0.8), 4),
    226         ((1, 1), 5),
    227         ((1.25, 1.5), 6),
    228         ((1.75, 2), 8),
    229         ((2.5, 2.5), 10),
    230         ((3, 3.5), 12),
    231         ((4, 5), 14),
    232         ((5.5, 6), 16)
    233     ]
    234     
    235     @staticmethod
    236     def calc_noc(pitch):
    237         for (pmin, pmax), value in ThreadCalculator.ranges_noc:
    238             if pmin <= pitch <= pmax:
    239                 return value
    240         return None
    241     
    242     
    243     # Functions for internal thread
    244 
    245     @staticmethod
    246     def half_ceil(x):
    247         return math.ceil(2*x)/2
    248 
    249     @staticmethod
    250     def depth_per_storke(depth):
    251         if (depth > 0) and (depth <= 6):
    252             return depth
    253         elif depth > 6:
    254             strokes = math.ceil(depth/6)
    255             return math.ceil(depth/strokes)
    256         else:
    257             return None