Merge branch 'develop' into enhancement/mark-bare-except

This commit is contained in:
Jakub Trllo 2024-04-15 16:25:46 +02:00
commit 2b9d5b9271
203 changed files with 933 additions and 429 deletions

View file

@ -15,6 +15,7 @@ from abc import ABCMeta, abstractmethod
import six
import appdirs
import ayon_api
from semver import VersionInfo
from ayon_core import AYON_CORE_ROOT
from ayon_core.lib import Logger, is_dev_mode_enabled
@ -46,6 +47,11 @@ IGNORED_HOSTS_IN_AYON = {
}
IGNORED_MODULES_IN_AYON = set()
# When addon was moved from ayon-core codebase
# - this is used to log the missing addon
MOVED_ADDON_MILESTONE_VERSIONS = {
"applications": VersionInfo(2, 0, 0),
}
# Inherit from `object` for Python 2 hosts
class _ModuleClass(object):
@ -192,6 +198,45 @@ def _get_ayon_addons_information(bundle_info):
return output
def _handle_moved_addons(addon_name, milestone_version, log):
"""Log message that addon version is not compatible with current core.
The function can return path to addon client code, but that can happen
only if ayon-core is used from code (for development), but still
logs a warning.
Args:
addon_name (str): Addon name.
milestone_version (str): Milestone addon version.
log (logging.Logger): Logger object.
Returns:
Union[str, None]: Addon dir or None.
"""
# Handle addons which were moved out of ayon-core
# - Try to fix it by loading it directly from server addons dir in
# ayon-core repository. But that will work only if ayon-core is
# used from code.
addon_dir = os.path.join(
os.path.dirname(os.path.dirname(AYON_CORE_ROOT)),
"server_addon",
addon_name,
"client",
)
if not os.path.exists(addon_dir):
log.error((
"Addon '{}' is not be available."
" Please update applications addon to '{}' or higher."
).format(addon_name, milestone_version))
return None
log.warning((
"Please update '{}' addon to '{}' or higher."
" Using client code from ayon-core repository."
).format(addon_name, milestone_version))
return addon_dir
def _load_ayon_addons(openpype_modules, modules_key, log):
"""Load AYON addons based on information from server.
@ -249,6 +294,7 @@ def _load_ayon_addons(openpype_modules, modules_key, log):
use_dev_path = dev_addon_info.get("enabled", False)
addon_dir = None
milestone_version = MOVED_ADDON_MILESTONE_VERSIONS.get(addon_name)
if use_dev_path:
addon_dir = dev_addon_info["path"]
if not addon_dir or not os.path.exists(addon_dir):
@ -257,6 +303,16 @@ def _load_ayon_addons(openpype_modules, modules_key, log):
).format(addon_name, addon_version, addon_dir))
continue
elif (
milestone_version is not None
and VersionInfo.parse(addon_version) < milestone_version
):
addon_dir = _handle_moved_addons(
addon_name, milestone_version, log
)
if not addon_dir:
continue
elif addons_dir_exists:
folder_name = "{}_{}".format(addon_name, addon_version)
addon_dir = os.path.join(addons_dir, folder_name)
@ -336,66 +392,9 @@ def _load_ayon_addons(openpype_modules, modules_key, log):
return addons_to_skip_in_core
def _load_ayon_core_addons_dir(
ignore_addon_names, openpype_modules, modules_key, log
):
addons_dir = os.path.join(AYON_CORE_ROOT, "addons")
if not os.path.exists(addons_dir):
return
imported_modules = []
# Make sure that addons which already have client code are not loaded
# from core again, with older code
filtered_paths = []
for name in os.listdir(addons_dir):
if name in ignore_addon_names:
continue
path = os.path.join(addons_dir, name)
if os.path.isdir(path):
filtered_paths.append(path)
for path in filtered_paths:
while path in sys.path:
sys.path.remove(path)
sys.path.insert(0, path)
for name in os.listdir(path):
fullpath = os.path.join(path, name)
if os.path.isfile(fullpath):
basename, ext = os.path.splitext(name)
if ext != ".py":
continue
else:
basename = name
try:
module = __import__(basename, fromlist=("",))
for attr_name in dir(module):
attr = getattr(module, attr_name)
if (
inspect.isclass(attr)
and issubclass(attr, AYONAddon)
):
new_import_str = "{}.{}".format(modules_key, basename)
sys.modules[new_import_str] = module
setattr(openpype_modules, basename, module)
imported_modules.append(module)
break
except Exception:
log.error(
"Failed to import addon '{}'.".format(fullpath),
exc_info=True
)
return imported_modules
def _load_addons_in_core(
ignore_addon_names, openpype_modules, modules_key, log
):
_load_ayon_core_addons_dir(
ignore_addon_names, openpype_modules, modules_key, log
)
# Add current directory at first place
# - has small differences in import logic
hosts_dir = os.path.join(AYON_CORE_ROOT, "hosts")

View file

@ -176,9 +176,9 @@ def run(script):
# future versions might remove it.
first_arg = sys.argv[0]
if is_running_from_build():
comp_path = os.path.join(os.environ["AYON_ROOT"], "start.py")
else:
comp_path = os.getenv("AYON_EXECUTABLE")
else:
comp_path = os.path.join(os.environ["AYON_ROOT"], "start.py")
# Compare paths and remove first argument if it is the same as AYON
if Path(first_arg).resolve() == Path(comp_path).resolve():
sys.argv.pop(0)

View file

@ -21,7 +21,7 @@ class BackgroundLoader(api.AfterEffectsLoader):
"""
label = "Load JSON Background"
product_types = {"background"}
representations = ["json"]
representations = {"json"}
def load(self, context, name=None, namespace=None, data=None):
stub = self.get_stub()

View file

@ -20,7 +20,7 @@ class FileLoader(api.AfterEffectsLoader):
"review",
"audio",
}
representations = ["*"]
representations = {"*"}
def load(self, context, name=None, namespace=None, data=None):
stub = self.get_stub()

View file

@ -41,7 +41,6 @@ class CollectAERender(publish.AbstractCollectRender):
def get_instances(self, context):
instances = []
instances_to_remove = []
app_version = CollectAERender.get_stub().get_app_version()
app_version = app_version[0:4]
@ -117,7 +116,10 @@ class CollectAERender(publish.AbstractCollectRender):
fps=fps,
app_version=app_version,
publish_attributes=inst.data.get("publish_attributes", {}),
file_names=[item.file_name for item in render_q]
file_names=[item.file_name for item in render_q],
# The source instance this render instance replaces
source_instance=inst
)
comp = compositions_by_id.get(comp_id)
@ -145,10 +147,7 @@ class CollectAERender(publish.AbstractCollectRender):
instance.families.remove("review")
instances.append(instance)
instances_to_remove.append(inst)
for instance in instances_to_remove:
context.remove(instance)
return instances
def get_expected_files(self, render_instance):

View file

@ -55,8 +55,7 @@ class BlenderAddon(AYONAddon, IHostAddon):
)
# Define Qt binding if not defined
if not env.get("QT_PREFERRED_BINDING"):
env["QT_PREFERRED_BINDING"] = "PySide2"
env.pop("QT_PREFERRED_BINDING", None)
def get_launch_hook_paths(self, app):
if app.host_name != self.host_name:

View file

@ -31,7 +31,7 @@ class InstallPySideToBlender(PreLaunchHook):
def inner_execute(self):
# Get blender's python directory
version_regex = re.compile(r"^[2-4]\.[0-9]+$")
version_regex = re.compile(r"^([2-4])\.[0-9]+$")
platform = system().lower()
executable = self.launch_context.executable.executable_path
@ -42,7 +42,8 @@ class InstallPySideToBlender(PreLaunchHook):
if os.path.basename(executable).lower() != expected_executable:
self.log.info((
f"Executable does not lead to {expected_executable} file."
"Can't determine blender's python to check/install PySide2."
"Can't determine blender's python to check/install"
" Qt binding."
))
return
@ -73,6 +74,15 @@ class InstallPySideToBlender(PreLaunchHook):
return
version_subfolder = version_subfolders[0]
before_blender_4 = False
if int(version_regex.match(version_subfolder).group(1)) < 4:
before_blender_4 = True
# Blender 4 has Python 3.11 which does not support 'PySide2'
# QUESTION could we always install PySide6?
qt_binding = "PySide2" if before_blender_4 else "PySide6"
# Use PySide6 6.6.3 because 6.7.0 had a bug
# - 'QTextEdit' can't be added to 'QBoxLayout'
qt_binding_version = None if before_blender_4 else "6.6.3"
python_dir = os.path.join(versions_dir, version_subfolder, "python")
python_lib = os.path.join(python_dir, "lib")
@ -116,22 +126,41 @@ class InstallPySideToBlender(PreLaunchHook):
return
# Check if PySide2 is installed and skip if yes
if self.is_pyside_installed(python_executable):
if self.is_pyside_installed(python_executable, qt_binding):
self.log.debug("Blender has already installed PySide2.")
return
# Install PySide2 in blender's python
if platform == "windows":
result = self.install_pyside_windows(python_executable)
result = self.install_pyside_windows(
python_executable,
qt_binding,
qt_binding_version,
before_blender_4,
)
else:
result = self.install_pyside(python_executable)
result = self.install_pyside(
python_executable,
qt_binding,
qt_binding_version,
)
if result:
self.log.info("Successfully installed PySide2 module to blender.")
self.log.info(
f"Successfully installed {qt_binding} module to blender."
)
else:
self.log.warning("Failed to install PySide2 module to blender.")
self.log.warning(
f"Failed to install {qt_binding} module to blender."
)
def install_pyside_windows(self, python_executable):
def install_pyside_windows(
self,
python_executable,
qt_binding,
qt_binding_version,
before_blender_4,
):
"""Install PySide2 python module to blender's python.
Installation requires administration rights that's why it is required
@ -139,7 +168,6 @@ class InstallPySideToBlender(PreLaunchHook):
administration rights.
"""
try:
import win32api
import win32con
import win32process
import win32event
@ -150,12 +178,37 @@ class InstallPySideToBlender(PreLaunchHook):
self.log.warning("Couldn't import \"pywin32\" modules")
return
if qt_binding_version:
qt_binding = f"{qt_binding}=={qt_binding_version}"
try:
# Parameters
# - use "-m pip" as module pip to install PySide2 and argument
# "--ignore-installed" is to force install module to blender's
# site-packages and make sure it is binary compatible
parameters = "-m pip install --ignore-installed PySide2"
fake_exe = "fake.exe"
site_packages_prefix = os.path.dirname(
os.path.dirname(python_executable)
)
args = [
fake_exe,
"-m",
"pip",
"install",
"--ignore-installed",
qt_binding,
]
if not before_blender_4:
# Define prefix for site package
# Python in blender 4.x is installing packages in AppData and
# not in blender's directory.
args.extend(["--prefix", site_packages_prefix])
parameters = (
subprocess.list2cmdline(args)
.lstrip(fake_exe)
.lstrip(" ")
)
# Execute command and ask for administrator's rights
process_info = ShellExecuteEx(
@ -173,20 +226,29 @@ class InstallPySideToBlender(PreLaunchHook):
except pywintypes.error:
pass
def install_pyside(self, python_executable):
"""Install PySide2 python module to blender's python."""
def install_pyside(
self,
python_executable,
qt_binding,
qt_binding_version,
):
"""Install Qt binding python module to blender's python."""
if qt_binding_version:
qt_binding = f"{qt_binding}=={qt_binding_version}"
try:
# Parameters
# - use "-m pip" as module pip to install PySide2 and argument
# - use "-m pip" as module pip to install qt binding and argument
# "--ignore-installed" is to force install module to blender's
# site-packages and make sure it is binary compatible
# TODO find out if blender 4.x on linux/darwin does install
# qt binding to correct place.
args = [
python_executable,
"-m",
"pip",
"install",
"--ignore-installed",
"PySide2",
qt_binding,
]
process = subprocess.Popen(
args, stdout=subprocess.PIPE, universal_newlines=True
@ -203,13 +265,15 @@ class InstallPySideToBlender(PreLaunchHook):
except subprocess.SubprocessError:
pass
def is_pyside_installed(self, python_executable):
def is_pyside_installed(self, python_executable, qt_binding):
"""Check if PySide2 module is in blender's pip list.
Check that PySide2 is installed directly in blender's site-packages.
It is possible that it is installed in user's site-packages but that
may be incompatible with blender's python.
"""
qt_binding_low = qt_binding.lower()
# Get pip list from blender's python executable
args = [python_executable, "-m", "pip", "list"]
process = subprocess.Popen(args, stdout=subprocess.PIPE)
@ -226,6 +290,6 @@ class InstallPySideToBlender(PreLaunchHook):
if not line:
continue
package_name = line[0:package_len].strip()
if package_name.lower() == "pyside2":
if package_name.lower() == qt_binding_low:
return True
return False

View file

@ -43,7 +43,7 @@ class AppendBlendLoader(plugin.AssetLoader):
so you could also use it as a new base.
"""
representations = ["blend"]
representations = {"blend"}
product_types = {"workfile"}
label = "Append Workfile"
@ -68,7 +68,7 @@ class ImportBlendLoader(plugin.AssetLoader):
so you could also use it as a new base.
"""
representations = ["blend"]
representations = {"blend"}
product_types = {"workfile"}
label = "Import Workfile"

View file

@ -27,7 +27,7 @@ class CacheModelLoader(plugin.AssetLoader):
At least for now it only supports Alembic files.
"""
product_types = {"model", "pointcache", "animation"}
representations = ["abc"]
representations = {"abc"}
label = "Load Alembic"
icon = "code-fork"

View file

@ -25,7 +25,7 @@ class BlendActionLoader(plugin.AssetLoader):
"""
product_types = {"action"}
representations = ["blend"]
representations = {"blend"}
label = "Link Action"
icon = "code-fork"

View file

@ -17,7 +17,7 @@ class BlendAnimationLoader(plugin.AssetLoader):
"""
product_types = {"animation"}
representations = ["blend"]
representations = {"blend"}
label = "Link Animation"
icon = "code-fork"

View file

@ -21,7 +21,7 @@ class AudioLoader(plugin.AssetLoader):
"""Load audio in Blender."""
product_types = {"audio"}
representations = ["wav"]
representations = {"wav"}
label = "Load Audio"
icon = "volume-up"

View file

@ -21,7 +21,7 @@ class BlendLoader(plugin.AssetLoader):
"""Load assets from a .blend file."""
product_types = {"model", "rig", "layout", "camera"}
representations = ["blend"]
representations = {"blend"}
label = "Append Blend"
icon = "code-fork"

View file

@ -19,7 +19,7 @@ class BlendSceneLoader(plugin.AssetLoader):
"""Load assets from a .blend file."""
product_types = {"blendScene"}
representations = ["blend"]
representations = {"blend"}
label = "Append Blend"
icon = "code-fork"

View file

@ -24,7 +24,7 @@ class AbcCameraLoader(plugin.AssetLoader):
"""
product_types = {"camera"}
representations = ["abc"]
representations = {"abc"}
label = "Load Camera (ABC)"
icon = "code-fork"

View file

@ -24,7 +24,7 @@ class FbxCameraLoader(plugin.AssetLoader):
"""
product_types = {"camera"}
representations = ["fbx"]
representations = {"fbx"}
label = "Load Camera (FBX)"
icon = "code-fork"

View file

@ -24,7 +24,7 @@ class FbxModelLoader(plugin.AssetLoader):
"""
product_types = {"model", "rig"}
representations = ["fbx"]
representations = {"fbx"}
label = "Load FBX"
icon = "code-fork"

View file

@ -27,7 +27,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
"""Load layout published from Unreal."""
product_types = {"layout"}
representations = ["json"]
representations = {"json"}
label = "Load Layout"
icon = "code-fork"
@ -167,7 +167,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
asset_group.empty_display_type = 'SINGLE_ARROW'
avalon_container.objects.link(asset_group)
self._process(libpath, asset, asset_group, None)
self._process(libpath, asset_name, asset_group, None)
bpy.context.scene.collection.objects.link(asset_group)

View file

@ -24,7 +24,7 @@ class BlendLookLoader(plugin.AssetLoader):
"""
product_types = {"look"}
representations = ["json"]
representations = {"json"}
label = "Load Look"
icon = "code-fork"

View file

@ -18,7 +18,7 @@ class LoadClip(opfapi.ClipLoader):
"""
product_types = {"render2d", "source", "plate", "render", "review"}
representations = ["*"]
representations = {"*"}
extensions = set(
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
)

View file

@ -17,7 +17,7 @@ class LoadClipBatch(opfapi.ClipLoader):
"""
product_types = {"render2d", "source", "plate", "render", "review"}
representations = ["*"]
representations = {"*"}
extensions = set(
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
)

View file

@ -3,8 +3,8 @@ import sys
import re
import contextlib
from ayon_core.lib import Logger
from ayon_core.lib import Logger, BoolDef, UILabelDef
from ayon_core.style import load_stylesheet
from ayon_core.pipeline import registered_host
from ayon_core.pipeline.create import CreateContext
from ayon_core.pipeline.context_tools import get_current_folder_entity
@ -181,7 +181,6 @@ def validate_comp_prefs(comp=None, force_repair=False):
from . import menu
from ayon_core.tools.utils import SimplePopup
from ayon_core.style import load_stylesheet
dialog = SimplePopup(parent=menu.menu)
dialog.setWindowTitle("Fusion comp has invalid configuration")
@ -340,9 +339,7 @@ def prompt_reset_context():
from ayon_core.tools.attribute_defs.dialog import (
AttributeDefinitionsDialog
)
from ayon_core.style import load_stylesheet
from ayon_core.lib import BoolDef, UILabelDef
from qtpy import QtWidgets, QtCore
from qtpy import QtCore
definitions = [
UILabelDef(

View file

@ -85,7 +85,6 @@ class InstallPySideToFusion(PreLaunchHook):
administration rights.
"""
try:
import win32api
import win32con
import win32process
import win32event

View file

@ -17,7 +17,7 @@ class FusionSetFrameRangeLoader(load.LoaderPlugin):
"pointcache",
"render",
}
representations = ["*"]
representations = {"*"}
extensions = {"*"}
label = "Set frame range"
@ -54,7 +54,7 @@ class FusionSetFrameRangeWithHandlesLoader(load.LoaderPlugin):
"pointcache",
"render",
}
representations = ["*"]
representations = {"*"}
label = "Set frame range (with handles)"
order = 12

