Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Maya: implement matchmove publishing #5445

Merged
merged 46 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
1720c77
OP-6360 - allow export of multiple cameras as alembic
kalisp Aug 10, 2023
ee3d91a
OP-6360 - make validation of camera count optional
kalisp Aug 10, 2023
4096e81
OP-6360 - make ValidatorCameraContents optional
kalisp Aug 11, 2023
96cafd1
OP-6360 - allow extraction of multiple cameras to .ma
kalisp Aug 11, 2023
4391b25
OP-6360 - update defaults for Ayon
kalisp Aug 11, 2023
635216a
OP-6360 - new matchmove creator
kalisp Aug 14, 2023
9cf0ef8
OP-6360 - updated camera extractors
kalisp Aug 14, 2023
6626da9
OP-6360 - added matchmove to reference loader
kalisp Aug 14, 2023
818e9ee
Revert "OP-6360 - make ValidatorCameraContents optional"
kalisp Aug 14, 2023
70da282
Revert "OP-6360 - update defaults for Ayon"
kalisp Aug 14, 2023
54613c3
OP-6360 - performance update
kalisp Aug 14, 2023
479ae76
Revert "OP-6360 - make validation of camera count optional"
kalisp Aug 15, 2023
cc32957
OP-6360 - explicitly cast to list for Maya functions
kalisp Aug 15, 2023
2953b5a
OP-6360 - added documentation about matchmove family
kalisp Aug 23, 2023
491c0b6
OP-6360 - copy input planes
kalisp Aug 28, 2023
da25aea
OP-6360 - expose Settings to keep Image planes
kalisp Aug 28, 2023
b4d0f72
OP-6360 - make both camera extractors optional
kalisp Aug 28, 2023
8b7bc7e
OP-6360 - used long name
kalisp Aug 28, 2023
bf9bb2e
OP-6360 - fix wrong variable
kalisp Aug 28, 2023
9050e3f
Update openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py
kalisp Aug 29, 2023
e5e2b54
Merge branch 'develop' of github.com:ynput/OpenPype into enhancement/…
kalisp Aug 29, 2023
e769940
Merge remote-tracking branch 'origin/enhancement/OP-6360_Maya-multipl…
kalisp Aug 29, 2023
2841590
OP-6360 - removed shortening of varible
kalisp Aug 29, 2023
35a23ac
OP-6360 - Hound
kalisp Aug 29, 2023
a65af8f
OP-6360 - fix wrong key
kalisp Aug 29, 2023
9f9bc99
Update openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py
kalisp Sep 1, 2023
4f40ad6
Update openpype/hosts/maya/api/lib.py
kalisp Sep 1, 2023
87dbaae
Update openpype/hosts/maya/plugins/publish/extract_camera_alembic.py
kalisp Sep 1, 2023
27ec8bc
OP-6360 - fix wrong variable
kalisp Sep 1, 2023
3647da9
OP-6360 - added reattaching method
kalisp Sep 1, 2023
9625bba
Revert "Update openpype/hosts/maya/api/lib.py"
kalisp Sep 1, 2023
5853f6e
OP-6360 - exported baked camera should be deleted
kalisp Sep 5, 2023
888b3e2
Merge branch 'develop' into enhancement/OP-6360_Maya-multiple-cameras…
kalisp Sep 25, 2023
b312e1a
OP-6360 - updated docstring
kalisp Sep 26, 2023
5e12ef0
OP-6360 - remove scale keys
kalisp Sep 26, 2023
a2aeed2
OP-6360 - cleaned up resetting of scale keys
kalisp Sep 26, 2023
084c20b
OP-6360 - removed unnecessary logging
kalisp Sep 27, 2023
fab7b54
OP-6360 - reattach image plane to original camera
kalisp Sep 27, 2023
4e9dc80
OP-6360 - added context manager to keep image planes attached to orig…
kalisp Sep 27, 2023
5bc201d
OP-6360 - refactored contextmanager
kalisp Sep 27, 2023
2c130e2
OP-6360 - renamed flag
kalisp Sep 29, 2023
99dd27a
OP-6360 - removed copyInputConnections
kalisp Sep 29, 2023
e2b5c44
OP-6360 - updated plugin labels
kalisp Sep 29, 2023
3cb6a9e
Update openpype/hosts/maya/plugins/create/create_matchmove.py
kalisp Sep 29, 2023
cbccbd4
OP-6360 - fixed formatting
kalisp Sep 29, 2023
b2cba2c
Merge remote-tracking branch 'origin/develop' into enhancement/OP-636…
kalisp Sep 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 60 additions & 2 deletions openpype/hosts/maya/api/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -2511,7 +2511,8 @@ def bake_to_world_space(nodes,
preserve_outside_keys=False,
disable_implicit_control=True,
shape=True,
step=1.0):
step=1.0,
copy_input_connections=False):
"""Bake the nodes to world space transformation (incl. other attributes)

Bakes the transforms to world space (while maintaining all its animated
Expand Down Expand Up @@ -2571,7 +2572,8 @@ def _get_attrs(node):
new_name = "{0}_baked".format(short_name)
new_node = cmds.duplicate(node,
name=new_name,
renameChildren=True)[0]
renameChildren=True,
inputConnections=copy_input_connections)[0] # noqa
kalisp marked this conversation as resolved.
Show resolved Hide resolved

# Connect all attributes on the node except for transform
# attributes
Expand Down Expand Up @@ -2622,10 +2624,14 @@ def _get_attrs(node):
for attr in transform_attrs:
cmds.setAttr('{0}.{1}'.format(new_node, attr), lock=False)

scale_keys = _remove_scale_keys(node)

# Constraints
delete_bin.extend(cmds.parentConstraint(node, new_node, mo=False))
delete_bin.extend(cmds.scaleConstraint(node, new_node, mo=False))

_set_scale_keys(scale_keys)

BigRoy marked this conversation as resolved.
Show resolved Hide resolved
world_space_nodes.append(new_node)

bake(world_space_nodes,
Expand All @@ -2639,6 +2645,58 @@ def _get_attrs(node):
return world_space_nodes


def _remove_scale_keys(node):
"""Remove keys on scale attributes on source camera.

