import os from PyQt5 import QtCore, QtWidgets, QtGui from qtvcp.widgets.gcode_editor import GcodeEditor as GCODE from qtvcp.widgets.mdi_line import MDILine as MDI_WIDGET from qtvcp.widgets.tool_offsetview import ToolOffsetView as TOOL_TABLE from qtvcp.widgets.origin_offsetview import OriginOffsetView as OFFSET_VIEW from qtvcp.widgets.stylesheeteditor import StyleSheetEditor as SSE from qtvcp.widgets.file_manager import FileManager as FM from qtvcp.lib.writer import writer from qtvcp.lib.keybindings import Keylookup from qtvcp.lib.gcodes import GCodes from qtvcp.core import Status, Action, Info, Path, Qhal from qtvcp import logger from shutil import copyfile LOG = logger.getLogger(__name__) KEYBIND = Keylookup() STATUS = Status() INFO = Info() ACTION = Action() PATH = Path() STYLEEDITOR = SSE() WRITER = writer.Main() QHAL = Qhal() # constants for tab pages TAB_MAIN = 0 TAB_FILE = 1 TAB_OFFSETS = 2 TAB_TOOL = 3 TAB_STATUS = 4 #TAB_PROBE = 5 TAB_CAMVIEW = 5 TAB_GCODES = 6 TAB_SETUP = 7 TAB_SETTINGS = 8 TAB_UTILS = 9 class HandlerClass: def __init__(self, halcomp, widgets, paths): self.h = halcomp self.w = widgets self.gcodes = GCodes(widgets) self.valid = QtGui.QDoubleValidator(-999.999, 999.999, 3) self.styleeditor = SSE(widgets, paths) KEYBIND.add_call('Key_F4', 'on_keycall_F4') KEYBIND.add_call('Key_F12', 'on_keycall_F12') KEYBIND.add_call('Key_Pause', 'on_keycall_PAUSE') KEYBIND.add_call('Key_Space', 'on_keycall_PAUSE') # some global variables self.factor = 1.0 self.probe = None self.default_setup = os.path.join( PATH.CONFIGPATH, "default_setup.html") self.docs = os.path.join( PATH.SCREENDIR, PATH.BASEPATH, 'docs/getting_started.html') self.start_line = 0 self.run_time = 0 self.time_tenths = 0 self.timer_on = False self.home_all = False self.min_spindle_rpm = INFO.MIN_SPINDLE_SPEED self.max_spindle_rpm = INFO.MAX_SPINDLE_SPEED self.max_spindle_power = INFO.get_error_safe_setting( 'DISPLAY', 'MAX_SPINDLE_POWER', "1500") self.max_linear_velocity = INFO.MAX_TRAJ_VELOCITY self.system_list = ["G54", "G55", "G56", "G57", "G58", "G59", "G59.1", "G59.2", "G59.3"] self.slow_jog_factor = 10 self.reload_tool = 0 self.last_loaded_program = "" self.first_turnon = True self.unit_label_list = ["ts_height", "tp_height", "zoffset_units", "max_probe_units"] self.lineedit_list = ["work_height", "touch_height", "sensor_height", "laser_x", "laser_y", "sensor_x", "sensor_y", "camera_x", "camera_y", "search_vel", "probe_vel", "max_probe", "eoffset_count"] self.onoff_list = ["frame_program", "frame_tool", "frame_offsets", "frame_dro", "frame_override", "frame_macro_buttons"] self.axis_a_list = ["label_axis_a", "dro_axis_a", "action_zero_a", "axistoolbutton_a", "action_home_a", "widget_jog_angular", "widget_increments_angular", "a_plus_jogbutton", "a_minus_jogbutton"] STATUS.connect('general', self.dialog_return) STATUS.connect('state-on', lambda w: self.enable_onoff(True)) STATUS.connect('state-off', lambda w: self.enable_onoff(False)) STATUS.connect('mode-auto', lambda w: self.enable_auto(True)) STATUS.connect('gcode-line-selected', lambda w, line: self.set_start_line(line)) STATUS.connect('hard-limits-tripped', self.hard_limit_tripped) STATUS.connect('program-pause-changed', lambda w, state: self.w.btn_pause_spindle.setEnabled(state)) STATUS.connect('user-system-changed', lambda w, data: self.user_system_changed(data)) STATUS.connect('metric-mode-changed', lambda w, mode: self.metric_mode_changed(mode)) STATUS.connect('file-loaded', self.file_loaded) STATUS.connect('all-homed', self.all_homed) STATUS.connect('not-all-homed', self.not_all_homed) STATUS.connect('periodic', lambda w: self.update_runtimer()) STATUS.connect('interp-idle', lambda w: self.stop_timer()) STATUS.connect('actual-spindle-speed-changed', self.update_spindle) STATUS.connect('requested-spindle-speed-changed', self.update_spindle_requested) STATUS.connect('override-limits-changed', lambda w, state, data: self._check_override_limits(state, data)) self.html = """ Test page for the download:// scheme

Setup Tab

If you select a file with .html as a file ending, it will be shown here..

