#!/usr/bin/env python # GladeVcp Widget - offsetpage # # Copyright (c) 2013 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. # This widget reads the offsets for current tool, current G5x, G92 using python library linuxcnc. # all other offsets are read directly from the Var file, if available, as linuxcnc does not give # access to all offsets thru NML, only current ones. # you can hide any axes or any columns # set metric or imperial # set the var file to search # set the text formatting for metric/imperial separately import sys, os, pango, linuxcnc from hal_glib import GStat datadir = os.path.abspath(os.path.dirname(__file__)) AXISLIST = ['offset', 'X', 'Y', 'Z', 'A', 'B', 'C', 'U', 'V', 'W', 'name'] # we need to know if linuxcnc isn't running when using the GLADE editor # as it causes big delays in response lncnc_running = False try: import gobject, gtk except: print('GTK not available') sys.exit(1) # localization import locale BASE = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "..")) LOCALEDIR = os.path.join(BASE, "share", "locale") locale.setlocale(locale.LC_ALL, '') # we put this in a try so there is no error in the glade editor # linuxcnc is probably not running then try: INIPATH = os.environ['INI_FILE_NAME'] except: pass class OffsetPage(gtk.VBox): __gtype_name__ = 'OffsetPage' __gproperties__ = { 'display_units_mm' : (gobject.TYPE_BOOLEAN, 'Display Units', 'Display in metric or not', False, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 'mm_text_template' : (gobject.TYPE_STRING, 'Text template for Metric Units', 'Text template to display. Python formatting may be used for one variable', "%10.3f", gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 'imperial_text_template' : (gobject.TYPE_STRING, 'Text template for Imperial Units', 'Text template to display. Python formatting may be used for one variable', "%9.4f", gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 'font' : (gobject.TYPE_STRING, 'Pango Font', 'Display font to use', "sans 12", gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 'highlight_color' : (gtk.gdk.Color.__gtype__, 'Highlight color', "", gobject.PARAM_READWRITE), 'foreground_color' : (gtk.gdk.Color.__gtype__, 'Active text color', "", gobject.PARAM_READWRITE), 'hide_columns' : (gobject.TYPE_STRING, 'Hidden Columns', 'A no-spaces list of axes to hide: xyzabcuvw and t are the options', "", gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 'hide_rows' : (gobject.TYPE_STRING, 'Hidden Rows', 'A no-spaces list of rows to hide: 0123456789abc are the options' , "", gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), } __gproperties = __gproperties__ __gsignals__ = { 'selection_changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_STRING,)), } def __init__(self, filename = None, *a, **kw): super(OffsetPage, self).__init__() self.gstat = GStat() self.filename = filename self.linuxcnc = linuxcnc self.status = linuxcnc.stat() self.cmd = linuxcnc.command() self.hash_check = None self.display_units_mm = 0 # imperial self.machine_units_mm = 0 # imperial self.program_units = 0 # imperial self.display_follows_program = False # display units are chosen indepenadently of G20/G21 self.font = "sans 12" self.editing_mode = False self.highlight_color = gtk.gdk.Color("lightblue") self.foreground_color = gtk.gdk.Color("red") self.unselectable_color = gtk.gdk.Color("lightgray") self.hidejointslist = [] self.hidecollist = [] self.wTree = gtk.Builder() self.wTree.set_translation_domain("linuxcnc") # for locale translations self.wTree.add_from_file(os.path.join(datadir, "offsetpage.glade")) self.current_system = None self.selection_mask = () self.axisletters = ["x", "y", "z", "a", "b", "c", "u", "v", "w"] # global references self.store = self.wTree.get_object("liststore2") self.all_window = self.wTree.get_object("all_window") self.view2 = self.wTree.get_object("treeview2") self.view2.connect('button_press_event', self.on_treeview2_button_press_event) self.selection = self.view2.get_selection() self.selection.set_mode(gtk.SELECTION_SINGLE) self.selection.connect("changed", self.on_selection_changed) self.modelfilter = self.wTree.get_object("modelfilter") self.edit_button = self.wTree.get_object("edit_button") self.edit_button.connect('toggled', self.set_editing) zero_g92_button = self.wTree.get_object("zero_g92_button") zero_g92_button.connect('clicked', self.zero_g92) zero_rot_button = self.wTree.get_object("zero_rot_button") zero_rot_button.connect('clicked', self.zero_rot) self.set_font(self.font) self.modelfilter.set_visible_column(10) self.buttonbox = self.wTree.get_object("buttonbox") for col, name in enumerate(AXISLIST): if col > 9:break temp = self.wTree.get_object("cell_%s" % name) temp.connect('edited', self.col_editted, col) temp = self.wTree.get_object("cell_name") temp.connect('edited', self.col_editted, 10) # reparent offsetpage box from Glades top level window to widgets VBox window = self.wTree.get_object("offsetpage_box") window.reparent(self) # check the ini file if UNITS are set to mm # first check the global settings # if not available then the X axis units try: self.inifile = self.linuxcnc.ini(INIPATH) units = self.inifile.find("TRAJ", "LINEAR_UNITS") if units == None: units = self.inifile.find("AXIS_0", "UNITS") except: print "**** Offsetpage widget ERROR: LINEAR_UNITS not found in INI's TRAJ section" units = "inch" # now setup the conversion array depending on the machine native units if units == "mm" or units == "metric" or units == "1.0": self.machine_units_mm = 1 self.conversion = [1.0 / 25.4] * 3 + [1] * 3 + [1.0 / 25.4] * 3 else: self.machine_units_mm = 0 self.conversion = [25.4] * 3 + [1] * 3 + [25.4] * 3 # check linuxcnc status every half second gobject.timeout_add(500, self.periodic_check) # Reload the offsets into display def reload_offsets(self): g54, g55, g56, g57, g58, g59, g59_1, g59_2, g59_3 = self.read_file() # Get the offsets arrays and convert the units if the display # is not in machine native units g5x = self.status.g5x_offset tool = self.status.tool_offset g92 = self.status.g92_offset rot = self.status.rotation_xy if self.display_units_mm != self.machine_units_mm: g5x = self.convert_units(g5x) tool = self.convert_units(tool) g92 = self.convert_units(g92) g54 = self.convert_units(g54) g55 = self.convert_units(g55) g56 = self.convert_units(g56) g57 = self.convert_units(g57) g58 = self.convert_units(g58) g59 = self.convert_units(g59) g59_1 = self.convert_units(g59_1) g59_2 = self.convert_units(g59_2) g59_3 = self.convert_units(g59_3) # set the text style based on unit type if self.display_units_mm: tmpl = self.mm_text_template else: tmpl = self.imperial_text_template degree_tmpl = "%11.2f" # fill each row of the liststore fron the offsets arrays for row, i in enumerate([tool, g5x, rot, g92, g54, g55, g56, g57, g58, g59, g59_1, g59_2, g59_3]): for column in range(0, 9): if row == 2: if column == 2: self.store[row][column + 1] = locale.format(degree_tmpl, rot) else: self.store[row][column + 1] = " " else: self.store[row][column + 1] = locale.format(tmpl, i[column]) # set the current system's label's color - to make it stand out a bit if self.store[row][0] == self.current_system: self.store[row][13] = self.foreground_color else: self.store[row][13] = None # mark unselectable rows a dirrerent color if self.store[row][0] in self.selection_mask: self.store[row][12] = self.unselectable_color # This is for adding a filename path after the offsetpage is already loaded. def set_filename(self, filename): self.filename = filename self.reload_offsets() # We read the var file directly # and pull out the info we need # if anything goes wrong we set all the info to 0 def read_file(self): g54 = [0, 0, 0, 0, 0, 0, 0, 0, 0] g55 = [0, 0, 0, 0, 0, 0, 0, 0, 0] g56 = [0, 0, 0, 0, 0, 0, 0, 0, 0] g57 = [0, 0, 0, 0, 0, 0, 0, 0, 0] g58 = [0, 0, 0, 0, 0, 0, 0, 0, 0] g59 = [0, 0, 0, 0, 0, 0, 0, 0, 0] g59_1 = [0, 0, 0, 0, 0, 0, 0, 0, 0] g59_2 = [0, 0, 0, 0, 0, 0, 0, 0, 0] g59_3 = [0, 0, 0, 0, 0, 0, 0, 0, 0] if self.filename == None: return g54, g55, g56, g57, g58, g59, g59_1, g59_2, g59_3 if not os.path.exists(self.filename): return g54, g55, g56, g57, g58, g59, g59_1, g59_2, g59_3 logfile = open(self.filename, "r").readlines() for line in logfile: temp = line.split() param = int(temp[0]) data = float(temp[1]) if 5229 >= param >= 5221: g54[param - 5221] = data elif 5249 >= param >= 5241: g55[param - 5241] = data elif 5269 >= param >= 5261: g56[param - 5261] = data elif 5289 >= param >= 5281: g57[param - 5281] = data elif 5309 >= param >= 5301: g58[param - 5301] = data elif 5329 >= param >= 5321: g59[param - 5321] = data elif 5349 >= param >= 5341: g59_1[param - 5341] = data elif 5369 >= param >= 5361: g59_2[param - 5361] = data elif 5389 >= param >= 5381: g59_3[param - 5381] = data return g54, g55, g56, g57, g58, g59, g59_1, g59_2, g59_3 # This allows hiding or showing columns from a text string of columnns # eg list ='ab' # default, all the columns are shown def set_col_visible(self, list, bool): try: for index in range(0, len(list)): colstr = str(list[index]) colnum = "xyzabcuvwt".index(colstr.lower()) name = AXISLIST[colnum + 1] renderer = self.wTree.get_object(name) renderer.set_property('visible', bool) except: pass # hide/show the offset rows from a text string of row ids # eg list ='123' def set_row_visible(self, list, bool): try: for index in range(0, len(list)): rowstr = str(list[index]) rownum = "0123456789abcd".index(rowstr.lower()) self.store[rownum][10] = bool except: pass # This does the units conversion # it just multiplies the two arrays def convert_units(self, v): c = self.conversion return map(lambda x, y: x * y, v, c) # make the cells editable and highlight them def set_editing(self, widget): state = widget.get_active() # stop updates from linuxcnc self.editing_mode = state # highlight editable rows if state: color = self.highlight_color else: color = None # Set rows editable for i in range(1, 13): if not self.store[i][0] in('G5x', 'Rot', 'G92', 'G54', 'G55', 'G56', 'G57', 'G58', 'G59', 'G59.1', 'G59.2', 'G59.3'): continue if self.store[i][0] in self.selection_mask: continue self.store[i][11] = state self.store[i][12] = color self.queue_draw() # When the column is edited this does the work # TODO the edited column does not end up showing the editted number even though linuxcnc # registered the change def col_editted(self, widget, filtered_path, new_text, col): (store_path,) = self.modelfilter.convert_path_to_child_path(filtered_path) row = store_path axisnum = col - 1 # print "EDITED:", new_text, col, int(filtered_path), row, "axis num:", axisnum def system_to_p(system): convert = { "G54":1, "G55":2, "G56":3, "G57":4, "G58":5, "G59":6, "G59.1":7, "G59.2":8, "G59.3":9} try: pnum = convert[system] except: pnum = None return pnum # Hack to not edit any rotational offset but Z axis if row == 2 and not col == 3: return # set the text style based on unit type if self.display_units_mm: tmpl = lambda s: self.mm_text_template % s else: tmpl = lambda s: self.imperial_text_template % s # allow 'name' columnn text to be arbitrarily changed if col == 10: self.store[row][14] = new_text return # set the text in the table try: self.store[row][col] = locale.format("%10.4f", locale.atof(new_text)) except: print "offsetpage widget error: unrecognized float input" # make sure we switch to correct units for machine and rotational, row 2, does not get converted try: if not self.display_units_mm == self.program_units and not row == 2: if self.program_units == 1: convert = 25.4 else: convert = 1.0 / 25.4 qualified = float(locale.atof(new_text)) * convert else: qualified = float(locale.atof(new_text)) except: print 'error' # now update linuxcnc to the change try: global lncnc_runnning if lncnc_running: if self.status.task_mode != self.linuxcnc.MODE_MDI: self.cmd.mode(self.linuxcnc.MODE_MDI) self.cmd.wait_complete() if row == 1: self.cmd.mdi("G10 L2 P0 %s %10.4f" % (self.axisletters[axisnum], qualified)) elif row == 2: if col == 3: self.cmd.mdi("G10 L2 P0 R %10.4f" % (qualified)) elif row == 3: self.cmd.mdi("G92 %s %10.4f" % (self.axisletters[axisnum], qualified)) else: pnum = system_to_p(self.store[row][0]) if not pnum == None: self.cmd.mdi("G10 L2 P%d %s %10.4f" % (pnum, self.axisletters[axisnum], qualified)) self.cmd.mode(self.linuxcnc.MODE_MANUAL) self.cmd.wait_complete() self.cmd.mode(self.linuxcnc.MODE_MDI) self.cmd.wait_complete() self.gstat.emit('reload-display') except: print "offsetpage widget error: MDI call error" self.reload_offsets() # callback to cancel G92 when button pressed def zero_g92(self, widget): # print "zero g92" if lncnc_running: try: if self.status.task_mode != self.linuxcnc.MODE_MDI: self.cmd.mode(self.linuxcnc.MODE_MDI) self.cmd.wait_complete() self.cmd.mdi("G92.1") self.cmd.mode(self.linuxcnc.MODE_MANUAL) self.cmd.wait_complete() self.cmd.mode(self.linuxcnc.MODE_MDI) self.cmd.wait_complete() self.gstat.emit('reload-display') except: print "MDI error in offsetpage widget -zero G92" # callback to zero rotational offset when button pressed def zero_rot(self, widget): # print "zero rotation offset" if lncnc_running: try: if self.status.task_mode != self.linuxcnc.MODE_MDI: self.cmd.mode(self.linuxcnc.MODE_MDI) self.cmd.wait_complete() self.cmd.mdi("G10 L2 P0 R 0") self.cmd.mode(self.linuxcnc.MODE_MANUAL) self.cmd.wait_complete() self.cmd.mode(self.linuxcnc.MODE_MDI) self.cmd.wait_complete() self.gstat.emit('reload-display') except: print "MDI error in offsetpage widget-zero rotational offset" # check for linnuxcnc ON and IDLE which is the only safe time to edit the tool file. # if in editing mode don't update else you can't actually edit def periodic_check(self): convert = ("None", "G54", "G55", "G56", "G57", "G58", "G59", "G59.1", "G59.2", "G59.3") try: self.status.poll() on = self.status.task_state > linuxcnc.STATE_OFF idle = self.status.interp_state == linuxcnc.INTERP_IDLE self.edit_button.set_sensitive(bool(on and idle)) self.current_system = convert[self.status.g5x_index] self.program_units = int(self.status.program_units == 2) if self.display_follows_program: self.display_units_mm = self.program_units global lncnc_running lncnc_running = True except: self.current_system = "G54" lncnc_running = False if self.filename and not self.editing_mode: self.reload_offsets() return True # sets the color when editing is active def set_highlight_color(self, value): self.highlight_color = gtk.gdk.Color(value) # sets the text color of the current system description name def set_foreground_color(self, value): self.foreground_color = gtk.gdk.Color(value) # Allows you to set the text font of all the rows and columns def set_font(self, value): for col, name in enumerate(AXISLIST): if col > 10:break temp = self.wTree.get_object("cell_" + name) temp.set_property('font', value) # helper function to set the units to inch def set_to_inch(self): self.display_units_mm = 0 # helper function to set the units to mm def set_to_mm(self): self.display_units_mm = 1 def set_display_follows_program_units(self): self.display_follows_program = True def set_display_independent_units(self): self.display_follows_program = False # helper function to hide control buttons def hide_buttonbox(self, state): if state: self.buttonbox.hide() else: self.buttonbox.show() # Mark the active system with cursor highlight def mark_active(self, system): try: pathlist = [] for row in self.modelfilter: if row[0] == system: pathlist.append(row.path) if len(pathlist) == 1: self.selection.select_path(pathlist[0]) except: print "offsetpage_widget error: cannot select coordinate system", system # Get the selected row the user clicked def get_selected(self): model, iter = self.selection.get_selected() if iter: system = model.get_value(iter, 0) name = model.get_value(iter, 14) # print "System:%s Name:%s"% (system,name) return system, name else: return None, None def on_selection_changed(self, treeselection): system, name = self.get_selected() # print self.status.g5x_index if system in self.selection_mask: self.mark_active(self.current_system) self.emit("selection_changed", system, name) def set_names(self, names): for offset, name in names: for row in range(0, 13): if offset == self.store[row][0]: self.store[row][14] = name def get_names(self): temp = [] for row in range(0, 13): temp.append([self.store[row][0], self.store[row][14]]) return temp # For single click selection when in edit mode def on_treeview2_button_press_event(self, widget, event): if event.button == 1 : # left click try: path, model, x, y = widget.get_path_at_pos(int(event.x), int(event.y)) self.view2.set_cursor(path, None, True) except: pass # standard Gobject method def do_get_property(self, property): name = property.name.replace('-', '_') if name in self.__gproperties.keys(): return getattr(self, name) else: raise AttributeError('unknown property %s' % property.name) # standard Gobject method # This is so that in the Glade editor, you can change the display def do_set_property(self, property, value): name = property.name.replace('-', '_') if name == 'font': try: self.set_font(value) except: pass if name == 'hide_columns': self.set_col_visible("xyzabcuvwt", True) self.set_col_visible("%s" % value, False) if name == 'hide_rows': self.set_row_visible("0123456789abc", True) self.set_row_visible("%s" % value, False) if name in self.__gproperties.keys(): setattr(self, name, value) # boiler code for variable access def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value) # for testing without glade editor: # Must linuxcnc running to see anything def main(filename = None): window = gtk.Dialog("My dialog", None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) offsetpage = OffsetPage() window.vbox.add(offsetpage) # offsetpage.set_filename("../../../configs/sim/gscreen_custom/sim.var") # offsetpage.set_col_visible("Yabuvw", False) # offsetpage.set_row_visible("456789abc", False) # offsetpage.set_row_visible("89abc", True) # offsetpage.set_to_mm() # offsetpage.set_font("sans 20") # offsetpage.set_property("highlight_color", gtk.gdk.Color('blue')) # offsetpage.set_highlight_color("violet") # offsetpage.set_foreground_color("yellow") # offsetpage.mark_active("G55") # offsetpage.selection_mask = ("Tool", "Rot", "G5x") # offsetpage.set_names([['G54', 'Default'], ["G55", "Vice1"], ['Rot', 'Rotational']]) # print offsetpage.get_names() window.connect("destroy", gtk.main_quit) window.show_all() response = window.run() if response == gtk.RESPONSE_ACCEPT: print "True" else: print "False" if __name__ == "__main__": main()