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

281 lines
10 KiB
Python

#!/usr/bin/env python3
# Qtvcp probe subprogram
#
# Copyright (c) 2018 Chris Morley <chrisinnanaimo@hotmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# This subprogram is used by both versa_probe and basic_probe widgets
import sys
import time
import json
from qtpy.QtCore import QObject
from qtvcp.core import Status, Action, Info
from qtvcp.widgets.probe_routines import ProbeRoutines
# Instantiate the libraries with global reference
# STATUS gives us status messages from linuxcnc
STATUS = Status()
ACTION = Action()
INFO = Info()
class ProbeSubprog(QObject, ProbeRoutines):
def __init__(self):
QObject.__init__(self)
ProbeRoutines.__init__(self)
if INFO.MACHINE_IS_METRIC:
self._format_template = "%3.3f"
else:
self._format_template = "%2.4f"
self.send_dict = {}
self.error_status = None
# list of parameters received from main probe program
# excluding booleans, these are handled separately
self.parm_list = ['probe_diam',
'latch_return_dist',
'max_travel',
'max_z_travel',
'search_vel',
'probe_vel',
'rapid_vel',
'side_edge_length',
'xy_clearance',
'adj_x',
'adj_y',
'adj_z',
'adj_angle',
'x_hint_bp',
'y_hint_bp',
'x_hint_rv',
'y_hint_rv',
'diameter_hint',
'z_clearance',
'extra_depth',
'cal_x_width',
'cal_y_width',
'cal_diameter',
'calibration_offset',
'ts_diam',
'z_max_clear',
'ts_x',
'ts_y',
'ts_z',
'ts_max',
'tool_diameter',
'tool_number',
'tool_probe_height',
'tool_block_height']
# data structure to hold parameters
# common
self.data_probe_diam = 1.0
self.data_latch_return_dist = 1.0
self.data_search_vel = 10.0
self.data_probe_vel = 10.0
self.data_rapid_vel = 10.0
self.data_max_travel = 10.0
self.data_max_z_travel = 10.0
self.data_side_edge_length = 1.0
self.data_xy_clearance = 1.0
self.data_z_clearance = 1.0
self.data_extra_depth = 0.0
self.allow_auto_zero = False
self.allow_auto_skew = False
# VersaProbe exclusive
self.data_adj_x = 0.0
self.data_adj_y = 0.0
self.data_adj_z = 0.0
self.data_adj_angle = 0.0
self.data_ts_diam = None
self.data_z_max_clear = None
self.data_ts_x= None
self.data_ts_y= None
self.data_ts_z= None
self.data_ts_max = None
self.data_tool_diameter = None
self.data_tool_number = None
self.data_tool_probe_height = None
self.data_tool_block_height = None
# BasicProbe exclusive
self.data_x_hint_bp = 0.0
self.data_y_hint_bp = 0.0
self.data_x_hint_rv = 0.0
self.data_y_hint_rv = 0.0
self.data_diameter_hint = 0.0
self.data_cal_x_width = 0.0
self.data_cal_y_width = 0.0
self.data_cal_diameter = 0.0
self.data_calibration_offset = 0.0
self.cal_avg_error = False
self.cal_x_error = False
self.cal_y_error = False
# list of results to be transferred to main program
self.status_list = ['xm', 'xc', 'xp', 'ym', 'yc', 'yp', 'lx', 'ly', 'z', 'd', 'a', 'delta','th','bh']
# data structure to hold result values
self.status_xm = None
self.status_xc = None
self.status_xp = None
self.status_ym = None
self.status_yc = None
self.status_yp = None
self.status_lx = None
self.status_ly = None
self.status_z = None
self.status_d = None
self.status_a = None
self.status_delta = None
self.status_th = None
self.status_bh = None
self.history_log = ""
self.process()
def process(self):
while 1:
try:
line = sys.stdin.readline()
except KeyboardInterrupt:
break
if line:
cmd = line
line = None
try:
error = self.process_command(cmd)
# block polling here 0 main program should start polling in their end
STATUS.block_error_polling()
# error = 1 means success,
# error = None means ignore,
# anything else is an error - a returned string is an error message
if error is not None:
if error != 1:
if type(error) == str:
sys.stdout.write("ERROR INFO Probe routine: {}\n".format(error))
else:
sys.stdout.write("ERROR Probe routine returned with error\n")
else:
self.collect_status()
sys.stdout.write("COMPLETE$" + json.dumps(self.send_dict) + "\n")
sys.stdout.flush()
sys.stdout.flush()
# print history
if self.history_log != "":
time.sleep(0.1)
sys.stdout.write("HISTORY {}\n".format(self.history_log))
self.history_log = ""
sys.stdout.flush()
except Exception as e:
sys.stdout.write("ERROR Command Error: {}\n".format(e))
sys.stdout.flush()
break
# check that the command is actually a method in our class else
# this message isn't for us - ignore it
def process_command(self, cmd):
cmd = cmd.rstrip().split('$')
if cmd[0] in dir(self):
if not STATUS.is_on_and_idle(): return None
pre = self.prechecks()
if pre is not None: return pre
parms = json.loads(cmd[1])
self.update_data(parms)
# start polling errors here - parent program should have blocked their polling
STATUS.unblock_error_polling()
error = self[cmd[0]]()
if (error != 1 or type(error)== str) and STATUS.is_on_and_idle():
ACTION.CALL_MDI("G90")
self.postreset()
return error
else:
return 'Command function {} not in probe routines'.format(cmd[0])
def update_data(self, parms):
for key in parms:
if key in self.parm_list:
try:
self['data_' + key] = float(parms[key].encode('utf-8'))
except:
pass
self.allow_auto_zero = True if parms['allow_auto_zero'] == '1' else False
self.allow_auto_skew = True if parms['allow_auto_skew'] == '1' else False
try:
self.cal_avg_error = True if parms['cal_avg_error'] == '1' else False
self.cal_x_error = True if parms['cal_x_error'] == '1' else False
self.cal_y_error = True if parms['cal_y_error'] == '1' else False
except:
pass
# adjust z_clearance data
#self.data_z_clearance += self.data_extra_depth
# clear all previous probe results
for i in (self.status_list):
self['status_' + i] = None
def collect_status(self):
try:
tmpl = lambda s: self._format_template % s
for key in self.status_list:
if self['status_' + key] is None:
data = 'None'
else:
data = tmpl(self['status_' + key])
self.send_dict.update( {key: data} )
except Exception as e:
print('ERROR ',e)
return
#for key in self.status_list:
# data = "{:3.3f}".format(self['status_' + key])
# self.send_dict.update( {key: data} )
#return
# need to be in the right mode - entries are in machine units
def prechecks(self):
# This is a work around. If a user sets the spindle running in MDI
# but turn it off with a manual button, then when M72 will turn the
# spindle back on! So we explicitly set M5 here.
ACTION.CALL_MDI('M5')
# record current G,S,M codes
ACTION.CALL_MDI('M70')
# set proper mode based on what machine is based
if INFO.MACHINE_IS_METRIC and STATUS.is_metric_mode():
return None
if not INFO.MACHINE_IS_METRIC and not STATUS.is_metric_mode():
return None
if INFO.MACHINE_IS_METRIC:
ACTION.CALL_MDI('g21')
else:
ACTION.CALL_MDI('g20')
return None
# return to previous motion modes
def postreset(self):
ACTION.CALL_MDI('M72')
########################################
# required boiler code
########################################
def __getitem__(self, item):
return getattr(self, item)
def __setitem__(self, item, value):
return setattr(self, item, value)
####################################
# Testing
####################################
if __name__ == "__main__":
w = ProbeSubprog()