lcnc_swoop
""" % (os.path.join(paths.IMAGEDIR, 'lcnc_swoop.png')) def class_patch__(self): self.old_fman = FM.load FM.load = self.load_code def initialized__(self): self.init_pins() self.init_preferences() self.init_widgets() self.init_probe() self.init_utils() self.w.stackedWidget_log.setCurrentIndex(0) self.w.stackedWidget_dro.setCurrentIndex(0) if self.probe is not None: self.probe.hide() self.w.btn_pause_spindle.setEnabled(False) self.w.btn_dimensions.setChecked(True) self.w.page_buttonGroup.buttonClicked.connect(self.main_tab_changed) self.w.filemanager.onUserClicked() self.w.filemanager_usb.onMediaClicked() # hide widgets for A axis if not present if "A" not in INFO.AVAILABLE_AXES: for i in self.axis_a_list: self.w[i].hide() self.w.lbl_increments_linear.setText("INCREMENTS") # set validators for lineEdit widgets for val in self.lineedit_list: self.w['lineEdit_' + val].setValidator(self.valid) # set unit labels according to machine mode unit = "MM" if INFO.MACHINE_IS_METRIC else "IN" for i in self.unit_label_list: self.w['lbl_' + i].setText(unit) unit = "MM/MIN" if INFO.MACHINE_IS_METRIC else "IN/MIN" for i in ["search_vel_units", "probe_vel_units", "jog_linear"]: self.w['lbl_' + i].setText(unit) self.w.setWindowFlags(QtCore.Qt.FramelessWindowHint) # Populate macrobuttons macro_button_list = [self.w.macrobutton0, self.w.macrobutton1, self.w.macrobutton2, self.w.macrobutton3, self.w.macrobutton4, self.w.macrobutton5, self.w.macrobutton6, self.w.macrobutton7, self.w.macrobutton8, self.w.macrobutton9 ] i = 0 for macrobutton in macro_button_list: try: # Have to replace plaintext '\n' with a newline mylabel = INFO.MACRO_LABELS[i].replace(r'\n', '\n') macrobutton.setText(mylabel) LOG.debug(f'Added macro {mylabel}') i+=1 except: macrobutton.hide() LOG.debug(f'There were {i} user-defined macros') ############################# # SPECIAL FUNCTIONS SECTION # ############################# def init_pins(self): # spindle control pins pin = QHAL.newpin("spindle-amps", QHAL.HAL_FLOAT, QHAL.HAL_IN) pin.value_changed.connect(self.spindle_pwr_changed) pin = QHAL.newpin("spindle-volts", QHAL.HAL_FLOAT, QHAL.HAL_IN) pin.value_changed.connect(self.spindle_pwr_changed) pin = QHAL.newpin("spindle-fault", QHAL.HAL_U32, QHAL.HAL_IN) pin.value_changed.connect(self.spindle_fault_changed) # QHAL.newpin("spindle_at_speed", QHAL.HAL_BIT, QHAL.HAL_IN) pin = QHAL.newpin("spindle-modbus-errors", QHAL.HAL_U32, QHAL.HAL_IN) pin.value_changed.connect(self.mb_errors_changed) QHAL.newpin("spindle-inhibit", QHAL.HAL_BIT, QHAL.HAL_OUT) # external offset control pins QHAL.newpin("eoffset-enable", QHAL.HAL_BIT, QHAL.HAL_OUT) QHAL.newpin("eoffset-clear", QHAL.HAL_BIT, QHAL.HAL_OUT) QHAL.newpin("eoffset-count", QHAL.HAL_S32, QHAL.HAL_OUT) pin = QHAL.newpin("eoffset-value", QHAL.HAL_FLOAT, QHAL.HAL_IN) pin.value_changed.connect(self.eoffset_changed) def init_preferences(self): if not self.w.PREFS_: self.add_status( "CRITICAL - no preference file found, enable preferences in screenoptions widget") return self.last_loaded_program = self.w.PREFS_.getpref( 'last_loaded_file', None, str, 'BOOK_KEEPING') self.reload_tool = self.w.PREFS_.getpref( 'Tool to load', 0, int, 'CUSTOM_FORM_ENTRIES') self.w.lineEdit_laser_x.setText( str(self.w.PREFS_.getpref('Laser X', 100, float, 'CUSTOM_FORM_ENTRIES'))) self.w.lineEdit_laser_y.setText( str(self.w.PREFS_.getpref('Laser Y', -20, float, 'CUSTOM_FORM_ENTRIES'))) self.w.lineEdit_sensor_x.setText( str(self.w.PREFS_.getpref('Sensor X', 10, float, 'CUSTOM_FORM_ENTRIES'))) self.w.lineEdit_sensor_y.setText( str(self.w.PREFS_.getpref('Sensor Y', 10, float, 'CUSTOM_FORM_ENTRIES'))) self.w.lineEdit_camera_x.setText( str(self.w.PREFS_.getpref('Camera X', 10, float, 'CUSTOM_FORM_ENTRIES'))) self.w.lineEdit_camera_y.setText( str(self.w.PREFS_.getpref('Camera Y', 10, float, 'CUSTOM_FORM_ENTRIES'))) self.w.lineEdit_work_height.setText( str(self.w.PREFS_.getpref('Work Height', 20, float, 'CUSTOM_FORM_ENTRIES'))) self.w.lineEdit_touch_height.setText( str(self.w.PREFS_.getpref('Touch Height', 40, float, 'CUSTOM_FORM_ENTRIES'))) self.w.lineEdit_sensor_height.setText( str(self.w.PREFS_.getpref('Sensor Height', 40, float, 'CUSTOM_FORM_ENTRIES'))) self.w.lineEdit_search_vel.setText(str(self.w.PREFS_.getpref( 'Search Velocity', 40, float, 'CUSTOM_FORM_ENTRIES'))) self.w.lineEdit_probe_vel.setText(str(self.w.PREFS_.getpref( 'Probe Velocity', 10, float, 'CUSTOM_FORM_ENTRIES'))) self.w.lineEdit_max_probe.setText( str(self.w.PREFS_.getpref('Max Probe', 10, float, 'CUSTOM_FORM_ENTRIES'))) self.w.lineEdit_eoffset_count.setText( str(self.w.PREFS_.getpref('Eoffset count', 0, int, 'CUSTOM_FORM_ENTRIES'))) self.w.chk_eoffsets.setChecked(self.w.PREFS_.getpref( 'External offsets', False, bool, 'CUSTOM_FORM_ENTRIES')) self.w.chk_reload_program.setChecked(self.w.PREFS_.getpref( 'Reload program', False, bool, 'CUSTOM_FORM_ENTRIES')) self.w.chk_reload_tool.setChecked(self.w.PREFS_.getpref( 'Reload tool', False, bool, 'CUSTOM_FORM_ENTRIES')) self.w.chk_use_keyboard.setChecked(self.w.PREFS_.getpref( 'Use keyboard', False, bool, 'CUSTOM_FORM_ENTRIES')) self.w.chk_use_tool_sensor.setChecked(self.w.PREFS_.getpref( 'Use tool sensor', False, bool, 'CUSTOM_FORM_ENTRIES')) self.w.chk_use_touchplate.setChecked(self.w.PREFS_.getpref( 'Use tool touchplate', False, bool, 'CUSTOM_FORM_ENTRIES')) self.w.chk_run_from_line.setChecked(self.w.PREFS_.getpref( 'Run from line', False, bool, 'CUSTOM_FORM_ENTRIES')) self.w.chk_use_virtual.setChecked(self.w.PREFS_.getpref( 'Use virtual keyboard', False, bool, 'CUSTOM_FORM_ENTRIES')) self.w.chk_use_camera.setChecked(self.w.PREFS_.getpref( 'Use camera', False, bool, 'CUSTOM_FORM_ENTRIES')) self.w.chk_alpha_mode.setChecked(self.w.PREFS_.getpref( 'Use alpha display mode', False, bool, 'CUSTOM_FORM_ENTRIES')) self.w.chk_inhibit_selection.setChecked(self.w.PREFS_.getpref( 'Inhibit display mouse selection', True, bool, 'CUSTOM_FORM_ENTRIES')) def closing_cleanup__(self): if not self.w.PREFS_: return if self.last_loaded_program is not None: self.w.PREFS_.putpref('last_loaded_directory', os.path.dirname( self.last_loaded_program), str, 'BOOK_KEEPING') self.w.PREFS_.putpref( 'last_loaded_file', self.last_loaded_program, str, 'BOOK_KEEPING') self.w.PREFS_.putpref( 'Tool to load', STATUS.get_current_tool(), int, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Laser X', self.w.lineEdit_laser_x.text().encode( 'utf-8'), float, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Laser Y', self.w.lineEdit_laser_y.text().encode( 'utf-8'), float, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Sensor X', self.w.lineEdit_sensor_x.text().encode( 'utf-8'), float, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Sensor Y', self.w.lineEdit_sensor_y.text().encode( 'utf-8'), float, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Camera X', self.w.lineEdit_camera_x.text().encode( 'utf-8'), float, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Camera Y', self.w.lineEdit_camera_y.text().encode( 'utf-8'), float, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Work Height', self.w.lineEdit_work_height.text().encode( 'utf-8'), float, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Touch Height', self.w.lineEdit_touch_height.text( ).encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Sensor Height', self.w.lineEdit_sensor_height.text( ).encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Search Velocity', self.w.lineEdit_search_vel.text( ).encode('utf-8'), float, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Probe Velocity', self.w.lineEdit_probe_vel.text().encode( 'utf-8'), float, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Max Probe', self.w.lineEdit_max_probe.text().encode( 'utf-8'), float, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Eoffset count', self.w.lineEdit_eoffset_count.text( ).encode('utf-8'), int, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref( 'External offsets', self.w.chk_eoffsets.isChecked(), bool, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Reload program', self.w.chk_reload_program.isChecked( ), bool, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref( 'Reload tool', self.w.chk_reload_tool.isChecked(), bool, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref( 'Use keyboard', self.w.chk_use_keyboard.isChecked(), bool, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Use tool sensor', self.w.chk_use_tool_sensor.isChecked( ), bool, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Use tool touchplate', self.w.chk_use_touchplate.isChecked( ), bool, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref( 'Run from line', self.w.chk_run_from_line.isChecked(), bool, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Use virtual keyboard', self.w.chk_use_virtual.isChecked( ), bool, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref( 'Use camera', self.w.chk_use_camera.isChecked(), bool, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Use alpha display mode', self.w.chk_alpha_mode.isChecked( ), bool, 'CUSTOM_FORM_ENTRIES') self.w.PREFS_.putpref('Inhibit display mouse selection', self.w.chk_inhibit_selection.isChecked(), bool, 'CUSTOM_FORM_ENTRIES') def init_widgets(self): self.w.main_tab_widget.setCurrentIndex(0) self.w.slider_jog_linear.setMaximum(INFO.MAX_LINEAR_JOG_VEL) self.w.slider_jog_linear.setValue(INFO.DEFAULT_LINEAR_JOG_VEL) self.w.slider_jog_angular.setMaximum(INFO.MAX_ANGULAR_JOG_VEL) self.w.slider_jog_angular.setValue(INFO.DEFAULT_ANGULAR_JOG_VEL) self.w.slider_feed_ovr.setMaximum(INFO.MAX_FEED_OVERRIDE) self.w.slider_feed_ovr.setValue(100) self.w.slider_spindle_ovr.setMinimum(INFO.MIN_SPINDLE_OVERRIDE) self.w.slider_spindle_ovr.setMaximum(INFO.MAX_SPINDLE_OVERRIDE) self.w.slider_spindle_ovr.setValue(100) self.w.chk_override_limits.setChecked(False) self.w.chk_override_limits.setEnabled(False) self.w.lbl_maxv_percent.setText("100 %") self.w.lbl_max_rapid.setText(str(self.max_linear_velocity)) self.w.lbl_home_x.setText( INFO.get_error_safe_setting('JOINT_0', 'HOME', "50")) self.w.lbl_home_y.setText( INFO.get_error_safe_setting('JOINT_1', 'HOME', "50")) self.w.cmb_gcode_history.addItem("No File Loaded") self.w.cmb_gcode_history.wheelEvent = lambda event: None self.w.jogincrements_linear.wheelEvent = lambda event: None self.w.jogincrements_angular.wheelEvent = lambda event: None self.w.gcode_editor.hide() self.w.filemanager.table.setShowGrid(False) self.w.filemanager_usb.table.setShowGrid(False) self.w.tooloffsetview.setShowGrid(False) self.w.offset_table.setShowGrid(False) self.w.divider_line.hide() # move clock to statusbar self.w.statusbar.addPermanentWidget(self.w.lbl_clock) # set up gcode list self.gcodes.setup_list() # check for default setup html file try: # web view widget for SETUP page if self.w.web_view: self.toolBar = QtWidgets.QToolBar(self.w) self.w.layout_setup.addWidget(self.toolBar) self.backBtn = QtWidgets.QPushButton(self.w) self.backBtn.setEnabled(True) self.backBtn.setIcon(QtGui.QIcon( ':/qt-project.org/styles/commonstyle/images/left-32.png')) self.backBtn.clicked.connect(self.back) self.toolBar.addWidget(self.backBtn) self.forBtn = QtWidgets.QPushButton(self.w) self.forBtn.setEnabled(True) self.forBtn.setIcon(QtGui.QIcon( ':/qt-project.org/styles/commonstyle/images/right-32.png')) self.forBtn.clicked.connect(self.forward) self.toolBar.addWidget(self.forBtn) self.writeBtn = QtWidgets.QPushButton('SetUp\n Writer', self.w) self.writeBtn.setEnabled(True) self.writeBtn.clicked.connect(self.writer) self.toolBar.addWidget(self.writeBtn) self.w.layout_setup.addWidget(self.w.web_view) if os.path.exists(self.default_setup): self.w.web_view.load( QtCore.QUrl.fromLocalFile(self.default_setup)) else: self.w.web_view.setHtml(self.html) except Exception as e: print("No default setup file found - {}".format(e)) # set up spindle gauge self.w.gauge_spindle.set_max_value(self.max_spindle_rpm) self.w.gauge_spindle.set_max_reading(self.max_spindle_rpm / 1000) self.w.gauge_spindle.set_threshold(self.min_spindle_rpm) self.w.gauge_spindle.set_label("RPM") def init_probe(self): probe = INFO.get_error_safe_setting( 'PROBE', 'USE_PROBE', 'none').lower() if probe == 'versaprobe': LOG.info("Using Versa Probe") from qtvcp.widgets.versa_probe import VersaProbe self.probe = VersaProbe() self.probe.setObjectName('versaprobe') elif probe == 'basicprobe': LOG.info("Using Basic Probe") from qtvcp.widgets.basic_probe import BasicProbe self.probe = BasicProbe() self.probe.setObjectName('basicprobe') else: LOG.info("No valid probe widget specified") self.w.btn_probe.hide() return self.w.probe_layout.addWidget(self.probe) self.probe.hal_init() def init_utils(self): from qtvcp.lib.gcode_utility.facing import Facing self.facing = Facing() self.w.layout_facing.addWidget(self.facing) from qtvcp.lib.gcode_utility.hole_circle import Hole_Circle self.hole_circle = Hole_Circle() self.w.layout_hole_circle.addWidget(self.hole_circle) def processed_focus_event__(self, receiver, event): if not self.w.chk_use_virtual.isChecked() or STATUS.is_auto_mode(): return if isinstance(receiver, QtWidgets.QLineEdit): if not receiver.isReadOnly(): self.w.stackedWidget_dro.setCurrentIndex(1) elif isinstance(receiver, QtWidgets.QTableView): self.w.stackedWidget_dro.setCurrentIndex(1) elif isinstance(receiver, QtWidgets.QCommonStyle): return def processed_key_event__(self, receiver, event, is_pressed, key, code, shift, cntrl): # when typing in MDI, we don't want keybinding to call functions # so we catch and process the events directly. # We do want ESC, F1 and F2 to call keybinding functions though if code not in (QtCore.Qt.Key_Escape, QtCore.Qt.Key_F1, QtCore.Qt.Key_F2): # QtCore.Qt.Key_F3,QtCore.Qt.Key_F4,QtCore.Qt.Key_F5): # search for the top widget of whatever widget received the event # then check if it's one we want the keypress events to go to flag = False receiver2 = receiver while receiver2 is not None and not flag: if isinstance(receiver2, QtWidgets.QDialog): flag = True break if isinstance(receiver2, QtWidgets.QLineEdit): flag = True break if isinstance(receiver2, MDI_WIDGET): flag = True break if isinstance(receiver2, GCODE): flag = True break if isinstance(receiver2, TOOL_TABLE): flag = True break if isinstance(receiver2, OFFSET_VIEW): flag = True break receiver2 = receiver2.parent() if flag: if isinstance(receiver2, GCODE): # if in manual or in readonly mode do our keybindings - otherwise # send events to gcode widget if STATUS.is_man_mode() == False or not receiver2.isReadOnly(): if is_pressed: receiver.keyPressEvent(event) event.accept() return True if is_pressed: receiver.keyPressEvent(event) event.accept() return True else: event.accept() return True if event.isAutoRepeat(): return True # ok if we got here then try keybindings function calls # KEYBINDING will call functions from handler file as # registered by KEYBIND.add_call(KEY,FUNCTION) above return KEYBIND.manage_function_calls(self, event, is_pressed, key, shift, cntrl) ######################### # CALLBACKS FROM STATUS # ######################### def update_spindle(self, w, data): self.w.gauge_spindle.update_value(abs(data)) def update_spindle_requested(self, w, data): self.w.gauge_spindle.set_setpoint(abs(data)) def spindle_pwr_changed(self, data): # this calculation assumes the voltage is line to neutral # that the current reported by the VFD is total current for all 3 phases # and that the synchronous motor spindle has a power factor of 0.9 try: power = float(self.h['spindle-volts'] * self.h['spindle-amps'] * 0.9) # V x I x PF pc_power = (power / float(self.max_spindle_power)) * 100 if pc_power > 100: pc_power = 100 self.w.spindle_power.setValue(int(pc_power)) except Exception as e: # print(e) self.w.spindle_power.setValue(0) def spindle_fault_changed(self, data): fault = hex(self.h['spindle-fault']) self.w.lbl_spindle_fault.setText(fault) def mb_errors_changed(self, data): errors = self.h['spindle-modbus-errors'] self.w.lbl_mb_errors.setText(str(errors)) def eoffset_changed(self, data): eoffset = "{:2.3f}".format(self.h['eoffset-value']) self.w.lbl_eoffset_value.setText(eoffset) def dialog_return(self, w, message): rtn = message.get('RETURN') name = message.get('NAME') plate_code = bool(message.get('ID') == '_touchplate_') sensor_code = bool(message.get('ID') == '_toolsensor_') wait_code = bool(message.get('ID') == '_wait_resume_') unhome_code = bool(message.get('ID') == '_unhome_') if plate_code and name == 'MESSAGE' and rtn is True: self.touchoff('touchplate') elif sensor_code and name == 'MESSAGE' and rtn is True: self.touchoff('sensor') elif wait_code and name == 'MESSAGE': self.h['eoffset-clear'] = False elif unhome_code and name == 'MESSAGE' and rtn is True: ACTION.SET_MACHINE_UNHOMED(-1) def user_system_changed(self, data): sys = self.system_list[int(data) - 1] self.w.offset_table.selectRow(int(data) + 3) self.w.actionbutton_rel.setText(sys) def metric_mode_changed(self, mode): unit = "MM" if mode else "IN" self.w.lbl_jog_linear.setText(unit + "/MIN") if INFO.MACHINE_IS_METRIC == mode: self.factor = 1.0 rapid = (float(self.w.slider_rapid_ovr.value()) / 100) * \ self.max_linear_velocity elif mode: self.factor = 25.4 rapid = (float(self.w.slider_rapid_ovr.value()) / 100) * \ self.max_linear_velocity * 25.4 else: self.factor = 1/25.4 rapid = (float(self.w.slider_rapid_ovr.value()) / 100) * \ self.max_linear_velocity / 25.4 self.w.lbl_max_rapid.setText("{:4.0f}".format(rapid)) def file_loaded(self, obj, filename): if os.path.basename(filename).count('.') > 1: self.last_loaded_program = "" return if filename is not None: self.add_status("Loaded file {}".format(filename)) self.w.progressBar.reset() self.last_loaded_program = filename self.w.lbl_runtime.setText("00:00:00") else: self.add_status("Filename not valid") def percent_loaded_changed(self, fraction): if fraction < 0: self.w.progressBar.setValue(0) self.w.progressBar.setFormat('PROGRESS') else: self.w.progressBar.setValue(fraction) self.w.progressBar.setFormat('LOADING: {}%'.format(fraction)) def percent_done_changed(self, fraction): self.w.progressBar.setValue(fraction) if fraction < 0: self.w.progressBar.setValue(0) self.w.progressBar.setFormat('PROGRESS') else: self.w.progressBar.setFormat('COMPLETE: {}%'.format(fraction)) def all_homed(self, obj): self.home_all = True self.w.btn_home_all.setText("ALL HOMED") if self.first_turnon is True: self.first_turnon = False if self.w.chk_reload_tool.isChecked(): command = "M61 Q{} G43".format(self.reload_tool) ACTION.CALL_MDI(command) if self.last_loaded_program is not None and self.w.chk_reload_program.isChecked(): if os.path.isfile(self.last_loaded_program): self.w.cmb_gcode_history.addItem(self.last_loaded_program) self.w.cmb_gcode_history.setCurrentIndex( self.w.cmb_gcode_history.count() - 1) self.w.cmb_gcode_history.setToolTip( self.last_loaded_program) ACTION.OPEN_PROGRAM(self.last_loaded_program) ACTION.SET_MANUAL_MODE() self.w.manual_mode_button.setChecked(True) def not_all_homed(self, obj, list): self.home_all = False self.w.btn_home_all.setText("HOME ALL") def hard_limit_tripped(self, obj, tripped, list_of_tripped): self.add_status("Hard limits tripped") self.w.chk_override_limits.setEnabled(tripped) if not tripped: self.w.chk_override_limits.setChecked(False) # keep check button in synch of external changes def _check_override_limits(self, state, data): if 0 in data: self.w.chk_override_limits.setChecked(False) else: self.w.chk_override_limits.setChecked(True) ####################### # CALLBACKS FROM FORM # ####################### # main button bar def main_tab_changed(self, btn): index = btn.property("index") if index == self.w.main_tab_widget.currentIndex(): self.w.stackedWidget_dro.setCurrentIndex(0) if index is None: return # if in automode still allow settings to show so override linits can be used if STATUS.is_auto_mode() and index != 8: self.add_status("Cannot switch pages while in AUTO mode") # make sure main page is showing self.w.main_tab_widget.setCurrentIndex(0) self.w.btn_main.setChecked(True) return if index == TAB_MAIN: self.w.stackedWidget_dro.setCurrentIndex(0) elif index == TAB_FILE and self.w.btn_gcode_edit.isChecked(): self.w.btn_gcode_edit.setChecked(False) self.w.btn_gcode_edit_clicked(False) if btn == self.w.btn_probe: self.probe.show() self.w.divider_line.show() elif self.probe is not None: self.probe.hide() self.w.divider_line.hide() self.w.main_tab_widget.setCurrentIndex(index) # gcode frame def cmb_gcode_history_clicked(self): if self.w.cmb_gcode_history.currentIndex() == 0: return filename = self.w.cmb_gcode_history.currentText().encode('utf-8') if filename == self.last_loaded_program: self.add_status("Selected program is already loaded") else: ACTION.OPEN_PROGRAM(filename) # program frame def btn_start_clicked(self, obj): if self.w.main_tab_widget.currentIndex() != 0: return if not STATUS.is_auto_mode(): self.add_status("Must be in AUTO mode to run a program") return if STATUS.is_auto_running(): self.add_status("Program is already running") return self.run_time = 0 self.w.lbl_runtime.setText("00:00:00") if self.start_line <= 1: ACTION.RUN(self.start_line) else: # instantiate run from line preset dialog info = 'Running From Line: {} <\b>'.format(self.start_line) mess = {'NAME': 'RUNFROMLINE', 'TITLE': 'Preset Dialog', 'ID': '_RUNFROMLINE', 'MESSAGE': info, 'LINE': self.start_line} ACTION.CALL_DIALOG(mess) self.add_status("Started program from line {}".format(self.start_line)) self.timer_on = True def btn_stop_clicked(self): if not self.w.btn_pause_spindle.isChecked(): return self.w.btn_pause_spindle.setChecked(False) self.btn_pause_spindle_clicked(False) def btn_reload_file_clicked(self): if self.last_loaded_program: self.w.progressBar.reset() self.add_status("Loaded program file {}".format( self.last_loaded_program)) ACTION.OPEN_PROGRAM(self.last_loaded_program) def btn_pause_spindle_clicked(self, state): self.w.action_pause.setEnabled(not state) self.w.action_step.setEnabled(not state) if state: # set external offsets to lift spindle self.h['eoffset-enable'] = self.w.chk_eoffsets.isChecked() fval = float(self.w.lineEdit_eoffset_count.text()) self.h['eoffset-count'] = int(fval) self.h['spindle-inhibit'] = True else: self.h['eoffset-count'] = 0 self.h['eoffset-clear'] = True self.h['spindle-inhibit'] = False if STATUS.is_auto_running(): # instantiate warning box info = "Wait for spindle at speed signal before resuming" mess = {'NAME': 'MESSAGE', 'ICON': 'WARNING', 'ID': '_wait_resume_', 'MESSAGE': 'CAUTION', 'MORE': info, 'TYPE': 'OK'} ACTION.CALL_DIALOG(mess) # offsets frame def btn_goto_location_clicked(self): dest = self.w.sender().property('location') if dest == 'home': x = float(self.w.lbl_home_x.text()) y = float(self.w.lbl_home_y.text()) elif dest == 'sensor': x = float(self.w.lineEdit_sensor_x.text()) y = float(self.w.lineEdit_sensor_y.text()) else: return if not STATUS.is_metric_mode(): x = x / 25.4 y = y / 25.4 ACTION.CALL_MDI("G90") ACTION.CALL_MDI_WAIT("G53 G0 Z0") command = "G53 G0 X{:3.4f} Y{:3.4f}".format(x, y) ACTION.CALL_MDI_WAIT(command, 10) def btn_ref_laser_clicked(self): x = float(self.w.lineEdit_laser_x.text()) y = float(self.w.lineEdit_laser_y.text()) if not STATUS.is_metric_mode(): x = x / 25.4 y = y / 25.4 self.add_status("Laser offsets set") command = "G10 L20 P0 X{:3.4f} Y{:3.4f}".format(x, y) ACTION.CALL_MDI(command) def btn_ref_camera_clicked(self): x = float(self.w.lineEdit_camera_x.text()) y = float(self.w.lineEdit_camera_y.text()) if not STATUS.is_metric_mode(): x = x / 25.4 y = y / 25.4 self.add_status("Camera offsets set") command = "G10 L20 P0 X{:3.4f} Y{:3.4f}".format(x, y) ACTION.CALL_MDI(command) def btn_touchoff_clicked(self): if STATUS.get_current_tool() == 0: self.add_status("Cannot touchoff with no tool loaded") return if not STATUS.is_all_homed(): self.add_status("Must be homed to perform tool touchoff") return # instantiate dialog box sensor = self.w.sender().property('sensor') info = "Ensure tooltip is within {} mm of tool sensor and click OK".format( self.w.lineEdit_max_probe.text()) mess = {'NAME': 'MESSAGE', 'ID': sensor, 'MESSAGE': 'TOOL TOUCHOFF', 'MORE': info, 'TYPE': 'OKCANCEL'} ACTION.CALL_DIALOG(mess) def chk_lock_wph_changed(self, state): self.w.lineEdit_work_height.setReadOnly(not state) # DRO frame def btn_home_all_clicked(self, obj): if self.home_all is False: ACTION.SET_MACHINE_HOMING(-1) else: # instantiate dialog box info = "Unhome All Axes?" mess = {'NAME': 'MESSAGE', 'ID': '_unhome_', 'MESSAGE': 'UNHOME ALL', 'MORE': info, 'TYPE': 'OKCANCEL'} ACTION.CALL_DIALOG(mess) def btn_home_clicked(self): joint = self.w.sender().property('joint') axis = INFO.GET_NAME_FROM_JOINT.get(joint).lower() if self.w["dro_axis_{}".format(axis)].property('isHomed') is True: ACTION.SET_MACHINE_UNHOMED(joint) # ACTION.SET_MACHINE_HOMING(joint) else: ACTION.SET_MACHINE_HOMING(joint) # override frame def slow_button_clicked(self, state): slider = self.w.sender().property('slider') current = self.w[slider].value() max = self.w[slider].maximum() if state: self.w.sender().setText("SLOW") self.w[slider].setMaximum(max / self.slow_jog_factor) self.w[slider].setValue(current / self.slow_jog_factor) self.w[slider].setPageStep(10) else: self.w.sender().setText("FAST") self.w[slider].setMaximum(max * self.slow_jog_factor) self.w[slider].setValue(current * self.slow_jog_factor) self.w[slider].setPageStep(100) def slider_maxv_changed(self, value): maxpc = (float(value) / self.max_linear_velocity) * 100 self.w.lbl_maxv_percent.setText("{:3.0f} %".format(maxpc)) def slider_rapid_changed(self, value): rapid = (float(value) / 100) * self.max_linear_velocity * self.factor self.w.lbl_max_rapid.setText("{:4.0f}".format(rapid)) def btn_maxv_100_clicked(self): self.w.slider_maxv_ovr.setValue(self.max_linear_velocity) def btn_maxv_50_clicked(self): self.w.slider_maxv_ovr.setValue(self.max_linear_velocity / 2) # file tab def btn_gcode_edit_clicked(self, state): if not STATUS.is_on_and_idle(): return if state: self.w.filemanager.hide() self.w.gcode_editor.show() self.w.gcode_editor.editMode() else: self.w.filemanager.show() self.w.gcode_editor.hide() self.w.gcode_editor.readOnlyMode() def btn_load_file_clicked(self): if self.w.btn_gcode_edit.isChecked(): return fname = self.w.filemanager.getCurrentSelected() if fname[1] is True: self.load_code(fname[0]) def btn_copy_file_clicked(self): if self.w.btn_gcode_edit.isChecked(): return if self.w.sender() == self.w.btn_copy_right: source = self.w.filemanager_usb.getCurrentSelected() target = self.w.filemanager.getCurrentSelected() elif self.w.sender() == self.w.btn_copy_left: source = self.w.filemanager.getCurrentSelected() target = self.w.filemanager_usb.getCurrentSelected() else: return if source[1] is False: self.add_status("Specified source is not a file") return if target[1] is True: destination = os.path.join(os.path.dirname( target[0]), os.path.basename(source[0])) else: destination = os.path.join(target[0], os.path.basename(source[0])) try: copyfile(source[0], destination) self.add_status("Copied file from {} to {}".format( source[0], destination)) except Exception as e: self.add_status("Unable to copy file. %s" % e) # tool tab def btn_m61_clicked(self): checked = self.w.tooloffsetview.get_checked_list() if len(checked) > 1: self.add_status("Select only 1 tool to load") elif checked: self.add_status("Loaded tool {}".format(checked[0])) ACTION.CALL_MDI("M61 Q{} G43".format(checked[0])) else: self.add_status("No tool selected") # status tab def btn_clear_status_clicked(self): STATUS.emit('update-machine-log', None, 'DELETE') def btn_save_status_clicked(self): text = self.w.machinelog.toPlainText() filename = self.w.lbl_clock.text().encode('utf-8') filename = 'status_' + filename.replace(' ', '_') + '.txt' self.add_status("Saving Status file to {}".format(filename)) with open(filename, 'w') as f: f.write(text) def btn_dimensions_clicked(self, state): self.w.gcodegraphics.show_extents_option = state self.w.gcodegraphics.clear_live_plotter() # camview tab def cam_zoom_changed(self, value): self.w.camview.scale = float(value) / 10 def cam_dia_changed(self, value): self.w.camview.diameter = value def cam_rot_changed(self, value): self.w.camview.rotation = float(value) / 10 # settings tab def chk_override_limits_checked(self, state): # only toggle override if it's not in synch with the button if state and not STATUS.is_limits_override_set(): self.add_status("Override limits set") ACTION.TOGGLE_LIMITS_OVERRIDE() elif not state and STATUS.is_limits_override_set(): error = ACTION.TOGGLE_LIMITS_OVERRIDE() # if override can't be released set the check button to reflect this if error == False: self.w.chk_override_limits.blockSignals(True) self.w.chk_override_limits.setChecked(True) self.w.chk_override_limits.blockSignals(False) else: self.add_status("Override limits not set") def chk_run_from_line_changed(self, state): if not state: self.w.btn_cycle_start.setText('CYCLE START') def chk_alpha_mode_changed(self, state): self.w.gcodegraphics.set_alpha_mode(state) def chk_inhibit_selection_changed(self, state): self.w.gcodegraphics.set_inhibit_selection(state) def chk_use_camera_changed(self, state): self.w.btn_ref_camera.setEnabled(state) if state: self.w.btn_camera.show() else: self.w.btn_camera.hide() def chk_use_sensor_changed(self, state): self.w.btn_tool_sensor.setEnabled(state) def chk_use_touchplate_changed(self, state): self.w.btn_touchplate.setEnabled(state) def chk_use_virtual_changed(self, state): codestring = "CALCULATOR" if state else "ENTRY" for i in ("x", "y", "z", "a"): self.w["axistoolbutton_" + i].set_dialog_code(codestring) if self.probe: self.probe.dialog_code = codestring if not state: self.w.stackedWidget_dro.setCurrentIndex(0) ##################### # GENERAL FUNCTIONS # ##################### def load_code(self, fname): if fname is None: return filename, file_extension = os.path.splitext(fname) if not file_extension in (".html", '.pdf'): if not (INFO.program_extension_valid(fname)): self.add_status( "Unknown or invalid filename extension {}".format(file_extension)) return self.w.cmb_gcode_history.addItem(fname) self.w.cmb_gcode_history.setCurrentIndex( self.w.cmb_gcode_history.count() - 1) self.w.cmb_gcode_history.setToolTip(fname) ACTION.OPEN_PROGRAM(fname) self.add_status("Loaded program file : {}".format(fname)) self.w.main_tab_widget.setCurrentIndex(TAB_MAIN) self.w.filemanager.recordBookKeeping() # adjust ending to check for related HTML setup files fname = filename+'.html' if os.path.exists(fname): self.w.web_view.load(QtCore.QUrl.fromLocalFile(fname)) self.add_status("Loaded HTML file : {}".format(fname)) else: self.w.web_view.setHtml(self.html) # look for PDF setup files # load it with system program fname = filename+'.pdf' if os.path.exists(fname): url = QtCore.QUrl.fromLocalFile(fname) QtGui.QDesktopServices.openUrl(url) self.add_status("Loaded PDF file : {}".format(fname)) return if file_extension == ".html": try: self.w.web_view.load(QtCore.QUrl.fromLocalFile(fname)) self.add_status("Loaded HTML file : {}".format(fname)) self.w.main_tab_widget.setCurrentIndex(TAB_SETUP) self.w.stackedWidget.setCurrentIndex(0) self.w.btn_setup.setChecked(True) self.w.jogging_frame.hide() except Exception as e: print("Error loading HTML file : {}".format(e)) else: # load PDF with system program if os.path.exists(fname): url = QtCore.QUrl.fromLocalFile(fname) QtGui.QDesktopServices.openUrl(url) self.add_status("Loaded PDF file : {}".format(fname)) def disable_spindle_pause(self): self.h['eoffset-count'] = 0 self.h['spindle-inhibit'] = False if self.w.btn_pause_spindle.isChecked(): self.w.btn_pause_spindle.setChecked(False) def touchoff(self, selector): if selector == 'touchplate': z_offset = float(self.w.lineEdit_touch_height.text()) elif selector == 'sensor': z_offset = float(self.w.lineEdit_sensor_height.text()) - \ float(self.w.lineEdit_work_height.text()) else: self.add_alarm("Unknown touchoff routine specified") return self.add_status("Touchoff to {} started".format(selector)) max_probe = self.w.lineEdit_max_probe.text() search_vel = self.w.lineEdit_search_vel.text() probe_vel = self.w.lineEdit_probe_vel.text() rtn = ACTION.TOUCHPLATE_TOUCHOFF( search_vel, probe_vel, max_probe, z_offset) if rtn == 0: self.add_status("Touchoff routine is already running") def kb_jog(self, state, joint, direction, fast=False, linear=True): if not STATUS.is_man_mode() or not STATUS.machine_is_on(): self.add_status('Machine must be ON and in Manual mode to jog') return if linear: distance = STATUS.get_jog_increment() rate = STATUS.get_jograte()/60 else: distance = STATUS.get_jog_increment_angular() rate = STATUS.get_jograte_angular()/60 if state: if fast: rate = rate * 2 ACTION.JOG(joint, direction, rate, distance) else: ACTION.JOG(joint, 0, 0, 0) def add_status(self, message): self.w.statusbar.showMessage(message) STATUS.emit('update-machine-log', message, 'TIME') def enable_auto(self, state): if state is True: self.w.btn_main.setChecked(True) self.w.main_tab_widget.setCurrentIndex(TAB_MAIN) if self.probe is not None: self.probe.hide() self.w.stackedWidget_dro.setCurrentIndex(0) def enable_onoff(self, state): if state: self.add_status("Machine ON") else: self.add_status("Machine OFF") self.w.btn_pause_spindle.setChecked(False) self.h['eoffset-count'] = 0 for widget in self.onoff_list: self.w[widget].setEnabled(state) def set_start_line(self, line): if self.w.chk_run_from_line.isChecked(): self.start_line = line self.w.btn_cycle_start.setText( "CYCLE START\nLINE {}".format(self.start_line)) else: self.start_line = 1 def use_keyboard(self): if self.w.chk_use_keyboard.isChecked(): return True else: self.add_status('Keyboard shortcuts are disabled') return False def update_runtimer(self): if self.timer_on is False or STATUS.is_auto_paused(): return self.time_tenths += 1 if self.time_tenths == 10: self.time_tenths = 0 self.run_time += 1 hours, remainder = divmod(self.run_time, 3600) minutes, seconds = divmod(remainder, 60) self.w.lbl_runtime.setText( "{:02d}:{:02d}:{:02d}".format(hours, minutes, seconds)) def stop_timer(self): if self.timer_on: self.timer_on = False self.add_status("Run timer stopped at {}".format( self.w.lbl_runtime.text())) def back(self): if os.path.exists(self.default_setup): self.w.web_view.load(QtCore.QUrl.fromLocalFile(self.default_setup)) else: self.w.web_view.setHtml(self.html) # self.w.web_view.page().triggerAction(QWebEnginePage.Back) def forward(self): self.w.web_view.load(QtCore.QUrl.fromLocalFile(self.docs)) # self.w.web_view.page().triggerAction(QWebEnginePage.Forward) def writer(self): WRITER.show() ##################### # KEY BINDING CALLS # ##################### def on_keycall_ESTOP(self, event, state, shift, cntrl): if state: ACTION.SET_ESTOP_STATE(True) def on_keycall_POWER(self, event, state, shift, cntrl): if state: ACTION.SET_MACHINE_STATE(False) def on_keycall_ABORT(self, event, state, shift, cntrl): if state: ACTION.ABORT() def on_keycall_HOME(self, event, state, shift, cntrl): if state and not STATUS.is_all_homed() and self.use_keyboard(): ACTION.SET_MACHINE_HOMING(-1) def on_keycall_PAUSE(self, event, state, shift, cntrl): if state and STATUS.is_auto_mode() and self.use_keyboard(): ACTION.PAUSE() def on_keycall_XPOS(self, event, state, shift, cntrl): if self.use_keyboard(): self.kb_jog(state, 0, 1, shift) def on_keycall_XNEG(self, event, state, shift, cntrl): if self.use_keyboard(): self.kb_jog(state, 0, -1, shift) def on_keycall_YPOS(self, event, state, shift, cntrl): if self.use_keyboard(): self.kb_jog(state, 1, 1, shift) def on_keycall_YNEG(self, event, state, shift, cntrl): if self.use_keyboard(): self.kb_jog(state, 1, -1, shift) def on_keycall_ZPOS(self, event, state, shift, cntrl): if self.use_keyboard(): self.kb_jog(state, 2, 1, shift) def on_keycall_ZNEG(self, event, state, shift, cntrl): if self.use_keyboard(): self.kb_jog(state, 2, -1, shift) def on_keycall_APOS(self, event, state, shift, cntrl): if self.use_keyboard(): self.kb_jog(state, 3, 1, shift, False) def on_keycall_ANEG(self, event, state, shift, cntrl): if self.use_keyboard(): self.kb_jog(state, 3, -1, shift, False) def on_keycall_F4(self, event, state, shift, cntrl): if state: mess = {'NAME': 'CALCULATOR', 'TITLE': 'Calculator', 'ID': '_calculator_'} ACTION.CALL_DIALOG(mess) def on_keycall_F12(self, event, state, shift, cntrl): if state: self.styleeditor.load_dialog() ############################## # 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)]