mirror of
https://github.com/blw1138/Zordon.git
synced 2025-12-17 08:48:13 +00:00
Compare commits
6 Commits
feature/10
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05cd0470dd | ||
|
|
7827f73530 | ||
| 562cb23da3 | |||
|
|
6b68d42b93 | ||
|
|
cdf4b2bbe1 | ||
|
|
dc8f4d3e2a |
11
README.md
11
README.md
@@ -1,6 +1,15 @@
|
|||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# Zordon
|
# Zordon
|
||||||
|
|
||||||
A tool designed for small render farms, such as those used in home studios or small businesses, to efficiently manage and run render jobs for Blender, FFMPEG, and other video renderers. It simplifies the process of distributing rendering tasks across multiple available machines, optimizing the rendering workflow for artists, animators, and video professionals.
|
A lightweight, zero-install, distributed rendering and management tool designed to streamline and optimize rendering workflows across multiple machines
|
||||||
|
|
||||||
|
## What is Zordon?
|
||||||
|
|
||||||
|
Zordon is tool designed for small render farms, such as those used in home studios or small businesses, to efficiently manage and run render jobs for Blender, FFMPEG, and other video renderers. It simplifies the process of distributing rendering tasks across multiple available machines, optimizing the rendering workflow for artists, animators, and video professionals.
|
||||||
|
|
||||||
|
|
||||||
Notice: This should be considered a beta and is meant for casual / hobbiest use. Do not use in mission critical environments!
|
Notice: This should be considered a beta and is meant for casual / hobbiest use. Do not use in mission critical environments!
|
||||||
|
|
||||||
|
|||||||
BIN
docs/screenshot.png
Normal file
BIN
docs/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 838 KiB |
@@ -5,8 +5,10 @@ from PyInstaller.utils.hooks import collect_all
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
sys.path.insert(0, os.path.abspath('.'))
|
src_path = os.path.abspath("src")
|
||||||
|
sys.path.insert(0, src_path)
|
||||||
from version import APP_NAME, APP_VERSION, APP_AUTHOR
|
from version import APP_NAME, APP_VERSION, APP_AUTHOR
|
||||||
|
sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
datas = [('resources', 'resources'), ('src/engines/blender/scripts/', 'src/engines/blender/scripts')]
|
datas = [('resources', 'resources'), ('src/engines/blender/scripts/', 'src/engines/blender/scripts')]
|
||||||
binaries = []
|
binaries = []
|
||||||
@@ -55,7 +57,7 @@ if platform.system() == 'Darwin': # macOS
|
|||||||
a.datas,
|
a.datas,
|
||||||
strip=True,
|
strip=True,
|
||||||
name=f'{APP_NAME}.app',
|
name=f'{APP_NAME}.app',
|
||||||
icon=None,
|
icon='resources/Server.png',
|
||||||
bundle_identifier=None,
|
bundle_identifier=None,
|
||||||
version=APP_VERSION
|
version=APP_VERSION
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -36,3 +36,6 @@ lxml>=5.1.0
|
|||||||
click>=8.1.7
|
click>=8.1.7
|
||||||
requests_toolbelt>=1.0.0
|
requests_toolbelt>=1.0.0
|
||||||
pyinstaller_versionfile>=2.1.1
|
pyinstaller_versionfile>=2.1.1
|
||||||
|
py-cpuinfo~=9.0.0
|
||||||
|
requests-toolbelt~=1.0.0
|
||||||
|
ifaddr~=0.2.0
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from init import run
|
from src.init import run
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
run(server_only=True)
|
run(server_only=True)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import tempfile
|
|||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
import cpuinfo
|
||||||
import psutil
|
import psutil
|
||||||
import yaml
|
import yaml
|
||||||
from flask import Flask, request, send_file, after_this_request, Response, redirect, url_for
|
from flask import Flask, request, send_file, after_this_request, Response, redirect, url_for
|
||||||
@@ -25,11 +26,13 @@ from src.utilities.config import Config
|
|||||||
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, \
|
||||||
current_system_os_version, num_to_alphanumeric
|
current_system_os_version, num_to_alphanumeric
|
||||||
from src.utilities.status_utils import string_to_status
|
from src.utilities.status_utils import string_to_status
|
||||||
|
from src.version import APP_VERSION
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
server = Flask(__name__)
|
server = Flask(__name__)
|
||||||
ssl._create_default_https_context = ssl._create_unverified_context # disable SSL for downloads
|
ssl._create_default_https_context = ssl._create_unverified_context # disable SSL for downloads
|
||||||
|
|
||||||
|
API_VERSION = "1"
|
||||||
|
|
||||||
def start_server(hostname=None):
|
def start_server(hostname=None):
|
||||||
|
|
||||||
@@ -228,6 +231,7 @@ def status():
|
|||||||
"system_os": current_system_os(),
|
"system_os": current_system_os(),
|
||||||
"system_os_version": current_system_os_version(),
|
"system_os_version": current_system_os_version(),
|
||||||
"system_cpu": current_system_cpu(),
|
"system_cpu": current_system_cpu(),
|
||||||
|
"system_cpu_brand": cpuinfo.get_cpu_info()['brand_raw'],
|
||||||
"cpu_percent": psutil.cpu_percent(percpu=False),
|
"cpu_percent": psutil.cpu_percent(percpu=False),
|
||||||
"cpu_percent_per_cpu": psutil.cpu_percent(percpu=True),
|
"cpu_percent_per_cpu": psutil.cpu_percent(percpu=True),
|
||||||
"cpu_count": psutil.cpu_count(logical=False),
|
"cpu_count": psutil.cpu_count(logical=False),
|
||||||
@@ -236,7 +240,9 @@ def status():
|
|||||||
"memory_percent": psutil.virtual_memory().percent,
|
"memory_percent": psutil.virtual_memory().percent,
|
||||||
"job_counts": RenderQueue.job_counts(),
|
"job_counts": RenderQueue.job_counts(),
|
||||||
"hostname": server.config['HOSTNAME'],
|
"hostname": server.config['HOSTNAME'],
|
||||||
"port": server.config['PORT']
|
"port": server.config['PORT'],
|
||||||
|
"app_version": APP_VERSION,
|
||||||
|
"api_version": API_VERSION
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class PreviewManager:
|
|||||||
_running_jobs = {}
|
_running_jobs = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __generate_job_preview_worker(cls, job, replace_existing=False, max_width=320):
|
def __generate_job_preview_worker(cls, job, replace_existing=False, max_width=480):
|
||||||
|
|
||||||
# Determine best source file to use for thumbs
|
# Determine best source file to use for thumbs
|
||||||
job_file_list = job.file_list()
|
job_file_list = job.file_list()
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class RenderServerProxy:
|
|||||||
self.system_cpu_count = None
|
self.system_cpu_count = None
|
||||||
self.system_os = None
|
self.system_os = None
|
||||||
self.system_os_version = None
|
self.system_os_version = None
|
||||||
|
self.system_api_version = None
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# Basics / Connection:
|
# Basics / Connection:
|
||||||
@@ -100,8 +101,10 @@ class RenderServerProxy:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def request(self, payload, timeout=5):
|
def request(self, payload, timeout=5):
|
||||||
|
from src.api.api_server import API_VERSION
|
||||||
hostname = LOOPBACK if self.is_localhost else self.hostname
|
hostname = LOOPBACK if self.is_localhost else self.hostname
|
||||||
return requests.get(f'http://{hostname}:{self.port}/api/{payload}', timeout=timeout)
|
return requests.get(f'http://{hostname}:{self.port}/api/{payload}', timeout=timeout,
|
||||||
|
headers={"X-API-Version": str(API_VERSION)})
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
# Background Updates:
|
# Background Updates:
|
||||||
@@ -162,6 +165,7 @@ class RenderServerProxy:
|
|||||||
self.system_cpu_count = status['cpu_count']
|
self.system_cpu_count = status['cpu_count']
|
||||||
self.system_os = status['system_os']
|
self.system_os = status['system_os']
|
||||||
self.system_os_version = status['system_os_version']
|
self.system_os_version = status['system_os_version']
|
||||||
|
self.system_api_version = status['api_version']
|
||||||
return status
|
return status
|
||||||
|
|
||||||
# --------------------------------------------
|
# --------------------------------------------
|
||||||
|
|||||||
@@ -374,13 +374,15 @@ class DistributedJobManager:
|
|||||||
:param system_os: str, Restrict results to servers running a specific OS
|
:param system_os: str, Restrict results to servers running a specific OS
|
||||||
:return: A list of dictionaries with each dict containing hostname and cpu_count of available servers
|
:return: A list of dictionaries with each dict containing hostname and cpu_count of available servers
|
||||||
"""
|
"""
|
||||||
|
from api.api_server import API_VERSION
|
||||||
available_servers = []
|
available_servers = []
|
||||||
for hostname in ZeroconfServer.found_hostnames():
|
for hostname in ZeroconfServer.found_hostnames():
|
||||||
host_properties = ZeroconfServer.get_hostname_properties(hostname)
|
host_properties = ZeroconfServer.get_hostname_properties(hostname)
|
||||||
if not system_os or (system_os and system_os == host_properties.get('system_os')):
|
if host_properties.get('api_version') == API_VERSION:
|
||||||
response = RenderServerProxy(hostname).is_engine_available(engine_name)
|
if not system_os or (system_os and system_os == host_properties.get('system_os')):
|
||||||
if response and response.get('available', False):
|
response = RenderServerProxy(hostname).is_engine_available(engine_name)
|
||||||
available_servers.append(response)
|
if response and response.get('available', False):
|
||||||
|
available_servers.append(response)
|
||||||
|
|
||||||
return available_servers
|
return available_servers
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ class BlenderRenderWorker(BaseRenderWorker):
|
|||||||
cmd.append('-b')
|
cmd.append('-b')
|
||||||
cmd.append(self.input_path)
|
cmd.append(self.input_path)
|
||||||
|
|
||||||
|
# Set Render Engine
|
||||||
|
blender_engine = self.args.get('engine')
|
||||||
|
if blender_engine:
|
||||||
|
blender_engine = blender_engine.upper()
|
||||||
|
cmd.extend(['-E', blender_engine])
|
||||||
|
|
||||||
# Start Python expressions - # todo: investigate splitting into separate 'setup' script
|
# Start Python expressions - # todo: investigate splitting into separate 'setup' script
|
||||||
cmd.append('--python-expr')
|
cmd.append('--python-expr')
|
||||||
python_exp = 'import bpy; bpy.context.scene.render.use_overwrite = False;'
|
python_exp = 'import bpy; bpy.context.scene.render.use_overwrite = False;'
|
||||||
@@ -40,8 +46,7 @@ class BlenderRenderWorker(BaseRenderWorker):
|
|||||||
if custom_camera:
|
if custom_camera:
|
||||||
python_exp = python_exp + f"bpy.context.scene.camera = bpy.data.objects['{custom_camera}'];"
|
python_exp = python_exp + f"bpy.context.scene.camera = bpy.data.objects['{custom_camera}'];"
|
||||||
|
|
||||||
# Set Render Device (gpu/cpu/any)
|
# Set Render Device for Cycles (gpu/cpu/any)
|
||||||
blender_engine = self.args.get('engine', 'BLENDER_EEVEE').upper()
|
|
||||||
if blender_engine == 'CYCLES':
|
if blender_engine == 'CYCLES':
|
||||||
render_device = self.args.get('render_device', 'any').lower()
|
render_device = self.args.get('render_device', 'any').lower()
|
||||||
if render_device not in {'any', 'gpu', 'cpu'}:
|
if render_device not in {'any', 'gpu', 'cpu'}:
|
||||||
@@ -66,7 +71,7 @@ class BlenderRenderWorker(BaseRenderWorker):
|
|||||||
# Remove the extension only if it is not composed entirely of digits
|
# Remove the extension only if it is not composed entirely of digits
|
||||||
path_without_ext = main_part if not ext[1:].isdigit() else self.output_path
|
path_without_ext = main_part if not ext[1:].isdigit() else self.output_path
|
||||||
path_without_ext += "_"
|
path_without_ext += "_"
|
||||||
cmd.extend(['-E', blender_engine, '-o', path_without_ext, '-F', export_format])
|
cmd.extend(['-o', path_without_ext, '-F', export_format])
|
||||||
|
|
||||||
# set frame range
|
# set frame range
|
||||||
cmd.extend(['-s', self.start_frame, '-e', self.end_frame, '-a'])
|
cmd.extend(['-s', self.start_frame, '-e', self.end_frame, '-a'])
|
||||||
|
|||||||
@@ -305,18 +305,21 @@ class EngineDownloadWorker(threading.Thread):
|
|||||||
self.cpu = cpu
|
self.cpu = cpu
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
existing_download = EngineManager.is_version_downloaded(self.engine, self.version, self.system_os, self.cpu)
|
try:
|
||||||
if existing_download:
|
existing_download = EngineManager.is_version_downloaded(self.engine, self.version, self.system_os, self.cpu)
|
||||||
logger.info(f"Requested download of {self.engine} {self.version}, but local copy already exists")
|
if existing_download:
|
||||||
return 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
|
# Get the appropriate downloader class based on the engine type
|
||||||
EngineManager.engine_with_name(self.engine).downloader().download_engine(
|
EngineManager.engine_with_name(self.engine).downloader().download_engine(
|
||||||
self.version, download_location=EngineManager.engines_path, system_os=self.system_os, cpu=self.cpu,
|
self.version, download_location=EngineManager.engines_path, system_os=self.system_os, cpu=self.cpu,
|
||||||
timeout=300)
|
timeout=300)
|
||||||
|
except Exception as e:
|
||||||
# remove itself from the downloader list
|
logger.error(f"Error in download worker: {e}")
|
||||||
EngineManager.download_tasks.remove(self)
|
finally:
|
||||||
|
# remove itself from the downloader list
|
||||||
|
EngineManager.download_tasks.remove(self)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
11
src/init.py
11
src/init.py
@@ -6,6 +6,9 @@ import sys
|
|||||||
import threading
|
import threading
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
|
import cpuinfo
|
||||||
|
|
||||||
|
from api.api_server import API_VERSION
|
||||||
from src.api.api_server import start_server
|
from src.api.api_server import start_server
|
||||||
from src.api.preview_manager import PreviewManager
|
from src.api.preview_manager import PreviewManager
|
||||||
from src.api.serverproxy_manager import ServerProxyManager
|
from src.api.serverproxy_manager import ServerProxyManager
|
||||||
@@ -16,7 +19,7 @@ from src.utilities.config import Config
|
|||||||
from src.utilities.misc_helper import (system_safe_path, current_system_cpu, current_system_os,
|
from src.utilities.misc_helper import (system_safe_path, current_system_cpu, current_system_os,
|
||||||
current_system_os_version, check_for_updates)
|
current_system_os_version, check_for_updates)
|
||||||
from src.utilities.zeroconf_server import ZeroconfServer
|
from src.utilities.zeroconf_server import ZeroconfServer
|
||||||
from version import APP_NAME, APP_VERSION, APP_REPO_NAME, APP_REPO_OWNER
|
from src.version import APP_NAME, APP_VERSION, APP_REPO_NAME, APP_REPO_OWNER
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
@@ -108,9 +111,11 @@ def run(server_only=False) -> int:
|
|||||||
# start zeroconf server
|
# start zeroconf server
|
||||||
ZeroconfServer.configure(f"_{APP_NAME.lower()}._tcp.local.", local_hostname, Config.port_number)
|
ZeroconfServer.configure(f"_{APP_NAME.lower()}._tcp.local.", local_hostname, Config.port_number)
|
||||||
ZeroconfServer.properties = {'system_cpu': current_system_cpu(),
|
ZeroconfServer.properties = {'system_cpu': current_system_cpu(),
|
||||||
|
'system_cpu_brand': cpuinfo.get_cpu_info()['brand_raw'],
|
||||||
'system_cpu_cores': multiprocessing.cpu_count(),
|
'system_cpu_cores': multiprocessing.cpu_count(),
|
||||||
'system_os': current_system_os(),
|
'system_os': current_system_os(),
|
||||||
'system_os_version': current_system_os_version()}
|
'system_os_version': current_system_os_version(),
|
||||||
|
'api_version': API_VERSION}
|
||||||
ZeroconfServer.start()
|
ZeroconfServer.start()
|
||||||
logger.info(f"{APP_NAME} Render Server started - Hostname: {local_hostname}")
|
logger.info(f"{APP_NAME} Render Server started - Hostname: {local_hostname}")
|
||||||
RenderQueue.start() # Start evaluating the render queue
|
RenderQueue.start() # Start evaluating the render queue
|
||||||
@@ -175,6 +180,8 @@ def __show_gui(buffer_handler):
|
|||||||
|
|
||||||
# load application
|
# load application
|
||||||
app: QApplication = QApplication(sys.argv)
|
app: QApplication = QApplication(sys.argv)
|
||||||
|
if app.style().objectName() != 'macos':
|
||||||
|
app.setStyle('Fusion')
|
||||||
|
|
||||||
# configure main window
|
# configure main window
|
||||||
from src.ui.main_window import MainWindow
|
from src.ui.main_window import MainWindow
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from PyQt6.QtCore import Qt
|
|||||||
from PyQt6.QtGui import QPixmap
|
from PyQt6.QtGui import QPixmap
|
||||||
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QLabel, QDialogButtonBox, QHBoxLayout
|
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QLabel, QDialogButtonBox, QHBoxLayout
|
||||||
|
|
||||||
from version import *
|
from src.version import *
|
||||||
|
|
||||||
|
|
||||||
class AboutDialog(QDialog):
|
class AboutDialog(QDialog):
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import datetime
|
|||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@@ -16,6 +15,7 @@ from PyQt6.QtWidgets import QMainWindow, QWidget, QHBoxLayout, QListWidget, QTab
|
|||||||
QTableWidgetItem, QLabel, QVBoxLayout, QHeaderView, QMessageBox, QGroupBox, QPushButton, QListWidgetItem, \
|
QTableWidgetItem, QLabel, QVBoxLayout, QHeaderView, QMessageBox, QGroupBox, QPushButton, QListWidgetItem, \
|
||||||
QFileDialog
|
QFileDialog
|
||||||
|
|
||||||
|
from api.api_server import API_VERSION
|
||||||
from src.render_queue import RenderQueue
|
from src.render_queue import RenderQueue
|
||||||
from src.utilities.misc_helper import get_time_elapsed, resources_dir, is_localhost
|
from src.utilities.misc_helper import get_time_elapsed, resources_dir, is_localhost
|
||||||
from src.utilities.status_utils import RenderStatus
|
from src.utilities.status_utils import RenderStatus
|
||||||
@@ -29,7 +29,8 @@ from src.ui.widgets.proportional_image_label import ProportionalImageLabel
|
|||||||
from src.ui.widgets.statusbar import StatusBar
|
from src.ui.widgets.statusbar import StatusBar
|
||||||
from src.ui.widgets.toolbar import ToolBar
|
from src.ui.widgets.toolbar import ToolBar
|
||||||
from src.api.serverproxy_manager import ServerProxyManager
|
from src.api.serverproxy_manager import ServerProxyManager
|
||||||
from src.utilities.misc_helper import launch_url
|
from src.utilities.misc_helper import launch_url, iso_datestring_to_formatted_datestring
|
||||||
|
from src.version import APP_NAME
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.buffer_handler = None
|
self.buffer_handler = None
|
||||||
|
|
||||||
# Window-Settings
|
# Window-Settings
|
||||||
self.setWindowTitle("Zordon")
|
self.setWindowTitle(APP_NAME)
|
||||||
self.setGeometry(100, 100, 900, 800)
|
self.setGeometry(100, 100, 900, 800)
|
||||||
central_widget = QWidget(self)
|
central_widget = QWidget(self)
|
||||||
self.setCentralWidget(central_widget)
|
self.setCentralWidget(central_widget)
|
||||||
@@ -242,7 +243,7 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# Use the get method with defaults to avoid KeyError
|
# Use the get method with defaults to avoid KeyError
|
||||||
os_info = f"OS: {server_info.get('system_os', 'Unknown')} {server_info.get('system_os_version', '')}"
|
os_info = f"OS: {server_info.get('system_os', 'Unknown')} {server_info.get('system_os_version', '')}"
|
||||||
cpu_info = f"CPU: {server_info.get('system_cpu', 'Unknown')} - {server_info.get('system_cpu_cores', 'Unknown')} cores"
|
cpu_info = f"CPU: {server_info.get('system_cpu_brand', 'Unknown')} ({server_info.get('system_cpu_cores', 'Unknown')} cores)"
|
||||||
|
|
||||||
self.server_info_os.setText(os_info.strip())
|
self.server_info_os.setText(os_info.strip())
|
||||||
self.server_info_cpu.setText(cpu_info)
|
self.server_info_cpu.setText(cpu_info)
|
||||||
@@ -256,7 +257,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.job_list_view.clear()
|
self.job_list_view.clear()
|
||||||
self.refresh_job_headers()
|
self.refresh_job_headers()
|
||||||
|
|
||||||
job_fetch = self.current_server_proxy.get_all_jobs(ignore_token=clear_table)
|
job_fetch = self.current_server_proxy.get_all_jobs(ignore_token=False)
|
||||||
if job_fetch:
|
if job_fetch:
|
||||||
num_jobs = len(job_fetch)
|
num_jobs = len(job_fetch)
|
||||||
self.job_list_view.setRowCount(num_jobs)
|
self.job_list_view.setRowCount(num_jobs)
|
||||||
@@ -276,10 +277,11 @@ class MainWindow(QMainWindow):
|
|||||||
renderer = f"{job.get('renderer', '')}-{job.get('renderer_version')}"
|
renderer = f"{job.get('renderer', '')}-{job.get('renderer_version')}"
|
||||||
priority = str(job.get('priority', ''))
|
priority = str(job.get('priority', ''))
|
||||||
total_frames = str(job.get('total_frames', ''))
|
total_frames = str(job.get('total_frames', ''))
|
||||||
|
date_created_string = iso_datestring_to_formatted_datestring(job['date_created'])
|
||||||
|
|
||||||
items = [QTableWidgetItem(job['id']), QTableWidgetItem(name), QTableWidgetItem(renderer),
|
items = [QTableWidgetItem(job['id']), QTableWidgetItem(name), QTableWidgetItem(renderer),
|
||||||
QTableWidgetItem(priority), QTableWidgetItem(display_status), QTableWidgetItem(time_elapsed),
|
QTableWidgetItem(priority), QTableWidgetItem(display_status), QTableWidgetItem(time_elapsed),
|
||||||
QTableWidgetItem(total_frames), QTableWidgetItem(job['date_created'])]
|
QTableWidgetItem(total_frames), QTableWidgetItem(date_created_string)]
|
||||||
|
|
||||||
for col, item in enumerate(items):
|
for col, item in enumerate(items):
|
||||||
self.job_list_view.setItem(row, col, item)
|
self.job_list_view.setItem(row, col, item)
|
||||||
@@ -410,6 +412,8 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def update_servers(self):
|
def update_servers(self):
|
||||||
found_servers = list(set(ZeroconfServer.found_hostnames() + self.added_hostnames))
|
found_servers = list(set(ZeroconfServer.found_hostnames() + self.added_hostnames))
|
||||||
|
found_servers = [x for x in found_servers if ZeroconfServer.get_hostname_properties(x)['api_version'] == API_VERSION]
|
||||||
|
|
||||||
# Always make sure local hostname is first
|
# Always make sure local hostname is first
|
||||||
if found_servers and not is_localhost(found_servers[0]):
|
if found_servers and not is_localhost(found_servers[0]):
|
||||||
for hostname in found_servers:
|
for hostname in found_servers:
|
||||||
@@ -591,3 +595,21 @@ class MainWindow(QMainWindow):
|
|||||||
if file_name:
|
if file_name:
|
||||||
self.new_job_window = NewRenderJobForm(file_name)
|
self.new_job_window = NewRenderJobForm(file_name)
|
||||||
self.new_job_window.show()
|
self.new_job_window.show()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# lazy load GUI frameworks
|
||||||
|
from PyQt6.QtWidgets import QApplication
|
||||||
|
|
||||||
|
# load application
|
||||||
|
# QtCore.QCoreApplication.setAttribute(QtCore.Qt.ApplicationAttribute.AA_MacDontSwapCtrlAndMeta)
|
||||||
|
app: QApplication = QApplication(sys.argv)
|
||||||
|
|
||||||
|
# configure main main_window
|
||||||
|
main_window = MainWindow()
|
||||||
|
# main_window.buffer_handler = buffer_handler
|
||||||
|
app.setActiveWindow(main_window)
|
||||||
|
|
||||||
|
main_window.show()
|
||||||
|
|
||||||
|
sys.exit(app.exec())
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class MenuBar(QMenuBar):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def check_for_updates():
|
def check_for_updates():
|
||||||
from src.utilities.misc_helper import check_for_updates
|
from src.utilities.misc_helper import check_for_updates
|
||||||
from version import APP_NAME, APP_VERSION, APP_REPO_NAME, APP_REPO_OWNER
|
from src.version import APP_NAME, APP_VERSION, APP_REPO_NAME, APP_REPO_OWNER
|
||||||
found_update = check_for_updates(APP_REPO_NAME, APP_REPO_OWNER, APP_NAME, APP_VERSION)
|
found_update = check_for_updates(APP_REPO_NAME, APP_REPO_OWNER, APP_NAME, APP_VERSION)
|
||||||
if found_update:
|
if found_update:
|
||||||
dialog = UpdateDialog(found_update, APP_VERSION)
|
dialog = UpdateDialog(found_update, APP_VERSION)
|
||||||
|
|||||||
@@ -210,3 +210,18 @@ def num_to_alphanumeric(num):
|
|||||||
result += characters[remainder]
|
result += characters[remainder]
|
||||||
|
|
||||||
return result[::-1] # Reverse the result to get the correct alphanumeric string
|
return result[::-1] # Reverse the result to get the correct alphanumeric string
|
||||||
|
|
||||||
|
|
||||||
|
def iso_datestring_to_formatted_datestring(iso_date_string):
|
||||||
|
from dateutil import parser
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
# Parse the ISO date string into a datetime object and convert timezones
|
||||||
|
date = parser.isoparse(iso_date_string).astimezone(pytz.UTC)
|
||||||
|
local_timezone = datetime.now().astimezone().tzinfo
|
||||||
|
date_local = date.astimezone(local_timezone)
|
||||||
|
|
||||||
|
# Format the date to the desired readable yet sortable format with 12-hour time
|
||||||
|
formatted_date = date_local.strftime('%Y-%m-%d %I:%M %p')
|
||||||
|
|
||||||
|
return formatted_date
|
||||||
|
|||||||
@@ -32,9 +32,11 @@ class ZeroconfServer:
|
|||||||
def start(cls, listen_only=False):
|
def start(cls, listen_only=False):
|
||||||
if not cls.service_type:
|
if not cls.service_type:
|
||||||
raise RuntimeError("The 'configure' method must be run before starting the zeroconf server")
|
raise RuntimeError("The 'configure' method must be run before starting the zeroconf server")
|
||||||
logger.debug("Starting zeroconf service")
|
elif not listen_only:
|
||||||
if not listen_only:
|
logger.debug(f"Starting zeroconf service")
|
||||||
cls._register_service()
|
cls._register_service()
|
||||||
|
else:
|
||||||
|
logger.debug(f"Starting zeroconf service - Listen only mode")
|
||||||
cls._browse_services()
|
cls._browse_services()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
Reference in New Issue
Block a user