Files
Zordon/src/engines/core/base_engine.py
Brett af6d6e1525 Document all the things! (#117)
Add lots of docstrings everywhere
2024-08-23 19:26:05 -05:00

163 lines
5.7 KiB
Python

import logging
import os
import platform
import subprocess
logger = logging.getLogger()
SUBPROCESS_TIMEOUT = 5
class BaseRenderEngine(object):
"""Base class for render engines. This class provides common functionality and structure for various rendering
engines. Create subclasses and override the methods marked below to add additional renderers
Attributes:
install_paths (list): A list of default installation paths where the render engine
might be found. This list can be populated with common paths to help locate the
executable on different operating systems or environments.
"""
install_paths = []
# --------------------------------------------
# Required Overrides for Subclasses:
# --------------------------------------------
def __init__(self, custom_path=None):
self.custom_renderer_path = custom_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: {self.renderer_path()}")
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()}")
os.chmod(self.renderer_path(), 0o755)
def version(self):
"""Return the version number as a string.
Returns:
str: Version number.
Raises:
NotImplementedError: If not overridden.
"""
raise NotImplementedError(f"version not implemented for {self.__class__.__name__}")
def get_project_info(self, project_path, timeout=10):
"""Extracts detailed project information from the given project path.
Args:
project_path (str): The path to the project file.
timeout (int, optional): The maximum time (in seconds) to wait for the operation. Default is 10 seconds.
Returns:
dict: A dictionary containing project information (subclasses should define the structure).
Raises:
NotImplementedError: If the method is not overridden in a subclass.
"""
raise NotImplementedError(f"get_project_info not implemented for {self.__class__.__name__}")
@classmethod
def get_output_formats(cls):
"""Returns a list of available output formats supported by the renderer.
Returns:
list[str]: A list of strings representing the available output formats.
"""
raise NotImplementedError(f"get_output_formats not implemented for {cls.__name__}")
@staticmethod
def worker_class(): # override when subclassing to link worker class
raise NotImplementedError("Worker class not implemented")
# --------------------------------------------
# Optional Overrides for Subclasses:
# --------------------------------------------
def supported_extensions(self):
"""
Returns:
list[str]: list of supported extensions
"""
return []
def get_help(self):
"""Retrieves the help documentation for the renderer.
This method runs the renderer's help command (default: '-h') and captures the output.
Override this method if the renderer uses a different help flag.
Returns:
str: The help documentation as a string.
Raises:
FileNotFoundError: If the renderer path is not found.
"""
path = self.renderer_path()
if not path:
raise FileNotFoundError("renderer path not found")
creationflags = subprocess.CREATE_NO_WINDOW if platform.system() == 'Windows' else 0
help_doc = subprocess.check_output([path, '-h'], stderr=subprocess.STDOUT,
timeout=SUBPROCESS_TIMEOUT, creationflags=creationflags).decode('utf-8')
return help_doc
def system_info(self):
"""Return additional information about the system specfic to the engine (configured GPUs, render engines, etc)
Returns:
dict: A dictionary with engine-specific system information
"""
return {}
def perform_presubmission_tasks(self, project_path):
"""Perform any pre-submission tasks on a project file before uploading it to a server (pack textures, etc.)
Override this method to:
1. Copy the project file to a temporary location (DO NOT MODIFY ORIGINAL PATH).
2. Perform additional modifications or tasks.
3. Return the path to the modified project file.
Args:
project_path (str): The original project file path.
Returns:
str: The path to the modified project file.
"""
return project_path
def get_arguments(self):
pass
@staticmethod
def downloader(): # override when subclassing if using a downloader class
return None
@staticmethod
def ui_options(system_info): # override to return options for ui
return {}
# --------------------------------------------
# Do Not Override These Methods:
# --------------------------------------------
def renderer_path(self):
return self.custom_renderer_path or self.default_renderer_path()
@classmethod
def name(cls):
return str(cls.__name__).lower()
@classmethod
def default_renderer_path(cls):
path = None
try: # Linux and macOS
path = subprocess.check_output(['which', cls.name()], timeout=SUBPROCESS_TIMEOUT).decode('utf-8').strip()
except (subprocess.CalledProcessError, FileNotFoundError):
for p in cls.install_paths:
if os.path.exists(p):
path = p
except Exception as e:
logger.exception(e)
return path