Maya: implement matchmove publishing (#5445)

* OP-6360 - allow export of multiple cameras as alembic

* OP-6360 - make validation of camera count optional

* OP-6360 - make ValidatorCameraContents optional

This validator checks number of cameras, without optionality publish wouldn't be possible.

* OP-6360 - allow extraction of multiple cameras to .ma

* OP-6360 - update defaults for Ayon

Changes to Ayon settings should also bump up version of addon.

* OP-6360 - new matchmove creator

This family should be for more complex sets (eg. multiple cameras, with geometry, planes etc.

* OP-6360 - updated camera extractors

Added matchmove family to extract multiple cameras.
Single camera is protected by required validator.

* OP-6360 - added matchmove to reference loader

* Revert "OP-6360 - make ValidatorCameraContents optional"

This reverts commit 4096e81f785b1299b54b1e485eb672403fb89a66.

* Revert "OP-6360 - update defaults for Ayon"

This reverts commit 4391b25cfc93fbb783146a726c6097477146c467.

* OP-6360 - performance update

Number of cameras might be quite large, set operations will be faster than loop.

* Revert "OP-6360 - make validation of camera count optional"

This reverts commit ee3d91a4cbec607b0f8cc9d47382684eba88d6d0.

* OP-6360 - explicitly cast to list for Maya functions

cmds.ls doesn't like sets in some older versions of Maya apparently. Sets are used here for performance reason, so explicitly cast them to list to make Maya happy.

* OP-6360 - added documentation about matchmove family

* OP-6360 - copy input planes

* OP-6360 - expose Settings to keep Image planes

Previous implementation didn't export Image planes in Maya file, to keep behavior backward compatible new Setting was added and set to False.

* OP-6360 - make both camera extractors optional

In Settings Alembic extractor was visible as optional even if code didn't follow that.

* OP-6360 - used long name

* OP-6360 - fix wrong variable

* Update openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py

Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com>

* OP-6360 - removed shortening of varible

* OP-6360 - Hound

* OP-6360 - fix wrong key

* Update openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py

Co-authored-by: Toke Jepsen <tokejepsen@gmail.com>

* Update openpype/hosts/maya/api/lib.py

Co-authored-by: Toke Jepsen <tokejepsen@gmail.com>

* Update openpype/hosts/maya/plugins/publish/extract_camera_alembic.py

Co-authored-by: Toke Jepsen <tokejepsen@gmail.com>

* OP-6360 - fix wrong variable

* OP-6360 - added reattaching method

Image planes were attached wrong, added method to reattach them properly.

* Revert "Update openpype/hosts/maya/api/lib.py"

This reverts commit 4f40ad613946903e8c51b2720ac52756e701f8b8.

* OP-6360 - exported baked camera should be deleted

Forgotten commenting just for development.

* OP-6360 - updated docstring

* OP-6360 - remove scale keys

Currently parentConstraint from old camera to new one doesn't work for keyed scale attributes. To key scale attributes doesn't make much sense so as a workaround, keys for scale attributes are checked AND if they are diferent from defaults (1.0) publish fails (as artist might want to actually key scale). If all scale keys are defaults, they are temporarily removed, cameras are parent constrained, exported and old camera returned to original state.

* OP-6360 - cleaned up resetting of scale keys

Batch calls used instead of one by one.
Cleaned up a return type as key value is no necessary as we are not setting it, just key.

* OP-6360 - removed unnecessary logging

* OP-6360 - reattach image plane to original camera

Image plane must be reattached before baked camera(s) are deleted.

* OP-6360 - added context manager to keep image planes attached to original camera

Without this image planes would disappear after removal of baked cameras.

* OP-6360 - refactored contextmanager

* OP-6360 - renamed flag

Input connections are not copied anymore as they might be dangerous. It is possible to epxlicitly attach only image planes instead.

* OP-6360 - removed copyInputConnections

Copying input connections might be dangerous (rig etc.), it is possible to explicitly attach only image planes.

* OP-6360 - updated plugin labels

* Update openpype/hosts/maya/plugins/create/create_matchmove.py

Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com>

* OP-6360 - fixed formatting

---------

Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com>
Co-authored-by: Toke Jepsen <tokejepsen@gmail.com>
This commit is contained in:
Petr Kalis 2023-09-29 17:33:28 +02:00 committed by GitHub
parent d83eb73e04
commit a66edaf1d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 229 additions and 77 deletions

View file

@ -2571,7 +2571,7 @@ def bake_to_world_space(nodes,
new_name = "{0}_baked".format(short_name)
new_node = cmds.duplicate(node,
name=new_name,
renameChildren=True)[0]
renameChildren=True)[0] # noqa
# Connect all attributes on the node except for transform
# attributes

View file

@ -0,0 +1,32 @@
from openpype.hosts.maya.api import (
lib,
plugin
)
from openpype.lib import BoolDef
class CreateMatchmove(plugin.MayaCreator):
"""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

View file

@ -101,7 +101,8 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
"camerarig",
"staticMesh",
"skeletalMesh",
"mvLook"]
"mvLook",
"matchmove"]
representations = ["ma", "abc", "fbx", "mb"]

View file

@ -6,17 +6,21 @@ from openpype.pipeline import publish
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.
"""
label = "Camera (Alembic)"
label = "Extract Camera (Alembic)"
hosts = ["maya"]
families = ["camera"]
families = ["camera", "matchmove"]
bake_attributes = []
def process(self, instance):
@ -35,10 +39,11 @@ class ExtractCameraAlembic(publish.Extractor):
# 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)
if not os.path.exists(dir_path):
os.makedirs(dir_path)
filename = "{0}.abc".format(instance.name)
path = os.path.join(dir_path, filename)
@ -64,9 +69,10 @@ class ExtractCameraAlembic(publish.Extractor):
# 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)
for member in members:
descendants = cmds.listRelatives(member,

View file

@ -2,11 +2,15 @@
"""Extract camera as Maya Scene."""
import os
import itertools
import contextlib
from maya import cmds
from openpype.pipeline import publish
from openpype.hosts.maya.api import lib
from openpype.lib import (
BoolDef
)
def massage_ma_file(path):
@ -78,7 +82,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*
@ -88,17 +93,22 @@ 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)
"""
label = "Camera (Maya Scene)"
label = "Extract Camera (Maya Scene)"
hosts = ["maya"]
families = ["camera"]
families = ["camera", "matchmove"]
scene_type = "ma"
keep_image_planes = True
def process(self, instance):
"""Plugin entry point."""
# get settings
@ -131,15 +141,15 @@ class ExtractCameraMayaScene(publish.Extractor):
"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)
@ -151,23 +161,21 @@ class ExtractCameraMayaScene(publish.Extractor):
with lib.evaluation("off"):
with lib.suspended_refresh():
if bake_to_worldspace:
self.log.debug(
"Performing camera bakes: {}".format(transform))
baked = lib.bake_to_world_space(
transform,
transforms,
frame_range=[start, end],
step=step
)
baked_camera_shapes = cmds.ls(baked,
type="camera",
dag=True,
shapes=True,
long=True)
baked_camera_shapes = set(cmds.ls(baked,
type="camera",
dag=True,
shapes=True,
long=True))
members = members + baked_camera_shapes
members.remove(camera)
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,
@ -186,19 +194,28 @@ class ExtractCameraMayaScene(publish.Extractor):
unlock(plug)
cmds.setAttr(plug, value)
self.log.debug("Performing extraction..")
cmds.select(cmds.ls(members, dag=True,
shapes=True, long=True), noExpand=True)
cmds.file(path,
force=True,
typ="mayaAscii" if self.scene_type == "ma" else "mayaBinary", # noqa: E501
exportSelected=True,
preserveReferences=False,
constructionHistory=False,
channels=True, # allow animation
constraints=False,
shader=False,
expressions=False)
attr_values = self.get_attr_values_from_data(
instance.data)
keep_image_planes = attr_values.get("keep_image_planes")
with transfer_image_planes(sorted(cameras),
sorted(baked_camera_shapes),
keep_image_planes):
self.log.info("Performing extraction..")
cmds.select(cmds.ls(list(members), dag=True,
shapes=True, long=True),
noExpand=True)
cmds.file(path,
force=True,
typ="mayaAscii" if self.scene_type == "ma" else "mayaBinary", # noqa: E501
exportSelected=True,
preserveReferences=False,
constructionHistory=False,
channels=True, # allow animation
constraints=False,
shader=False,
expressions=False)
# Delete the baked hierarchy
if bake_to_worldspace:
@ -219,3 +236,62 @@ class ExtractCameraMayaScene(publish.Extractor):
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_image_planes",
label="Keep Image Planes",
tooltip="Preserving connected image planes on camera",
default=cls.keep_image_planes),
])
return defs
@contextlib.contextmanager
def transfer_image_planes(source_cameras, target_cameras,
keep_input_connections):
"""Reattaches image planes to baked or original cameras.
Baked cameras are duplicates of original ones.
This attaches it to duplicated camera properly and after
export it reattaches it back to original to keep image plane in workfile.
"""
originals = {}
try:
for source_camera, target_camera in zip(source_cameras,
target_cameras):
image_planes = cmds.listConnections(source_camera,
type="imagePlane") or []
# Split of the parent path they are attached - we want
# the image plane node name.
# TODO: Does this still mean the image plane name is unique?
image_planes = [x.split("->", 1)[1] for x in image_planes]
if not image_planes:
continue
originals[source_camera] = []
for image_plane in image_planes:
if keep_input_connections:
if source_camera == target_camera:
continue
_attach_image_plane(target_camera, image_plane)
else: # explicitly dettaching image planes
cmds.imagePlane(image_plane, edit=True, detach=True)
originals[source_camera].append(image_plane)
yield
finally:
for camera, image_planes in originals.items():
for image_plane in image_planes:
_attach_image_plane(camera, image_plane)
def _attach_image_plane(camera, image_plane):
cmds.imagePlane(image_plane, edit=True, detach=True)
cmds.imagePlane(image_plane, edit=True, camera=camera)

View file

@ -1338,6 +1338,12 @@
"active": true,
"bake_attributes": []
},
"ExtractCameraMayaScene": {
"enabled": true,
"optional": true,
"active": true,
"keep_image_planes": false
},
"ExtractGLB": {
"enabled": true,
"active": true,

View file

@ -978,6 +978,35 @@
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "ExtractCameraMayaScene",
"label": "Extract camera to Maya scene",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "boolean",
"key": "optional",
"label": "Optional"
},
{
"type": "boolean",
"key": "active",
"label": "Active"
},
{
"type": "boolean",
"key": "keep_image_planes",
"label": "Export Image planes"
}
]
},
{
"type": "dict",
"collapsible": true,

View file

@ -33,39 +33,41 @@ The Instances are categorized into families based on what type of data the
Following family definitions and requirements are OpenPype defaults and what we consider good industry practice, but most of the requirements can be easily altered to suit the studio or project needs.
Here's a list of supported families
| Family | Comment | Example Subsets |
| ----------------------- | ------------------------------------------------ | ------------------------- |
| [Model](#model) | Cleaned geo without materials | main, proxy, broken |
| [Look](#look) | Package of shaders, assignments and textures | main, wet, dirty |
| [Rig](#rig) | Characters or props with animation controls | main, deform, sim |
| [Assembly](#assembly) | A complex model made from multiple other models. | main, deform, sim |
| [Layout](#layout) | Simple representation of the environment | main, |
| [Setdress](#setdress) | Environment containing only referenced assets | main, |
| [Camera](#camera) | May contain trackers or proxy geo | main, tracked, anim |
| [Animation](#animation) | Animation exported from a rig. | characterA, vehicleB |
| [Cache](#cache) | Arbitrary animated geometry or fx cache | rest, ROM , pose01 |
| MayaAscii | Maya publishes that don't fit other categories | |
| [Render](#render) | Rendered frames from CG or Comp | |
| RenderSetup | Scene render settings, AOVs and layers | |
| Plate | Ingested, transcode, conformed footage | raw, graded, imageplane |
| Write | Nuke write nodes for rendering | |
| Image | Any non-plate image to be used by artists | Reference, ConceptArt |
| LayeredImage | Software agnostic layered image with metadata | Reference, ConceptArt |
| Review | Reviewable video or image. | |
| Matchmove | Matchmoved camera, potentially with geometry | main |
| Workfile | Backup of the workfile with all its content | uses the task name |
| Nukenodes | Any collection of nuke nodes | maskSetup, usefulBackdrop |
| Yeticache | Cached out yeti fur setup | |
| YetiRig | Yeti groom ready to be applied to geometry cache | main, destroyed |
| VrayProxy | Vray proxy geometry for rendering | |
| VrayScene | Vray full scene export | |
| ArnodldStandin | All arnold .ass archives for rendering | main, wet, dirty |
| LUT | | |
| Nukenodes | | |
| Gizmo | | |
| Nukenodes | | |
| Harmony.template | | |
| Harmony.palette | | |
| Family | Comment | Example Subsets |
|-------------------------|-------------------------------------------------------| ------------------------- |
| [Model](#model) | Cleaned geo without materials | main, proxy, broken |
| [Look](#look) | Package of shaders, assignments and textures | main, wet, dirty |
| [Rig](#rig) | Characters or props with animation controls | main, deform, sim |
| [Assembly](#assembly) | A complex model made from multiple other models. | main, deform, sim |
| [Layout](#layout) | Simple representation of the environment | main, |
| [Setdress](#setdress) | Environment containing only referenced assets | main, |
| [Camera](#camera) | May contain trackers or proxy geo, only single camera | main, tracked, anim |
| | expected. | |
| [Animation](#animation) | Animation exported from a rig. | characterA, vehicleB |
| [Cache](#cache) | Arbitrary animated geometry or fx cache | rest, ROM , pose01 |
| MayaAscii | Maya publishes that don't fit other categories | |
| [Render](#render) | Rendered frames from CG or Comp | |
| RenderSetup | Scene render settings, AOVs and layers | |
| Plate | Ingested, transcode, conformed footage | raw, graded, imageplane |
| Write | Nuke write nodes for rendering | |
| Image | Any non-plate image to be used by artists | Reference, ConceptArt |
| LayeredImage | Software agnostic layered image with metadata | Reference, ConceptArt |
| Review | Reviewable video or image. | |
| Matchmove | Matchmoved camera, potentially with geometry, allows | main |
| | multiple cameras even with planes. | |
| Workfile | Backup of the workfile with all its content | uses the task name |
| Nukenodes | Any collection of nuke nodes | maskSetup, usefulBackdrop |
| Yeticache | Cached out yeti fur setup | |
| YetiRig | Yeti groom ready to be applied to geometry cache | main, destroyed |
| VrayProxy | Vray proxy geometry for rendering | |
| VrayScene | Vray full scene export | |
| ArnodldStandin | All arnold .ass archives for rendering | main, wet, dirty |
| LUT | | |
| Nukenodes | | |
| Gizmo | | |
| Nukenodes | | |
| Harmony.template | | |
| Harmony.palette | | |
@ -161,7 +163,7 @@ Example Representations:
### Animation
Published result of an animation created with a rig. Animation can be extracted
as animation curves, cached out geometry or even fully animated rig with all the controllers.
as animation curves, cached out geometry or even fully animated rig with all the controllers.
Animation cache is usually defined by a rigger in the rig file of a character or
by FX TD in the effects rig, to ensure consistency of outputs.