#!/usr/bin/env python3
import tkinter as tk
from tkinter import ttk
import json
import os
import pyperclip
from plotter import Plotter, STATUS_FILE
POS_FILE = os.path.expanduser("~/.plotter_gui_state.json")
class PlotterGUI:
def __init__(self, root):
self.root = root
self.root.title("Plotter Control")
self.root.resizable(False, False)
# Add global padding
for i in range(7):
self.root.grid_rowconfigure(i, pad=5)
for i in range(5):
self.root.grid_columnconfigure(i, pad=5)
self.plotter = Plotter()
# Load saved state if available
saved_state = self.load_state()
# Load saved positions
pos = self.plotter.get_position()
# Step size slider value
self.step_size = tk.DoubleVar(value=saved_state.get("step_size", 1.0))
# Z slider value (spindle speed), fall back to saved state if available
self.z_value = tk.DoubleVar(value=saved_state.get("z_value", pos.get("z", 0.0)))
# Coordinates display
self.coord_label = ttk.Label(root, text=self.format_position(pos))
self.coord_label.grid(row=0, column=0, columnspan=4, pady=5)
# Copy JSON button
self.copy_button = ttk.Button(root, text="Copy JSON", command=self.copy_position)
self.copy_button.grid(row=0, column=4, padx=5)
# Clipboard feedback directly under Copy JSON button
self.feedback_label = ttk.Label(root, text="", foreground="green")
self.feedback_label.grid(row=1, column=4, pady=2)
# Directional buttons
self.up_btn = ttk.Button(root, text="^", command=lambda: self.move(0, self.step_size.get()))
self.up_btn.grid(row=2, column=1)
self.left_btn = ttk.Button(root, text="<", command=lambda: self.move(-self.step_size.get(), 0))
self.left_btn.grid(row=3, column=0)
self.home_btn = ttk.Button(root, text="Set Home", command=self.set_home)
self.home_btn.grid(row=3, column=1)
self.right_btn = ttk.Button(root, text=">", command=lambda: self.move(self.step_size.get(), 0))
self.right_btn.grid(row=3, column=2)
self.down_btn = ttk.Button(root, text="V", command=lambda: self.move(0, -self.step_size.get()))
self.down_btn.grid(row=4, column=1)
# Step size slider spans across the movement button area
self.step_slider = tk.Scale(root, from_=0.1, to=10, resolution=0.1,
orient="horizontal", variable=self.step_size, length=250)
self.step_slider.grid(row=5, column=0, columnspan=3, pady=5)
self.step_label = ttk.Label(root, text="Step size (mm)")
self.step_label.grid(row=6, column=0, columnspan=3)
# Z slider (spindle speed) with 40 at bottom and 0 at top
self.z_slider = tk.Scale(root, from_=0, to=40, orient="vertical", variable=self.z_value)
self.z_slider.grid(row=2, column=4, rowspan=3, padx=10)
self.set_z_btn = ttk.Button(root, text="Set Z", command=self.set_z)
self.set_z_btn.grid(row=5, column=4)
# Restore last window position or centre
self.restore_window_position(saved_state)
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
# Update display
self.update_position()
def format_position(self, pos):
return f"X: {pos['x']:.2f} Y: {pos['y']:.2f} Z: {pos['z']:.2f}"
def update_position(self):
pos = self.plotter.get_position()
self.coord_label.config(text=self.format_position(pos))
self.root.after(500, self.update_position)
def move(self, x, y):
self.plotter.move(x=x, y=y)
self.update_position()
def set_z(self):
z = self.z_value.get()
self.plotter.set_spindle(z)
self.update_position()
def set_home(self):
self.plotter.set_home()
self.update_position()
def copy_position(self):
pos = self.plotter.get_position()
json_str = json.dumps(pos)
pyperclip.copy(json_str)
self.feedback_label.config(text="Copied JSON")
self.root.after(2000, lambda: self.feedback_label.config(text=""))
def load_state(self):
if os.path.exists(POS_FILE):
try:
with open(POS_FILE, "r") as f:
return json.load(f)
except Exception:
return {}
return {}
def restore_window_position(self, state):
geom = state.get("geometry")
if geom:
self.root.geometry(geom)
else:
self.center_window()
def center_window(self):
self.root.update_idletasks()
w = self.root.winfo_width()
h = self.root.winfo_height()
ws = self.root.winfo_screenwidth()
hs = self.root.winfo_screenheight()
x = (ws // 2) - (w // 2)
y = (hs // 2) - (h // 2)
self.root.geometry(f"+{x}+{y}")
def on_close(self):
try:
state = {
"geometry": self.root.geometry(),
"step_size": self.step_size.get(),
"z_value": self.z_value.get()
}
with open(POS_FILE, "w") as f:
json.dump(state, f)
except Exception:
pass
self.root.destroy()
if __name__ == "__main__":
root = tk.Tk()
app = PlotterGUI(root)
root.mainloop()