Merge branch 'develop' into enhancement/reduce-system-settings-conversion

This commit is contained in:
Jakub Trllo 2024-02-23 13:54:08 +01:00
commit 83326b3581
20 changed files with 269 additions and 74 deletions

View file

@ -0,0 +1,42 @@
from pymxs import runtime as rt
import pyblish.api
from ayon_core.pipeline.publish import get_errored_instances_from_context
class SelectInvalidAction(pyblish.api.Action):
"""Select invalid objects in Blender when a publish plug-in failed."""
label = "Select Invalid"
on = "failed"
icon = "search"
def process(self, context, plugin):
errored_instances = get_errored_instances_from_context(context,
plugin=plugin)
# Get the invalid nodes for the plug-ins
self.log.info("Finding invalid nodes...")
invalid = list()
for instance in errored_instances:
invalid_nodes = plugin.get_invalid(instance)
if invalid_nodes:
if isinstance(invalid_nodes, (list, tuple)):
invalid.extend(invalid_nodes)
else:
self.log.warning(
"Failed plug-in doesn't have any selectable objects."
)
if not invalid:
self.log.info("No invalid nodes found.")
return
invalid_names = [obj.name for obj in invalid if not isinstance(obj, tuple)]
if not invalid_names:
invalid_names = [obj.name for obj, _ in invalid]
invalid = [obj for obj, _ in invalid]
self.log.info(
"Selecting invalid objects: %s", ", ".join(invalid_names)
)
rt.Select(invalid)

View file

@ -245,3 +245,27 @@ def get_previous_loaded_object(container: str):
if str(obj) in sel_list:
node_list.append(obj)
return node_list
def remove_container_data(container_node: str):
"""Function to remove container data after updating, switching or deleting it.
Args:
container_node (str): container node
"""
if container_node.modifiers[0].name == "OP Data":
all_set_members_names = [
member.node for member
in container_node.modifiers[0].openPypeData.all_handles]
# clean up the children of alembic dummy objects
for current_set_member in all_set_members_names:
shape_list = [members for members in current_set_member.Children
if rt.ClassOf(members) == rt.AlembicObject
or rt.isValidNode(members)]
if shape_list: # noqa
rt.Delete(shape_list)
rt.Delete(current_set_member)
rt.deleteModifier(container_node, container_node.modifiers[0])
rt.Delete(container_node)
rt.redrawViews()

View file

