#!/usr/bin/env python3 # Qtvcp basic probe # # Copyright (c) 2020 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. # # 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 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']*>', 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('''

Basic Probe Help not available

{} '''.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() )