New UI Redesign in pyqt6 (#56)

* Initial commit for new UI

* Initial commit for new UI

* WIP

* Status bar updates and has an icon for online / offline

* Add log_viewer.py

* Use JSON for delete_engine_download API

* Fix class issue with Downloaders

* Move Config class to new ui

* Add engine_browser.py

* Add a close event handler to the main window

* Fix issue with engine manager not deleting engines properly

* Rearrange all the files

* Add icons and resources

* Cache system info in RenderServerProxy

* Toolbar polish

* Fix resource path in status bar

* Add config_dir to misc_helper.py

* Add try block to zeroconf setup

* Add add_job.py

* Add raw args to add_job.py
This commit is contained in:
2023-11-04 09:52:15 -05:00
committed by GitHub
parent bc8e88ea59
commit 65c256b641
45 changed files with 1491 additions and 53 deletions

289
src/ui/add_job.py Normal file
View File

@@ -0,0 +1,289 @@
import os.path
import socket
import threading
import psutil
from PyQt6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QFileDialog, QSpinBox, QComboBox,
QGroupBox, QCheckBox, QProgressBar, QPlainTextEdit
)
from src.api.server_proxy import RenderServerProxy
from src.engines.engine_manager import EngineManager
from src.utilities.zeroconf_server import ZeroconfServer
class NewRenderJobForm(QWidget):
def __init__(self):
super().__init__()
# UI
self.raw_args = None
self.submit_progress_label = None
self.submit_progress = None
self.renderer_type = None
self.process_label = None
self.process_progress_bar = None
self.splitjobs_same_os = None
self.enable_splitjobs = None
self.server_input = None
self.submit_button = None
self.notes_input = None
self.priority_input = None
self.end_frame_input = None
self.start_frame_input = None
self.output_path_browse_button = None
self.output_path_input = None
self.scene_file_input = None
self.scene_file_browse_button = None
self.job_name_input = None
# Setup
self.setWindowTitle("New Job")
self.setup_ui()
# Job / Server Data
self.server_proxy = RenderServerProxy(socket.gethostname())
self.renderer_info = None
self.project_info = None
# get renderer info in bg thread
t = threading.Thread(target=self.update_renderer_info)
t.start()
self.show()
def setup_ui(self):
# Main Layout
main_layout = QVBoxLayout(self)
# Server Group
# Server List
server_group = QGroupBox("Server")
server_layout = QVBoxLayout(server_group)
server_list_layout = QHBoxLayout()
server_list_layout.setSpacing(0)
self.server_input = QComboBox()
server_list_layout.addWidget(QLabel("Hostname:"), 1)
server_list_layout.addWidget(self.server_input, 3)
server_layout.addLayout(server_list_layout)
main_layout.addWidget(server_group)
self.update_server_list()
# Priority
priority_layout = QHBoxLayout()
priority_layout.addWidget(QLabel("Priority:"), 1)
self.priority_input = QComboBox()
self.priority_input.addItems(["High", "Medium", "Low"])
self.priority_input.setCurrentIndex(1)
priority_layout.addWidget(self.priority_input, 3)
server_layout.addLayout(priority_layout)
# Splitjobs
self.enable_splitjobs = QCheckBox("Automatically split render across multiple servers")
self.enable_splitjobs.setEnabled(True)
server_layout.addWidget(self.enable_splitjobs)
self.splitjobs_same_os = QCheckBox("Only render on same OS")
self.splitjobs_same_os.setEnabled(True)
server_layout.addWidget(self.splitjobs_same_os)
# Scene File Group
scene_file_group = QGroupBox("Project")
scene_file_layout = QVBoxLayout(scene_file_group)
scene_file_picker_layout = QHBoxLayout()
self.scene_file_input = QLineEdit()
self.scene_file_browse_button = QPushButton("Browse...")
self.scene_file_browse_button.clicked.connect(self.browse_scene_file)
scene_file_picker_layout.addWidget(self.scene_file_input)
scene_file_picker_layout.addWidget(self.scene_file_browse_button)
scene_file_layout.addLayout(scene_file_picker_layout)
# progress bar
progress_layout = QHBoxLayout()
self.process_progress_bar = QProgressBar()
self.process_progress_bar.setMinimum(0)
self.process_progress_bar.setMaximum(0)
self.process_progress_bar.setHidden(True)
self.process_label = QLabel("Processing")
self.process_label.setHidden(True)
progress_layout.addWidget(self.process_label)
progress_layout.addWidget(self.process_progress_bar)
scene_file_layout.addLayout(progress_layout)
main_layout.addWidget(scene_file_group)
# Output Settings Group
output_settings_group = QGroupBox("Output Settings")
output_settings_layout = QVBoxLayout(output_settings_group)
frame_range_layout = QHBoxLayout(output_settings_group)
self.start_frame_input = QSpinBox()
self.start_frame_input.setRange(1, 99999)
self.end_frame_input = QSpinBox()
self.end_frame_input.setRange(1, 99999)
frame_range_layout.addWidget(QLabel("Frames:"))
frame_range_layout.addWidget(self.start_frame_input)
frame_range_layout.addWidget(QLabel("to"))
frame_range_layout.addWidget(self.end_frame_input)
output_settings_layout.addLayout(frame_range_layout)
# output path
output_path_layout = QHBoxLayout()
output_path_layout.addWidget(QLabel("Render name:"))
self.output_path_input = QLineEdit()
# self.output_path_browse_button = QPushButton("Browse...")
# self.output_path_browse_button.clicked.connect(self.browse_output_path)
output_path_layout.addWidget(self.output_path_input)
output_path_layout.addWidget(self.output_path_browse_button)
output_settings_layout.addLayout(output_path_layout)
main_layout.addWidget(output_settings_group)
# Renderer Group
renderer_group = QGroupBox("Renderer Settings")
renderer_layout = QVBoxLayout(renderer_group)
self.renderer_type = QComboBox()
renderer_layout.addWidget(self.renderer_type)
# Raw Args
raw_args_layout = QHBoxLayout(renderer_group)
raw_args_layout.addWidget(QLabel("Raw Args:"))
self.raw_args = QLineEdit()
raw_args_layout.addWidget(self.raw_args)
args_help_button = QPushButton("?")
args_help_button.clicked.connect(self.args_help_button_clicked)
raw_args_layout.addWidget(args_help_button)
renderer_layout.addLayout(raw_args_layout)
main_layout.addWidget(renderer_group)
# Notes Group
notes_group = QGroupBox("Additional Notes")
notes_layout = QVBoxLayout(notes_group)
self.notes_input = QPlainTextEdit()
notes_layout.addWidget(self.notes_input)
main_layout.addWidget(notes_group)
# Submit Button
self.submit_button = QPushButton("Submit Job")
self.submit_button.clicked.connect(self.submit_job)
main_layout.addWidget(self.submit_button)
self.submit_progress = QProgressBar()
self.submit_progress.setMinimum(0)
self.submit_progress.setMaximum(0)
self.submit_progress.setHidden(True)
main_layout.addWidget(self.submit_progress)
self.submit_progress_label = QLabel("Submitting...")
self.submit_progress_label.setHidden(True)
main_layout.addWidget(self.submit_progress_label)
self.toggle_renderer_enablement(False)
def update_renderer_info(self):
self.renderer_info = self.server_proxy.get_renderer_info()
self.renderer_type.addItems(self.renderer_info.keys())
def update_server_list(self):
clients = ZeroconfServer.found_clients()
self.server_input.clear()
self.server_input.addItems(clients)
def browse_scene_file(self):
def get_project_info():
self.process_progress_bar.setHidden(False)
self.process_label.setHidden(False)
self.toggle_renderer_enablement(False)
output_name, _ = os.path.splitext(os.path.basename(file_name))
self.output_path_input.setText(output_name)
engine = EngineManager.engine_for_project_path(file_name)
self.project_info = engine().get_scene_info(file_name)
index = self.renderer_type.findText(engine.name().lower())
if index >= 0:
self.renderer_type.setCurrentIndex(index)
self.update_project_ui()
self.process_progress_bar.setHidden(True)
self.process_label.setHidden(True)
self.toggle_renderer_enablement(True)
file_name, _ = QFileDialog.getOpenFileName(self, "Select Scene File")
if file_name:
self.scene_file_input.setText(file_name)
# analyze the file
update_thread = threading.Thread(target=get_project_info)
update_thread.start()
def browse_output_path(self):
directory = QFileDialog.getExistingDirectory(self, "Select Output Directory")
if directory:
self.output_path_input.setText(directory)
def args_help_button_clicked(self):
#todo: create a popup window showing args
pass
# -------- Update --------
def update_project_ui(self):
self.start_frame_input.setValue(self.project_info.get('frame_start'))
self.end_frame_input.setValue(self.project_info.get('frame_end'))
def toggle_renderer_enablement(self, enabled=False):
self.start_frame_input.setEnabled(enabled)
self.end_frame_input.setEnabled(enabled)
self.notes_input.setEnabled(enabled)
self.output_path_input.setEnabled(enabled)
self.submit_button.setEnabled(enabled)
# -------- Submit Job Calls --------
def submit_job(self):
def submit_job_worker():
def create_callback(encoder):
encoder_len = encoder.len
def callback(monitor):
percent = f"{monitor.bytes_read / encoder_len * 100:.0f}"
self.submit_progress_label.setText(f"Transferring to {hostname} - {percent}%")
self.submit_progress.setMaximum(100)
self.submit_progress.setValue(int(percent))
return callback
self.submit_progress.setHidden(False)
self.submit_progress_label.setHidden(False)
self.submit_button.setHidden(True)
hostname = self.server_input.currentText()
job_json = {'owner': psutil.Process().username() + '@' + socket.gethostname(),
'renderer': self.renderer_type.currentText().lower(),
# 'input_path': self.scene_file_input.text(),
# 'output_path': os.path.join(os.path.dirname(self.chosen_file), self.output_entry.get()),
'args': {'raw': self.raw_args.text()},
'output_path': self.output_path_input.text(),
'start_frame': self.start_frame_input.value(),
'end_frame': self.end_frame_input.value(),
'priority': self.priority_input.currentIndex() + 1,
'notes': self.notes_input.toPlainText(),
'enable_split_jobs': self.enable_splitjobs.isChecked()}
input_path = self.scene_file_input.text()
job_list = [job_json]
self.submit_progress.setMaximum(0)
result = self.server_proxy.post_job_to_server(file_path=input_path, job_list=job_list,
callback=create_callback)
self.submit_progress.setMaximum(0)
print(result.json())
self.submit_button.setHidden(False)
self.submit_progress.setHidden(True)
self.submit_progress_label.setHidden(True)
# submit thread
worker_thread = threading.Thread(target=submit_job_worker)
worker_thread.start()
# Run the application
if __name__ == '__main__':
app = QApplication([])
window = NewRenderJobForm()
app.exec()