mirror of
https://github.com/blw1138/Zordon.git
synced 2026-06-09 13:39:24 -05:00
Add Job Window Redesign (#128)
* Initial refactor of add_job_window * Improved project naming and fixed Blender engine issue * Improve time representation in main window * Cleanup Blender job creation * Send resolution / fps data in job submission * More window improvements * EngineManager renaming and refactoring * FFMPEG path fixes for ffprobe * More backend refactoring / improvements * Performance improvements / API refactoring * Show current job count in add window UI before submission * Move some UI update code out of background thread * Move some main window UI update code out of background thread
This commit is contained in:
+83
-72
@@ -2,7 +2,6 @@
|
||||
import ast
|
||||
import datetime
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
@@ -10,18 +9,17 @@ import threading
|
||||
import time
|
||||
|
||||
import PIL
|
||||
import humanize
|
||||
from PIL import Image
|
||||
from PyQt6.QtCore import Qt, QByteArray, QBuffer, QIODevice, QThread
|
||||
from PyQt6.QtCore import Qt, QByteArray, QBuffer, QIODevice, QThread, pyqtSignal
|
||||
from PyQt6.QtGui import QPixmap, QImage, QFont, QIcon
|
||||
from PyQt6.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QListWidget, QTableWidget, QAbstractItemView, \
|
||||
QTableWidgetItem, QLabel, QVBoxLayout, QHeaderView, QMessageBox, QGroupBox, QPushButton, QListWidgetItem, \
|
||||
QFileDialog
|
||||
|
||||
from src.api.api_server import API_VERSION
|
||||
from src.api.serverproxy_manager import ServerProxyManager
|
||||
from src.render_queue import RenderQueue
|
||||
from src.utilities.misc_helper import get_time_elapsed, resources_dir, is_localhost
|
||||
from src.utilities.status_utils import RenderStatus
|
||||
from src.utilities.zeroconf_server import ZeroconfServer
|
||||
from src.ui.add_job_window import NewRenderJobForm
|
||||
from src.ui.console_window import ConsoleWindow
|
||||
from src.ui.engine_browser import EngineBrowserWindow
|
||||
@@ -30,8 +28,10 @@ from src.ui.widgets.menubar import MenuBar
|
||||
from src.ui.widgets.proportional_image_label import ProportionalImageLabel
|
||||
from src.ui.widgets.statusbar import StatusBar
|
||||
from src.ui.widgets.toolbar import ToolBar
|
||||
from src.api.serverproxy_manager import ServerProxyManager
|
||||
from src.utilities.misc_helper import launch_url, iso_datestring_to_formatted_datestring
|
||||
from src.utilities.misc_helper import get_time_elapsed, resources_dir, is_localhost
|
||||
from src.utilities.misc_helper import launch_url
|
||||
from src.utilities.status_utils import RenderStatus
|
||||
from src.utilities.zeroconf_server import ZeroconfServer
|
||||
from src.version import APP_NAME
|
||||
|
||||
logger = logging.getLogger()
|
||||
@@ -99,8 +99,10 @@ class MainWindow(QMainWindow):
|
||||
self.create_toolbars()
|
||||
|
||||
# start background update
|
||||
self.bg_update_thread = QThread()
|
||||
self.bg_update_thread.run = self.__background_update
|
||||
self.found_servers = []
|
||||
self.job_data = {}
|
||||
self.bg_update_thread = BackgroundUpdater(window=self)
|
||||
self.bg_update_thread.updated_signal.connect(self.update_ui_data)
|
||||
self.bg_update_thread.start()
|
||||
|
||||
# Setup other windows
|
||||
@@ -161,44 +163,35 @@ class MainWindow(QMainWindow):
|
||||
self.job_list_view.verticalHeader().setVisible(False)
|
||||
self.job_list_view.itemSelectionChanged.connect(self.job_picked)
|
||||
self.job_list_view.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
||||
self.refresh_job_headers()
|
||||
|
||||
# Image Layout
|
||||
image_group = QGroupBox("Job Preview")
|
||||
image_layout = QVBoxLayout(image_group)
|
||||
image_layout.setContentsMargins(0, 0, 0, 0)
|
||||
image_center_layout = QHBoxLayout()
|
||||
image_center_layout.addWidget(self.image_label)
|
||||
image_layout.addWidget(self.image_label)
|
||||
# image_layout.addLayout(image_center_layout)
|
||||
# Setup Job Headers
|
||||
self.job_list_view.setHorizontalHeaderLabels(["ID", "Name", "Engine", "Priority", "Status",
|
||||
"Time Elapsed", "Frames", "Date Created"])
|
||||
self.job_list_view.setColumnHidden(0, True)
|
||||
|
||||
# Job Layout
|
||||
job_list_group = QGroupBox("Render Jobs")
|
||||
self.job_list_view.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
|
||||
self.job_list_view.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.job_list_view.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.job_list_view.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.job_list_view.horizontalHeader().setSectionResizeMode(5, QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.job_list_view.horizontalHeader().setSectionResizeMode(6, QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.job_list_view.horizontalHeader().setSectionResizeMode(7, QHeaderView.ResizeMode.ResizeToContents)
|
||||
|
||||
# Job List Layout
|
||||
job_list_group = QGroupBox("Job Preview")
|
||||
job_list_layout = QVBoxLayout(job_list_group)
|
||||
job_list_layout.setContentsMargins(0, 0, 0, 0)
|
||||
image_layout.addWidget(self.job_list_view, stretch=True)
|
||||
image_layout.addLayout(job_list_layout)
|
||||
job_list_layout.addWidget(self.image_label)
|
||||
job_list_layout.addWidget(self.job_list_view, stretch=True)
|
||||
|
||||
# Add them all to the window
|
||||
main_layout.addLayout(info_layout)
|
||||
|
||||
right_layout = QVBoxLayout()
|
||||
right_layout.setContentsMargins(0, 0, 0, 0)
|
||||
right_layout.addWidget(image_group)
|
||||
# right_layout.addWidget(job_list_group)
|
||||
right_layout.addWidget(job_list_group)
|
||||
main_layout.addLayout(right_layout)
|
||||
|
||||
def __background_update(self):
|
||||
while True:
|
||||
try:
|
||||
self.update_servers()
|
||||
self.fetch_jobs()
|
||||
except RuntimeError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error(f"Uncaught exception in background update: {e}")
|
||||
time.sleep(0.5)
|
||||
|
||||
def closeEvent(self, event):
|
||||
running_jobs = len(RenderQueue.running_jobs())
|
||||
if running_jobs:
|
||||
@@ -214,6 +207,10 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# -- Server Code -- #
|
||||
|
||||
def refresh_job_list(self):
|
||||
self.job_list_view.clearContents()
|
||||
self.bg_update_thread.needs_update = True
|
||||
|
||||
@property
|
||||
def current_server_proxy(self):
|
||||
return ServerProxyManager.get_proxy_for_hostname(self.current_hostname)
|
||||
@@ -229,7 +226,7 @@ class MainWindow(QMainWindow):
|
||||
# Update the current hostname and clear the job list
|
||||
self.current_hostname = new_hostname
|
||||
self.job_list_view.setRowCount(0)
|
||||
self.fetch_jobs(clear_table=True)
|
||||
self.refresh_job_list()
|
||||
|
||||
# Select the first row if there are jobs listed
|
||||
if self.job_list_view.rowCount():
|
||||
@@ -281,21 +278,19 @@ class MainWindow(QMainWindow):
|
||||
self.server_info_ram.setText(memory_info)
|
||||
self.server_info_gpu.setText(gpu_info)
|
||||
|
||||
def fetch_jobs(self, clear_table=False):
|
||||
def update_ui_data(self):
|
||||
|
||||
self.update_servers()
|
||||
|
||||
if not self.current_server_proxy:
|
||||
return
|
||||
|
||||
if clear_table:
|
||||
self.job_list_view.clear()
|
||||
self.refresh_job_headers()
|
||||
|
||||
job_fetch = self.current_server_proxy.get_all_jobs(ignore_token=False)
|
||||
if job_fetch:
|
||||
num_jobs = len(job_fetch)
|
||||
server_job_data = self.job_data.get(self.current_server_proxy.hostname)
|
||||
if server_job_data:
|
||||
num_jobs = len(server_job_data)
|
||||
self.job_list_view.setRowCount(num_jobs)
|
||||
|
||||
for row, job in enumerate(job_fetch):
|
||||
for row, job in enumerate(server_job_data):
|
||||
|
||||
display_status = job['status'] if job['status'] != RenderStatus.RUNNING.value else \
|
||||
('%.0f%%' % (job['percent_complete'] * 100)) # if running, show percent, otherwise just show status
|
||||
@@ -307,14 +302,15 @@ class MainWindow(QMainWindow):
|
||||
get_time_elapsed(start_time, end_time)
|
||||
|
||||
name = job.get('name') or os.path.basename(job.get('input_path', ''))
|
||||
engine_name = f"{job.get('renderer', '')}-{job.get('renderer_version')}"
|
||||
engine_name = f"{job.get('engine', '')}-{job.get('engine_version')}"
|
||||
priority = str(job.get('priority', ''))
|
||||
total_frames = str(job.get('total_frames', ''))
|
||||
date_created_string = iso_datestring_to_formatted_datestring(job['date_created'])
|
||||
converted_time = datetime.datetime.fromisoformat(job['date_created'])
|
||||
humanized_time = humanize.naturaltime(converted_time)
|
||||
|
||||
items = [QTableWidgetItem(job['id']), QTableWidgetItem(name), QTableWidgetItem(engine_name),
|
||||
QTableWidgetItem(priority), QTableWidgetItem(display_status), QTableWidgetItem(time_elapsed),
|
||||
QTableWidgetItem(total_frames), QTableWidgetItem(date_created_string)]
|
||||
QTableWidgetItem(total_frames), QTableWidgetItem(humanized_time)]
|
||||
|
||||
for col, item in enumerate(items):
|
||||
self.job_list_view.setItem(row, col, item)
|
||||
@@ -395,18 +391,6 @@ class MainWindow(QMainWindow):
|
||||
except AttributeError:
|
||||
return []
|
||||
|
||||
def refresh_job_headers(self):
|
||||
self.job_list_view.setHorizontalHeaderLabels(["ID", "Name", "Engine", "Priority", "Status",
|
||||
"Time Elapsed", "Frames", "Date Created"])
|
||||
self.job_list_view.setColumnHidden(0, True)
|
||||
|
||||
self.job_list_view.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
|
||||
self.job_list_view.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.job_list_view.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.job_list_view.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.job_list_view.horizontalHeader().setSectionResizeMode(5, QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.job_list_view.horizontalHeader().setSectionResizeMode(6, QHeaderView.ResizeMode.ResizeToContents)
|
||||
self.job_list_view.horizontalHeader().setSectionResizeMode(7, QHeaderView.ResizeMode.ResizeToContents)
|
||||
|
||||
# -- Image Code -- #
|
||||
|
||||
@@ -444,28 +428,25 @@ class MainWindow(QMainWindow):
|
||||
logger.error(f"Error loading image data: {e}")
|
||||
|
||||
def update_servers(self):
|
||||
found_servers = list(set(ZeroconfServer.found_hostnames() + self.added_hostnames))
|
||||
found_servers = [x for x in found_servers if ZeroconfServer.get_hostname_properties(x)['api_version'] == API_VERSION]
|
||||
|
||||
# Always make sure local hostname is first
|
||||
if found_servers and not is_localhost(found_servers[0]):
|
||||
for hostname in found_servers:
|
||||
if self.found_servers and not is_localhost(self.found_servers[0]):
|
||||
for hostname in self.found_servers:
|
||||
if is_localhost(hostname):
|
||||
found_servers.remove(hostname)
|
||||
found_servers.insert(0, hostname)
|
||||
self.found_servers.remove(hostname)
|
||||
self.found_servers.insert(0, hostname)
|
||||
break
|
||||
|
||||
old_count = self.server_list_view.count()
|
||||
|
||||
# Update proxys
|
||||
for hostname in found_servers:
|
||||
for hostname in self.found_servers:
|
||||
ServerProxyManager.get_proxy_for_hostname(hostname) # setup background updates
|
||||
|
||||
# Add in all the missing servers
|
||||
current_server_list = []
|
||||
for i in range(self.server_list_view.count()):
|
||||
current_server_list.append(self.server_list_view.item(i).text())
|
||||
for hostname in found_servers:
|
||||
for hostname in self.found_servers:
|
||||
if hostname not in current_server_list:
|
||||
properties = ZeroconfServer.get_hostname_properties(hostname)
|
||||
image_path = os.path.join(resources_dir(), f"{properties.get('system_os', 'Monitor')}.png")
|
||||
@@ -476,7 +457,7 @@ class MainWindow(QMainWindow):
|
||||
servers_to_remove = []
|
||||
for i in range(self.server_list_view.count()):
|
||||
name = self.server_list_view.item(i).text()
|
||||
if name not in found_servers:
|
||||
if name not in self.found_servers:
|
||||
servers_to_remove.append(name)
|
||||
|
||||
# remove any servers that shouldn't be shown any longer
|
||||
@@ -572,7 +553,7 @@ class MainWindow(QMainWindow):
|
||||
if msg_box.exec() == QMessageBox.StandardButton.Yes:
|
||||
for job_id in job_ids:
|
||||
self.current_server_proxy.cancel_job(job_id, confirm=True)
|
||||
self.fetch_jobs(clear_table=True)
|
||||
self.refresh_job_list()
|
||||
|
||||
def delete_job(self, event):
|
||||
"""
|
||||
@@ -599,7 +580,7 @@ class MainWindow(QMainWindow):
|
||||
if msg_box.exec() == QMessageBox.StandardButton.Yes:
|
||||
for job_id in job_ids:
|
||||
self.current_server_proxy.delete_job(job_id, confirm=True)
|
||||
self.fetch_jobs(clear_table=True)
|
||||
self.refresh_job_list()
|
||||
|
||||
def download_files(self, event):
|
||||
|
||||
@@ -630,6 +611,36 @@ class MainWindow(QMainWindow):
|
||||
self.new_job_window.show()
|
||||
|
||||
|
||||
class BackgroundUpdater(QThread):
|
||||
"""Worker class to fetch job and server information and update the UI"""
|
||||
|
||||
updated_signal = pyqtSignal()
|
||||
error_signal = pyqtSignal(str)
|
||||
|
||||
def __init__(self, window):
|
||||
super().__init__()
|
||||
self.window = window
|
||||
self.needs_update = True
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
last_run = 0
|
||||
while True:
|
||||
now = time.monotonic()
|
||||
if now - last_run >= 1.0 or self.needs_update:
|
||||
self.window.found_servers = list(set(ZeroconfServer.found_hostnames() + self.window.added_hostnames))
|
||||
self.window.found_servers = [x for x in self.window.found_servers if
|
||||
ZeroconfServer.get_hostname_properties(x)['api_version'] == API_VERSION]
|
||||
if self.window.current_server_proxy:
|
||||
self.window.job_data[self.window.current_server_proxy.hostname] =\
|
||||
self.window.current_server_proxy.get_all_jobs(ignore_token=False)
|
||||
self.needs_update = False
|
||||
self.updated_signal.emit()
|
||||
time.sleep(0.05)
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}")
|
||||
self.error_signal.emit(str(e))
|
||||
|
||||
if __name__ == "__main__":
|
||||
# lazy load GUI frameworks
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
|
||||
Reference in New Issue
Block a user