diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py
index 653f97b3dd..8edccd48d4 100644
--- a/openpype/hooks/pre_add_last_workfile_arg.py
+++ b/openpype/hooks/pre_add_last_workfile_arg.py
@@ -17,11 +17,12 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook):
"nuke",
"nukex",
"hiero",
+ "houdini",
"nukestudio",
"blender",
"photoshop",
"tvpaint",
- "afftereffects"
+ "aftereffects"
]
def execute(self):
diff --git a/openpype/hooks/pre_copy_template_workfile.py b/openpype/hooks/pre_copy_template_workfile.py
index 5c56d721e8..dffac22ee2 100644
--- a/openpype/hooks/pre_copy_template_workfile.py
+++ b/openpype/hooks/pre_copy_template_workfile.py
@@ -19,7 +19,7 @@ class CopyTemplateWorkfile(PreLaunchHook):
# Before `AddLastWorkfileToLaunchArgs`
order = 0
- app_groups = ["blender", "photoshop", "tvpaint", "afftereffects"]
+ app_groups = ["blender", "photoshop", "tvpaint", "aftereffects"]
def execute(self):
"""Check if can copy template for context and do it if possible.
@@ -44,7 +44,7 @@ class CopyTemplateWorkfile(PreLaunchHook):
return
if os.path.exists(last_workfile):
- self.log.debug("Last workfile exits. Skipping {} process.".format(
+ self.log.debug("Last workfile exists. Skipping {} process.".format(
self.__class__.__name__
))
return
@@ -120,7 +120,7 @@ class CopyTemplateWorkfile(PreLaunchHook):
f"Creating workfile from template: \"{template_path}\""
)
- # Copy template workfile to new destinantion
+ # Copy template workfile to new destination
shutil.copy2(
os.path.normpath(template_path),
os.path.normpath(last_workfile)
diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py
index bbb7c38119..74d9e7607a 100644
--- a/openpype/hosts/flame/api/lib.py
+++ b/openpype/hosts/flame/api/lib.py
@@ -527,6 +527,7 @@ def get_segment_attributes(segment):
# Add timeline segment to tree
clip_data = {
+ "shot_name": segment.shot_name.get_value(),
"segment_name": segment.name.get_value(),
"segment_comment": segment.comment.get_value(),
"tape_name": segment.tape_name,
diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py
index db1793cba8..ec49db1601 100644
--- a/openpype/hosts/flame/api/plugin.py
+++ b/openpype/hosts/flame/api/plugin.py
@@ -361,6 +361,7 @@ class PublishableClip:
vertical_sync_default = False
driving_layer_default = ""
index_from_segment_default = False
+ use_shot_name_default = False
def __init__(self, segment, **kwargs):
self.rename_index = kwargs["rename_index"]
@@ -376,6 +377,7 @@ class PublishableClip:
# segment (clip) main attributes
self.cs_name = self.clip_data["segment_name"]
self.cs_index = int(self.clip_data["segment"])
+ self.shot_name = self.clip_data["shot_name"]
# get track name and index
self.track_index = int(self.clip_data["track"])
@@ -419,18 +421,21 @@ class PublishableClip:
# deal with clip name
new_name = self.marker_data.pop("newClipName")
- if self.rename:
+ if self.rename and not self.use_shot_name:
# rename segment
self.current_segment.name = str(new_name)
self.marker_data["asset"] = str(new_name)
+ elif self.use_shot_name:
+ self.marker_data["asset"] = self.shot_name
+ self.marker_data["hierarchyData"]["shot"] = self.shot_name
else:
self.marker_data["asset"] = self.cs_name
self.marker_data["hierarchyData"]["shot"] = self.cs_name
if self.marker_data["heroTrack"] and self.review_layer:
- self.marker_data.update({"reviewTrack": self.review_layer})
+ self.marker_data["reviewTrack"] = self.review_layer
else:
- self.marker_data.update({"reviewTrack": None})
+ self.marker_data["reviewTrack"] = None
# create pype tag on track_item and add data
fpipeline.imprint(self.current_segment, self.marker_data)
@@ -463,6 +468,8 @@ class PublishableClip:
# ui_inputs data or default values if gui was not used
self.rename = self.ui_inputs.get(
"clipRename", {}).get("value") or self.rename_default
+ self.use_shot_name = self.ui_inputs.get(
+ "useShotName", {}).get("value") or self.use_shot_name_default
self.clip_name = self.ui_inputs.get(
"clipName", {}).get("value") or self.clip_name_default
self.hierarchy = self.ui_inputs.get(
diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py
index f055c77a89..11c00dab42 100644
--- a/openpype/hosts/flame/plugins/create/create_shot_clip.py
+++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py
@@ -87,41 +87,48 @@ class CreateShotClip(opfapi.Creator):
"target": "tag",
"toolTip": "Parents folder for shot root folder, Template filled with `Hierarchy Data` section", # noqa
"order": 0},
+ "useShotName": {
+ "value": True,
+ "type": "QCheckBox",
+ "label": "Use Shot Name",
+ "target": "ui",
+ "toolTip": "Use name form Shot name clip attribute", # noqa
+ "order": 1},
"clipRename": {
"value": False,
"type": "QCheckBox",
"label": "Rename clips",
"target": "ui",
"toolTip": "Renaming selected clips on fly", # noqa
- "order": 1},
+ "order": 2},
"clipName": {
"value": "{sequence}{shot}",
"type": "QLineEdit",
"label": "Clip Name Template",
"target": "ui",
"toolTip": "template for creating shot namespaused for renaming (use rename: on)", # noqa
- "order": 2},
+ "order": 3},
"segmentIndex": {
"value": True,
"type": "QCheckBox",
"label": "Segment index",
"target": "ui",
"toolTip": "Take number from segment index", # noqa
- "order": 3},
+ "order": 4},
"countFrom": {
"value": 10,
"type": "QSpinBox",
"label": "Count sequence from",
"target": "ui",
"toolTip": "Set when the sequence number stafrom", # noqa
- "order": 4},
+ "order": 5},
"countSteps": {
"value": 10,
"type": "QSpinBox",
"label": "Stepping number",
"target": "ui",
"toolTip": "What number is adding every new step", # noqa
- "order": 5},
+ "order": 6},
}
},
"hierarchyData": {
diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py
index 72f1c8e71f..5a087ea276 100644
--- a/openpype/hosts/houdini/api/lib.py
+++ b/openpype/hosts/houdini/api/lib.py
@@ -542,3 +542,37 @@ def maintained_selection():
if previous_selection:
for node in previous_selection:
node.setSelected(on=True)
+
+
+def reset_framerange():
+ """Set frame range to current asset"""
+
+ asset_name = api.Session["AVALON_ASSET"]
+ asset = io.find_one({"name": asset_name, "type": "asset"})
+
+ frame_start = asset["data"].get("frameStart")
+ frame_end = asset["data"].get("frameEnd")
+ # Backwards compatibility
+ if frame_start is None or frame_end is None:
+ frame_start = asset["data"].get("edit_in")
+ frame_end = asset["data"].get("edit_out")
+
+ if frame_start is None or frame_end is None:
+ log.warning("No edit information found for %s" % asset_name)
+ return
+
+ handles = asset["data"].get("handles") or 0
+ handle_start = asset["data"].get("handleStart")
+ if handle_start is None:
+ handle_start = handles
+
+ handle_end = asset["data"].get("handleEnd")
+ if handle_end is None:
+ handle_end = handles
+
+ frame_start -= int(handle_start)
+ frame_end += int(handle_end)
+
+ hou.playbar.setFrameRange(frame_start, frame_end)
+ hou.playbar.setPlaybackRange(frame_start, frame_end)
+ hou.setFrame(frame_start)
diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py
index c3dbdc5ef5..1c08e72d65 100644
--- a/openpype/hosts/houdini/api/pipeline.py
+++ b/openpype/hosts/houdini/api/pipeline.py
@@ -4,6 +4,7 @@ import logging
import contextlib
import hou
+import hdefereval
import pyblish.api
import avalon.api
@@ -66,9 +67,10 @@ def install():
sys.path.append(hou_pythonpath)
- # Set asset FPS for the empty scene directly after launch of Houdini
- # so it initializes into the correct scene FPS
- _set_asset_fps()
+ # Set asset settings for the empty scene directly after launch of Houdini
+ # so it initializes into the correct scene FPS, Frame Range, etc.
+ # todo: make sure this doesn't trigger when opening with last workfile
+ _set_context_settings()
def uninstall():
@@ -279,18 +281,49 @@ def on_open(*args):
def on_new(_):
"""Set project resolution and fps when create a new file"""
+
+ if hou.hipFile.isLoadingHipFile():
+ # This event also triggers when Houdini opens a file due to the
+ # new event being registered to 'afterClear'. As such we can skip
+ # 'new' logic if the user is opening a file anyway
+ log.debug("Skipping on new callback due to scene being opened.")
+ return
+
log.info("Running callback on new..")
- _set_asset_fps()
+ _set_context_settings()
+
+ # It seems that the current frame always gets reset to frame 1 on
+ # new scene. So we enforce current frame to be at the start of the playbar
+ # with execute deferred
+ def _enforce_start_frame():
+ start = hou.playbar.playbackRange()[0]
+ hou.setFrame(start)
+
+ hdefereval.executeDeferred(_enforce_start_frame)
-def _set_asset_fps():
- """Set Houdini scene FPS to the default required for current asset"""
+def _set_context_settings():
+ """Apply the project settings from the project definition
+
+ Settings can be overwritten by an asset if the asset.data contains
+ any information regarding those settings.
+
+ Examples of settings:
+ fps
+ resolution
+ renderer
+
+ Returns:
+ None
+ """
# Set new scene fps
fps = get_asset_fps()
print("Setting scene FPS to %i" % fps)
lib.set_scene_fps(fps)
+ lib.reset_framerange()
+
def on_pyblish_instance_toggled(instance, new_value, old_value):
"""Toggle saver tool passthrough states on instance toggles."""
diff --git a/openpype/hosts/houdini/plugins/publish/save_scene.py b/openpype/hosts/houdini/plugins/publish/save_scene.py
index 1b12efa603..fe5962fbd3 100644
--- a/openpype/hosts/houdini/plugins/publish/save_scene.py
+++ b/openpype/hosts/houdini/plugins/publish/save_scene.py
@@ -2,26 +2,14 @@ import pyblish.api
import avalon.api
-class SaveCurrentScene(pyblish.api.InstancePlugin):
+class SaveCurrentScene(pyblish.api.ContextPlugin):
"""Save current scene"""
label = "Save current file"
- order = pyblish.api.IntegratorOrder - 0.49
+ order = pyblish.api.ExtractorOrder - 0.49
hosts = ["houdini"]
- families = ["usdrender",
- "redshift_rop"]
- targets = ["local"]
- def process(self, instance):
-
- # This should be a ContextPlugin, but this is a workaround
- # for a bug in pyblish to run once for a family: issue #250
- context = instance.context
- key = "__hasRun{}".format(self.__class__.__name__)
- if context.data.get(key, False):
- return
- else:
- context.data[key] = True
+ def process(self, context):
# Filename must not have changed since collecting
host = avalon.api.registered_host()
diff --git a/openpype/hosts/houdini/plugins/publish/save_scene_deadline.py b/openpype/hosts/houdini/plugins/publish/save_scene_deadline.py
deleted file mode 100644
index a0efd0610c..0000000000
--- a/openpype/hosts/houdini/plugins/publish/save_scene_deadline.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import pyblish.api
-
-
-class SaveCurrentSceneDeadline(pyblish.api.ContextPlugin):
- """Save current scene"""
-
- label = "Save current file"
- order = pyblish.api.IntegratorOrder - 0.49
- hosts = ["houdini"]
- targets = ["deadline"]
-
- def process(self, context):
- import hou
-
- assert (
- context.data["currentFile"] == hou.hipFile.path()
- ), "Collected filename from current scene name."
-
- if hou.hipFile.hasUnsavedChanges():
- self.log.info("Saving current file..")
- hou.hipFile.save(save_to_recent_files=True)
- else:
- self.log.debug("No unsaved changes, skipping file save..")
diff --git a/openpype/hosts/houdini/plugins/publish/validate_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_output_node.py
deleted file mode 100644
index 0b60ab5c48..0000000000
--- a/openpype/hosts/houdini/plugins/publish/validate_output_node.py
+++ /dev/null
@@ -1,77 +0,0 @@
-import pyblish.api
-
-
-class ValidateOutputNode(pyblish.api.InstancePlugin):
- """Validate the instance SOP Output Node.
-
- This will ensure:
- - The SOP Path is set.
- - The SOP Path refers to an existing object.
- - The SOP Path node is a SOP node.
- - The SOP Path node has at least one input connection (has an input)
- - The SOP Path has geometry data.
-
- """
-
- order = pyblish.api.ValidatorOrder
- families = ["pointcache", "vdbcache"]
- hosts = ["houdini"]
- label = "Validate Output Node"
-
- def process(self, instance):
-
- invalid = self.get_invalid(instance)
- if invalid:
- raise RuntimeError(
- "Output node(s) `%s` are incorrect. "
- "See plug-in log for details." % invalid
- )
-
- @classmethod
- def get_invalid(cls, instance):
-
- import hou
-
- output_node = instance.data["output_node"]
-
- if output_node is None:
- node = instance[0]
- cls.log.error(
- "SOP Output node in '%s' does not exist. "
- "Ensure a valid SOP output path is set." % node.path()
- )
-
- return [node.path()]
-
- # Output node must be a Sop node.
- if not isinstance(output_node, hou.SopNode):
- cls.log.error(
- "Output node %s is not a SOP node. "
- "SOP Path must point to a SOP node, "
- "instead found category type: %s"
- % (output_node.path(), output_node.type().category().name())
- )
- return [output_node.path()]
-
- # For the sake of completeness also assert the category type
- # is Sop to avoid potential edge case scenarios even though
- # the isinstance check above should be stricter than this category
- assert output_node.type().category().name() == "Sop", (
- "Output node %s is not of category Sop. This is a bug.."
- % output_node.path()
- )
-
- # Check if output node has incoming connections
- if not output_node.inputConnections():
- cls.log.error(
- "Output node `%s` has no incoming connections"
- % output_node.path()
- )
- return [output_node.path()]
-
- # Ensure the output node has at least Geometry data
- if not output_node.geometry():
- cls.log.error(
- "Output node `%s` has no geometry data." % output_node.path()
- )
- return [output_node.path()]
diff --git a/openpype/hosts/houdini/startup/MainMenuCommon.xml b/openpype/hosts/houdini/startup/MainMenuCommon.xml
index b8c7f93d76..abfa3f136e 100644
--- a/openpype/hosts/houdini/startup/MainMenuCommon.xml
+++ b/openpype/hosts/houdini/startup/MainMenuCommon.xml
@@ -66,6 +66,14 @@ host_tools.show_workfiles(parent)
]]>
+
+
+
+
+
diff --git a/openpype/hosts/houdini/startup/scripts/123.py b/openpype/hosts/houdini/startup/python2.7libs/pythonrc.py
similarity index 100%
rename from openpype/hosts/houdini/startup/scripts/123.py
rename to openpype/hosts/houdini/startup/python2.7libs/pythonrc.py
diff --git a/openpype/hosts/houdini/startup/scripts/houdinicore.py b/openpype/hosts/houdini/startup/python3.7libs/pythonrc.py
similarity index 100%
rename from openpype/hosts/houdini/startup/scripts/houdinicore.py
rename to openpype/hosts/houdini/startup/python3.7libs/pythonrc.py
diff --git a/openpype/hosts/maya/api/__init__.py b/openpype/hosts/maya/api/__init__.py
index 9ea798e927..5d76bf0f04 100644
--- a/openpype/hosts/maya/api/__init__.py
+++ b/openpype/hosts/maya/api/__init__.py
@@ -10,12 +10,6 @@ from .pipeline import (
ls,
containerise,
-
- lock,
- unlock,
- is_locked,
- lock_ignored,
-
)
from .plugin import (
Creator,
@@ -38,11 +32,9 @@ from .lib import (
read,
apply_shaders,
- without_extension,
maintained_selection,
suspended_refresh,
- unique_name,
unique_namespace,
)
@@ -54,11 +46,6 @@ __all__ = [
"ls",
"containerise",
- "lock",
- "unlock",
- "is_locked",
- "lock_ignored",
-
"Creator",
"Loader",
@@ -76,11 +63,9 @@ __all__ = [
"lsattrs",
"read",
- "unique_name",
"unique_namespace",
"apply_shaders",
- "without_extension",
"maintained_selection",
"suspended_refresh",
diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py
index 09d1a68223..0b05182bb1 100644
--- a/openpype/hosts/maya/api/lib.py
+++ b/openpype/hosts/maya/api/lib.py
@@ -2,7 +2,6 @@
import os
import sys
-import re
import platform
import uuid
import math
@@ -154,73 +153,59 @@ def maintained_selection():
cmds.select(clear=True)
-def unique_name(name, format="%02d", namespace="", prefix="", suffix=""):
- """Return unique `name`
-
- The function takes into consideration an optional `namespace`
- and `suffix`. The suffix is included in evaluating whether a
- name exists - such as `name` + "_GRP" - but isn't included
- in the returned value.
-
- If a namespace is provided, only names within that namespace
- are considered when evaluating whether the name is unique.
-
- Arguments:
- format (str, optional): The `name` is given a number, this determines
- how this number is formatted. Defaults to a padding of 2.
- E.g. my_name01, my_name02.
- namespace (str, optional): Only consider names within this namespace.
- suffix (str, optional): Only consider names with this suffix.
-
- Example:
- >>> name = cmds.createNode("transform", name="MyName")
- >>> cmds.objExists(name)
- True
- >>> unique = unique_name(name)
- >>> cmds.objExists(unique)
- False
-
- """
-
- iteration = 1
- unique = prefix + (name + format % iteration) + suffix
-
- while cmds.objExists(namespace + ":" + unique):
- iteration += 1
- unique = prefix + (name + format % iteration) + suffix
-
- if suffix:
- return unique[:-len(suffix)]
-
- return unique
-
-
def unique_namespace(namespace, format="%02d", prefix="", suffix=""):
"""Return unique namespace
- Similar to :func:`unique_name` but evaluating namespaces
- as opposed to object names.
-
Arguments:
namespace (str): Name of namespace to consider
format (str, optional): Formatting of the given iteration number
suffix (str, optional): Only consider namespaces with this suffix.
+ >>> unique_namespace("bar")
+ # bar01
+ >>> unique_namespace(":hello")
+ # :hello01
+ >>> unique_namespace("bar:", suffix="_NS")
+ # bar01_NS:
+
"""
+ def current_namespace():
+ current = cmds.namespaceInfo(currentNamespace=True,
+ absoluteName=True)
+ # When inside a namespace Maya adds no trailing :
+ if not current.endswith(":"):
+ current += ":"
+ return current
+
+ # Always check against the absolute namespace root
+ # There's no clash with :x if we're defining namespace :a:x
+ ROOT = ":" if namespace.startswith(":") else current_namespace()
+
+ # Strip trailing `:` tokens since we might want to add a suffix
+ start = ":" if namespace.startswith(":") else ""
+ end = ":" if namespace.endswith(":") else ""
+ namespace = namespace.strip(":")
+ if ":" in namespace:
+ # Split off any nesting that we don't uniqify anyway.
+ parents, namespace = namespace.rsplit(":", 1)
+ start += parents + ":"
+ ROOT += start
+
+ def exists(n):
+ # Check for clash with nodes and namespaces
+ fullpath = ROOT + n
+ return cmds.objExists(fullpath) or cmds.namespace(exists=fullpath)
+
iteration = 1
- unique = prefix + (namespace + format % iteration) + suffix
+ while True:
+ nr_namespace = namespace + format % iteration
+ unique = prefix + nr_namespace + suffix
+
+ if not exists(unique):
+ return start + unique + end
- # The `existing` set does not just contain the namespaces but *all* nodes
- # within "current namespace". We need all because the namespace could
- # also clash with a node name. To be truly unique and valid one needs to
- # check against all.
- existing = set(cmds.namespaceInfo(listNamespace=True))
- while unique in existing:
iteration += 1
- unique = prefix + (namespace + format % iteration) + suffix
-
- return unique
def read(node):
@@ -286,153 +271,6 @@ def pairwise(iterable):
return itertools.izip(a, a)
-def unique(name):
- assert isinstance(name, string_types), "`name` must be string"
-
- while cmds.objExists(name):
- matches = re.findall(r"\d+$", name)
-
- if matches:
- match = matches[-1]
- name = name.rstrip(match)
- number = int(match) + 1
- else:
- number = 1
-
- name = name + str(number)
-
- return name
-
-
-def uv_from_element(element):
- """Return the UV coordinate of given 'element'
-
- Supports components, meshes, nurbs.
-
- """
-
- supported = ["mesh", "nurbsSurface"]
-
- uv = [0.5, 0.5]
-
- if "." not in element:
- type = cmds.nodeType(element)
- if type == "transform":
- geometry_shape = cmds.listRelatives(element, shapes=True)
-
- if len(geometry_shape) >= 1:
- geometry_shape = geometry_shape[0]
- else:
- return
-
- elif type in supported:
- geometry_shape = element
-
- else:
- cmds.error("Could not do what you wanted..")
- return
- else:
- # If it is indeed a component - get the current Mesh
- try:
- parent = element.split(".", 1)[0]
-
- # Maya is funny in that when the transform of the shape
- # of the component element has children, the name returned
- # by that elementection is the shape. Otherwise, it is
- # the transform. So lets see what type we're dealing with here.
- if cmds.nodeType(parent) in supported:
- geometry_shape = parent
- else:
- geometry_shape = cmds.listRelatives(parent, shapes=1)[0]
-
- if not geometry_shape:
- cmds.error("Skipping %s: Could not find shape." % element)
- return
-
- if len(cmds.ls(geometry_shape)) > 1:
- cmds.warning("Multiple shapes with identical "
- "names found. This might not work")
-
- except TypeError as e:
- cmds.warning("Skipping %s: Didn't find a shape "
- "for component elementection. %s" % (element, e))
- return
-
- try:
- type = cmds.nodeType(geometry_shape)
-
- if type == "nurbsSurface":
- # If a surfacePoint is elementected on a nurbs surface
- root, u, v = element.rsplit("[", 2)
- uv = [float(u[:-1]), float(v[:-1])]
-
- if type == "mesh":
- # -----------
- # Average the U and V values
- # ===========
- uvs = cmds.polyListComponentConversion(element, toUV=1)
- if not uvs:
- cmds.warning("Couldn't derive any UV's from "
- "component, reverting to default U and V")
- raise TypeError
-
- # Flatten list of Uv's as sometimes it returns
- # neighbors like this [2:3] instead of [2], [3]
- flattened = []
-
- for uv in uvs:
- flattened.extend(cmds.ls(uv, flatten=True))
-
- uvs = flattened
-
- sumU = 0
- sumV = 0
- for uv in uvs:
- try:
- u, v = cmds.polyEditUV(uv, query=True)
- except Exception:
- cmds.warning("Couldn't find any UV coordinated, "
- "reverting to default U and V")
- raise TypeError
-
- sumU += u
- sumV += v
-
- averagedU = sumU / len(uvs)
- averagedV = sumV / len(uvs)
-
- uv = [averagedU, averagedV]
- except TypeError:
- pass
-
- return uv
-
-
-def shape_from_element(element):
- """Return shape of given 'element'
-
- Supports components, meshes, and surfaces
-
- """
-
- try:
- # Get either shape or transform, based on element-type
- node = cmds.ls(element, objectsOnly=True)[0]
- except Exception:
- cmds.warning("Could not find node in %s" % element)
- return None
-
- if cmds.nodeType(node) == 'transform':
- try:
- return cmds.listRelatives(node, shapes=True)[0]
- except Exception:
- cmds.warning("Could not find shape in %s" % element)
- return None
-
- else:
- return node
-
-
def export_alembic(nodes,
file,
frame_range=None,
@@ -577,115 +415,6 @@ def imprint(node, data):
cmds.setAttr(node + "." + key, value, **set_type)
-def serialise_shaders(nodes):
- """Generate a shader set dictionary
-
- Arguments:
- nodes (list): Absolute paths to nodes
-
- Returns:
- dictionary of (shader: id) pairs
-
- Schema:
- {
- "shader1": ["id1", "id2"],
- "shader2": ["id3", "id1"]
- }
-
- Example:
- {
- "Bazooka_Brothers01_:blinn4SG": [
- "f9520572-ac1d-11e6-b39e-3085a99791c9.f[4922:5001]",
- "f9520572-ac1d-11e6-b39e-3085a99791c9.f[4587:4634]",
- "f9520572-ac1d-11e6-b39e-3085a99791c9.f[1120:1567]",
- "f9520572-ac1d-11e6-b39e-3085a99791c9.f[4251:4362]"
- ],
- "lambert2SG": [
- "f9520571-ac1d-11e6-9dbb-3085a99791c9"
- ]
- }
-
- """
-
- valid_nodes = cmds.ls(
- nodes,
- long=True,
- recursive=True,
- showType=True,
- objectsOnly=True,
- type="transform"
- )
-
- meshes_by_id = {}
- for mesh in valid_nodes:
- shapes = cmds.listRelatives(valid_nodes[0],
- shapes=True,
- fullPath=True) or list()
-
- if shapes:
- shape = shapes[0]
- if not cmds.nodeType(shape):
- continue
-
- try:
- id_ = cmds.getAttr(mesh + ".mbID")
-
- if id_ not in meshes_by_id:
- meshes_by_id[id_] = list()
-
- meshes_by_id[id_].append(mesh)
-
- except ValueError:
- continue
-
- meshes_by_shader = dict()
- for mesh in meshes_by_id.values():
- shape = cmds.listRelatives(mesh,
- shapes=True,
- fullPath=True) or list()
-
- for shader in cmds.listConnections(shape,
- type="shadingEngine") or list():
-
- # Objects in this group are those that haven't got
- # any shaders. These are expected to be managed
- # elsewhere, such as by the default model loader.
- if shader == "initialShadingGroup":
- continue
-
- if shader not in meshes_by_shader:
- meshes_by_shader[shader] = list()
-
- shaded = cmds.sets(shader, query=True) or list()
- meshes_by_shader[shader].extend(shaded)
-
- shader_by_id = {}
- for shader, shaded in meshes_by_shader.items():
-
- if shader not in shader_by_id:
- shader_by_id[shader] = list()
-
- for mesh in shaded:
-
- # Enable shader assignment to faces.
- name = mesh.split(".f[")[0]
-
- transform = name
- if cmds.objectType(transform) == "mesh":
- transform = cmds.listRelatives(name, parent=True)[0]
-
- try:
- id_ = cmds.getAttr(transform + ".mbID")
- shader_by_id[shader].append(mesh.replace(name, id_))
- except KeyError:
- continue
-
- # Remove duplicates
- shader_by_id[shader] = list(set(shader_by_id[shader]))
-
- return shader_by_id
-
-
def lsattr(attr, value=None):
"""Return nodes matching `key` and `value`
@@ -764,17 +493,6 @@ def lsattrs(attrs):
return list(matches)
-@contextlib.contextmanager
-def without_extension():
- """Use cmds.file with defaultExtensions=False"""
- previous_setting = cmds.file(defaultExtensions=True, query=True)
- try:
- cmds.file(defaultExtensions=False)
- yield
- finally:
- cmds.file(defaultExtensions=previous_setting)
-
-
@contextlib.contextmanager
def attribute_values(attr_values):
"""Remaps node attributes to values during context.
@@ -853,26 +571,6 @@ def evaluation(mode="off"):
cmds.evaluationManager(mode=original)
-@contextlib.contextmanager
-def no_refresh():
- """Temporarily disables Maya's UI updates
-
- Note:
- This only disabled the main pane and will sometimes still
- trigger updates in torn off panels.
-
- """
-
- pane = _get_mel_global('gMainPane')
- state = cmds.paneLayout(pane, query=True, manage=True)
- cmds.paneLayout(pane, edit=True, manage=False)
-
- try:
- yield
- finally:
- cmds.paneLayout(pane, edit=True, manage=state)
-
-
@contextlib.contextmanager
def empty_sets(sets, force=False):
"""Remove all members of the sets during the context"""
@@ -1539,15 +1237,6 @@ def extract_alembic(file,
return file
-def maya_temp_folder():
- scene_dir = os.path.dirname(cmds.file(query=True, sceneName=True))
- tmp_dir = os.path.abspath(os.path.join(scene_dir, "..", "tmp"))
- if not os.path.isdir(tmp_dir):
- os.makedirs(tmp_dir)
-
- return tmp_dir
-
-
# region ID
def get_id_required_nodes(referenced_nodes=False, nodes=None):
"""Filter out any node which are locked (reference) or readOnly
@@ -1732,22 +1421,6 @@ def set_id(node, unique_id, overwrite=False):
cmds.setAttr(attr, unique_id, type="string")
-def remove_id(node):
- """Remove the id attribute from the input node.
-
- Args:
- node (str): The node name
-
- Returns:
- bool: Whether an id attribute was deleted
-
- """
- if cmds.attributeQuery("cbId", node=node, exists=True):
- cmds.deleteAttr("{}.cbId".format(node))
- return True
- return False
-
-
# endregion ID
def get_reference_node(path):
"""
@@ -2423,6 +2096,7 @@ def reset_scene_resolution():
set_scene_resolution(width, height, pixelAspect)
+
def set_context_settings():
"""Apply the project settings from the project definition
@@ -2881,7 +2555,7 @@ def get_attr_in_layer(attr, layer):
def fix_incompatible_containers():
- """Return whether the current scene has any outdated content"""
+ """Backwards compatibility: old containers to use new ReferenceLoader"""
host = api.registered_host()
for container in host.ls():
@@ -3120,7 +2794,7 @@ class RenderSetupListObserver:
cmds.delete(render_layer_set_name)
-class RenderSetupItemObserver():
+class RenderSetupItemObserver:
"""Handle changes in render setup items."""
def __init__(self, item):
@@ -3356,7 +3030,7 @@ def set_colorspace():
@contextlib.contextmanager
def root_parent(nodes):
# type: (list) -> list
- """Context manager to un-parent provided nodes and return then back."""
+ """Context manager to un-parent provided nodes and return them back."""
import pymel.core as pm # noqa
node_parents = []
diff --git a/openpype/hosts/maya/api/menu.json b/openpype/hosts/maya/api/menu.json
deleted file mode 100644
index a2efd5233c..0000000000
--- a/openpype/hosts/maya/api/menu.json
+++ /dev/null
@@ -1,924 +0,0 @@
-[
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\save_scene_incremental.py",
- "sourcetype": "file",
- "title": "# Version Up",
- "tooltip": "Incremental save with a specific format"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\open_current_folder.py",
- "sourcetype": "file",
- "title": "Open working folder..",
- "tooltip": "Show current scene in Explorer"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\avalon\\launch_manager.py",
- "sourcetype": "file",
- "title": "# Project Manager",
- "tooltip": "Add assets to the project"
- },
- {
- "type": "action",
- "command": "from openpype.tools.assetcreator import app as assetcreator; assetcreator.show(context='maya')",
- "sourcetype": "python",
- "title": "Asset Creator",
- "tooltip": "Open the Asset Creator"
- },
- {
- "type": "separator"
- },
- {
- "type": "menu",
- "title": "Modeling",
- "items": [
- {
- "type": "action",
- "command": "import easyTreezSource; reload(easyTreezSource); easyTreezSource.easyTreez()",
- "sourcetype": "python",
- "tags": ["modeling", "trees", "generate", "create", "plants"],
- "title": "EasyTreez",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\separateMeshPerShader.py",
- "sourcetype": "file",
- "tags": ["modeling", "separateMeshPerShader"],
- "title": "# Separate Mesh Per Shader",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\polyDetachSeparate.py",
- "sourcetype": "file",
- "tags": ["modeling", "poly", "detach", "separate"],
- "title": "# Polygon Detach and Separate",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py",
- "sourcetype": "file",
- "tags": ["modeling", "select", "nth", "edge", "ui"],
- "title": "# Select Every Nth Edge"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\djPFXUVs.py",
- "sourcetype": "file",
- "tags": ["modeling", "djPFX", "UVs"],
- "title": "# dj PFX UVs",
- "tooltip": ""
- }
- ]
- },
- {
- "type": "menu",
- "title": "Rigging",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\advancedSkeleton.py",
- "sourcetype": "file",
- "tags": [
- "rigging",
- "autorigger",
- "advanced",
- "skeleton",
- "advancedskeleton",
- "file"
- ],
- "title": "Advanced Skeleton"
- }
- ]
- },
- {
- "type": "menu",
- "title": "Shading",
- "items": [
- {
- "type": "menu",
- "title": "# VRay",
- "items": [
- {
- "type": "action",
- "title": "# Import Proxies",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayImportProxies.py",
- "sourcetype": "file",
- "tags": ["shading", "vray", "import", "proxies"],
- "tooltip": ""
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "title": "# Select All GES",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectAllGES.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "select All GES"]
- },
- {
- "type": "action",
- "title": "# Select All GES Under Selection",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "select", "all", "GES"]
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "title": "# Selection To VRay Mesh",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "selection", "vraymesh"]
- },
- {
- "type": "action",
- "title": "# Add VRay Round Edges Attribute",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "round edges", "attribute"]
- },
- {
- "type": "action",
- "title": "# Add Gamma",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayAddGamma.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "add gamma"]
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py",
- "sourcetype": "file",
- "title": "# Select Unconnected Shader Materials",
- "tags": [
- "shading",
- "vray",
- "select",
- "vraymesh",
- "materials",
- "unconnected shader slots"
- ],
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py",
- "sourcetype": "file",
- "title": "# Merge Similar VRay Mesh Materials",
- "tags": [
- "shading",
- "vray",
- "Merge",
- "VRayMesh",
- "Materials"
- ],
- "tooltip": ""
- },
- {
- "type": "action",
- "title": "# Create Two Sided Material",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py",
- "sourcetype": "file",
- "tooltip": "Creates two sided material for selected material and renames it",
- "tags": ["shading", "vray", "two sided", "material"]
- },
- {
- "type": "action",
- "title": "# Create Two Sided Material For Selected",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py",
- "sourcetype": "file",
- "tooltip": "Select material to create a two sided version from it",
- "tags": [
- "shading",
- "vray",
- "Create2SidedMtlForSelectedMtl.py"
- ]
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "title": "# Add OpenSubdiv Attribute",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": [
- "shading",
- "vray",
- "add",
- "open subdiv",
- "attribute"
- ]
- },
- {
- "type": "action",
- "title": "# Remove OpenSubdiv Attribute",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": [
- "shading",
- "vray",
- "remove",
- "opensubdiv",
- "attributee"
- ]
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "title": "# Add Subdivision Attribute",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": [
- "shading",
- "vray",
- "addVraySubdivisionAttribute"
- ]
- },
- {
- "type": "action",
- "title": "# Remove Subdivision Attribute.py",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": [
- "shading",
- "vray",
- "remove",
- "subdivision",
- "attribute"
- ]
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "title": "# Add Vray Object Ids",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayObjectIds.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "add", "object id"]
- },
- {
- "type": "action",
- "title": "# Add Vray Material Ids",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "addVrayMaterialIds.py"]
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "title": "# Set Physical DOF Depth",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "physical", "DOF ", "Depth"]
- },
- {
- "type": "action",
- "title": "# Magic Vray Proxy UI",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "magicVrayProxyUI"]
- }
- ]
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\pyblish\\lighting\\set_filename_prefix.py",
- "sourcetype": "file",
- "tags": [
- "shading",
- "lookdev",
- "assign",
- "shaders",
- "prefix",
- "filename",
- "render"
- ],
- "title": "# Set filename prefix",
- "tooltip": "Set the render file name prefix."
- },
- {
- "type": "action",
- "command": "import mayalookassigner; mayalookassigner.show()",
- "sourcetype": "python",
- "tags": ["shading", "look", "assign", "shaders", "auto"],
- "title": "Look Manager",
- "tooltip": "Open the Look Manager UI for look assignment"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\LightLinkUi.py",
- "sourcetype": "file",
- "tags": ["shading", "light", "link", "ui"],
- "title": "# Light Link UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vdviewer_ui.py",
- "sourcetype": "file",
- "tags": [
- "shading",
- "look",
- "vray",
- "displacement",
- "shaders",
- "auto"
- ],
- "title": "# VRay Displ Viewer",
- "tooltip": "Open the VRay Displacement Viewer, select and control the content of the set"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py",
- "sourcetype": "file",
- "tags": ["shading", "CLRImage", "textures", "preview"],
- "title": "# Set Texture Preview To CLRImage",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py",
- "sourcetype": "file",
- "tags": ["shading", "fix", "DefaultShaderSet", "Behavior"],
- "title": "# Fix Default Shader Set Behavior",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py",
- "sourcetype": "file",
- "tags": [
- "shading",
- "fix",
- "Selected",
- "Shapes",
- "Reference",
- "Assignments"
- ],
- "title": "# Fix Shapes Reference Assignments",
- "tooltip": "Select shapes to fix the reference assignments"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\selectLambert1Members.py",
- "sourcetype": "file",
- "tags": ["shading", "selectLambert1Members"],
- "title": "# Select Lambert1 Members",
- "tooltip": "Selects all objects which have the Lambert1 shader assigned"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\selectShapesWithoutShader.py",
- "sourcetype": "file",
- "tags": ["shading", "selectShapesWithoutShader"],
- "title": "# Select Shapes Without Shader",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py",
- "sourcetype": "file",
- "tags": ["shading", "fixRenderLayerOutAdjustmentErrors"],
- "title": "# Fix RenderLayer Out Adjustment Errors",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\fix_renderlayer_missing_node_override.py",
- "sourcetype": "file",
- "tags": [
- "shading",
- "renderlayer",
- "missing",
- "reference",
- "switch",
- "layer"
- ],
- "title": "# Fix RenderLayer Missing Referenced Nodes Overrides",
- "tooltip": ""
- },
- {
- "type": "action",
- "title": "# Image 2 Tiled EXR",
- "command": "$OPENPYPE_SCRIPTS\\shading\\open_img2exr.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "exr"]
- }
- ]
- },
- {
- "type": "menu",
- "title": "# Rendering",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\pyblish\\open_deadline_submission_settings.py",
- "sourcetype": "file",
- "tags": ["settings", "deadline", "globals", "render"],
- "title": "# DL Submission Settings UI",
- "tooltip": "Open the Deadline Submission Settings UI"
- }
- ]
- },
- {
- "type": "menu",
- "title": "Animation",
- "items": [
- {
- "type": "menu",
- "title": "# Attributes",
- "tooltip": "",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyValues.py",
- "sourcetype": "file",
- "tags": ["animation", "copy", "attributes"],
- "title": "# Copy Values",
- "tooltip": "Copy attribute values"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyInConnections.py",
- "sourcetype": "file",
- "tags": [
- "animation",
- "copy",
- "attributes",
- "connections",
- "incoming"
- ],
- "title": "# Copy In Connections",
- "tooltip": "Copy incoming connections"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyOutConnections.py",
- "sourcetype": "file",
- "tags": [
- "animation",
- "copy",
- "attributes",
- "connections",
- "out"
- ],
- "title": "# Copy Out Connections",
- "tooltip": "Copy outcoming connections"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformLocal.py",
- "sourcetype": "file",
- "tags": [
- "animation",
- "copy",
- "attributes",
- "transforms",
- "local"
- ],
- "title": "# Copy Local Transforms",
- "tooltip": "Copy local transforms"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py",
- "sourcetype": "file",
- "tags": [
- "animation",
- "copy",
- "attributes",
- "transforms",
- "matrix"
- ],
- "title": "# Copy Matrix Transforms",
- "tooltip": "Copy Matrix transforms"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformUI.py",
- "sourcetype": "file",
- "tags": [
- "animation",
- "copy",
- "attributes",
- "transforms",
- "UI"
- ],
- "title": "# Copy Transforms UI",
- "tooltip": "Open the Copy Transforms UI"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\simpleCopyUI.py",
- "sourcetype": "file",
- "tags": [
- "animation",
- "copy",
- "attributes",
- "transforms",
- "UI",
- "simple"
- ],
- "title": "# Simple Copy UI",
- "tooltip": "Open the simple Copy Transforms UI"
- }
- ]
- },
- {
- "type": "menu",
- "title": "# Optimize",
- "tooltip": "Optimization scripts",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py",
- "sourcetype": "file",
- "tags": ["animation", "hierarchy", "toggle", "freeze"],
- "title": "# Toggle Freeze Hierarchy",
- "tooltip": "Freeze and unfreeze hierarchy"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py",
- "sourcetype": "file",
- "tags": ["animation", "nucleus", "toggle", "parallel"],
- "title": "# Toggle Parallel Nucleus",
- "tooltip": "Toggle parallel nucleus"
- }
- ]
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py",
- "tags": ["animation", "bake", "selection", "worldspace.py"],
- "title": "# Bake Selected To Worldspace",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\timeStepper.py",
- "tags": ["animation", "time", "stepper"],
- "title": "# Time Stepper",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\capture_ui.py",
- "tags": [
- "animation",
- "capture",
- "ui",
- "screen",
- "movie",
- "image"
- ],
- "title": "# Capture UI",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\simplePlayblastUI.py",
- "tags": ["animation", "simple", "playblast", "ui"],
- "title": "# Simple Playblast UI",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\tweenMachineUI.py",
- "tags": ["animation", "tween", "machine"],
- "title": "# Tween Machine UI",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\selectAllAnimationCurves.py",
- "tags": ["animation", "select", "curves"],
- "title": "# Select All Animation Curves",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\pathAnimation.py",
- "tags": ["animation", "path", "along"],
- "title": "# Path Animation",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\offsetSelectedObjectsUI.py",
- "tags": ["animation", "offsetSelectedObjectsUI.py"],
- "title": "# Offset Selected Objects UI",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\key_amplifier_ui.py",
- "tags": ["animation", "key", "amplifier"],
- "title": "# Key Amplifier UI",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\anim_scene_optimizer.py",
- "tags": ["animation", "anim_scene_optimizer.py"],
- "title": "# Anim_Scene_Optimizer",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\zvParentMaster.py",
- "tags": ["animation", "zvParentMaster.py"],
- "title": "# ZV Parent Master",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\animLibrary.py",
- "tags": ["animation", "studiolibrary.py"],
- "title": "Anim Library",
- "type": "action"
- }
- ]
- },
- {
- "type": "menu",
- "title": "# Layout",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\alignDistributeUI.py",
- "sourcetype": "file",
- "tags": ["layout", "align", "Distribute", "UI"],
- "title": "# Align Distribute UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\alignSimpleUI.py",
- "sourcetype": "file",
- "tags": ["layout", "align", "UI", "Simple"],
- "title": "# Align Simple UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\center_locator.py",
- "sourcetype": "file",
- "tags": ["layout", "center", "locator"],
- "title": "# Center Locator",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\average_locator.py",
- "sourcetype": "file",
- "tags": ["layout", "average", "locator"],
- "title": "# Average Locator",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\selectWithinProximityUI.py",
- "sourcetype": "file",
- "tags": ["layout", "select", "proximity", "ui"],
- "title": "# Select Within Proximity UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\dupCurveUI.py",
- "sourcetype": "file",
- "tags": ["layout", "Duplicate", "Curve", "UI"],
- "title": "# Duplicate Curve UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\randomDeselectUI.py",
- "sourcetype": "file",
- "tags": ["layout", "random", "Deselect", "UI"],
- "title": "# Random Deselect UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\multiReferencerUI.py",
- "sourcetype": "file",
- "tags": ["layout", "multi", "reference"],
- "title": "# Multi Referencer UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\duplicateOffsetUI.py",
- "sourcetype": "file",
- "tags": ["layout", "duplicate", "offset", "UI"],
- "title": "# Duplicate Offset UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\spPaint3d.py",
- "sourcetype": "file",
- "tags": ["layout", "spPaint3d", "paint", "tool"],
- "title": "# SP Paint 3d",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\randomizeUI.py",
- "sourcetype": "file",
- "tags": ["layout", "randomize", "UI"],
- "title": "# Randomize UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\distributeWithinObjectUI.py",
- "sourcetype": "file",
- "tags": ["layout", "distribute", "ObjectUI", "within"],
- "title": "# Distribute Within Object UI",
- "tooltip": ""
- }
- ]
- },
- {
- "type": "menu",
- "title": "# Particles",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjects.py",
- "sourcetype": "file",
- "tags": ["particles", "instancerToObjects"],
- "title": "# Instancer To Objects",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsInstances.py",
- "sourcetype": "file",
- "tags": ["particles", "instancerToObjectsInstances"],
- "title": "# Instancer To Objects Instances",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsInstancesWithAnimation.py",
- "sourcetype": "file",
- "tags": [
- "particles",
- "instancerToObjectsInstancesWithAnimation"
- ],
- "title": "# Instancer To Objects Instances With Animation",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsWithAnimation.py",
- "sourcetype": "file",
- "tags": ["particles", "instancerToObjectsWithAnimation"],
- "title": "# Instancer To Objects With Animation",
- "tooltip": ""
- }
- ]
- },
- {
- "type": "menu",
- "title": "Cleanup",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\repair_faulty_containers.py",
- "sourcetype": "file",
- "tags": ["cleanup", "repair", "containers"],
- "title": "# Find and Repair Containers",
- "tooltip": ""
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeNamespaces.py",
- "sourcetype": "file",
- "tags": ["cleanup", "remove", "namespaces"],
- "title": "# Remove Namespaces",
- "tooltip": "Remove all namespaces"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\remove_user_defined_attributes.py",
- "sourcetype": "file",
- "tags": ["cleanup", "remove_user_defined_attributes"],
- "title": "# Remove User Defined Attributes",
- "tooltip": "Remove all user-defined attributes from all nodes"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeUnknownNodes.py",
- "sourcetype": "file",
- "tags": ["cleanup", "removeUnknownNodes"],
- "title": "# Remove Unknown Nodes",
- "tooltip": "Remove all unknown nodes"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeUnloadedReferences.py",
- "sourcetype": "file",
- "tags": ["cleanup", "removeUnloadedReferences"],
- "title": "# Remove Unloaded References",
- "tooltip": "Remove all unloaded references"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeReferencesFailedEdits.py",
- "sourcetype": "file",
- "tags": ["cleanup", "removeReferencesFailedEdits"],
- "title": "# Remove References Failed Edits",
- "tooltip": "Remove failed edits for all references"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\remove_unused_looks.py",
- "sourcetype": "file",
- "tags": ["cleanup", "removeUnusedLooks"],
- "title": "# Remove Unused Looks",
- "tooltip": "Remove all loaded yet unused Avalon look containers"
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\uniqifyNodeNames.py",
- "sourcetype": "file",
- "tags": ["cleanup", "uniqifyNodeNames"],
- "title": "# Uniqify Node Names",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\autoRenameFileNodes.py",
- "sourcetype": "file",
- "tags": ["cleanup", "auto", "rename", "filenodes"],
- "title": "# Auto Rename File Nodes",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\update_asset_id.py",
- "sourcetype": "file",
- "tags": ["cleanup", "update", "database", "asset", "id"],
- "title": "# Update Asset ID",
- "tooltip": "Will replace the Colorbleed ID with a new one (asset ID : Unique number)"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\ccRenameReplace.py",
- "sourcetype": "file",
- "tags": ["cleanup", "rename", "ui"],
- "title": "Renamer",
- "tooltip": "Rename UI"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\renameShapesToTransform.py",
- "sourcetype": "file",
- "tags": ["cleanup", "renameShapesToTransform"],
- "title": "# Rename Shapes To Transform",
- "tooltip": ""
- }
- ]
- }
-]
diff --git a/openpype/hosts/maya/api/menu_backup.json b/openpype/hosts/maya/api/menu_backup.json
deleted file mode 100644
index e2a558aedc..0000000000
--- a/openpype/hosts/maya/api/menu_backup.json
+++ /dev/null
@@ -1,1567 +0,0 @@
-[
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\save_scene_incremental.py",
- "sourcetype": "file",
- "title": "Version Up",
- "tooltip": "Incremental save with a specific format"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\show_current_scene_in_explorer.py",
- "sourcetype": "file",
- "title": "Explore current scene..",
- "tooltip": "Show current scene in Explorer"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\avalon\\launch_manager.py",
- "sourcetype": "file",
- "title": "Project Manager",
- "tooltip": "Add assets to the project"
- },
- {
- "type": "separator"
- },
- {
- "type": "menu",
- "title": "Modeling",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\duplicate_normalized.py",
- "sourcetype": "file",
- "tags": ["modeling", "duplicate", "normalized"],
- "title": "Duplicate Normalized",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\transferUVs.py",
- "sourcetype": "file",
- "tags": ["modeling", "transfer", "uv"],
- "title": "Transfer UVs",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\mirrorSymmetry.py",
- "sourcetype": "file",
- "tags": ["modeling", "mirror", "symmetry"],
- "title": "Mirror Symmetry",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\selectOutlineUI.py",
- "sourcetype": "file",
- "tags": ["modeling", "select", "outline", "ui"],
- "title": "Select Outline UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\polyDeleteOtherUVSets.py",
- "sourcetype": "file",
- "tags": ["modeling", "polygon", "uvset", "delete"],
- "title": "Polygon Delete Other UV Sets",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\polyCombineQuick.py",
- "sourcetype": "file",
- "tags": ["modeling", "combine", "polygon", "quick"],
- "title": "Polygon Combine Quick",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\separateMeshPerShader.py",
- "sourcetype": "file",
- "tags": ["modeling", "separateMeshPerShader"],
- "title": "Separate Mesh Per Shader",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\polyDetachSeparate.py",
- "sourcetype": "file",
- "tags": ["modeling", "poly", "detach", "separate"],
- "title": "Polygon Detach and Separate",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\polyRelaxVerts.py",
- "sourcetype": "file",
- "tags": ["modeling", "relax", "verts"],
- "title": "Polygon Relax Vertices",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py",
- "sourcetype": "file",
- "tags": ["modeling", "select", "nth", "edge", "ui"],
- "title": "Select Every Nth Edge"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\modeling\\djPFXUVs.py",
- "sourcetype": "file",
- "tags": ["modeling", "djPFX", "UVs"],
- "title": "dj PFX UVs",
- "tooltip": ""
- }
- ]
- },
- {
- "type": "menu",
- "title": "Rigging",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\addCurveBetween.py",
- "sourcetype": "file",
- "tags": ["rigging", "addCurveBetween", "file"],
- "title": "Add Curve Between"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\averageSkinWeights.py",
- "sourcetype": "file",
- "tags": ["rigging", "average", "skin weights", "file"],
- "title": "Average Skin Weights"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\cbSmoothSkinWeightUI.py",
- "sourcetype": "file",
- "tags": ["rigging", "cbSmoothSkinWeightUI", "file"],
- "title": "CB Smooth Skin Weight UI"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\channelBoxManagerUI.py",
- "sourcetype": "file",
- "tags": ["rigging", "channelBoxManagerUI", "file"],
- "title": "Channel Box Manager UI"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\characterAutorigger.py",
- "sourcetype": "file",
- "tags": ["rigging", "characterAutorigger", "file"],
- "title": "Character Auto Rigger"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\connectUI.py",
- "sourcetype": "file",
- "tags": ["rigging", "connectUI", "file"],
- "title": "Connect UI"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\copySkinWeightsLocal.py",
- "sourcetype": "file",
- "tags": ["rigging", "copySkinWeightsLocal", "file"],
- "title": "Copy Skin Weights Local"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\createCenterLocator.py",
- "sourcetype": "file",
- "tags": ["rigging", "createCenterLocator", "file"],
- "title": "Create Center Locator"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\freezeTransformToGroup.py",
- "sourcetype": "file",
- "tags": ["rigging", "freezeTransformToGroup", "file"],
- "title": "Freeze Transform To Group"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\groupSelected.py",
- "sourcetype": "file",
- "tags": ["rigging", "groupSelected", "file"],
- "title": "Group Selected"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\ikHandlePoleVectorLocator.py",
- "sourcetype": "file",
- "tags": ["rigging", "ikHandlePoleVectorLocator", "file"],
- "title": "IK Handle Pole Vector Locator"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\jointOrientUI.py",
- "sourcetype": "file",
- "tags": ["rigging", "jointOrientUI", "file"],
- "title": "Joint Orient UI"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\jointsOnCurve.py",
- "sourcetype": "file",
- "tags": ["rigging", "jointsOnCurve", "file"],
- "title": "Joints On Curve"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py",
- "sourcetype": "file",
- "tags": ["rigging", "resetBindSelectedSkinJoints", "file"],
- "title": "Reset Bind Selected Skin Joints"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py",
- "sourcetype": "file",
- "tags": [
- "rigging",
- "selectSkinclusterJointsFromSelectedComponents",
- "file"
- ],
- "title": "Select Skincluster Joints From Selected Components"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py",
- "sourcetype": "file",
- "tags": [
- "rigging",
- "selectSkinclusterJointsFromSelectedMesh",
- "file"
- ],
- "title": "Select Skincluster Joints From Selected Mesh"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\setJointLabels.py",
- "sourcetype": "file",
- "tags": ["rigging", "setJointLabels", "file"],
- "title": "Set Joint Labels"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py",
- "sourcetype": "file",
- "tags": [
- "rigging",
- "setJointOrientationFromCurrentRotation",
- "file"
- ],
- "title": "Set Joint Orientation From Current Rotation"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py",
- "sourcetype": "file",
- "tags": ["rigging", "setSelectedJointsOrientationZero", "file"],
- "title": "Set Selected Joints Orientation Zero"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\mirrorCurveShape.py",
- "sourcetype": "file",
- "tags": ["rigging", "mirrorCurveShape", "file"],
- "title": "Mirror Curve Shape"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\setRotationOrderUI.py",
- "sourcetype": "file",
- "tags": ["rigging", "setRotationOrderUI", "file"],
- "title": "Set Rotation Order UI"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\paintItNowUI.py",
- "sourcetype": "file",
- "tags": ["rigging", "paintItNowUI", "file"],
- "title": "Paint It Now UI"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\parentScaleConstraint.py",
- "sourcetype": "file",
- "tags": ["rigging", "parentScaleConstraint", "file"],
- "title": "Parent Scale Constraint"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\quickSetWeightsUI.py",
- "sourcetype": "file",
- "tags": ["rigging", "quickSetWeightsUI", "file"],
- "title": "Quick Set Weights UI"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\rapidRig.py",
- "sourcetype": "file",
- "tags": ["rigging", "rapidRig", "file"],
- "title": "Rapid Rig"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\regenerate_blendshape_targets.py",
- "sourcetype": "file",
- "tags": ["rigging", "regenerate_blendshape_targets", "file"],
- "title": "Regenerate Blendshape Targets"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\removeRotationAxis.py",
- "sourcetype": "file",
- "tags": ["rigging", "removeRotationAxis", "file"],
- "title": "Remove Rotation Axis"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\resetBindSelectedMeshes.py",
- "sourcetype": "file",
- "tags": ["rigging", "resetBindSelectedMeshes", "file"],
- "title": "Reset Bind Selected Meshes"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\simpleControllerOnSelection.py",
- "sourcetype": "file",
- "tags": ["rigging", "simpleControllerOnSelection", "file"],
- "title": "Simple Controller On Selection"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\simpleControllerOnSelectionHierarchy.py",
- "sourcetype": "file",
- "tags": [
- "rigging",
- "simpleControllerOnSelectionHierarchy",
- "file"
- ],
- "title": "Simple Controller On Selection Hierarchy"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\superRelativeCluster.py",
- "sourcetype": "file",
- "tags": ["rigging", "superRelativeCluster", "file"],
- "title": "Super Relative Cluster"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\tfSmoothSkinWeight.py",
- "sourcetype": "file",
- "tags": ["rigging", "tfSmoothSkinWeight", "file"],
- "title": "TF Smooth Skin Weight"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\toggleIntermediates.py",
- "sourcetype": "file",
- "tags": ["rigging", "toggleIntermediates", "file"],
- "title": "Toggle Intermediates"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\toggleSegmentScaleCompensate.py",
- "sourcetype": "file",
- "tags": ["rigging", "toggleSegmentScaleCompensate", "file"],
- "title": "Toggle Segment Scale Compensate"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\rigging\\toggleSkinclusterDeformNormals.py",
- "sourcetype": "file",
- "tags": ["rigging", "toggleSkinclusterDeformNormals", "file"],
- "title": "Toggle Skincluster Deform Normals"
- }
- ]
- },
- {
- "type": "menu",
- "title": "Shading",
- "items": [
- {
- "type": "menu",
- "title": "VRay",
- "items": [
- {
- "type": "action",
- "title": "Import Proxies",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayImportProxies.py",
- "sourcetype": "file",
- "tags": ["shading", "vray", "import", "proxies"],
- "tooltip": ""
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "title": "Select All GES",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectAllGES.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "select All GES"]
- },
- {
- "type": "action",
- "title": "Select All GES Under Selection",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "select", "all", "GES"]
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "title": "Selection To VRay Mesh",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "selection", "vraymesh"]
- },
- {
- "type": "action",
- "title": "Add VRay Round Edges Attribute",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "round edges", "attribute"]
- },
- {
- "type": "action",
- "title": "Add Gamma",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayAddGamma.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "add gamma"]
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py",
- "sourcetype": "file",
- "title": "Select Unconnected Shader Materials",
- "tags": [
- "shading",
- "vray",
- "select",
- "vraymesh",
- "materials",
- "unconnected shader slots"
- ],
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py",
- "sourcetype": "file",
- "title": "Merge Similar VRay Mesh Materials",
- "tags": [
- "shading",
- "vray",
- "Merge",
- "VRayMesh",
- "Materials"
- ],
- "tooltip": ""
- },
- {
- "type": "action",
- "title": "Create Two Sided Material",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py",
- "sourcetype": "file",
- "tooltip": "Creates two sided material for selected material and renames it",
- "tags": ["shading", "vray", "two sided", "material"]
- },
- {
- "type": "action",
- "title": "Create Two Sided Material For Selected",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py",
- "sourcetype": "file",
- "tooltip": "Select material to create a two sided version from it",
- "tags": [
- "shading",
- "vray",
- "Create2SidedMtlForSelectedMtl.py"
- ]
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "title": "Add OpenSubdiv Attribute",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": [
- "shading",
- "vray",
- "add",
- "open subdiv",
- "attribute"
- ]
- },
- {
- "type": "action",
- "title": "Remove OpenSubdiv Attribute",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": [
- "shading",
- "vray",
- "remove",
- "opensubdiv",
- "attributee"
- ]
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "title": "Add Subdivision Attribute",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": [
- "shading",
- "vray",
- "addVraySubdivisionAttribute"
- ]
- },
- {
- "type": "action",
- "title": "Remove Subdivision Attribute.py",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": [
- "shading",
- "vray",
- "remove",
- "subdivision",
- "attribute"
- ]
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "title": "Add Vray Object Ids",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayObjectIds.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "add", "object id"]
- },
- {
- "type": "action",
- "title": "Add Vray Material Ids",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "addVrayMaterialIds.py"]
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "title": "Set Physical DOF Depth",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "physical", "DOF ", "Depth"]
- },
- {
- "type": "action",
- "title": "Magic Vray Proxy UI",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "magicVrayProxyUI"]
- }
- ]
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\pyblish\\lighting\\set_filename_prefix.py",
- "sourcetype": "file",
- "tags": [
- "shading",
- "lookdev",
- "assign",
- "shaders",
- "prefix",
- "filename",
- "render"
- ],
- "title": "Set filename prefix",
- "tooltip": "Set the render file name prefix."
- },
- {
- "type": "action",
- "command": "import mayalookassigner; mayalookassigner.show()",
- "sourcetype": "python",
- "tags": ["shading", "look", "assign", "shaders", "auto"],
- "title": "Look Manager",
- "tooltip": "Open the Look Manager UI for look assignment"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\LightLinkUi.py",
- "sourcetype": "file",
- "tags": ["shading", "light", "link", "ui"],
- "title": "Light Link UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\vdviewer_ui.py",
- "sourcetype": "file",
- "tags": [
- "shading",
- "look",
- "vray",
- "displacement",
- "shaders",
- "auto"
- ],
- "title": "VRay Displ Viewer",
- "tooltip": "Open the VRay Displacement Viewer, select and control the content of the set"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py",
- "sourcetype": "file",
- "tags": ["shading", "CLRImage", "textures", "preview"],
- "title": "Set Texture Preview To CLRImage",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py",
- "sourcetype": "file",
- "tags": ["shading", "fix", "DefaultShaderSet", "Behavior"],
- "title": "Fix Default Shader Set Behavior",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py",
- "sourcetype": "file",
- "tags": [
- "shading",
- "fix",
- "Selected",
- "Shapes",
- "Reference",
- "Assignments"
- ],
- "title": "Fix Shapes Reference Assignments",
- "tooltip": "Select shapes to fix the reference assignments"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\selectLambert1Members.py",
- "sourcetype": "file",
- "tags": ["shading", "selectLambert1Members"],
- "title": "Select Lambert1 Members",
- "tooltip": "Selects all objects which have the Lambert1 shader assigned"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\selectShapesWithoutShader.py",
- "sourcetype": "file",
- "tags": ["shading", "selectShapesWithoutShader"],
- "title": "Select Shapes Without Shader",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py",
- "sourcetype": "file",
- "tags": ["shading", "fixRenderLayerOutAdjustmentErrors"],
- "title": "Fix RenderLayer Out Adjustment Errors",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\shading\\fix_renderlayer_missing_node_override.py",
- "sourcetype": "file",
- "tags": [
- "shading",
- "renderlayer",
- "missing",
- "reference",
- "switch",
- "layer"
- ],
- "title": "Fix RenderLayer Missing Referenced Nodes Overrides",
- "tooltip": ""
- },
- {
- "type": "action",
- "title": "Image 2 Tiled EXR",
- "command": "$OPENPYPE_SCRIPTS\\shading\\open_img2exr.py",
- "sourcetype": "file",
- "tooltip": "",
- "tags": ["shading", "vray", "exr"]
- }
- ]
- },
- {
- "type": "menu",
- "title": "Rendering",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\pyblish\\open_deadline_submission_settings.py",
- "sourcetype": "file",
- "tags": ["settings", "deadline", "globals", "render"],
- "title": "DL Submission Settings UI",
- "tooltip": "Open the Deadline Submission Settings UI"
- }
- ]
- },
- {
- "type": "menu",
- "title": "Animation",
- "items": [
- {
- "type": "menu",
- "title": "Attributes",
- "tooltip": "",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyValues.py",
- "sourcetype": "file",
- "tags": ["animation", "copy", "attributes"],
- "title": "Copy Values",
- "tooltip": "Copy attribute values"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyInConnections.py",
- "sourcetype": "file",
- "tags": [
- "animation",
- "copy",
- "attributes",
- "connections",
- "incoming"
- ],
- "title": "Copy In Connections",
- "tooltip": "Copy incoming connections"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyOutConnections.py",
- "sourcetype": "file",
- "tags": [
- "animation",
- "copy",
- "attributes",
- "connections",
- "out"
- ],
- "title": "Copy Out Connections",
- "tooltip": "Copy outcoming connections"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformLocal.py",
- "sourcetype": "file",
- "tags": [
- "animation",
- "copy",
- "attributes",
- "transforms",
- "local"
- ],
- "title": "Copy Local Transforms",
- "tooltip": "Copy local transforms"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py",
- "sourcetype": "file",
- "tags": [
- "animation",
- "copy",
- "attributes",
- "transforms",
- "matrix"
- ],
- "title": "Copy Matrix Transforms",
- "tooltip": "Copy Matrix transforms"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformUI.py",
- "sourcetype": "file",
- "tags": [
- "animation",
- "copy",
- "attributes",
- "transforms",
- "UI"
- ],
- "title": "Copy Transforms UI",
- "tooltip": "Open the Copy Transforms UI"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\simpleCopyUI.py",
- "sourcetype": "file",
- "tags": [
- "animation",
- "copy",
- "attributes",
- "transforms",
- "UI",
- "simple"
- ],
- "title": "Simple Copy UI",
- "tooltip": "Open the simple Copy Transforms UI"
- }
- ]
- },
- {
- "type": "menu",
- "title": "Optimize",
- "tooltip": "Optimization scripts",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py",
- "sourcetype": "file",
- "tags": ["animation", "hierarchy", "toggle", "freeze"],
- "title": "Toggle Freeze Hierarchy",
- "tooltip": "Freeze and unfreeze hierarchy"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py",
- "sourcetype": "file",
- "tags": ["animation", "nucleus", "toggle", "parallel"],
- "title": "Toggle Parallel Nucleus",
- "tooltip": "Toggle parallel nucleus"
- }
- ]
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py",
- "tags": ["animation", "bake", "selection", "worldspace.py"],
- "title": "Bake Selected To Worldspace",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\timeStepper.py",
- "tags": ["animation", "time", "stepper"],
- "title": "Time Stepper",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\capture_ui.py",
- "tags": [
- "animation",
- "capture",
- "ui",
- "screen",
- "movie",
- "image"
- ],
- "title": "Capture UI",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\simplePlayblastUI.py",
- "tags": ["animation", "simple", "playblast", "ui"],
- "title": "Simple Playblast UI",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\tweenMachineUI.py",
- "tags": ["animation", "tween", "machine"],
- "title": "Tween Machine UI",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\selectAllAnimationCurves.py",
- "tags": ["animation", "select", "curves"],
- "title": "Select All Animation Curves",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\pathAnimation.py",
- "tags": ["animation", "path", "along"],
- "title": "Path Animation",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\offsetSelectedObjectsUI.py",
- "tags": ["animation", "offsetSelectedObjectsUI.py"],
- "title": "Offset Selected Objects UI",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\key_amplifier_ui.py",
- "tags": ["animation", "key", "amplifier"],
- "title": "Key Amplifier UI",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\anim_scene_optimizer.py",
- "tags": ["animation", "anim_scene_optimizer.py"],
- "title": "Anim_Scene_Optimizer",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\zvParentMaster.py",
- "tags": ["animation", "zvParentMaster.py"],
- "title": "ZV Parent Master",
- "type": "action"
- },
- {
- "sourcetype": "file",
- "command": "$OPENPYPE_SCRIPTS\\animation\\poseLibrary.py",
- "tags": ["animation", "poseLibrary.py"],
- "title": "Pose Library",
- "type": "action"
- }
- ]
- },
- {
- "type": "menu",
- "title": "Layout",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\alignDistributeUI.py",
- "sourcetype": "file",
- "tags": ["layout", "align", "Distribute", "UI"],
- "title": "Align Distribute UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\alignSimpleUI.py",
- "sourcetype": "file",
- "tags": ["layout", "align", "UI", "Simple"],
- "title": "Align Simple UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\center_locator.py",
- "sourcetype": "file",
- "tags": ["layout", "center", "locator"],
- "title": "Center Locator",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\average_locator.py",
- "sourcetype": "file",
- "tags": ["layout", "average", "locator"],
- "title": "Average Locator",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\selectWithinProximityUI.py",
- "sourcetype": "file",
- "tags": ["layout", "select", "proximity", "ui"],
- "title": "Select Within Proximity UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\dupCurveUI.py",
- "sourcetype": "file",
- "tags": ["layout", "Duplicate", "Curve", "UI"],
- "title": "Duplicate Curve UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\randomDeselectUI.py",
- "sourcetype": "file",
- "tags": ["layout", "random", "Deselect", "UI"],
- "title": "Random Deselect UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\multiReferencerUI.py",
- "sourcetype": "file",
- "tags": ["layout", "multi", "reference"],
- "title": "Multi Referencer UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\duplicateOffsetUI.py",
- "sourcetype": "file",
- "tags": ["layout", "duplicate", "offset", "UI"],
- "title": "Duplicate Offset UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\spPaint3d.py",
- "sourcetype": "file",
- "tags": ["layout", "spPaint3d", "paint", "tool"],
- "title": "SP Paint 3d",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\randomizeUI.py",
- "sourcetype": "file",
- "tags": ["layout", "randomize", "UI"],
- "title": "Randomize UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\layout\\distributeWithinObjectUI.py",
- "sourcetype": "file",
- "tags": ["layout", "distribute", "ObjectUI", "within"],
- "title": "Distribute Within Object UI",
- "tooltip": ""
- }
- ]
- },
- {
- "type": "menu",
- "title": "Particles",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjects.py",
- "sourcetype": "file",
- "tags": ["particles", "instancerToObjects"],
- "title": "Instancer To Objects",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsInstances.py",
- "sourcetype": "file",
- "tags": ["particles", "instancerToObjectsInstances"],
- "title": "Instancer To Objects Instances",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\objectsToParticlesAndInstancerCleanSource.py",
- "sourcetype": "file",
- "tags": [
- "particles",
- "objects",
- "Particles",
- "Instancer",
- "Clean",
- "Source"
- ],
- "title": "Objects To Particles & Instancer - Clean Source",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\particleComponentsToLocators.py",
- "sourcetype": "file",
- "tags": ["particles", "components", "locators"],
- "title": "Particle Components To Locators",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\objectsToParticlesAndInstancer.py",
- "sourcetype": "file",
- "tags": ["particles", "objects", "particles", "instancer"],
- "title": "Objects To Particles And Instancer",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\spawnParticlesOnMesh.py",
- "sourcetype": "file",
- "tags": ["particles", "spawn", "on", "mesh"],
- "title": "Spawn Particles On Mesh",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsInstancesWithAnimation.py",
- "sourcetype": "file",
- "tags": [
- "particles",
- "instancerToObjectsInstancesWithAnimation"
- ],
- "title": "Instancer To Objects Instances With Animation",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\objectsToParticles.py",
- "sourcetype": "file",
- "tags": ["particles", "objectsToParticles"],
- "title": "Objects To Particles",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\add_particle_cacheFile_attrs.py",
- "sourcetype": "file",
- "tags": ["particles", "add_particle_cacheFile_attrs"],
- "title": "Add Particle CacheFile Attributes",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\mergeParticleSystems.py",
- "sourcetype": "file",
- "tags": ["particles", "mergeParticleSystems"],
- "title": "Merge Particle Systems",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\particlesToLocators.py",
- "sourcetype": "file",
- "tags": ["particles", "particlesToLocators"],
- "title": "Particles To Locators",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsWithAnimation.py",
- "sourcetype": "file",
- "tags": ["particles", "instancerToObjectsWithAnimation"],
- "title": "Instancer To Objects With Animation",
- "tooltip": ""
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\mayaReplicateHoudiniTool.py",
- "sourcetype": "file",
- "tags": [
- "particles",
- "houdini",
- "houdiniTool",
- "houdiniEngine"
- ],
- "title": "Replicate Houdini Tool",
- "tooltip": ""
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\clearInitialState.py",
- "sourcetype": "file",
- "tags": ["particles", "clearInitialState"],
- "title": "Clear Initial State",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\particles\\killSelectedParticles.py",
- "sourcetype": "file",
- "tags": ["particles", "killSelectedParticles"],
- "title": "Kill Selected Particles",
- "tooltip": ""
- }
- ]
- },
- {
- "type": "menu",
- "title": "Yeti",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\yeti\\yeti_rig_manager.py",
- "sourcetype": "file",
- "tags": ["yeti", "rig", "fur", "manager"],
- "title": "Open Yeti Rig Manager",
- "tooltip": ""
- }
- ]
- },
- {
- "type": "menu",
- "title": "Cleanup",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\repair_faulty_containers.py",
- "sourcetype": "file",
- "tags": ["cleanup", "repair", "containers"],
- "title": "Find and Repair Containers",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\selectByType.py",
- "sourcetype": "file",
- "tags": ["cleanup", "selectByType"],
- "title": "Select By Type",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\selectIntermediateObjects.py",
- "sourcetype": "file",
- "tags": ["cleanup", "selectIntermediateObjects"],
- "title": "Select Intermediate Objects",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\selectNonUniqueNames.py",
- "sourcetype": "file",
- "tags": ["cleanup", "select", "non unique", "names"],
- "title": "Select Non Unique Names",
- "tooltip": ""
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeNamespaces.py",
- "sourcetype": "file",
- "tags": ["cleanup", "remove", "namespaces"],
- "title": "Remove Namespaces",
- "tooltip": "Remove all namespaces"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\remove_user_defined_attributes.py",
- "sourcetype": "file",
- "tags": ["cleanup", "remove_user_defined_attributes"],
- "title": "Remove User Defined Attributes",
- "tooltip": "Remove all user-defined attributes from all nodes"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeUnknownNodes.py",
- "sourcetype": "file",
- "tags": ["cleanup", "removeUnknownNodes"],
- "title": "Remove Unknown Nodes",
- "tooltip": "Remove all unknown nodes"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeUnloadedReferences.py",
- "sourcetype": "file",
- "tags": ["cleanup", "removeUnloadedReferences"],
- "title": "Remove Unloaded References",
- "tooltip": "Remove all unloaded references"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeReferencesFailedEdits.py",
- "sourcetype": "file",
- "tags": ["cleanup", "removeReferencesFailedEdits"],
- "title": "Remove References Failed Edits",
- "tooltip": "Remove failed edits for all references"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\remove_unused_looks.py",
- "sourcetype": "file",
- "tags": ["cleanup", "removeUnusedLooks"],
- "title": "Remove Unused Looks",
- "tooltip": "Remove all loaded yet unused Avalon look containers"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\deleteGhostIntermediateObjects.py",
- "sourcetype": "file",
- "tags": ["cleanup", "deleteGhostIntermediateObjects"],
- "title": "Delete Ghost Intermediate Objects",
- "tooltip": ""
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\resetViewportCache.py",
- "sourcetype": "file",
- "tags": ["cleanup", "reset", "viewport", "cache"],
- "title": "Reset Viewport Cache",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\uniqifyNodeNames.py",
- "sourcetype": "file",
- "tags": ["cleanup", "uniqifyNodeNames"],
- "title": "Uniqify Node Names",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\autoRenameFileNodes.py",
- "sourcetype": "file",
- "tags": ["cleanup", "auto", "rename", "filenodes"],
- "title": "Auto Rename File Nodes",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\update_asset_id.py",
- "sourcetype": "file",
- "tags": ["cleanup", "update", "database", "asset", "id"],
- "title": "Update Asset ID",
- "tooltip": "Will replace the Colorbleed ID with a new one (asset ID : Unique number)"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\colorbleedRename.py",
- "sourcetype": "file",
- "tags": ["cleanup", "rename", "ui"],
- "title": "Colorbleed Renamer",
- "tooltip": "Colorbleed Rename UI"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\renameShapesToTransform.py",
- "sourcetype": "file",
- "tags": ["cleanup", "renameShapesToTransform"],
- "title": "Rename Shapes To Transform",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\reorderUI.py",
- "sourcetype": "file",
- "tags": ["cleanup", "reorderUI"],
- "title": "Reorder UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\cleanup\\pastedCleaner.py",
- "sourcetype": "file",
- "tags": ["cleanup", "pastedCleaner"],
- "title": "Pasted Cleaner",
- "tooltip": ""
- }
- ]
- },
- {
- "type": "menu",
- "title": "Others",
- "items": [
- {
- "type": "menu",
- "sourcetype": "file",
- "title": "Yeti",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\yeti\\cache_selected_yeti_nodes.py",
- "sourcetype": "file",
- "tags": ["others", "yeti", "cache", "selected"],
- "title": "Cache Selected Yeti Nodes",
- "tooltip": ""
- }
- ]
- },
- {
- "type": "menu",
- "title": "Hair",
- "tooltip": "",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\hair\\recolorHairCurrentCurve",
- "sourcetype": "file",
- "tags": ["others", "selectSoftSelection"],
- "title": "Select Soft Selection",
- "tooltip": ""
- }
- ]
- },
- {
- "type": "menu",
- "command": "$OPENPYPE_SCRIPTS\\others\\display",
- "sourcetype": "file",
- "tags": ["others", "display"],
- "title": "Display",
- "items": [
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\display\\wireframeSelectedObjects.py",
- "sourcetype": "file",
- "tags": ["others", "wireframe", "selected", "objects"],
- "title": "Wireframe Selected Objects",
- "tooltip": ""
- }
- ]
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\archiveSceneUI.py",
- "sourcetype": "file",
- "tags": ["others", "archiveSceneUI"],
- "title": "Archive Scene UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\getSimilarMeshes.py",
- "sourcetype": "file",
- "tags": ["others", "getSimilarMeshes"],
- "title": "Get Similar Meshes",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\createBoundingBoxEachSelected.py",
- "sourcetype": "file",
- "tags": ["others", "createBoundingBoxEachSelected"],
- "title": "Create BoundingBox Each Selected",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\curveFromPositionEveryFrame.py",
- "sourcetype": "file",
- "tags": ["others", "curveFromPositionEveryFrame"],
- "title": "Curve From Position",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\instanceLeafSmartTransform.py",
- "sourcetype": "file",
- "tags": ["others", "instance", "leaf", "smart", "transform"],
- "title": "Instance Leaf Smart Transform",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\instanceSmartTransform.py",
- "sourcetype": "file",
- "tags": ["others", "instance", "smart", "transform"],
- "title": "Instance Smart Transform",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\randomizeUVShellsSelectedObjects.py",
- "sourcetype": "file",
- "tags": ["others", "randomizeUVShellsSelectedObjects"],
- "title": "Randomize UV Shells",
- "tooltip": "Select objects before running action"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\centerPivotGroup.py",
- "sourcetype": "file",
- "tags": ["others", "centerPivotGroup"],
- "title": "Center Pivot Group",
- "tooltip": ""
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\locatorsOnSelectedFaces.py",
- "sourcetype": "file",
- "tags": ["others", "locatorsOnSelectedFaces"],
- "title": "Locators On Selected Faces",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\locatorsOnEdgeSelectionPrompt.py",
- "sourcetype": "file",
- "tags": ["others", "locatorsOnEdgeSelectionPrompt"],
- "title": "Locators On Edge Selection Prompt",
- "tooltip": ""
- },
- {
- "type": "separator"
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\copyDeformers.py",
- "sourcetype": "file",
- "tags": ["others", "copyDeformers"],
- "title": "Copy Deformers",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\selectInReferenceEditor.py",
- "sourcetype": "file",
- "tags": ["others", "selectInReferenceEditor"],
- "title": "Select In Reference Editor",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\selectConstrainingObject.py",
- "sourcetype": "file",
- "tags": ["others", "selectConstrainingObject"],
- "title": "Select Constraining Object",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\deformerSetRelationsUI.py",
- "sourcetype": "file",
- "tags": ["others", "deformerSetRelationsUI"],
- "title": "Deformer Set Relations UI",
- "tooltip": ""
- },
- {
- "type": "action",
- "command": "$OPENPYPE_SCRIPTS\\others\\recreateBaseNodesForAllLatticeNodes.py",
- "sourcetype": "file",
- "tags": ["others", "recreate", "base", "nodes", "lattice"],
- "title": "Recreate Base Nodes For Lattice Nodes",
- "tooltip": ""
- }
- ]
- }
-]
diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py
index 14e8f4eb45..1b3bb9feb3 100644
--- a/openpype/hosts/maya/api/pipeline.py
+++ b/openpype/hosts/maya/api/pipeline.py
@@ -184,76 +184,6 @@ def uninstall():
menu.uninstall()
-def lock():
- """Lock scene
-
- Add an invisible node to your Maya scene with the name of the
- current file, indicating that this file is "locked" and cannot
- be modified any further.
-
- """
-
- if not cmds.objExists("lock"):
- with lib.maintained_selection():
- cmds.createNode("objectSet", name="lock")
- cmds.addAttr("lock", ln="basename", dataType="string")
-
- # Permanently hide from outliner
- cmds.setAttr("lock.verticesOnlySet", True)
-
- fname = cmds.file(query=True, sceneName=True)
- basename = os.path.basename(fname)
- cmds.setAttr("lock.basename", basename, type="string")
-
-
-def unlock():
- """Permanently unlock a locked scene
-
- Doesn't throw an error if scene is already unlocked.
-
- """
-
- try:
- cmds.delete("lock")
- except ValueError:
- pass
-
-
-def is_locked():
- """Query whether current scene is locked"""
- fname = cmds.file(query=True, sceneName=True)
- basename = os.path.basename(fname)
-
- if self._ignore_lock:
- return False
-
- try:
- return cmds.getAttr("lock.basename") == basename
- except ValueError:
- return False
-
-
-@contextlib.contextmanager
-def lock_ignored():
- """Context manager for temporarily ignoring the lock of a scene
-
- The purpose of this function is to enable locking a scene and
- saving it with the lock still in place.
-
- Example:
- >>> with lock_ignored():
- ... pass # Do things without lock
-
- """
-
- self._ignore_lock = True
-
- try:
- yield
- finally:
- self._ignore_lock = False
-
-
def parse_container(container):
"""Return the container node's full container data.
diff --git a/openpype/modules/base.py b/openpype/modules/base.py
index d566692439..437f5efdbc 100644
--- a/openpype/modules/base.py
+++ b/openpype/modules/base.py
@@ -43,6 +43,7 @@ DEFAULT_OPENPYPE_MODULES = (
"standalonepublish_action",
"job_queue",
"timers_manager",
+ "sync_server",
)
diff --git a/openpype/modules/slack/plugins/publish/collect_slack_family.py b/openpype/modules/slack/plugins/publish/collect_slack_family.py
index 2110c0703b..6c965b04cd 100644
--- a/openpype/modules/slack/plugins/publish/collect_slack_family.py
+++ b/openpype/modules/slack/plugins/publish/collect_slack_family.py
@@ -20,18 +20,23 @@ class CollectSlackFamilies(pyblish.api.InstancePlugin):
def process(self, instance):
task_name = io.Session.get("AVALON_TASK")
family = self.main_family_from_instance(instance)
-
key_values = {
"families": family,
"tasks": task_name,
"hosts": instance.data["anatomyData"]["app"],
+ "subsets": instance.data["subset"]
}
profile = filter_profiles(self.profiles, key_values,
logger=self.log)
+ if not profile:
+ self.log.info("No profile found, notification won't be send")
+ return
+
# make slack publishable
if profile:
+ self.log.info("Found profile: {}".format(profile))
if instance.data.get('families'):
instance.data['families'].append('slack')
else:
diff --git a/openpype/modules/default_modules/sync_server/README.md b/openpype/modules/sync_server/README.md
similarity index 100%
rename from openpype/modules/default_modules/sync_server/README.md
rename to openpype/modules/sync_server/README.md
diff --git a/openpype/modules/default_modules/sync_server/__init__.py b/openpype/modules/sync_server/__init__.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/__init__.py
rename to openpype/modules/sync_server/__init__.py
diff --git a/openpype/modules/default_modules/sync_server/providers/__init__.py b/openpype/modules/sync_server/providers/__init__.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/providers/__init__.py
rename to openpype/modules/sync_server/providers/__init__.py
diff --git a/openpype/modules/default_modules/sync_server/providers/abstract_provider.py b/openpype/modules/sync_server/providers/abstract_provider.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/providers/abstract_provider.py
rename to openpype/modules/sync_server/providers/abstract_provider.py
diff --git a/openpype/modules/default_modules/sync_server/providers/dropbox.py b/openpype/modules/sync_server/providers/dropbox.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/providers/dropbox.py
rename to openpype/modules/sync_server/providers/dropbox.py
diff --git a/openpype/modules/default_modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/providers/gdrive.py
rename to openpype/modules/sync_server/providers/gdrive.py
diff --git a/openpype/modules/default_modules/sync_server/providers/lib.py b/openpype/modules/sync_server/providers/lib.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/providers/lib.py
rename to openpype/modules/sync_server/providers/lib.py
diff --git a/openpype/modules/default_modules/sync_server/providers/local_drive.py b/openpype/modules/sync_server/providers/local_drive.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/providers/local_drive.py
rename to openpype/modules/sync_server/providers/local_drive.py
diff --git a/openpype/modules/default_modules/sync_server/providers/resources/dropbox.png b/openpype/modules/sync_server/providers/resources/dropbox.png
similarity index 100%
rename from openpype/modules/default_modules/sync_server/providers/resources/dropbox.png
rename to openpype/modules/sync_server/providers/resources/dropbox.png
diff --git a/openpype/modules/default_modules/sync_server/providers/resources/folder.png b/openpype/modules/sync_server/providers/resources/folder.png
similarity index 100%
rename from openpype/modules/default_modules/sync_server/providers/resources/folder.png
rename to openpype/modules/sync_server/providers/resources/folder.png
diff --git a/openpype/modules/default_modules/sync_server/providers/resources/gdrive.png b/openpype/modules/sync_server/providers/resources/gdrive.png
similarity index 100%
rename from openpype/modules/default_modules/sync_server/providers/resources/gdrive.png
rename to openpype/modules/sync_server/providers/resources/gdrive.png
diff --git a/openpype/modules/default_modules/sync_server/providers/resources/local_drive.png b/openpype/modules/sync_server/providers/resources/local_drive.png
similarity index 100%
rename from openpype/modules/default_modules/sync_server/providers/resources/local_drive.png
rename to openpype/modules/sync_server/providers/resources/local_drive.png
diff --git a/openpype/modules/default_modules/sync_server/providers/resources/sftp.png b/openpype/modules/sync_server/providers/resources/sftp.png
similarity index 100%
rename from openpype/modules/default_modules/sync_server/providers/resources/sftp.png
rename to openpype/modules/sync_server/providers/resources/sftp.png
diff --git a/openpype/modules/default_modules/sync_server/providers/resources/studio.png b/openpype/modules/sync_server/providers/resources/studio.png
similarity index 100%
rename from openpype/modules/default_modules/sync_server/providers/resources/studio.png
rename to openpype/modules/sync_server/providers/resources/studio.png
diff --git a/openpype/modules/default_modules/sync_server/providers/sftp.py b/openpype/modules/sync_server/providers/sftp.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/providers/sftp.py
rename to openpype/modules/sync_server/providers/sftp.py
diff --git a/openpype/modules/default_modules/sync_server/resources/paused.png b/openpype/modules/sync_server/resources/paused.png
similarity index 100%
rename from openpype/modules/default_modules/sync_server/resources/paused.png
rename to openpype/modules/sync_server/resources/paused.png
diff --git a/openpype/modules/default_modules/sync_server/resources/refresh.png b/openpype/modules/sync_server/resources/refresh.png
similarity index 100%
rename from openpype/modules/default_modules/sync_server/resources/refresh.png
rename to openpype/modules/sync_server/resources/refresh.png
diff --git a/openpype/modules/default_modules/sync_server/resources/synced.png b/openpype/modules/sync_server/resources/synced.png
similarity index 100%
rename from openpype/modules/default_modules/sync_server/resources/synced.png
rename to openpype/modules/sync_server/resources/synced.png
diff --git a/openpype/modules/default_modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/sync_server.py
rename to openpype/modules/sync_server/sync_server.py
diff --git a/openpype/modules/default_modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/sync_server_module.py
rename to openpype/modules/sync_server/sync_server_module.py
diff --git a/openpype/modules/default_modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/tray/app.py
rename to openpype/modules/sync_server/tray/app.py
diff --git a/openpype/modules/default_modules/sync_server/tray/delegates.py b/openpype/modules/sync_server/tray/delegates.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/tray/delegates.py
rename to openpype/modules/sync_server/tray/delegates.py
diff --git a/openpype/modules/default_modules/sync_server/tray/lib.py b/openpype/modules/sync_server/tray/lib.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/tray/lib.py
rename to openpype/modules/sync_server/tray/lib.py
diff --git a/openpype/modules/default_modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/tray/models.py
rename to openpype/modules/sync_server/tray/models.py
diff --git a/openpype/modules/default_modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/tray/widgets.py
rename to openpype/modules/sync_server/tray/widgets.py
diff --git a/openpype/modules/default_modules/sync_server/utils.py b/openpype/modules/sync_server/utils.py
similarity index 100%
rename from openpype/modules/default_modules/sync_server/utils.py
rename to openpype/modules/sync_server/utils.py
diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py
index 3d48fb92ee..486718d8c4 100644
--- a/openpype/plugins/publish/integrate_new.py
+++ b/openpype/plugins/publish/integrate_new.py
@@ -478,6 +478,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
if index_frame_start is not None:
dst_padding_exp = "%0{}d".format(frame_start_padding)
dst_padding = dst_padding_exp % (index_frame_start + frame_number) # noqa: E501
+ elif repre.get("udim"):
+ dst_padding = int(i)
dst = "{0}{1}{2}".format(
dst_head,
diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json
index b601f9bcba..6fb6f55528 100644
--- a/openpype/settings/defaults/project_settings/flame.json
+++ b/openpype/settings/defaults/project_settings/flame.json
@@ -2,7 +2,8 @@
"create": {
"CreateShotClip": {
"hierarchy": "{folder}/{sequence}",
- "clipRename": true,
+ "useShotName": true,
+ "clipRename": false,
"clipName": "{sequence}{shot}",
"segmentIndex": true,
"countFrom": 10,
diff --git a/openpype/settings/defaults/project_settings/slack.json b/openpype/settings/defaults/project_settings/slack.json
index 2d10bd173d..d77b8c2208 100644
--- a/openpype/settings/defaults/project_settings/slack.json
+++ b/openpype/settings/defaults/project_settings/slack.json
@@ -10,6 +10,7 @@
"hosts": [],
"task_types": [],
"tasks": [],
+ "subsets": [],
"channel_messages": []
}
]
diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json
index 912e2d9924..2f99200a88 100644
--- a/openpype/settings/defaults/system_settings/applications.json
+++ b/openpype/settings/defaults/system_settings/applications.json
@@ -135,7 +135,7 @@
"OPENPYPE_WIRETAP_TOOLS": "/opt/Autodesk/wiretap/tools/2021"
}
},
- "2021.1": {
+ "2021_1": {
"use_python_2": true,
"executables": {
"windows": [],
@@ -159,7 +159,7 @@
},
"__dynamic_keys_labels__": {
"2021": "2021",
- "2021.1": "2021.1"
+ "2021_1": "2021.1"
}
}
},
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json
index 9ef05fa832..dc88d11f61 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json
@@ -28,6 +28,11 @@
"key": "hierarchy",
"label": "Shot parent hierarchy"
},
+ {
+ "type": "boolean",
+ "key": "useShotName",
+ "label": "Use Shot Name"
+ },
{
"type": "boolean",
"key": "clipRename",
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json
index 4e82c991e7..14814d8b01 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json
@@ -69,6 +69,12 @@
"type": "list",
"object_type": "text"
},
+ {
+ "key": "subsets",
+ "label": "Subset names",
+ "type": "list",
+ "object_type": "text"
+ },
{
"type": "separator"
},
diff --git a/openpype/tools/launcher/lib.py b/openpype/tools/launcher/lib.py
index 4d678b96ae..b4e6a0c3e9 100644
--- a/openpype/tools/launcher/lib.py
+++ b/openpype/tools/launcher/lib.py
@@ -15,7 +15,7 @@ provides a bridge between the file-based project inventory and configuration.
"""
import os
-from Qt import QtGui, QtCore
+from Qt import QtGui
from avalon.vendor import qtawesome
from openpype.api import resources
@@ -23,77 +23,6 @@ ICON_CACHE = {}
NOT_FOUND = type("NotFound", (object, ), {})
-class ProjectHandler(QtCore.QObject):
- """Handler of project model and current project in Launcher tool.
-
- Helps to organize two separate widgets handling current project selection.
-
- It is easier to trigger project change callbacks from one place than from
- multiple different places without proper handling or sequence changes.
-
- Args:
- dbcon(AvalonMongoDB): Mongo connection with Session.
- model(ProjectModel): Object of projects model which is shared across
- all widgets using projects. Arg dbcon should be used as source for
- the model.
- """
- # Project list will be refreshed each 10000 msecs
- # - this is not part of helper implementation but should be used by widgets
- # that may require reshing of projects
- refresh_interval = 10000
-
- # Signal emitted when project has changed
- project_changed = QtCore.Signal(str)
- projects_refreshed = QtCore.Signal()
- timer_timeout = QtCore.Signal()
-
- def __init__(self, dbcon, model):
- super(ProjectHandler, self).__init__()
- self._active = False
- # Store project model for usage
- self.model = model
- # Store dbcon
- self.dbcon = dbcon
-
- self.current_project = dbcon.Session.get("AVALON_PROJECT")
-
- refresh_timer = QtCore.QTimer()
- refresh_timer.setInterval(self.refresh_interval)
- refresh_timer.timeout.connect(self._on_timeout)
-
- self.refresh_timer = refresh_timer
-
- def _on_timeout(self):
- if self._active:
- self.timer_timeout.emit()
- self.refresh_model()
-
- def set_active(self, active):
- self._active = active
-
- def start_timer(self, trigger=False):
- self.refresh_timer.start()
- if trigger:
- self._on_timeout()
-
- def stop_timer(self):
- self.refresh_timer.stop()
-
- def set_project(self, project_name):
- # Change current project of this handler
- self.current_project = project_name
- # Change session project to take effect for other widgets using the
- # dbcon object.
- self.dbcon.Session["AVALON_PROJECT"] = project_name
-
- # Trigger change signal when everything is updated to new project
- self.project_changed.emit(project_name)
-
- def refresh_model(self):
- self.model.refresh()
- self.projects_refreshed.emit()
-
-
def get_action_icon(action):
icon_name = action.icon
if not icon_name:
diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py
index ecee8b1575..effa283318 100644
--- a/openpype/tools/launcher/models.py
+++ b/openpype/tools/launcher/models.py
@@ -1,8 +1,30 @@
+import re
import uuid
import copy
import logging
import collections
+import time
+
import appdirs
+from Qt import QtCore, QtGui
+from avalon.vendor import qtawesome
+from avalon import api
+from openpype.lib import JSONSettingRegistry
+from openpype.lib.applications import (
+ CUSTOM_LAUNCH_APP_GROUPS,
+ ApplicationManager
+)
+from openpype.tools.utils.lib import DynamicQThread
+from openpype.tools.utils.assets_widget import (
+ AssetModel,
+ ASSET_NAME_ROLE
+)
+from openpype.tools.utils.tasks_widget import (
+ TasksModel,
+ TasksProxyModel,
+ TASK_TYPE_ROLE,
+ TASK_ASSIGNEE_ROLE
+)
from . import lib
from .constants import (
@@ -13,17 +35,13 @@ from .constants import (
FORCE_NOT_OPEN_WORKFILE_ROLE
)
from .actions import ApplicationAction
-from Qt import QtCore, QtGui
-from avalon.vendor import qtawesome
-from avalon import api
-from openpype.lib import JSONSettingRegistry
-from openpype.lib.applications import (
- CUSTOM_LAUNCH_APP_GROUPS,
- ApplicationManager
-)
log = logging.getLogger(__name__)
+# Must be different than roles in default asset model
+ASSET_TASK_TYPES_ROLE = QtCore.Qt.UserRole + 10
+ASSET_ASSIGNEE_ROLE = QtCore.Qt.UserRole + 11
+
class ActionModel(QtGui.QStandardItemModel):
def __init__(self, dbcon, parent=None):
@@ -330,21 +348,483 @@ class ActionModel(QtGui.QStandardItemModel):
return compare_data
+class LauncherModel(QtCore.QObject):
+ # Refresh interval of projects
+ refresh_interval = 10000
+
+ # Signals
+ # Current project has changed
+ project_changed = QtCore.Signal(str)
+ # Filters has changed (any)
+ filters_changed = QtCore.Signal()
+
+ # Projects were refreshed
+ projects_refreshed = QtCore.Signal()
+
+ # Signals ONLY for assets model!
+ # - other objects should listen to asset model signals
+ # Asset refresh started
+ assets_refresh_started = QtCore.Signal()
+ # Assets refresh finished
+ assets_refreshed = QtCore.Signal()
+
+ # Refresh timer timeout
+ # - give ability to tell parent window that this timer still runs
+ timer_timeout = QtCore.Signal()
+
+ # Duplication from AssetsModel with "data.tasks"
+ _asset_projection = {
+ "name": 1,
+ "parent": 1,
+ "data.visualParent": 1,
+ "data.label": 1,
+ "data.icon": 1,
+ "data.color": 1,
+ "data.tasks": 1
+ }
+
+ def __init__(self, dbcon):
+ super(LauncherModel, self).__init__()
+ # Refresh timer
+ # - should affect only projects
+ refresh_timer = QtCore.QTimer()
+ refresh_timer.setInterval(self.refresh_interval)
+ refresh_timer.timeout.connect(self._on_timeout)
+
+ self._refresh_timer = refresh_timer
+
+ # Launcher is active
+ self._active = False
+
+ # Global data
+ self._dbcon = dbcon
+ # Available project names
+ self._project_names = set()
+
+ # Context data
+ self._asset_docs = []
+ self._asset_docs_by_id = {}
+ self._asset_filter_data_by_id = {}
+ self._assignees = set()
+ self._task_types = set()
+
+ # Filters
+ self._asset_name_filter = ""
+ self._assignee_filters = set()
+ self._task_type_filters = set()
+
+ # Last project for which were assets queried
+ self._last_project_name = None
+ # Asset refresh thread is running
+ self._refreshing_assets = False
+ # Asset refresh thread
+ self._asset_refresh_thread = None
+
+ def _on_timeout(self):
+ """Refresh timer timeout."""
+ if self._active:
+ self.timer_timeout.emit()
+ self.refresh_projects()
+
+ def set_active(self, active):
+ """Window change active state."""
+ self._active = active
+
+ def start_refresh_timer(self, trigger=False):
+ """Start refresh timer."""
+ self._refresh_timer.start()
+ if trigger:
+ self._on_timeout()
+
+ def stop_refresh_timer(self):
+ """Stop refresh timer."""
+ self._refresh_timer.stop()
+
+ @property
+ def project_name(self):
+ """Current project name."""
+ return self._dbcon.Session.get("AVALON_PROJECT")
+
+ @property
+ def refreshing_assets(self):
+ """Refreshing thread is running."""
+ return self._refreshing_assets
+
+ @property
+ def asset_docs(self):
+ """Access to asset docs."""
+ return self._asset_docs
+
+ @property
+ def project_names(self):
+ """Available project names."""
+ return self._project_names
+
+ @property
+ def asset_filter_data_by_id(self):
+ """Prepared filter data by asset id."""
+ return self._asset_filter_data_by_id
+
+ @property
+ def assignees(self):
+ """All assignees for all assets in current project."""
+ return self._assignees
+
+ @property
+ def task_types(self):
+ """All task types for all assets in current project.
+
+ TODO: This could be maybe taken from project document where are all
+ task types...
+ """
+ return self._task_types
+
+ @property
+ def task_type_filters(self):
+ """Currently set task type filters."""
+ return self._task_type_filters
+
+ @property
+ def assignee_filters(self):
+ """Currently set assignee filters."""
+ return self._assignee_filters
+
+ @property
+ def asset_name_filter(self):
+ """Asset name filter (can be used as regex filter)."""
+ return self._asset_name_filter
+
+ def get_asset_doc(self, asset_id):
+ """Get single asset document by id."""
+ return self._asset_docs_by_id.get(asset_id)
+
+ def set_project_name(self, project_name):
+ """Change project name and refresh asset documents."""
+ if project_name == self.project_name:
+ return
+ self._dbcon.Session["AVALON_PROJECT"] = project_name
+ self.project_changed.emit(project_name)
+
+ self.refresh_assets(force=True)
+
+ def refresh(self):
+ """Trigger refresh of whole model."""
+ self.refresh_projects()
+ self.refresh_assets(force=False)
+
+ def refresh_projects(self):
+ """Refresh projects."""
+ current_project = self.project_name
+ project_names = set()
+ for project_doc in self._dbcon.projects(only_active=True):
+ project_names.add(project_doc["name"])
+
+ self._project_names = project_names
+ self.projects_refreshed.emit()
+ if (
+ current_project is not None
+ and current_project not in project_names
+ ):
+ self.set_project_name(None)
+
+ def _set_asset_docs(self, asset_docs=None):
+ """Set asset documents and all related data.
+
+ Method extract and prepare data needed for assets and tasks widget and
+ prepare filtering data.
+ """
+ if asset_docs is None:
+ asset_docs = []
+
+ all_task_types = set()
+ all_assignees = set()
+ asset_docs_by_id = {}
+ asset_filter_data_by_id = {}
+ for asset_doc in asset_docs:
+ task_types = set()
+ assignees = set()
+ asset_id = asset_doc["_id"]
+ asset_docs_by_id[asset_id] = asset_doc
+ asset_tasks = asset_doc.get("data", {}).get("tasks")
+ asset_filter_data_by_id[asset_id] = {
+ "assignees": assignees,
+ "task_types": task_types
+ }
+ if not asset_tasks:
+ continue
+
+ for task_data in asset_tasks.values():
+ task_assignees = set()
+ _task_assignees = task_data.get("assignees")
+ if _task_assignees:
+ for assignee in _task_assignees:
+ task_assignees.add(assignee["username"])
+
+ task_type = task_data.get("type")
+ if task_assignees:
+ assignees |= set(task_assignees)
+ if task_type:
+ task_types.add(task_type)
+
+ all_task_types |= task_types
+ all_assignees |= assignees
+
+ self._asset_docs_by_id = asset_docs_by_id
+ self._asset_docs = asset_docs
+ self._asset_filter_data_by_id = asset_filter_data_by_id
+ self._assignees = all_assignees
+ self._task_types = all_task_types
+
+ self.assets_refreshed.emit()
+
+ def set_task_type_filter(self, task_types):
+ """Change task type filter.
+
+ Args:
+ task_types (set): Set of task types that should be visible.
+ Pass empty set to turn filter off.
+ """
+ self._task_type_filters = task_types
+ self.filters_changed.emit()
+
+ def set_assignee_filter(self, assignees):
+ """Change assignees filter.
+
+ Args:
+ assignees (set): Set of assignees that should be visible.
+ Pass empty set to turn filter off.
+ """
+ self._assignee_filters = assignees
+ self.filters_changed.emit()
+
+ def set_asset_name_filter(self, text_filter):
+ """Change asset name filter.
+
+ Args:
+ text_filter (str): Asset name filter. Pass empty string to
+ turn filter off.
+ """
+ self._asset_name_filter = text_filter
+ self.filters_changed.emit()
+
+ def refresh_assets(self, force=True):
+ """Refresh assets."""
+ self.assets_refresh_started.emit()
+
+ if self.project_name is None:
+ self._set_asset_docs()
+ return
+
+ if (
+ not force
+ and self._last_project_name == self.project_name
+ ):
+ return
+
+ self._stop_fetch_thread()
+
+ self._refreshing_assets = True
+ self._last_project_name = self.project_name
+ self._asset_refresh_thread = DynamicQThread(self._refresh_assets)
+ self._asset_refresh_thread.start()
+
+ def _stop_fetch_thread(self):
+ self._refreshing_assets = False
+ if self._asset_refresh_thread is not None:
+ while self._asset_refresh_thread.isRunning():
+ # TODO this is blocking UI should be done in a different way
+ time.sleep(0.01)
+ self._asset_refresh_thread = None
+
+ def _refresh_assets(self):
+ asset_docs = list(self._dbcon.find(
+ {"type": "asset"},
+ self._asset_projection
+ ))
+ if not self._refreshing_assets:
+ return
+ self._refreshing_assets = False
+ self._set_asset_docs(asset_docs)
+
+
+class LauncherTasksProxyModel(TasksProxyModel):
+ """Tasks proxy model with more filtering.
+
+ TODO:
+ This can be (with few modifications) used in default tasks widget too.
+ """
+ def __init__(self, launcher_model, *args, **kwargs):
+ self._launcher_model = launcher_model
+ super(LauncherTasksProxyModel, self).__init__(*args, **kwargs)
+
+ launcher_model.filters_changed.connect(self._on_filter_change)
+
+ self._task_types_filter = set()
+ self._assignee_filter = set()
+
+ def _on_filter_change(self):
+ self._task_types_filter = self._launcher_model.task_type_filters
+ self._assignee_filter = self._launcher_model.assignee_filters
+ self.invalidateFilter()
+
+ def filterAcceptsRow(self, row, parent):
+ if not self._task_types_filter and not self._assignee_filter:
+ return True
+
+ model = self.sourceModel()
+ source_index = model.index(row, self.filterKeyColumn(), parent)
+ if not source_index.isValid():
+ return False
+
+ # Check current index itself
+ if self._task_types_filter:
+ task_type = model.data(source_index, TASK_TYPE_ROLE)
+ if task_type not in self._task_types_filter:
+ return False
+
+ if self._assignee_filter:
+ assignee = model.data(source_index, TASK_ASSIGNEE_ROLE)
+ if not self._assignee_filter.intersection(assignee):
+ return False
+ return True
+
+
+class LauncherTaskModel(TasksModel):
+ def __init__(self, launcher_model, *args, **kwargs):
+ self._launcher_model = launcher_model
+ super(LauncherTaskModel, self).__init__(*args, **kwargs)
+
+ def set_asset_id(self, asset_id):
+ asset_doc = None
+ if self._context_is_valid():
+ asset_doc = self._launcher_model.get_asset_doc(asset_id)
+ self._set_asset(asset_doc)
+
+
+class AssetRecursiveSortFilterModel(QtCore.QSortFilterProxyModel):
+ def __init__(self, launcher_model, *args, **kwargs):
+ self._launcher_model = launcher_model
+
+ super(AssetRecursiveSortFilterModel, self).__init__(*args, **kwargs)
+
+ launcher_model.filters_changed.connect(self._on_filter_change)
+ self._name_filter = ""
+ self._task_types_filter = set()
+ self._assignee_filter = set()
+
+ def _on_filter_change(self):
+ self._name_filter = self._launcher_model.asset_name_filter
+ self._task_types_filter = self._launcher_model.task_type_filters
+ self._assignee_filter = self._launcher_model.assignee_filters
+ self.invalidateFilter()
+
+ """Filters to the regex if any of the children matches allow parent"""
+ def filterAcceptsRow(self, row, parent):
+ if (
+ not self._name_filter
+ and not self._task_types_filter
+ and not self._assignee_filter
+ ):
+ return True
+
+ model = self.sourceModel()
+ source_index = model.index(row, self.filterKeyColumn(), parent)
+ if not source_index.isValid():
+ return False
+
+ # Check current index itself
+ valid = True
+ if self._name_filter:
+ name = model.data(source_index, ASSET_NAME_ROLE)
+ if (
+ name is None
+ or not re.search(self._name_filter, name, re.IGNORECASE)
+ ):
+ valid = False
+
+ if valid and self._task_types_filter:
+ task_types = model.data(source_index, ASSET_TASK_TYPES_ROLE)
+ if not self._task_types_filter.intersection(task_types):
+ valid = False
+
+ if valid and self._assignee_filter:
+ assignee = model.data(source_index, ASSET_ASSIGNEE_ROLE)
+ if not self._assignee_filter.intersection(assignee):
+ valid = False
+
+ if valid:
+ return True
+
+ # Check children
+ rows = model.rowCount(source_index)
+ for child_row in range(rows):
+ if self.filterAcceptsRow(child_row, source_index):
+ return True
+ return False
+
+
+class LauncherAssetsModel(AssetModel):
+ def __init__(self, launcher_model, dbcon, parent=None):
+ self._launcher_model = launcher_model
+ # Make sure that variable is available (even if is in AssetModel)
+ self._last_project_name = None
+
+ super(LauncherAssetsModel, self).__init__(dbcon, parent)
+
+ launcher_model.project_changed.connect(self._on_project_change)
+ launcher_model.assets_refresh_started.connect(
+ self._on_launcher_refresh_start
+ )
+ launcher_model.assets_refreshed.connect(self._on_launcher_refresh)
+
+ def _on_launcher_refresh_start(self):
+ self._refreshing = True
+ project_name = self._launcher_model.project_name
+ if self._last_project_name != project_name:
+ self._clear_items()
+ self._last_project_name = project_name
+
+ def _on_launcher_refresh(self):
+ self._fill_assets(self._launcher_model.asset_docs)
+ self._refreshing = False
+ self.refreshed.emit(bool(self._items_by_asset_id))
+
+ def _fill_assets(self, *args, **kwargs):
+ super(LauncherAssetsModel, self)._fill_assets(*args, **kwargs)
+ asset_filter_data_by_id = self._launcher_model.asset_filter_data_by_id
+ for asset_id, item in self._items_by_asset_id.items():
+ filter_data = asset_filter_data_by_id.get(asset_id)
+
+ assignees = filter_data["assignees"]
+ task_types = filter_data["task_types"]
+
+ item.setData(assignees, ASSET_ASSIGNEE_ROLE)
+ item.setData(task_types, ASSET_TASK_TYPES_ROLE)
+
+ def _on_project_change(self):
+ self._clear_items()
+
+ def refresh(self, *args, **kwargs):
+ raise ValueError("This is a bug!")
+
+ def stop_refresh(self, *args, **kwargs):
+ raise ValueError("This is a bug!")
+
+
class ProjectModel(QtGui.QStandardItemModel):
"""List of projects"""
- def __init__(self, dbcon, parent=None):
+ def __init__(self, launcher_model, parent=None):
super(ProjectModel, self).__init__(parent=parent)
- self.dbcon = dbcon
+ self._launcher_model = launcher_model
self.project_icon = qtawesome.icon("fa.map", color="white")
self._project_names = set()
- def refresh(self):
- project_names = set()
- for project_doc in self.get_projects():
- project_names.add(project_doc["name"])
+ launcher_model.projects_refreshed.connect(self._on_refresh)
+ def _on_refresh(self):
+ project_names = set(self._launcher_model.project_names)
origin_project_names = set(self._project_names)
self._project_names = project_names
@@ -387,7 +867,3 @@ class ProjectModel(QtGui.QStandardItemModel):
items.append(item)
self.invisibleRootItem().insertRows(row, items)
-
- def get_projects(self):
- return sorted(self.dbcon.projects(only_active=True),
- key=lambda x: x["name"])
diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py
index 5dad41c349..30e6531843 100644
--- a/openpype/tools/launcher/widgets.py
+++ b/openpype/tools/launcher/widgets.py
@@ -4,11 +4,21 @@ import collections
from Qt import QtWidgets, QtCore, QtGui
from avalon.vendor import qtawesome
+from openpype.tools.flickcharm import FlickCharm
+from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget
+from openpype.tools.utils.tasks_widget import TasksWidget
+
from .delegates import ActionDelegate
from . import lib
+from .models import (
+ ActionModel,
+ ProjectModel,
+ LauncherAssetsModel,
+ AssetRecursiveSortFilterModel,
+ LauncherTaskModel,
+ LauncherTasksProxyModel
+)
from .actions import ApplicationAction
-from .models import ActionModel
-from openpype.tools.flickcharm import FlickCharm
from .constants import (
ACTION_ROLE,
GROUP_ROLE,
@@ -22,15 +32,15 @@ from .constants import (
class ProjectBar(QtWidgets.QWidget):
- def __init__(self, project_handler, parent=None):
+ def __init__(self, launcher_model, parent=None):
super(ProjectBar, self).__init__(parent)
project_combobox = QtWidgets.QComboBox(self)
# Change delegate so stylysheets are applied
project_delegate = QtWidgets.QStyledItemDelegate(project_combobox)
project_combobox.setItemDelegate(project_delegate)
-
- project_combobox.setModel(project_handler.model)
+ model = ProjectModel(launcher_model)
+ project_combobox.setModel(model)
project_combobox.setRootModelIndex(QtCore.QModelIndex())
layout = QtWidgets.QHBoxLayout(self)
@@ -42,16 +52,17 @@ class ProjectBar(QtWidgets.QWidget):
QtWidgets.QSizePolicy.Maximum
)
- self.project_handler = project_handler
+ self._launcher_model = launcher_model
self.project_delegate = project_delegate
self.project_combobox = project_combobox
+ self._model = model
# Signals
self.project_combobox.currentIndexChanged.connect(self.on_index_change)
- project_handler.project_changed.connect(self._on_project_change)
+ launcher_model.project_changed.connect(self._on_project_change)
# Set current project by default if it's set.
- project_name = project_handler.current_project
+ project_name = launcher_model.project_name
if project_name:
self.set_project(project_name)
@@ -67,7 +78,7 @@ class ProjectBar(QtWidgets.QWidget):
index = self.project_combobox.findText(project_name)
if index < 0:
# Try refresh combobox model
- self.project_handler.refresh_model()
+ self._launcher_model.refresh_projects()
index = self.project_combobox.findText(project_name)
if index >= 0:
@@ -78,7 +89,70 @@ class ProjectBar(QtWidgets.QWidget):
return
project_name = self.get_current_project()
- self.project_handler.set_project(project_name)
+ self._launcher_model.set_project_name(project_name)
+
+
+class LauncherTaskWidget(TasksWidget):
+ def __init__(self, launcher_model, *args, **kwargs):
+ self._launcher_model = launcher_model
+
+ super(LauncherTaskWidget, self).__init__(*args, **kwargs)
+
+ def _create_source_model(self):
+ return LauncherTaskModel(self._launcher_model, self._dbcon)
+
+ def _create_proxy_model(self, source_model):
+ proxy = LauncherTasksProxyModel(self._launcher_model)
+ proxy.setSourceModel(source_model)
+ return proxy
+
+
+class LauncherAssetsWidget(SingleSelectAssetsWidget):
+ def __init__(self, launcher_model, *args, **kwargs):
+ self._launcher_model = launcher_model
+
+ super(LauncherAssetsWidget, self).__init__(*args, **kwargs)
+
+ launcher_model.assets_refresh_started.connect(self._on_refresh_start)
+
+ self.set_current_asset_btn_visibility(False)
+
+ def _on_refresh_start(self):
+ self._set_loading_state(loading=True, empty=True)
+ self.refresh_triggered.emit()
+
+ @property
+ def refreshing(self):
+ return self._model.refreshing
+
+ def refresh(self):
+ self._launcher_model.refresh_assets(force=True)
+
+ def stop_refresh(self):
+ raise ValueError("bug stop_refresh called")
+
+ def _refresh_model(self, clear=False):
+ raise ValueError("bug _refresh_model called")
+
+ def _create_source_model(self):
+ model = LauncherAssetsModel(self._launcher_model, self.dbcon)
+ model.refreshed.connect(self._on_model_refresh)
+ return model
+
+ def _create_proxy_model(self, source_model):
+ proxy = AssetRecursiveSortFilterModel(self._launcher_model)
+ proxy.setSourceModel(source_model)
+ proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
+ proxy.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
+ return proxy
+
+ def _on_model_refresh(self, has_item):
+ self._proxy.sort(0)
+ self._set_loading_state(loading=False, empty=not has_item)
+ self.refreshed.emit()
+
+ def _on_filter_text_change(self, new_text):
+ self._launcher_model.set_asset_name_filter(new_text)
class ActionBar(QtWidgets.QWidget):
@@ -86,10 +160,10 @@ class ActionBar(QtWidgets.QWidget):
action_clicked = QtCore.Signal(object)
- def __init__(self, project_handler, dbcon, parent=None):
+ def __init__(self, launcher_model, dbcon, parent=None):
super(ActionBar, self).__init__(parent)
- self.project_handler = project_handler
+ self._launcher_model = launcher_model
self.dbcon = dbcon
view = QtWidgets.QListView(self)
@@ -136,7 +210,7 @@ class ActionBar(QtWidgets.QWidget):
self.set_row_height(1)
- project_handler.projects_refreshed.connect(self._on_projects_refresh)
+ launcher_model.projects_refreshed.connect(self._on_projects_refresh)
view.clicked.connect(self.on_clicked)
view.customContextMenuRequested.connect(self.on_context_menu)
@@ -182,8 +256,8 @@ class ActionBar(QtWidgets.QWidget):
self.update()
def _start_animation(self, index):
- # Offset refresh timeout
- self.project_handler.start_timer()
+ # Offset refresh timout
+ self._launcher_model.start_refresh_timer()
action_id = index.data(ACTION_ID_ROLE)
item = self.model.items_by_id.get(action_id)
if item:
@@ -250,8 +324,8 @@ class ActionBar(QtWidgets.QWidget):
self.action_clicked.emit(action)
return
- # Offset refresh timeout
- self.project_handler.start_timer()
+ # Offset refresh timout
+ self._launcher_model.start_refresh_timer()
actions = index.data(ACTION_ROLE)
diff --git a/openpype/tools/launcher/window.py b/openpype/tools/launcher/window.py
index a8f65894f2..b5b6368865 100644
--- a/openpype/tools/launcher/window.py
+++ b/openpype/tools/launcher/window.py
@@ -8,17 +8,19 @@ from avalon.api import AvalonMongoDB
from openpype import style
from openpype.api import resources
-from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget
-from openpype.tools.utils.tasks_widget import TasksWidget
-
from avalon.vendor import qtawesome
-from .models import ProjectModel
-from .lib import get_action_label, ProjectHandler
+from .models import (
+ LauncherModel,
+ ProjectModel
+)
+from .lib import get_action_label
from .widgets import (
ProjectBar,
ActionBar,
ActionHistory,
- SlidePageWidget
+ SlidePageWidget,
+ LauncherAssetsWidget,
+ LauncherTaskWidget
)
from openpype.tools.flickcharm import FlickCharm
@@ -89,15 +91,15 @@ class ProjectIconView(QtWidgets.QListView):
class ProjectsPanel(QtWidgets.QWidget):
"""Projects Page"""
- def __init__(self, project_handler, parent=None):
+ def __init__(self, launcher_model, parent=None):
super(ProjectsPanel, self).__init__(parent=parent)
view = ProjectIconView(parent=self)
view.setSelectionMode(QtWidgets.QListView.NoSelection)
flick = FlickCharm(parent=self)
flick.activateOn(view)
-
- view.setModel(project_handler.model)
+ model = ProjectModel(launcher_model)
+ view.setModel(model)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
@@ -105,13 +107,14 @@ class ProjectsPanel(QtWidgets.QWidget):
view.clicked.connect(self.on_clicked)
+ self._model = model
self.view = view
- self.project_handler = project_handler
+ self._launcher_model = launcher_model
def on_clicked(self, index):
if index.isValid():
project_name = index.data(QtCore.Qt.DisplayRole)
- self.project_handler.set_project(project_name)
+ self._launcher_model.set_project_name(project_name)
class AssetsPanel(QtWidgets.QWidget):
@@ -119,7 +122,7 @@ class AssetsPanel(QtWidgets.QWidget):
back_clicked = QtCore.Signal()
session_changed = QtCore.Signal()
- def __init__(self, project_handler, dbcon, parent=None):
+ def __init__(self, launcher_model, dbcon, parent=None):
super(AssetsPanel, self).__init__(parent=parent)
self.dbcon = dbcon
@@ -129,7 +132,7 @@ class AssetsPanel(QtWidgets.QWidget):
btn_back = QtWidgets.QPushButton(self)
btn_back.setIcon(btn_back_icon)
- project_bar = ProjectBar(project_handler, self)
+ project_bar = ProjectBar(launcher_model, self)
project_bar_layout = QtWidgets.QHBoxLayout()
project_bar_layout.setContentsMargins(0, 0, 0, 0)
@@ -138,12 +141,14 @@ class AssetsPanel(QtWidgets.QWidget):
project_bar_layout.addWidget(project_bar)
# Assets widget
- assets_widget = SingleSelectAssetsWidget(dbcon=self.dbcon, parent=self)
+ assets_widget = LauncherAssetsWidget(
+ launcher_model, dbcon=self.dbcon, parent=self
+ )
# Make assets view flickable
assets_widget.activate_flick_charm()
# Tasks widget
- tasks_widget = TasksWidget(self.dbcon, self)
+ tasks_widget = LauncherTaskWidget(launcher_model, self.dbcon, self)
# Body
body = QtWidgets.QSplitter(self)
@@ -165,19 +170,20 @@ class AssetsPanel(QtWidgets.QWidget):
layout.addWidget(body)
# signals
- project_handler.project_changed.connect(self._on_project_changed)
+ launcher_model.project_changed.connect(self._on_project_changed)
assets_widget.selection_changed.connect(self._on_asset_changed)
assets_widget.refreshed.connect(self._on_asset_changed)
tasks_widget.task_changed.connect(self._on_task_change)
btn_back.clicked.connect(self.back_clicked)
- self.project_handler = project_handler
self.project_bar = project_bar
self.assets_widget = assets_widget
self._tasks_widget = tasks_widget
self._btn_back = btn_back
+ self._launcher_model = launcher_model
+
def select_asset(self, asset_name):
self.assets_widget.select_asset_by_name(asset_name)
@@ -196,8 +202,6 @@ class AssetsPanel(QtWidgets.QWidget):
def _on_project_changed(self):
self.session_changed.emit()
- self.assets_widget.refresh()
-
def _on_asset_changed(self):
"""Callback on asset selection changed
@@ -250,18 +254,17 @@ class LauncherWindow(QtWidgets.QDialog):
| QtCore.Qt.WindowCloseButtonHint
)
- project_model = ProjectModel(self.dbcon)
- project_handler = ProjectHandler(self.dbcon, project_model)
+ launcher_model = LauncherModel(self.dbcon)
- project_panel = ProjectsPanel(project_handler)
- asset_panel = AssetsPanel(project_handler, self.dbcon)
+ project_panel = ProjectsPanel(launcher_model)
+ asset_panel = AssetsPanel(launcher_model, self.dbcon)
page_slider = SlidePageWidget()
page_slider.addWidget(project_panel)
page_slider.addWidget(asset_panel)
# actions
- actions_bar = ActionBar(project_handler, self.dbcon, self)
+ actions_bar = ActionBar(launcher_model, self.dbcon, self)
# statusbar
message_label = QtWidgets.QLabel(self)
@@ -303,8 +306,8 @@ class LauncherWindow(QtWidgets.QDialog):
# signals
actions_bar.action_clicked.connect(self.on_action_clicked)
action_history.trigger_history.connect(self.on_history_action)
- project_handler.project_changed.connect(self.on_project_change)
- project_handler.timer_timeout.connect(self._on_refresh_timeout)
+ launcher_model.project_changed.connect(self.on_project_change)
+ launcher_model.timer_timeout.connect(self._on_refresh_timeout)
asset_panel.back_clicked.connect(self.on_back_clicked)
asset_panel.session_changed.connect(self.on_session_changed)
@@ -314,7 +317,7 @@ class LauncherWindow(QtWidgets.QDialog):
self._message_timer = message_timer
- self.project_handler = project_handler
+ self._launcher_model = launcher_model
self._message_label = message_label
self.project_panel = project_panel
@@ -324,19 +327,19 @@ class LauncherWindow(QtWidgets.QDialog):
self.page_slider = page_slider
def showEvent(self, event):
- self.project_handler.set_active(True)
- self.project_handler.start_timer(True)
+ self._launcher_model.set_active(True)
+ self._launcher_model.start_refresh_timer(True)
super(LauncherWindow, self).showEvent(event)
def _on_refresh_timeout(self):
# Stop timer if widget is not visible
if not self.isVisible():
- self.project_handler.stop_timer()
+ self._launcher_model.stop_refresh_timer()
def changeEvent(self, event):
if event.type() == QtCore.QEvent.ActivationChange:
- self.project_handler.set_active(self.isActiveWindow())
+ self._launcher_model.set_active(self.isActiveWindow())
super(LauncherWindow, self).changeEvent(event)
def set_page(self, page):
@@ -371,7 +374,7 @@ class LauncherWindow(QtWidgets.QDialog):
self.discover_actions()
def on_back_clicked(self):
- self.project_handler.set_project(None)
+ self._launcher_model.set_project_name(None)
self.set_page(0)
self.discover_actions()
diff --git a/openpype/tools/pyblish_pype/control.py b/openpype/tools/pyblish_pype/control.py
index 9774c8020b..455a338499 100644
--- a/openpype/tools/pyblish_pype/control.py
+++ b/openpype/tools/pyblish_pype/control.py
@@ -152,6 +152,8 @@ class Controller(QtCore.QObject):
self.instance_toggled.connect(self._on_instance_toggled)
self._main_thread_processor = MainThreadProcess()
+ self._current_state = ""
+
def reset_variables(self):
self.log.debug("Resetting pyblish context variables")
@@ -159,6 +161,7 @@ class Controller(QtCore.QObject):
self.is_running = False
self.stopped = False
self.errored = False
+ self._current_state = ""
# Active producer of pairs
self.pair_generator = None
@@ -167,7 +170,6 @@ class Controller(QtCore.QObject):
# Orders which changes GUI
# - passing collectors order disables plugin/instance toggle
- self.collectors_order = None
self.collect_state = 0
# - passing validators order disables validate button and gives ability
@@ -176,11 +178,8 @@ class Controller(QtCore.QObject):
self.validated = False
# Get collectors and validators order
- self.order_groups.reset()
- plugin_groups = self.order_groups.groups()
- plugin_groups_keys = list(plugin_groups.keys())
- self.collectors_order = plugin_groups_keys[0]
- self.validators_order = self.order_groups.validation_order()
+ plugin_groups_keys = list(self.order_groups.groups.keys())
+ self.validators_order = self.order_groups.validation_order
next_group_order = None
if len(plugin_groups_keys) > 1:
next_group_order = plugin_groups_keys[1]
@@ -191,13 +190,18 @@ class Controller(QtCore.QObject):
"stop_on_validation": False,
# Used?
"last_plugin_order": None,
- "current_group_order": self.collectors_order,
+ "current_group_order": plugin_groups_keys[0],
"next_group_order": next_group_order,
"nextOrder": None,
"ordersWithError": set()
}
+ self._set_state_by_order()
self.log.debug("Reset of pyblish context variables done")
+ @property
+ def current_state(self):
+ return self._current_state
+
def presets_by_hosts(self):
# Get global filters as base
presets = get_project_settings(os.environ['AVALON_PROJECT']) or {}
@@ -293,6 +297,9 @@ class Controller(QtCore.QObject):
def on_published(self):
if self.is_running:
self.is_running = False
+ self._current_state = (
+ "Published" if not self.errored else "Published, with errors"
+ )
self.was_finished.emit()
self._main_thread_processor.stop()
@@ -355,7 +362,7 @@ class Controller(QtCore.QObject):
new_current_group_order = self.processing["next_group_order"]
if new_current_group_order is not None:
current_next_order_found = False
- for order in self.order_groups.groups().keys():
+ for order in self.order_groups.groups.keys():
if current_next_order_found:
new_next_group_order = order
break
@@ -370,6 +377,10 @@ class Controller(QtCore.QObject):
if self.collect_state == 0:
self.collect_state = 1
+ self._current_state = (
+ "Ready" if not self.errored else
+ "Collected, with errors"
+ )
self.switch_toggleability.emit(True)
self.passed_group.emit(current_group_order)
yield IterationBreak("Collected")
@@ -377,6 +388,11 @@ class Controller(QtCore.QObject):
else:
self.passed_group.emit(current_group_order)
if self.errored:
+ self._current_state = (
+ "Stopped, due to errors" if not
+ self.processing["stop_on_validation"] else
+ "Validated, with errors"
+ )
yield IterationBreak("Last group errored")
if self.collect_state == 1:
@@ -386,17 +402,23 @@ class Controller(QtCore.QObject):
if not self.validated and plugin.order > self.validators_order:
self.validated = True
if self.processing["stop_on_validation"]:
+ self._current_state = (
+ "Validated" if not self.errored else
+ "Validated, with errors"
+ )
yield IterationBreak("Validated")
# Stop if was stopped
if self.stopped:
self.stopped = False
+ self._current_state = "Paused"
yield IterationBreak("Stopped")
# check test if will stop
self.processing["nextOrder"] = plugin.order
message = self.test(**self.processing)
if message:
+ self._current_state = "Paused"
yield IterationBreak("Stopped due to \"{}\"".format(message))
self.processing["last_plugin_order"] = plugin.order
@@ -426,6 +448,7 @@ class Controller(QtCore.QObject):
# Stop if was stopped
if self.stopped:
self.stopped = False
+ self._current_state = "Paused"
yield IterationBreak("Stopped")
yield (plugin, instance)
@@ -536,20 +559,27 @@ class Controller(QtCore.QObject):
MainThreadItem(on_next)
)
+ def _set_state_by_order(self):
+ order = self.processing["current_group_order"]
+ self._current_state = self.order_groups.groups[order]["state"]
+
def collect(self):
""" Iterate and process Collect plugins
- load_plugins method is launched again when finished
"""
+ self._set_state_by_order()
self._main_thread_processor.process(self._start_collect)
self._main_thread_processor.start()
def validate(self):
""" Process plugins to validations_order value."""
+ self._set_state_by_order()
self._main_thread_processor.process(self._start_validate)
self._main_thread_processor.start()
def publish(self):
""" Iterate and process all remaining plugins."""
+ self._set_state_by_order()
self._main_thread_processor.process(self._start_publish)
self._main_thread_processor.start()
diff --git a/openpype/tools/pyblish_pype/model.py b/openpype/tools/pyblish_pype/model.py
index bb1aff2a9a..0faadb5940 100644
--- a/openpype/tools/pyblish_pype/model.py
+++ b/openpype/tools/pyblish_pype/model.py
@@ -428,12 +428,12 @@ class PluginModel(QtGui.QStandardItemModel):
self.clear()
def append(self, plugin):
- plugin_groups = self.controller.order_groups.groups()
+ plugin_groups = self.controller.order_groups.groups
label = None
order = None
- for _order, _label in reversed(plugin_groups.items()):
+ for _order, item in reversed(plugin_groups.items()):
if _order is None or plugin.order < _order:
- label = _label
+ label = item["label"]
order = _order
else:
break
diff --git a/openpype/tools/pyblish_pype/util.py b/openpype/tools/pyblish_pype/util.py
index d3d76b187c..9f3697be16 100644
--- a/openpype/tools/pyblish_pype/util.py
+++ b/openpype/tools/pyblish_pype/util.py
@@ -95,224 +95,44 @@ def collect_families_from_instances(instances, only_active=False):
class OrderGroups:
- # Validator order can be set with environment "PYBLISH_VALIDATION_ORDER"
- # - this variable sets when validation button will hide and proecssing
- # of validation will end with ability to continue in process
- default_validation_order = pyblish.api.ValidatorOrder + 0.5
-
- # Group range can be set with environment "PYBLISH_GROUP_RANGE"
- default_group_range = 1
-
- # Group string can be set with environment "PYBLISH_GROUP_SETTING"
- default_groups = {
- pyblish.api.CollectorOrder + 0.5: "Collect",
- pyblish.api.ValidatorOrder + 0.5: "Validate",
- pyblish.api.ExtractorOrder + 0.5: "Extract",
- pyblish.api.IntegratorOrder + 0.5: "Integrate",
- None: "Other"
- }
-
- # *** This example should have same result as is `default_groups` if
- # `group_range` is set to "1"
- __groups_str_example__ = (
- # half of `group_range` is added to 0 because number means it is Order
- "0=Collect"
- # if `<` is before than it means group range is not used
- # but is expected that number is already max
- ",<1.5=Validate"
- # "Extractor" will be used in range `<1.5; 2.5)`
- ",<2.5=Extract"
- ",<3.5=Integrate"
- # "Other" if number is not set than all remaining plugins are in
- # - in this case Other's range is <3.5; infinity)
- ",Other"
- )
-
- _groups = None
- _validation_order = None
- _group_range = None
-
- def __init__(
- self, group_str=None, group_range=None, validation_order=None
- ):
- super(OrderGroups, self).__init__()
- # Override class methods with object methods
- self.groups = self._object_groups
- self.validation_order = self._object_validation_order
- self.group_range = self._object_group_range
- self.reset = self._object_reset
-
- # set
- if group_range is not None:
- self._group_range = self.parse_group_range(
- group_range
- )
-
- if group_str is not None:
- self._groups = self.parse_group_str(
- group_str
- )
-
- if validation_order is not None:
- self._validation_order = self.parse_validation_order(
- validation_order
- )
-
- @staticmethod
- def _groups_method(obj):
- if obj._groups is None:
- obj._groups = obj.parse_group_str(
- group_range=obj.group_range()
- )
- return obj._groups
-
- @staticmethod
- def _reset_method(obj):
- obj._groups = None
- obj._validation_order = None
- obj._group_range = None
-
- @classmethod
- def reset(cls):
- return cls._reset_method(cls)
-
- def _object_reset(self):
- return self._reset_method(self)
-
- @classmethod
- def groups(cls):
- return cls._groups_method(cls)
-
- def _object_groups(self):
- return self._groups_method(self)
-
- @staticmethod
- def _validation_order_method(obj):
- if obj._validation_order is None:
- obj._validation_order = obj.parse_validation_order(
- group_range=obj.group_range()
- )
- return obj._validation_order
-
- @classmethod
- def validation_order(cls):
- return cls._validation_order_method(cls)
-
- def _object_validation_order(self):
- return self._validation_order_method(self)
-
- @staticmethod
- def _group_range_method(obj):
- if obj._group_range is None:
- obj._group_range = obj.parse_group_range()
- return obj._group_range
-
- @classmethod
- def group_range(cls):
- return cls._group_range_method(cls)
-
- def _object_group_range(self):
- return self._group_range_method(self)
-
- @staticmethod
- def sort_groups(_groups_dict):
- sorted_dict = collections.OrderedDict()
-
- # make sure won't affect any dictionary as pointer
- groups_dict = copy.deepcopy(_groups_dict)
- last_order = None
- if None in groups_dict:
- last_order = groups_dict.pop(None)
-
- for key in sorted(groups_dict):
- sorted_dict[key] = groups_dict[key]
-
- if last_order is not None:
- sorted_dict[None] = last_order
-
- return sorted_dict
-
- @staticmethod
- def parse_group_str(groups_str=None, group_range=None):
- if groups_str is None:
- groups_str = os.environ.get("PYBLISH_GROUP_SETTING")
-
- if groups_str is None:
- return OrderGroups.sort_groups(OrderGroups.default_groups)
-
- items = groups_str.split(",")
- groups = {}
- for item in items:
- if "=" not in item:
- order = None
- label = item
- else:
- order, label = item.split("=")
- order = order.strip()
- if not order:
- order = None
- elif order.startswith("<"):
- order = float(order.replace("<", ""))
- else:
- if group_range is None:
- group_range = OrderGroups.default_group_range
- print(
- "Using default Plugin group range \"{}\".".format(
- OrderGroups.default_group_range
- )
- )
- order = float(order) + float(group_range) / 2
-
- if order in groups:
- print((
- "Order \"{}\" is registered more than once."
- " Using first found."
- ).format(str(order)))
- continue
-
- groups[order] = label
-
- return OrderGroups.sort_groups(groups)
-
- @staticmethod
- def parse_validation_order(validation_order_value=None, group_range=None):
- if validation_order_value is None:
- validation_order_value = os.environ.get("PYBLISH_VALIDATION_ORDER")
-
- if validation_order_value is None:
- return OrderGroups.default_validation_order
-
- if group_range is None:
- group_range = OrderGroups.default_group_range
-
- group_range_half = float(group_range) / 2
-
- if isinstance(validation_order_value, numbers.Integral):
- return validation_order_value + group_range_half
-
- if validation_order_value.startswith("<"):
- validation_order_value = float(
- validation_order_value.replace("<", "")
- )
- else:
- validation_order_value = (
- float(validation_order_value)
- + group_range_half
- )
- return validation_order_value
-
- @staticmethod
- def parse_group_range(group_range=None):
- if group_range is None:
- group_range = os.environ.get("PYBLISH_GROUP_RANGE")
-
- if group_range is None:
- return OrderGroups.default_group_range
-
- if isinstance(group_range, numbers.Integral):
- return group_range
-
- return float(group_range)
+ validation_order = pyblish.api.ValidatorOrder + 0.5
+ groups = collections.OrderedDict((
+ (
+ pyblish.api.CollectorOrder + 0.5,
+ {
+ "label": "Collect",
+ "state": "Collecting.."
+ }
+ ),
+ (
+ pyblish.api.ValidatorOrder + 0.5,
+ {
+ "label": "Validate",
+ "state": "Validating.."
+ }
+ ),
+ (
+ pyblish.api.ExtractorOrder + 0.5,
+ {
+ "label": "Extract",
+ "state": "Extracting.."
+ }
+ ),
+ (
+ pyblish.api.IntegratorOrder + 0.5,
+ {
+ "label": "Integrate",
+ "state": "Integrating.."
+ }
+ ),
+ (
+ None,
+ {
+ "label": "Other",
+ "state": "Finishing.."
+ }
+ )
+ ))
def env_variable_to_bool(env_key, default=False):
diff --git a/openpype/tools/pyblish_pype/window.py b/openpype/tools/pyblish_pype/window.py
index 9c4d4b10f4..38907c1a52 100644
--- a/openpype/tools/pyblish_pype/window.py
+++ b/openpype/tools/pyblish_pype/window.py
@@ -296,25 +296,6 @@ class Window(QtWidgets.QDialog):
self.main_layout.setSpacing(0)
self.main_layout.addWidget(main_widget)
- # Display info
- info_effect = QtWidgets.QGraphicsOpacityEffect(footer_info)
- footer_info.setGraphicsEffect(info_effect)
-
- on = QtCore.QPropertyAnimation(info_effect, b"opacity")
- on.setDuration(0)
- on.setStartValue(0)
- on.setEndValue(1)
-
- fade = QtCore.QPropertyAnimation(info_effect, b"opacity")
- fade.setDuration(500)
- fade.setStartValue(1.0)
- fade.setEndValue(0.0)
-
- animation_info_msg = QtCore.QSequentialAnimationGroup()
- animation_info_msg.addAnimation(on)
- animation_info_msg.addPause(2000)
- animation_info_msg.addAnimation(fade)
-
"""Setup
Widgets are referred to in CSS via their object-name. We
@@ -448,6 +429,8 @@ class Window(QtWidgets.QDialog):
self.footer_button_validate = footer_button_validate
self.footer_button_play = footer_button_play
+ self.footer_info = footer_info
+
self.overview_instance_view = overview_instance_view
self.overview_plugin_view = overview_plugin_view
self.plugin_model = plugin_model
@@ -457,8 +440,6 @@ class Window(QtWidgets.QDialog):
self.presets_button = presets_button
- self.animation_info_msg = animation_info_msg
-
self.terminal_model = terminal_model
self.terminal_proxy = terminal_proxy
self.terminal_view = terminal_view
@@ -984,6 +965,8 @@ class Window(QtWidgets.QDialog):
self.footer_button_stop.setEnabled(True)
self.footer_button_play.setEnabled(False)
+ self._update_state()
+
def on_passed_group(self, order):
for group_item in self.instance_model.group_items.values():
group_index = self.instance_sort_proxy.mapFromSource(
@@ -1015,6 +998,8 @@ class Window(QtWidgets.QDialog):
self.overview_plugin_view.setAnimated(False)
self.overview_plugin_view.collapse(group_index)
+ self._update_state()
+
def on_was_stopped(self):
self.overview_plugin_view.setAnimated(settings.Animated)
errored = self.controller.errored
@@ -1039,6 +1024,9 @@ class Window(QtWidgets.QDialog):
and not self.controller.stopped
)
self.button_suspend_logs.setEnabled(suspend_log_bool)
+
+ self._update_state()
+
if not self.isVisible():
self.setVisible(True)
@@ -1084,6 +1072,7 @@ class Window(QtWidgets.QDialog):
)
self.update_compatibility()
+ self._update_state()
def on_was_processed(self, result):
existing_ids = set(self.instance_model.instance_items.keys())
@@ -1165,6 +1154,8 @@ class Window(QtWidgets.QDialog):
self.controller.validate()
+ self._update_state()
+
def publish(self):
self.info(self.tr("Preparing publish.."))
self.footer_button_stop.setEnabled(True)
@@ -1176,6 +1167,8 @@ class Window(QtWidgets.QDialog):
self.controller.publish()
+ self._update_state()
+
def act(self, plugin_item, action):
self.info("%s %s.." % (self.tr("Preparing"), action))
@@ -1290,6 +1283,9 @@ class Window(QtWidgets.QDialog):
#
# -------------------------------------------------------------------------
+ def _update_state(self):
+ self.footer_info.setText(self.controller.current_state)
+
def info(self, message):
"""Print user-facing information
@@ -1297,19 +1293,12 @@ class Window(QtWidgets.QDialog):
message (str): Text message for the user
"""
-
- info = self.findChild(QtWidgets.QLabel, "FooterInfo")
- info.setText(message)
-
# Include message in terminal
self.terminal_model.append([{
"label": message,
"type": "info"
}])
- self.animation_info_msg.stop()
- self.animation_info_msg.start()
-
if settings.PrintInfo:
# Print message to console
util.u_print(message)
diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py
index 18a9764b34..14e25a54d8 100644
--- a/openpype/tools/settings/settings/categories.py
+++ b/openpype/tools/settings/settings/categories.py
@@ -91,6 +91,8 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
state_changed = QtCore.Signal()
saved = QtCore.Signal(QtWidgets.QWidget)
restart_required_trigger = QtCore.Signal()
+ reset_started = QtCore.Signal()
+ reset_finished = QtCore.Signal()
full_path_requested = QtCore.Signal(str, str)
require_restart_label_text = (
@@ -379,7 +381,12 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
"""Change path of widget based on category full path."""
pass
+ def change_path(self, path):
+ """Change path and go to widget."""
+ self.breadcrumbs_bar.change_path(path)
+
def set_path(self, path):
+ """Called from clicked widget."""
self.breadcrumbs_bar.set_path(path)
def _add_developer_ui(self, footer_layout, footer_widget):
@@ -492,6 +499,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
self._update_labels_visibility()
def reset(self):
+ self.reset_started.emit()
self.set_state(CategoryState.Working)
self._on_reset_start()
@@ -596,6 +604,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
self._on_reset_crash()
else:
self._on_reset_success()
+ self.reset_finished.emit()
def _on_source_version_change(self, version):
if self._updating_root:
diff --git a/openpype/tools/settings/settings/search_dialog.py b/openpype/tools/settings/settings/search_dialog.py
new file mode 100644
index 0000000000..3f987c0010
--- /dev/null
+++ b/openpype/tools/settings/settings/search_dialog.py
@@ -0,0 +1,186 @@
+import re
+import collections
+
+from Qt import QtCore, QtWidgets, QtGui
+
+ENTITY_LABEL_ROLE = QtCore.Qt.UserRole + 1
+ENTITY_PATH_ROLE = QtCore.Qt.UserRole + 2
+
+
+def get_entity_children(entity):
+ # TODO find better way how to go through all children
+ if hasattr(entity, "values"):
+ return entity.values()
+ return []
+
+
+class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel):
+ """Filters recursively to regex in all columns"""
+
+ def __init__(self):
+ super(RecursiveSortFilterProxyModel, self).__init__()
+
+ # Note: Recursive filtering was introduced in Qt 5.10.
+ self.setRecursiveFilteringEnabled(True)
+
+ def filterAcceptsRow(self, row, parent):
+ if not parent.isValid():
+ return False
+
+ regex = self.filterRegExp()
+ if not regex.isEmpty() and regex.isValid():
+ pattern = regex.pattern()
+ compiled_regex = re.compile(pattern)
+ source_model = self.sourceModel()
+
+ # Check current index itself in all columns
+ source_index = source_model.index(row, 0, parent)
+ if source_index.isValid():
+ for role in (ENTITY_PATH_ROLE, ENTITY_LABEL_ROLE):
+ value = source_model.data(source_index, role)
+ if value and compiled_regex.search(value):
+ return True
+ return False
+
+ return super(
+ RecursiveSortFilterProxyModel, self
+ ).filterAcceptsRow(row, parent)
+
+
+class SearchEntitiesDialog(QtWidgets.QDialog):
+ path_clicked = QtCore.Signal(str)
+
+ def __init__(self, parent):
+ super(SearchEntitiesDialog, self).__init__(parent=parent)
+
+ self.setWindowTitle("Search Settings")
+
+ filter_edit = QtWidgets.QLineEdit(self)
+ filter_edit.setPlaceholderText("Search...")
+
+ model = EntityTreeModel()
+ proxy = RecursiveSortFilterProxyModel()
+ proxy.setSourceModel(model)
+ proxy.setDynamicSortFilter(True)
+
+ view = QtWidgets.QTreeView(self)
+ view.setAllColumnsShowFocus(True)
+ view.setSortingEnabled(True)
+ view.setModel(proxy)
+ model.setColumnCount(3)
+
+ layout = QtWidgets.QVBoxLayout(self)
+ layout.addWidget(filter_edit)
+ layout.addWidget(view)
+
+ filter_changed_timer = QtCore.QTimer()
+ filter_changed_timer.setInterval(200)
+
+ view.selectionModel().selectionChanged.connect(
+ self._on_selection_change
+ )
+ filter_changed_timer.timeout.connect(self._on_filter_timer)
+ filter_edit.textChanged.connect(self._on_filter_changed)
+
+ self._filter_edit = filter_edit
+ self._model = model
+ self._proxy = proxy
+ self._view = view
+ self._filter_changed_timer = filter_changed_timer
+
+ self._first_show = True
+
+ def set_root_entity(self, entity):
+ self._model.set_root_entity(entity)
+ self._view.resizeColumnToContents(0)
+
+ def showEvent(self, event):
+ super(SearchEntitiesDialog, self).showEvent(event)
+ if self._first_show:
+ self._first_show = False
+ self.resize(700, 500)
+
+ def _on_filter_changed(self, txt):
+ self._filter_changed_timer.start()
+
+ def _on_filter_timer(self):
+ text = self._filter_edit.text()
+ self._proxy.setFilterRegExp(text)
+
+ # WARNING This expanding and resizing is relatively slow.
+ self._view.expandAll()
+ self._view.resizeColumnToContents(0)
+
+ def _on_selection_change(self):
+ current = self._view.currentIndex()
+ path = current.data(ENTITY_PATH_ROLE)
+ self.path_clicked.emit(path)
+
+
+class EntityTreeModel(QtGui.QStandardItemModel):
+ def __init__(self, *args, **kwargs):
+ super(EntityTreeModel, self).__init__(*args, **kwargs)
+ self.setColumnCount(3)
+
+ def data(self, index, role=None):
+ if role is None:
+ role = QtCore.Qt.DisplayRole
+
+ col = index.column()
+ if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
+ if col == 0:
+ pass
+ elif col == 1:
+ role = ENTITY_LABEL_ROLE
+ elif col == 2:
+ role = ENTITY_PATH_ROLE
+
+ if col > 0:
+ index = self.index(index.row(), 0, index.parent())
+ return super(EntityTreeModel, self).data(index, role)
+
+ def flags(self, index):
+ if index.column() > 0:
+ index = self.index(index.row(), 0, index.parent())
+ return super(EntityTreeModel, self).flags(index)
+
+ def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
+ if role == QtCore.Qt.DisplayRole:
+ if section == 0:
+ return "Key"
+ elif section == 1:
+ return "Label"
+ elif section == 2:
+ return "Path"
+ return ""
+ return super(EntityTreeModel, self).headerData(
+ section, orientation, role
+ )
+
+ def set_root_entity(self, root_entity):
+ parent = self.invisibleRootItem()
+ parent.removeRows(0, parent.rowCount())
+ if not root_entity:
+ return
+
+ # We don't want to see the root entity so we directly add its children
+ fill_queue = collections.deque()
+ fill_queue.append((root_entity, parent))
+ cols = self.columnCount()
+ while fill_queue:
+ parent_entity, parent_item = fill_queue.popleft()
+ child_items = []
+ for child in get_entity_children(parent_entity):
+ label = child.label
+ path = child.path
+ key = path.split("/")[-1]
+ item = QtGui.QStandardItem(key)
+ item.setEditable(False)
+ item.setData(label, ENTITY_LABEL_ROLE)
+ item.setData(path, ENTITY_PATH_ROLE)
+ item.setColumnCount(cols)
+ child_items.append(item)
+ fill_queue.append((child, item))
+
+ if child_items:
+ parent_item.appendRows(child_items)
diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py
index 8a01bf1bce..22778e4a5b 100644
--- a/openpype/tools/settings/settings/window.py
+++ b/openpype/tools/settings/settings/window.py
@@ -9,6 +9,7 @@ from .widgets import (
RestartDialog,
SettingsTabWidget
)
+from .search_dialog import SearchEntitiesDialog
from openpype import style
from openpype.lib import is_admin_password_required
@@ -58,15 +59,22 @@ class MainWidget(QtWidgets.QWidget):
self.setLayout(layout)
+ search_dialog = SearchEntitiesDialog(self)
+
self._shadow_widget = ShadowWidget("Working...", self)
self._shadow_widget.setVisible(False)
+ header_tab_widget.currentChanged.connect(self._on_tab_changed)
+ search_dialog.path_clicked.connect(self._on_search_path_clicked)
+
for tab_widget in tab_widgets:
tab_widget.saved.connect(self._on_tab_save)
tab_widget.state_changed.connect(self._on_state_change)
tab_widget.restart_required_trigger.connect(
self._on_restart_required
)
+ tab_widget.reset_started.connect(self._on_reset_started)
+ tab_widget.reset_started.connect(self._on_reset_finished)
tab_widget.full_path_requested.connect(self._on_full_path_request)
header_tab_widget.context_menu_requested.connect(
@@ -75,6 +83,7 @@ class MainWidget(QtWidgets.QWidget):
self._header_tab_widget = header_tab_widget
self.tab_widgets = tab_widgets
+ self._search_dialog = search_dialog
def _on_tab_save(self, source_widget):
for tab_widget in self.tab_widgets:
@@ -170,6 +179,21 @@ class MainWidget(QtWidgets.QWidget):
for tab_widget in self.tab_widgets:
tab_widget.reset()
+ def _update_search_dialog(self, clear=False):
+ if self._search_dialog.isVisible():
+ entity = None
+ if not clear:
+ widget = self._header_tab_widget.currentWidget()
+ entity = widget.entity
+ self._search_dialog.set_root_entity(entity)
+
+ def _on_tab_changed(self):
+ self._update_search_dialog()
+
+ def _on_search_path_clicked(self, path):
+ widget = self._header_tab_widget.currentWidget()
+ widget.change_path(path)
+
def _on_restart_required(self):
# Don't show dialog if there are not registered slots for
# `trigger_restart` signal.
@@ -184,3 +208,26 @@ class MainWidget(QtWidgets.QWidget):
result = dialog.exec_()
if result == 1:
self.trigger_restart.emit()
+
+ def _on_reset_started(self):
+ widget = self.sender()
+ current_widget = self._header_tab_widget.currentWidget()
+ if current_widget is widget:
+ self._update_search_dialog(True)
+
+ def _on_reset_finished(self):
+ widget = self.sender()
+ current_widget = self._header_tab_widget.currentWidget()
+ if current_widget is widget:
+ self._update_search_dialog()
+
+ def keyPressEvent(self, event):
+ if event.matches(QtGui.QKeySequence.Find):
+ # todo: search in all widgets (or in active)?
+ widget = self._header_tab_widget.currentWidget()
+ self._search_dialog.show()
+ self._search_dialog.set_root_entity(widget.entity)
+ event.accept()
+ return
+
+ return super(MainWidget, self).keyPressEvent(event)
diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py
index be9f442d10..17164d9e0f 100644
--- a/openpype/tools/utils/assets_widget.py
+++ b/openpype/tools/utils/assets_widget.py
@@ -354,7 +354,6 @@ class AssetModel(QtGui.QStandardItemModel):
Args:
force (bool): Stop currently running refresh start new refresh.
- clear (bool): Clear model before refresh thread starts.
"""
# Skip fetch if there is already other thread fetching documents
if self._refreshing:
diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py
index 6c7787d06a..2a8a45626c 100644
--- a/openpype/tools/utils/tasks_widget.py
+++ b/openpype/tools/utils/tasks_widget.py
@@ -9,6 +9,7 @@ from .views import DeselectableTreeView
TASK_NAME_ROLE = QtCore.Qt.UserRole + 1
TASK_TYPE_ROLE = QtCore.Qt.UserRole + 2
TASK_ORDER_ROLE = QtCore.Qt.UserRole + 3
+TASK_ASSIGNEE_ROLE = QtCore.Qt.UserRole + 4
class TasksModel(QtGui.QStandardItemModel):
@@ -144,11 +145,19 @@ class TasksModel(QtGui.QStandardItemModel):
task_type_icon = task_type_info.get("icon")
icon = self._get_icon(task_icon, task_type_icon)
+ task_assignees = set()
+ assignees_data = task_info.get("assignees") or []
+ for assignee in assignees_data:
+ username = assignee.get("username")
+ if username:
+ task_assignees.add(username)
+
label = "{} ({})".format(task_name, task_type or "type N/A")
item = QtGui.QStandardItem(label)
item.setData(task_name, TASK_NAME_ROLE)
item.setData(task_type, TASK_TYPE_ROLE)
item.setData(task_order, TASK_ORDER_ROLE)
+ item.setData(task_assignees, TASK_ASSIGNEE_ROLE)
item.setData(icon, QtCore.Qt.DecorationRole)
item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
items.append(item)
diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py
index 4b5bf07b47..3a772a038c 100644
--- a/openpype/tools/workfiles/app.py
+++ b/openpype/tools/workfiles/app.py
@@ -726,9 +726,9 @@ class FilesWidget(QtWidgets.QWidget):
self.file_opened.emit()
def save_changes_prompt(self):
- self._messagebox = messagebox = QtWidgets.QMessageBox()
-
- messagebox.setWindowFlags(QtCore.Qt.FramelessWindowHint)
+ self._messagebox = messagebox = QtWidgets.QMessageBox(parent=self)
+ messagebox.setWindowFlags(messagebox.windowFlags() |
+ QtCore.Qt.FramelessWindowHint)
messagebox.setIcon(messagebox.Warning)
messagebox.setWindowTitle("Unsaved Changes!")
messagebox.setText(
@@ -739,10 +739,6 @@ class FilesWidget(QtWidgets.QWidget):
messagebox.Yes | messagebox.No | messagebox.Cancel
)
- # Parenting the QMessageBox to the Widget seems to crash
- # so we skip parenting and explicitly apply the stylesheet.
- messagebox.setStyle(self.style())
-
result = messagebox.exec_()
if result == messagebox.Yes:
return True
diff --git a/website/docs/artist_hosts_resolve.md b/website/docs/artist_hosts_resolve.md
index afd050648f..7c462484f5 100644
--- a/website/docs/artist_hosts_resolve.md
+++ b/website/docs/artist_hosts_resolve.md
@@ -9,7 +9,7 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
:::warning
-Before you are able to start with OpenPype tools in DaVinci Resolve, installation of its own Python 3.6 interpreter and PySide 2 has to be done. Go to [Installation of python and pyside](#installation-of-python-and-pyside) link for more information
+Before you are able to start with OpenPype tools in DaVinci Resolve, installation of its own Python 3.6 interpreter and PySide 2 has to be done. Go to [Installation of python and pyside](admin_hosts_resolve.md#installation-of-python-and-pyside) link for more information
:::
diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md
index 67b200259d..3a2842da63 100644
--- a/website/docs/module_slack.md
+++ b/website/docs/module_slack.md
@@ -47,7 +47,7 @@ It is possible to create multiple tokens and configure different scopes for them
### Profiles
Profiles are used to select when to trigger notification. One or multiple profiles
-could be configured, `Families`, `Task names` (regex available), `Host names` combination is needed.
+could be configured, `Families`, `Task names` (regex available), `Host names`, `Subset names` (regex available) combination is needed.
Eg. If I want to be notified when render is published from Maya, setting is: