Merge branch 'develop' into maya_new_publisher

# Conflicts:
#	openpype/hosts/maya/api/lib.py
#	openpype/hosts/maya/api/plugin.py
#	openpype/hosts/maya/plugins/create/create_animation.py
#	openpype/hosts/maya/plugins/create/create_review.py
#	openpype/hosts/maya/plugins/load/load_reference.py
#	openpype/modules/deadline/plugins/publish/submit_maya_deadline.py
This commit is contained in:
Toke Stuart Jepsen 2023-05-16 10:23:11 +01:00
commit 544c043ece
451 changed files with 15825 additions and 3076 deletions

View file

@ -34,6 +34,16 @@ from openpype.pipeline import (
)
from openpype.lib import NumberDef
from openpype.pipeline.context_tools import get_current_project_asset
from openpype.pipeline.create import (
legacy_create,
get_legacy_creator_by_name,
)
from openpype.pipeline.context_tools import (
get_current_asset_name,
get_current_project_name,
get_current_task_name
)
from openpype.lib.profiles_filtering import filter_profiles
self = sys.modules[__name__]
@ -113,6 +123,18 @@ FLOAT_FPS = {23.98, 23.976, 29.97, 47.952, 59.94}
RENDERLIKE_INSTANCE_FAMILIES = ["rendering", "vrayscene"]
DISPLAY_LIGHTS_VALUES = [
"project_settings", "default", "all", "selected", "flat", "none"
]
DISPLAY_LIGHTS_LABELS = [
"Use Project Settings",
"Default Lighting",
"All Lights",
"Selected Lights",
"Flat Lighting",
"No Lights"
]
def get_main_window():
"""Acquire Maya's main window"""
@ -293,18 +315,23 @@ def collect_animation_defs(fps=False):
"""
# get scene values as defaults
start = cmds.playbackOptions(query=True, animationStartTime=True)
end = cmds.playbackOptions(query=True, animationEndTime=True)
frame_start = cmds.playbackOptions(query=True, minTime=True)
frame_end = cmds.playbackOptions(query=True, maxTime=True)
handle_start = cmds.playbackOptions(query=True, animationStartTime=True)
handle_end = cmds.playbackOptions(query=True, animationEndTime=True)
handle_start = frame_start - handle_start
handle_end = handle_end - frame_end
# build attributes
defs = [
NumberDef("frameStart",
label="Frame Start",
default=start,
default=frame_start,
decimals=0),
NumberDef("frameEnd",
label="Frame End",
default=end,
default=frame_end,
decimals=0),
NumberDef("handleStart",
label="Handle Start",
@ -2156,12 +2183,22 @@ def set_scene_resolution(width, height, pixelAspect):
cmds.setAttr("%s.pixelAspect" % control_node, pixelAspect)
def get_frame_range():
"""Get the current assets frame range and handles."""
def get_frame_range(include_animation_range=False):
"""Get the current assets frame range and handles.
Args:
include_animation_range (bool, optional): Whether to include
`animationStart` and `animationEnd` keys to define the outer
range of the timeline. It is excluded by default.
Returns:
dict: Asset's expected frame range values.
"""
# Set frame start/end
project_name = legacy_io.active_project()
asset_name = legacy_io.Session["AVALON_ASSET"]
project_name = get_current_project_name()
asset_name = get_current_asset_name()
asset = get_asset_by_name(project_name, asset_name)
frame_start = asset["data"].get("frameStart")
@ -2174,12 +2211,39 @@ def get_frame_range():
handle_start = asset["data"].get("handleStart") or 0
handle_end = asset["data"].get("handleEnd") or 0
return {
frame_range = {
"frameStart": frame_start,
"frameEnd": frame_end,
"handleStart": handle_start,
"handleEnd": handle_end
}
if include_animation_range:
# The animation range values are only included to define whether
# the Maya time slider should include the handles or not.
# Some usages of this function use the full dictionary to define
# instance attributes for which we want to exclude the animation
# keys. That is why these are excluded by default.
task_name = get_current_task_name()
settings = get_project_settings(project_name)
include_handles_settings = settings["maya"]["include_handles"]
current_task = asset.get("data").get("tasks").get(task_name)
animation_start = frame_start
animation_end = frame_end
include_handles = include_handles_settings["include_handles_default"]
for item in include_handles_settings["per_task_type"]:
if current_task["type"] in item["task_type"]:
include_handles = item["include_handles"]
break
if include_handles:
animation_start -= int(handle_start)
animation_end += int(handle_end)
frame_range["animationStart"] = animation_start
frame_range["animationEnd"] = animation_end
return frame_range
def reset_frame_range(playback=True, render=True, fps=True):
@ -2192,25 +2256,29 @@ def reset_frame_range(playback=True, render=True, fps=True):
Defaults to True.
fps (bool, Optional): Whether to set scene FPS. Defaults to True.
"""
if fps:
fps = convert_to_maya_fps(
float(legacy_io.Session.get("AVALON_FPS", 25))
)
set_scene_fps(fps)
frame_range = get_frame_range()
frame_range = get_frame_range(include_animation_range=True)
if not frame_range:
# No frame range data found for asset
return
frame_start = frame_range["frameStart"] - int(frame_range["handleStart"])
frame_end = frame_range["frameEnd"] + int(frame_range["handleEnd"])
frame_start = frame_range["frameStart"]
frame_end = frame_range["frameEnd"]
animation_start = frame_range["animationStart"]
animation_end = frame_range["animationEnd"]
if playback:
cmds.playbackOptions(minTime=frame_start)
cmds.playbackOptions(maxTime=frame_end)
cmds.playbackOptions(animationStartTime=frame_start)
cmds.playbackOptions(animationEndTime=frame_end)
cmds.playbackOptions(minTime=frame_start)
cmds.playbackOptions(maxTime=frame_end)
cmds.playbackOptions(
minTime=frame_start,
maxTime=frame_end,
animationStartTime=animation_start,
animationEndTime=animation_end
)
cmds.currentTime(frame_start)
if render:
@ -3681,7 +3749,17 @@ def get_color_management_preferences():
# Split view and display from view_transform. view_transform comes in
# format of "{view} ({display})".
regex = re.compile(r"^(?P<view>.+) \((?P<display>.+)\)$")
if int(cmds.about(version=True)) <= 2020:
# view_transform comes in format of "{view} {display}" in 2020.
regex = re.compile(r"^(?P<view>.+) (?P<display>.+)$")
match = regex.match(data["view_transform"])
if not match:
raise ValueError(
"Unable to parse view and display from Maya view transform: '{}' "
"using regex '{}'".format(data["view_transform"], regex.pattern)
)
data.update({
"display": match.group("display"),
"view": match.group("view")
@ -3838,3 +3916,98 @@ def get_all_children(nodes):
iterator.next() # noqa: B305
return list(traversed)
def get_capture_preset(task_name, task_type, subset, project_settings, log):
"""Get capture preset for playblasting.
Logic for transitioning from old style capture preset to new capture preset
profiles.
Args:
task_name (str): Task name.
take_type (str): Task type.
subset (str): Subset name.
project_settings (dict): Project settings.
log (object): Logging object.
"""
capture_preset = None
filtering_criteria = {
"hosts": "maya",
"families": "review",
"task_names": task_name,
"task_types": task_type,
"subset": subset
}
plugin_settings = project_settings["maya"]["publish"]["ExtractPlayblast"]
if plugin_settings["profiles"]:
profile = filter_profiles(
plugin_settings["profiles"],
filtering_criteria,
logger=log
)
capture_preset = profile.get("capture_preset")
else:
log.warning("No profiles present for Extract Playblast")
# Backward compatibility for deprecated Extract Playblast settings
# without profiles.
if capture_preset is None:
log.debug(
"Falling back to deprecated Extract Playblast capture preset "
"because no new style playblast profiles are defined."
)
capture_preset = plugin_settings["capture_preset"]
return capture_preset or {}
def create_rig_animation_instance(nodes, context, namespace, log=None):
"""Create an animation publish instance for loaded rigs.
See the RecreateRigAnimationInstance inventory action on how to use this
for loaded rig containers.
Arguments:
nodes (list): Member nodes of the rig instance.
context (dict): Representation context of the rig container
namespace (str): Namespace of the rig container
log (logging.Logger, optional): Logger to log to if provided
Returns:
None
"""
output = next((node for node in nodes if
node.endswith("out_SET")), None)
controls = next((node for node in nodes if
node.endswith("controls_SET")), None)
assert output, "No out_SET in rig, this is a bug."
assert controls, "No controls_SET in rig, this is a bug."
# Find the roots amongst the loaded nodes
roots = (
cmds.ls(nodes, assemblies=True, long=True) or
get_highest_in_hierarchy(nodes)
)
assert roots, "No root nodes in rig, this is a bug."
asset = legacy_io.Session["AVALON_ASSET"]
dependency = str(context["representation"]["_id"])
if log:
log.info("Creating subset: {}".format(namespace))
# Create the animation instance
creator_plugin = get_legacy_creator_by_name("CreateAnimation")
with maintained_selection():
cmds.select([output, controls] + roots, noExpand=True)
legacy_create(
creator_plugin,
name=namespace,
asset=asset,
options={"useSelection": True},
data={"dependencies": dependency}
)

View file

@ -4,6 +4,7 @@ from abc import (
ABCMeta
)
import six
import re
from maya import cmds
from maya.app.renderSetup.model import renderSetup
@ -24,7 +25,7 @@ from openpype.pipeline import (
)
from openpype.lib import BoolDef
from .lib import imprint, read
from openpype.pipeline.load import LoadError
from openpype.settings import get_project_settings
from .pipeline import containerise
@ -103,6 +104,44 @@ def get_reference_node_parents(ref):
return parents
def get_custom_namespace(custom_namespace):
"""Return unique namespace.
The input namespace can contain a single group
of '#' number tokens to indicate where the namespace's
unique index should go. The amount of tokens defines
the zero padding of the number, e.g ### turns into 001.
Warning: Note that a namespace will always be
prefixed with a _ if it starts with a digit
Example:
>>> get_custom_namespace("myspace_##_")
# myspace_01_
>>> get_custom_namespace("##_myspace")
# _01_myspace
>>> get_custom_namespace("myspace##")
# myspace01
"""
split = re.split("([#]+)", custom_namespace, 1)
if len(split) == 3:
base, padding, suffix = split
padding = "%0{}d".format(len(padding))
else:
base = split[0]
padding = "%02d" # default padding
suffix = ""
return lib.unique_namespace(
base,
format=padding,
prefix="_" if not base or base[0].isdigit() else "",
suffix=suffix
)
class Creator(LegacyCreator):
defaults = ['Main']
@ -539,15 +578,46 @@ class ReferenceLoader(Loader):
assert os.path.exists(self.fname), "%s does not exist." % self.fname
asset = context['asset']
subset = context['subset']
settings = get_project_settings(context['project']['name'])
custom_naming = settings['maya']['load']['reference_loader']
loaded_containers = []
count = options.get("count") or 1
for c in range(0, count):
namespace = namespace or lib.unique_namespace(
"{}_{}_".format(asset["name"], context["subset"]["name"]),
prefix="_" if asset["name"][0].isdigit() else "",
suffix="_",
if not custom_naming['namespace']:
raise LoadError("No namespace specified in "
"Maya ReferenceLoader settings")
elif not custom_naming['group_name']:
raise LoadError("No group name specified in "
"Maya ReferenceLoader settings")
formatting_data = {
"asset_name": asset['name'],
"asset_type": asset['type'],
"subset": subset['name'],
"family": (
subset['data'].get('family') or
subset['data']['families'][0]
)
}
custom_namespace = custom_naming['namespace'].format(
**formatting_data
)
custom_group_name = custom_naming['group_name'].format(
**formatting_data
)
count = options.get("count") or 1
for c in range(0, count):
namespace = get_custom_namespace(custom_namespace)
group_name = "{}:{}".format(
namespace,
custom_group_name
)
options['group_name'] = group_name
# Offset loaded subset
if "offset" in options:
@ -583,7 +653,7 @@ class ReferenceLoader(Loader):
return loaded_containers
def process_reference(self, context, name, namespace, data):
def process_reference(self, context, name, namespace, options):
"""To be implemented by subclass"""
raise NotImplementedError("Must be implemented by subclass")

View file

@ -43,7 +43,24 @@ class MayaTemplateBuilder(AbstractTemplateBuilder):
))
cmds.sets(name=PLACEHOLDER_SET, empty=True)
new_nodes = cmds.file(path, i=True, returnNewNodes=True)
new_nodes = cmds.file(
path,
i=True,
returnNewNodes=True,
preserveReferences=True,
loadReferenceDepth="all",
)
# make default cameras non-renderable
default_cameras = [cam for cam in cmds.ls(cameras=True)
if cmds.camera(cam, query=True, startupCamera=True)]
for cam in default_cameras:
if not cmds.attributeQuery("renderable", node=cam, exists=True):
self.log.debug(
"Camera {} has no attribute 'renderable'".format(cam)
)
continue
cmds.setAttr("{}.renderable".format(cam), 0)
cmds.setAttr(PLACEHOLDER_SET + ".hiddenInOutliner", True)
@ -234,26 +251,10 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin):
return self.get_load_plugin_options(options)
def cleanup_placeholder(self, placeholder, failed):
"""Hide placeholder, parent them to root
add them to placeholder set and register placeholder's parent
to keep placeholder info available for future use
"""Hide placeholder, add them to placeholder set
"""
node = placeholder._scene_identifier
node_parent = placeholder.data["parent"]
if node_parent:
cmds.setAttr(node + ".parent", node_parent, type="string")
if cmds.getAttr(node + ".index") < 0:
cmds.setAttr(node + ".index", placeholder.data["index"])
holding_sets = cmds.listSets(object=node)
if holding_sets:
for set in holding_sets:
cmds.sets(node, remove=set)
if cmds.listRelatives(node, p=True):
node = cmds.parent(node, world=True)[0]
cmds.sets(node, addElement=PLACEHOLDER_SET)
cmds.hide(node)
cmds.setAttr(node + ".hiddenInOutliner", True)
@ -286,8 +287,6 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin):
elif not cmds.sets(root, q=True):
return
if placeholder.data["parent"]:
cmds.parent(nodes_to_parent, placeholder.data["parent"])
# Move loaded nodes to correct index in outliner hierarchy
placeholder_form = cmds.xform(
placeholder.scene_identifier,