/ lib / python / qtvcp / qt_tstat.py
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