Files
linuxcncAllWasm/configs/sim/woodpecker/zlevel.py
2026-08-20 06:17:39 +08:00

192 lines
7.7 KiB
Python

#!/usr/bin/env python3
import sys
import os
import tempfile
import atexit
import shutil
from qtpy import QtCore, QtGui, QtWidgets, uic
from qtpy.QtCore import QFile
from qtpy.QtWidgets import QFileDialog
#from linuxcnc import OPERATOR_ERROR, NML_ERROR
from qtvcp.core import Status, Action, Info
INFO = Info()
STATUS = Status()
ACTION = Action()
HERE = os.path.dirname(os.path.abspath(__file__))
class ZLevel(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ZLevel, self).__init__(parent)
# Load the widgets UI file:
self.filename = os.path.join(HERE, 'zlevel.ui')
try:
self.instance = uic.loadUi(self.filename, self)
except AttributeError as e:
print("Error: ", e)
# Initial values
self._tmp = None
self.size_x = 100
self.size_y = 100
self.x_steps = 2
self.y_steps = 2
self.unit_code = "G21"
self.unit_text = "metric"
# list of zero reference locations
self.reference = ["top-left", "top-right", "center", "bottom-left", "bottom-right"]
# set valid input formats for lineEdits
self.lineEdit_size_x.setValidator(QtGui.QDoubleValidator(0, 999, 3).setLocale(QtCore.QLocale("en_US")))
self.lineEdit_size_y.setValidator(QtGui.QDoubleValidator(0, 999, 3).setLocale(QtCore.QLocale("en_US")))
self.lineEdit_steps_x.setValidator(QtGui.QIntValidator(0, 100))
self.lineEdit_steps_y.setValidator(QtGui.QIntValidator(0, 100))
self.lineEdit_probe_vel.setValidator(QtGui.QIntValidator(0, 999).setLocale(QtCore.QLocale("en_US")))
self.lineEdit_zsafe.setValidator(QtGui.QDoubleValidator(0, 99, 1).setLocale(QtCore.QLocale("en_US")))
self.lineEdit_max_probe.setValidator(QtGui.QDoubleValidator(0, 99, 1).setLocale(QtCore.QLocale("en_US")))
self.lineEdit_size_x.setText(str(self.size_x))
self.lineEdit_size_y.setText(str(self.size_y))
self.lineEdit_steps_x.setText(str(self.x_steps))
self.lineEdit_steps_y.setText(str(self.y_steps))
self.lineEdit_zsafe.setText('20.0')
self.lineEdit_probe_vel.setText('200')
self.lineEdit_max_probe.setText('2.0')
self.lineEdit_comment.setText('Program comment')
# populate combobox
self.cmb_zero_ref.addItems(self.reference)
self.cmb_zero_ref.setCurrentIndex(2)
# signal connections
self.btn_make_gcode.pressed.connect(self.calculate_toolpath)
self.btn_save_gcode.pressed.connect(self.save_gcode)
self.btn_load_gcode.pressed.connect(self.load_gcode)
self.btn_mm.clicked.connect(lambda obj: self.units_changed('mm'))
self.btn_inch.clicked.connect(lambda obj: self.units_changed('in'))
def _hal_init(self):
def homed_on_status():
return (STATUS.machine_is_on() and (STATUS.is_all_homed() or INFO.NO_HOME_REQUIRED))
STATUS.connect('state_off', lambda w: self.setEnabled(False))
STATUS.connect('state_estop', lambda w: self.setEnabled(False))
STATUS.connect('interp-idle', lambda w: self.setEnabled(homed_on_status()))
STATUS.connect('all-homed', lambda w: self.setEnabled(True))
def units_changed(self, unit):
if unit == 'in':
self.unit_code = "G20"
self.unit_text = 'imperial'
elif unit == 'mm':
self.unit_code = "G21"
self.unit_text = "metric"
def save_gcode(self):
if not self._tmp:
self.add_status("GCode file not created yet")
return
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
fileName, _ = QFileDialog.getSaveFileName(self,"Save to file","","All Files (*);;ngc Files (*.ngc)", options=options)
if fileName:
shutil.copy(self._tmp, fileName)
self.add_status("GCode saved to {}".format(fileName))
else:
self.add_status("None or invalid filename specified")
def load_gcode(self):
if not self._tmp:
self.add_status("GCode file not created yet")
return
ACTION.OPEN_PROGRAM(self._tmp)
self.add_status("GCode loaded to LinuxCNC")
def calculate_toolpath(self):
# some sanity checks
try:
self.size_x = float(self.lineEdit_size_x.text())
self.size_y = float(self.lineEdit_size_y.text())
self.x_steps = int(self.lineEdit_steps_x.text())
self.y_steps = int(self.lineEdit_steps_y.text())
z_safe = float(self.lineEdit_zsafe.text())
z_probe = float(self.lineEdit_probe_vel.text())
max_probe = float(self.lineEdit_max_probe.text())
probe_vel = int(self.lineEdit_probe_vel.text())
except:
self.add_status("Input fields cannot be empty")
return
if self.size_x <= 0 or self.size_y <= 0:
self.add_status("Size X and Y must be greater than 0")
return
if self.x_steps < 2 or self.y_steps < 2:
self.add_status("Number of steps must be greater than 1")
return
# precalculate all X and Y coordinates
x_coords = []
y_coords = []
zref = self.cmb_zero_ref.currentIndex()
if zref == 2:
xoff = -self.size_x / 2
yoff = -self.size_y / 2
else:
xoff = 0 if zref == 0 or zref == 3 else -self.size_x
yoff = 0 if zref == 3 or zref == 4 else -self.size_y
inc = self.x_steps - 1
for i in range(self.x_steps):
next_x = ((i * self.size_x) / inc) + xoff
x_coords.append(next_x)
inc = self.y_steps - 1
for i in range(self.y_steps):
next_y = ((i * self.size_y) / inc) + yoff
y_coords.append(next_y)
self._tmp = self._mktemp()
# opening preamble
comment = self.lineEdit_comment.text()
self.line_num = 5
self.file = open(self._tmp, 'w')
self.file.write("%\n")
self.file.write("(Rectangular probe routine for Z Level Compensation)\n")
self.file.write("(Created for QtDragon_hd by persei8 - August 2022)\n")
self.file.write("({})\n".format(comment))
self.file.write("(Units are {})\n".format(self.unit_text))
self.file.write("(Area: X {} by Y {})\n".format(self.size_x, self.size_y))
self.file.write("(Steps: X {} by Y {})\n".format(self.x_steps, self.y_steps))
self.file.write("(Safe Z travel height {})".format(z_safe))
self.file.write("\n")
self.next_line("{} G40 G49 G64 P0.03".format(self.unit_code))
self.next_line("G17")
self.next_line("G90")
self.next_line("(PROBEOPEN probe_points.txt)")
self.next_line("G0 Z{}".format(z_safe))
self.next_line("G0 X0.0 Y0.0")
# main section
for y in y_coords:
for x in x_coords:
self.next_line("G0 Z{}".format(z_safe))
self.next_line("G0 X{:3.3f} Y{:3.3f}".format(x, y))
self.next_line("G38.2 Z-{} F{}".format(max_probe, probe_vel))
# closing section
self.next_line("(PROBECLOSE)")
self.next_line("G0 Z{}".format(z_safe))
self.next_line("M2")
self.file.write("%\n")
self.file.close()
def next_line(self, text):
self.file.write("N{} ".format(self.line_num) + text + "\n")
self.line_num += 5
def add_status(self, message):
self.lineEdit_status.setText(message)
STATUS.emit('update-machine-log', message, 'TIME')
def _mktemp(self):
tmp = tempfile.mkstemp(prefix='zlevel_', suffix='.ngc')
atexit.register(lambda: os.remove(tmp[1]))
return tmp[1]
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = ZLevel()
w.show()
sys.exit( app.exec_() )