qt_tstat.py
1 #!/usr/bin/env python 2 # Qtvcp 3 # 4 # Copyright (c) 2017 Chris Morley <chrisinnanaimo@hotmail.com> 5 # 6 # This program is free software: you can redistribute it and/or modify 7 # it under the terms of the GNU General Public License as published by 8 # the Free Software Foundation, either version 2 of the License, or 9 # (at your option) any later version. 10 # 11 # This program is distributed in the hope that it will be useful, 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 # GNU General Public License for more details. 15 # 16 ############################################################################### 17 18 import os 19 import hashlib 20 21 from qtvcp.core import Status, Info, Action 22 # Set up logging 23 import logger 24 25 STATUS = Status() 26 INFO = Info() 27 ACTION = Action() 28 LOG = logger.getLogger(__name__) 29 # Set the log level for this module 30 LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL 31 32 KEYWORDS = ['T', 'P', 'X', 'Y', 'Z', 'A', 'B', 'C', 'U', 'V', 'W', 'D', 'I', 'J', 'Q', ';'] 33 34 35 class _TStat(object): 36 37 def __init__(self): 38 # only initialize once for all instances 39 if self.__class__._instanceNum >=1: 40 return 41 self.__class__._instanceNum += 1 42 self._delay = 0 43 self._hash_code = None 44 self.NUM = 0 45 self.POCKET = 1 46 self.X = 2 47 self.Y = 3 48 self.Z = 4 49 self.A = 5 50 self.B = 6 51 self.C = 7 52 self.U = 8 53 self.V = 9 54 self.W = 10 55 self.DIAMETER = 11 56 self.FRONTANGLE = 12 57 self.BACKANGLE = 13 58 self.ORIENTATION = 14 59 self.COMMENTS = 15 60 self.hash_check = None 61 self.toolfile = INFO.TOOL_FILE_PATH 62 self.tool_wear_info = None 63 self.current_tool_num = -1 64 self.toolinfo = None 65 STATUS.connect('periodic', self.periodic_check) 66 STATUS.connect('forced-update',lambda o: self.emit_update()) 67 68 def GET_TOOL_INFO(self, toolnum): 69 self.current_tool_num = int(toolnum) 70 self._reload() 71 return self.toolinfo 72 73 def GET_TOOL_ARRAY(self): 74 info = self.GET_TOOL_MODELS() 75 return info[0]+info[1] 76 77 def GET_TOOL_MODELS(self): 78 return self._reload() 79 80 def SAVE_TOOLFILE(self, array): 81 return self._save(array) 82 83 def ADD_TOOL(self, newtool = [-99, 0,'0','0','0','0','0','0','0','0','0','0','0','0', 0,'New Tool']): 84 info = self.GET_TOOL_MODELS() 85 info[0].insert(0, newtool) 86 return self._save(info[0]+info[1]) 87 88 def DELETE_TOOLS(self, tools): 89 ta = self.GET_TOOL_ARRAY() 90 if type(tools) == int: 91 tools = [tools] 92 return self._save(ta, tools) 93 94 # [0] = tool number 95 # [1] = pocket number 96 # [2] = X offset 97 # [3] = Y offset 98 # [4] = Z offset 99 # [5] = A offset 100 # [6] = B offset 101 # [7] = C offset 102 # [8] = U offset 103 # [9] = V offset 104 # [10] = W offset 105 # [11] = tool diameter 106 # [12] = frontangle 107 # [13] = backangle 108 # [14] = tool orientation 109 # [15] = tool comments 110 # Reload the tool file into the array model and update tool_info 111 def _reload(self): 112 if self.toolfile is None or not os.path.exists(self.toolfile): 113 LOG.debug("Toolfile does not exist' {}".format(self.toolfile)) 114 return None 115 #print 'file',self.toolfile 116 # clear the current liststore, search the tool file, and add each tool 117 tool_model = [] 118 wear_model = [] 119 logfile = open(self.toolfile, "r").readlines() 120 self.toolinfo = None 121 toolinfo_flag = False 122 for rawline in logfile: 123 # strip the comments from line and add directly to array 124 # if index = -1 the delimiter ; is missing - clear comments 125 index = rawline.find(";") 126 comment ='' 127 if not index == -1: 128 comment = (rawline[index+1:]) 129 comment = comment.rstrip("\n") 130 line = rawline.rstrip(comment) 131 else: 132 line = rawline 133 array = [0, 0,'0','0','0','0','0','0','0','0','0','0','0','0', 0,comment] 134 wear_flag = False 135 # search beginning of each word for keyword letters 136 # if i = ';' that is the comment and we have already added it 137 # offset 0 and 1 are integers the rest floats 138 # we strip leading and following spaces from the comments 139 for offset,i in enumerate(KEYWORDS): 140 if i == ';': continue 141 for word in line.split(): 142 if word.startswith(';'): break 143 if word.startswith(i): 144 if offset == 0: 145 if int(word.lstrip(i)) == self.current_tool_num: 146 toolinfo_flag = True 147 # This array's tool num is the current tool num 148 # remember it for later 149 temp = array 150 # check if tool is greater then 10000 -then it's a wear tool 151 if int(word.lstrip(i)) > 10000: 152 wear_flag = True 153 if offset in(0,1,14): 154 try: 155 array[offset]= int(word.lstrip(i)) 156 except ValueError as e: 157 try: 158 array[offset]= int(float(word.lstrip(i))) 159 except Exception as e: 160 LOG.error("toolfile integer access: {} : {}".format(word.lstrip(i), e)) 161 else: 162 try: 163 if float(word.lstrip(i)) < 0.000001: 164 array[offset]= ("0") 165 else: 166 array[offset]= ("%10.4f" % float(word.lstrip(i))) 167 except: 168 LOG.error("toolfile float access: {}".format(self.toolfile)) 169 break 170 171 # add array line to model array 172 if wear_flag: 173 wear_model.append(array) 174 else: 175 tool_model.append(array) 176 if toolinfo_flag: 177 self.toolinfo = temp 178 else: 179 self.toolinfo = [0, 0,'0','0','0','0','0','0','0','0','0','0','0','0', 0,'No Tool'] 180 return (tool_model, wear_model) 181 182 # converts from linuxcnc toolfile array to toolwear array 183 # linuxcnc handles toolwear by having tool wear as extra tools with tool numbers above 10000 (fanuc style) 184 # qtvcp just adds the extra tool wear positions (x and z) to the original array 185 def CONVERT_TO_WEAR_TYPE(self, data): 186 if data is None: 187 data = ([],[]) 188 if not INFO.MACHINE_IS_LATHE: 189 maintool = data[0] + data[1] 190 weartool = [] 191 else: 192 maintool = data[0] 193 weartool = data[1] 194 #print 'main',data 195 tool_num_list = {} 196 full_tool_list = [] 197 for rnum, row in enumerate(maintool): 198 new_line = [False, 0, 0,'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0', 0,'No Tool'] 199 valuesInRow = [ value for value in row ] 200 for cnum,i in enumerate(valuesInRow): 201 if cnum == 0: 202 # keep a dict of actual tools numbers vrs row index 203 tool_num_list[i] = rnum 204 if cnum in(0,1,2): 205 # transfer these positions directly to new line (offset by 1 for checkbox) 206 new_line[cnum +1] = i 207 elif cnum == 3: 208 # move Y past x wear position 209 new_line[5] = i 210 elif cnum == 4: 211 # move z past y wear position 212 new_line[7] = i 213 elif cnum in(5,6,7,8,9,10,11,12,13,14,15): 214 # a;; the rest past z wear position 215 new_line[cnum+4] = i 216 full_tool_list.append(new_line) 217 #print 'row',row 218 #print 'new row',new_line 219 # any tool number over 10000 is a wear offset 220 # It's already been separated in the weartool variable. 221 # now we pull the values we need out and put it in our 222 # full tool list's tool variable's parent tool row 223 # eg 10001 goes to tool 1, 10002 goes to tool 2 etc 224 for rnum, row in enumerate(weartool): 225 values = [ value for value in row ] 226 parent_tool = tool_num_list[( values[0]-10000)] 227 full_tool_list[parent_tool][4] = values[2] 228 full_tool_list[parent_tool][6] = values[3] 229 full_tool_list[parent_tool][8] = values[4] 230 return full_tool_list 231 232 # converts from toolwear array to linuxcnc toolfile array 233 # linuxcnc handles toolwear by having tool wear as extra tools with tool numbers above 10000 (fanuc style) 234 # qtvcp just adds the extra tool wear positions (x and z) to the original array 235 def CONVERT_TO_STANDARD_TYPE(self, data): 236 if data is None: 237 data = ([]) 238 tool_wear_list = [] 239 full_tool_list = [] 240 for rnum, row in enumerate(data): 241 new_line = [0, 0,'0','0','0','0','0','0','0','0','0','0','0','0', 0,''] 242 new_wear_line = [0, 0,'0','0','0','0','0','0','0','0','0','0','0','0', 0,'Wear Offset'] 243 wear_flag = False 244 values = [ value for value in row ] 245 for cnum,i in enumerate(values): 246 #print cnum, i, type(i) 247 if cnum in(1,2): 248 new_line[cnum-1] = int(i) 249 elif cnum == 3: 250 new_line[cnum-1] = float(i) 251 elif cnum == 4 and i !='0': 252 wear_flag = True 253 new_wear_line[2] = float(i) 254 elif cnum == 5 and i !='0': 255 new_line[cnum-2] = float(i) 256 elif cnum == 6 and i !='0': 257 wear_flag = True 258 new_wear_line[3] = float(i) 259 elif cnum == 7 and i !='0': 260 new_line[cnum-3] = float(i) 261 elif cnum == 8 and i !='0': 262 wear_flag = True 263 new_wear_line[4] = float(i) 264 elif cnum in(9,10,11,12,13,14,15,16,17): 265 new_line[cnum-4] = float(i) 266 elif cnum == 18: 267 new_line[cnum-4] = int(i) 268 elif cnum == 19: 269 new_line[cnum-4] = str(i) 270 if wear_flag: 271 new_wear_line[0] = int(values[1]+10000) 272 new_wear_line[15] = 'Wear Offset Tool %d'% values[1] 273 tool_wear_list.append(new_wear_line) 274 # add tool line to tool list 275 full_tool_list.append(new_line) 276 # add wear list to full tool list 277 full_tool_list = full_tool_list + tool_wear_list 278 return full_tool_list 279 280 # TODO check for linnuxcnc ON and IDLE which is the only safe time to edit/SAVE the tool file. 281 282 def _save(self, new_model, delete=()): 283 if self.toolfile == None: 284 return True 285 file = open(self.toolfile, "w") 286 for row in new_model: 287 values = [ value for value in row ] 288 #print values 289 line = "" 290 skip = False 291 for num,i in enumerate(values): 292 #print KEYWORDS[num], i, #type(i), int(i) 293 if num == 0 and i in delete: 294 LOG.debug("delete tool ' {}".format(i)) 295 skip = True 296 if num in (0,1,14): # tool# pocket# orientation 297 line = line + "%s%d "%(KEYWORDS[num], i) 298 elif num == 15: # comments 299 test = i.strip() 300 line = line + "%s%s "%(KEYWORDS[num],test) 301 else: 302 test = str(i).lstrip() # floats 303 line = line + "%s%s "%(KEYWORDS[num], test) 304 LOG.debug("Save line: {}".format(line)) 305 if not skip: 306 print >>file,line 307 # Theses lines are required to make sure the OS doesn't cache the data 308 # That would make linuxcnc and the widget to be out of synch leading to odd errors 309 file.flush() 310 os.fsync(file.fileno()) 311 # tell linuxcnc we changed the tool table entries 312 try: 313 ACTION.RELOAD_TOOLTABLE() 314 except: 315 LOG.error("reloading of tool table into linuxcnc: {}".format(self.toolfile)) 316 return True 317 318 # create a hash code 319 def md5sum(self,filename): 320 try: 321 f = open(filename, "rb") 322 except: 323 return None 324 else: 325 return hashlib.md5(f.read()).hexdigest() 326 327 # push the update to whoever using STATUS 328 def emit_update(self): 329 data = self.GET_TOOL_MODELS() 330 if data is not None: 331 STATUS.emit('toolfile-stale',data) 332 333 # check the hash code on the toolfile against 334 # the saved hash code when last reloaded. 335 def periodic_check(self, w): 336 if self._delay < 9: 337 self._delay += 1 338 return 339 if STATUS.is_status_valid() == False: 340 return 341 self._delay = 0 342 m1 = self.md5sum(self.toolfile) 343 if m1 and self._hash_code != m1: 344 self._hash_code = m1 345 self.emit_update() 346