[Automated] Merged develop into main

This commit is contained in:
ynbot 2023-07-05 05:30:49 +02:00 committed by GitHub
commit f99aa73fe5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 219 additions and 60 deletions

View file

@ -35,6 +35,7 @@ body:
label: Version
description: What version are you running? Look to OpenPype Tray
options:
- 3.15.12-nightly.2
- 3.15.12-nightly.1
- 3.15.11
- 3.15.11-nightly.5
@ -134,7 +135,6 @@ body:
- 3.14.4-nightly.4
- 3.14.4-nightly.3
- 3.14.4-nightly.2
- 3.14.4-nightly.1
validations:
required: true
- type: dropdown

View file

@ -73,6 +73,14 @@ class ValidateAbcPrimitiveToDetail(pyblish.api.InstancePlugin):
cls.log.debug("Checking Primitive to Detail pattern: %s" % pattern)
cls.log.debug("Checking with path attribute: %s" % path_attr)
if not hasattr(output_node, "geometry"):
# In the case someone has explicitly set an Object
# node instead of a SOP node in Geometry context
# then for now we ignore - this allows us to also
# export object transforms.
cls.log.warning("No geometry output node found, skipping check..")
return
# Check if the primitive attribute exists
frame = instance.data.get("frameStart", 0)
geo = output_node.geometryAtFrame(frame)

View file

@ -60,6 +60,14 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin):
cls.log.debug("Checking for attribute: %s" % path_attr)
if not hasattr(output_node, "geometry"):
# In the case someone has explicitly set an Object
# node instead of a SOP node in Geometry context
# then for now we ignore - this allows us to also
# export object transforms.
cls.log.warning("No geometry output node found, skipping check..")
return
# Check if the primitive attribute exists
frame = instance.data.get("frameStart", 0)
geo = output_node.geometryAtFrame(frame)

View file

@ -53,6 +53,8 @@ def update_mode_context(mode):
def get_geometry_at_frame(sop_node, frame, force=True):
"""Return geometry at frame but force a cooked value."""
if not hasattr(sop_node, "geometry"):
return
with update_mode_context(hou.updateMode.AutoUpdate):
sop_node.cook(force=force, frame_range=(frame, frame))
return sop_node.geometryAtFrame(frame)

View file

@ -3238,36 +3238,21 @@ def iter_shader_edits(relationships, shader_nodes, nodes_by_id, label=None):
def set_colorspace():
"""Set Colorspace from project configuration
"""
"""Set Colorspace from project configuration"""
# set color spaces for rendering space and view transforms
def _colormanage(**kwargs):
"""Wrapper around `cmds.colorManagementPrefs`.
This logs errors instead of raising an error so color management
settings get applied as much as possible.
"""
assert len(kwargs) == 1, "Must receive one keyword argument"
try:
cmds.colorManagementPrefs(edit=True, **kwargs)
log.debug("Setting Color Management Preference: {}".format(kwargs))
except RuntimeError as exc:
log.error(exc)
project_name = os.getenv("AVALON_PROJECT")
project_name = get_current_project_name()
imageio = get_project_settings(project_name)["maya"]["imageio"]
# ocio compatibility variables
ocio_v2_maya_version = 2022
maya_version = int(cmds.about(version=True))
ocio_v2_support = use_ocio_v2 = maya_version >= ocio_v2_maya_version
is_ocio_set = bool(os.environ.get("OCIO"))
root_dict = {}
use_workfile_settings = imageio.get("workfile", {}).get("enabled")
if use_workfile_settings:
root_dict = imageio["workfile"]
else:
# TODO: deprecated code from 3.15.5 - remove
# Maya 2022+ introduces new OCIO v2 color management settings that
# can override the old color management preferences. OpenPype has
@ -3290,40 +3275,63 @@ def set_colorspace():
if not isinstance(root_dict, dict):
msg = "set_colorspace(): argument should be dictionary"
log.error(msg)
return
else:
root_dict = imageio["workfile"]
# backward compatibility
# TODO: deprecated code from 3.15.5 - remove with deprecated code above
view_name = root_dict.get("viewTransform")
if view_name is None:
view_name = root_dict.get("viewName")
log.debug(">> root_dict: {}".format(pformat(root_dict)))
if not root_dict:
return
if root_dict:
# enable color management
cmds.colorManagementPrefs(e=True, cmEnabled=True)
cmds.colorManagementPrefs(e=True, ocioRulesEnabled=True)
# set color spaces for rendering space and view transforms
def _colormanage(**kwargs):
"""Wrapper around `cmds.colorManagementPrefs`.
# backward compatibility
# TODO: deprecated code from 3.15.5 - refactor to use new settings
view_name = root_dict.get("viewTransform")
if view_name is None:
view_name = root_dict.get("viewName")
This logs errors instead of raising an error so color management
settings get applied as much as possible.
if use_ocio_v2:
# Use Maya 2022+ default OCIO v2 config
"""
assert len(kwargs) == 1, "Must receive one keyword argument"
try:
cmds.colorManagementPrefs(edit=True, **kwargs)
log.debug("Setting Color Management Preference: {}".format(kwargs))
except RuntimeError as exc:
log.error(exc)
# enable color management
cmds.colorManagementPrefs(edit=True, cmEnabled=True)
cmds.colorManagementPrefs(edit=True, ocioRulesEnabled=True)
if use_ocio_v2:
log.info("Using Maya OCIO v2")
if not is_ocio_set:
# Set the Maya 2022+ default OCIO v2 config file path
log.info("Setting default Maya OCIO v2 config")
cmds.colorManagementPrefs(edit=True, configFilePath="")
# Note: Setting "" as value also sets this default however
# introduces a bug where launching a file on startup will prompt
# to save the empty scene before it, so we set using the path.
# This value has been the same for 2022, 2023 and 2024
path = "<MAYA_RESOURCES>/OCIO-configs/Maya2022-default/config.ocio"
cmds.colorManagementPrefs(edit=True, configFilePath=path)
# set rendering space and view transform
_colormanage(renderingSpaceName=root_dict["renderSpace"])
_colormanage(viewName=view_name)
_colormanage(displayName=root_dict["displayName"])
else:
# set rendering space and view transform
_colormanage(renderingSpaceName=root_dict["renderSpace"])
_colormanage(viewName=view_name)
_colormanage(displayName=root_dict["displayName"])
else:
log.info("Using Maya OCIO v1 (legacy)")
if not is_ocio_set:
# Set the Maya default config file path
log.info("Setting default Maya OCIO v1 legacy config")
cmds.colorManagementPrefs(edit=True, configFilePath="legacy")
# set rendering space and view transform
_colormanage(renderingSpaceName=root_dict["renderSpace"])
_colormanage(viewTransformName=view_name)
# set rendering space and view transform
_colormanage(renderingSpaceName=root_dict["renderSpace"])
_colormanage(viewTransformName=view_name)
@contextlib.contextmanager

