From d3b84c6212db99b0d16c6589b371250aca7cc4ad Mon Sep 17 00:00:00 2001 From: Brett Date: Sat, 4 Nov 2023 16:13:40 -0500 Subject: [PATCH] Remove legacy client (#58) * Remove legacy client * Misc cleanup --- dashboard.py | 5 +- main.py | 2 +- requirements.txt | 5 +- start_server.py => server.py | 0 src/client/__init__.py | 0 src/client/dashboard_window.py | 399 ----------------------------- src/client/new_job_window.py | 452 --------------------------------- start_client.py | 5 - 8 files changed, 5 insertions(+), 863 deletions(-) mode change 100755 => 100644 dashboard.py mode change 100644 => 100755 main.py rename start_server.py => server.py (100%) delete mode 100644 src/client/__init__.py delete mode 100644 src/client/dashboard_window.py delete mode 100755 src/client/new_job_window.py delete mode 100755 start_client.py diff --git a/dashboard.py b/dashboard.py old mode 100755 new mode 100644 index cfe39d8..96372f8 --- a/dashboard.py +++ b/dashboard.py @@ -6,7 +6,6 @@ import threading import time import traceback -import requests from rich import box from rich.console import Console from rich.layout import Layout @@ -17,8 +16,8 @@ from rich.table import Table from rich.text import Text from rich.tree import Tree -from src.workers.base_worker import RenderStatus, string_to_status -from src.server_proxy import RenderServerProxy +from src.engines.core.base_worker import RenderStatus, string_to_status +from src.api.server_proxy import RenderServerProxy from src.utilities.misc_helper import get_time_elapsed from start_server import start_server diff --git a/main.py b/main.py old mode 100644 new mode 100755 index c46883d..814cf87 --- a/main.py +++ b/main.py @@ -1,4 +1,4 @@ -''' main.py ''' +#!/usr/bin/env python3 from src import init if __name__ == '__main__': diff --git a/requirements.txt b/requirements.txt index 4b2c6b5..d496de6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,8 +3,7 @@ psutil==5.9.6 PyYAML==6.0.1 Flask==3.0.0 rich==13.6.0 -Werkzeug==3.0.0 -future==0.18.3 +Werkzeug~=3.0.1 json2html~=1.3.0 SQLAlchemy~=2.0.15 Pillow==10.1.0 @@ -12,5 +11,5 @@ zeroconf==0.119.0 Pypubsub~=4.0.3 tqdm==4.66.1 plyer==2.1.0 -PyQt6~=6.5.3 +PyQt6~=6.6.0 PySide6~=6.6.0 \ No newline at end of file diff --git a/start_server.py b/server.py similarity index 100% rename from start_server.py rename to server.py diff --git a/src/client/__init__.py b/src/client/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/client/dashboard_window.py b/src/client/dashboard_window.py deleted file mode 100644 index 429c0af..0000000 --- a/src/client/dashboard_window.py +++ /dev/null @@ -1,399 +0,0 @@ -import datetime -import logging -import os -import socket -import threading -import time -import tkinter as tk -from tkinter import ttk, messagebox, simpledialog - -from PIL import Image, ImageTk - -from src.client.new_job_window import NewJobWindow -# from src.client.server_details import create_server_popup -from src.api.server_proxy import RenderServerProxy -from src.utilities.misc_helper import launch_url, file_exists_in_mounts, get_time_elapsed -from src.utilities.zeroconf_server import ZeroconfServer -from src.engines.core.base_worker import RenderStatus - -logger = logging.getLogger() - - -def sort_column(tree, col, reverse=False): - data = [(tree.set(child, col), child) for child in tree.get_children('')] - data.sort(reverse=reverse) - for index, (_, child) in enumerate(data): - tree.move(child, '', index) - - -def make_sortable(tree): - for col in tree["columns"]: - tree.heading(col, text=col, command=lambda c=col: sort_column(tree, c)) - - -class DashboardWindow: - - lib_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - image_path = os.path.join(lib_path, 'web', 'static', 'images') - default_image = Image.open(os.path.join(image_path, 'desktop.png')) - - def __init__(self): - - # Create a Treeview widget - self.root = tk.Tk() - self.root.title("Zordon Dashboard") - self.current_hostname = None - self.server_proxies = {} - self.added_hostnames = [] - - # Setup zeroconf - ZeroconfServer.configure("_zordon._tcp.local.", socket.gethostname(), 8080) - ZeroconfServer.start(listen_only=True) - - # Setup photo preview - photo_pad = tk.Frame(self.root, background="gray") - photo_pad.pack(fill=tk.BOTH, pady=5, padx=5) - self.photo_label = tk.Label(photo_pad, height=500) - self.photo_label.pack(fill=tk.BOTH, expand=True) - self.set_image(self.default_image) - - server_frame = tk.LabelFrame(self.root, text="Server") - server_frame.pack(fill=tk.BOTH, pady=5, padx=5, expand=True) - - # Create server tree - left_frame = tk.Frame(server_frame) - left_frame.pack(side=tk.LEFT, expand=True, fill=tk.BOTH) - self.server_tree = ttk.Treeview(left_frame, show="headings") - self.server_tree.pack(expand=True, fill=tk.BOTH) - self.server_tree["columns"] = ("Server", "Status") - self.server_tree.bind("<>", self.server_picked) - self.server_tree.column("Server", width=200) - self.server_tree.column("Status", width=80) - - left_button_frame = tk.Frame(left_frame) - left_button_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=5, pady=5, expand=False) - - # Create buttons - self.remove_server_button = tk.Button(left_button_frame, text="-", command=self.remove_server_button) - self.remove_server_button.pack(side=tk.RIGHT) - self.remove_server_button.config(state='disabled') - add_server_button = tk.Button(left_button_frame, text="+", command=self.add_server_button) - add_server_button.pack(side=tk.RIGHT) - - # Create separator - separator = ttk.Separator(server_frame, orient=tk.VERTICAL) - separator.pack(side=tk.LEFT, padx=5, pady=5, fill=tk.Y) - - # Setup the Tree - self.job_tree = ttk.Treeview(server_frame, show="headings") - self.job_tree.tag_configure(RenderStatus.RUNNING.value, background='lawn green', font=('', 0, 'bold')) - self.job_tree.bind("<>", self.job_picked) - self.job_tree["columns"] = ("id", "Name", "Renderer", "Priority", "Status", "Time Elapsed", "Frames", - "Date Added", "Parent", "") - - # Format the columns - self.job_tree.column("id", width=0, stretch=False) - self.job_tree.column("Name", width=300) - self.job_tree.column("Renderer", width=100, stretch=False) - self.job_tree.column("Priority", width=50, stretch=False) - self.job_tree.column("Status", width=100, stretch=False) - self.job_tree.column("Time Elapsed", width=100, stretch=False) - self.job_tree.column("Frames", width=50, stretch=False) - self.job_tree.column("Date Added", width=150, stretch=True) - self.job_tree.column("Parent", width=250, stretch=True) - - # Create the column headings - for name in self.job_tree['columns']: - self.job_tree.heading(name, text=name) - - # Pack the Treeview widget - self.job_tree.pack(fill=tk.BOTH, expand=True) - - button_frame = tk.Frame(server_frame) - button_frame.pack(pady=5, fill=tk.X, expand=False) - - # Create buttons - self.logs_button = tk.Button(button_frame, text="Logs", command=self.open_logs) - self.show_files_button = tk.Button(button_frame, text="Show Files", command=self.show_files) - self.stop_button = tk.Button(button_frame, text="Stop", command=self.stop_job) - self.delete_button = tk.Button(button_frame, text="Delete", command=self.delete_job) - add_job_button = tk.Button(button_frame, text="Add Job", command=self.show_new_job_window) - - # Pack the buttons in the frame - self.stop_button.pack(side=tk.LEFT) - self.stop_button.config(state='disabled') - self.delete_button.pack(side=tk.LEFT) - self.delete_button.config(state='disabled') - self.show_files_button.pack(side=tk.LEFT) - self.show_files_button.config(state='disabled') - self.logs_button.pack(side=tk.LEFT) - self.logs_button.config(state='disabled') - add_job_button.pack(side=tk.RIGHT) - - # Start the Tkinter event loop - self.root.geometry("500x600+300+300") - self.root.maxsize(width=2000, height=1200) - self.root.minsize(width=900, height=800) - make_sortable(self.job_tree) - make_sortable(self.server_tree) - - # update servers - self.update_servers() - try: - selected_server = self.server_tree.get_children()[0] - self.server_tree.selection_set(selected_server) - self.server_picked() - except IndexError: - pass - - # update jobs - self.update_jobs() - try: - selected_job = self.job_tree.get_children()[0] - self.job_tree.selection_set(selected_job) - self.job_picked() - except IndexError: - pass - - # start background update - x = threading.Thread(target=self.__background_update) - x.daemon = True - x.start() - - @property - def current_server_proxy(self): - return self.server_proxies.get(self.current_hostname, None) - - def remove_server_button(self): - new_hostname = self.server_tree.selection()[0] - if new_hostname in self.added_hostnames: - self.added_hostnames.remove(new_hostname) - self.update_servers() - if self.server_tree.get_children(): - self.server_tree.selection_set(self.server_tree.get_children()[0]) - self.server_picked(event=None) - - def add_server_button(self): - hostname = simpledialog.askstring("Server Hostname", "Enter the server hostname to add:") - if hostname: - hostname = hostname.strip() - if hostname not in self.added_hostnames: - if RenderServerProxy(hostname=hostname).connect(): - self.added_hostnames.append(hostname) - self.update_servers() - else: - messagebox.showerror("Cannot Connect", f"Cannot connect to server at hostname: '{hostname}'") - - def server_picked(self, event=None): - try: - new_hostname = self.server_tree.selection()[0] - self.remove_server_button.config(state="normal" if new_hostname in self.added_hostnames else "disabled") - if self.current_hostname == new_hostname: - return - self.current_hostname = new_hostname - self.update_jobs(clear_table=True) - except IndexError: - pass - - def selected_job_ids(self): - selected_items = self.job_tree.selection() # Get the selected item - row_data = [self.job_tree.item(item) for item in selected_items] # Get the text of the selected item - job_ids = [row['values'][0] for row in row_data] - return job_ids - - def stop_job(self): - job_ids = self.selected_job_ids() - for job_id in job_ids: - self.current_server_proxy.cancel_job(job_id, confirm=True) - self.update_jobs(clear_table=True) - - def delete_job(self): - job_ids = self.selected_job_ids() - if len(job_ids) == 1: - job = next((d for d in self.current_server_proxy.get_all_jobs() if d.get('id') == job_ids[0]), None) - display_name = job['name'] or os.path.basename(job['input_path']) - message = f"Are you sure you want to delete the job:\n{display_name}?" - else: - message = f"Are you sure you want to delete these {len(job_ids)} jobs?" - - result = messagebox.askyesno("Confirmation", message) - if result: - for job_id in job_ids: - self.current_server_proxy.request_data(f'job/{job_id}/delete?confirm=true') - self.update_jobs(clear_table=True) - - def set_image(self, image): - thumb_image = ImageTk.PhotoImage(image) - if thumb_image: - self.photo_label.configure(image=thumb_image) - self.photo_label.image = thumb_image - - def job_picked(self, event=None): - job_id = self.selected_job_ids()[0] if self.selected_job_ids() else None - if job_id: - # update thumb - def fetch_preview(): - try: - before_fetch_hostname = self.current_server_proxy.hostname - response = self.current_server_proxy.request(f'job/{job_id}/thumbnail?size=big') - if response.ok: - import io - image_data = response.content - image = Image.open(io.BytesIO(image_data)) - if self.current_server_proxy.hostname == before_fetch_hostname and job_id == self.selected_job_ids()[0]: - self.set_image(image) - except ConnectionError as e: - logger.error(f"Connection error fetching image: {e}") - except Exception as e: - logger.error(f"Error fetching image: {e}") - - fetch_thread = threading.Thread(target=fetch_preview) - fetch_thread.daemon = True - fetch_thread.start() - else: - self.set_image(self.default_image) - - # update button status - current_jobs = self.current_server_proxy.get_all_jobs() or [] - job = next((d for d in current_jobs if d.get('id') == job_id), None) - stop_button_state = 'normal' if job and job['status'] == RenderStatus.RUNNING.value else 'disabled' - self.stop_button.config(state=stop_button_state) - - generic_button_state = 'normal' if job else 'disabled' - self.show_files_button.config(state=generic_button_state) - self.delete_button.config(state=generic_button_state) - self.logs_button.config(state=generic_button_state) - - def show_files(self): - if not self.selected_job_ids(): - return - - job = next((d for d in self.current_server_proxy.get_all_jobs() if d.get('id') == self.selected_job_ids()[0]), None) - output_path = os.path.dirname(job['output_path']) # check local filesystem - if not os.path.exists(output_path): - output_path = file_exists_in_mounts(output_path) # check any attached network shares - if not output_path: - return messagebox.showerror("File Not Found", "The file could not be found. Check your network mounts.") - launch_url(output_path) - - def open_logs(self): - if self.selected_job_ids(): - url = f'http://{self.current_server_proxy.hostname}:{self.current_server_proxy.port}/api/job/{self.selected_job_ids()[0]}/logs' - launch_url(url) - - def mainloop(self): - self.root.mainloop() - - def __background_update(self): - while True: - self.update_servers() - self.update_jobs() - time.sleep(1) - - def update_servers(self): - - def update_row(tree, id, new_values, tags=None): - for item in tree.get_children(): - values = tree.item(item, "values") - if values[0] == id: - if tags: - tree.item(item, values=new_values, tags=tags) - else: - tree.item(item, values=new_values) - break - - current_servers = list(set(ZeroconfServer.found_clients() + self.added_hostnames)) - for hostname in current_servers: - if not self.server_proxies.get(hostname, None): - new_proxy = RenderServerProxy(hostname=hostname) - new_proxy.start_background_update() - self.server_proxies[hostname] = new_proxy - - try: - for hostname, proxy in self.server_proxies.items(): - if hostname not in self.server_tree.get_children(): - self.server_tree.insert("", tk.END, iid=hostname, values=(hostname, proxy.status(), )) - else: - update_row(self.server_tree, hostname, new_values=(hostname, proxy.status())) - except RuntimeError: - pass - - # remove any servers that don't belong - for row in self.server_tree.get_children(): - if row not in current_servers: - self.server_tree.delete(row) - proxy = self.server_proxies.get(row, None) - if proxy: - proxy.stop_background_update() - self.server_proxies.pop(row) - - def update_jobs(self, clear_table=False): - - if not self.current_server_proxy: - return - - def update_row(tree, id, new_values, tags=None): - for item in tree.get_children(): - values = tree.item(item, "values") - if values[0] == id: - tree.item(item, values=new_values, tags=tags) - break - - if clear_table: - self.job_tree.delete(*self.job_tree.get_children()) - - job_fetch = self.current_server_proxy.get_all_jobs(ignore_token=clear_table) - if job_fetch: - for job in job_fetch: - display_status = job['status'] if job['status'] != RenderStatus.RUNNING.value else \ - ('%.0f%%' % (job['percent_complete'] * 100)) # if running, show percent, otherwise just show status - tags = (job['status'],) - start_time = datetime.datetime.fromisoformat(job['start_time']) if job['start_time'] else None - end_time = datetime.datetime.fromisoformat(job['end_time']) if job['end_time'] else None - - time_elapsed = "" if (job['status'] != RenderStatus.RUNNING.value and not end_time) else \ - get_time_elapsed(start_time, end_time) - - values = (job['id'], - job['name'] or os.path.basename(job['input_path']), - job['renderer'] + "-" + job['renderer_version'], - job['priority'], - display_status, - time_elapsed, - job['total_frames'], - job['date_created'], - job['parent']) - try: - if self.job_tree.exists(job['id']): - update_row(self.job_tree, job['id'], new_values=values, tags=tags) - else: - self.job_tree.insert("", tk.END, iid=job['id'], values=values, tags=tags) - except tk.TclError: - pass - - # remove any jobs that don't belong - all_job_ids = [job['id'] for job in job_fetch] - for row in self.job_tree.get_children(): - if row not in all_job_ids: - self.job_tree.delete(row) - - def show_new_job_window(self): - new_window = tk.Toplevel(self.root) - new_window.title("New Window") - new_window.geometry("500x600+300+300") - new_window.resizable(False, height=True) - x = NewJobWindow(parent=new_window, clients=list(self.server_tree.get_children())) - x.pack() - - -def start_client(): - - logging.basicConfig(format='%(asctime)s: %(levelname)s: %(module)s: %(message)s', datefmt='%d-%b-%y %H:%M:%S', - level='INFO'.upper()) - - x = DashboardWindow() - x.mainloop() - - -if __name__ == '__main__': - start_client() diff --git a/src/client/new_job_window.py b/src/client/new_job_window.py deleted file mode 100755 index a1aabb3..0000000 --- a/src/client/new_job_window.py +++ /dev/null @@ -1,452 +0,0 @@ -#!/usr/bin/env python3 -import copy -import logging -import os.path -import pathlib -import socket -import threading -from tkinter import * -from tkinter import filedialog, messagebox -from tkinter.ttk import Frame, Label, Entry, Combobox, Progressbar - -import psutil - -from src.api.server_proxy import RenderServerProxy -from src.engines.blender.blender_worker import Blender -from src.engines.ffmpeg.ffmpeg_worker import FFMPEG - -logger = logging.getLogger() - -label_width = 9 -header_padding = 6 - - -# CheckListBox source - https://stackoverflow.com/questions/50398649/python-tkinter-tk-support-checklist-box -class ChecklistBox(Frame): - def __init__(self, parent, choices, **kwargs): - super().__init__(parent, **kwargs) - - self.vars = [] - for choice in choices: - var = StringVar(value="") - self.vars.append(var) - cb = Checkbutton(self, text=choice, onvalue=choice, offvalue="", anchor="w", width=20, - relief="flat", highlightthickness=0, variable=var) - cb.pack(side="top", fill="x", anchor="w") - - def getCheckedItems(self): - values = [] - for var in self.vars: - value = var.get() - if value: - values.append(value) - return values - - def resetCheckedItems(self): - values = [] - for var in self.vars: - var.set(value='') - return values - - -class NewJobWindow(Frame): - - def __init__(self, parent=None, clients=None): - super().__init__(parent) - - self.root = parent - self.clients = clients or [] - self.server_proxy = RenderServerProxy(hostname=clients[0] if clients else None) - self.chosen_file = None - self.project_info = {} - self.presets = {} - self.renderer_info = {} - self.priority = IntVar(value=2) - - self.master.title("New Job") - self.pack(fill=BOTH, expand=True) - - # project frame - job_frame = LabelFrame(self, text="Job Settings") - job_frame.pack(fill=X, padx=5, pady=5) - - # project frame - project_frame = Frame(job_frame) - project_frame.pack(fill=X) - - project_label = Label(project_frame, text="Project", width=label_width) - project_label.pack(side=LEFT, padx=5, pady=5) - - self.project_button = Button(project_frame, text="no file selected", width=6, command=self.choose_file_button) - self.project_button.pack(fill=X, padx=5, expand=True) - - # client frame - client_frame = Frame(job_frame) - client_frame.pack(fill=X) - - Label(client_frame, text="Client", width=label_width).pack(side=LEFT, padx=5, pady=5) - - self.client_combo = Combobox(client_frame, state="readonly") - self.client_combo.pack(fill=X, padx=5, expand=True) - self.client_combo.bind('<>', self.client_picked) - self.client_combo['values'] = self.clients - if self.clients: - self.client_combo.current(0) - - # renderer frame - renderer_frame = Frame(job_frame) - renderer_frame.pack(fill=X) - - Label(renderer_frame, text="Renderer", width=label_width).pack(side=LEFT, padx=5, pady=5) - self.renderer_combo = Combobox(renderer_frame, state="readonly") - self.renderer_combo.pack(fill=X, padx=5, expand=True) - self.renderer_combo.bind('<>', self.refresh_renderer_settings) - - # priority frame - priority_frame = Frame(job_frame) - priority_frame.pack(fill=X) - - Label(priority_frame, text="Priority", width=label_width).pack(side=LEFT, padx=5, pady=5) - Radiobutton(priority_frame, text="1", value=1, variable=self.priority).pack(anchor=W, side=LEFT, padx=5) - Radiobutton(priority_frame, text="2", value=2, variable=self.priority).pack(anchor=W, side=LEFT, padx=5) - Radiobutton(priority_frame, text="3", value=3, variable=self.priority).pack(anchor=W, side=LEFT, padx=5) - - # presets - presets_frame = Frame(job_frame) - presets_frame.pack(fill=X) - - Label(presets_frame, text="Presets", width=label_width).pack(side=LEFT, padx=5, pady=5) - self.presets_combo = Combobox(presets_frame, state="readonly") - self.presets_combo.pack(side=LEFT, padx=5, pady=5, expand=True, fill=X) - self.presets_combo.bind('<>', self.chose_preset) - - # output frame - output_frame = Frame(job_frame) - output_frame.pack(fill=X) - - Label(output_frame, text="Output", width=label_width).pack(side=LEFT, padx=5, pady=5) - - self.output_entry = Entry(output_frame) - self.output_entry.pack(side=LEFT, padx=5, expand=True, fill=X) - - self.output_format = Combobox(output_frame, state="readonly", values=['JPG', 'MOV', 'PNG'], width=9) - self.output_format.pack(side=LEFT, padx=5, pady=5) - self.output_format['state'] = DISABLED - - # frame_range frame - frame_range_frame = Frame(job_frame) - frame_range_frame.pack(fill=X) - - Label(frame_range_frame, text="Frames", width=label_width).pack(side=LEFT, padx=5, pady=5, expand=False) - - self.start_frame_spinbox = Spinbox(frame_range_frame, from_=0, to=50000, width=5) - self.start_frame_spinbox.pack(side=LEFT, expand=False, padx=5, pady=5) - - Label(frame_range_frame, text="to").pack(side=LEFT, pady=5, expand=False) - self.end_frame_spinbox = Spinbox(frame_range_frame, from_=0, to=50000, width=5) - self.end_frame_spinbox.pack(side=LEFT, expand=False, padx=5, pady=5) - - # Blender - self.blender_frame = None - self.blender_cameras_frame = None - self.blender_engine = StringVar(value='CYCLES') - self.blender_pack_textures = BooleanVar(value=False) - self.blender_multiple_cameras = BooleanVar(value=False) - self.blender_cameras_list = None - - # Custom Args / Submit Button - self.custom_args_frame = None - self.custom_args_entry = None - self.submit_frame = None - - self.progress_frame = None - self.progress_label = None - self.progress_bar = None - self.upload_status = None - - self.fetch_server_data() - - def client_picked(self, event=None): - self.server_proxy.hostname = self.client_combo.get() - self.fetch_server_data() - - def fetch_server_data(self): - self.renderer_info = self.server_proxy.request_data('renderer_info', timeout=3) or {} - self.presets = self.server_proxy.request_data('presets', timeout=3) or {} - - # update available renders - self.renderer_combo['values'] = list(self.renderer_info.keys()) - if self.renderer_info.keys(): - self.renderer_combo.current(0) - - self.refresh_renderer_settings() - - def choose_file_button(self): - self.chosen_file = filedialog.askopenfilename() - button_text = os.path.basename(self.chosen_file) if self.chosen_file else "no file selected" - self.project_button.configure(text=button_text) - - # Update the output label - self.output_entry.delete(0, END) - if self.chosen_file: - # Generate a default output name - output_name = os.path.splitext(os.path.basename(self.chosen_file))[-1].strip('.') - self.output_entry.insert(0, os.path.basename(output_name)) - - # Try to determine file type - extension = os.path.splitext(self.chosen_file)[-1].strip('.') # not the best way to do this - for renderer, renderer_info in self.renderer_info.items(): - supported = [x.lower().strip('.') for x in renderer_info.get('supported_extensions', [])] - if extension.lower().strip('.') in supported: - if renderer in self.renderer_combo['values']: - self.renderer_combo.set(renderer) - - self.refresh_renderer_settings() - - def chose_preset(self, event=None): - preset_name = self.presets_combo.get() - renderer = self.renderer_combo.get() - - presets_to_show = {key: value for key, value in self.presets.items() if value.get("renderer") == renderer} - matching_dict = next((value for value in presets_to_show.values() if value.get("name") == preset_name), None) - if matching_dict: - self.custom_args_entry.delete(0, END) - self.custom_args_entry.insert(0, matching_dict['args']) - - def refresh_renderer_settings(self, event=None): - renderer = self.renderer_combo.get() - - # clear old settings - if self.blender_frame: - self.blender_frame.pack_forget() - - if not self.chosen_file: - return - - if renderer == 'blender': - self.project_info = Blender().get_scene_info(self.chosen_file) - self.draw_blender_settings() - elif renderer == 'ffmpeg': - f = FFMPEG.get_frame_count(self.chosen_file) - self.project_info['frame_end'] = f - - # set frame start / end numbers fetched from fils - if self.project_info.get('frame_start'): - self.start_frame_spinbox.delete(0, 'end') - self.start_frame_spinbox.insert(0, self.project_info['frame_start']) - if self.project_info.get('frame_end'): - self.end_frame_spinbox.delete(0, 'end') - self.end_frame_spinbox.insert(0, self.project_info['frame_end']) - - # redraw lower ui - self.draw_custom_args() - self.draw_submit_button() - - # check supported export formats - if self.renderer_info.get(renderer, {}).get('supported_export_formats', None): - formats = self.renderer_info[renderer]['supported_export_formats'] - if formats and isinstance(formats[0], dict): - formats = [x.get('name', str(x)) for x in formats] - formats.sort() - self.output_format['values'] = formats - self.output_format['state'] = NORMAL - self.output_format.current(0) - else: - self.output_format['values'] = [] - self.output_format['state'] = DISABLED - - # update presets - presets_to_show = {key: value for key, value in self.presets.items() if value.get("renderer") == renderer} - self.presets_combo['values'] = [value['name'] for value in presets_to_show.values()] - - def draw_custom_args(self): - if hasattr(self, 'custom_args_frame') and self.custom_args_frame: - self.custom_args_frame.forget() - self.custom_args_frame = LabelFrame(self, text="Advanced") - self.custom_args_frame.pack(side=TOP, fill=X, expand=False, padx=5, pady=5) - Label(self.custom_args_frame, text="Custom Args", width=label_width).pack(side=LEFT, padx=5, pady=5) - self.custom_args_entry = Entry(self.custom_args_frame) - self.custom_args_entry.pack(side=TOP, padx=5, expand=True, fill=X) - - def draw_submit_button(self): - if hasattr(self, 'submit_frame') and self.submit_frame: - self.submit_frame.forget() - self.submit_frame = Frame(self) - self.submit_frame.pack(fill=BOTH, expand=True) - # Label(self.submit_frame, text="").pack(fill=BOTH, expand=True) - submit_button = Button(self.submit_frame, text="Submit", command=self.submit_job) - submit_button.pack(fill=Y, anchor="s", pady=header_padding) - - def draw_progress_frame(self): - if hasattr(self, 'progress_frame') and self.progress_frame: - self.progress_frame.forget() - self.progress_frame = LabelFrame(self, text="Job Submission") - self.progress_frame.pack(side=TOP, fill=X, expand=False, padx=5, pady=5) - self.progress_bar = Progressbar(self.progress_frame, length=300, mode="determinate") - self.progress_bar.pack() - self.progress_label = Label(self.progress_frame, text="Starting Up") - self.progress_label.pack(pady=5, padx=5) - - def draw_blender_settings(self): - - # blender settings - self.blender_frame = LabelFrame(self, text="Blender Settings") - self.blender_frame.pack(fill=X, padx=5) - - blender_engine_frame = Frame(self.blender_frame) - blender_engine_frame.pack(fill=X) - - Label(blender_engine_frame, text="Engine", width=label_width).pack(side=LEFT, padx=5, pady=5) - - Radiobutton(blender_engine_frame, text="Cycles", value="CYCLES", variable=self.blender_engine).pack( - anchor=W, side=LEFT, padx=5) - Radiobutton(blender_engine_frame, text="Eevee", value="BLENDER_EEVEE", variable=self.blender_engine).pack( - anchor=W, side=LEFT, padx=5) - - # options - pack_frame = Frame(self.blender_frame) - pack_frame.pack(fill=X) - - Label(pack_frame, text="Options", width=label_width).pack(side=LEFT, padx=5, pady=5) - - Checkbutton(pack_frame, text="Pack Textures", variable=self.blender_pack_textures, onvalue=True, offvalue=False - ).pack(anchor=W, side=LEFT, padx=5) - - # multi cams - def draw_scene_cams(event=None): - if self.project_info: - show_cams_checkbutton['state'] = NORMAL - if self.blender_multiple_cameras.get(): - self.blender_cameras_frame = Frame(self.blender_frame) - self.blender_cameras_frame.pack(fill=X) - - Label(self.blender_cameras_frame, text="Cameras", width=label_width).pack(side=LEFT, padx=5, pady=5) - - choices = [f"{x['name']} - {int(float(x['lens']))}mm" for x in self.project_info['cameras']] - choices.sort() - self.blender_cameras_list = ChecklistBox(self.blender_cameras_frame, choices, relief="sunken") - self.blender_cameras_list.pack(padx=5, fill=X) - elif self.blender_cameras_frame: - self.blender_cameras_frame.pack_forget() - else: - show_cams_checkbutton['state'] = DISABLED - if self.blender_cameras_frame: - self.blender_cameras_frame.pack_forget() - - # multiple cameras checkbox - camera_count = len(self.project_info.get('cameras', [])) if self.project_info else 0 - show_cams_checkbutton = Checkbutton(pack_frame, text=f'Multiple Cameras ({camera_count})', offvalue=False, - onvalue=True, - variable=self.blender_multiple_cameras, command=draw_scene_cams) - show_cams_checkbutton.pack(side=LEFT, padx=5) - show_cams_checkbutton['state'] = NORMAL if camera_count > 1 else DISABLED - - def submit_job(self): - - def submit_job_worker(): - - self.draw_progress_frame() - self.progress_bar['value'] = 0 - self.progress_bar.configure(mode='determinate') - self.progress_bar.start() - self.progress_label.configure(text="Preparing files...") - - # start the progress UI - client = self.client_combo.get() - - renderer = self.renderer_combo.get() - job_json = {'owner': psutil.Process().username() + '@' + socket.gethostname(), - 'renderer': renderer, - 'client': client, - 'output_path': os.path.join(os.path.dirname(self.chosen_file), self.output_entry.get()), - 'args': {'raw': self.custom_args_entry.get()}, - 'start_frame': self.start_frame_spinbox.get(), - 'end_frame': self.end_frame_spinbox.get(), - 'name': None} - job_list = [] - - input_path = self.chosen_file - - temp_files = [] - if renderer == 'blender': - if self.blender_pack_textures.get(): - self.progress_label.configure(text="Packing Blender file...") - new_path = Blender().pack_project_file(project_path=input_path, timeout=300) - if new_path: - logger.info(f"New Path is now {new_path}") - input_path = new_path - temp_files.append(new_path) - else: - err_msg = f'Failed to pack Blender file: {input_path}' - messagebox.showinfo("Error", err_msg) - return - # add all Blender args - job_json['args']['engine'] = self.blender_engine.get() - job_json['args']['export_format'] = self.output_format.get() - - # multiple camera rendering - if self.blender_cameras_list and self.blender_multiple_cameras.get(): - selected_cameras = self.blender_cameras_list.getCheckedItems() - for cam in selected_cameras: - job_copy = copy.deepcopy(job_json) - job_copy['args']['camera'] = cam.rsplit('-', 1)[0].strip() - job_copy['name'] = pathlib.Path(input_path).stem.replace(' ', '_') + "-" + cam.replace(' ', '') - job_list.append(job_copy) - - # Submit to server - job_list = job_list or [job_json] - self.progress_label.configure(text="Posting to server...") - self.progress_bar.stop() - self.progress_bar.configure(mode='determinate') - self.progress_bar.start() - - def create_callback(encoder): - encoder_len = encoder.len - - def callback(monitor): - percent = f"{monitor.bytes_read / encoder_len * 100:.0f}" - self.progress_label.configure(text=f"Transferring to {client} - {percent}%") - self.progress_bar['value'] = int(percent) - return callback - - result = self.server_proxy.post_job_to_server(file_path=input_path, job_list=job_list, - callback=create_callback) - - self.progress_bar.stop() - # clean up - for temp in temp_files: - os.remove(temp) - - def finish_on_main(): - if result.ok: - message = "Job successfully submitted to server." - self.progress_label.configure(text=message) - messagebox.showinfo("Success", message) - logger.info(message) - else: - message = result.text or "Unknown error" - self.progress_label.configure(text=message) - logger.warning(message) - messagebox.showinfo("Error", message) - self.progress_label.configure(text="") - self.progress_frame.forget() - - self.root.after(0, finish_on_main) - - # Start the job submit task as a bg thread - bg_thread = threading.Thread(target=submit_job_worker) - bg_thread.start() - - -def main(): - - root = Tk() - root.geometry("500x600+300+300") - root.maxsize(width=1000, height=2000) - root.minsize(width=600, height=600) - app = NewJobWindow(root) - root.mainloop() - - -if __name__ == '__main__': - main() diff --git a/start_client.py b/start_client.py deleted file mode 100755 index 0ad54ca..0000000 --- a/start_client.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python3 -from src.client.dashboard_window import start_client - -if __name__ == '__main__': - start_client()