403 lines
14 KiB
Python
403 lines
14 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.
|
|
###############################################################################
|
|
|
|
from qtpy.QtWidgets import QWidget, QProgressBar, QToolButton, QHBoxLayout, QMenu, QAction
|
|
from qtpy.QtCore import Qt, Property, QBasicTimer, Signal
|
|
from qtpy.QtGui import QPainter, QFont, QIcon
|
|
|
|
from qtvcp.widgets.widget_baseclass import _HalWidgetBase
|
|
from qtvcp.core import Status, Action, Info
|
|
from qtvcp.lib.aux_program_loader import Aux_program_loader
|
|
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()
|
|
AUX_PRGM = Aux_program_loader()
|
|
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
|
|
|
|
|
|
# So we can customize the label
|
|
# meaning we can use a new line
|
|
# to have two levels of text
|
|
class LabeledBar(QProgressBar):
|
|
def __init__(self, parent=None):
|
|
super(LabeledBar, self).__init__(parent)
|
|
self.text = ''
|
|
self.setTextVisible(False)
|
|
self.font = QFont()
|
|
self.font.setPointSize(8)
|
|
self.font.setBold(True)
|
|
self.font.setWeight(75)
|
|
|
|
def paintEvent(self, event):
|
|
super(LabeledBar, self).paintEvent(event)
|
|
qp = QPainter()
|
|
qp.begin(self)
|
|
self.drawText(qp)
|
|
qp.end()
|
|
|
|
def drawText(self, qp):
|
|
qp.setOpacity(1.0)
|
|
qp.setFont(self.font)
|
|
qp.drawText(self.rect(), Qt.AlignCenter, self.text)
|
|
|
|
|
|
class HAdjustmentBar(QWidget):
|
|
valueChanged = Signal(int)
|
|
def __init__(self, parent=None):
|
|
super(HAdjustmentBar, self).__init__(parent)
|
|
self.value = 50
|
|
self.step = 1
|
|
self.hi_value = 100
|
|
self.low_value = 50
|
|
self.timer_value = 100
|
|
self.texttemplate = 'Value = %s'
|
|
self.timer = QBasicTimer()
|
|
self.showToggleButton = True
|
|
self.showSettingMenu = True
|
|
self.bar = LabeledBar()
|
|
#self.bar.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding)
|
|
# black magic class patching
|
|
# so calling these functions actually calls the self.bar's functions.
|
|
self.minimum = self.bar.minimum
|
|
self.maximum = self.bar.maximum
|
|
|
|
def buildWidget(self):
|
|
layout = QHBoxLayout(self)
|
|
layout.setContentsMargins(0,0,0,0)
|
|
SettingMenu = QMenu()
|
|
exitButton = QAction(QIcon('exit24.png'), 'Set As High', self)
|
|
exitButton.triggered.connect(self.setHigh)
|
|
SettingMenu.addAction(exitButton)
|
|
setlowButton = QAction(QIcon('exit24.png'), 'Set As Low', self)
|
|
setlowButton.triggered.connect(self.setLow)
|
|
SettingMenu.addAction(setlowButton)
|
|
|
|
self.tb_down = QToolButton()
|
|
self.tb_down.pressed.connect(self.on_click_down)
|
|
self.tb_down.released.connect(self.on_released)
|
|
self.tb_down.setArrowType(Qt.LeftArrow)
|
|
|
|
self.tb_up = QToolButton()
|
|
self.tb_up.pressed.connect(self.on_click_up)
|
|
self.tb_up.released.connect(self.on_released)
|
|
self.tb_up.setArrowType(Qt.RightArrow)
|
|
|
|
if self.showToggleButton:
|
|
tb_set = QToolButton()
|
|
tb_set.clicked.connect(self.on_click_set_value)
|
|
tb_set.setText('<>')
|
|
if self.showSettingMenu:
|
|
tb_set.setMenu(SettingMenu)
|
|
tb_set.setPopupMode(QToolButton.DelayedPopup)
|
|
|
|
layout.addWidget(self.tb_down)
|
|
layout.addWidget(self.bar)
|
|
layout.addWidget(self.tb_up)
|
|
if self.showToggleButton:
|
|
layout.addWidget(tb_set)
|
|
layout.setSpacing(0)
|
|
|
|
def on_click_up(self):
|
|
self.timer.start(self.timer_value, self)
|
|
self.value += self.step
|
|
self._setValue()
|
|
|
|
def on_click_down(self):
|
|
self.timer.start(self.timer_value, self)
|
|
self.value -= self.step
|
|
self._setValue()
|
|
def on_released(self):
|
|
self.timer.stop()
|
|
|
|
def on_click_set_value(self):
|
|
if self.value == self.hi_value:
|
|
self.value = self.low_value
|
|
else:
|
|
self.value = self.hi_value
|
|
self._setValue()
|
|
|
|
def timerEvent(self, e):
|
|
if self.value < self.maximum() and self.value > self.minimum():
|
|
if self.tb_down.isDown():
|
|
self.value -= self.step
|
|
else:
|
|
self.value += self.step
|
|
self._setValue()
|
|
else:
|
|
self.timer.stop()
|
|
|
|
def _setValue(self):
|
|
if self.value < self.minimum():self. value = self.minimum()
|
|
if self.value > self.maximum(): self.value = self.maximum()
|
|
self.valueChanged.emit(self.value)
|
|
|
|
def setValue(self, value):
|
|
if value < self.minimum(): value = self.minimum()
|
|
if value > self.maximum(): value = self.maximum()
|
|
self.value = int(value)
|
|
tmpl = lambda s: self.texttemplate % s
|
|
try:
|
|
self.bar.text = tmpl(int(value))
|
|
except:
|
|
self.bar.text = self.texttemplate
|
|
self.bar.setValue(int(value))
|
|
self.bar.update()
|
|
|
|
def setHigh(self):
|
|
self.hi_value = self.value
|
|
|
|
def setLow(self):
|
|
self.low_value = self.value
|
|
|
|
def setMaximum(self, data):
|
|
if data < self.hi_value:
|
|
self.hi_value = data
|
|
self.bar.setMaximum(data)
|
|
|
|
def setMinimum(self, data):
|
|
if data > self.low_value:
|
|
self.low_value = data
|
|
self.bar.setMinimum(data)
|
|
|
|
def setStep(self, data):
|
|
self.step = data
|
|
|
|
class StatusAdjustmentBar(HAdjustmentBar, _HalWidgetBase):
|
|
def __init__(self, parent=None):
|
|
super(StatusAdjustmentBar, self).__init__(parent)
|
|
self._block_signal = False
|
|
self.rapid = True
|
|
self.feed = False
|
|
self.spindle = False
|
|
self.jograte = False
|
|
self.jograte_angular = False
|
|
self.maxv = False
|
|
self.texttemplate = 'Value = %s'
|
|
|
|
# self.PREFS_
|
|
# self.HAL_NAME_
|
|
# comes from base class
|
|
def _hal_init(self):
|
|
|
|
# if we build the widget before now, designer options are not
|
|
# taken account
|
|
self.buildWidget()
|
|
|
|
# when estopped disable buttons
|
|
STATUS.connect('state-estop', lambda w: self.setEnabled(False))
|
|
STATUS.connect('state-estop-reset', lambda w: self.setEnabled(True))
|
|
|
|
# set options
|
|
if self.rapid:
|
|
STATUS.connect('rapid-override-changed', lambda w, data: self.setValue(data))
|
|
self.setMaximum(100)
|
|
elif self.feed:
|
|
STATUS.connect('feed-override-changed', lambda w, data: self.setValue(data))
|
|
self.setMaximum(int(INFO.MAX_FEED_OVERRIDE))
|
|
elif self.spindle:
|
|
STATUS.connect('spindle-override-changed', lambda w, data: self.setValue(data))
|
|
self.setMaximum(int(INFO.MAX_SPINDLE_OVERRIDE))
|
|
self.setMinimum(int(INFO.MIN_SPINDLE_OVERRIDE))
|
|
elif self.jograte:
|
|
STATUS.connect('jograte-changed', lambda w, data: self.setValue(data))
|
|
self.setMaximum(int(INFO.MAX_LINEAR_JOG_VEL))
|
|
elif self.jograte_angular:
|
|
STATUS.connect('jograte-angular-changed', lambda w, data: self.setValue(data))
|
|
print(int(INFO.MAX_ANGULAR_JOG_VEL))
|
|
self.setMaximum(int(INFO.MAX_ANGULAR_JOG_VEL))
|
|
elif self.maxv:
|
|
STATUS.connect('max-velocity-override-changed', lambda w, data: self.setValue(data))
|
|
self.setMaximum(int(INFO.MAX_TRAJ_VELOCITY))
|
|
else:
|
|
LOG.error('{} : no option recognised'.format(self.HAL_NAME_))
|
|
|
|
# If there is a preference file object use it to load the hi/low toggle points
|
|
if self.PREFS_:
|
|
self.hi_value = self.PREFS_.getpref(self.HAL_NAME_+'-hi-value', 75, int, 'SCREEN_CONTROL_LAST_SETTING')
|
|
self.low_value = self.PREFS_.getpref(self.HAL_NAME_+'-low-value', 25, int, 'SCREEN_CONTROL_LAST_SETTING')
|
|
|
|
# connect a signal and callback function to the button
|
|
self.valueChanged.connect(self._action)
|
|
|
|
# when qtvcp closes this gets called
|
|
def _hal_cleanup(self):
|
|
if self.PREFS_:
|
|
LOG.debug('Saving {} data to file.'.format(self.HAL_NAME_))
|
|
self.PREFS_.putpref(self.HAL_NAME_+'-hi-value', self.hi_value, int, 'SCREEN_CONTROL_LAST_SETTING')
|
|
self.PREFS_.putpref(self.HAL_NAME_+'-low-value', self.low_value, int, 'SCREEN_CONTROL_LAST_SETTING')
|
|
|
|
def _action(self, value):
|
|
if self.rapid:
|
|
ACTION.SET_RAPID_RATE(value)
|
|
elif self.feed:
|
|
ACTION.SET_FEED_RATE(value)
|
|
elif self.spindle:
|
|
ACTION.SET_SPINDLE_RATE(value)
|
|
elif self.jograte:
|
|
ACTION.SET_JOG_RATE(value)
|
|
elif self.jograte_angular:
|
|
ACTION.SET_JOG_RATE_ANGULAR(value)
|
|
elif self.maxv:
|
|
ACTION.SET_MAX_VELOCITY_RATE(value)
|
|
else:
|
|
LOG.error('{} no action recognised'.format(self.HAL_NAME_))
|
|
#########################################################################
|
|
# 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 = ('rapid', 'feed', 'spindle', 'jograte', 'jograte_angular', 'maxv')
|
|
|
|
for i in data:
|
|
if not i == picked:
|
|
self[i+'_rate'] = False
|
|
|
|
def setrapid(self, data):
|
|
self.rapid = data
|
|
if data:
|
|
self._toggle_properties('rapid')
|
|
def getrapid(self):
|
|
return self.rapid
|
|
def resetrapid(self):
|
|
self.rapid = False
|
|
|
|
def setfeed(self, data):
|
|
self.feed = data
|
|
if data:
|
|
self._toggle_properties('feed')
|
|
def getfeed(self):
|
|
return self.feed
|
|
def resetfeed(self):
|
|
self.feed = False
|
|
|
|
def setspindle(self, data):
|
|
self.spindle = data
|
|
if data:
|
|
self._toggle_properties('spindle')
|
|
def getspindle(self):
|
|
return self.spindle
|
|
def resetspindle(self):
|
|
self.spindle = False
|
|
|
|
def setjograte(self, data):
|
|
self.jograte = data
|
|
if data:
|
|
self._toggle_properties('jograte')
|
|
def getjograte(self):
|
|
return self.jograte
|
|
def resetjograte(self):
|
|
self.jograte = False
|
|
|
|
def setjograteangular(self, data):
|
|
self.jograte_angular = data
|
|
if data:
|
|
self._toggle_properties('jograte_angular')
|
|
def getjograteangular(self):
|
|
return self.jograte_angular
|
|
def resetjograteangular(self):
|
|
self.jograte_angular = False
|
|
|
|
def setmaxv(self, data):
|
|
self.maxv = data
|
|
if data:
|
|
self._toggle_properties('maxv')
|
|
def getmaxv(self):
|
|
return self.maxv
|
|
def resetmaxv(self):
|
|
self.maxv = False
|
|
|
|
def setshowtoggle(self, data):
|
|
self.showToggleButton = data
|
|
def getshowtoggle(self):
|
|
return self.showToggleButton
|
|
def resetshowtoggle(self):
|
|
self.showToggleButton = True
|
|
|
|
def setsettingmenu(self, data):
|
|
self.showSettingMenu = data
|
|
def getsettingmenu(self):
|
|
return self.showSettingMenu
|
|
def resetsettingmenu(self):
|
|
self.showSettingMenu = True
|
|
|
|
# Designer makes '\n' into '\\n' if added
|
|
# we put fix it here so we actually get the newline
|
|
def settexttemplate(self, data):
|
|
data = data.replace('\\n' , '\n')
|
|
self.texttemplate = data
|
|
def gettexttemplate(self):
|
|
return self.texttemplate
|
|
def resettexttemplate(self):
|
|
self.texttemplate = 'Value = %s'
|
|
|
|
def setsteprate(self, data):
|
|
self.step = data
|
|
def getsteprate(self):
|
|
return self.step
|
|
def resetsteprate(self):
|
|
self.steprate = 1
|
|
|
|
rapid_rate = Property(bool, getrapid, setrapid, resetrapid)
|
|
feed_rate = Property(bool, getfeed, setfeed, resetfeed)
|
|
spindle_rate = Property(bool, getspindle, setspindle, resetspindle)
|
|
jograte_rate = Property(bool, getjograte, setjograte, resetjograte)
|
|
jograte_angular_rate = Property(bool, getjograteangular, setjograteangular, resetjograteangular)
|
|
max_velocity_rate = Property(bool, getmaxv, setmaxv, resetmaxv)
|
|
show_toggle_button = Property(bool, getshowtoggle, setshowtoggle, resetshowtoggle)
|
|
show_setting_menu = Property(bool, getsettingmenu, setsettingmenu, resetsettingmenu)
|
|
|
|
text_template = Property(str, gettexttemplate, settexttemplate, resettexttemplate)
|
|
step_rate = Property(int, getsteprate, setsteprate, resetsteprate)
|
|
|
|
##############################
|
|
# 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 = StatusAdjustmentBar()
|
|
widget.PREFS_=None
|
|
widget.buildWidget()
|
|
widget.settexttemplate('Text %s')
|
|
widget.setValue(75)
|
|
widget.show()
|
|
|
|
sys.exit(app.exec())
|
|
if __name__ == "__main__":
|
|
main()
|