From fd7e7bfa26de4e06d89fe30c51b0b6c75bfbcd06 Mon Sep 17 00:00:00 2001 From: Nuwan Jayawardene Date: Tue, 21 May 2024 21:35:43 +0530 Subject: [PATCH] Add frame range to Blender command in CueSubmit (#1337) * Add frame range to Blender render command Extracted from layerRange value in layerData Import regex module * Fix linting issue Line too long * Add variables for frame range tokens * Update Blender command with tokens for animation frame range * Remove unused import * Add suffix to frame start/end variables * Update comment Update comment explaining render frame range Blender command * Add unit tests For single frame and multi frame Blender renders * Add missing services line to blender unit test layer data --- cuesubmit/cuesubmit/Constants.py | 5 ++- cuesubmit/cuesubmit/Submission.py | 11 ++++- cuesubmit/tests/Submission_tests.py | 70 +++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/cuesubmit/cuesubmit/Constants.py b/cuesubmit/cuesubmit/Constants.py index b4f82f13f..3aeae767d 100644 --- a/cuesubmit/cuesubmit/Constants.py +++ b/cuesubmit/cuesubmit/Constants.py @@ -17,7 +17,6 @@ Some values can be overridden by custom config, see Config.py.""" - from __future__ import print_function from __future__ import division from __future__ import absolute_import @@ -38,6 +37,8 @@ NUKE_RENDER_CMD = config.get('NUKE_RENDER_CMD', 'nuke') BLENDER_RENDER_CMD = config.get('BLENDER_RENDER_CMD', 'blender') FRAME_TOKEN = config.get('FRAME_TOKEN', '#IFRAME#') +FRAME_START_TOKEN = config.get('FRAME_START', '#FRAME_START#') +FRAME_END_TOKEN = config.get('FRAME_END', '#FRAME_END#') # Tokens are replaced by cuebot during dispatch with their computed value. # see: cuebot/src/main/java/com/imageworks/spcue/dispatcher/DispatchSupportService.java @@ -55,7 +56,7 @@ BLENDER_FORMATS = ['', 'AVIJPEG', 'AVIRAW', 'BMP', 'CINEON', 'DPX', 'EXR', 'HDR', 'IRIS', 'IRIZ', 'JP2', 'JPEG', 'MPEG', 'MULTILAYER', 'PNG', 'RAWTGA', 'TGA', 'TIFF'] BLENDER_OUTPUT_OPTIONS_URL = \ - 'https://docs.blender.org/manual/en/latest/advanced/command_line/arguments.html#render-options' + 'https://docs.blender.org/manual/en/latest/advanced/command_line/arguments.html#render-options' DIR_PATH = os.path.dirname(__file__) diff --git a/cuesubmit/cuesubmit/Submission.py b/cuesubmit/cuesubmit/Submission.py index 94a1f97b9..59799663f 100644 --- a/cuesubmit/cuesubmit/Submission.py +++ b/cuesubmit/cuesubmit/Submission.py @@ -63,6 +63,7 @@ def buildBlenderCmd(layerData): blenderFile = layerData.cmd.get('blenderFile') outputPath = layerData.cmd.get('outputPath') outputFormat = layerData.cmd.get('outputFormat') + frameRange = layerData.layerRange if not blenderFile: raise ValueError('No Blender file provided. Cannot submit job.') @@ -72,8 +73,14 @@ def buildBlenderCmd(layerData): renderCommand += ' -o {}'.format(outputPath) if outputFormat: renderCommand += ' -F {}'.format(outputFormat) - # The render frame must come after the scene and output - renderCommand += ' -f {frameToken}'.format(frameToken=Constants.FRAME_TOKEN) + if frameRange: + # Render frames from start to end (inclusive) via '-a' command argument + renderCommand += (' -s {startFrame} -e {endFrame} -a' + .format(startFrame=Constants.FRAME_START_TOKEN, + endFrame=Constants.FRAME_END_TOKEN)) + else: + # The render frame must come after the scene and output + renderCommand += ' -f {frameToken}'.format(frameToken=Constants.FRAME_TOKEN) return renderCommand diff --git a/cuesubmit/tests/Submission_tests.py b/cuesubmit/tests/Submission_tests.py index 751df915a..e2219070d 100644 --- a/cuesubmit/tests/Submission_tests.py +++ b/cuesubmit/tests/Submission_tests.py @@ -49,6 +49,27 @@ 'services': ['nuke'], } +BLENDER_MULTI_LAYER_DATA = { + 'name': 'arbitraryBlenderLayer_name', + 'layerType': cuesubmit.JobTypes.JobTypes.BLENDER, + 'cmd': {'outputPath': '/path/to/output', + 'blenderFile': '/path/to/scene.blend', + 'outputFormat': 'PNG'}, + 'layerRange': '3-9', + 'cores': '2', + 'services': ['blender'] +} + +BLENDER_SINGLE_LAYER_DATA = { + 'name': 'arbitraryBlenderLayer_name', + 'layerType': cuesubmit.JobTypes.JobTypes.BLENDER, + 'cmd': {'outputPath': '/path/to/output', + 'blenderFile': '/path/to/scene.blend', + 'outputFormat': 'PNG'}, + 'cores': '2', + 'services': ['blender'] +} + SHELL_LAYER_DATA = { 'name': 'arbitraryShellLayer_name', 'layerType': cuesubmit.JobTypes.JobTypes.SHELL, @@ -109,6 +130,55 @@ def testSubmitNukeJob(self, launchMock): self.assertEqual(NUKE_LAYER_DATA['layerRange'], layer.get_frame_range()) self.assertEqual('nuke', layer.get_service()) + def testSubmitSingleFrameBlenderJob(self, launchMock): + cuesubmit.Submission.submitJob({ + 'name': 'arbitrary-blender-job', + 'shot': 'arbitrary-shot-name', + 'show': 'arbitrary-show-name', + 'username': 'arbitrary-user', + 'layers': [cuesubmit.Layer.LayerData.buildFactory(**BLENDER_SINGLE_LAYER_DATA)], + }) + + ol = launchMock.call_args[0][0] + self.assertEqual(1, len(ol.get_layers())) + layer = ol.get_layer(BLENDER_SINGLE_LAYER_DATA['name']) + self.assertEqual(BLENDER_SINGLE_LAYER_DATA['name'], layer.get_name()) + self.assertEqual( + [ + 'blender', '-b', '-noaudio', BLENDER_SINGLE_LAYER_DATA['cmd']['blenderFile'], + '-o', BLENDER_SINGLE_LAYER_DATA['cmd']['outputPath'], + '-F', BLENDER_SINGLE_LAYER_DATA['cmd']['outputFormat'], + '-f', '#IFRAME#' + ], + layer.get_arg('command') + ) + self.assertEqual('blender', layer.get_service()) + + def testSubmitMultiFrameBlenderJob(self, launchMock): + cuesubmit.Submission.submitJob({ + 'name': 'arbitrary-blender-job', + 'shot': 'arbitrary-shot-name', + 'show': 'arbitrary-show-name', + 'username': 'arbitrary-user', + 'layers': [cuesubmit.Layer.LayerData.buildFactory(**BLENDER_MULTI_LAYER_DATA)], + }) + + ol = launchMock.call_args[0][0] + self.assertEqual(1, len(ol.get_layers())) + layer = ol.get_layer(BLENDER_MULTI_LAYER_DATA['name']) + self.assertEqual(BLENDER_MULTI_LAYER_DATA['name'], layer.get_name()) + self.assertEqual( + [ + 'blender', '-b', '-noaudio', BLENDER_MULTI_LAYER_DATA['cmd']['blenderFile'], + '-o', BLENDER_MULTI_LAYER_DATA['cmd']['outputPath'], + '-F', BLENDER_MULTI_LAYER_DATA['cmd']['outputFormat'], + '-s', '#FRAME_START#', '-e', '#FRAME_END#', '-a' + ], + layer.get_arg('command') + ) + self.assertEqual(BLENDER_MULTI_LAYER_DATA['layerRange'], layer.get_frame_range()) + self.assertEqual('blender', layer.get_service()) + def testSubmitMayaAndShellJob(self, launchMock): cuesubmit.Submission.submitJob({ 'name': 'arbitrary-maya-shell-job',