Import LinuxCNC source tree
This commit is contained in:
512
lib/python/qtvcp/widgets/dro_widget.py
Normal file
512
lib/python/qtvcp/widgets/dro_widget.py
Normal file
@@ -0,0 +1,512 @@
|
||||
#!/usr/bin/env python3
|
||||
# qtVcp Widget - DRO label widget
|
||||
# This widgets displays linuxcnc axis position information.
|
||||
#
|
||||
# Copyright (c) 2017 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 linuxcnc
|
||||
|
||||
from qtpy import QtCore
|
||||
from qtpy.QtWidgets import QMenu, QAction
|
||||
from qtpy.QtGui import QIcon
|
||||
|
||||
from qtvcp.widgets.simple_widgets import ScaledLabel
|
||||
from qtvcp.widgets.widget_baseclass import _HalWidgetBase
|
||||
from qtvcp.core import Status, Info, Action
|
||||
from qtvcp import logger
|
||||
|
||||
# Instantiate the libraries with global reference
|
||||
# INFO holds INI file details
|
||||
# STATUS gives us status messages from linuxcnc
|
||||
# LOG is for running code logging
|
||||
STATUS = Status()
|
||||
INFO = Info()
|
||||
ACTION = Action()
|
||||
LOG = logger.getLogger(__name__)
|
||||
|
||||
# Force the log level for this module
|
||||
# LOG.setLevel(logger.INFO) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
|
||||
|
||||
class DROLabel(ScaledLabel, _HalWidgetBase):
|
||||
def __init__(self, parent=None):
|
||||
super(DROLabel, self).__init__(parent)
|
||||
self._mode = False
|
||||
self._joint_type = 1
|
||||
self.diameter = False
|
||||
self.reference_type = 0
|
||||
# axis index of 9 axis
|
||||
self.axis_index = 0
|
||||
# linuxcnc joint number
|
||||
self._jointNum = 0
|
||||
self.display_units_mm = 0
|
||||
self.metric_text_template = '%10.3f'
|
||||
self.imperial_text_template = '%9.4f'
|
||||
self.angular_text_template = '%9.2f'
|
||||
self.setText('--------------')
|
||||
self.allow_reference_change_requests = True
|
||||
self.follow_m7m8_mode = True
|
||||
self.force_diameter = False
|
||||
self.force_radius = False
|
||||
self._scale = 1
|
||||
self._user = 0
|
||||
self._text =' -000.0000'
|
||||
|
||||
# menu stuff
|
||||
self.dialog_code = 'CALCULATOR'
|
||||
self._showGoto = False
|
||||
self._showZero = False
|
||||
self._showSet = False
|
||||
self._showLast = False
|
||||
self._showDivide = False
|
||||
|
||||
# for stylesheet reading
|
||||
self._isHomed = False
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
menu = QMenu(self)
|
||||
state = (STATUS.machine_is_on()
|
||||
and (STATUS.is_all_homed() or INFO.NO_HOME_REQUIRED))
|
||||
|
||||
if self._showZero:
|
||||
self.zeroButton = QAction(QIcon('exit24.png'), 'Zero', self)
|
||||
self.zeroButton.triggered.connect(self.Zero)
|
||||
self.zeroButton.setEnabled(state)
|
||||
menu.addAction(self.zeroButton)
|
||||
if self._showSet:
|
||||
self.setButton = QAction(QIcon('exit24.png'), 'Set', self)
|
||||
self.setButton.triggered.connect(self.SetOrigin)
|
||||
self.setButton.setEnabled(state)
|
||||
menu.addAction(self.setButton)
|
||||
if self._showDivide:
|
||||
self.divideButton = QAction(QIcon('exit24.png'), 'Divide By 2', self)
|
||||
self.divideButton.triggered.connect(self.Divide)
|
||||
self.divideButton.setEnabled(state)
|
||||
menu.addAction(self.divideButton)
|
||||
if self._showLast:
|
||||
self.lastButton = QAction(QIcon('exit24.png'), 'Set To Last', self)
|
||||
self.lastButton.triggered.connect(self.Last)
|
||||
self.lastButton.setEnabled(state)
|
||||
menu.addAction(self.lastButton)
|
||||
if self._showGoto:
|
||||
text = 'Go To G53 Origin in {}'.format(INFO.GET_NAME_FROM_JOINT.get(self.axis_index))
|
||||
self.goToG53Button = QAction(QIcon('exit24.png'), text, self)
|
||||
self.goToG53Button.triggered.connect(self.goToG53)
|
||||
self.goToG53Button.setEnabled(state)
|
||||
menu.addAction(self.goToG53Button)
|
||||
text = 'Go To G5x Origin in {}'.format(INFO.GET_NAME_FROM_JOINT.get(self.axis_index))
|
||||
self.goToG5xButton = QAction(QIcon('exit24.png'), text, self)
|
||||
self.goToG5xButton.triggered.connect(self.goToG5x)
|
||||
self.goToG5xButton.setEnabled(state)
|
||||
menu.addAction(self.goToG5xButton)
|
||||
|
||||
menu.popup(event.globalPos())
|
||||
|
||||
def _hal_init(self):
|
||||
super(DROLabel, self)._hal_init()
|
||||
STATUS.connect('homed', lambda w,d: self._home_status_polish(int(d), True))
|
||||
STATUS.connect('unhomed', lambda w,d: self._home_status_polish(int(d), False))
|
||||
# get position update from STATUS every 100 ms
|
||||
if self.axis_index == 10:
|
||||
STATUS.connect('current-z-rotation', self.update_rotation)
|
||||
else:
|
||||
self._jointNum = INFO.GET_JOINT_NUM_FROM_AXIS_INDEX.get(self.axis_index)
|
||||
if self._jointNum is None:
|
||||
LOG.debug('axis number {} not found in available-axis to joint conversion dict {} of widget: {}'.format(self.axis_index, INFO.GET_JOINT_NUM_FROM_AXIS_INDEX, self.objectName()))
|
||||
self._jointNum = 0
|
||||
|
||||
STATUS.connect('motion-mode-changed',self.motion_mode)
|
||||
STATUS.connect('current-position', self.update)
|
||||
STATUS.connect('metric-mode-changed', self._switch_units)
|
||||
if self.follow_m7m8_mode:
|
||||
STATUS.connect('diameter-mode', self._switch_modes)
|
||||
if self.allow_reference_change_requests:
|
||||
STATUS.connect('dro-reference-change-request', self._status_reference_change)
|
||||
|
||||
try:
|
||||
self._joint_type = INFO.JOINT_TYPE_INT[self._jointNum]
|
||||
except:
|
||||
self._joint_type = 1
|
||||
|
||||
if self._joint_type == linuxcnc.ANGULAR:
|
||||
self._current_text_template = self.angular_text_template
|
||||
elif self.display_units_mm:
|
||||
self._current_text_template = self.metric_text_template
|
||||
else:
|
||||
self._current_text_template = self.imperial_text_template
|
||||
|
||||
STATUS.connect('general',self.return_value)
|
||||
|
||||
# update ishomed property
|
||||
# polish widget so stylesheet sees the property change
|
||||
# some stylesheets color the text on home/unhome
|
||||
def _home_status_polish(self, d, state):
|
||||
if d == self._jointNum or (self.axis_index==10 and d==1):
|
||||
self.setProperty('isHomed', state)
|
||||
self.style().unpolish(self)
|
||||
self.style().polish(self)
|
||||
|
||||
def motion_mode(self, w, mode):
|
||||
if mode == linuxcnc.TRAJ_MODE_COORD:
|
||||
pass
|
||||
# Joint mode
|
||||
elif mode == linuxcnc.TRAJ_MODE_FREE:
|
||||
self._mode = False
|
||||
# axis
|
||||
elif mode == linuxcnc.TRAJ_MODE_TELEOP:
|
||||
self._mode = True
|
||||
|
||||
@QtCore.Slot(int)
|
||||
@QtCore.Slot(float)
|
||||
def update_user(self, data):
|
||||
self._user = data
|
||||
|
||||
def update_rotation(self, widget, rotation):
|
||||
degtmpl = lambda s: self.angular_text_template % s
|
||||
self.setText(degtmpl(rotation))
|
||||
|
||||
def update(self, widget, absolute, relative, dtg, joint):
|
||||
if self.display_units_mm != INFO.MACHINE_IS_METRIC:
|
||||
absolute = INFO.convert_units_9(absolute)
|
||||
relative = INFO.convert_units_9(relative)
|
||||
dtg = INFO.convert_units_9(dtg)
|
||||
|
||||
tmpl = lambda s: self._current_text_template % s
|
||||
|
||||
try:
|
||||
if self.reference_type == 0:
|
||||
if not self._mode and STATUS.stat.kinematics_type != linuxcnc.KINEMATICS_IDENTITY:
|
||||
self.setText(tmpl(joint[self._jointNum]))
|
||||
else:
|
||||
self.setText(tmpl(absolute[self.axis_index]*self._scale))
|
||||
elif self.reference_type == 1:
|
||||
self.setText(tmpl(relative[self.axis_index]*self._scale))
|
||||
elif self.reference_type == 2:
|
||||
self.setText(tmpl(dtg[self.axis_index]*self._scale))
|
||||
|
||||
elif self.reference_type == 10:
|
||||
self.setText(tmpl(self._user*self._scale))
|
||||
except:
|
||||
pass
|
||||
|
||||
def _status_reference_change(self,w ,value):
|
||||
self.reference_type = value
|
||||
|
||||
def _switch_units(self, widget, data):
|
||||
self.display_units_mm = data
|
||||
self.update_units()
|
||||
|
||||
def update_units(self):
|
||||
if self._joint_type == linuxcnc.ANGULAR:
|
||||
self._current_text_template = self.angular_text_template
|
||||
elif self.display_units_mm:
|
||||
self._current_text_template = self.metric_text_template
|
||||
else:
|
||||
self._current_text_template = self.imperial_text_template
|
||||
|
||||
def _switch_modes(self, w, mode):
|
||||
self.diameter = mode
|
||||
# only joint 0 (X) can use diameter mode
|
||||
if mode and self.axis_index == 0:
|
||||
self._scale = 2.0
|
||||
else:
|
||||
self._scale = 1
|
||||
|
||||
def set_to_inch(self):
|
||||
self.display_units_mm = 0
|
||||
if self._joint_type == linuxcnc.ANGULAR:
|
||||
self._current_text_template = self.angular_text_template
|
||||
else:
|
||||
self._current_text_template = self.imperial_text_template
|
||||
|
||||
def set_to_mm(self):
|
||||
self.display_units_mm = 1
|
||||
if self._joint_type == linuxcnc.ANGULAR:
|
||||
self._current_text_template = self.angular_text_template
|
||||
else:
|
||||
self._current_text_template = self.metric_text_template
|
||||
|
||||
def set_to_diameter(self):
|
||||
self.diameter = True
|
||||
if self.axis_index == 0:
|
||||
self._scale = 2.0
|
||||
|
||||
def set_to_radius(self):
|
||||
self.diameter = False
|
||||
self._scale = 1.0
|
||||
|
||||
# index = index of 9 axis
|
||||
def set_joint_type(self, index):
|
||||
# convert to linxcnc joint number
|
||||
self._jointNum = INFO.GET_JOINT_NUM_FROM_AXIS_INDEX.get(index)
|
||||
if self._jointNum is None:
|
||||
self._joint_type = 1
|
||||
else:
|
||||
self._joint_type = INFO.JOINT_TYPE_INT[self._jointNum]
|
||||
self. update_units()
|
||||
|
||||
#######################
|
||||
# menu option code
|
||||
#######################
|
||||
|
||||
def Zero(self):
|
||||
pos = self.get_current_position()
|
||||
if not pos is None:
|
||||
axis = "XYZABCUVW"[self.axis_index]
|
||||
ACTION.SET_AXIS_ORIGIN(axis, 0)
|
||||
STATUS.emit('update-machine-log', 'Zeroed Axis %s' % axis, 'TIME,SUCCESS')
|
||||
|
||||
def SetOrigin(self):
|
||||
pos = self.get_current_position()
|
||||
if not pos is None:
|
||||
axis = "XYZABCUVW"[self.axis_index]
|
||||
mess = {'NAME':self.dialog_code,'ID':'%s__' % self.objectName(),
|
||||
'AXIS':axis,
|
||||
'CURRENT':pos,
|
||||
'TITLE':'Set %s Origin'% axis,
|
||||
'GEONAME':'DROWidgetDialog_{}'.format(self.dialog_code),
|
||||
'AXIS':axis}
|
||||
STATUS.emit('dialog-request', mess)
|
||||
LOG.debug('message sent:{}'.format (mess))
|
||||
|
||||
# process the STATUS return message
|
||||
def return_value(self, w, message):
|
||||
num = message['RETURN']
|
||||
code = bool(message.get('ID') == '%s__'% self.objectName())
|
||||
name = bool(message.get('NAME') == self.dialog_code)
|
||||
if code and name and num is not None:
|
||||
LOG.debug('message return:{}'.format (message))
|
||||
axis = message.get('AXIS')
|
||||
ACTION.SET_AXIS_ORIGIN(axis, num)
|
||||
STATUS.emit('update-machine-log', 'Set Origin of Axis %s to %f' %(axis, num), 'TIME,SUCCESS')
|
||||
|
||||
def Divide(self):
|
||||
pos = self.get_current_position()
|
||||
if not pos is None:
|
||||
axis = "XYZABCUVW"[self.axis_index]
|
||||
try:
|
||||
x = pos/2.0
|
||||
ACTION.SET_AXIS_ORIGIN(axis, x)
|
||||
text = 'Divided Axis %s in Half - %f'% (axis, x)
|
||||
STATUS.emit('update-machine-log', text, 'TIME,SUCCESS')
|
||||
except ZeroDivisionError:
|
||||
pass
|
||||
|
||||
def Last(self):
|
||||
pos = self.get_current_position()
|
||||
if not pos is None:
|
||||
axis = "XYZABCUVW"[self.axis_index]
|
||||
last = ACTION.GET_LAST_RECORDED_ORIGIN(axis)
|
||||
ACTION.SET_AXIS_ORIGIN(axis, last)
|
||||
text = 'Reset Axis %s from %f to Last Value: %f' %(axis, pos, last)
|
||||
STATUS.emit('update-machine-log', text, 'TIME,SUCCESS')
|
||||
|
||||
def get_current_position(self):
|
||||
if STATUS.is_joint_mode():
|
||||
return None
|
||||
p,r,d = STATUS.get_position()
|
||||
if self.display_units_mm != INFO.MACHINE_IS_METRIC:
|
||||
r = INFO.convert_units_9(r)
|
||||
return r[self.axis_index]
|
||||
|
||||
def goToG53(self):
|
||||
ACTION.CALL_MDI('G53 G0 {}0'.format(INFO.GET_NAME_FROM_JOINT.get(self.axis_index)))
|
||||
def goToG5x(self):
|
||||
ACTION.CALL_MDI('G0 {}0'.format(INFO.GET_NAME_FROM_JOINT.get(self.axis_index)))
|
||||
|
||||
#########################################################################
|
||||
# This is how designer can interact with our widget properties.
|
||||
# designer will show the Property properties in the editor
|
||||
# it will use the get set and reset calls to do those actions
|
||||
#
|
||||
# _toggle_properties makes it so we can only select one option
|
||||
########################################################################
|
||||
|
||||
def _toggle_properties(self, picked):
|
||||
data = ('always_display_diameter','always_display_radius',
|
||||
'display_as_per_m7m8')
|
||||
|
||||
for i in data:
|
||||
if not i == picked:
|
||||
self[i] = False
|
||||
|
||||
# property getter/setters
|
||||
def set_force_diameter(self, data):
|
||||
self.force_diameter = data
|
||||
if data:
|
||||
self.set_to_diameter()
|
||||
self._toggle_properties('always_display_diameter')
|
||||
def get_force_diameter(self):
|
||||
return self.force_diameter
|
||||
def reset_force_diameter(self):
|
||||
self.force_diameter = False
|
||||
always_display_diameter = QtCore.Property(bool, get_force_diameter, set_force_diameter, reset_force_diameter)
|
||||
|
||||
def set_force_radius(self, data):
|
||||
self.force_radius = data
|
||||
if data:
|
||||
self.set_to_radius()
|
||||
self._toggle_properties('always_display_radius')
|
||||
def get_force_radius(self):
|
||||
return self.force_radius
|
||||
def reset_force_radius(self):
|
||||
self.force_radius = False
|
||||
always_display_radius = QtCore.Property(bool, get_force_radius, set_force_radius, reset_force_radius)
|
||||
|
||||
def set_follow_m7m8_mode(self, data):
|
||||
self.follow_m7m8_mode = data
|
||||
if data:
|
||||
self._toggle_properties('display_as_per_m7m8')
|
||||
def get_follow_m7m8_mode(self):
|
||||
return self.follow_m7m8_mode
|
||||
def reset_follow_m7m8_mode(self):
|
||||
self.follow_m7m8_mode = True
|
||||
display_as_per_m7m8 = QtCore.Property(bool, get_follow_m7m8_mode, set_follow_m7m8_mode, reset_follow_m7m8_mode)
|
||||
|
||||
def set_follow_reference(self, data):
|
||||
self.allow_reference_change_requests = data
|
||||
def get_follow_reference(self):
|
||||
return self.allow_reference_change_requests
|
||||
def reset_follow_reference(self):
|
||||
self.allow_reference_change_requests = True
|
||||
follow_reference_changes = QtCore.Property(bool, get_follow_reference, set_follow_reference, reset_follow_reference)
|
||||
|
||||
# JOINT Number TODO this is actually joint index of the 9 axes
|
||||
# rather then linuxcnc's idea of joint number
|
||||
def setjoint_number(self, data):
|
||||
if data >10: data = 10
|
||||
if data < 0: data = 0
|
||||
self.axis_index = data
|
||||
if data < 10:
|
||||
self.set_joint_type(data)
|
||||
def getjoint_number(self):
|
||||
return self.axis_index
|
||||
def resetjoint_number(self):
|
||||
self.axis_index = 0
|
||||
Qjoint_number = QtCore.Property(int, getjoint_number, setjoint_number, resetjoint_number)
|
||||
|
||||
# user system Number
|
||||
def setreference_type(self, data):
|
||||
self.reference_type = data
|
||||
def getreference_type(self):
|
||||
return self.reference_type
|
||||
def resetreference_type(self):
|
||||
self.reference_type = 0
|
||||
Qreference_type = QtCore.Property(int, getreference_type, setreference_type, resetreference_type)
|
||||
|
||||
def setmetrictemplate(self, data):
|
||||
self.metric_text_template = data
|
||||
self.update_units()
|
||||
def getmetrictemplate(self):
|
||||
return self.metric_text_template
|
||||
def resetmetrictemplate(self):
|
||||
self.metric_text_template = '%10.3f'
|
||||
metric_template = QtCore.Property(str, getmetrictemplate, setmetrictemplate, resetmetrictemplate)
|
||||
|
||||
def setimperialtexttemplate(self, data):
|
||||
self.imperial_text_template = data
|
||||
self.update_units()
|
||||
def getimperialtexttemplate(self):
|
||||
return self.imperial_text_template
|
||||
def resetimperialtexttemplate(self):
|
||||
self.imperial_text_template = '%9.4f'
|
||||
imperial_template = QtCore.Property(str, getimperialtexttemplate, setimperialtexttemplate, resetimperialtexttemplate)
|
||||
|
||||
def setangulartexttemplate(self, data):
|
||||
self.angular_text_template = data
|
||||
self.update_units()
|
||||
def getangulartexttemplate(self):
|
||||
return self.angular_text_template
|
||||
def resetangulartexttemplate(self):
|
||||
self.angular_text_template = '%9.2f'
|
||||
angular_template = QtCore.Property(str, getangulartexttemplate, setangulartexttemplate, resetangulartexttemplate)
|
||||
|
||||
def setisHomed(self, data):
|
||||
self._isHomed = data
|
||||
def getisHomed(self):
|
||||
return self._isHomed
|
||||
|
||||
isHomed = QtCore.Property(bool, getisHomed, setisHomed)
|
||||
|
||||
####################
|
||||
## menu properties
|
||||
####################
|
||||
|
||||
def set_dialog_code(self, data):
|
||||
self.dialog_code = data
|
||||
def get_dialog_code(self):
|
||||
return self.dialog_code
|
||||
def reset_dialog_code(self):
|
||||
self.dialog_code = 'CALCULATOR'
|
||||
dialogName = QtCore.Property(str, get_dialog_code, set_dialog_code, reset_dialog_code)
|
||||
|
||||
def set_showSet(self, data):
|
||||
self._showSet = data
|
||||
def get_showSet(self):
|
||||
return self._showSet
|
||||
def reset_showSet(self):
|
||||
self._showSet = False
|
||||
showSetOrigin = QtCore.Property(bool, get_showSet, set_showSet, reset_showSet)
|
||||
|
||||
def set_showZero(self, data):
|
||||
self._showZero = data
|
||||
def get_showZero(self):
|
||||
return self._showZero
|
||||
def reset_showZero(self):
|
||||
self._showZero = False
|
||||
showZeroOrigin = QtCore.Property(bool, get_showZero, set_showZero, reset_showZero)
|
||||
|
||||
def set_showGoto(self, data):
|
||||
self._showGoto = data
|
||||
def get_showGoto(self):
|
||||
return self._showGoto
|
||||
def reset_showGoto(self):
|
||||
self._showGoto = False
|
||||
showGotoOrigin = QtCore.Property(bool, get_showGoto, set_showGoto, reset_showGoto)
|
||||
|
||||
def set_showLast(self, data):
|
||||
self._showLast = data
|
||||
def get_showLast(self):
|
||||
return self._showLast
|
||||
def reset_showLast(self):
|
||||
self._showLast = False
|
||||
showLast = QtCore.Property(bool, get_showLast, set_showLast, reset_showLast)
|
||||
|
||||
def set_showDivide(self, data):
|
||||
self._showDivide = data
|
||||
def get_showDivide(self):
|
||||
return self._showDivide
|
||||
def reset_showDivide(self):
|
||||
self._showDivide = False
|
||||
showDivide = QtCore.Property(bool, get_showDivide, set_showDivide, reset_showDivide)
|
||||
|
||||
##############################
|
||||
# required class boiler code #
|
||||
##############################
|
||||
|
||||
def __getitem__(self, item):
|
||||
return getattr(self, item)
|
||||
def __setitem__(self, item, value):
|
||||
return setattr(self, item, value)
|
||||
|
||||
# for testing without editor:
|
||||
def main():
|
||||
import sys
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
widget = DROLabel()
|
||||
widget.show()
|
||||
sys.exit(app.exec())
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user