mirror of
https://github.com/blw1138/Zordon.git
synced 2025-12-17 08:48:13 +00:00
Compare commits
14 Commits
v0.0.1-dev
...
feature/84
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a76b0340f9 | ||
|
|
f9c114bf32 | ||
|
|
dad9b8c250 | ||
|
|
8826382f86 | ||
|
|
58822c4a20 | ||
|
|
dc7f3877b2 | ||
|
|
b1280ad445 | ||
|
|
0cebb93ba2 | ||
|
|
a2785400ac | ||
|
|
d9201b5082 | ||
|
|
7d633d97c2 | ||
|
|
e6e2ff8e07 | ||
|
|
7986960b21 | ||
|
|
ebb847b09e |
279
dashboard.py
279
dashboard.py
@@ -1,279 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import datetime
|
|
||||||
import os.path
|
|
||||||
import socket
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from rich import box
|
|
||||||
from rich.console import Console
|
|
||||||
from rich.layout import Layout
|
|
||||||
from rich.live import Live
|
|
||||||
from rich.panel import Panel
|
|
||||||
from rich.table import Column
|
|
||||||
from rich.table import Table
|
|
||||||
from rich.text import Text
|
|
||||||
from rich.tree import Tree
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
"""
|
|
||||||
The RenderDashboard is designed to be run on a remote machine or on the local server
|
|
||||||
This provides a detailed status of all jobs running on the server
|
|
||||||
"""
|
|
||||||
|
|
||||||
status_colors = {RenderStatus.ERROR: "red", RenderStatus.CANCELLED: 'orange1', RenderStatus.COMPLETED: 'green',
|
|
||||||
RenderStatus.NOT_STARTED: "yellow", RenderStatus.SCHEDULED: 'purple',
|
|
||||||
RenderStatus.RUNNING: 'cyan'}
|
|
||||||
|
|
||||||
categories = [RenderStatus.RUNNING, RenderStatus.ERROR, RenderStatus.NOT_STARTED, RenderStatus.SCHEDULED,
|
|
||||||
RenderStatus.COMPLETED, RenderStatus.CANCELLED, RenderStatus.UNDEFINED]
|
|
||||||
|
|
||||||
renderer_colors = {'ffmpeg': '[magenta]', 'blender': '[orange1]', 'aerender': '[purple]'}
|
|
||||||
|
|
||||||
local_hostname = socket.gethostname()
|
|
||||||
|
|
||||||
|
|
||||||
def status_string_to_color(status_string):
|
|
||||||
job_status = string_to_status(status_string)
|
|
||||||
job_color = '[{}]'.format(status_colors[job_status])
|
|
||||||
return job_color
|
|
||||||
|
|
||||||
|
|
||||||
def sorted_jobs(all_jobs):
|
|
||||||
|
|
||||||
sort_by_date = True
|
|
||||||
if not sort_by_date:
|
|
||||||
sorted_job_list = []
|
|
||||||
if all_jobs:
|
|
||||||
for status_category in categories:
|
|
||||||
found_jobs = [x for x in all_jobs if x['status'] == status_category.value]
|
|
||||||
if found_jobs:
|
|
||||||
sorted_found_jobs = sorted(found_jobs, key=lambda d: datetime.datetime.fromisoformat(d['date_created']), reverse=True)
|
|
||||||
sorted_job_list.extend(sorted_found_jobs)
|
|
||||||
else:
|
|
||||||
sorted_job_list = sorted(all_jobs, key=lambda d: datetime.datetime.fromisoformat(d['date_created']), reverse=True)
|
|
||||||
return sorted_job_list
|
|
||||||
|
|
||||||
|
|
||||||
def create_node_tree(all_server_data) -> Tree:
|
|
||||||
main_tree = Tree("[magenta]Server Cluster")
|
|
||||||
|
|
||||||
for server_host, server_data in all_server_data['servers'].items():
|
|
||||||
|
|
||||||
node_title_local = f"[cyan bold]{server_host}[/] [yellow](This Computer)[default]"
|
|
||||||
node_title_remote = f"[cyan]{server_host} [magenta](Remote)[default]"
|
|
||||||
node_tree_text = node_title_local if (server_host == local_hostname) else node_title_remote
|
|
||||||
|
|
||||||
if server_data.get('is_online', False):
|
|
||||||
|
|
||||||
node_tree_text = node_tree_text + " - [green]Running"
|
|
||||||
node_tree = Tree(node_tree_text)
|
|
||||||
|
|
||||||
stats_text = f"CPU: [yellow]{server_data['status']['cpu_percent']}% [default]| RAM: " \
|
|
||||||
f"[yellow]{server_data['status']['memory_percent']}% [default]| Cores: " \
|
|
||||||
f"[yellow]{server_data['status']['cpu_count']} [default]| " \
|
|
||||||
f"{server_data['status']['platform'].split('-')[0]}"
|
|
||||||
|
|
||||||
node_tree.add(Tree(stats_text))
|
|
||||||
|
|
||||||
running_jobs = [job for job in server_data['jobs'] if job['status'] == RenderStatus.RUNNING.value]
|
|
||||||
not_started = [job for job in server_data['jobs'] if job['status'] == RenderStatus.NOT_STARTED.value]
|
|
||||||
scheduled = [job for job in server_data['jobs'] if job['status'] == RenderStatus.SCHEDULED.value]
|
|
||||||
jobs_to_display = running_jobs + not_started + scheduled
|
|
||||||
|
|
||||||
jobs_tree = Tree(f"Running: [green]{len(running_jobs)} [default]| Queued: [cyan]{len(not_started)}"
|
|
||||||
f"[default] | Scheduled: [cyan]{len(scheduled)}")
|
|
||||||
|
|
||||||
for job in jobs_to_display:
|
|
||||||
renderer = f"{renderer_colors[job['renderer']]}{job['renderer']}[default]"
|
|
||||||
filename = os.path.basename(job['input_path']).split('.')[0]
|
|
||||||
if job['status'] == RenderStatus.RUNNING.value:
|
|
||||||
jobs_tree.add(f"[bold]{renderer} {filename} ({job['id']}) - {status_string_to_color(job['status'])}{(float(job['percent_complete']) * 100):.1f}%")
|
|
||||||
else:
|
|
||||||
jobs_tree.add(f"{filename} ({job['id']}) - {status_string_to_color(job['status'])}{job['status'].title()}")
|
|
||||||
|
|
||||||
if not jobs_to_display:
|
|
||||||
jobs_tree.add("[italic]No running jobs")
|
|
||||||
|
|
||||||
node_tree.add(jobs_tree)
|
|
||||||
main_tree.add(node_tree)
|
|
||||||
else:
|
|
||||||
# if server is offline
|
|
||||||
node_tree_text = node_tree_text + " - [red]Offline"
|
|
||||||
node_tree = Tree(node_tree_text)
|
|
||||||
main_tree.add(node_tree)
|
|
||||||
return main_tree
|
|
||||||
|
|
||||||
|
|
||||||
def create_jobs_table(all_server_data) -> Table:
|
|
||||||
table = Table("ID", "Name", "Renderer", Column(header="Priority", justify="center"),
|
|
||||||
Column(header="Status", justify="center"), Column(header="Time Elapsed", justify="right"),
|
|
||||||
Column(header="# Frames", justify="right"), "Client", show_lines=True,
|
|
||||||
box=box.HEAVY_HEAD)
|
|
||||||
|
|
||||||
all_jobs = []
|
|
||||||
for server_name, server_data in all_server_data['servers'].items():
|
|
||||||
for job in server_data['jobs']:
|
|
||||||
#todo: clean this up
|
|
||||||
all_jobs.append(job)
|
|
||||||
|
|
||||||
all_jobs = sorted_jobs(all_jobs)
|
|
||||||
|
|
||||||
for job in all_jobs:
|
|
||||||
|
|
||||||
job_status = string_to_status(job['status'])
|
|
||||||
job_color = '[{}]'.format(status_colors[job_status])
|
|
||||||
job_text = f"{job_color}" + job_status.value.title()
|
|
||||||
|
|
||||||
if job_status == RenderStatus.ERROR and job['errors']:
|
|
||||||
job_text = job_text + "\n" + "\n".join(job['errors'])
|
|
||||||
|
|
||||||
# Project name
|
|
||||||
project_name = job_color + (job['name'] or os.path.basename(job['input_path']))
|
|
||||||
elapsed_time = get_time_elapsed(datetime.datetime.fromisoformat(job['start_time']),
|
|
||||||
datetime.datetime.fromisoformat(job['end_time']))
|
|
||||||
|
|
||||||
if job_status == RenderStatus.RUNNING:
|
|
||||||
job_text = f"{job_color}[bold]Running - {float(job['percent_complete']) * 100:.1f}%"
|
|
||||||
elapsed_time = "[bold]" + elapsed_time
|
|
||||||
project_name = "[bold]" + project_name
|
|
||||||
elif job_status == RenderStatus.CANCELLED or job_status == RenderStatus.ERROR:
|
|
||||||
project_name = "[strike]" + project_name
|
|
||||||
|
|
||||||
# Priority
|
|
||||||
priority_color = ["red", "yellow", "cyan"][(job['priority'] - 1)]
|
|
||||||
|
|
||||||
client_name = job['client'] or 'unknown'
|
|
||||||
client_colors = {'unknown': '[red]', local_hostname: '[yellow]'}
|
|
||||||
client_title = client_colors.get(client_name, '[magenta]') + client_name
|
|
||||||
|
|
||||||
table.add_row(
|
|
||||||
job['id'],
|
|
||||||
project_name,
|
|
||||||
renderer_colors.get(job['renderer'], '[cyan]') + job['renderer'] + '[default]-' + job['renderer_version'],
|
|
||||||
f"[{priority_color}]{job['priority']}",
|
|
||||||
job_text,
|
|
||||||
elapsed_time,
|
|
||||||
str(max(int(job['total_frames']), 1)),
|
|
||||||
client_title
|
|
||||||
)
|
|
||||||
|
|
||||||
return table
|
|
||||||
|
|
||||||
|
|
||||||
def create_status_panel(all_server_data):
|
|
||||||
for key, value in all_server_data['servers'].items():
|
|
||||||
if key == local_hostname:
|
|
||||||
return str(value['status'])
|
|
||||||
return "no status"
|
|
||||||
|
|
||||||
|
|
||||||
class KeyboardThread(threading.Thread):
|
|
||||||
|
|
||||||
def __init__(self, input_cbk = None, name='keyboard-input-thread'):
|
|
||||||
self.input_cbk = input_cbk
|
|
||||||
super(KeyboardThread, self).__init__(name=name)
|
|
||||||
self.start()
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
while True:
|
|
||||||
self.input_cbk(input()) #waits to get input + Return
|
|
||||||
|
|
||||||
|
|
||||||
def my_callback(inp):
|
|
||||||
#evaluate the keyboard input
|
|
||||||
print('You Entered:', inp)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
|
||||||
get_server_ip = input("Enter server IP or None for local: ") or local_hostname
|
|
||||||
|
|
||||||
server_proxy = RenderServerProxy(get_server_ip, "8080")
|
|
||||||
|
|
||||||
if not server_proxy.connect():
|
|
||||||
if server_proxy.hostname == local_hostname:
|
|
||||||
start_server_input = input("Local server not running. Start server? (y/n) ")
|
|
||||||
if start_server_input and start_server_input[0].lower() == "y":
|
|
||||||
# Startup the local server
|
|
||||||
start_server()
|
|
||||||
test = server_proxy.connect()
|
|
||||||
print(f"connected? {test}")
|
|
||||||
else:
|
|
||||||
print(f"\nUnable to connect to server: {server_proxy.hostname}")
|
|
||||||
print("\nVerify IP address is correct and server is running")
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
# start the Keyboard thread
|
|
||||||
# kthread = KeyboardThread(my_callback)
|
|
||||||
|
|
||||||
# Console Layout
|
|
||||||
console = Console()
|
|
||||||
layout = Layout()
|
|
||||||
|
|
||||||
# Divide the "screen" in to three parts
|
|
||||||
layout.split(
|
|
||||||
Layout(name="header", size=3),
|
|
||||||
Layout(ratio=1, name="main")
|
|
||||||
# Layout(size=10, name="footer"),
|
|
||||||
)
|
|
||||||
# Divide the "main" layout in to "side" and "body"
|
|
||||||
layout["main"].split_row(
|
|
||||||
Layout(name="side"),
|
|
||||||
Layout(name="body",
|
|
||||||
ratio=3))
|
|
||||||
# Divide the "side" layout in to two
|
|
||||||
layout["side"].split(Layout(name="side_top"), Layout(name="side_bottom"))
|
|
||||||
|
|
||||||
# Server connection header
|
|
||||||
header_text = Text(f"Connected to server: ")
|
|
||||||
header_text.append(f"{server_proxy.hostname} ", style="green")
|
|
||||||
if server_proxy.hostname == local_hostname:
|
|
||||||
header_text.append("(This Computer)", style="yellow")
|
|
||||||
else:
|
|
||||||
header_text.append("(Remote)", style="magenta")
|
|
||||||
|
|
||||||
# background process to update server data independent of the UI
|
|
||||||
def fetch_server_data(server):
|
|
||||||
while True:
|
|
||||||
fetched_data = server.get_data(timeout=5)
|
|
||||||
if fetched_data:
|
|
||||||
server.fetched_status_data = fetched_data
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
x = threading.Thread(target=fetch_server_data, args=(server_proxy,))
|
|
||||||
x.daemon = True
|
|
||||||
x.start()
|
|
||||||
|
|
||||||
# draw and update the UI
|
|
||||||
with Live(console=console, screen=False, refresh_per_second=1, transient=True) as live:
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
if server_proxy.fetched_status_data:
|
|
||||||
|
|
||||||
server_online = False
|
|
||||||
if server_proxy.fetched_status_data.get('timestamp', None):
|
|
||||||
timestamp = datetime.datetime.fromisoformat(server_proxy.fetched_status_data['timestamp'])
|
|
||||||
time_diff = datetime.datetime.now() - timestamp
|
|
||||||
server_online = time_diff.seconds < 10 # client is offline if not updated in certain time
|
|
||||||
|
|
||||||
layout["body"].update(create_jobs_table(server_proxy.fetched_status_data))
|
|
||||||
layout["side_top"].update(Panel(create_node_tree(server_proxy.fetched_status_data)))
|
|
||||||
layout["side_bottom"].update(Panel(create_status_panel(server_proxy.fetched_status_data)))
|
|
||||||
|
|
||||||
online_text = "Online" if server_online else "Offline"
|
|
||||||
online_color = "green" if server_online else "red"
|
|
||||||
layout["header"].update(Panel(Text(f"Zordon Render Client - Version 0.0.1 alpha - {online_text}",
|
|
||||||
justify="center", style=online_color)))
|
|
||||||
live.update(layout, refresh=False)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Exception updating table: {e}")
|
|
||||||
traceback.print_exception(e)
|
|
||||||
time.sleep(1)
|
|
||||||
# # # todo: Add input prompt to manage running jobs (ie add, cancel, get info, etc)
|
|
||||||
|
|
||||||
8
setup.py
8
setup.py
@@ -11,7 +11,13 @@ from setuptools import setup
|
|||||||
APP = ['main.py']
|
APP = ['main.py']
|
||||||
DATA_FILES = [('config', glob.glob('config/*.*')),
|
DATA_FILES = [('config', glob.glob('config/*.*')),
|
||||||
('resources', glob.glob('resources/*.*'))]
|
('resources', glob.glob('resources/*.*'))]
|
||||||
OPTIONS = {}
|
OPTIONS = {
|
||||||
|
'excludes': ['PySide6'],
|
||||||
|
'includes': ['zeroconf', 'zeroconf._services.info'],
|
||||||
|
'plist': {
|
||||||
|
'LSMinimumSystemVersion': '10.15', # Specify minimum macOS version
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
app=APP,
|
app=APP,
|
||||||
|
|||||||
@@ -463,6 +463,7 @@ def delete_engine_download():
|
|||||||
@server.get('/api/renderer/<renderer>/args')
|
@server.get('/api/renderer/<renderer>/args')
|
||||||
def get_renderer_args(renderer):
|
def get_renderer_args(renderer):
|
||||||
try:
|
try:
|
||||||
|
# todo: possibly deprecate
|
||||||
renderer_engine_class = EngineManager.engine_with_name(renderer)
|
renderer_engine_class = EngineManager.engine_with_name(renderer)
|
||||||
return renderer_engine_class().get_arguments()
|
return renderer_engine_class().get_arguments()
|
||||||
except LookupError:
|
except LookupError:
|
||||||
|
|||||||
@@ -1,22 +1,74 @@
|
|||||||
|
import glob
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from src.engines.core.base_engine import BaseRenderEngine
|
from src.engines.core.base_engine import BaseRenderEngine
|
||||||
|
|
||||||
|
logger = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
class AERender(BaseRenderEngine):
|
class AERender(BaseRenderEngine):
|
||||||
|
|
||||||
supported_extensions = ['.aep']
|
file_extensions = ['aepx']
|
||||||
|
|
||||||
def version(self):
|
def version(self):
|
||||||
version = None
|
version = None
|
||||||
try:
|
try:
|
||||||
render_path = self.renderer_path()
|
render_path = self.renderer_path()
|
||||||
if render_path:
|
if render_path:
|
||||||
ver_out = subprocess.check_output([render_path, '-version'], timeout=SUBPROCESS_TIMEOUT)
|
ver_out = subprocess.run([render_path, '-version'], capture_output=True, text=True)
|
||||||
version = ver_out.decode('utf-8').split(" ")[-1].strip()
|
version = ver_out.stdout.split(" ")[-1].strip()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f'Failed to get {self.name()} version: {e}')
|
logger.error(f'Failed to get {self.name()} version: {e}')
|
||||||
return version
|
return version
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default_renderer_path(cls):
|
||||||
|
paths = glob.glob('/Applications/*After Effects*/aerender')
|
||||||
|
if len(paths) > 1:
|
||||||
|
logger.warning('Multiple After Effects installations detected')
|
||||||
|
elif not paths:
|
||||||
|
logger.error('After Effects installation not found')
|
||||||
|
return paths[0]
|
||||||
|
|
||||||
|
def get_project_info(self, project_path, timeout=10):
|
||||||
|
scene_info = {}
|
||||||
|
try:
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
tree = ET.parse(project_path)
|
||||||
|
root = tree.getroot()
|
||||||
|
namespace = {'ae': 'http://www.adobe.com/products/aftereffects'}
|
||||||
|
|
||||||
|
comp_names = []
|
||||||
|
for item in root.findall(".//ae:Item", namespace):
|
||||||
|
if item.find("ae:Layr", namespace) is not None:
|
||||||
|
for string in item.findall("./ae:string", namespace):
|
||||||
|
comp_names.append(string.text)
|
||||||
|
scene_info['comp_names'] = comp_names
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'Error getting file details for .aepx file: {e}')
|
||||||
|
return scene_info
|
||||||
|
|
||||||
|
def run_javascript(self, script_path, project_path, timeout=None):
|
||||||
|
# todo: implement
|
||||||
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_output_formats(cls):
|
def get_output_formats(cls):
|
||||||
# todo: create implementation
|
# todo: create implementation
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def ui_options(self, project_info):
|
||||||
|
from src.engines.aerender.aerender_ui import AERenderUI
|
||||||
|
return AERenderUI.get_options(self, project_info)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def worker_class(cls):
|
||||||
|
from src.engines.aerender.aerender_worker import AERenderWorker
|
||||||
|
return AERenderWorker
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
x = AERender().get_project_info('/Users/brett/ae_testing/project.aepx')
|
||||||
|
print(x)
|
||||||
|
|||||||
8
src/engines/aerender/aerender_ui.py
Normal file
8
src/engines/aerender/aerender_ui.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
class AERenderUI:
|
||||||
|
@staticmethod
|
||||||
|
def get_options(instance, project_info):
|
||||||
|
options = [
|
||||||
|
{'name': 'comp', 'options': project_info.get('comp_names', [])}
|
||||||
|
]
|
||||||
|
return options
|
||||||
@@ -9,72 +9,39 @@ import time
|
|||||||
from src.engines.core.base_worker import BaseRenderWorker, timecode_to_frames
|
from src.engines.core.base_worker import BaseRenderWorker, timecode_to_frames
|
||||||
from src.engines.aerender.aerender_engine import AERender
|
from src.engines.aerender.aerender_engine import AERender
|
||||||
|
|
||||||
|
logger = logging.getLogger()
|
||||||
def aerender_path():
|
|
||||||
paths = glob.glob('/Applications/*After Effects*/aerender')
|
|
||||||
if len(paths) > 1:
|
|
||||||
logging.warning('Multiple After Effects installations detected')
|
|
||||||
elif not paths:
|
|
||||||
logging.error('After Effects installation not found')
|
|
||||||
else:
|
|
||||||
return paths[0]
|
|
||||||
|
|
||||||
|
|
||||||
class AERenderWorker(BaseRenderWorker):
|
class AERenderWorker(BaseRenderWorker):
|
||||||
|
|
||||||
supported_extensions = ['.aep']
|
|
||||||
engine = AERender
|
engine = AERender
|
||||||
|
|
||||||
def __init__(self, input_path, output_path, args=None, parent=None, name=None):
|
def __init__(self, input_path, output_path, engine_path, args=None, parent=None, name=None):
|
||||||
super(AERenderWorker, self).__init__(input_path=input_path, output_path=output_path, args=args,
|
super(AERenderWorker, self).__init__(input_path=input_path, output_path=output_path, engine_path=engine_path,
|
||||||
parent=parent, name=name)
|
args=args, parent=parent, name=name)
|
||||||
|
|
||||||
self.comp = args.get('comp', None)
|
# temp files for processing stdout
|
||||||
self.render_settings = args.get('render_settings', None)
|
self.__progress_history = []
|
||||||
self.omsettings = args.get('omsettings', None)
|
self.__temp_attributes = {}
|
||||||
|
|
||||||
self.progress = 0
|
|
||||||
self.progress_history = []
|
|
||||||
self.attributes = {}
|
|
||||||
|
|
||||||
def generate_worker_subprocess(self):
|
def generate_worker_subprocess(self):
|
||||||
|
|
||||||
if os.path.exists('nexrender-cli-macos'):
|
comp = self.args.get('comp', 'Comp 1')
|
||||||
logging.info('nexrender found')
|
render_settings = self.args.get('render_settings', None)
|
||||||
# {
|
omsettings = self.args.get('omsettings', None)
|
||||||
# "template": {
|
|
||||||
# "src": String,
|
command = [self.renderer_path, '-project', self.input_path, '-comp', f'"{comp}"']
|
||||||
# "composition": String,
|
|
||||||
#
|
if render_settings:
|
||||||
# "frameStart": Number,
|
command.extend(['-RStemplate', render_settings])
|
||||||
# "frameEnd": Number,
|
|
||||||
# "frameIncrement": Number,
|
if omsettings:
|
||||||
#
|
command.extend(['-OMtemplate', omsettings])
|
||||||
# "continueOnMissing": Boolean,
|
|
||||||
# "settingsTemplate": String,
|
command.extend(['-s', self.start_frame,
|
||||||
# "outputModule": String,
|
'-e', self.end_frame,
|
||||||
# "outputExt": String,
|
'-output', self.output_path])
|
||||||
# },
|
return command
|
||||||
# "assets": [],
|
|
||||||
# "actions": {
|
|
||||||
# "prerender": [],
|
|
||||||
# "postrender": [],
|
|
||||||
# },
|
|
||||||
# "onChange": Function,
|
|
||||||
# "onRenderProgress": Function
|
|
||||||
# }
|
|
||||||
job = {'template':
|
|
||||||
{
|
|
||||||
'src': 'file://' + self.input_path, 'composition': self.comp.replace('"', ''),
|
|
||||||
'settingsTemplate': self.render_settings.replace('"', ''),
|
|
||||||
'outputModule': self.omsettings.replace('"', ''), 'outputExt': 'mov'}
|
|
||||||
}
|
|
||||||
x = ['./nexrender-cli-macos', "'{}'".format(json.dumps(job))]
|
|
||||||
else:
|
|
||||||
logging.info('nexrender not found')
|
|
||||||
x = [aerender_path(), '-project', self.input_path, '-comp', self.comp, '-RStemplate', self.render_settings,
|
|
||||||
'-OMtemplate', self.omsettings, '-output', self.output_path]
|
|
||||||
return x
|
|
||||||
|
|
||||||
def _parse_stdout(self, line):
|
def _parse_stdout(self, line):
|
||||||
|
|
||||||
@@ -83,12 +50,12 @@ class AERenderWorker(BaseRenderWorker):
|
|||||||
# print 'progress'
|
# print 'progress'
|
||||||
trimmed = line.replace('PROGRESS:', '').strip()
|
trimmed = line.replace('PROGRESS:', '').strip()
|
||||||
if len(trimmed):
|
if len(trimmed):
|
||||||
self.progress_history.append(line)
|
self.__progress_history.append(line)
|
||||||
if 'Seconds' in trimmed:
|
if 'Seconds' in trimmed:
|
||||||
self._update_progress(line)
|
self._update_progress(line)
|
||||||
elif ': ' in trimmed:
|
elif ': ' in trimmed:
|
||||||
tmp = trimmed.split(': ')
|
tmp = trimmed.split(': ')
|
||||||
self.attributes[tmp[0].strip()] = tmp[1].strip()
|
self.__temp_attributes[tmp[0].strip()] = tmp[1].strip()
|
||||||
elif line.startswith('WARNING:'):
|
elif line.startswith('WARNING:'):
|
||||||
trimmed = line.replace('WARNING:', '').strip()
|
trimmed = line.replace('WARNING:', '').strip()
|
||||||
self.warnings.append(trimmed)
|
self.warnings.append(trimmed)
|
||||||
@@ -99,28 +66,28 @@ class AERenderWorker(BaseRenderWorker):
|
|||||||
def _update_progress(self, line):
|
def _update_progress(self, line):
|
||||||
|
|
||||||
if not self.total_frames:
|
if not self.total_frames:
|
||||||
duration_string = self.attributes.get('Duration', None)
|
duration_string = self.__temp_attributes.get('Duration', None)
|
||||||
frame_rate = self.attributes.get('Frame Rate', '0').split(' ')[0]
|
frame_rate = self.__temp_attributes.get('Frame Rate', '0').split(' ')[0]
|
||||||
self.total_frames = timecode_to_frames(duration_string.split('Duration:')[-1], float(frame_rate))
|
self.total_frames = timecode_to_frames(duration_string.split('Duration:')[-1], float(frame_rate))
|
||||||
|
|
||||||
match = re.match(r'PROGRESS:.*\((?P<frame>\d+)\): (?P<time>\d+)', line).groupdict()
|
match = re.match(r'PROGRESS:.*\((?P<frame>\d+)\): (?P<time>\d+)', line).groupdict()
|
||||||
self.last_frame = match['frame']
|
self.current_frame = match['frame']
|
||||||
|
|
||||||
def average_frame_duration(self):
|
def average_frame_duration(self):
|
||||||
|
|
||||||
total_durations = 0
|
total_durations = 0
|
||||||
|
|
||||||
for line in self.progress_history:
|
for line in self.__progress_history:
|
||||||
match = re.match(r'PROGRESS:.*\((?P<frame>\d+)\): (?P<time>\d+)', line)
|
match = re.match(r'PROGRESS:.*\((?P<frame>\d+)\): (?P<time>\d+)', line)
|
||||||
if match:
|
if match:
|
||||||
total_durations += int(match.group(2))
|
total_durations += int(match.group(2))
|
||||||
|
|
||||||
average = float(total_durations) / self.last_frame
|
average = float(total_durations) / self.current_frame
|
||||||
return average
|
return average
|
||||||
|
|
||||||
def percent_complete(self):
|
def percent_complete(self):
|
||||||
if self.total_frames:
|
if self.total_frames:
|
||||||
return (float(self.last_frame) / float(self.total_frames)) * 100
|
return (float(self.current_frame) / float(self.total_frames)) * 100
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -128,8 +95,11 @@ class AERenderWorker(BaseRenderWorker):
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
logging.basicConfig(format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S', level=logging.DEBUG)
|
logging.basicConfig(format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S', level=logging.DEBUG)
|
||||||
|
|
||||||
r = AERenderWorker('/Users/brett/Desktop/Youtube_Vids/Film_Formats/Frame_Animations.aep', '"Film Pan"',
|
r = AERenderWorker(input_path='/Users/brett/ae_testing/project.aepx',
|
||||||
'"Draft Settings"', '"ProRes"', '/Users/brett/Desktop/test_render')
|
output_path='/Users/brett/ae_testing/project.mp4',
|
||||||
|
engine_path=AERenderWorker.engine.default_renderer_path(),
|
||||||
|
args={'start_frame': 1, 'end_frame': 5})
|
||||||
|
|
||||||
r.start()
|
r.start()
|
||||||
while r.is_running():
|
while r.is_running():
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|||||||
@@ -11,25 +11,22 @@ class Blender(BaseRenderEngine):
|
|||||||
|
|
||||||
install_paths = ['/Applications/Blender.app/Contents/MacOS/Blender']
|
install_paths = ['/Applications/Blender.app/Contents/MacOS/Blender']
|
||||||
binary_names = {'linux': 'blender', 'windows': 'blender.exe', 'macos': 'Blender'}
|
binary_names = {'linux': 'blender', 'windows': 'blender.exe', 'macos': 'Blender'}
|
||||||
|
file_extensions = ['blend']
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def downloader():
|
def downloader():
|
||||||
from src.engines.blender.blender_downloader import BlenderDownloader
|
from src.engines.blender.blender_downloader import BlenderDownloader
|
||||||
return BlenderDownloader
|
return BlenderDownloader
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def worker_class():
|
def worker_class(cls):
|
||||||
from src.engines.blender.blender_worker import BlenderRenderWorker
|
from src.engines.blender.blender_worker import BlenderRenderWorker
|
||||||
return BlenderRenderWorker
|
return BlenderRenderWorker
|
||||||
|
|
||||||
def ui_options(self):
|
def ui_options(self, project_info):
|
||||||
from src.engines.blender.blender_ui import BlenderUI
|
from src.engines.blender.blender_ui import BlenderUI
|
||||||
return BlenderUI.get_options(self)
|
return BlenderUI.get_options(self)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def supported_extensions():
|
|
||||||
return ['blend']
|
|
||||||
|
|
||||||
def version(self):
|
def version(self):
|
||||||
version = None
|
version = None
|
||||||
try:
|
try:
|
||||||
@@ -115,7 +112,7 @@ class Blender(BaseRenderEngine):
|
|||||||
logger.error(f'Error packing .blend file: {e}')
|
logger.error(f'Error packing .blend file: {e}')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_arguments(self):
|
def get_arguments(self): # possibly deprecate
|
||||||
help_text = subprocess.check_output([self.renderer_path(), '-h']).decode('utf-8')
|
help_text = subprocess.check_output([self.renderer_path(), '-h']).decode('utf-8')
|
||||||
lines = help_text.splitlines()
|
lines = help_text.splitlines()
|
||||||
|
|
||||||
|
|||||||
@@ -12,17 +12,12 @@ class BlenderRenderWorker(BaseRenderWorker):
|
|||||||
engine = Blender
|
engine = Blender
|
||||||
|
|
||||||
def __init__(self, input_path, output_path, engine_path, args=None, parent=None, name=None):
|
def __init__(self, input_path, output_path, engine_path, args=None, parent=None, name=None):
|
||||||
super(BlenderRenderWorker, self).__init__(input_path=input_path, output_path=output_path, engine_path=engine_path, args=args, parent=parent, name=name)
|
super(BlenderRenderWorker, self).__init__(input_path=input_path, output_path=output_path,
|
||||||
|
engine_path=engine_path, args=args, parent=parent, name=name)
|
||||||
|
|
||||||
# Stats
|
# Stats
|
||||||
self.__frame_percent_complete = 0.0
|
self.__frame_percent_complete = 0.0
|
||||||
|
self.current_frame = -1 # todo: is this necessary?
|
||||||
# Scene Info
|
|
||||||
self.scene_info = Blender(engine_path).get_project_info(input_path)
|
|
||||||
self.start_frame = int(self.scene_info.get('start_frame', 1))
|
|
||||||
self.end_frame = int(self.scene_info.get('end_frame', self.start_frame))
|
|
||||||
self.project_length = (self.end_frame - self.start_frame) + 1
|
|
||||||
self.current_frame = -1
|
|
||||||
|
|
||||||
def generate_worker_subprocess(self):
|
def generate_worker_subprocess(self):
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ for cam_obj in bpy.data.cameras:
|
|||||||
|
|
||||||
data = {'cameras': cameras,
|
data = {'cameras': cameras,
|
||||||
'engine': scene.render.engine,
|
'engine': scene.render.engine,
|
||||||
'frame_start': scene.frame_start,
|
'start_frame': scene.frame_start,
|
||||||
'frame_end': scene.frame_end,
|
'end_frame': scene.frame_end,
|
||||||
'resolution_x': scene.render.resolution_x,
|
'resolution_x': scene.render.resolution_x,
|
||||||
'resolution_y': scene.render.resolution_y,
|
'resolution_y': scene.render.resolution_y,
|
||||||
'resolution_percentage': scene.render.resolution_percentage,
|
'resolution_percentage': scene.render.resolution_percentage,
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ SUBPROCESS_TIMEOUT = 5
|
|||||||
class BaseRenderEngine(object):
|
class BaseRenderEngine(object):
|
||||||
|
|
||||||
install_paths = []
|
install_paths = []
|
||||||
supported_extensions = []
|
file_extensions = []
|
||||||
|
|
||||||
def __init__(self, custom_path=None):
|
def __init__(self, custom_path=None):
|
||||||
self.custom_renderer_path = custom_path
|
self.custom_renderer_path = custom_path
|
||||||
if not self.renderer_path() or not os.path.exists(self.renderer_path()):
|
if not self.renderer_path() or not os.path.exists(self.renderer_path()):
|
||||||
raise FileNotFoundError(f"Cannot find path to renderer for {self.name()} instance")
|
raise FileNotFoundError(f"Cannot find path ({self.renderer_path()}) for renderer '{self.name()}'")
|
||||||
|
|
||||||
if not os.access(self.renderer_path(), os.X_OK):
|
if not os.access(self.renderer_path(), os.X_OK):
|
||||||
logger.warning(f"Path is not executable. Setting permissions to 755 for {self.renderer_path()}")
|
logger.warning(f"Path is not executable. Setting permissions to 755 for {self.renderer_path()}")
|
||||||
@@ -47,19 +47,18 @@ class BaseRenderEngine(object):
|
|||||||
def downloader(): # override when subclassing if using a downloader class
|
def downloader(): # override when subclassing if using a downloader class
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def worker_class(): # override when subclassing to link worker class
|
def worker_class(cls): # override when subclassing to link worker class
|
||||||
raise NotImplementedError("Worker class not implemented")
|
raise NotImplementedError(f"Worker class not implemented for engine {cls.name()}")
|
||||||
|
|
||||||
def ui_options(self): # override to return options for ui
|
def ui_options(self, project_info): # override to return options for ui
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_help(self): # override if renderer uses different help flag
|
def get_help(self): # override if renderer uses different help flag
|
||||||
path = self.renderer_path()
|
path = self.renderer_path()
|
||||||
if not path:
|
if not path:
|
||||||
raise FileNotFoundError("renderer path not found")
|
raise FileNotFoundError("renderer path not found")
|
||||||
help_doc = subprocess.check_output([path, '-h'], stderr=subprocess.STDOUT,
|
help_doc = subprocess.run([path, '-h'], capture_output=True, text=True).stdout.strip()
|
||||||
timeout=SUBPROCESS_TIMEOUT).decode('utf-8')
|
|
||||||
return help_doc
|
return help_doc
|
||||||
|
|
||||||
def get_project_info(self, project_path, timeout=10):
|
def get_project_info(self, project_path, timeout=10):
|
||||||
@@ -69,6 +68,10 @@ class BaseRenderEngine(object):
|
|||||||
def get_output_formats(cls):
|
def get_output_formats(cls):
|
||||||
raise NotImplementedError(f"get_output_formats not implemented for {cls.__name__}")
|
raise NotImplementedError(f"get_output_formats not implemented for {cls.__name__}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def supported_extensions(cls):
|
||||||
|
return cls.file_extensions
|
||||||
|
|
||||||
def get_arguments(self):
|
def get_arguments(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -81,8 +81,11 @@ class BaseRenderWorker(Base):
|
|||||||
# Frame Ranges
|
# Frame Ranges
|
||||||
self.project_length = 0 # is this necessary?
|
self.project_length = 0 # is this necessary?
|
||||||
self.current_frame = 0
|
self.current_frame = 0
|
||||||
self.start_frame = 0
|
|
||||||
self.end_frame = None
|
# Get Project Info
|
||||||
|
self.scene_info = self.engine(engine_path).get_project_info(project_path=input_path)
|
||||||
|
self.start_frame = int(self.scene_info.get('start_frame', 1))
|
||||||
|
self.end_frame = int(self.scene_info.get('end_frame', self.start_frame))
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
self.start_time = None
|
self.start_time = None
|
||||||
@@ -192,7 +195,7 @@ class BaseRenderWorker(Base):
|
|||||||
|
|
||||||
f.write(f"{self.start_time.isoformat()} - Starting {self.engine.name()} {self.renderer_version} "
|
f.write(f"{self.start_time.isoformat()} - Starting {self.engine.name()} {self.renderer_version} "
|
||||||
f"render for {self.input_path}\n\n")
|
f"render for {self.input_path}\n\n")
|
||||||
f.write(f"Running command: {subprocess_cmds}\n")
|
f.write(f"Running command: \"{' '.join(subprocess_cmds)}\"\n")
|
||||||
f.write('=' * 80 + '\n\n')
|
f.write('=' * 80 + '\n\n')
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@@ -207,9 +210,9 @@ class BaseRenderWorker(Base):
|
|||||||
else:
|
else:
|
||||||
f.write(f'\n{"=" * 20} Attempt #{failed_attempts + 1} {"=" * 20}\n\n')
|
f.write(f'\n{"=" * 20} Attempt #{failed_attempts + 1} {"=" * 20}\n\n')
|
||||||
logger.warning(f"Restarting render - Attempt #{failed_attempts + 1}")
|
logger.warning(f"Restarting render - Attempt #{failed_attempts + 1}")
|
||||||
self.status = RenderStatus.RUNNING
|
|
||||||
|
|
||||||
# Start process and get updates
|
# Start process and get updates
|
||||||
|
self.status = RenderStatus.RUNNING
|
||||||
self.__process = subprocess.Popen(subprocess_cmds, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
self.__process = subprocess.Popen(subprocess_cmds, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||||
universal_newlines=False)
|
universal_newlines=False)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import concurrent.futures
|
|||||||
|
|
||||||
from src.engines.blender.blender_engine import Blender
|
from src.engines.blender.blender_engine import Blender
|
||||||
from src.engines.ffmpeg.ffmpeg_engine import FFMPEG
|
from src.engines.ffmpeg.ffmpeg_engine import FFMPEG
|
||||||
|
from src.engines.aerender.aerender_engine import AERender
|
||||||
from src.utilities.misc_helper import system_safe_path, current_system_os, current_system_cpu
|
from src.utilities.misc_helper import system_safe_path, current_system_os, current_system_cpu
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
@@ -18,7 +19,7 @@ class EngineManager:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def supported_engines():
|
def supported_engines():
|
||||||
return [Blender, FFMPEG]
|
return [Blender, FFMPEG, AERender]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def engine_with_name(cls, engine_name):
|
def engine_with_name(cls, engine_name):
|
||||||
@@ -79,17 +80,20 @@ class EngineManager:
|
|||||||
'type': 'system'
|
'type': 'system'
|
||||||
}
|
}
|
||||||
|
|
||||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
if not filter_name:
|
||||||
futures = {
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||||
executor.submit(fetch_engine_details, eng): eng.name()
|
futures = {
|
||||||
for eng in cls.supported_engines()
|
executor.submit(fetch_engine_details, eng): eng.name()
|
||||||
if eng.default_renderer_path() and (not filter_name or filter_name == eng.name())
|
for eng in cls.supported_engines()
|
||||||
}
|
if eng.default_renderer_path()
|
||||||
|
}
|
||||||
|
|
||||||
for future in concurrent.futures.as_completed(futures):
|
for future in concurrent.futures.as_completed(futures):
|
||||||
result = future.result()
|
result = future.result()
|
||||||
if result:
|
if result:
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
else:
|
||||||
|
results.append(fetch_engine_details(cls.engine_with_name(filter_name)))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@@ -294,6 +298,6 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
# print(EngineManager.newest_engine_version('blender', 'macos', 'arm64'))
|
# print(EngineManager.newest_engine_version('blender', 'macos', 'arm64'))
|
||||||
# EngineManager.delete_engine_download('blender', '3.2.1', 'macos', 'a')
|
# EngineManager.delete_engine_download('blender', '3.2.1', 'macos', 'a')
|
||||||
EngineManager.engines_path = "/Users/brettwilliams/zordon-uploads/engines"
|
EngineManager.engines_path = "/Users/brettwilliams/zordon-uploads/engines/"
|
||||||
# print(EngineManager.is_version_downloaded("ffmpeg", "6.0"))
|
# print(EngineManager.is_version_downloaded("ffmpeg", "6.0"))
|
||||||
print(EngineManager.get_engines())
|
print(EngineManager.get_engines())
|
||||||
|
|||||||
@@ -12,24 +12,26 @@ class FFMPEG(BaseRenderEngine):
|
|||||||
from src.engines.ffmpeg.ffmpeg_downloader import FFMPEGDownloader
|
from src.engines.ffmpeg.ffmpeg_downloader import FFMPEGDownloader
|
||||||
return FFMPEGDownloader
|
return FFMPEGDownloader
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def worker_class():
|
def worker_class(cls):
|
||||||
from src.engines.ffmpeg.ffmpeg_worker import FFMPEGRenderWorker
|
from src.engines.ffmpeg.ffmpeg_worker import FFMPEGRenderWorker
|
||||||
return FFMPEGRenderWorker
|
return FFMPEGRenderWorker
|
||||||
|
|
||||||
def ui_options(self):
|
def ui_options(self, project_info):
|
||||||
from src.engines.ffmpeg.ffmpeg_ui import FFMPEGUI
|
from src.engines.ffmpeg.ffmpeg_ui import FFMPEGUI
|
||||||
return FFMPEGUI.get_options(self)
|
return FFMPEGUI.get_options(self, project_info)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def supported_extensions(cls):
|
def supported_extensions(cls):
|
||||||
help_text = (subprocess.check_output([cls().renderer_path(), '-h', 'full'], stderr=subprocess.STDOUT)
|
if not cls.file_extensions:
|
||||||
.decode('utf-8'))
|
help_text = (subprocess.check_output([cls().renderer_path(), '-h', 'full'], stderr=subprocess.STDOUT)
|
||||||
found = re.findall(r'extensions that .* is allowed to access \(default "(.*)"', help_text)
|
.decode('utf-8'))
|
||||||
found_extensions = set()
|
found = re.findall(r'extensions that .* is allowed to access \(default "(.*)"', help_text)
|
||||||
for match in found:
|
found_extensions = set()
|
||||||
found_extensions.update(match.split(','))
|
for match in found:
|
||||||
return list(found_extensions)
|
found_extensions.update(match.split(','))
|
||||||
|
cls.file_extensions = list(found_extensions)
|
||||||
|
return cls.file_extensions
|
||||||
|
|
||||||
def version(self):
|
def version(self):
|
||||||
version = None
|
version = None
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class FFMPEGUI:
|
class FFMPEGUI:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_options(instance):
|
def get_options(instance, project_info):
|
||||||
options = []
|
options = []
|
||||||
return options
|
return options
|
||||||
|
|||||||
@@ -322,12 +322,22 @@ class NewRenderJobForm(QWidget):
|
|||||||
self.load_file_group.setHidden(True)
|
self.load_file_group.setHidden(True)
|
||||||
self.toggle_renderer_enablement(True)
|
self.toggle_renderer_enablement(True)
|
||||||
|
|
||||||
# Load scene data
|
# -- Load scene data
|
||||||
self.start_frame_input.setValue(self.project_info.get('frame_start'))
|
# start / end frames
|
||||||
self.end_frame_input.setValue(self.project_info.get('frame_end'))
|
self.start_frame_input.setValue(self.project_info.get('start_frame', 0))
|
||||||
self.resolution_x_input.setValue(self.project_info.get('resolution_x'))
|
self.end_frame_input.setValue(self.project_info.get('end_frame', 0))
|
||||||
self.resolution_y_input.setValue(self.project_info.get('resolution_y'))
|
self.start_frame_input.setEnabled(bool(self.project_info.get('start_frame')))
|
||||||
self.frame_rate_input.setValue(self.project_info.get('fps'))
|
self.end_frame_input.setEnabled(bool(self.project_info.get('start_frame')))
|
||||||
|
|
||||||
|
# resolution
|
||||||
|
self.resolution_x_input.setValue(self.project_info.get('resolution_x', 1920))
|
||||||
|
self.resolution_y_input.setValue(self.project_info.get('resolution_y', 1080))
|
||||||
|
self.resolution_x_input.setEnabled(bool(self.project_info.get('resolution_x')))
|
||||||
|
self.resolution_y_input.setEnabled(bool(self.project_info.get('resolution_y')))
|
||||||
|
|
||||||
|
# frame rate
|
||||||
|
self.frame_rate_input.setValue(self.project_info.get('fps', 24))
|
||||||
|
self.frame_rate_input.setEnabled(bool(self.project_info.get('fps')))
|
||||||
|
|
||||||
# Cameras
|
# Cameras
|
||||||
self.cameras_list.clear()
|
self.cameras_list.clear()
|
||||||
@@ -350,7 +360,7 @@ class NewRenderJobForm(QWidget):
|
|||||||
# Dynamic Engine Options
|
# Dynamic Engine Options
|
||||||
clear_layout(self.renderer_options_layout) # clear old options
|
clear_layout(self.renderer_options_layout) # clear old options
|
||||||
# dynamically populate option list
|
# dynamically populate option list
|
||||||
self.current_engine_options = engine().ui_options()
|
self.current_engine_options = engine().ui_options(self.project_info)
|
||||||
for option in self.current_engine_options:
|
for option in self.current_engine_options:
|
||||||
h_layout = QHBoxLayout()
|
h_layout = QHBoxLayout()
|
||||||
label = QLabel(option['name'].replace('_', ' ').capitalize() + ':')
|
label = QLabel(option['name'].replace('_', ' ').capitalize() + ':')
|
||||||
@@ -496,8 +506,12 @@ class SubmitWorker(QThread):
|
|||||||
engine = EngineManager.engine_with_name(self.window.renderer_type.currentText().lower())
|
engine = EngineManager.engine_with_name(self.window.renderer_type.currentText().lower())
|
||||||
input_path = engine().perform_presubmission_tasks(input_path)
|
input_path = engine().perform_presubmission_tasks(input_path)
|
||||||
# submit
|
# submit
|
||||||
result = self.window.server_proxy.post_job_to_server(file_path=input_path, job_list=job_list,
|
result = None
|
||||||
callback=create_callback)
|
try:
|
||||||
|
result = self.window.server_proxy.post_job_to_server(file_path=input_path, job_list=job_list,
|
||||||
|
callback=create_callback)
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
self.message_signal.emit(result)
|
self.message_signal.emit(result)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user