From 32fffa9aa4a97aba52ad489860fe901d68c2b722 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 12 Sep 2017 18:52:59 +0200 Subject: [PATCH] Separate Camera extraction for Alembic and Maya Ascii to separate plug-ins, implement the usage of a "bakeToWorldSpace" attribute. Note: The Camera mayaAscii extractor does not support `bakeToWorldSpace=False` yet. See the TODO comment. --- .../maya/publish/extract_camera_alembic.py | 82 ++++++++++ .../maya/publish/extract_camera_baked.py | 148 ------------------ .../maya/publish/extract_camera_mayaAscii.py | 134 ++++++++++++++++ 3 files changed, 216 insertions(+), 148 deletions(-) create mode 100644 colorbleed/plugins/maya/publish/extract_camera_alembic.py delete mode 100644 colorbleed/plugins/maya/publish/extract_camera_baked.py create mode 100644 colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py diff --git a/colorbleed/plugins/maya/publish/extract_camera_alembic.py b/colorbleed/plugins/maya/publish/extract_camera_alembic.py new file mode 100644 index 0000000000..418ba8ad12 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_camera_alembic.py @@ -0,0 +1,82 @@ +import os + +from maya import cmds + +import avalon.maya +import colorbleed.api + +import cb.utils.maya.context as context + + +class ExtractCameraAlembic(colorbleed.api.Extractor): + """Extract a Camera as Alembic. + + The cameras gets baked to world space by default. Only when the instance's + `bakeToWorldSpace` is set to False it will include its full hierarchy. + + The extracted Maya ascii file gets "massaged" removing the uuid values + so they are valid for older versions of Fusion (e.g. 6.4) + + """ + + label = "Camera (Alembic)" + hosts = ["maya"] + families = ["colorbleed.camera"] + + def process(self, instance): + + # get settings + framerange = [instance.data.get("startFrame", 1), + instance.data.get("endFrame", 1)] + handles = instance.data.get("handles", 0) + step = instance.data.get("step", 1.0) + bake_to_worldspace = instance.data("bakeToWorldSpace", True) + + # get cameras + members = instance.data['setMembers'] + cameras = cmds.ls(members, leaf=True, shapes=True, long=True, + dag=True, type="camera") + + # validate required settings + assert len(cameras) == 1, "Not a single camera found in extraction" + assert isinstance(step, float), "Step must be a float value" + camera = cameras[0] + + # Define extract output file path + dir_path = self.staging_dir(instance) + filename = "{0}.abc".format(instance.name) + path = os.path.join(dir_path, filename) + + # Perform alembic extraction + with avalon.maya.maintained_selection(): + cmds.select(camera, replace=True, noExpand=True) + + # Enforce forward slashes for AbcExport because we're + # embedding it into a job string + path = path.replace("\\", "/") + + job_str = ' -selection -dataFormat "ogawa" ' + job_str += ' -attrPrefix cb' + job_str += ' -frameRange {0} {1} '.format(framerange[0] - handles, + framerange[1] + handles) + + if bake_to_worldspace: + transform = cmds.listRelatives(camera, + parent=True, + fullPath=True)[0] + job_str += ' -worldSpace -root {0}'.format(transform) + + job_str += ' -file "{0}"'.format(path) + job_str += ' -step {0} '.format(step) + + with context.evaluation("off"): + with context.no_refresh(): + cmds.AbcExport(j=job_str, verbose=False) + + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + + self.log.info("Extracted instance '{0}' to: {1}".format( + instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_camera_baked.py b/colorbleed/plugins/maya/publish/extract_camera_baked.py deleted file mode 100644 index 01645279ef..0000000000 --- a/colorbleed/plugins/maya/publish/extract_camera_baked.py +++ /dev/null @@ -1,148 +0,0 @@ -import os - -from maya import cmds - -import avalon.maya -import colorbleed.api - -import cb.utils.maya.context as context -from cb.utils.maya.animation import bakeToWorldSpace - - -def massage_ma_file(path): - """Clean up .ma file for backwards compatibility. - - Massage the .ma of baked camera to stay - backwards compatible with older versions - of Fusion (6.4) - - """ - # Get open file's lines - f = open(path, "r+") - lines = f.readlines() - f.seek(0) # reset to start of file - - # Rewrite the file - for line in lines: - # Skip all 'rename -uid' lines - stripped = line.strip() - if stripped.startswith("rename -uid "): - continue - - f.write(line) - - f.truncate() # remove remainder - f.close() - - -class ExtractCameraBaked(colorbleed.api.Extractor): - """Extract as Maya Ascii and Alembic a baked camera. - - The cameras gets baked to world space and then extracted. - - The extracted Maya ascii file gets "massaged" removing the uuid values - so they are valid for older versions of Fusion (e.g. 6.4) - - """ - - label = "Camera Baked (Maya Ascii + Alembic)" - hosts = ["maya"] - families = ["colorbleed.camera"] - - def process(self, instance): - - file_names = [] - nodetype = 'camera' - # Define extract output file path - dir_path = self.staging_dir(instance) - alembic_as_baked = instance.data("cameraBakedAlembic", True) - - # get cameras - members = instance.data['setMembers'] - cameras = cmds.ls(members, leaf=True, shapes=True, - dag=True, type=nodetype) - - # Bake the cameras - transforms = cmds.listRelatives(cameras, parent=True, - fullPath=True) or [] - - framerange = [instance.data.get("startFrame", 1), - instance.data.get("endFrame", 1)] - - self.log.info("Performing camera bakes for: {0}".format(transforms)) - with context.evaluation("off"): - with context.no_refresh(): - baked = bakeToWorldSpace(transforms, frameRange=framerange) - - # Extract using the shape so it includes that and its hierarchy - # above. Otherwise Alembic takes only the transform - baked_shapes = cmds.ls(baked, type=nodetype, dag=True, - shapes=True, long=True) - - # Perform maya ascii extraction - filename = "{0}.ma".format(instance.name) - file_names.append(filename) - path = os.path.join(dir_path, filename) - - self.log.info("Performing extraction..") - with avalon.maya.maintained_selection(): - cmds.select(baked_shapes, noExpand=True) - cmds.file(path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=False, - constructionHistory=False, - channels=True, # allow animation - constraints=False, - shader=False, - expressions=False) - - massage_ma_file(path) - - # Perform alembic extraction - filename = "{0}.abc".format(instance.name) - file_names.append(filename) - path = os.path.join(dir_path, filename) - - if alembic_as_baked: - abc_shapes = baked_shapes - else: - # get cameras in the instance - members = instance.data['setMembers'] - abc_shapes = cmds.ls(members, leaf=True, shapes=True, dag=True, - long=True, type=nodetype) - - # Whenever the camera was baked and Maya's scene time warp was enabled - # then we want to disable it whenever we publish the baked camera - # otherwise we'll get double the scene time warping. But whenever - # we *do not* publish a baked camera we want to keep it enabled. This - # way what the artist has in the scene visually represents the output. - with context.timewarp(state=not alembic_as_baked): - with avalon.maya.maintained_selection(): - cmds.select(abc_shapes, replace=True, noExpand=True) - - # Enforce forward slashes for AbcExport because we're - # embedding it into a job string - path = path.replace("\\", "/") - - job_str = ' -selection -dataFormat "ogawa" ' - job_str += ' -attrPrefix cb' - job_str += ' -frameRange {0} {1} '.format(framerange[0], - framerange[1]) - job_str += ' -file "{0}"'.format(path) - - with context.evaluation("off"): - with context.no_refresh(): - cmds.AbcExport(j=job_str, verbose=False) - - # Delete the baked camera (using transform to leave no trace) - cmds.delete(baked) - - if "files" not in instance.data: - instance.data["files"] = list() - - instance.data["files"].extend(file_names) - - self.log.info("Extracted instance '{0}' to: {1}".format( - instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py new file mode 100644 index 0000000000..35a9d431c0 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py @@ -0,0 +1,134 @@ +import os + +from maya import cmds + +import avalon.maya +import colorbleed.api + +import cb.utils.maya.context as context +from cb.utils.maya.animation import bakeToWorldSpace + + +def massage_ma_file(path): + """Clean up .ma file for backwards compatibility. + + Massage the .ma of baked camera to stay + backwards compatible with older versions + of Fusion (6.4) + + """ + # Get open file's lines + f = open(path, "r+") + lines = f.readlines() + f.seek(0) # reset to start of file + + # Rewrite the file + for line in lines: + # Skip all 'rename -uid' lines + stripped = line.strip() + if stripped.startswith("rename -uid "): + continue + + f.write(line) + + f.truncate() # remove remainder + f.close() + + +class ExtractCameraMayaAscii(colorbleed.api.Extractor): + """Extract a Camera as Maya Ascii. + + This will create a duplicate of the camera that will be baked *with + substeps and handles for the required frames. This temporary duplicate + will be published. + + The cameras gets baked to world space by default. Only when the instance's + `bakeToWorldSpace` is set to False it will include its full hierarchy. + + Note: + The extracted Maya ascii file gets "massaged" removing the uuid values + so they are valid for older versions of Fusion (e.g. 6.4) + + """ + + label = "Camera (Maya Ascii)" + hosts = ["maya"] + families = ["colorbleed.camera"] + + def process(self, instance): + + # get settings + framerange = [instance.data.get("startFrame", 1), + instance.data.get("endFrame", 1)] + handles = instance.data.get("handles", 0) + step = instance.data.get("step", 1.0) + bake_to_worldspace = instance.data("bakeToWorldSpace", True) + + # TODO: Implement a bake to non-world space + # Currently it will always bake the resulting camera to world-space + # and it does not allow to include the parent hierarchy, even though + # with `bakeToWorldSpace` set to False it should include its hierarchy + # to be correct with the family implementation. + if not bake_to_worldspace: + self.log.warning("Camera (Maya Ascii) export only supports world" + "space baked camera extractions. The disabled " + "bake to world space is ignored...") + + # get cameras + members = instance.data['setMembers'] + cameras = cmds.ls(members, leaf=True, shapes=True, long=True, + dag=True, type="camera") + + range_with_handles = [framerange[0] - handles, + framerange[1] + handles] + + # validate required settings + assert len(cameras) == 1, "Not a single camera found in extraction" + assert isinstance(step, float), "Step must be a float value" + camera = cameras[0] + transform = cmds.listRelatives(camera, parent=True, fullPath=True) + + # Define extract output file path + dir_path = self.staging_dir(instance) + filename = "{0}.ma".format(instance.name) + path = os.path.join(dir_path, filename) + + # Perform extraction + self.log.info("Performing camera bakes for: {0}".format(transform)) + with avalon.maya.maintained_selection(): + with context.evaluation("off"): + with context.no_refresh(): + baked = bakeToWorldSpace(transform, + frameRange=range_with_handles, + step=step) + baked_shapes = cmds.ls(baked, + type="camera", + dag=True, + shapes=True, + long=True) + + self.log.info("Performing extraction..") + cmds.select(baked_shapes, noExpand=True) + cmds.file(path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + constructionHistory=False, + channels=True, # allow animation + constraints=False, + shader=False, + expressions=False) + + # Delete the baked hierarchy + cmds.delete(baked) + + massage_ma_file(path) + + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + + self.log.info("Extracted instance '{0}' to: {1}".format( + instance.name, path))