Constraint parenting doesn't work with keyed scale attributes. This method
removes keys, they need to be set again after constraint parenting.
Args:
node (str): identifier of original camera
Returns
(dict): {camera.scaleAttribute : [time1, time2]}
Raises:
RuntimeError: when scale key is non default (eg. != 1.0)
"""
scale_attrs = set(["sx", "sy", "sz"])

saved_keys = {}
default_scale_value = 1.0
for attr in scale_attrs:
orig_node_attr = '{0}.{1}'.format(node, attr)

times = cmds.keyframe(orig_node_attr, query=True, timeChange=True)
if not times:
continue
else:
values = cmds.keyframe(orig_node_attr, query=True,
valueChange=True)
invalid_times = [time for time, value in zip(times, values) if
value != default_scale_value]
if invalid_times:
raise RuntimeError(
"{} scale keys contain non default values at {}.".format(
node, invalid_times) +
"These cannot be baked out. Please remove keys for " +
"scale attributes")
saved_keys[orig_node_attr] = [(time, time) for time in times]

for orig_node_attr, times in saved_keys.items():
cmds.cutKey(orig_node_attr, time=times, option="keys")

return saved_keys


def _set_scale_keys(scale_keys):
"""Adds keys back on scale attributes on source camera.

Args:
(dict): {camera.scaleAttribute : [(time1, time1), (time2, time2)]}
"""
for orig_node_attr, times in scale_keys.items():
cleaned_times = [time[0] for time in times]
cmds.setKeyframe(orig_node_attr, time=cleaned_times)


BigRoy marked this conversation as resolved.
Show resolved Hide resolved
def load_capture_preset(data=None):
"""Convert OpenPype Extract Playblast settings to `capture` arguments

Expand Down
32 changes: 32 additions & 0 deletions openpype/hosts/maya/plugins/create/create_matchmove.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from openpype.hosts.maya.api import (
lib,
plugin
)
from openpype.lib import BoolDef


class MatchMove(plugin.MayaCreator):
kalisp marked this conversation as resolved.
Show resolved Hide resolved
kalisp marked this conversation as resolved.
Show resolved Hide resolved
"""Instance for more complex setup of cameras.

Might contain multiple cameras, geometries etc.

It is expected to be extracted into .abc or .ma
"""

identifier = "io.openpype.creators.maya.matchmove"
label = "Matchmove"
family = "matchmove"
icon = "video-camera"

