Files
linuxcncAllWasm/lib/python/qtvcp/widgets/versa_probe.py
2026-08-20 06:17:39 +08:00

796 lines
36 KiB
Python

#!/usr/bin/env python3
# Qtvcp versa probe
#
# 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.
#
# a probe screen based on Versa probe screen
import sys
import os
import hal
import json
import re
from qtpy import QtGui, QtCore, QtWidgets, uic
from qtpy.QtCore import QProcess, QEvent, Qt, Property
from qtpy.QtWidgets import QDialogButtonBox, QAbstractSlider
from qtvcp.widgets.widget_baseclass import _HalWidgetBase
from qtvcp.core import Status, Action, Info, Path
from qtvcp.widgets.dialogMixin import GeometryMixin
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()
ACTION = Action()
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'))
# can use/favours local image and help files
HELP = PATH.find_widget_path()
ICONPATH = os.path.join(PATH.find_image_path(), 'probe_icons')
DEFAULT = 0
WARNING = 1
CRITICAL = 2
class VersaProbeParent(QtWidgets.QWidget, _HalWidgetBase):
def __init__(self, parent=None):
super(VersaProbeParent, self).__init__(parent)
self.proc = None
self.tool_diameter = None
self.tool_number = None
self.probe_number = -1
self._nextIndex = 0
self._cmd = None
self._runImmediately = True
STATUS.connect('tool-info-changed', lambda w, data: self._tool_info(data))
if INFO.MACHINE_IS_METRIC:
self.valid = QtGui.QRegularExpressionValidator(QtCore.QRegularExpression(r'^((\d{1,4}(\.\d{1,3})?)|(\.\d{1,3}))$'))
else:
self.valid = QtGui.QRegularExpressionValidator(QtCore.QRegularExpression(r'^((\d{1,3}(\.\d{1,4})?)|(\.\d{1,4}))$'))
self.setMinimumSize(600, 420)
# Load the widgets UI file will use local file if available:
self.filename = PATH.find_widget_path('versa_probe.ui')
try:
self.instance = uic.loadUi(self.filename, self)
except AttributeError as e:
LOG.critical(e)
self.dialog_code = 'CALCULATOR'
self.hilightStyle = "border: 2px solid red;"
#create parameter dictionary
self.send_dict = {}
# these parameters are sent to the subprogram
# the is also the order of the next widget when dialog 'next' button is pressed
self.parm_list = ['probe_diam', 'rapid_vel', 'search_vel', 'probe_vel',
'xy_clearance', 'z_clearance', 'side_edge_length',
'max_travel', 'max_z_travel', 'latch_return_dist',
'tool_probe_height', 'tool_block_height',
'adj_x', 'adj_y', 'adj_z', 'adj_angle',]
self.status_list = ['xm', 'xc', 'xp', 'ym', 'yc', 'yp', 'lx', 'ly', 'z', 'd', 'a','th','bh']
for i in self.parm_list:
self['input_' + i].setValidator(self.valid)
# button connections
self.btn_help.clicked.connect(self.help_clicked)
self.inside_buttonGroup.buttonClicked.connect(self.probe_btn_clicked)
self.outside_buttonGroup.buttonClicked.connect(self.probe_btn_clicked)
self.skew_buttonGroup.buttonClicked.connect(self.probe_btn_clicked)
self.length_buttonGroup.buttonClicked.connect(self.probe_btn_clicked)
self.tool_buttonGroup.buttonClicked.connect(self.probe_btn_clicked)
self.pbtn_set_x.released.connect(self.pbtn_set_x_released)
self.pbtn_set_y.released.connect(self.pbtn_set_y_released)
self.pbtn_set_z.released.connect(self.pbtn_set_z_released)
self.pbtn_set_angle.released.connect(self.pbtn_set_angle_released)
self.buildToolTip(self.input_search_vel, 'Search Velocity', 'search_vel')
self.buildToolTip(self.input_probe_vel, 'Probe Velocity', 'probe_vel')
self.buildToolTip(self.input_z_clearance, 'Z Clearence Distance', 'Zclearance')
self.buildToolTip(self.input_max_travel, 'Maximum Probe Search Distance', 'rapid')
self.buildToolTip(self.input_latch_return_dist, 'Return After Latch Distance', 'rapid_return')
self.buildToolTip(self.input_probe_diam,'Probe Diameter','probe_diam')
self.buildToolTip(self.input_xy_clearance, 'XY Clearence Distance', 'XYclearance')
self.buildToolTip(self.input_side_edge_length, 'Edge length', 'edge_length')
#self.buildToolTip(self.input_tool_probe_height, '', '')
#self.buildToolTip(self.input_tool_block_height, '', '')
#self.buildToolTip(self.input_adj_x, '', '')
#self.buildToolTip(self.input_adj_y, '', '')
#self.buildToolTip(self.input_adj_z, '', '')
#self.buildToolTip(self.input_adj_angle, '', '')
self.buildToolTip(self.input_rapid_vel, 'Rapid Velocity', 'rapid_vel')
# catch focusIn event to pop calculator dialog
def eventFilter(self, obj, event):
if event.type() == QEvent.FocusIn:
if isinstance(obj, QtWidgets.QLineEdit):
# only if mouse selected
if event.reason () == 0:
self._nextIndex = self.parm_list.index(obj.objectName().replace('input_',''))
self.popEntry(obj)
if self.dialog_code == 'CALCULATOR':
obj.clearFocus()
event.accept()
return True
return super(VersaProbeParent, self).eventFilter(obj, event)
# keep track of tool number and diameter
# update the probe loaded HAL pin
# can be used to inhibit the spindle
def _tool_info(self, data):
if data.id != -1:
self.tool_diameter = data.diameter
self.tool_number = data.id
if self.probe_number == self.tool_number:
self.probe_loaded.set(True)
else:
self.probe_loaded.set(False)
return
self.tool_diameter = None
self.tool_number = None
self.probe_loaded.set(False)
def _hal_init(self):
def homed_on_test():
return (STATUS.machine_is_on() and (STATUS.is_all_homed() or INFO.NO_HOME_REQUIRED))
# get current style of the line input
self._oldstyle = self.input_search_vel.styleSheet()
# have to call hal_init on widgets in this widget ourselves
# qtvcp doesn't see them otherwise
oldname = self.HAL_GCOMP_.comp.getprefix()
self.HAL_GCOMP_.comp.setprefix('qtversaprobe')
self.pbtn_use_tool_measurement.setProperty('pin_name','enable')
self.pbtn_use_tool_measurement.hal_init()
self.HAL_GCOMP_.comp.setprefix(oldname)
self.allow_auto_skew.hal_init()
self.allow_auto_zero.hal_init()
self.statuslabel_motiontype.hal_init()
self.statelabel_machineUnits.hal_init()
self.statelabel_machineUnits_2.hal_init()
self.help = HelpDialog(self.QTVCP_INSTANCE_)
self.help.hal_init(HAL_NAME='_versa_help')
# connect to STATUS
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_test()))
STATUS.connect('all-homed', lambda w: self.setEnabled(homed_on_test()))
# STATUS.connect('error', self.send_error)
STATUS.connect('periodic', lambda w: self.check_probe())
STATUS.connect('general',self.return_value)
# install event filters on all the lineedits
# so we can call up a dialog when lineedit get focus
self.input_search_vel.installEventFilter(self)
self.input_probe_vel.installEventFilter(self)
self.input_z_clearance.installEventFilter(self)
self.input_max_travel.installEventFilter(self)
self.input_max_z_travel.installEventFilter(self)
self.input_latch_return_dist.installEventFilter(self)
self.input_probe_diam.installEventFilter(self)
self.input_xy_clearance.installEventFilter(self)
self.input_side_edge_length.installEventFilter(self)
self.input_tool_probe_height.installEventFilter(self)
self.input_tool_block_height.installEventFilter(self)
self.input_adj_x.installEventFilter(self)
self.input_adj_y.installEventFilter(self)
self.input_adj_z.installEventFilter(self)
self.input_adj_angle.installEventFilter(self)
self.input_rapid_vel.installEventFilter(self)
self.set_checkableButtons(not self._runImmediately)
if self.PREFS_:
self.probe_number = self.PREFS_.getpref('ps_probe_tool', -1, int, 'VERSA_PROBE_OPTIONS')
self.input_search_vel.setText(str(self.PREFS_.getpref( "ps_searchvel", 300.0, float, 'VERSA_PROBE_OPTIONS')) )
self.input_probe_vel.setText(str(self.PREFS_.getpref( "ps_probevel", 10.0, float, 'VERSA_PROBE_OPTIONS')) )
self.input_z_clearance.setText(str(self.PREFS_.getpref( "ps_z_clearance", 3.0, float, 'VERSA_PROBE_OPTIONS')) )
self.input_max_travel.setText(str(self.PREFS_.getpref( "ps_probe_max", 1.0, float, 'VERSA_PROBE_OPTIONS')) )
self.input_max_z_travel.setText(str(self.PREFS_.getpref( "ps_probe_max_z_travel", 1.0, float, 'VERSA_PROBE_OPTIONS')) )
self.input_latch_return_dist.setText(str(self.PREFS_.getpref( "ps_probe_latch", 0.5, float, 'VERSA_PROBE_OPTIONS')) )
self.input_probe_diam.setText(str(self.PREFS_.getpref( "ps_probe_diam", 2.0, float, 'VERSA_PROBE_OPTIONS')) )
self.input_xy_clearance.setText(str(self.PREFS_.getpref( "ps_xy_clearance", 5.0, float, 'VERSA_PROBE_OPTIONS')) )
self.input_side_edge_length.setText(str(self.PREFS_.getpref( "ps_side_edge_length", 5.0, float, 'VERSA_PROBE_OPTIONS')) )
self.input_tool_probe_height.setText(str(self.PREFS_.getpref( "ps_probe_height", 20.0, float, 'VERSA_PROBE_OPTIONS')) )
self.input_tool_block_height.setText(str(self.PREFS_.getpref( "ps_block_height", 20.0, float, 'VERSA_PROBE_OPTIONS')) )
self.input_adj_x.setText(str(self.PREFS_.getpref( "ps_offs_x", 0.0, float, 'VERSA_PROBE_OPTIONS')) )
self.input_adj_y.setText(str(self.PREFS_.getpref( "ps_offs_y", 0.0, float, 'VERSA_PROBE_OPTIONS')) )
self.input_adj_z.setText(str(self.PREFS_.getpref( "ps_offs_z", 0.0, float, 'VERSA_PROBE_OPTIONS')) )
self.input_adj_angle.setText(str(self.PREFS_.getpref( "ps_offs_angle", 0.0, float, 'VERSA_PROBE_OPTIONS')) )
self.input_rapid_vel.setText(str(self.PREFS_.getpref( "ps_probe_rapid_vel", 60.0, float, 'VERSA_PROBE_OPTIONS')) )
self.pbtn_use_tool_measurement.setChecked((self.PREFS_.getpref( "use_tool_measurement", True, bool, 'VERSA_PROBE_OPTIONS')) )
self.allow_auto_zero.setChecked((self.PREFS_.getpref( "use_auto_zero", True, bool, 'VERSA_PROBE_OPTIONS')) )
self.allow_auto_skew.setChecked((self.PREFS_.getpref( "use_auto_skew", True, bool, 'VERSA_PROBE_OPTIONS')) )
self.z_max_clear = INFO.get_safe_float("VERSA_TOOLSETTER", "Z_MAX_CLEAR")
self.ts_x = INFO.get_safe_float('VERSA_TOOLSETTER','X')
self.ts_y = INFO.get_safe_float('VERSA_TOOLSETTER','Y')
self.ts_z = INFO.get_safe_float('VERSA_TOOLSETTER','Z')
self.ts_max = INFO.get_safe_float('VERSA_TOOLSETTER','MAXPROBE')
self.ts_diam = INFO.get_safe_float('VERSA_TOOLSETTER','DIAMETER')
# make pins available for tool measure remaps
oldname = self.HAL_GCOMP_.comp.getprefix()
self.HAL_GCOMP_.comp.setprefix('qtversaprobe')
self.pin_svel = self.HAL_GCOMP_.newpin("searchvel", hal.HAL_FLOAT, hal.HAL_OUT)
self.pin_svel.set(float(self.input_search_vel.text()))
self.pin_pvel = self.HAL_GCOMP_.newpin("probevel", hal.HAL_FLOAT, hal.HAL_OUT)
self.pin_pvel.set(float(self.input_probe_vel.text()))
self.pin_pheight = self.HAL_GCOMP_.newpin("probeheight", hal.HAL_FLOAT, hal.HAL_OUT)
self.pin_pheight.set(float(self.input_tool_probe_height.text()))
self.pin_bheight = self.HAL_GCOMP_.newpin("blockheight", hal.HAL_FLOAT, hal.HAL_OUT)
self.pin_bheight.set(float(self.input_tool_block_height.text()))
self.pin_latch_rtn = self.HAL_GCOMP_.newpin("backoffdist", hal.HAL_FLOAT, hal.HAL_OUT)
self.pin_latch_rtn.set(float(self.input_latch_return_dist.text()))
self.probe_loaded = self.HAL_GCOMP_.newpin("probe-loaded", hal.HAL_BIT, hal.HAL_OUT)
self.HAL_GCOMP_.comp.setprefix(oldname)
# install callbacks to update HAL pins
self.input_search_vel.textChanged.connect(self.update_search_vel_pin)
self.input_probe_vel.textChanged.connect(self.update_probe_vel_pin)
self.input_tool_probe_height.textChanged.connect(self.update_probe_height_pin)
self.input_tool_block_height.textChanged.connect(self.update_block_height_pin)
self.input_latch_return_dist.textChanged.connect(self.update_latch_return_dist_pin)
# when qtvcp closes this gets called
def _hal_cleanup(self):
if self.PREFS_:
LOG.debug('Saving Versa probe data to preference file.')
self.PREFS_.putpref('ps_probe_tool', self.probe_number, int, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_searchvel", float(self.input_search_vel.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_probevel", float(self.input_probe_vel.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_z_clearance", float(self.input_z_clearance.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_probe_max", float(self.input_max_travel.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_probe_max_z_travel", float(self.input_max_z_travel.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_probe_latch", float(self.input_latch_return_dist.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_probe_diam", float(self.input_probe_diam.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_xy_clearance", float(self.input_xy_clearance.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_side_edge_length", float(self.input_side_edge_length.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_probe_height", float(self.input_tool_probe_height.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_block_height", float(self.input_tool_block_height.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_offs_x", float(self.input_adj_x.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_offs_y", float(self.input_adj_y.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_offs_z", float(self.input_adj_z.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_offs_angle", float(self.input_adj_angle.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "ps_probe_rapid_vel", float(self.input_rapid_vel.text()), float, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "use_tool_measurement", bool(self.pbtn_use_tool_measurement.isChecked()), bool, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "use_auto_zero", bool(self.allow_auto_zero.isChecked()), bool, 'VERSA_PROBE_OPTIONS')
self.PREFS_.putpref( "use_auto_skew", bool(self.allow_auto_skew.isChecked()), bool, 'VERSA_PROBE_OPTIONS')
# 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))
obj.setText(str(num))
# clear high lighting
obj.setStyleSheet(self._oldstyle)
# request for next input widget from parm_list
if next:
self._nextIndex += 1
if self._nextIndex == len(self.parm_list):
self._nextIndex = 0
newobj = self['input_{}'.format(self.parm_list[self._nextIndex])]
# update the dialog
self.popEntry(newobj,True)
elif back:
self._nextIndex -= 1
if self._nextIndex == -1:
self._nextIndex = len(self.parm_list)-1
newobj = self['input_{}'.format(self.parm_list[self._nextIndex])]
# update the dialog
self.popEntry(newobj,True)
def popEntry(self, obj, next=False):
obj.setStyleSheet(self.hilightStyle)
mess = {'NAME':self.dialog_code,
'ID':'%s__' % self.objectName(),
'OVERLAY':False,
'OBJECT':obj,
'TITLE':'Set Entry for {}'.format(obj.objectName().upper()),
'GEONAME':'_{}'.format(self.dialog_code),
'NEXT':next,
'WIDGETCYCLE': True
}
STATUS.emit('dialog-request', mess)
LOG.debug('message sent:{}'.format (mess))
def buildToolTip(self,obj, text, icon):
path = os.path.join(ICONPATH, icon)
obj.setToolTip('''<html><head/><body><p align="center">
<span style=" font-weight:600;">{}</span></p><p align="center">
<img src="{}"/></p></body></html>''' .format(text, path))
#############################################
# 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()
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("Versa_Probe 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('Versa Probe process finished in error')
elif "PROBE_ROUTINES" in line:
if LOG.getEffectiveLevel() < logger.INFO:
print(line)
elif "INFO" in line:
pass
elif "COMPLETE" in line:
STATUS.unblock_error_polling()
LOG.info("Versa 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, DEFAULT):
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))
#####################################################
# button callbacks
#####################################################
def help_clicked(self):
self.pop_help()
def probe_btn_clicked(self, button):
cmd = button
#print("Button clicked ", cmd.property('probe'),self._cmd)
# run probe when buttons are pressed
if self._runImmediately:
self.set_statusbar('Versa 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('Versa probe: Selected: {}'.format(cmd.toolTip()),DEFAULT,noLog=True)
self._cmd = cmd
# called externally to run selected routine
# if not in _runImmediately mode
def cycle_start(self):
if self._runImmediately:
self.set_statusbar('Versa Probe set to run buttons immediately',WARNING)
return
if self._cmd is None:
self.set_statusbar('No Versa Probe probe selected',WARNING)
return
self.set_statusbar('Versa Probe: Start Cycle: {}'.format(self._cmd.toolTip(),DEFAULT))
self.get_parms()
self.start_probe(self._cmd.property('probe'))
###### set origin offset ######################
def pbtn_set_x_released(self):
ACTION.SET_AXIS_ORIGIN('X', float(self.input_adj_x.text()))
def pbtn_set_y_released(self):
ACTION.SET_AXIS_ORIGIN('Y', float(self.input_adj_y.text()))
def pbtn_set_z_released(self):
ACTION.SET_AXIS_ORIGIN('Z', float(self.input_adj_z.text()))
def pbtn_set_angle_released(self):
self.status_a = "%.3f" % float(self.input_adj_angle.text())
s="G10 L2 P0"
if self.allow_auto_zero.isChecked():
s += " X%.4f"% float(self.input_adj_x.text())
s += " Y%.4f"% float(self.input_adj_y.text())
else :
a = STATUS.get_probed_position_with_offsets()
s += " X%.4f"%a[0]
s += " Y%.4f"%a[1]
s += " R%.4f"% float(self.input_adj_angle.text())
ACTION.CALL_MDI_WAIT(s, 30)
# selects probe routine group of buttons
def input_next(self):
next = self.stackedWidget_probe_type.currentIndex() +1
if next == self.stackedWidget_probe_type.count():
next = 0
self.stackedWidget_probe_type.setCurrentIndex(next)
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
#####################################################
# Entry callbacks
#####################################################
def update_search_vel_pin(self, text):
try:
self.pin_svel.set(float(text))
except:
pass
def update_probe_vel_pin(self, text):
try:
self.pin_pvel.set(float(text))
except:
pass
def update_probe_height_pin(self, text):
try:
self.pin_pheight.set(float(text))
except:
pass
def update_block_height_pin(self, text):
value = float(text)
#if value == self.pin_bheight.get(): return
origin = INFO.INI.getreal("AXIS_Z", "MIN_LIMIT") + value
ACTION.CALL_MDI_WAIT( "G10 L2 P0 Z%s" % origin )
try:
self.pin_bheight.set(value)
except:
pass
def update_latch_return_dist_pin(self, text):
try:
self.pin_latch_rtn.set(float(text))
except:
pass
#####################################################
# Helper functions
#####################################################
# 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
def get_parms(self):
self.send_dict = {key: self['input_' + key].text() for key in (self.parm_list)}
for key in ['allow_auto_zero', 'allow_auto_skew']:
val = '1' if self[key].isChecked() else '0'
self.send_dict.update( {key: val} )
# come from INI
for key in ['ts_diam','z_max_clear','ts_x','ts_y','ts_z','ts_max','tool_diameter','tool_number']:
val = str(self[key])
if val == 'NONE': val = None
self.send_dict.update( {key: val} )
def check_probe(self):
try:
self.led_probe_function_chk.setState(hal.get_value('motion.probe-input'))
except:
pass
def show_results(self, line):
for key in self.status_list:
if key in('th','bh'):
if key == 'bh' and line[key] != 'None':
self.input_tool_block_height.setText(line[key])
elif line[key] != 'None':
self.input_tool_probe_height.setText(line[key])
elif line[key] != 'None':
self['status_' + key].setText(line[key])
else:
self['status_' + key].setText('')
def pop_help(self):
self.help.showDialog()
# 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
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.length_buttonGroup.buttons():
i.setCheckable(state)
for i in self.tool_buttonGroup.buttons():
i.setCheckable(state)
########################################
# required boiler code
########################################
def __getitem__(self, item):
return getattr(self, item)
def __setitem__(self, item, value):
return setattr(self, item, value)
#########################################################################
# 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)
class HelpDialog(QtWidgets.QDialog, GeometryMixin):
def __init__(self, parent=None):
super(HelpDialog, self).__init__(parent)
self._title = 'Versa Help'
self.setWindowFlags(self.windowFlags() | Qt.Tool |
Qt.Dialog | Qt.WindowStaysOnTopHint |
Qt.WindowSystemMenuHint)
self.currentHelpPage=-1
self.setMinimumWidth(600)
self.setMinimumHeight(600)
self.helpPages = ['versa_usage.html','versa_usage1.html','versa_usage2.html',
'versa_usage3.html','versa_usage4.html','versa_usage5.html',
'versa_usage6.html','versa_usage7.html','versa_usage8.html']
def _hal_init(self):
self.buildWidget()
self.set_default_geometry()
self.read_preference_geometry('VersaHelpDialog-geometry')
def buildWidget(self):
l = QtWidgets.QVBoxLayout()
t = QtWidgets.QTextEdit('Versa 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/","{}/".format(PATH.IMAGEDIR))
self._set_scaled_html(t, html)
if t.verticalScrollBar().isVisible():
t.verticalScrollBar().setPageStep(20)
self.pageStepDwnbutton.show()
self.pageStepUpbutton.show()
else:
self.pageStepDwnbutton.hide()
self.pageStepUpbutton.hide()
except Exception as e:
t.setText('Versa Probe Help file Unavailable:\n\n{}'.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('Versa 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 Versa Probe
module = PATH.find_custom_widget_path('versa_probe.py','VersaProbeCustom')
if not module:
module = VersaProbeParent
class VersaProbe(module):
def __init__(self, parent=None):
super(VersaProbe, 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 = VersaProbeParent()
w.setObjectName('versaprobeParent')
w.show()
sys.exit( app.exec() )