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.
This commit is contained in:
aardschok 2017-09-12 18:52:59 +02:00
parent cf82b2d0a9
commit 32fffa9aa4
3 changed files with 216 additions and 148 deletions

View file

@ -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))

View file

@ -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))

View file

@ -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))