def get_instance_attr_defs(self):

defs = lib.collect_animation_defs()

defs.extend([
BoolDef("bakeToWorldSpace",
label="Bake Cameras to World-Space",
tooltip="Bake Cameras to World-Space",
default=True),
])

return defs
3 changes: 2 additions & 1 deletion openpype/hosts/maya/plugins/load/load_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
"camerarig",
"staticMesh",
"skeletalMesh",
"mvLook"]
"mvLook",
"matchmove"]

representations = ["ma", "abc", "fbx", "mb"]

Expand Down
20 changes: 13 additions & 7 deletions openpype/hosts/maya/plugins/publish/extract_camera_alembic.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@
from openpype.hosts.maya.api import lib


class ExtractCameraAlembic(publish.Extractor):
class ExtractCameraAlembic(publish.Extractor,
publish.OptionalPyblishPluginMixin):
"""Extract a Camera as Alembic.

The cameras gets baked to world space by default. Only when the instance's
The camera gets baked to world space by default. Only when the instance's
`bakeToWorldSpace` is set to False it will include its full hierarchy.

'camera' family expects only single camera, if multiple cameras are needed,
'matchmove' is better choice.
kalisp marked this conversation as resolved.
Show resolved Hide resolved

"""

label = "Camera (Alembic)"
hosts = ["maya"]
families = ["camera"]
families = ["camera", "matchmove"]
bake_attributes = []

def process(self, instance):
Expand All @@ -35,10 +39,11 @@ def process(self, instance):

# validate required settings
assert isinstance(step, float), "Step must be a float value"
camera = cameras[0]

# Define extract output file path
dir_path = self.staging_dir(instance)
tokejepsen marked this conversation as resolved.
Show resolved Hide resolved
if not os.path.exists(dir_path):
os.makedirs(dir_path)
filename = "{0}.abc".format(instance.name)
path = os.path.join(dir_path, filename)

Expand All @@ -64,9 +69,10 @@ def process(self, instance):

# if baked, drop the camera hierarchy to maintain
# clean output and backwards compatibility
camera_root = cmds.listRelatives(
camera, parent=True, fullPath=True)[0]
job_str += ' -root {0}'.format(camera_root)
camera_roots = cmds.listRelatives(
cameras, parent=True, fullPath=True)
for camera_root in camera_roots:
job_str += ' -root {0}'.format(camera_root)
kalisp marked this conversation as resolved.
Show resolved Hide resolved