View file

@ -13,7 +13,7 @@ class FusionLoadAlembicMesh(load.LoaderPlugin):
"""Load Alembic mesh into Fusion"""
product_types = {"pointcache", "model"}
representations = ["*"]
representations = {"*"}
extensions = {"abc"}
label = "Load alembic mesh"

View file

@ -13,7 +13,7 @@ class FusionLoadFBXMesh(load.LoaderPlugin):
"""Load FBX mesh into Fusion"""
product_types = {"*"}
representations = ["*"]
representations = {"*"}
extensions = {
"3ds",
"amc",

View file

@ -137,7 +137,7 @@ class FusionLoadSequence(load.LoaderPlugin):
"image",
"online",
}
representations = ["*"]
representations = {"*"}
extensions = set(
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
)

View file

@ -17,7 +17,7 @@ class FusionLoadUSD(load.LoaderPlugin):
"""
product_types = {"*"}
representations = ["*"]
representations = {"*"}
extensions = {"usd", "usda", "usdz"}
label = "Load USD"

View file

@ -15,7 +15,7 @@ class FusionLoadWorkfile(load.LoaderPlugin):
"""Load the content of a workfile into Fusion"""
product_types = {"workfile"}
representations = ["*"]
representations = {"*"}
extensions = {"comp"}
label = "Load Workfile"

View file

@ -37,14 +37,13 @@ class CollectFusionRender(
aspect_x = comp_frame_format_prefs["AspectX"]
aspect_y = comp_frame_format_prefs["AspectY"]
instances = []
instances_to_remove = []
current_file = context.data["currentFile"]
version = context.data["version"]
project_entity = context.data["projectEntity"]
instances = []
for inst in context:
if not inst.data.get("active", True):
continue
@ -91,7 +90,10 @@ class CollectFusionRender(
frameStep=1,
fps=comp_frame_format_prefs.get("Rate"),
app_version=comp.GetApp().Version,
publish_attributes=inst.data.get("publish_attributes", {})
publish_attributes=inst.data.get("publish_attributes", {}),
# The source instance this render instance replaces
source_instance=inst
)
render_target = inst.data["creator_attributes"]["render_target"]
@ -114,13 +116,7 @@ class CollectFusionRender(
# to skip ExtractReview locally
instance.families.remove("review")
# add new instance to the list and remove the original
# instance since it is not needed anymore
instances.append(instance)
instances_to_remove.append(inst)
for instance in instances_to_remove:
context.remove(instance)
return instances

View file

@ -590,7 +590,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
"reference",
"review",
}
representations = ["*"]
representations = {"*"}
extensions = {"jpeg", "png", "jpg"}
def load(self, context, name=None, namespace=None, data=None):

View file

@ -36,7 +36,7 @@ class ImportAudioLoader(load.LoaderPlugin):
"""Import audio."""
product_types = {"shot", "audio"}
representations = ["wav"]
representations = {"wav"}
label = "Import Audio"
def load(self, context, name=None, namespace=None, data=None):

View file

@ -234,7 +234,7 @@ class BackgroundLoader(load.LoaderPlugin):
Stores the imported asset in a container named after the asset.
"""
product_types = {"background"}
representations = ["json"]
representations = {"json"}
def load(self, context, name=None, namespace=None, data=None):

