mirror of
https://github.com/blw1138/Zordon.git
synced 2025-12-17 08:48:13 +00:00
Compare commits
4 Commits
dynamic_re
...
loopback_f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82e50d80bc | ||
| c0d0ec64a8 | |||
| 32afcf945d | |||
| e9f9521924 |
@@ -423,10 +423,12 @@ def renderer_info():
|
||||
# Get all installed versions of engine
|
||||
installed_versions = EngineManager.all_versions_for_engine(engine.name())
|
||||
if installed_versions:
|
||||
install_path = installed_versions[0]['path']
|
||||
# fixme: using system versions only because downloaded versions may have permissions issues
|
||||
system_installed_versions = [x for x in installed_versions if x['type'] == 'system']
|
||||
install_path = system_installed_versions[0]['path'] if system_installed_versions else installed_versions[0]['path']
|
||||
renderer_data[engine.name()] = {'is_available': RenderQueue.is_available_for_job(engine.name()),
|
||||
'versions': installed_versions,
|
||||
'supported_extensions': engine.supported_extensions,
|
||||
'supported_extensions': engine.supported_extensions(),
|
||||
'supported_export_formats': engine(install_path).get_output_formats()}
|
||||
return renderer_data
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ categories = [RenderStatus.RUNNING, RenderStatus.WAITING_FOR_SUBJOBS, RenderStat
|
||||
|
||||
logger = logging.getLogger()
|
||||
OFFLINE_MAX = 2
|
||||
LOOPBACK = '127.0.0.1'
|
||||
|
||||
|
||||
class RenderServerProxy:
|
||||
@@ -34,6 +35,7 @@ class RenderServerProxy:
|
||||
self.__background_thread = None
|
||||
self.__offline_flags = 0
|
||||
self.update_cadence = 5
|
||||
self.is_localhost = is_localhost(hostname)
|
||||
|
||||
# Cache some basic server info
|
||||
self.system_cpu = None
|
||||
@@ -75,7 +77,7 @@ class RenderServerProxy:
|
||||
return None
|
||||
|
||||
def request(self, payload, timeout=5):
|
||||
return requests.get(f'http://{self.hostname}:{self.port}/api/{payload}', timeout=timeout)
|
||||
return requests.get(f'http://{self.optimized_hostname()}:{self.port}/api/{payload}', timeout=timeout)
|
||||
|
||||
def start_background_update(self):
|
||||
if self.__update_in_background:
|
||||
@@ -140,15 +142,15 @@ class RenderServerProxy:
|
||||
return self.request_data('all_engines')
|
||||
|
||||
def notify_parent_of_status_change(self, parent_id, subjob):
|
||||
return requests.post(f'http://{self.hostname}:{self.port}/api/job/{parent_id}/notify_parent_of_status_change',
|
||||
return requests.post(f'http://{self.optimized_hostname()}:{self.port}/api/job/{parent_id}/notify_parent_of_status_change',
|
||||
json=subjob.json())
|
||||
|
||||
def post_job_to_server(self, file_path, job_list, callback=None):
|
||||
|
||||
# bypass uploading file if posting to localhost
|
||||
if is_localhost(self.hostname):
|
||||
if self.is_localhost:
|
||||
jobs_with_path = [{**item, "local_path": file_path} for item in job_list]
|
||||
return requests.post(f'http://{self.hostname}:{self.port}/api/add_job', data=json.dumps(jobs_with_path),
|
||||
return requests.post(f'http://{LOOPBACK}:{self.port}/api/add_job', data=json.dumps(jobs_with_path),
|
||||
headers={'Content-Type': 'application/json'})
|
||||
|
||||
# Prepare the form data
|
||||
@@ -168,7 +170,7 @@ class RenderServerProxy:
|
||||
return requests.post(f'http://{self.hostname}:{self.port}/api/add_job', data=monitor, headers=headers)
|
||||
|
||||
def get_job_files(self, job_id, save_path):
|
||||
url = f"http://{self.hostname}:{self.port}/api/job/{job_id}/download_all"
|
||||
url = f"http://{self.optimized_hostname()}:{self.port}/api/job/{job_id}/download_all"
|
||||
return self.download_file(url, filename=save_path)
|
||||
|
||||
@staticmethod
|
||||
@@ -188,4 +190,7 @@ class RenderServerProxy:
|
||||
|
||||
def delete_engine(self, engine, version, system_cpu=None):
|
||||
form_data = {'engine': engine, 'version': version, 'system_cpu': system_cpu}
|
||||
return requests.post(f'http://{self.hostname}:{self.port}/api/delete_engine', json=form_data)
|
||||
return requests.post(f'http://{self.optimized_hostname()}:{self.port}/api/delete_engine', json=form_data)
|
||||
|
||||
def optimized_hostname(self):
|
||||
return LOOPBACK if self.is_localhost else self.hostname
|
||||
@@ -10,7 +10,6 @@ logger = logging.getLogger()
|
||||
class Blender(BaseRenderEngine):
|
||||
|
||||
install_paths = ['/Applications/Blender.app/Contents/MacOS/Blender']
|
||||
supported_extensions = ['.blend']
|
||||
binary_names = {'linux': 'blender', 'windows': 'blender.exe', 'macos': 'Blender'}
|
||||
|
||||
@staticmethod
|
||||
@@ -23,6 +22,14 @@ class Blender(BaseRenderEngine):
|
||||
from src.engines.blender.blender_worker import BlenderRenderWorker
|
||||
return BlenderRenderWorker
|
||||
|
||||
def ui_options(self):
|
||||
from src.engines.blender.blender_ui import BlenderUI
|
||||
return BlenderUI.get_options(self)
|
||||
|
||||
@staticmethod
|
||||
def supported_extensions():
|
||||
return ['blend']
|
||||
|
||||
def version(self):
|
||||
version = None
|
||||
try:
|
||||
@@ -150,13 +157,6 @@ class Blender(BaseRenderEngine):
|
||||
render_engines = [x.strip() for x in engine_output.split('Blender Engine Listing:')[-1].strip().splitlines()]
|
||||
return render_engines
|
||||
|
||||
# UI and setup
|
||||
def get_options(self):
|
||||
options = [
|
||||
{'name': 'engine', 'options': self.supported_render_engines()},
|
||||
]
|
||||
return options
|
||||
|
||||
def perform_presubmission_tasks(self, project_path):
|
||||
packed_path = self.pack_project_file(project_path, timeout=30)
|
||||
return packed_path
|
||||
|
||||
8
src/engines/blender/blender_ui.py
Normal file
8
src/engines/blender/blender_ui.py
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
class BlenderUI:
|
||||
@staticmethod
|
||||
def get_options(instance):
|
||||
options = [
|
||||
{'name': 'engine', 'options': instance.supported_render_engines()},
|
||||
]
|
||||
return options
|
||||
@@ -47,7 +47,10 @@ class BaseRenderEngine(object):
|
||||
def worker_class(): # override when subclassing to link worker class
|
||||
raise NotImplementedError("Worker class not implemented")
|
||||
|
||||
def get_help(self):
|
||||
def ui_options(self): # override to return options for ui
|
||||
return {}
|
||||
|
||||
def get_help(self): # override if renderer uses different help flag
|
||||
path = self.renderer_path()
|
||||
if not path:
|
||||
raise FileNotFoundError("renderer path not found")
|
||||
@@ -56,7 +59,7 @@ class BaseRenderEngine(object):
|
||||
return help_doc
|
||||
|
||||
def get_project_info(self, project_path, timeout=10):
|
||||
raise NotImplementedError(f"get_project_info not implemented for {cls.__name__}")
|
||||
raise NotImplementedError(f"get_project_info not implemented for {self.__name__}")
|
||||
|
||||
@classmethod
|
||||
def get_output_formats(cls):
|
||||
|
||||
@@ -47,7 +47,7 @@ class BaseRenderWorker(Base):
|
||||
name=None):
|
||||
|
||||
if not ignore_extensions:
|
||||
if not any(ext in input_path for ext in self.engine.supported_extensions):
|
||||
if not any(ext in input_path for ext in self.engine.supported_extensions()):
|
||||
err_meg = f'Cannot find valid project with supported file extension for {self.engine.name()} renderer'
|
||||
logger.error(err_meg)
|
||||
raise ValueError(err_meg)
|
||||
|
||||
@@ -76,7 +76,9 @@ class EngineManager:
|
||||
|
||||
@classmethod
|
||||
def all_versions_for_engine(cls, engine):
|
||||
return [x for x in cls.all_engines() if x['engine'] == engine]
|
||||
versions = [x for x in cls.all_engines() if x['engine'] == engine]
|
||||
sorted_versions = sorted(versions, key=lambda x: x['version'], reverse=True)
|
||||
return sorted_versions
|
||||
|
||||
@classmethod
|
||||
def newest_engine_version(cls, engine, system_os=None, cpu=None):
|
||||
@@ -84,9 +86,8 @@ class EngineManager:
|
||||
cpu = cpu or current_system_cpu()
|
||||
|
||||
try:
|
||||
filtered = [x for x in cls.all_engines() if x['engine'] == engine and x['system_os'] == system_os and x['cpu'] == cpu]
|
||||
versions = sorted(filtered, key=lambda x: x['version'], reverse=True)
|
||||
return versions[0]
|
||||
filtered = [x for x in cls.all_versions_for_engine(engine) if x['system_os'] == system_os and x['cpu'] == cpu]
|
||||
return filtered[0]
|
||||
except IndexError:
|
||||
logger.error(f"Cannot find newest engine version for {engine}-{system_os}-{cpu}")
|
||||
return None
|
||||
@@ -129,15 +130,6 @@ class EngineManager:
|
||||
|
||||
@classmethod
|
||||
def download_engine(cls, engine, version, system_os=None, cpu=None, background=False):
|
||||
def download_engine_task(engine, version, system_os=None, cpu=None):
|
||||
existing_download = cls.is_version_downloaded(engine, version, system_os, cpu)
|
||||
if existing_download:
|
||||
logger.info(f"Requested download of {engine} {version}, but local copy already exists")
|
||||
return existing_download
|
||||
|
||||
# Get the appropriate downloader class based on the engine type
|
||||
cls.engine_with_name(engine).downloader().download_engine(version, download_location=cls.engines_path,
|
||||
system_os=system_os, cpu=cpu, timeout=300)
|
||||
|
||||
engine_to_download = cls.engine_with_name(engine)
|
||||
existing_task = cls.is_already_downloading(engine, version, system_os, cpu)
|
||||
@@ -152,8 +144,7 @@ class EngineManager:
|
||||
elif not cls.engines_path:
|
||||
raise FileNotFoundError("Engines path must be set before requesting downloads")
|
||||
|
||||
thread = threading.Thread(target=download_engine_task, args=(engine, version, system_os, cpu),
|
||||
name=f'{engine}-{version}-{system_os}-{cpu}')
|
||||
thread = EngineDownloadWorker(engine, version, system_os, cpu)
|
||||
cls.download_tasks.append(thread)
|
||||
thread.start()
|
||||
|
||||
@@ -244,13 +235,37 @@ class EngineManager:
|
||||
@classmethod
|
||||
def engine_for_project_path(cls, path):
|
||||
name, extension = os.path.splitext(path)
|
||||
extension = extension.lower().strip('.')
|
||||
for engine in cls.supported_engines():
|
||||
if extension in engine.supported_extensions:
|
||||
if extension in engine.supported_extensions():
|
||||
return engine
|
||||
undefined_renderer_support = [x for x in cls.supported_engines() if not x.supported_extensions]
|
||||
undefined_renderer_support = [x for x in cls.supported_engines() if not x.supported_extensions()]
|
||||
return undefined_renderer_support[0]
|
||||
|
||||
|
||||
class EngineDownloadWorker(threading.Thread):
|
||||
def __init__(self, engine, version, system_os=None, cpu=None):
|
||||
super().__init__()
|
||||
self.engine = engine
|
||||
self.version = version
|
||||
self.system_os = system_os
|
||||
self.cpu = cpu
|
||||
|
||||
def run(self):
|
||||
existing_download = EngineManager.is_version_downloaded(self.engine, self.version, self.system_os, self.cpu)
|
||||
if existing_download:
|
||||
logger.info(f"Requested download of {self.engine} {self.version}, but local copy already exists")
|
||||
return existing_download
|
||||
|
||||
# Get the appropriate downloader class based on the engine type
|
||||
EngineManager.engine_with_name(self.engine).downloader().download_engine(
|
||||
self.version, download_location=EngineManager.engines_path, system_os=self.system_os, cpu=self.cpu,
|
||||
timeout=300)
|
||||
|
||||
# remove itself from the downloader list
|
||||
EngineManager.download_tasks.remove(self)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
|
||||
|
||||
@@ -18,6 +18,20 @@ class FFMPEG(BaseRenderEngine):
|
||||
from src.engines.ffmpeg.ffmpeg_worker import FFMPEGRenderWorker
|
||||
return FFMPEGRenderWorker
|
||||
|
||||
def ui_options(self):
|
||||
from src.engines.ffmpeg.ffmpeg_ui import FFMPEGUI
|
||||
return FFMPEGUI.get_options(self)
|
||||
|
||||
@classmethod
|
||||
def supported_extensions(cls):
|
||||
help_text = (subprocess.check_output([cls().renderer_path(), '-h', 'full'], stderr=subprocess.STDOUT)
|
||||
.decode('utf-8'))
|
||||
found = re.findall('extensions that .* is allowed to access \(default "(.*)"', help_text)
|
||||
found_extensions = set()
|
||||
for match in found:
|
||||
found_extensions.update(match.split(','))
|
||||
return list(found_extensions)
|
||||
|
||||
def version(self):
|
||||
version = None
|
||||
try:
|
||||
@@ -31,15 +45,11 @@ class FFMPEG(BaseRenderEngine):
|
||||
return version
|
||||
|
||||
def get_project_info(self, project_path, timeout=10):
|
||||
return self.get_video_info_ffprobe(project_path)
|
||||
|
||||
@staticmethod
|
||||
def get_video_info_ffprobe(video_path):
|
||||
try:
|
||||
# Run ffprobe and parse the output as JSON
|
||||
cmd = [
|
||||
'ffprobe', '-v', 'quiet', '-print_format', 'json',
|
||||
'-show_streams', '-select_streams', 'v', video_path
|
||||
'-show_streams', '-select_streams', 'v', project_path
|
||||
]
|
||||
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
video_info = json.loads(result.stdout)
|
||||
@@ -85,7 +95,7 @@ class FFMPEG(BaseRenderEngine):
|
||||
try:
|
||||
formats_raw = subprocess.check_output([self.renderer_path(), '-formats'], stderr=subprocess.DEVNULL,
|
||||
timeout=SUBPROCESS_TIMEOUT).decode('utf-8')
|
||||
pattern = '(?P<type>[DE]{1,2})\s+(?P<id>\S{2,})\s+(?P<name>.*)\r'
|
||||
pattern = '(?P<type>[DE]{1,2})\s+(?P<id>\S{2,})\s+(?P<name>.*)'
|
||||
all_formats = [m.groupdict() for m in re.finditer(pattern, formats_raw)]
|
||||
return all_formats
|
||||
except Exception as e:
|
||||
@@ -105,7 +115,7 @@ class FFMPEG(BaseRenderEngine):
|
||||
return found_extensions
|
||||
|
||||
def get_output_formats(self):
|
||||
return [x for x in self.get_all_formats() if 'E' in x['type'].upper()]
|
||||
return [x['id'] for x in self.get_all_formats() if 'E' in x['type'].upper()]
|
||||
|
||||
def get_frame_count(self, path_to_file):
|
||||
raw_stdout = subprocess.check_output([self.renderer_path(), '-i', path_to_file, '-map', '0:v:0', '-c', 'copy',
|
||||
@@ -117,7 +127,8 @@ class FFMPEG(BaseRenderEngine):
|
||||
return frame_number
|
||||
|
||||
def get_arguments(self):
|
||||
help_text = subprocess.check_output([self.renderer_path(), '-h', 'long'], stderr=subprocess.STDOUT).decode('utf-8')
|
||||
help_text = (subprocess.check_output([self.renderer_path(), '-h', 'long'], stderr=subprocess.STDOUT)
|
||||
.decode('utf-8'))
|
||||
lines = help_text.splitlines()
|
||||
|
||||
options = {}
|
||||
|
||||
5
src/engines/ffmpeg/ffmpeg_ui.py
Normal file
5
src/engines/ffmpeg/ffmpeg_ui.py
Normal file
@@ -0,0 +1,5 @@
|
||||
class FFMPEGUI:
|
||||
@staticmethod
|
||||
def get_options(instance):
|
||||
options = []
|
||||
return options
|
||||
@@ -10,15 +10,9 @@ class FFMPEGRenderWorker(BaseRenderWorker):
|
||||
|
||||
engine = FFMPEG
|
||||
|
||||
def __init__(self, input_path, output_path, args=None, parent=None, name=None):
|
||||
super(FFMPEGRenderWorker, self).__init__(input_path=input_path, output_path=output_path, args=args,
|
||||
parent=parent, name=name)
|
||||
|
||||
stream_info = subprocess.check_output([self.renderer_path, "-i", # https://stackoverflow.com/a/61604105
|
||||
input_path, "-map", "0:v:0", "-c", "copy", "-f", "null", "-y",
|
||||
"/dev/null"], stderr=subprocess.STDOUT).decode('utf-8')
|
||||
found_frames = re.findall('frame=\s*(\d+)', stream_info)
|
||||
self.project_length = found_frames[-1] if found_frames else '-1'
|
||||
def __init__(self, input_path, output_path, engine_path, args=None, parent=None, name=None):
|
||||
super(FFMPEGRenderWorker, self).__init__(input_path=input_path, output_path=output_path,
|
||||
engine_path=engine_path, args=args, parent=parent, name=name)
|
||||
self.current_frame = -1
|
||||
|
||||
def generate_worker_subprocess(self):
|
||||
|
||||
@@ -5,7 +5,7 @@ import socket
|
||||
import threading
|
||||
|
||||
import psutil
|
||||
from PyQt6.QtCore import QThread, pyqtSignal, Qt
|
||||
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
|
||||
@@ -21,10 +21,11 @@ from src.utilities.zeroconf_server import ZeroconfServer
|
||||
class NewRenderJobForm(QWidget):
|
||||
def __init__(self, project_path=None):
|
||||
super().__init__()
|
||||
|
||||
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.renderer_options_layout = None
|
||||
@@ -73,41 +74,41 @@ class NewRenderJobForm(QWidget):
|
||||
# Main Layout
|
||||
main_layout = QVBoxLayout(self)
|
||||
|
||||
# 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_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)
|
||||
scene_file_layout.addLayout(scene_file_picker_layout)
|
||||
# Loading File Group
|
||||
self.load_file_group = QGroupBox("Loading")
|
||||
load_file_layout = QVBoxLayout(self.load_file_group)
|
||||
# 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)
|
||||
load_file_layout.addLayout(progress_layout)
|
||||
main_layout.addWidget(self.load_file_group)
|
||||
|
||||
# Server Group
|
||||
# Project Group
|
||||
self.project_group = QGroupBox("Project")
|
||||
server_layout = QVBoxLayout(self.project_group)
|
||||
# File Path
|
||||
scene_file_picker_layout = QHBoxLayout()
|
||||
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(QLabel("File:"))
|
||||
scene_file_picker_layout.addWidget(self.scene_file_input)
|
||||
scene_file_picker_layout.addWidget(self.scene_file_browse_button)
|
||||
server_layout.addLayout(scene_file_picker_layout)
|
||||
# Server List
|
||||
self.server_group = QGroupBox("Server")
|
||||
server_layout = QVBoxLayout(self.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(self.server_group)
|
||||
main_layout.addWidget(self.project_group)
|
||||
self.update_server_list()
|
||||
# Priority
|
||||
priority_layout = QHBoxLayout()
|
||||
@@ -234,8 +235,13 @@ class NewRenderJobForm(QWidget):
|
||||
self.toggle_renderer_enablement(False)
|
||||
|
||||
def update_renderer_info(self):
|
||||
# get the renderer info and add them all to the ui
|
||||
self.renderer_info = self.server_proxy.get_renderer_info()
|
||||
self.renderer_type.addItems(self.renderer_info.keys())
|
||||
# select the best renderer for the file type
|
||||
engine = EngineManager.engine_for_project_path(self.project_path)
|
||||
self.renderer_type.setCurrentText(engine.name().lower())
|
||||
# refresh ui
|
||||
self.renderer_changed()
|
||||
|
||||
def renderer_changed(self):
|
||||
@@ -294,15 +300,18 @@ class NewRenderJobForm(QWidget):
|
||||
# Set the best renderer we can find
|
||||
input_path = self.scene_file_input.text()
|
||||
engine = EngineManager.engine_for_project_path(input_path)
|
||||
index = self.renderer_type.findText(engine.name().lower())
|
||||
if index >= 0:
|
||||
self.renderer_type.setCurrentIndex(index)
|
||||
|
||||
engine_index = self.renderer_type.findText(engine.name().lower())
|
||||
if engine_index >= 0:
|
||||
self.renderer_type.setCurrentIndex(engine_index)
|
||||
else:
|
||||
self.renderer_type.setCurrentIndex(0) #todo: find out why we don't have renderer info yet
|
||||
# not ideal but if we don't have the renderer info we have to pick something
|
||||
|
||||
self.output_path_input.setText(os.path.basename(input_path))
|
||||
|
||||
# cleanup progress UI
|
||||
self.process_progress_bar.setHidden(True)
|
||||
self.process_label.setHidden(True)
|
||||
self.load_file_group.setHidden(True)
|
||||
self.toggle_renderer_enablement(True)
|
||||
|
||||
# Load scene data
|
||||
@@ -331,12 +340,9 @@ class NewRenderJobForm(QWidget):
|
||||
self.cameras_group.setHidden(True)
|
||||
|
||||
# Dynamic Engine Options
|
||||
engine_name = self.renderer_type.currentText()
|
||||
engine = EngineManager.engine_with_name(engine_name)
|
||||
# clear old options
|
||||
clear_layout(self.renderer_options_layout)
|
||||
clear_layout(self.renderer_options_layout) # clear old options
|
||||
# dynamically populate option list
|
||||
self.current_engine_options = engine().get_options()
|
||||
self.current_engine_options = engine().ui_options()
|
||||
for option in self.current_engine_options:
|
||||
h_layout = QHBoxLayout()
|
||||
label = QLabel(option['name'].capitalize() + ':')
|
||||
@@ -355,7 +361,7 @@ class NewRenderJobForm(QWidget):
|
||||
|
||||
def toggle_renderer_enablement(self, enabled=False):
|
||||
"""Toggle on/off all the render settings"""
|
||||
self.server_group.setHidden(not enabled)
|
||||
self.project_group.setHidden(not enabled)
|
||||
self.output_settings_group.setHidden(not enabled)
|
||||
self.renderer_group.setHidden(not enabled)
|
||||
self.notes_group.setHidden(not enabled)
|
||||
@@ -402,14 +408,23 @@ class NewRenderJobForm(QWidget):
|
||||
|
||||
# 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(Response)
|
||||
update_ui_signal = pyqtSignal(str, str)
|
||||
|
||||
def __init__(self, window):
|
||||
super().__init__()
|
||||
@@ -421,10 +436,7 @@ class SubmitWorker(QThread):
|
||||
|
||||
def callback(monitor):
|
||||
percent = f"{monitor.bytes_read / encoder_len * 100:.0f}"
|
||||
self.window.submit_progress_label.setText(f"Transferring to {hostname} - {percent}%")
|
||||
self.window.submit_progress.setMaximum(100)
|
||||
self.window.submit_progress.setValue(int(percent))
|
||||
|
||||
self.update_ui_signal.emit(hostname, percent)
|
||||
return callback
|
||||
|
||||
hostname = self.window.server_input.currentText()
|
||||
|
||||
@@ -4,6 +4,7 @@ import subprocess
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from PyQt6.QtCore import QTimer
|
||||
from PyQt6.QtWidgets import (
|
||||
QMainWindow, QWidget, QVBoxLayout, QPushButton, QTableWidget, QTableWidgetItem, QHBoxLayout, QAbstractItemView,
|
||||
QHeaderView, QProgressBar, QLabel, QMessageBox
|
||||
@@ -28,6 +29,7 @@ class EngineBrowserWindow(QMainWindow):
|
||||
self.setGeometry(100, 100, 500, 300)
|
||||
self.engine_data = []
|
||||
self.initUI()
|
||||
self.init_timer()
|
||||
|
||||
def initUI(self):
|
||||
# Central widget
|
||||
@@ -82,6 +84,12 @@ class EngineBrowserWindow(QMainWindow):
|
||||
|
||||
self.update_download_status()
|
||||
|
||||
def init_timer(self):
|
||||
# Set up the timer
|
||||
self.timer = QTimer(self)
|
||||
self.timer.timeout.connect(self.update_download_status)
|
||||
self.timer.start(1000)
|
||||
|
||||
def update_table(self):
|
||||
|
||||
def update_table_worker():
|
||||
@@ -124,9 +132,15 @@ class EngineBrowserWindow(QMainWindow):
|
||||
hide_progress = not bool(running_tasks)
|
||||
self.progress_bar.setHidden(hide_progress)
|
||||
self.progress_label.setHidden(hide_progress)
|
||||
|
||||
# todo: update progress bar with status
|
||||
self.progress_label.setText(f"Downloading {len(running_tasks)} engines")
|
||||
# Update the status labels
|
||||
if len(EngineManager.download_tasks) == 0:
|
||||
new_status = ""
|
||||
elif len(EngineManager.download_tasks) == 1:
|
||||
task = EngineManager.download_tasks[0]
|
||||
new_status = f"Downloading {task.engine.capitalize()} {task.version}..."
|
||||
else:
|
||||
new_status = f"Downloading {len(EngineManager.download_tasks)} engines..."
|
||||
self.progress_label.setText(new_status)
|
||||
|
||||
def launch_button_click(self):
|
||||
engine_info = self.engine_data[self.table_widget.currentRow()]
|
||||
|
||||
@@ -477,7 +477,7 @@ class MainWindow(QMainWindow):
|
||||
"""
|
||||
selected_job_ids = self.selected_job_ids()
|
||||
if selected_job_ids:
|
||||
url = f'http://{self.current_server_proxy.hostname}:{self.current_server_proxy.port}/api/job/{selected_job_ids[0]}/logs'
|
||||
url = f'http://{self.current_server_proxy.optimized_hostname()}:{self.current_server_proxy.port}/api/job/{selected_job_ids[0]}/logs'
|
||||
self.log_viewer_window = LogViewer(url)
|
||||
self.log_viewer_window.show()
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from PyQt6.QtGui import QPixmap
|
||||
from PyQt6.QtWidgets import QStatusBar, QLabel
|
||||
|
||||
from src.api.server_proxy import RenderServerProxy
|
||||
from src.engines.engine_manager import EngineManager
|
||||
from src.utilities.misc_helper import resources_dir
|
||||
|
||||
|
||||
@@ -28,17 +29,23 @@ class StatusBar(QStatusBar):
|
||||
proxy = RenderServerProxy(socket.gethostname())
|
||||
proxy.start_background_update()
|
||||
image_names = {'Ready': 'GreenCircle.png', 'Offline': "RedSquare.png"}
|
||||
last_update = None
|
||||
|
||||
# Check for status change every 1s on background thread
|
||||
while True:
|
||||
new_status = proxy.status()
|
||||
if new_status is not last_update:
|
||||
new_image_name = image_names.get(new_status, 'Synchronize.png')
|
||||
image_path = os.path.join(resources_dir(), 'icons', new_image_name)
|
||||
self.label.setPixmap((QPixmap(image_path).scaled(16, 16, Qt.AspectRatioMode.KeepAspectRatio)))
|
||||
self.messageLabel.setText(new_status)
|
||||
last_update = new_status
|
||||
new_image_name = image_names.get(new_status, 'Synchronize.png')
|
||||
image_path = os.path.join(resources_dir(), 'icons', new_image_name)
|
||||
self.label.setPixmap((QPixmap(image_path).scaled(16, 16, Qt.AspectRatioMode.KeepAspectRatio)))
|
||||
|
||||
# add download status
|
||||
if EngineManager.download_tasks:
|
||||
if len(EngineManager.download_tasks) == 1:
|
||||
task = EngineManager.download_tasks[0]
|
||||
new_status = f"{new_status} | Downloading {task.engine.capitalize()} {task.version}..."
|
||||
else:
|
||||
new_status = f"{new_status} | Downloading {len(EngineManager.download_tasks)} engines"
|
||||
|
||||
self.messageLabel.setText(new_status)
|
||||
time.sleep(1)
|
||||
|
||||
background_thread = threading.Thread(target=background_update,)
|
||||
|
||||
Reference in New Issue
Block a user