405 lines
16 KiB
Python
405 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# QTVcp Widget
|
|
# Copyright (c) 2018 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 hal
|
|
|
|
from qtpy.QtWidgets import QToolButton, QMenu, QAction
|
|
from qtpy.QtCore import Property
|
|
from qtpy.QtGui import QIcon
|
|
|
|
from qtvcp.widgets.widget_baseclass import _HalWidgetBase
|
|
from qtvcp.widgets.indicatorMixIn import IndicatedMixIn
|
|
from qtvcp.core import Status, Action, Info
|
|
from qtvcp import logger
|
|
|
|
# Instiniate the libraries with global reference
|
|
# STATUS gives us status messages from linuxcnc
|
|
# AUX_PRGM holds helper program loader
|
|
# INI holds ini details
|
|
# ACTION gives commands to 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.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL
|
|
|
|
class AxisToolButton(QToolButton, IndicatedMixIn):
|
|
def __init__(self, parent=None):
|
|
super(AxisToolButton, self).__init__(parent)
|
|
self._joint = 0
|
|
self._axis = ''
|
|
self._block_signal = False
|
|
self._halpin_option = True
|
|
self.dialog_code = 'ENTRY'
|
|
self.display_units_mm = 0
|
|
homeOption = False
|
|
|
|
self._showGoto = False
|
|
self._showZero = True
|
|
self._showSet = True
|
|
self._showLast = True
|
|
self._showDivide = True
|
|
|
|
SettingMenu = QMenu(self)
|
|
self.settingMenu = SettingMenu
|
|
self.zeroButton = QAction(QIcon('exit24.png'), 'Zero', self)
|
|
self.zeroButton.triggered.connect(self.Zero)
|
|
SettingMenu.addAction(self.zeroButton)
|
|
self.setButton = QAction(QIcon('exit24.png'), 'Set', self)
|
|
self.setButton.triggered.connect(self.SetOrigin)
|
|
SettingMenu.addAction(self.setButton)
|
|
self.divideButton = QAction(QIcon('exit24.png'), 'Divide By 2', self)
|
|
self.divideButton.triggered.connect(self.Divide)
|
|
SettingMenu.addAction(self.divideButton)
|
|
self.lastButton = QAction(QIcon('exit24.png'), 'Set To Last', self)
|
|
self.lastButton.triggered.connect(self.Last)
|
|
SettingMenu.addAction(self.lastButton)
|
|
self.goToG53Button = QAction(self)
|
|
self.goToG53Button.triggered.connect(self.goToG53)
|
|
SettingMenu.addAction(self.goToG53Button)
|
|
self.goToG5xButton = QAction(self)
|
|
self.goToG5xButton.triggered.connect(self.goToG5x)
|
|
SettingMenu.addAction(self.goToG5xButton)
|
|
|
|
# option for later - should put a sub menu in for joints homing though
|
|
if homeOption:
|
|
SettingMenu.addSeparator()
|
|
self.homeButton = QAction(QIcon('exit24.png'), 'Home', self)
|
|
self.homeButton.triggered.connect(self.Home)
|
|
SettingMenu.addAction(self.homeButton)
|
|
#self.homeButton.setEnabled(False)
|
|
|
|
self.UnHomeButton = QAction(QIcon('exit24.png'), 'Unhome', self)
|
|
self.UnHomeButton.triggered.connect(self.Unhome)
|
|
SettingMenu.addAction(self.UnHomeButton)
|
|
#self.homeButton.setEnabled(False)
|
|
|
|
self.setMenu(SettingMenu)
|
|
self.clicked.connect(self.selectJoint)
|
|
self.toggled.connect(self.selectJoint)
|
|
|
|
# Override setText function so we can toggle displayed text
|
|
def setText(self, text):
|
|
if not self._state_text:
|
|
super(AxisToolButton,self).setText(text)
|
|
return
|
|
if self.isCheckable():
|
|
if self.isChecked():
|
|
super(AxisToolButton,self).setText(self._true_string)
|
|
else:
|
|
super(AxisToolButton,self).setText(self._false_string)
|
|
elif self._indicator_state:
|
|
super(AxisToolButton,self).setText(self._true_string)
|
|
else:
|
|
super(AxisToolButton,self).setText(self._false_string)
|
|
|
|
def _hal_init(self):
|
|
super(AxisToolButton, self)._hal_init()
|
|
def homed_on_test():
|
|
return (STATUS.machine_is_on()
|
|
and (STATUS.is_all_homed() or INFO.NO_HOME_REQUIRED))
|
|
|
|
STATUS.connect('metric-mode-changed', self._switch_units)
|
|
STATUS.connect('state-off', lambda w: self._enableGroup(True,False))
|
|
STATUS.connect('state-on', lambda w: self._enableGroup(True, True))
|
|
STATUS.connect('interp-idle', lambda w: self._enableGroup(homed_on_test(),STATUS.machine_is_on()))
|
|
STATUS.connect('interp-run', lambda w: self._enableGroup(False, False))
|
|
STATUS.connect('all-homed', lambda w: self._enableGroup(homed_on_test(), STATUS.machine_is_on()))
|
|
STATUS.connect('not-all-homed', lambda w, data: self._enableGroup(False,STATUS.machine_is_on()))
|
|
STATUS.connect('interp-paused', lambda w: self._enableGroup(False,False))
|
|
STATUS.connect('motion-mode-changed', lambda w,data: self.modeChanged(data))
|
|
STATUS.connect('joint-selection-changed', lambda w,data: self.ChangeState(joint = data, axis= STATUS.get_selected_axis()))
|
|
STATUS.connect('axis-selection-changed', lambda w,data: self.ChangeState(joint = STATUS.get_selected_joint(), axis = data))
|
|
if self._halpin_option and self._joint != -1:
|
|
if self._pin_name_ == '':
|
|
pname = self.HAL_NAME_
|
|
else:
|
|
pname = self._pin_name_
|
|
|
|
self.hal_pin_joint = self.HAL_GCOMP_.newpin(str(pname + '-joint'), hal.HAL_BIT, hal.HAL_OUT)
|
|
self.hal_pin_axis = self.HAL_GCOMP_.newpin(str(pname + '-axis'), hal.HAL_BIT, hal.HAL_OUT)
|
|
STATUS.connect('general',self.return_value)
|
|
|
|
def _enableGroup(self, state,bstate):
|
|
for i in(self.zeroButton, self.setButton,self.divideButton,
|
|
self.lastButton,self.goToG53Button, self.goToG5xButton ):
|
|
i.setEnabled(state)
|
|
|
|
self.setEnabled(bstate)
|
|
|
|
def Zero(self):
|
|
axis, now = self._a_from_j(self._axis)
|
|
if axis:
|
|
ACTION.SET_AXIS_ORIGIN(axis, 0)
|
|
STATUS.emit('update-machine-log', 'Zeroed Axis %s' % axis, 'TIME,SUCCESS')
|
|
|
|
def SetOrigin(self):
|
|
axis, now = self._a_from_j(self._axis)
|
|
if axis:
|
|
mess = {'NAME':self.dialog_code,'ID':'%s__' % self.objectName(),
|
|
'AXIS':axis,'CURRENT':now,
|
|
'TITLE':'Set %s Origin'% axis,
|
|
'GEONAME':'axisToolButtonDialog_{}'.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):
|
|
axis, now = self._a_from_j(self._axis)
|
|
if axis:
|
|
try:
|
|
x = now/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):
|
|
axis, now = self._a_from_j(self._axis)
|
|
if axis:
|
|
last = ACTION.GET_LAST_RECORDED_ORIGIN(axis)
|
|
ACTION.SET_AXIS_ORIGIN(axis, last)
|
|
text = 'Reset Axis %s from %f to Last Value: %f' %(axis, now, last)
|
|
STATUS.emit('update-machine-log', text, 'TIME,SUCCESS')
|
|
|
|
def Home(self):
|
|
#axis, now = self._a_from_j(self._axis)
|
|
#if axis:
|
|
ACTION.SET_MACHINE_HOMING(self._joint)
|
|
STATUS.emit('update-machine-log', 'Homed Axis %s' % self._joint, 'TIME,SUCCESS')
|
|
|
|
def Unhome(self):
|
|
#axis, now = self._a_from_j(self._axis)
|
|
#if axis:
|
|
ACTION.SET_MACHINE_UNHOMED(self._joint)
|
|
STATUS.emit('update-machine-log', 'Unhomed Axis %s' % self._joint, 'TIME,WARNING')
|
|
|
|
def goToG53(self):
|
|
ACTION.CALL_MDI('G53 G0 {}0'.format(self._axis))
|
|
|
|
def goToG5x(self):
|
|
ACTION.CALL_MDI('G0 {}0'.format(self._axis))
|
|
|
|
# if in joint mode -nope
|
|
# if axis is '' the find the selected axis from gstat
|
|
# find the joint number so we can get current position
|
|
def _a_from_j(self, axis):
|
|
if STATUS.is_joint_mode():
|
|
return None, None
|
|
if axis == '':
|
|
axis = STATUS.get_selected_axis()
|
|
j = "XYZABCUVW"
|
|
try:
|
|
jnum = j.find(axis)
|
|
except:
|
|
return None, None
|
|
p,r,d = STATUS.get_position()
|
|
if self.display_units_mm != INFO.MACHINE_IS_METRIC:
|
|
r = INFO.convert_units_9(r)
|
|
return axis, r[jnum]
|
|
|
|
def selectJoint(self):
|
|
if self._block_signal or self._joint == -1 or self._axis == '': return
|
|
if self.isChecked() == True:
|
|
if STATUS.is_joint_mode():
|
|
ACTION.SET_SELECTED_JOINT(self._joint)
|
|
if self._halpin_option:
|
|
self.hal_pin_joint.set(self.isChecked())
|
|
else:
|
|
ACTION.SET_SELECTED_AXIS(self._axis)
|
|
# set this without causing a STATUS message output
|
|
# in case we are selecting an axis to un/home
|
|
STATUS.selected_joint = self._joint
|
|
if self._halpin_option:
|
|
self.hal_pin_axis.set(self.isChecked())
|
|
else:
|
|
if STATUS.is_joint_mode():
|
|
ACTION.SET_SELECTED_JOINT(-1)
|
|
else:
|
|
ACTION.SET_SELECTED_AXIS('None')
|
|
# set this without causing a STATUS message output
|
|
# in case we are selecting an axis to un/home
|
|
STATUS.selected_joint = -1
|
|
if self._halpin_option:
|
|
self.hal_pin_axis.set(False)
|
|
|
|
def ChangeState(self, joint = None, axis = None):
|
|
#print(self.objectName(),'change',joint,axis,self._axis)
|
|
# joint mode
|
|
if STATUS.is_joint_mode():
|
|
if int(joint) != self._joint:
|
|
self._block_signal = True
|
|
self.setChecked(False)
|
|
self._block_signal = False
|
|
if self._halpin_option and self._joint != -1:
|
|
self.hal_pin_joint.set(False)
|
|
else:
|
|
self._block_signal = True
|
|
self.setChecked(True)
|
|
self._block_signal = False
|
|
if self._halpin_option and self._axis != '':
|
|
self.hal_pin_joint.set(True)
|
|
# axis mode
|
|
else:
|
|
if str(axis) != self._axis and self.isChecked():
|
|
self._block_signal = True
|
|
self.setChecked(False)
|
|
self._block_signal = False
|
|
if self._halpin_option and self._axis != '':
|
|
self.hal_pin_joint.set(False)
|
|
elif str(axis) == self._axis and not self.isChecked():
|
|
self._block_signal = True
|
|
self.setChecked(True)
|
|
self._block_signal = False
|
|
if self._halpin_option and self._axis != '':
|
|
self.hal_pin_joint.set(True)
|
|
|
|
def modeChanged(self, mode):
|
|
self.selectJoint()
|
|
|
|
def _switch_units(self, widget, data):
|
|
self.display_units_mm = data
|
|
|
|
#########################################################################
|
|
# 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
|
|
#
|
|
########################################################################
|
|
|
|
def set_joint(self, data):
|
|
self._joint = data
|
|
if data == -1:
|
|
self.setPopupMode(QToolButton.InstantPopup)
|
|
def get_joint(self):
|
|
return self._joint
|
|
def reset_joint(self):
|
|
self._joint = -1
|
|
joint_number = Property(int, get_joint, set_joint, reset_joint)
|
|
|
|
def set_axis(self, data):
|
|
if data.upper() in('X','Y','Z','A','B','C','U','V','W'):
|
|
self._axis = str(data.upper())
|
|
text = 'Go To G53 Origin in {}'.format(self._axis)
|
|
self.goToG53Button.setText(text)
|
|
text = 'Go To G5x Origin in {}'.format(self._axis)
|
|
self.goToG5xButton.setText(text)
|
|
else:
|
|
self._axis = str('')
|
|
|
|
def get_axis(self):
|
|
return self._axis
|
|
def reset_axis(self):
|
|
self._axis = 'X'
|
|
axis_letter = Property(str, get_axis, set_axis, reset_axis)
|
|
|
|
def set_halpin_option(self, value):
|
|
self._halpin_option = value
|
|
def get_halpin_option(self):
|
|
return self._halpin_option
|
|
def reset_halpin_option(self):
|
|
self._halpin_option = True
|
|
halpin_option = Property(bool, get_halpin_option, set_halpin_option, reset_halpin_option)
|
|
|
|
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 = 'ENTRY'
|
|
dialog_code_string = Property(str, get_dialog_code, set_dialog_code, reset_dialog_code)
|
|
|
|
####################
|
|
## menu properties
|
|
####################
|
|
|
|
def set_showSet(self, data):
|
|
self._showSet = data
|
|
self.setButton.setVisible(data)
|
|
def get_showSet(self):
|
|
return self._showSet
|
|
def reset_showSet(self):
|
|
self._showSet = True
|
|
showSetOrigin = Property(bool, get_showSet, set_showSet, reset_showSet)
|
|
|
|
def set_showZero(self, data):
|
|
self._showZero = data
|
|
self.zeroButton.setVisible(data)
|
|
def get_showZero(self):
|
|
return self._showZero
|
|
def reset_showZero(self):
|
|
self._showZero = True
|
|
showZeroOrigin = Property(bool, get_showZero, set_showZero, reset_showZero)
|
|
|
|
def set_showGoto(self, data):
|
|
self._showGoto = data
|
|
self.goToG53Button.setVisible(data)
|
|
self.goToG5xButton.setVisible(data)
|
|
def get_showGoto(self):
|
|
return self._showGoto
|
|
def reset_showGoto(self):
|
|
self._showGoto = False
|
|
showGotoOrigin = Property(bool, get_showGoto, set_showGoto, reset_showGoto)
|
|
|
|
def set_showLast(self, data):
|
|
self._showLast = data
|
|
self.lastButton.setVisible(data)
|
|
def get_showLast(self):
|
|
return self._showLast
|
|
def reset_showLast(self):
|
|
self._showLast = True
|
|
showLast = Property(bool, get_showLast, set_showLast, reset_showLast)
|
|
|
|
def set_showDivide(self, data):
|
|
self._showDivide = data
|
|
self.divideButton.setVisible(data)
|
|
def get_showDivide(self):
|
|
return self._showDivide
|
|
def reset_showDivide(self):
|
|
self._showDivide = True
|
|
showDivide = Property(bool, get_showDivide, set_showDivide, reset_showDivide)
|
|
|
|
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 = AxisToolButton()
|
|
widget.show()
|
|
|
|
sys.exit(app.exec())
|
|
if __name__ == "__main__":
|
|
main()
|