View file

@ -28,7 +28,7 @@ class ImageSequenceLoader(load.LoaderPlugin):
"reference",
"review",
}
representations = ["*"]
representations = {"*"}
extensions = {"jpeg", "png", "jpg"}
def load(self, context, name=None, namespace=None, data=None):

View file

@ -12,7 +12,7 @@ class ImportPaletteLoader(load.LoaderPlugin):
"""Import palettes."""
product_types = {"palette", "harmony.palette"}
representations = ["plt"]
representations = {"plt"}
label = "Import Palette"
def load(self, context, name=None, namespace=None, data=None):

View file

@ -24,7 +24,7 @@ class TemplateLoader(load.LoaderPlugin):
"""
product_types = {"template", "workfile"}
representations = ["*"]
representations = {"*"}
label = "Load Template"
icon = "gift"

View file

@ -14,7 +14,7 @@ class ImportTemplateLoader(load.LoaderPlugin):
"""Import templates."""
product_types = {"harmony.template", "workfile"}
representations = ["*"]
representations = {"*"}
label = "Import Template"
def load(self, context, name=None, namespace=None, data=None):
@ -61,5 +61,5 @@ class ImportWorkfileLoader(ImportTemplateLoader):
"""Import workfiles."""
product_types = {"workfile"}
representations = ["zip"]
representations = {"zip"}
label = "Import Workfile"

View file

@ -16,7 +16,7 @@ class LoadClip(phiero.SequenceLoader):
"""
product_types = {"render2d", "source", "plate", "render", "review"}
representations = ["*"]
representations = {"*"}
extensions = set(
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
)

View file

@ -15,7 +15,7 @@ class LoadEffects(load.LoaderPlugin):
"""Loading colorspace soft effect exported from nukestudio"""
product_types = {"effect"}
representations = ["*"]
representations = {"*"}
extension = {"json"}
label = "Load Effects"

View file

@ -92,10 +92,6 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
folder_path, folder_name = self._get_folder_data(tag_data)
product_name = tag_data.get("productName")
if product_name is None:
product_name = tag_data["subset"]
families = [str(f) for f in tag_data["families"]]
# TODO: remove backward compatibility
@ -293,7 +289,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
label += " {}".format(product_name)
data.update({
"name": "{}_{}".format(folder_path, subset),
"name": "{}_{}".format(folder_path, product_name),
"label": label,
"productName": product_name,
"productType": product_type,

View file

@ -1001,6 +1001,82 @@ def add_self_publish_button(node):
node.setParmTemplateGroup(template)
def get_scene_viewer():
"""
Return an instance of a visible viewport.
There may be many, some could be closed, any visible are current
Returns:
Optional[hou.SceneViewer]: A scene viewer, if any.
"""
panes = hou.ui.paneTabs()
panes = [x for x in panes if x.type() == hou.paneTabType.SceneViewer]
panes = sorted(panes, key=lambda x: x.isCurrentTab())
if panes:
return panes[-1]
return None
def sceneview_snapshot(
sceneview,
filepath="$HIP/thumbnails/$HIPNAME.$F4.jpg",
frame_start=None,
frame_end=None):
"""Take a snapshot of your scene view.
It takes snapshot of your scene view for the given frame range.
So, it's capable of generating snapshots image sequence.
It works in different Houdini context e.g. Objects, Solaris
Example:
This is how the function can be used::
from ayon_core.hosts.houdini.api import lib
sceneview = hou.ui.paneTabOfType(hou.paneTabType.SceneViewer)
lib.sceneview_snapshot(sceneview)
Notes:
.png output will render poorly, so use .jpg.
How it works:
Get the current sceneviewer (may be more than one or hidden)
and screengrab the perspective viewport to a file in the
publish location to be picked up with the publish.
Credits:
https://www.sidefx.com/forum/topic/42808/?page=1#post-354796
Args:
sceneview (hou.SceneViewer): The scene view pane from which you want
to take a snapshot.
filepath (str): thumbnail filepath. it expects `$F4` token
when frame_end is bigger than frame_star other wise
each frame will override its predecessor.
frame_start (int): the frame at which snapshot starts
frame_end (int): the frame at which snapshot ends
"""
if frame_start is None:
frame_start = hou.frame()
if frame_end is None:
frame_end = frame_start
if not isinstance(sceneview, hou.SceneViewer):
log.debug("Wrong Input. {} is not of type hou.SceneViewer."
.format(sceneview))
return
viewport = sceneview.curViewport()
flip_settings = sceneview.flipbookSettings().stash()
flip_settings.frameRange((frame_start, frame_end))
flip_settings.output(filepath)
flip_settings.outputToMPlay(False)
sceneview.flipbook(viewport, flip_settings)
log.debug("A snapshot of sceneview has been saved to: {}".format(filepath))
def update_content_on_context_change():
"""Update all Creator instances to current asset"""
host = registered_host()

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating alembic camera products."""
from ayon_core.hosts.houdini.api import plugin
from ayon_core.pipeline import CreatedInstance, CreatorError
from ayon_core.pipeline import CreatorError
import hou
@ -23,7 +23,7 @@ class CreateAlembicCamera(plugin.HoudiniCreator):
instance = super(CreateAlembicCamera, self).create(
product_name,
instance_data,
pre_create_data) # type: CreatedInstance
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))
parms = {

View file

@ -29,7 +29,7 @@ class CreateArnoldAss(plugin.HoudiniCreator):
instance = super(CreateArnoldAss, self).create(
product_name,
instance_data,
pre_create_data) # type: plugin.CreatedInstance
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))

View file

@ -31,7 +31,7 @@ class CreateArnoldRop(plugin.HoudiniCreator):
instance = super(CreateArnoldRop, self).create(
product_name,
instance_data,
pre_create_data) # type: plugin.CreatedInstance
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating pointcache bgeo files."""
from ayon_core.hosts.houdini.api import plugin
from ayon_core.pipeline import CreatedInstance, CreatorError
from ayon_core.pipeline import CreatorError
import hou
from ayon_core.lib import EnumDef, BoolDef
@ -25,7 +25,7 @@ class CreateBGEO(plugin.HoudiniCreator):
instance = super(CreateBGEO, self).create(
product_name,
instance_data,
pre_create_data) # type: CreatedInstance
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating composite sequences."""
from ayon_core.hosts.houdini.api import plugin
from ayon_core.pipeline import CreatedInstance, CreatorError
from ayon_core.pipeline import CreatorError
import hou
@ -25,7 +25,7 @@ class CreateCompositeSequence(plugin.HoudiniCreator):
instance = super(CreateCompositeSequence, self).create(
product_name,
instance_data,
pre_create_data) # type: CreatedInstance
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))
filepath = "{}{}".format(

View file

@ -78,7 +78,7 @@ class CreateHDA(plugin.HoudiniCreator):
instance = super(CreateHDA, self).create(
product_name,
instance_data,
pre_create_data) # type: plugin.CreatedInstance
pre_create_data)
return instance

View file

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Creator plugin to create Karma ROP."""
from ayon_core.hosts.houdini.api import plugin
from ayon_core.pipeline import CreatedInstance
from ayon_core.lib import BoolDef, EnumDef, NumberDef
@ -25,7 +24,7 @@ class CreateKarmaROP(plugin.HoudiniCreator):
instance = super(CreateKarmaROP, self).create(
product_name,
instance_data,
pre_create_data) # type: CreatedInstance
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))

