297 lines
10 KiB
Python
297 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
# qtvcp
|
|
#
|
|
# Copyright (c) 2017 Chris Morley <chrisinnanaimo@hotmail.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.
|
|
#
|
|
#################################################################################
|
|
|
|
import hal
|
|
|
|
from qtpy import QtWidgets
|
|
from qtpy.QtCore import Property, Signal
|
|
from qtvcp.widgets.widget_baseclass import _HalWidgetBase
|
|
from qtvcp.core import Status, Action, Info
|
|
from qtvcp import logger
|
|
|
|
# Instantiate the libraries with global reference
|
|
# STATUS gives us status messages from linuxcnc
|
|
# ACTION gives commands to linuxcnc
|
|
# INFO is INI file details
|
|
# LOG is for running code logging
|
|
STATUS = Status()
|
|
ACTION = Action()
|
|
INFO = Info()
|
|
LOG = logger.getLogger(__name__)
|
|
|
|
# Force the log level for this module
|
|
# LOG.setLevel(logger.INFO) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL
|
|
|
|
|
|
# Based on https://stackoverflow.com/questions/42820380/use-float-for-qslider
|
|
class DoubleSlider(QtWidgets.QSlider):
|
|
|
|
# create our our signal that we can connect to if necessary
|
|
doubleValueChanged = Signal(float)
|
|
|
|
def __init__(self, *args, **kargs):
|
|
super(DoubleSlider, self).__init__( *args, **kargs)
|
|
self._multi = 1 ** 2 # arbitrarily set
|
|
|
|
# not needed at this time
|
|
self.valueChanged.connect(self.emitDoubleValueChanged)
|
|
|
|
def emitDoubleValueChanged(self):
|
|
value = float(super(DoubleSlider, self).value())/self._multi
|
|
self.doubleValueChanged.emit(value)
|
|
|
|
def value(self):
|
|
return float(super(DoubleSlider, self).value()) / self._multi
|
|
|
|
def setMinimum(self, value):
|
|
return super(DoubleSlider, self).setMinimum(int(value * self._multi))
|
|
|
|
def setMaximum(self, value):
|
|
return super(DoubleSlider, self).setMaximum(int(value * self._multi))
|
|
|
|
def setSingleStep(self, value):
|
|
return super(DoubleSlider, self).setSingleStep(value * self._multi)
|
|
|
|
def singleStep(self):
|
|
return float(super(DoubleSlider, self).singleStep()) / self._multi
|
|
|
|
def setValue(self, value):
|
|
super(DoubleSlider, self).setValue(int(value * self._multi))
|
|
|
|
class StatusSlider(DoubleSlider, _HalWidgetBase):
|
|
def __init__(self, parent=None):
|
|
super(StatusSlider, self).__init__(parent)
|
|
self._block_signal = False
|
|
self._halpin_option = True
|
|
self.rapid = True
|
|
self.feed = False
|
|
self.spindle = False
|
|
self.jograte = False
|
|
self.jograte_angular = False
|
|
self.max_velocity = False
|
|
|
|
# for syslesheet dynamic property
|
|
self._alertState = ''
|
|
self._alertOver = 100.0
|
|
self._alertUnder = 50.0
|
|
|
|
def _hal_init(self):
|
|
STATUS.connect('state-estop', lambda w: self.setEnabled(False))
|
|
STATUS.connect('state-estop-reset', lambda w: self.setEnabled(True))
|
|
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.setMinimum(int(INFO.MIN_LINEAR_JOG_VEL))
|
|
self.setMaximum(int(INFO.MAX_LINEAR_JOG_VEL))
|
|
elif self.jograte_angular:
|
|
STATUS.connect('jograte-angular-changed', lambda w, data: self.setValue(data))
|
|
self.setMinimum(int(INFO.MIN_ANGULAR_JOG_VEL))
|
|
self.setMaximum(int(INFO.MAX_ANGULAR_JOG_VEL))
|
|
elif self.max_velocity:
|
|
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 self._halpin_option:
|
|
if self._pin_name_ == '':
|
|
pname = self.HAL_NAME_
|
|
else:
|
|
pname = self._pin_name_
|
|
self.hal_pin = self.HAL_GCOMP_.newpin(str(pname), hal.HAL_FLOAT, hal.HAL_OUT)
|
|
|
|
# connect a signal and callback function to the button
|
|
self.doubleValueChanged.connect(self._action)
|
|
# If the widget uses dynamic properties in stylesheet...
|
|
self._style_polish(state= self.get_alert_cmd(self.value()))
|
|
|
|
# catch any programmed settings and update HAL pin
|
|
def setValue(self, v):
|
|
super(StatusSlider, self).setValue(v)
|
|
if self._halpin_option:
|
|
self.hal_pin.set(v)
|
|
|
|
def _action(self, value):
|
|
self._style_polish(state= self.get_alert_cmd(value))
|
|
if self._halpin_option:
|
|
self.hal_pin.set(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.max_velocity:
|
|
ACTION.SET_MAX_VELOCITY_RATE(value)
|
|
else:
|
|
LOG.error('{} : no action recognised'.format(self.HAL_NAME_))
|
|
|
|
# convert numeric value to string command
|
|
def get_alert_cmd(self, value):
|
|
if value > self._alertOver:
|
|
return 'over'
|
|
elif value < self._alertUnder:
|
|
return 'under'
|
|
else:
|
|
return'normal'
|
|
|
|
# polish widget so stylesheet sees the property change
|
|
# some stylesheets color the widget based on the arbitrary hi/lo range
|
|
def _style_polish(self, prop = 'alertState',state = 'normal'):
|
|
if self._alertState != state:
|
|
self.setProperty(prop, state)
|
|
self.style().unpolish(self)
|
|
self.style().polish(self)
|
|
|
|
#########################################################################
|
|
# 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',
|
|
'max-velocity')
|
|
|
|
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 setjograte_angular(self, data):
|
|
self.jograte_angular = data
|
|
if data:
|
|
self._toggle_properties('jograte_angular')
|
|
def getjograte_angular(self):
|
|
return self.jograte_angular
|
|
def resetjograte_angular(self):
|
|
self.jograte_angular = False
|
|
|
|
def setmax_velocity(self, data):
|
|
self.max_velocity = data
|
|
if data:
|
|
self._toggle_properties('max_velocity')
|
|
def getmax_velocity(self):
|
|
return self.max_velocity
|
|
def resetmax_velocity(self):
|
|
self.max_velocity = False
|
|
|
|
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
|
|
|
|
def set_pin_name(self, value):
|
|
self._pin_name_ = value
|
|
def get_pin_name(self):
|
|
return self._pin_name_
|
|
def reset_pin_name(self):
|
|
self._pin_name_ = ''
|
|
|
|
pin_name = Property(str, get_pin_name, set_pin_name, reset_pin_name)
|
|
halpin_option = Property(bool, get_halpin_option, set_halpin_option, reset_halpin_option)
|
|
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, getjograte_angular, setjograte_angular, resetjograte_angular)
|
|
max_velocity_rate = Property(bool, getmax_velocity, setmax_velocity, resetmax_velocity)
|
|
|
|
def setAlertState(self, data):
|
|
self._alertState = data
|
|
def getAlertState(self):
|
|
return self._alertState
|
|
|
|
def setAlertUnder(self, data):
|
|
self._alertUnder = data
|
|
def getAlertUnder(self):
|
|
return self._alertUnder
|
|
def resetAlertUnder(self):
|
|
self._alertUnder = 50.0
|
|
|
|
def setAlertOver(self, data):
|
|
self._alertOver = data
|
|
def getAlertOver(self):
|
|
return self._alertOver
|
|
def resetAlertOver(self):
|
|
self._alertOver = 100.0
|
|
|
|
alertState = Property(str, getAlertState, setAlertState)
|
|
alertUnder = Property(float, getAlertUnder, setAlertUnder, resetAlertUnder)
|
|
alertOver = Property(float, getAlertOver, setAlertOver, resetAlertOver)
|
|
|
|
##############################
|
|
# required class boiler code #
|
|
##############################
|
|
|
|
def __getitem__(self, item):
|
|
return getattr(self, item)
|
|
def __setitem__(self, item, value):
|
|
return setattr(self, item, value)
|