diff --git a/openpype/modules/default_modules/royal_render/api.py b/openpype/modules/default_modules/royal_render/api.py index 02a4dd3e48..59f6eeb1ff 100644 --- a/openpype/modules/default_modules/royal_render/api.py +++ b/openpype/modules/default_modules/royal_render/api.py @@ -1,12 +1,69 @@ # -*- coding: utf-8 -*- """Wrapper around Royal Render API.""" -from .rr_job import RRJob, SubmitFile +import sys +import os +from xml.dom import minidom + +from openpype.settings import get_project_settings +from openpype.lib.local_settings import OpenPypeSettingsRegistry +from openpype.lib import PypeLogger +from .rr_job import RRJob, SubmitFile, SubmitterParameter + + +log = PypeLogger.get_logger("RoyalRender") class Api: + _settings = None + RR_SUBMIT_CONSOLE = 1 + RR_SUBMIT_API = 2 + + def __init__(self, settings): + self._settings = settings + + def initialize_rr_modules(self, project=None): + # type: (str) -> None + + is_64bit_python = sys.maxsize > 2 ** 32 + if project: + project_settings = get_project_settings(project) + rr_path = ( + project_settings + ["royalrender"] + ["rr_paths"] + ) + else: + rr_path = ( + self._settings + ["modules"] + ["royalrender"] + ["rr_path"] + ["default"] + ) + + # default for linux + rr_module_path = "/bin/lx64/lib" + + if sys.platform.lower() == "win32": + rr_module_path = "/bin/win64" + if not is_64bit_python: + # we are using 64bit python + rr_module_path = "/bin/win" + rr_module_path = rr_module_path.replace( + "/", os.path.sep + ) + + if sys.platform.lower() == "darwin": + rr_module_path = "/bin/mac64/lib/python/27" + if not is_64bit_python: + rr_module_path = "/bin/mac/lib/python/27" + + sys.path.append(os.path.join(rr_path, rr_module_path)) + os.environ["RR_ROOT"] = rr_path + def create_submission(self, jobs, submitter_attributes, file_name=None): - # type: (list, list, str) -> SubmitFile + # type: (list[RRJob], list[SubmitterParameter], str) -> SubmitFile """Create jobs submission file. Args: @@ -21,6 +78,79 @@ class Api: """ raise NotImplementedError - def send_job_file(self, submit_file): - # type: (str) -> None + def submit_file(self, file, mode=RR_SUBMIT_CONSOLE): + # type: (SubmitFile, int) -> None + if mode == self.RR_SUBMIT_CONSOLE: + self._submit_using_console(file) + + # RR v7 supports only Python 2.7 so we bail out in fear + # until there is support for Python 3 😰 + raise NotImplementedError( + "Submission via RoyalRender API is not supported yet") + # self._submit_using_api(file) + + def _submit_using_console(self, file): + # type: (SubmitFile) -> bool ... + + def _submit_using_api(self, file): + # type: (SubmitFile) -> None + """Use RR API to submit jobs. + + Args: + file (SubmitFile): Submit jobs definition. + + Throws: + RoyalRenderException: When something fails. + + """ + self.initialize_rr_modules() + + import libpyRR2 as rrLib + from rrJob import getClass_JobBasics + import libpyRR2 as _RenderAppBasic + + tcp = rrLib._rrTCP("") + rr_server = tcp.getRRServer() + + if len(rr_server) == 0: + log.info("Got RR IP address {}".format(rr_server)) + + # TODO: Port is hardcoded in RR? If not, move it to Settings + if not tcp.setServer(rr_server, 7773): + log.error( + "Can not set RR server: {}".format(tcp.errorMessage())) + raise RoyalRenderException(tcp.errorMessage()) + + # TODO: This need UI and better handling of username/password. + # We can't store password in keychain as it is pulled multiple + # times and users on linux must enter keychain password every time. + # Probably best way until we setup our own user management would be + # to encrypt password and save it to json locally. Not bulletproof + # but at least it is not stored in plaintext. + reg = OpenPypeSettingsRegistry() + try: + rr_user = reg.get_item("rr_username") + rr_password = reg.get_item("rr_password") + except ValueError: + # user has no rr credentials set + pass + else: + # login to RR + tcp.setLogin(rr_user, rr_password) + + job = getClass_JobBasics() + renderer = _RenderAppBasic() + + # iterate over SubmitFile, set _JobBasic (job) and renderer + # and feed it to jobSubmitNew() + # not implemented yet + + job.renderer = renderer + + tcp.jobSubmitNew(job) + + +class RoyalRenderException(Exception): + """Exception used in various error states coming from RR.""" + pass diff --git a/openpype/modules/default_modules/royal_render/royal_render_module.py b/openpype/modules/default_modules/royal_render/royal_render_module.py index 48862b6a45..e49ff9a487 100644 --- a/openpype/modules/default_modules/royal_render/royal_render_module.py +++ b/openpype/modules/default_modules/royal_render/royal_render_module.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Module providing support for Royal Render.""" import os +import openpype.modules from openpype.modules import OpenPypeModule from openpype_interfaces import IPluginPaths @@ -9,28 +10,37 @@ class RoyalRenderModule(OpenPypeModule, IPluginPaths): """Class providing basic Royal Render implementation logic.""" name = "royalrender" _api = None + settings = None @property def api(self): if not self._api: # import royal render modules from . import api as rr_api - self._api = rr_api.Api() + self._api = rr_api.Api(self.settings) return self._api def __init__(self, manager, settings): + # type: (openpype.modules.base.ModulesManager, dict) -> None self.rr_paths = {} + self.settings = settings super(RoyalRenderModule, self).__init__(manager, settings) def initialize(self, module_settings): + # type: (dict) -> None rr_settings = module_settings[self.name] self.enabled = rr_settings["enabled"] self.rr_paths = rr_settings.get("rr_paths") @staticmethod def get_plugin_paths(self): - """Deadline plugin paths.""" + # type: (None) -> dict + """Royal Render plugin paths. + + Returns: + dict: Dictionary of plugin paths for RR. + """ current_dir = os.path.dirname(os.path.abspath(__file__)) return { "publish": [os.path.join(current_dir, "plugins", "publish")] diff --git a/openpype/modules/default_modules/royal_render/rr_job.py b/openpype/modules/default_modules/royal_render/rr_job.py index f72af5cc6c..8cf7dac007 100644 --- a/openpype/modules/default_modules/royal_render/rr_job.py +++ b/openpype/modules/default_modules/royal_render/rr_job.py @@ -140,7 +140,7 @@ class RRJob: class SubmitterParameter: - + """Wrapper for Submitter Parameters.""" def __init__(self, parameter, *args): # type: (str, list) -> None self._parameter = parameter @@ -148,6 +148,14 @@ class SubmitterParameter: def serialize(self): # type: () -> str + """Serialize submitter parameter as a string value. + + This can be later on used as text node in job xml file. + + Returns: + str: concatenated string of parameter values. + + """ return '"{param}={val}"'.format( param=self._parameter, val="~".join(self._values)) @@ -172,7 +180,7 @@ class SubmitFile: @staticmethod def _process_submitter_parameters(parameters, dom, append_to): - # type: (list, md.Document, md.Element) -> None + # type: (list[SubmitterParameter], md.Document, md.Element) -> None """Take list of :class:`SubmitterParameter` and process it as XML. This will take :class:`SubmitterParameter`, create XML element @@ -202,6 +210,7 @@ class SubmitFile: """ def filter_data(a, v): + """Skip private attributes.""" if a.name.startswith("_"): return False if v is None: @@ -209,10 +218,12 @@ class SubmitFile: return True root = md.Document() + # root element: job_file = root.createElement('RR_Job_File') job_file.setAttribute("syntax_version", self.syntax_version) # handle Submitter Parameters for batch + # foo=bar~baz~goo self._process_submitter_parameters( self.SubmitterParameters, root, job_file)