View file

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating pointcache alembics."""
from ayon_core.hosts.houdini.api import plugin
from ayon_core.pipeline import CreatedInstance
from ayon_core.lib import BoolDef
@ -22,7 +21,7 @@ class CreateMantraIFD(plugin.HoudiniCreator):
instance = super(CreateMantraIFD, self).create(
product_name,
instance_data,
pre_create_data) # type: CreatedInstance
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))

View file

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Creator plugin to create Mantra ROP."""
from ayon_core.hosts.houdini.api import plugin
from ayon_core.pipeline import CreatedInstance
from ayon_core.lib import EnumDef, BoolDef
@ -28,7 +27,7 @@ class CreateMantraROP(plugin.HoudiniCreator):
instance = super(CreateMantraROP, self).create(
product_name,
instance_data,
pre_create_data) # type: CreatedInstance
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))

View file

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating USDs."""
from ayon_core.hosts.houdini.api import plugin
from ayon_core.pipeline import CreatedInstance
import hou
@ -22,7 +21,7 @@ class CreateUSD(plugin.HoudiniCreator):
instance = super(CreateUSD, self).create(
product_name,
instance_data,
pre_create_data) # type: CreatedInstance
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))

View file

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating USD renders."""
from ayon_core.hosts.houdini.api import plugin
from ayon_core.pipeline import CreatedInstance
class CreateUSDRender(plugin.HoudiniCreator):
@ -23,7 +22,7 @@ class CreateUSDRender(plugin.HoudiniCreator):
instance = super(CreateUSDRender, self).create(
product_name,
instance_data,
pre_create_data) # type: CreatedInstance
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))

View file

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating VDB Caches."""
from ayon_core.hosts.houdini.api import plugin
from ayon_core.pipeline import CreatedInstance
from ayon_core.lib import BoolDef
import hou
@ -26,7 +25,7 @@ class CreateVDBCache(plugin.HoudiniCreator):
instance = super(CreateVDBCache, self).create(
product_name,
instance_data,
pre_create_data) # type: CreatedInstance
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))
file_path = "{}{}".format(

View file

@ -3,7 +3,7 @@
import hou
from ayon_core.hosts.houdini.api import plugin
from ayon_core.pipeline import CreatedInstance, CreatorError
from ayon_core.pipeline import CreatorError
from ayon_core.lib import EnumDef, BoolDef
@ -31,7 +31,7 @@ class CreateVrayROP(plugin.HoudiniCreator):
instance = super(CreateVrayROP, self).create(
product_name,
instance_data,
pre_create_data) # type: CreatedInstance
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))

View file

@ -15,7 +15,7 @@ class SetFrameRangeLoader(load.LoaderPlugin):
"vdbcache",
"usd",
}
representations = ["abc", "vdb", "usd"]
representations = {"abc", "vdb", "usd"}
label = "Set frame range"
order = 11
@ -52,7 +52,7 @@ class SetFrameRangeWithHandlesLoader(load.LoaderPlugin):
"vdbcache",
"usd",
}
representations = ["abc", "vdb", "usd"]
representations = {"abc", "vdb", "usd"}
label = "Set frame range (with handles)"
order = 12

View file

@ -11,7 +11,7 @@ class AbcLoader(load.LoaderPlugin):
product_types = {"model", "animation", "pointcache", "gpuCache"}
label = "Load Alembic"
representations = ["*"]
representations = {"*"}
extensions = {"abc"}
order = -10
icon = "code-fork"

View file

@ -11,7 +11,7 @@ class AbcArchiveLoader(load.LoaderPlugin):
product_types = {"model", "animation", "pointcache", "gpuCache"}
label = "Load Alembic as Archive"
representations = ["*"]
representations = {"*"}
extensions = {"abc"}
order = -5
icon = "code-fork"

View file

@ -13,7 +13,7 @@ class AssLoader(load.LoaderPlugin):
product_types = {"ass"}
label = "Load Arnold Procedural"
representations = ["ass"]
representations = {"ass"}
order = -10
icon = "code-fork"
color = "orange"

View file

@ -14,9 +14,9 @@ class BgeoLoader(load.LoaderPlugin):
label = "Load bgeo"
product_types = {"model", "pointcache", "bgeo"}
representations = [
representations = {
"bgeo", "bgeosc", "bgeogz",
"bgeo.sc", "bgeo.gz", "bgeo.lzma", "bgeo.bz2"]
"bgeo.sc", "bgeo.gz", "bgeo.lzma", "bgeo.bz2"}
order = -10
icon = "code-fork"
color = "orange"

View file

@ -89,7 +89,7 @@ class CameraLoader(load.LoaderPlugin):
product_types = {"camera"}
label = "Load Camera (abc)"
representations = ["abc"]
representations = {"abc"}
order = -10
icon = "code-fork"

View file

@ -17,7 +17,7 @@ class FbxLoader(load.LoaderPlugin):
order = -10
product_types = {"*"}
representations = ["*"]
representations = {"*"}
extensions = {"fbx"}
def load(self, context, name=None, namespace=None, data=None):

View file

@ -22,7 +22,7 @@ class FilePathLoader(load.LoaderPlugin):
icon = "link"
color = "white"
product_types = {"*"}
representations = ["*"]
representations = {"*"}
def load(self, context, name=None, namespace=None, data=None):

View file

@ -12,7 +12,7 @@ class HdaLoader(load.LoaderPlugin):
product_types = {"hda"}
label = "Load Hda"
representations = ["hda"]
representations = {"hda"}
order = -10
icon = "code-fork"
color = "orange"

View file

@ -54,7 +54,7 @@ class ImageLoader(load.LoaderPlugin):
"online",
}
label = "Load Image (COP2)"
representations = ["*"]
representations = {"*"}
order = -10
icon = "code-fork"

View file

@ -15,7 +15,7 @@ class RedshiftProxyLoader(load.LoaderPlugin):
product_types = {"redshiftproxy"}
label = "Load Redshift Proxy"
representations = ["rs"]
representations = {"rs"}
order = -10
icon = "code-fork"
color = "orange"

View file

@ -14,7 +14,7 @@ class USDSublayerLoader(load.LoaderPlugin):
"usdCamera",
}
label = "Sublayer USD"
representations = ["usd", "usda", "usdlc", "usdnc", "abc"]
representations = {"usd", "usda", "usdlc", "usdnc", "abc"}
order = 1
icon = "code-fork"

View file

@ -14,7 +14,7 @@ class USDReferenceLoader(load.LoaderPlugin):
"usdCamera",
}
label = "Reference USD"
representations = ["usd", "usda", "usdlc", "usdnc", "abc"]
representations = {"usd", "usda", "usdlc", "usdnc", "abc"}
order = -8
icon = "code-fork"

View file

@ -9,7 +9,7 @@ class SopUsdImportLoader(load.LoaderPlugin):
label = "Load USD to SOPs"
product_types = {"*"}
representations = ["usd"]
representations = {"usd"}
order = -6
icon = "code-fork"
color = "orange"

View file

@ -13,7 +13,7 @@ class VdbLoader(load.LoaderPlugin):
product_types = {"vdbcache"}
label = "Load VDB"
representations = ["vdb"]
representations = {"vdb"}
order = -10
icon = "code-fork"
color = "orange"

View file

@ -10,7 +10,7 @@ class ShowInUsdview(load.LoaderPlugin):
"""Open USD file in usdview"""
label = "Show in usdview"
representations = ["*"]
representations = {"*"}
product_types = {"*"}
extensions = {"usd", "usda", "usdlc", "usdnc", "abc"}
order = 15

View file

@ -1,9 +1,21 @@
from collections import deque
import pyblish.api
from ayon_core.pipeline import registered_host
def collect_input_containers(nodes):
def get_container_members(container):
node = container["node"]
# Usually the loaded containers don't have any complex references
# and the contained children should be all we need. So we disregard
# checking for .references() on the nodes.
members = set(node.allSubChildren())
members.add(node) # include the node itself
return members
def collect_input_containers(containers, nodes):
"""Collect containers that contain any of the node in `nodes`.
This will return any loaded Avalon container that contains at least one of
@ -11,30 +23,13 @@ def collect_input_containers(nodes):
there are member nodes of that container.
Returns:
list: Input avalon containers
list: Loaded containers that contain the `nodes`
"""
# Lookup by node ids
lookup = frozenset(nodes)
containers = []
host = registered_host()
for container in host.ls():
node = container["node"]
# Usually the loaded containers don't have any complex references
# and the contained children should be all we need. So we disregard
# checking for .references() on the nodes.
members = set(node.allSubChildren())
members.add(node) # include the node itself
# If there's an intersection
if not lookup.isdisjoint(members):
containers.append(container)
return containers
# Assume the containers have collected their cached '_members' data
# in the collector.
return [container for container in containers
if any(node in container["_members"] for node in nodes)]
def iter_upstream(node):
@ -54,7 +49,7 @@ def iter_upstream(node):
)
# Initialize process queue with the node's ancestors itself
queue = list(upstream)
queue = deque(upstream)
collected = set(upstream)
# Traverse upstream references for all nodes and yield them as we
@ -72,6 +67,10 @@ def iter_upstream(node):
# Include the references' ancestors that have not been collected yet.
for reference in references:
if reference in collected:
# Might have been collected in previous iteration
continue
ancestors = reference.inputAncestors(
include_ref_inputs=True, follow_subnets=True
)
@ -108,13 +107,32 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin):
)
return
# Collect all upstream parents
nodes = list(iter_upstream(output))
nodes.append(output)
# For large scenes the querying of "host.ls()" can be relatively slow
# e.g. up to a second. Many instances calling it easily slows this
# down. As such, we cache it so we trigger it only once.
# todo: Instead of hidden cache make "CollectContainers" plug-in
cache_key = "__cache_containers"
scene_containers = instance.context.data.get(cache_key, None)
if scene_containers is None:
# Query the scenes' containers if there's no cache yet
host = registered_host()
scene_containers = list(host.ls())
for container in scene_containers:
# Embed the members into the container dictionary
container_members = set(get_container_members(container))
container["_members"] = container_members
instance.context.data[cache_key] = scene_containers
# Collect containers for the given set of nodes
containers = collect_input_containers(nodes)
inputs = []
if scene_containers:
# Collect all upstream parents
nodes = list(iter_upstream(output))
nodes.append(output)
# Collect containers for the given set of nodes
containers = collect_input_containers(scene_containers, nodes)
inputs = [c["representation"] for c in containers]
inputs = [c["representation"] for c in containers]
instance.data["inputRepresentations"] = inputs
self.log.debug("Collected inputs: %s" % inputs)

View file

@ -0,0 +1,55 @@
import pyblish.api
import tempfile
from ayon_core.pipeline import publish
from ayon_core.hosts.houdini.api import lib
from ayon_core.hosts.houdini.api.pipeline import IS_HEADLESS
class ExtractActiveViewThumbnail(publish.Extractor):
"""Set instance thumbnail to a screengrab of current active viewport.
This makes it so that if an instance does not have a thumbnail set yet that
it will get a thumbnail of the currently active view at the time of
publishing as a fallback.
"""
order = pyblish.api.ExtractorOrder + 0.49
label = "Extract Active View Thumbnail"
families = ["workfile"]
hosts = ["houdini"]
def process(self, instance):
if IS_HEADLESS:
self.log.debug(
"Skip extraction of active view thumbnail, due to being in"
"headless mode."
)
return
thumbnail = instance.data.get("thumbnailPath")
if thumbnail:
# A thumbnail was already set for this instance
return
view_thumbnail = self.get_view_thumbnail(instance)
if not view_thumbnail:
return
self.log.debug("Setting instance thumbnail path to: {}"
.format(view_thumbnail)
)
instance.data["thumbnailPath"] = view_thumbnail
def get_view_thumbnail(self, instance):
sceneview = lib.get_scene_viewer()
if sceneview is None:
self.log.debug("Skipping Extract Active View Thumbnail"
" because no scene view was detected.")
return
with tempfile.NamedTemporaryFile("w", suffix=".jpg", delete=False) as tmp:
lib.sceneview_snapshot(sceneview, tmp.name)
thumbnail_path = tmp.name
instance.context.data["cleanupFullPaths"].append(thumbnail_path)
return thumbnail_path

View file

@ -3,7 +3,6 @@ import pyblish.api
from ayon_core.lib import version_up
from ayon_core.pipeline import registered_host
from ayon_core.pipeline.publish import get_errored_plugins_from_context
from ayon_core.hosts.houdini.api import HoudiniHost
from ayon_core.pipeline.publish import KnownPublishError
@ -39,7 +38,7 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin):
)
# Filename must not have changed since collecting
host = registered_host() # type: HoudiniHost
host = registered_host()
current_file = host.current_file()
if context.data["currentFile"] != current_file:
raise KnownPublishError(

View file

@ -19,7 +19,7 @@ class FbxLoader(load.LoaderPlugin):
"""Fbx Loader."""
product_types = {"camera"}
representations = ["fbx"]
representations = {"fbx"}
order = -9
icon = "code-fork"
color = "white"

View file

@ -78,7 +78,7 @@ class MaxSceneLoader(load.LoaderPlugin):
"model",
}
representations = ["max"]
representations = {"max"}
order = -8
icon = "code-fork"
color = "green"

View file

@ -16,7 +16,7 @@ class ModelAbcLoader(load.LoaderPlugin):
product_types = {"model"}
label = "Load Model with Alembic"
representations = ["abc"]
representations = {"abc"}
order = -10
icon = "code-fork"
color = "orange"

View file

@ -18,7 +18,7 @@ class FbxModelLoader(load.LoaderPlugin):
"""Fbx Model Loader."""
product_types = {"model"}
representations = ["fbx"]
representations = {"fbx"}
order = -9
icon = "code-fork"
color = "white"

View file

@ -20,7 +20,7 @@ class ObjLoader(load.LoaderPlugin):
"""Obj Loader."""
product_types = {"model"}
representations = ["obj"]
representations = {"obj"}
order = -9
icon = "code-fork"
color = "white"

View file

@ -24,7 +24,7 @@ class ModelUSDLoader(load.LoaderPlugin):
product_types = {"model"}
label = "Load Model(USD)"
representations = ["usda"]
representations = {"usda"}
order = -10
icon = "code-fork"
color = "orange"

View file

@ -20,7 +20,7 @@ class AbcLoader(load.LoaderPlugin):
product_types = {"camera", "animation", "pointcache"}
label = "Load Alembic"
representations = ["abc"]
representations = {"abc"}
order = -10
icon = "code-fork"
color = "orange"

View file

@ -23,7 +23,7 @@ class OxAbcLoader(load.LoaderPlugin):
product_types = {"camera", "animation", "pointcache"}
label = "Load Alembic with Ornatrix"
representations = ["abc"]
representations = {"abc"}
order = -10
icon = "code-fork"
color = "orange"

View file

@ -18,7 +18,7 @@ class PointCloudLoader(load.LoaderPlugin):
"""Point Cloud Loader."""
product_types = {"pointcloud"}
representations = ["prt"]
representations = {"prt"}
order = -8
icon = "code-fork"
color = "green"

View file

@ -24,7 +24,7 @@ class RedshiftProxyLoader(load.LoaderPlugin):
label = "Load Redshift Proxy"
product_types = {"redshiftproxy"}
representations = ["rs"]
representations = {"rs"}
order = -9
icon = "code-fork"
color = "white"

View file

@ -17,7 +17,7 @@ class TyCacheLoader(load.LoaderPlugin):
"""TyCache Loader."""
product_types = {"tycache"}
representations = ["tyc"]
representations = {"tyc"}
order = -8
icon = "code-fork"
color = "green"

View file

@ -22,15 +22,15 @@ class MayaAddon(AYONAddon, IHostAddon):
if norm_path not in new_python_paths:
new_python_paths.append(norm_path)
# add vendor path
new_python_paths.append(
os.path.join(MAYA_ROOT_DIR, "vendor", "python")
)
env["PYTHONPATH"] = os.pathsep.join(new_python_paths)
# Set default environments
envs = {
"AYON_LOG_NO_COLORS": "1",
# For python module 'qtpy'
"QT_API": "PySide2",
# For python module 'Qt'
"QT_PREFERRED_BINDING": "PySide2"
}
for key, value in envs.items():
env[key] = value

View file

@ -2,8 +2,6 @@
"""Tools to work with FBX."""
import logging
from pyblish.api import Instance
from maya import cmds # noqa
import maya.mel as mel # noqa
from ayon_core.hosts.maya.api.lib import maintained_selection
@ -146,7 +144,6 @@ class FBXExtractor:
return options
def set_options_from_instance(self, instance):
# type: (Instance) -> None
"""Sets FBX export options from data in the instance.
Args:

View file

@ -1917,6 +1917,29 @@ def apply_attributes(attributes, nodes_by_id):
set_attribute(attr, value, node)
def is_valid_reference_node(reference_node):
"""Return whether Maya considers the reference node a valid reference.
Maya might report an error when using `maya.cmds.referenceQuery`:
Reference node 'reference_node' is not associated with a reference file.
Note that this does *not* check whether the reference node points to an
existing file. Instead it only returns whether maya considers it valid
and thus is not an unassociated reference node
Arguments:
reference_node (str): Reference node name
Returns:
bool: Whether reference node is a valid reference
"""
sel = OpenMaya.MSelectionList()
sel.add(reference_node)
depend_node = sel.getDependNode(0)
return OpenMaya.MFnReference(depend_node).isValidReference()
def get_container_members(container):
"""Returns the members of a container.
This includes the nodes from any loaded references in the container.
@ -1942,7 +1965,16 @@ def get_container_members(container):
if ref.rsplit(":", 1)[-1].startswith("_UNKNOWN_REF_NODE_"):
continue
reference_members = cmds.referenceQuery(ref, nodes=True, dagPath=True)
try:
reference_members = cmds.referenceQuery(ref,
nodes=True,
dagPath=True)
except RuntimeError:
# Ignore reference nodes that are not associated with a
# referenced file on which `referenceQuery` command fails
if not is_valid_reference_node(ref):
continue
raise
reference_members = cmds.ls(reference_members,
long=True,
objectsOnly=True)
@ -4238,6 +4270,9 @@ def get_reference_node(members, log=None):
if ref.rsplit(":", 1)[-1].startswith("_UNKNOWN_REF_NODE_"):
continue
if not is_valid_reference_node(ref):
continue
references.add(ref)
assert references, "No reference node found in container"
@ -4268,15 +4303,19 @@ def get_reference_node_parents(ref):
list: The upstream parent reference nodes.
"""
parent = cmds.referenceQuery(ref,
referenceNode=True,
parent=True)
def _get_parent(reference_node):
"""Return parent reference node, but ignore invalid reference nodes"""
if not is_valid_reference_node(reference_node):
return
return cmds.referenceQuery(reference_node,
referenceNode=True,
parent=True)
parent = _get_parent(ref)
parents = []
while parent:
parents.append(parent)
parent = cmds.referenceQuery(parent,
referenceNode=True,
parent=True)
parent = _get_parent(parent)
return parents