@ -1,6 +1,6 @@
import os
from ayon_core.hosts.max.api import lib, maintained_selection
from ayon_core.hosts.max.api import lib
from ayon_core.hosts.max.api.lib import (
unique_namespace,
get_namespace,
@ -9,7 +9,8 @@ from ayon_core.hosts.max.api.lib import (
from ayon_core.hosts.max.api.pipeline import (
containerise,
get_previous_loaded_object,
update_custom_attribute_data
update_custom_attribute_data,
remove_container_data
)
from ayon_core.pipeline import get_representation_path, load
@ -96,4 +97,4 @@ class FbxLoader(load.LoaderPlugin):
from pymxs import runtime as rt
node = rt.GetNodeByName(container["instance_node"])
rt.Delete(node)
remove_container_data(node)

View file

@ -8,7 +8,8 @@ from ayon_core.hosts.max.api.lib import (
)
from ayon_core.hosts.max.api.pipeline import (
containerise, get_previous_loaded_object,
update_custom_attribute_data
update_custom_attribute_data,
remove_container_data
)
from ayon_core.pipeline import get_representation_path, load
@ -93,6 +94,5 @@ class MaxSceneLoader(load.LoaderPlugin):
def remove(self, container):
from pymxs import runtime as rt
node = rt.GetNodeByName(container["instance_node"])
rt.Delete(node)
remove_container_data(node)

View file

@ -2,7 +2,8 @@ import os
from ayon_core.pipeline import load, get_representation_path
from ayon_core.hosts.max.api.pipeline import (
containerise,
get_previous_loaded_object
get_previous_loaded_object,
remove_container_data
)
from ayon_core.hosts.max.api import lib
from ayon_core.hosts.max.api.lib import (
@ -97,9 +98,9 @@ class ModelAbcLoader(load.LoaderPlugin):
def remove(self, container):
from pymxs import runtime as rt
node = rt.GetNodeByName(container["instance_node"])
rt.Delete(node)
remove_container_data(node)
@staticmethod
def get_container_children(parent, type_name):

View file

@ -2,7 +2,8 @@ import os
from ayon_core.pipeline import load, get_representation_path
from ayon_core.hosts.max.api.pipeline import (
containerise, get_previous_loaded_object,
update_custom_attribute_data
update_custom_attribute_data,
remove_container_data
)
from ayon_core.hosts.max.api import lib
from ayon_core.hosts.max.api.lib import (
@ -92,6 +93,5 @@ class FbxModelLoader(load.LoaderPlugin):
def remove(self, container):
from pymxs import runtime as rt
node = rt.GetNodeByName(container["instance_node"])
rt.Delete(node)
remove_container_data(node)

View file

@ -11,7 +11,8 @@ from ayon_core.hosts.max.api.lib import maintained_selection
from ayon_core.hosts.max.api.pipeline import (
containerise,
get_previous_loaded_object,
update_custom_attribute_data
update_custom_attribute_data,
remove_container_data
)
from ayon_core.pipeline import get_representation_path, load
@ -84,6 +85,5 @@ class ObjLoader(load.LoaderPlugin):
def remove(self, container):
from pymxs import runtime as rt
node = rt.GetNodeByName(container["instance_node"])
rt.Delete(node)
remove_container_data(node)

View file

@ -13,7 +13,8 @@ from ayon_core.hosts.max.api.lib import maintained_selection
from ayon_core.hosts.max.api.pipeline import (
containerise,
get_previous_loaded_object,
update_custom_attribute_data
update_custom_attribute_data,
remove_container_data
)
from ayon_core.pipeline import get_representation_path, load
@ -113,5 +114,6 @@ class ModelUSDLoader(load.LoaderPlugin):
self.update(container, representation)
def remove(self, container):
from pymxs import runtime as rt
node = rt.GetNodeByName(container["instance_node"])
rt.Delete(node)
remove_container_data(node)

View file

@ -10,7 +10,8 @@ from ayon_core.hosts.max.api import lib, maintained_selection
from ayon_core.hosts.max.api.lib import unique_namespace
from ayon_core.hosts.max.api.pipeline import (
containerise,
get_previous_loaded_object
get_previous_loaded_object,
remove_container_data
)
@ -103,9 +104,9 @@ class AbcLoader(load.LoaderPlugin):
def remove(self, container):
from pymxs import runtime as rt
node = rt.GetNodeByName(container["instance_node"])
rt.Delete(node)
remove_container_data(node)
@staticmethod
def get_container_children(parent, type_name):

View file

@ -4,7 +4,8 @@ from ayon_core.pipeline.load import LoadError
from ayon_core.hosts.max.api.pipeline import (
containerise,
get_previous_loaded_object,
update_custom_attribute_data
update_custom_attribute_data,
remove_container_data
)
from ayon_core.hosts.max.api.lib import (
@ -104,5 +105,6 @@ class OxAbcLoader(load.LoaderPlugin):
self.update(container, representation)
def remove(self, container):
from pymxs import runtime as rt
node = rt.GetNodeByName(container["instance_node"])
rt.Delete(node)
remove_container_data(node)

View file

@ -8,7 +8,8 @@ from ayon_core.hosts.max.api.lib import (
from ayon_core.hosts.max.api.pipeline import (
containerise,
get_previous_loaded_object,
update_custom_attribute_data
update_custom_attribute_data,
remove_container_data
)
from ayon_core.pipeline import get_representation_path, load
@ -63,6 +64,5 @@ class PointCloudLoader(load.LoaderPlugin):
def remove(self, container):
"""remove the container"""
from pymxs import runtime as rt
node = rt.GetNodeByName(container["instance_node"])
rt.Delete(node)
remove_container_data(node)

View file

@ -9,7 +9,8 @@ from ayon_core.pipeline.load import LoadError
from ayon_core.hosts.max.api.pipeline import (
containerise,
update_custom_attribute_data,
get_previous_loaded_object
get_previous_loaded_object,
remove_container_data
)
from ayon_core.hosts.max.api import lib
from ayon_core.hosts.max.api.lib import (
@ -72,6 +73,5 @@ class RedshiftProxyLoader(load.LoaderPlugin):
def remove(self, container):
from pymxs import runtime as rt
node = rt.getNodeByName(container["instance_node"])
rt.delete(node)
node = rt.GetNodeByName(container["instance_node"])
remove_container_data(node)

View file

@ -7,7 +7,8 @@ from ayon_core.hosts.max.api.lib import (
from ayon_core.hosts.max.api.pipeline import (
containerise,
get_previous_loaded_object,
update_custom_attribute_data
update_custom_attribute_data,
remove_container_data
)
from ayon_core.pipeline import get_representation_path, load
@ -59,6 +60,5 @@ class TyCacheLoader(load.LoaderPlugin):
def remove(self, container):
"""remove the container"""
from pymxs import runtime as rt
node = rt.GetNodeByName(container["instance_node"])
rt.Delete(node)
remove_container_data(node)

View file

@ -0,0 +1,88 @@
import pyblish.api
from pymxs import runtime as rt
from ayon_core.pipeline.publish import (
RepairAction,
OptionalPyblishPluginMixin,
PublishValidationError
)
from ayon_core.hosts.max.api.action import SelectInvalidAction
class ValidateCameraAttributes(OptionalPyblishPluginMixin,
pyblish.api.InstancePlugin):
"""Validates Camera has no invalid attribute properties
or values.(For 3dsMax Cameras only)
"""
order = pyblish.api.ValidatorOrder
families = ['camera']
hosts = ['max']
label = 'Validate Camera Attributes'
actions = [SelectInvalidAction, RepairAction]
optional = True
DEFAULTS = ["fov", "nearrange", "farrange",
"nearclip", "farclip"]
CAM_TYPE = ["Freecamera", "Targetcamera",
"Physical"]
@classmethod
def get_invalid(cls, instance):
invalid = []
if rt.units.DisplayType != rt.Name("Generic"):
cls.log.warning(
"Generic Type is not used as a scene unit\n\n"
"sure you tweak the settings with your own values\n\n"
"before validation.")
cameras = instance.data["members"]
project_settings = instance.context.data["project_settings"].get("max")
cam_attr_settings = (
project_settings["publish"]["ValidateCameraAttributes"]
)
for camera in cameras:
if str(rt.ClassOf(camera)) not in cls.CAM_TYPE:
cls.log.debug(
"Skipping camera created from external plugin..")
continue
for attr in cls.DEFAULTS:
default_value = cam_attr_settings.get(attr)
if default_value == float(0):
cls.log.debug(
f"the value of {attr} in setting set to"
" zero. Skipping the check.")
continue
if round(rt.getProperty(camera, attr), 1) != default_value:
cls.log.error(
f"Invalid attribute value for {camera.name}:{attr} "
f"(should be: {default_value}))")
invalid.append(camera)
return invalid
def process(self, instance):
if not self.is_active(instance.data):
self.log.debug("Skipping Validate Camera Attributes.")
return
invalid = self.get_invalid(instance)
if invalid:
raise PublishValidationError(
"Invalid camera attributes found. See log.")
@classmethod
def repair(cls, instance):
invalid_cameras = cls.get_invalid(instance)
project_settings = instance.context.data["project_settings"].get("max")
cam_attr_settings = (
project_settings["publish"]["ValidateCameraAttributes"]
)
for camera in invalid_cameras:
for attr in cls.DEFAULTS:
expected_value = cam_attr_settings.get(attr)
if expected_value == float(0):
cls.log.debug(
f"the value of {attr} in setting set to zero.")
continue
rt.setProperty(camera, attr, expected_value)

View file

@ -108,7 +108,7 @@ class BaseCreateRoyalRenderJob(pyblish.api.InstancePlugin,
context = instance.context
self._rr_root = instance.data.get("rrPathName")
self._rr_root = instance.data.get("rr_root")
if not self._rr_root:
raise KnownPublishError(
("Missing RoyalRender root. "

View file

@ -1,41 +1,52 @@
# -*- coding: utf-8 -*-
"""
Requires:
instance.context.data["project_settings"]
Provides:
instance.data["rr_root"] (str) - root folder of RoyalRender server
"""
import os.path
import pyblish.api
from ayon_core.modules.royalrender.rr_job import get_rr_platform
class CollectRRPathFromInstance(pyblish.api.InstancePlugin):
"""Collect RR Path from instance."""
"""Collect RR Path from instance.
All RoyalRender server roots are set in `Studio Settings`, each project
uses only key pointing to that part to limit typos inside of Project
settings.
Eventually could be possible to add dropdown with these keys to the
Creators to allow artists to select which RR server they would like to use.
"""
order = pyblish.api.CollectorOrder
label = "Collect Royal Render path name from the Instance"
families = ["render", "prerender", "renderlayer"]
def process(self, instance):
instance.data["rrPathName"] = self._collect_rr_path_name(instance)
instance.data["rr_root"] = self._collect_root(instance)
self.log.info(
"Using '{}' for submission.".format(instance.data["rrPathName"]))
"Using '{}' for submission.".format(instance.data["rr_root"]))
@staticmethod
def _collect_rr_path_name(instance):
def _collect_root(self, instance):
# type: (pyblish.api.Instance) -> str
"""Get Royal Render pat name from render instance."""
# TODO there are no "rrPaths" on instance, if Publisher should expose
# this (eg. artist could select specific server) it must be added
# to publisher
instance_rr_paths = instance.data.get("rrPaths")
if instance_rr_paths is None:
return "default"
"""Get Royal Render pat name from render instance.
If artist should be able to select specific RR server it must be added
to creator. It is not there yet.
"""
rr_settings = instance.context.data["project_settings"]["royalrender"]
rr_paths = rr_settings["rr_paths"]
selected_paths = rr_settings["selected_rr_paths"]
selected_keys = rr_settings["selected_rr_paths"]
rr_servers = {
path_key
for path_key in selected_paths
if path_key in rr_paths
platform = get_rr_platform()
key_to_path = {
item["name"]: item["value"][platform]
for item in rr_paths
}
for instance_rr_path in instance_rr_paths:
if instance_rr_path in rr_servers:
return instance_rr_path
return "default"
for selected_key in selected_keys:
rr_root = key_to_path[selected_key]
if os.path.exists(rr_root):
return rr_root

View file

@ -37,8 +37,8 @@ class SubmitJobsToRoyalRender(pyblish.api.ContextPlugin):
isinstance(job, RRJob)
for job in instance.data.get("rrJobs")):
jobs += instance.data.get("rrJobs")
if instance.data.get("rrPathName"):
instance_rr_path = instance.data["rrPathName"]
if instance.data.get("rr_root"):
instance_rr_path = instance.data["rr_root"]
if jobs:
self._rr_root = instance_rr_path

View file

@ -25,24 +25,9 @@ from ayon_core.client import get_ayon_server_api_connection
# --------- Project settings ---------
def _convert_royalrender_project_settings(ayon_settings, output):
if "royalrender" not in ayon_settings:
return
ayon_royalrender = ayon_settings["royalrender"]
rr_paths = ayon_royalrender.get("selected_rr_paths", [])
output["royalrender"] = {
"publish": ayon_royalrender["publish"],
"rr_paths": rr_paths,
}
def convert_project_settings(ayon_settings, default_settings):
default_settings = copy.deepcopy(default_settings)
output = {}
_convert_royalrender_project_settings(ayon_settings, output)
for key, value in ayon_settings.items():
if key not in output:
output[key] = value

View file

@ -56,6 +56,16 @@
"enabled": false,
"attributes": {}
},
"ValidateCameraAttributes": {
"enabled": true,
"optional": true,
"active": false,
"fov": 45.0,
"nearrange": 0.0,
"farrange": 1000.0,
"nearclip": 1.0,
"farclip": 1000.0
},
"ValidateLoadedPlugin": {
"enabled": false,
"optional": true,

View file

@ -27,6 +27,17 @@ class ValidateAttributesModel(BaseSettingsModel):
return value
class ValidateCameraAttributesModel(BaseSettingsModel):
enabled: bool = SettingsField(title="Enabled")
optional: bool = SettingsField(title="Optional")
active: bool = SettingsField(title="Active")
fov: float = SettingsField(0.0, title="Focal Length")
nearrange: float = SettingsField(0.0, title="Near Range")
farrange: float = SettingsField(0.0, title="Far Range")
nearclip: float = SettingsField(0.0, title="Near Clip")
farclip: float = SettingsField(0.0, title="Far Clip")
class FamilyMappingItemModel(BaseSettingsModel):
families: list[str] = SettingsField(
default_factory=list,
@ -63,7 +74,14 @@ class PublishersModel(BaseSettingsModel):
default_factory=ValidateAttributesModel,
title="Validate Attributes"
)
ValidateCameraAttributes: ValidateCameraAttributesModel = SettingsField(
default_factory=ValidateCameraAttributesModel,
title="Validate Camera Attributes",
description=(
"If the value of the camera attributes set to 0, "
"the system automatically skips checking it"
)
)
ValidateLoadedPlugin: ValidateLoadedPluginModel = SettingsField(
default_factory=ValidateLoadedPluginModel,
title="Validate Loaded Plugin"
@ -101,6 +119,16 @@ DEFAULT_PUBLISH_SETTINGS = {
"enabled": False,
"attributes": "{}"
},
"ValidateCameraAttributes": {
"enabled": True,
"optional": True,
"active": False,
"fov": 45.0,
"nearrange": 0.0,
"farrange": 1000.0,
"nearclip": 1.0,
"farclip": 1000.0
},
"ValidateLoadedPlugin": {
"enabled": False,
"optional": True,