mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into enhancement/OP-8105_Validate-Context
This commit is contained in:
commit
672685bf58
37 changed files with 855 additions and 599 deletions
|
|
@ -54,25 +54,36 @@ class LoadClip(phiero.SequenceLoader):
|
|||
|
||||
plugin_name = cls.__name__
|
||||
|
||||
plugin_settings = None
|
||||
# Look for plugin settings in host specific settings
|
||||
if plugin_name in plugin_type_settings:
|
||||
plugin_settings = plugin_type_settings[plugin_name]
|
||||
|
||||
plugin_settings = plugin_type_settings.get(plugin_name)
|
||||
if not plugin_settings:
|
||||
return
|
||||
|
||||
print(">>> We have preset for {}".format(plugin_name))
|
||||
for option, value in plugin_settings.items():
|
||||
if option == "representations":
|
||||
continue
|
||||
|
||||
if option == "product_types":
|
||||
# TODO remove the key conversion when loaders can filter by
|
||||
# product types
|
||||
# convert 'product_types' to 'families'
|
||||
option = "families"
|
||||
|
||||
elif option == "clip_name_template":
|
||||
# TODO remove the formatting replacement
|
||||
value = (
|
||||
value
|
||||
.replace("{folder[name]}", "{asset}")
|
||||
.replace("{product[name]}", "{subset}")
|
||||
)
|
||||
|
||||
if option == "enabled" and value is False:
|
||||
print(" - is disabled by preset")
|
||||
elif option == "representations":
|
||||
continue
|
||||
else:
|
||||
print(" - setting `{}`: `{}`".format(option, value))
|
||||
setattr(cls, option, value)
|
||||
|
||||
|
||||
def load(self, context, name, namespace, options):
|
||||
# add clip name template to options
|
||||
options.update({
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -720,17 +720,17 @@ def get_created_node_imageio_setting_legacy(nodeclass, creator, subset):
|
|||
"`{}`: Missing mandatory kwargs `host`, `cls`".format(__file__))
|
||||
|
||||
imageio_nodes = get_nuke_imageio_settings()["nodes"]
|
||||
required_nodes = imageio_nodes["requiredNodes"]
|
||||
required_nodes = imageio_nodes["required_nodes"]
|
||||
|
||||
# HACK: for backward compatibility this needs to be optional
|
||||
override_nodes = imageio_nodes.get("overrideNodes", [])
|
||||
override_nodes = imageio_nodes.get("override_nodes", [])
|
||||
|
||||
imageio_node = None
|
||||
for node in required_nodes:
|
||||
log.info(node)
|
||||
if (
|
||||
nodeclass in node["nukeNodeClass"]
|
||||
and creator in node["plugins"]
|
||||
nodeclass in node["nuke_node_class"]
|
||||
and creator in node["plugins"]
|
||||
):
|
||||
imageio_node = node
|
||||
break
|
||||
|
|
@ -741,10 +741,10 @@ def get_created_node_imageio_setting_legacy(nodeclass, creator, subset):
|
|||
override_imageio_node = None
|
||||
for onode in override_nodes:
|
||||
log.info(onode)
|
||||
if nodeclass not in node["nukeNodeClass"]:
|
||||
if nodeclass not in onode["nuke_node_class"]:
|
||||
continue
|
||||
|
||||
if creator not in node["plugins"]:
|
||||
if creator not in onode["plugins"]:
|
||||
continue
|
||||
|
||||
if (
|
||||
|
|
@ -766,26 +766,33 @@ def get_created_node_imageio_setting_legacy(nodeclass, creator, subset):
|
|||
knob_names = [k["name"] for k in imageio_node["knobs"]]
|
||||
|
||||
for oknob in override_imageio_node["knobs"]:
|
||||
oknob_name = oknob["name"]
|
||||
oknob_type = oknob["type"]
|
||||
oknob_value = oknob[oknob_type]
|
||||
for knob in imageio_node["knobs"]:
|
||||
# override matching knob name
|
||||
if oknob["name"] == knob["name"]:
|
||||
log.debug(
|
||||
"_ overriding knob: `{}` > `{}`".format(
|
||||
knob, oknob
|
||||
))
|
||||
if not oknob["value"]:
|
||||
# remove original knob if no value found in oknob
|
||||
imageio_node["knobs"].remove(knob)
|
||||
else:
|
||||
# override knob value with oknob's
|
||||
knob["value"] = oknob["value"]
|
||||
|
||||
knob_name = knob["name"]
|
||||
# add missing knobs into imageio_node
|
||||
if oknob["name"] not in knob_names:
|
||||
if oknob_name not in knob_names:
|
||||
log.debug(
|
||||
"_ adding knob: `{}`".format(oknob))
|
||||
imageio_node["knobs"].append(oknob)
|
||||
knob_names.append(oknob["name"])
|
||||
knob_names.append(oknob_name)
|
||||
continue
|
||||
|
||||
# override matching knob name
|
||||
if oknob_name != knob_name:
|
||||
continue
|
||||
|
||||
knob_type = knob["type"]
|
||||
log.debug(
|
||||
"_ overriding knob: `{}` > `{}`".format(knob, oknob)
|
||||
)
|
||||
if not oknob_value:
|
||||
# remove original knob if no value found in oknob
|
||||
imageio_node["knobs"].remove(knob)
|
||||
else:
|
||||
# override knob value with oknob's
|
||||
knob[knob_type] = oknob_value
|
||||
|
||||
log.info("ImageIO node: {}".format(imageio_node))
|
||||
return imageio_node
|
||||
|
|
@ -795,14 +802,14 @@ def get_imageio_node_setting(node_class, plugin_name, subset):
|
|||
''' Get preset data for dataflow (fileType, compression, bitDepth)
|
||||
'''
|
||||
imageio_nodes = get_nuke_imageio_settings()["nodes"]
|
||||
required_nodes = imageio_nodes["requiredNodes"]
|
||||
required_nodes = imageio_nodes["required_nodes"]
|
||||
|
||||
imageio_node = None
|
||||
for node in required_nodes:
|
||||
log.info(node)
|
||||
if (
|
||||
node_class in node["nukeNodeClass"]
|
||||
and plugin_name in node["plugins"]
|
||||
node_class in node["nuke_node_class"]
|
||||
and plugin_name in node["plugins"]
|
||||
):
|
||||
imageio_node = node
|
||||
break
|
||||
|
|
@ -830,14 +837,14 @@ def get_imageio_node_override_setting(
|
|||
''' Get imageio node overrides from settings
|
||||
'''
|
||||
imageio_nodes = get_nuke_imageio_settings()["nodes"]
|
||||
override_nodes = imageio_nodes["overrideNodes"]
|
||||
override_nodes = imageio_nodes["override_nodes"]
|
||||
|
||||
# find matching override node
|
||||
override_imageio_node = None
|
||||
for onode in override_nodes:
|
||||
log.debug("__ onode: {}".format(onode))
|
||||
log.debug("__ subset: {}".format(subset))
|
||||
if node_class not in onode["nukeNodeClass"]:
|
||||
if node_class not in onode["nuke_node_class"]:
|
||||
continue
|
||||
|
||||
if plugin_name not in onode["plugins"]:
|
||||
|
|
@ -862,26 +869,31 @@ def get_imageio_node_override_setting(
|
|||
knob_names = [k["name"] for k in knobs_settings]
|
||||
|
||||
for oknob in override_imageio_node["knobs"]:
|
||||
oknob_name = oknob["name"]
|
||||
oknob_type = oknob["type"]
|
||||
oknob_value = oknob[oknob_type]
|
||||
for knob in knobs_settings:
|
||||
# override matching knob name
|
||||
if oknob["name"] == knob["name"]:
|
||||
log.debug(
|
||||
"_ overriding knob: `{}` > `{}`".format(
|
||||
knob, oknob
|
||||
))
|
||||
if not oknob["value"]:
|
||||
# remove original knob if no value found in oknob
|
||||
knobs_settings.remove(knob)
|
||||
else:
|
||||
# override knob value with oknob's
|
||||
knob["value"] = oknob["value"]
|
||||
|
||||
# add missing knobs into imageio_node
|
||||
if oknob["name"] not in knob_names:
|
||||
log.debug(
|
||||
"_ adding knob: `{}`".format(oknob))
|
||||
if oknob_name not in knob_names:
|
||||
log.debug("_ adding knob: `{}`".format(oknob))
|
||||
knobs_settings.append(oknob)
|
||||
knob_names.append(oknob["name"])
|
||||
knob_names.append(oknob_name)
|
||||
continue
|
||||
|
||||
if oknob_name != knob["name"]:
|
||||
continue
|
||||
|
||||
knob_type = knob["type"]
|
||||
# override matching knob name
|
||||
log.debug(
|
||||
"_ overriding knob: `{}` > `{}`".format(knob, oknob)
|
||||
)
|
||||
if not oknob_value:
|
||||
# remove original knob if no value found in oknob
|
||||
knobs_settings.remove(knob)
|
||||
else:
|
||||
# override knob value with oknob's
|
||||
knob[knob_type] = oknob_value
|
||||
|
||||
return knobs_settings
|
||||
|
||||
|
|
@ -890,7 +902,7 @@ def get_imageio_input_colorspace(filename):
|
|||
''' Get input file colorspace based on regex in settings.
|
||||
'''
|
||||
imageio_regex_inputs = (
|
||||
get_nuke_imageio_settings()["regexInputs"]["inputs"])
|
||||
get_nuke_imageio_settings()["regex_inputs"]["inputs"])
|
||||
|
||||
preset_clrsp = None
|
||||
for regexInput in imageio_regex_inputs:
|
||||
|
|
@ -1177,8 +1189,9 @@ def create_prenodes(
|
|||
):
|
||||
last_node = None
|
||||
for_dependency = {}
|
||||
for name, node in nodes_setting.items():
|
||||
for node in nodes_setting:
|
||||
# get attributes
|
||||
name = node["name"]
|
||||
nodeclass = node["nodeclass"]
|
||||
knobs = node["knobs"]
|
||||
|
||||
|
|
@ -1240,8 +1253,8 @@ def create_write_node(
|
|||
name (str): name of node
|
||||
data (dict): creator write instance data
|
||||
input (node)[optional]: selected node to connect to
|
||||
prenodes (dict)[optional]:
|
||||
nodes to be created before write with dependency
|
||||
prenodes (Optional[list[dict]]): nodes to be created before write
|
||||
with dependency
|
||||
review (bool)[optional]: adding review knob
|
||||
farm (bool)[optional]: rendering workflow target
|
||||
kwargs (dict)[optional]: additional key arguments for formatting
|
||||
|
|
@ -1270,7 +1283,7 @@ def create_write_node(
|
|||
Return:
|
||||
node (obj): group node with avalon data as Knobs
|
||||
'''
|
||||
prenodes = prenodes or {}
|
||||
prenodes = prenodes or []
|
||||
|
||||
# filtering variables
|
||||
plugin_name = data["creator"]
|
||||
|
|
@ -1285,7 +1298,8 @@ def create_write_node(
|
|||
|
||||
for knob in imageio_writes["knobs"]:
|
||||
if knob["name"] == "file_type":
|
||||
ext = knob["value"]
|
||||
knot_type = knob["type"]
|
||||
ext = knob[knot_type]
|
||||
|
||||
data.update({
|
||||
"imageio_writes": imageio_writes,
|
||||
|
|
@ -1400,12 +1414,17 @@ def create_write_node(
|
|||
# set tile color
|
||||
tile_color = next(
|
||||
iter(
|
||||
k["value"] for k in imageio_writes["knobs"]
|
||||
k[k["type"]] for k in imageio_writes["knobs"]
|
||||
if "tile_color" in k["name"]
|
||||
), [255, 0, 0, 255]
|
||||
)
|
||||
new_tile_color = []
|
||||
for c in tile_color:
|
||||
if isinstance(c, float):
|
||||
c = int(c * 255)
|
||||
new_tile_color.append(c)
|
||||
GN["tile_color"].setValue(
|
||||
color_gui_to_int(tile_color))
|
||||
color_gui_to_int(new_tile_color))
|
||||
|
||||
return GN
|
||||
|
||||
|
|
@ -1701,42 +1720,32 @@ def set_node_knobs_from_settings(node, knob_settings, **kwargs):
|
|||
"""
|
||||
for knob in knob_settings:
|
||||
log.debug("__ knob: {}".format(pformat(knob)))
|
||||
knob_type = knob["type"]
|
||||
knob_name = knob["name"]
|
||||
|
||||
if knob_name not in node.knobs():
|
||||
continue
|
||||
|
||||
knob_type = knob["type"]
|
||||
knob_value = knob[knob_type]
|
||||
if knob_type == "expression":
|
||||
knob_expression = knob["expression"]
|
||||
node[knob_name].setExpression(
|
||||
knob_expression
|
||||
)
|
||||
node[knob_name].setExpression(knob_value)
|
||||
continue
|
||||
|
||||
# first deal with formattable knob settings
|
||||
if knob_type == "formatable":
|
||||
template = knob["template"]
|
||||
to_type = knob["to_type"]
|
||||
template = knob_value["template"]
|
||||
to_type = knob_value["to_type"]
|
||||
try:
|
||||
_knob_value = template.format(
|
||||
**kwargs
|
||||
)
|
||||
knob_value = template.format(**kwargs)
|
||||
except KeyError as msg:
|
||||
raise KeyError(
|
||||
"Not able to format expression: {}".format(msg))
|
||||
|
||||
# convert value to correct type
|
||||
if to_type == "2d_vector":
|
||||
knob_value = _knob_value.split(";").split(",")
|
||||
else:
|
||||
knob_value = _knob_value
|
||||
knob_value = knob_value.split(";").split(",")
|
||||
|
||||
knob_type = to_type
|
||||
|
||||
else:
|
||||
knob_value = knob["value"]
|
||||
|
||||
if not knob_value:
|
||||
continue
|
||||
|
||||
|
|
@ -1747,29 +1756,46 @@ def set_node_knobs_from_settings(node, knob_settings, **kwargs):
|
|||
|
||||
|
||||
def convert_knob_value_to_correct_type(knob_type, knob_value):
|
||||
# first convert string types to string
|
||||
# just to ditch unicode
|
||||
if isinstance(knob_value, six.text_type):
|
||||
knob_value = str(knob_value)
|
||||
# Convert 'text' to string to avoid unicode
|
||||
if knob_type == "text":
|
||||
return str(knob_value)
|
||||
|
||||
# set correctly knob types
|
||||
if knob_type == "bool":
|
||||
knob_value = bool(knob_value)
|
||||
elif knob_type == "decimal_number":
|
||||
knob_value = float(knob_value)
|
||||
elif knob_type == "number":
|
||||
knob_value = int(knob_value)
|
||||
elif knob_type == "text":
|
||||
knob_value = knob_value
|
||||
elif knob_type == "color_gui":
|
||||
knob_value = color_gui_to_int(knob_value)
|
||||
elif knob_type in ["2d_vector", "3d_vector", "color", "box"]:
|
||||
knob_value = [float(val_) for val_ in knob_value]
|
||||
if knob_type == "boolean":
|
||||
return bool(knob_value)
|
||||
|
||||
if knob_type == "decimal_number":
|
||||
return float(knob_value)
|
||||
|
||||
if knob_type == "number":
|
||||
return int(knob_value)
|
||||
|
||||
if knob_type == "color_gui":
|
||||
new_color = []
|
||||
for value in knob_value:
|
||||
if isinstance(value, float):
|
||||
value = int(value * 255)
|
||||
new_color.append(value)
|
||||
return color_gui_to_int(new_color)
|
||||
|
||||
if knob_type == "box":
|
||||
return [
|
||||
knob_value["x"], knob_value["y"],
|
||||
knob_value["r"], knob_value["t"]
|
||||
]
|
||||
|
||||
if knob_type == "vector_2d":
|
||||
return [knob_value["x"], knob_value["y"]]
|
||||
|
||||
if knob_type == "vector_3d":
|
||||
return [knob_value["x"], knob_value["y"], knob_value["z"]]
|
||||
|
||||
return knob_value
|
||||
|
||||
|
||||
def color_gui_to_int(color_gui):
|
||||
# Append alpha channel if not present
|
||||
if len(color_gui) == 3:
|
||||
color_gui = list(color_gui) + [255]
|
||||
hex_value = (
|
||||
"0x{0:0>2x}{1:0>2x}{2:0>2x}{3:0>2x}").format(*color_gui)
|
||||
return int(hex_value, 16)
|
||||
|
|
@ -2016,41 +2042,21 @@ class WorkfileSettings(object):
|
|||
host_name="nuke"
|
||||
)
|
||||
|
||||
workfile_settings = imageio_host["workfile"]
|
||||
viewer_process_settings = imageio_host["viewer"]["viewerProcess"]
|
||||
workfile_settings = imageio_host["workfile"]
|
||||
color_management = workfile_settings["color_management"]
|
||||
native_ocio_config = workfile_settings["native_ocio_config"]
|
||||
|
||||
if not config_data:
|
||||
# TODO: backward compatibility for old projects - remove later
|
||||
# perhaps old project overrides is having it set to older version
|
||||
# with use of `customOCIOConfigPath`
|
||||
resolved_path = None
|
||||
if workfile_settings.get("customOCIOConfigPath"):
|
||||
unresolved_path = workfile_settings["customOCIOConfigPath"]
|
||||
ocio_paths = unresolved_path[platform.system().lower()]
|
||||
# no ocio config found and no custom path used
|
||||
if self._root_node["colorManagement"].value() \
|
||||
not in color_management:
|
||||
self._root_node["colorManagement"].setValue(color_management)
|
||||
|
||||
for ocio_p in ocio_paths:
|
||||
resolved_path = str(ocio_p).format(**os.environ)
|
||||
if not os.path.exists(resolved_path):
|
||||
continue
|
||||
|
||||
if resolved_path:
|
||||
# set values to root
|
||||
self._root_node["colorManagement"].setValue("OCIO")
|
||||
self._root_node["OCIO_config"].setValue("custom")
|
||||
self._root_node["customOCIOConfigPath"].setValue(
|
||||
resolved_path)
|
||||
else:
|
||||
# no ocio config found and no custom path used
|
||||
if self._root_node["colorManagement"].value() \
|
||||
not in str(workfile_settings["colorManagement"]):
|
||||
self._root_node["colorManagement"].setValue(
|
||||
str(workfile_settings["colorManagement"]))
|
||||
|
||||
# second set ocio version
|
||||
if self._root_node["OCIO_config"].value() \
|
||||
not in str(workfile_settings["OCIO_config"]):
|
||||
self._root_node["OCIO_config"].setValue(
|
||||
str(workfile_settings["OCIO_config"]))
|
||||
# second set ocio version
|
||||
if self._root_node["OCIO_config"].value() \
|
||||
not in native_ocio_config:
|
||||
self._root_node["OCIO_config"].setValue(native_ocio_config)
|
||||
|
||||
else:
|
||||
# OCIO config path is defined from prelaunch hook
|
||||
|
|
@ -2063,22 +2069,17 @@ class WorkfileSettings(object):
|
|||
residual_path
|
||||
))
|
||||
|
||||
# we dont need the key anymore
|
||||
workfile_settings.pop("customOCIOConfigPath", None)
|
||||
workfile_settings.pop("colorManagement", None)
|
||||
workfile_settings.pop("OCIO_config", None)
|
||||
|
||||
# get monitor lut from settings respecting Nuke version differences
|
||||
monitor_lut = workfile_settings.pop("monitorLut", None)
|
||||
monitor_lut = workfile_settings["thumbnail_space"]
|
||||
monitor_lut_data = self._get_monitor_settings(
|
||||
viewer_process_settings, monitor_lut)
|
||||
|
||||
# set monitor related knobs luts (MonitorOut, Thumbnails)
|
||||
for knob, value_ in monitor_lut_data.items():
|
||||
workfile_settings[knob] = value_
|
||||
viewer_process_settings, monitor_lut
|
||||
)
|
||||
monitor_lut_data["workingSpaceLUT"] = (
|
||||
workfile_settings["working_space"]
|
||||
)
|
||||
|
||||
# then set the rest
|
||||
for knob, value_ in workfile_settings.items():
|
||||
for knob, value_ in monitor_lut_data.items():
|
||||
# skip unfilled ocio config path
|
||||
# it will be dict in value
|
||||
if isinstance(value_, dict):
|
||||
|
|
@ -2360,25 +2361,8 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies.
|
|||
if not write_node:
|
||||
return
|
||||
|
||||
try:
|
||||
# write all knobs to node
|
||||
for knob in nuke_imageio_writes["knobs"]:
|
||||
value = knob["value"]
|
||||
if isinstance(value, six.text_type):
|
||||
value = str(value)
|
||||
if str(value).startswith("0x"):
|
||||
value = int(value, 16)
|
||||
|
||||
log.debug("knob: {}| value: {}".format(
|
||||
knob["name"], value
|
||||
))
|
||||
write_node[knob["name"]].setValue(value)
|
||||
except TypeError:
|
||||
log.warning(
|
||||
"Legacy workflow didn't work, switching to current")
|
||||
|
||||
set_node_knobs_from_settings(
|
||||
write_node, nuke_imageio_writes["knobs"])
|
||||
set_node_knobs_from_settings(
|
||||
write_node, nuke_imageio_writes["knobs"])
|
||||
|
||||
def set_reads_colorspace(self, read_clrs_inputs):
|
||||
""" Setting colorspace to Read nodes
|
||||
|
|
@ -2456,7 +2440,7 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies.
|
|||
log.error(_error)
|
||||
|
||||
log.info("Setting colorspace to read nodes...")
|
||||
read_clrs_inputs = nuke_colorspace["regexInputs"].get("inputs", [])
|
||||
read_clrs_inputs = nuke_colorspace["regex_inputs"].get("inputs", [])
|
||||
if read_clrs_inputs:
|
||||
self.set_reads_colorspace(read_clrs_inputs)
|
||||
|
||||
|
|
|
|||
|
|
@ -396,17 +396,25 @@ class NukeWriteCreator(NukeCreator):
|
|||
|
||||
# plugin settings
|
||||
plugin_settings = self.get_creator_settings(project_settings)
|
||||
|
||||
temp_rendering_path_template = (
|
||||
plugin_settings.get("temp_rendering_path_template")
|
||||
or self.temp_rendering_path_template
|
||||
)
|
||||
# TODO remove template key replacements
|
||||
temp_rendering_path_template = (
|
||||
temp_rendering_path_template
|
||||
.replace("{product[name]}", "{subset}")
|
||||
.replace("{product[type]}", "{family}")
|
||||
.replace("{task[name]}", "{task}")
|
||||
.replace("{folder[name]}", "{asset}")
|
||||
)
|
||||
# individual attributes
|
||||
self.instance_attributes = plugin_settings.get(
|
||||
"instance_attributes") or self.instance_attributes
|
||||
self.prenodes = plugin_settings["prenodes"]
|
||||
self.default_variants = plugin_settings.get(
|
||||
"default_variants") or self.default_variants
|
||||
self.temp_rendering_path_template = (
|
||||
plugin_settings.get("temp_rendering_path_template")
|
||||
or self.temp_rendering_path_template
|
||||
)
|
||||
self.temp_rendering_path_template = temp_rendering_path_template
|
||||
|
||||
|
||||
class OpenPypeCreator(LegacyCreator):
|
||||
|
|
@ -1061,7 +1069,7 @@ class AbstractWriteRender(OpenPypeCreator):
|
|||
icon = "sign-out"
|
||||
defaults = ["Main", "Mask"]
|
||||
knobs = []
|
||||
prenodes = {}
|
||||
prenodes = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AbstractWriteRender, self).__init__(*args, **kwargs)
|
||||
|
|
@ -1167,7 +1175,7 @@ class AbstractWriteRender(OpenPypeCreator):
|
|||
bool: True if legacy
|
||||
"""
|
||||
imageio_nodes = get_nuke_imageio_settings()["nodes"]
|
||||
node = imageio_nodes["requiredNodes"][0]
|
||||
node = imageio_nodes["required_nodes"][0]
|
||||
if "type" not in node["knobs"][0]:
|
||||
# if type is not yet in project anatomy
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class LoadClip(plugin.NukeLoader):
|
|||
color = "white"
|
||||
|
||||
# Loaded from settings
|
||||
_representations = []
|
||||
representations_include = []
|
||||
|
||||
script_start = int(nuke.root()["first_frame"].value())
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ class LoadClip(plugin.NukeLoader):
|
|||
|
||||
@classmethod
|
||||
def get_representations(cls):
|
||||
return cls._representations or cls.representations
|
||||
return cls.representations_include or cls.representations
|
||||
|
||||
def load(self, context, name, namespace, options):
|
||||
"""Load asset via database
|
||||
|
|
@ -457,7 +457,7 @@ class LoadClip(plugin.NukeLoader):
|
|||
colorspace = repre_data.get("colorspace")
|
||||
colorspace = colorspace or version_data.get("colorspace")
|
||||
|
||||
# colorspace from `project_settings/nuke/imageio/regexInputs`
|
||||
# colorspace from `project_settings/nuke/imageio/regex_inputs`
|
||||
iio_colorspace = get_imageio_input_colorspace(path)
|
||||
|
||||
# Set colorspace defined in version data
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class LoadImage(load.LoaderPlugin):
|
|||
color = "white"
|
||||
|
||||
# Loaded from settings
|
||||
_representations = []
|
||||
representations_include = []
|
||||
|
||||
node_name_template = "{class_name}_{ext}"
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ class LoadImage(load.LoaderPlugin):
|
|||
|
||||
@classmethod
|
||||
def get_representations(cls):
|
||||
return cls._representations or cls.representations
|
||||
return cls.representations_include or cls.representations
|
||||
|
||||
def load(self, context, name, namespace, options):
|
||||
self.log.info("__ options: `{}`".format(options))
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
|
|||
hosts = ["nuke", "nukeassist"]
|
||||
|
||||
# presets
|
||||
sync_workfile_version_on_families = []
|
||||
sync_workfile_version_on_product_types = []
|
||||
|
||||
def process(self, instance):
|
||||
family = instance.data["family"]
|
||||
|
|
@ -25,7 +25,7 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
|
|||
pixel_aspect = format_.pixelAspect()
|
||||
|
||||
# sync workfile version
|
||||
if family in self.sync_workfile_version_on_families:
|
||||
if family in self.sync_workfile_version_on_product_types:
|
||||
self.log.debug(
|
||||
"Syncing version with workfile for '{}'".format(
|
||||
family
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ class ExtractReviewIntermediates(publish.Extractor):
|
|||
cls.outputs = current_setting["outputs"]
|
||||
|
||||
def process(self, instance):
|
||||
# TODO 'families' should not be included for filtering of outputs
|
||||
families = set(instance.data["families"])
|
||||
|
||||
# add main family to make sure all families are compared
|
||||
|
|
@ -75,29 +76,33 @@ class ExtractReviewIntermediates(publish.Extractor):
|
|||
# generate data
|
||||
with maintained_selection():
|
||||
generated_repres = []
|
||||
for o_name, o_data in self.outputs.items():
|
||||
for o_data in self.outputs:
|
||||
o_name = o_data["name"]
|
||||
self.log.debug(
|
||||
"o_name: {}, o_data: {}".format(o_name, pformat(o_data)))
|
||||
f_families = o_data["filter"]["families"]
|
||||
f_product_types = o_data["filter"]["product_types"]
|
||||
f_task_types = o_data["filter"]["task_types"]
|
||||
f_subsets = o_data["filter"]["subsets"]
|
||||
product_names = o_data["filter"]["product_names"]
|
||||
|
||||
self.log.debug(
|
||||
"f_families `{}` > families: {}".format(
|
||||
f_families, families))
|
||||
"f_product_types `{}` > families: {}".format(
|
||||
f_product_types, families))
|
||||
|
||||
self.log.debug(
|
||||
"f_task_types `{}` > task_type: {}".format(
|
||||
f_task_types, task_type))
|
||||
|
||||
self.log.debug(
|
||||
"f_subsets `{}` > subset: {}".format(
|
||||
f_subsets, subset))
|
||||
"product_names `{}` > subset: {}".format(
|
||||
product_names, subset))
|
||||
|
||||
# test if family found in context
|
||||
# using intersection to make sure all defined
|
||||
# families are present in combination
|
||||
if f_families and not families.intersection(f_families):
|
||||
if (
|
||||
f_product_types
|
||||
and not families.intersection(f_product_types)
|
||||
):
|
||||
continue
|
||||
|
||||
# test task types from filter
|
||||
|
|
@ -105,8 +110,9 @@ class ExtractReviewIntermediates(publish.Extractor):
|
|||
continue
|
||||
|
||||
# test subsets from filter
|
||||
if f_subsets and not any(
|
||||
re.search(s, subset) for s in f_subsets):
|
||||
if product_names and not any(
|
||||
re.search(p, subset) for p in product_names
|
||||
):
|
||||
continue
|
||||
|
||||
self.log.debug(
|
||||
|
|
@ -117,7 +123,7 @@ class ExtractReviewIntermediates(publish.Extractor):
|
|||
# check if settings have more then one preset
|
||||
# so we dont need to add outputName to representation
|
||||
# in case there is only one preset
|
||||
multiple_presets = len(self.outputs.keys()) > 1
|
||||
multiple_presets = len(self.outputs) > 1
|
||||
|
||||
# adding bake presets to instance data for other plugins
|
||||
if not instance.data.get("bakePresets"):
|
||||
|
|
|
|||
|
|
@ -29,9 +29,15 @@ class ExtractSlateFrame(publish.Extractor):
|
|||
|
||||
# Settings values
|
||||
key_value_mapping = {
|
||||
"f_submission_note": [True, "{comment}"],
|
||||
"f_submitting_for": [True, "{intent[value]}"],
|
||||
"f_vfx_scope_of_work": [False, ""]
|
||||
"f_submission_note": {
|
||||
"enabled": True, "template": "{comment}"
|
||||
},
|
||||
"f_submitting_for": {
|
||||
"enabled": True, "template": "{intent[value]}"
|
||||
},
|
||||
"f_vfx_scope_of_work": {
|
||||
"enabled": False, "template": ""
|
||||
}
|
||||
}
|
||||
|
||||
def process(self, instance):
|
||||
|
|
@ -316,11 +322,11 @@ class ExtractSlateFrame(publish.Extractor):
|
|||
})
|
||||
|
||||
for key, _values in self.key_value_mapping.items():
|
||||
enabled, template = _values
|
||||
if not enabled:
|
||||
if not _values["enabled"]:
|
||||
self.log.debug("Key \"{}\" is disabled".format(key))
|
||||
continue
|
||||
|
||||
template = _values["template"]
|
||||
try:
|
||||
value = template.format(**fill_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
### How to repair?
|
||||
|
||||
Contact your supervisor or fix it in project settings at
|
||||
'project_settings/nuke/imageio/nodes/requiredNodes' at knobs.
|
||||
'project_settings/nuke/imageio/nodes/required_nodes' at knobs.
|
||||
Each '__legacy__' type has to be defined accordingly to its type.
|
||||
</description>
|
||||
</error>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ class ValidateKnobs(pyblish.api.ContextPlugin):
|
|||
actions = [RepairContextAction]
|
||||
optional = True
|
||||
|
||||
knobs = "{}"
|
||||
|
||||
def process(self, context):
|
||||
invalid = self.get_invalid(context, compute=True)
|
||||
if invalid:
|
||||
|
|
@ -61,6 +63,8 @@ class ValidateKnobs(pyblish.api.ContextPlugin):
|
|||
invalid_knobs = []
|
||||
|
||||
for instance in context:
|
||||
# Load fresh knobs data for each instance
|
||||
settings_knobs = json.loads(cls.knobs)
|
||||
|
||||
# Filter families.
|
||||
families = [instance.data["family"]]
|
||||
|
|
@ -74,12 +78,12 @@ class ValidateKnobs(pyblish.api.ContextPlugin):
|
|||
family = family.split(".")[0]
|
||||
|
||||
# avoid families not in settings
|
||||
if family not in cls.knobs:
|
||||
if family not in settings_knobs:
|
||||
continue
|
||||
|
||||
# get presets of knobs
|
||||
for preset in cls.knobs[family]:
|
||||
knobs[preset] = cls.knobs[family][preset]
|
||||
for preset in settings_knobs[family]:
|
||||
knobs[preset] = settings_knobs[family][preset]
|
||||
|
||||
# Get invalid knobs.
|
||||
nodes = []
|
||||
|
|
|
|||
|
|
@ -86,7 +86,10 @@ class ValidateNukeWriteNode(
|
|||
# Collect key values of same type in a list.
|
||||
values_by_name = defaultdict(list)
|
||||
for knob_data in correct_data["knobs"]:
|
||||
values_by_name[knob_data["name"]].append(knob_data["value"])
|
||||
knob_type = knob_data["type"]
|
||||
knob_value = knob_data[knob_type]
|
||||
|
||||
values_by_name[knob_data["name"]].append(knob_value)
|
||||
|
||||
for knob_data in correct_data["knobs"]:
|
||||
knob_type = knob_data["type"]
|
||||
|
|
@ -97,7 +100,7 @@ class ValidateNukeWriteNode(
|
|||
raise PublishXmlValidationError(
|
||||
self, (
|
||||
"Please update data in settings 'project_settings"
|
||||
"/nuke/imageio/nodes/requiredNodes'"
|
||||
"/nuke/imageio/nodes/required_nodes'"
|
||||
),
|
||||
key="legacy"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -127,8 +127,8 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel):
|
|||
knobs_nodes = []
|
||||
settings = [
|
||||
node_settings for node_settings
|
||||
in get_nuke_imageio_settings()["nodes"]["overrideNodes"]
|
||||
if node_settings["nukeNodeClass"] == "Write"
|
||||
in get_nuke_imageio_settings()["nodes"]["override_nodes"]
|
||||
if node_settings["nuke_node_class"] == "Write"
|
||||
and node_settings["subsets"]
|
||||
]
|
||||
if not settings:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from .module import JobQueueModule
|
||||
from .addon import JobQueueAddon
|
||||
|
||||
|
||||
__all__ = (
|
||||
"JobQueueModule",
|
||||
"JobQueueAddon",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -41,29 +41,24 @@ import json
|
|||
import copy
|
||||
import platform
|
||||
|
||||
from ayon_core.addon import click_wrap
|
||||
from ayon_core.modules import OpenPypeModule
|
||||
from ayon_core.addon import AYONAddon, click_wrap
|
||||
from ayon_core.settings import get_system_settings
|
||||
|
||||
|
||||
class JobQueueModule(OpenPypeModule):
|
||||
class JobQueueAddon(AYONAddon):
|
||||
name = "job_queue"
|
||||
|
||||
def initialize(self, modules_settings):
|
||||
module_settings = modules_settings.get(self.name) or {}
|
||||
server_url = module_settings.get("server_url") or ""
|
||||
def initialize(self, studio_settings):
|
||||
addon_settings = studio_settings.get(self.name) or {}
|
||||
server_url = addon_settings.get("server_url") or ""
|
||||
|
||||
self._server_url = self.url_conversion(server_url)
|
||||
jobs_root_mapping = self._roots_mapping_conversion(
|
||||
module_settings.get("jobs_root")
|
||||
addon_settings.get("jobs_root")
|
||||
)
|
||||
|
||||
self._jobs_root_mapping = jobs_root_mapping
|
||||
|
||||
# Is always enabled
|
||||
# - the module does nothing until is used
|
||||
self.enabled = True
|
||||
|
||||
@classmethod
|
||||
def _root_conversion(cls, root_path):
|
||||
"""Make sure root path does not end with slash."""
|
||||
|
|
@ -127,8 +122,8 @@ class JobQueueModule(OpenPypeModule):
|
|||
|
||||
@classmethod
|
||||
def get_jobs_root_from_settings(cls):
|
||||
module_settings = get_system_settings()["modules"]
|
||||
jobs_root_mapping = module_settings.get(cls.name, {}).get("jobs_root")
|
||||
studio_settings = get_system_settings()
|
||||
jobs_root_mapping = studio_settings.get(cls.name, {}).get("jobs_root")
|
||||
converted_mapping = cls._roots_mapping_conversion(jobs_root_mapping)
|
||||
|
||||
return converted_mapping[platform.system().lower()]
|
||||
|
|
@ -157,9 +152,9 @@ class JobQueueModule(OpenPypeModule):
|
|||
|
||||
@classmethod
|
||||
def get_server_url_from_settings(cls):
|
||||
module_settings = get_system_settings()["modules"]
|
||||
studio_settings = get_system_settings()
|
||||
return cls.url_conversion(
|
||||
module_settings
|
||||
studio_settings
|
||||
.get(cls.name, {})
|
||||
.get("server_url")
|
||||
)
|
||||
|
|
@ -214,7 +209,7 @@ class JobQueueModule(OpenPypeModule):
|
|||
|
||||
|
||||
@click_wrap.group(
|
||||
JobQueueModule.name,
|
||||
JobQueueAddon.name,
|
||||
help="Application job server. Can be used as render farm."
|
||||
)
|
||||
def cli_main():
|
||||
|
|
@ -228,7 +223,7 @@ def cli_main():
|
|||
@click_wrap.option("--port", help="Server port")
|
||||
@click_wrap.option("--host", help="Server host (ip address)")
|
||||
def cli_start_server(port, host):
|
||||
JobQueueModule.start_server(port, host)
|
||||
JobQueueAddon.start_server(port, host)
|
||||
|
||||
|
||||
@cli_main.command(
|
||||
|
|
@ -241,4 +236,4 @@ def cli_start_server(port, host):
|
|||
"--server_url",
|
||||
help="Server url which handle workers and jobs.")
|
||||
def cli_start_worker(app_name, server_url):
|
||||
JobQueueModule.start_worker(app_name, server_url)
|
||||
JobQueueAddon.start_worker(app_name, server_url)
|
||||
|
|
@ -133,221 +133,6 @@ def convert_system_settings(ayon_settings, default_settings, addon_versions):
|
|||
|
||||
|
||||
# --------- Project settings ---------
|
||||
def _convert_nuke_knobs(knobs):
|
||||
new_knobs = []
|
||||
for knob in knobs:
|
||||
knob_type = knob["type"]
|
||||
|
||||
if knob_type == "boolean":
|
||||
knob_type = "bool"
|
||||
|
||||
if knob_type != "bool":
|
||||
value = knob[knob_type]
|
||||
elif knob_type in knob:
|
||||
value = knob[knob_type]
|
||||
else:
|
||||
value = knob["boolean"]
|
||||
|
||||
new_knob = {
|
||||
"type": knob_type,
|
||||
"name": knob["name"],
|
||||
}
|
||||
new_knobs.append(new_knob)
|
||||
|
||||
if knob_type == "formatable":
|
||||
new_knob["template"] = value["template"]
|
||||
new_knob["to_type"] = value["to_type"]
|
||||
continue
|
||||
|
||||
value_key = "value"
|
||||
if knob_type == "expression":
|
||||
value_key = "expression"
|
||||
|
||||
elif knob_type == "color_gui":
|
||||
value = _convert_color(value)
|
||||
|
||||
elif knob_type == "vector_2d":
|
||||
value = [value["x"], value["y"]]
|
||||
|
||||
elif knob_type == "vector_3d":
|
||||
value = [value["x"], value["y"], value["z"]]
|
||||
|
||||
elif knob_type == "box":
|
||||
value = [value["x"], value["y"], value["r"], value["t"]]
|
||||
|
||||
new_knob[value_key] = value
|
||||
return new_knobs
|
||||
|
||||
|
||||
def _convert_nuke_project_settings(ayon_settings, output):
|
||||
if "nuke" not in ayon_settings:
|
||||
return
|
||||
|
||||
ayon_nuke = ayon_settings["nuke"]
|
||||
|
||||
# --- Load ---
|
||||
ayon_load = ayon_nuke["load"]
|
||||
ayon_load["LoadClip"]["_representations"] = (
|
||||
ayon_load["LoadClip"].pop("representations_include")
|
||||
)
|
||||
ayon_load["LoadImage"]["_representations"] = (
|
||||
ayon_load["LoadImage"].pop("representations_include")
|
||||
)
|
||||
|
||||
# --- Create ---
|
||||
ayon_create = ayon_nuke["create"]
|
||||
for creator_name in (
|
||||
"CreateWritePrerender",
|
||||
"CreateWriteImage",
|
||||
"CreateWriteRender",
|
||||
):
|
||||
create_plugin_settings = ayon_create[creator_name]
|
||||
create_plugin_settings["temp_rendering_path_template"] = (
|
||||
create_plugin_settings["temp_rendering_path_template"]
|
||||
.replace("{product[name]}", "{subset}")
|
||||
.replace("{product[type]}", "{family}")
|
||||
.replace("{task[name]}", "{task}")
|
||||
.replace("{folder[name]}", "{asset}")
|
||||
)
|
||||
new_prenodes = {}
|
||||
for prenode in create_plugin_settings["prenodes"]:
|
||||
name = prenode.pop("name")
|
||||
prenode["knobs"] = _convert_nuke_knobs(prenode["knobs"])
|
||||
new_prenodes[name] = prenode
|
||||
|
||||
create_plugin_settings["prenodes"] = new_prenodes
|
||||
|
||||
# --- Publish ---
|
||||
ayon_publish = ayon_nuke["publish"]
|
||||
slate_mapping = ayon_publish["ExtractSlateFrame"]["key_value_mapping"]
|
||||
for key in tuple(slate_mapping.keys()):
|
||||
value = slate_mapping[key]
|
||||
slate_mapping[key] = [value["enabled"], value["template"]]
|
||||
|
||||
ayon_publish["ValidateKnobs"]["knobs"] = json.loads(
|
||||
ayon_publish["ValidateKnobs"]["knobs"]
|
||||
)
|
||||
|
||||
new_review_data_outputs = {}
|
||||
outputs_settings = []
|
||||
# Check deprecated ExtractReviewDataMov
|
||||
# settings for backwards compatibility
|
||||
deprecrated_review_settings = ayon_publish["ExtractReviewDataMov"]
|
||||
current_review_settings = (
|
||||
ayon_publish.get("ExtractReviewIntermediates")
|
||||
)
|
||||
if deprecrated_review_settings["enabled"]:
|
||||
outputs_settings = deprecrated_review_settings["outputs"]
|
||||
elif current_review_settings is None:
|
||||
pass
|
||||
elif current_review_settings["enabled"]:
|
||||
outputs_settings = current_review_settings["outputs"]
|
||||
|
||||
for item in outputs_settings:
|
||||
item_filter = item["filter"]
|
||||
if "product_names" in item_filter:
|
||||
item_filter["subsets"] = item_filter.pop("product_names")
|
||||
item_filter["families"] = item_filter.pop("product_types")
|
||||
|
||||
reformat_nodes_config = item.get("reformat_nodes_config") or {}
|
||||
reposition_nodes = reformat_nodes_config.get(
|
||||
"reposition_nodes") or []
|
||||
|
||||
for reposition_node in reposition_nodes:
|
||||
if "knobs" not in reposition_node:
|
||||
continue
|
||||
reposition_node["knobs"] = _convert_nuke_knobs(
|
||||
reposition_node["knobs"]
|
||||
)
|
||||
|
||||
name = item.pop("name")
|
||||
new_review_data_outputs[name] = item
|
||||
|
||||
if deprecrated_review_settings["enabled"]:
|
||||
deprecrated_review_settings["outputs"] = new_review_data_outputs
|
||||
elif current_review_settings["enabled"]:
|
||||
current_review_settings["outputs"] = new_review_data_outputs
|
||||
|
||||
collect_instance_data = ayon_publish["CollectInstanceData"]
|
||||
if "sync_workfile_version_on_product_types" in collect_instance_data:
|
||||
collect_instance_data["sync_workfile_version_on_families"] = (
|
||||
collect_instance_data.pop(
|
||||
"sync_workfile_version_on_product_types"))
|
||||
|
||||
# --- ImageIO ---
|
||||
# NOTE 'monitorOutLut' is maybe not yet in v3 (ut should be)
|
||||
ayon_imageio = ayon_nuke["imageio"]
|
||||
|
||||
# workfile
|
||||
imageio_workfile = ayon_imageio["workfile"]
|
||||
workfile_keys_mapping = (
|
||||
("color_management", "colorManagement"),
|
||||
("native_ocio_config", "OCIO_config"),
|
||||
("working_space", "workingSpaceLUT"),
|
||||
("thumbnail_space", "monitorLut"),
|
||||
)
|
||||
for src, dst in workfile_keys_mapping:
|
||||
if (
|
||||
src in imageio_workfile
|
||||
and dst not in imageio_workfile
|
||||
):
|
||||
imageio_workfile[dst] = imageio_workfile.pop(src)
|
||||
|
||||
# regex inputs
|
||||
if "regex_inputs" in ayon_imageio:
|
||||
ayon_imageio["regexInputs"] = ayon_imageio.pop("regex_inputs")
|
||||
|
||||
# nodes
|
||||
ayon_imageio_nodes = ayon_imageio["nodes"]
|
||||
if "required_nodes" in ayon_imageio_nodes:
|
||||
ayon_imageio_nodes["requiredNodes"] = (
|
||||
ayon_imageio_nodes.pop("required_nodes"))
|
||||
if "override_nodes" in ayon_imageio_nodes:
|
||||
ayon_imageio_nodes["overrideNodes"] = (
|
||||
ayon_imageio_nodes.pop("override_nodes"))
|
||||
|
||||
for item in ayon_imageio_nodes["requiredNodes"]:
|
||||
if "nuke_node_class" in item:
|
||||
item["nukeNodeClass"] = item.pop("nuke_node_class")
|
||||
item["knobs"] = _convert_nuke_knobs(item["knobs"])
|
||||
|
||||
for item in ayon_imageio_nodes["overrideNodes"]:
|
||||
if "nuke_node_class" in item:
|
||||
item["nukeNodeClass"] = item.pop("nuke_node_class")
|
||||
item["knobs"] = _convert_nuke_knobs(item["knobs"])
|
||||
|
||||
output["nuke"] = ayon_nuke
|
||||
|
||||
|
||||
def _convert_hiero_project_settings(ayon_settings, output):
|
||||
if "hiero" not in ayon_settings:
|
||||
return
|
||||
|
||||
ayon_hiero = ayon_settings["hiero"]
|
||||
|
||||
new_gui_filters = {}
|
||||
for item in ayon_hiero.pop("filters", []):
|
||||
subvalue = {}
|
||||
key = item["name"]
|
||||
for subitem in item["value"]:
|
||||
subvalue[subitem["name"]] = subitem["value"]
|
||||
new_gui_filters[key] = subvalue
|
||||
ayon_hiero["filters"] = new_gui_filters
|
||||
|
||||
ayon_load_clip = ayon_hiero["load"]["LoadClip"]
|
||||
if "product_types" in ayon_load_clip:
|
||||
ayon_load_clip["families"] = ayon_load_clip.pop("product_types")
|
||||
|
||||
ayon_load_clip = ayon_hiero["load"]["LoadClip"]
|
||||
ayon_load_clip["clip_name_template"] = (
|
||||
ayon_load_clip["clip_name_template"]
|
||||
.replace("{folder[name]}", "{asset}")
|
||||
.replace("{product[name]}", "{subset}")
|
||||
)
|
||||
|
||||
output["hiero"] = ayon_hiero
|
||||
|
||||
|
||||
def _convert_royalrender_project_settings(ayon_settings, output):
|
||||
if "royalrender" not in ayon_settings:
|
||||
return
|
||||
|
|
@ -364,9 +149,6 @@ def convert_project_settings(ayon_settings, default_settings):
|
|||
default_settings = copy.deepcopy(default_settings)
|
||||
output = {}
|
||||
|
||||
_convert_nuke_project_settings(ayon_settings, output)
|
||||
_convert_hiero_project_settings(ayon_settings, output)
|
||||
|
||||
_convert_royalrender_project_settings(ayon_settings, output)
|
||||
|
||||
for key, value in ayon_settings.items():
|
||||
|
|
|
|||
|
|
@ -61,6 +61,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,
|
||||
|
|
|
|||
|
|
@ -202,19 +202,39 @@ class Controller(QtCore.QObject):
|
|||
def current_state(self):
|
||||
return self._current_state
|
||||
|
||||
@staticmethod
|
||||
def _convert_filter_presets(filter_presets):
|
||||
"""Convert AYON settings presets to dictionary.
|
||||
|
||||
Returns:
|
||||
dict[str, dict[str, Any]]: Filter presets converted to dictionary.
|
||||
"""
|
||||
if not isinstance(filter_presets, list):
|
||||
return filter_presets
|
||||
|
||||
return {
|
||||
filter_preset["name"]: {
|
||||
item["name"]: item["value"]
|
||||
for item in filter_preset["value"]
|
||||
}
|
||||
for filter_preset in filter_presets
|
||||
}
|
||||
|
||||
def presets_by_hosts(self):
|
||||
# Get global filters as base
|
||||
presets = get_current_project_settings()
|
||||
if not presets:
|
||||
return {}
|
||||
|
||||
result = presets.get("core", {}).get("filters", {})
|
||||
result = {}
|
||||
hosts = pyblish.api.registered_hosts()
|
||||
for host in hosts:
|
||||
host_presets = presets.get(host, {}).get("filters")
|
||||
if not host_presets:
|
||||
continue
|
||||
|
||||
host_presets = self._convert_filter_presets(host_presets)
|
||||
|
||||
for key, value in host_presets.items():
|
||||
if value is None:
|
||||
if key in result:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from .tray import main
|
||||
|
||||
|
||||
__all__ = (
|
||||
"main",
|
||||
)
|
||||
|
|
|
|||
155
client/ayon_core/tools/tray/dialogs.py
Normal file
155
client/ayon_core/tools/tray/dialogs.py
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import os
|
||||
|
||||
from qtpy import QtWidgets, QtCore, QtGui
|
||||
|
||||
from ayon_core import resources, style
|
||||
from ayon_core.tools.utils import paint_image_with_color
|
||||
|
||||
|
||||
class PixmapLabel(QtWidgets.QLabel):
|
||||
"""Label resizing image to height of font."""
|
||||
def __init__(self, pixmap, parent):
|
||||
super(PixmapLabel, self).__init__(parent)
|
||||
self._empty_pixmap = QtGui.QPixmap(0, 0)
|
||||
self._source_pixmap = pixmap
|
||||
|
||||
def set_source_pixmap(self, pixmap):
|
||||
"""Change source image."""
|
||||
self._source_pixmap = pixmap
|
||||
self._set_resized_pix()
|
||||
|
||||
def _get_pix_size(self):
|
||||
size = self.fontMetrics().height() * 3
|
||||
return size, size
|
||||
|
||||
def _set_resized_pix(self):
|
||||
if self._source_pixmap is None:
|
||||
self.setPixmap(self._empty_pixmap)
|
||||
return
|
||||
width, height = self._get_pix_size()
|
||||
self.setPixmap(
|
||||
self._source_pixmap.scaled(
|
||||
width,
|
||||
height,
|
||||
QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation
|
||||
)
|
||||
)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
self._set_resized_pix()
|
||||
super(PixmapLabel, self).resizeEvent(event)
|
||||
|
||||
|
||||
class UpdateDialog(QtWidgets.QDialog):
|
||||
restart_requested = QtCore.Signal()
|
||||
ignore_requested = QtCore.Signal()
|
||||
|
||||
_min_width = 400
|
||||
_min_height = 130
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(UpdateDialog, self).__init__(parent)
|
||||
|
||||
icon = QtGui.QIcon(resources.get_ayon_icon_filepath())
|
||||
self.setWindowIcon(icon)
|
||||
self.setWindowTitle("AYON update")
|
||||
self.setWindowFlags(
|
||||
self.windowFlags()
|
||||
| QtCore.Qt.WindowStaysOnTopHint
|
||||
)
|
||||
|
||||
self.setMinimumWidth(self._min_width)
|
||||
self.setMinimumHeight(self._min_height)
|
||||
|
||||
top_widget = QtWidgets.QWidget(self)
|
||||
|
||||
gift_pixmap = self._get_gift_pixmap()
|
||||
gift_icon_label = PixmapLabel(gift_pixmap, top_widget)
|
||||
|
||||
label_widget = QtWidgets.QLabel(
|
||||
(
|
||||
"Your AYON needs to update."
|
||||
"<br/><br/>Please restart AYON launcher and all running"
|
||||
" applications as soon as possible."
|
||||
),
|
||||
top_widget
|
||||
)
|
||||
label_widget.setWordWrap(True)
|
||||
|
||||
top_layout = QtWidgets.QHBoxLayout(top_widget)
|
||||
top_layout.setSpacing(10)
|
||||
top_layout.addWidget(gift_icon_label, 0, QtCore.Qt.AlignCenter)
|
||||
top_layout.addWidget(label_widget, 1)
|
||||
|
||||
ignore_btn = QtWidgets.QPushButton("Ignore", self)
|
||||
restart_btn = QtWidgets.QPushButton("Restart && Change", self)
|
||||
restart_btn.setObjectName("TrayRestartButton")
|
||||
|
||||
btns_layout = QtWidgets.QHBoxLayout()
|
||||
btns_layout.addStretch(1)
|
||||
btns_layout.addWidget(ignore_btn, 0)
|
||||
btns_layout.addWidget(restart_btn, 0)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(top_widget, 0)
|
||||
layout.addStretch(1)
|
||||
layout.addLayout(btns_layout, 0)
|
||||
|
||||
ignore_btn.clicked.connect(self._on_ignore)
|
||||
restart_btn.clicked.connect(self._on_reset)
|
||||
|
||||
self._label_widget = label_widget
|
||||
self._gift_icon_label = gift_icon_label
|
||||
self._ignore_btn = ignore_btn
|
||||
self._restart_btn = restart_btn
|
||||
|
||||
self._restart_accepted = False
|
||||
self._current_is_higher = False
|
||||
|
||||
self._close_silently = False
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
def close_silently(self):
|
||||
self._close_silently = True
|
||||
self.close()
|
||||
|
||||
def showEvent(self, event):
|
||||
super(UpdateDialog, self).showEvent(event)
|
||||
self._close_silently = False
|
||||
self._restart_accepted = False
|
||||
|
||||
def closeEvent(self, event):
|
||||
super(UpdateDialog, self).closeEvent(event)
|
||||
if self._restart_accepted or self._current_is_higher:
|
||||
return
|
||||
|
||||
if self._close_silently:
|
||||
return
|
||||
|
||||
# Trigger ignore requested only if restart was not clicked and current
|
||||
# version is lower
|
||||
self.ignore_requested.emit()
|
||||
|
||||
def _on_ignore(self):
|
||||
self.reject()
|
||||
|
||||
def _on_reset(self):
|
||||
self._restart_accepted = True
|
||||
self.restart_requested.emit()
|
||||
self.accept()
|
||||
|
||||
def _get_gift_pixmap(self):
|
||||
image_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"images",
|
||||
"gifts.png"
|
||||
)
|
||||
src_image = QtGui.QImage(image_path)
|
||||
color_value = style.get_objected_colors("font")
|
||||
|
||||
return paint_image_with_color(
|
||||
src_image,
|
||||
color_value.get_qcolor()
|
||||
)
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import collections
|
||||
import os
|
||||
import sys
|
||||
import collections
|
||||
import atexit
|
||||
|
||||
import platform
|
||||
|
||||
import ayon_api
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
|
||||
from ayon_core import resources, style
|
||||
|
|
@ -12,57 +13,25 @@ from ayon_core.lib import (
|
|||
Logger,
|
||||
get_ayon_launcher_args,
|
||||
run_detached_process,
|
||||
is_dev_mode_enabled,
|
||||
is_staging_enabled,
|
||||
is_running_from_build,
|
||||
)
|
||||
from ayon_core.lib import is_running_from_build
|
||||
from ayon_core.settings import get_ayon_settings
|
||||
from ayon_core.addon import (
|
||||
ITrayAction,
|
||||
ITrayService,
|
||||
TrayAddonsManager,
|
||||
)
|
||||
from ayon_core.settings import get_system_settings
|
||||
from ayon_core.tools.utils import (
|
||||
WrappedCallbackItem,
|
||||
get_ayon_qt_app,
|
||||
)
|
||||
|
||||
from .info_widget import InfoWidget
|
||||
|
||||
|
||||
# TODO PixmapLabel should be moved to 'utils' in other future PR so should be
|
||||
# imported from there
|
||||
class PixmapLabel(QtWidgets.QLabel):
|
||||
"""Label resizing image to height of font."""
|
||||
def __init__(self, pixmap, parent):
|
||||
super(PixmapLabel, self).__init__(parent)
|
||||
self._empty_pixmap = QtGui.QPixmap(0, 0)
|
||||
self._source_pixmap = pixmap
|
||||
|
||||
def set_source_pixmap(self, pixmap):
|
||||
"""Change source image."""
|
||||
self._source_pixmap = pixmap
|
||||
self._set_resized_pix()
|
||||
|
||||
def _get_pix_size(self):
|
||||
size = self.fontMetrics().height() * 3
|
||||
return size, size
|
||||
|
||||
def _set_resized_pix(self):
|
||||
if self._source_pixmap is None:
|
||||
self.setPixmap(self._empty_pixmap)
|
||||
return
|
||||
width, height = self._get_pix_size()
|
||||
self.setPixmap(
|
||||
self._source_pixmap.scaled(
|
||||
width,
|
||||
height,
|
||||
QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation
|
||||
)
|
||||
)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
self._set_resized_pix()
|
||||
super(PixmapLabel, self).resizeEvent(event)
|
||||
from .dialogs import (
|
||||
UpdateDialog,
|
||||
)
|
||||
|
||||
|
||||
class TrayManager:
|
||||
|
|
@ -78,22 +47,26 @@ class TrayManager:
|
|||
|
||||
self.log = Logger.get_logger(self.__class__.__name__)
|
||||
|
||||
system_settings = get_system_settings()
|
||||
studio_settings = get_ayon_settings()
|
||||
|
||||
version_check_interval = system_settings["general"].get(
|
||||
"version_check_interval"
|
||||
update_check_interval = studio_settings["core"].get(
|
||||
"update_check_interval"
|
||||
)
|
||||
if version_check_interval is None:
|
||||
version_check_interval = 5
|
||||
self._version_check_interval = version_check_interval * 60 * 1000
|
||||
if update_check_interval is None:
|
||||
update_check_interval = 5
|
||||
self._update_check_interval = update_check_interval * 60 * 1000
|
||||
|
||||
self._addons_manager = TrayAddonsManager()
|
||||
|
||||
self.errors = []
|
||||
|
||||
self.main_thread_timer = None
|
||||
self._update_check_timer = None
|
||||
self._outdated_dialog = None
|
||||
|
||||
self._main_thread_timer = None
|
||||
self._main_thread_callbacks = collections.deque()
|
||||
self._execution_in_progress = None
|
||||
self._closing = False
|
||||
|
||||
@property
|
||||
def doubleclick_callback(self):
|
||||
|
|
@ -107,29 +80,25 @@ class TrayManager:
|
|||
if callback:
|
||||
self.execute_in_main_thread(callback)
|
||||
|
||||
def _restart_and_install(self):
|
||||
self.restart(use_expected_version=True)
|
||||
def show_tray_message(self, title, message, icon=None, msecs=None):
|
||||
"""Show tray message.
|
||||
|
||||
def execute_in_main_thread(self, callback, *args, **kwargs):
|
||||
if isinstance(callback, WrappedCallbackItem):
|
||||
item = callback
|
||||
else:
|
||||
item = WrappedCallbackItem(callback, *args, **kwargs)
|
||||
Args:
|
||||
title (str): Title of message.
|
||||
message (str): Content of message.
|
||||
icon (QSystemTrayIcon.MessageIcon): Message's icon. Default is
|
||||
Information icon, may differ by Qt version.
|
||||
msecs (int): Duration of message visibility in milliseconds.
|
||||
Default is 10000 msecs, may differ by Qt version.
|
||||
"""
|
||||
args = [title, message]
|
||||
kwargs = {}
|
||||
if icon:
|
||||
kwargs["icon"] = icon
|
||||
if msecs:
|
||||
kwargs["msecs"] = msecs
|
||||
|
||||
self._main_thread_callbacks.append(item)
|
||||
|
||||
return item
|
||||
|
||||
def _main_thread_execution(self):
|
||||
if self._execution_in_progress:
|
||||
return
|
||||
self._execution_in_progress = True
|
||||
for _ in range(len(self._main_thread_callbacks)):
|
||||
if self._main_thread_callbacks:
|
||||
item = self._main_thread_callbacks.popleft()
|
||||
item.execute()
|
||||
|
||||
self._execution_in_progress = False
|
||||
self.tray_widget.showMessage(*args, **kwargs)
|
||||
|
||||
def initialize_addons(self):
|
||||
"""Add addons to tray."""
|
||||
|
|
@ -140,7 +109,9 @@ class TrayManager:
|
|||
self.tray_widget.menu.addMenu(admin_submenu)
|
||||
|
||||
# Add services if they are
|
||||
services_submenu = ITrayService.services_submenu(self.tray_widget.menu)
|
||||
services_submenu = ITrayService.services_submenu(
|
||||
self.tray_widget.menu
|
||||
)
|
||||
self.tray_widget.menu.addMenu(services_submenu)
|
||||
|
||||
# Add separator
|
||||
|
|
@ -165,39 +136,172 @@ class TrayManager:
|
|||
main_thread_timer.timeout.connect(self._main_thread_execution)
|
||||
main_thread_timer.start()
|
||||
|
||||
self.main_thread_timer = main_thread_timer
|
||||
self._main_thread_timer = main_thread_timer
|
||||
|
||||
update_check_timer = QtCore.QTimer()
|
||||
if self._update_check_interval > 0:
|
||||
update_check_timer.timeout.connect(self._on_update_check_timer)
|
||||
update_check_timer.setInterval(self._update_check_interval)
|
||||
update_check_timer.start()
|
||||
self._update_check_timer = update_check_timer
|
||||
|
||||
self.execute_in_main_thread(self._startup_validations)
|
||||
|
||||
def restart(self):
|
||||
"""Restart Tray tool.
|
||||
|
||||
First creates new process with same argument and close current tray.
|
||||
"""
|
||||
|
||||
self._closing = True
|
||||
|
||||
args = get_ayon_launcher_args()
|
||||
|
||||
# Create a copy of sys.argv
|
||||
additional_args = list(sys.argv)
|
||||
# Remove first argument from 'sys.argv'
|
||||
# - when running from code the first argument is 'start.py'
|
||||
# - when running from build the first argument is executable
|
||||
additional_args.pop(0)
|
||||
additional_args = [
|
||||
arg
|
||||
for arg in additional_args
|
||||
if arg not in {"--use-staging", "--use-dev"}
|
||||
]
|
||||
|
||||
if is_dev_mode_enabled():
|
||||
additional_args.append("--use-dev")
|
||||
elif is_staging_enabled():
|
||||
additional_args.append("--use-staging")
|
||||
|
||||
args.extend(additional_args)
|
||||
|
||||
envs = dict(os.environ.items())
|
||||
for key in {
|
||||
"AYON_BUNDLE_NAME",
|
||||
}:
|
||||
envs.pop(key, None)
|
||||
|
||||
run_detached_process(args, env=envs)
|
||||
self.exit()
|
||||
|
||||
def exit(self):
|
||||
self._closing = True
|
||||
self.tray_widget.exit()
|
||||
|
||||
def on_exit(self):
|
||||
self._addons_manager.on_exit()
|
||||
|
||||
def execute_in_main_thread(self, callback, *args, **kwargs):
|
||||
if isinstance(callback, WrappedCallbackItem):
|
||||
item = callback
|
||||
else:
|
||||
item = WrappedCallbackItem(callback, *args, **kwargs)
|
||||
|
||||
self._main_thread_callbacks.append(item)
|
||||
|
||||
return item
|
||||
|
||||
def _on_update_check_timer(self):
|
||||
try:
|
||||
bundles = ayon_api.get_bundles()
|
||||
user = ayon_api.get_user()
|
||||
# This is a workaround for bug in ayon-python-api
|
||||
if user.get("code") == 401:
|
||||
raise Exception("Unauthorized")
|
||||
except Exception:
|
||||
self._revalidate_ayon_auth()
|
||||
if self._closing:
|
||||
return
|
||||
|
||||
try:
|
||||
bundles = ayon_api.get_bundles()
|
||||
except Exception:
|
||||
return
|
||||
|
||||
if is_dev_mode_enabled():
|
||||
return
|
||||
|
||||
bundle_type = (
|
||||
"stagingBundle"
|
||||
if is_staging_enabled()
|
||||
else "productionBundle"
|
||||
)
|
||||
|
||||
expected_bundle = bundles.get(bundle_type)
|
||||
current_bundle = os.environ.get("AYON_BUNDLE_NAME")
|
||||
is_expected = expected_bundle == current_bundle
|
||||
if is_expected or expected_bundle is None:
|
||||
self._restart_action.setVisible(False)
|
||||
if (
|
||||
self._outdated_dialog is not None
|
||||
and self._outdated_dialog.isVisible()
|
||||
):
|
||||
self._outdated_dialog.close_silently()
|
||||
return
|
||||
|
||||
self._restart_action.setVisible(True)
|
||||
|
||||
if self._outdated_dialog is None:
|
||||
self._outdated_dialog = UpdateDialog()
|
||||
self._outdated_dialog.restart_requested.connect(
|
||||
self._restart_and_install
|
||||
)
|
||||
self._outdated_dialog.ignore_requested.connect(
|
||||
self._outdated_bundle_ignored
|
||||
)
|
||||
|
||||
self._outdated_dialog.show()
|
||||
self._outdated_dialog.raise_()
|
||||
self._outdated_dialog.activateWindow()
|
||||
|
||||
def _revalidate_ayon_auth(self):
|
||||
result = self._show_ayon_login(restart_on_token_change=False)
|
||||
if self._closing:
|
||||
return False
|
||||
|
||||
if not result.new_token:
|
||||
self.exit()
|
||||
return False
|
||||
return True
|
||||
|
||||
def _restart_and_install(self):
|
||||
self.restart()
|
||||
|
||||
def _outdated_bundle_ignored(self):
|
||||
self.show_tray_message(
|
||||
"AYON update ignored",
|
||||
(
|
||||
"Please restart AYON launcher as soon as possible"
|
||||
" to propagate updates."
|
||||
)
|
||||
)
|
||||
|
||||
def _main_thread_execution(self):
|
||||
if self._execution_in_progress:
|
||||
return
|
||||
self._execution_in_progress = True
|
||||
for _ in range(len(self._main_thread_callbacks)):
|
||||
if self._main_thread_callbacks:
|
||||
item = self._main_thread_callbacks.popleft()
|
||||
try:
|
||||
item.execute()
|
||||
except BaseException:
|
||||
self.log.erorr(
|
||||
"Main thread execution failed", exc_info=True
|
||||
)
|
||||
|
||||
self._execution_in_progress = False
|
||||
|
||||
def _startup_validations(self):
|
||||
"""Run possible startup validations."""
|
||||
pass
|
||||
|
||||
def show_tray_message(self, title, message, icon=None, msecs=None):
|
||||
"""Show tray message.
|
||||
|
||||
Args:
|
||||
title (str): Title of message.
|
||||
message (str): Content of message.
|
||||
icon (QSystemTrayIcon.MessageIcon): Message's icon. Default is
|
||||
Information icon, may differ by Qt version.
|
||||
msecs (int): Duration of message visibility in milliseconds.
|
||||
Default is 10000 msecs, may differ by Qt version.
|
||||
"""
|
||||
args = [title, message]
|
||||
kwargs = {}
|
||||
if icon:
|
||||
kwargs["icon"] = icon
|
||||
if msecs:
|
||||
kwargs["msecs"] = msecs
|
||||
|
||||
self.tray_widget.showMessage(*args, **kwargs)
|
||||
# Trigger bundle validation on start
|
||||
self._update_check_timer.timeout.emit()
|
||||
|
||||
def _add_version_item(self):
|
||||
login_action = QtWidgets.QAction("Login", self.tray_widget)
|
||||
login_action.triggered.connect(self._on_ayon_login)
|
||||
self.tray_widget.menu.addAction(login_action)
|
||||
|
||||
version_string = os.getenv("AYON_VERSION", "AYON Info")
|
||||
|
||||
version_action = QtWidgets.QAction(version_string, self.tray_widget)
|
||||
|
|
@ -216,16 +320,24 @@ class TrayManager:
|
|||
self._restart_action = restart_action
|
||||
|
||||
def _on_ayon_login(self):
|
||||
self.execute_in_main_thread(self._show_ayon_login)
|
||||
self.execute_in_main_thread(
|
||||
self._show_ayon_login,
|
||||
restart_on_token_change=True
|
||||
)
|
||||
|
||||
def _show_ayon_login(self):
|
||||
def _show_ayon_login(self, restart_on_token_change):
|
||||
from ayon_common.connection.credentials import change_user_ui
|
||||
|
||||
result = change_user_ui()
|
||||
if result.shutdown:
|
||||
self.exit()
|
||||
return result
|
||||
|
||||
elif result.restart or result.token_changed:
|
||||
restart = result.restart
|
||||
if restart_on_token_change and result.token_changed:
|
||||
restart = True
|
||||
|
||||
if restart:
|
||||
# Remove environment variables from current connection
|
||||
# - keep develop, staging, headless values
|
||||
for key in {
|
||||
|
|
@ -235,23 +347,13 @@ class TrayManager:
|
|||
}:
|
||||
os.environ.pop(key, None)
|
||||
self.restart()
|
||||
return result
|
||||
|
||||
def _on_restart_action(self):
|
||||
self.restart(use_expected_version=True)
|
||||
self.restart()
|
||||
|
||||
def restart(self, use_expected_version=False, reset_version=False):
|
||||
"""Restart Tray tool.
|
||||
|
||||
First creates new process with same argument and close current tray.
|
||||
|
||||
Args:
|
||||
use_expected_version(bool): OpenPype version is set to expected
|
||||
version.
|
||||
reset_version(bool): OpenPype version is cleaned up so igniters
|
||||
logic will decide which version will be used.
|
||||
"""
|
||||
def _restart_ayon(self):
|
||||
args = get_ayon_launcher_args()
|
||||
envs = dict(os.environ.items())
|
||||
|
||||
# Create a copy of sys.argv
|
||||
additional_args = list(sys.argv)
|
||||
|
|
@ -259,35 +361,28 @@ class TrayManager:
|
|||
# - when running from code the first argument is 'start.py'
|
||||
# - when running from build the first argument is executable
|
||||
additional_args.pop(0)
|
||||
additional_args = [
|
||||
arg
|
||||
for arg in additional_args
|
||||
if arg not in {"--use-staging", "--use-dev"}
|
||||
]
|
||||
|
||||
cleanup_additional_args = False
|
||||
if use_expected_version:
|
||||
cleanup_additional_args = True
|
||||
reset_version = True
|
||||
|
||||
# Pop OPENPYPE_VERSION
|
||||
if reset_version:
|
||||
cleanup_additional_args = True
|
||||
envs.pop("OPENPYPE_VERSION", None)
|
||||
|
||||
if cleanup_additional_args:
|
||||
_additional_args = []
|
||||
for arg in additional_args:
|
||||
if arg == "--use-staging" or arg.startswith("--use-version"):
|
||||
continue
|
||||
_additional_args.append(arg)
|
||||
additional_args = _additional_args
|
||||
if is_dev_mode_enabled():
|
||||
additional_args.append("--use-dev")
|
||||
elif is_staging_enabled():
|
||||
additional_args.append("--use-staging")
|
||||
|
||||
args.extend(additional_args)
|
||||
|
||||
envs = dict(os.environ.items())
|
||||
for key in {
|
||||
"AYON_BUNDLE_NAME",
|
||||
}:
|
||||
envs.pop(key, None)
|
||||
|
||||
run_detached_process(args, env=envs)
|
||||
self.exit()
|
||||
|
||||
def exit(self):
|
||||
self.tray_widget.exit()
|
||||
|
||||
def on_exit(self):
|
||||
self._addons_manager.on_exit()
|
||||
|
||||
def _on_version_action(self):
|
||||
if self._info_widget is None:
|
||||
self._info_widget = InfoWidget()
|
||||
|
|
|
|||
|
|
@ -279,7 +279,8 @@ def create_server_package(
|
|||
def main(
|
||||
output_dir: Optional[str]=None,
|
||||
skip_zip: bool=False,
|
||||
keep_sources: bool=False
|
||||
keep_sources: bool=False,
|
||||
clear_output_dir: bool=False
|
||||
):
|
||||
log = logging.getLogger("create_package")
|
||||
log.info("Start creating package")
|
||||
|
|
@ -292,7 +293,8 @@ def main(
|
|||
new_created_version_dir = os.path.join(
|
||||
output_dir, ADDON_NAME, ADDON_VERSION
|
||||
)
|
||||
if os.path.isdir(new_created_version_dir):
|
||||
|
||||
if os.path.isdir(new_created_version_dir) and clear_output_dir:
|
||||
log.info(f"Purging {new_created_version_dir}")
|
||||
shutil.rmtree(output_dir)
|
||||
|
||||
|
|
@ -339,6 +341,15 @@ if __name__ == "__main__":
|
|||
"Keep folder structure when server package is created."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--clear-output-dir",
|
||||
dest="clear_output_dir",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Clear output directory before package creation."
|
||||
)
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-o", "--output",
|
||||
dest="output_dir",
|
||||
|
|
@ -350,4 +361,9 @@ if __name__ == "__main__":
|
|||
)
|
||||
|
||||
args = parser.parse_args(sys.argv[1:])
|
||||
main(args.output_dir, args.skip_zip, args.keep_sources)
|
||||
main(
|
||||
args.output_dir,
|
||||
args.skip_zip,
|
||||
args.keep_sources,
|
||||
args.clear_output_dir
|
||||
)
|
||||
|
|
|
|||
|
|
@ -121,6 +121,11 @@ class CoreSettings(BaseSettingsModel):
|
|||
widget="textarea",
|
||||
scope=["studio"],
|
||||
)
|
||||
update_check_interval: int = SettingsField(
|
||||
5,
|
||||
title="Update check interval (minutes)",
|
||||
ge=0
|
||||
)
|
||||
disk_mapping: DiskMappingModel = SettingsField(
|
||||
default_factory=DiskMappingModel,
|
||||
title="Disk mapping",
|
||||
|
|
|
|||
|
|
@ -287,6 +287,13 @@ class ProcessSubmittedJobOnFarmModel(BaseSettingsModel):
|
|||
default_factory=list,
|
||||
title="Skip integration of representation with ext"
|
||||
)
|
||||
families_transfer: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title=(
|
||||
"List of family names to transfer\n"
|
||||
"to generated instances (AOVs for example)."
|
||||
)
|
||||
)
|
||||
aov_filter: list[AOVFilterSubmodel] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Reviewable products filter",
|
||||
|
|
@ -470,6 +477,7 @@ DEFAULT_DEADLINE_PLUGINS_SETTINGS = {
|
|||
"deadline_priority": 50,
|
||||
"publishing_script": "",
|
||||
"skip_integration_repre_list": [],
|
||||
"families_transfer": ["render3d", "render2d", "ftrack", "slate"],
|
||||
"aov_filter": [
|
||||
{
|
||||
"name": "maya",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -67,7 +78,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"
|
||||
|
|
@ -110,6 +128,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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue