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

6432 lines
330 KiB
Python

VERSION = '016.087'
LCNCVER = '2.10'
'''
qtplasmac_handler.py
Copyright (C) 2020-2025 Phillip A Carter
Copyright (C) 2020-2026 Gregory D Carl
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.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
'''
import os
import sys
from shutil import copy as COPY
from subprocess import Popen, PIPE
from subprocess import run as RUN
from subprocess import call as CALL
from importlib import reload
import time
import tarfile
import math
import glob
import linuxcnc
import hal
from qtpy import QtCore, QtWidgets, QtGui
from qtpy.QtCore import *
from qtpy.QtWidgets import *
from qtpy.QtGui import *
try:
from qtpy.Qsci import QsciScintilla
except ImportError:
try:
from PyQt5.Qsci import QsciScintilla
except ImportError:
from PyQt6.Qsci import QsciScintilla
from qtvcp import logger
from qtvcp.core import Status, Action, Info, Tool
try:
from qtpy.QtWebEngineWidgets import QWebEngineView as _QWebEngineView
_WEBENGINE_AVAILABLE = True
except Exception:
_WEBENGINE_AVAILABLE = False
from qtvcp.lib.gcodes import GCodes
from qtvcp.lib.keybindings import Keylookup
from qtvcp.lib.preferences import Access
from qtvcp.lib.qtplasmac import tooltips as TOOLTIPS
from qtvcp.lib.qtplasmac import set_offsets as OFFSETS
from qtvcp.lib.qtplasmac import updater as UPDATER
from qtvcp.lib.qtplasmac.interlock_rules import INTERLOCK_RULES
from qtvcp.widgets.camview_widget import CamView as CAM
from qtvcp.widgets.file_manager import FileManager as FILE_MAN
from qtvcp.widgets.gcode_editor import GcodeEditor as EDITOR
from qtvcp.widgets.gcode_editor import GcodeDisplay as DISPLAY
from qtvcp.widgets.mdi_history import MDIHistory as MDI_HISTORY
from qtvcp.widgets.mdi_line import MDILine as MDI_LINE
from qtvcp.widgets.origin_offsetview import OriginOffsetView as OFFSETVIEW
from qtvcp.widgets.origin_offsetview import MyTableModel as OFFSET_TABLE
from qtvcp.widgets.screen_options import ScreenOptions as OPTIONS
from qtvcp.widgets.simple_widgets import DoubleScale as DOUBLESCALE
from qtvcp.widgets.status_label import StatusLabel as STATLABEL
from qtvcp.widgets.stylesheeteditor import StyleSheetEditor as SSE
from qtvcp.lib.aux_program_loader import Aux_program_loader
from plasmac import run_from_line as RFL
from rs274.glcanon import GlCanonDraw as DRAW
from qt5_graphics import Lcnc_3dGraphics as DRO
LOG = logger.getLogger(__name__)
KEYBIND = Keylookup()
STATUS = Status()
INFO = Info()
ACTION = Action()
TOOL = Tool()
AUX_PRGM = Aux_program_loader()
INIPATH = os.environ.get('INI_FILE_NAME', '/dev/null')
_translate = QCoreApplication.translate
# a vertical line as a separator on the status bar
class VLine(QFrame):
def __init__(self):
super(VLine, self).__init__()
self.setFrameShape(self.VLine | self.Plain)
# dummy class to raise an exception for style issues
class ColorError(Exception):
pass
# click signal for some labels
def click_signal(widget):
class Filter(QObject):
clicked = Signal()
def eventFilter(self, obj, event):
if obj == widget:
if event.type() == QEvent.MouseButtonRelease:
if obj.rect().contains(event.pos()):
self.clicked.emit()
return True
return False
filter = Filter(widget)
widget.installEventFilter(filter)
return filter.clicked
# the main handler
class HandlerClass:
from qtvcp.lib.qtplasmac import conversational as CONV
# when self.w.button_frame changes size
def eventFilter(self, object, event):
if event.type() == QtCore.QEvent.Resize:
self.size_changed(object)
return True
def __init__(self, halcomp, widgets, paths):
self.interlockRules = INTERLOCK_RULES.copy()
self.state = {
'all_homed': False,
'consumable_change': False,
'consumable_changing': False,
'estop_cleared': False,
'file_bounds_error': False,
'file_opened': False,
'framing': False,
'gcode_loaded': False,
'gcodestack_is_mdi': False,
'interp_idle': True,
'interp_paused': False,
'interp_running': False,
'laser_button_state': True,
'machine_on': False,
'manual_cut_active': False,
'offsets_active': False,
'ohmic_probe_enable': False,
'ohmic_test': False,
'plasmac_idle': True,
'previewstack_is_edit': False,
'previewstack_is_camera': False,
'previewstack_is_user_manual': False,
'previewstack_is_offsets': False,
'previewstack_is_open': False,
'previewstack_is_preview': True,
'probe_bounds_error': False,
'probe_test': False,
'rfl_dialog': False,
'single_cut_dialog': False,
'torch_enable': False,
'torch_pulse': False,
}
self.firstRun = True
self.h = halcomp
self.w = widgets
self.h.comp.setprefix('qtplasmac')
self.PATHS = paths
self.iniFile = INFO.INI
self.foreColor = '#ffee06'
# can we find M190 in the USER_M_PATH
# if not we will attempt to find a valid USER_M_PATH path later
self.mPath = self.iniFile.find('RS274NGC', 'USER_M_PATH').split(':')
for path in self.mPath:
if path.startswith('.'):
path = os.path.join(self.PATHS.CONFIGPATH, path)
if os.path.isfile(os.path.join(path, 'M190')):
self.mPath = 'valid'
break
self.machineName = self.iniFile.getstring('EMC', 'MACHINE', fallback='<unknown>')
self.machineTitle = f'{self.machineName} - QtPlasmaC v{LCNCVER}-{VERSION}, powered by QtVCP and LinuxCNC'
self.docsVer = 'devel' if 'pre' in linuxcnc.version else LCNCVER
self.prefsFile = os.path.join(self.PATHS.CONFIGPATH, self.machineName + '.prefs')
self.materialFile = os.path.join(self.PATHS.CONFIGPATH, self.machineName + '_material.cfg')
self.unitsPerMm = 1
self.units = self.iniFile.find('TRAJ', 'LINEAR_UNITS')
if self.units == 'inch':
self.units = 'in'
self.unitsPerMm = 0.03937
# open prefs file so we can use it for updates
# it will not exist prior to V1.222.170 2022/03/08
if os.path.isfile(self.prefsFile):
self.PREFS = Access(self.prefsFile)
else:
self.PREFS = None
self.updateIni = {}
self.updateData = []
self.update_check()
self.PREFS = Access(self.prefsFile)
self.MATS = Access(self.materialFile)
self.STYLEEDITOR = SSE(widgets, paths)
self.GCODES = GCodes(widgets)
self.IMAGES = os.path.join(self.PATHS.IMAGEDIR, 'qtplasmac/images/')
self.landscape = True
if os.path.basename(self.PATHS.XML) == 'qtplasmac_9x16.ui':
self.landscape = False
self.upFile = os.path.join(self.PATHS.CONFIGPATH, 'user_periodic.py')
self.umUrl = QUrl(f'https://linuxcnc.org/docs/{self.docsVer}/html/plasma/qtplasmac.html')
KEYBIND.add_call('Key_F12', 'on_keycall_F12')
KEYBIND.add_call('Key_F9', 'on_keycall_F9')
KEYBIND.add_call('Key_Plus', 'on_keycall_PLUS')
KEYBIND.add_call('Key_Minus', 'on_keycall_MINUS')
KEYBIND.add_call('Key_R', 'on_keycall_RUN')
KEYBIND.add_call('Key_Any', 'on_keycall_PAUSE')
KEYBIND.add_call('Key_o', 'on_keycall_OPEN')
KEYBIND.add_call('Key_l', 'on_keycall_LOAD')
KEYBIND.add_call('Key_j', 'on_keycall_JOINT')
KEYBIND.add_call('Key_1', 'on_keycall_NUMBER', 1)
KEYBIND.add_call('Key_Exclam', 'on_keycall_NUMBER', 1)
KEYBIND.add_call('Key_2', 'on_keycall_NUMBER', 2)
KEYBIND.add_call('Key_At', 'on_keycall_NUMBER', 2)
KEYBIND.add_call('Key_3', 'on_keycall_NUMBER', 3)
KEYBIND.add_call('Key_NumberSign', 'on_keycall_NUMBER', 3)
KEYBIND.add_call('Key_4', 'on_keycall_NUMBER', 4)
KEYBIND.add_call('Key_Dollar', 'on_keycall_NUMBER', 4)
KEYBIND.add_call('Key_5', 'on_keycall_NUMBER', 5)
KEYBIND.add_call('Key_Percent', 'on_keycall_NUMBER', 5)
KEYBIND.add_call('Key_6', 'on_keycall_NUMBER', 6)
KEYBIND.add_call('Key_AsciiCircum', 'on_keycall_NUMBER', 6)
KEYBIND.add_call('Key_7', 'on_keycall_NUMBER', 7)
KEYBIND.add_call('Key_Ampersand', 'on_keycall_NUMBER', 7)
KEYBIND.add_call('Key_8', 'on_keycall_NUMBER', 8)
KEYBIND.add_call('Key_Asterisk', 'on_keycall_NUMBER', 8)
KEYBIND.add_call('Key_9', 'on_keycall_NUMBER', 9)
KEYBIND.add_call('Key_ParenLeft', 'on_keycall_NUMBER', 9)
KEYBIND.add_call('Key_0', 'on_keycall_NUMBER', 0)
KEYBIND.add_call('Key_ParenRight', 'on_keycall_NUMBER', 0)
# there are no default keys for joint/axis 4
KEYBIND.add_call('Key_Comma', 'on_keycall_BPOS')
KEYBIND.add_call('Key_Less', 'on_keycall_BPOS')
KEYBIND.add_call('Key_Period', 'on_keycall_BNEG')
KEYBIND.add_call('Key_Greater', 'on_keycall_BNEG')
KEYBIND.add_call('Key_End', 'on_keycall_END')
KEYBIND.add_call('Key_Delete', 'on_keycall_DELETE')
KEYBIND.add_call('Alt+Key_Return', 'on_keycall_ALTRETURN')
KEYBIND.add_call('Alt+Key_Enter', 'on_keycall_ALTRETURN')
KEYBIND.add_call('Key_Return', 'on_keycall_RETURN')
KEYBIND.add_call('Key_Enter', 'on_keycall_RETURN')
KEYBIND.add_call('Key_QuoteLeft', 'on_keycall_QUOTELEFT')
KEYBIND.add_call('Key_AsciiTilde', 'on_keycall_QUOTELEFT')
self.axes = {'valid': [x.lower() for x in INFO.AVAILABLE_AXES]}
for axis in 'abc':
self.axes[axis] = []
for n in ['dro_0', 'dro_label_0', 'home_0', 'touch_0', 'jog_0_plus', 'jog_0_minus']:
self.axes[axis].append(n.replace('0', axis))
self.systemList = ['G53', 'G54', 'G55', 'G56', 'G57', 'G58', 'G59', 'G59.1', 'G59.2', 'G59.3']
self.slowJogFactor = 10
self.jogFast = False
self.jogSlow = False
self.lastLoadedProgram = 'None'
self.set_interlock_defaults()
self.jogButtonList = ['jog_x_plus', 'jog_x_minus', 'jog_y_plus', 'jog_y_minus',
'jog_z_plus', 'jog_z_minus', 'jog_a_plus', 'jog_a_minus',
'jog_b_plus', 'jog_b_minus', 'jog_c_plus', 'jog_c_minus']
self.jogSyncList = []
self.xMin = self.iniFile.getreal('AXIS_X', 'MIN_LIMIT')
self.xMax = self.iniFile.getreal('AXIS_X', 'MAX_LIMIT')
self.yMin = self.iniFile.getreal('AXIS_Y', 'MIN_LIMIT')
self.yMax = self.iniFile.getreal('AXIS_Y', 'MAX_LIMIT')
self.zMin = self.iniFile.getreal('AXIS_Z', 'MIN_LIMIT')
self.zMax = self.iniFile.getreal('AXIS_Z', 'MAX_LIMIT')
self.xLen = self.xMax - self.xMin
self.yLen = self.yMax - self.yMin
self.thcFeedRate = self.iniFile.getreal('AXIS_Z', 'MAX_VELOCITY') * \
self.iniFile.getreal('AXIS_Z', 'OFFSET_AV_RATIO') * 60
self.offsetFeedRate = min(self.iniFile.getreal('AXIS_X', 'MAX_VELOCITY') * 30,
self.iniFile.getreal('AXIS_Y', 'MAX_VELOCITY') * 30,
self.iniFile.getreal('TRAJ', 'MAX_LINEAR_VELOCITY', fallback=100000))
self.maxHeight = self.zMax - self.zMin
self.maxPidP = self.thcFeedRate / self.unitsPerMm * 0.1
self.tmpPath = '/tmp/qtplasmac/'
if not os.path.isdir(self.tmpPath):
os.mkdir(self.tmpPath)
self.tmpMaterialGcode = f'{self.tmpPath}{self.machineName}_material.gcode'
self.gcodeErrorFile = f'{self.tmpPath}gcode_errors.txt'
self.materialUpdate = False
self.autoChange = False
self.pmx485Exists = False
self.pmx485Connected = False
self.pmx485CommsError = False
self.pmx485FaultCode = 0.0
self.pmx485ArcTime = 0.0
self.pmx485LabelState = None
self.currentX = self.currentY = 0
self.degreeSymbol = u"\u00b0"
self.cameraOn = False
self.fTmp = f'{self.tmpPath}temp.ngc'
self.fNgc = f'{self.tmpPath}shape.ngc'
self.fNgcBkp = f'{self.tmpPath}backup.ngc'
self.fNgcSent = f'{self.tmpPath}sent_shape.ngc'
self.filteredBkp = f'{self.tmpPath}filtered_bkp.ngc'
self.oldConvButton = False
self.convWidgetsLoaded = False
self.programPrefix = self.iniFile.getstring('DISPLAY', 'PROGRAM_PREFIX', fallback=os.environ['LINUXCNC_NCFILES_DIR'])
self.dialogError = False
self.cutTypeText = ''
self.heightOvr = 0.0
self.heightOvrScale = 0.1
self.old_ovr_counts = 0
self.startLine = 0
self.preRflFile = ''
self.preClearFile = ''
self.rflActive = False
self.torchOn = False
self.progRun = False
self.rapidOn = False
self.probeOn = False
self.gcodeProps = {}
self.framing = False
self.fileBoundsError = False
self.probeBoundsError = False
self.singleCutDialog = False
self.ohmicTest = False
self.obLayout = ''
self.notifyColor = 'normal'
self.firstHoming = False
self.droScale = 1
self.mdiError = False
self.extLaserButton = False
self.virtualMachine = False
self.realTimeDelay = False
self.preSingleCutMaterial = None
self.preFileSaveMaterial = None
self.laserDryRunCoords = None
self.runType = {'type': 'end'}
# plasmac states
self.IDLE = 0
self.PROBE_HEIGHT = 1
self.PROBE_DOWN = 2
self.PROBE_UP = 3
self.ZERO_HEIGHT = 4
self.PIERCE_HEIGHT = 5
self.TORCH_ON = 6
self.ARC_OK = 7
self.PIERCE_DELAY = 8
self.PUDDLE_JUMP = 9
self.CUT_HEIGHT = 10
self.CUT_MODE_01 = 11
self.CUT_MODE_2 = 12
self.PAUSE_AT_END = 13
self.SAFE_HEIGHT = 14
self.MAX_HEIGHT = 15
self.END_CUT = 16
self.END_JOB = 17
self.TORCH_PULSE = 18
self.PAUSED_MOTION = 19
self.OHMIC_TEST = 20
self.PROBE_TEST = 21
self.SCRIBING = 22
self.CONS_CHNG_ON = 23
self.CONS_CHNG_OFF = 24
self.CUT_REC_ON = 25
self.CUT_REC_OFF = 26
self.DEBUG = 27
# main tab widget tab indexes
self.MAIN = 0
self.CONVERSATIONAL = 1
self.PARAMETERS = 2
self.SETTINGS = 3
self.STATISTICS = 4
# map index to name for interlock rules
self.tabNames = {
self.MAIN: 'main_tab',
self.CONVERSATIONAL: 'conversational_tab',
self.PARAMETERS: 'parameters_tab',
self.SETTINGS: 'settings_tab',
self.STATISTICS: 'statistics_tab',
}
# preview stack indexes
self.PREVIEW = 0
self.OPEN = 1
self.EDIT = 2
self.CAMERA = 3
self.OFFSETS = 4
self.USER_MANUAL = 5
# gcode stack indexes
self.GCODE = 0
self.MDI = 1
# jog stack indexes
self.JOG = 0
self.CUT_RECOVERY = 1
# tool indexes
self.TORCH = 0
self.SCRIBE = 1
self.UNKNOWN = 2
self.tool = self.TORCH
# called by qtvcp.py
def initialized__(self):
head = _translate('HandlerClass', 'QtPlasmaC GUI Message')
if '.'.join(linuxcnc.version.split('.')[:2]) != LCNCVER:
msg0 = _translate('HandlerClass', 'LinuxCNC version should be')
msg1 = _translate('HandlerClass', 'The detected version is')
msg2 = _translate('HandlerClass', 'QtPlasmac is closing')
msg = f'{head}:\n{msg0} {LCNCVER}\n\n{msg1} {linuxcnc.version.split(".")[:2]}\n\n{msg2}\n'
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, msg)
quit()
# if USER_M_PATH is not valid try to find a valid USER_M_PATH in the possible default locations
if self.mPath != 'valid' and not self.updateIni:
msg0 = _translate('HandlerClass', 'cannot be found in the path')
msg1 = _translate('HandlerClass', 'Please edit [RS274NGC]USER_M_PATH in the .ini file')
msg2 = _translate('HandlerClass', 'QtPlasmac is closing')
msg3 = _translate('HandlerClass', 'does exist in')
for path in ['/usr/share/doc/linuxcnc/examples/nc_files/plasmac/m_files', self.PATHS.BASEDIR, self.PATHS.CONFIGPATH]:
mPath = self.find_a_file('M190', path)
if mPath:
break
if not mPath:
msg3 = _translate('HandlerClass', 'does not exist in the default locations')
msg = f'{head}:\nM190 {msg0}:\n{":".join(self.mPath)}\n\n{msg1}\n\nM190 {msg3}:\n{mPath[:-5]}\n\n{msg2}\n'
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, msg)
quit()
ucFile = os.path.join(self.PATHS.CONFIGPATH, 'qtplasmac_custom.py')
if os.path.isfile(ucFile):
exec(compile(open(ucFile, 'rb').read(), ucFile, 'exec'))
# ensure we get all startup errors
STATUS.connect('error', self.error_update)
STATUS.connect('graphics-gcode-error', lambda o, e: self.error_update(o, linuxcnc.OPERATOR_ERROR, e))
STATUS.emit('update-machine-log', f'--- {self.machineTitle} ---', None)
self.make_hal_pins()
self.init_preferences()
self.hide_widgets()
self.init_widgets()
# hijack the qtvcp shutdown to our own close event
self.w.screen_options.QTVCP_INSTANCE_.closeEvent = self.closeEvent
self.w.button_frame.installEventFilter(self.w)
self.link_hal_pins()
self.statistics_init()
self.set_axes_and_joints()
self.set_spinbox_parameters()
self.load_plasma_parameters()
self.set_mode()
self.user_button_setup()
self.load_material_file()
self.offset_peripherals()
self.set_probe_offset_pins()
STATUS.connect('state-estop', lambda w: self.estop_state(True))
STATUS.connect('state-estop-reset', lambda w: self.estop_state(False))
STATUS.connect('state-on', lambda w: self.power_state(True))
STATUS.connect('state-off', lambda w: self.power_state(False))
STATUS.connect('hard-limits-tripped', self.hard_limit_tripped)
STATUS.connect('user-system-changed', self.user_system_changed)
STATUS.connect('file-loaded', self.file_loaded)
STATUS.connect('homed', self.joint_homed)
STATUS.connect('all-homed', self.joints_all_homed)
STATUS.connect('not-all-homed', self.joint_unhomed)
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('g-code-changed', self.gcodes_changed)
STATUS.connect('m-code-changed', self.mcodes_changed)
STATUS.connect('program-pause-changed', self.pause_changed)
STATUS.connect('graphics-loading-progress', self.progress_changed)
STATUS.connect('interp-paused', self.interp_paused)
STATUS.connect('interp-idle', self.interp_idle)
STATUS.connect('interp-reading', self.interp_reading)
STATUS.connect('interp-waiting', self.interp_waiting)
STATUS.connect('interp-run', self.interp_running)
STATUS.connect('jograte-changed', self.jog_rate_changed)
STATUS.connect('graphics-gcode-properties', lambda w, d: self.update_gcode_properties(d))
STATUS.connect('system_notify_button_pressed', self.system_notify_button_pressed)
STATUS.connect('tool-in-spindle-changed', self.tool_changed)
STATUS.connect('periodic', lambda w: self.update_periodic())
STATUS.connect('metric-mode-changed', self.metric_mode_changed)
STATUS.connect('motion-type-changed', lambda w, data: self.motion_type_changed(data))
STATUS.connect('reload-display', lambda w: self.g10_reload())
self.startupTimer = QTimer()
self.startupTimer.timeout.connect(self.startup_timeout)
self.startupTimer.setSingleShot(True)
self.shutdownTimer = QTimer()
self.shutdownTimer.timeout.connect(self.shutdown_timeout)
self.shutdownTime = 2000
self.laserTimer = QTimer()
self.laserTimer.timeout.connect(self.laser_timeout)
self.laserTimer.setSingleShot(True)
self.ohmicLedTimer = QTimer()
self.ohmicLedTimer.timeout.connect(self.ohmic_led_timeout)
self.ohmicLedTimer.setSingleShot(True)
self.watchDogTimer = QTimer()
self.watchDogTimer.timeout.connect(self.watchdog_has_bit)
self.watchDogTimer.setSingleShot(True)
self.watchDogState = None
self.set_color_styles()
self.vm_check()
# set hal pins only after initialized__ has begun
# some locales won't set pins before this phase
self.thcFeedRatePin.set(self.thcFeedRate)
self.pmPort = None
if self.PREFS.getpref('Port', '', str, 'POWERMAX'):
self.pmPort = self.PREFS.getpref('Port', '', str, 'POWERMAX')
if self.pmPort and self.pmx485_check(self.pmPort):
self.pmx485_startup(self.pmPort)
else:
self.w.gas_pressure.hide()
self.w.gas_pressure_label.hide()
self.w.cut_mode.hide()
self.w.cut_mode_label.hide()
self.w.pmx485_frame.hide()
self.w.pmx_stats_frame.hide()
if self.w.mdihistory.rows:
self.mdiLast = self.w.mdihistory.model.item(self.w.mdihistory.rows - 1).text()
else:
self.mdiLast = None
self.w.mdihistory.MDILine.spindle_inhibit(True)
self.w.mdihistory.MDILine.g92_inhibit(True)
if self.updateIni:
self.update_iniwrite()
updateLog = os.path.join(self.PATHS.CONFIGPATH, 'update_log.txt')
if self.updateData:
restart = False
msgType = linuxcnc.OPERATOR_TEXT
msgText = ''
with open(updateLog, 'a') as f:
for update in self.updateData:
if update[2]:
f.write(f'{time.strftime("%y-%m-%d")} {update[2]}\n')
msgText += f'{update[2]}\n'
if update[0]:
restart = True
if update[1]:
msgType = linuxcnc.OPERATOR_ERROR
head = _translate('HandlerClass', 'QtPlasmaC Configuration Update')
STATUS.emit('error', msgType, f'{head}:\n{msgText}\n')
if restart:
msg = _translate('HandlerClass', 'Due to configuration changes a restart is required')
STATUS.emit('error', linuxcnc.OPERATOR_TEXT, f'{head}:\n{msg}\n')
quit()
if not os.path.isfile(updateLog):
with open(updateLog, 'w') as f:
f.write(f'{time.strftime("%y-%m-%d")} Initial V{LCNCVER}-{VERSION}\n')
self.startupTimer.start(250)
# turning off autorepeat should always be the last thing intialized__ does
self.autorepeat_keys(False)
# called by qtvcp.py, can override qtvcp settings or qtvcp allowed user options (via INI)
def before_loop__(self):
self.w.setWindowTitle(self.machineTitle)
self.iconPath = 'share/icons/hicolor/scalable/apps/linuxcnc_alt/linuxcncicon_plasma.svg'
appPath = os.path.realpath(os.path.dirname(sys.argv[0]))
self.iconBase = '/usr' if appPath == '/usr/bin' else appPath.replace('/bin', '/debian/extras/usr')
self.w.setWindowIcon(QIcon(os.path.join(self.iconBase, self.iconPath)))
#########################################################################################################################
# CLASS PATCHING SECTION #
# note that the widget must be initialized before it can be patched (Example: gcodegraphics needs patched later)
#########################################################################################################################
# called by qtvcp.py
def class_patch__(self):
self.file_manager_patch()
self.gcode_editor_patch()
self.camview_patch()
self.offset_table_patch()
self.qt5_graphics_patch()
self.screen_options_patch()
# patched file manager functions
def file_manager_patch(self):
self.old_load = FILE_MAN.load
FILE_MAN.load = self.new_load
# remove temporary materials before loading a file
def new_load(self, fname=None):
try:
if fname is None:
self.w.filemanager._getPathActivated()
return
self.w.filemanager.recordBookKeeping()
self.remove_temp_materials()
ACTION.OPEN_PROGRAM(fname)
STATUS.emit('update-machine-log', 'Loaded: ' + fname, 'TIME')
except Exception as e:
LOG.error(f'Load file error: {e}')
STATUS.emit('error', linuxcnc.NML_ERROR, f'Load file error: {e}')
# patched gcode editor functions
def gcode_editor_patch(self):
self.old_saveReturn = EDITOR.saveReturn
EDITOR.saveReturn = self.new_saveReturn
self.old_openReturn = EDITOR.openReturn
EDITOR.openReturn = self.new_openReturn
self.old_exitCall = EDITOR.exitCall
EDITOR.exitCall = self.new_exitCall
self.old_gcodeLexerCall = EDITOR.gcodeLexerCall
EDITOR.gcodeLexerCall = self.new_gcodeLexerCall
self.old_pythonLexerCall = EDITOR.pythonLexerCall
EDITOR.pythonLexerCall = self.new_pythonLexerCall
self.old_returnFromDialog = EDITOR.returnFromDialog
EDITOR.returnFromDialog = self.new_returnFromDialog
DISPLAY.load_preference = self.new_load_preference
self.old_set_line_number = DISPLAY.set_line_number
DISPLAY.set_line_number = self.new_set_line_number
# save a non gcode file and don't load it into linuxcnc
def new_saveReturn(self, filename):
saved = ACTION.SAVE_PROGRAM(self.w.gcode_editor.editor.text(), filename)
if saved is not None:
self.w.gcode_editor.editor.setModified(False)
if saved[-3:] in ['ngc', '.nc', 'tap']:
self.preFileSaveMaterial = int(self.w.materials_box.currentText().split(': ', 1)[0])
if self.rflActive:
self.clear_rfl()
self.remove_temp_materials()
ACTION.OPEN_PROGRAM(saved)
# open a non gcode file and don't load it into linuxcnc
def new_openReturn(self, filename):
if filename[-3:] in ['ngc', '.nc', 'tap']:
self.remove_temp_materials()
ACTION.OPEN_PROGRAM(filename)
else:
self.w.gcode_editor.editor.load_text(filename)
self.w.gcode_editor.editor.setModified(False)
# modify the closing of the gcode editor
def new_exitCall(self, index):
proceed = self.editor_close_check()
if not proceed:
return
self.w.preview_stack.setCurrentIndex(self.PREVIEW)
if self.fileOpened and self.w.gcode_editor.editor.isModified():
self.w.gcode_editor.editor.setModified(False)
self.file_reload_clicked()
elif not self.fileOpened:
self.w.gcode_editor.editor.new_text()
self.w.gcode_editor.editor.setModified(False)
self.view_t_pressed(self.w.gcodegraphics)
self.w.gcode_editor.editMode()
self.vkb_hide()
ACTION.SET_MANUAL_MODE()
# we don't use lexer colors
def new_gcodeLexerCall(self):
pass
# we don't use lexer colors
def new_pythonLexerCall(self):
pass
# don't allow rfl.ngc as a file name
def new_returnFromDialog(self, w, message):
if message.get('NAME') == self.w.gcode_editor.load_dialog_code:
path = message.get('RETURN')
code = bool(message.get('ID') == '%s__' % self.w.gcode_editor.objectName())
if path and code:
self.w.gcode_editor.openReturn(path)
elif message.get('NAME') == self.w.gcode_editor.save_dialog_code:
path = message.get('RETURN')
code = bool(message.get('ID') == '%s__' % self.w.gcode_editor.objectName())
if path and code:
if not os.path.basename(path) in ['rfl', 'rfl.ngc']:
self.w.gcode_editor.saveReturn(path)
else:
head = _translate('HandlerClass', 'Save Error')
msg0 = _translate('HandlerClass', 'The file name')
msg1 = _translate('HandlerClass', 'is not allowed')
self.dialog_show_ok(QMessageBox.Warning, f'{head}', f'\n{msg0} "{os.path.basename(path)}" {msg1}\n\n')
self.w.gcode_editor.getSaveFileName()
return
# load the qtplasmac preferences file rather than the qtvcp preferences file
def new_load_preference(self, w):
self.w.gcode_editor.editor.load_text(self.prefsFile)
self.w.gcode_editor.editor.setCursorPosition(self.w.gcode_editor.editor.lines(), 0)
# dont highlight lines selected from the editor in the preview window
def new_set_line_number(self, line):
if self.w.sender():
if self.w.sender().objectName() == 'gcode_editor_display':
return
else:
STATUS.emit('gcode-line-selected', line+1)
# patched camera functions
def camview_patch(self):
self.old_wheelEvent = CAM.wheelEvent
CAM.wheelEvent = self.new_wheelEvent
self.old_drawText = CAM.drawText
CAM.drawText = self.new_drawText
self.old_mousePressEvent = CAM.mousePressEvent
CAM.mousePressEvent = self.new_mousePressEvent
self.old_mouseDoubleClickEvent = CAM.mouseDoubleClickEvent
CAM.mouseDoubleClickEvent = self.new_mouseDoubleClickEvent
# format the angle display
def new_drawText(self, event, qp):
qp.setPen(self.w.camview.text_color)
qp.setFont(self.w.camview.font)
if self.w.camview.pix:
qp.drawText(self.w.camview.rect(), QtCore.Qt.AlignTop, f'{self.w.camview.rotation:0.3f}{self.degreeSymbol}')
else:
qp.drawText(self.w.camview.rect(), QtCore.Qt.AlignCenter, self.w.camview.text)
# limit scale and diameter, don't allow mouse rotation
def new_wheelEvent(self, event):
mouseState = qApp.mouseButtons()
w = self.w.camview.size().width()
h = self.w.camview.size().height()
if event.angleDelta().y() < 0:
if mouseState == QtCore.Qt.NoButton:
self.w.camview.diameter -= 2
if mouseState == QtCore.Qt.LeftButton:
self.w.camview.scale -= .1
else:
if mouseState == QtCore.Qt.NoButton:
self.w.camview.diameter += 2
if mouseState == QtCore.Qt.LeftButton:
self.w.camview.scale += .1
if self.w.camview.diameter < 2:
self.w.camview.diameter = 2
if self.w.camview.diameter > w - 5:
self.w.camview.diameter = w - 5
if self.w.camview.diameter > h - 5:
self.w.camview.diameter = h - 5
if self.w.camview.scale < 1:
self.w.camview.scale = 1
if self.w.camview.scale > 5:
self.w.camview.scale = 5
# inhibit mouse single clicks
def new_mousePressEvent(self, event):
pass
# don't reset rotation with double click
def new_mouseDoubleClickEvent(self, event):
if event.button() & QtCore.Qt.LeftButton:
self.w.camview.scale = 1
elif event.button() & QtCore.Qt.MiddleButton:
self.w.camview.diameter = 20
# patched offset table functions
def offset_table_patch(self):
self.old_flags = OFFSET_TABLE.flags
OFFSET_TABLE.flags = self.new_flags
# we don't allow editing z axis or g92 offsets
def new_flags(self, index):
if not index.isValid():
return None
if index.column() == 9 and index.row() in (0, 1, 2, 3):
return Qt.ItemIsEnabled
elif index.row() == 0:
return Qt.ItemIsEnabled
elif index.row() == 1 and not index.column() == 2:
return Qt.NoItemFlags
# prevent z axis offset editing
elif index.column() == 2:
return Qt.ItemIsEnabled
# prevent g92 offset editing
elif index.row() == 2:
return Qt.ItemIsEnabled
else:
return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
# patched qt5_graphics functions
def qt5_graphics_patch(self):
self.old_dro_format = DRO.dro_format
DRO.dro_format = self.new_dro_format
# replace dro with current material
def new_dro_format(self, s, spd, dtg, limit, homed, positions, axisdtg, g5x_offset, g92_offset, tlo_offset):
text = self.get_overlay_text()
return limit, homed, text, text
# patched screen options functions
def screen_options_patch(self):
self.old_process_error = OPTIONS.process_error
OPTIONS.process_error = self.new_process_error
# we want custom notifications for jog errors
def new_process_error(self, w, kind, text):
self.realTimeDelay = False
O = self.w.screen_options
N = O.QTVCP_INSTANCE_._NOTICE
if 'jog-inhibit' in text:
if self.w.led_float_switch.hal_pin.get():
text = _translate('HandlerClass', 'Float Switch has disabled jogging')
elif self.ohmicLedInPin.get():
text = _translate('HandlerClass', 'Ohmic Probe has disabled jogging')
elif self.w.led_breakaway_switch.hal_pin.get():
text = _translate('HandlerClass', 'Breakaway Switch has disabled jogging')
elif 'jog-stop' in text or 'jog-stop-immediate' in text:
if self.w.led_float_switch.hal_pin.get():
text = _translate('HandlerClass', 'Float Switch has aborted active jogging')
elif self.ohmicLedInPin.get():
text = _translate('HandlerClass', 'Ohmic Probe has aborted active jogging')
elif self.w.led_breakaway_switch.hal_pin.get():
text = _translate('HandlerClass', 'Breakaway Switch has aborted active jogging')
elif self.virtualMachine and 'unexpected realtime delay' in text.lower():
text = f'Error suppressed:\n"{text.strip()}"\nRealtime delays are expected in a virtual environment'
self.realTimeDelay = True
if O.desktop_notify:
if 'on limit switch error' in text:
N.update(O.notify_hard_limits, title='Machine Error:', message=text, msgs=O.notify_max_msgs)
elif kind == linuxcnc.OPERATOR_ERROR and not self.realTimeDelay:
N.update(O.notify_critical, icon='dialog-error', title='Operator Error:', message=text, msgs=O.notify_max_msgs)
elif kind == linuxcnc.OPERATOR_TEXT:
N.update(O.notify_critical, icon='dialog-information', title='Operator Text:', message=text, msgs=O.notify_max_msgs)
elif kind == linuxcnc.OPERATOR_DISPLAY:
N.update(O.notify_critical, icon='dialog-messages', title='Operator Display:', message=text, msgs=O.notify_max_msgs)
elif kind == linuxcnc.NML_ERROR:
N.update(O.notify_critical, title='Internal NML Error:', message=text, msgs=O.notify_max_msgs)
elif kind == linuxcnc.NML_TEXT:
N.update(O.notify_critical, title='Internal NML Text:', message=text, msgs=O.notify_max_msgs)
elif kind == linuxcnc.NML_DISPLAY:
N.update(O.notify_critical, title='Internal NML Display:', message=text, msgs=O.notify_max_msgs)
elif kind == STATUS.TEMPORARY_MESSAGE:
N.update(O.notify_normal,
title='Operator Info:',
message=text,
status_timeout=0,
timeout=2,
msgs=O.notify_max_msgs)
if O.play_sounds and O.mchnMsg_play_sound:
STATUS.emit('play-sound', '%s' % O.mchnMsg_sound_type)
if O.mchnMsg_speak_errors:
if kind in (linuxcnc.OPERATOR_ERROR, linuxcnc.NML_ERROR):
STATUS.emit('play-sound', 'SPEAK %s ' % text)
if O.mchnMsg_speak_text:
if kind in (linuxcnc.OPERATOR_TEXT, linuxcnc.NML_TEXT,
linuxcnc.OPERATOR_DISPLAY, STATUS.TEMPORARY_MESSAGE):
STATUS.emit('play-sound', 'SPEAK %s ' % text)
STATUS.emit('update-machine-log', text, 'TIME')
#########################################################################################################################
# SPECIAL FUNCTIONS SECTION #
#########################################################################################################################
def make_hal_pins(self):
self.consChangePin = self.h.newpin('consumable_changing', hal.HAL_BIT, hal.HAL_IN)
self.convTabDisable = self.h.newpin('conv_disable', hal.HAL_BIT, hal.HAL_IN)
self.cutTypePin = self.h.newpin('cut_type', hal.HAL_S32, hal.HAL_IN)
self.developmentPin = self.h.newpin('development', hal.HAL_BIT, hal.HAL_IN)
self.extAbortPin = self.h.newpin('ext_abort', hal.HAL_BIT, hal.HAL_IN)
self.extAutoVoltsEnablePin = self.h.newpin('ext_auto_volts_enable', hal.HAL_BIT, hal.HAL_IN)
self.extChangeConsPin = self.h.newpin('ext_consumables', hal.HAL_BIT, hal.HAL_IN)
self.extCornerLockEnablePin = self.h.newpin('ext_cornerlock_enable', hal.HAL_BIT, hal.HAL_IN)
self.extCutRecCancelPin = self.h.newpin('ext_cutrec_cancel', hal.HAL_BIT, hal.HAL_IN)
self.extCutRecRevPin = self.h.newpin('ext_cutrec_rev', hal.HAL_BIT, hal.HAL_IN)
self.extCutRecFwdPin = self.h.newpin('ext_cutrec_fwd', hal.HAL_BIT, hal.HAL_IN)
self.extCutRecNPin = self.h.newpin('ext_cutrec_n', hal.HAL_BIT, hal.HAL_IN)
self.extCutRecNEPin = self.h.newpin('ext_cutrec_ne', hal.HAL_BIT, hal.HAL_IN)
self.extCutRecEPin = self.h.newpin('ext_cutrec_e', hal.HAL_BIT, hal.HAL_IN)
self.extCutRecSEPin = self.h.newpin('ext_cutrec_se', hal.HAL_BIT, hal.HAL_IN)
self.extCutRecSPin = self.h.newpin('ext_cutrec_s', hal.HAL_BIT, hal.HAL_IN)
self.extCutRecSWPin = self.h.newpin('ext_cutrec_sw', hal.HAL_BIT, hal.HAL_IN)
self.extCutRecWPin = self.h.newpin('ext_cutrec_w', hal.HAL_BIT, hal.HAL_IN)
self.extCutRecNWPin = self.h.newpin('ext_cutrec_nw', hal.HAL_BIT, hal.HAL_IN)
self.extFramingPin = self.h.newpin('ext_frame_job', hal.HAL_BIT, hal.HAL_IN)
self.extHeightOvrCountsPin = self.h.newpin('ext_height_ovr_counts', hal.HAL_S32, hal.HAL_IN)
self.extHeightOvrCountEnablePin = self.h.newpin('ext_height_ovr_count_enable', hal.HAL_BIT, hal.HAL_IN)
self.extHeightOvrMinusPin = self.h.newpin('ext_height_ovr_minus', hal.HAL_BIT, hal.HAL_IN)
self.extHeightOvrPlusPin = self.h.newpin('ext_height_ovr_plus', hal.HAL_BIT, hal.HAL_IN)
self.extHeightOvrResetPin = self.h.newpin('ext_height_ovr_reset', hal.HAL_BIT, hal.HAL_IN)
self.extHeightOvrScalePin = self.h.newpin('ext_height_ovr_scale', hal.HAL_FLOAT, hal.HAL_IN)
self.extIgnoreArcOkPin = self.h.newpin('ext_ignore_arc_ok', hal.HAL_BIT, hal.HAL_IN)
self.extJogSlowPin = self.h.newpin('ext_jog_slow', hal.HAL_BIT, hal.HAL_IN)
# self.extKerfCrossEnablePin = self.h.newpin('ext_kerfcross_enable', hal.HAL_BIT, hal.HAL_IN)
self.extLaserTouchOffPin = self.h.newpin('ext_laser_touchoff', hal.HAL_BIT, hal.HAL_IN)
self.extLaserTogglePin = self.h.newpin('ext_laser_toggle', hal.HAL_BIT, hal.HAL_IN)
self.extMeshModePin = self.h.newpin('ext_mesh_mode', hal.HAL_BIT, hal.HAL_IN)
self.extOhmicPin = self.h.newpin('ext_ohmic', hal.HAL_BIT, hal.HAL_IN)
self.extOhmicProbeEnablePin = self.h.newpin('ext_ohmic_probe_enable', hal.HAL_BIT, hal.HAL_IN)
self.extPausePin = self.h.newpin('ext_pause', hal.HAL_BIT, hal.HAL_IN)
self.extPauseOnlyPin = self.h.newpin('ext_pause_only', hal.HAL_BIT, hal.HAL_IN)
self.extPowerPin = self.h.newpin('ext_power', hal.HAL_BIT, hal.HAL_IN)
self.extProbePin = self.h.newpin('ext_probe', hal.HAL_BIT, hal.HAL_IN)
self.extPulsePin = self.h.newpin('ext_pulse', hal.HAL_BIT, hal.HAL_IN)
self.extResumePin = self.h.newpin('ext_resume', hal.HAL_BIT, hal.HAL_IN)
self.extRunPausePin = self.h.newpin('ext_run_pause', hal.HAL_BIT, hal.HAL_IN)
self.extRunPin = self.h.newpin('ext_run', hal.HAL_BIT, hal.HAL_IN)
self.extThcEnablePin = self.h.newpin('ext_thc_enable', hal.HAL_BIT, hal.HAL_IN)
self.extTorchEnablePin = self.h.newpin('ext_torch_enable', hal.HAL_BIT, hal.HAL_IN)
self.extTouchOffPin = self.h.newpin('ext_touchoff', hal.HAL_BIT, hal.HAL_IN)
self.extVoidLockEnablePin = self.h.newpin('ext_voidlock_enable', hal.HAL_BIT, hal.HAL_IN)
self.gcodeScalePin = self.h.newpin('gcode_scale', hal.HAL_FLOAT, hal.HAL_OUT)
self.heightOverridePin = self.h.newpin('height_override', hal.HAL_FLOAT, hal.HAL_OUT)
self.jogInhibited = self.h.newpin('jog_inhibited', hal.HAL_BIT, hal.HAL_IN)
self.laserOnPin = self.h.newpin('laser_on', hal.HAL_BIT, hal.HAL_OUT)
self.offsetSetProbePin = self.h.newpin('offset_set_probe', hal.HAL_BIT, hal.HAL_OUT)
self.offsetSetScribePin = self.h.newpin('offset_set_scribe', hal.HAL_BIT, hal.HAL_OUT)
self.laserRecStatePin = self.h.newpin('laser_recovery_state', hal.HAL_S32, hal.HAL_IN)
self.materialChangePin = self.h.newpin('material_change', hal.HAL_S32, hal.HAL_IN)
self.materialChangeNumberPin = self.h.newpin('material_change_number', hal.HAL_S32, hal.HAL_IN)
self.materialChangeTimeoutPin = self.h.newpin('material_change_timeout', hal.HAL_BIT, hal.HAL_IN)
self.materialReloadPin = self.h.newpin('material_reload', hal.HAL_BIT, hal.HAL_IN)
self.materialTempPin = self.h.newpin('material_temp', hal.HAL_S32, hal.HAL_IN)
self.offsetsActivePin = self.h.newpin('offsets_active', hal.HAL_BIT, hal.HAL_IN)
self.ohmicLedInPin = self.h.newpin('ohmic_led_in', hal.HAL_BIT, hal.HAL_IN)
self.paramTabDisable = self.h.newpin('param_disable', hal.HAL_BIT, hal.HAL_IN)
self.settingsTabDisable = self.h.newpin('settings_disable', hal.HAL_BIT, hal.HAL_IN)
self.simStyleUpdate = self.h.newpin('sim_style_update', hal.HAL_BIT, hal.HAL_OUT)
self.plasmacStatePin = self.h.newpin('plasmac_state', hal.HAL_S32, hal.HAL_IN)
self.plasmacStopPin = self.h.newpin('plasmac_stop', hal.HAL_S32, hal.HAL_IN)
self.pmx485CurrentPin = self.h.newpin('pmx485_current', hal.HAL_FLOAT, hal.HAL_IN)
self.pmx485CurrentMaxPin = self.h.newpin('pmx485_current_max', hal.HAL_FLOAT, hal.HAL_IN)
self.pmx485CurrentMinPin = self.h.newpin('pmx485_current_min', hal.HAL_FLOAT, hal.HAL_IN)
self.pmx485FaultPin = self.h.newpin('pmx485_fault', hal.HAL_FLOAT, hal.HAL_IN)
self.pmx485ModePin = self.h.newpin('pmx485_mode', hal.HAL_FLOAT, hal.HAL_IN)
self.pmx485PressurePin = self.h.newpin('pmx485_pressure', hal.HAL_FLOAT, hal.HAL_IN)
self.pmx485PressureMaxPin = self.h.newpin('pmx485_pressure_max', hal.HAL_FLOAT, hal.HAL_IN)
self.pmx485PressureMinPin = self.h.newpin('pmx485_pressure_min', hal.HAL_FLOAT, hal.HAL_IN)
self.pmx485StatusPin = self.h.newpin('pmx485_status', hal.HAL_BIT, hal.HAL_IN)
self.pmx485ArcTimePin = self.h.newpin('pmx485_arc_time', hal.HAL_FLOAT, hal.HAL_IN)
self.probeTestErrorPin = self.h.newpin('probe_test_error', hal.HAL_BIT, hal.HAL_IN)
self.out0Pin = self.h.newpin('ext_out_0', hal.HAL_BIT, hal.HAL_OUT)
self.out1Pin = self.h.newpin('ext_out_1', hal.HAL_BIT, hal.HAL_OUT)
self.out2Pin = self.h.newpin('ext_out_2', hal.HAL_BIT, hal.HAL_OUT)
self.sensorActive = self.h.newpin('sensor_active', hal.HAL_BIT, hal.HAL_IN)
self.tabsAlwaysEnabled = self.h.newpin('tabs_always_enabled', hal.HAL_BIT, hal.HAL_IN)
self.thcFeedRatePin = self.h.newpin('thc_feed_rate', hal.HAL_FLOAT, hal.HAL_OUT)
self.velReduct = self.h.newpin('vel_reduct', hal.HAL_FLOAT, hal.HAL_IN)
self.xOffsetPin = self.h.newpin('x_offset', hal.HAL_FLOAT, hal.HAL_IN)
self.yOffsetPin = self.h.newpin('y_offset', hal.HAL_FLOAT, hal.HAL_IN)
self.zHeightPin = self.h.newpin('z_height', hal.HAL_FLOAT, hal.HAL_IN)
self.zOffsetPin = self.h.newpin('z_offset_counts', hal.HAL_S32, hal.HAL_IN)
self.xMinPierceExtentPin = self.h.newpin('x_min_pierce_extent', hal.HAL_FLOAT, hal.HAL_IN)
self.xMaxPierceExtentPin = self.h.newpin('x_max_pierce_extent', hal.HAL_FLOAT, hal.HAL_IN)
self.yMinPierceExtentPin = self.h.newpin('y_min_pierce_extent', hal.HAL_FLOAT, hal.HAL_IN)
self.yMaxPierceExtentPin = self.h.newpin('y_max_pierce_extent', hal.HAL_FLOAT, hal.HAL_IN)
def link_hal_pins(self):
# arc parameters
CALL(['halcmd', 'net', 'plasmac:arc-fail-delay', 'qtplasmac.arc_fail_delay-f', 'plasmac.arc-fail-delay'])
CALL(['halcmd', 'net', 'plasmac:arc-max-starts', 'qtplasmac.arc_max_starts-s', 'plasmac.arc-max-starts'])
CALL(['halcmd', 'net', 'plasmac:restart-delay', 'qtplasmac.arc_restart_delay-f', 'plasmac.restart-delay'])
CALL(['halcmd', 'net', 'plasmac:arc-voltage-scale', 'qtplasmac.arc_voltage_scale-f', 'plasmac.arc-voltage-scale'])
CALL(['halcmd', 'net', 'plasmac:arc-voltage-offset', 'qtplasmac.arc_voltage_offset-f', 'plasmac.arc-voltage-offset'])
CALL(['halcmd', 'net', 'plasmac:height-per-volt', 'qtplasmac.height_per_volt-f', 'plasmac.height-per-volt'])
CALL(['halcmd', 'net', 'plasmac:arc-ok-high', 'qtplasmac.arc_ok_high-f', 'plasmac.arc-ok-high'])
CALL(['halcmd', 'net', 'plasmac:arc-ok-low', 'qtplasmac.arc_ok_low-f', 'plasmac.arc-ok-low'])
# thc parameters
CALL(['halcmd', 'net', 'plasmac:thc-feed-rate', 'qtplasmac.thc_feed_rate', 'plasmac.thc-feed-rate'])
CALL(['halcmd', 'net', 'plasmac:thc-auto', 'qtplasmac.thc_auto', 'plasmac.thc-auto'])
CALL(['halcmd', 'net', 'plasmac:thc-delay', 'qtplasmac.thc_delay-f', 'plasmac.thc-delay'])
CALL(['halcmd', 'net', 'plasmac:thc-sample-counts', 'qtplasmac.thc_sample_counts-s', 'plasmac.thc-sample-counts'])
CALL(['halcmd', 'net', 'plasmac:thc-sample-threshold', 'qtplasmac.thc_sample_threshold-f', 'plasmac.thc-sample-threshold'])
CALL(['halcmd', 'net', 'plasmac:thc-threshold', 'qtplasmac.thc_threshold-f', 'plasmac.thc-threshold'])
CALL(['halcmd', 'net', 'plasmac:pid-p-gain', 'qtplasmac.pid_p_gain-f', 'plasmac.pid-p-gain'])
CALL(['halcmd', 'net', 'plasmac:pid-i-gain', 'qtplasmac.pid_i_gain-f', 'plasmac.pid-i-gain'])
CALL(['halcmd', 'net', 'plasmac:pid-d-gain', 'qtplasmac.pid_d_gain-f', 'plasmac.pid-d-gain'])
CALL(['halcmd', 'net', 'plasmac:cornerlock-threshold', 'qtplasmac.cornerlock_threshold-f', 'plasmac.cornerlock-threshold'])
CALL(['halcmd', 'net', 'plasmac:voidlock-slope', 'qtplasmac.voidlock_slope-s', 'plasmac.voidlock-slope'])
# probe parameters
CALL(['halcmd', 'net', 'plasmac:float-switch-travel', 'qtplasmac.float_switch_travel-f', 'plasmac.float-switch-travel'])
CALL(['halcmd', 'net', 'plasmac:probe-feed-rate', 'qtplasmac.probe_feed_rate-f', 'plasmac.probe-feed-rate'])
CALL(['halcmd', 'net', 'plasmac:probe-start-height', 'qtplasmac.probe_start_height-f', 'plasmac.probe-start-height'])
CALL(['halcmd', 'net', 'plasmac:ohmic-probe-offset', 'qtplasmac.ohmic_probe_offset-f', 'plasmac.ohmic-probe-offset'])
CALL(['halcmd', 'net', 'plasmac:ohmic-max-attempts', 'qtplasmac.ohmic_max_attempts-s', 'plasmac.ohmic-max-attempts'])
CALL(['halcmd', 'net', 'plasmac:skip-ihs-distance', 'qtplasmac.skip_ihs_distance-f', 'plasmac.skip-ihs-distance'])
CALL(['halcmd', 'net', 'plasmac:slat-height', 'qtplasmac.slat_height-f', 'plasmac.slat-height'])
CALL(['halcmd', 'net', 'plasmac:offset-feed-rate', 'qtplasmac.offset_feed_rate-f', 'plasmac.offset-feed-rate'])
# safety parameters
CALL(['halcmd', 'net', 'plasmac:safe-height', 'qtplasmac.safe_height-f', 'plasmac.safe-height'])
# scribe parameters
CALL(['halcmd', 'net', 'plasmac:scribe-arm-delay', 'qtplasmac.scribe_arm_delay-f', 'plasmac.scribe-arm-delay'])
CALL(['halcmd', 'net', 'plasmac:scribe-on-delay', 'qtplasmac.scribe_on_delay-f', 'plasmac.scribe-on-delay'])
# spotting parameters
CALL(['halcmd', 'net', 'plasmac:spotting-threshold', 'qtplasmac.spotting_threshold-f', 'plasmac.spotting-threshold'])
CALL(['halcmd', 'net', 'plasmac:spotting-time', 'qtplasmac.spotting_time-f', 'plasmac.spotting-time'])
# motion parameters
CALL(['halcmd', 'net', 'plasmac:setup-feed-rate', 'qtplasmac.setup_feed_rate-f', 'plasmac.setup-feed-rate'])
# material parameters
CALL(['halcmd', 'net', 'plasmac:cut-feed-rate', 'qtplasmac.cut_feed_rate-f', 'plasmac.cut-feed-rate'])
CALL(['halcmd', 'net', 'plasmac:cut-height', 'qtplasmac.cut_height-f', 'plasmac.cut-height'])
CALL(['halcmd', 'net', 'plasmac:cut-volts', 'qtplasmac.cut_volts-f', 'plasmac.cut-volts'])
CALL(['halcmd', 'net', 'plasmac:kerf-width', 'qtplasmac.kerf_width-f', 'plasmac.kerf-width'])
CALL(['halcmd', 'net', 'plasmac:pause-at-end', 'qtplasmac.pause_at_end-f', 'plasmac.pause-at-end'])
CALL(['halcmd', 'net', 'plasmac:pierce-delay', 'qtplasmac.pierce_delay-f', 'plasmac.pierce-delay'])
CALL(['halcmd', 'net', 'plasmac:pierce-height', 'qtplasmac.pierce_height-f', 'plasmac.pierce-height'])
CALL(['halcmd', 'net', 'plasmac:puddle-jump-delay', 'qtplasmac.puddle_jump_delay-f', 'plasmac.puddle-jump-delay'])
CALL(['halcmd', 'net', 'plasmac:puddle-jump-height', 'qtplasmac.puddle_jump_height-f', 'plasmac.puddle-jump-height'])
CALL(['halcmd', 'net', 'plasmac:material-thickness', 'qtplasmac.material_thickness-f', 'plasmac.material-thickness'])
# monitor
CALL(['halcmd', 'net', 'plasmac:arc_ok_out', 'plasmac.arc-ok-out', 'qtplasmac.led_arc_ok'])
CALL(['halcmd', 'net', 'plasmac:arc_voltage_out', 'plasmac.arc-voltage-out', 'qtplasmac.arc_voltage'])
CALL(['halcmd', 'net', 'plasmac:breakaway-switch-out', 'qtplasmac.led_breakaway_switch'])
CALL(['halcmd', 'net', 'plasmac:cornerlock-is-locked', 'plasmac.cornerlock-is-locked', 'qtplasmac.led_corner_lock'])
CALL(['halcmd', 'net', 'plasmac:float-switch-out', 'qtplasmac.led_float_switch'])
CALL(['halcmd', 'net', 'plasmac:voidlock-is-locked', 'plasmac.voidlock-is-locked', 'qtplasmac.led_void_lock'])
CALL(['halcmd', 'net', 'plasmac:led-up', 'plasmac.led-up', 'qtplasmac.led_thc_up'])
CALL(['halcmd', 'net', 'plasmac:led-down', 'plasmac.led-down', 'qtplasmac.led_thc_down'])
CALL(['halcmd', 'net', 'plasmac:ohmic-probe-out', 'qtplasmac.ohmic_led_in'])
CALL(['halcmd', 'net', 'plasmac:thc-active', 'plasmac.thc-active', 'qtplasmac.led_thc_active'])
CALL(['halcmd', 'net', 'plasmac:thc-enabled', 'plasmac.thc-enabled', 'qtplasmac.led_thc_enabled'])
CALL(['halcmd', 'net', 'plasmac:torch-on', 'qtplasmac.led_torch_on'])
# control
CALL(['halcmd', 'net', 'plasmac:cornerlock-enable', 'qtplasmac.cornerlock_enable', 'plasmac.cornerlock-enable'])
CALL(['halcmd', 'net', 'plasmac:voidlock-enable', 'qtplasmac.voidlock_enable', 'plasmac.voidlock-enable'])
CALL(['halcmd', 'net', 'plasmac:mesh-enable', 'qtplasmac.mesh_enable', 'plasmac.mesh-enable'])
CALL(['halcmd', 'net', 'plasmac:ignore-arc-ok-1', 'qtplasmac.ignore_arc_ok', 'plasmac.ignore-arc-ok-1'])
CALL(['halcmd', 'net', 'plasmac:ohmic-probe-enable', 'qtplasmac.ohmic_probe_enable', 'plasmac.ohmic-probe-enable'])
CALL(['halcmd', 'net', 'plasmac:thc-enable', 'qtplasmac.thc_enable', 'plasmac.thc-enable'])
CALL(['halcmd', 'net', 'plasmac:use-auto-volts', 'qtplasmac.use_auto_volts', 'plasmac.use-auto-volts'])
CALL(['halcmd', 'net', 'plasmac:torch-enable', 'qtplasmac.torch_enable', 'plasmac.torch-enable'])
# offsets
CALL(['halcmd', 'net', 'plasmac:x-offset-current', 'qtplasmac.x_offset'])
CALL(['halcmd', 'net', 'plasmac:y-offset-current', 'qtplasmac.y_offset'])
CALL(['halcmd', 'net', 'plasmac:offsets-active', 'qtplasmac.offsets_active'])
# override
CALL(['halcmd', 'net', 'plasmac:height-override', 'qtplasmac.height_override', 'plasmac.height-override'])
# INI
CALL(['halcmd', 'net', 'plasmac:axis-x-max-limit', 'ini.x.max_limit', 'plasmac.axis-x-max-limit'])
CALL(['halcmd', 'net', 'plasmac:axis-x-min-limit', 'ini.x.min_limit', 'plasmac.axis-x-min-limit'])
CALL(['halcmd', 'net', 'plasmac:axis-y-max-limit', 'ini.y.max_limit', 'plasmac.axis-y-max-limit'])
CALL(['halcmd', 'net', 'plasmac:axis-y-min-limit', 'ini.y.min_limit', 'plasmac.axis-y-min-limit'])
CALL(['halcmd', 'net', 'plasmac:axis-z-max-limit', 'ini.z.max_limit', 'plasmac.axis-z-max-limit'])
CALL(['halcmd', 'net', 'plasmac:axis-z-min-limit', 'ini.z.min_limit', 'plasmac.axis-z-min-limit'])
# misc
CALL(['halcmd', 'net', 'plasmac:consumable-changing', 'plasmac.consumable-changing', 'qtplasmac.consumable_changing'])
CALL(['halcmd', 'net', 'plasmac:gcode-scale', 'plasmac.gcode-scale', 'qtplasmac.gcode_scale'])
CALL(['halcmd', 'net', 'plasmac:jog-inhibit', 'qtplasmac.jog_inhibited'])
CALL(['halcmd', 'net', 'plasmac:laser-on', 'qtplasmac.laser_on'])
CALL(['halcmd', 'net', 'plasmac:laser-recovery-state', 'plasmac.laser-recovery-state', 'qtplasmac.laser_recovery_state'])
CALL(['halcmd', 'net', 'plasmac:probe-test-error', 'plasmac.probe-test-error', 'qtplasmac.probe_test_error'])
CALL(['halcmd', 'net', 'plasmac:sensor_active', 'plasmac.sensor-active', 'qtplasmac.sensor_active'])
CALL(['halcmd', 'net', 'plasmac:state', 'plasmac.state-out', 'qtplasmac.plasmac_state'])
CALL(['halcmd', 'net', 'plasmac:stop', 'plasmac.stop-type-out', 'qtplasmac.plasmac_stop'])
CALL(['halcmd', 'net', 'plasmac:z-height', 'plasmac.z-height', 'qtplasmac.z_height'])
CALL(['halcmd', 'net', 'plasmac:z-offset-counts', 'qtplasmac.z_offset_counts'])
CALL(['halcmd', 'net', 'plasmac:offset-set-probe', 'plasmac.offset-set-probe', 'qtplasmac.offset_set_probe'])
CALL(['halcmd', 'net', 'plasmac:offset-set-scribe', 'plasmac.offset-set-scribe', 'qtplasmac.offset_set_scribe'])
CALL(['halcmd', 'net', 'plasmac:adaptive-feed', 'qtplasmac.vel_reduct'])
# *** add system hal pin changes here that may affect existing configs ***
# *** these may be removed after auto updating is implemented ***
if not hal.pin_has_writer('plasmac.feed-upm'): # if feed-upm is not yet connected in hal
CALL(['halcmd', 'net', 'plasmac:feed-upm', 'motion.feed-upm', 'plasmac.feed-upm'])
def init_preferences(self):
self.mode = self.PREFS.getpref('Mode', 0, int, 'GUI_OPTIONS')
self.autorepeat_skip = self.PREFS.getpref('Autorepeat all', False, bool, 'GUI_OPTIONS')
self.flash_error = self.PREFS.getpref('Flash error', False, bool, 'GUI_OPTIONS')
self.w.chk_keyboard_shortcuts.setChecked(self.PREFS.getpref('Use keyboard shortcuts', False, bool, 'GUI_OPTIONS'))
self.w.chk_soft_keyboard.setChecked(self.PREFS.getpref('Use soft keyboard', False, bool, 'GUI_OPTIONS'))
self.w.chk_overlay.setChecked(self.PREFS.getpref('Show materials', True, bool, 'GUI_OPTIONS'))
self.w.chk_run_from_line.setChecked(self.PREFS.getpref('Run from line', True, bool, 'GUI_OPTIONS'))
self.w.chk_tool_tips.setChecked(self.PREFS.getpref('Tool tips', True, bool, 'GUI_OPTIONS'))
self.w.chk_exit_warning.setChecked(self.PREFS.getpref('Exit warning', True, bool, 'GUI_OPTIONS'))
self.exitMessage = self.PREFS.getpref('Exit warning text', '', str, 'GUI_OPTIONS')
self.w.cone_size.setValue(self.PREFS.getpref('Preview cone size', 0.5, float, 'GUI_OPTIONS'))
self.w.grid_size.setValue(self.PREFS.getpref('Preview grid size', 0.0, float, 'GUI_OPTIONS'))
self.w.table_zoom_scale.setValue(self.PREFS.getpref('T view zoom scale', 1.0, float, 'GUI_OPTIONS'))
self.zPlusOverrideJog = self.PREFS.getpref('Override jog inhibit via Z+', False, bool, 'GUI_OPTIONS')
self.w.color_foregrnd.setStyleSheet(f'background-color: {self.PREFS.getpref("Foreground", "#ffee06", str, "COLOR_OPTIONS")}')
self.w.color_foregalt.setStyleSheet(f'background-color: {self.PREFS.getpref("Highlight", "#ffee06", str, "COLOR_OPTIONS")}')
self.w.color_led.setStyleSheet(f'background-color: {self.PREFS.getpref("LED", "#ffee06", str, "COLOR_OPTIONS")}')
self.w.color_backgrnd.setStyleSheet(f'background-color: {self.PREFS.getpref("Background", "#16160e", str, "COLOR_OPTIONS")}')
self.w.color_backgalt.setStyleSheet(f'background-color: {self.PREFS.getpref("Background Alt", "#36362e", str, "COLOR_OPTIONS")}')
self.w.color_frams.setStyleSheet(f'background-color: {self.PREFS.getpref("Frames", "#ffee06", str, "COLOR_OPTIONS")}')
self.w.color_estop.setStyleSheet(f'background-color: {self.PREFS.getpref("Estop", "#ff0000", str, "COLOR_OPTIONS")}')
self.w.color_disabled.setStyleSheet(f'background-color: {self.PREFS.getpref("Disabled", "#b0b0b0", str, "COLOR_OPTIONS")}')
self.w.color_preview.setStyleSheet(f'background-color: {self.PREFS.getpref("Preview", "#000000", str, "COLOR_OPTIONS")}')
self.lastLoadedProgram = self.w.PREFS_.getpref('RecentPath_0', 'None', str, 'BOOK_KEEPING')
TOOLTIPS.tool_tips_changed(self, self.w)
self.soft_keyboard()
self.cone_size_changed(self.w.cone_size.value())
self.grid_size_changed(self.w.grid_size.value())
self.set_basic_colors()
self.w.sd_text.setText(self.exitMessage)
def hide_widgets(self):
for b in ['RUN', 'PAUSE', 'ABORT']:
if self.PREFS.getpref(f'Hide {b.lower()}', False, bool, 'GUI_OPTIONS'):
self.w[b.lower()].hide()
if self.landscape:
self.w.machine_frame.setMaximumHeight(self.w.machine_frame.maximumHeight() - 44)
self.w.machine_frame.setMinimumHeight(self.w.machine_frame.maximumHeight())
def init_widgets(self):
droPos = self.PREFS.getpref('DRO position', 'bottom', str, 'GUI_OPTIONS')
if droPos.lower() == 'top':
# designer can change the layout name in the .ui file
# this will find the name and move the dro to the top
lay = self.w.dro_gcode_frame.children()[0].objectName()
self.w[lay].removeWidget(self.w.dro_frame)
self.w[lay].insertWidget(0, self.w.dro_frame)
text = _translate('HandlerClass', 'CONTINUOUS')
self.w.jogincrements.setItemText(0, text)
self.w.main_tab_widget.setCurrentIndex(self.MAIN)
self.w.preview_stack.setCurrentIndex(self.PREVIEW)
self.prevPreviewIndex = self.PREVIEW
self.w.gcode_stack.setCurrentIndex(self.GCODE)
self.w.jog_stack.setCurrentIndex(self.JOG)
self.w.thc_enable.setChecked(self.PREFS.getpref('THC enable', True, bool, 'ENABLE_OPTIONS'))
self.w.cornerlock_enable.setChecked(self.PREFS.getpref('Corner lock enable', True, bool, 'ENABLE_OPTIONS'))
self.w.voidlock_enable.setChecked(self.PREFS.getpref('Void lock enable', False, bool, 'ENABLE_OPTIONS'))
self.w.use_auto_volts.setChecked(self.PREFS.getpref('Use auto volts', True, bool, 'ENABLE_OPTIONS'))
self.w.ohmic_probe_enable.setChecked(self.PREFS.getpref('Ohmic probe enable', False, bool, 'ENABLE_OPTIONS'))
self.w.thc_auto.setChecked(self.PREFS.getpref('THC auto', False, bool, 'ENABLE_OPTIONS'))
self.thc_auto_changed(self.w.thc_auto.isChecked())
self.w.error_label = QLabel()
self.w.tool_label = STATLABEL()
self.w.gcodes_label = STATLABEL()
self.w.mcodes_label = STATLABEL()
self.w.lbl_tool = STATLABEL()
self.w.lbl_gcodes = STATLABEL()
self.w.lbl_mcodes = STATLABEL()
self.w.statusbar.addPermanentWidget(self.w.error_label, stretch=1)
self.w.error_label.setObjectName('error_label')
self.w.statusbar.addPermanentWidget(VLine())
self.w.statusbar.addPermanentWidget(self.w.tool_label)
self.w.tool_label.setObjectName('tool_label')
self.w.tool_label.setText('TOOL: ')
self.w.statusbar.addPermanentWidget(self.w.lbl_tool)
self.w.lbl_tool.setObjectName('lbl_tool')
self.w.statusbar.addPermanentWidget(VLine())
self.w.statusbar.addPermanentWidget(self.w.gcodes_label)
self.w.gcodes_label.setObjectName('gcodes_label')
self.w.gcodes_label.setText('G-CODES: ')
self.w.statusbar.addPermanentWidget(self.w.lbl_gcodes)
self.w.lbl_gcodes.setObjectName('lbl_gcodes')
self.w.statusbar.addPermanentWidget(VLine())
self.w.statusbar.addPermanentWidget(self.w.mcodes_label)
self.w.mcodes_label.setObjectName('mcodes_label')
self.w.mcodes_label.setText('M-CODES: ')
self.w.statusbar.addPermanentWidget(self.w.lbl_mcodes)
self.w.lbl_mcodes.setObjectName('lbl_mcodes')
text = _translate('HandlerClass', 'MOVE')
self.w.cut_rec_move_label.setText(f'{text}\n{self.w.kerf_width.text()}')
self.w.filemanager.windowLayout.setContentsMargins(4, 0, 4, 0)
self.w.filemanager.windowLayout.setSpacing(4)
# hide load button (redundant to select button)
self.w.filemanager.loadButton.hide()
# for copy/paste control if required
self.w.filemanager.copy_control.hide()
# add vertical and horizontal scroll bars to the materials QComboBoxs
self.w.material_selector.view().setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.w.material_selector.view().setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.w.conv_material.view().setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.w.conv_material.view().setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.w.materials_box.view().setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.w.materials_box.view().setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.w.gcode_display.set_margin_metric(3)
self.w.gcode_display.setBraceMatching(False)
self.w.gcode_display.setCaretWidth(0)
self.w.gcode_display.setCornerWidget(QLabel())
self.w.gcode_editor.topBox.setContentsMargins(4, 4, 4, 4)
self.w.gcode_editor.bottomBox.setContentsMargins(4, 4, 4, 4)
self.w.gcode_editor.set_margin_metric(3)
self.w.gcode_editor.editor.setBraceMatching(False)
self.w.gcode_editor.editor.setCaretWidth(4)
self.w.gcode_editor.editor.setCornerWidget(QLabel())
self.w.gcode_editor.editMode()
self.w.gcode_editor.pythonLexerAction.setVisible(False)
self.w.gcode_editor.gCodeLexerAction.setVisible(False)
self.w.gcode_editor.label.setText('')
self.w.gcodegraphics.enable_dro = True
self.w.gcodegraphics.set_alpha_mode(True)
self.w.gcodegraphics.setShowOffsets(False)
self.w.gcodegraphics._font = 'monospace 11'
self.w.conv_preview.enable_dro = True
self.w.conv_preview.set_cone_basesize(0.1)
self.w.conv_preview.set_view('Z')
self.w.conv_preview.show_tool = False
self.w.conv_preview.show_limits = False
self.w.conv_preview.show_small_origin = False
self.w.conv_preview.set_alpha_mode(True)
self.w.conv_preview.setShowOffsets(False)
self.w.conv_preview._font = 'monospace 11'
self.w.conv_preview.inhibit_selection = True
self.w.conv_preview.update()
self.w.conv_preview.setInhibitControls(True)
self.w.estopButton = self.PREFS.getpref('Estop type', 0, int, 'GUI_OPTIONS')
if self.w.estopButton == 0:
self.w.estop.setEnabled(False)
if self.w.estopButton == 1:
self.w.estop.hide()
self.w.camview.cross_color = QtCore.Qt.red
self.w.camview.cross_pointer_color = QtCore.Qt.red
self.w.camview.font = QFont('arial,helvetica', 16)
self.flashState = False
self.flashRate = 4
self.flasher = self.flashRate
self.manualCut = False
self.jogPreManCut = [False, INFO.DEFAULT_LINEAR_JOG_VEL, 0]
self.probeTest = False
self.torchPulse = False
self.rflSelected = False
self.fileOpened = False
self.fileClear = False
self.error_present = False
self.laserButtonState = 'laser'
self.camButtonState = 'markedge'
self.overlayProgress = QProgressBar(self.w.gcode_display)
self.overlayProgress.setOrientation(Qt.Vertical)
self.overlayProgress.setInvertedAppearance(True)
self.overlayProgress.setFormat('')
self.overlayProgress.hide()
self.camNum = self.PREFS.getpref('Camera port', 0, int, 'CAMERA_OFFSET')
def get_overlay_text(self):
text = ['']
if not self.w.chk_overlay.isChecked():
return text
scale = 1
if self.units == 'in' and self.w.dro_z.display_units_mm:
scale = 25.4
elif self.units == 'mm' and not self.w.dro_z.display_units_mm:
scale = 1 / 25.4
if not self.w.dro_z.display_units_mm:
fr = 1
ou = 3
else:
fr = 0
ou = 2
dp = '.' if '.' in self.w.pierce_height.text() else ','
if '.' in self.w.cut_feed_rate.text().replace(",", ".") and len(self.w.cut_feed_rate.text().replace(",", ".").split('.')[0]) > 3:
text.append(f'FR: {float(self.w.cut_feed_rate.text().replace(",", ".").split(".")[0]) * scale:.{fr}f}'.replace(".", dp))
else:
text.append(f'FR: {float(self.w.cut_feed_rate.text().replace(",", ".")) * scale:.{fr}f}'.replace(".", dp))
text.append(f'PH: {float(self.w.pierce_height.text().replace(",", ".")) * scale:.{ou}f}'.replace(".", dp))
text.append(f'PD: {self.w.pierce_delay.text()}')
text.append(f'CH: {float(self.w.cut_height.text().replace(",", ".")) * scale:.{ou}f}'.replace(".", dp))
text.append(f'KW: {float(self.w.kerf_width.text().replace(",", ".")) * scale:.{ou}f}'.replace(".", dp))
if self.pmx485Exists:
text.append(f'CA: {self.w.cut_amps.text()}')
return text
def offset_peripherals(self):
self.camOffsetX = 0.0
self.camOffsetY = 0.0
self.laserOffsetX = 0.0
self.laserOffsetY = 0.0
self.probeOffsetX = 0.0
self.probeOffsetY = 0.0
self.probeDelay = 0.0
head = _translate('HandlerClass', 'Prefs File Error')
# laser
try:
self.laserOffsetX = self.PREFS.getpref('X axis', 0.0, float, 'LASER_OFFSET')
self.laserOffsetY = self.PREFS.getpref('Y axis', 0.0, float, 'LASER_OFFSET')
except:
self.w.laser.hide()
msg0 = _translate('HandlerClass', 'Invalid entry for laser offset')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}\n')
if not (self.laserOffsetX or self.laserOffsetY):
self.w.laser.hide()
# camera
try:
self.camOffsetX = self.PREFS.getpref('X axis', 0.0, float, 'CAMERA_OFFSET')
self.camOffsetY = self.PREFS.getpref('Y axis', 0.0, float, 'CAMERA_OFFSET')
except:
self.w.camera.hide()
msg0 = _translate('HandlerClass', 'Invalid entry for camera offset')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}\n')
if self.camOffsetX or self.camOffsetY:
self.w.camview.set_camnum(self.camNum)
else:
self.w.camera.hide()
# probing
try:
self.probeOffsetX = self.PREFS.getpref('X axis', 0.0, float, 'OFFSET_PROBING')
self.probeOffsetY = self.PREFS.getpref('Y axis', 0.0, float, 'OFFSET_PROBING')
self.probeDelay = self.PREFS.getpref('Delay', 0.0, float, 'OFFSET_PROBING')
except:
self.w.offset_feed_rate.hide()
self.w.offset_feed_rate_lbl.hide()
msg0 = _translate('HandlerClass', 'Invalid entry for probe offset')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}\n')
if not self.probeOffsetX and not self.probeOffsetY:
self.w.offset_feed_rate.hide()
self.w.offset_feed_rate_lbl.hide()
# called by the modified closeEvent function in this handler
def closing_cleanup__(self):
# disconnect powermax
self.w.pmx485_enable.setChecked(False)
# close soft keyboard
self.vkb_hide()
# turn autorepeat back on for the OS
self.autorepeat_keys(True)
# save the log files
self.save_logfile(5)
# save preferences
if not self.PREFS:
return
self.PREFS.putpref('Use keyboard shortcuts', self.w.chk_keyboard_shortcuts.isChecked(), bool, 'GUI_OPTIONS')
self.PREFS.putpref('Use soft keyboard', self.w.chk_soft_keyboard.isChecked(), bool, 'GUI_OPTIONS')
self.PREFS.putpref('Show materials', self.w.chk_overlay.isChecked(), bool, 'GUI_OPTIONS')
self.PREFS.putpref('Run from line', self.w.chk_run_from_line.isChecked(), bool, 'GUI_OPTIONS')
self.PREFS.putpref('Tool tips', self.w.chk_tool_tips.isChecked(), bool, 'GUI_OPTIONS')
self.PREFS.putpref('Exit warning', self.w.chk_exit_warning.isChecked(), bool, 'GUI_OPTIONS')
self.PREFS.putpref('Exit warning text', self.exitMessage, str, 'GUI_OPTIONS')
self.PREFS.putpref('Preview cone size', self.w.cone_size.value(), float, 'GUI_OPTIONS')
self.PREFS.putpref('Preview grid size', self.w.grid_size.value(), float, 'GUI_OPTIONS')
self.PREFS.putpref('T view zoom scale', self.w.table_zoom_scale.value(), float, 'GUI_OPTIONS')
self.PREFS.putpref('THC auto', self.w.thc_auto.isChecked(), bool, 'ENABLE_OPTIONS')
self.PREFS.putpref('Override jog inhibit via Z+', self.zPlusOverrideJog, bool, 'GUI_OPTIONS')
self.PREFS.putpref('THC enable', self.w.thc_enable.isChecked(), bool, 'ENABLE_OPTIONS')
self.PREFS.putpref('Corner lock enable', self.w.cornerlock_enable.isChecked(), bool, 'ENABLE_OPTIONS')
self.PREFS.putpref('Void lock enable', self.w.voidlock_enable.isChecked(), bool, 'ENABLE_OPTIONS')
self.PREFS.putpref('Use auto volts', self.w.use_auto_volts.isChecked(), bool, 'ENABLE_OPTIONS')
self.PREFS.putpref('Ohmic probe enable', self.w.ohmic_probe_enable.isChecked(), bool, 'ENABLE_OPTIONS')
def save_logfile(self, numLogs):
logPre = 'machine_log_'
logFiles = []
logFiles = [f for f in os.listdir(self.PATHS.CONFIGPATH) if f.startswith(logPre)]
logFiles.sort()
if len(logFiles) > (numLogs - 1):
for f in range(0, len(logFiles) - (numLogs - 1)):
os.remove(logFiles[0])
logFiles = logFiles[1:]
text = self.w.machinelog.getLogText()
logName = f'{self.PATHS.CONFIGPATH}/{logPre}{time.strftime("%y-%m-%d_%H-%M-%S")}.txt'
with open(logName, 'w') as f:
f.write(text)
# called by qt_makegui.py
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 (Qt.Key_Escape, Qt.Key_F1, Qt.Key_F2):
# 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
allowTab = False
mdiBlank = False
receiver2 = receiver
while receiver2 is not None and not flag:
if isinstance(receiver2, QtWidgets.QDialog):
flag = True
break
if isinstance(receiver2, QtWidgets.QListView):
flag = True
break
if isinstance(receiver2, FILE_MAN):
flag = True
break
if isinstance(receiver2, MDI_LINE):
if self.w.mdihistory.MDILine.text().rstrip() == '':
mdiBlank = True
flag = True
break
if isinstance(receiver2, MDI_HISTORY):
if self.w.mdihistory.MDILine.text().rstrip() == '':
mdiBlank = True
flag = True
break
if isinstance(receiver2, EDITOR):
flag = True
break
if isinstance(receiver2, DOUBLESCALE):
flag = True
break
if isinstance(receiver2, OFFSETVIEW):
flag = True
break
if self.w.main_tab_widget.currentIndex() not in (self.MAIN, self.STATISTICS):
allowTab = True
flag = True
break
receiver2 = receiver2.parent()
if flag:
if is_pressed:
if allowTab and (code == Qt.Key_Tab or code == Qt.Key_BackTab):
self.keyPressEvent(event)
else:
receiver.keyPressEvent(event)
if mdiBlank and (code == Qt.Key_Return or code == Qt.Key_Enter):
self.keyPressEvent(event)
event.accept()
return True
else:
event.accept()
return True
if event.isAutoRepeat():
return True
return KEYBIND.manage_function_calls(self, event, is_pressed, key, shift, cntrl)
def size_changed(self, object):
bHeight = self.w.file_edit.height() + 4
rows = int((object.height() - 24) / bHeight)
if self.landscape:
if rows == 4:
self.buttons_hide(7, 20)
elif rows == 5:
self.buttons_hide(9, 20)
self.buttons_show(7, 8)
elif rows == 6:
self.buttons_hide(11, 20)
self.buttons_show(7, 10)
elif rows == 7:
self.buttons_hide(13, 20)
self.buttons_show(7, 12)
elif rows == 8:
self.buttons_hide(15, 20)
self.buttons_show(7, 14)
elif rows == 9:
self.buttons_hide(17, 20)
self.buttons_show(7, 16)
elif rows == 10:
self.buttons_hide(19, 20)
self.buttons_show(7, 18)
else:
self.buttons_show(7, 20)
else:
if rows == 15:
self.buttons_hide(16, 20)
self.buttons_show(9, 15)
if rows == 16:
self.buttons_hide(17, 20)
self.buttons_show(9, 16)
elif rows == 17:
self.buttons_hide(18, 20)
self.buttons_show(9, 17)
elif rows == 18:
self.buttons_hide(19, 20)
self.buttons_show(9, 18)
elif rows == 19:
self.buttons_hide(19, 20)
self.buttons_show(9, 19)
else:
self.buttons_show(9, 20)
def buttons_hide(self, first, last):
for b in range(first, last + 1):
self.w[f'button_{b}'].hide()
def buttons_show(self, first, last):
for b in range(first, last + 1):
self.w[f'button_{b}'].show()
#########################################################################################################################
# CALLBACKS FROM STATUS #
#########################################################################################################################
def estop_state(self, state):
if state:
self.w.power.setChecked(False)
if not self.firstRun:
log = _translate('HandlerClass', 'Emergency stop pressed')
STATUS.emit('update-machine-log', log, 'TIME')
else:
if not self.firstRun:
log = _translate('HandlerClass', 'Emergency stop cleared')
STATUS.emit('update-machine-log', log, 'TIME')
def power_state(self, state):
if state:
self.w.power.setChecked(True)
if not self.firstRun:
log = _translate('HandlerClass', 'GUI power on')
STATUS.emit('update-machine-log', log, 'TIME')
else:
self.w.power.setChecked(False)
if self.tpButton and self.torchPulse:
self.torch_pulse_abort()
if self.ptButton and hal.get_value('plasmac.probe-test'):
self.probe_test_stop()
log = _translate('HandlerClass', 'Probe test aborted due to GUI power off')
STATUS.emit('update-machine-log', log, 'TIME')
if not self.firstRun:
log = _translate('HandlerClass', 'GUI power off')
STATUS.emit('update-machine-log', log, 'TIME')
self.refresh_button_states()
def interp_idle(self, obj):
hal.set_p('plasmac.consumable-change', '0')
if self.single_cut_request:
self.single_cut_request = False
if self.oldFile and self.fileOpened:
self.remove_temp_materials()
ACTION.OPEN_PROGRAM(self.oldFile)
else:
self.fileOpened = True
self.file_clear_clicked()
if self.g91:
ACTION.CALL_MDI_WAIT('G91')
self.w.jog_stack.setCurrentIndex(self.JOG)
if self.ccButton and not self.button_normal_check(self.ccButton):
self.button_normal(self.ccButton)
if self.laserDryRunCoords:
oldX = STATUS.get_position()[0][0] - self.laserDryRunCoords[0]
oldY = STATUS.get_position()[0][1] - self.laserDryRunCoords[1]
units = '20' if self.gcodeProps['gcode_units'] == 'in' else '21'
ACTION.CALL_MDI_WAIT(f'G{units} G10 L20 P0 X{oldX / self.boundsMultiplier} Y{oldY / self.boundsMultiplier}')
self.laserDryRunCoords = None
self.laserOnPin.set(0)
hal.set_p('plasmac.dry-run', '0')
self.w.gcodegraphics.logger.clear()
if self.framing:
self.framing = False
self.laserOnPin.set(0)
self.w.gcodegraphics.logger.clear()
if self.w.preview_stack.currentIndex() != self.PREVIEW:
self.preview_stack_changed()
if self.jobRunning and obj:
if self.w.torch_enable.isChecked():
self.statistics_save()
else:
self.jobRunning = False
log = _translate('HandlerClass', 'Machine idle')
STATUS.emit('update-machine-log', log, 'TIME')
self.statistics_show()
ACTION.SET_MANUAL_MODE()
self.refresh_button_states()
def interp_paused(self, obj):
pass
def interp_running(self, obj):
if STATUS.is_auto_mode() and self.w.gcode_stack.currentIndex() != self.GCODE:
self.w.gcode_stack.setCurrentIndex(self.GCODE)
self.refresh_button_states()
def interp_reading(self, obj):
pass
def interp_waiting(self, obj):
pass
def pause_changed(self, obj, state):
if state:
log = _translate('HandlerClass', 'Cycle paused')
STATUS.emit('update-machine-log', log, 'TIME')
else:
self.w.jog_stack.setCurrentIndex(self.JOG)
self.refresh_button_states()
def jog_rate_changed(self, object, value):
msg0 = _translate('HandlerClass', 'JOG')
self.w.jogs_label.setText(f'{msg0}\n{STATUS.get_jograte():.0f}')
def progress_changed(self, object, percent):
if percent < 0:
self.overlayProgress.setValue(0)
self.overlayProgress.hide()
self.w.gcode_display.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.w.gcode_display.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
else:
self.overlayProgress.setValue(percent)
self.overlayProgress.show()
self.overlayProgress.setFixedHeight(self.w.gcode_display.geometry().height())
self.overlayProgress.move(self.w.gcode_display.geometry().width() - 20, 0)
self.w.gcode_display.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.w.gcode_display.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
def user_system_changed(self, obj, data):
userSys = self.systemList[int(data)]
self.w.wcs_button.setText(f'WCS\n{userSys}')
if ACTION.prefilter_path:
self.file_reload_clicked()
def file_loaded(self, obj, filename):
if filename is not None:
if 'qtplasmac_file_clear.ngc' in filename:
self.fileClear = True
self.overlayProgress.setValue(0)
if not any(name in filename for name in ['qtplasmac_file_clear.ngc', 'single_cut.ngc']):
self.lastLoadedProgram = filename
if not self.cameraOn:
self.preview_index_return(self.w.preview_stack.currentIndex())
self.w.file_open.setText(os.path.basename(filename))
if not self.single_cut_request:
self.fileOpened = True
text = _translate('HandlerClass', 'EDIT')
self.w.edit_label.setText(f'{text}: {filename}')
if self.w.gcode_stack.currentIndex() != self.GCODE:
self.w.gcode_stack.setCurrentIndex(self.GCODE)
self.w.gcodegraphics.logger.clear()
self.w.gcodegraphics.clear_highlight()
if self.preRflFile and self.preRflFile != ACTION.prefilter_path:
self.rflActive = False
self.startLine = 0
self.preRflFile = ''
self.fileBoundsError, fileErrMsg = self.bounds_check_file()
self.probeBoundsError, probeErrMsg = self.bounds_check_probe(False)
errMsg = fileErrMsg + probeErrMsg
if self.fileBoundsError or self.probeBoundsError:
head = _translate('HandlerClass', 'Axis Limit Error')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{errMsg}\n')
if self.single_cut_request:
self.single_cut_request = False
if self.oldFile and 'single_cut' not in self.oldFile:
self.remove_temp_materials()
ACTION.OPEN_PROGRAM(self.oldFile)
else:
# allows file clear to work on error
self.fileOpened = True
else:
if self.single_cut_request:
ACTION.RUN()
if self.fileClear:
self.fileClear = False
self.fileOpened = False
text = _translate('HandlerClass', 'OPEN')
self.w.file_open.setText(text)
text = _translate('HandlerClass', 'EDIT')
self.w.edit_label.setText(f'{text}')
self.w.gcode_editor.editor.new_text()
self.w.gcode_editor.editor.setModified(False)
self.w.gcode_display.new_text()
self.w.gcode_display.set_margin_metric(3)
self.w.gcode_editor.set_margin_metric(3)
ACTION.SET_MANUAL_MODE()
if not len(self.w.gcode_display.text()):
self.w.gcode_display.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
if self.w.main_tab_widget.currentIndex() != self.MAIN:
self.w.main_tab_widget.setCurrentIndex(self.MAIN)
# forces the view to remain "table view" if T is checked when a file is loaded, or change to table view upon clicking CLEAR
if self.w.view_t.isChecked() or 'qtplasmac_file_clear.ngc' in filename:
self.view_t_pressed(self.w.gcodegraphics)
if 'single_cut.ngc' not in filename:
self.preSingleCutMaterial = None
# remove unused temporary materials from comboboxes
self.getMaterialBusy = True
if int(self.w.materials_box.currentText().split(": ", 1)[0]) not in self.materialList:
self.change_material(self.defaultMaterial)
for idx in range(self.w.materials_box.count() - 1, -1, -1):
matNum = int(self.w.materials_box.itemText(idx).split(': ', 1)[0])
if matNum < 1000000:
break
if matNum not in self.materialList:
self.w.materials_box.removeItem(idx)
self.w.material_selector.removeItem(idx)
self.w.conv_material.removeItem(idx)
self.getMaterialBusy = False
self.refresh_button_states()
def joints_all_homed(self, obj):
self.interp_idle(None)
if not self.firstHoming:
ACTION.CALL_MDI_WAIT('T0 M6')
ACTION.SET_MANUAL_MODE()
self.firstHoming = True
self.w.gcodegraphics.update()
self.w.conv_preview.update()
log = _translate('HandlerClass', 'Machine homed')
STATUS.emit('update-machine-log', log, 'TIME')
def joint_homed(self, obj, joint):
dro = self.coordinates[int(joint)]
self.w[f'dro_{dro}'].setProperty('homed', True)
self.w[f'dro_{dro}'].setStyle(self.w[f'dro_{dro}'].style())
self.w[f'dro_label_{dro}'].setProperty('homed', True)
self.w[f'dro_label_{dro}'].setStyle(self.w[f'dro_label_{dro}'].style())
self.w.update
STATUS.emit('dro-reference-change-request', 1)
self.w.gcodegraphics.logger.clear()
def joint_unhomed(self, obj, joints):
for joint in joints:
dro = self.coordinates[int(joint)]
self.w[f'dro_{dro}'].setProperty('homed', False)
self.w[f'dro_{dro}'].setStyle(self.w[f'dro_{dro}'].style())
self.w[f'dro_label_{dro}'].setProperty('homed', False)
self.w[f'dro_label_{dro}'].setStyle(self.w[f'dro_label_{dro}'].style())
if len(joints) < len(self.coordinates):
self.w.home_all.setEnabled(True)
self.w.update
STATUS.emit('dro-reference-change-request', 1)
self.interp_idle(None)
self.w.gcodegraphics.update()
self.w.conv_preview.update()
def hard_limit_tripped(self, obj, tripped, list_of_tripped):
self.w.chk_override_limits.setEnabled(tripped)
if not tripped:
self.w.chk_override_limits.setChecked(False)
def jog_inhibited_changed(self, state):
if state:
self.w.chk_override_jog.setEnabled(state)
self.w.releaseKeyboard()
else:
if not self.w.led_float_switch.hal_pin.get() and not self.ohmicLedInPin.get() and \
not self.w.led_breakaway_switch.hal_pin.get():
self.w.chk_override_jog.setChecked(False)
hal.set_p('plasmac.override-jog', '0')
def sensor_active_changed(self, state):
if not state:
self.w.chk_override_jog.setEnabled(False)
self.w.chk_override_jog.setChecked(False)
hal.set_p('plasmac.override-jog', str(state))
def z_offset_changed(self, height):
currentState = self.plasmacStatePin.get()
if currentState in [self.PROBE_HEIGHT, self.PROBE_DOWN, self.PROBE_UP,
self.ZERO_HEIGHT, self.PIERCE_HEIGHT, self.PUDDLE_JUMP,
self.CUT_HEIGHT, self.SAFE_HEIGHT, self.MAX_HEIGHT]:
self.watchDogState = currentState
self.watchDogTimer.start(3000)
if STATUS.is_interp_paused() and not height and \
(hal.get_value('plasmac.stop-type-out') or hal.get_value('plasmac.cut-recovering')):
self.w.set_cut_recovery()
def override_jog_changed(self, state):
if state:
hal.set_p('plasmac.override-jog', '1')
else:
hal.set_p('plasmac.override-jog', '0')
def tool_changed(self, obj, tool):
if tool == 0:
self.tool = self.TORCH
text = _translate('HandlerClass', 'TORCH')
self.w.lbl_tool.setText(text)
elif tool == 1:
self.tool = self.SCRIBE
text = _translate('HandlerClass', 'SCRIBE')
self.w.lbl_tool.setText(text)
else:
self.tool = self.UNKNOWN
text = _translate('HandlerClass', 'UNKNOWN')
self.w.lbl_tool.setText(text)
def gcodes_changed(self, obj, cod):
if self.units == 'in' and STATUS.is_metric_mode():
self.droScale = 25.4
self.gcodeScalePin.set(25.4)
elif self.units == 'mm' and not STATUS.is_metric_mode():
self.droScale = 1 / 25.4
self.gcodeScalePin.set(1 / 25.4)
else:
self.droScale = 1
self.gcodeScalePin.set(1)
self.w.lbl_gcodes.setText(f'{cod}')
def mcodes_changed(self, obj, cod):
self.w.lbl_mcodes.setText(f'{cod}')
def metric_mode_changed(self, obj, state):
self.w.gcodegraphics.update()
def set_start_line(self, line):
if self.fileOpened:
if self.w.chk_run_from_line.isChecked():
if self.w.sender():
if self.w.sender().objectName() == 'gcode_editor_display':
return
if line > 1:
msg0 = _translate('HandlerClass', 'SELECTED')
self.w.run.setText(f'{msg0} {line}')
self.runText = f'{msg0} {line}'
self.rflSelected = True
self.startLine = line - 1
elif self.rflActive:
txt0 = _translate('HandlerClass', 'RUN FROM LINE')
txt1 = _translate('HandlerClass', 'CYCLE START')
self.runText = f'{txt0}\n{txt1}'
self.rflSelected = False
else:
self.startLine = 0
self.rflSelected = False
self.w.gcodegraphics.clear_highlight()
if line < 1:
self.w.gcode_display.setCursorPosition(0, 0)
self.w.gcode_display.moveMarker(0)
def update_gcode_properties(self, props):
if props:
self.gcodeProps = props
if props['gcode_units'] == 'in':
STATUS.emit('metric-mode-changed', False)
else:
STATUS.emit('metric-mode-changed', True)
def error_update(self, obj, kind, error):
if not self.realTimeDelay:
if kind == linuxcnc.OPERATOR_ERROR:
self.error_status(True)
self.mdiError = True
if kind == linuxcnc.NML_ERROR:
self.error_status(True)
#########################################################################################################################
# CALLBACKS FROM FORM #
#########################################################################################################################
def ext_run(self, state):
if self.w.run.isEnabled() and state:
self.run_clicked()
def ext_abort(self, state):
if self.w.abort.isEnabled() and state:
self.abort_pressed()
def ext_pause(self, state):
if self.w.pause_resume.isEnabled() and state:
if STATUS.stat.paused:
self.pause_resume_pressed()
ACTION.PAUSE()
def ext_pause_only(self, state):
if self.w.pause_resume.isEnabled() and state:
ACTION.PAUSE_MACHINE()
def ext_resume(self, state):
if self.w.pause_resume.isEnabled() and state:
self.pause_resume_pressed()
ACTION.RESUME()
def ext_touch_off(self, state):
if self.w.touch_xy.isEnabled() and state:
self.touch_xy_clicked()
def ext_laser_touch_off(self, state):
if self.w.laser.isEnabled():
if state:
self.extLaserButton = True
self.laser_pressed()
else:
self.extLaserButton = False
self.laser_clicked()
def ext_laser_toggle(self, state):
if self.w.laser.isEnabled() and state:
self.laserOnPin.set(not self.laserOnPin.get())
def ext_jog_slow(self, state):
if self.w.jog_slow.isEnabled() and state:
self.jog_slow_pressed(True)
def ext_run_pause(self, state):
if self.w.run.isEnabled() and state:
self.run_clicked()
elif self.w.pause_resume.isEnabled() and state:
if STATUS.stat.paused:
self.pause_resume_pressed()
ACTION.PAUSE()
def power_button(self, action, state):
if action == 'pressed':
self.shutdownTimer.start(self.shutdownTime)
elif action == 'released':
self.shutdownTimer.stop()
elif action == 'clicked':
if STATUS.estop_is_clear():
ACTION.SET_MACHINE_STATE(not STATUS.machine_is_on())
else:
self.w.power.setChecked(False)
elif action == 'external' and self.w.power.isEnabled() and not self.firstRun:
if state:
self.shutdownTimer.start(self.shutdownTime)
self.w.power.setDown(True)
else:
self.shutdownTimer.stop()
self.w.power.setDown(False)
self.w.power.click()
def run_clicked(self):
if self.startLine and self.rflSelected:
self.rflActive = True
self.refresh_button_states()
self.rflSelected = False
if self.developmentPin.get():
reload(RFL)
self.runType = self.dialog_rfl_type()
if self.runType['cancel']:
if 'rfl.ngc' in self.lastLoadedProgram:
self.set_start_line(-1)
else:
self.clear_rfl()
return
lastLine = 0
if self.runType['type'] == 'cut':
count = 0
start = 0
start_found = False
in_keep_z_motion = False
with open(self.lastLoadedProgram, 'r') as inFile:
for line in inFile:
stripped = line.replace(' ', '')
# track keep-z-motion state
if '#<keep-z-motion>=1' in stripped:
in_keep_z_motion = True
elif '#<keep-z-motion>=0' in stripped:
in_keep_z_motion = False
# find start: last G00 XY before or first G00 XY after startLine
if line[:3] == 'G00' and 'G53' not in line and ('X' in line or 'Y' in line):
if count <= self.startLine or not start_found:
start = count
start_found = (count >= self.startLine)
# find end: after both startLine and start
if count > max(self.startLine, start):
if (in_keep_z_motion and (('G53' in line and 'G00' in line and 'Z' in line) or '#<keep-z-motion>=0' in stripped)) or \
(not in_keep_z_motion and line[:3] == 'M05'):
break
count += 1
lastLine = count
self.startLine = start
head = _translate('HandlerClass', 'Gcode Error')
data = RFL.run_from_line_get(self.lastLoadedProgram, self.startLine, lastLine)
# cannot do run from line within a subroutine or if using cutter compensation
if data['error']:
if data['compError']:
msg0 = _translate('HandlerClass', 'Cannot run from line while')
msg1 = _translate('HandlerClass', 'cutter compensation is active')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}\n{msg1}\n')
if data['subError']:
msg0 = _translate('HandlerClass', 'Cannot do run from line')
msg1 = _translate('HandlerClass', 'inside subroutine')
msg2 = ''
for sub in data['subError']:
msg2 += f' {sub}'
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}\n{msg1}{msg2}\n')
self.clear_rfl()
else:
# are we in a cutting operation (needs leadin dialog)
is_cutting = (data['codes']['spindle']['line'] and
'M03' in data['codes']['spindle']['line'] and
'$0' in data['codes']['spindle']['line'] and not
data.get('keep_z_motion_active', False))
# skip leadin dialog for "this cutpath" or non-cutting operations
if self.runType['type'] == 'cut' or not is_cutting:
# fake user input
userInput = {'cancel': False, 'do': False, 'length': 0, 'angle': 0}
# get user input
else:
userInput = self.dialog_run_from_line()
# rfl cancel clicked
if userInput['cancel']:
if 'rfl.ngc' in self.lastLoadedProgram:
self.set_start_line(-1)
else:
self.clear_rfl()
return
# rfl load clicked
rflFile = f'{self.tmpPath}rfl.ngc'
result = RFL.run_from_line_set(rflFile, data, userInput, self.unitsPerMm)
# leadin cannot be used
if result['error']:
msg0 = _translate('HandlerClass', 'Unable to calculate a leadin for this cut')
msg1 = _translate('HandlerClass', 'Program will run from selected line with no leadin applied')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}\n{msg1}\n')
# load rfl file
if ACTION.prefilter_path or self.lastLoadedProgram != 'None':
self.preRflFile = ACTION.prefilter_path or self.lastLoadedProgram
ACTION.OPEN_PROGRAM(rflFile)
ACTION.prefilter_path = self.preRflFile
if self.runType['type'] == 'cut':
log = _translate('HandlerClass', 'Run from line - this cutpath loaded')
else:
log = _translate('HandlerClass', 'Run from line - here to end loaded')
log1 = _translate('HandlerClass', 'Start line')
STATUS.emit('update-machine-log', f'{log} - {log1}: {(self.startLine + 1)}', 'TIME')
elif self.cut_critical_toggle_check():
self.jobRunning = True
ACTION.RUN(0)
log = _translate('HandlerClass', 'Cycle started')
if self.w.torch_enable.isChecked():
log1 = _translate('HandlerClass', 'Torch enabled')
else:
log1 = _translate('HandlerClass', 'Torch disabled')
STATUS.emit('update-machine-log', f'{log} - {log1}', 'TIME')
self.refresh_button_states()
def abort_pressed(self):
if self.manualCut:
ACTION.SET_SPINDLE_STOP(0)
self.manualCut = False
self.set_mc_states(True)
if self.mcButton:
self.button_normal(self.mcButton)
log = _translate('HandlerClass', 'Manual cut aborted')
STATUS.emit('update-machine-log', log, 'TIME')
return
elif self.probeTest:
self.probe_test_stop()
log = _translate('HandlerClass', 'Probe test aborted')
STATUS.emit('update-machine-log', log, 'TIME')
return
elif self.torchPulse:
self.torch_pulse_abort()
elif self.framing:
ACTION.ABORT()
self.framing = False
ACTION.SET_MANUAL_MODE()
self.laserOnPin.set(0)
self.w.gcodegraphics.logger.clear()
else:
ACTION.ABORT()
if hal.get_value('plasmac.cut-recovery'):
hal.set_p('plasmac.cut-recovery', '0')
self.laserOnPin.set(0)
self.interp_idle(None)
log = _translate('HandlerClass', 'Cycle aborted')
STATUS.emit('update-machine-log', log, 'TIME')
def pause_resume_pressed(self):
if hal.get_value('plasmac.cut-recovering'):
self.w.jog_stack.setCurrentIndex(self.JOG)
self.laserOnPin.set(0)
if STATUS.is_auto_paused():
log = _translate('HandlerClass', 'Cycle resumed')
STATUS.emit('update-machine-log', log, 'TIME')
def user_button_pressed(self, button):
self.user_button_down(button)
def user_button_released(self, button):
self.user_button_up(button)
def height_ovr_pressed(self, state, height):
if state:
if height:
self.heightOvr += height * self.w.thc_threshold.value()
else:
self.heightOvr = 0
if self.heightOvr < -9:
self.heightOvr = -9
if self.heightOvr > 9:
self.heightOvr = 9
self.heightOverridePin.set(self.heightOvr)
self.w.height_ovr_label.setText(f'{self.heightOvr:.2f}')
def height_ovr_encoder(self, value):
if (value != self.old_ovr_counts and self.extHeightOvrCountEnablePin.get()):
self.heightOvr += (value-self.old_ovr_counts) * self.w.thc_threshold.value() * self.heightOvrScale
if self.heightOvr < -9:
self.heightOvr = -9
if self.heightOvr > 9:
self.heightOvr = 9
self.heightOverridePin.set(self.heightOvr)
self.w.height_ovr_label.setText(f'{self.heightOvr:.2f}')
self.old_ovr_counts = value
def height_ovr_scale_change(self, value):
if value:
self.heightOvrScale = value
def touch_xy_clicked(self):
self.touch_off_xy(0, 0)
self.w.gcodegraphics.logger.clear()
def setup_feed_rate_changed(self):
if self.w.probe_feed_rate.value() > self.w.setup_feed_rate.value():
self.w.probe_feed_rate.setValue(self.w.setup_feed_rate.value())
self.w.probe_feed_rate.setMaximum(self.w.setup_feed_rate.value())
def save_plasma_clicked(self):
self.save_plasma_parameters()
def reload_plasma_clicked(self):
self.load_plasma_parameters()
def backup_clicked(self):
self.save_logfile(6)
bkpPath = f'{os.path.expanduser("~")}'
bkpName = f'{self.machineName}_V{LCNCVER}-{VERSION}_{time.strftime("%y-%m-%d_%H-%M-%S")}.tar.gz'
tmpFile = os.path.join(self.PATHS.CONFIGPATH, 'config_info.txt')
lcncInfo = (Popen('linuxcnc_info -s', stdout=PIPE, stderr=PIPE, shell=True).communicate()[0]).decode('utf-8')
network = (Popen('lspci | grep -i net', stdout=PIPE, stderr=PIPE, shell=True).communicate()[0]).decode('utf-8')
with open(tmpFile, 'a') as outFile:
outFile.write(f'locale:\n{os.getenv("LANG")}\n\n')
if network:
outFile.write(f'lspci | grep -i net:\n{network}\n')
else:
outFile.write('Unknown error with "lspci | grep -i net" command\n\n')
if lcncInfo:
outFile.write('linuxcnc_info:\n')
try:
with open('/tmp/linuxcnc_info.txt', 'r') as inFile:
for line in inFile:
outFile.write(line)
except:
outFile.write("Unknown error opening /tmp/linuxcnc_info.txt\n")
else:
outFile.write('Unknown error creating /tmp/linuxcnc_info.txt\n')
with tarfile.open(f'{bkpPath}/{bkpName}', mode='w:gz', ) as archive:
archive.add(f'{self.PATHS.CONFIGPATH}')
try:
os.remove(tmpFile)
except:
log = _translate('HandlerClass', 'Unknown error removing')
STATUS.emit('update-machine-log', f'{log} {tmpFile}', 'TIME')
head = _translate('HandlerClass', 'Backup Complete')
msg0 = _translate('HandlerClass', 'A compressed backup of the machine configuration including the machine logs has been saved in your home directory as')
msg1 = _translate('HandlerClass', 'It is safe to delete this file at any time')
self.dialog_show_ok(QMessageBox.Information, head, f'\n{msg0}:\n{bkpName}\n\n{msg1}\n')
def set_offsets_clicked(self):
if self.developmentPin.get():
reload(OFFSETS)
self.w.main_tab_widget.setCurrentIndex(self.MAIN)
if STATUS.stat.rotation_xy:
ACTION.CALL_MDI_WAIT('G10 L2 P0 R0')
ACTION.SET_MANUAL_MODE()
OFFSETS.dialog_show(self, self.w, self.PREFS, INIPATH, STATUS, ACTION, TOOL)
def feed_label_pressed(self):
self.w.feed_slider.setValue(100)
def rapid_label_pressed(self):
self.w.rapid_slider.setValue(100)
def jogs_label_pressed(self):
self.w.jog_slider.setValue(INFO.DEFAULT_LINEAR_JOG_VEL)
def gui_button_jog(self, state, joint, direction):
shift = False
if state and joint == 'z' and direction == 1 and self.zPlusOverrideJog and self.w.chk_override_jog.isEnabled():
self.w.chk_override_jog.setChecked(True)
if STATUS.is_joint_mode():
self.kb_jog(state, self.coordinates.index(joint), direction, shift)
else:
self.kb_jog(state, ['x', 'y', 'z', 'a', 'b', 'c'].index(joint), direction, shift)
def view_t_pressed(self, widget):
t = time.time() + 0.01
while time.time() < t:
QApplication.processEvents()
widget.set_view('Z')
mid = DRAW.extents_info(widget)[0]
mult = 1 if self.units == 'in' else 25.4
zoomScale = self.w.table_zoom_scale.value() * 2
xTableCenter = (self.xMin + self.xLen / 2) / mult - mid[0]
yTableCenter = (self.yMin + self.yLen / 2) / mult - mid[1]
xSize = self.xLen / mult / zoomScale
ySize = self.yLen / mult / zoomScale
widget.translate_modelview(-xTableCenter, -yTableCenter, 0)
widget.set_eyepoint_from_extents(xSize, ySize)
widget.perspective = False
widget.lat = widget.lon = 0
widget.update()
def view_p_pressed(self):
self.w.gcodegraphics.set_view('P')
def view_z_pressed(self, widget):
widget.set_view('Z')
def view_clear_pressed(self):
self.w.gcodegraphics.logger.clear()
def pan_left_pressed(self, widget):
widget.recordMouse(0, 0)
widget.translateOrRotate(-widget._view_incr, 0)
def pan_right_pressed(self, widget):
widget.recordMouse(0, 0)
widget.translateOrRotate(widget._view_incr, 0)
def pan_up_pressed(self, widget):
widget.recordMouse(0, 0)
widget.translateOrRotate(0, -widget._view_incr)
def pan_down_pressed(self, widget):
widget.recordMouse(0, 0)
widget.translateOrRotate(0, widget._view_incr)
def zoom_in_pressed(self, widget):
widget.zoomin()
def zoom_out_pressed(self, widget):
widget.zoomout()
def gcode_display_loaded(self):
gcodeLines = len(str(self.w.gcode_display.lines()))
self.w.gcode_display.set_margin_metric(gcodeLines)
self.w.gcode_editor.set_margin_metric(gcodeLines)
def file_clear_clicked(self):
proceed = self.editor_close_check()
if not proceed:
return
self.w.preview_stack.setCurrentIndex(self.PREVIEW)
self.w.gcode_editor.editor.new_text()
self.w.gcode_editor.editor.setModified(False)
# clear error message list and error status
if self.w.screen_options.desktop_notify:
self.w.screen_options.QTVCP_INSTANCE_._NOTICE.alarmpage = []
self.w.screen_options.QTVCP_INSTANCE_._NOTICE.external_close()
self.error_status(False)
# return cut-type button to Normal
if self.cutType:
self.cutType = 0
self.cutTypePin.set(0)
self.button_normal(self.ctButton)
self.w[self.ctButton].setText(self.cutTypeText)
if self.fileOpened:
if self.rflActive:
self.clear_rfl()
clearFile = f'{self.tmpPath}qtplasmac_file_clear.ngc'
with open(clearFile, 'w') as outFile:
outFile.write('m2')
if ACTION.prefilter_path:
if 'single_cut' in ACTION.prefilter_path:
self.preClearFile = self.oldFile
else:
if self.lastLoadedProgram != 'None':
self.preClearFile = ACTION.prefilter_path or self.lastLoadedProgram
self.w.materials_box.setCurrentIndex(self.materialList.index(self.defaultMaterial))
self.w.material_selector.setCurrentIndex(self.w.materials_box.currentIndex())
self.w.conv_material.setCurrentIndex(self.w.materials_box.currentIndex())
self.remove_temp_materials()
ACTION.OPEN_PROGRAM(clearFile)
ACTION.prefilter_path = self.preClearFile
if self.tool != self.TORCH and STATUS.is_on_and_idle() and STATUS.is_all_homed():
ACTION.CALL_MDI_WAIT('T0 M6')
ACTION.CALL_MDI_WAIT('G43 H0')
ACTION.SET_MANUAL_MODE()
log = _translate('HandlerClass', 'Program cleared')
STATUS.emit('update-machine-log', log, 'TIME')
else:
self.view_t_pressed(self.w.gcodegraphics)
def file_open_clicked(self):
if self.w.preview_stack.currentIndex() != self.OPEN:
self.w.preview_stack.setCurrentIndex(self.OPEN)
else:
self.preview_index_return(self.w.preview_stack.currentIndex())
def file_edit_clicked(self):
if STATUS.stat.interp_state == linuxcnc.INTERP_IDLE and self.w.preview_stack.currentIndex() != self.EDIT:
self.w.preview_stack.setCurrentIndex(self.EDIT)
self.w.gcode_editor.editor.setFocus()
if self.fileOpened and not self.w.gcode_editor.editor.isModified():
self.w.gcode_editor.editor.load_text(ACTION.prefilter_path)
text = _translate('HandlerClass', 'EDIT')
self.w.edit_label.setText(f'{text}: {ACTION.prefilter_path}')
self.w.gcode_editor.editor.setModified(False)
try:
if os.path.getsize(self.gcodeErrorFile):
with open(self.gcodeErrorFile, 'r') as inFile:
self.w.gcode_editor.select_line(int(inFile.readline().rstrip()) - 1)
inFile.seek(0)
for line in inFile:
self.w.gcode_editor.editor.userHandle = \
self.w.gcode_editor.editor.markerAdd(int(line) - 1, self.w.gcode_editor.editor.USER_MARKER_NUM)
except:
pass
self.vkb_show()
else:
self.new_exitCall(self.PREVIEW)
def mdi_show_clicked(self):
if STATUS.is_on_and_idle() and STATUS.is_all_homed() and self.w.gcode_stack.currentIndex() != self.MDI:
self.w.gcode_stack.setCurrentIndex(self.MDI)
self.vkb_show()
else:
self.w.gcode_stack.setCurrentIndex(self.GCODE)
ACTION.SET_MANUAL_MODE()
self.vkb_hide()
def file_cancel_clicked(self):
self.preview_index_return(self.w.preview_stack.currentIndex())
def cone_size_changed(self, data):
self.w.gcodegraphics.set_cone_basesize(data)
def grid_size_changed(self, data):
# grid size is in inches
grid = data / self.unitsPerMm / 25.4
self.w.gcodegraphics.grid_size = grid
def main_tab_changed(self, tab):
# stop an active jog if user changed tabs while holding a jog key
if STATUS.machine_is_on() and STATUS.is_all_homed() and STATUS.is_interp_idle():
self.w.releaseKeyboard()
for joint in range(len(self.coordinates)):
ACTION.JOG(joint, 0, 0, 0)
t = time.time() + 0.01
while time.time() < t:
QApplication.processEvents()
if tab == self.MAIN:
if self.w.preview_stack.currentIndex() == self.OPEN:
self.vkb_show()
else:
self.vkb_hide()
if self.w.gcode_stack.currentIndex() == self.GCODE and self.w.preview_stack.currentIndex() == self.PREVIEW:
self.autorepeat_keys(False)
elif tab == self.CONVERSATIONAL:
self.w.conv_preview.logger.clear()
self.w.conv_preview.set_current_view()
if self.developmentPin.get():
reload(self.CONV)
self.CONV.conv_setup(self, self.w)
self.vkb_show(True)
self.autorepeat_keys(True)
elif tab == self.PARAMETERS:
self.vkb_show(True)
self.autorepeat_keys(True)
elif tab == self.SETTINGS:
self.vkb_show()
self.autorepeat_keys(True)
elif tab == self.STATISTICS:
self.vkb_hide()
self.w.machinelog.scrollToBottom()
self.w.machinelog.hideCursor()
self.error_status(False)
def z_height_changed(self, value):
self.w.dro_z.update_user(value * self.droScale)
def offsets_active_changed(self, value):
if not value:
# set z dro to normal made
self.w.dro_z.setProperty('Qreference_type', 1)
def consumable_change_changed(self, value):
if self.ccButton:
if value:
log = _translate('HandlerClass', 'Consumable change initiated')
STATUS.emit('update-machine-log', log, 'TIME')
else:
self.button_normal(self.ccButton)
log = _translate('HandlerClass', 'Consumable change completed')
STATUS.emit('update-machine-log', log, 'TIME')
self.refresh_button_states()
def plasmac_state_changed(self, state):
if (state > self.PROBE_UP or state == self.PROBE_TEST) and hal.get_value('axis.z.eoffset-counts'):
# set z dro to offset mode
self.w.dro_z.setProperty('Qreference_type', 10)
if state == self.IDLE:
self.refresh_button_states()
if state in [self.PROBE_HEIGHT, self.PROBE_DOWN, self.PROBE_UP,
self.ZERO_HEIGHT, self.PIERCE_HEIGHT, self.PUDDLE_JUMP,
self.CUT_HEIGHT, self.SAFE_HEIGHT, self.MAX_HEIGHT]:
self.watchDogState = state
self.watchDogTimer.start(3000)
else:
self.watchDogTimer.stop()
self.watchDogState = None
def plasmac_stop_changed(self, state):
if not state and not self.plasmacStatePin.get():
for pin in ['pierce-type', 'pierce-motion-delay', 'cut-height-delay', 'pierce-end-height',
'gouge-speed', 'gouge-speed-distance', 'creep-speed', 'creep-speed-distance']:
hal.set_p(f'plasmac.{pin}', '0')
def g10_reload(self):
if self.fileOpened:
self.file_reload_clicked()
def file_reload_clicked(self):
proceed = self.editor_close_check()
if not proceed:
return
if self.rflActive:
self.clear_rfl()
if ACTION.prefilter_path or self.lastLoadedProgram != 'None':
file = ACTION.prefilter_path or self.lastLoadedProgram
if os.path.exists(file):
self.overlayProgress.setValue(0)
self.remove_temp_materials()
ACTION.OPEN_PROGRAM(file)
log = _translate('HandlerClass', 'Reloaded')
STATUS.emit('update-machine-log', f'{log}: {file}', 'TIME')
else:
head = _translate('HandlerClass', 'File Error')
msg0 = _translate('HandlerClass', 'does not exist')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{file} {msg0}\n')
def jog_slow_pressed(self, external=False):
if self.w.jog_slow.isChecked():
self.w.jog_slow.setText(_translate('HandlerClass', 'FAST'))
self.w.jog_slider.setMaximum(self.w.jog_slider.maximum() * self.slowJogFactor)
self.w.jog_slider.setValue(self.w.jog_slider.value() * self.slowJogFactor)
self.w.jog_slider.setPageStep(100)
self.previousJogSpeed = self.w.jog_slider.value()
if external:
self.w.jog_slow.setChecked(False)
else:
self.w.jog_slow.setText(_translate('HandlerClass', 'SLOW'))
self.w.jog_slider.setValue(self.w.jog_slider.value() / self.slowJogFactor)
self.w.jog_slider.setMaximum(self.w.jog_slider.maximum() / self.slowJogFactor)
self.w.jog_slider.setPageStep(10)
if external:
self.w.jog_slow.setChecked(True)
def chk_override_limits_changed(self, state):
if state:
ACTION.TOGGLE_LIMITS_OVERRIDE()
def save_shutdown_message_clicked(self):
self.PREFS.putpref('Exit warning text', self.w.sd_text.text(), str, 'GUI_OPTIONS')
self.exitMessage = self.w.sd_text.text()
def reload_shutdown_message_clicked(self):
self.w.sd_text.setText(self.PREFS.getpref('Exit warning text', '', str, 'GUI_OPTIONS'))
def save_user_button_clicked(self):
self.set_interlock_defaults()
for n in range(1, 21):
self.PREFS.putpref(f'{n} Name', self.w[f'ub_name_{n}'].text(), str, 'BUTTONS')
self.PREFS.putpref(f'{n} Code', self.w[f'ub_code_{n}'].text(), str, 'BUTTONS')
self.interlockRules = INTERLOCK_RULES.copy()
self.user_button_setup()
self.preview_stack_changed()
self.refresh_button_states()
def reload_user_button_clicked(self):
for n in range(1, 21):
self.w[f'ub_name_{n}'].clear()
self.w[f'ub_code_{n}'].clear()
self.interlockRules = INTERLOCK_RULES.copy()
self.user_button_setup()
self.refresh_button_states()
def web_back_pressed(self):
self.w.webview.back()
def web_forward_pressed(self):
self.w.webview.forward()
def web_reload_pressed(self):
self.w.webview.load(self.umUrl)
#########################################################################################################################
# GENERAL FUNCTIONS #
#########################################################################################################################
# called by ScreenOptions, this function overrides ScreenOption's closeEvent
def closeEvent(self, event):
O = self.w.screen_options
if self.w.chk_exit_warning.isChecked() or not STATUS.is_interp_idle():
icon = QMessageBox.Question if STATUS.is_interp_idle() else QMessageBox.Critical
head = _translate('HandlerClass', 'Shutdown')
if not STATUS.is_interp_idle():
msg0 = _translate('HandlerClass', 'Current operation is not complete')
msg0 += '!\n\n'
else:
msg0 = ''
if self.exitMessage:
exitLines = self.exitMessage.split('\\')
for line in exitLines:
msg0 += f'{line}\n'
msg0 += '\n'
msg0 += _translate('HandlerClass', 'Do you want to shutdown QtPlasmaC')
if self.dialog_show_yesno(icon, head, f'\n{msg0}?\n'):
if O.PREFS_ and O.play_sounds and O.shutdown_play_sound:
STATUS.emit('play-sound', O.shutdown_exit_sound_type)
O.QTVCP_INSTANCE_.settings.sync()
O.QTVCP_INSTANCE_.shutdown()
O.QTVCP_INSTANCE_.panel_.shutdown()
STATUS.shutdown()
event.accept()
else:
event.ignore()
else:
if O.PREFS_ and O.play_sounds and O.shutdown_play_sound:
STATUS.emit('play-sound', O.shutdown_exit_sound_type)
O.QTVCP_INSTANCE_.settings.sync()
O.QTVCP_INSTANCE_.shutdown()
O.QTVCP_INSTANCE_.panel_.shutdown()
STATUS.shutdown()
def update_check(self):
# newest update must be added last in this function
# if any writing to the INI file is required then that needs
# to be done later in the update_iniwrite function
halfiles = self.iniFile.findall('HAL', 'HALFILE')
qtvcpPrefsFile = os.path.join(self.PATHS.CONFIGPATH, 'qtvcp.prefs')
self.restart = False
# use qtplasmac_comp.hal for component connections (pre V1.221.154 2022/01/18)
if halfiles and not [f for f in halfiles if 'plasmac.tcl' in f] and not \
[f for f in halfiles if 'qtplasmac_comp.hal' in f]:
restart, error, text = UPDATER.add_component_hal_file(self.PATHS.CONFIGPATH, halfiles)
self.updateData.append([restart, error, text])
if error:
return
self.updateIni[154] = True
# split out qtplasmac specific prefs into a separate file (pre V1.222.170 2022/03/08)
if not os.path.isfile(self.prefsFile):
old = os.path.join(self.PATHS.CONFIGPATH, 'qtplasmac.prefs')
if os.path.isfile(old):
restart, error, text = UPDATER.split_prefs_file(old, qtvcpPrefsFile, self.prefsFile)
self.updateData.append([restart, error, text])
if error:
return
self.PREFS = Access(self.prefsFile)
# move conversational prefs from qtvcp.prefs to <machine_name>.prefs (pre V1.222.187 2022/05/03)
if os.path.isfile(qtvcpPrefsFile) and os.path.isfile(self.prefsFile):
with open(qtvcpPrefsFile, 'r') as inFile:
data = inFile.readlines()
if [line for line in data if '[CONVERSATIONAL]' in line]:
restart, error, text = UPDATER.move_prefs(qtvcpPrefsFile, self.prefsFile)
self.updateData.append([restart, error, text])
if error:
return
# change RS274 startup parameters from a subroutine (pre V1.224.207 2022/06/22)
startupCode = self.iniFile.find('RS274NGC', 'RS274NGC_STARTUP_CODE')
if 'metric_startup' in startupCode or 'imperial_startup' in startupCode:
self.updateIni[207] = True
# remove the qtplasmac link from the config directory (pre V1.225.208 2022/06/29)
if os.path.islink(os.path.join(self.PATHS.CONFIGPATH, 'qtplasmac')):
# stage 1: set up for unlinking on the next run of qtplasmac
if 'code.py' in self.iniFile.find('FILTER', 'ngc'):
self.updateIni[208] = True
# stage 2: remove the qtplasmac link
else:
os.unlink(os.path.join(self.PATHS.CONFIGPATH, 'qtplasmac'))
# move qtplasmac options from INI file to prefs file pre V1.227.219 2022/07/14)
if not self.PREFS.has_section('BUTTONS'):
update = False
for n in range(1, 21):
if self.iniFile.find('QTPLASMAC', f'BUTTON_{n}_NAME'):
update = True
break
if update:
restart, error, text = UPDATER.move_options_to_prefs_file(self.iniFile, self.PREFS)
self.updateData.append([restart, error, text])
if error:
return
self.updateIni[219] = True
# move port info from [GUI_OPTIONS] section (if it was moved via V1.227.219 update) to [POWERMAX] section
if self.PREFS.has_option('GUI_OPTIONS', 'Port'):
restart, error, text = UPDATER.move_port(self.PREFS)
self.updateData.append([restart, error, text])
if error:
return
# move default material from prefs file to material 0 in materials file (pre V1.236.278 2023/07/07)
if self.PREFS.has_section('DEFAULT MATERIAL'):
restart, error, text = UPDATER.move_default_material(self.PREFS, self.materialFile, self.unitsPerMm)
self.updateData.append([restart, error, text])
if error:
return
# change runcritical to cutcritical in <machine_name>.prefs file (pre V2.10-001.015 2023/12/23)
if os.path.isfile(self.prefsFile):
with open(self.prefsFile, 'r') as inFile:
text = inFile.read()
if 'runcritical' in text:
restart, error, text = UPDATER.rename_runcritical(self.prefsFile)
self.updateData.append([restart, error, text])
if error:
return
# set user_m_path to include the nc_files directory (pre V2.10-001.017 2024/01/23)
mPathIni = self.iniFile.find('RS274NGC', 'USER_M_PATH')
if 'nc_files/plasmac/m_files' not in mPathIni:
if '/usr' in self.PATHS.BASEDIR:
mPath = '/usr/share/doc/linuxcnc/examples/nc_files/plasmac/m_files'
# simPath = os.path.join(self.PATHS.BASEDIR, 'share/doc/linuxcnc/examples/sample-configs/sim/qtplasmac')
# we need elevated privileges to remove a file from here so forget it...
# we may revisit this.
simPath = None
else:
mPath = os.path.realpath(os.path.join(self.PATHS.BASEDIR, 'nc_files/plasmac/m_files'))
simPath = os.path.join(self.PATHS.BASEDIR, 'configs/sim/qtplasmac')
restart, error, text = UPDATER.insert_user_m_path(self.PATHS.CONFIGPATH, simPath)
if error:
return
self.updateIni['001-017'] = mPath
# add M52P1 to the [CONVERSATIONAL] Preamble and Postamble (pre V2.10-009.070 2025/08/07)
if any(self.PREFS.has_option('CONVERSATIONAL', option) and 'M52P1' not in self.PREFS.get('CONVERSATIONAL', option) for option in ('Preamble', 'Postamble')):
restart, error, text = UPDATER.add_adaptive(self.PREFS)
self.updateData.append([restart, error, text])
if error:
return
# add newlines between codes [CONVERSATIONAL] Preamble and Postamble (pre V2.10-009.071 2025/08/09)
if any(self.PREFS.has_option('CONVERSATIONAL', option) and ' ' in self.PREFS.get('CONVERSATIONAL', option) for option in ('Preamble', 'Postamble')):
restart, error, text = UPDATER.add_newlines(self.PREFS)
self.updateData.append([restart, error, text])
if error:
return
def update_iniwrite(self):
# this is for updates that write to the INI file
if 154 in self.updateIni:
restart, error, text = UPDATER.add_component_hal_file_iniwrite(INIPATH)
if restart:
self.restart = True
self.updateData.append([self.restart, error, text])
if error:
return
if 207 in self.updateIni:
restart, error, text = UPDATER.rs274ngc_startup_code_iniwrite(INIPATH)
if restart:
self.restart = True
self.updateData.append([self.restart, error, text])
if error:
return
if 208 in self.updateIni:
restart, error, text = UPDATER.remove_qtplasmac_link_iniwrite(INIPATH)
if restart:
self.restart = True
self.updateData.append([self.restart, error, text])
if error:
return
if 219 in self.updateIni:
restart, error, text = UPDATER.move_options_to_prefs_file_iniwrite(INIPATH)
if restart:
self.restart = True
self.updateData.append([self.restart, error, text])
if error:
return
if '001-017' in self.updateIni:
restart, error, text = UPDATER.insert_user_m_path_iniwrite(INIPATH, self.updateIni['001-017'])
if restart:
self.restart = True
self.updateData.append([self.restart, error, text])
if error:
return
def find_a_file(self, name, path):
''' find a file "name" in the path "path" '''
for root, dirs, files in os.walk(path):
# variable is not currently used
del dirs
if name in files:
return os.path.join(root, name)
def motion_type_changed(self, value):
if value == 0 and STATUS.is_mdi_mode():
ACTION.SET_MANUAL_MODE()
def set_interlock_defaults(self):
self.ccButton, self.otButton, self.ptButton, self.tpButton = '', '', '', ''
self.ctButton, self.scButton, self.frButton, self.mcButton = '', '', '', ''
self.ovButton, self.llButton, self.tlButton, self.umButton, self.jtButton = '', '', [], '', ''
self.halTogglePins = {}
self.halPulsePins = {}
self.dualCodeButtons = {}
def preview_index_return(self, index):
if self.w.gcode_editor.editor.isModified():
self.new_exitCall(index)
else:
if index == self.PREVIEW:
pass
elif index == self.OPEN:
self.vkb_hide()
ACTION.SET_MANUAL_MODE()
elif index == self.EDIT:
pass
elif index == self.CAMERA:
self.button_normal('camera')
self.cameraOn = False
self.vkb_hide()
ACTION.SET_MANUAL_MODE()
elif index == self.OFFSETS:
pass
elif index == self.USER_MANUAL:
pass
self.w.preview_stack.setCurrentIndex(self.PREVIEW)
def editor_close_check(self):
if self.w.gcode_editor.editor.isModified():
head = _translate('HandlerClass', 'Unsaved Editor Changes')
msg0 = _translate('HandlerClass', 'Unsaved changes will be lost')
msg1 = _translate('HandlerClass', 'Do you want to proceed')
if not self.dialog_show_yesno(QMessageBox.Question, head, f'\n{msg0}\n\n{msg1}?\n'):
self.w.preview_stack.setCurrentIndex(self.EDIT)
self.w.gcode_editor.editor.setFocus()
return False
return True
def system_notify_button_pressed(self, object, button, state):
if button in ['background', 'clearAll', 'close', 'lastFive'] and state:
self.error_status(False)
def error_status(self, state):
self.error_present = state
if state:
text = _translate('HandlerClass', 'ERROR SENT TO MACHINE LOG')
else:
text = ''
self.w.error_label.setText(f'{text}')
def touch_off_xy(self, x, y):
if STATUS.is_on_and_idle() and STATUS.is_all_homed():
ACTION.CALL_MDI_WAIT(f'G10 L20 P0 X{x} Y{y}')
if self.fileOpened:
self.file_reload_clicked()
ACTION.SET_MANUAL_MODE()
def bounds_check_file(self):
msg = _translate('HandlerClass', 'G-code')
self.boundsMultiplier = 1
if 'gcode_units' in self.gcodeProps:
if self.units == 'in' and self.gcodeProps['gcode_units'] == 'mm':
self.boundsMultiplier = 0.03937
elif self.units == 'mm' and self.gcodeProps['gcode_units'] == 'in':
self.boundsMultiplier = 25.4
xMin = float(self.gcodeProps['x'].split()[0]) * self.boundsMultiplier
xMax = float(self.gcodeProps['x'].split()[2]) * self.boundsMultiplier
yMin = float(self.gcodeProps['y'].split()[0]) * self.boundsMultiplier
yMax = float(self.gcodeProps['y'].split()[2]) * self.boundsMultiplier
errMsg = self.bounds_compare(xMin, xMax, yMin, yMax, msg)
return (True, errMsg) if errMsg else (False, '')
def bounds_check_probe(self, static):
if not (self.probeOffsetX or self.probeOffsetY) and not self.cutTypePin.get():
return False, ''
msg = _translate('HandlerClass', 'Move to probe coordinate')
xPierceOffset = yPierceOffset = xMinP = xMaxP = yMinP = yMaxP = 0
if static:
xMinP = xMaxP = STATUS.get_position()[0][0] + self.probeOffsetX
yMinP = yMaxP = STATUS.get_position()[0][1] + self.probeOffsetY
elif any((self.xMinPierceExtentPin.get(), self.xMaxPierceExtentPin.get(),
self.yMinPierceExtentPin.get(), self.yMaxPierceExtentPin.get())):
if self.cutTypePin.get():
xPierceOffset = self.w.x_pierce_offset.value()
yPierceOffset = self.w.y_pierce_offset.value()
if self.probeOffsetX or (xPierceOffset and self.cutTypePin.get()):
xMinP = self.xMinPierceExtentPin.get() * self.boundsMultiplier + self.probeOffsetX + STATUS.stat.g5x_offset[0] + xPierceOffset
xMaxP = self.xMaxPierceExtentPin.get() * self.boundsMultiplier + self.probeOffsetX + STATUS.stat.g5x_offset[0] + xPierceOffset
if self.probeOffsetY or (yPierceOffset and self.cutTypePin.get()):
yMinP = self.yMinPierceExtentPin.get() * self.boundsMultiplier + self.probeOffsetY + STATUS.stat.g5x_offset[1] + yPierceOffset
yMaxP = self.yMaxPierceExtentPin.get() * self.boundsMultiplier + self.probeOffsetY + STATUS.stat.g5x_offset[1] + yPierceOffset
errMsg = self.bounds_compare(xMinP, xMaxP, yMinP, yMaxP, msg)
return (True, errMsg) if errMsg else (False, '')
def bounds_check_framing(self, xOffset=0, yOffset=0, laser=False):
msg = _translate('HandlerClass', 'Framing move')
msg1 = ''
if laser:
msg1 = _translate('HandlerClass', 'due to laser offset')
xStart = STATUS.stat.g5x_offset[0] + xOffset
yStart = STATUS.stat.g5x_offset[1] + yOffset
xMin = float(self.gcodeProps['x_zero_rxy'].split()[0]) * self.boundsMultiplier + xOffset
xMax = float(self.gcodeProps['x_zero_rxy'].split()[2]) * self.boundsMultiplier + xOffset
yMin = float(self.gcodeProps['y_zero_rxy'].split()[0]) * self.boundsMultiplier + yOffset
yMax = float(self.gcodeProps['y_zero_rxy'].split()[2]) * self.boundsMultiplier + yOffset
coordinates = [[xStart, yStart], [xMin, yMin], [xMin, yMax], [xMax, yMax], [xMax, yMin]]
framePoints, xMin, yMin, xMax, yMax = self.rotate_frame(coordinates)
errMsg = self.bounds_compare(xMin, xMax, yMin, yMax, msg, msg1)
return (errMsg, framePoints)
def bounds_compare(self, xMin, xMax, yMin, yMax, msg, msg1=''):
errMsg = ''
epsilon = 1e-4
txtxMin = _translate('HandlerClass', 'exceeds the X minimum limit by')
txtxMax = _translate('HandlerClass', 'exceeds the X maximum limit by')
txtyMin = _translate('HandlerClass', 'exceeds the Y minimum limit by')
txtyMax = _translate('HandlerClass', 'exceeds the Y maximum limit by')
lessThan = _translate('HandlerClass', 'less than')
if xMin < self.xMin:
errMsg += f'{msg} {txtxMin} {lessThan} {epsilon} {self.units} {msg1}\n' if (self.xMin - xMin) < epsilon \
else f'{msg} {txtxMin} {self.xMin - xMin:0.4f} {self.units} {msg1}\n'
if xMax > self.xMax:
errMsg += f'{msg} {txtxMax} {lessThan} {epsilon} {self.units} {msg1}\n' if (xMax - self.xMax) < epsilon \
else f'{msg} {txtxMax} {xMax - self.xMax:0.4f} {self.units} {msg1}\n'
if yMin < self.yMin:
errMsg += f'{msg} {txtyMin} {lessThan} {epsilon} {self.units} {msg1}\n' if (self.yMin - yMin) < epsilon \
else f'{msg} {txtyMin} {self.yMin - yMin:0.4f} {self.units} {msg1}\n'
if yMax > self.yMax:
errMsg += f'{msg} {txtyMax} {lessThan} {epsilon} {self.units} {msg1}\n' if (yMax - self.yMax) < epsilon \
else f'{msg} {txtyMax} {yMax - self.yMax:0.4f} {self.units} {msg1}\n'
return errMsg
def rotate_frame(self, coordinates):
angle = math.radians(STATUS.stat.rotation_xy)
cos = math.cos(angle)
sin = math.sin(angle)
framePoints = [coordinates[0]]
ox = framePoints[0][0]
oy = framePoints[0][1]
for x, y in coordinates[1:]:
tox = x - ox
toy = y - oy
rx = (tox * cos) - (toy * sin) + ox
ry = (tox * sin) + (toy * cos) + oy
framePoints.append([rx, ry])
xMin = min(framePoints[1:])[0]
xMax = max(framePoints[1:])[0]
yMin = min(framePoints[1:])[1]
yMax = max(framePoints[1:])[1]
return framePoints, xMin, yMin, xMax, yMax
def save_plasma_parameters(self):
self.PREFS.putpref('Arc OK High', self.w.arc_ok_high.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Arc OK Low', self.w.arc_ok_low.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Arc Maximum Starts', self.w.arc_max_starts.value(), int, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Arc Fail Timeout', self.w.arc_fail_delay.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Arc Voltage Offset', self.w.arc_voltage_offset.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Arc Voltage Scale', self.w.arc_voltage_scale.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Velocity Anti Dive Threshold', self.w.cornerlock_threshold.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Float Switch Travel', self.w.float_switch_travel.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Height Per Volt', self.w.height_per_volt.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Void Sense Slope', self.w.voidlock_slope.value(), int, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Offset Feed Rate', self.w.offset_feed_rate.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Ohmic Maximum Attempts', self.w.ohmic_max_attempts.value(), int, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Ohmic Probe Offset', self.w.ohmic_probe_offset.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Pid P Gain', self.w.pid_p_gain.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Pid D Gain', self.w.pid_d_gain.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Pid I Gain', self.w.pid_i_gain.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Probe Feed Rate', self.w.probe_feed_rate.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Probe Start Height', self.w.probe_start_height.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Slat Height', self.w.slat_height.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Arc Restart Delay', self.w.arc_restart_delay.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Safe Height', self.w.safe_height.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Scribe Arming Delay', self.w.scribe_arm_delay.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Scribe On Delay', self.w.scribe_on_delay.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Setup Feed Rate', self.w.setup_feed_rate.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Skip IHS Distance', self.w.skip_ihs_distance.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Spotting Threshold', self.w.spotting_threshold.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Spotting Time', self.w.spotting_time.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('THC Delay', self.w.thc_delay.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('THC Sample Counts', self.w.thc_sample_counts.value(), int, 'PLASMA_PARAMETERS')
self.PREFS.putpref('THC Sample Threshold', self.w.thc_sample_threshold.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('THC Threshold', self.w.thc_threshold.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('X Pierce Offset', self.w.x_pierce_offset.value(), float, 'PLASMA_PARAMETERS')
self.PREFS.putpref('Y Pierce Offset', self.w.y_pierce_offset.value(), float, 'PLASMA_PARAMETERS')
def load_plasma_parameters(self):
self.w.arc_fail_delay.setValue(self.PREFS.getpref('Arc Fail Timeout', 3.0, float, 'PLASMA_PARAMETERS'))
self.w.arc_ok_high.setValue(self.PREFS.getpref('Arc OK High', 250.0, float, 'PLASMA_PARAMETERS'))
self.w.arc_ok_low.setValue(self.PREFS.getpref('Arc OK Low', 60.0, float, 'PLASMA_PARAMETERS'))
self.w.arc_max_starts.setValue(self.PREFS.getpref('Arc Maximum Starts', 3, int, 'PLASMA_PARAMETERS'))
self.w.arc_voltage_offset.setValue(self.PREFS.getpref('Arc Voltage Offset', 0.0, float, 'PLASMA_PARAMETERS'))
self.w.arc_voltage_scale.setValue(self.PREFS.getpref('Arc Voltage Scale', 1.0, float, 'PLASMA_PARAMETERS'))
self.w.cornerlock_threshold.setValue(self.PREFS.getpref('Velocity Anti Dive Threshold', 90.0, float, 'PLASMA_PARAMETERS'))
self.w.float_switch_travel.setValue(self.PREFS.getpref('Float Switch Travel', round(1.5 * self.unitsPerMm, 2), float, 'PLASMA_PARAMETERS'))
self.w.height_per_volt.setValue(self.PREFS.getpref('Height Per Volt', round(0.1 * self.unitsPerMm, 3), float, 'PLASMA_PARAMETERS'))
self.w.offset_feed_rate.setValue(self.PREFS.getpref('Offset Feed Rate', self.offsetFeedRate * 0.8, float, 'PLASMA_PARAMETERS'))
self.w.ohmic_max_attempts.setValue(self.PREFS.getpref('Ohmic Maximum Attempts', 0, int, 'PLASMA_PARAMETERS'))
self.w.ohmic_probe_offset.setValue(self.PREFS.getpref('Ohmic Probe Offset', 0.0, float, 'PLASMA_PARAMETERS'))
self.w.pid_p_gain.setValue(self.PREFS.getpref('Pid P Gain', 10.0, float, 'PLASMA_PARAMETERS'))
self.w.pid_d_gain.setValue(self.PREFS.getpref('Pid D Gain', 0.0, float, 'PLASMA_PARAMETERS'))
self.w.pid_i_gain.setValue(self.PREFS.getpref('Pid I Gain', 0.0, float, 'PLASMA_PARAMETERS'))
self.w.probe_feed_rate.setValue(self.PREFS.getpref('Probe Feed Rate', round(300.0 * self.unitsPerMm, 0), float, 'PLASMA_PARAMETERS'))
self.w.probe_start_height.setValue(self.PREFS.getpref('Probe Start Height', round(25.0 * self.unitsPerMm, 0), float, 'PLASMA_PARAMETERS'))
self.w.slat_height.setValue(self.PREFS.getpref('Slat Height', 0.0, float, 'PLASMA_PARAMETERS'))
self.w.arc_restart_delay.setValue(self.PREFS.getpref('Arc Restart Delay', 1.0, float, 'PLASMA_PARAMETERS'))
self.w.safe_height.setValue(self.PREFS.getpref('Safe Height', round(25.0 * self.unitsPerMm, 0), float, 'PLASMA_PARAMETERS'))
self.w.setup_feed_rate.setValue(self.PREFS.getpref('Setup Feed Rate', self.thcFeedRate * 0.8, float, 'PLASMA_PARAMETERS'))
self.w.scribe_arm_delay.setValue(self.PREFS.getpref('Scribe Arming Delay', 0.0, float, 'PLASMA_PARAMETERS'))
self.w.scribe_on_delay.setValue(self.PREFS.getpref('Scribe On Delay', 0.0, float, 'PLASMA_PARAMETERS'))
self.w.skip_ihs_distance.setValue(self.PREFS.getpref('Skip IHS Distance', 0.0, float, 'PLASMA_PARAMETERS'))
self.w.spotting_threshold.setValue(self.PREFS.getpref('Spotting Threshold', 1.0, float, 'PLASMA_PARAMETERS'))
self.w.spotting_time.setValue(self.PREFS.getpref('Spotting Time', 0.0, float, 'PLASMA_PARAMETERS'))
self.w.thc_delay.setValue(self.PREFS.getpref('THC Delay', 0.5, float, 'PLASMA_PARAMETERS'))
self.w.thc_sample_counts.setValue(self.PREFS.getpref('THC Sample Counts', 50, int, 'PLASMA_PARAMETERS'))
self.w.thc_sample_threshold.setValue(self.PREFS.getpref('THC Sample Threshold', 1.0, float, 'PLASMA_PARAMETERS'))
self.w.thc_threshold.setValue(self.PREFS.getpref('THC Threshold', 1.0, float, 'PLASMA_PARAMETERS'))
self.w.voidlock_slope.setValue(self.PREFS.getpref('Void Sense Slope', 500, int, 'PLASMA_PARAMETERS'))
self.w.x_pierce_offset.setValue(self.PREFS.getpref('X Pierce Offset', round(1.6 * self.unitsPerMm, 2), float, 'PLASMA_PARAMETERS'))
self.w.y_pierce_offset.setValue(self.PREFS.getpref('Y Pierce Offset', 0.0, float, 'PLASMA_PARAMETERS'))
def set_signal_connections(self):
self.w.power.pressed.connect(lambda: self.power_button("pressed", True))
self.w.power.released.connect(lambda: self.power_button("released", False))
self.w.power.clicked.connect(lambda: self.power_button("clicked", None))
self.w.run.clicked.connect(self.run_clicked)
self.w.pause_resume.pressed.connect(self.pause_resume_pressed)
self.w.abort.pressed.connect(self.abort_pressed)
self.w.file_reload.clicked.connect(self.file_reload_clicked)
self.w.jog_slow.pressed.connect(self.jog_slow_pressed)
self.w.chk_soft_keyboard.stateChanged.connect(self.soft_keyboard)
self.w.chk_override_limits.stateChanged.connect(self.chk_override_limits_changed)
self.w.chk_overlay.stateChanged.connect(self.overlay_update)
self.w.chk_tool_tips.stateChanged.connect(lambda: TOOLTIPS.tool_tips_changed(self, self.w))
self.w.torch_enable.stateChanged.connect(lambda w: self.torch_enable_changed(w))
self.w.ohmic_probe_enable.stateChanged.connect(self.refresh_button_states)
self.w.thc_auto.stateChanged.connect(lambda w: self.thc_auto_changed(w))
self.w.cone_size.valueChanged.connect(self.cone_size_changed)
self.w.grid_size.valueChanged.connect(self.grid_size_changed)
self.w.gcode_display.linesChanged.connect(self.gcode_display_loaded)
self.w.gcode_editor.percentDone.connect(lambda w: self.progress_changed(None, w))
self.w.file_clear.clicked.connect(self.file_clear_clicked)
self.w.file_open.clicked.connect(self.file_open_clicked)
self.w.file_edit.clicked.connect(self.file_edit_clicked)
self.w.mdi_show.clicked.connect(self.mdi_show_clicked)
self.w.file_cancel.clicked.connect(self.file_cancel_clicked)
self.w.color_foregrnd.clicked.connect(lambda: self.openColorDialog(self.w.color_foregrnd))
self.w.color_foregalt.clicked.connect(lambda: self.openColorDialog(self.w.color_foregalt))
self.w.color_led.clicked.connect(lambda: self.openColorDialog(self.w.color_led))
self.w.color_backgrnd.clicked.connect(lambda: self.openColorDialog(self.w.color_backgrnd))
self.w.color_backgalt.clicked.connect(lambda: self.openColorDialog(self.w.color_backgalt))
self.w.color_frams.clicked.connect(lambda: self.openColorDialog(self.w.color_frams))
self.w.color_estop.clicked.connect(lambda: self.openColorDialog(self.w.color_estop))
self.w.color_disabled.clicked.connect(lambda: self.openColorDialog(self.w.color_disabled))
self.w.color_preview.clicked.connect(lambda: self.openColorDialog(self.w.color_preview))
self.w.save_plasma.clicked.connect(self.save_plasma_clicked)
self.w.reload_plasma.clicked.connect(self.reload_plasma_clicked)
self.w.backup.clicked.connect(self.backup_clicked)
self.w.set_offsets.clicked.connect(self.set_offsets_clicked)
self.w.save_material.clicked.connect(self.save_materials_clicked)
self.w.reload_material.clicked.connect(self.reload_materials_clicked)
self.w.new_material.clicked.connect(lambda: self.new_material_clicked(0, 0))
self.w.delete_material.clicked.connect(self.delete_material_clicked)
self.w.setup_feed_rate.valueChanged.connect(self.setup_feed_rate_changed)
self.w.touch_xy.clicked.connect(self.touch_xy_clicked)
self.w.materials_box.currentIndexChanged.connect(lambda w: self.material_changed(w))
self.w.material_selector.currentIndexChanged.connect(lambda w: self.selector_changed(w))
self.w.conv_material.currentIndexChanged.connect(lambda w: self.conv_material_changed(w))
self.w.default_material.currentIndexChanged.connect(lambda w: self.default_material_changed(w))
self.w.sd_save.clicked.connect(self.save_shutdown_message_clicked)
self.w.sd_reload.clicked.connect(self.reload_shutdown_message_clicked)
self.w.ub_save.clicked.connect(self.save_user_button_clicked)
self.w.ub_reload.clicked.connect(self.reload_user_button_clicked)
self.materialChangePin.value_changed.connect(lambda w: self.material_change_pin_changed(w))
self.materialChangeNumberPin.value_changed.connect(lambda w: self.material_change_number_pin_changed(w))
self.materialChangeTimeoutPin.value_changed.connect(lambda w: self.material_change_timeout_pin_changed(w))
self.materialReloadPin.value_changed.connect(lambda w: self.material_reload_pin_changed(w))
self.materialTempPin.value_changed.connect(lambda w: self.material_temp_pin_changed(w))
self.w.height_lower.pressed.connect(lambda: self.height_ovr_pressed(1, -1))
self.w.height_raise.pressed.connect(lambda: self.height_ovr_pressed(1, 1))
self.w.height_reset.pressed.connect(lambda: self.height_ovr_pressed(1, 0))
self.w.button_1.pressed.connect(lambda: self.user_button_pressed(1))
self.w.button_1.released.connect(lambda: self.user_button_released(1))
self.w.button_2.pressed.connect(lambda: self.user_button_pressed(2))
self.w.button_2.released.connect(lambda: self.user_button_released(2))
self.w.button_3.pressed.connect(lambda: self.user_button_pressed(3))
self.w.button_3.released.connect(lambda: self.user_button_released(3))
self.w.button_4.pressed.connect(lambda: self.user_button_pressed(4))
self.w.button_4.released.connect(lambda: self.user_button_released(4))
self.w.button_5.pressed.connect(lambda: self.user_button_pressed(5))
self.w.button_5.released.connect(lambda: self.user_button_released(5))
self.w.button_6.pressed.connect(lambda: self.user_button_pressed(6))
self.w.button_6.released.connect(lambda: self.user_button_released(6))
self.w.button_7.pressed.connect(lambda: self.user_button_pressed(7))
self.w.button_7.released.connect(lambda: self.user_button_released(7))
self.w.button_8.pressed.connect(lambda: self.user_button_pressed(8))
self.w.button_8.released.connect(lambda: self.user_button_released(8))
self.w.button_9.pressed.connect(lambda: self.user_button_pressed(9))
self.w.button_9.released.connect(lambda: self.user_button_released(9))
self.w.button_10.pressed.connect(lambda: self.user_button_pressed(10))
self.w.button_10.released.connect(lambda: self.user_button_released(10))
self.w.button_11.pressed.connect(lambda: self.user_button_pressed(11))
self.w.button_11.released.connect(lambda: self.user_button_released(11))
self.w.button_12.pressed.connect(lambda: self.user_button_pressed(12))
self.w.button_12.released.connect(lambda: self.user_button_released(12))
self.w.button_13.pressed.connect(lambda: self.user_button_pressed(13))
self.w.button_13.released.connect(lambda: self.user_button_released(13))
self.w.button_14.pressed.connect(lambda: self.user_button_pressed(14))
self.w.button_14.released.connect(lambda: self.user_button_released(14))
self.w.button_15.pressed.connect(lambda: self.user_button_pressed(15))
self.w.button_15.released.connect(lambda: self.user_button_released(15))
self.w.button_16.pressed.connect(lambda: self.user_button_pressed(16))
self.w.button_16.released.connect(lambda: self.user_button_released(16))
self.w.button_17.pressed.connect(lambda: self.user_button_pressed(17))
self.w.button_17.released.connect(lambda: self.user_button_released(17))
self.w.button_18.pressed.connect(lambda: self.user_button_pressed(18))
self.w.button_18.released.connect(lambda: self.user_button_released(18))
self.w.button_19.pressed.connect(lambda: self.user_button_pressed(19))
self.w.button_19.released.connect(lambda: self.user_button_released(19))
self.w.button_20.pressed.connect(lambda: self.user_button_pressed(20))
self.w.button_20.released.connect(lambda: self.user_button_released(20))
self.w.cut_rec_speed.valueChanged.connect(lambda w: self.cutrec_speed_changed(w))
self.w.kerf_width.valueChanged.connect(lambda w: self.cutrec_move_changed(w))
self.w.jog_x_plus.pressed.connect(lambda: self.gui_button_jog(1, 'x', 1))
self.w.jog_x_plus.released.connect(lambda: self.gui_button_jog(0, 'x', 1))
self.w.jog_x_minus.pressed.connect(lambda: self.gui_button_jog(1, 'x', -1))
self.w.jog_x_minus.released.connect(lambda: self.gui_button_jog(0, 'x', -1))
self.w.jog_y_plus.pressed.connect(lambda: self.gui_button_jog(1, 'y', 1))
self.w.jog_y_plus.released.connect(lambda: self.gui_button_jog(0, 'y', 1))
self.w.jog_y_minus.pressed.connect(lambda: self.gui_button_jog(1, 'y', -1))
self.w.jog_y_minus.released.connect(lambda: self.gui_button_jog(0, 'y', -1))
self.w.jog_z_plus.pressed.connect(lambda: self.gui_button_jog(1, 'z', 1))
self.w.jog_z_plus.released.connect(lambda: self.gui_button_jog(0, 'z', 1))
self.w.jog_z_minus.pressed.connect(lambda: self.gui_button_jog(1, 'z', -1))
self.w.jog_z_minus.released.connect(lambda: self.gui_button_jog(0, 'z', -1))
self.w.jog_a_plus.pressed.connect(lambda: self.gui_button_jog(1, 'a', 1))
self.w.jog_a_plus.released.connect(lambda: self.gui_button_jog(0, 'a', 1))
self.w.jog_a_minus.pressed.connect(lambda: self.gui_button_jog(1, 'a', -1))
self.w.jog_a_minus.released.connect(lambda: self.gui_button_jog(0, 'a', -1))
self.w.jog_b_plus.pressed.connect(lambda: self.gui_button_jog(1, 'b', 1))
self.w.jog_b_plus.released.connect(lambda: self.gui_button_jog(0, 'b', 1))
self.w.jog_b_minus.pressed.connect(lambda: self.gui_button_jog(1, 'b', -1))
self.w.jog_b_minus.released.connect(lambda: self.gui_button_jog(0, 'b', -1))
self.w.jog_c_plus.pressed.connect(lambda: self.gui_button_jog(1, 'c', 1))
self.w.jog_c_plus.released.connect(lambda: self.gui_button_jog(0, 'c', 1))
self.w.jog_c_minus.pressed.connect(lambda: self.gui_button_jog(1, 'c', -1))
self.w.jog_c_minus.released.connect(lambda: self.gui_button_jog(0, 'c', -1))
self.w.cut_rec_fwd.pressed.connect(lambda: self.cutrec_motion(1))
self.w.cut_rec_fwd.released.connect(lambda: self.cutrec_motion(0))
self.w.cut_rec_rev.pressed.connect(lambda: self.cutrec_motion(-1))
self.w.cut_rec_rev.released.connect(lambda: self.cutrec_motion(0))
self.w.cut_rec_cancel.pressed.connect(lambda: self.cutrec_cancel_pressed(1))
self.w.cut_rec_n.pressed.connect(lambda: self.cutrec_move(1, 0, 1))
self.w.cut_rec_ne.pressed.connect(lambda: self.cutrec_move(1, 1, 1))
self.w.cut_rec_e.pressed.connect(lambda: self.cutrec_move(1, 1, 0))
self.w.cut_rec_se.pressed.connect(lambda: self.cutrec_move(1, 1, -1))
self.w.cut_rec_s.pressed.connect(lambda: self.cutrec_move(1, 0, -1))
self.w.cut_rec_sw.pressed.connect(lambda: self.cutrec_move(1, -1, -1))
self.w.cut_rec_w.pressed.connect(lambda: self.cutrec_move(1, -1, 0))
self.w.cut_rec_nw.pressed.connect(lambda: self.cutrec_move(1, -1, 1))
self.xOffsetPin.value_changed.connect(lambda v: self.cutrec_offset_changed(v, self.yOffsetPin.get()))
self.yOffsetPin.value_changed.connect(lambda v: self.cutrec_offset_changed(self.xOffsetPin.get(), v))
self.offsetsActivePin.value_changed.connect(lambda v: self.offsets_active_changed(v))
self.consChangePin.value_changed.connect(lambda v: self.consumable_change_changed(v))
self.w.cam_mark.clicked.connect(self.cam_mark_clicked)
self.w.cam_goto.clicked.connect(self.cam_goto_clicked)
self.w.cam_zoom_plus.pressed.connect(self.cam_zoom_plus_pressed)
self.w.cam_zoom_minus.pressed.connect(self.cam_zoom_minus_pressed)
self.w.cam_dia_plus.pressed.connect(self.cam_dia_plus_pressed)
self.w.cam_dia_minus.pressed.connect(self.cam_dia_minus_pressed)
self.w.view_p.pressed.connect(self.view_p_pressed)
self.w.view_z.pressed.connect(lambda: self.view_z_pressed(self.w.gcodegraphics))
self.w.view_t.pressed.connect(lambda: self.view_t_pressed(self.w.gcodegraphics))
self.w.view_clear.pressed.connect(self.view_clear_pressed)
self.w.pan_left.pressed.connect(lambda: self.pan_left_pressed(self.w.gcodegraphics))
self.w.pan_right.pressed.connect(lambda: self.pan_right_pressed(self.w.gcodegraphics))
self.w.pan_up.pressed.connect(lambda: self.pan_up_pressed(self.w.gcodegraphics))
self.w.pan_down.pressed.connect(lambda: self.pan_down_pressed(self.w.gcodegraphics))
self.w.zoom_in.pressed.connect(lambda: self.zoom_in_pressed(self.w.gcodegraphics))
self.w.zoom_out.pressed.connect(lambda: self.zoom_out_pressed(self.w.gcodegraphics))
self.w.conv_view_z.pressed.connect(lambda: self.view_z_pressed(self.w.conv_preview))
self.w.conv_view_t.pressed.connect(lambda: self.view_t_pressed(self.w.conv_preview))
self.w.conv_pan_left.pressed.connect(lambda: self.pan_left_pressed(self.w.conv_preview))
self.w.conv_pan_right.pressed.connect(lambda: self.pan_right_pressed(self.w.conv_preview))
self.w.conv_pan_up.pressed.connect(lambda: self.pan_up_pressed(self.w.conv_preview))
self.w.conv_pan_down.pressed.connect(lambda: self.pan_down_pressed(self.w.conv_preview))
self.w.conv_zoom_in.pressed.connect(lambda: self.zoom_in_pressed(self.w.conv_preview))
self.w.conv_zoom_out.pressed.connect(lambda: self.zoom_out_pressed(self.w.conv_preview))
self.w.camera.pressed.connect(self.camera_pressed)
self.w.laser.pressed.connect(self.laser_pressed)
self.w.laser.clicked.connect(self.laser_clicked)
self.w.main_tab_widget.currentChanged.connect(lambda w: self.main_tab_changed(w))
self.zHeightPin.value_changed.connect(lambda v: self.z_height_changed(v))
self.plasmacStatePin.value_changed.connect(lambda v: self.plasmac_state_changed(v))
self.plasmacStopPin.value_changed.connect(lambda v: self.plasmac_stop_changed(v))
self.w.feed_label.pressed.connect(self.feed_label_pressed)
self.w.rapid_label.pressed.connect(self.rapid_label_pressed)
self.w.jogs_label.pressed.connect(self.jogs_label_pressed)
self.paramTabDisable.value_changed.connect(lambda v: self.w.main_tab_widget.setTabVisible(self.PARAMETERS, not v))
self.settingsTabDisable.value_changed.connect(lambda v: self.w.main_tab_widget.setTabVisible(self.SETTINGS, not v))
self.convTabDisable.value_changed.connect(lambda v: self.w.main_tab_widget.setTabVisible(self.CONVERSATIONAL, not v))
self.w.cut_time_reset.pressed.connect(lambda: self.statistic_reset('cut_time', 'Cut time'))
self.w.probe_time_reset.pressed.connect(lambda: self.statistic_reset('probe_time', 'Probe time'))
self.w.paused_time_reset.pressed.connect(lambda: self.statistic_reset('paused_time', 'Paused time'))
self.w.run_time_reset.pressed.connect(lambda: self.statistic_reset('run_time', 'Program run time'))
self.w.torch_time_reset.pressed.connect(lambda: self.statistic_reset('torch_time', 'Torch on time'))
self.w.rapid_time_reset.pressed.connect(lambda: self.statistic_reset('rapid_time', 'Rapid time'))
self.w.cut_length_reset.pressed.connect(lambda: self.statistic_reset('cut_length', 'Cut length'))
self.w.pierce_reset.pressed.connect(lambda: self.statistic_reset('pierce_count', 'Pierce count'))
self.w.all_reset.pressed.connect(self.statistics_reset)
self.extPowerPin.value_changed.connect(lambda v: self.power_button("external", v))
self.extRunPin.value_changed.connect(lambda v: self.ext_run(v))
self.extPausePin.value_changed.connect(lambda v: self.ext_pause(v))
self.extPauseOnlyPin.value_changed.connect(lambda v: self.ext_pause_only(v))
self.extResumePin.value_changed.connect(lambda v: self.ext_resume(v))
self.extAbortPin.value_changed.connect(lambda v: self.ext_abort(v))
self.extTouchOffPin.value_changed.connect(lambda v: self.ext_touch_off(v))
self.extLaserTouchOffPin.value_changed.connect(lambda v: self.ext_laser_touch_off(v))
self.extLaserTogglePin.value_changed.connect(lambda v: self.ext_laser_toggle(v))
self.extRunPausePin.value_changed.connect(lambda v: self.ext_run_pause(v))
self.extHeightOvrPlusPin.value_changed.connect(lambda v: self.height_ovr_pressed(v, 1))
self.extHeightOvrMinusPin.value_changed.connect(lambda v: self.height_ovr_pressed(v, -1))
self.extHeightOvrResetPin.value_changed.connect(lambda v: self.height_ovr_pressed(v, 0))
self.extHeightOvrCountsPin.value_changed.connect(lambda v: self.height_ovr_encoder(v))
self.extHeightOvrScalePin.value_changed.connect(lambda v: self.height_ovr_scale_change(v))
self.extChangeConsPin.value_changed.connect(lambda v: self.ext_change_consumables(v))
self.extCutRecRevPin.value_changed.connect(lambda v: self.cutrec_motion(-v))
self.extCutRecFwdPin.value_changed.connect(lambda v: self.cutrec_motion(v))
self.extCutRecNPin.value_changed.connect(lambda v: self.cutrec_move(v, 0, 1))
self.extCutRecNEPin.value_changed.connect(lambda v: self.cutrec_move(v, 1, 1))
self.extCutRecEPin.value_changed.connect(lambda v: self.cutrec_move(v, 1, 0))
self.extCutRecSEPin.value_changed.connect(lambda v: self.cutrec_move(v, 1, -1))
self.extCutRecSPin.value_changed.connect(lambda v: self.cutrec_move(v, 0, -1))
self.extCutRecSWPin.value_changed.connect(lambda v: self.cutrec_move(v, -1, -1))
self.extCutRecWPin.value_changed.connect(lambda v: self.cutrec_move(v, -1, 0))
self.extCutRecNWPin.value_changed.connect(lambda v: self.cutrec_move(v, -1, 1))
self.extCutRecCancelPin.value_changed.connect(lambda v: self.cutrec_cancel_pressed(v))
self.extTorchEnablePin.value_changed.connect(lambda v: self.ext_torch_enable_changed(v))
self.extThcEnablePin.value_changed.connect(lambda v: self.ext_thc_enable_changed(v))
self.extCornerLockEnablePin.value_changed.connect(lambda v: self.ext_corner_lock_enable_changed(v))
self.extVoidLockEnablePin.value_changed.connect(lambda v: self.ext_void_lock_enable_changed(v))
self.extIgnoreArcOkPin.value_changed.connect(lambda v: self.ext_ignore_arc_ok_changed(v))
self.extMeshModePin.value_changed.connect(lambda v: self.ext_mesh_mode_changed(v))
self.extOhmicProbeEnablePin.value_changed.connect(lambda v: self.ext_ohmic_probe_enable_changed(v))
self.extAutoVoltsEnablePin.value_changed.connect(lambda v: self.ext_auto_volts_enable_changed(v))
self.extJogSlowPin.value_changed.connect(self.ext_jog_slow)
self.extProbePin.value_changed.connect(lambda v: self.ext_probe_test(v))
self.extPulsePin.value_changed.connect(lambda v: self.ext_torch_pulse(v))
self.extOhmicPin.value_changed.connect(lambda v: self.ext_ohmic_test(v))
self.extFramingPin.value_changed.connect(lambda v: self.ext_frame_job(v))
self.probeTestErrorPin.value_changed.connect(lambda v: self.probe_test_error(v))
self.w.preview_stack.currentChanged.connect(self.preview_stack_changed)
self.w.gcode_stack.currentChanged.connect(self.gcode_stack_changed)
click_signal(self.w.material_label).connect(self.show_material_selector)
click_signal(self.w.velocity_label).connect(self.show_material_selector)
click_signal(self.w.velocity_show).connect(self.show_material_selector)
self.w.conv_line.pressed.connect(lambda: self.conv_call('line'))
self.w.conv_circle.pressed.connect(lambda: self.conv_call('circle'))
self.w.conv_ellipse.pressed.connect(lambda: self.conv_call('ellipse'))
self.w.conv_triangle.pressed.connect(lambda: self.conv_call('triangle'))
self.w.conv_rectangle.pressed.connect(lambda: self.conv_call('rectangle'))
self.w.conv_polygon.pressed.connect(lambda: self.conv_call('polygon'))
self.w.conv_bolt.pressed.connect(lambda: self.conv_call('bolt'))
self.w.conv_slot.pressed.connect(lambda: self.conv_call('slot'))
self.w.conv_star.pressed.connect(lambda: self.conv_call('star'))
self.w.conv_gusset.pressed.connect(lambda: self.conv_call('gusset'))
self.w.conv_sector.pressed.connect(lambda: self.conv_call('sector'))
self.w.conv_block.pressed.connect(lambda: self.conv_call('block'))
self.w.conv_new.pressed.connect(lambda: self.conv_call('new'))
self.w.conv_save.pressed.connect(lambda: self.conv_call('save'))
self.w.conv_settings.pressed.connect(lambda: self.conv_call('settings'))
self.w.conv_send.pressed.connect(lambda: self.conv_call('send'))
self.w.chk_override_jog.stateChanged.connect(self.override_jog_changed)
self.jogInhibited.value_changed.connect(lambda v: self.jog_inhibited_changed(v))
self.sensorActive.value_changed.connect(lambda v: self.sensor_active_changed(v))
self.zOffsetPin.value_changed.connect(lambda v: self.z_offset_changed(v))
self.laserRecStatePin.value_changed.connect(lambda v: self.laser_recovery_state_changed(v))
self.ohmicLedInPin.value_changed.connect(lambda v: self.ohmic_sensed(v))
self.w.webview_back.pressed.connect(self.web_back_pressed)
self.w.webview_forward.pressed.connect(self.web_forward_pressed)
self.w.webview_reload.pressed.connect(self.web_reload_pressed)
self.velReduct.value_changed.connect(lambda: self.vel_reduct())
def conv_call(self, operation):
if self.developmentPin.get():
reload(self.CONV)
if operation == 'block':
self.CONV.conv_block_pressed(self, self.w)
elif operation == 'new':
self.CONV.conv_new_pressed(self, self.w, 'button')
elif operation == 'save':
self.CONV.conv_save_pressed(self, self.w)
elif operation == 'settings':
self.CONV.conv_settings_pressed(self, self.w)
elif operation == 'send':
self.CONV.conv_send_pressed(self, self.w)
else:
self.CONV.conv_shape_request(self, self.w, f'conv_{operation}')
def set_axes_and_joints(self):
self.coordinates = 'xyz' # backup in case we cannot find valid coordinates
kinematics = self.iniFile.find('KINS', 'KINEMATICS').lower().split() or None
if not kinematics:
head = _translate('HandlerClass', 'INI File Error')
msg0 = _translate('HandlerClass', 'Error in [KINS]KINEMATICS in the INI file')
msg1 = _translate('HandlerClass', 'reverting to default coordinates of xyz')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}\n{msg1}\n')
else:
coords = [s for s in kinematics if 'coordinates' in s]
if coords:
self.coordinates = coords[0].split('=')[1].strip()
else:
coords = self.iniFile.find('KINS', 'COORDINATES').lower() or None
if coords:
self.coordinates = coords
# hide axes a, b, and c if not being used
for axis in 'abc':
if axis not in self.axes['valid']:
for i in self.axes[axis]:
self.w[i].hide()
# setup home buttons
for axis in self.axes['valid']:
self.w[f'home_{axis}'].set_joint(self.coordinates.index(axis))
self.w[f'home_{axis}'].set_joint_number(self.coordinates.index(axis))
for joint in range(len(self.coordinates)):
# check if home all button required
if not self.iniFile.find(f'JOINT_{joint}', 'HOME_SEQUENCE'):
self.w.home_all.hide()
# check if not joggable before homing
elif self.iniFile.find(f'JOINT_{joint}', 'HOME_SEQUENCE').startswith('-'):
if f'jog_{self.coordinates[joint]}_plus' not in self.jogSyncList:
self.jogSyncList.append(f'jog_{self.coordinates[joint]}_plus')
self.jogSyncList.append(f'jog_{self.coordinates[joint]}_minus')
self.jogButtonList.remove(f'jog_{self.coordinates[joint]}_plus')
self.jogButtonList.remove(f'jog_{self.coordinates[joint]}_minus')
# remove 'all_homed' requirement for buttons in self.jogButtonList
for jogButton in self.jogButtonList:
andRules = self.interlockRules[jogButton].get('and', {})
if 'all_homed' in andRules:
del andRules['all_homed']
def set_mode(self):
block1 = ['arc_ok_high', 'arc_ok_high_lbl', 'arc_ok_low', 'arc_ok_low_lbl']
block2 = ['arc_voltage_scale', 'arc_voltage_scale_lbl', 'arc_voltage_offset', 'arc_voltage_offset_lbl',
'voidlock_frm', 'height_per_volt', 'height_per_volt_lbl', 'thc_delay', 'thc_delay_lbl',
'thc_sample_counts', 'thc_sample_counts_lbl', 'thc_sample_threshold', 'thc_sample_threshold_lbl',
'thc_threshold', 'thc_threshold_lbl', 'pid_i_gain', 'pid_i_gain_lbl', 'pid_d_gain', 'pid_d_gain_lbl',
'use_auto_volts', 'use_auto_volts_lbl', 'led_thc_active', 'led_thc_active_lbl', 'arc_voltage',
'arc_override_frm', 'voidlock_slope', 'voidlock_slope_lbl', 'thc_auto', 'thc_auto_lbl']
if self.mode == 1:
hal.set_p('plasmac.mode', '1')
for widget in block1:
self.w[widget].hide()
elif self.mode == 2:
hal.set_p('plasmac.mode', '2')
for widget in block1 + block2:
self.w[widget].hide()
self.w.pid_p_gain_lbl.setText(_translate('HandlerClass', 'Speed %'))
def set_spinbox_parameters(self):
if self.units == 'in':
self.w.setup_feed_rate.setRange(4.0, int(self.thcFeedRate))
self.w.setup_feed_rate.setDecimals(1)
self.w.setup_feed_rate.setSingleStep(0.1)
self.w.safe_height.setRange(0, int(self.maxHeight))
self.w.safe_height.setDecimals(3)
self.w.safe_height.setSingleStep(0.01)
self.w.probe_feed_rate.setRange(4.0, int(self.thcFeedRate))
self.w.probe_feed_rate.setDecimals(1)
self.w.probe_feed_rate.setSingleStep(0.1)
self.w.probe_start_height.setRange(0, int(self.maxHeight))
self.w.probe_start_height.setDecimals(3)
self.w.probe_start_height.setSingleStep(0.01)
self.w.offset_feed_rate.setRange(4.0, int(self.offsetFeedRate))
self.w.offset_feed_rate.setDecimals(1)
self.w.offset_feed_rate.setSingleStep(0.1)
self.w.float_switch_travel.setRange(-1.0, 1.0)
self.w.float_switch_travel.setDecimals(3)
self.w.float_switch_travel.setSingleStep(0.001)
self.w.height_per_volt.setRange(0.001, 0.01)
self.w.height_per_volt.setDecimals(3)
self.w.height_per_volt.setSingleStep(0.001)
self.w.ohmic_probe_offset.setRange(-1.0, 1.0)
self.w.ohmic_probe_offset.setDecimals(3)
self.w.ohmic_probe_offset.setSingleStep(0.001)
self.w.skip_ihs_distance.setRange(0.0, 40.0)
self.w.skip_ihs_distance.setDecimals(1)
self.w.skip_ihs_distance.setSingleStep(0.1)
self.w.kerf_width.setRange(0.0, 1.0)
self.w.kerf_width.setDecimals(3)
self.w.kerf_width.setSingleStep(0.001)
self.w.cut_feed_rate.setRange(0.0, 999.0)
self.w.cut_feed_rate.setDecimals(1)
self.w.cut_feed_rate.setSingleStep(0.1)
self.w.cut_height.setRange(0.0, 1.0)
self.w.cut_height.setDecimals(3)
self.w.cut_height.setSingleStep(0.001)
self.w.pierce_height.setRange(0.0, 1.0)
self.w.pierce_height.setDecimals(3)
self.w.pierce_height.setSingleStep(0.001)
self.w.x_pierce_offset.setDecimals(3)
self.w.x_pierce_offset.setRange(-0.2, 0.2)
self.w.x_pierce_offset.setSingleStep(0.01)
self.w.y_pierce_offset.setDecimals(3)
self.w.y_pierce_offset.setRange(-0.2, 0.2)
self.w.y_pierce_offset.setSingleStep(0.01)
self.w.material_thickness.setRange(0.0, int(self.maxHeight))
self.w.material_thickness.setDecimals(3)
self.w.material_thickness.setSingleStep(0.001)
self.w.slat_height.setRange(0.0, int(self.maxHeight))
self.w.slat_height.setDecimals(3)
self.w.slat_height.setSingleStep(0.001)
else:
self.w.setup_feed_rate.setMaximum(int(self.thcFeedRate))
self.w.safe_height.setMaximum(int(self.maxHeight))
self.w.probe_feed_rate.setMaximum(int(self.thcFeedRate))
self.w.probe_start_height.setMaximum(int(self.maxHeight))
self.w.offset_feed_rate.setMaximum(int(self.offsetFeedRate))
self.w.material_thickness.setMaximum(int(self.maxHeight))
self.w.slat_height.setMaximum(int(self.maxHeight))
def set_probe_offset_pins(self):
hal.set_p('plasmac.offset-probe-x', f'{self.probeOffsetX}')
hal.set_p('plasmac.offset-probe-y', f'{self.probeOffsetY}')
hal.set_p('plasmac.offset-probe-delay', f'{self.probeDelay}')
def kb_jog(self, state, joint, direction, shift=False, linear=True):
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 not STATUS.is_man_mode() or not STATUS.machine_is_on() or \
(self.offsetsActivePin.get() and not self.manualCut):
return
if (shift or self.jogFast) and not self.manualCut:
rate = INFO.MAX_LINEAR_JOG_VEL
elif self.jogSlow and not self.w.jog_slow.isChecked():
rate = STATUS.get_jograte()/60/self.slowJogFactor
ACTION.JOG(joint, direction, rate, distance)
self.w.grabKeyboard()
else:
self.w.releaseKeyboard()
if not STATUS.get_jog_increment():
ACTION.JOG(joint, 0, 0, 0)
def keyboard_shortcuts(self):
if self.w.chk_keyboard_shortcuts.isChecked():
return True
else:
return False
def soft_keyboard(self):
if self.w.chk_soft_keyboard.isChecked():
inputType = 'CALCULATOR'
self.w.originoffsetview.setProperty('dialog_code_string', 'CALCULATOR')
self.w.originoffsetview.setProperty('text_dialog_code_string', 'KEYBOARD')
self.w.gcode_display.SendScintilla(QsciScintilla.SCI_SETEXTRAASCENT, 4)
self.w.gcode_display.SendScintilla(QsciScintilla.SCI_SETEXTRADESCENT, 4)
self.w.gcode_editor.editor.SendScintilla(QsciScintilla.SCI_SETEXTRAASCENT, 4)
self.w.gcode_editor.editor.SendScintilla(QsciScintilla.SCI_SETEXTRADESCENT, 4)
self.vkb_check()
if self.w.main_tab_widget.currentIndex() == self.PARAMETERS:
self.vkb_show(True)
elif self.w.main_tab_widget.currentIndex() == self.SETTINGS:
self.vkb_show()
self.w.chk_keyboard_shortcuts.setChecked(False)
self.w.chk_keyboard_shortcuts.setEnabled(False)
else:
inputType = 'ENTRY'
self.w.originoffsetview.setProperty('dialog_code_string', '')
self.w.originoffsetview.setProperty('text_dialog_code_string', '')
self.w.gcode_display.SendScintilla(QsciScintilla.SCI_SETEXTRAASCENT, 1)
self.w.gcode_display.SendScintilla(QsciScintilla.SCI_SETEXTRADESCENT, 1)
self.w.gcode_editor.editor.SendScintilla(QsciScintilla.SCI_SETEXTRAASCENT, 1)
self.w.gcode_editor.editor.SendScintilla(QsciScintilla.SCI_SETEXTRADESCENT, 1)
self.vkb_hide()
self.w.chk_keyboard_shortcuts.setEnabled(True)
for axis in 'xyzabc':
button = f'touch_{axis}'
self.w[button].dialog_code = inputType
def overlay_update(self, state):
self.w.gcodegraphics.update()
self.w.conv_preview.update()
def dialog_show_ok(self, icon, title, error, bText=_translate('HandlerClass', 'OK')):
msg = QMessageBox(self.w)
buttonY = msg.addButton(QMessageBox.Yes)
buttonY.setText(bText)
buttonY.setIcon(QIcon())
msg.setIcon(icon)
msg.setWindowTitle(title)
msg.setText(error)
msg.setWindowFlag(Qt.Popup)
msg.exec_()
self.dialogError = False
return msg
def dialog_show_yesno(self, icon, title, error, bY=_translate('HandlerClass', 'YES'), bN=_translate('HandlerClass', 'NO')):
msg = QMessageBox(self.w)
buttonY = msg.addButton(QMessageBox.Yes)
buttonY.setText(bY)
buttonY.setIcon(QIcon())
buttonN = msg.addButton(QMessageBox.No)
buttonN.setText(bN)
buttonN.setIcon(QIcon())
msg.setIcon(icon)
msg.setWindowTitle(title)
msg.setText(error)
msg.setWindowFlag(Qt.WindowStaysOnTopHint) if 'shutdown' in error else msg.setWindowFlag(Qt.Popup)
choice = msg.exec_()
if choice == QMessageBox.Yes:
return True
else:
return False
# virtkb: 0=none, 1=alpha~close, 2=num~close, 3=alpha~num, 4=num~num, 5=alpha~alpha, 6=num~alpha
def dialog_input(self, virtkb, title, text, btn1, btn2, delay=None):
dialog = QInputDialog(self.w)
dialog.setWindowTitle(title)
dialog.setLabelText(f'{text}')
if btn1:
dialog.setOkButtonText(btn1)
if btn2:
dialog.setCancelButtonText(btn2)
if delay is not None:
dialog.setTextValue(f'{delay:0.2f}')
for button in dialog.findChildren(QPushButton):
button.setIcon(QIcon())
if virtkb in (1, 3, 5):
self.vkb_show(False)
elif virtkb in (2, 4, 6):
self.vkb_show(True)
valid = dialog.exec_()
if virtkb < 3:
self.vkb_hide()
elif virtkb in (3, 4):
self.vkb_show(True)
elif virtkb in (5, 6):
self.vkb_show(False)
out = dialog.textValue()
return valid, out
def dialog_run_from_line(self):
rFl = QDialog(self.w)
rFl.setWindowTitle(_translate('HandlerClass', 'Run From Line'))
lbl1 = QLabel(_translate('HandlerClass', 'USE LEADIN:'))
lbl2 = QLabel(_translate('HandlerClass', 'LEADIN LENGTH:'))
lbl3 = QLabel(_translate('HandlerClass', 'LEADIN ANGLE:'))
lbl4 = QLabel('')
leadinDo = QCheckBox()
leadinLength = QDoubleSpinBox()
leadinAngle = QDoubleSpinBox()
buttons = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
buttonBox = QDialogButtonBox(buttons)
buttonBox.accepted.connect(rFl.accept)
buttonBox.rejected.connect(rFl.reject)
buttonBox.button(QDialogButtonBox.Ok).setText(_translate('HandlerClass', 'LOAD'))
buttonBox.button(QDialogButtonBox.Ok).setIcon(QIcon())
buttonBox.button(QDialogButtonBox.Cancel).setText(_translate('HandlerClass', 'CANCEL'))
buttonBox.button(QDialogButtonBox.Cancel).setIcon(QIcon())
layout = QGridLayout()
layout.addWidget(lbl1, 0, 0)
layout.addWidget(lbl2, 1, 0)
layout.addWidget(lbl3, 2, 0)
layout.addWidget(lbl4, 3, 0)
layout.addWidget(leadinDo, 0, 1)
layout.addWidget(leadinLength, 1, 1)
layout.addWidget(leadinAngle, 2, 1)
layout.addWidget(buttonBox, 4, 0, 1, 2)
rFl.setLayout(layout)
lbl1.setAlignment(Qt.AlignRight | Qt.AlignBottom)
lbl2.setAlignment(Qt.AlignRight | Qt.AlignBottom)
lbl3.setAlignment(Qt.AlignRight | Qt.AlignBottom)
if self.units == 'in':
leadinLength.setDecimals(2)
leadinLength.setSingleStep(0.05)
leadinLength.setSuffix(' inch')
leadinLength.setMinimum(0.05)
else:
leadinLength.setDecimals(0)
leadinLength.setSingleStep(1)
leadinLength.setSuffix(' mm')
leadinLength.setMinimum(1)
leadinAngle.setDecimals(0)
leadinAngle.setSingleStep(1)
leadinAngle.setSuffix(' deg')
leadinAngle.setRange(-359, 359)
leadinAngle.setWrapping(True)
self.vkb_show(True)
result = rFl.exec_()
self.vkb_hide()
# load clicked
if result:
return {'cancel': False, 'do': leadinDo.isChecked(), 'length': leadinLength.value(), 'angle': leadinAngle.value()}
# cancel clicked
else:
return {'cancel': True}
def dialog_rfl_type(self):
rflT = QDialog(self.w)
rflT.setWindowTitle(_translate('HandlerClass', 'Run From Line'))
run = QRadioButton(_translate('HandlerClass', 'HERE TO END'))
cut = QRadioButton(_translate('HandlerClass', 'THIS CUTPATH'))
lbl = QLabel('')
buttons = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
buttonBox = QDialogButtonBox(buttons)
buttonBox.accepted.connect(rflT.accept)
buttonBox.rejected.connect(rflT.reject)
buttonBox.button(QDialogButtonBox.Ok).setText(_translate('HandlerClass', 'OK'))
buttonBox.button(QDialogButtonBox.Ok).setIcon(QIcon())
buttonBox.button(QDialogButtonBox.Cancel).setText(_translate('HandlerClass', 'CANCEL'))
buttonBox.button(QDialogButtonBox.Cancel).setIcon(QIcon())
layout = QGridLayout()
layout.addWidget(run, 0, 0)
layout.addWidget(cut, 1, 0)
layout.addWidget(lbl, 2, 0)
layout.addWidget(buttonBox, 3, 0)
rflT.setLayout(layout)
run.setChecked(True)
self.vkb_show(True)
result = rflT.exec_()
self.vkb_hide()
# ok clicked
if result:
if cut.isChecked():
type_ = 'cut'
else:
type_ = 'end'
return {'cancel': False, 'type': type_}
# cancel clicked
else:
return {'cancel': True, 'type': 'end'}
def show_cut_critical_dialog(self, rcButtonList):
checkStyle = 'QCheckBox::indicator { margin-left: 8px; margin-right: 8px; }\n \
QCheckBox { font-size: 11pt; }'
ccr = QDialog(self.w)
ccr.setWindowTitle(_translate('HandlerClass', 'Untoggled Cut Critical Buttons'))
icon = QApplication.style().standardIcon(QStyle.SP_MessageBoxWarning)
iconLabel = QLabel()
iconLabel.setPixmap(icon.pixmap(32, 32))
msg0 = _translate('HandlerClass', 'The following buttons have not been toggled')
msg1 = _translate('HandlerClass', 'Select items to be toggled when CONTINUE is clicked')
lbl0 = QLabel(f'\n{msg0}:\n')
lbl1 = QLabel('')
lbl2 = QLabel(f'\n{msg1}\n')
lbl2.setStyleSheet("padding-left: 1px;")
buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttonBox.accepted.connect(ccr.accept)
buttonBox.rejected.connect(ccr.reject)
buttonBox.button(QDialogButtonBox.Ok).setText(_translate('HandlerClass', 'CONTINUE'))
buttonBox.button(QDialogButtonBox.Ok).setIcon(QIcon())
buttonBox.button(QDialogButtonBox.Ok).setMinimumWidth(120)
buttonBox.button(QDialogButtonBox.Cancel).setText(_translate('HandlerClass', 'CANCEL'))
buttonBox.button(QDialogButtonBox.Cancel).setIcon(QIcon())
hLayout = QHBoxLayout()
hLayout.addWidget(iconLabel)
hLayout.addWidget(lbl0)
hLayout.addStretch()
vLayout = QVBoxLayout()
vLayout.addLayout(hLayout)
checkBoxes = []
for bText in rcButtonList:
checkBox = QCheckBox(bText)
checkBox.setStyleSheet(checkStyle)
vLayout.addWidget(checkBox)
checkBoxes.append(checkBox)
if len(rcButtonList) > 1:
toggleAll = QCheckBox(_translate('HandlerClass', 'TOGGLE ALL'))
toggleAll.setStyleSheet(checkStyle)
vLayout.addWidget(lbl1)
vLayout.addWidget(toggleAll)
toggleAll.stateChanged.connect(lambda state: [checkBox.setChecked(state == Qt.Checked) for checkBox in checkBoxes])
vLayout.addWidget(lbl2)
vLayout.addWidget(buttonBox)
ccr.setLayout(vLayout)
result = ccr.exec_()
checkList = [checkBox.text() for checkBox in checkBoxes if checkBox.isChecked()]
return result, checkList
def invert_pin_state(self, halpin):
if 'qtplasmac.ext_out_' in halpin:
pin = f'out{halpin.split("out_")[1]}Pin'
self[pin].set(not hal.get_value(halpin))
else:
hal.set_p(halpin, str(not hal.get_value(halpin)))
self.set_button_color()
def set_button_color(self):
for halpin in self.halTogglePins:
if hal.get_value(halpin):
if self.button_normal_check(self.halTogglePins[halpin][0]):
self.button_active(self.halTogglePins[halpin][0])
text = 3
else:
if not self.button_normal_check(self.halTogglePins[halpin][0]):
self.button_normal(self.halTogglePins[halpin][0])
text = 2
if self.halTogglePins[halpin][3]:
toggleText = self.halTogglePins[halpin][text].replace('\\', '\n')
self.w[self.halTogglePins[halpin][0]].setText(f'{toggleText}')
for halpin in self.halPulsePins:
if hal.get_value(halpin):
if self.button_normal_check(self.halPulsePins[halpin][0]):
self.button_active(self.halPulsePins[halpin][0])
else:
if not self.button_normal_check(self.halPulsePins[halpin][0]):
self.button_normal(self.halPulsePins[halpin][0])
if self.tlButton:
for button in self.tlButton:
if self.laserOnPin.get():
if self.button_normal_check(button):
self.button_active(button)
else:
if not self.button_normal_check(button):
self.button_normal(button)
def cut_critical_toggle_check(self):
# self.halTogglePins format is: button name, run critical flag, button text, alt button text
checkDict = {
self.halTogglePins[halpin][2].replace('\n', ' '): halpin
for halpin in self.halTogglePins
if self.halTogglePins[halpin][1] and not hal.get_value(halpin)
}
if checkDict and self.w.torch_enable.isChecked():
result, checked = self.show_cut_critical_dialog(list(checkDict.keys()))
if result:
for bText in checked:
halpin = checkDict[bText]
self.user_button_down(int(self.halTogglePins[halpin][0].replace('button_','')))
return result
return True
def preview_stack_changed(self):
index = self.w.preview_stack.currentIndex()
if index == self.PREVIEW:
self.autorepeat_keys(False)
elif index == self.OPEN:
self.button_active(self.w.file_open.objectName())
self.autorepeat_keys(True)
self.vkb_hide()
self.w.filemanager.table.setFocus()
elif index == self.EDIT:
self.button_active(self.w.file_edit.objectName())
text0 = _translate('HandlerClass', 'EDIT')
text1 = _translate('HandlerClass', 'CLOSE')
self.w.file_edit.setText(f'{text0}\n{text1}')
self.autorepeat_keys(True)
elif index == self.CAMERA:
self.button_active('camera')
self.cameraOn = True
elif index == self.OFFSETS:
self.button_active(self.ovButton)
for row in range(self.w.originoffsetview.tablemodel.rowCount(self.w.originoffsetview)):
self.w.originoffsetview.resizeRowToContents(row)
for column in range(self.w.originoffsetview.tablemodel.rowCount(self.w.originoffsetview)-1):
self.w.originoffsetview.resizeColumnToContents(column)
elif index == self.USER_MANUAL:
self.button_active(self.umButton)
self.autorepeat_keys(True)
if index != self.OPEN or index == self.PREVIEW:
self.button_normal(self.w.file_open.objectName())
if index != self.EDIT or index == self.PREVIEW:
self.button_normal(self.w.file_edit.objectName())
self.w.file_edit.setText(_translate('HandlerClass', 'EDIT'))
if index != self.CAMERA or index == self.PREVIEW:
self.button_normal('camera')
if index != self.OFFSETS or index == self.PREVIEW:
if self.ovButton:
self.button_normal(self.ovButton)
if index != self.USER_MANUAL or index == self.PREVIEW:
if self.umButton:
self.button_normal(self.umButton)
self.refresh_button_states()
def gcode_stack_changed(self):
if self.w.gcode_stack.currentIndex() == self.MDI:
self.button_active(self.w.mdi_show.objectName())
text0 = _translate('HandlerClass', 'MDI')
text1 = _translate('HandlerClass', 'CLOSE')
self.w.mdi_show.setText(f'{text0}\n{text1}')
self.w.mdihistory.reload()
self.w.mdihistory.MDILine.setFocus()
self.autorepeat_keys(True)
else:
self.button_normal(self.w.mdi_show.objectName())
self.w.mdi_show.setText(_translate('HandlerClass', 'MDI'))
if self.w.preview_stack.currentIndex() != self.EDIT:
self.autorepeat_keys(False)
self.refresh_button_states()
def set_mc_states(self, state):
if self.manualCut:
self.jogPreManCut[0] = self.w.jog_slow.isChecked()
self.jogPreManCut[1] = self.w.jog_slider.value()
self.jogPreManCut[2] = self.w.jogincrements.currentIndex()
if self.w.jog_slow.isChecked():
self.jog_slow_pressed(True)
self.w.jog_slider.setValue(self.w.cut_feed_rate.value())
self.w.jogincrements.setCurrentIndex(0)
else:
if self.jogPreManCut[0]:
self.jog_slow_pressed(True)
self.w.jog_slider.setValue(self.jogPreManCut[1])
self.w.jogincrements.setCurrentIndex(self.jogPreManCut[2])
self.refresh_button_states()
def show_material_selector(self):
if STATUS.is_interp_idle():
self.w.material_selector.showPopup()
def autorepeat_keys(self, state):
if not self.autorepeat_skip:
if state:
ACTION.ENABLE_AUTOREPEAT_KEYS(' ')
else:
ACTION.DISABLE_AUTOREPEAT_KEYS(' ')
def clear_rfl(self):
self.rflActive = False
self.startLine = 0
self.preRflFile = ''
self.w.gcodegraphics.clear_highlight()
def ohmic_sensed(self, state):
if state:
hal.set_p('qtplasmac.led_ohmic_probe', '1')
self.ohmicLedTimer.start(150)
def vm_check(self):
try:
response = (Popen('cat /sys/class/dmi/id/product_name', stdout=PIPE, stderr=PIPE, shell=True).communicate()[0]).decode('utf-8')
if 'virtual' in response.lower() or 'vmware' in response.lower():
self.virtualMachine = True
STATUS.emit('update-machine-log', f'"{response.strip()}" Virtual Machine detected', 'TIME')
except:
pass
def toggle_joint_mode(self):
if not STATUS.is_all_homed():
return
if hal.get_value('halui.mode.is-teleop'):
teleop = False
elif hal.get_value('halui.mode.is-joint'):
teleop = True
ACTION.cmd.teleop_enable(teleop)
for axis in self.axes['valid']:
self.w[f'touch_{axis}'].setEnabled(teleop)
self.w[f'dro_{axis}'].setEnabled(teleop)
self.w[f'dro_label_{axis}'].setProperty('homed', teleop)
self.w[f'dro_label_{axis}'].setStyle(self.w[f'dro_label_{axis}'].style())
time.sleep(0.1)
self.w.gcodegraphics.update()
self.w.conv_preview.update()
# called when qtplasmac.vel-reduct pin value changes, and by update_periodic every 100mS when not idle to ensure
# the label does not get stuck due to timing discrepancies between the GUI and the component poll rates
def vel_reduct(self):
factor = self.velReduct.get()
self.w.velocity_label.setText('VEL:' if factor == 1.0 else f'VEL@{factor * 100:.0f}%:')
#########################################################################################################################
# TIMER FUNCTIONS #
#########################################################################################################################
def shutdown_timeout(self):
self.w.close()
def startup_timeout(self):
if STATUS.stat.estop:
self.estop_state(True)
self.w.gcode_display.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.view_t_pressed(self.w.gcodegraphics)
self.set_signal_connections()
if self.firstRun is True:
self.firstRun = False
def watchdog_has_bit(self):
states = {
self.PROBE_HEIGHT: _translate('HandlerClass', 'moving to probe height'),
self.PROBE_DOWN: _translate('HandlerClass', 'probing down'),
self.PROBE_UP: _translate('HandlerClass', 'probing up'),
self.ZERO_HEIGHT: _translate('HandlerClass', 'moving to zero height'),
self.PIERCE_HEIGHT: _translate('HandlerClass', 'moving to pierce height'),
self.PUDDLE_JUMP: _translate('HandlerClass', 'moving to puddle jump height'),
self.CUT_HEIGHT: _translate('HandlerClass', 'moving to cut height'),
self.SAFE_HEIGHT: _translate('HandlerClass', 'moving to safe height'),
self.MAX_HEIGHT: _translate('HandlerClass', 'moving to maximum height')
}
head = _translate('HandlerClass', 'Z Axis Motion Timeout')
msg0 = _translate('HandlerClass', 'Z axis did not reach the destination while')
msg1 = _translate('HandlerClass', 'program is paused')
msg2 = _translate('HandlerClass', 'check settings on the PARAMETERS tab')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} {states[self.watchDogState]}\n{msg1}\n{msg2}\n')
ACTION.PAUSE()
def update_periodic(self):
if not STATUS.is_interp_idle():
self.vel_reduct()
self.set_button_color()
if self.flasher:
self.flasher -= 1
else:
self.flasher = self.flashRate
self.flashState = not self.flashState
self.flasher_timeout()
if not self.firstRun and self.pmPort and not hal.component_exists('pmx485'):
if self.pmx485_check(self.pmPort, True):
self.w.gas_pressure.show()
self.w.gas_pressure_label.show()
self.w.cut_mode.show()
self.w.cut_mode_label.show()
self.w.pmx485_frame.show()
self.w.pmx_stats_frame.show()
self.pmx485_startup(self.pmPort)
if not self.firstRun and os.path.isfile(self.upFile):
exec(open(self.upFile).read())
def flasher_timeout(self):
if STATUS.is_auto_paused():
if self.flashState:
self.w.pause_resume.setText(_translate('HandlerClass', 'CYCLE RESUME'))
else:
self.w.pause_resume.setText('')
elif self.w.jog_stack.currentIndex() == self.JOG:
self.w.pause_resume.setText(_translate('HandlerClass', 'CYCLE PAUSE'))
text = _translate('HandlerClass', 'FEED')
if self.w.feed_slider.value() != 100:
if self.flashState:
self.w.feed_label.setText(f'{text}\n{STATUS.stat.feedrate * 100:.0f}%')
else:
self.w.feed_label.setText(' \n ')
else:
self.w.feed_label.setText(f'{text}\n{STATUS.stat.feedrate * 100:.0f}%')
text = _translate('HandlerClass', 'RAPID')
if self.w.rapid_slider.value() != 100:
if self.flashState:
self.w.rapid_label.setText(f'{text}\n{STATUS.stat.rapidrate * 100:.0f}%')
else:
self.w.rapid_label.setText(' \n ')
else:
self.w.rapid_label.setText(f'{text}\n{STATUS.stat.rapidrate * 100:.0f}%')
text = _translate('HandlerClass', 'JOG')
if self.manualCut:
if self.flashState:
self.w.run.setText(_translate('HandlerClass', 'MANUAL CUT'))
self.w.jogs_label.setText(f'{text}\n{STATUS.get_jograte():.0f}')
else:
self.w.run.setText('')
self.w.jogs_label.setText(' \n ')
if self.heightOvr > 0.01 or self.heightOvr < -0.01:
if self.flashState:
self.w.height_ovr_label.setText(f'{self.heightOvr:.2f}')
else:
self.w.height_ovr_label.setText('')
else:
self.w.height_ovr_label.setText(f'{self.heightOvr:.2f}')
if self.flash_error and self.error_present:
if self.flashState:
self.w.error_label.setText(_translate('HandlerClass', 'ERROR SENT TO MACHINE LOG'))
else:
self.w.error_label.setText('')
if self.startLine > 0:
if not self.w.run.text().startswith(_translate('HandlerClass', 'RUN')):
if self.flashState:
self.w.run.setText(self.runText)
else:
self.w.run.setText('')
elif not self.manualCut:
self.w.run.setText(_translate('HandlerClass', 'CYCLE START'))
if not self.w.pmx485_enable.isChecked():
self.w.pmx485_label.setText('')
self.pmx485LabelState = None
elif self.pmx485CommsError:
if self.flashState:
self.w.pmx485_label.setText(_translate('HandlerClass', 'COMMS ERROR'))
self.pmx485LabelState = None
else:
self.w.pmx485_label.setText('')
self.pmx485LabelState = None
elif not self.pmx485LabelState:
if self.flashState:
self.w.pmx485_label.setText(f'Fault Code: {self.pmx485FaultCode}')
self.pmx485LabelState = None
else:
self.w.pmx485_label.setText('')
self.pmx485LabelState = None
def probe_timeout(self):
if self.probeTime > 1:
self.probeTime -= 1
self.probeTimer.start(1000)
self.w[self.ptButton].setText(f'{self.probeTime}')
else:
self.probe_test_stop()
log = _translate('HandlerClass', 'Probe test completed')
STATUS.emit('update-machine-log', log, 'TIME')
def torch_timeout(self):
self.tpRemaining = max(0.0, self.tpRemaining - 0.1)
self.w[self.tpButton].setText(f'{self.tpRemaining:.1f}')
if self.tpRemaining > 0:
return
self.tpTimer.stop()
if not hal.get_value('plasmac.torch-pulse-hold') and not self.extPulsePin.get():
self.torch_pulse_states(True)
log = _translate('HandlerClass', 'Torch pulse completed')
STATUS.emit('update-machine-log', log, 'TIME')
else:
text0 = _translate('HandlerClass', 'TORCH')
text1 = _translate('HandlerClass', 'ON')
self.w[self.tpButton].setText(f'{text0}\n{text1}')
def pulse_timer_timeout(self):
# halPulsePins format is: button name, pulse time, button text, remaining time, button number
active = False
try:
for halpin in self.halPulsePins:
if self.halPulsePins[halpin][3] > 0.05:
active = True
if self.halPulsePins[halpin][1] == self.halPulsePins[halpin][3]:
self.invert_pin_state(halpin)
self.halPulsePins[halpin][3] -= 0.1
self.w[self.halPulsePins[halpin][0]].setText(f'{self.halPulsePins[halpin][3]:0.1f}')
elif self.w[self.halPulsePins[halpin][0]].text() != self.halPulsePins[halpin][2]:
self.invert_pin_state(halpin)
self.halPulsePins[halpin][3] = 0
self.w[self.halPulsePins[halpin][0]].setText(f'{self.halPulsePins[halpin][2]}')
except Exception as err:
self.halPulsePins[halpin][3] = 0
bNum = self.halPulsePins[halpin][4]
head = _translate('HandlerClass', 'HAL Pin Error')
msg0 = _translate('HandlerClass', 'Invalid code for user button')
msg1 = _translate('HandlerClass', 'Failed to pulse HAL pin')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}\n"{halpin}" {err}\n')
if not active:
self.pulseTimer.stop()
def laser_timeout(self):
if self.w.laser.isDown() or self.extLaserButton:
if self.w.run.isEnabled() and self.laserButtonState in ['reset', 'laser']:
framingError = self.bounds_check_framing(self.laserOffsetX, self.laserOffsetY, True)[0]
if framingError:
head = _translate('HandlerClass', 'Axis Limit Error')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{framingError}\n')
self.laserButtonState = 'reset'
self.button_press_timeout('laser')
return
newX = STATUS.get_position()[0][0] - STATUS.stat.g5x_offset[0] - self.laserOffsetX
newY = STATUS.get_position()[0][1] - STATUS.stat.g5x_offset[1] - self.laserOffsetY
units = '20' if self.gcodeProps['gcode_units'] == 'in' else '21'
ACTION.CALL_MDI_WAIT(f'G{units} G10 L20 P0 X{newX / self.boundsMultiplier} Y{newY / self.boundsMultiplier}')
self.laserDryRunCoords = [STATUS.stat.g5x_offset[0], STATUS.stat.g5x_offset[1]]
self.laserOnPin.set(1)
hal.set_p('plasmac.dry-run', '1')
self.run_clicked()
return
else:
self.laserButtonState = 'reset'
self.button_press_timeout('laser')
self.laserOnPin.set(0)
self.laserButtonState = 'reset'
self.w.laser.setText(_translate('HandlerClass', 'LASER'))
def ohmic_led_timeout(self):
if not self.ohmicLedInPin.get():
hal.set_p('qtplasmac.led_ohmic_probe', '0')
else:
self.ohmicLedTimer.start(50)
#########################################################################################################################
# USER BUTTON FUNCTIONS #
#########################################################################################################################
def user_button_setup(self):
self.iniButtonCodes = ['Codes']
iniButtonCodes = ['Codes']
self.probePressed = False
self.probeTime = 0
self.probeTimer = QTimer()
self.probeTimer.setSingleShot(True)
self.probeTimer.timeout.connect(self.probe_timeout)
self.tpRemaining = 0.0
self.tpTimer = QTimer()
self.tpTimer.setSingleShot(False)
self.tpTimer.setInterval(100)
self.tpTimer.timeout.connect(self.torch_timeout)
self.pulseTime = 0
self.pulseTimer = QTimer()
self.pulseTimer.timeout.connect(self.pulse_timer_timeout)
self.cutType = 0
self.single_cut_request = False
self.oldFile = None
singleCodes = ['change-consumables', 'cut-type', 'framing', 'manual-cut', 'offsets-view', 'ohmic-test',
'probe-test', 'single-cut', 'torch-pulse', 'user-manual', 'latest-file', 'toggle-joint']
head = _translate('HandlerClass', 'User Button Error')
for bNum in range(1, 21):
self.w[f'button_{bNum}'].setCheckable(False)
bName = self.PREFS.getpref(f'{bNum} Name', '', str, 'BUTTONS') or None
bCode = self.PREFS.getpref(f'{bNum} Code', '', str, 'BUTTONS') or None
if bName or bCode:
self.w[f'ub_name_{bNum}'].setText(bName)
self.w[f'ub_code_{bNum}'].setText(bCode)
if (bCode and not bName) or (not bCode and bName):
msg0 = _translate('HandlerClass', 'are both required')
msg1 = _translate('HandlerClass', 'only one has been specified for')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\nCODE + NAME {msg0}\n{msg1} BUTTON_{bNum}\n')
self.w[f'button_{bNum}'].setText('')
self.iniButtonCodes.append('')
continue
if not bCode:
self.w[f'button_{bNum}'].setText('')
self.iniButtonCodes.append('')
continue
code = bCode.lower().strip().split()[0]
if code in singleCodes and code in iniButtonCodes:
msg1 = _translate('HandlerClass', 'Duplicate code entry for')
msg2 = _translate('HandlerClass', 'Using first instance only of')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg1} BUTTON_{iniButtonCodes.index(code)} + BUTTON_{bNum}\n{msg2} {bCode.split()[0]}\n')
self.w[f'button_{bNum}'].setText('')
self.iniButtonCodes.append('')
continue
self.iniButtonCodes.append(bCode)
iniButtonCodes.append(code)
msg0 = _translate('HandlerClass', 'Invalid code for user button')
bNames = bName.split('\\')
bLabel = bNames[0]
if len(bNames) > 1:
for name in range(1, len(bNames)):
bLabel += f'\n{bNames[name]}'
self.w[f'button_{bNum}'].setText(bLabel)
# toggle-laser can be anywhere in the button code
if 'toggle-laser' in bCode:
self.tlButton.append(f'button_{bNum}')
continue
# button code is required to start with the following codes
if code == 'change-consumables':
self.ccParm = bCode.replace('change-consumables', '').replace(' ', '').lower() or None
if self.ccParm is not None and ('x' in self.ccParm or 'y' in self.ccParm):
self.ccButton = f'button_{bNum}'
self.interlockRules[self.ccButton] = self.interlockRules['change-consumables_template'].copy()
else:
msg1 = _translate('HandlerClass', 'Check button code for invalid or missing arguments')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}\n')
continue
elif code == 'probe-test':
if len(bCode.split()) < 3:
if bCode.lower().replace('probe-test', '').strip():
try:
self.ptTime = round(float(bCode.lower().replace('probe-test', '').strip()))
except:
msg1 = _translate('HandlerClass', 'Check button code for invalid seconds argument')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}\n')
continue
else:
self.ptTime = 10
self.ptButton = f'button_{bNum}'
self.interlockRules[self.ptButton] = self.interlockRules['probe-test_template'].copy()
self.probeText = self.w[self.ptButton].text()
else:
msg1 = _translate('HandlerClass', 'Check button code for extra arguments')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}\n')
continue
elif code == 'torch-pulse':
if len(bCode.split()) < 3:
if bCode.lower().replace('torch-pulse', '').strip():
try:
self.tpTime = round(float(bCode.lower().replace('torch-pulse', '').strip()), 1)
except:
msg1 = _translate('HandlerClass', 'Check button code for invalid seconds argument')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}\n')
continue
self.tpTime = 3.0 if self.tpTime > 3.0 else self.tpTime
else:
self.tpTime = 1.0
self.tpButton = f'button_{bNum}'
self.interlockRules[self.tpButton] = self.interlockRules['torch-pulse_template'].copy()
self.tpText = self.w[self.tpButton].text()
else:
msg1 = _translate('HandlerClass', 'Check button code for extra arguments')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}\n')
continue
elif code == 'ohmic-test':
self.otButton = f'button_{bNum}'
self.interlockRules[self.otButton] = self.interlockRules['ohmic-test_template'].copy()
elif code == 'framing':
frButton = True
self.defaultZ = True
self.frFeed = 0
bCode = bCode.lower().replace('framing', '').strip()
if 'usecurrentzheight' in bCode:
bCode = bCode.lower().replace('usecurrentzheight', '').strip()
self.defaultZ = False
if len(bCode):
if bCode[0] == 'f':
try:
self.frFeed = float(bCode.replace('f', ''))
except:
msg1 = _translate('HandlerClass', 'Check button code for invalid feed argument')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}\n')
frButton = False
continue
else:
msg1 = _translate('HandlerClass', 'Check button code for invalid arguments')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}\n')
continue
if frButton:
self.frButton = f'button_{bNum}'
self.interlockRules[self.frButton] = self.interlockRules['framing_template'].copy()
elif code == 'cut-type':
self.ctButton = f'button_{bNum}'
self.interlockRules[self.ctButton] = self.interlockRules['cut-type_template'].copy()
elif code == 'single-cut':
self.scButton = f'button_{bNum}'
self.interlockRules[self.scButton] = self.interlockRules['single-cut_template'].copy()
elif code == 'manual-cut':
self.mcButton = f'button_{bNum}'
self.interlockRules[self.mcButton] = self.interlockRules['manual-cut_template'].copy()
elif code == 'load':
self.interlockRules[f'button_{bNum}'] = self.interlockRules['load_template'].copy()
elif code == 'toggle-halpin':
head = _translate('HandlerClass', 'HAL Pin Error')
altLabel = None
if ';;' in bCode:
altLabel = bCode[bCode.index(';;') + 2:].strip()
bCode = bCode[:bCode.index(';;')].strip()
if len(bCode.split()) == 3 and 'cutcritical' in bCode.lower():
critical = True
elif len(bCode.split()) == 2:
critical = False
else:
head = _translate('HandlerClass', 'User Button Error')
msg1 = _translate('HandlerClass', 'Check button code for invalid arguments')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}\n')
continue
halpin = bCode.lower().split('toggle-halpin')[1].split(' ')[1].strip()
excludedHalPins = ('plasmac.torch-pulse-start', 'plasmac.ohmic-test',
'plasmac.probe-test', 'plasmac.consumable-change')
if halpin in excludedHalPins:
msg1 = _translate('HandlerClass', 'HAL pin')
msg2 = _translate('HandlerClass', 'must be toggled')
msg3 = _translate('HandlerClass', 'using standard button code')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1} "{halpin}" {msg2}\n{msg3}\n')
continue
else:
try:
hal.get_value(halpin)
self.interlockRules[f'button_{bNum}'] = self.interlockRules['toggle-halpin_template'].copy()
except:
msg1 = _translate('HandlerClass', 'HAL pin')
msg2 = _translate('HandlerClass', 'does not exist')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1} "{halpin}" {msg2}\n')
continue
# halTogglePins format is: button name, run critical flag, button text, alt button text
self.halTogglePins[halpin] = [f'button_{bNum}', critical, bLabel, altLabel]
elif code == 'pulse-halpin':
if len(bCode.split()) < 4:
try:
code, halpin, delay = bCode.lower().strip().split()
except:
try:
code, halpin = bCode.lower().strip().split()
delay = '1.0'
except:
head = _translate('HandlerClass', 'User Button Error')
msg1 = _translate('HandlerClass', 'Check button code for invalid arguments')
code = halpin = delay = ''
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}\n')
continue
excludedHalPins = ('plasmac.torch-pulse-start', 'plasmac.ohmic-test',
'plasmac.probe-test', 'plasmac.consumable-change')
head = _translate('HandlerClass', 'HAL Pin Error')
if halpin in excludedHalPins:
msg1 = _translate('HandlerClass', 'HAL pin')
msg2 = _translate('HandlerClass', 'must be pulsed')
msg3 = _translate('HandlerClass', 'using standard button code')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1} "{halpin}" {msg2}\n{msg3}\n')
continue
else:
try:
hal.get_value(halpin)
self.interlockRules[f'button_{bNum}'] = self.interlockRules['pulse-halpin_template'].copy()
except:
msg1 = _translate('HandlerClass', 'HAL pin')
msg2 = _translate('HandlerClass', 'does not exist')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1} "{halpin}" {msg2}\n')
continue
# halPulsePins format is: button name, pulse time, button text, remaining time, button number
try:
self.halPulsePins[halpin] = [f'button_{bNum}', float(delay), bLabel, 0.0, bNum]
except:
head = _translate('HandlerClass', 'User Button Error')
msg1 = _translate('HandlerClass', 'Check button code for invalid seconds argument')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}\n')
continue
else:
head = _translate('HandlerClass', 'User Button Error')
msg1 = _translate('HandlerClass', 'Check button code for invalid arguments')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}\n')
continue
elif code == 'offsets-view':
self.ovButton = f'button_{bNum}'
self.interlockRules[self.ovButton] = self.interlockRules['offsets-view_template'].copy()
elif code == 'latest-file':
self.llButton = f'button_{bNum}'
self.interlockRules[self.llButton] = self.interlockRules['latest-file_template'].copy()
elif code == 'user-manual':
self.umButton = f'button_{bNum}'
self.interlockRules[self.umButton] = self.interlockRules['user-manual_template'].copy()
if _WEBENGINE_AVAILABLE:
self.w.webview.page().loadFinished.connect(self.style_user_manual)
self.w.webview.page().setBackgroundColor(QColor(self.backColor))
else:
head = _translate('HandlerClass', 'User Button Warning')
msg1 = _translate('HandlerClass', 'QtWebEngine dependency missing for user button')
msg2 = _translate('HandlerClass', 'User Manual styling will not match GUI')
msg3 = _translate('HandlerClass', 'Fix using "sudo apt install python3-pyqt5.qtwebengine"')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg1} #{bNum}\n{msg2}\n{msg3}\n')
self.w.webview.load(self.umUrl)
elif code == 'toggle-joint':
self.jtButton = f'button_{bNum}'
self.interlockRules[self.jtButton] = self.interlockRules['toggle-joint_template'].copy()
else:
if 'dual-code' in bCode:
# incoming code is: "dual-code" ;; code1 ;; label1 ;; code2 ;; checked (optional = true)
data = bCode.split(';;')
if len(data) not in [4, 5]:
head = _translate('HandlerClass', 'User Button Error')
msg1 = _translate('HandlerClass', 'Check button code for invalid arguments')
code = halpin = delay = ''
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}\n')
continue
else:
if len(data) == 5 and data[4].strip().lower() == 'true':
self.w[f'button_{bNum}'].setCheckable(True)
checked = True
else:
checked = True
self.dualCodeButtons[bNum] = [data[1], data[2], data[3], bLabel, checked]
# dualCodeButtons format is: code1 ;; label1 ;; code2 ;; label2 ;; checked
commands = f'{data[1]}\\{data[3]}'
else:
commands = bCode
for command in commands.split('\\'):
command = command.strip()
if command and command[0].lower() in 'xyzabcgmfsto' and command.replace(' ', '')[1] in '0123456789<':
if f'button_{bNum}' not in self.interlockRules:
self.interlockRules[f'button_{bNum}'] = self.interlockRules['dual-code_template'].copy()
elif command and command[0] == '%':
cmd = command.lstrip('%').lstrip(' ').split(' ', 1)[0]
if cmd[-3:] == '.py':
reply = os.path.exists(os.path.expanduser(cmd))
else:
reply = Popen(f'which {cmd}', stdout=PIPE, stderr=PIPE, shell=True).communicate()[0]
if not reply:
head = _translate('HandlerClass', 'External Code Error')
msg1 = _translate('HandlerClass', 'External command')
msg2 = _translate('HandlerClass', 'does not exist')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1} "{cmd}" {msg2}\n')
else:
self.interlockRules[f'button_{bNum}'] = self.interlockRules['always-on_template'].copy()
else:
head = _translate('HandlerClass', 'Code Error')
msg1 = self.w[f'button_{bNum}'].text().replace('\n', ' ')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}: "{command}"\n')
self.remove_button(f'button_{bNum}')
break
for button_name in self.tlButton:
self.interlockRules[button_name] = self.interlockRules['toggle-laser_template'].copy()
template_keys = [key for key in self.interlockRules.keys() if key.endswith('_template')]
for template_key in template_keys:
self.interlockRules.pop(template_key)
def user_button_down(self, bNum):
bCode = self.iniButtonCodes[bNum]
if not bCode:
return
# toggle-laser is the only code that can appear anywhere in the button code
if 'toggle-laser' in bCode.lower():
self.laserOnPin.set(not self.laserOnPin.get())
for command in bCode.split('\\'):
command = command.strip()
if command != 'toggle-laser':
self.user_button_command(bNum, command)
ACTION.SET_MANUAL_MODE()
elif bCode.lower().startswith('change-consumables') and 'e-halpin' not in bCode.lower():
self.change_consumables(True)
elif bCode.lower().startswith('probe-test') and 'e-halpin' not in bCode.lower():
self.probe_test(True)
elif bCode.lower().startswith('torch-pulse') and 'e-halpin' not in bCode.lower():
self.torch_pulse(True)
elif bCode.lower().startswith('ohmic-test') and 'e-halpin' not in bCode.lower():
self.ohmic_test(True)
elif bCode.lower().startswith('framing'):
self.frame_job(True)
elif bCode.lower().startswith('cut-type'):
self.w.gcodegraphics.logger.clear()
self.cutType ^= 1
if self.cutType:
self.cutTypePin.set(1)
self.button_active(self.ctButton)
self.cutTypeText = self.w[self.ctButton].text()
self.w[self.ctButton].setText(_translate('HandlerClass', 'PIERCE\nONLY'))
else:
self.cutTypePin.set(0)
self.button_normal(self.ctButton)
self.w[self.ctButton].setText(self.cutTypeText)
self.overlayProgress.setValue(0)
if self.fileOpened:
self.file_reload_clicked()
elif bCode.lower().startswith('load'):
lFile = f'{self.programPrefix}/{bCode.split("load", 1)[1].strip()}'
self.overlayProgress.setValue(0)
self.remove_temp_materials()
ACTION.OPEN_PROGRAM(lFile)
elif bCode.lower().startswith('toggle-halpin'):
halpin = bCode.lower().split('toggle-halpin')[1].split(' ')[1].strip()
try:
if halpin in self.halPulsePins and self.halPulsePins[halpin][3] > 0.05:
self.halPulsePins[halpin][3] = 0.0
else:
self.invert_pin_state(halpin)
except Exception as err:
head = _translate('HandlerClass', 'HAL Pin Error')
msg0 = _translate('HandlerClass', 'Invalid code for user button')
msg1 = _translate('HandlerClass', 'Failed to toggle HAL pin')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head,}:\n{msg0} #{bNum}\n{msg1}\n"{halpin}" {err}\n')
elif bCode.lower().startswith('pulse-halpin'):
head = _translate('HandlerClass', 'HAL Pin Error')
msg1 = _translate('HandlerClass', 'Failed to pulse HAL pin')
halpin = bCode.lower().strip().split()[1]
# halPulsePins format is: button name, pulse time, button text, remaining time, button number
try:
if self.halPulsePins[halpin][3] > 0.05:
self.halPulsePins[halpin][3] = 0.0
else:
self.w[self.halPulsePins[halpin][0]].setText(f'{self.halPulsePins[halpin][2]}')
self.halPulsePins[halpin][3] = self.halPulsePins[halpin][1]
if not self.pulseTimer.isActive():
self.pulseTimer.start(100)
except:
msg0 = _translate('HandlerClass', 'Invalid code for user button')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1} "{halpin}"\n')
elif bCode.lower().startswith('single-cut'):
self.single_cut()
elif bCode.lower().startswith('manual-cut'):
self.manual_cut()
elif bCode.lower().startswith('offsets-view'):
if self.w.preview_stack.currentIndex() != self.OFFSETS:
self.w.preview_stack.setCurrentIndex(self.OFFSETS)
else:
self.preview_index_return(self.w.preview_stack.currentIndex())
elif bCode.lower().startswith('latest-file'):
try:
if len(bCode.split()) == 2:
lastDir = bCode.split()[1]
else:
lastDir = self.w.PREFS_.getpref('last_loaded_directory', '', str, 'BOOK_KEEPING')
files = glob.glob(f'{lastDir}/*.ngc')
latest = max(files, key=os.path.getctime)
self.overlayProgress.setValue(0)
self.remove_temp_materials()
ACTION.OPEN_PROGRAM(latest)
except:
head = _translate('HandlerClass', 'File Error')
msg0 = _translate('HandlerClass', 'Cannot open latest file from user button')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n')
elif bCode.lower().startswith('user-manual'):
if self.w.preview_stack.currentIndex() != self.USER_MANUAL:
self.prevPreviewIndex = self.w.preview_stack.currentIndex()
self.w.preview_stack.setCurrentIndex(self.USER_MANUAL)
else:
self.w.preview_stack.setCurrentIndex(self.prevPreviewIndex)
self.prevPreviewIndex = self.USER_MANUAL
elif bCode.lower().startswith('toggle-joint'):
self.toggle_joint_mode()
else:
self.reloadRequired = False
if bCode.lower().startswith('dual-code'):
# dualCodeButtons format is: code1 ;; label1 ;; code2 ;; label2 ;; checked
if self.w[f'button_{bNum}'].text() == self.dualCodeButtons[bNum][3]:
bCode = self.dualCodeButtons[bNum][0]
self.w[f'button_{bNum}'].setText(self.dualCodeButtons[bNum][1])
self.w[f'button_{bNum}'].setChecked(True)
else:
bCode = self.dualCodeButtons[bNum][2]
self.w[f'button_{bNum}'].setText(self.dualCodeButtons[bNum][3])
self.w[f'button_{bNum}'].setChecked(False)
for command in bCode.split('\\'):
command = command.strip()
self.user_button_command(bNum, command)
if command[0] == "%":
continue
if command.lower().replace(' ', '').startswith('g10l20') and self.fileOpened:
self.reloadRequired = True
if self.reloadRequired:
self.file_reload_clicked()
else:
self.w.gcodegraphics.logger.clear()
ACTION.SET_MANUAL_MODE()
# for G-code commands and external commands
def user_button_command(self, bNum, command):
if command and command[0].lower() in 'xyzabcgmfsto' and command.replace(' ', '')[1] in '0123456789<':
if '{' in command:
newCommand = subCommand = ''
for char in command:
if char == '{':
subCommand = ':'
elif char == '}':
if len(subCommand.split()) > 1:
section, option = subCommand.replace(':', '').split(' ', 1)
else:
head = _translate('HandlerClass', 'Code Error')
msg0 = _translate('HandlerClass', 'Value Error in user button')
msg1 = self.w[f'button_{bNum}'].text().replace('\n', ' ')
errorCode = f'{subCommand.replace(":", "{")}' + '}'
msg2 = _translate('HandlerClass', 'Requires a valid section and option pair')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum} ({msg1}):\n"{errorCode}"\n{msg2}\n')
return
if self.PREFS.has_option(section, option):
newCommand += str(self.PREFS.get(section, option))
elif self.iniFile.find(section, option):
newCommand += self.iniFile.find(section, option)
else:
head = _translate('HandlerClass', 'Code Error')
msg0 = _translate('HandlerClass', 'Invalid code in user button')
msg1 = self.w[f'button_{bNum}'].text().replace('\n', ' ')
errorCode = f'{subCommand.replace(":", "{")}' + '}'
msg2 = _translate('HandlerClass', 'Section')
msg3 = _translate('HandlerClass', 'or option')
msg4 = _translate('HandlerClass', 'does not exist in')
msg5 = _translate('HandlerClass', 'Section and option pairs are case sensative')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum} ({msg1}):\n"{errorCode}"\n{msg2}: "[{section}]" {msg3}: "{option}" {msg4}:\n{self.prefsFile}\n{msg5}\n')
return
subCommand = ''
elif subCommand.startswith(':'):
subCommand += char
else:
newCommand += char
command = newCommand
ACTION.CALL_MDI(command)
while not STATUS.is_interp_idle():
self.w.gcodegraphics.update()
QApplication.processEvents()
elif command and command[0] == '%':
command = command.lstrip('%').lstrip()
if command[-3:] == '.py':
cmd = 'python3 ' + command
Popen(cmd, stdin=None, stdout=PIPE, stderr=PIPE, shell=True)
else:
command += '&'
Popen(command, stdout=PIPE, stderr=PIPE, shell=True)
else:
head = _translate('HandlerClass', 'Code Error')
msg0 = _translate('HandlerClass', 'Invalid code for user button')
msg1 = self.w[f'button_{bNum}'].text().replace('\n', ' ')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{bNum}\n{msg1}: "{command}"\n')
def user_button_up(self, bNum):
commands = self.iniButtonCodes[bNum]
if not commands:
return
elif 'torch-pulse' in commands.lower() and 'e-halpin' not in commands.lower():
self.torch_pulse(False)
if 'ohmic-test' in commands.lower() and 'e-halpin' not in commands.lower():
self.ohmic_test(False)
def torch_enable_changed(self, state):
if self.torchPulse:
self.torch_pulse_abort()
self.refresh_button_states()
def ext_torch_enable_changed(self, state):
if state:
self.w.torch_enable.toggle()
def ext_thc_enable_changed(self, state):
if state:
self.w.thc_enable.toggle()
def ext_corner_lock_enable_changed(self, state):
if state:
self.w.cornerlock_enable.toggle()
def ext_void_lock_enable_changed(self, state):
if state:
self.w.voidlock_enable.toggle()
def ext_ignore_arc_ok_changed(self, state):
if state:
self.w.ignore_arc_ok.toggle()
def ext_mesh_mode_changed(self, state):
if state:
self.w.mesh_enable.toggle()
def ext_ohmic_probe_enable_changed(self, state):
if state:
self.w.ohmic_probe_enable.toggle()
def ext_auto_volts_enable_changed(self, state):
if state:
self.w.use_auto_volts.toggle()
def thc_auto_changed(self, state):
if state:
self.w.thc_delay.hide()
self.w.thc_delay_lbl.hide()
self.w.thc_sample_counts.show()
self.w.thc_sample_counts_lbl.show()
self.w.thc_sample_threshold.show()
self.w.thc_sample_threshold_lbl.show()
else:
self.w.thc_delay.show()
self.w.thc_delay_lbl.show()
self.w.thc_sample_counts.hide()
self.w.thc_sample_counts_lbl.hide()
self.w.thc_sample_threshold.hide()
self.w.thc_sample_threshold_lbl.hide()
def consumable_change_setup(self):
self.ccXpos = self.ccYpos = self.ccFeed = None
X = Y = F = ''
ccAxis = [X, Y, F]
ccName = ['x', 'y', 'f']
for loop in range(3):
count = 0
if ccName[loop] in self.ccParm:
while 1:
if not self.ccParm[count]:
break
if self.ccParm[count] == ccName[loop]:
count += 1
break
count += 1
while 1:
if count == len(self.ccParm):
break
if self.ccParm[count].isdigit() or self.ccParm[count] in '.-':
ccAxis[loop] += self.ccParm[count]
else:
break
count += 1
if ccName[loop] == 'x' and ccAxis[loop]:
self.ccXpos = float(ccAxis[loop])
elif ccName[loop] == 'y' and ccAxis[loop]:
self.ccYpos = float(ccAxis[loop])
elif ccName[loop] == 'f' and ccAxis[loop]:
self.ccFeed = float(ccAxis[loop])
if not self.ccFeed or self.ccFeed < 1:
msg0 = _translate('HandlerClass', 'Invalid feed rate for consumable change')
msg1 = _translate('HandlerClass', 'Defaulting to materials cut feed rate')
STATUS.emit('update-machine-log', f'{msg0}, {msg1}', 'TIME')
self.ccFeed = float(self.w.cut_feed_rate.text().replace(',', '.'))
def ext_change_consumables(self, state):
if self.ccButton and self.w[self.ccButton].isEnabled():
self.change_consumables(state)
def change_consumables(self, state):
if hal.get_value('axis.x.eoffset-counts') or hal.get_value('axis.y.eoffset-counts'):
hal.set_p('plasmac.consumable-change', '0')
hal.set_p('plasmac.x-offset', '0')
hal.set_p('plasmac.y-offset', '0')
self.button_normal(self.ccButton)
else:
self.consumable_change_setup()
if not self.ccXpos:
self.ccXpos = STATUS.get_position()[0][0]
if self.ccXpos < round(self.xMin, 6) + (10 * self.unitsPerMm):
self.ccXpos = round(self.xMin, 6) + (10 * self.unitsPerMm)
elif self.ccXpos > round(self.xMax, 6) - (10 * self.unitsPerMm):
self.ccXpos = round(self.xMax, 6) - (10 * self.unitsPerMm)
if not self.ccYpos:
self.ccYpos = STATUS.get_position()[0][1]
if self.ccYpos < round(self.yMin, 6) + (10 * self.unitsPerMm):
self.ccYpos = round(self.yMin, 6) + (10 * self.unitsPerMm)
elif self.ccYpos > round(self.yMax, 6) - (10 * self.unitsPerMm):
self.ccYpos = round(self.yMax, 6) - (10 * self.unitsPerMm)
hal.set_p('plasmac.xy-feed-rate', str(float(self.ccFeed)))
hal.set_p('plasmac.x-offset', f'{(self.ccXpos - STATUS.get_position()[0][0]) / hal.get_value("plasmac.offset-scale"):.0f}')
hal.set_p('plasmac.y-offset', f'{(self.ccYpos - STATUS.get_position()[0][1]) / hal.get_value("plasmac.offset-scale"):.0f}')
hal.set_p('plasmac.consumable-change', '1')
self.button_active(self.ccButton)
self.refresh_button_states()
def ext_probe_test(self, state):
if self.ptButton and self.w[self.ptButton].isEnabled():
self.probe_test(state)
def probe_test(self, state):
if state:
if self.probeTimer.remainingTime() <= 0 and not self.offsetsActivePin.get():
probeError, errMsg = self.bounds_check_probe(True)
if probeError:
head = _translate('HandlerClass', 'Axis Limit Error')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{errMsg}\n')
return
self.probeTime = self.ptTime
self.probeTimer.start(1000)
self.probeTest = True
hal.set_p('plasmac.probe-test', '1')
self.w[self.ptButton].setText(f'{self.probeTime}')
self.button_active(self.ptButton)
log = _translate('HandlerClass', 'Probe test started')
STATUS.emit('update-machine-log', log, 'TIME')
else:
self.probe_test_stop()
log = _translate('HandlerClass', 'Probe test aborted')
STATUS.emit('update-machine-log', log, 'TIME')
self.refresh_button_states()
def probe_test_stop(self):
self.probeTest = False
self.probeTimer.stop()
self.probeTime = 0
hal.set_p('plasmac.probe-test', '0')
self.w[self.ptButton].setText(self.probeText)
self.button_normal(self.ptButton)
self.refresh_button_states()
def probe_test_error(self, state):
if state:
self.probe_test_stop()
log = _translate('HandlerClass', 'Probe test error detected')
STATUS.emit('update-machine-log', log, 'TIME')
def ext_torch_pulse(self, state):
if self.tpButton and self.w[self.tpButton].isEnabled():
self.torch_pulse(state)
def torch_pulse(self, state):
if state:
self.torch_pulse_pressed()
else:
self.torch_pulse_released()
def torch_pulse_pressed(self):
# second press while active aborts
if self.tpRemaining > 0:
self.torch_pulse_abort()
return
if hal.get_value('plasmac.torch-on'):
return
self.tpRemaining = self.tpTime
self.torchPulse = True
hal.set_p('plasmac.torch-pulse-time', str(self.tpRemaining))
hal.set_p('plasmac.torch-pulse-start', '1')
hal.set_p('plasmac.torch-pulse-hold', '1')
self.tpTimer.start()
self.w[self.tpButton].setText(f'{self.tpRemaining}')
self.button_active(self.tpButton)
self.torch_pulse_states(False)
log = _translate('HandlerClass', 'Torch pulse started')
STATUS.emit('update-machine-log', log, 'TIME')
def torch_pulse_released(self):
hal.set_p('plasmac.torch-pulse-start', '0')
hal.set_p('plasmac.torch-pulse-hold', '0')
if self.tpRemaining == 0:
self.torch_pulse_states(True)
log = _translate('HandlerClass', 'Torch pulse hold released')
STATUS.emit('update-machine-log', log, 'TIME')
def torch_pulse_abort(self):
hal.set_p('plasmac.torch-pulse-abort', '1')
QTimer.singleShot(50, lambda: hal.set_p('plasmac.torch-pulse-abort', '0'))
hal.set_p('plasmac.torch-pulse-start', '0')
hal.set_p('plasmac.torch-pulse-hold', '0')
self.tpTimer.stop()
self.tpRemaining = 0.0
self.torch_pulse_states(True)
log = _translate('HandlerClass', 'Torch pulse aborted')
STATUS.emit('update-machine-log', log, 'TIME')
def torch_pulse_states(self, state):
if state:
self.w[self.tpButton].setText(self.tpText)
self.button_normal(self.tpButton)
self.torchPulse = False
self.refresh_button_states()
def ext_ohmic_test(self, state):
if self.otButton and self.w[self.otButton].isEnabled():
self.ohmic_test(state)
def ohmic_test(self, state):
self.ohmicTest = state
hal.set_p('plasmac.ohmic-test', str(state))
self.refresh_button_states()
def ext_frame_job(self, state):
if self.frButton and self.w[self.frButton].isEnabled():
self.frame_job(state)
def frame_job(self, state):
if self.gcodeProps and state:
response = False
if self.w.laser.isEnabled():
framingError, framePoints = self.bounds_check_framing(self.laserOffsetX, self.laserOffsetY, True)
if framingError:
head = _translate('HandlerClass', 'Axis Limit Error')
framingError += _translate('HandlerClass', '\n\nFrame the job using the torch instead?\n')
response = self.dialog_show_yesno(QMessageBox.Warning, f'{head}', f'\n{framingError}')
if response:
framePoints = self.bounds_check_framing()[1]
else:
return
else:
self.laserOnPin.set(1)
else:
framePoints = self.bounds_check_framing()[1]
if not self.frFeed:
feed = float(self.w.cut_feed_rate.text().replace(',', '.'))
else:
feed = self.frFeed
zHeight = self.zMax - (hal.get_value('plasmac.max-offset') * self.unitsPerMm)
if STATUS.is_on_and_idle() and STATUS.is_all_homed():
self.framing = True
previousMode = ''
if self.units == 'in' and STATUS.is_metric_mode():
previousMode = 'G21'
ACTION.CALL_MDI('G20')
elif self.units == 'mm' and not STATUS.is_metric_mode():
previousMode = 'G20'
ACTION.CALL_MDI('G21')
ACTION.CALL_MDI_WAIT(f'G64 P{0.25 * self.unitsPerMm:0.3f}')
if self.defaultZ:
ACTION.CALL_MDI(f'G53 G0 Z{zHeight:0.4f}')
ACTION.CALL_MDI(f'G53 G0 X{framePoints[1][0]:0.2f} Y{framePoints[1][1]:0.2f}')
ACTION.CALL_MDI(f'G53 G1 X{framePoints[2][0]:0.2f} Y{framePoints[2][1]:0.2f} F{feed:0.0f}')
ACTION.CALL_MDI(f'G53 G1 X{framePoints[3][0]:0.2f} Y{framePoints[3][1]:0.2f}')
ACTION.CALL_MDI(f'G53 G1 X{framePoints[4][0]:0.2f} Y{framePoints[4][1]:0.2f}')
ACTION.CALL_MDI(f'G53 G1 X{framePoints[1][0]:0.2f} Y{framePoints[1][1]:0.2f}')
ACTION.CALL_MDI('G0 X0 Y0')
ACTION.CALL_MDI(previousMode)
self.refresh_button_states()
def single_cut(self):
self.singleCutDialog = True
self.refresh_button_states()
sC = QDialog(self.w)
sC.setWindowTitle(_translate('HandlerClass', 'Single Cut'))
l1 = QLabel(_translate('HandlerClass', 'X LENGTH:'))
xLength = QDoubleSpinBox()
xLength.setAlignment(Qt.AlignRight)
xLength.setMinimum(-9999)
xLength.setMaximum(9999)
xLength.setDecimals(1)
l2 = QLabel(_translate('HandlerClass', 'Y LENGTH:'))
yLength = QDoubleSpinBox()
yLength.setAlignment(Qt.AlignRight)
yLength.setMinimum(-9999)
yLength.setMaximum(9999)
yLength.setDecimals(1)
l3 = QLabel('')
buttons = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
buttonBox = QDialogButtonBox(buttons)
buttonBox.accepted.connect(sC.accept)
buttonBox.rejected.connect(sC.reject)
buttonBox.button(QDialogButtonBox.Ok).setText(_translate('HandlerClass', 'CUT'))
buttonBox.button(QDialogButtonBox.Ok).setIcon(QIcon())
buttonBox.button(QDialogButtonBox.Cancel).setText(_translate('HandlerClass', 'CANCEL'))
buttonBox.button(QDialogButtonBox.Cancel).setIcon(QIcon())
layout = QVBoxLayout()
layout.addWidget(l1)
layout.addWidget(xLength)
layout.addWidget(l2)
layout.addWidget(yLength)
layout.addWidget(l3)
layout.addWidget(buttonBox)
sC.setLayout(layout)
xLength.setValue(self.PREFS.getpref('X length', 0.0, float, 'SINGLE CUT'))
yLength.setValue(self.PREFS.getpref('Y length', 0.0, float, 'SINGLE CUT'))
self.vkb_show(True)
result = sC.exec_()
self.singleCutDialog = False
self.refresh_button_states()
self.vkb_hide()
if not result or not self.cut_critical_toggle_check():
return
self.PREFS.putpref('X length', xLength.value(), float, 'SINGLE CUT')
self.PREFS.putpref('Y length', yLength.value(), float, 'SINGLE CUT')
self.oldFile = ACTION.prefilter_path if ACTION.prefilter_path else None
self.g91 = True if 910 in STATUS.stat.gcodes else False
xEnd = STATUS.get_position()[0][0] + xLength.value()
yEnd = STATUS.get_position()[0][1] + yLength.value()
newFile = f'{self.tmpPath}single_cut.ngc'
matNum = int(self.w.materials_box.currentText().split(': ', 1)[0])
with open(newFile, 'w') as f:
f.write('G90\n')
f.write(f'M190 P{matNum}\n')
f.write('F#<_hal[plasmac.cut-feed-rate]>\n')
f.write(f'G53 G0 X{STATUS.get_position()[0][0]:0.6f} Y{STATUS.get_position()[0][1]:0.6f}\n')
f.write('M3 $0 S1\n')
f.write(f'G53 G1 X{xEnd:0.6f} Y{yEnd:0.6f}\n')
f.write('M5 $0\n')
f.write('M2\n')
self.single_cut_request = True
if self.fileOpened:
self.preSingleCutMaterial = matNum
ACTION.OPEN_PROGRAM(newFile)
def manual_cut(self):
if self.manualCut:
self.abort_pressed()
elif STATUS.machine_is_on() and STATUS.is_all_homed() and STATUS.is_interp_idle() and self.cut_critical_toggle_check():
probeError, errMsg = self.bounds_check_probe(True)
if probeError:
head = _translate('HandlerClass', 'Axis Limit Error')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{errMsg}\n')
return
self.manualCut = True
self.set_mc_states(False)
if self.mcButton:
self.button_active(self.mcButton)
ACTION.SET_SPINDLE_ROTATION(1, 1, 0)
log = _translate('HandlerClass', 'Manual cut started')
STATUS.emit('update-machine-log', log, 'TIME')
def button_active(self, button):
self.w[button].setStyleSheet(f'QPushButton {{ color: {self.backColor}; background: {self.fore1Color} }} \
QPushButton:pressed {{ color: {self.backColor}; background: {self.fore1Color} }} \
QPushButton:disabled {{ color: {self.disabledColor}}}')
def button_normal(self, button):
if button == 'file_open':
self.w[button].setStyleSheet(f'QPushButton {{ color: {self.backColor}; background: {self.foreColor} }} \
QPushButton:pressed {{ color: {self.foreColor}; background: {self.backColor} }} \
QPushButton:disabled {{ color: {self.backColor}; background: {self.disabledColor} }}')
else:
self.w[button].setStyleSheet(f'QPushButton {{ color: {self.foreColor}; background: {self.backColor} }} \
QPushButton:pressed {{ color: {self.backColor}; background: {self.fore1Color} }} \
QPushButton:disabled {{ color: {self.disabledColor} }}')
def button_press_timeout(self, button):
self.w[button].setStyleSheet(f'QPushButton:pressed {{ color: {self.foreColor}; background: {self.backColor} }}')
def button_normal_check(self, button):
'''Returns True if the button is in the normal state (background color of the button matches the background color of the GUI)'''
return self.w[button].palette().color(QtGui.QPalette.Background) \
== self.w.color_backgrnd.palette().color(QPalette.Background)
def can_enable(self, widgetName):
rules = self.interlockRules.get(widgetName, {})
overrideRules = rules.get('override', {})
if overrideRules:
for condition, value in overrideRules.items():
if condition not in self.state:
LOG.error(f"'{condition}' does not exist")
return False
if self.state.get(condition) == value:
return True
allRules = rules.get('all', {})
for condition, value in allRules.items():
if condition not in self.state:
LOG.error(f"'{condition}' does not exist")
return False
if self.state.get(condition) != value:
return False
anyRules = rules.get('any', {})
if anyRules:
match = False
for condition, value in anyRules.items():
if condition not in self.state:
LOG.error(f"'{condition}' does not exist")
return False
if self.state.get(condition) == value:
match = True
break
if not match:
return False
return True
def sync_conditions(self):
self.state['all_homed'] = STATUS.is_all_homed()
self.state['consumable_change'] = hal.get_value('plasmac.consumable-change')
self.state['consumable_changing'] = self.consChangePin.get()
self.state['estop_cleared'] = STATUS.estop_is_clear()
self.state['file_bounds_error'] = self.fileBoundsError
self.state['file_opened'] = self.fileOpened
self.state['framing'] = self.framing
self.state['gcode_loaded'] = self.w.gcode_display.lines() > 1
self.state['gcodestack_is_mdi'] = self.w.gcode_stack.currentIndex() == self.MDI
self.state['interp_idle'] = STATUS.is_interp_idle()
self.state['interp_paused'] = STATUS.is_interp_paused()
self.state['interp_running'] = STATUS.is_interp_running()
self.state['laser_button_state'] = self.laserButtonState == 'laser'
self.state['machine_on'] = STATUS.machine_is_on()
self.state['manual_cut_active'] = self.manualCut
self.state['offsets_active'] = self.offsetsActivePin.get()
self.state['ohmic_probe_enable'] = self.w.ohmic_probe_enable.isChecked()
self.state['ohmic_test'] = self.ohmicTest
self.state['plasmac_idle'] = self.plasmacStatePin.get() == self.IDLE
self.state['previewstack_is_camera'] = self.w.preview_stack.currentIndex() == self.CAMERA
self.state['previewstack_is_edit'] = self.w.preview_stack.currentIndex() == self.EDIT
self.state['previewstack_is_offsets'] = self.w.preview_stack.currentIndex() == self.OFFSETS
self.state['previewstack_is_open'] = self.w.preview_stack.currentIndex() == self.OPEN
self.state['previewstack_is_preview'] = self.w.preview_stack.currentIndex() == self.PREVIEW
self.state['previewstack_is_user_manual'] = self.w.preview_stack.currentIndex() == self.USER_MANUAL
self.state['probe_bounds_error'] = self.probeBoundsError
self.state['probe_test'] = hal.get_value('plasmac.probe-test')
self.state['rfl_dialog'] = self.rflActive
self.state['single_cut_dialog'] = self.singleCutDialog
self.state['torch_enable'] = self.w.torch_enable.isChecked()
self.state['torch_pulse'] = self.torchPulse
def refresh_button_states(self):
self.sync_conditions()
for widgetName in self.interlockRules.keys():
try:
widget = getattr(self.w, widgetName, None)
if widget is not None:
shouldEnable = self.can_enable(widgetName)
widget.setEnabled(shouldEnable)
except Exception as e:
LOG.error(f"Error updating button '{widgetName}': {e}")
# Update tab states
if self.tabsAlwaysEnabled.get():
return
for tabIndex, tabName in self.tabNames.items():
try:
if tabName in self.interlockRules:
shouldEnable = self.can_enable(tabName)
self.w.main_tab_widget.setTabEnabled(tabIndex, shouldEnable)
except Exception as e:
LOG.error(f"Error updating state for tab '{tabName}': {e}")
def remove_button(self, widgetName):
if widgetName in self.interlockRules:
del self.interlockRules[widgetName]
#########################################################################################################################
# ONBOARD VIRTUAL KEYBOARD FUNCTIONS #
#########################################################################################################################
def vkb_check(self):
if self.w.chk_soft_keyboard.isChecked() and not os.path.isfile('/usr/bin/onboard'):
head = _translate('HandlerClass', 'Virtual Keyboard Error')
msg0 = _translate('HandlerClass', '"onboard" virtual keyboard is not installed')
msg1 = _translate('HandlerClass', 'some keyboard functions are not available')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}\n{msg1}\n')
return
try:
cmd = 'gsettings get org.onboard.window.landscape width'
self.obWidth = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True).communicate()[0].decode().strip()
cmd = 'gsettings get org.onboard.window.landscape height'
self.obHeight = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True).communicate()[0].decode().strip()
cmd = 'gsettings get org.onboard layout'
layout = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True).communicate()[0].decode().strip()
if '/numpad' in layout or '/keyboard' in layout:
self.obLayout = 'compact'
else:
self.obLayout = layout
except:
self.obWidth = '700'
self.obHeight = '300'
self.obLayout = 'compact'
def vkb_show(self, numpad=False):
if self.firstRun:
return
if os.path.isfile('/usr/bin/onboard'):
if self.w.chk_soft_keyboard.isChecked():
w = '240' if numpad else '740'
h = '240'
l = 'numpad' if numpad else 'keyboard'
self.vkb_setup(w, h, os.path.join(self.PATHS.IMAGEDIR, 'qtplasmac', l))
cmd = 'dbus-send'
cmd += ' --type=method_call'
cmd += ' --dest=org.onboard.Onboard'
cmd += ' /org/onboard/Onboard/Keyboard'
cmd += ' org.onboard.Onboard.Keyboard.Show'
Popen(cmd, stdout=PIPE, shell=True)
def vkb_hide(self, custom=False):
if os.path.isfile('/usr/bin/onboard') and self.obLayout:
cmd = 'dbus-send'
cmd += ' --type=method_call'
cmd += ' --dest=org.onboard.Onboard'
cmd += ' /org/onboard/Onboard/Keyboard'
cmd += ' org.onboard.Onboard.Keyboard.Hide'
Popen(cmd, stdout=PIPE, shell=True)
if not custom:
self.vkb_setup(self.obWidth, self.obHeight, self.obLayout)
def vkb_setup(self, w, h, l):
if os.path.isfile('/usr/bin/onboard'):
Popen(f'gsettings set org.onboard.window.landscape width {int(w)-1}', stdout=PIPE, shell=True)
Popen(f'gsettings set org.onboard.window.landscape height {int(h)-1}', stdout=PIPE, shell=True)
Popen(f'gsettings set org.onboard layout {l}', stdout=PIPE, shell=True)
Popen(f'gsettings set org.onboard.window.landscape width {int(w)}', stdout=PIPE, shell=True)
Popen(f'gsettings set org.onboard.window.landscape height {int(h)}', stdout=PIPE, shell=True)
#########################################################################################################################
# MATERIAL HANDLING FUNCTIONS #
#########################################################################################################################
def save_materials_clicked(self):
matNum = self.materialChangeNumberPin.get()
if matNum == -1:
matNum = self.defaultMaterial
index = self.w.materials_box.currentIndex()
self.save_material_file(matNum, index)
def reload_materials_clicked(self):
self.materialUpdate = True
index = self.w.materials_box.currentIndex()
self.load_material_file(True)
self.w.materials_box.setCurrentIndex(index)
self.materialUpdate = False
self.materialReloadPin.set(0)
def new_material_clicked(self, repeat, value):
head = _translate('HandlerClass', 'Add Material')
msg1 = _translate('HandlerClass', 'Enter New Material Number')
msgs = msg1
btn1 = _translate('HandlerClass', 'ADD')
btn2 = _translate('HandlerClass', 'CANCEL')
virtkb = 4
while 1:
valid, matNum = self.dialog_input(virtkb, head, f'{msgs}:', btn1, btn2)
if not valid:
return
try:
matNum = int(matNum)
except:
if not matNum:
msg0 = _translate('HandlerClass', 'A material number is required')
msgs = f'{msg0}.\n\n{msg1}'
else:
msg0 = _translate('HandlerClass', 'is not a valid number')
msgs = f'{matNum} {msg0}.\n\n{msg1}'
continue
if matNum in self.materialNumList:
msg0 = _translate('HandlerClass', 'Material')
msg2 = _translate('HandlerClass', 'is in use')
msgs = f'{msg0} #{matNum} {msg2}.\n\n{msg1}'
continue
elif matNum >= 1000000:
msg0 = _translate('HandlerClass', 'Material numbers need to be less than 1000000')
msgs = f'{msg0}.\n\n{msg1}'
continue
break
msg1 = _translate('HandlerClass', 'Enter New Material Name')
msgs = msg1
virtkb = 3
while 1:
valid, matNam = self.dialog_input(virtkb, head, f'{msgs}:', btn1, btn2)
if not valid:
return
if not matNam:
msg0 = _translate('HandlerClass', 'Material name is required')
msgs = f'{msg0}.\n\n{msg1}'
continue
break
mat = [matNum, matNam]
mat[2:] = self.materialDict[self.materialList[self.w.materials_box.currentIndex()]][1:]
self.write_one_material(mat)
self.materialUpdate = True
self.load_material_file(True)
self.w.materials_box.setCurrentIndex(self.materialList.index(matNum))
self.materialUpdate = False
def delete_material_clicked(self):
head = _translate('HandlerClass', 'Delete Material')
msg1 = _translate('HandlerClass', 'Enter Material Number To Delete')
btn1 = _translate('HandlerClass', 'DELETE')
btn2 = _translate('HandlerClass', 'CANCEL')
msgs = msg1
virtkb = 4
while 1:
valid, matNum = self.dialog_input(virtkb, head, f'{msgs}:', btn1, btn2)
if not valid:
return
try:
matNum = int(matNum)
except:
if not matNum:
msg0 = _translate('HandlerClass', 'A material number is required')
msgs = f'{msg0}.\n\n{msg1}'
else:
msg0 = _translate('HandlerClass', 'is not a valid number')
msgs = f'{matNum} {msg0}.\n\n{msg1}'
continue
if matNum == self.defaultMaterial:
msg0 = _translate('HandlerClass', 'Default material cannot be deleted')
msgs = f'{msg0}.\n\n{msg1}'
continue
elif matNum >= 1000000 and matNum in self.materialList:
msg0 = _translate('HandlerClass', 'Temporary material')
msg3 = _translate('HandlerClass', 'cannot be deleted')
msgs = f'{msg0} #{matNum} {msg3}.\n\n{msg1}'
continue
elif matNum not in self.materialNumList:
msg0 = _translate('HandlerClass', 'Material')
msg3 = _translate('HandlerClass', 'does not exist')
msgs = f'{msg0} #{matNum} {msg3}.\n\n{msg1}'
continue
break
head = _translate('HandlerClass', 'Delete Material')
msg0 = _translate('HandlerClass', 'Do you really want to delete material')
if not self.dialog_show_yesno(QMessageBox.Question, f'{head}', f'\n{msg0} #{matNum}?\n'):
return
self.MATS.remove_section(f'MATERIAL_NUMBER_{matNum}')
self.MATS.write(open(self.MATS.fn, 'w'))
self.materialUpdate = True
self.load_material_file(True)
self.materialUpdate = False
def remove_temp_materials(self):
mats = [m for m in self.materialList if m >= 1000000]
if mats:
for mat in mats:
self.materialDict.pop(mat)
self.materialList.remove(mat)
def selector_changed(self, index):
if index == -1 or self.getMaterialBusy:
return
if self.w.material_selector.currentIndex() != self.w.materials_box.currentIndex():
self.w.materials_box.setCurrentIndex(index)
self.w.conv_material.setCurrentIndex(index)
def conv_material_changed(self, index):
if index == -1 or self.getMaterialBusy:
return
if self.w.conv_material.currentIndex() != self.w.materials_box.currentIndex():
self.w.materials_box.setCurrentIndex(index)
self.w.material_selector.setCurrentIndex(index)
def material_changed(self, index):
if index == -1 or self.getMaterialBusy:
return
if self.w.materials_box.currentText():
if self.getMaterialBusy:
self.materialChangePin.set(-1)
self.autoChange = False
return
matNum = int(self.w.materials_box.currentText().split(': ', 1)[0])
if matNum >= 1000000:
self.w.save_material.setEnabled(False)
else:
self.w.save_material.setEnabled(True)
if self.autoChange:
hal.set_p('motion.digital-in-03', '0')
if self.preSingleCutMaterial is None and self.preFileSaveMaterial is None:
self.change_material(matNum)
self.materialChangePin.set(2)
hal.set_p('motion.digital-in-03', '1')
else:
self.change_material(matNum)
self.w.material_selector.setCurrentIndex(self.w.materials_box.currentIndex())
self.w.conv_material.setCurrentIndex(self.w.materials_box.currentIndex())
self.autoChange = False
self.overlay_update(None)
def material_change_pin_changed(self, halpin):
if halpin == 0:
hal.set_p('motion.digital-in-03', '0')
elif halpin == 3:
hal.set_p('motion.digital-in-03', '1')
self.materialChangePin.set(0)
def material_change_number_pin_changed(self, halpin):
if halpin == -1:
halpin = self.defaultMaterial
if self.getMaterialBusy or halpin == int(self.w.materials_box.currentText().split(': ', 1)[0]):
return
if self.materialChangePin.get() == 1:
self.autoChange = True
if not self.material_exists(halpin):
self.autoChange = False
return
if self.preSingleCutMaterial is not None:
self.materialChangeNumberPin.set(self.preSingleCutMaterial)
self.preSingleCutMaterial = None
elif self.preFileSaveMaterial is not None:
self.materialChangeNumberPin.set(self.preFileSaveMaterial)
self.preFileSaveMaterial = None
else:
self.w.materials_box.setCurrentIndex(self.materialList.index(halpin))
def material_change_timeout_pin_changed(self, halpin):
head = _translate('HandlerClass', 'Materials Error')
if halpin:
# should we stop or pause the program if a timeout occurs???
matNum = int(self.w.materials_box.currentText().split(': ', 1)[0])
msg0 = _translate('HandlerClass', 'Material change timeout occurred for material')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:{msg0} #{matNum}\n')
self.materialChangeNumberPin.set(matNum)
self.materialChangeTimeoutPin.set(0)
hal.set_p('motion.digital-in-03', '0')
def material_reload_pin_changed(self, halpin):
if halpin:
self.reload_materials_clicked()
def material_temp_pin_changed(self, halpin):
if halpin:
mat = [halpin, f'Temporary {halpin}', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
with open(self.tmpMaterialGcode, 'r') as f_in:
for line in f_in:
if line.startswith('NAME'):
mat[1] = line.split('=')[1].strip()
if line.startswith('KERF_WIDTH'):
mat[2] = float(line.split('=')[1].strip())
elif line.startswith('PIERCE_HEIGHT'):
mat[3] = float(line.split('=')[1].strip())
elif line.startswith('PIERCE_DELAY'):
mat[4] = float(line.split('=')[1].strip())
elif line.startswith('PUDDLE_JUMP_HEIGHT'):
mat[5] = float(line.split('=')[1].strip())
elif line.startswith('PUDDLE_JUMP_DELAY'):
mat[6] = float(line.split('=')[1].strip())
elif line.startswith('CUT_HEIGHT'):
mat[7] = float(line.split('=')[1].strip())
elif line.startswith('CUT_SPEED'):
mat[8] = float(line.split('=')[1].strip())
elif line.startswith('CUT_AMPS'):
mat[9] = float(line.split('=')[1].strip())
elif line.startswith('CUT_VOLTS'):
mat[10] = float(line.split('=')[1].strip())
elif line.startswith('PAUSE_AT_END'):
mat[11] = float(line.split('=')[1].strip())
elif line.startswith('GAS_PRESSURE'):
mat[12] = float(line.split('=')[1].strip())
elif line.startswith('CUT_MODE'):
mat[13] = float(line.split('=')[1].strip())
elif line.startswith('THICKNESS'):
mat[14] = float(line.split('=')[1].strip())
self.write_materials_to_dict(mat)
if halpin not in self.materialList:
self.materialList.append(halpin)
exists = False
for n in range(self.w.materials_box.count()):
if self.w.materials_box.itemText(n) .startswith(str(halpin)):
self.w.materials_box.setItemText(n, f'{halpin:05d}: {self.materialDict[halpin][0]}')
self.w.material_selector.setItemText(n, f'{halpin:05d}: {self.materialDict[halpin][0]}')
self.w.conv_material.setItemText(n, f'{halpin:05d}: {self.materialDict[halpin][0]}')
exists = True
if not exists:
self.w.materials_box.addItem(f'{halpin:05d}: {self.materialDict[halpin][0]}')
self.w.material_selector.addItem(f'{halpin:05d}: {self.materialDict[halpin][0]}')
self.w.conv_material.addItem(f'{halpin:05d}: {self.materialDict[halpin][0]}')
if halpin == int(self.w.materials_box.currentText().split(': ', 1)[0]):
self.change_material(halpin)
self.materialTempPin.set(0)
def write_materials_to_dict(self, matNum):
self.materialDict[matNum[0]] = matNum[1:]
def display_materials(self):
self.materialList = []
self.w.materials_box.clear()
self.w.material_selector.clear()
self.w.conv_material.clear()
for key in sorted(self.materialDict):
self.w.materials_box.addItem(f'{key:05d}: {self.materialDict[key][0]}')
self.w.material_selector.addItem(f'{key:05d}: {self.materialDict[key][0]}')
self.w.conv_material.addItem(f'{key:05d}: {self.materialDict[key][0]}')
self.materialList.append(key)
def change_material(self, matNum):
self.materialName = self.materialDict[matNum][0]
self.w.kerf_width.setValue(self.materialDict[matNum][1])
self.w.pierce_height.setValue(self.materialDict[matNum][2])
self.w.pierce_delay.setValue(self.materialDict[matNum][3])
self.w.puddle_jump_height.setValue(self.materialDict[matNum][4])
self.w.puddle_jump_delay.setValue(self.materialDict[matNum][5])
self.w.cut_height.setValue(self.materialDict[matNum][6])
self.w.cut_feed_rate.setValue(self.materialDict[matNum][7])
self.w.cut_amps.setValue(self.materialDict[matNum][8])
self.w.cut_volts.setValue(self.materialDict[matNum][9])
self.w.pause_at_end.setValue(self.materialDict[matNum][10])
self.w.gas_pressure.setValue(self.materialDict[matNum][11])
self.w.cut_mode.setValue(self.materialDict[matNum][12])
self.w.material_thickness.setValue(self.materialDict[matNum][13])
self.materialChangeNumberPin.set(matNum)
def save_material_file(self, matNum, index):
mat = [matNum]
mat.append(self.w.materials_box.currentText().split(': ', 1)[1].strip())
mat.append(self.w.kerf_width.value())
mat.append(self.w.pierce_height.value())
mat.append(self.w.pierce_delay.value())
mat.append(self.w.puddle_jump_height.value())
mat.append(self.w.puddle_jump_delay.value())
mat.append(self.w.cut_height.value())
mat.append(self.w.cut_feed_rate.value())
mat.append(self.w.cut_amps.value())
mat.append(self.w.cut_volts.value())
mat.append(self.w.pause_at_end.value())
mat.append(self.w.gas_pressure.value())
mat.append(self.w.cut_mode.value())
mat.append(self.w.material_thickness.value())
self.write_one_material(mat)
self.materialUpdate = True
self.load_material_file(True)
self.w.materials_box.setCurrentIndex(index)
self.materialUpdate = False
self.set_saved_material()
def set_saved_material(self):
mat = [int(self.w.materials_box.currentText().split(': ', 1)[0])]
mat.append(self.materialName)
mat.append(self.w.kerf_width.value())
mat.append(self.w.pierce_height.value())
mat.append(self.w.pierce_delay.value())
mat.append(self.w.puddle_jump_height.value())
mat.append(self.w.puddle_jump_delay.value())
mat.append(self.w.cut_height.value())
mat.append(self.w.cut_feed_rate.value())
mat.append(self.w.cut_amps.value())
mat.append(self.w.cut_volts.value())
mat.append(self.w.pause_at_end.value())
mat.append(self.w.gas_pressure.value())
mat.append(self.w.cut_mode.value())
mat.append(self.w.material_thickness.value())
self.write_materials_to_dict(mat)
def load_material_file(self, keepTemp=False):
self.getMaterialBusy = True
self.MATS = Access(self.materialFile)
# don't remove temporary materials unless required
if keepTemp:
pop = [key for key in self.materialDict if key < 1000000]
for e in pop:
self.materialDict.pop(e)
else:
self.materialDict = {}
self.materialNumList = []
# create a basic default material if no materials exist
if not self.MATS.sections():
if self.units == 'mm':
mat = [0, 'Basic default Material', 1, 3, .1, 0, 0, 1, 1000, 45, 100, 0, 0, 1, 0]
else:
mat = [0, 'Basic default Material', .04, .12, .1, 0, 0, .04, 40, 45, 100, 0, 0, 1, 0]
self.write_one_material(mat)
head = _translate('HandlerClass', 'Materials Error')
# read all the materials into the materials dict
for section in self.MATS.sections():
matNum = int(section.rsplit('_', 1)[1])
if matNum >= 1000000:
msg0 = _translate('HandlerClass', 'Material number')
msg1 = _translate('HandlerClass', 'is invalid')
msg2 = _translate('HandlerClass', 'Material numbers need to be less than 1000000')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} "{matNum}" {msg1}\n{msg2}\n')
continue
mat = self.read_one_material(section)
self.materialNumList.append(matNum)
self.write_materials_to_dict(mat)
self.display_materials()
self.defaultMaterial = self.PREFS.getpref('Default material', self.materialNumList[0], int, 'GUI_OPTIONS')
if not self.material_exists(self.defaultMaterial):
self.defaultMaterial = self.materialList[0]
self.PREFS.putpref('Default material', self.defaultMaterial, int, 'GUI_OPTIONS')
self.change_material(self.defaultMaterial)
self.w.materials_box.setCurrentIndex(self.materialList.index(self.defaultMaterial))
self.w.material_selector.setCurrentIndex(self.w.materials_box.currentIndex())
self.w.conv_material.setCurrentIndex(self.w.materials_box.currentIndex())
self.set_default_material()
self.getMaterialBusy = False
def material_exists(self, matNum):
if int(matNum) in self.materialList:
return True
else:
if self.autoChange:
self.materialChangePin.set(-1)
self.materialChangeNumberPin.set(int(self.w.materials_box.currentText().split(': ', 1)[0]))
head = _translate('HandlerClass', 'Materials Error')
msg0 = _translate('HandlerClass', 'Material #')
msg1 = _translate('HandlerClass', 'not in material list')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0} #{int(matNum)} {msg1}\n')
return False
def read_one_material(self, section):
mat = [int(section.rsplit('_', 1)[1])]
mat.append(self.MATS.getpref('NAME', 'Material', str, section))
mat.append(self.MATS.getpref('KERF_WIDTH', 1.0 / self.unitsPerMm, float, section))
mat.append(self.MATS.getpref('PIERCE_HEIGHT', 3.0 / self.unitsPerMm, float, section))
mat.append(self.MATS.getpref('PIERCE_DELAY', 0.2, float, section))
mat.append(self.MATS.getpref('PUDDLE_JUMP_HEIGHT', 0.0, float, section))
mat.append(self.MATS.getpref('PUDDLE_JUMP_DELAY', 0.0, float, section))
mat.append(self.MATS.getpref('CUT_HEIGHT', 1.0 / self.unitsPerMm, float, section))
mat.append(self.MATS.getpref('CUT_SPEED', 2000.0 / self.unitsPerMm, float, section))
mat.append(self.MATS.getpref('CUT_AMPS', 45.0, float, section))
mat.append(self.MATS.getpref('CUT_VOLTS', 100, float, section))
mat.append(self.MATS.getpref('PAUSE_AT_END', 0.0, float, section))
mat.append(self.MATS.getpref('GAS_PRESSURE', 0.0, float, section))
mat.append(self.MATS.getpref('CUT_MODE', 1.0, float, section))
mat.append(self.MATS.getpref('THICKNESS', 0.0, float, section))
return mat
def write_one_material(self, mat):
section = f'MATERIAL_NUMBER_{mat[0]}'
self.MATS.putpref('NAME', mat[1], str, section)
self.MATS.putpref('KERF_WIDTH', mat[2], float, section)
self.MATS.putpref('PIERCE_HEIGHT', mat[3], float, section)
self.MATS.putpref('PIERCE_DELAY', mat[4], float, section)
self.MATS.putpref('PUDDLE_JUMP_HEIGHT', mat[5], float, section)
self.MATS.putpref('PUDDLE_JUMP_DELAY', mat[6], float, section)
self.MATS.putpref('CUT_HEIGHT', mat[7], float, section)
self.MATS.putpref('CUT_SPEED', mat[8], float, section)
self.MATS.putpref('CUT_AMPS', mat[9], float, section)
self.MATS.putpref('CUT_VOLTS', mat[10], float, section)
self.MATS.putpref('PAUSE_AT_END', mat[11], float, section)
self.MATS.putpref('GAS_PRESSURE', mat[12], float, section)
self.MATS.putpref('CUT_MODE', mat[13], float, section)
self.MATS.putpref('THICKNESS', mat[14], float, section)
def set_default_material(self):
self.getMaterialBusy = True
self.w.default_material.clear()
for n in self.materialNumList:
self.w.default_material.addItem(str(n))
self.getMaterialBusy = False
self.w.default_material.setCurrentIndex(self.materialList.index(self.defaultMaterial))
def default_material_changed(self, index):
if self.getMaterialBusy:
return
self.defaultMaterial = self.materialList[index]
self.change_material(self.defaultMaterial)
self.w.materials_box.setCurrentIndex(self.materialList.index(self.defaultMaterial))
self.PREFS.putpref('Default material', self.defaultMaterial, int, 'GUI_OPTIONS')
#########################################################################################################################
# CAMERA AND LASER FUNCTIONS #
#########################################################################################################################
def camera_pressed(self):
self.w.camview.rotation = STATUS.stat.rotation_xy
if self.w.preview_stack.currentIndex() != self.CAMERA:
self.w.preview_stack.setCurrentIndex(self.CAMERA)
else:
self.preview_index_return(self.w.preview_stack.currentIndex())
def laser_recovery_state_changed(self, value):
hal.set_p('plasmac.laser-recovery-start', '0')
def laser_clicked(self):
if STATUS.is_interp_paused():
return
if self.laserButtonState == 'reset':
self.laserButtonState = 'laser'
self.button_normal('laser')
self.refresh_button_states()
return
xPos = STATUS.get_position()[0][0] - self.laserOffsetX
yPos = STATUS.get_position()[0][1] - self.laserOffsetY
if xPos < self.xMin or xPos > self.xMax or yPos < self.yMin or yPos > self.yMax:
head = _translate('HandlerClass', 'Laser Error')
msg0 = _translate('HandlerClass', 'Laser is outside the machine boundary')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}\n')
return
if self.laserButtonState == 'laser':
self.w.laser.setText(_translate('HandlerClass', 'MARK\nEDGE'))
self.laserButtonState = 'markedge'
self.button_active('laser')
self.laserOnPin.set(1)
self.refresh_button_states()
return
elif self.laserButtonState == 'setorigin':
self.button_normal('laser')
self.laserOnPin.set(0)
self.laserButtonState = self.sheet_align(self.laserButtonState, self.w.laser, self.laserOffsetX, self.laserOffsetY)
self.refresh_button_states()
def laser_pressed(self):
if STATUS.is_interp_paused() and not self.laserRecStatePin.get():
xPos = STATUS.get_position()[0][0] + self.laserOffsetX
yPos = STATUS.get_position()[0][1] + self.laserOffsetY
if xPos < self.xMin or xPos > self.xMax or yPos < self.yMin or yPos > self.yMax:
head = _translate('HandlerClass', 'Laser Error')
msg0 = _translate('HandlerClass', 'Torch cannot move outside the machine boundary')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}\n')
return
hal.set_p('plasmac.laser-x-offset', f'{int(self.laserOffsetX / self.oScale)}')
hal.set_p('plasmac.laser-y-offset', f'{int(self.laserOffsetY / self.oScale)}')
hal.set_p('plasmac.laser-recovery-start', '1')
hal.set_p('plasmac.cut-recovery', '1')
self.laserOnPin.set(1)
return
self.laserTimer.start(750)
def sheet_align(self, button_state, button, offsetX, offsetY):
if button_state == 'markedge':
zAngle = self.w.camview.rotation = 0
ACTION.CALL_MDI_WAIT('G10 L2 P0 R0', 3)
ACTION.SET_MANUAL_MODE()
self.w.gcodegraphics.logger.clear()
self.w.cam_goto.setEnabled(False)
button.setText(_translate('HandlerClass', 'SET\nORIGIN'))
button_state = 'setorigin'
self.currentX = STATUS.get_position()[0][0]
self.currentY = STATUS.get_position()[0][1]
else:
if button == self.w.cam_mark:
button.setText(_translate('HandlerClass', 'MARK\nEDGE'))
button_state = 'markedge'
else:
button.setText(_translate('HandlerClass', 'LASER'))
button_state = 'laser'
xDiff = STATUS.get_position()[0][0] - self.currentX
yDiff = STATUS.get_position()[0][1] - self.currentY
if xDiff and yDiff:
zAngle = math.degrees(math.atan(yDiff / xDiff))
if xDiff > 0:
zAngle += 180
elif yDiff > 0:
zAngle += 360
if abs(xDiff) < abs(yDiff):
zAngle -= 90
elif xDiff:
if xDiff > 0:
zAngle = 180
else:
zAngle = 0
elif yDiff:
if yDiff > 0:
zAngle = 180
else:
zAngle = 0
else:
zAngle = 0
self.w.camview.rotation = zAngle
ACTION.CALL_MDI_WAIT(f'G10 L20 P0 X{offsetX} Y{offsetY}')
ACTION.CALL_MDI_WAIT(f'G10 L2 P0 R{zAngle}')
ACTION.CALL_MDI('G0 X0 Y0')
while not STATUS.is_interp_idle():
self.w.gcodegraphics.update()
if self.fileOpened:
self.file_reload_clicked()
self.w.gcodegraphics.logger.clear()
self.w.cam_goto.setEnabled(True)
ACTION.SET_MANUAL_MODE()
return button_state
def cam_mark_clicked(self):
xPos = STATUS.get_position()[0][0] - self.camOffsetX
yPos = STATUS.get_position()[0][1] - self.camOffsetY
if xPos < self.xMin or xPos > self.xMax or yPos < self.yMin or yPos > self.yMax:
head = _translate('HandlerClass', 'Camera Error')
msg0 = _translate('HandlerClass', 'Camera is outside the machine boundary')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}\n')
return
self.camButtonState = self.sheet_align(self.camButtonState, self.w.cam_mark, self.camOffsetX, self.camOffsetY)
def cam_goto_clicked(self):
ACTION.CALL_MDI_WAIT('G0 X0 Y0')
while not STATUS.is_interp_idle():
self.w.gcodegraphics.update()
ACTION.SET_MANUAL_MODE()
def cam_zoom_plus_pressed(self):
if self.w.camview.scale >= 5:
return
self.w.camview.scale += 0.1
def cam_zoom_minus_pressed(self):
if self.w.camview.scale <= 1:
return
self.w.camview.scale -= 0.1
def cam_dia_plus_pressed(self):
if self.w.camview.size().height() < self.w.camview.size().width():
size = self.w.camview.size().height()
else:
size = self.w.camview.size().width()
if self.w.camview.diameter >= size - 5:
return
self.w.camview.diameter += 2
def cam_dia_minus_pressed(self):
if self.w.camview.diameter <= 2:
return
self.w.camview.diameter -= 2
#########################################################################################################################
# STATISTICS FUNCTIONS #
#########################################################################################################################
def statistics_show(self):
for stat in ['cut', 'paused', 'probe', 'run', 'torch', 'rapid']:
self.display_hms(f'{stat}_time', hal.get_value(f'plasmac.{stat}-time'))
self.w.cut_length.setText(f'{hal.get_value("plasmac.cut-length") / self.statsDivisor:0.2f}')
self.w.pierce_count.setText(f'{hal.get_value("plasmac.pierce-count"):d}')
self.statistics_load()
def statistics_save(self, reset=False):
self.PREFS.putpref('Cut time', f'{self.statsSaved["cut"] + hal.get_value("plasmac.cut-time"):0.2f}', float, 'STATISTICS')
self.PREFS.putpref('Paused time', f'{self.statsSaved["paused"] + hal.get_value("plasmac.paused-time"):0.2f}', float, 'STATISTICS')
self.PREFS.putpref('Probe time', f'{self.statsSaved["probe"] + hal.get_value("plasmac.probe-time"):0.2f}', float, 'STATISTICS')
self.PREFS.putpref('Program run time', f'{self.statsSaved["run"] + hal.get_value("plasmac.run-time"):0.2f}', float, 'STATISTICS')
self.PREFS.putpref('Torch on time', f'{self.statsSaved["torch"] + hal.get_value("plasmac.torch-time"):0.2f}', float, 'STATISTICS')
self.PREFS.putpref('Rapid time', f'{self.statsSaved["rapid"] + hal.get_value("plasmac.rapid-time"):0.2f}', float, 'STATISTICS')
self.PREFS.putpref('Cut length', f'{self.statsSaved["length"] + hal.get_value("plasmac.cut-length"):0.2f}', float, 'STATISTICS')
self.PREFS.putpref('Pierce count', f'{self.statsSaved["pierce"] + hal.get_value("plasmac.pierce-count"):d}', int, 'STATISTICS')
self.statistics_load()
self.jobRunning = False
def statistics_load(self):
self.statsSaved['cut'] = self.PREFS.getpref('Cut time', 0.0, float, 'STATISTICS')
self.statsSaved['paused'] = self.PREFS.getpref('Paused time', 0.0, float, 'STATISTICS')
self.statsSaved['probe'] = self.PREFS.getpref('Probe time', 0.0, float, 'STATISTICS')
self.statsSaved['run'] = self.PREFS.getpref('Program run time', 0.0, float, 'STATISTICS')
self.statsSaved['torch'] = self.PREFS.getpref('Torch on time', 0.0, float, 'STATISTICS')
self.statsSaved['rapid'] = self.PREFS.getpref('Rapid time', 0.0, float, 'STATISTICS')
self.statsSaved['length'] = self.PREFS.getpref('Cut length', 0.0, float, 'STATISTICS')
self.statsSaved['pierce'] = self.PREFS.getpref('Pierce count', 0, int, 'STATISTICS')
for stat in ['cut', 'paused', 'probe', 'run', 'torch', 'rapid']:
self.display_hms(f'{stat}_time_t', self.statsSaved[f'{stat}'])
self.w.cut_length_t.setText(f'{self.statsSaved["length"] / self.statsDivisor:0.2f}')
self.w.pierce_count_t.setText(f'{self.statsSaved["pierce"]:d}')
def display_hms(self, widget, time):
m, s = divmod(time, 60)
h, m = divmod(m, 60)
self.w[widget].setText(f'{h:.0f}:{m:02.0f}:{s:02.0f}')
def statistic_reset(self, stat, statT):
if stat in ['cut_time', 'paused_time', 'probe_time', 'run_time', 'torch_time', 'rapid_time']:
self.display_hms(f'{stat}', 0)
elif stat == 'cut_length':
self.w.cut_length.setText('0.00')
elif stat == 'pierce_count':
self.w.pierce_count.setText('0')
self.PREFS.putpref(statT, 0.0, float, 'STATISTICS')
self.statistics_load()
def statistics_reset(self):
for stat in ['cut', 'paused', 'probe', 'run', 'torch', 'rapid']:
self.display_hms(f'{stat}_time', 0)
self.w.cut_length.setText('0.00')
self.w.pierce_count.setText('0')
for stat in ['Cut time', 'Paused time', 'Probe time', 'Program run time', 'Torch on time',
'Rapid time', 'Cut length']:
self.PREFS.putpref(stat, 0.0, float, 'STATISTICS')
self.PREFS.putpref('Pierce count', 0, int, 'STATISTICS')
self.statistics_load()
def statistics_init(self):
if self.unitsPerMm == 1:
self.statsDivisor = 1000
unit = _translate('HandlerClass', 'Metres')
else:
self.statsDivisor = 1
unit = _translate('HandlerClass', 'Inches')
msg0 = _translate('HandlerClass', 'CUT LENGTH')
self.w.cut_length_label.setText(f'{msg0} ({unit})')
self.statsSaved = {'cut': 0, 'length': 0, 'pierce': 0, 'paused': 0, 'probe': 0, 'rapid': 0, 'run': 0, 'torch': 0}
self.jobRunning = False
self.statistics_load()
#########################################################################################################################
# POWERMAX COMMUNICATIONS FUNCTIONS #
#########################################################################################################################
def pmx485_check(self, port, periodic=False):
try:
import serial
import serial.tools.list_ports as PORTS
head = _translate('HandlerClass', 'Port Error')
msg1 = _translate('HandlerClass', 'Powermax communications are disabled')
ports = []
for p in PORTS.comports():
ports.append(p[0])
if port in ports:
try:
sPort = serial.Serial(port, 19200)
sPort.close()
except Exception as err:
if not periodic:
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{err}\n{msg1}\n')
return False
else:
if not periodic:
msg0 = _translate('HandlerClass', 'cannot be found')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{port} {msg0}\n{msg1}\n')
return False
except:
if not periodic:
head = _translate('HandlerClass', 'Module Error')
msg0 = _translate('HandlerClass', 'python3-serial cannot be found')
msg1 = _translate('HandlerClass', 'Install python3-serial or linuxcnc-dev')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}\n{msg1}\n')
return False
return True
def pmx485_startup(self, port):
self.pmx485CommsError = False
self.w.pmx485Status = False
self.meshMode = False
self.w.pmx485_enable.stateChanged.connect(lambda w: self.pmx485_enable_changed(self.w.pmx485_enable.isChecked()))
self.pmx485StatusPin.value_changed.connect(lambda w: self.pmx485_status_changed(w))
self.pmx485ModePin.value_changed.connect(self.pmx485_mode_changed)
self.pmx485FaultPin.value_changed.connect(lambda w: self.pmx485_fault_changed(w))
self.pmx485ArcTimePin.value_changed.connect(lambda w: self.pmx485_arc_time_changed(w))
self.w.gas_pressure.valueChanged.connect(self.pmx485_pressure_changed)
self.w.mesh_enable.stateChanged.connect(lambda w: self.pmx485_mesh_enable_changed(self.w.mesh_enable.isChecked()))
self.pmx485CommsTimer = QTimer()
self.pmx485CommsTimer.timeout.connect(self.pmx485_timeout)
self.pmx485RetryTimer = QTimer()
self.pmx485RetryTimer.timeout.connect(lambda: self.pmx485_enable_changed(True))
self.oldCutMode = self.w.cut_mode.value()
self.pressure = self.w.gas_pressure.value()
if self.pmx485_load(port):
return
self.pmx485Exists = True
self.pmx485_setup()
self.w.pmx485_enable.setChecked(True)
def pmx485_load(self, port):
head = _translate('HandlerClass', 'Comms Error')
msg0 = _translate('HandlerClass', 'PMX485 component is not able to be loaded,')
msg1 = _translate('HandlerClass', 'Powermax communications are not available')
err = f'{head}:\n{msg0}\n{msg1}\n'
count = 0
while not hal.component_exists('pmx485'):
if count >= 3:
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, err)
return 1
RUN(['halcmd', 'loadusr', '-Wn', 'pmx485', 'pmx485', f'{port}'])
count += 1
return 0
def pmx485_setup(self):
self.pins485Comp = ['pmx485.enable', 'pmx485.status', 'pmx485.fault',
'pmx485.mode_set', 'pmx485.mode',
'pmx485.current_set', 'pmx485.current', 'pmx485.current_min', 'pmx485.current_max',
'pmx485.pressure_set', 'pmx485.pressure', 'pmx485.pressure_min', 'pmx485.pressure_max', 'pmx485.arcTime']
pinsSelf = ['pmx485_enable', 'pmx485_status', 'pmx485_fault',
'cut_mode-f', 'pmx485_mode',
'cut_amps-f', 'pmx485_current', 'pmx485_current_min', 'pmx485_current_max',
'gas_pressure-f', 'pmx485_pressure', 'pmx485_pressure_min', 'pmx485_pressure_max', 'pmx485_arc_time']
pinType = [hal.HAL_BIT, hal.HAL_BIT, hal.HAL_FLOAT,
hal.HAL_FLOAT, hal.HAL_FLOAT,
hal.HAL_FLOAT, hal.HAL_FLOAT, hal.HAL_FLOAT, hal.HAL_FLOAT,
hal.HAL_FLOAT, hal.HAL_FLOAT, hal.HAL_FLOAT, hal.HAL_FLOAT, hal.HAL_FLOAT]
for pin in self.pins485Comp:
hal.new_sig(f'plasmac:{pin.replace("pmx485.", "pmx485_")}', pinType[self.pins485Comp.index(pin)])
hal.connect(pin, f'plasmac:{pin.replace("pmx485.", "pmx485_")}')
hal.connect(f'qtplasmac.{pinsSelf[self.pins485Comp.index(pin)]}', f'plasmac:{pin.replace("pmx485.", "pmx485_")}')
self.pmx485_mesh_enable_changed(self.w.mesh_enable.isChecked())
self.w.cut_amps.setToolTip(_translate('HandlerClass', 'Powermax cutting current'))
def pmx485_enable_changed(self, state):
if state:
self.pmx485CommsError = False
self.pmx485RetryTimer.stop()
if self.pmx485_load(self.PREFS.getpref('Port', '', str, 'POWERMAX')):
return
# if pins not connected then connect them
if not hal.pin_has_writer('pmx485.enable'):
self.pmx485_setup()
# ensure valid parameters before trying to connect
if self.w.cut_mode.value() == 0 or self.w.cut_amps.value() == 0:
head = _translate('HandlerClass', 'Materials Error')
msg0 = _translate('HandlerClass', 'Invalid Cut Mode or Cut Amps,')
msg1 = _translate('HandlerClass', 'cannot connect to Powermax')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}\n{msg1}\n')
self.w.pmx485_enable.setChecked(False)
return
# good to go
else:
self.w.pmx485_label.setText(_translate('HandlerClass', 'CONNECTING'))
self.pmx485LabelState = 'CONNECT'
self.pmx485CommsTimer.start(3000)
else:
self.pmx485Connected = False
self.pmx485CommsError = False
self.w.pmx485_label.setText('')
self.pmx485LabelState = None
self.w.pmx485_label.setToolTip(_translate('HandlerClass', 'Status of PMX485 communications'))
self.pmx485CommsTimer.stop()
self.pmx485RetryTimer.stop()
def pmx485_mode_changed(self, widget):
if self.pmx485Connected:
self.w.gas_pressure.setValue(0)
def pmx485_pressure_changed(self, pressure):
if self.pmx485Connected:
if pressure < self.pressure:
if pressure < 0:
self.w.gas_pressure.setValue(self.gas_maximum)
elif self.w.gas_pressure.value() < self.gas_minimum:
self.w.gas_pressure.setValue(0)
elif pressure > self.pressure:
if pressure > self.gas_maximum:
self.w.gas_pressure.setValue(0)
elif pressure < self.gas_minimum:
self.w.gas_pressure.setValue(self.gas_minimum)
self.pressure = self.w.gas_pressure.value()
def pmx485_min_max_changed(self):
if not self.pmx485Connected:
return
self.w.cut_amps.setMinimum(self.pmx485CurrentMinPin.get())
self.w.cut_amps.setMaximum(self.pmx485CurrentMaxPin.get())
self.gas_minimum = self.pmx485PressureMinPin.get()
self.gas_maximum = self.pmx485PressureMaxPin.get()
self.w.gas_pressure.setMinimum(-1)
self.w.gas_pressure.setMaximum(self.gas_maximum + 1)
if self.gas_maximum > 15:
self.w.gas_pressure.setSuffix(' psi')
self.w.gas_pressure.setDecimals(0)
self.w.gas_pressure.setSingleStep(1)
else:
self.w.gas_pressure.setSuffix(' bar')
self.w.gas_pressure.setDecimals(1)
self.w.gas_pressure.setSingleStep(0.1)
def pmx485_status_changed(self, state):
if state != self.pmx485Connected:
if state:
self.pmx485CommsError = False
self.w.pmx485_label.setText(_translate('HandlerClass', 'CONNECTED'))
self.pmx485LabelState = 'CONNECT'
self.pmx485Connected = True
self.pmx485_min_max_changed()
if self.pmx485ArcTimePin.get():
self.pmx485_arc_time_changed(self.pmx485ArcTimePin.get())
if self.pmx485FaultPin.get():
self.pmx485_fault_changed(self.pmx485FaultPin.get())
self.pmx485CommsTimer.stop()
self.pmx485RetryTimer.stop()
else:
self.w.pmx485_label.setText(_translate('HandlerClass', 'COMMS ERROR'))
self.pmx485LabelState = None
self.pmx485CommsError = True
self.pmx485Connected = False
self.pmx485RetryTimer.start(3000)
def pmx485_arc_time_changed(self, time):
if self.pmx485Connected:
self.pmx485ArcTime = self.pmx485ArcTimePin.get()
self.w.pmx_arc_time_label.setText(_translate('HandlerClass', 'ARC ON TIME'))
self.display_hms('pmx_arc_time_t', self.pmx485ArcTime)
def pmx485_fault_changed(self, fault):
if self.pmx485Connected:
faultRaw = f'{fault:04.0f}'
self.pmx485FaultCode = f'{faultRaw[0]}-{faultRaw[1:3]}-{faultRaw[3]}'
head = _translate('HandlerClass', 'Powermax Error')
code = _translate('HandlerClass', 'Fault Code')
text = _translate('HandlerClass', 'Powermax error')
if faultRaw == '0000':
self.w.pmx485_label.setText(_translate('HandlerClass', 'CONNECTED'))
self.pmx485LabelState = 'CONNECT'
self.w.pmx485_label.setToolTip(_translate('HandlerClass', 'Status of PMX485 communications'))
elif faultRaw in self.pmx485FaultName.keys():
if faultRaw == '0210' and self.w.pmx485.current_max.value() > 110:
faultMsg = self.pmx485FaultName[faultRaw][1]
elif faultRaw == '0210':
faultMsg = self.pmx485FaultName[faultRaw][0]
else:
faultMsg = self.pmx485FaultName[faultRaw]
self.w.pmx485_label.setText(f'{code}: {self.pmx485FaultCode}')
self.pmx485LabelState = None
self.w.pmx485_label.setStatusTip(f'{text} ({self.pmx485FaultCode}) {faultMsg}')
msg0 = _translate('HandlerClass', 'CODE')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}: {self.pmx485FaultCode}\n{faultMsg}\n')
else:
self.w.pmx485_label.setText(f'{code}: {faultRaw}')
self.pmx485LabelState = None
msg0 = _translate('HandlerClass', 'Unknown Powermax fault code')
self.w.pmx485_label.setStatusTip(f'{msg0} ({faultRaw})')
STATUS.emit('error', linuxcnc.OPERATOR_ERROR, f'{head}:\n{msg0}: {faultRaw}\n')
def pmx485_mesh_enable_changed(self, state):
if state and not self.meshMode:
self.oldCutMode = self.w.cut_mode.value()
self.w.cut_mode.setValue(2)
self.w.cut_mode.setEnabled(False)
self.meshMode = True
elif not hal.get_value('plasmac.mesh-enable') and self.meshMode:
self.w.cut_mode.setValue(self.oldCutMode)
self.w.cut_mode.setEnabled(True)
self.meshMode = False
def pmx485_timeout(self):
self.pmx485CommsTimer.stop()
self.w.pmx485_label.setText(_translate('HandlerClass', 'COMMS ERROR'))
self.pmx485LabelState = None
self.pmx485CommsError = True
self.pmx485Connected = False
self.pmx485RetryTimer.start(3000)
pmx485FaultName = {
'0110': 'Remote controller mode invalid',
'0111': 'Remote controller current invalid',
'0112': 'Remote controller pressure invalid',
'0120': 'Low input gas pressure',
'0121': 'Output gas pressure low',
'0122': 'Output gas pressure high',
'0123': 'Output gas pressure unstable',
'0130': 'AC input power unstable',
'0199': 'Power board hardware protection',
'0200': 'Low gas pressure',
'0210': ('Gas flow lost while cutting', 'Excessive arc voltage'),
'0220': 'No gas input',
'0300': 'Torch stuck open',
'0301': 'Torch stuck closed',
'0320': 'End of consumable life',
'0400': 'PFC/Boost IGBT module under temperature',
'0401': 'PFC/Boost IGBT module over temperature',
'0402': 'Inverter IGBT module under temperature',
'0403': 'Inverter IGBT module over temperature',
'0500': 'Retaining cap off',
'0510': 'Start/trigger signal on at power up',
'0520': 'Torch not connected',
'0600': 'AC input voltage phase loss',
'0601': 'AC input voltage too low',
'0602': 'AC input voltage too high',
'0610': 'AC input unstable',
'0980': 'Internal communication failure',
'0990': 'System hardware fault',
'1000': 'Digital signal processor fault',
'1100': 'A/D converter fault',
'1200': 'I/O fault',
'2000': 'A/D converter value out of range',
'2010': 'Auxiliary switch disconnected',
'2100': 'Inverter module temp sensor open',
'2101': 'Inverter module temp sensor shorted',
'2110': 'Pressure sensor is open',
'2111': 'Pressure sensor is shorted',
'2200': 'DSP does not recognize the torch',
'3000': 'Bus voltage fault',
'3100': 'Fan speed fault',
'3101': 'Fan fault',
'3110': 'PFC module temperature sensor open',
'3111': 'PFC module temperature sensor shorted',
'3112': 'PFC module temperature sensor circuit fault',
'3200': 'Fill valve',
'3201': 'Dump valve',
'3202': 'Valve ID',
'3203': 'Electronic regulator is disconnected',
'3410': 'Drive fault',
'3420': '5 or 24 VDC fault',
'3421': '18 VDC fault',
'3430': 'Inverter capacitors unbalanced',
'3441': 'PFC over current',
'3511': 'Inverter saturation fault',
'3520': 'Inverter shoot-through fault',
'3600': 'Power board fault',
'3700': 'Internal serial communications fault',
}
#########################################################################################################################
# CUT RECOVERY FUNCTIONS #
#########################################################################################################################
def set_cut_recovery(self):
if hal.get_value('plasmac.cut-recovering'):
self.w.jog_stack.setCurrentIndex(self.CUT_RECOVERY)
return
self.w.jog_stack.setCurrentIndex(self.CUT_RECOVERY)
self.cutrec_speed_changed(self.w.cut_rec_speed.value())
hal.set_p('plasmac.cut-recovery', '0')
self.laserOnPin.set(0)
self.xOrig = hal.get_value('axis.x.eoffset-counts')
self.yOrig = hal.get_value('axis.y.eoffset-counts')
self.zOrig = hal.get_value('axis.z.eoffset-counts')
self.oScale = hal.get_value('plasmac.offset-scale')
def cutrec_speed_changed(self, speed):
text = _translate('HandlerClass', 'FEED')
if STATUS.is_metric_mode():
self.w.cut_rec_feed.setText(f'{text}\n{self.w.cut_feed_rate.value() * speed * 0.01:0.0f}')
else:
self.w.cut_rec_feed.setText(f'{text}\n{self.w.cut_feed_rate.value() * speed * 0.01:0.1f}')
def cutrec_move_changed(self, distance):
text = _translate('HandlerClass', 'MOVE')
self.w.cut_rec_move_label.setText(f'{text}\n{distance}')
def cutrec_motion(self, direction):
if self.w.cut_rec_fwd.isEnabled() and self.w.cut_rec_rev.isEnabled():
speed = float(self.w.cut_rec_speed.value()) * 0.01 * direction
hal.set_p('plasmac.paused-motion-speed', str(speed))
hal.set_p('plasmac.cut-recovery', '1')
def cutrec_move(self, state, x, y):
if not STATUS.is_interp_paused():
return
if state:
maxMove = 10
if self.units == 'in':
maxMove = 0.4
laser = self.laserRecStatePin.get() > 0
distX = hal.get_value('qtplasmac.kerf_width-f') * x
distY = hal.get_value('qtplasmac.kerf_width-f') * y
xNew = hal.get_value('plasmac.axis-x-position') + hal.get_value('axis.x.eoffset') - (self.laserOffsetX * laser) + distX
yNew = hal.get_value('plasmac.axis-y-position') + hal.get_value('axis.y.eoffset') - (self.laserOffsetY * laser) + distY
if xNew > self.xMax or xNew < self.xMin or yNew > self.yMax or yNew < self.yMin:
return
xTotal = hal.get_value('axis.x.eoffset') - (self.laserOffsetX * laser) + distX
yTotal = hal.get_value('axis.y.eoffset') - (self.laserOffsetY * laser) + distY
if xTotal > maxMove or xTotal < -maxMove or yTotal > maxMove or yTotal < -maxMove:
return
moveX = int(distX / self.oScale)
moveY = int(distY / self.oScale)
hal.set_p('plasmac.x-offset', f'{hal.get_value("plasmac.x-offset") + moveX}')
hal.set_p('plasmac.y-offset', f'{hal.get_value("plasmac.y-offset") + moveY}')
hal.set_p('plasmac.cut-recovery', '1')
def cutrec_offset_changed(self, xOffset, yOffset):
if hal.get_value('plasmac.consumable-changing'):
return
if xOffset > 0.001 * self.unitsPerMm or xOffset < -0.001 * self.unitsPerMm or \
yOffset > 0.001 * self.unitsPerMm or yOffset < -0.001 * self.unitsPerMm:
self.refresh_button_states()
elif not self.laserRecStatePin.get():
hal.set_p('plasmac.cut-recovery', '0')
hal.set_p('plasmac.x-offset', '0')
hal.set_p('plasmac.y-offset', '0')
self.laserOnPin.set(0)
def cutrec_cancel_pressed(self, state):
if state:
if hal.get_value('plasmac.cut-recovery'):
hal.set_p('plasmac.cut-recovery', '0')
self.laserOnPin.set(0)
#########################################################################################################################
# STYLING FUNCTIONS #
#########################################################################################################################
def openColorDialog(self, widget):
initColor = QColor(widget.palette().color(QPalette.Background))
options = QColorDialog.DontUseNativeDialog
options |= QColorDialog.ShowAlphaChannel
color = QColorDialog.getColor(initColor, options=options)
if color.isValid():
widget.setStyleSheet(f'background-color: {color.name()}')
buttons = ['foregrnd', 'foregalt', 'led', 'backgrnd', 'backgalt', 'frams', 'estop', 'disabled', 'preview']
labels = ['Foreground', 'Highlight', 'LED', 'Background', 'Background Alt', 'Frames', 'Estop', 'Disabled', 'Preview']
button = widget.objectName()
label = labels[buttons.index(button.split('_')[1])]
self.PREFS.putpref(label, color.name(), str, 'COLOR_OPTIONS')
self.set_basic_colors()
self.set_color_styles()
self.preview_stack_changed()
if self.umButton and _WEBENGINE_AVAILABLE:
self.w.webview.page().loadFinished.connect(self.style_user_manual)
self.w.webview.page().setBackgroundColor(QColor(self.backColor))
self.w.webview.reload()
self.simStyleUpdate.set(not self.simStyleUpdate.get())
def set_basic_colors(self):
self.foreColor = self.PREFS.getpref('Foreground', '#ffee06', str, 'COLOR_OPTIONS')
self.fore1Color = self.PREFS.getpref('Highlight', '#ffee06', str, 'COLOR_OPTIONS')
self.backColor = self.PREFS.getpref('Background', '#16160e', str, 'COLOR_OPTIONS')
self.back1Color = self.PREFS.getpref('Background Alt', '#26261e', str, 'COLOR_OPTIONS')
self.disabledColor = self.PREFS.getpref('Disabled', '#b0b0b0', str, 'COLOR_OPTIONS')
self.estopColor = self.PREFS.getpref('Estop', '#ff0000', str, 'COLOR_OPTIONS')
def set_color_styles(self):
self.styleSheetFile = os.path.join(self.PATHS.CONFIGPATH, 'qtplasmac.qss')
ssFile = self.PREFS.getpref('Custom style', 'None', str, 'GUI_OPTIONS')
# if custom stylesheet try to use it
if ssFile != 'None':
COPY(ssFile, self.styleSheetFile)
self.custom_stylesheet()
# otherwise use the standard stylesheet
else:
self.standard_stylesheet()
# apply the new stylesheet
self.w.setStyleSheet('')
with open(self.styleSheetFile, 'r') as set_style:
self.w.setStyleSheet(set_style.read())
# style some buttons
buttons = ['jog_x_minus', 'jog_x_plus', 'jog_y_minus', 'jog_y_plus',
'jog_z_minus', 'jog_z_plus', 'jog_a_minus', 'jog_a_plus',
'jog_b_minus', 'jog_b_plus', 'jog_c_minus', 'jog_c_plus',
'cut_rec_n', 'cut_rec_ne', 'cut_rec_e', 'cut_rec_se',
'cut_rec_s', 'cut_rec_sw', 'cut_rec_w', 'cut_rec_nw',
'conv_line', 'conv_circle', 'conv_ellipse', 'conv_triangle',
'conv_rectangle', 'conv_polygon', 'conv_bolt', 'conv_slot',
'conv_star', 'conv_gusset', 'conv_sector', 'conv_block']
conv_images = ['conv_line_point', 'conv_line_angle', 'conv_line_3p',
'conv_line_2pr', 'conv_arc_angle', 'conv_bolt_l',
'conv_circle_l', 'conv_ellipse_l', 'conv_gusset_l',
'conv_polygon_l', 'conv_rectangle_l', 'conv_sector_l',
'conv_slot_l', 'conv_star_l', 'conv_triangle_l']
for button in buttons:
self.color_item(button, self.foreColor, 'button')
self.w[button].setStyleSheet(f'QPushButton {{ background: {self.backColor} }} \
QPushButton:pressed {{ background: {self.backColor} }}')
for conv_image in conv_images:
self.color_item(conv_image, self.foreColor, 'image')
# some gcode display/editor colors cannot use .qss file
# gcode display current gcode line
self.w.gcode_display.setMarkerBackgroundColor(QColor(self.back1Color))
# gcode display active line
self.w.gcode_display.setCaretLineBackgroundColor(QColor(self.back1Color))
# gcode editor current gcode line
self.w.gcode_editor.editor.setMarkerBackgroundColor(QColor(self.back1Color))
self.w.gcode_editor.editor.setCaretForegroundColor(QColor(self.fore1Color))
# gcode editor active line
self.w.gcode_editor.editor.setCaretLineBackgroundColor(QColor(self.backColor))
def standard_stylesheet(self):
baseStyleFile = os.path.join(self.PATHS.SCREENDIR, self.PATHS.BASEPATH, 'qtplasmac.style')
customStyleFile = os.path.join(self.PATHS.CONFIGPATH, 'qtplasmac_custom.qss')
# Read in the base stylesheet file
with open(baseStyleFile, 'r') as inFile:
lines = inFile.readlines()
# Append the base file with changes if custom stylesheet is found
if os.path.isfile(customStyleFile):
with open(customStyleFile, 'r') as inFile:
lines += inFile.readlines()
elementColorMap = {
'backalt': self.w.color_backgalt.styleSheet().split(':')[1].strip(),
'backgnd': self.w.color_backgrnd.styleSheet().split(':')[1].strip(),
'e-stop': self.w.color_estop.styleSheet().split(':')[1].strip(),
'foregnd': self.w.color_foregrnd.styleSheet().split(':')[1].strip(),
'frames': self.w.color_frams.styleSheet().split(':')[1].strip(),
'highlight': self.w.color_foregalt.styleSheet().split(':')[1].strip(),
'inactive': self.w.color_disabled.styleSheet().split(':')[1].strip(),
'l-e-d': self.w.color_led.styleSheet().split(':')[1].strip(),
'prevu': self.w.color_preview.styleSheet().split(':')[1].strip()
}
with open(self.styleSheetFile, 'w') as outFile:
for line in lines:
for element, color in elementColorMap.items():
if element in line:
line = line.replace(element, color)
outFile.write(line)
def custom_stylesheet(self):
head = _translate('HandlerClass', 'Stylesheet Error')
try:
# set basic colors from stylesheet header
colors = [0, 0, 0, 0, 0]
with open(self.styleSheetFile, 'r') as inFile:
for line in inFile:
if line.startswith('color1'):
colors[0] += 1
self.foreColor = QColor(line.split('=')[1].strip()).name()
elif line.startswith('color2'):
colors[1] += 1
self.backColor = QColor(line.split('=')[1].strip()).name()
elif line.startswith('color3'):
colors[2] += 1
self.fore1Color = QColor(line.split('=')[1].strip()).name()
elif line.startswith('color4'):
colors[3] += 1
self.back1Color = QColor(line.split('=')[1].strip()).name()
elif line.startswith('color5'):
colors[4] += 1
self.disabledColor = QColor(line.split('=')[1].strip()).name()
if line.startswith('*'):
break
if colors != [1, 1, 1, 1, 1]:
raise ColorError()
# hide color buttons
for button in ['color_foregrnd', 'color_foregrnd_lbl', 'color_foregalt',
'color_foregalt_lbl', 'color_backgrnd', 'color_backgrnd_lbl',
'color_backgalt', 'color_backgalt_lbl', 'color_frams',
'color_frams_lbl', 'color_estop', 'color_estop_lbl',
'color_disabled', 'color_disabled_lbl', 'color_preview',
'color_preview_lbl', 'color_led', 'color_led_lbl']:
self.w[button].hide()
for button in ['camera', 'laser', self.ctButton, self.tpButton, self.ptButton, self.ccButton]:
if button:
self.button_normal(button)
except ColorError:
msg0 = _translate('HandlerClass', 'Invalid number of colors defined')
msg1 = _translate('HandlerClass', 'in custom stylesheet header')
msg2 = _translate('HandlerClass', 'Reverting to standard stylesheet')
self.dialog_show_ok(QMessageBox.Warning, f'{head}', f'\n{msg0}\n{msg1}\n\n{msg2}\n')
self.standard_stylesheet()
except:
msg0 = _translate('HandlerClass', 'Cannot open custom stylesheet')
msg1 = _translate('HandlerClass', 'Reverting to standard stylesheet')
self.dialog_show_ok(QMessageBox.Warning, f'{head}', f'\n{msg0}\n\n{msg1}\n')
self.standard_stylesheet()
def color_item(self, item, color, type):
image_path = f'{self.IMAGES}{item}.svg'
self.image = QPixmap(image_path)
colorChange = QPainter(self.image)
colorChange.setCompositionMode(QPainter.CompositionMode_SourceIn)
colorChange.fillRect(self.image.rect(), QColor(color))
colorChange.end()
if type == 'button':
self.w[item].setIcon(QIcon(self.image))
elif type == 'image':
self[item] = QPixmap(self.image)
def style_user_manual(self):
# There is a brief delay between the "loadFinished" signal and the versioning site's readiness for CSS changes
delayTime = 500 if 'qtplasmac/versions.html' in self.w.webview.url().toString() else 0
customStyling = f"""
setTimeout(function() {{
var style = document.createElement('style');
style.innerHTML = `
/* Apply background color to elements */
.caption, a, blockquote, body, figcaption, caption, code, div, div.content,
h1, h2, h3, h4, h5, h6, table, td, th, pre, ol, ul {{
background-color: {self.backColor} !important; }}
/* Apply foreground color to elements */
body, blockquote, caption, div, li, td, p {{
color: {self.foreColor} !important; }}
/* Apply highlight color to elements */
.caption, a, code, div.title, dt, em, figcaption, h1, h2, h3, h4, h5, h6,
span, strong, th, tt, ul {{
color: {self.fore1Color} !important; }}
/* Change table borders color and fix sizing */
table {{
border: 2px solid {self.foreColor} !important;
border-collapse: collapse !important; }}
/* Change table divider color and fix sizing */
td, th {{
border: 1px solid {self.foreColor} !important; }}
/* Remove borders from these elements, or things look odd after the other styling */
hr, div, div.content {{
border: none !important; }}
/* Apply highlight color to header underline */
h1, h2, h3, h4, h5, h6 {{
border-bottom: solid {self.fore1Color} !important; }}
/* Some images have a transparent background, this makes them visible */
img {{
background-color: white !important; }}
/* Apply alternate background color to highlighted sections (on section link click from TOC) */
:target {{
background: {self.back1Color} !important; }}
/* The following change scroll bar to match GUI styling */
::-webkit-scrollbar {{
width: 20px;
height: 20px; }}
::-webkit-scrollbar-thumb {{
background: {self.foreColor} !important;
border-radius: 4px;
min-height: 40px !important;
min-width: 40px !important; }}
::-webkit-scrollbar-track {{
background: {self.back1Color} !important;
border-radius: 4px; }}
::-webkit-scrollbar-corner {{
background: {self.backColor} !important; }}
`;
document.head.appendChild(style); }}, {delayTime});
"""
self.w.webview.page().runJavaScript(customStyling)
#########################################################################################################################
# KEY BINDING CALLS #
#########################################################################################################################
def key_is_valid(self, event, state):
return self.keyboard_shortcuts() and state and not event.isAutoRepeat()
def jog_is_valid(self, key, event):
return self.keyboard_shortcuts() and not event.isAutoRepeat() and self.w.main_tab_widget.currentIndex() == self.MAIN and self.w[f'jog_{key}'].isEnabled()
def on_keycall_ESTOP(self, event, state, shift, cntrl):
if self.key_is_valid(event, state) and self.w.estopButton == 2:
ACTION.SET_ESTOP_STATE(STATUS.estop_is_clear())
def on_keycall_POWER(self, event, state, shift, cntrl):
if self.key_is_valid(event, state):
ACTION.SET_MACHINE_STATE(not STATUS.machine_is_on())
def on_keycall_ABORT(self, event, state, shift, cntrl):
if self.key_is_valid(event, state):
self.abort_pressed()
def on_keycall_HOME(self, event, state, shift, cntrl):
if self.key_is_valid(event, state) and cntrl and not shift and self.w.main_tab_widget.currentIndex() == self.MAIN and STATUS.is_on_and_idle() and self.w.home_all.isEnabled():
if STATUS.is_all_homed():
ACTION.SET_MACHINE_UNHOMED(-1)
else:
ACTION.SET_MACHINE_HOMING(-1)
def on_keycall_RUN(self, event, state, shift, cntrl):
if self.key_is_valid(event, state) and cntrl and not shift and self.w.main_tab_widget.currentIndex() == self.MAIN:
if self.w.run.isEnabled():
self.run_clicked()
elif self.w.pause_resume.isEnabled() and STATUS.stat.paused:
self.pause_resume_pressed()
ACTION.RESUME()
def on_keycall_PAUSE(self, event, state, shift, cntrl):
if self.key_is_valid(event, state) and not shift and self.w.main_tab_widget.currentIndex() == self.MAIN:
if cntrl:
if self.w.screen_options.desktop_notify:
self.w.screen_options.QTVCP_INSTANCE_._NOTICE.external_close()
self.error_status(False)
elif self.w.pause_resume.isEnabled() and not STATUS.stat.paused:
ACTION.PAUSE()
def on_keycall_OPEN(self, event, state, shift, cntrl):
if self.key_is_valid(event, state) and self.w.main_tab_widget.currentIndex() == self.MAIN and \
self.w.file_open.isEnabled():
self.file_open_clicked()
def on_keycall_LOAD(self, event, state, shift, cntrl):
if self.key_is_valid(event, state) and self.w.main_tab_widget.currentIndex() == self.MAIN and \
self.w.file_reload.isEnabled():
self.file_reload_clicked()
def on_keycall_JOINT(self, event, state, shift, cntrl):
if self.key_is_valid(event, state) and self.w.main_tab_widget.currentIndex() == self.MAIN:
self.toggle_joint_mode()
def on_keycall_F12(self, event, state, shift, cntrl):
if self.key_is_valid(event, state):
self.STYLEEDITOR.load_dialog()
def on_keycall_F9(self, event, state, shift, cntrl):
if self.key_is_valid(event, state) and self.w.main_tab_widget.currentIndex() == self.MAIN and \
not self.probeTest and not self.torchPulse and not self.framing and \
(STATUS.is_interp_idle() or (self.manualCut and STATUS.is_interp_paused())):
self.manual_cut()
def on_keycall_XPOS(self, event, state, shift, cntrl):
if self.jog_is_valid('x_plus', event):
if STATUS.is_joint_mode():
self.kb_jog(state, self.coordinates.index('x'), 1, shift)
else:
self.kb_jog(state, 0, 1, shift)
def on_keycall_XNEG(self, event, state, shift, cntrl):
if self.jog_is_valid('x_minus', event):
if STATUS.is_joint_mode():
self.kb_jog(state, self.coordinates.index('x'), -1, shift)
else:
self.kb_jog(state, 0, -1, shift)
def on_keycall_YPOS(self, event, state, shift, cntrl):
if self.jog_is_valid('y_plus', event):
if STATUS.is_joint_mode():
self.kb_jog(state, self.coordinates.index('y'), 1, shift)
else:
self.kb_jog(state, 1, 1, shift)
def on_keycall_YNEG(self, event, state, shift, cntrl):
if self.jog_is_valid('y_minus', event):
if STATUS.is_joint_mode():
self.kb_jog(state, self.coordinates.index('y'), -1, shift)
else:
self.kb_jog(state, 1, -1, shift)
def on_keycall_ZPOS(self, event, state, shift, cntrl):
if self.jog_is_valid('z_plus', event):
if self.zPlusOverrideJog and self.w.chk_override_jog.isEnabled():
self.w.chk_override_jog.setChecked(True)
if STATUS.is_joint_mode():
self.kb_jog(state, self.coordinates.index('z'), 1, shift)
else:
self.kb_jog(state, 2, 1, shift)
def on_keycall_ZNEG(self, event, state, shift, cntrl):
if self.jog_is_valid('z_minus', event):
if STATUS.is_joint_mode():
self.kb_jog(state, self.coordinates.index('z'), -1, shift)
else:
self.kb_jog(state, 2, -1, shift)
def on_keycall_APOS(self, event, state, shift, cntrl):
if self.jog_is_valid('a_plus', event):
if STATUS.is_joint_mode():
self.kb_jog(state, self.coordinates.index('a'), 1, shift)
else:
self.kb_jog(state, 3, 1, shift)
def on_keycall_ANEG(self, event, state, shift, cntrl):
if self.jog_is_valid('a_minus', event):
if STATUS.is_joint_mode():
self.kb_jog(state, self.coordinates.index('a'), -1, shift)
else:
self.kb_jog(state, 3, -1, shift)
def on_keycall_BPOS(self, event, state, shift, cntrl):
if self.jog_is_valid('b_plus', event):
if STATUS.is_joint_mode():
self.kb_jog(state, self.coordinates.index('b'), 1, shift)
else:
self.kb_jog(state, 4, 1, shift)
def on_keycall_BNEG(self, event, state, shift, cntrl):
if self.jog_is_valid('b_minus', event):
if STATUS.is_joint_mode():
self.kb_jog(state, self.coordinates.index('b'), -1, shift)
else:
self.kb_jog(state, 4, -1, shift)
# FIXME - keys for these jogs not defined yet
# def on_keycall_CPOS(self, event, state, shift, cntrl):
# if self.jog_is_valid('c_plus', event):
# if STATUS.is_joint_mode():
# self.kb_jog(state, self.coordinates.index('c'), 1, shift)
# else:
# self.kb_jog(state, 5, 1, shift)
# def on_keycall_CNEG(self, event, state, shift, cntrl):
# if self.jog_is_valid('c_minus', event):
# if STATUS.is_joint_mode():
# self.kb_jog(state, self.coordinates.index('c'), -1, shift)
# else:
# self.kb_jog(state, 5, -1, shift)
def on_keycall_PLUS(self, event, state, shift, cntrl):
if self.key_is_valid(event, state) and self.w.main_tab_widget.currentIndex() == self.MAIN and self.jogSlow and self.w.jog_slider.isEnabled():
return
if self.key_is_valid(event, state) and self.w.main_tab_widget.currentIndex() == self.MAIN:
self.jogFast = True
else:
self.jogFast = False
def on_keycall_MINUS(self, event, state, shift, cntrl):
if self.key_is_valid(event, state) and self.w.main_tab_widget.currentIndex() == self.MAIN and self.jogFast and self.w.jog_slider.isEnabled():
return
if self.key_is_valid(event, state) and self.w.main_tab_widget.currentIndex() == self.MAIN:
self.jogSlow = True
else:
self.jogSlow = False
def on_keycall_NUMBER(self, event, state, shift, cntrl, number):
if self.key_is_valid(event, state) and self.w.main_tab_widget.currentIndex() == self.MAIN:
if shift and cntrl:
pass
elif shift and not cntrl:
if number:
self.w.rapid_slider.setValue(10 * number)
else:
self.w.rapid_slider.setValue(100)
elif cntrl and not shift:
if number:
self.w.feed_slider.setValue(10 * number)
else:
self.w.feed_slider.setValue(100)
else:
if number and self.w.jog_slider.isEnabled():
if self.w.jog_slow.isChecked():
self.w.jog_slider.setValue(INFO.DEFAULT_LINEAR_JOG_VEL * 0.10 * number / self.slowJogFactor)
else:
self.w.jog_slider.setValue(INFO.DEFAULT_LINEAR_JOG_VEL * 0.10 * number)
elif self.w.jog_slider.isEnabled():
if self.w.jog_slow.isChecked():
self.w.jog_slider.setValue(INFO.DEFAULT_LINEAR_JOG_VEL / self.slowJogFactor)
else:
self.w.jog_slider.setValue(INFO.DEFAULT_LINEAR_JOG_VEL)
def on_keycall_END(self, event, state, shift, cntrl):
if self.key_is_valid(event, state) and self.w.main_tab_widget.currentIndex() == self.MAIN and self.w.touch_xy.isEnabled():
self.touch_xy_clicked()
def on_keycall_DELETE(self, event, state, shift, cntrl):
if self.keyboard_shortcuts() and self.w.main_tab_widget.currentIndex() == self.MAIN and self.w.laser.isEnabled():
if state and not event.isAutoRepeat():
self.extLaserButton = True
self.laser_pressed()
else:
self.extLaserButton = False
self.laser_clicked()
def on_keycall_ALTRETURN(self, event, state, shift, cntrl):
if self.key_is_valid(event, state) and not cntrl and not shift and self.w.main_tab_widget.currentIndex() == self.MAIN and self.w.mdi_show.isEnabled():
self.mdi_show_clicked()
def on_keycall_RETURN(self, event, state, shift, cntrl):
if self.key_is_valid(event, state) and not cntrl and not shift and self.w.main_tab_widget.currentIndex() == self.MAIN and self.w.gcode_stack.currentIndex() == self.MDI and self.w.mdi_show.isEnabled():
self.mdi_show_clicked()
def on_keycall_QUOTELEFT(self, event, state, shift, cntrl):
if self.key_is_valid(event, state) and self.w.main_tab_widget.currentIndex() == self.MAIN:
if shift and cntrl:
pass
elif cntrl and not shift:
pass
elif shift and not cntrl:
self.w.rapid_slider.setValue(0)
else:
self.w.jog_slider.setValue(0)
#########################################################################################################################
# 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)]