mirror of
https://github.com/blw1138/Zordon.git
synced 2026-02-05 05:36:09 +00:00
669 lines
28 KiB
Python
669 lines
28 KiB
Python
import os.path
|
|
import socket
|
|
|
|
import psutil
|
|
from PyQt6.QtCore import QThread, pyqtSignal, Qt, pyqtSlot
|
|
from PyQt6.QtWidgets import (
|
|
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QFileDialog, QSpinBox, QComboBox,
|
|
QGroupBox, QCheckBox, QProgressBar, QPlainTextEdit, QDoubleSpinBox, QMessageBox, QListWidget, QListWidgetItem,
|
|
QTabWidget
|
|
)
|
|
|
|
from src.api.server_proxy import RenderServerProxy
|
|
from src.engines.engine_manager import EngineManager
|
|
from src.ui.engine_help_window import EngineHelpViewer
|
|
from src.utilities.zeroconf_server import ZeroconfServer
|
|
from src.utilities.misc_helper import COMMON_RESOLUTIONS, COMMON_FRAME_RATES
|
|
|
|
|
|
class NewRenderJobForm(QWidget):
|
|
def __init__(self, project_path=None):
|
|
super().__init__()
|
|
self.resolution_options_list = None
|
|
self.resolution_x_input = None
|
|
self.resolution_y_input = None
|
|
self.fps_options_list = None
|
|
self.fps_input = None
|
|
self.engine_group = None
|
|
self.notes_group = None
|
|
self.output_settings_group = None
|
|
self.project_path = project_path
|
|
|
|
# UI
|
|
self.project_group = None
|
|
self.load_file_group = None
|
|
self.current_engine_options = None
|
|
self.file_format_combo = None
|
|
self.engine_options_layout = None
|
|
self.cameras_list = None
|
|
self.cameras_group = None
|
|
self.engine_version_combo = None
|
|
self.worker_thread = None
|
|
self.msg_box = None
|
|
self.engine_help_viewer = None
|
|
self.raw_args = None
|
|
self.submit_progress_label = None
|
|
self.submit_progress = None
|
|
self.engine_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.job_name_input = None
|
|
self.scene_file_input = None
|
|
self.scene_file_browse_button = None
|
|
self.tabs = None
|
|
|
|
# Job / Server Data
|
|
self.server_proxy = RenderServerProxy(socket.gethostname())
|
|
self.project_info = None
|
|
self.installed_engines = {}
|
|
self.preferred_engine = None
|
|
|
|
# Setup
|
|
self.setWindowTitle("New Job")
|
|
self.setup_ui()
|
|
self.setup_project()
|
|
|
|
self.show()
|
|
|
|
def setup_ui(self):
|
|
# Main widget layout
|
|
main_layout = QVBoxLayout(self)
|
|
|
|
# Tabs
|
|
self.tabs = QTabWidget()
|
|
|
|
# ==================== Loading Section (outside tabs) ====================
|
|
self.load_file_group = QGroupBox("Loading")
|
|
load_file_layout = QVBoxLayout(self.load_file_group)
|
|
|
|
progress_layout = QHBoxLayout()
|
|
self.process_label = QLabel("Processing")
|
|
self.process_progress_bar = QProgressBar()
|
|
self.process_progress_bar.setMinimum(0)
|
|
self.process_progress_bar.setMaximum(0) # Indeterminate
|
|
progress_layout.addWidget(self.process_label)
|
|
progress_layout.addWidget(self.process_progress_bar)
|
|
load_file_layout.addLayout(progress_layout)
|
|
|
|
# Scene File
|
|
job_overview_group = QGroupBox("Project File")
|
|
file_group_layout = QVBoxLayout(job_overview_group)
|
|
|
|
# Job Name
|
|
job_name_layout = QHBoxLayout()
|
|
job_name_layout.addWidget(QLabel("Job name:"))
|
|
self.job_name_input = QLineEdit()
|
|
job_name_layout.addWidget(self.job_name_input)
|
|
self.engine_type = QComboBox()
|
|
job_name_layout.addWidget(self.engine_type)
|
|
file_group_layout.addLayout(job_name_layout)
|
|
|
|
# Job File
|
|
scene_file_picker_layout = QHBoxLayout()
|
|
scene_file_picker_layout.addWidget(QLabel("File:"))
|
|
self.scene_file_input = QLineEdit()
|
|
self.scene_file_input.setText(self.project_path)
|
|
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)
|
|
file_group_layout.addLayout(scene_file_picker_layout)
|
|
|
|
main_layout.addWidget(job_overview_group)
|
|
|
|
main_layout.addWidget(self.load_file_group)
|
|
main_layout.addWidget(self.tabs)
|
|
|
|
# ==================== Tab 1: Job Settings ====================
|
|
self.project_group = QWidget()
|
|
project_layout = QVBoxLayout(self.project_group) # Fixed: proper parent
|
|
|
|
# Server / Hostname
|
|
server_list_layout = QHBoxLayout()
|
|
server_list_layout.addWidget(QLabel("Render Target:"))
|
|
self.server_input = QComboBox()
|
|
server_list_layout.addWidget(self.server_input)
|
|
project_layout.addLayout(server_list_layout)
|
|
|
|
# Priority
|
|
priority_layout = QHBoxLayout()
|
|
priority_layout.addWidget(QLabel("Priority:"))
|
|
self.priority_input = QComboBox()
|
|
self.priority_input.addItems(["High", "Medium", "Low"])
|
|
self.priority_input.setCurrentIndex(1)
|
|
priority_layout.addWidget(self.priority_input)
|
|
project_layout.addLayout(priority_layout)
|
|
|
|
# Split Jobs Options
|
|
self.enable_splitjobs = QCheckBox("Automatically split render across multiple servers")
|
|
project_layout.addWidget(self.enable_splitjobs)
|
|
|
|
self.splitjobs_same_os = QCheckBox("Only render on same OS")
|
|
project_layout.addWidget(self.splitjobs_same_os)
|
|
|
|
project_layout.addStretch() # Push everything up
|
|
|
|
# ==================== Tab 2: Output Settings ====================
|
|
self.output_settings_group = QWidget()
|
|
output_settings_layout = QVBoxLayout(self.output_settings_group)
|
|
|
|
# File Format
|
|
format_group = QGroupBox("Format / Range")
|
|
output_settings_layout.addWidget(format_group)
|
|
format_group_layout = QVBoxLayout()
|
|
format_group.setLayout(format_group_layout)
|
|
file_format_layout = QHBoxLayout()
|
|
file_format_layout.addWidget(QLabel("Format:"))
|
|
self.file_format_combo = QComboBox()
|
|
self.file_format_combo.setFixedWidth(200)
|
|
file_format_layout.addWidget(self.file_format_combo)
|
|
file_format_layout.addStretch()
|
|
format_group_layout.addLayout(file_format_layout)
|
|
|
|
# Frame Range
|
|
frame_range_layout = QHBoxLayout()
|
|
frame_range_layout.addWidget(QLabel("Frames:"))
|
|
self.start_frame_input = QSpinBox()
|
|
self.start_frame_input.setRange(1, 99999)
|
|
self.start_frame_input.setFixedWidth(80)
|
|
self.end_frame_input = QSpinBox()
|
|
self.end_frame_input.setRange(1, 99999)
|
|
self.end_frame_input.setFixedWidth(80)
|
|
frame_range_layout.addWidget(self.start_frame_input)
|
|
frame_range_layout.addWidget(QLabel("to"))
|
|
frame_range_layout.addWidget(self.end_frame_input)
|
|
frame_range_layout.addStretch()
|
|
format_group_layout.addLayout(frame_range_layout)
|
|
|
|
# --- Resolution & FPS Group ---
|
|
resolution_group = QGroupBox("Resolution / Frame Rate")
|
|
output_settings_layout.addWidget(resolution_group)
|
|
resolution_group_layout = QVBoxLayout()
|
|
resolution_group.setLayout(resolution_group_layout)
|
|
|
|
# Resolution
|
|
resolution_layout = QHBoxLayout()
|
|
self.resolution_options_list = QComboBox()
|
|
self.resolution_options_list.setFixedWidth(200)
|
|
self.resolution_options_list.addItem("Original Size")
|
|
for res in COMMON_RESOLUTIONS:
|
|
self.resolution_options_list.addItem(res)
|
|
self.resolution_options_list.currentIndexChanged.connect(self._resolution_preset_changed)
|
|
resolution_layout.addWidget(self.resolution_options_list)
|
|
resolution_group_layout.addLayout(resolution_layout)
|
|
self.resolution_x_input = QSpinBox()
|
|
self.resolution_x_input.setRange(1, 9999)
|
|
self.resolution_x_input.setValue(1920)
|
|
self.resolution_x_input.setFixedWidth(80)
|
|
resolution_layout.addWidget(self.resolution_x_input)
|
|
self.resolution_y_input = QSpinBox()
|
|
self.resolution_y_input.setRange(1, 9999)
|
|
self.resolution_y_input.setValue(1080)
|
|
self.resolution_y_input.setFixedWidth(80)
|
|
resolution_layout.addWidget(QLabel("x"))
|
|
resolution_layout.addWidget(self.resolution_y_input)
|
|
resolution_layout.addStretch()
|
|
|
|
fps_layout = QHBoxLayout()
|
|
self.fps_options_list = QComboBox()
|
|
self.fps_options_list.setFixedWidth(200)
|
|
self.fps_options_list.addItem("Original FPS")
|
|
for fps_option in COMMON_FRAME_RATES:
|
|
self.fps_options_list.addItem(fps_option)
|
|
self.fps_options_list.currentIndexChanged.connect(self._fps_preset_changed)
|
|
fps_layout.addWidget(self.fps_options_list)
|
|
|
|
self.fps_input = QDoubleSpinBox()
|
|
self.fps_input.setDecimals(3)
|
|
self.fps_input.setRange(1.0, 999.0)
|
|
self.fps_input.setValue(23.976)
|
|
self.fps_input.setFixedWidth(80)
|
|
|
|
fps_layout.addWidget(self.fps_input)
|
|
fps_layout.addWidget(QLabel("fps"))
|
|
fps_layout.addStretch()
|
|
resolution_group_layout.addLayout(fps_layout)
|
|
|
|
output_settings_layout.addStretch()
|
|
|
|
# ==================== Tab 3: Engine Settings ====================
|
|
self.engine_group = QWidget()
|
|
engine_group_layout = QVBoxLayout(self.engine_group)
|
|
|
|
engine_layout = QHBoxLayout()
|
|
engine_layout.addWidget(QLabel("Engine Version:"))
|
|
self.engine_version_combo = QComboBox()
|
|
self.engine_version_combo.addItem('latest')
|
|
engine_layout.addWidget(self.engine_version_combo)
|
|
engine_group_layout.addLayout(engine_layout)
|
|
|
|
# Dynamic engine options
|
|
self.engine_options_layout = QVBoxLayout()
|
|
engine_group_layout.addLayout(self.engine_options_layout)
|
|
|
|
# Raw Args
|
|
raw_args_layout = QHBoxLayout()
|
|
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)
|
|
engine_group_layout.addLayout(raw_args_layout)
|
|
engine_group_layout.addStretch()
|
|
|
|
# ==================== Tab 4: Cameras ====================
|
|
self.cameras_group = QWidget()
|
|
cameras_layout = QVBoxLayout(self.cameras_group)
|
|
self.cameras_list = QListWidget()
|
|
self.cameras_list.itemChanged.connect(self.update_job_count)
|
|
cameras_layout.addWidget(self.cameras_list)
|
|
|
|
# ==================== Tab 5: Misc / Notes ====================
|
|
self.notes_group = QWidget()
|
|
notes_layout = QVBoxLayout(self.notes_group)
|
|
self.notes_input = QPlainTextEdit()
|
|
notes_layout.addWidget(self.notes_input)
|
|
|
|
# == Create Tabs
|
|
self.tabs.addTab(self.project_group, "Job Settings")
|
|
self.tabs.addTab(self.output_settings_group, "Output Settings")
|
|
self.tabs.addTab(self.engine_group, "Engine Settings")
|
|
self.tabs.addTab(self.cameras_group, "Cameras")
|
|
self.tabs.addTab(self.notes_group, "Notes")
|
|
self.update_server_list()
|
|
|
|
index = self.tabs.indexOf(self.cameras_group)
|
|
if index != -1:
|
|
self.tabs.setTabEnabled(index, False)
|
|
|
|
# ==================== Submit Section (outside tabs) ====================
|
|
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)
|
|
|
|
# Initial engine state
|
|
self.toggle_engine_enablement(False)
|
|
self.tabs.setCurrentIndex(0)
|
|
|
|
def update_job_count(self, changed_item=None):
|
|
checked = 0
|
|
total = self.cameras_list.count()
|
|
|
|
for i in range(total):
|
|
item = self.cameras_list.item(i)
|
|
if item.checkState() == Qt.CheckState.Checked:
|
|
checked += 1
|
|
|
|
message = f"Submit {checked} Jobs" if checked > 1 else "Submit Job"
|
|
self.submit_button.setText(message)
|
|
self.submit_button.setEnabled(bool(checked))
|
|
|
|
def _resolution_preset_changed(self, index):
|
|
selected_res = COMMON_RESOLUTIONS.get(self.resolution_options_list.currentText())
|
|
if selected_res:
|
|
self.resolution_x_input.setValue(selected_res[0])
|
|
self.resolution_y_input.setValue(selected_res[1])
|
|
elif index == 0:
|
|
self.resolution_x_input.setValue(self.project_info.get('resolution_x'))
|
|
self.resolution_y_input.setValue(self.project_info.get('resolution_y'))
|
|
|
|
def _fps_preset_changed(self, index):
|
|
selected_fps = COMMON_FRAME_RATES.get(self.fps_options_list.currentText())
|
|
if selected_fps:
|
|
self.fps_input.setValue(selected_fps)
|
|
elif index == 0:
|
|
self.fps_input.setValue(self.project_info.get('fps'))
|
|
|
|
def engine_changed(self):
|
|
# load the version numbers
|
|
current_engine = self.engine_type.currentText().lower() or self.engine_type.itemText(0)
|
|
self.engine_version_combo.clear()
|
|
self.engine_version_combo.addItem('latest')
|
|
self.file_format_combo.clear()
|
|
if current_engine:
|
|
engine_info = self.server_proxy.get_engine_info(current_engine, 'full', timeout=10)
|
|
self.current_engine_options = engine_info.get('options', [])
|
|
if not engine_info:
|
|
raise FileNotFoundError(f"Cannot get information about engine '{current_engine}'")
|
|
engine_vers = [v['version'] for v in engine_info['versions']]
|
|
self.engine_version_combo.addItems(engine_vers)
|
|
self.file_format_combo.addItems(engine_info.get('supported_export_formats'))
|
|
|
|
def update_server_list(self):
|
|
clients = ZeroconfServer.found_hostnames()
|
|
self.server_input.clear()
|
|
self.server_input.addItems(clients)
|
|
|
|
def browse_scene_file(self):
|
|
file_name, _ = QFileDialog.getOpenFileName(self, "Select Scene File")
|
|
if file_name:
|
|
self.scene_file_input.setText(file_name)
|
|
self.setup_project()
|
|
|
|
def setup_project(self):
|
|
# UI stuff on main thread
|
|
self.process_progress_bar.setHidden(False)
|
|
self.process_label.setHidden(False)
|
|
self.toggle_engine_enablement(False)
|
|
|
|
output_name, _ = os.path.splitext(os.path.basename(self.scene_file_input.text()))
|
|
output_name = output_name.replace(' ', '_')
|
|
self.job_name_input.setText(output_name)
|
|
file_name = self.scene_file_input.text()
|
|
|
|
# setup bg worker
|
|
self.worker_thread = GetProjectInfoWorker(window=self, project_path=file_name)
|
|
self.worker_thread.message_signal.connect(self.post_get_project_info_update)
|
|
self.worker_thread.error_signal.connect(self.show_error_message)
|
|
self.worker_thread.start()
|
|
|
|
def browse_output_path(self):
|
|
directory = QFileDialog.getExistingDirectory(self, "Select Output Directory")
|
|
if directory:
|
|
self.job_name_input.setText(directory)
|
|
|
|
def args_help_button_clicked(self):
|
|
url = (f'http://{self.server_proxy.hostname}:{self.server_proxy.port}/api/engine/'
|
|
f'{self.engine_type.currentText()}/help')
|
|
self.engine_help_viewer = EngineHelpViewer(url)
|
|
self.engine_help_viewer.show()
|
|
|
|
def show_error_message(self, message):
|
|
msg = QMessageBox(self)
|
|
msg.setIcon(QMessageBox.Icon.Critical)
|
|
msg.setWindowTitle("Error")
|
|
msg.setText(message)
|
|
msg.exec()
|
|
|
|
# -------- Update --------
|
|
|
|
def post_get_project_info_update(self):
|
|
"""Called by the GetProjectInfoWorker - Do not call directly."""
|
|
try:
|
|
|
|
self.engine_type.addItems(self.installed_engines.keys())
|
|
self.engine_type.setCurrentText(self.preferred_engine)
|
|
self.engine_changed()
|
|
|
|
# Set the best engine we can find
|
|
input_path = self.scene_file_input.text()
|
|
engine = EngineManager.engine_class_for_project_path(input_path)
|
|
|
|
engine_index = self.engine_type.findText(engine.name().lower())
|
|
if engine_index >= 0:
|
|
self.engine_type.setCurrentIndex(engine_index)
|
|
else:
|
|
self.engine_type.setCurrentIndex(0) #todo: find out why we don't have engine info yet
|
|
# not ideal but if we don't have the engine info we have to pick something
|
|
|
|
# cleanup progress UI
|
|
self.load_file_group.setHidden(True)
|
|
self.toggle_engine_enablement(True)
|
|
|
|
# Load scene data
|
|
self.start_frame_input.setValue(self.project_info.get('frame_start'))
|
|
self.end_frame_input.setValue(self.project_info.get('frame_end'))
|
|
self.resolution_x_input.setValue(self.project_info.get('resolution_x'))
|
|
self.resolution_y_input.setValue(self.project_info.get('resolution_y'))
|
|
self.fps_input.setValue(self.project_info.get('fps'))
|
|
|
|
# Cameras
|
|
self.cameras_list.clear()
|
|
index = self.tabs.indexOf(self.cameras_group)
|
|
if self.project_info.get('cameras'):
|
|
self.tabs.setTabEnabled(index, True)
|
|
found_active = False
|
|
for camera in self.project_info['cameras']:
|
|
# create the list items and make them checkable
|
|
item = QListWidgetItem(f"{camera['name']} - {camera['lens']}mm")
|
|
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
|
is_checked = camera['is_active'] or len(self.project_info['cameras']) == 1
|
|
found_active = found_active or is_checked
|
|
item.setCheckState(Qt.CheckState.Checked if is_checked else Qt.CheckState.Unchecked)
|
|
self.cameras_list.addItem(item)
|
|
if not found_active:
|
|
self.cameras_list.item(0).setCheckState(Qt.CheckState.Checked)
|
|
else:
|
|
self.tabs.setTabEnabled(index, False)
|
|
self.update_job_count()
|
|
|
|
# Dynamic Engine Options
|
|
clear_layout(self.engine_options_layout) # clear old options
|
|
# dynamically populate option list
|
|
for option in self.current_engine_options:
|
|
h_layout = QHBoxLayout()
|
|
label = QLabel(option['name'].replace('_', ' ').capitalize() + ':')
|
|
h_layout.addWidget(label)
|
|
if option.get('options'):
|
|
combo_box = QComboBox()
|
|
for opt in option['options']:
|
|
combo_box.addItem(opt)
|
|
h_layout.addWidget(combo_box)
|
|
else:
|
|
text_box = QLineEdit()
|
|
h_layout.addWidget(text_box)
|
|
self.engine_options_layout.addLayout(h_layout)
|
|
except AttributeError:
|
|
pass
|
|
|
|
def toggle_engine_enablement(self, enabled=False):
|
|
"""Toggle on/off all the render settings"""
|
|
indexes = [self.tabs.indexOf(self.project_group),
|
|
self.tabs.indexOf(self.output_settings_group),
|
|
self.tabs.indexOf(self.engine_group),
|
|
self.tabs.indexOf(self.cameras_group),
|
|
self.tabs.indexOf(self.notes_group)]
|
|
for idx in indexes:
|
|
self.tabs.setTabEnabled(idx, enabled)
|
|
self.submit_button.setEnabled(enabled)
|
|
|
|
def after_job_submission(self, error_string):
|
|
|
|
# UI cleanup
|
|
self.submit_progress.setMaximum(0)
|
|
self.submit_button.setHidden(False)
|
|
self.submit_progress.setHidden(True)
|
|
self.submit_progress_label.setHidden(True)
|
|
self.process_progress_bar.setHidden(True)
|
|
self.process_label.setHidden(True)
|
|
self.toggle_engine_enablement(True)
|
|
|
|
self.msg_box = QMessageBox()
|
|
if not error_string:
|
|
self.msg_box.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
|
self.msg_box.setIcon(QMessageBox.Icon.Information)
|
|
self.msg_box.setText("Job successfully submitted to server. Submit another?")
|
|
self.msg_box.setWindowTitle("Success")
|
|
x = self.msg_box.exec()
|
|
if x == QMessageBox.StandardButton.No:
|
|
self.close()
|
|
else:
|
|
self.msg_box.setStandardButtons(QMessageBox.StandardButton.Ok)
|
|
self.msg_box.setIcon(QMessageBox.Icon.Critical)
|
|
self.msg_box.setText(error_string)
|
|
self.msg_box.setWindowTitle("Error")
|
|
self.msg_box.exec()
|
|
|
|
# -------- Submit Job Calls --------
|
|
|
|
def submit_job(self):
|
|
|
|
# Pre-worker UI
|
|
self.submit_progress.setHidden(False)
|
|
self.submit_progress_label.setHidden(False)
|
|
self.submit_button.setHidden(True)
|
|
self.submit_progress.setMaximum(0)
|
|
|
|
# submit job in background thread
|
|
self.worker_thread = SubmitWorker(window=self)
|
|
self.worker_thread.update_ui_signal.connect(self.update_submit_progress)
|
|
self.worker_thread.message_signal.connect(self.after_job_submission)
|
|
self.worker_thread.start()
|
|
|
|
@pyqtSlot(str, str)
|
|
def update_submit_progress(self, hostname, percent):
|
|
# Update the UI here. This slot will be executed in the main thread
|
|
self.submit_progress_label.setText(f"Transferring to {hostname} - {percent}%")
|
|
self.submit_progress.setMaximum(100)
|
|
self.submit_progress.setValue(int(percent))
|
|
|
|
|
|
class SubmitWorker(QThread):
|
|
"""Worker class called to submit all the jobs to the server and update the UI accordingly"""
|
|
|
|
message_signal = pyqtSignal(str)
|
|
update_ui_signal = pyqtSignal(str, str)
|
|
|
|
def __init__(self, window):
|
|
super().__init__()
|
|
self.window = window
|
|
|
|
def run(self):
|
|
def create_callback(encoder):
|
|
encoder_len = encoder.len
|
|
|
|
def callback(monitor):
|
|
percent = f"{monitor.bytes_read / encoder_len * 100:.0f}"
|
|
self.update_ui_signal.emit(hostname, percent)
|
|
return callback
|
|
|
|
try:
|
|
hostname = self.window.server_input.currentText()
|
|
resolution = (self.window.resolution_x_input.text(), self.window.resolution_y_input.text())
|
|
job_json = {'owner': psutil.Process().username() + '@' + socket.gethostname(),
|
|
'engine_name': self.window.engine_type.currentText().lower(),
|
|
'engine_version': self.window.engine_version_combo.currentText(),
|
|
'args': {'raw': self.window.raw_args.text(),
|
|
'export_format': self.window.file_format_combo.currentText(),
|
|
'resolution': resolution,
|
|
'fps': self.window.fps_input.text(),},
|
|
'output_path': self.window.job_name_input.text(),
|
|
'start_frame': self.window.start_frame_input.value(),
|
|
'end_frame': self.window.end_frame_input.value(),
|
|
'priority': self.window.priority_input.currentIndex() + 1,
|
|
'notes': self.window.notes_input.toPlainText(),
|
|
'enable_split_jobs': self.window.enable_splitjobs.isChecked(),
|
|
'split_jobs_same_os': self.window.splitjobs_same_os.isChecked(),
|
|
'name': self.window.job_name_input.text()}
|
|
|
|
# get the dynamic args
|
|
for i in range(self.window.engine_options_layout.count()):
|
|
item = self.window.engine_options_layout.itemAt(i)
|
|
layout = item.layout() # get the layout
|
|
for x in range(layout.count()):
|
|
z = layout.itemAt(x)
|
|
widget = z.widget()
|
|
if isinstance(widget, QComboBox):
|
|
job_json['args'][self.window.current_engine_options[i]['name']] = widget.currentText()
|
|
elif isinstance(widget, QLineEdit):
|
|
job_json['args'][self.window.current_engine_options[i]['name']] = widget.text()
|
|
|
|
# determine if any cameras are checked
|
|
selected_cameras = []
|
|
if self.window.cameras_list.count() and not self.window.cameras_group.isHidden():
|
|
for index in range(self.window.cameras_list.count()):
|
|
item = self.window.cameras_list.item(index)
|
|
if item.checkState() == Qt.CheckState.Checked:
|
|
selected_cameras.append(item.text().rsplit('-', 1)[0].strip()) # cleanup to just camera name
|
|
|
|
# process cameras into nested format
|
|
input_path = self.window.scene_file_input.text()
|
|
if selected_cameras and self.window.cameras_list.count() > 1:
|
|
children_jobs = []
|
|
for cam in selected_cameras:
|
|
child_job_data = dict()
|
|
child_job_data['args'] = {}
|
|
child_job_data['args']['camera'] = cam
|
|
child_job_data['name'] = job_json['name'].replace(' ', '-') + "_" + cam.replace(' ', '')
|
|
child_job_data['output_path'] = child_job_data['name']
|
|
children_jobs.append(child_job_data)
|
|
job_json['child_jobs'] = children_jobs
|
|
|
|
# presubmission tasks - use local installs
|
|
engine_class = EngineManager.engine_class_with_name(self.window.engine_type.currentText().lower())
|
|
latest_engine = EngineManager.get_latest_engine_instance(engine_class)
|
|
input_path = latest_engine.perform_presubmission_tasks(input_path)
|
|
# submit
|
|
err_msg = ""
|
|
result = self.window.server_proxy.post_job_to_server(file_path=input_path, job_data=job_json,
|
|
callback=create_callback)
|
|
if not (result and result.ok):
|
|
err_msg = f"Error posting job to server: {result.message}"
|
|
|
|
self.message_signal.emit(err_msg)
|
|
|
|
except Exception as e:
|
|
self.message_signal.emit(str(e))
|
|
|
|
|
|
class GetProjectInfoWorker(QThread):
|
|
"""Worker class called to retrieve information about a project file on a background thread and update the UI"""
|
|
|
|
message_signal = pyqtSignal()
|
|
error_signal = pyqtSignal(str)
|
|
|
|
def __init__(self, window, project_path):
|
|
super().__init__()
|
|
self.window = window
|
|
self.project_path = project_path
|
|
|
|
def run(self):
|
|
try:
|
|
# get the engine info and add them all to the ui
|
|
self.window.installed_engines = self.window.server_proxy.get_installed_engines()
|
|
# select the best engine for the file type
|
|
self.window.preferred_engine = self.window.server_proxy.get_engine_for_filename(self.project_path)
|
|
|
|
# this should be the only time we use a local engine instead of using the proxy besides submitting
|
|
engine_class = EngineManager.engine_class_for_project_path(self.project_path)
|
|
engine = EngineManager.get_latest_engine_instance(engine_class)
|
|
self.window.project_info = engine.get_project_info(self.project_path)
|
|
self.message_signal.emit()
|
|
except Exception as e:
|
|
self.error_signal.emit(str(e))
|
|
|
|
|
|
def clear_layout(layout):
|
|
if layout is not None:
|
|
# Go through the layout's items in reverse order
|
|
for i in reversed(range(layout.count())):
|
|
# Take the item at the current position
|
|
item = layout.takeAt(i)
|
|
|
|
# Check if the item is a widget
|
|
if item.widget():
|
|
# Remove the widget and delete it
|
|
widget_to_remove = item.widget()
|
|
widget_to_remove.setParent(None)
|
|
widget_to_remove.deleteLater()
|
|
elif item.layout():
|
|
# If the item is a sub-layout, clear its contents recursively
|
|
clear_layout(item.layout())
|
|
# Then delete the layout
|
|
item.layout().deleteLater()
|
|
|
|
# Run the application
|
|
if __name__ == '__main__':
|
|
app = QApplication([])
|
|
window = NewRenderJobForm()
|
|
app.exec()
|