View file

@ -51,7 +51,7 @@ class AbcLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader):
"camera",
"pointcache",
}
representations = ["abc"]
representations = {"abc"}
label = "Reference animation"
order = -10
@ -81,7 +81,7 @@ class FbxLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader):
"animation",
"camera",
}
representations = ["fbx"]
representations = {"fbx"}
label = "Reference animation"
order = -10

View file

@ -19,7 +19,7 @@ class SetFrameRangeLoader(load.LoaderPlugin):
"proxyAbc",
"pointcache",
}
representations = ["abc"]
representations = {"abc"}
label = "Set frame range"
order = 11
@ -54,7 +54,7 @@ class SetFrameRangeWithHandlesLoader(load.LoaderPlugin):
"proxyAbc",
"pointcache",
}
representations = ["abc"]
representations = {"abc"}
label = "Set frame range (with handles)"
order = 12
@ -94,7 +94,7 @@ class ImportMayaLoader(ayon_core.hosts.maya.api.plugin.Loader):
so you could also use it as a new base.
"""
representations = ["ma", "mb", "obj"]
representations = {"ma", "mb", "obj"}
product_types = {
"model",
"pointcache",

View file

@ -31,7 +31,7 @@ class ArnoldStandinLoader(load.LoaderPlugin):
product_types = {
"ass", "animation", "model", "proxyAbc", "pointcache", "usd"
}
representations = ["ass", "abc", "usda", "usdc", "usd"]
representations = {"ass", "abc", "usda", "usdc", "usd"}
label = "Load as Arnold standin"
order = -5

View file

@ -13,7 +13,7 @@ from ayon_core.hosts.maya.api import setdress
class AssemblyLoader(load.LoaderPlugin):
product_types = {"assembly"}
representations = ["json"]
representations = {"json"}
label = "Load Set Dress"
order = -9

View file

@ -13,7 +13,7 @@ class AudioLoader(load.LoaderPlugin):
product_types = {"audio"}
label = "Load audio"
representations = ["wav"]
representations = {"wav"}
icon = "volume-up"
color = "orange"

View file

@ -14,7 +14,7 @@ class GpuCacheLoader(load.LoaderPlugin):
"""Load Alembic as gpuCache"""
product_types = {"model", "animation", "proxyAbc", "pointcache"}
representations = ["abc", "gpu_cache"]
representations = {"abc", "gpu_cache"}
label = "Load Gpu Cache"
order = -5

View file

@ -93,7 +93,7 @@ class FileNodeLoader(load.LoaderPlugin):
product_types = {"image", "plate", "render"}
label = "Load file node"
representations = ["exr", "tif", "png", "jpg"]
representations = {"exr", "tif", "png", "jpg"}
icon = "image"
color = "orange"
order = 2

View file

@ -89,7 +89,7 @@ class ImagePlaneLoader(load.LoaderPlugin):
product_types = {"image", "plate", "render"}
label = "Load imagePlane"
representations = ["mov", "exr", "preview", "png", "jpg"]
representations = {"mov", "exr", "preview", "png", "jpg"}
icon = "image"
color = "orange"
@ -171,7 +171,7 @@ class ImagePlaneLoader(load.LoaderPlugin):
plug = "{}.{}".format(image_plane_shape, attr)
cmds.setAttr(plug, value)
movie_representations = ["mov", "preview"]
movie_representations = {"mov", "preview"}
if context["representation"]["name"] in movie_representations:
cmds.setAttr(image_plane_shape + ".type", 2)

View file

@ -18,7 +18,7 @@ class LookLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader):
"""Specific loader for lookdev"""
product_types = {"look"}
representations = ["ma"]
representations = {"ma"}
label = "Reference look"
order = -10

View file

@ -9,7 +9,7 @@ class MatchmoveLoader(load.LoaderPlugin):
"""
product_types = {"matchmove"}
representations = ["py", "mel"]
representations = {"py", "mel"}
defaults = ["Camera", "Object", "Mocap"]
label = "Run matchmove script"

View file

@ -17,7 +17,7 @@ class MayaUsdLoader(load.LoaderPlugin):
"""Read USD data in a Maya USD Proxy"""
product_types = {"model", "usd", "pointcache", "animation"}
representations = ["usd", "usda", "usdc", "usdz", "abc"]
representations = {"usd", "usda", "usdc", "usdz", "abc"}
label = "Load USD to Maya Proxy"
order = -1

View file

@ -28,7 +28,7 @@ class MultiverseUsdLoader(load.LoaderPlugin):
"pointcache",
"animation",
}
representations = ["usd", "usda", "usdc", "usdz", "abc"]
representations = {"usd", "usda", "usdc", "usdz", "abc"}
label = "Load USD to Multiverse"
order = -10

View file

@ -20,7 +20,7 @@ class MultiverseUsdOverLoader(load.LoaderPlugin):
"""Reference file"""
product_types = {"mvUsdOverride"}
representations = ["usda", "usd", "udsz"]
representations = {"usda", "usd", "udsz"}
label = "Load Usd Override into Compound"
order = -10

Some files were not shown because too many files have changed in this diff Show more