for member in members:
descendants = cmds.listRelatives(member,
Expand Down
102 changes: 79 additions & 23 deletions openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

from openpype.pipeline import publish
from openpype.hosts.maya.api import lib
from openpype.lib import (
BoolDef
)


def massage_ma_file(path):
Expand Down Expand Up @@ -78,7 +81,8 @@ def unlock(plug):
cmds.disconnectAttr(source, destination)


class ExtractCameraMayaScene(publish.Extractor):
class ExtractCameraMayaScene(publish.Extractor,
publish.OptionalPyblishPluginMixin):
"""Extract a Camera as Maya Scene.

This will create a duplicate of the camera that will be baked *with*
Expand All @@ -88,6 +92,9 @@ class ExtractCameraMayaScene(publish.Extractor):
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.

'camera' family expects only single camera, if multiple cameras are needed,
'matchmove' is better choice.

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)
Expand All @@ -96,9 +103,11 @@ class ExtractCameraMayaScene(publish.Extractor):

label = "Camera (Maya Scene)"
hosts = ["maya"]
families = ["camera"]
families = ["camera", "matchmove"]
scene_type = "ma"

keep_input_connections = False

def process(self, instance):
"""Plugin entry point."""
# get settings
Expand Down Expand Up @@ -131,15 +140,15 @@ def process(self, instance):
"bake to world space is ignored...")

# get cameras
members = cmds.ls(instance.data['setMembers'], leaf=True, shapes=True,
long=True, dag=True)
cameras = cmds.ls(members, leaf=True, shapes=True, long=True,
dag=True, type="camera")
members = set(cmds.ls(instance.data['setMembers'], leaf=True,
shapes=True, long=True, dag=True))
cameras = set(cmds.ls(members, leaf=True, shapes=True, long=True,
dag=True, type="camera"))

# validate required settings
assert isinstance(step, float), "Step must be a float value"
camera = cameras[0]
transform = cmds.listRelatives(camera, parent=True, fullPath=True)
transforms = cmds.listRelatives(list(cameras),
parent=True, fullPath=True)

# Define extract output file path
dir_path = self.staging_dir(instance)
Expand All @@ -151,23 +160,31 @@ def process(self, instance):
with lib.evaluation("off"):
with lib.suspended_refresh():
if bake_to_worldspace:
self.log.debug(
"Performing camera bakes: {}".format(transform))
self.log.info(
"Performing camera bakes: {}".format(transforms))
attr_values = self.get_attr_values_from_data(
instance.data)
keep_input_connections = attr_values.get("keep_input_connections") # noqa

baked = lib.bake_to_world_space(
transform,
transforms,
frame_range=[start, end],
step=step
step=step,
copy_input_connections=keep_input_connections
)
baked_camera_shapes = cmds.ls(baked,
type="camera",
dag=True,
shapes=True,
long=True)

members = members + baked_camera_shapes
members.remove(camera)
baked_camera_shapes = set(cmds.ls(baked,
type="camera",
dag=True,
shapes=True,
long=True))

for cam in baked_camera_shapes:
self.ensure_image_plane_attachment(cam)
kalisp marked this conversation as resolved.
Show resolved Hide resolved

members.update(baked_camera_shapes)
members.difference_update(cameras)
else:
baked_camera_shapes = cmds.ls(cameras,
baked_camera_shapes = cmds.ls(list(cameras),
type="camera",
dag=True,
shapes=True,
Expand All @@ -186,8 +203,8 @@ def process(self, instance):
unlock(plug)
cmds.setAttr(plug, value)

self.log.debug("Performing extraction..")
cmds.select(cmds.ls(members, dag=True,
self.log.info("Performing extraction..")
cmds.select(cmds.ls(list(members), dag=True,
shapes=True, long=True), noExpand=True)
cmds.file(path,
force=True,
Expand All @@ -202,6 +219,8 @@ def process(self, instance):

# Delete the baked hierarchy
if bake_to_worldspace:
for cam in baked_camera_shapes:
self.ensure_image_plane_attachment(cam, True)
cmds.delete(baked)
if self.scene_type == "ma":
massage_ma_file(path)
Expand All @@ -219,3 +238,40 @@ def process(self, instance):

self.log.debug("Extracted instance '{0}' to: {1}".format(
instance.name, path))

@classmethod
def get_attribute_defs(cls):
defs = super(ExtractCameraMayaScene, cls).get_attribute_defs()

defs.extend([
BoolDef("keep_input_connections",
label="Keep Input Connections",
tooltip="Preserving input connections will allow Image Planes to stay connected on publish", # noqa
default=cls.keep_input_connections),

])

return defs

def ensure_image_plane_attachment(self, camera, reattach=False):
"""Reattaches image planes to baked or original cameras.

Baked cameras are duplicates of original ones, even with
`keep_input_connections` the connection of image plane is still on the
original one. This attaches it to duplicated camera properly.
If 'reattach' it attaches back image plane to original camera.
TODO figure out use of some context manager
(`delete_after` doesnt work)
kalisp marked this conversation as resolved.
Show resolved Hide resolved
"""
image_planes = cmds.listConnections(camera, type="imagePlane")
if not image_planes:
return
if reattach:
# find original camera name
camera = camera[0:camera.find("_baked")]
kalisp marked this conversation as resolved.
Show resolved Hide resolved

image_planes = [x.split("->")[1] for x in image_planes]
kalisp marked this conversation as resolved.
Show resolved Hide resolved
for image_plane in image_planes:
self.log.debug("reattaching {}".format(image_plane))
cmds.imagePlane(image_plane, edit=True, detach=True)
cmds.imagePlane(image_plane, edit=True, camera=camera)
6 changes: 6 additions & 0 deletions openpype/settings/defaults/project_settings/maya.json
Original file line number Diff line number Diff line change
Expand Up @@ -1338,6 +1338,12 @@
"active": true,
"bake_attributes": []
},
"ExtractCameraMayaScene": {
"enabled": true,
"optional": true,
"active": true,
"keep_input_connections": false
},
"ExtractGLB": {
"enabled": true,
"active": true,
Expand Down
Loading
Loading