View file

@ -2,6 +2,7 @@ import os
import errno
import logging
import contextlib
import shutil
from maya import utils, cmds, OpenMaya
import maya.api.OpenMaya as om
@ -113,6 +114,9 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost):
register_event_callback("taskChanged", on_task_changed)
register_event_callback("workfile.open.before", before_workfile_open)
register_event_callback("workfile.save.before", before_workfile_save)
register_event_callback(
"workfile.save.before", workfile_save_before_xgen
)
register_event_callback("workfile.save.after", after_workfile_save)
def open_workfile(self, filepath):
@ -681,6 +685,91 @@ def before_workfile_save(event):
create_workspace_mel(workdir_path, project_name)
def workfile_save_before_xgen(event):
"""Manage Xgen external files when switching context.
Xgen has various external files that needs to be unique and relative to the
workfile, so we need to copy and potentially overwrite these files when
switching context.
Args:
event (Event) - openpype/lib/events.py
"""
if not cmds.pluginInfo("xgenToolkit", query=True, loaded=True):
return
import xgenm
current_work_dir = legacy_io.Session["AVALON_WORKDIR"].replace("\\", "/")
expected_work_dir = event.data["workdir_path"].replace("\\", "/")
if current_work_dir == expected_work_dir:
return
palettes = cmds.ls(type="xgmPalette", long=True)
if not palettes:
return
transfers = []
overwrites = []
attribute_changes = {}
attrs = ["xgFileName", "xgBaseFile"]
for palette in palettes:
sanitized_palette = palette.replace("|", "")
project_path = xgenm.getAttr("xgProjectPath", sanitized_palette)
_, maya_extension = os.path.splitext(event.data["filename"])
for attr in attrs:
node_attr = "{}.{}".format(palette, attr)
attr_value = cmds.getAttr(node_attr)
if not attr_value:
continue
source = os.path.join(project_path, attr_value)
attr_value = event.data["filename"].replace(
maya_extension,
"__{}{}".format(
sanitized_palette.replace(":", "__"),
os.path.splitext(attr_value)[1]
)
)
target = os.path.join(expected_work_dir, attr_value)
transfers.append((source, target))
attribute_changes[node_attr] = attr_value
relative_path = xgenm.getAttr(
"xgDataPath", sanitized_palette
).split(os.pathsep)[0]
absolute_path = relative_path.replace("${PROJECT}", project_path)
for root, _, files in os.walk(absolute_path):
for f in files:
source = os.path.join(root, f).replace("\\", "/")
target = source.replace(project_path, expected_work_dir + "/")
transfers.append((source, target))
if os.path.exists(target):
overwrites.append(target)
# Ask user about overwriting files.
if overwrites:
log.warning(
"WARNING! Potential loss of data.\n\n"
"Found duplicate Xgen files in new context.\n{}".format(
"\n".join(overwrites)
)
)
return
for source, destination in transfers:
if not os.path.exists(os.path.dirname(destination)):
os.makedirs(os.path.dirname(destination))
shutil.copy(source, destination)
for attribute, value in attribute_changes.items():
cmds.setAttr(attribute, value, type="string")
def after_workfile_save(event):
workfile_name = event["filename"]
if (

View file

@ -272,7 +272,12 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin):
return
roots = cmds.sets(container, q=True)
ref_node = get_reference_node(roots)
ref_node = None
try:
ref_node = get_reference_node(roots)
except AssertionError as e:
self.log.info(e.args[0])
nodes_to_parent = []
for root in roots:
if ref_node:

