#!/usr/bin/env python3 # Qtvcp # # Copyright (c) 2017 Chris Morley # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # ############################################################################### import os import hashlib from qtvcp.core import Status, Info, Action # Set up logging from . import logger STATUS = Status() INFO = Info() ACTION = Action() LOG = logger.getLogger(__name__) # Force the log level for this module # LOG.setLevel(logger.INFO) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL KEYWORDS = ['T', 'P', 'X', 'Y', 'Z', 'A', 'B', 'C', 'U', 'V', 'W', 'D', 'I', 'J', 'Q', ';'] class _TStat(object): def __init__(self): # only initialize once for all instances if self.__class__._instanceNum >= 1: return self.__class__._instanceNum += 1 self._delay = 0 self._hash_code = None self.NUM = 0 self.POCKET = 1 self.X = 2 self.Y = 3 self.Z = 4 self.A = 5 self.B = 6 self.C = 7 self.U = 8 self.V = 9 self.W = 10 self.DIAMETER = 11 self.FRONTANGLE = 12 self.BACKANGLE = 13 self.ORIENTATION = 14 self.COMMENTS = 15 self.hash_check = None self.toolfile = INFO.TOOL_FILE_PATH self.tool_wear_info = None self.current_tool_num = -1 self.toolinfo = None STATUS.connect('periodic', self.periodic_check) STATUS.connect('forced-update', lambda o: self.emit_update()) def GET_TOOL_INFO(self, toolnum): self.current_tool_num = int(toolnum) self._reload() return self.toolinfo def GET_TOOL_ARRAY(self): info = self.GET_TOOL_MODELS() return info[0] + info[1] def GET_TOOL_MODELS(self): return self._reload() def SAVE_TOOLFILE(self, array): return self._save(array) def ADD_TOOL(self, newtool=[-99, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 'New Tool']): info = self.GET_TOOL_MODELS() info[0].insert(0, newtool) return self._save(info[0] + info[1]) def DELETE_TOOLS(self, tools): ta = self.GET_TOOL_ARRAY() if type(tools) == int: tools = [tools] return self._save(ta, tools) # [0] = tool number # [1] = pocket number # [2] = X offset # [3] = Y offset # [4] = Z offset # [5] = A offset # [6] = B offset # [7] = C offset # [8] = U offset # [9] = V offset # [10] = W offset # [11] = tool diameter # [12] = frontangle # [13] = backangle # [14] = tool orientation # [15] = tool comments # Reload the tool file into the array model and update tool_info def _reload(self): if self.toolfile is None or not os.path.exists(self.toolfile): LOG.debug("Toolfile does not exist' {}".format(self.toolfile)) return None # print 'file',self.toolfile # clear the current liststore, search the tool file, and add each tool tool_model = [] wear_model = [] logfile = open(self.toolfile, "r").readlines() self.toolinfo = None toolinfo_flag = False for rawline in logfile: # ignore blank lines if rawline == '': continue # print 'raw:',rawline # strip the comments from line and add directly to array # if index = -1 the delimiter ; is missing - clear comments index = rawline.find(";") comment = '' if not index == -1: comment = (rawline[index + 1:]) comment = comment.rstrip("\n") line = rawline.rstrip(comment) else: line = rawline array = [0, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, comment] wear_flag = False # search beginning of each word for keyword letters # if i = ';' that is the comment and we have already added it # offset 0 and 1 are integers the rest floats # we strip leading and following spaces from the comments for offset, i in enumerate(KEYWORDS): if i == ';': continue for word in line.split(): if word.startswith(';'): break if word.startswith(i): if offset == 0: if int(word.lstrip(i)) == self.current_tool_num: toolinfo_flag = True # This array's tool num is the current tool num # remember it for later temp = array # check if tool is greater then 10000 -then it's a wear tool if int(word.lstrip(i)) > 10000: wear_flag = True if offset in (0, 1, 14): try: array[offset] = int(word.lstrip(i)) except ValueError as e: try: array[offset] = int(float(word.lstrip(i))) except Exception as e: LOG.error("toolfile integer access: {} : {}".format(word.lstrip(i), e)) else: try: # we will call this range zero: if float(word.lstrip(i)) < 0.000001 and float(word.lstrip(i)) > -0.000001: array[offset] = 0.0 else: array[offset] = float(word.lstrip(i)) except: LOG.error("toolfile float access: {}".format(self.toolfile)) break # add array line to model array if wear_flag: wear_model.append(array) else: tool_model.append(array) if toolinfo_flag: self.toolinfo = temp else: self.toolinfo = [0, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 'No Tool'] # print 'load' # for i in tool_model: # print i # print wear_model return (tool_model, wear_model) # converts from linuxcnc toolfile array to toolwear array # linuxcnc handles toolwear by having tool wear as extra tools with tool numbers above 10000 (fanuc style) # qtvcp just adds the extra tool wear positions (x and z) to the original array def CONVERT_TO_WEAR_TYPE(self, data): if data is None: data = ([], []) if not INFO.MACHINE_IS_LATHE: maintool = data[0] + data[1] weartool = [] else: maintool = data[0] weartool = data[1] # print 'main',data tool_num_list = {} full_tool_list = [] for rnum, row in enumerate(maintool): new_line = [False, 0, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 'No Tool'] valuesInRow = [value for value in row] for cnum, i in enumerate(valuesInRow): if cnum == 0: # keep a dict of actual tools numbers vrs row index tool_num_list[i] = rnum if cnum in (0, 1, 2): # transfer these positions directly to new line (offset by 1 for checkbox) new_line[cnum + 1] = i elif cnum == 3: # move Y past x wear position new_line[5] = i elif cnum == 4: # move z past y wear position new_line[7] = i elif cnum in (5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15): # a;; the rest past z wear position new_line[cnum + 4] = i full_tool_list.append(new_line) # print 'row',row # print 'new row',new_line # any tool number over 10000 is a wear offset # It's already been separated in the weartool variable. # now we pull the values we need out and put it in our # full tool list's tool variable's parent tool row # eg 10001 goes to tool 1, 10002 goes to tool 2 etc # for now only if in lathe mode if INFO.MACHINE_IS_LATHE: for rnum, row in enumerate(weartool): values = [value for value in row] try: parent_tool = tool_num_list[(values[0] - 10000)] except KeyError: LOG.error("tool wear number has no parent Tool: {}".format(values)) continue else: full_tool_list[parent_tool][4] = values[2] full_tool_list[parent_tool][6] = values[3] full_tool_list[parent_tool][8] = values[4] return full_tool_list # converts from toolwear array to linuxcnc toolfile array # linuxcnc handles toolwear by having tool wear as extra tools with tool numbers above 10000 (fanuc style) # qtvcp just adds the extra tool wear positions (x and z) to the original array def CONVERT_TO_STANDARD_TYPE(self, data): # for i in data: # print i if data is None: data = ([]) tool_wear_list = [] full_tool_list = [] for rnum, row in enumerate(data): new_line = [0, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, ''] new_wear_line = [0, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 'Wear Offset'] wear_flag = False values = [value for value in row] for cnum, i in enumerate(values): # print cnum, i, type(i) if cnum in (1, 2): new_line[cnum - 1] = int(i) elif cnum == 3: new_line[cnum - 1] = float(i) elif cnum == 4 and i != '0': wear_flag = True new_wear_line[2] = float(i) elif cnum == 5 and i != '0': new_line[cnum - 2] = float(i) elif cnum == 6 and i != '0': wear_flag = True new_wear_line[3] = float(i) elif cnum == 7 and i != '0': new_line[cnum - 3] = float(i) elif cnum == 8 and i != '0': wear_flag = True new_wear_line[4] = float(i) elif cnum in (9, 10, 11, 12, 13, 14, 15, 16, 17): new_line[cnum - 4] = float(i) elif cnum == 18: new_line[cnum - 4] = int(i) elif cnum == 19: new_line[cnum - 4] = str(i) if wear_flag: new_wear_line[0] = int(values[1] + 10000) new_wear_line[15] = 'Wear Offset %d' % values[1] tool_wear_list.append(new_wear_line) # add tool line to tool list full_tool_list.append(new_line) LOG.debug("converted line: {}".format(new_line)) # add wear list to full tool list if in lathe mode if INFO.MACHINE_IS_LATHE: full_tool_list = full_tool_list + tool_wear_list return full_tool_list # TODO check for linnuxcnc ON and IDLE which is the only safe time to edit/SAVE the tool file. def _save(self, new_model, delete=()): if self.toolfile == None: return True file = open(self.toolfile, "w") for row in new_model: values = [value for value in row] line = "" skip = False for num, i in enumerate(values): # print KEYWORDS[num], i, #type(i), int(i) if num == 0 and i in delete: LOG.debug("delete tool ' {}".format(i)) skip = True if num in (0, 1, 14): # tool# pocket# orientation line = line + "%s%d " % (KEYWORDS[num], i) elif num == 15: # comments test = i.strip() line = line + "%s%s " % (KEYWORDS[num], test) else: test = float(str(i).lstrip()) # floats if test == 0.0: line = line + "%s%d " % (KEYWORDS[num], test) elif num in (12, 13): line = line + "%s%3.1f " % (KEYWORDS[num], test) else: line = line + "%s%.5f " % (KEYWORDS[num], test) LOG.debug("Save line: {}".format(line)) if not skip: print(line, file=file) # These lines are required to make sure the OS doesn't cache the data # That would make linuxcnc and the widget to be out of synch leading to odd errors file.flush() os.fsync(file.fileno()) # tell linuxcnc we changed the tool table entries try: ACTION.RELOAD_TOOLTABLE() except: LOG.error("reloading of tool table into linuxcnc: {}".format(self.toolfile)) return True # create a hash code def md5sum(self, filename): try: f = open(filename, "rb") except: return None else: return hashlib.md5(f.read()).hexdigest() # push the update to whoever using STATUS def emit_update(self): data = self.GET_TOOL_MODELS() if data is not None: STATUS.emit('toolfile-stale', data) # check the hash code on the toolfile against # the saved hash code when last reloaded. def periodic_check(self, w): if self._delay < 9: self._delay += 1 return if STATUS.is_status_valid() == False: return self._delay = 0 m1 = self.md5sum(self.toolfile) if m1 and self._hash_code != m1: self._hash_code = m1 self.emit_update()