350 lines
14 KiB
Python
350 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
# GladeVcp Widget - DRO label widget
|
|
# This widget displays linuxcnc joint/axis position information.
|
|
#
|
|
# Copyright (c) 2012 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 sys, os
|
|
import math
|
|
import re
|
|
import inspect
|
|
|
|
import gi
|
|
gi.require_version("Gtk","3.0")
|
|
gi.require_version("Gdk","3.0")
|
|
from gi.repository import Gtk
|
|
from gi.repository import Gdk
|
|
from gi.repository import GObject
|
|
from gi.repository import GLib
|
|
|
|
import linuxcnc
|
|
from gladevcp.core import Status as GStat
|
|
|
|
# Set up logging
|
|
from common import logger
|
|
LOG = logger.getLogger(__name__)
|
|
# LOG.setLevel(logger.INFO) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL, VERBOSE
|
|
|
|
# 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 HAL_DRO(Gtk.Label):
|
|
__gtype_name__ = 'HAL_DRO'
|
|
__gproperties__ = {
|
|
'display_units_mm' : ( GObject.TYPE_BOOLEAN, 'Display Units', 'Display in metric or not',
|
|
False, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
|
'actual' : ( GObject.TYPE_BOOLEAN, 'Actual Position', 'Display Actual or Commanded Position',
|
|
True, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
|
'diameter' : ( GObject.TYPE_BOOLEAN, 'Diameter Adjustment', 'Display Position As Diameter',
|
|
False, GObject.ParamFlags.READWRITE | GObject.ParamFlags.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.ParamFlags.READWRITE|GObject.ParamFlags.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.ParamFlags.READWRITE|GObject.ParamFlags.CONSTRUCT),
|
|
'joint_number' : ( GObject.TYPE_INT, 'Joint Number', '0:X 1:Y 2:Z etc',
|
|
0, 8, 0, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
|
'reference_type' : ( GObject.TYPE_INT, 'Reference Type', '0: Absolute 1:Relative 2:Distance-to-go',
|
|
0, 2, 0, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
|
'font_family' : ( GObject.TYPE_STRING, 'Font Family', 'The font family of the DRO text: e.g. mono',
|
|
"sans", GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
|
'font_size' : ( GObject.TYPE_INT, 'Font Size', 'The size of the DRO text from 8 to 96',
|
|
8, 96, 26, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
|
'font_weight' : ( GObject.TYPE_STRING, 'Font Weight',
|
|
'The size of the DRO text: lighter, normal, bold, or bolder',
|
|
"bold", GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT),
|
|
'unhomed_color' : (Gdk.RGBA.__gtype__, 'Unhomed Color', 'The color of the DRO text when not homed',
|
|
GObject.ParamFlags.READWRITE),
|
|
'homed_color' : (Gdk.RGBA.__gtype__, 'Homed Color', 'The color of the DRO text when homed',
|
|
GObject.ParamFlags.READWRITE),
|
|
'background_color' : (Gdk.RGBA.__gtype__, 'Background Color', 'The color of the DRO background',
|
|
GObject.ParamFlags.READWRITE),
|
|
}
|
|
__gproperties = __gproperties__
|
|
|
|
def __init__(self, *a, **kw):
|
|
self.css = Gtk.CssProvider()
|
|
self.css_text = f"""label {{
|
|
font-family: sans;
|
|
font-size: 26px;
|
|
font-weight: bold;
|
|
background: black;
|
|
}}
|
|
.dro_unhomed {{color: red}}
|
|
.dro_homed {{color: green}}
|
|
"""
|
|
Gtk.Label.__init__(self, *a, **kw)
|
|
self.status = linuxcnc.stat()
|
|
self.display_units_mm = 0
|
|
self.machine_units_mm = 0
|
|
self.unit_convert=[1]*9
|
|
try:
|
|
self.inifile = linuxcnc.ini(INIPATH)
|
|
# check the INI file if UNITS are set to mm"
|
|
# first check the global settings
|
|
units=self.inifile.find("TRAJ","LINEAR_UNITS")
|
|
if units==None:
|
|
# else then the X axis units
|
|
units=self.inifile.find("AXIS_X","UNITS")
|
|
self.linuxcnc = True
|
|
except:
|
|
units = "inch"
|
|
self.linuxcnc = False
|
|
if units == "mm" or units == "metric" or units == "1.0":
|
|
self.machine_units_mm = 1
|
|
conversion = [1.0/25.4]*3+[1]*3+[1.0/25.4]*3
|
|
else:
|
|
self.machine_units_mm = 0
|
|
conversion = [25.4]*3+[1]*3+[25.4]*3
|
|
self.set_machine_units(self.machine_units_mm, conversion)
|
|
self.glade = True if len(inspect.stack()) == 1 else False
|
|
self.get_style_context().add_provider(self.css, Gtk.STYLE_PROVIDER_PRIORITY_FALLBACK)
|
|
self.get_style_context().add_class('dro_unhomed')
|
|
self.unhomed_color = self.str_to_rgba('red')
|
|
self.homed_color = self.str_to_rgba('green')
|
|
self.background_color = self.str_to_rgba('black')
|
|
if self.linuxcnc:
|
|
GStat().connect('homed', self._homed )
|
|
GStat().connect('unhomed', self._homed )
|
|
GLib.timeout_add(100, self.periodic)
|
|
|
|
def do_get_property(self, property):
|
|
name = property.name.replace('-', '_')
|
|
if name in list(self.__gproperties.keys()):
|
|
return getattr(self, name)
|
|
else:
|
|
raise AttributeError('unknown property %s' % property.name)
|
|
|
|
def do_set_property(self, property, value):
|
|
name = property.name.replace('-', '_')
|
|
if name in ('mm_text_template','imperial_text_template'):
|
|
try:
|
|
v = value % 0.0
|
|
except:
|
|
LOG.error(f"Invalid format string '{value}' for '{name}'")
|
|
return False
|
|
elif name == 'font_family':
|
|
if not isinstance(value, str):
|
|
LOG.warning(f"Invalid font_family '{value}', it must exist")
|
|
value = "sans"
|
|
self.set_style("family", value)
|
|
elif name == "font_size":
|
|
try:
|
|
if int(value) < 8 and int(value) > 96:
|
|
LOG.warning(f"Invalid font_size '{value}', " \
|
|
"it must be an integer from 8 to 96")
|
|
value = 26
|
|
except:
|
|
LOG.warning(f"Invalid font_size '{value}', " \
|
|
"it must be an integer from 8 to 96")
|
|
value = 26
|
|
self.set_style("size", value)
|
|
elif name == 'font_weight':
|
|
if value not in ('lighter', 'normal', 'bold', 'bolder'):
|
|
if not self.glade:
|
|
LOG.warning(f"Invalid font_weight '{value}', " \
|
|
"it must be one of 'lighter', 'normal', 'bold', or 'bolder'")
|
|
value = "bold"
|
|
self.set_style("weight", value)
|
|
elif name == "unhomed_color":
|
|
if not value: value = self.unhomed_color
|
|
if isinstance(value, gi.overrides.Gdk.RGBA):
|
|
self.set_style("unhomed", self.rgba_to_hex(value))
|
|
else:
|
|
LOG.warning(f"Invalid unhomed_color '{value}', " \
|
|
"it should be a Gdk.RGBA color")
|
|
elif name == "homed_color":
|
|
if not value: value = self.homed_color
|
|
if isinstance(value, gi.overrides.Gdk.RGBA):
|
|
self.set_style("homed", self.rgba_to_hex(value))
|
|
else:
|
|
LOG.warning(f"Invalid homed_color '{value}', " \
|
|
"it should be a Gdk.RGBA color")
|
|
elif name == "background_color":
|
|
if not value: value = self.background_color
|
|
if isinstance(value, gi.overrides.Gdk.RGBA):
|
|
self.set_style("background", self.rgba_to_hex(value))
|
|
else:
|
|
LOG.warning(f"Invalid background_color '{value}', " \
|
|
"it should be a Gdk.RGBA color")
|
|
if name in list(self.__gproperties.keys()):
|
|
setattr(self, name, value)
|
|
self.queue_draw()
|
|
else:
|
|
raise AttributeError('unknown property %s' % property.name)
|
|
|
|
def set_style(self, property, value):
|
|
if property == "family":
|
|
old = '-fam.*'
|
|
new = f"-family: {value};"
|
|
elif property == "size":
|
|
old = '-siz.*'
|
|
new = f"-size: {value}px;"
|
|
elif property == "weight":
|
|
old = '-wei.*'
|
|
new = f"-weight: {value};"
|
|
elif property == "background":
|
|
old = 'background.*'
|
|
new = f"background: {value};"
|
|
elif property == "unhomed":
|
|
old = '.dro_u.*'
|
|
new = f".dro_unhomed {{color: {value}}}"
|
|
elif property == "homed":
|
|
old = '.dro_h.*'
|
|
new = f".dro_homed {{color: {value}}}"
|
|
else:
|
|
LOG.warning(f"'{property}' is an unknown property")
|
|
return
|
|
self.css_text = re.sub(old , new, self.css_text)
|
|
try:
|
|
self.css.load_from_data(bytes(self.css_text, 'utf-8'))
|
|
except:
|
|
if not self.glade:
|
|
LOG.warning(f"'{value}' is an invalid value for '{property}'")
|
|
self.queue_draw()
|
|
|
|
def rgba_to_hex(self, rgba):
|
|
rgbatuple = ((int(rgba.red * 255), int(rgba.green * 255), int(rgba.blue * 255)))
|
|
hex = f"#{''.join(f'{i:02X}' for i in rgbatuple)}"
|
|
return hex
|
|
|
|
def str_to_rgba(self, str):
|
|
rgba = Gdk.RGBA()
|
|
rgba.parse(str)
|
|
return rgba
|
|
|
|
def periodic(self):
|
|
if self.linuxcnc:
|
|
self.status.poll()
|
|
absolute,relative,dtg = self.position()
|
|
else:
|
|
sys = 0
|
|
relative = absolute = dtg = [9999.999,0,0,0,0,0,0,0,0]
|
|
if self.display_units_mm:
|
|
tmpl = lambda s: self.mm_text_template % s
|
|
else:
|
|
tmpl = lambda s: self.imperial_text_template % s
|
|
if self.diameter:
|
|
scale = 2.0
|
|
else:
|
|
scale = 1
|
|
if self.reference_type == 0:
|
|
self.set_text(tmpl(absolute[self.joint_number]*scale))
|
|
elif self.reference_type == 1:
|
|
self.set_text(tmpl(relative[self.joint_number]*scale))
|
|
elif self.reference_type == 2:
|
|
self.set_text(tmpl(dtg[self.joint_number]*scale))
|
|
return True
|
|
|
|
def position(self):
|
|
if self.actual:
|
|
p = self.status.actual_position
|
|
else:
|
|
p = self.status.position
|
|
dtg = self.status.dtg
|
|
|
|
x = p[0] - self.status.g5x_offset[0] - self.status.tool_offset[0]
|
|
y = p[1] - self.status.g5x_offset[1] - self.status.tool_offset[1]
|
|
z = p[2] - self.status.g5x_offset[2] - self.status.tool_offset[2]
|
|
a = p[3] - self.status.g5x_offset[3] - self.status.tool_offset[3]
|
|
b = p[4] - self.status.g5x_offset[4] - self.status.tool_offset[4]
|
|
c = p[5] - self.status.g5x_offset[5] - self.status.tool_offset[5]
|
|
u = p[6] - self.status.g5x_offset[6] - self.status.tool_offset[6]
|
|
v = p[7] - self.status.g5x_offset[7] - self.status.tool_offset[7]
|
|
w = p[8] - self.status.g5x_offset[8] - self.status.tool_offset[8]
|
|
|
|
if self.status.rotation_xy != 0:
|
|
t = math.radians(-self.status.rotation_xy)
|
|
xr = x * math.cos(t) - y * math.sin(t)
|
|
yr = x * math.sin(t) + y * math.cos(t)
|
|
x = xr
|
|
y = yr
|
|
|
|
x -= self.status.g92_offset[0]
|
|
y -= self.status.g92_offset[1]
|
|
z -= self.status.g92_offset[2]
|
|
a -= self.status.g92_offset[3]
|
|
b -= self.status.g92_offset[4]
|
|
c -= self.status.g92_offset[5]
|
|
u -= self.status.g92_offset[6]
|
|
v -= self.status.g92_offset[7]
|
|
w -= self.status.g92_offset[8]
|
|
|
|
relp = [x, y, z, a, b, c, u, v, w]
|
|
if self.display_units_mm != self.machine_units_mm:
|
|
p = self.convert_units(p)
|
|
relp = self.convert_units(relp)
|
|
dtg = self.convert_units(dtg)
|
|
return p,relp,dtg
|
|
|
|
def _homed(self, widget, data=None):
|
|
if self.status.kinematics_type != linuxcnc.KINEMATICS_IDENTITY:
|
|
return
|
|
else:
|
|
self.status.poll()
|
|
homed = self.status.homed[self.joint_number]
|
|
if homed:
|
|
self.get_style_context().remove_class('dro_unhomed')
|
|
self.get_style_context().add_class('dro_homed')
|
|
self.set_property("homed-color", self.homed_color)
|
|
else:
|
|
self.get_style_context().remove_class('dro_homed')
|
|
self.get_style_context().add_class('dro_unhomed')
|
|
self.set_property("unhomed-color", self.unhomed_color)
|
|
|
|
def set_machine_units(self, u, c):
|
|
self.machine_units_mm = u
|
|
self.unit_convert = c
|
|
|
|
def convert_units(self, v):
|
|
c = self.unit_convert
|
|
return list(map(lambda x,y: x*y, v, c))
|
|
|
|
def set_to_inch(self):
|
|
self.display_units_mm = 0
|
|
|
|
def set_to_mm(self):
|
|
self.display_units_mm = 1
|
|
|
|
def set_to_diameter(self):
|
|
self.diameter = True
|
|
|
|
def set_to_radius(self):
|
|
self.diameter = False
|
|
|
|
# for testing without glade editor:
|
|
def main():
|
|
window = Gtk.Dialog("My dialog",
|
|
None,
|
|
modal = True,
|
|
destroy_with_parent = True)
|
|
window.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT,
|
|
Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT)
|
|
dro = HAL_DRO()
|
|
window.vbox.add(dro)
|
|
window.connect("destroy", Gtk.main_quit)
|
|
window.show_all()
|
|
response = window.run()
|
|
if response == Gtk.ResponseType.ACCEPT:
|
|
print("ok")
|
|
else:
|
|
print("cancel")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|