View file

@ -4,6 +4,8 @@ from .utils import (
LoadError,
IncompatibleLoaderError,
InvalidRepresentationContext,
LoaderSwitchNotImplementedError,
LoaderNotFoundError,
get_repres_contexts,
get_contexts_for_repre_docs,
@ -55,6 +57,8 @@ __all__ = (
"LoadError",
"IncompatibleLoaderError",
"InvalidRepresentationContext",
"LoaderSwitchNotImplementedError",
"LoaderNotFoundError",
"get_repres_contexts",
"get_contexts_for_repre_docs",

View file

@ -79,6 +79,16 @@ class InvalidRepresentationContext(ValueError):
pass
class LoaderSwitchNotImplementedError(NotImplementedError):
"""Error when `switch` is used with Loader that has no implementation."""
pass
class LoaderNotFoundError(RuntimeError):
"""Error when Loader plugin for a loader name is not found."""
pass
def get_repres_contexts(representation_ids, dbcon=None):
"""Return parenthood context for representation.
@ -432,7 +442,10 @@ def remove_container(container):
Loader = _get_container_loader(container)
if not Loader:
raise RuntimeError("Can't remove container. See log for details.")
raise LoaderNotFoundError(
"Can't remove container because loader '{}' was not found."
.format(container.get("loader"))
)
loader = Loader(get_representation_context(container["representation"]))
return loader.remove(container)
@ -480,7 +493,10 @@ def update_container(container, version=-1):
# Run update on the Loader for this container
Loader = _get_container_loader(container)
if not Loader:
raise RuntimeError("Can't update container. See log for details.")
raise LoaderNotFoundError(
"Can't update container because loader '{}' was not found."
.format(container.get("loader"))
)
loader = Loader(get_representation_context(container["representation"]))
return loader.update(container, new_representation)
@ -502,15 +518,18 @@ def switch_container(container, representation, loader_plugin=None):
loader_plugin = _get_container_loader(container)
if not loader_plugin:
raise RuntimeError("Can't switch container. See log for details.")
raise LoaderNotFoundError(
"Can't switch container because loader '{}' was not found."
.format(container.get("loader"))
)
if not hasattr(loader_plugin, "switch"):
# Backwards compatibility (classes without switch support
# might be better to just have "switch" raise NotImplementedError
# on the base class of Loader\
raise RuntimeError("Loader '{}' does not support 'switch'".format(
loader_plugin.label
))
raise LoaderSwitchNotImplementedError(
"Loader {} does not support 'switch'".format(loader_plugin.label)
)
# Get the new representation to switch to
project_name = legacy_io.active_project()
@ -520,7 +539,11 @@ def switch_container(container, representation, loader_plugin=None):
new_context = get_representation_context(new_representation)
if not is_compatible_loader(loader_plugin, new_context):
raise AssertionError("Must be compatible Loader")
raise IncompatibleLoaderError(
"Loader {} is incompatible with {}".format(
loader_plugin.__name__, new_context["subset"]["name"]
)
)
loader = loader_plugin(new_context)

View file

@ -421,9 +421,9 @@
},
"workfile": {
"enabled": false,
"renderSpace": "ACEScg",
"displayName": "sRGB",
"viewName": "ACES 1.0 SDR-video"
"renderSpace": "ACES - ACEScg",
"displayName": "ACES",
"viewName": "sRGB"
},
"colorManagementPreference_v2": {
"enabled": true,

View file

@ -19,6 +19,9 @@ from openpype.pipeline.load import (
switch_container,
get_repres_contexts,
loaders_from_repre_context,
LoaderSwitchNotImplementedError,
IncompatibleLoaderError,
LoaderNotFoundError
)
from .widgets import (
@ -1298,19 +1301,28 @@ class SwitchAssetDialog(QtWidgets.QDialog):
else:
repre_doc = repres_by_name[container_repre_name]
error = None
try:
switch_container(container, repre_doc, loader)
except (
LoaderSwitchNotImplementedError,
IncompatibleLoaderError,
LoaderNotFoundError,
) as exc:
error = str(exc)
except Exception:
msg = (
error = (
"Switch asset failed. "
"Search console log for more details."
)
if error is not None:
log.warning((
"Couldn't switch asset."
"See traceback for more information."
)
log.warning(msg, exc_info=True)
), exc_info=True)
dialog = QtWidgets.QMessageBox(self)
dialog.setWindowTitle("Switch asset failed")
dialog.setText(
"Switch asset failed. Search console log for more details"
)
dialog.setText(error)
dialog.exec_()
self.switched.emit()