#!/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()