#!/usr/bin/env python3 # QtVcp Widget - user temporary DRO label widget # This widgets displays linuxcnc axis position information. # # Copyright (c) 2025 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 math import isclose from qtpy import QtCore from qtpy.QtWidgets import QMenu, QAction from qtpy.QtGui import QIcon, QColor 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 UserDROLabel(ScaledLabel, _HalWidgetBase): def __init__(self, parent=None): super(UserDROLabel, self).__init__(parent) # axis index of 9 axis self.axis_index = 0 # linuxcnc joint number self._joint_type = 1 self.display_units_mm = 0 self.metric_text_template = '%10.3f' self.imperial_text_template = '%9.4f' self.angular_text_template = '%9.2f' self._text =' -000.0000' self._offset = [0.0] * 9 self._lastOffset = [0.0] * 9 self._negativeColor = QColor(0, 0, 100, 150) self._positiveColor = QColor(100, 0, 0, 150) self._zeroColor = QColor(0, 100, 0, 150) self._imperialTolerence = .00005 self._metricTolerence = .005 self._currentTolerence = .0005 self._lastNum = 0.0 # menu stuff self.dialog_code = 'CALCULATOR' def mousePressEvent(self, event): 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.lastButton = QAction(QIcon('exit24.png'), 'Set To Last', self) self.lastButton.triggered.connect(self.Last) SettingMenu.addAction(self.lastButton) SettingMenu.popup(event.globalPos()) def _hal_init(self): super(UserDROLabel, self)._hal_init() STATUS.connect('current-position', self.update) STATUS.connect('metric-mode-changed', self._switch_units) STATUS.connect('general',self.return_value) 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 if INFO.MACHINE_IS_METRIC: self._currentTolerence = self._metricTolerence else: self._currentTolerence = self._imperialTolerence def update(self, widget, absolute, relative, dtg, joint): if self.display_units_mm != INFO.MACHINE_IS_METRIC: absolute = INFO.convert_units_9(absolute) tmpl = lambda s: self._current_text_template % s try: num = absolute[self.axis_index] + self._offset[self.axis_index] self.setColor(num) self.setText(tmpl(num)) except Exception as e: print(e) pass def setColor(self, data): if not data == self._lastNum: if isclose(data, 0,abs_tol = self._currentTolerence) : self.setStyleSheet("QLabel { background-color :"+self._zeroColor.name()+" ;}"); elif data <0: self.setStyleSheet("QLabel { background-color :"+self._negativeColor.name()+" ;}"); else: self.setStyleSheet("QLabel { background-color :"+self._positiveColor.name()+" ;}"); self._lastNum = data 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 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 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: p = INFO.convert_units_9(p) return p[self.axis_index] @QtCore.Slot(bool) def zero (self, data=True): p = self.get_current_position() self._lastOffset[self.axis_index] = self._offset[self.axis_index] self._offset[self.axis_index] = - p @QtCore.Slot(int) def setAxisIndex(self, num): self.axis_index = num @QtCore.Slot(str) def setAxisLetter(self, let): self.axis_index = self.axisToIndex(let) print(let,self.axis_index) def axisToIndex(self, let): conversion = {"X":0, "Y":1, "Z":2, "A":3, "B":4, "C":5, "U":6, "V":7, "W":8} return conversion[let] def indexToAxis(self, index): conversion = {0:"X", 1:"Y", 2:"Z", 3:"A", 4:"B", 5:"C", 6:"U", 7:"V", 8:"W"} return conversion[index] ##################### # Menu callback code ##################### def Zero(self): self.zero() def SetOrigin(self): now = self.get_current_position() axis = self.indexToAxis(self.axis_index) if axis: mess = {'NAME':self.dialog_code,'ID':'%s__' % self.objectName(), 'AXIS':axis,'CURRENT':now, 'TITLE':'Set %s Reference'% axis, 'GEONAME':'UserDROLabelDialog_{}'.format(self.dialog_code), 'AXIS':axis} STATUS.emit('dialog-request', mess) LOG.debug('message sent:{}'.format (mess)) def Last(self): axis = self.indexToAxis(self.axis_index) now = self.get_current_position() + self._offset[self.axis_index] if axis: temp = self._offset[self.axis_index] self._offset[self.axis_index] = self._lastOffset[self.axis_index] self._lastOffset[self.axis_index] = temp text = 'Reset Axis %s from %f to Last Value: %f' %(axis, now, self._lastOffset[self.axis_index] ) # 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') # do something p = self.get_current_position() self._lastOffset[self.axis_index] = self._offset[self.axis_index] self._offset[self.axis_index] = - (p-num) ######################################################################### # 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 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' angularTemplate = QtCore.Property(str, getangulartexttemplate, setangulartexttemplate, resetangulartexttemplate) def getNegativeColor(self): return self._negativeColor def setNegativeColor(self, value): self._negativeColor = value negativeColor = QtCore.Property(QColor, getNegativeColor, setNegativeColor) def getPositiveColor(self): return self._positiveColor def setPositiveColor(self, value): self._positiveColor = value positiveColor = QtCore.Property(QColor, getPositiveColor, setPositiveColor) #################### ## 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) ############################## # 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 = UserDROLabel() widget.show() sys.exit(app.exec()) if __name__ == "__main__": main()