Recreate this using html: import tkinter as tk
from tkinter import filedialog, ttk, messagebox
import json
import serial
import threading
import time
from PIL import Image, ImageTk
from ttkthemes import ThemedTk

class LaserCutterGUI:
    def __init__(self, master):
        self.master = master
        self.master.title("Advanced Ortur Laser Cutter Control Panel")
        self.master.geometry("800x600")
        self.serial_conn = None
        self.customer_profiles = self.load_profiles()
        self.is_connected = False
        self.job_running = False
        self.stop_thread = False

        # Apply dark theme
        self.style = ttk.Style(self.master)
        self.master.set_theme('equilux')  # Dark theme
        self.style.configure('.', foreground='white', background='#464646')
        self.style.configure('TLabel', foreground='white', background='#464646')
        self.style.configure('TButton', foreground='white', background='#666666')
        self.style.configure('TEntry', foreground='white', fieldbackground='#666666', background='#666666')
        self.style.configure('TNotebook', background='#333333')
        self.style.configure('TNotebook.Tab', foreground='white', background='#333333')
        self.style.map('TButton', background=[('active', '#787878')])
        self.style.map('TNotebook.Tab', background=[('selected', '#464646')])

        # Notebook for Tabs
        self.notebook = ttk.Notebook(master)
        self.notebook.pack(expand=True, fill="both")

        # Tabs
        self.create_control_tab()
        self.create_profiles_tab()
        self.create_design_tab()

    def create_control_tab(self):
        control_tab = ttk.Frame(self.notebook)
        self.notebook.add(control_tab, text="Control Panel")

        # Connection frame
        conn_frame = ttk.LabelFrame(control_tab, text="Connection")
        conn_frame.pack(padx=10, pady=10, fill="x")

        ttk.Label(conn_frame, text="Connection Status:").grid(row=0, column=0, sticky="w")
        self.conn_status = ttk.Label(conn_frame, text="Disconnected", foreground="red")
        self.conn_status.grid(row=0, column=1, sticky="w")

        ttk.Label(conn_frame, text="Port:").grid(row=1, column=0, sticky="w")
        self.port_entry = ttk.Entry(conn_frame)
        self.port_entry.grid(row=1, column=1, sticky="w")
        self.port_entry.insert(0, "COM3")  # Default port

        ttk.Label(conn_frame, text="Baudrate:").grid(row=2, column=0, sticky="w")
        self.baudrate_entry = ttk.Entry(conn_frame)
        self.baudrate_entry.grid(row=2, column=1, sticky="w")
        self.baudrate_entry.insert(0, "115200")

        self.connect_button = ttk.Button(conn_frame, text="Connect to Laser", command=self.connect_laser)
        self.connect_button.grid(row=3, column=0, pady=5)

        self.disconnect_button = ttk.Button(conn_frame, text="Disconnect", command=self.disconnect_laser, state="disabled")
        self.disconnect_button.grid(row=3, column=1, pady=5)

        # Laser Control
        control_frame = ttk.LabelFrame(control_tab, text="Laser Control")
        control_frame.pack(padx=10, pady=10, fill="x")

        self.home_button = ttk.Button(control_frame, text="Home", command=self.home_laser, state="disabled")
        self.home_button.grid(row=0, column=0, padx=5, pady=5)

        self.unlock_button = ttk.Button(control_frame, text="Unlock", command=self.unlock_laser, state="disabled")
        self.unlock_button.grid(row=0, column=1, padx=5, pady=5)

        self.position_button = ttk.Button(control_frame, text="Get Position", command=self.get_position, state="disabled")
        self.position_button.grid(row=0, column=2, padx=5, pady=5)

        # Job Control
        job_frame = ttk.LabelFrame(control_tab, text="Job Control")
        job_frame.pack(padx=10, pady=10, fill="x")

        self.start_button = ttk.Button(job_frame, text="Start Job", command=self.start_job, state="disabled")
        self.pause_button = ttk.Button(job_frame, text="Pause Job", command=self.pause_job, state="disabled")
        self.stop_button = ttk.Button(job_frame, text="Stop Job", command=self.stop_job, state="disabled")

        self.start_button.grid(row=0, column=0, padx=5, pady=5)
        self.pause_button.grid(row=0, column=1, padx=5, pady=5)
        self.stop_button.grid(row=0, column=2, padx=5, pady=5)

        # Console Output
        console_frame = ttk.LabelFrame(control_tab, text="Console")
        console_frame.pack(padx=10, pady=10, fill="both", expand=True)

        self.console_output = tk.Text(console_frame, height=10, bg='#333333', fg='white', insertbackground='white')
        self.console_output.pack(fill="both", expand=True)

    def create_profiles_tab(self):
        profiles_tab = ttk.Frame(self.notebook)
        self.notebook.add(profiles_tab, text="Customer Profiles")

        # Customer Profile Widgets
        left_frame = ttk.Frame(profiles_tab)
        left_frame.pack(side="left", fill="y", padx=10, pady=10)

        self.profile_list = tk.Listbox(left_frame, bg='#333333', fg='white', selectbackground='#666666', highlightbackground='#464646')
        self.profile_list.pack(side="left", fill="y")
        self.profile_list.bind('<<ListboxSelect>>', self.load_profile_settings)

        scroll_bar = ttk.Scrollbar(left_frame, orient="vertical", command=self.profile_list.yview)
        scroll_bar.pack(side="right", fill="y")
        self.profile_list.config(yscrollcommand=scroll_bar.set)

        right_frame = ttk.Frame(profiles_tab)
        right_frame.pack(side="right", fill="both", expand=True, padx=10, pady=10)

        profile_details = ttk.LabelFrame(right_frame, text="Profile Details")
        profile_details.pack(fill="both", expand=True)

        ttk.Label(profile_details, text="Profile Name:").grid(row=0, column=0, sticky="w", pady=5)
        self.profile_name = ttk.Entry(profile_details)
        self.profile_name.grid(row=0, column=1, sticky="w", pady=5)

        # Laser settings
        ttk.Label(profile_details, text="Power (%):").grid(row=1, column=0, sticky="w", pady=5)
        self.power_entry = ttk.Entry(profile_details)
        self.power_entry.grid(row=1, column=1, sticky="w", pady=5)

        ttk.Label(profile_details, text="Speed (mm/min):").grid(row=2, column=0, sticky="w", pady=5)
        self.speed_entry = ttk.Entry(profile_details)
        self.speed_entry.grid(row=2, column=1, sticky="w", pady=5)

        ttk.Label(profile_details, text="Passes:").grid(row=3, column=0, sticky="w", pady=5)
        self.passes_entry = ttk.Entry(profile_details)
        self.passes_entry.grid(row=3, column=1, sticky="w", pady=5)

        ttk.Label(profile_details, text="Description:").grid(row=4, column=0, sticky="nw", pady=5)
        self.description_text = tk.Text(profile_details, height=5, bg='#333333', fg='white', insertbackground='white', highlightbackground='#464646')
        self.description_text.grid(row=4, column=1, sticky="w", pady=5)

        save_button = ttk.Button(profile_details, text="Save Profile", command=self.save_profile)
        save_button.grid(row=5, column=0, pady=10)

        delete_button = ttk.Button(profile_details, text="Delete Profile", command=self.delete_profile)
        delete_button.grid(row=5, column=1, pady=10)

        self.update_profile_list()

    def create_design_tab(self):
        design_tab = ttk.Frame(self.notebook)
        self.notebook.add(design_tab, text="Design and Preview")

        file_frame = ttk.Frame(design_tab)
        file_frame.pack(fill="x", padx=10, pady=10)

        self.file_button = ttk.Button(file_frame, text="Import Design File", command=self.load_file)
        self.file_button.pack(side="left")

        self.file_label = ttk.Label(file_frame, text="No file selected")
        self.file_label.pack(side="left", padx=10)

        # Design Preview Canvas
        self.preview_canvas = tk.Canvas(design_tab, width=400, height=400, bg="#333333", highlightthickness=0)
        self.preview_canvas.pack(padx=10, pady=10)

        # Controls for modifying design
        controls_frame = ttk.Frame(design_tab)
        controls_frame.pack(fill="x", padx=10, pady=10)

        ttk.Label(controls_frame, text="Scale:").grid(row=0, column=0, sticky="w")
        self.scale_var = tk.DoubleVar(value=1.0)
        self.scale_scale = ttk.Scale(controls_frame, variable=self.scale_var, from_=0.1, to=5.0, orient="horizontal", command=self.update_preview)
        self.scale_scale.grid(row=0, column=1, sticky="we")

        ttk.Label(controls_frame, text="Rotation (°):").grid(row=1, column=0, sticky="w")
        self.rotation_var = tk.DoubleVar(value=0)
        self.rotation_scale = ttk.Scale(controls_frame, variable=self.rotation_var, from_=0, to=360, orient="horizontal", command=self.update_preview)
        self.rotation_scale.grid(row=1, column=1, sticky="we")

        # G-code generation and sending
        gcode_frame = ttk.Frame(design_tab)
        gcode_frame.pack(fill="x", padx=10, pady=10)

        self.generate_button = ttk.Button(gcode_frame, text="Generate G-code", command=self.generate_gcode)
        self.generate_button.pack(side="left")

        self.send_button = ttk.Button(gcode_frame, text="Send G-code to Laser", command=self.send_gcode, state="disabled")
        self.send_button.pack(side="left", padx=5)

    # Connection Methods
    def connect_laser(self):
        port = self.port_entry.get()
        baudrate = int(self.baudrate_entry.get())
        try:
            self.serial_conn = serial.Serial(port, baudrate, timeout=1)
            self.is_connected = True
            self.conn_status.config(text="Connected", foreground="green")
            self.connect_button.config(state="disabled")
            self.disconnect_button.config(state="normal")

            # Enable buttons
            self.home_button.config(state="normal")
            self.unlock_button.config(state="normal")
            self.position_button.config(state="normal")
            self.start_button.config(state="normal")
            self.stop_button.config(state="normal")
            self.send_button.config(state="normal")

            # Start thread to read serial data
            self.stop_thread = False
            self.read_thread = threading.Thread(target=self.read_serial)
            self.read_thread.start()

            # Reset laser position and state
            self.unlock_laser()
            self.home_laser()

            self.log_to_console("Laser connected successfully.")
        except serial.SerialException as e:
            messagebox.showerror("Connection Error", f"Could not connect to laser cutter: {e}")
            self.log_to_console(f"Connection failed: {e}")

    def disconnect_laser(self):
        self.stop_thread = True
        if hasattr(self, 'read_thread') and self.read_thread.is_alive():
            self.read_thread.join()
        if self.serial_conn and self.serial_conn.is_open:
            self.serial_conn.close()
        self.is_connected = False
        self.conn_status.config(text="Disconnected", foreground="red")
        self.connect_button.config(state="normal")
        self.disconnect_button.config(state="disabled")

        # Disable buttons
        self.home_button.config(state="disabled")
        self.unlock_button.config(state="disabled")
        self.position_button.config(state="disabled")
        self.start_button.config(state="disabled")
        self.pause_button.config(state="disabled")
        self.stop_button.config(state="disabled")
        self.send_button.config(state="disabled")

        self.log_to_console("Laser disconnected.")

    def read_serial(self):
        while not self.stop_thread:
            if self.serial_conn.in_waiting:
                line = self.serial_conn.readline().decode('utf-8', errors='ignore').strip()
                if line:
                    self.log_to_console(f"Laser: {line}")
            time.sleep(0.1)

    def send_command(self, command):
        if self.serial_conn and self.serial_conn.is_open:
            try:
                self.serial_conn.write(f"{command}\n".encode('utf-8'))
                self.log_to_console(f"Sent: {command}")
            except serial.SerialException as e:
                self.log_to_console(f"Error sending command: {e}")
        else:
            self.log_to_console("Serial connection is not open.")

    def home_laser(self):
        self.send_command("$H")

    def unlock_laser(self):
        self.send_command("$X")

    def get_position(self):
        self.send_command("?")

    # Job Control Methods
    def start_job(self):
        if not self.is_connected:
            messagebox.showwarning("Not Connected", "Please connect to the laser cutter first.")
            return

        if not hasattr(self, 'gcode_commands') or not self.gcode_commands:
            messagebox.showwarning("No G-code", "Please generate or load G-code commands first.")
            return

        if self.job_running:
            messagebox.showwarning("Job Running", "A job is already running.")
            return

        self.job_thread = threading.Thread(target=self.run_job)
        self.stop_thread = False
        self.job_thread.start()

        self.start_button.config(state="disabled")
        self.pause_button.config(state="normal")
        self.stop_button.config(state="normal")
        self.job_running = True
        self.log_to_console("Job started.")

    def run_job(self):
        for command in self.gcode_commands:
            if self.stop_thread:
                break
            self.send_command(command)
            time.sleep(0.1)  # Adjust timing as necessary
        self.job_running = False
        self.log_to_console("Job completed.")
        self.start_button.config(state="normal")
        self.pause_button.config(state="disabled")
        self.stop_button.config(state="disabled")

    def pause_job(self):
        # Pausing and resuming is more complex and requires tracking the state
        # For simplicity, pausing stops the job and can be resumed by starting again
        self.stop_thread = True
        if self.job_thread.is_alive():
            self.job_thread.join()
        self.job_running = False
        self.log_to_console("Job paused.")
        self.start_button.config(state="normal")
        self.pause_button.config(state="disabled")
        self.stop_button.config(state="disabled")

    def stop_job(self):
        self.stop_thread = True
        if self.job_thread.is_alive():
            self.job_thread.join()
        self.job_running = False
        self.log_to_console("Job stopped.")
        self.start_button.config(state="normal")
        self.pause_button.config(state="disabled")
        self.stop_button.config(state="disabled")
        # Send immediate stop command
        self.send_command("M112")  # Emergency stop command for GRBL controllers

    # Profile Management Methods
    def load_profiles(self):
        try:
            with open('profiles.json', 'r') as file:
                return json.load(file)
        except FileNotFoundError:
            return {}

    def save_profile(self):
        profile_name = self.profile_name.get().strip()
        if profile_name:
            self.customer_profiles[profile_name] = {
                'power': self.power_entry.get(),
                'speed': self.speed_entry.get(),
                'passes': self.passes_entry.get(),
                'description': self.description_text.get("1.0", tk.END).strip()
            }
            with open('profiles.json', 'w') as file:
                json.dump(self.customer_profiles, file, indent=4)
            self.update_profile_list()
            self.log_to_console(f"Profile '{profile_name}' saved.")
        else:
            messagebox.showwarning("Invalid Input", "Profile name cannot be empty.")

    def delete_profile(self):
        selected_profile = self.profile_list.get(tk.ACTIVE)
        if selected_profile:
            confirm = messagebox.askyesno("Delete Profile", f"Are you sure you want to delete '{selected_profile}'?")
            if confirm:
                del self.customer_profiles[selected_profile]
                with open('profiles.json', 'w') as file:
                    json.dump(self.customer_profiles, file, indent=4)
                self.update_profile_list()
                self.clear_profile_fields()
                self.log_to_console(f"Profile '{selected_profile}' deleted.")
        else:
            messagebox.showwarning("No Selection", "Please select a profile to delete.")

    def update_profile_list(self):
        self.profile_list.delete(0, tk.END)
        for profile_name in self.customer_profiles:
            self.profile_list.insert(tk.END, profile_name)

    def load_profile_settings(self, event):
        selection = self.profile_list.curselection()
        if selection:
            selected_profile = self.profile_list.get(selection)
            profile_data = self.customer_profiles.get(selected_profile, {})
            self.profile_name.delete(0, tk.END)
            self.profile_name.insert(0, selected_profile)
            self.power_entry.delete(0, tk.END)
            self.power_entry.insert(0, profile_data.get('power', ''))
            self.speed_entry.delete(0, tk.END)
            self.speed_entry.insert(0, profile_data.get('speed', ''))
            self.passes_entry.delete(0, tk.END)
            self.passes_entry.insert(0, profile_data.get('passes', ''))
            self.description_text.delete("1.0", tk.END)
            self.description_text.insert(tk.END, profile_data.get('description', ''))
        else:
            self.clear_profile_fields()

    def clear_profile_fields(self):
        self.profile_name.delete(0, tk.END)
        self.power_entry.delete(0, tk.END)
        self.speed_entry.delete(0, tk.END)
        self.passes_entry.delete(0, tk.END)
        self.description_text.delete("1.0", tk.END)

    # Design Methods
    def load_file(self):
        file_path = filedialog.askopenfilename(filetypes=[("SVG files", "*.svg"), ("Image files", "*.png;*.jpg;*.jpeg"), ("All files", "*.*")])
        if file_path:
            self.file_path = file_path
            self.file_label.config(text=file_path)
            self.load_preview_image(file_path)
            self.log_to_console(f"Loaded file: {file_path}")
        else:
            self.log_to_console("No file selected")

    def load_preview_image(self, file_path):
        # Load image and display on canvas
        try:
            image = Image.open(file_path)
            self.original_image = image.copy()
            self.update_preview()
        except Exception as e:
            messagebox.showerror("Error", f"Failed to load image: {e}")
            self.log_to_console(f"Error loading image: {e}")

    def update_preview(self, *args):
        # Update the preview image based on scale and rotation
        if hasattr(self, 'original_image'):
            try:
                image = self.original_image.copy()
                scale = self.scale_var.get()
                rotation = self.rotation_var.get()
                new_size = (int(image.width * scale), int(image.height * scale))
                image = image.resize(new_size, Image.ANTIALIAS)
                image = image.rotate(rotation, expand=True)
                image.thumbnail((400, 400), Image.ANTIALIAS)
                self.preview_image = ImageTk.PhotoImage(image)
                self.preview_canvas.delete("all")
                self.preview_canvas.create_image(200, 200, image=self.preview_image)
            except Exception as e:
                self.log_to_console(f"Error updating preview: {e}")

    def generate_gcode(self):
        # For demonstration purposes, we'll simulate G-code generation
        power = self.power_entry.get()
        speed = self.speed_entry.get()
        passes = int(self.passes_entry.get() or 1)

        if not (power and speed):
            messagebox.showwarning("Missing Parameters", "Please set power and speed in the profile.")
            return

        # Simulate G-code commands
        self.gcode_commands = [
            "G21 ; Set units to mm",
            f"G90 ; Absolute positioning",
            f"S{power} ; Set laser power",
            f"F{speed} ; Set feed rate",
            # Add movement commands here depending on the design
            "M5 ; Laser off"
        ] * passes  # Repeat for the number of passes

        self.log_to_console("G-code generated.")
        self.send_button.config(state="normal")

    def send_gcode(self):
        # Placeholder for sending G-code to the laser
        if hasattr(self, 'gcode_commands') and self.gcode_commands:
            self.start_job()
        else:
            messagebox.showwarning("No G-code", "Please generate G-code first.")

    # Console Logging
    def log_to_console(self, message):
        self.console_output.insert(tk.END, f"{message}\n")
        self.console_output.see(tk.END)

    def on_closing(self):
        if self.is_connected:
            self.disconnect_laser()
        self.master.destroy()

if __name__ == "__main__":
    root = ThemedTk(theme="equilux")
    app = LaserCutterGUI(root)
    root.protocol("WM_DELETE_WINDOW", app.on_closing)
    root.mainloop()