qt_istat.py
1 import os 2 import linuxcnc 3 import collections 4 5 # Set up logging 6 import logger 7 log = logger.getLogger(__name__) 8 # Set the log level for this module 9 log.setLevel(logger.INFO) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL 10 11 try: 12 LINUXCNCVERSION = os.environ['LINUXCNCVERSION'] 13 except: 14 LINUXCNCVERSION = 'UNAVAILABLE' 15 16 INIPATH = os.environ.get('INI_FILE_NAME', '/dev/null') 17 18 HOME = os.environ.get('EMC2_HOME', '/usr') 19 if HOME is not None: 20 IMAGEDIR = os.path.join(HOME, "share","qtvcp","images") 21 else: 22 IMAGEDIR = None 23 24 class _IStat(object): 25 def __init__(self): 26 # only initialize once for all instances 27 if self.__class__._instanceNum >=1: 28 return 29 self.__class__._instanceNum += 1 30 self.LINUXCNC_IS_RUNNING = bool(INIPATH != '/dev/null') 31 if not self.LINUXCNC_IS_RUNNING: 32 # Reset the log level for this module 33 # Linuxcnc isn't running so we expect INI errors 34 log.setLevel(logger.CRITICAL) 35 self.LINUXCNC_VERSION = LINUXCNCVERSION 36 self.inifile = linuxcnc.ini(INIPATH) 37 self.MDI_HISTORY_PATH = '~/.axis_mdi_history' 38 self.QTVCP_LOG_HISTORY_PATH = '~/qtvcp.log' 39 self.MACHINE_LOG_HISTORY_PATH = '~/.machine_log_history' 40 self.PREFERENCE_PATH = '~/.Preferences' 41 self.SUB_PATH = None 42 self.IMAGE_PATH = IMAGEDIR 43 self.LIB_PATH = os.path.join(HOME, "share","qtvcp") 44 45 self.MACHINE_IS_LATHE = False 46 self.MACHINE_IS_METRIC = False 47 self.MACHINE_UNIT_CONVERSION = 1 48 self.MACHINE_UNIT_CONVERSION_9 = [1]*9 49 self.AVAILABLE_AXES = ['X','Y','Z'] 50 self.AVAILABLE_JOINTS = [0,1,2] 51 self.GET_NAME_FROM_JOINT = {0:'X',1:'Y',2:'Z'} 52 self.GET_JOG_FROM_NAME = {'X':0,'Y':1,'Z':2} 53 self.NO_HOME_REQUIRED = False 54 self.JOG_INCREMENTS = None 55 self.ANGULAR_INCREMENTS = None 56 57 self.MAX_LINEAR_VELOCITY = 60 58 self.DEFAULT_LINEAR_VELOCITY = 15.0 59 60 self.DEFAULT_SPINDLE_SPEED = 200 61 self.MAX_SPINDLE_SPEED = 2500 62 self.MAX_FEED_OVERRIDE = 1.5 63 self.MAX_SPINDLE_OVERRIDE = 1.5 64 self.MIN_SPINDLE_OVERRIDE = 0.5 65 66 self.update() 67 68 def update(self): 69 self.MDI_HISTORY_PATH = self.inifile.find('DISPLAY', 'MDI_HISTORY_FILE') or '~/.axis_mdi_history' 70 self.QTVCP_LOG_HISTORY_PATH = self.inifile.find('DISPLAY', 'LOG_FILE') or '~/qtvcp.log' 71 self.MACHINE_LOG_HISTORY_PATH = self.inifile.find('DISPLAY', 'MACHINE_LOG_PATH') or '~/.machine_log_history' 72 self.PREFERENCE_PATH = self.inifile.find("DISPLAY","PREFERENCE_FILE_PATH") or None 73 self.SUB_PATH = (self.inifile.find("RS274NGC", "SUBROUTINE_PATH")) or None 74 if self.SUB_PATH is not None: 75 for mpath in (self.SUB_PATH.split(':')): 76 if 'macro' in mpath: 77 path = mpath 78 break 79 self.MACRO_PATH = mpath or None 80 else: 81 self.MACRO_PATH = None 82 self.MACHINE_IS_LATHE = bool(self.inifile.find("DISPLAY", "LATHE")) 83 extensions = self.inifile.findall("FILTER", "PROGRAM_EXTENSION") 84 self.PROGRAM_FILTERS = ([e.split(None, 1) for e in extensions]) or None 85 self.PARAMETER_FILE = (self.inifile.find("RS274NGC", "PARAMETER_FILE")) or None 86 try: 87 # check the ini file if UNITS are set to mm" 88 # first check the global settings 89 units=self.inifile.find("TRAJ","LINEAR_UNITS") 90 if units==None: 91 # else then the X axis units 92 units=self.inifile.find("AXIS_0","UNITS") 93 except: 94 units = "inch" 95 # set up the conversion arrays based on what units we discovered 96 if units=="mm" or units=="metric" or units == "1.0": 97 self.MACHINE_IS_METRIC = True 98 self.MACHINE_UNIT_CONVERSION = 1.0/25.4 99 self.MACHINE_UNIT_CONVERSION_9 = [1.0/25.4]*3+[1]*3+[1.0/25.4]*3 100 log.debug('Machine is METRIC based. unit Conversion constant={}'.format(self.MACHINE_UNIT_CONVERSION )) 101 else: 102 self.MACHINE_IS_METRIC = False 103 self.MACHINE_UNIT_CONVERSION = 25.4 104 self.MACHINE_UNIT_CONVERSION_9 = [25.4]*3+[1]*3+[25.4]*3 105 log.debug('Machine is IMPERIAL based. unit Conversion constant={}'.format(self.MACHINE_UNIT_CONVERSION )) 106 107 axes = self.inifile.find("TRAJ", "COORDINATES") 108 if axes is not None: # i.e. LCNC is running, not just in Qt Desinger 109 axes = axes.replace(" ", "") 110 log.debug('TRAJ COORDINATES: {}'.format(axes)) 111 self.AVAILABLE_AXES = [] 112 self.GET_NAME_FROM_JOINT = {} 113 self.AVAILABLE_JOINTS = [] 114 self.GET_JOG_FROM_NAME = {} 115 temp = [] 116 for num, letter in enumerate(axes): 117 temp.append(letter) 118 119 # list of available axes 120 if letter not in self.AVAILABLE_AXES: 121 self.AVAILABLE_AXES.append(letter.upper()) 122 123 # map of axis designation from joint number 124 # This allows calling joints x2 or y2 etc 125 count = collections.Counter(temp) 126 if count[letter]>1: c = letter+str(count[letter]) 127 else: c = letter 128 self.GET_NAME_FROM_JOINT[num] = c 129 130 # map of axis designation to joint-to-jog when in axis mode. 131 # so then you can jog either joint of an axis to move the axis 132 if count[letter]>1: 133 self.GET_JOG_FROM_NAME[c] = self.GET_JOG_FROM_NAME[letter] 134 else: 135 self.GET_JOG_FROM_NAME[c] = num 136 137 # list of availble joint numbers 138 self.AVAILABLE_JOINTS.append(num) 139 140 # AXIS sanity check 141 av = self.inifile.find('AXIS_%s'% letter.upper(), 'MAX_VELOCITY') or None 142 aa = self.inifile.find('AXIS_%s'% letter.upper(), 'MAX_ACCELERATION') or None 143 if av is None or aa is None: 144 log.critical('MISSING [AXIS_{}] MAX VeLOCITY or MAX ACCELERATION entry in INI file.'.format(letter.upper())) 145 self.NO_HOME_REQUIRED = int(self.inifile.find("TRAJ", "NO_FORCE_HOMING") or 0) 146 147 # home all check 148 self.HOME_ALL_FLAG = 1 149 # set Home All Flage only if ALL joints specify a HOME_SEQUENCE 150 jointcount = len(self.AVAILABLE_JOINTS) 151 self.JOINTSEQUENCELIST = {} 152 for j in range(jointcount): 153 seq = self.inifile.find("JOINT_"+str(j), "HOME_SEQUENCE") 154 if seq is None: 155 seq = -1 156 self.HOME_ALL_FLAG = 0 157 self.JOINTSEQUENCELIST[j] = seq 158 # joint sequence/type 159 self.JOINT_TYPE = [None] * jointcount 160 self.JOINT_SEQUENCE = [None] * jointcount 161 for j in range(jointcount): 162 section = "JOINT_%d" % j 163 self.JOINT_TYPE[j] = self.inifile.find(section, "TYPE") or "LINEAR" 164 self.JOINT_SEQUENCE[j] = self.inifile.find(section, "HOME_SEQUENCE") or "" 165 166 # jogging increments 167 increments = self.inifile.find("DISPLAY", "INCREMENTS") 168 if increments: 169 if "," in increments: 170 self.JOG_INCREMENTS = [i.strip() for i in increments.split(",")] 171 else: 172 self.JOG_INCREMENTS = increments.split() 173 if not "continuous" in increments: 174 self.JOG_INCREMENTS.insert(0, "Continuous") 175 else: 176 if self.MACHINE_IS_METRIC: 177 self.JOG_INCREMENTS = ["Continuous",".001 mm",".01 mm",".1 mm","1 mm"] 178 else: 179 self.JOG_INCREMENTS = ["Continuous",".0001 in",".001 in",".01 in",".1 in"] 180 181 # angular jogging increments 182 increments = self.inifile.find("DISPLAY", "ANGULAR_INCREMENTS") 183 if increments: 184 if "," in increments: 185 self.ANGULAR_INCREMENTS = [i.strip() for i in increments.split(",")] 186 else: 187 self.ANGULAR_INCREMENTS = increments.split() 188 if not "continuous" in increments: 189 self.ANGULAR_INCREMENTS.insert(0, "Continuous") 190 else: 191 self.ANGULAR_INCREMENTS = ["Continuous","1","45","180","360"] 192 temp = self.inifile.find("TRAJ", "COORDINATES") 193 if temp: 194 self.TRAJ_COORDINATES = temp.lower().replace(" ","") 195 else: 196 self.TRAJ_COORDINATES = None 197 self.JOINT_COUNT = int(self.inifile.find("KINS","JOINTS")or 0) 198 self.DEFAULT_LINEAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY","DEFAULT_LINEAR_VELOCITY", 1)) * 60 199 self.MIN_LINEAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY","MIN_LINEAR_VELOCITY",1)) * 60 200 self.MAX_LINEAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY","MAX_LINEAR_VELOCITY",5)) * 60 201 self.DEFAULT_ANGULAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY","DEFAULT_ANGULAR_VELOCITY",6)) * 60 202 self.MIN_ANGULAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY","MIN_ANGULAR_VELOCITY",1)) * 60 203 self.MAX_ANGULAR_JOG_VEL = float(self.get_error_safe_setting("DISPLAY","MAX_ANGULAR_VELOCITY",60)) * 60 204 self.DEFAULT_SPINDLE_SPEED = int(self.get_error_safe_setting("DISPLAY","DEFAULT_SPINDLE_SPEED",200)) 205 self.MAX_SPINDLE_SPEED = int(self.get_error_safe_setting("DISPLAY","MAX_SPINDLE_SPEED",2500)) 206 self.MAX_SPINDLE_OVERRIDE = float(self.get_error_safe_setting("DISPLAY","MAX_SPINDLE_OVERRIDE",1)) * 100 207 self.MIN_SPINDLE_OVERRIDE = float(self.get_error_safe_setting("DISPLAY","MIN_SPINDLE_OVERRIDE",0.5)) * 100 208 self.MAX_FEED_OVERRIDE = float(self.get_error_safe_setting("DISPLAY","MAX_FEED_OVERRIDE",1.5)) * 100 209 self.MAX_TRAJ_VELOCITY = float(self.get_error_safe_setting("TRAJ","MAX_LINEAR_VELOCITY", 210 self.get_error_safe_setting("AXIS_X","MAX_VELOCITY", 5) )) * 60 211 212 # user message dialog system 213 self.USRMESS_BOLDTEXT = self.inifile.findall("DISPLAY", "MESSAGE_BOLDTEXT") 214 self.USRMESS_TEXT = self.inifile.findall("DISPLAY", "MESSAGE_TEXT") 215 self.USRMESS_TYPE = self.inifile.findall("DISPLAY", "MESSAGE_TYPE") 216 self.USRMESS_PINNAME = self.inifile.findall("DISPLAY", "MESSAGE_PINNAME") 217 self.USRMESS_DETAILS = self.inifile.findall("DISPLAY", "MESSAGE_DETAILS") 218 self.USRMESS_ICON = self.inifile.findall("DISPLAY", "MESSAGE_ICON") 219 if len(self.USRMESS_TEXT) != len(self.USRMESS_TYPE): 220 log.warning('Invalid message configuration (missing text or type) in INI File [DISPLAY] section ') 221 if len(self.USRMESS_TEXT) != len(self.USRMESS_PINNAME): 222 log.warning('Invalid message configuration (missing pinname) in INI File [DISPLAY] section') 223 if len(self.USRMESS_TEXT) != len(self.USRMESS_BOLDTEXT): 224 log.warning('Invalid message configuration (missing boldtext) in INI File [DISPLAY] sectioN') 225 if len(self.USRMESS_TEXT) != len(self.USRMESS_DETAILS): 226 log.warning('Invalid message configuration (missing details) in INI File [DISPLAY] sectioN') 227 try: 228 self.ZIPPED_USRMESS = zip(self.USRMESS_BOLDTEXT,self.USRMESS_TEXT,self.USRMESS_DETAILS,self.USRMESS_TYPE,self.USRMESS_PINNAME) 229 except: 230 self.ZIPPED_USRMESS = None 231 232 # XEmbed tabs 233 # AXIS panel style: 234 self.GLADEVCP = (self.inifile.find("DISPLAY", "GLADEVCP")) or None 235 236 # tab style for qtvcp tab style is used everty where 237 self.TAB_NAMES = (self.inifile.findall("DISPLAY", "EMBED_TAB_NAME")) or None 238 self.TAB_LOCATIONS = (self.inifile.findall("DISPLAY", "EMBED_TAB_LOCATION")) or [] 239 self.TAB_CMDS = (self.inifile.findall("DISPLAY", "EMBED_TAB_COMMAND")) or None 240 if self.TAB_NAMES is not None and len(self.TAB_NAMES) != len(self.TAB_CMDS): 241 log.critical('Embeded tab configuration -invalaid number of TAB_NAMES vrs TAB_CMDs') 242 if self.TAB_NAMES is not None and len(self.TAB_LOCATIONS) != len(self.TAB_NAMES): 243 log.warning('Embeded tab configuration -invalaid number of TAB_NAMES vrs TAB_LOCATION - guessng default.') 244 for num,i in enumerate(self.TAB_NAMES): 245 try: 246 if self.TAB_LOCATIONS[num]: 247 continue 248 except: 249 self.TAB_LOCATIONS.append("default") 250 try: 251 self.ZIPPED_TABS = zip(self.TAB_NAMES,self.TAB_LOCATIONS,self.TAB_CMDS) 252 except: 253 self.ZIPPED_TABS = None 254 255 self.MDI_COMMAND_LIST = (self.inifile.findall("MDI_COMMAND_LIST", "MDI_COMMAND")) or None 256 self.TOOL_FILE_PATH = self.get_error_safe_setting("EMCIO", "TOOL_TABLE") 257 self.POSTGUI_HALFILE_PATH = (self.inifile.find("HAL", "POSTGUI_HALFILE")) or None 258 259 ################### 260 # helper functions 261 ################### 262 263 def get_error_safe_setting(self, heading, detail, default=None): 264 result = self.inifile.find(heading, detail) 265 if result: 266 return result 267 else: 268 log.warning('INI Parsing Error, No {} Entry in {}, Using: {}'.format(detail, heading, default)) 269 return default 270 271 def convert_machine_to_metric(self, data): 272 if self.MACHINE_IS_METRIC: 273 return data 274 else: 275 return data * 25.4 276 277 def convert_machine_to_imperial(self, data): 278 if self.MACHINE_IS_METRIC: 279 return data * (1/25.4) 280 else: 281 return data 282 283 def convert_metric_to_machine(self, data): 284 if self.MACHINE_IS_METRIC: 285 return data 286 else: 287 return data * (1/25.4) 288 289 def convert_imperial_to_machine(self, data): 290 if self.MACHINE_IS_METRIC: 291 return data * 25.4 292 else: 293 return data 294 295 def convert_9_metric_to_machine(self,v): 296 if self.MACHINE_IS_METRIC: 297 return v 298 else: 299 c = [1.0/25.4]*3+[1]*3+[1.0/25.4]*3 300 return map(lambda x,y: x*y, v, c) 301 302 def convert_9_imperial_to_machine(self,v): 303 if self.MACHINE_IS_METRIC: 304 c = [25.4]*3+[1]*3+[25.4]*3 305 return map(lambda x,y: x*y, v, c) 306 else: 307 return v 308 309 def convert_units(self, data): 310 return data * self.MACHINE_UNIT_CONVERSION 311 312 def convert_units_9(self,v): 313 c = self.MACHINE_UNIT_CONVERSION_9 314 return map(lambda x,y: x*y, v, c) 315 316 # This finds the filter program's initilizing 317 # program eg python for .py from INI 318 def get_filter_program(self, fname): 319 ext = os.path.splitext(fname)[1] 320 if ext: 321 return self.inifile.find("FILTER", ext[1:]) 322 else: 323 return None 324 325 # get filter extensions in QT format 326 def get_qt_filter_extensions(self,): 327 all_extensions = [("G code (*.ngc)")] 328 try: 329 for k, v in self.PROGRAM_FILTERS: 330 k = k.replace('.',' *.') 331 k = k.replace(',',' ') 332 all_extensions.append( ( ';;%s (%s)'%(v,k)) ) 333 all_extensions.append((';;All (*)')) 334 temp ='' 335 for i in all_extensions: 336 temp = '%s %s'%(temp ,i) 337 return temp 338 except: 339 return ('All (*)') 340 341