453 lines
16 KiB
Python
453 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
# qtvcp
|
|
#
|
|
# Copyright (c) 2018 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 os
|
|
|
|
from qtpy.QtCore import Signal, Property
|
|
from qtpy.QtWidgets import QLabel
|
|
from qtpy.QtGui import QPixmap
|
|
|
|
from qtvcp.widgets.widget_baseclass import _HalWidgetBase
|
|
from qtvcp.core import Status, Info, Tool, Path
|
|
from qtvcp import logger
|
|
|
|
# Instantiate the libraries with global reference
|
|
# STATUS gives us status messages from linuxcnc
|
|
# LOG is for running code logging
|
|
STATUS = Status()
|
|
INFO = Info()
|
|
TOOL = Tool()
|
|
PATH = Path()
|
|
LOG = logger.getLogger(__name__)
|
|
|
|
# Force the log level for this module
|
|
# LOG.setLevel(logger.INFO) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL
|
|
|
|
if PATH.IMAGEDIR is not None:
|
|
DEFAULTIMAGE = (os.path.join(PATH.IMAGEDIR,'applet-critical.png')) or ''
|
|
else:
|
|
DEFAULTIMAGE = ''
|
|
|
|
class ImageSwitcher(QLabel, _HalWidgetBase):
|
|
widgetChanged = Signal(int)
|
|
|
|
def __init__(self, parent=None):
|
|
super(ImageSwitcher, self).__init__(parent)
|
|
self._defaultImage = DEFAULTIMAGE
|
|
self._imagePath = [DEFAULTIMAGE]
|
|
self._current_number = 0
|
|
#self.setScaledContents(True)
|
|
self.defaultPixmap = QPixmap()
|
|
|
|
def _hal_init(self):
|
|
self.show_image_by_number(self._current_number)
|
|
|
|
def _designerInit(self):
|
|
self._hal_init()
|
|
|
|
# Show the widgets based on a reference number
|
|
def show_image_by_number(self, number):
|
|
#print (self.objectName(),len(self._imagePath),number)
|
|
try:
|
|
# outside limits or list
|
|
if number <0 or number > len(self._imagePath)-1:
|
|
LOG.debug('Path reference number out of range: {}'.format(number))
|
|
self.setPixmap(self.defaultPixmap)
|
|
return
|
|
# specifically ask for None
|
|
if self._imagePath[number].upper() == 'NONE':
|
|
self.setPixmap(QPixmap())
|
|
return
|
|
# specifically ask for Default image
|
|
if self._imagePath[number].upper() == 'DEFAULT':
|
|
self.setPixmap(self.defaultPixmap)
|
|
return
|
|
# resources file images.
|
|
if ':/' in self._imagePath[number]:
|
|
path = self._imagePath[number]
|
|
pixmap = QPixmap(path)
|
|
# no valid image
|
|
if pixmap.isNull():
|
|
self.setPixmap(self.defaultPixmap)
|
|
return
|
|
self.setPixmap(pixmap)
|
|
return
|
|
else:
|
|
path = os.path.expanduser(self._imagePath[number])
|
|
except Exception as e:
|
|
LOG.error('Path reference number: {}'.format(e))
|
|
self.setPixmap(self.defaultPixmap)
|
|
return
|
|
path = os.path.expanduser(self.defaultPixmap)
|
|
#print 'requested:',number,self._imagePath[number]
|
|
# if path doesn't exist try referencing
|
|
# from the built in image folder
|
|
if not os.path.exists(path):
|
|
path = os.path.join(PATH.IMAGEDIR, path)
|
|
if not os.path.exists(path):
|
|
LOG.debug('No Path: {}'.format(path))
|
|
pixmap = QPixmap(path)
|
|
self.setPixmap(pixmap)
|
|
|
|
def set_default_image(self, path):
|
|
self._defaultImage = path
|
|
|
|
def set_image_number(self, data):
|
|
self._current_number = data
|
|
self.show_image_by_number(data)
|
|
def get_image_number(self):
|
|
return self._current_number
|
|
def reset_image_number(self):
|
|
self._current_number = 0
|
|
image_number = Property(int, get_image_number, set_image_number, reset_image_number)
|
|
|
|
def set_image_l(self, data):
|
|
self._imagePath = data
|
|
def get_image_l(self):
|
|
return self._imagePath
|
|
def reset_image_l(self):
|
|
self._imagePath = [self._defaultImage]
|
|
image_list = Property('QStringList', get_image_l, set_image_l, reset_image_l)
|
|
|
|
def setImagePath(self, data):
|
|
self.defaultPixmap = data
|
|
def getImagePath(self):
|
|
return self.defaultPixmap
|
|
def resetImagePath(self):
|
|
self.defaultPixmap
|
|
|
|
image_default = Property(QPixmap, getImagePath, setImagePath, resetImagePath)
|
|
|
|
|
|
##############################
|
|
# required class boiler code #
|
|
##############################
|
|
|
|
def __getitem__(self, item):
|
|
return getattr(self, item)
|
|
def __setitem__(self, item, value):
|
|
return setattr(self, item, value)
|
|
|
|
|
|
|
|
class StatusImageSwitcher(ImageSwitcher):
|
|
|
|
def __init__(self, parent=None):
|
|
super(StatusImageSwitcher, self).__init__(parent)
|
|
self._imagePath = [os.path.join(PATH.IMAGEDIR,'applet-critical.png'),
|
|
os.path.join(PATH.IMAGEDIR,'spindle_ccw.gif'),
|
|
os.path.join(PATH.IMAGEDIR,'spindle_cw.gif')]
|
|
self.spindle = True
|
|
self.all_homed = False
|
|
self.axis_homed = False
|
|
self.hard_limits = False
|
|
self.machine_state = False
|
|
self.command_state = False
|
|
self.feedmode_state = False
|
|
self.spindlemode_state = False
|
|
self.toolnumber_state = False
|
|
self.tool_orientation_state = False
|
|
|
|
self._last_limit = []
|
|
self.axis = 'X'
|
|
for i in range(0,len(INFO.AVAILABLE_JOINTS)):
|
|
self._last_limit.append([0,0])
|
|
|
|
def _hal_init(self):
|
|
if self.spindle:
|
|
STATUS.connect('spindle-control-changed', lambda w, a, b, c, d: self.switch_on_spindle(b,c,d))
|
|
elif self.all_homed:
|
|
STATUS.connect('not-all-homed', lambda w, data: self.switch_on_homed(0))
|
|
STATUS.connect('all-homed', lambda w: self.switch_on_homed(1))
|
|
elif self.axis_homed:
|
|
STATUS.connect('unhomed', lambda w, data: self.switch_on_axis_unhomed(data))
|
|
STATUS.connect('homed', lambda w, data: self.switch_on_axis_homed(data))
|
|
elif self.hard_limits:
|
|
STATUS.connect('hard-limits-tripped', lambda w, data, group: self.switch_on_hard_limits(data, group))
|
|
elif self.machine_state:
|
|
STATUS.connect('state-estop', lambda w: self.switch_on_machine_state(0))
|
|
STATUS.connect('interp-run', lambda w: self.switch_on_machine_state(1))
|
|
STATUS.connect('interp-idle', lambda w: self.switch_on_machine_state(2))
|
|
STATUS.connect('interp-paused', lambda w: self.switch_on_machine_state(3))
|
|
STATUS.connect('interp-waiting', lambda w: self.switch_on_machine_state(4))
|
|
STATUS.connect('interp-reading', lambda w: self.switch_on_machine_state(5))
|
|
elif self.command_state:
|
|
STATUS.connect('command-running', lambda w: self.switch_on_command_state(0))
|
|
STATUS.connect('command-stopped', lambda w: self.switch_on_command_state(1))
|
|
STATUS.connect('command-error', lambda w: self.switch_on_command_state(2))
|
|
elif self.feedmode_state:
|
|
STATUS.connect('fpm-mode', lambda w, d: self.switch_on_feedmode_state(0, d))
|
|
STATUS.connect('fpr-mode', lambda w, d: self.switch_on_feedmode_state(1,d))
|
|
STATUS.connect('itime-mode', lambda w, d: self.switch_on_feedmode_state(2,d))
|
|
elif self.spindlemode_state:
|
|
STATUS.connect('rpm-mode', lambda w, d: self.switch_on_spindlemode_state(0, d))
|
|
STATUS.connect('css-mode', lambda w, d: self.switch_on_spindlemode_state(1, d))
|
|
elif self.toolnumber_state:
|
|
STATUS.connect('tool-in-spindle-changed', lambda w, d:self.switch_on_toolnumber_state(d))
|
|
elif self.tool_orientation_state:
|
|
STATUS.connect('tool-info-changed', lambda w, d:self.switch_on_toolinfo_state(d, TOOL.ORIENTATION))
|
|
|
|
|
|
def _designerInit(self):
|
|
self.show_image_by_number(0)
|
|
|
|
# M5 disabled = image 0
|
|
# M3 at speed = image 1
|
|
# M4 at speed = image 2
|
|
# M3 accelerating = image 3
|
|
# M4 accelerating = image 4
|
|
def switch_on_spindle(self, on, speed, up):
|
|
if not on:
|
|
num = 0
|
|
elif not up:
|
|
if speed<0: num = 4
|
|
else: num = 3
|
|
elif speed <0: num = 2
|
|
else: num = 1
|
|
self.set_image_number(num)
|
|
|
|
def switch_on_homed(self, data):
|
|
if not data <0:
|
|
self.set_image_number(data)
|
|
|
|
def switch_on_axis_homed(self, num):
|
|
data = INFO.GET_NAME_FROM_JOINT[int(num)]
|
|
if data == self.axis:
|
|
self.set_image_number(1)
|
|
|
|
def switch_on_axis_unhomed(self, num):
|
|
data = INFO.GET_NAME_FROM_JOINT[int(num)]
|
|
if data == self.axis:
|
|
self.set_image_number(0)
|
|
|
|
def switch_on_hard_limits(self, data, group):
|
|
if not data:
|
|
# tripped
|
|
self.set_image_number(0)
|
|
elif (len(self._imagePath)) == 2:
|
|
#print 'bool images'
|
|
self.set_image_number(1)
|
|
elif (len(self._imagePath)-1) == (len(INFO.AVAILABLE_JOINTS)):
|
|
#print 'per joint limits images', self._last_limit, group
|
|
for i in range(0,len(INFO.AVAILABLE_JOINTS)):
|
|
if group[i] == self._last_limit[i]:
|
|
pass
|
|
elif group[i] == [0,0]:
|
|
pass
|
|
else:
|
|
self.set_image_number(i+1)
|
|
break
|
|
elif (len(self._imagePath)-1) == (len(INFO.AVAILABLE_JOINTS) * 2):
|
|
pass
|
|
#print 'per joint and per end limits images'
|
|
self._last_limit = group
|
|
|
|
def switch_on_machine_state(self, state):
|
|
self.set_image_number(state)
|
|
|
|
def switch_on_command_state(self, state):
|
|
self.set_image_number(state)
|
|
|
|
def switch_on_feedmode_state(self, mode, state):
|
|
if state:
|
|
self.set_image_number(mode)
|
|
|
|
def switch_on_spindlemode_state(self, mode, state):
|
|
if state:
|
|
self.set_image_number(mode)
|
|
|
|
def switch_on_toolnumber_state(self, number):
|
|
self.set_image_number(number)
|
|
|
|
def switch_on_toolinfo_state(self, tool_entry, index):
|
|
toolnum = tool_entry[0]
|
|
tool_table_line = TOOL.GET_TOOL_INFO(toolnum)
|
|
try:
|
|
self.set_image_number(tool_table_line[index])
|
|
except Exception as e:
|
|
LOG.warning('Problem with tool file info')
|
|
|
|
#########################################################################
|
|
# 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 = ('spindle','all_homed', 'axis_homed','hard_limits',
|
|
'machine_state', 'command_state', 'feedmode_state',
|
|
'spindlemode_state', 'tool_orientation_state',
|
|
'toolnumber_state' )
|
|
|
|
for i in data:
|
|
if not i == picked:
|
|
self['watch_'+i] = False
|
|
|
|
# property getter/setters
|
|
|
|
# machine_spindle status
|
|
def set_spindle(self, data):
|
|
self.spindle = data
|
|
if data:
|
|
self._toggle_properties('spindle')
|
|
def get_spindle(self):
|
|
return self.spindle
|
|
def reset_spindle(self):
|
|
self.spindle = False
|
|
watch_spindle = Property(bool, get_spindle, set_spindle, reset_spindle)
|
|
|
|
# machine_homed status
|
|
def set_homed(self, data):
|
|
self.all_homed = data
|
|
if data:
|
|
self._toggle_properties('all_homed')
|
|
def get_homed(self):
|
|
return self.all_homed
|
|
def reset_homed(self):
|
|
self.all_homed = False
|
|
watch_all_homed = Property(bool, get_homed, set_homed, reset_homed)
|
|
|
|
# machine_axis_homed status
|
|
def set_axis_homed(self, data):
|
|
self.axis_homed = data
|
|
if data:
|
|
self._toggle_properties('axis_homed')
|
|
def get_axis_homed(self):
|
|
return self.axis_homed
|
|
def reset_axis_homed(self):
|
|
self.axis_homed = False
|
|
watch_axis_homed = Property(bool, get_axis_homed, set_axis_homed, reset_axis_homed)
|
|
|
|
|
|
# machine_limits status
|
|
def set_limits(self, data):
|
|
self.hard_limits = data
|
|
if data:
|
|
self._toggle_properties('hard_limits')
|
|
def get_limits(self):
|
|
return self.hard_limits
|
|
def reset_limits(self):
|
|
self.hard_limits = False
|
|
watch_hard_limits = Property(bool, get_limits, set_limits, reset_limits)
|
|
|
|
# machine_state status
|
|
def set_machine_state(self, data):
|
|
self.machine_state = data
|
|
if data:
|
|
self._toggle_properties('machine_state')
|
|
def get_machine_state(self):
|
|
return self.machine_state
|
|
def reset_machine_state(self):
|
|
self.machine_state = False
|
|
watch_machine_state = Property(bool, get_machine_state, set_machine_state,
|
|
reset_machine_state)
|
|
|
|
# command_state status
|
|
def set_command_state(self, data):
|
|
self.command_state = data
|
|
if data:
|
|
self._toggle_properties('command_state')
|
|
def get_command_state(self):
|
|
return self.command_state
|
|
def reset_command_state(self):
|
|
self.command_state = False
|
|
watch_command_state = Property(bool, get_command_state, set_command_state,
|
|
reset_command_state)
|
|
|
|
# feedmode_state status
|
|
def set_feedmode_state(self, data):
|
|
self.feedmode_state = data
|
|
if data:
|
|
self._toggle_properties('feedmode_state')
|
|
def get_feedmode_state(self):
|
|
return self.feedmode_state
|
|
def reset_feedmode_state(self):
|
|
self.feedmode_state = False
|
|
watch_feedmode_state = Property(bool, get_feedmode_state, set_feedmode_state,
|
|
reset_feedmode_state)
|
|
# spindlemode_state status
|
|
def set_spindlemode_state(self, data):
|
|
self.spindlemode_state = data
|
|
if data:
|
|
self._toggle_properties('spindlemode_state')
|
|
def get_spindlemode_state(self):
|
|
return self.spindlemode_state
|
|
def reset_spindlemode_state(self):
|
|
self.spindlemode_state = False
|
|
watch_spindlemode_state = Property(bool, get_spindlemode_state, set_spindlemode_state,
|
|
reset_spindlemode_state)
|
|
|
|
# toolnumber_state status
|
|
def set_toolnumber_state(self, data):
|
|
self.toolnumber_state = data
|
|
if data:
|
|
self._toggle_properties('toolnumber_state')
|
|
def get_toolnumber_state(self):
|
|
return self.toolnumber_state
|
|
def reset_toolnumber_state(self):
|
|
self.toolnumber_state = False
|
|
watch_toolnumber_state = Property(bool, get_toolnumber_state, set_toolnumber_state,
|
|
reset_toolnumber_state)
|
|
|
|
# tool_orientation_state status
|
|
def set_tool_orientation_state(self, data):
|
|
self.tool_orientation_state = data
|
|
if data:
|
|
self._toggle_properties('tool_orientation_state')
|
|
def get_tool_orientation_state(self):
|
|
return self.tool_orientation_state
|
|
def reset_tool_orientation_state(self):
|
|
self.tool_orientation_state = False
|
|
watch_tool_orientation_state = Property(bool, get_tool_orientation_state, set_tool_orientation_state,
|
|
reset_tool_orientation_state)
|
|
|
|
def set_axis(self, data):
|
|
if data.upper() in('X','Y','Z','A','B','C','U','V','W'):
|
|
self.axis = data.upper()
|
|
def get_axis(self):
|
|
return self.axis
|
|
def reset_axis(self):
|
|
self.axis = 'X'
|
|
axis_letter = Property(str, get_axis, set_axis, reset_axis)
|
|
|
|
##############################
|
|
# 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 = ImageSwitcher()
|
|
widget._hal_init()
|
|
|
|
widget.show()
|
|
sys.exit(app.exec())
|
|
if __name__ == "__main__":
|
|
main()
|
|
|