mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
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:
parent
cf82b2d0a9
commit
32fffa9aa4
3 changed files with 216 additions and 148 deletions
82
colorbleed/plugins/maya/publish/extract_camera_alembic.py
Normal file
82
colorbleed/plugins/maya/publish/extract_camera_alembic.py
Normal 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))
|
||||
|
|
@ -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))
|
||||
134
colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py
Normal file
134
colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py
Normal 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))
|
||||
Loading…
Add table
Add a link
Reference in a new issue