Import LinuxCNC source tree
This commit is contained in:
725
lib/python/qtvcp/widgets/basic_probe.py
Normal file
725
lib/python/qtvcp/widgets/basic_probe.py
Normal file
@@ -0,0 +1,725 @@
|
||||
#!/usr/bin/env python3
|
||||
# Qtvcp basic probe
|
||||
#
|
||||
# Copyright (c) 2020 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.
|
||||
#
|
||||
# a probe screen based on ProbeBasic screen
|
||||
|
||||
import sys
|
||||
import os
|
||||
import hal
|
||||
import json
|
||||
import re
|
||||
from qtpy.QtCore import QProcess, QRegularExpression, QFile, QEvent, Qt, Property
|
||||
from qtpy import QtGui, QtWidgets, uic, QtCore
|
||||
from qtpy.QtWidgets import QDialogButtonBox, QAbstractSlider, QLineEdit, QApplication
|
||||
from qtvcp.widgets.widget_baseclass import _HalWidgetBase
|
||||
from qtvcp.core import Action, Status, Info, Path
|
||||
from qtvcp.widgets.dialogMixin import GeometryMixin
|
||||
from qtvcp import logger
|
||||
|
||||
ACTION = Action()
|
||||
STATUS = Status()
|
||||
INFO = Info()
|
||||
PATH = Path()
|
||||
LOG = logger.getLogger(__name__)
|
||||
# Force the log level for this module
|
||||
#LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
|
||||
current_dir = os.path.dirname(__file__)
|
||||
SUBPROGRAM = os.path.abspath(os.path.join(current_dir, 'probe_subprog.py'))
|
||||
CONFIG_DIR = os.getcwd()
|
||||
|
||||
# can use/favours local image and help files
|
||||
HELP = PATH.find_widget_path()
|
||||
|
||||
DEFAULT = 0
|
||||
WARNING = 1
|
||||
CRITICAL = 2
|
||||
|
||||
class BasicProbeParent(QtWidgets.QWidget, _HalWidgetBase):
|
||||
def __init__(self, parent=None):
|
||||
super(BasicProbeParent, self).__init__(parent)
|
||||
self.proc = None
|
||||
self._cmd = None
|
||||
self._runImmediately = True
|
||||
self.dialog_code = 'CALCULATOR'
|
||||
self.hilightStyle = "border: 2px solid red;"
|
||||
|
||||
if INFO.MACHINE_IS_METRIC:
|
||||
self.valid = QtGui.QRegularExpressionValidator(QRegularExpression(r'^[+-]?((\d+(\.\d{,4})?)|(\.\d{,4}))$'))
|
||||
else:
|
||||
self.valid = QtGui.QRegularExpressionValidator(QRegularExpression(r'^[+-]?((\d+(\.\d{,3})?)|(\.\d{,3}))$'))
|
||||
self.setMinimumSize(600, 420)
|
||||
# load the widgets ui file
|
||||
self.filename = PATH.find_widget_path('basic_probe.ui')
|
||||
try:
|
||||
self.instance = uic.loadUi(self.filename, self)
|
||||
except AttributeError as e:
|
||||
LOG.critical(e)
|
||||
# load the probe help file
|
||||
self.filename = PATH.find_widget_path('basic_probe_help.ui')
|
||||
try:
|
||||
self.dialog = uic.loadUi(self.filename)
|
||||
except AttributeError as e:
|
||||
LOG.critical(e)
|
||||
|
||||
self.probe_list = ["OUTSIDE CORNERS", "INSIDE CORNERS", "EDGE ANGLE", "BOSS and POCKETS",
|
||||
"RIDGE and VALLEY", "CALIBRATE"]
|
||||
self.status_list = ['xm', 'xc', 'xp', 'ym', 'yc', 'yp', 'lx', 'ly', 'z', 'd', 'a', 'delta']
|
||||
|
||||
#create parameter dictionary
|
||||
self.send_dict = {}
|
||||
# these parameters are sent to the subprogram
|
||||
self.parm_list = ['probe_diam',
|
||||
'rapid_vel',
|
||||
'search_vel',
|
||||
'probe_vel',
|
||||
'extra_depth',
|
||||
'latch_return_dist',
|
||||
'max_travel',
|
||||
'max_z_travel',
|
||||
'xy_clearance',
|
||||
'z_clearance',
|
||||
'side_edge_length',
|
||||
'diameter_hint',
|
||||
'x_hint_bp',
|
||||
'y_hint_bp',
|
||||
'x_hint_rv',
|
||||
'y_hint_rv',
|
||||
'calibration_offset',
|
||||
'cal_diameter',
|
||||
'cal_x_width',
|
||||
'cal_y_width',
|
||||
]
|
||||
self.nextlist = ['probe_tool']
|
||||
self.nextlist = self.nextlist + self.parm_list
|
||||
|
||||
# button connections
|
||||
self.cmb_probe_select.activated.connect(self.cmb_probe_select_changed)
|
||||
self.outside_buttonGroup.buttonClicked.connect(self.probe_btn_clicked)
|
||||
self.inside_buttonGroup.buttonClicked.connect(self.probe_btn_clicked)
|
||||
self.skew_buttonGroup.buttonClicked.connect(self.probe_btn_clicked)
|
||||
self.boss_pocket_buttonGroup.buttonClicked.connect(self.boss_pocket_clicked)
|
||||
self.ridge_valley_buttonGroup.buttonClicked.connect(self.ridge_valley_clicked)
|
||||
self.cal_buttonGroup.buttonClicked.connect(self.cal_btn_clicked)
|
||||
self.clear_buttonGroup.buttonClicked.connect(self.clear_results_clicked)
|
||||
self.btn_probe_help.clicked.connect(self.probe_help_clicked)
|
||||
|
||||
self.cmb_probe_select.clear()
|
||||
self.cmb_probe_select.addItems(self.probe_list)
|
||||
self.stackedWidget_probe_buttons.setCurrentIndex(0)
|
||||
# define validators for all lineEdit widgets
|
||||
self.lineEdit_probe_tool.setValidator(QtGui.QRegularExpressionValidator(QRegularExpression('[0-9]{0,5}')))
|
||||
for i in self.parm_list:
|
||||
self['lineEdit_' + i].setValidator(self.valid)
|
||||
|
||||
STATUS.connect('tool-info-changed', lambda w, data: self._tool_info(data))
|
||||
|
||||
# catch focusIn event to pop calculator dialog
|
||||
def eventFilter(self, obj, event):
|
||||
if event.type() == QEvent.FocusIn:
|
||||
if isinstance(obj, QLineEdit):
|
||||
# only if mouse selected
|
||||
if event.reason () == 0:
|
||||
self._nextIndex = self.nextlist.index(obj.objectName().replace('lineEdit_',''))
|
||||
self.popEntry(obj)
|
||||
if self.dialog_code == 'CALCULATOR':
|
||||
obj.clearFocus()
|
||||
event.accept()
|
||||
return True
|
||||
return super(BasicProbeParent, self).eventFilter(obj, event)
|
||||
|
||||
# update the probe loaded HAL pin
|
||||
# can be used to inhibit the spindle
|
||||
def _tool_info(self, data):
|
||||
if data.id != -1:
|
||||
if data.id == int(self.lineEdit_probe_tool.text()):
|
||||
self.probe_loaded.set(True)
|
||||
else:
|
||||
self.probe_loaded.set(False)
|
||||
return
|
||||
self.probe_loaded.set(False)
|
||||
|
||||
def _hal_init(self):
|
||||
def homed_on_status():
|
||||
return (STATUS.machine_is_on() and (STATUS.is_all_homed() or INFO.NO_HOME_REQUIRED))
|
||||
STATUS.connect('state_off', lambda w: self.setEnabled(False))
|
||||
STATUS.connect('state_estop', lambda w: self.setEnabled(False))
|
||||
STATUS.connect('interp-idle', lambda w: self.setEnabled(homed_on_status()))
|
||||
STATUS.connect('all-homed', lambda w: self.setEnabled(True))
|
||||
STATUS.connect('general',self.return_value)
|
||||
|
||||
# must directly initialize
|
||||
self.statuslabel_motiontype.hal_init()
|
||||
self.led_probe.hal_init()
|
||||
self.statelabel_machineUnits.hal_init()
|
||||
self.help = HelpDialog(self.QTVCP_INSTANCE_)
|
||||
self.help.hal_init(HAL_NAME='_basic_help')
|
||||
|
||||
# install event filters on all the lineedits
|
||||
# so we can call up a dialog when lineedit get focus
|
||||
for i in self.nextlist:
|
||||
self['lineEdit_' + i].installEventFilter(self)
|
||||
|
||||
self.set_checkableButtons(not self._runImmediately)
|
||||
|
||||
if self.PREFS_:
|
||||
tool = self.PREFS_.getpref('Probe tool', '0', str, 'PROBE OPTIONS')
|
||||
if not tool.isdecimal():
|
||||
tool = '-1'
|
||||
self.lineEdit_probe_tool.setText(tool)
|
||||
self.lineEdit_probe_diam.setText(self.PREFS_.getpref('Probe diameter', '4', str, 'PROBE OPTIONS'))
|
||||
self.lineEdit_rapid_vel.setText(self.PREFS_.getpref('Probe rapid', '10', str, 'PROBE OPTIONS'))
|
||||
self.lineEdit_probe_vel.setText(self.PREFS_.getpref('Probe feed', '10', str, 'PROBE OPTIONS'))
|
||||
self.lineEdit_search_vel.setText(self.PREFS_.getpref('Probe search', '10', str, 'PROBE OPTIONS'))
|
||||
self.lineEdit_max_travel.setText(self.PREFS_.getpref('Probe max travel', '10', str, 'PROBE OPTIONS'))
|
||||
self.lineEdit_max_z_travel.setText(self.PREFS_.getpref('Probe max z', '2', str, 'PROBE OPTIONS'))
|
||||
self.lineEdit_extra_depth.setText(self.PREFS_.getpref('Probe extra depth', '0', str, 'PROBE OPTIONS'))
|
||||
self.lineEdit_latch_return_dist.setText(self.PREFS_.getpref('Probe step off', '10', str, 'PROBE OPTIONS'))
|
||||
self.lineEdit_xy_clearance.setText(self.PREFS_.getpref('Probe xy clearance', '10', str, 'PROBE OPTIONS'))
|
||||
self.lineEdit_z_clearance.setText(self.PREFS_.getpref('Probe z clearance', '10', str, 'PROBE OPTIONS'))
|
||||
self.lineEdit_side_edge_length.setText(self.PREFS_.getpref('Probe edge width', '10', str, 'PROBE OPTIONS'))
|
||||
self.lineEdit_calibration_offset.setText(self.PREFS_.getpref('Calibration offset', '0', str, 'PROBE OPTIONS'))
|
||||
self.lineEdit_cal_x_width.setText(self.PREFS_.getpref('Cal x width', '0', str, 'PROBE OPTIONS'))
|
||||
self.lineEdit_cal_y_width.setText(self.PREFS_.getpref('Cal y width', '0', str, 'PROBE OPTIONS'))
|
||||
self.lineEdit_cal_diameter.setText(self.PREFS_.getpref('Cal diameter', '0', str, 'PROBE OPTIONS'))
|
||||
|
||||
# make pins available for tool measure remaps
|
||||
oldname = self.HAL_GCOMP_.comp.getprefix()
|
||||
self.HAL_GCOMP_.comp.setprefix('qtbasicprobe')
|
||||
self.probe_loaded = self.HAL_GCOMP_.newpin("probe-loaded", hal.HAL_BIT, hal.HAL_OUT)
|
||||
self.HAL_GCOMP_.comp.setprefix(oldname)
|
||||
|
||||
# get current style of the line input
|
||||
self._oldstyle = self.lineEdit_probe_diam.styleSheet()
|
||||
|
||||
def _hal_cleanup(self):
|
||||
if self.PREFS_:
|
||||
LOG.debug('Saving Basic Probe data to preference file.')
|
||||
self.PREFS_.putpref('Probe tool', self.lineEdit_probe_tool.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Probe diameter', self.lineEdit_probe_diam.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Probe rapid', self.lineEdit_rapid_vel.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Probe feed', self.lineEdit_probe_vel.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Probe search', self.lineEdit_search_vel.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Probe max travel', self.lineEdit_max_travel.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Probe max z', self.lineEdit_max_z_travel.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Probe extra depth', self.lineEdit_extra_depth.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Probe step off', self.lineEdit_latch_return_dist.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Probe xy clearance', self.lineEdit_xy_clearance.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Probe z clearance', self.lineEdit_z_clearance.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Probe edge width', self.lineEdit_side_edge_length.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Calibration offset', self.lineEdit_calibration_offset.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Cal x width', self.lineEdit_cal_x_width.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Cal y width', self.lineEdit_cal_y_width.text(), str, 'PROBE OPTIONS')
|
||||
self.PREFS_.putpref('Cal diameter', self.lineEdit_cal_diameter.text(), str, 'PROBE OPTIONS')
|
||||
if self.proc is not None: self.proc.terminate()
|
||||
|
||||
# process the STATUS return message
|
||||
# set the line edit to the value if not cancelled
|
||||
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)
|
||||
next = message.get('NEXT', False)
|
||||
back = message.get('BACK', False)
|
||||
if code and name:
|
||||
obj = message.get('OBJECT')
|
||||
if num is not None:
|
||||
LOG.debug('message return:{}'.format (message))
|
||||
if obj is self.lineEdit_probe_tool:
|
||||
obj.setText(str(int(num)))
|
||||
else:
|
||||
obj.setText(str(num))
|
||||
# clear high lighting
|
||||
obj.setStyleSheet(self._oldstyle)
|
||||
# request for next input widget from nextlist
|
||||
if next:
|
||||
newobj = self.findNext(self._nextIndex)
|
||||
# update the dialog
|
||||
self.popEntry(newobj,True)
|
||||
elif back:
|
||||
newobj = self.findBack(self._nextIndex)
|
||||
# update the dialog
|
||||
self.popEntry(newobj,True)
|
||||
|
||||
def findNext(self, num):
|
||||
while 1:
|
||||
self._nextIndex += 1
|
||||
if self._nextIndex == len(self.nextlist):
|
||||
self._nextIndex = 0
|
||||
newobj = self['lineEdit_{}'.format(self.nextlist[self._nextIndex])]
|
||||
if newobj.isVisible():
|
||||
break
|
||||
return newobj
|
||||
|
||||
def findBack(self, num):
|
||||
while 1:
|
||||
self._nextIndex -= 1
|
||||
if self._nextIndex == -1:
|
||||
self._nextIndex = len(self.nextlist)-1
|
||||
newobj = self['lineEdit_{}'.format(self.nextlist[self._nextIndex])]
|
||||
if newobj.isVisible():
|
||||
break
|
||||
return newobj
|
||||
|
||||
def popEntry(self, obj, next=False):
|
||||
STATUS.emit('focus-overlay-changed', False, None, None)
|
||||
obj.setStyleSheet(self.hilightStyle)
|
||||
QApplication.processEvents()
|
||||
|
||||
mess = {'NAME':self.dialog_code,
|
||||
'ID':'%s__' % self.objectName(),
|
||||
'OVERLAY':False,
|
||||
'OBJECT':obj,
|
||||
'TITLE':'Set Entry for {}'.format(obj.toolTip().upper()),
|
||||
'GEONAME':'_{}'.format(self.dialog_code),
|
||||
'OVERLAY':True,
|
||||
'NEXT':next,
|
||||
'WIDGETCYCLE': True
|
||||
}
|
||||
STATUS.emit('dialog-request', mess)
|
||||
LOG.debug('message sent:{}'.format (mess))
|
||||
|
||||
#################
|
||||
# process control
|
||||
#################
|
||||
def start_process(self):
|
||||
self.proc = QProcess()
|
||||
self.proc.setReadChannel(QProcess.StandardOutput)
|
||||
self.proc.started.connect(self.process_started)
|
||||
self.proc.readyReadStandardOutput.connect(self.read_stdout)
|
||||
self.proc.readyReadStandardError.connect(self.read_stderror)
|
||||
self.proc.finished.connect(self.process_finished)
|
||||
self.proc.start('python3 {}'.format(SUBPROGRAM))
|
||||
|
||||
def start_probe(self, cmd):
|
||||
if self.proc is not None:
|
||||
LOG.info("Probe Routine processor is busy")
|
||||
return
|
||||
ACTION.RECORD_CURRENT_MODE()
|
||||
t = int(self.lineEdit_probe_tool.text())
|
||||
if t != STATUS.get_current_tool():
|
||||
msg = "Probe tool # {}. not mounted in spindle".format(t)
|
||||
if not self.set_statusbar(msg,CRITICAL):
|
||||
STATUS.emit('update-machine-log', msg, 'TIME,CRITICAL')
|
||||
ACTION.SET_ERROR_MESSAGE(msg)
|
||||
return
|
||||
|
||||
self.start_process()
|
||||
string_to_send = cmd + '$' + json.dumps(self.send_dict) + '\n'
|
||||
#print("String to send ", string_to_send)
|
||||
STATUS.block_error_polling()
|
||||
self.proc.writeData(bytes(string_to_send, 'utf-8'))
|
||||
|
||||
def process_started(self):
|
||||
LOG.info("Basic_Probe subprogram started with PID {}\n".format(self.proc.processId()))
|
||||
|
||||
def read_stdout(self):
|
||||
qba = self.proc.readAllStandardOutput()
|
||||
line = qba.data()
|
||||
self.parse_input(line)
|
||||
|
||||
def read_stderror(self):
|
||||
qba = self.proc.readAllStandardError()
|
||||
line = qba.data()
|
||||
self.parse_input(line)
|
||||
|
||||
def process_finished(self, exitCode, exitStatus):
|
||||
LOG.info(("Probe Process finished - exitCode {} exitStatus {}".format(exitCode, exitStatus)))
|
||||
self.proc = None
|
||||
STATUS.unblock_error_polling()
|
||||
ACTION.RESTORE_RECORDED_MODE()
|
||||
|
||||
def parse_input(self, line):
|
||||
line = line.decode("utf-8")
|
||||
if "ERROR INFO" in line:
|
||||
ACTION.SET_ERROR_MESSAGE(line)
|
||||
elif "ERROR" in line:
|
||||
#print(line)
|
||||
STATUS.unblock_error_polling()
|
||||
ACTION.SET_ERROR_MESSAGE('Basic Probe process finished in error')
|
||||
elif "INFO" in line:
|
||||
pass
|
||||
elif "PROBE_ROUTINES" in line:
|
||||
if LOG.getEffectiveLevel() < logger.INFO:
|
||||
print(line)
|
||||
elif "COMPLETE" in line:
|
||||
STATUS.unblock_error_polling()
|
||||
LOG.info("Basic Probing routine completed without errors")
|
||||
return_data = line.rstrip().split('$')
|
||||
data = json.loads(return_data[1])
|
||||
self.show_results(data)
|
||||
elif "HISTORY" in line:
|
||||
if not self.set_statusbar(line, WARNING):
|
||||
STATUS.emit('update-machine-log', line, 'TIME')
|
||||
elif "DEBUG" in line:
|
||||
pass
|
||||
else:
|
||||
LOG.error("Error parsing return data from sub_processor. Line={}".format(line))
|
||||
|
||||
# return false if failed so other ways of reporting can be used.
|
||||
# there might not be a statusbar in main screen.
|
||||
def set_statusbar(self, msg, priority = DEFAULT, noLog = False):
|
||||
try:
|
||||
self.QTVCP_INSTANCE_.add_status(msg, priority, noLog)
|
||||
except:
|
||||
return False
|
||||
return True
|
||||
|
||||
# Main button handler routines
|
||||
|
||||
# called externally to run selected routine
|
||||
# if not in _runImmediately mode
|
||||
def cycle_start(self):
|
||||
if self._runImmediately:
|
||||
self.set_statusbar('Basic probe set to run buttons immediately',WARNING)
|
||||
return
|
||||
if self._cmd is None:
|
||||
self.set_statusbar('No Basic probe probe selected',WARNING)
|
||||
return
|
||||
self.set_statusbar('Basic probe: Start Cycle: {}'.format(self._cmd.toolTip(),DEFAULT))
|
||||
self.get_parms()
|
||||
self.start_probe(self._cmd.property('probe'))
|
||||
|
||||
def probe_help_clicked(self):
|
||||
self.help.showDialog()
|
||||
|
||||
def cmb_probe_select_changed(self, index):
|
||||
self.stackedWidget_probe_buttons.setCurrentIndex(index)
|
||||
|
||||
if self._runImmediately:
|
||||
return
|
||||
# Auto exclusive doesn't allow unchecking all buttons
|
||||
# We force it here
|
||||
if not self._cmd is None:
|
||||
button = self._cmd
|
||||
button.group().setExclusive(False)
|
||||
button.blockSignals(True)
|
||||
button.setChecked(False)
|
||||
button.blockSignals(False)
|
||||
button.group().setExclusive(True)
|
||||
self._cmd = None
|
||||
|
||||
def probe_btn_clicked(self, button):
|
||||
self.generalButtonCall(button)
|
||||
|
||||
def boss_pocket_clicked(self, button):
|
||||
for i in ['x_hint_bp', 'y_hint_bp', 'diameter_hint']:
|
||||
if self['lineEdit_' + i].text() is None: return
|
||||
self.generalButtonCall(button)
|
||||
|
||||
def ridge_valley_clicked(self, button):
|
||||
for i in ['x_hint_rv', 'y_hint_rv']:
|
||||
if self['lineEdit_' + i].text() is None: return
|
||||
self.generalButtonCall(button)
|
||||
|
||||
def cal_btn_clicked(self, button):
|
||||
for i in ['calibration_offset', 'cal_diameter', 'cal_x_width', 'cal_y_width']:
|
||||
if self['lineEdit_' + i].text() is None: return
|
||||
self.generalButtonCall(button)
|
||||
|
||||
def generalButtonCall(self, button):
|
||||
cmd = button
|
||||
print("Button clicked ", cmd.property('probe'),self._cmd)
|
||||
|
||||
# run probe when buttons are pressed
|
||||
if self._runImmediately:
|
||||
self.set_statusbar('Basic probe: Start Cycle: {}'.format(cmd.toolTip(),DEFAULT))
|
||||
self.get_parms()
|
||||
self.start_probe(cmd.property('probe'))
|
||||
|
||||
# select probe when buttons pressed, run when start_cycle function called
|
||||
else:
|
||||
# Auto exclusive doesn't allow unchecking all buttons
|
||||
# We force it here
|
||||
if cmd == self._cmd:
|
||||
button.group().setExclusive(False)
|
||||
button.blockSignals(True)
|
||||
button.setChecked(False)
|
||||
button.blockSignals(False)
|
||||
button.group().setExclusive(True)
|
||||
self._cmd = None
|
||||
return
|
||||
self.set_statusbar('Basic probe: Selected: {}'.format(cmd.toolTip()),DEFAULT,noLog=True)
|
||||
self._cmd = cmd
|
||||
|
||||
def clear_results_clicked(self, button):
|
||||
cmd = button.property('clear')
|
||||
if cmd in dir(self): self[cmd]()
|
||||
|
||||
def clear_x(self):
|
||||
self.status_xm.setText('0')
|
||||
self.status_xp.setText('0')
|
||||
self.status_xc.setText('0')
|
||||
self.status_lx.setText('0')
|
||||
|
||||
def clear_y(self):
|
||||
self.status_ym.setText('0')
|
||||
self.status_yp.setText('0')
|
||||
self.status_yc.setText('0')
|
||||
self.status_ly.setText('0')
|
||||
|
||||
def clear_all(self):
|
||||
self.clear_x()
|
||||
self.clear_y()
|
||||
self.status_z.setText('0')
|
||||
self.status_d.setText('0')
|
||||
self.status_delta.setText('0')
|
||||
self.status_a.setText('0')
|
||||
|
||||
# Helper functions
|
||||
def get_parms(self):
|
||||
self.send_dict = {key: self['lineEdit_' + key].text() for key in (self.parm_list)}
|
||||
#print(self.send_dict)
|
||||
for key in ['allow_auto_zero', 'allow_auto_skew', 'cal_avg_error', 'cal_x_error', 'cal_y_error']:
|
||||
val = '1' if self[key].isChecked() else '0'
|
||||
self.send_dict.update( {key: val} )
|
||||
|
||||
def show_results(self, line):
|
||||
for key in self.status_list:
|
||||
if line[key] != 'None':
|
||||
self['status_' + key].setText(line[key])
|
||||
else:
|
||||
self['status_' + key].setText('')
|
||||
|
||||
# run-immediately buttons are momentary
|
||||
# pre-select buttons are checkable
|
||||
# focus policy changes for 'cycle start button steering'
|
||||
# with pre-select option
|
||||
def set_checkableButtons(self, state):
|
||||
if state:
|
||||
policy = Qt.ClickFocus
|
||||
else:
|
||||
policy = Qt.NoFocus
|
||||
self.setFocusPolicy(policy)
|
||||
for i in self.outside_buttonGroup.buttons():
|
||||
i.setCheckable(state)
|
||||
for i in self.inside_buttonGroup.buttons():
|
||||
i.setCheckable(state)
|
||||
for i in self.skew_buttonGroup.buttons():
|
||||
i.setCheckable(state)
|
||||
for i in self.ridge_valley_buttonGroup.buttons():
|
||||
i.setCheckable(state)
|
||||
for i in self.boss_pocket_buttonGroup.buttons():
|
||||
i.setCheckable(state)
|
||||
for i in self.cal_buttonGroup.buttons():
|
||||
i.setCheckable(state)
|
||||
|
||||
#########################################################################
|
||||
# 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_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'
|
||||
dialogCodeString = Property(str, get_dialog_code, set_dialog_code, reset_dialog_code)
|
||||
|
||||
def set_runImmediately(self, data):
|
||||
self._runImmediately = data
|
||||
self.set_checkableButtons(not data)
|
||||
def get_runImmediately(self):
|
||||
return self._runImmediately
|
||||
def reset_runImmediately(self):
|
||||
self._runImmediately = True
|
||||
|
||||
# toggle run on button push or run on function call
|
||||
runImmediately = Property(bool, get_runImmediately, set_runImmediately, reset_runImmediately)
|
||||
|
||||
##############################
|
||||
# required class boiler code #
|
||||
##############################
|
||||
def __getitem__(self, item):
|
||||
return getattr(self, item)
|
||||
|
||||
def __setitem__(self, item, value):
|
||||
return setattr(self, item, value)
|
||||
|
||||
class HelpDialog(QtWidgets.QDialog, GeometryMixin):
|
||||
def __init__(self, parent=None):
|
||||
super(HelpDialog, self).__init__(parent)
|
||||
self._title = 'Basic Probe Help'
|
||||
self.setWindowFlags(self.windowFlags() | Qt.Tool |
|
||||
Qt.Dialog | Qt.WindowStaysOnTopHint |
|
||||
Qt.WindowSystemMenuHint)
|
||||
self.currentHelpPage=-1
|
||||
self.setMinimumWidth(600)
|
||||
self.setMinimumHeight(600)
|
||||
self.helpPages = ['basic_help.html','basic_help1.html','basic_help2.html',
|
||||
'basic_help3.html','basic_help4.html','basic_help5.html',
|
||||
'basic_help6.html','basic_help7.html','basic_help8.html']
|
||||
|
||||
def _hal_init(self):
|
||||
self.buildWidget()
|
||||
self.set_default_geometry()
|
||||
self.read_preference_geometry('basicProbeHelpDialog-geometry')
|
||||
|
||||
def buildWidget(self):
|
||||
|
||||
l = QtWidgets.QVBoxLayout()
|
||||
t = QtWidgets.QTextEdit('Basic Probe Help')
|
||||
t.setReadOnly(True)
|
||||
l.addWidget(t)
|
||||
|
||||
buttons = QDialogButtonBox()
|
||||
|
||||
closebutton = QtWidgets.QPushButton()
|
||||
closebutton.setIconSize(QtCore.QSize(38, 38))
|
||||
closebutton.setIcon(QtGui.QIcon(':/qt-project.org/styles/commonstyle/images/standardbutton-cancel-128.png'))
|
||||
closebutton.clicked.connect(lambda : self.close())
|
||||
|
||||
nextbutton = QtWidgets.QPushButton()
|
||||
nextbutton.setIconSize(QtCore.QSize(38, 38))
|
||||
nextbutton.setIcon(QtGui.QIcon(':/qt-project.org/styles/commonstyle/images/right-32.png'))
|
||||
nextbutton.clicked.connect(lambda : self.next(t,True))
|
||||
|
||||
previousbutton = QtWidgets.QPushButton()
|
||||
previousbutton.setIconSize(QtCore.QSize(38, 38))
|
||||
previousbutton.setIcon(QtGui.QIcon(':/qt-project.org/styles/commonstyle/images/left-32.png'))
|
||||
previousbutton.clicked.connect(lambda : self.next(t,False))
|
||||
|
||||
self.pageStepUpbutton = QtWidgets.QPushButton()
|
||||
self.pageStepUpbutton.setIconSize(QtCore.QSize(38, 38))
|
||||
self.pageStepUpbutton.setIcon(QtGui.QIcon(':/qt-project.org/styles/commonstyle/images/up-32.png'))
|
||||
self.pageStepUpbutton.clicked.connect(lambda : self.pageStep(t,False))
|
||||
|
||||
self.pageStepDwnbutton = QtWidgets.QPushButton()
|
||||
self.pageStepDwnbutton.setIconSize(QtCore.QSize(38, 38))
|
||||
self.pageStepDwnbutton.setIcon(QtGui.QIcon(':/qt-project.org/styles/commonstyle/images/down-32.png'))
|
||||
self.pageStepDwnbutton.clicked.connect(lambda : self.pageStep(t,True))
|
||||
|
||||
bBox = QDialogButtonBox(buttons)
|
||||
bBox.addButton(self.pageStepUpbutton, QDialogButtonBox.ActionRole)
|
||||
bBox.addButton(self.pageStepDwnbutton, QDialogButtonBox.ActionRole)
|
||||
bBox.addButton(previousbutton, QDialogButtonBox.ActionRole)
|
||||
bBox.addButton(nextbutton, QDialogButtonBox.ActionRole)
|
||||
bBox.addButton(closebutton, QDialogButtonBox.DestructiveRole)
|
||||
bBox.rejected.connect(self.reject)
|
||||
|
||||
l.addWidget(bBox)
|
||||
self.setLayout(l)
|
||||
|
||||
# Load the first help page lazily (on first show): pre-scaling its
|
||||
# images at construction can hang or crash qtvcp headless.
|
||||
self._helpText = t
|
||||
self._helpLoaded = False
|
||||
|
||||
def _set_scaled_html(self, t, html):
|
||||
# QTextEdit scales raster images with a nearest-neighbour filter, which
|
||||
# makes the help diagrams look jagged. Pre-scale each raster <img> to
|
||||
# its requested size with a smooth filter and register it as a document
|
||||
# resource so Qt draws it 1:1. SVG images are left untouched so Qt's
|
||||
# vector renderer draws them crisply at any size.
|
||||
doc = t.document()
|
||||
for tag in re.findall(r'<img\b[^>]*>', html):
|
||||
match = re.search(r'src="([^"]+)"', tag)
|
||||
if match is None or match.group(1).lower().endswith('.svg'):
|
||||
continue
|
||||
src = match.group(1)
|
||||
image = QtGui.QImage(src)
|
||||
if image.isNull():
|
||||
continue
|
||||
width = re.search(r'width="(\d+)"', tag)
|
||||
height = re.search(r'height="(\d+)"', tag)
|
||||
if width is not None and int(width.group(1)) != image.width():
|
||||
image = image.scaledToWidth(int(width.group(1)), QtCore.Qt.SmoothTransformation)
|
||||
elif height is not None and int(height.group(1)) != image.height():
|
||||
image = image.scaledToHeight(int(height.group(1)), QtCore.Qt.SmoothTransformation)
|
||||
doc.addResource(QtGui.QTextDocument.ImageResource, QtCore.QUrl(src), image)
|
||||
t.setHtml(html)
|
||||
|
||||
def next(self,t,direction=None):
|
||||
if direction is None:
|
||||
self.currentHelpPage = 0
|
||||
elif direction:
|
||||
self.currentHelpPage +=1
|
||||
if self.currentHelpPage > len(self.helpPages)-1:
|
||||
self.currentHelpPage = len(self.helpPages)-1
|
||||
else:
|
||||
self.currentHelpPage -=1
|
||||
if self.currentHelpPage < 0:
|
||||
self.currentHelpPage = 0
|
||||
try:
|
||||
pagePath = os.path.join(HELP, self.helpPages[self.currentHelpPage])
|
||||
file = QtCore.QFile(pagePath)
|
||||
file.open(QtCore.QFile.ReadOnly)
|
||||
html = file.readAll()
|
||||
html = str(html, encoding='utf8')
|
||||
html = html.replace("../images/widgets/","{}/widgets/".format(PATH.IMAGEDIR))
|
||||
self._set_scaled_html(t, html)
|
||||
if t.verticalScrollBar().isVisible():
|
||||
t.verticalScrollBar().setPageStep(100)
|
||||
self.pageStepDwnbutton.show()
|
||||
self.pageStepUpbutton.show()
|
||||
else:
|
||||
self.pageStepDwnbutton.hide()
|
||||
self.pageStepUpbutton.hide()
|
||||
|
||||
except Exception as e:
|
||||
t.setHtml('''
|
||||
<h1 style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:xx-large; font-weight:600;">Basic Probe Help not available</span> </h1>
|
||||
{}
|
||||
'''.format(e))
|
||||
if direction is None:
|
||||
return
|
||||
self.show()
|
||||
|
||||
def pageStep(self, t, state):
|
||||
if state:
|
||||
t.verticalScrollBar().triggerAction (QAbstractSlider.SliderPageStepAdd)
|
||||
else:
|
||||
t.verticalScrollBar().triggerAction (QAbstractSlider.SliderPageStepSub)
|
||||
|
||||
# accept button applies presets and if line number given starts linuxcnc
|
||||
def close(self):
|
||||
self.record_geometry()
|
||||
super(HelpDialog, self).close()
|
||||
|
||||
def showDialog(self):
|
||||
if not self._helpLoaded:
|
||||
try:
|
||||
self.next(self._helpText)
|
||||
except Exception as e:
|
||||
self._helpText.setText('Basic Probe Help file Unavailable:\n\n{}'.format(e))
|
||||
self._helpLoaded = True
|
||||
self.setWindowTitle(self._title);
|
||||
self.set_geometry()
|
||||
retval = self.exec()
|
||||
LOG.debug('Value of pressed button: {}'.format(retval))
|
||||
|
||||
# look for a custom version of basicProbe
|
||||
module = PATH.find_custom_widget_path('basic_probe.py','BasicProbeCustom')
|
||||
if not module:
|
||||
module = BasicProbeParent
|
||||
|
||||
class BasicProbe(module):
|
||||
def __init__(self, parent=None):
|
||||
super(BasicProbe, self).__init__(parent)
|
||||
|
||||
#############################
|
||||
# Testing #
|
||||
#############################
|
||||
if __name__ == "__main__":
|
||||
from qtpy.QtWidgets import *
|
||||
from qtpy.QtCore import *
|
||||
from qtpy.QtGui import *
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
w = BasicProbe()
|
||||
w.show()
|
||||
sys.exit( app.exec() )
|
||||
|
||||
Reference in New Issue
Block a user