Files
linuxcncAllWasm/share/qtvcp/screens/qtdragon/qtdragon_handler.py
2026-08-20 06:17:39 +08:00

2431 lines
107 KiB
Python

import os, time
from qtpy import QtCore, QtWidgets, QtGui
from qtpy.QtWidgets import QAction, QMenu
from qtpy.QtGui import QIcon
from qtpy.QtCore import QCoreApplication
from qtvcp.widgets.gcode_editor import GcodeEditor as GCODE
from qtvcp.widgets.gcode_graphics import GCodeGraphics as GRAPHICS
from qtvcp.widgets.mdi_line import MDILine as MDI_WIDGET
from qtvcp.widgets.tool_offsetview import ToolOffsetView as TOOL_TABLE
from qtvcp.widgets.origin_offsetview import OriginOffsetView as OFFSET_VIEW
from qtvcp.widgets.stylesheeteditor import StyleSheetEditor as SSE
from qtvcp.widgets.file_manager import FileManager as FM
from qtvcp.widgets.status_slider import StatusSlider as SLIDER
from qtvcp.lib.qt_ngcgui.ngcgui import NgcGui
from qtvcp.lib.writer import writer
from qtvcp.lib.keybindings import Keylookup
from qtvcp.lib.gcodes import GCodes
from qtvcp.lib.qt_pdf import PDFViewer
from qtvcp.core import Status, Action, Info, Path, Qhal
from qtvcp import logger
from shutil import copyfile
LOG = logger.getLogger(__name__)
KEYBIND = Keylookup()
STATUS = Status()
INFO = Info()
ACTION = Action()
PATH = Path()
STYLEEDITOR = SSE()
WRITER = writer.Main()
QHAL = Qhal()
try:
from qtpy.QtWebEngineWidgets import QWebEnginePage
except:
LOG.warning('QtDragon Warning with loading QtWebEngineWidget - is python3-pyqt5.qtwebengine installed?')
_translate = QCoreApplication.translate
# constants for tab pages
TAB_MAIN = 0
TAB_FILE = 1
TAB_OFFSETS = 2
TAB_TOOL = 3
TAB_STATUS = 4
TAB_PROBE = 5
TAB_CAMERA = 6
TAB_GCODES = 7
TAB_SETUP = 8
TAB_SETTINGS = 9
TAB_UTILITIES = 10
TAB_USER = 11
# constants for (left side) stacked widget
PAGE_UNCHANGED = -1
PAGE_GCODE = 0
PAGE_FILE = 1
PAGE_OFFSET = 2
PAGE_TOOL = 3
PAGE_NGCGUI = 4
# message constants
DEFAULT = 0
WARNING = 1
CRITICAL = 2
DEBUG = 3
SUCCESS = 4
ERROR = 5
VERSION ='1.4'
class HandlerClass:
def __init__(self, halcomp, widgets, paths):
self.p = paths
self.h = halcomp
self.w = widgets
self.gcodes = GCodes(widgets)
# This validator precludes using comma as a decimal
self.valid = QtGui.QRegularExpressionValidator(QtCore.QRegularExpression('-?[0-9]{0,6}[.][0-9]{0,3}'))
self.KEYBIND = KEYBIND
KEYBIND.add_call('Key_F11','on_keycall_F11')
KEYBIND.add_call('Key_F12','on_keycall_F12')
KEYBIND.add_call('Key_Pause', 'on_keycall_pause')
KEYBIND.add_call('Key_Any', 'on_keycall_pause')
KEYBIND.add_call('Key_Period','on_keycall_jograte',1)
KEYBIND.add_call('Key_Comma','on_keycall_jograte',0)
KEYBIND.add_call('Key_Greater','on_keycall_angular_jograte',1)
KEYBIND.add_call('Key_Less','on_keycall_angular_jograte',0)
# some global variables
self.factor = 1.0
self._dialog_message = None
self._spindle_wait = False
self.probe = None
self.default_setup = os.path.join(PATH.CONFIGPATH, "default_setup.html")
self.docs = os.path.join(PATH.SCREENDIR, PATH.BASEPATH,'docs/getting_started.html')
self.start_line = 0
self.run_time = 0
self.time_tenths = 0
self._last_count = 0
self.timer_on = False
self.home_all = False
self.min_spindle_rpm = INFO.MIN_SPINDLE_SPEED
self.max_spindle_rpm = INFO.MAX_SPINDLE_SPEED
self.spindle_lift_pins_present = False
self.system_list = ["G54","G55","G56","G57","G58","G59","G59.1","G59.2","G59.3"]
self.slow_jog_factor = 10
self.reload_tool = 0
self.last_loaded_program = ""
self.first_turnon = True
self._maintab_cycle = 1
self._lastSelectButton = None
self.MPGFocusWidget = None
self.CycleFocusWidget = None
self.auto_mode_switch = False
self.lineedit_list = ["work_height", "touch_height", "sensor_height", "laser_x", "laser_y",
"sensor_x", "sensor_y", "camera_x", "camera_y",
"search_vel", "probe_vel", "max_probe", "eoffset_count"]
self.onoff_list = ["frame_program", "frame_tool", "frame_dro", "frame_override", "frame_status"]
self.auto_list = ["chk_eoffsets", "cmb_gcode_history","lineEdit_eoffset_count"]
self.axis_list = ["axis_select_", "dro_axis_", "action_zero_",
"dro_button_stack_", "plus_jogbutton_", "minus_jogbutton_"]
self.button_response_list = ["btn_start", "btn_home_all", "btn_home_0", "btn_home_1",
"btn_home_2", "btn_home_3","btn_home_4", "btn_reload_file", "macrobutton0", "macrobutton1",
"macrobutton2", "macrobutton3", "macrobutton4", "macrobutton5", "macrobutton6",
"macrobutton7", "macrobutton8", "macrobutton9"]
self.statusbar_reset_time = 10000 # ten seconds
STATUS.connect('general', self.dialog_return)
STATUS.connect('dialog-request', self.dialog_request)
STATUS.connect('state-on', lambda w: self.enable_onoff(True))
STATUS.connect('state-off', lambda w: self.enable_onoff(False))
STATUS.connect('mode-manual', lambda w: self.enable_auto(True))
STATUS.connect('mode-mdi', lambda w: self.enable_auto(True))
STATUS.connect('mode-auto', lambda w: self.enable_auto(False))
STATUS.connect('interp-run', lambda w: self.set_button_response_state(True))
STATUS.connect('interp-idle', lambda w: self.set_button_response_state(False))
STATUS.connect('gcode-line-selected', lambda w, line: self.set_start_line(line))
STATUS.connect('graphics-line-selected', lambda w, line: self.set_start_line(line))
STATUS.connect('hard-limits-tripped', self.hard_limit_tripped)
STATUS.connect('program-pause-changed', lambda w, state: self.update_pause_button(state))
STATUS.connect('actual-spindle-speed-changed', lambda w, speed: self.update_rpm(speed))
STATUS.connect('user-system-changed', lambda w, data: self.user_system_changed(data))
STATUS.connect('metric-mode-changed', lambda w, mode: self.metric_mode_changed(mode))
STATUS.connect('file-loaded', self.file_loaded)
STATUS.connect('all-homed', self.all_homed)
STATUS.connect('not-all-homed', self.not_all_homed)
STATUS.connect('periodic', lambda w: self.periodic_update())
STATUS.connect('command-stopped', lambda w: self.stop_timer())
STATUS.connect('progress', lambda w,p,t: self.updateProgress(p,t))
STATUS.connect('override-limits-changed', lambda w, state, data: self._check_override_limits(state, data))
STATUS.connect('graphics-gcode-properties', lambda w, d: self.update_gcode_properties(d))
STATUS.connect('status-message', lambda w, d, o: self.add_external_status(d,o))
STATUS.connect('runstop-line-changed', lambda w, l :self.lastRunLine(l))
STATUS.connect('cycle-start-request', lambda w, state :self.btn_start_clicked(state))
STATUS.connect('cycle-pause-request', lambda w, state: self.btn_pause_clicked(state))
STATUS.connect('macro-call-request', lambda w, name: self.request_macro_call(name))
STATUS.connect('ok-request', lambda w, state: self.dialog_ext_control(w,1,1))
STATUS.connect('cancel-request', lambda w, state: self.dialog_ext_control(w,1,0))
self.swoopPath = os.path.join(paths.IMAGEDIR,'lcnc_swoop.png')
self.swoopURL = QtCore.QUrl.fromLocalFile(self.swoopPath)
txt1 = _translate("HandlerClass","Setup Tab")
txt2 = _translate("HandlerClass","If you select a file with .html as a file ending, it will be shown here.")
txt3 = _translate("HandlerClass","Documents online")
txt4 = _translate("HandlerClass","QtDragon online")
txt5 = _translate("HandlerClass","Local files")
self.html = """<html>
<head>
<title>Test page for the download:// scheme</title>
</head>
<body>
<h1>%s</h1>
<p>%s.</p>
<li><a href="http://linuxcnc.org/docs/devel/html/">%s</a></li>
<li><a href="http://linuxcnc.org/docs/2.9/html/gui/qtdragon.html">%s</a></li>
<li><a href="file://%s">%s</a></li>
<img src="file://%s" alt="lcnc_swoop" />
<hr />
</body>
</html>
"""%( txt1, txt2, txt3, txt4, os.path.expanduser('~/linuxcnc'), txt5,
self.swoopPath)
def class_patch__(self):
# override file manager load button
self.old_fman = FM.load
FM.load = self.load_code
# override NGCGui path check
NgcGui.check_linuxcnc_paths_fail = self.check_linuxcnc_paths_fail_override
# override action class macro run function
ACTION.RUN_MACRO = self.run_macro
def initialized__(self):
self.init_pins()
self.init_preferences()
self.init_widgets()
self.init_probe()
self.init_utils()
self.w.stackedWidget_log.setCurrentIndex(0)
self.w.stackedWidget.setCurrentIndex(0)
self.w.stackedWidget_dro.setCurrentIndex(0)
self.w.btn_spindle_pause.setEnabled(False)
self.w.page_buttonGroup.buttonClicked.connect(self.main_tab_changed)
self.w.selectButtonGroup.buttonClicked.connect(self.MPG_select_changed)
self.w.filemanager_usb.showMediaDir(quiet = True)
# hide or initiate 4th/5th AXIS dro/jog
flag = False
for num in range(0,5):
if num < len(INFO.AVAILABLE_AXES):
temp = INFO.AVAILABLE_AXES[num]
if temp in ('A','B','C'):
flag = True
self.initiate_axis_dro(num,temp)
else:
try:
for i in self.axis_list:
self.w[i+str(num)].hide()
except:
pass
# angular increment controls
if flag:
self.w.lbl_increments_linear.setText(_translate('HandlerClass',"INCREMENTS"))
else:
self.w.widget_jog_angular.hide()
self.w.widget_increments_angular.hide()
# set validators for lineEdit widgets
for val in self.lineedit_list:
self.w['lineEdit_' + val].setValidator(self.valid)
self.w.lineEdit_eoffset_count.setValidator(QtGui.QIntValidator(0,100))
# check for default setup html file
try:
# web view widget for SETUP page
if self.w.webwidget:
self.toolBar = QtWidgets.QToolBar(self.w)
self.w.tabWidget_setup.setCornerWidget(self.toolBar)
self.zoomBtn = QtWidgets.QPushButton(self.w)
self.zoomBtn.setEnabled(True)
self.zoomBtn.setMinimumSize(64, 40)
self.zoomBtn.setIconSize(QtCore.QSize(38, 38))
self.zoomBtn.setIcon(QtGui.QIcon(QtGui.QPixmap(':/buttons/images/zoom.png')))
self.zoomBtn.clicked.connect(self.zoomWeb)
self.toolBar.addWidget(self.zoomBtn)
self.homeBtn = QtWidgets.QPushButton(self.w)
self.homeBtn.setEnabled(True)
self.homeBtn.setMinimumSize(64, 40)
self.homeBtn.setIconSize(QtCore.QSize(38, 38))
self.homeBtn.setIcon(QtGui.QIcon(':/qt-project.org/styles/commonstyle/images/up-32.png'))
self.homeBtn.clicked.connect(self.homeWeb)
self.toolBar.addWidget(self.homeBtn)
self.backBtn = QtWidgets.QPushButton(self.w)
self.backBtn.setEnabled(True)
self.backBtn.setMinimumSize(64, 40)
self.backBtn.setIconSize(QtCore.QSize(38, 38))
self.backBtn.setIcon(QtGui.QIcon(':/qt-project.org/styles/commonstyle/images/left-32.png'))
self.backBtn.clicked.connect(self.back)
self.toolBar.addWidget(self.backBtn)
self.forBtn = QtWidgets.QPushButton(self.w)
self.forBtn.setEnabled(True)
self.forBtn.setMinimumSize(64, 40)
self.forBtn.setIconSize(QtCore.QSize(38, 38))
self.forBtn.setIcon(QtGui.QIcon(':/qt-project.org/styles/commonstyle/images/right-32.png'))
self.forBtn.clicked.connect(self.forward)
self.toolBar.addWidget(self.forBtn)
self.writeBtn = QtWidgets.QPushButton('SetUp\n Writer',self.w)
self.writeBtn.setMinimumSize(64, 40)
self.writeBtn.setEnabled(True)
self.writeBtn.clicked.connect(self.writer)
self.toolBar.addWidget(self.writeBtn)
if os.path.exists(self.default_setup):
self.w.webwidget.load(QtCore.QUrl.fromLocalFile(self.default_setup))
else:
self.w.webwidget.setHtml(self.html, self.swoopURL)
self.w.webwidget.page().urlChanged.connect(self.onLoadFinished)
except Exception as e:
text = _translate("HandlerClass","No default setup file found -")
print(f"{text} {e}")
# PDF setup page
self.PDFView = PDFViewer.PDFView()
self.w.layout_PDF.addWidget(self.PDFView)
self.PDFView.loadSample('setup_tab')
self.configureMacroButtons()
self.log_version()
STATUS.emit('update-machine-log', '', 'OFF')
# preset MPG focus object
self.MPGFocusWidget = self.w.gcodegraphics
self.MPGFocusWidgetBorder= 'stackedWidget_mainTabPage1'
def init_utils(self):
from qtvcp.lib.gcode_utility.facing import Facing
self.facing = Facing()
self.w.layout_facing.addWidget(self.facing)
try:
from qtvcp.lib.gcode_utility.hole_enlarge import Hole_Enlarge
self.hole_enlarge = Hole_Enlarge()
ACTION.ADD_WIDGET_TO_TAB(self.w.tabWidget_utilities,self.hole_enlarge, 'Hole Enlarge')
except Exception as e:
LOG.info("Utility hole enlarge unavailable: {}".format(e))
from qtvcp.lib.gcode_utility.hole_circle import Hole_Circle
self.hole_circle = Hole_Circle()
self.w.layout_hole_circle.addWidget(self.hole_circle)
# load the NgcGui widget into the utilities tab
# then move (warp) the info tab from it to the left tab widget
self.ngcgui = NgcGui()
self.w.layout_ngcgui.addWidget(self.ngcgui)
self.ngcgui.warp_info_frame(self.w.ngcGuiLeftLayout)
#############################
# SPECIAL FUNCTIONS SECTION #
#############################
def init_pins(self):
# spindle control pins
pin = QHAL.newPin("spindle-amps", QHAL.HAL_FLOAT, QHAL.HAL_IN)
pin.pinValueChanged.connect(lambda p,v: self.spindle_pwr_changed(v))
pin = QHAL.newPin("spindle-volts", QHAL.HAL_FLOAT, QHAL.HAL_IN)
pin.pinValueChanged.connect(lambda p,v: self.spindle_pwr_changed(v))
pin = QHAL.newPin("spindle-fault-u32", QHAL.HAL_U32, QHAL.HAL_IN)
pin.pinValueChanged.connect(lambda p,v: self.spindle_fault_changed(v))
pin = QHAL.newPin("spindle-fault", QHAL.HAL_S32, QHAL.HAL_IN)
pin.pinValueChanged.connect(lambda p,v: self.spindle_fault_changed(v))
pin = QHAL.newPin("spindle-modbus-errors-u32", QHAL.HAL_U32, QHAL.HAL_IN)
pin.pinValueChanged.connect(lambda p,v: self.mb_errors_changed(v))
pin = QHAL.newPin("spindle-modbus-errors", QHAL.HAL_S32, QHAL.HAL_IN)
pin.pinValueChanged.connect(lambda p,v: self.mb_errors_changed(v))
pin = QHAL.newPin("spindle-modbus-connection", QHAL.HAL_BIT, QHAL.HAL_IN)
pin.pinValueChanged.connect(lambda p,v: self.mb_connection_changed(v))
QHAL.newPin("spindle-inhibit", QHAL.HAL_BIT, QHAL.HAL_OUT)
pin = QHAL.newPin("external-pause", QHAL.HAL_BIT, QHAL.HAL_IN)
pin.pinValueChanged.connect(lambda p,v: self.btn_pause_clicked(v))
# external offset control pins
QHAL.newPin("eoffset-enable", QHAL.HAL_BIT, QHAL.HAL_OUT)
QHAL.newPin("eoffset-clear", QHAL.HAL_BIT, QHAL.HAL_OUT)
self.h['eoffset-clear'] = True
QHAL.newPin("eoffset-spindle-count", QHAL.HAL_S32, QHAL.HAL_OUT)
pin = QHAL.newPin("eoffset-is-active", QHAL.HAL_BIT, QHAL.HAL_IN)
pin.pinValueChanged.connect(lambda p,v: self.external_offset_state_changed(v))
# total external offset
pin = QHAL.newPin("eoffset-value", QHAL.HAL_FLOAT, QHAL.HAL_IN)
# MPG scrolling pin
self.pin_mpg_in = QHAL.newPin('mpg-in',QHAL.HAL_S32, QHAL.HAL_IN)
self.pin_mpg_in.pinValueChanged.connect(lambda p,v: self.external_mpg(v))
# dialog answer pins
pin = QHAL.newPin("dialog-ok", QHAL.HAL_BIT, QHAL.HAL_IN)
pin.pinValueChanged.connect(lambda p,v: self.dialog_ext_control(p,v,1))
pin = QHAL.newPin("dialog-no", QHAL.HAL_BIT, QHAL.HAL_IN)
pin.pinValueChanged.connect(lambda p,v: self.dialog_ext_control(p,v,2))
pin = QHAL.newPin("dialog-cancel", QHAL.HAL_BIT, QHAL.HAL_IN)
pin.pinValueChanged.connect(lambda p,v: self.dialog_ext_control(p,v,0))
def init_preferences(self):
if not self.w.PREFS_:
txt = _translate("HandlerClass","No preference file found, enable preferences in screenoptions widget")
self.add_status(txt, CRITICAL)
return
self.last_loaded_program = self.w.PREFS_.getpref('last_loaded_file', None, str,'BOOK_KEEPING')
self.reload_tool = self.w.PREFS_.getpref('Tool to load', 0, int,'CUSTOM_FORM_ENTRIES')
self.w.lineEdit_laser_x.setText(str(self.w.PREFS_.getpref('Laser X', 100, float, 'CUSTOM_FORM_ENTRIES')))
self.w.lineEdit_laser_y.setText(str(self.w.PREFS_.getpref('Laser Y', -20, float, 'CUSTOM_FORM_ENTRIES')))
self.w.lineEdit_sensor_x.setText(str(self.w.PREFS_.getpref('Sensor X', 10, float, 'CUSTOM_FORM_ENTRIES')))
self.w.lineEdit_sensor_y.setText(str(self.w.PREFS_.getpref('Sensor Y', 10, float, 'CUSTOM_FORM_ENTRIES')))
self.w.lineEdit_camera_x.setText(str(self.w.PREFS_.getpref('Camera X', 10, float, 'CUSTOM_FORM_ENTRIES')))
self.w.lineEdit_camera_y.setText(str(self.w.PREFS_.getpref('Camera Y', 10, float, 'CUSTOM_FORM_ENTRIES')))
self.w.lineEdit_work_height.setText(str(self.w.PREFS_.getpref('Work Height', 20, float, 'CUSTOM_FORM_ENTRIES')))
self.w.lineEdit_touch_height.setText(str(self.w.PREFS_.getpref('Touch Height', 40, float, 'CUSTOM_FORM_ENTRIES')))
self.w.lineEdit_sensor_height.setText(str(self.w.PREFS_.getpref('Sensor Height', 40, float, 'CUSTOM_FORM_ENTRIES')))
self.w.lineEdit_search_vel.setText(str(self.w.PREFS_.getpref('Search Velocity', 40, float, 'CUSTOM_FORM_ENTRIES')))
self.w.lineEdit_probe_vel.setText(str(self.w.PREFS_.getpref('Probe Velocity', 10, float, 'CUSTOM_FORM_ENTRIES')))
self.w.lineEdit_max_probe.setText(str(self.w.PREFS_.getpref('Max Probe', 10, float, 'CUSTOM_FORM_ENTRIES')))
self.w.lineEdit_retract_distance.setText(str(self.w.PREFS_.getpref('Retract Distance', 10, float, 'CUSTOM_FORM_ENTRIES')))
self.w.lineEdit_z_safe_travel.setText(str(self.w.PREFS_.getpref('Z Safe Travel', 10, float, 'CUSTOM_FORM_ENTRIES')))
self.w.lineEdit_eoffset_count.setText(str(self.w.PREFS_.getpref('Eoffset count', 0, int, 'CUSTOM_FORM_ENTRIES')))
self.w.chk_eoffsets.setChecked(self.w.PREFS_.getpref('External offsets', False, bool, 'CUSTOM_FORM_ENTRIES'))
self.w.chk_reload_program.setChecked(self.w.PREFS_.getpref('Reload program', False, bool,'CUSTOM_FORM_ENTRIES'))
self.w.chk_reload_tool.setChecked(self.w.PREFS_.getpref('Reload tool', False, bool,'CUSTOM_FORM_ENTRIES'))
self.w.chk_use_keyboard.setChecked(self.w.PREFS_.getpref('Use keyboard', False, bool, 'CUSTOM_FORM_ENTRIES'))
self.w.chk_run_from_line.setChecked(self.w.PREFS_.getpref('Run from line', False, bool, 'CUSTOM_FORM_ENTRIES'))
self.w.chk_use_virtual.setChecked(self.w.PREFS_.getpref('Use virtual keyboard', False, bool, 'CUSTOM_FORM_ENTRIES'))
self.w.chk_use_tool_sensor.setChecked(self.w.PREFS_.getpref('Use tool sensor', False, bool, 'CUSTOM_FORM_ENTRIES'))
self.w.chk_use_camera.setChecked(self.w.PREFS_.getpref('Use camera', False, bool, 'CUSTOM_FORM_ENTRIES'))
self.w.chk_auto_mode_ext_macro.setChecked(self.w.PREFS_.getpref('Auto mode external macro', True, bool, 'CUSTOM_FORM_ENTRIES'))
self.w.chk_auto_mode_macro_buttons.setChecked(self.w.PREFS_.getpref('Auto mode macro buttons', True, bool, 'CUSTOM_FORM_ENTRIES'))
# make sure the button's property are current
self.chk_auto_mode_macro_changed(self.w.chk_auto_mode_macro_buttons.isChecked())
self.w.chk_alpha_mode.setChecked(self.w.PREFS_.getpref('Use alpha display mode', False, bool, 'CUSTOM_FORM_ENTRIES'))
self.w.chk_inhibit_selection.setChecked(self.w.PREFS_.getpref('Inhibit display mouse selection', True, bool, 'CUSTOM_FORM_ENTRIES'))
self.cam_xscale_changed(self.w.PREFS_.getpref('Camview xscale', 100, int, 'CUSTOM_FORM_ENTRIES'))
self.cam_yscale_changed(self.w.PREFS_.getpref('Camview yscale', 100, int, 'CUSTOM_FORM_ENTRIES'))
self.w.camview._camNum = self.w.PREFS_.getpref('Camview cam number', 0, int, 'CUSTOM_FORM_ENTRIES')
self.w.camview.setAPI(self.w.PREFS_.getpref('Camview cam api', 'ANY', str, 'CUSTOM_FORM_ENTRIES'))
self.w.camview.setResolution(self.w.PREFS_.getpref('Camview cam resolution', 'DEFAULT', str, 'CUSTOM_FORM_ENTRIES'))
self.auto_mode_switch = self.w.PREFS_.getpref('auto mode switching', False, bool, 'CUSTOM_FORM_ENTRIES')
def closing_cleanup__(self):
if not self.w.PREFS_: return
LOG.debug("Saving Preferences")
if self.last_loaded_program is not None:
self.w.PREFS_.putpref('last_loaded_directory', os.path.dirname(self.last_loaded_program), str, 'BOOK_KEEPING')
self.w.PREFS_.putpref('last_loaded_file', self.last_loaded_program, str, 'BOOK_KEEPING')
self.w.PREFS_.putpref('Tool to load', STATUS.get_current_tool(), int, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Laser X', self.w.lineEdit_laser_x.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Laser Y', self.w.lineEdit_laser_y.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Sensor X', self.w.lineEdit_sensor_x.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Sensor Y', self.w.lineEdit_sensor_y.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Camera X', self.w.lineEdit_camera_x.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Camera Y', self.w.lineEdit_camera_y.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Work Height', self.w.lineEdit_work_height.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Touch Height', self.w.lineEdit_touch_height.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Sensor Height', self.w.lineEdit_sensor_height.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Search Velocity', self.w.lineEdit_search_vel.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Probe Velocity', self.w.lineEdit_probe_vel.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Max Probe', self.w.lineEdit_max_probe.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Retract Distance', self.w.lineEdit_retract_distance.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Z Safe Travel', self.w.lineEdit_z_safe_travel.text().encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Eoffset count', self.w.lineEdit_eoffset_count.text().encode('utf-8'), int, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('External offsets', self.w.chk_eoffsets.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Reload program', self.w.chk_reload_program.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Reload tool', self.w.chk_reload_tool.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Use keyboard', self.w.chk_use_keyboard.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Run from line', self.w.chk_run_from_line.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Use virtual keyboard', self.w.chk_use_virtual.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Use tool sensor', self.w.chk_use_tool_sensor.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Use camera', self.w.chk_use_camera.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Auto mode external macro', self.w.chk_auto_mode_ext_macro.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Auto mode macro buttons', self.w.chk_auto_mode_macro_buttons.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Use alpha display mode', self.w.chk_alpha_mode.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Inhibit display mouse selection', self.w.chk_inhibit_selection.isChecked(), bool, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Camview xscale', self.cam_xscale_percent(), int, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Camview yscale', self.cam_yscale_percent(), int, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Camview cam number', self.w.camview._camNum, int, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Camview cam api', self.w.camview.getAPIName(self.w.camview._camNum), str, 'CUSTOM_FORM_ENTRIES')
self.w.PREFS_.putpref('Camview cam resolution', self.w.camview.resolution, str, 'CUSTOM_FORM_ENTRIES')
def init_widgets(self):
self.adjust_stacked_widgets(TAB_MAIN)
self.w.chk_override_limits.setChecked(False)
self.w.chk_override_limits.setEnabled(False)
self.w.lbl_maxv_percent.setText("100 %")
self.w.lbl_home_x.setText(INFO.get_error_safe_setting('JOINT_0', 'HOME',"50"))
self.w.lbl_home_y.setText(INFO.get_error_safe_setting('JOINT_1', 'HOME',"50"))
txt = _translate("HandlerClass","No File Loaded")
self.w.cmb_gcode_history.addItem(txt)
self.w.cmb_gcode_history.wheelEvent = lambda event: None
self.w.jogincrements_linear.wheelEvent = lambda event: None
self.w.jogincrements_angular.wheelEvent = lambda event: None
self.w.gcode_editor.hide()
self.w.filemanager.list.setAlternatingRowColors(False)
self.w.filemanager_usb.list.setAlternatingRowColors(False)
self.w.filemanager_usb.showList()
if not INFO.MACHINE_IS_METRIC:
self.w.lbl_tool_sensor_B2W.setText('INCH')
self.w.lbl_tool_sensor_B2S.setText('INCH')
self.w.lbl_touchheight_units.setText('INCH')
self.w.lbl_max_probe_units.setText('INCH')
self.w.lbl_search_vel_units.setText('INCH/<sup> MIN</sup>')
self.w.lbl_probe_vel_units.setText('INCH/<sup> MIN</sup>')
self.w.lbl_z_ext_offset.setText('INCH')
self.w.lbl_tool_sensor_loc.setText('INCH')
self.w.lbl_laser_offset.setText('INCH')
self.w.lbl_camera_offset.setText('INCH')
self.w.lbl_touchheight_units.setText('INCH')
self.w.lbl_retract_dist_units.setText('INCH')
self.w.lbl_z_safe_travel_units.setText('INCH')
#set up gcode list
self.gcodes.setup_list()
# hide user tab button if no user tabs
if self.w.stackedWidget_mainTab.count() == 11:
self.w.btn_user.hide()
# see if a popup window panels is required
self.Btn = None
if not INFO.ZIPPED_TABS is None:
for name, loc, cmd in INFO.ZIPPED_TABS:
if loc =='WINDOW':
# add panel to a dialog window
self.w['popup'] = d = QtWidgets.QDialog(self.w)
temp = self.w[name.replace(' ','_')]
# set to apropriate size for panel
d.setMinimumSize(600,400)
d.setWindowTitle(name)
d.setWindowFlags(d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
d.finished.connect(self.onClosePopup)
d._lastgeometry = None
layout = QtWidgets.QGridLayout(d)
layout.setContentsMargins(0,0,0,0)
layout.addWidget(temp, 0, 0)
# add launch button to screen
self.btn = QtWidgets.QPushButton(self.w)
self.btn.setEnabled(True)
self.btn.setMinimumSize(64, 40)
self.btn.setIconSize(QtCore.QSize(38, 38))
self.btn.setIcon(QtGui.QIcon(':/qt-project.org/styles/commonstyle/images/up-32.png'))
self.btn.clicked.connect(self.togglePopup)
self.w.layout_buttonbar.insertWidget(len(self.w.layout_buttonbar) - 4, self.btn)
# only one allowed
break
def init_probe(self):
probe = INFO.get_error_safe_setting('PROBE', 'USE_PROBE', 'none').lower()
if probe == 'versaprobe':
LOG.info("Using Versa Probe")
from qtvcp.widgets.versa_probe import VersaProbe
self.probe = VersaProbe()
self.probe.setObjectName('versaprobe')
elif probe == 'basicprobe':
LOG.info("Using Basic Probe")
from qtvcp.widgets.basic_probe import BasicProbe
self.probe = BasicProbe()
self.probe.setObjectName('basicprobe')
else:
LOG.info("No valid probe widget specified")
self.w.btn_probe.hide()
return
self.w.probe_layout.addWidget(self.probe)
self.probe.hal_init()
def processed_focus_event__(self, receiver, event):
#print(receiver.parent(), receiver)
if isinstance(receiver.parent(), GCODE):
#print ('Gcode editor focus',receiver.parent().parent().objectName())
self.removeMPGFocusBorder()
name = receiver.parent().parent().objectName()
color = self.w.screen_options.property('user4Color').name()
self.colorMPGFocusBorder(name, receiver.parent(), color)
elif isinstance(receiver, GRAPHICS):
#print ('Gcode graphics focus',receiver,receiver.parent().objectName())
self.removeMPGFocusBorder()
name = receiver.parent().objectName()
color = self.w.screen_options.property('user4Color').name()
self.colorMPGFocusBorder(name, receiver, color)
elif isinstance(receiver.parent(), FM):
self.removeMPGFocusBorder()
#print ('File Manager focus',receiver.parent().parent().objectName())
name = receiver.parent().parent().objectName()
color = self.w.screen_options.property('user4Color').name()
self.colorMPGFocusBorder(name, receiver.parent(), color)
elif isinstance(receiver, SLIDER):
#print('Slider',receiver.objectName(),receiver.parent())
if not receiver in(self.w.slider_jog_linear,self.w.slider_jog_angular):
self.removeMPGFocusBorder()
name = receiver.objectName()
color = self.w.screen_options.property('user4Color').name()
self.colorMPGFocusBorder(name, receiver, color)
elif isinstance(receiver, TOOL_TABLE):
self.removeMPGFocusBorder()
#print ('Tool Offset focus',receiver.parent().objectName())
name = receiver.parent().objectName()
color = self.w.screen_options.property('user4Color').name()
self.colorMPGFocusBorder(name, receiver, color)
elif isinstance(receiver, OFFSET_VIEW):
self.removeMPGFocusBorder()
#print ('origon Offset focus',receiver.parent().objectName())
name = receiver.parent().objectName()
color = self.w.screen_options.property('user4Color').name()
self.colorMPGFocusBorder(name, receiver, color)
elif isinstance(receiver, MDI_WIDGET):
self.removeMPGFocusBorder()
self.removeCycleFocusBorder()
#print ('MDI line focus',receiver.parent().objectName())
name = receiver.parent().objectName()
color = self.w.screen_options.property('user5Color').name()
self.colorCycleFocusBorder(name, receiver, color)
elif isinstance(receiver, self._probeLibrary):
self.removeCycleFocusBorder()
#print ('Versa/Basic Probe focus',receiver.parent().objectName())
name = receiver.parent().objectName()
color = self.w.screen_options.property('user5Color').name()
self.colorCycleFocusBorder(name, receiver, color)
# show virtual keyboard
if not self.w.chk_use_virtual.isChecked() or STATUS.is_auto_mode(): return
if isinstance(receiver, QtWidgets.QLineEdit):
if not receiver.isReadOnly():
self.w.stackedWidget_dro.setCurrentIndex(1)
elif isinstance(receiver, QtWidgets.QTableView):
self.w.stackedWidget_dro.setCurrentIndex(1)
def removeMPGFocusBorder(self):
try:
self.MPGFocusWidget.setStyleSheet( '')
name = self.MPGFocusWidgetBorder
self.w[name].setStyleSheet('')
except:
pass
def removeCycleFocusBorder(self):
return
try:
self.CycleFocusWidget.setStyleSheet( '')
name = self.CycleFocusWidgetBorder
self.w[name].setStyleSheet('')
except:
pass
def recolorMPGFocusBorder(self):
try:
colorName = self.w.screen_options.property('user4Color').name()
name = self.MPGFocusWidgetBorder
self.w[name].setStyleSheet('#%s {border: 3px solid %s;}'%(name,colorName))
except:
pass
def colorMPGFocusBorder(self, name, receiver, colorName):
if self.w.btn_mpg_scroll.isChecked():
self.MPGFocusWidgetBorder = name
self.MPGFocusWidget = receiver
self.w[name].setStyleSheet('#%s {border: 3px solid %s;}'%(name,colorName))
def colorCycleFocusBorder(self, name, receiver, colorName):
return
self.CycleFocusWidgetBorder = name
self.CycleFocusWidget = receiver
self.w[name].setStyleSheet('#%s {border: 3px solid %s;}'%(name,colorName))
def processed_key_event__(self,receiver,event,is_pressed,key,code,shift,cntrl):
# when typing in MDI, we don't want keybinding to call functions
# so we catch and process the events directly.
# We do want ESC, F1 and F2 to call keybinding functions though
if code not in(QtCore.Qt.Key_Escape,QtCore.Qt.Key_F1 ,QtCore.Qt.Key_F2):
# QtCore.Qt.Key_F3,QtCore.Qt.Key_F4,QtCore.Qt.Key_F5):
# search for the top widget of whatever widget received the event
# then check if it's one we want the keypress events to go to
flag = False
receiver2 = receiver
while receiver2 is not None and not flag:
if isinstance(receiver2, QtWidgets.QDialog):
flag = True
break
if isinstance(receiver2, QtWidgets.QLineEdit):
flag = True
break
if isinstance(receiver2, MDI_WIDGET):
flag = True
break
if isinstance(receiver2, GCODE):
flag = True
break
if isinstance(receiver2, TOOL_TABLE):
flag = True
break
if isinstance(receiver2, OFFSET_VIEW):
flag = True
break
if isinstance(receiver2, writer.Main):
flag = True
break
receiver2 = receiver2.parent()
if flag:
if isinstance(receiver2, GCODE):
# if in manual or in readonly mode do our keybindings - otherwise
# send events to gcode widget
if STATUS.is_man_mode() == False or not receiver2.isReadOnly():
if is_pressed:
receiver.keyPressEvent(event)
event.accept()
return True
elif is_pressed:
receiver.keyPressEvent(event)
event.accept()
return True
else:
event.accept()
return True
if event.isAutoRepeat():return True
# ok if we got here then try keybindings function calls
# KEYBINDING will call functions from handler file as
# registered by KEYBIND.add_call(KEY,FUNCTION) above
return KEYBIND.manage_function_calls(self,event,is_pressed,key,shift,cntrl)
def before_loop__(self):
# Should be initialized and ready to log good info
STATUS.emit('update-machine-log', '', 'ON')
# no spindle lift without pins connected
self.spindle_lift_pins_present = True
for i in ('qtdragon.eoffset-is-active','qtdragon.spindle-inhibit','qtdragon.eoffset-clear',
'qtdragon.eoffset-spindle-count','qtdragon.eoffset-value'):
# no driving pin cennected?
if not self.h.hal.pin_has_writer(i):
self.spindle_lift_pins_present = False
#self.add_status('{} pin not connected in a HAL FILE'.format(i))
LOG.warning("{} {}".format(_translate("HandlerClass","not connected"), i))
# halui pins could cause resume without the spindle running
# and would not honour spindle lift. Should use qtdragon pin provided
for i in ('halui.program.resume','halui.program.pause'):
if self.h.hal.pin_has_writer(i):
self.spindle_lift_pins_present = False
LOG.warning(_translate("HandlerClass","HALUI pause/resume pin(s) connected - spindle lift not available"))
txt1 = _translate("HandlerClass","Warning:")
txt2 = _translate("HandlerClass", "pin connected in a HAL file, spindle lift disabled")
self.add_status(f"{txt2} {i} {txt2}", WARNING)
break
#########################
# CALLBACKS FROM STATUS #
#########################
def spindle_pwr_changed(self, data):
# this calculation assumes the voltage is line to neutral
# and that the synchronous motor spindle has a power factor of 0.9
power = self.h['spindle-volts'] * self.h['spindle-amps'] * 0.9 # Watts = V x I x PF
amps = "{:1.1f}".format(self.h['spindle-amps'])
pwr = "{:1.1f}".format(power)
self.w.lbl_spindle_amps.setText(amps)
self.w.lbl_spindle_power.setText(pwr)
def spindle_fault_changed(self, data):
fault = hex(data)
self.w.lbl_spindle_fault.setText(fault)
def mb_errors_changed(self, data):
self.w.lbl_mb_errors.setText(str(data))
def mb_connection_changed(self, data):
if data:
self.w.lbl_mb_errors.setStyleSheet('')
else:
self.w.lbl_mb_errors.setStyleSheet('background-color:{};'.format(
self.w.screen_options.property('user5Color').name()))
def dialog_return(self, w, message):
rtn = message.get('RETURN')
name = message.get('NAME')
plate_code = bool(message.get('ID') == '_touchplate_')
sensor_code = bool(message.get('ID') == '_toolsensor_')
wait_code = bool(message.get('ID') == '_wait_resume_')
lower_code = bool(message.get('ID') == '_wait_to_lower_')
unhome_code = bool(message.get('ID') == '_unhome_')
overwrite = bool(message.get('ID') == '_overwrite_')
if plate_code and name == 'MESSAGE' and rtn is True:
self.touchoff('touchplate')
elif sensor_code and name == 'MESSAGE' and rtn is True:
self.touchoff('sensor')
elif wait_code and name == 'MESSAGE':
self.lowerSpindle()
elif unhome_code and name == 'MESSAGE' and rtn is True:
ACTION.SET_MACHINE_UNHOMED(-1)
elif overwrite and name == 'MESSAGE':
if rtn is True:
self.do_file_copy()
else:
self.add_status(_translate("HandlerClass","File not copied"), CRITICAL)
elif lower_code and name == 'MESSAGE':
self.h['eoffset-spindle-count'] = 0
self.h['spindle-inhibit'] = False
self.add_status(_translate("HandlerClass",'Spindle lowered after machine stopped'))
self._dialog_message = None
def dialog_request(self,w, message):
self._dialog_message = message
def user_system_changed(self, data):
sys = self.system_list[int(data) - 1]
self.w.offset_table.selectRow(int(data) + 3)
self.w.systemtoolbutton.setText(sys)
def metric_mode_changed(self, mode):
rate = (float(self.w.slider_rapid_ovr.value()) / 100)
if mode is False:
self.w.lbl_jog_linear.setText('INCH/<sup>MIN</sup>')
self.factor = INFO.convert_machine_to_imperial(INFO.MAX_TRAJ_VELOCITY)
else:
self.w.lbl_jog_linear.setText('MM/<sup>MIN</sup>')
self.factor = INFO.convert_machine_to_metric(INFO.MAX_TRAJ_VELOCITY)
self.w.lbl_max_rapid.setText("{:4.0f}".format(rate * self.factor))
def file_loaded(self, obj, filename):
if os.path.basename(filename).count('.') > 1:
if INFO.INI.find("DISPLAY", "NO_MULTIPLE_DOT_FILENAME"):
self.last_loaded_program = ""
return
if filename is not None:
txt = _translate("HandlerClass","Loaded file")
self.add_status(f"{txt} {filename}")
self.w.progressBar.setValue(0)
self.last_loaded_program = filename
self.w.lbl_runtime.setText("00:00:00")
else:
self.add_status(_translate("HandlerClass","Filename not valid"), CRITICAL)
def updateProgress(self, p,text):
if p <0:
self.w.progressBar.setValue(0)
self.w.progressBar.setFormat(_translate("HandlerClass",'PROGRESS'))
else:
self.w.progressBar.setValue(p)
self.w.progressBar.setFormat('{}: {}%'.format(text, p))
def percent_loaded_changed(self, fraction):
if fraction <0:
self.w.progressBar.setValue(0)
self.w.progressBar.setFormat(_translate("HandlerClass",'PROGRESS'))
else:
self.w.progressBar.setValue(fraction)
txt = _translate("HandlerClass","LOADING")
self.w.progressBar.setFormat('{}: {}%'.format(txt, fraction))
def percent_done_changed(self, fraction):
self.w.progressBar.setValue(fraction)
if fraction <0:
self.w.progressBar.setValue(0)
self.w.progressBar.setFormat(_translate("HandlerClass",'PROGRESS'))
else:
txt = _translate("HandlerClass","COMPLETE")
self.w.progressBar.setFormat('{}: {}%'.format(txt, fraction))
def all_homed(self, obj):
self.home_all = True
self.w.btn_home_all.setText(_translate("HandlerClass","ALL\nHOMED"))
self.add_status(_translate("HandlerClass","All homed"))
if self.first_turnon is True:
self.first_turnon = False
if self.w.chk_reload_tool.isChecked():
command = "M61 Q{} G43".format(self.reload_tool)
ACTION.CALL_MDI(command, mode_return=True)
if self.last_loaded_program is not None and self.w.chk_reload_program.isChecked():
if os.path.isfile(self.last_loaded_program):
self.w.cmb_gcode_history.addItem(self.last_loaded_program)
self.w.cmb_gcode_history.setCurrentIndex(self.w.cmb_gcode_history.count() - 1)
self.w.cmb_gcode_history.setToolTip(self.last_loaded_program)
ACTION.OPEN_PROGRAM(self.last_loaded_program)
ACTION.SET_MANUAL_MODE()
self.w.manual_mode_button.setChecked(True)
def not_all_homed(self, obj, list):
self.home_all = False
self.w.btn_home_all.setText(_translate("HandlerClass","HOME\nALL"))
def hard_limit_tripped(self, obj, tripped, list_of_tripped):
if tripped:
self.add_status(_translate("HandlerClass","Hard limits tripped"), CRITICAL)
self.w.chk_override_limits.setEnabled(tripped)
else:
self.w.chk_override_limits.setChecked(False)
self.add_status(_translate("HandlerClass","Hard Limits Clear"),)
# keep check button in sync of external changes
def _check_override_limits(self,state,data):
if 0 in data:
self.w.chk_override_limits.setChecked(False)
else:
self.w.chk_override_limits.setChecked(True)
def set_button_response_state(self, state):
for i in (self.button_response_list):
self.w[i].setEnabled(not state)
def add_external_status(self, message, option):
level = option.get('LEVEL', STATUS.DEFAULT)
log = option.get("LOG", True)
title = message.get('TITLE', '')
mess = message.get('SHORTTEXT', '')
logtext = message.get('DETAILS', '')
self.add_status(mess, level, noLog=True)
if log:
STATUS.emit('update-machine-log', "{}\n{}".format(title, logtext), 'TIME')
# Log the last run line (in auto mode) if stopped
def lastRunLine(self, line):
if line >0:
self.add_status(_translate("HandlerClass",'last running line before stoppage: {}'.format(line)))
# called from hal_glib to run macros from external event
def request_macro_call(self, data):
if not self.w.chk_auto_mode_ext_macro.isChecked() and not STATUS.is_mdi_mode():
self.add_status(_translate("HandlerClass",'Machine must be in MDI mode to run macros'), WARNING)
return
if 'ini-macro-cmd' in data:
data = data.replace('ini-macro-cmd-','')
try:
temp = INFO.MACRO_COMMAND_DICT.get(data).get('cmd')
self.run_macro(data=temp)
return
except:
self.add_status(_translate(f"HandlerClass",'External requested INI macro data not recognized:{data}'), CRITICAL)
elif 'ini-mdi-cmd' in data:
for b in range(0,10):
button = self.w['macrobutton{}'.format(b)]
# prefer named INI MDI commands
key = button.property('ini_mdi_key')
code = INFO.get_ini_mdi_command(key)
#print(data,key,code)
if key == '' or code is None:
# fallback to legacy nth line
key = button.property('ini_mdi_number')
code = INFO.get_ini_mdi_command(key)
if code is None:
continue
if str(key) in data:
#print('match',button.objectName())
text = button.text().replace('\n',' ')
self.add_status(_translate("HandlerClass",'Running macro: {} {}'.format(key, text)))
try:
button.click()
except Exception as e:
self.add_status(_translate("HandlerClass",'Error running macro: {} {}\n{}'.format(key, text, e)))
break
else:
self.add_status(_translate(f"HandlerClass",'External requested INI mdi {data} does not match button name/number'), CRITICAL)
else:
self.add_status(_translate(f"HandlerClass",'External requested INI macro data not recognized:{data}'), CRITICAL)
#######################
# CALLBACKS FROM FORM #
#######################
# main button bar
def main_tab_changed(self, btn):
index = btn.property("index")
if index == TAB_USER:
pass
# if you select the tab showing, force the DRO to show
elif index == self.w.stackedWidget_mainTab.currentIndex():
if index == TAB_MAIN and STATUS.is_auto_mode():
self._maintab_cycle +=1
if index is None: return
# adjust the stack widgets depending on modes
self.adjust_stacked_widgets(index)
def hideVirtualKeyboard(self):
self.w.stackedWidget_dro.setCurrentIndex(0)
# gcode frame
def cmb_gcode_history_clicked(self):
if self.w.cmb_gcode_history.currentIndex() == 0: return
filename = self.w.cmb_gcode_history.currentText()
if filename == self.last_loaded_program:
self.add_status(_translate("HandlerClass","Selected program is already loaded"))
else:
ACTION.OPEN_PROGRAM(filename)
# program frame
def btn_start_clicked(self, obj):
if not STATUS.is_all_homed():
self.add_status(_translate("HandlerClass","Machine must be is homed"), CRITICAL)
return
if not os.path.exists(self.last_loaded_program):
self.add_status(_translate("HandlerClass","No program to execute"), WARNING)
return
if not STATUS.is_auto_mode() and not self.auto_mode_switch:
self.add_status(_translate("HandlerClass","Must be in AUTO mode to run a program"), WARNING)
return
if self.w.stackedWidget_mainTab.currentIndex() != 0:
self.add_status(_translate("HandlerClass","Switch view mode to MAIN"), WARNING)
return
if STATUS.is_auto_running():
self.add_status(_translate("HandlerClass","Program is already running"), WARNING)
return
if self.start_line <= 1:
ACTION.RUN(self.start_line)
else:
# instantiate run from line preset dialog
info = '<b>{}: {} <\b>'.format(_translate("HandlerClass","Running From Line"),self.start_line)
mess = {'NAME':'RUNFROMLINE', 'TITLE':'Preset Dialog', 'ID':'_RUNFROMLINE', 'MESSAGE':info, 'LINE':self.start_line}
ACTION.CALL_DIALOG(mess)
self.start_timer()
self.add_status("{} {}".format(_translate("HandlerClass","Started program from line"),self.start_line))
def btn_reload_file_clicked(self):
if self.last_loaded_program:
self.w.progressBar.setValue(0)
self.add_status("{} {}".format(_translate("HandlerClass","Loaded program file"), self.last_loaded_program))
ACTION.OPEN_PROGRAM(self.last_loaded_program)
# DRO frame
def btn_home_all_clicked(self, obj):
if self.home_all is False:
ACTION.SET_MACHINE_HOMING(-1)
else:
# instantiate dialog box
info = _translate("HandlerClass","Unhome All Axes?")
mess = {'NAME':'MESSAGE', 'ID':'_unhome_', 'MESSAGE':'UNHOME ALL', 'MORE':info, 'TYPE':'OKCANCEL'}
ACTION.CALL_DIALOG(mess)
def btn_home_clicked(self):
axisnum = self.w.sender().property('joint')
joint = INFO.get_jnum_from_axisnum(axisnum)
if STATUS.is_joint_homed(joint) == True:
ACTION.SET_MACHINE_UNHOMED(joint)
else:
ACTION.SET_MACHINE_HOMING(joint)
# override frame
def slow_button_clicked(self, state):
slider = self.w.sender().property('slider')
current = self.w[slider].value()
max = self.w[slider].maximum()
if state:
self.w.sender().setText(_translate("HandlerClass","SLOW"))
self.w[slider].setMaximum(max / self.slow_jog_factor)
self.w[slider].setValue(current / self.slow_jog_factor)
self.w[slider].setPageStep(10)
else:
self.w.sender().setText(_translate("HandlerClass","FAST"))
self.w[slider].setMaximum(max * self.slow_jog_factor)
self.w[slider].setValue(current * self.slow_jog_factor)
self.w[slider].setPageStep(100)
def slider_maxv_changed(self, value):
maxpc = (float(value) / INFO.MAX_LINEAR_JOG_VEL) * 100
self.w.lbl_maxv_percent.setText("{:3.0f} %".format(maxpc))
def slider_rapid_changed(self, value):
rapid = (float(value) / 100) * self.factor
self.w.lbl_max_rapid.setText("{:4.0f}".format(rapid))
def btn_maxv_100_clicked(self):
self.w.slider_maxv_ovr.setValue(INFO.MAX_LINEAR_JOG_VEL)
def btn_maxv_50_clicked(self):
self.w.slider_maxv_ovr.setValue(INFO.MAX_LINEAR_JOG_VEL / 2)
# file tab
def btn_gcode_edit_clicked(self, state):
if not STATUS.is_on_and_idle():
return
if state:
self.w.filemanager.hide()
self.w.widget_file_copy.hide()
self.w.gcode_editor.show()
self.w.gcode_editor.editMode()
else:
self.w.filemanager.show()
self.w.widget_file_copy.show()
self.w.gcode_editor.hide()
self.w.gcode_editor.readOnlyMode()
def btn_load_file_clicked(self):
fname = self.w.filemanager.getCurrentSelected()
if fname[1] is True:
self.load_code(fname[0])
def btn_copy_file_clicked(self):
if self.w.btn_gcode_edit.isChecked(): return
if self.w.sender() == self.w.btn_copy_right:
source = self.w.filemanager_usb.getCurrentSelected()
target = self.w.filemanager.getCurrentSelected()
elif self.w.sender() == self.w.btn_copy_left:
source = self.w.filemanager.getCurrentSelected()
target = self.w.filemanager_usb.getCurrentSelected()
else:
return
if source[1] is False:
self.add_status(_translate("HandlerClass","Specified source is not a file"), WARNING)
return
self.source_file = source[0]
if target[1] is True:
self.destination_file = os.path.join(os.path.dirname(target[0]), os.path.basename(source[0]))
else:
self.destination_file = os.path.join(target[0], os.path.basename(source[0]))
if os.path.isfile(self.destination_file):
info = "{} {}".format(_translate("HandlerClass","already exists in destination directory"), self.destination_file)
mess = {'NAME':'MESSAGE', 'ICON':'WARNING', 'ID':'_overwrite_', 'MESSAGE':'OVERWRITE FILE?', 'MORE':info, 'TYPE':'YESNO','NONBLOCKING':True}
ACTION.CALL_DIALOG(mess)
else:
self.do_file_copy()
# offsets tab
def btn_goto_sensor_clicked(self):
x = float(self.w.lineEdit_sensor_x.text())
y = float(self.w.lineEdit_sensor_y.text())
if STATUS.is_metric_mode():
x = INFO.convert_machine_to_metric(x)
y = INFO.convert_machine_to_metric(y)
else:
x = INFO.convert_machine_to_imperial(x)
y = INFO.convert_machine_to_imperial(y)
# call mdi in a background process so screen stay responsive.
cmd =["G90","G53 G0 Z0","G53 G0 X{:3.4f} Y{:3.4f}".format(x, y)]
ACTION.CALL_BACKGROUND_MDI(cmd, label='Goto Sensor Button', timeout=30)
def btn_ref_laser_clicked(self):
x = float(self.w.lineEdit_laser_x.text())
y = float(self.w.lineEdit_laser_y.text())
if STATUS.is_metric_mode():
x = INFO.convert_machine_to_metric(x)
y = INFO.convert_machine_to_metric(y)
else:
x = INFO.convert_machine_to_imperial(x)
y = INFO.convert_machine_to_imperial(y)
self.add_status(_translate("HandlerClass","Laser offsets set"))
command = "G10 L20 P0 X{:3.4f} Y{:3.4f}".format(x, y)
ACTION.CALL_MDI(command, mode_return=True)
def btn_ref_camera_clicked(self):
x = float(self.w.lineEdit_camera_x.text())
y = float(self.w.lineEdit_camera_y.text())
if STATUS.is_metric_mode():
x = INFO.convert_machine_to_metric(x)
y = INFO.convert_machine_to_metric(y)
else:
x = INFO.convert_machine_to_imperial(x)
y = INFO.convert_machine_to_imperial(y)
self.add_status(_translate("HandlerClass","Camera offsets set"))
command = "G10 L20 P0 X{:3.4f} Y{:3.4f}".format(x, y)
ACTION.CALL_MDI(command, mode_return=True)
# tool tab
def btn_m61_clicked(self):
checked = self.w.tooloffsetview.get_checked_list()
if len(checked) > 1:
self.add_status(_translate("HandlerClass","Select only 1 tool to load"), WARNING)
elif checked:
self.add_status("{} {}".format(_translate("HandlerClass","Loaded tool"), checked[0]))
ACTION.CALL_MDI("M61 Q{} G43".format(checked[0]), mode_return=True)
else:
self.add_status(_translate("HandlerClass","No tool selected"), CRITICAL)
def btn_touchoff_clicked(self):
if STATUS.get_current_tool() == 0:
self.add_status(_translate("HandlerClass","Cannot touchoff with no tool loaded"), CRITICAL)
return
if not STATUS.is_all_homed():
self.add_status(_translate("HandlerClass","Must be homed to perform tool touchoff"), CRITICAL)
return
# instantiate dialog box
sensor = self.w.sender().property('sensor')
unit = "mm" if INFO.MACHINE_IS_METRIC else "in"
info = "{} {} {} {}".format(
_translate("HandlerClass","Ensure tooltip is within"),
_translate("HandlerClass","of tool sensor and click OK"),
self.w.lineEdit_max_probe.text(),unit)
mess = {'NAME':'MESSAGE', 'ID':sensor, 'MESSAGE':'TOOL TOUCHOFF', 'MORE':info, 'TYPE':'OKCANCEL'}
ACTION.CALL_DIALOG(mess)
# status tab
def btn_clear_status_clicked(self):
STATUS.emit('update-machine-log', None, 'DELETE')
def btn_save_status_clicked(self):
if self.w.stackedWidget_log.currentIndex():
text = self.w.integrator_log.getLogText()
name = 'sysLog_'
else:
text = self.w.machinelog.getLogText()
name = 'mchnLog_'
filename = self.w.lbl_clock.text()
filename = name + filename.replace(' ','_') + '.txt'
self.add_status("{} {}".format(_translate("HandlerClass","Saving Log file to"), filename))
with open(filename, 'w') as f:
f.write(text)
def btn_dimensions_clicked(self, state):
self.w.gcodegraphics.show_extents_option = state
self.w.gcodegraphics.clear_live_plotter()
# camview tab
def cam_zoom_changed(self, value):
self.w.camview.scale = float(value) / 10
def cam_dia_changed(self, value):
self.w.camview.diameter = value
def cam_rot_changed(self, value):
self.w.camview.rotation = float(value) / 10
# scaling of the camera image for size aspect corrections
# set from preference file
def cam_xscale_changed(self, value):
self.w.camview.scaleX = float(value/100)
def cam_xscale_percent(self):
return self.w.camview.scaleX * 100
def cam_yscale_changed(self, value):
self.w.camview.scaleY = float(value/100)
def cam_yscale_percent(self):
return self.w.camview.scaleY * 100
# settings tab
def chk_override_limits_checked(self, state):
# only toggle override if it's not in sync with the button
if state and not STATUS.is_limits_override_set():
self.add_status(_translate("HandlerClass","Override limits set"), WARNING)
ACTION.TOGGLE_LIMITS_OVERRIDE()
elif not state and STATUS.is_limits_override_set():
error = ACTION.TOGGLE_LIMITS_OVERRIDE()
# if override can't be released set the check button to reflect this
if error == False:
self.w.chk_override_limits.blockSignals(True)
self.w.chk_override_limits.setChecked(True)
self.w.chk_override_limits.blockSignals(False)
else:
self.add_status(_translate("HandlerClass","Override limits cleared"))
def chk_run_from_line_checked(self, state):
self.w.btn_start.setText(_translate("HandlerClass","START\n1")) if state else self.w.btn_start.setText(_translate("HandlerClass","CYCLE\nSTART"))
def chk_alpha_mode_changed(self, state):
self.w.gcodegraphics.set_alpha_mode(state)
def chk_inhibit_selection_changed(self, state):
self.w.gcodegraphics.set_inhibit_selection(state)
def chk_use_camera_changed(self, state):
self.w.btn_ref_camera.setEnabled(state)
self.w.btn_camera.show() if state else self.w.btn_camera.hide()
def chk_auto_mode_macro_changed(self, state):
for b in range(0,10):
button = self.w['macrobutton{}'.format(b)]
button.setProperty('mdi_mode_check_action',not state)
def chk_auto_mode_external_macro_changed(self, state):
pass
def chk_use_sensor_changed(self, state):
self.w.btn_touch_sensor.setEnabled(state)
def chk_use_virtual_changed(self, state):
if not state:
self.w.stackedWidget_dro.setCurrentIndex(0)
# show ngcgui info tab (in the stackedWidget) if ngcgui utilites
# tab is selected
def tab_utilities_changed(self, num):
if 'ngc' in self.w.tabWidget_utilities.tabText(num).lower():
self.w.stackedWidget.setCurrentIndex(PAGE_NGCGUI)
else:
self.w.stackedWidget.setCurrentIndex(PAGE_GCODE)
def btn_about_clicked(self):
self.log_version()
info = ACTION.GET_ABOUT_INFO()
self.w.aboutDialog_.showdialog()
def btn_zoomin_clicked(self):
self.w.gcode_viewer.editor.zoomIn()
def btn_zoomout_clicked(self):
self.w.gcode_viewer.editor.zoomOut()
def btn_spindle_z_up_clicked(self):
fval = int(self.w.lineEdit_eoffset_count.text())
if INFO.MACHINE_IS_METRIC:
fval += 5
else:
fval += 1
self.w.lineEdit_eoffset_count.setText(str(fval))
if self.h['eoffset-clear'] != True:
self.h['eoffset-spindle-count'] = int(fval)
def btn_spindle_z_down_clicked(self):
fval = int(self.w.lineEdit_eoffset_count.text())
if INFO.MACHINE_IS_METRIC:
fval -= 5
else:
fval -= 1
if fval <0: fval = 0
self.w.lineEdit_eoffset_count.setText(str(fval))
if self.h['eoffset-clear'] != True:
self.h['eoffset-spindle-count'] = int(fval)
def btn_pause_clicked(self, data):
# pause request
if data:
ACTION.PAUSE_MACHINE()
self.w.btn_spindle_pause.setEnabled(False)
self.liftSpindle()
# no lowering required - resume
elif int(self.w.lineEdit_eoffset_count.text()) == 0 or \
not self.w.btn_spindle_pause.isChecked() or \
self.h['eoffset-clear']:
ACTION.RESUME()
# only enable the lift selection button, if pins are connected
if self.spindle_lift_pins_present:
self.w.btn_spindle_pause.setEnabled(True)
# lowering required before resuming
elif STATUS.is_auto_paused():
self.h['spindle-inhibit'] = False
self.add_status(_translate("HandlerClass",'Request Spindle Speed Wait'))
# If spindle at speed is connected to a pin, use it lower spindle
if self.h.hal.pin_has_writer('spindle.0.at-speed'):
self._spindle_wait = True
return
else:
# or wait for dialog to close before lowering spindle
# instantiate warning box
info = _translate("HandlerClass","Wait for spindle at speed signal before resuming")
mess = {'NAME':'MESSAGE', 'ICON':'WARNING', 'ID':'_wait_resume_',
'MESSAGE':'CAUTION', 'MORE':info, 'TYPE':'OK'}
ACTION.CALL_DIALOG(mess)
# from HAL pin 'eoffset-is-active'
# unpause machine if external offsets state is false
def external_offset_state_changed(self, data):
# only if running a program and only if machine in on
if not STATUS.is_auto_running() or not STATUS.machine_is_on():
return
# if GUI setting is at zero lift - ignore
if int(self.w.lineEdit_eoffset_count.text()) == 0:return
# only if pin is false (external offsets are off)
if not data:
# spindle better be un-inhibitted
if self.h['spindle-inhibit'] == True:
self.add_status(_translate("HandlerClass",'Spindle not running - keeping paused'),CRITICAL)
return
ACTION.RESUME()
# only enable the lift selection button if pins are connected
if self.spindle_lift_pins_present and self.w.chk_eoffsets.isChecked():
self.w.btn_spindle_pause.setEnabled(True)
else:
self.w.btn_spindle_pause.setEnabled(False)
self.w.btn_spindle_pause.setChecked(False)
# make step program button active again
self.w.action_step.setEnabled(True)
def liftSpindle(self):
# if we were waiting for restart dialog to return
# cancel it - we want to relift
mess = {'NAME':'MESSAGE', 'ID':'_wait_resume_', 'CLOSE':True}
ACTION.CALL_DIALOG(mess)
# cancel auto lowering when spindle at speed
self._spindle_wait = False
# only enable the lift selection button if pins are connected
if self.spindle_lift_pins_present and self.w.chk_eoffsets.isChecked():
pass
else:
if not self.w.chk_eoffsets.isChecked() and self.w.btn_spindle_pause.isChecked():
self.add_status(_translate("HandlerClass",'Eoffsets Setting Unchecked - Can Not Raise Spindle'),CRITICAL)
self.w.btn_spindle_pause.setEnabled(False)
self.w.btn_spindle_pause.setChecked(False)
return
# do we need to lift spindle?
if int(self.w.lineEdit_eoffset_count.text()) == 0: return
if not self.w.btn_spindle_pause.isChecked(): return
# can't step when lifted
self.w.action_step.setEnabled(False)
# set external offsets to lift spindle
self.h['eoffset-clear'] = False
self.h['eoffset-enable'] = True
fval = int(self.w.lineEdit_eoffset_count.text())
self.h['eoffset-spindle-count'] = int(fval)
# stop the spindle
self.h['spindle-inhibit'] = True
self.add_status("{} {}".format(_translate("HandlerClass","Spindle stopped and raising to"), fval))
def lowerSpindle(self):
# spindle better be un-inhibitted
if self.h['spindle-inhibit'] == True:
self.add_status(_translate("HandlerClass",'Spindle not running - keeping raised and paused'),CRITICAL)
return
self.h['eoffset-spindle-count'] = 0
self.h['eoffset-clear'] = True
self.add_status('Spindle lowering')
# from abort button
def btn_stop_clicked(self,data):
if not self.w.btn_spindle_pause.isChecked(): return
self.w.btn_spindle_pause.setChecked(False)
if not self.h['eoffset-spindle-count'] == 0:
# or wait for dialog to close before lowering spindle
# instantiate warning box
info = _translate("HandlerClass","Spindle lift still active. press OK to lower spindle")
mess = {'NAME':'MESSAGE', 'ICON':'WARNING', 'ID':'_wait_to_lower_',
'MESSAGE':'CAUTION', 'MORE':info, 'TYPE':'OK'}
ACTION.CALL_DIALOG(mess)
# make sure the pause button follows STATUS pause messages
# as well as physical button pushes
def update_pause_button(self,data):
self.w.action_pause._blockSignals(True)
self.w.action_pause.setChecked(data)
self.pause_timer(data)
# only enable the lift selection button if pins are connected
if self.spindle_lift_pins_present and self.w.chk_eoffsets.isChecked():
self.w.btn_spindle_pause.setEnabled(not data)
self.w.action_pause._blockSignals(False)
def btn_systemtool_toggled(self, state):
if state:
STATUS.emit('dro-reference-change-request', 1)
def MPG_select_changed(self, button):
#print(button)
# Auto exclusive doesn't allow unchecking all buttons
# We force it here
if button == self._lastSelectButton:
button.group().setExclusive(False)
button.setChecked(False)
button.group().setExclusive(True)
self._lastSelectButton = None
if button == self.w.btn_mpg_scroll:
self.removeMPGFocusBorder()
return
if button == self.w.btn_mpg_scroll:
if self.w.btn_mpg_scroll.isChecked():
self.recolorMPGFocusBorder()
else:
self.removeMPGFocusBorder()
#self.set_statusbar('MPG output Selected: {}'.format(cmd.toolTip()),DEFAULT,noLog=True)
self._lastSelectButton = button
# if a macro has been requested, do path checks and run it
def run_macro( self, data = None ):
o_codes = data.split()
command = str( "O<" + o_codes[0] + "> call" )
# confirm oword path exists
rtn = ACTION.check_macro_path(command)
if not rtn is True:
for i in rtn:
self.add_status(i, WARNING)
return
for code in o_codes[1:]:
# wait but don't block:
parameter, ok = self.w.calculatorDialog_.getValue(f"Enter a value for: {code}:")
if not ok:
self.add_status('Macro cancelled')
return
command = command + " [" + str(parameter) + "] "
# pop a dialog of the properties
msg = QtWidgets.QMessageBox()
msg.setIcon(QtWidgets.QMessageBox.Information)
msg.setText(_translate("HandlerClass",f"Run Macro Command: {command}"))
msg.setWindowTitle(_translate("HandlerClass","Confirm To Run Macro Command"))
msg.setStandardButtons(QtWidgets.QMessageBox.Ok|QtWidgets.QMessageBox.Cancel)
msg.show()
retval = msg.exec_()
if retval == QtWidgets.QMessageBox.Ok:
self.add_status(f'Run Macro Command:{command}')
ACTION.SET_GRAPHICS_VIEW('clear')
ACTION.CALL_MDI(command, mode_return=True)
return
self.add_status('Macro cancelled')
#####################
# GENERAL FUNCTIONS #
#####################
# file manager widget overridden function
def load_code(self, fname):
if fname is None: return
filename, file_extension = os.path.splitext(fname)
# loading ngc then HTML/PDF
if not file_extension in (".html", '.pdf'):
if not (INFO.program_extension_valid(fname)):
self.add_status("{} {}".format(_translate("HandlerClass","Unknown or invalid filename extension"),file_extension), CRITICAL)
return
self.w.cmb_gcode_history.addItem(fname)
self.w.cmb_gcode_history.setCurrentIndex(self.w.cmb_gcode_history.count() - 1)
self.w.cmb_gcode_history.setToolTip(fname)
ACTION.OPEN_PROGRAM(fname)
self.add_status("{} : {}".format(_translate("HandlerClass","Loaded program file"), fname))
self.adjust_stacked_widgets(TAB_MAIN)
self.w.filemanager.recordBookKeeping()
# adjust ending to check for related HTML setup files
fname = filename+'.html'
try:
if os.path.exists(fname):
self.w.webwidget.loadFile(fname)
self.add_status("{} : {}".format(_translate("HandlerClass","Loaded HTML file"), fname), CRITICAL)
else:
self.w.webwidget.setHtml(self.html, self.swoopURL)
except Exception as e:
self.add_status("{} {} : {}".format(_translate("HandlerClass","Error loading HTML file"), fname,e))
# look for PDF setup files
# load it with system program
fname = filename+'.pdf'
if os.path.exists(fname):
self.PDFView.loadView(fname)
self.add_status("{} : {}".format(_translate("HandlerClass","Loaded PDF file"), fname))
else:
self.PDFView.loadSample('setup_tab')
return
# loading HTML/PDF directly
if file_extension == ".html":
try:
self.w.webwidget.loadFile(fname)
self.add_status("{} : {}".format(_translate("HandlerClass","Loaded HTML file"),fname))
self.w.stackedWidget_mainTab.setCurrentIndex(TAB_SETUP)
self.w.stackedWidget.setCurrentIndex(0)
self.w.tabWidget_setup.setCurrentIndex(1)
self.w.btn_setup.setChecked(True)
self.w.jogging_frame.hide()
except Exception as e:
self.add_status("{} {} : {}".format(_translate("HandlerClass","Error loading HTML file"), fname,e))
else:
if os.path.exists(fname):
self.PDFView.loadView(fname)
self.add_status("{} : {}".format(_translate("HandlerClass","Loaded PDF file"), fname))
self.w.stackedWidget_mainTab.setCurrentIndex(TAB_SETUP)
self.w.stackedWidget.setCurrentIndex(0)
self.w.tabWidget_setup.setCurrentIndex(1)
self.w.btn_setup.setChecked(True)
self.w.jogging_frame.hide()
# NGCGui library overridden function
# adds an error message to status
def check_linuxcnc_paths_fail_override(self, fname):
self.add_status("{} {} {}".format(_translate("HandlerClass","NGCGUI Path"), _translate("HandlerClass","not in linuxcnc's SUBROUTINE_PATH INI entry"), fname), CRITICAL)
return ''
def update_gcode_properties(self, props ):
# substitute nice looking text:
property_names = {
'name': "Name:", 'size': "Size:",
' tools': "Tool order:", 'g0': "Rapid distance:",
'g1': "Feed distance:", 'g': "Total distance:",
'run': "Run time:",'machine_unit_sys':"Machine Unit System:",
'x': "X bounds:",'x_zero_rxy':'X @ Zero Rotation:',
'y': "Y bounds:",'y_zero_rxy':'Y @ Zero Rotation:',
'z': "Z bounds:",'z_zero_rxy':'Z @ Zero Rotation:',
'a': "A bounds:", 'b': "B bounds:",
'c': "C bounds:",'toollist':'Tool Change List:',
'gcode_units':"Gcode Units:"
}
smallmess = mess = ''
if props:
for i in props:
smallmess += '<b>%s</b>: %s<br>' % (property_names.get(i), props[i])
mess += '<span style=" font-size:18pt; font-weight:600; color:black;">%s </span>\
<span style=" font-size:18pt; font-weight:600; color:#aa0000;">%s</span>\
<br>'% (property_names.get(i), props[i])
# put the details into the properties page
self.w.textedit_properties.setText(mess)
return
# pop a dialog of the properties
msg = QtWidgets.QMessageBox()
msg.setIcon(QtWidgets.QMessageBox.Information)
msg.setText(smallmess)
msg.setWindowTitle(_translate("HandlerClass","Gcode Properties"))
msg.setStandardButtons(QtWidgets.QMessageBox.Ok)
msg.show()
retval = msg.exec_()
def touchoff(self, selector):
if selector == 'touchplate':
z_offset = float(self.w.lineEdit_touch_height.text())
elif selector == 'sensor':
z_offset = float(self.w.lineEdit_sensor_height.text()) - float(self.w.lineEdit_work_height.text())
else:
self.add_status(_translate("HandlerClass","Unknown touchoff routine specified"), CRITICAL)
return
max_probe = self.w.lineEdit_max_probe.text()
search_vel = self.w.lineEdit_search_vel.text()
probe_vel = self.w.lineEdit_probe_vel.text()
retract = self.w.lineEdit_retract_distance.text()
safe_z = self.w.lineEdit_z_safe_travel.text()
self.add_status("{} {} {} {} {} {} {} {} {}".format(
_translate("HandlerClass","Touchoff to"),_translate("HandlerClass","started with"), selector,
search_vel, probe_vel, max_probe,
z_offset, retract, safe_z))
rtn = ACTION.TOUCHPLATE_TOUCHOFF(search_vel, probe_vel, max_probe,
z_offset, retract, safe_z, self.touchoff_return,self.touchoff_error)
if not rtn == 1:
self.add_status(rtn, CRITICAL)
def touchoff_return(self, data):
self.add_status(_translate("HandlerClass","Touchplate touchoff routine returned successfully"))
self.add_status(_translate("HandlerClass","Touchplate returned: ")+data, CRITICAL)
def touchoff_error(self, data):
ACTION.SET_ERROR_MESSAGE(data)
self.add_status(data, CRITICAL)
def kb_jog(self, state, joint, direction, fast = False, linear = True):
ACTION.SET_MANUAL_MODE()
if not STATUS.is_man_mode() or not STATUS.machine_is_on():
self.add_status(_translate("HandlerClass",'Machine must be ON and in Manual mode to jog'), CRITICAL)
return
if linear:
distance = STATUS.get_jog_increment()
rate = STATUS.get_jograte()/60
else:
distance = STATUS.get_jog_increment_angular()
rate = STATUS.get_jograte_angular()/60
if state:
if fast:
rate = rate * 2
ACTION.JOG(joint, direction, rate, distance)
else:
# incremental jogging?
if joint in (3,4,5,'A','B','C'): # angualar axis
if STATUS.get_jog_increment_angular() != 0: return
elif STATUS.get_jog_increment() != 0: return
# otherwise stop jogging when key released
ACTION.JOG(joint, 0, 0, 0)
def add_status(self, message, alertLevel = DEFAULT, noLog = False):
opt = 'TIME'
if alertLevel==DEFAULT:
self.set_style_default()
elif alertLevel==SUCCESS:
opt += ',SUCCESS'
self.set_style_default()
elif alertLevel==WARNING:
opt += ',WARNING'
self.set_style_warning()
elif alertLevel==ERROR:
opt += ',ERROR'
self.set_style_warning()
else:
opt += ',CRITICAL'
self.set_style_critical()
self.w.lineEdit_statusbar.setText(message)
if noLog:
return
STATUS.emit('update-machine-log', message, opt)
def enable_auto(self, state):
for widget in self.auto_list:
self.w[widget].setEnabled(state)
# need to let adjust stack function know the mode changed
# not auto
if state is True:
self.adjust_stacked_widgets(self.w.stackedWidget_mainTab.currentIndex(),mode_change = True)
# auto mode
else:
self.w.btn_main.setChecked(True)
self.adjust_stacked_widgets(TAB_MAIN,mode_change = True)
def enable_onoff(self, state):
if state:
self.add_status(_translate("HandlerClass","Machine ON"), SUCCESS)
else:
if not STATUS.estop_is_clear():
self.add_status(_translate("HandlerClass","ESTOP!"), CRITICAL)
else:
self.add_status(_translate("HandlerClass","MACHINE OFF"), ERROR)
self.w.btn_spindle_pause.setChecked(False)
self.h['eoffset-spindle-count'] = 0
for widget in self.onoff_list:
self.w[widget].setEnabled(state)
def set_start_line(self, line):
if self.w.chk_run_from_line.isChecked():
self.start_line = line
self.w.btn_start.setText("{}\n{}".format(_translate("HandlerClass","START"), self.start_line))
else:
self.start_line = 1
def use_keyboard(self):
if self.w.chk_use_keyboard.isChecked():
return True
else:
self.add_status(_translate("HandlerClass",'Keyboard shortcuts are disabled'))
return False
def do_file_copy(self):
try:
copyfile(self.source_file, self.destination_file)
txt1 = _translate("HandlerClass","Copied file from")
txt2 = _translate("HandlerClass","to")
self.add_status("{} {} {} {}".format(txt1,self.source_file, txt2, self.destination_file))
except Exception as e:
self.add_status("{}. {}".format(_translate("HandlerClass","Unable to copy file"), e))
def update_rpm(self, speed):
if self.max_spindle_rpm < int(speed) < self.min_spindle_rpm:
if STATUS.is_spindle_on():
self.w.lbl_spindle_set.setProperty('in_range', False)
self.w.lbl_spindle_set.style().unpolish(self.w.lbl_spindle_set)
self.w.lbl_spindle_set.style().polish(self.w.lbl_spindle_set)
else:
self.w.lbl_spindle_set.setProperty('in_range', True)
self.w.lbl_spindle_set.style().unpolish(self.w.lbl_spindle_set)
self.w.lbl_spindle_set.style().polish(self.w.lbl_spindle_set)
def periodic_update(self):
# if waiting and up to speed, lower spindle
if self._spindle_wait:
if bool(self.h.hal.get_value('spindle.0.at-speed')):
self.lowerSpindle()
self._spindle_wait = False
self.update_runtimer()
def update_runtimer(self):
if not self.timer_on or STATUS.is_auto_paused():
return
tick = time.time()
elapsed_time = tick - self.timer_tick
self.run_time += elapsed_time
self.timer_tick = tick
hours, remainder = divmod(int(self.run_time), 3600)
minutes, seconds = divmod(remainder, 60)
self.w.lbl_runtime.setText("{:02d}:{:02d}:{:02d}".format(hours, minutes, seconds))
def start_timer(self):
self.run_time = 0
self.timer_on = True
self.timer_tick = time.time()
def pause_timer(self, state):
# stop the timer update but
# keep track of accumulated time
if state:
self.timer_on = False
tick = time.time()
self.run_time += tick - self.timer_tick
txt = _translate("HandlerClass","Run timer paused at")
self.add_status("{} {}".format(txt, self.w.lbl_runtime.text()))
# reset the time reference point to now
# but don't reset the accumulated time
else:
self.timer_on = True
self.timer_tick = time.time()
def stop_timer(self):
self.timer_on = False
if STATUS.is_auto_mode():
self.add_status("{} {}".format(_translate("HandlerClass","Run timer stopped at"), self.w.lbl_runtime.text()))
# web page zoom
def zoomWeb(self):
# webview
try:
f = self.w.webwidget.zoomFactor() +.5
if f > 2:f = 1
self.w.webwidget.setZoomFactor(f)
except:
pass
# PDF
try:
f = self.PDFView.zoomFactor() +.5
if f > 2:f = 1
self.PDFView.setZoomFactor(f)
except:
pass
# go directly the default HTML page
def homeWeb(self):
try:
if os.path.exists(self.default_setup):
self.w.webwidget.load(QtCore.QUrl.fromLocalFile(self.default_setup))
else:
self.w.webwidget.setHtml(self.html, self.swoopURL)
except:
pass
# setup tab's web page back button
def back(self):
try:
try:
self.w.webwidget.page().triggerAction(QWebEnginePage.Back)
except:
if os.path.exists(self.default_setup):
self.w.webwidget.load(QtCore.QUrl.fromLocalFile(self.default_setup))
else:
self.w.webwidget.setHtml(self.html, self.swoopURL)
except:
pass
# setup tab's web page forward button
def forward(self):
try:
try:
self.w.webwidget.page().triggerAction(QWebEnginePage.Forward)
except:
self.w.webwidget.load(QtCore.QUrl.fromLocalFile(self.docs))
except:
pass
# setup tab's web page - enable/disable buttons
def onLoadFinished(self):
try:
if self.w.webwidget.history().canGoBack():
self.backBtn.setEnabled(True)
else:
self.backBtn.setEnabled(False)
if self.w.webwidget.history().canGoForward():
self.forBtn.setEnabled(True)
else:
self.forBtn.setEnabled(False)
except:
pass
def writer(self):
WRITER.show()
def endcolor(self):
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.clear_status_bar)
self.timer.start(self.statusbar_reset_time)
def clear_status_bar(self):
self.set_style_default()
self.w.lineEdit_statusbar.setText('')
# change Status bar text color
def set_style_default(self):
c = self.w.screen_options.property('user1Color').name()
self.w.lineEdit_statusbar.setStyleSheet(
"background-color: {} ;color: rgb(0,0,0)".format(c)) #default white
def set_style_warning(self):
c = self.w.screen_options.property('user2Color').name()
self.w.lineEdit_statusbar.setStyleSheet(
"background-color: {} ;color: rgb(0,0,0)".format(c)) #yellow
self.endcolor()
def set_style_critical(self):
c = self.w.screen_options.property('user3Color').name()
self.w.lineEdit_statusbar.setStyleSheet(
"background-color: {} ;color: rgb(0,0,0)".format(c)) #orange
self.endcolor()
def adjust_stacked_widgets(self,requestedIndex,mode_change=False):
IGNORE = -1
SHOW_DRO = 0
mode = ['','','Auto','MDI'][STATUS.get_current_mode()]
if mode_change:
premode = ['','','Auto','MDI'][STATUS.get_previous_mode()]
else:
premode=mode
currentIndex = self.w.stackedWidget_mainTab.currentIndex()
indexList = ['main','file','offsets','tool','status','probe','cam',
'gcode','setup','settings','util','user']
if self.auto_mode_switch and not STATUS.is_auto_running():
opt = requestedIndex,PAGE_FILE,True,IGNORE,False
else:
opt = TAB_MAIN,PAGE_GCODE,False,SHOW_DRO,False
if mode == 'Auto':
seq = {TAB_MAIN: (TAB_MAIN,PAGE_GCODE,False,SHOW_DRO,False),
TAB_FILE: (opt),
TAB_OFFSETS: (TAB_MAIN,PAGE_GCODE,False,SHOW_DRO,False),
TAB_TOOL: (TAB_MAIN,PAGE_GCODE,False,SHOW_DRO,False),
TAB_STATUS: (requestedIndex,PAGE_GCODE,False,SHOW_DRO,False),
TAB_PROBE: (TAB_MAIN,PAGE_GCODE,False,SHOW_DRO,False),
TAB_CAMERA: (requestedIndex,PAGE_UNCHANGED,False,SHOW_DRO,False),
TAB_GCODES: (requestedIndex,PAGE_UNCHANGED,False,SHOW_DRO,False),
TAB_SETUP: (requestedIndex,PAGE_UNCHANGED,False,SHOW_DRO,False),
TAB_SETTINGS: (requestedIndex,PAGE_GCODE,False,SHOW_DRO,False),
TAB_UTILITIES: (TAB_MAIN,PAGE_GCODE,False,SHOW_DRO,False),
TAB_USER: (requestedIndex,PAGE_UNCHANGED,IGNORE,IGNORE,False) }
else:
seq = {TAB_MAIN: (requestedIndex,PAGE_GCODE,True,SHOW_DRO,True),
TAB_FILE: (requestedIndex,PAGE_FILE,True,IGNORE,False),
TAB_OFFSETS: (requestedIndex,PAGE_OFFSET,True,IGNORE,False),
TAB_TOOL: (requestedIndex,PAGE_TOOL,True,IGNORE,False),
TAB_STATUS: (requestedIndex,PAGE_UNCHANGED,True,SHOW_DRO,False),
TAB_PROBE: (requestedIndex,PAGE_GCODE,True,SHOW_DRO,False),
TAB_CAMERA: (requestedIndex,PAGE_UNCHANGED,True,IGNORE,True),
TAB_GCODES: (requestedIndex,PAGE_UNCHANGED,False,SHOW_DRO,False),
TAB_SETUP: (requestedIndex,PAGE_UNCHANGED,False,IGNORE,False),
TAB_SETTINGS: (requestedIndex,PAGE_UNCHANGED,False,SHOW_DRO,False),
TAB_UTILITIES: (requestedIndex,PAGE_UNCHANGED,True,SHOW_DRO,False),
TAB_USER: (requestedIndex,PAGE_UNCHANGED,IGNORE,IGNORE,True) }
rtn = seq.get(requestedIndex)
# if not found (None) use defaults
if rtn is None:
main_index = requestedIndex
stacked_index = 0
show_JogControls = True
show_dro = 0
show_macro = True
else:
main_index,stacked_index,show_JogControls,show_dro,show_macro = rtn
# user tab button covers multiple tabs so adjust name
# for extra user tabs
if currentIndex >= len(indexList):
tabId = 'user{}'.format(currentIndex - len(indexList))
else:
tabId = indexList[currentIndex]
name = 'splitterSettings-{}{}'.format(tabId,premode)
#print ('CURRENT:',name)
# record current qsplitter settings
self.w.settings.beginGroup("qtdragon-{}".format(self.w.splitter_h.objectName()))
self.w.settings.setValue(name, QtCore.QVariant(self.w.splitter_h.saveState().data()))
self.w.settings.endGroup()
# ignore, show or hide jog controls
if show_JogControls == IGNORE:
pass
elif show_JogControls:
self.w.jogging_frame.show()
else:
self.w.jogging_frame.hide()
# show DRO rather then keyboard.
if show_dro > IGNORE:
self.w.stackedWidget_dro.setCurrentIndex(0)
# macros can only be run in manual or mdi mode
if show_macro:
self.w.frame_macro_buttons.show()
else:
self.w.frame_macro_buttons.hide()
# show ngcgui info tab if utilities tab is selected
# but only if the utilities tab has ngcgui selected
if main_index == TAB_UTILITIES:
num = self.w.tabWidget_utilities.currentIndex()
if 'ngc' in self.w.tabWidget_utilities.tabText(num).lower():
self.w.stackedWidget.setCurrentIndex(PAGE_NGCGUI)
else:
self.w.stackedWidget.setCurrentIndex(PAGE_GCODE)
# adjust the stacked widget (left side MDI/Gcode stack)
if stacked_index > PAGE_UNCHANGED:
self.w.stackedWidget.setCurrentIndex(stacked_index)
# toggle home/tool offsets buttons in DRO section
if main_index == TAB_TOOL:
num = 1
else:
num = 0
for n,i in enumerate(INFO.AVAILABLE_AXES):
self.w['dro_button_stack_%s'%(n)].setCurrentIndex(num)
# user tabs button cycles between all user tabs
main_current = self.w.stackedWidget_mainTab.currentIndex()
if main_index == TAB_USER and main_current >= TAB_USER:
next = main_current +1
if next == self.w.stackedWidget_mainTab.count():
next = TAB_USER
self.w.stackedWidget_mainTab.setCurrentIndex(next)
else:
# set main tab to adjusted index
self.w.stackedWidget_mainTab.setCurrentIndex(main_index)
# switch modes if file is pressed in auto mode
if main_index == TAB_FILE and self.auto_mode_switch:
if STATUS.is_auto_mode():
ACTION.SET_MANUAL_MODE()
# if indexes don't match then request is disallowed
# give a warning and reset the button check
if main_index != requestedIndex and not main_index in(TAB_CAMERA, TAB_GCODES,
TAB_STATUS, TAB_SETUP):
self.add_status("Cannot switch pages while in AUTO mode", WARNING)
self.w.stackedWidget_mainTab.setCurrentIndex(0)
self.w.btn_main.setChecked(True)
# user tab button covers multiple tabs
# adjust name depending in what user tab is showing
cur = self.w.stackedWidget_mainTab.currentIndex()
if cur >= len(indexList):
tabId = 'user{}'.format(cur - len(indexList))
else:
tabId = indexList[cur]
# if in auto mode and the main tab button is pressed
# cycle between full gcode, split and pull graphics
if main_index == TAB_MAIN and mode =='Auto':
if self._maintab_cycle >3: self._maintab_cycle = 1
if self._maintab_cycle == 2:
self.w.frame_top_left.hide()
self.w.frm_backplot.show()
return
elif self._maintab_cycle == 3:
self.w.frame_top_left.show()
self.w.frm_backplot.hide()
return
else:
self.w.frame_top_left.show()
self.w.frm_backplot.show()
else:
self.w.frame_top_left.show()
self.w.frm_backplot.show()
# adjust window splitter size as per saved adjustments
name = 'splitterSettings-{}{}'.format(tabId,mode)
#print ('NOW:',name)
# restore new qsplitter setting
self.w.settings.beginGroup("qtdragon-{}".format(self.w.splitter_h.objectName()))
splitterSetting = self.w.settings.value(name)
self.w.settings.endGroup()
if not splitterSetting is None:
try:
self.w.splitter_h.restoreState(QtCore.QByteArray(splitterSetting))
except Exception as e:
print(e)
# if you click the current tab again, hide/show the lower page to
# expand the upper page.
if currentIndex == requestedIndex and not mode_change:
self.w.lower_widget.setVisible(not self.w.lower_widget.isVisible())
# set axis 4/5 dro widgets to the proper axis
# TODO do this with all the axes for more flexibility
def initiate_axis_dro(self, num, axis):
jnum = INFO.GET_JOG_FROM_NAME.get(axis)
# DRO uses axis index
index = "XYZABCUVW".index(axis)
self.w['dro_axis_{}'.format(num)].setProperty('Qjoint_number',index)
self.w['action_zero_{}'.format(num)].setProperty('axis_letter',axis)
try:
self.w['axis_select_{}'.format(num)].setProperty('axis_letter',axis)
self.w['axis_select_{}'.format(num)].setText('{}'.format(axis))
except Exception as e:
print(e)
try:
cmd = 'G90 G0 {}0'.format(axis)
self.w['action_cmd_{}'.format(num)].setProperty('command_text_string',cmd)
except:
pass
self.w['btn_home_{}'.format(num)].setProperty('axis_letter',axis)
self.w['btn_home_{}'.format(num)].setProperty('joint_number_status',jnum)
self.w['btn_home_{}'.format(num)].setProperty('joint',index)
self.w['offsettoolbutton_{}'.format(num)].setProperty('axis_letter',axis)
self.w['plus_jogbutton_{}'.format(num)].setProperty('axis_letter',axis)
self.w['plus_jogbutton_{}'.format(num)].setProperty('joint_number',jnum)
a = axis.lower()
try:
icn = QtGui.QIcon(QtGui.QPixmap(':/buttons/images/{}_plus_jog_button.png'.format(a)))
if icn.isNull(): raise Exception
self.w['plus_jogbutton_{}'.format(num)].setIcon(icn)
except Exception as e:
print(e)
self.w['plus_jogbutton_{}'.format(num)].setProperty('text','{}+'.format(axis))
self.w['minus_jogbutton_{}'.format(num)].setProperty('axis_letter',axis)
self.w['minus_jogbutton_{}'.format(num)].setProperty('joint_number',jnum)
try:
icn = QtGui.QIcon(QtGui.QPixmap(':/buttons/images/{}_minus_jog_button.png'.format(a)))
if icn.isNull(): raise Exception
self.w['minus_jogbutton_{}'.format(num)].setIcon(icn)
except Exception as e:
self.w['minus_jogbutton_{}'.format(num)].setProperty('text','{}-'.format(axis))
# MPG scrolling of program or MDI history
def external_mpg(self, count):
diff = count - self._last_count
if self.w.btn_mpg_scroll.isChecked():
currentIndex = self.w.stackedWidget_mainTab.currentIndex()
if isinstance(self.MPGFocusWidget, SLIDER):
#print('Slider',self.MPGFocusWidget.objectName(),self.MPGFocusWidget.parent())
if self.MPGFocusWidget is self.w.slider_feed_ovr:
scaled = (STATUS.stat.feedrate * 100 + diff)
if scaled <0 :scaled = 0
elif scaled > INFO.MAX_FEED_OVERRIDE:scaled = INFO.MAX_FEED_OVERRIDE
ACTION.SET_FEED_RATE(scaled)
elif self.MPGFocusWidget is self.w.slider_rapid_ovr:
scaled = (STATUS.stat.rapidrate * 100 + diff)
if scaled <0 :scaled = 0
elif scaled > 100:scaled = 100
ACTION.SET_RAPID_RATE(scaled)
elif self.MPGFocusWidget is self.w.slider_spindle_ovr:
scaled = (STATUS.stat.spindle[0]['override'] * 100 + diff)
if scaled < INFO.MIN_SPINDLE_OVERRIDE:scaled = INFO.MIN_SPINDLE_OVERRIDE
elif scaled > INFO.MAX_SPINDLE_OVERRIDE:scaled = INFO.MAX_SPINDLE_OVERRIDE
ACTION.SET_SPINDLE_RATE(scaled)
elif isinstance(self.MPGFocusWidget, GRAPHICS):
if self.w.actionbutton_pan_rpyaye.isChecked():
ACTION.ADJUST_GRAPHICS_ROTATE(diff,diff)
else:
ACTION.ADJUST_GRAPHICS_PAN(diff,0)
elif isinstance(self.MPGFocusWidget, GCODE):
self.w.gcode_editor.jump_line(diff)
self.w.gcode_viewer.jump_line(diff)
elif isinstance(self.MPGFocusWidget, MDI_WIDGET):
if diff <0:
self.MPGFocusWidget.line_down()
else:
self.MPGFocusWidget.line_up()
elif currentIndex == TAB_FILE:
if isinstance(self.MPGFocusWidget, FM):
widget = self.MPGFocusWidget
else:
widget = self.w.filemanager
if diff <0:
widget.down()
else:
widget.up()
elif currentIndex == TAB_TOOL:
if isinstance(self.MPGFocusWidget, TOOL_TABLE):
print('scroll offset view')
if diff <0:
self.MPGFocusWidget.down()
else:
self.MPGFocusWidget.up()
elif currentIndex == TAB_OFFSETS:
if isinstance(self.MPGFocusWidget, OFFSET_VIEW):
print('scroll offset view')
if diff <0:
self.MPGFocusWidget.down()
else:
self.MPGFocusWidget.up()
self._last_count = count
def dialog_ext_control(self, pin, value, answer):
if value:
# search for a visible notification first
# and close it
chk = self.w._NOTICE.find_visible()
if not chk is None:
chk.close()
return
# search the registered dialogs for a match
dlist = self.w.getRegisteredDialogList()
for i in (dlist):
if i.isVisible():
LOG.verbose('Found dialog',i.objectName())
name = i.getIdName()
STATUS.emit('dialog-update',{'NAME':name,'response':answer})
return
def log_version(self):
if INFO.RIP_FLAG:
t = _translate("HandlerClass","(RIP)")
else:
t = _translate("HandlerClass","(Installed)")
mess = "--- {} {} {} {} {} ---".format(
_translate("HandlerClass","QtDragon Version"),
VERSION,
_translate("HandlerClass","on Linuxcnc"),
STATUS.get_linuxcnc_version(),
t)
self.add_status(mess, CRITICAL,noLog=True)
STATUS.emit('update-machine-log', mess, None)
# show/hide macro buttons depending on the INI definitions
def configureMacroButtons(self):
# Show assigned macrobuttons define in INI under [MDI_COMMAND_LIST]
flag = True
for b in range(0,10):
button = self.w['macrobutton{}'.format(b)]
# prefer named INI MDI commands
key = button.property('ini_mdi_key')
if key == '' or INFO.get_ini_mdi_command(key) is None:
# fallback to legacy nth line
key = button.property('ini_mdi_number')
try:
code = INFO.get_ini_mdi_command(key)
if code is None: raise Exception
flag = False
except:
button.hide()
# add any INI defined macros if there is less then
# ten INI MDI commands
start = len(INFO.MDI_COMMAND_DICT)
for b in range(start,10):
button = self.w['macrobutton{}'.format(b)]
adj = b-start
# prefer named INI MDI commands
try:
# find what ini_mdi button we are going to convert
key = button.property('ini_mdi_key')
code = INFO.get_ini_macro_command(key)
if code is None:
code = INFO.get_ini_macro_command(b-start)
if code is None:
raise Exception
else: key = b-start
flag = False
button.show()
try:
label = INFO.get_ini_macro_label(key)
label = label.replace(r'\n', '\n')
button.setText(label)
except:
pass
try:
tooltiplabel = 'INI MACRO CMD {}:\n'.format(key)
tooltiplabel += INFO.get_ini_macro_command(key).replace(';', '\n')
button.setToolTip(tooltiplabel)
except Exception as e:
pass
button.setProperty('ini_mdi_command_action', False)
button.setProperty('ini_macro_command_action', True)
button.setProperty('ini_macro_number',b-start)
button.setProperty('ini_macro_key',key)
except Exception as e:
button.hide()
# no buttons hide frame
if flag:
self.w.frame_macro_buttons.hide()
# if there are more then 10 add a menu to the last button for selection
if len(INFO.MDI_COMMAND_DICT)>10:
button.setText(_translate("HandlerClass",'MORE\nMACROS'))
button.setProperty('ini_mdi_command_action', False)
button.setProperty('no_action', True)
button.setToolTip('')
SettingMenu = QMenu(button)
button.setMenu(SettingMenu)
for i in range(0,len(INFO.MDI_COMMAND_DICT)-10):
try:
name = 'MACRO{}'.format(i+10)
label = INFO.MDI_COMMAND_DICT[name]['label']
except:
label = INFO.MDI_COMMAND_LABEL_LIST[i+10]
action = QAction(QIcon.fromTheme('application-exit'), label.replace("\\n"," "), button)
action.triggered.connect(lambda s, i=i, b=button : self.midiAction(b, i+10))
# tooltips don't work on Qmenu items unless in toolbar
#tooltiplabel = 'INI MDI CMD {}:\n'.format(name)
#tooltiplabel += INFO.get_ini_mdi_command(key).replace(';', '\n')
#action.setToolTip(tooltiplabel)
SettingMenu.addAction(action)
# use the button to run macros selected from the buttons menu
def midiAction(self, button, num):
name = 'MACRO{}'.format(num)
try:
code = INFO.MDI_COMMAND_DICT[name]['cmd']
button.setProperty('ini_mdi_key', name)
except:
code = INFO.MDI_COMMAND_LIST[num]
button.setProperty('ini_mdi_key', '')
button.setProperty('ini_mdi_number', num)
print('number',num,button.ini_mdi_num)
button.setProperty('ini_mdi_command_action', True)
button.pressed.emit()
button.setProperty('ini_mdi_command_action', False)
# show/hide a popup window panel (if defined in the INI)
def togglePopup(self):
if self.w['popup'].isVisible():
self.w['popup']._lastgeometry = self.w['popup'].geometry()
self.w['popup'].hide()
else:
self.w['popup'].show()
if not self.w['popup']._lastgeometry is None:
self.w['popup'].setGeometry(self.w['popup']._lastgeometry)
def onClosePopup(self, *args):
self.w['popup']._lastgeometry = self.w['popup'].geometry()
#####################
# KEY BINDING CALLS #
#####################
def on_keycall_ESTOP(self,event,state,shift,cntrl):
if state:
ACTION.SET_ESTOP_STATE(True)
def on_keycall_POWER(self,event,state,shift,cntrl):
if state:
ACTION.SET_MACHINE_STATE(False)
def on_keycall_ABORT(self,event,state,shift,cntrl):
if state:
ACTION.ABORT()
def on_keycall_HOME(self,event,state,shift,cntrl):
if state and not STATUS.is_all_homed() and self.use_keyboard():
ACTION.SET_MACHINE_HOMING(-1)
def on_keycall_pause(self,event,state,shift,cntrl):
if state and self.use_keyboard():
self.w.action_pause.click()
def on_keycall_jograte(self,event,state,shift,cntrl,value):
if state and self.use_keyboard():
if value == 1:
ACTION.SET_JOG_RATE_FASTER()
else:
ACTION.SET_JOG_RATE_SLOWER()
def on_keycall_angular_jograte(self,event,state,shift,cntrl,value):
if state and self.use_keyboard():
if value == 1:
ACTION.SET_JOG_RATE_ANGULAR_FASTER()
else:
ACTION.SET_JOG_RATE_ANGULAR_SLOWER()
def on_keycall_XPOS(self,event,state,shift,cntrl):
if self.use_keyboard():
self.kb_jog(state, 0, 1, shift)
def on_keycall_XNEG(self,event,state,shift,cntrl):
if self.use_keyboard():
self.kb_jog(state, 0, -1, shift)
def on_keycall_YPOS(self,event,state,shift,cntrl):
if self.use_keyboard():
self.kb_jog(state, 1, 1, shift)
def on_keycall_YNEG(self,event,state,shift,cntrl):
if self.use_keyboard():
self.kb_jog(state, 1, -1, shift)
def on_keycall_ZPOS(self,event,state,shift,cntrl):
if self.use_keyboard():
self.kb_jog(state, 2, 1, shift)
def on_keycall_ZNEG(self,event,state,shift,cntrl):
if self.use_keyboard():
self.kb_jog(state, 2, -1, shift)
def on_keycall_APOS(self,event,state,shift,cntrl):
if self.use_keyboard():
self.kb_jog(state, 3, 1, shift, False)
def on_keycall_ANEG(self,event,state,shift,cntrl):
if self.use_keyboard():
self.kb_jog(state, 3, -1, shift, False)
def on_keycall_F12(self,event,state,shift,cntrl):
if state:
STYLEEDITOR.load_dialog()
def on_keycall_F11(self,event,state,shift,cntrl):
if self.w.isFullScreen() == False and state == True:
self.w.showFullScreen()
else:
if self.w.isFullScreen() == True and state == True:
self.w.showNormal()
##############################
# required class boiler code #
##############################
def __getitem__(self, item):
return getattr(self, item)
def __setitem__(self, item, value):
return setattr(self, item, value)
################################
# required handler boiler code #
################################
def get_handlers(halcomp, widgets, paths):
return [HandlerClass(halcomp, widgets, paths)]