s/ini/INI/ - smallish correction Update lib/python/gladevcp/hal_gremlin.py Co-authored-by: Jérémie Tarot <silopolis@gmail.com> Update lib/python/gremlin_view.py Co-authored-by: Jérémie Tarot <silopolis@gmail.com> Update lib/python/qtvcp/widgets/adjustment_bar.py Co-authored-by: Jérémie Tarot <silopolis@gmail.com> Update src/hal/user_comps/vfs11_vfd/vfs11_vfd.c Co-authored-by: Jérémie Tarot <silopolis@gmail.com> Update lib/python/qtvcp/widgets/adjustment_bar.py Co-authored-by: Jérémie Tarot <silopolis@gmail.com> More 'ini' -> 'INI' in src ini -> INI to sync tests Update lib/python/gremlin_view.py Co-authored-by: Jérémie Tarot <silopolis@gmail.com> Update lib/python/qtvcp/widgets/offset_tool_button.py Co-authored-by: Jérémie Tarot <silopolis@gmail.com> s/modbus/Modbus/ Update lib/python/qtvcp/widgets/offset_tool_button.py Co-authored-by: Jérémie Tarot <silopolis@gmail.com> Update scripts/linuxcnc.in Co-authored-by: Jérémie Tarot <silopolis@gmail.com> Update src/emc/rs274ngc/interp_namedparams.cc Co-authored-by: Jérémie Tarot <silopolis@gmail.com> Update src/emc/usr_intf/emcsh.cc Co-authored-by: Jérémie Tarot <silopolis@gmail.com> docs: inifile -> INI file, etc
566 lines
18 KiB
Python
566 lines
18 KiB
Python
#!/usr/bin/env python3
|
|
|
|
#------------------------------------------------------------------------------
|
|
# Copyright: 2013
|
|
# Author: Dewey Garrett <dgarrett@panix.com>
|
|
#
|
|
# 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.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
#------------------------------------------------------------------------------
|
|
|
|
"""gremlin_view
|
|
Provide class: GremlinView for gremlin with buttons for simpler embedding
|
|
Standalone functionality if linuxcnc running
|
|
|
|
A default ui file (gremlin_view.ui) is provided for a default
|
|
button arrangement but a user may provide their own by supplying
|
|
the glade_file argument.
|
|
|
|
The following objects are mandatory:
|
|
|
|
'gremlin_view_window' toplevel window
|
|
'gremlin_view_hal_gremlin' hal_gremlin
|
|
'gremlin_view_box' HBox or VBox' containing hal_gremlin
|
|
|
|
Optional radiobutton group names:
|
|
'select_p_view'
|
|
'select_x_view'
|
|
'select_y_view'
|
|
'select_z_view'
|
|
'select_z2_view'
|
|
|
|
Optional Checkbuttons names:
|
|
'enable_dro'
|
|
'show_machine_speed
|
|
'show_distance_to_go'
|
|
'show_limits'
|
|
'show_extents'
|
|
'show_tool'
|
|
'show_metric'
|
|
|
|
Callbacks are provided for the following buttons actions
|
|
on_clear_live_plotter_clicked
|
|
on_enable_dro_clicked
|
|
on_zoomin_pressed
|
|
on_zoomout_pressed
|
|
on_pan_x_minus_pressed
|
|
on_pan_x_plus_pressed
|
|
on_pan_y_minus_pressed
|
|
on_pan_y_plus_pressed
|
|
on_show_tool_clicked
|
|
on_show_metric_clicked
|
|
on_show_extents_clicked
|
|
on_select_p_view_clicked
|
|
on_select_x_view_clicked
|
|
on_select_y_view_clicked
|
|
on_select_z_view_clicked
|
|
on_select_z2_view_clicked
|
|
on_show_distance_to_go_clicked
|
|
on_show_machine_speed_clicked
|
|
on_show_limits_clicked
|
|
"""
|
|
|
|
import gi
|
|
gi.require_version("Gtk","3.0")
|
|
from gi.repository import Gtk
|
|
from gi.repository import GObject
|
|
from gi.repository import GLib
|
|
import os
|
|
import sys
|
|
import gladevcp.hal_actions # reqd for Builder
|
|
import linuxcnc
|
|
import time
|
|
import subprocess
|
|
import gettext
|
|
import datetime
|
|
import glib # for glib.GError
|
|
|
|
g_ui_dir = linuxcnc.SHARE + "/linuxcnc"
|
|
g_periodic_secs = 1 # integer
|
|
g_delta_pixels = 10
|
|
g_move_delay_secs = 0.2
|
|
g_progname = os.path.basename(sys.argv[0])
|
|
g_verbose = False
|
|
|
|
LOCALEDIR = linuxcnc.SHARE + "/locale"
|
|
gettext.install("linuxcnc", localedir=LOCALEDIR)
|
|
|
|
def ini_check ():
|
|
"""set environmental variable and change directory"""
|
|
# Note:
|
|
# hal_gremlin gets INI file from os.environ (only)
|
|
# hal_gremlin expects cwd to be same as INI file
|
|
ini_filename = get_linuxcnc_ini_file()
|
|
if ini_filename is not None:
|
|
os.putenv('INI_FILE_NAME',ini_filename) # ineffective
|
|
os.environ['INI_FILE_NAME'] = ini_filename # need for hal_gremlin
|
|
os.chdir(os.path.dirname(ini_filename))
|
|
if g_verbose:
|
|
print(('ini_check: INI_FILENAME= %s' % ini_filename))
|
|
print(('ini_check: curdir= %s' % os.path.curdir))
|
|
return True # success
|
|
print((_('%s: LinuxCNC INI file not available') % g_progname))
|
|
return False # exit here crashes glade-gtk2
|
|
|
|
def get_linuxcnc_ini_file():
|
|
"""find LinuxCNC INI file with pgrep"""
|
|
ps = subprocess.Popen('ps -C linuxcncsvr --no-header -o args'.split(),
|
|
stdout=subprocess.PIPE
|
|
)
|
|
p,e = ps.communicate()
|
|
if p is not None: p=p.decode()
|
|
if e is not None: e=e.decode()
|
|
|
|
if ps.returncode:
|
|
print(_('get_linuxcnc_ini_file: stdout= %s') % p)
|
|
print(_('get_linuxcnc_ini_file: stderr= %s') % e)
|
|
return None
|
|
|
|
ans = p.split()[p.split().index('-ini')+1]
|
|
return ans
|
|
|
|
class GremlinView():
|
|
"""Implement a standalone gremlin with some buttons
|
|
and provide means to embed using a glade ui file"""
|
|
def __init__(self
|
|
,glade_file=None #None: use default ui
|
|
,parent=None
|
|
,width=None
|
|
,height=None
|
|
,alive=True
|
|
,gtk_theme_name="Follow System Theme"
|
|
):
|
|
|
|
self.alive = alive
|
|
linuxcnc_running = False
|
|
if ini_check():
|
|
linuxcnc_running = True
|
|
|
|
if not linuxcnc_running:
|
|
print("gremlin_view: LinuxCNC must be running")
|
|
sys.exit(1)
|
|
|
|
if (glade_file == None):
|
|
glade_file = os.path.join(g_ui_dir,'gremlin_view.ui')
|
|
|
|
print("gremlin_view glade_file=",glade_file)
|
|
|
|
bldr = Gtk.Builder()
|
|
try:
|
|
bldr.add_from_file(glade_file)
|
|
except glib.GError as detail:
|
|
print('\nGremlinView:%s\n' % detail)
|
|
raise glib.GError(detail) # re-raise
|
|
|
|
# required objects:
|
|
self.topwindow = bldr.get_object('gremlin_view_window')
|
|
self.gbox = bldr.get_object('gremlin_view_box')
|
|
self.halg = bldr.get_object('gremlin_view_hal_gremlin')
|
|
|
|
#self.halg.show_lathe_radius = 1 # for test, hal_gremlin default is Dia
|
|
|
|
if not linuxcnc_running:
|
|
# blanks display area:
|
|
self.halg.set_has_window(False)
|
|
|
|
# radiobuttons for selecting view: (expect at least one)
|
|
select_view_letters = ['p','x','y','z','z2']
|
|
for vletter in select_view_letters:
|
|
try:
|
|
obj = bldr.get_object('select_' + vletter + '_view')
|
|
self.my_view = vletter # first letter found is initial view
|
|
break
|
|
except:
|
|
continue
|
|
|
|
check_button_objects = ['enable_dro'
|
|
,'show_machine_speed'
|
|
,'show_distance_to_go'
|
|
,'show_limits'
|
|
,'show_extents'
|
|
,'show_tool'
|
|
,'show_metric'
|
|
]
|
|
for objname in check_button_objects:
|
|
obj = bldr.get_object(objname)
|
|
if obj is not None:
|
|
setattr(self,'objname',obj)
|
|
obj.set_active(True)
|
|
else:
|
|
if g_verbose:
|
|
print('%s: Optional object omitted <%s>'
|
|
% (__file__,objname))
|
|
|
|
# show_metric: use INI file
|
|
#FIXME show_metric,lunits s/b mandatory?
|
|
try:
|
|
objname = 'show_metric'
|
|
self.show_metric = bldr.get_object('show_metric')
|
|
lunits = self.halg.inifile.find('TRAJ','LINEAR_UNITS')
|
|
except AttributeError:
|
|
if g_verbose:
|
|
print('%s: Problem for <%s>' % (__file__,objname))
|
|
|
|
if linuxcnc_running:
|
|
if lunits == 'inch':
|
|
self.halg.metric_units = False
|
|
elif lunits == 'mm':
|
|
self.halg.metric_units = True
|
|
else:
|
|
raise AttributeError('%s: unknown [TRAJ]LINEAR_UNITS] <%s>'
|
|
% (__file__,lunits))
|
|
|
|
if self.halg.get_show_metric():
|
|
self.show_metric.set_active(True)
|
|
else:
|
|
self.show_metric.set_active(False)
|
|
|
|
if alive:
|
|
bldr.connect_signals(self)
|
|
# todo: to remove other signals on halg:
|
|
# bldr.disconnect(integer_handle_id)
|
|
# bldr.disconnect_by_func('func_name')
|
|
# bldr.handler_disconnect()
|
|
|
|
minwidth = 300 # smallest size
|
|
minheight = 300 # smallest size
|
|
|
|
if (width is None):
|
|
width = minwidth
|
|
else:
|
|
width = int(width)
|
|
|
|
if (height is None):
|
|
height = minheight
|
|
else:
|
|
height = int(height)
|
|
|
|
if width < minwidth:
|
|
width = minwidth
|
|
if height < minheight:
|
|
height = minheight
|
|
|
|
# err from gremlin if omit this
|
|
self.halg.width = width
|
|
self.halg.height = height
|
|
self.halg.set_size_request(width,height)
|
|
|
|
# self.x,self.y used in conjunction with pan buttons
|
|
# but using mouse may change internal values in gremlin
|
|
# resulting in unexpected movement if both mouse and
|
|
# pan buttons are used
|
|
self.x = 0
|
|
self.y = 0
|
|
|
|
# prevent flashing topwindow
|
|
self.topwindow.iconify()
|
|
self.topwindow.show_all()
|
|
self.topwindow.hide()
|
|
|
|
self.preview_file(None)
|
|
if linuxcnc_running:
|
|
try:
|
|
self.preview_file(None)
|
|
except linuxcnc.error as detail:
|
|
print('linuxcnc.error')
|
|
print(' detail=',detail)
|
|
|
|
try:
|
|
self.last_file = self.halg._current_file
|
|
except AttributeError:
|
|
self.last_file = None
|
|
self.last_file_mtime = None
|
|
|
|
self.parent = parent
|
|
if self.parent is None:
|
|
# topwindow (standalone) application
|
|
# print "TOP:",gtk_theme_name
|
|
screen = self.topwindow.get_screen()
|
|
else:
|
|
# print "REPARENT:",gtk_theme_name
|
|
screen = self.halg.get_screen()
|
|
|
|
# not valid for Gtk3:
|
|
# settings = Gtk.settings_get_for_screen(screen)
|
|
# systname = settings.get_property("gtk-theme-name")
|
|
# if ( (gtk_theme_name is None)
|
|
# or (gtk_theme_name == "")
|
|
# or (gtk_theme_name == "Follow System Theme")):
|
|
# gtk_theme_name = systname
|
|
# settings.set_string_property('gtk-theme-name',gtk_theme_name,"")
|
|
|
|
self.topwindow.connect('destroy',self._topwindowquit)
|
|
self.topwindow.show_all()
|
|
self.running = True
|
|
|
|
if self.last_file is not None:
|
|
self.topwindow.set_title(g_progname
|
|
+ ': ' + os.path.basename(self.last_file))
|
|
self.last_file_mtime = datetime.datetime.fromtimestamp(
|
|
os.path.getmtime(self.last_file))
|
|
|
|
self.ct = 0
|
|
if self.parent is None: self.topwindow.deiconify()
|
|
self._periodic('BEGIN')
|
|
GLib.timeout_add_seconds(g_periodic_secs,self._periodic,'Continue')
|
|
# or use GLib.timeout_add() interval units in mS
|
|
|
|
def _periodic(self,arg):
|
|
# print "_periodic:",self.ct,arg
|
|
self.ct +=1
|
|
self.halg.poll()
|
|
|
|
if (self.parent is not None) and (self.ct) == 2:
|
|
# not sure why delay is needed for reparenting
|
|
# but without, the display of the (rgb) axes
|
|
# and the cone to not appear in gremlin
|
|
# print "REPARENT:",self.gbox, self.parent
|
|
#-----------------------------------------------------------------------------
|
|
# determine if glade interface designer is running
|
|
# to avoid assertion error:
|
|
# gtk_widget_reparent_fixup_child: assertion failed: (client_data != NULL)
|
|
is_glade = False
|
|
if 'glade' in sys.argv[0] and 'gladevcp' not in sys.argv[0]:
|
|
for d in os.environ['PATH'].split(':'):
|
|
f = os.path.join(d,sys.argv[0])
|
|
if ( os.path.isfile(f)
|
|
and os.access(f, os.X_OK)):
|
|
is_glade = True
|
|
break
|
|
#-----------------------------------------------------------------------------
|
|
if (not is_glade):
|
|
self.gbox.reparent(self.parent)
|
|
self.gbox.show_all()
|
|
self.gbox.connect('destroy',self._gboxquit)
|
|
return True
|
|
|
|
try:
|
|
current_file = self.halg._current_file
|
|
except AttributeError:
|
|
current_file = None
|
|
if current_file is None:
|
|
return True # keep trying _periodic()
|
|
current_file_mtime = datetime.datetime.fromtimestamp(
|
|
os.path.getmtime(current_file))
|
|
if ( current_file != self.last_file
|
|
or current_file_mtime != self.last_file_mtime):
|
|
# print('old,new',self.last_file_mtime,current_file_mtime)
|
|
self.last_file = current_file
|
|
self.last_file_mtime = current_file_mtime
|
|
self.halg.hide()
|
|
self.halg.load()
|
|
getattr(self.halg,'set_view_%s' % self.my_view)()
|
|
self.halg.show()
|
|
if self.topwindow is not None:
|
|
self.topwindow.set_title(g_progname
|
|
+ ': ' + os.path.basename(self.last_file))
|
|
return True # repeat _periodic()
|
|
|
|
def preview_file(self,filename):
|
|
self.halg.hide()
|
|
# handle exception in case glade is running
|
|
try:
|
|
self.halg.load(filename or None)
|
|
except Exception as detail:
|
|
if self.alive:
|
|
print("file load fail:",Exception,detail)
|
|
pass
|
|
getattr(self.halg,'set_view_%s' % self.my_view)()
|
|
self.halg.show()
|
|
|
|
def _gboxquit(self,w):
|
|
self.running = False # stop periodic checks
|
|
|
|
def _topwindowquit(self,w):
|
|
self.running = False # stop periodic checks
|
|
Gtk.main_quit()
|
|
|
|
def expose(self):
|
|
self.halg.expose()
|
|
|
|
def on_zoomin_pressed(self,w):
|
|
while w.get_state() == Gtk.StateType.ACTIVE:
|
|
self.halg.zoomin()
|
|
time.sleep(g_move_delay_secs)
|
|
Gtk.main_iteration_do(blocking=0)
|
|
|
|
def on_zoomout_pressed(self,w):
|
|
while w.get_state() == Gtk.StateType.ACTIVE:
|
|
self.halg.zoomout()
|
|
time.sleep(g_move_delay_secs)
|
|
Gtk.main_iteration_do(blocking=0)
|
|
|
|
def on_pan_x_minus_pressed(self,w):
|
|
while w.get_state() == Gtk.StateType.ACTIVE:
|
|
self.x -= g_delta_pixels
|
|
self.halg.translate(self.x,self.y)
|
|
time.sleep(g_move_delay_secs)
|
|
Gtk.main_iteration_do(blocking=0)
|
|
|
|
def on_pan_x_plus_pressed(self,w):
|
|
while w.get_state() == Gtk.StateType.ACTIVE:
|
|
self.x += g_delta_pixels
|
|
self.halg.translate(self.x,self.y)
|
|
time.sleep(g_move_delay_secs)
|
|
Gtk.main_iteration_do(blocking=0)
|
|
|
|
def on_pan_y_minus_pressed(self,w):
|
|
while w.get_state() == Gtk.StateType.ACTIVE:
|
|
self.y += g_delta_pixels
|
|
self.halg.translate(self.x,self.y)
|
|
time.sleep(g_move_delay_secs)
|
|
Gtk.main_iteration_do(blocking=0)
|
|
|
|
def on_pan_y_plus_pressed(self,w):
|
|
while w.get_state() == Gtk.StateType.ACTIVE:
|
|
self.y -= g_delta_pixels
|
|
self.halg.translate(self.x,self.y)
|
|
time.sleep(g_move_delay_secs)
|
|
Gtk.main_iteration_do(blocking=0)
|
|
|
|
def on_clear_live_plotter_clicked(self,w):
|
|
self.halg.clear_live_plotter()
|
|
|
|
def on_enable_dro_clicked(self,w):
|
|
if w.get_active():
|
|
self.halg.enable_dro = True
|
|
else:
|
|
self.halg.enable_dro = False
|
|
self.expose()
|
|
|
|
def on_show_machine_speed_clicked(self,w):
|
|
if w.get_active():
|
|
self.halg.show_velocity = True
|
|
else:
|
|
self.halg.show_velocity = False
|
|
self.expose()
|
|
|
|
def on_show_distance_to_go_clicked(self,w):
|
|
if w.get_active():
|
|
self.halg.show_dtg = True
|
|
else:
|
|
self.halg.show_dtg = False
|
|
self.expose()
|
|
|
|
def on_show_limits_clicked(self,w):
|
|
if w.get_active():
|
|
self.halg.show_limits = True
|
|
else:
|
|
self.halg.show_limits = False
|
|
self.expose()
|
|
|
|
def on_show_extents_clicked(self,w):
|
|
if w.get_active():
|
|
self.halg.show_extents_option = True
|
|
else:
|
|
self.halg.show_extents_option = False
|
|
self.expose()
|
|
|
|
def on_show_tool_clicked(self,w):
|
|
if w.get_active():
|
|
self.halg.show_tool = True
|
|
else:
|
|
self.halg.show_tool = False
|
|
self.expose()
|
|
|
|
def on_show_metric_clicked(self,w):
|
|
if w.get_active():
|
|
self.halg.metric_units = True
|
|
else:
|
|
self.halg.metric_units = False
|
|
self.expose()
|
|
|
|
def on_select_p_view_clicked(self,w):
|
|
self.set_view_per_w(w,'p')
|
|
|
|
def on_select_x_view_clicked(self,w):
|
|
self.set_view_per_w(w,'x')
|
|
|
|
def on_select_y_view_clicked(self,w):
|
|
self.set_view_per_w(w,'y')
|
|
|
|
def on_select_z_view_clicked(self,w):
|
|
self.set_view_per_w(w,'z')
|
|
|
|
def on_select_z2_view_clicked(self,w):
|
|
self.set_view_per_w(w,'z2')
|
|
|
|
def set_view_per_w(self,w,vletter):
|
|
self.my_view = vletter
|
|
getattr(self.halg,'set_view_%s' % vletter)()
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Standalone (and demo) usage:
|
|
def standalone_gremlin_view():
|
|
|
|
import getopt
|
|
#---------------------------------------
|
|
def usage(msg=None):
|
|
|
|
print(("""\n
|
|
Usage: %s [options]\n
|
|
Options: [-h | --help]
|
|
[-v | --verbose]
|
|
[-W | --width] width
|
|
[-H | --height] height
|
|
[-f | --file] glade_file
|
|
|
|
Note: linuxcnc must be running on same machine
|
|
""") % g_progname)
|
|
if msg:
|
|
print('\n%s' % msg)
|
|
#---------------------------------------
|
|
|
|
glade_file = None
|
|
width = None
|
|
height = None
|
|
vbose = False
|
|
try:
|
|
options,remainder = getopt.getopt(sys.argv[1:]
|
|
, 'f:hH:vW:'
|
|
, ['file='
|
|
,'help'
|
|
,'width='
|
|
,'height='
|
|
]
|
|
)
|
|
except getopt.GetoptError as msg:
|
|
usage()
|
|
print('GetoptError: %s' % msg)
|
|
sys.exit(1)
|
|
for opt,arg in options:
|
|
if opt in ('-h','--help'):
|
|
usage(),sys.exit(0)
|
|
if opt in ('-v','--verbose'):
|
|
g_verbose = True
|
|
continue
|
|
if opt in ('-W','--width' ): width=arg
|
|
if opt in ('-H','--height'): height=arg
|
|
if opt in ('-f','--file'): glade_file=arg
|
|
if remainder:
|
|
usage('unknown argument:%s' % remainder)
|
|
sys.exit(1)
|
|
|
|
try:
|
|
g = GremlinView(glade_file=glade_file
|
|
,width=width
|
|
,height=height
|
|
)
|
|
Gtk.main()
|
|
except linuxcnc.error as detail:
|
|
Gtk.main()
|
|
print('linuxcnc.error:',detail)
|
|
usage()
|
|
|
|
# vim: sts=4 sw=4 et
|