Merge branch 'release/3.15.x' into bugfix/update_dialog_sizes

This commit is contained in:
Jakub Trllo 2022-12-20 15:14:21 +01:00
commit 1453d15903
210 changed files with 2296 additions and 769 deletions

View file

@ -1,5 +1,73 @@
# Changelog
## [3.14.9](https://github.com/pypeclub/OpenPype/tree/3.14.9)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.8...3.14.9)
### 📖 Documentation
- Documentation: Testing on Deadline [\#4185](https://github.com/pypeclub/OpenPype/pull/4185)
- Consistent Python version [\#4160](https://github.com/pypeclub/OpenPype/pull/4160)
**🆕 New features**
- Feature/op 4397 gl tf extractor for maya [\#4192](https://github.com/pypeclub/OpenPype/pull/4192)
- Maya: Extractor for Unreal SkeletalMesh [\#4174](https://github.com/pypeclub/OpenPype/pull/4174)
- 3dsmax: integration [\#4168](https://github.com/pypeclub/OpenPype/pull/4168)
- Blender: Extract Alembic Animations [\#4128](https://github.com/pypeclub/OpenPype/pull/4128)
- Unreal: Load Alembic Animations [\#4127](https://github.com/pypeclub/OpenPype/pull/4127)
**🚀 Enhancements**
- Houdini: Use new interface class name for publish host [\#4220](https://github.com/pypeclub/OpenPype/pull/4220)
- General: Default command for headless mode is interactive [\#4203](https://github.com/pypeclub/OpenPype/pull/4203)
- Maya: Enhanced ASS publishing [\#4196](https://github.com/pypeclub/OpenPype/pull/4196)
- Feature/op 3924 implement ass extractor [\#4188](https://github.com/pypeclub/OpenPype/pull/4188)
- File transactions: Source path is destination path [\#4184](https://github.com/pypeclub/OpenPype/pull/4184)
- Deadline: improve environment processing [\#4182](https://github.com/pypeclub/OpenPype/pull/4182)
- General: Comment per instance in Publisher [\#4178](https://github.com/pypeclub/OpenPype/pull/4178)
- Ensure Mongo database directory exists in Windows. [\#4166](https://github.com/pypeclub/OpenPype/pull/4166)
- Note about unrestricted execution on Windows. [\#4161](https://github.com/pypeclub/OpenPype/pull/4161)
- Maya: Enable thumbnail transparency on extraction. [\#4147](https://github.com/pypeclub/OpenPype/pull/4147)
- Maya: Disable viewport Pan/Zoom on playblast extraction. [\#4146](https://github.com/pypeclub/OpenPype/pull/4146)
- Maya: Optional viewport refresh on pointcache extraction [\#4144](https://github.com/pypeclub/OpenPype/pull/4144)
- CelAction: refactory integration to current openpype [\#4140](https://github.com/pypeclub/OpenPype/pull/4140)
- Maya: create and publish bounding box geometry [\#4131](https://github.com/pypeclub/OpenPype/pull/4131)
- Changed the UOpenPypePublishInstance to use the UDataAsset class [\#4124](https://github.com/pypeclub/OpenPype/pull/4124)
- General: Collection Audio speed up [\#4110](https://github.com/pypeclub/OpenPype/pull/4110)
- Maya: keep existing AOVs when creating render instance [\#4087](https://github.com/pypeclub/OpenPype/pull/4087)
- General: Oiio conversion multipart fix [\#4060](https://github.com/pypeclub/OpenPype/pull/4060)
**🐛 Bug fixes**
- Publisher: Signal type issues in Python 2 DCCs [\#4230](https://github.com/pypeclub/OpenPype/pull/4230)
- Blender: Fix Layout Family Versioning [\#4228](https://github.com/pypeclub/OpenPype/pull/4228)
- Blender: Fix Create Camera "Use selection" [\#4226](https://github.com/pypeclub/OpenPype/pull/4226)
- TrayPublisher - join needs list [\#4224](https://github.com/pypeclub/OpenPype/pull/4224)
- General: Event callbacks pass event to callbacks as expected [\#4210](https://github.com/pypeclub/OpenPype/pull/4210)
- Build:Revert .toml update of Gazu [\#4207](https://github.com/pypeclub/OpenPype/pull/4207)
- Nuke: fixed imageio node overrides subset filter [\#4202](https://github.com/pypeclub/OpenPype/pull/4202)
- Maya: pointcache [\#4201](https://github.com/pypeclub/OpenPype/pull/4201)
- Unreal: Support for Unreal Engine 5.1 [\#4199](https://github.com/pypeclub/OpenPype/pull/4199)
- General: Integrate thumbnail looks for thumbnail to multiple places [\#4181](https://github.com/pypeclub/OpenPype/pull/4181)
- Various minor bugfixes [\#4172](https://github.com/pypeclub/OpenPype/pull/4172)
- Nuke/Hiero: Remove tkinter library paths before launch [\#4171](https://github.com/pypeclub/OpenPype/pull/4171)
- Flame: vertical alignment of layers [\#4169](https://github.com/pypeclub/OpenPype/pull/4169)
- Nuke: correct detection of viewer and display [\#4165](https://github.com/pypeclub/OpenPype/pull/4165)
- Settings UI: Don't create QApplication if already exists [\#4156](https://github.com/pypeclub/OpenPype/pull/4156)
- General: Extract review handle start offset of sequences [\#4152](https://github.com/pypeclub/OpenPype/pull/4152)
- Maya: Maintain time connections on Alembic update. [\#4143](https://github.com/pypeclub/OpenPype/pull/4143)
**🔀 Refactored code**
- General: Use qtpy in modules and hosts UIs which are running in OpenPype process [\#4225](https://github.com/pypeclub/OpenPype/pull/4225)
- Tools: Use qtpy instead of Qt in standalone tools [\#4223](https://github.com/pypeclub/OpenPype/pull/4223)
- General: Use qtpy in settings UI [\#4215](https://github.com/pypeclub/OpenPype/pull/4215)
**Merged pull requests:**
- layout publish more than one container issue [\#4098](https://github.com/pypeclub/OpenPype/pull/4098)
## [3.14.8](https://github.com/pypeclub/OpenPype/tree/3.14.8)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.7...3.14.8)
@ -21,7 +89,6 @@
- Maya: Looks - add all connections [\#4135](https://github.com/pypeclub/OpenPype/pull/4135)
- General: Fix variable check in collect anatomy instance data [\#4117](https://github.com/pypeclub/OpenPype/pull/4117)
## [3.14.7](https://github.com/pypeclub/OpenPype/tree/3.14.7)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.6...3.14.7)

View file

@ -1,5 +1,74 @@
# Changelog
## [3.14.9](https://github.com/pypeclub/OpenPype/tree/3.14.9)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.8...3.14.9)
### 📖 Documentation
- Documentation: Testing on Deadline [\#4185](https://github.com/pypeclub/OpenPype/pull/4185)
- Consistent Python version [\#4160](https://github.com/pypeclub/OpenPype/pull/4160)
**🆕 New features**
- Feature/op 4397 gl tf extractor for maya [\#4192](https://github.com/pypeclub/OpenPype/pull/4192)
- Maya: Extractor for Unreal SkeletalMesh [\#4174](https://github.com/pypeclub/OpenPype/pull/4174)
- 3dsmax: integration [\#4168](https://github.com/pypeclub/OpenPype/pull/4168)
- Blender: Extract Alembic Animations [\#4128](https://github.com/pypeclub/OpenPype/pull/4128)
- Unreal: Load Alembic Animations [\#4127](https://github.com/pypeclub/OpenPype/pull/4127)
**🚀 Enhancements**
- Houdini: Use new interface class name for publish host [\#4220](https://github.com/pypeclub/OpenPype/pull/4220)
- General: Default command for headless mode is interactive [\#4203](https://github.com/pypeclub/OpenPype/pull/4203)
- Maya: Enhanced ASS publishing [\#4196](https://github.com/pypeclub/OpenPype/pull/4196)
- Feature/op 3924 implement ass extractor [\#4188](https://github.com/pypeclub/OpenPype/pull/4188)
- File transactions: Source path is destination path [\#4184](https://github.com/pypeclub/OpenPype/pull/4184)
- Deadline: improve environment processing [\#4182](https://github.com/pypeclub/OpenPype/pull/4182)
- General: Comment per instance in Publisher [\#4178](https://github.com/pypeclub/OpenPype/pull/4178)
- Ensure Mongo database directory exists in Windows. [\#4166](https://github.com/pypeclub/OpenPype/pull/4166)
- Note about unrestricted execution on Windows. [\#4161](https://github.com/pypeclub/OpenPype/pull/4161)
- Maya: Enable thumbnail transparency on extraction. [\#4147](https://github.com/pypeclub/OpenPype/pull/4147)
- Maya: Disable viewport Pan/Zoom on playblast extraction. [\#4146](https://github.com/pypeclub/OpenPype/pull/4146)
- Maya: Optional viewport refresh on pointcache extraction [\#4144](https://github.com/pypeclub/OpenPype/pull/4144)
- CelAction: refactory integration to current openpype [\#4140](https://github.com/pypeclub/OpenPype/pull/4140)
- Maya: create and publish bounding box geometry [\#4131](https://github.com/pypeclub/OpenPype/pull/4131)
- Changed the UOpenPypePublishInstance to use the UDataAsset class [\#4124](https://github.com/pypeclub/OpenPype/pull/4124)
- General: Collection Audio speed up [\#4110](https://github.com/pypeclub/OpenPype/pull/4110)
- Maya: keep existing AOVs when creating render instance [\#4087](https://github.com/pypeclub/OpenPype/pull/4087)
- General: Oiio conversion multipart fix [\#4060](https://github.com/pypeclub/OpenPype/pull/4060)
**🐛 Bug fixes**
- Publisher: Signal type issues in Python 2 DCCs [\#4230](https://github.com/pypeclub/OpenPype/pull/4230)
- Blender: Fix Layout Family Versioning [\#4228](https://github.com/pypeclub/OpenPype/pull/4228)
- Blender: Fix Create Camera "Use selection" [\#4226](https://github.com/pypeclub/OpenPype/pull/4226)
- TrayPublisher - join needs list [\#4224](https://github.com/pypeclub/OpenPype/pull/4224)
- General: Event callbacks pass event to callbacks as expected [\#4210](https://github.com/pypeclub/OpenPype/pull/4210)
- Build:Revert .toml update of Gazu [\#4207](https://github.com/pypeclub/OpenPype/pull/4207)
- Nuke: fixed imageio node overrides subset filter [\#4202](https://github.com/pypeclub/OpenPype/pull/4202)
- Maya: pointcache [\#4201](https://github.com/pypeclub/OpenPype/pull/4201)
- Unreal: Support for Unreal Engine 5.1 [\#4199](https://github.com/pypeclub/OpenPype/pull/4199)
- General: Integrate thumbnail looks for thumbnail to multiple places [\#4181](https://github.com/pypeclub/OpenPype/pull/4181)
- Various minor bugfixes [\#4172](https://github.com/pypeclub/OpenPype/pull/4172)
- Nuke/Hiero: Remove tkinter library paths before launch [\#4171](https://github.com/pypeclub/OpenPype/pull/4171)
- Flame: vertical alignment of layers [\#4169](https://github.com/pypeclub/OpenPype/pull/4169)
- Nuke: correct detection of viewer and display [\#4165](https://github.com/pypeclub/OpenPype/pull/4165)
- Settings UI: Don't create QApplication if already exists [\#4156](https://github.com/pypeclub/OpenPype/pull/4156)
- General: Extract review handle start offset of sequences [\#4152](https://github.com/pypeclub/OpenPype/pull/4152)
- Maya: Maintain time connections on Alembic update. [\#4143](https://github.com/pypeclub/OpenPype/pull/4143)
**🔀 Refactored code**
- General: Use qtpy in modules and hosts UIs which are running in OpenPype process [\#4225](https://github.com/pypeclub/OpenPype/pull/4225)
- Tools: Use qtpy instead of Qt in standalone tools [\#4223](https://github.com/pypeclub/OpenPype/pull/4223)
- General: Use qtpy in settings UI [\#4215](https://github.com/pypeclub/OpenPype/pull/4215)
**Merged pull requests:**
- layout publish more than one container issue [\#4098](https://github.com/pypeclub/OpenPype/pull/4098)
## [3.14.8](https://github.com/pypeclub/OpenPype/tree/3.14.8)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.7...3.14.8)

View file

@ -24,7 +24,7 @@ def open_dialog():
if os.getenv("OPENPYPE_HEADLESS_MODE"):
print("!!! Can't open dialog in headless mode. Exiting.")
sys.exit(1)
from Qt import QtWidgets, QtCore
from qtpy import QtWidgets, QtCore
from .install_dialog import InstallDialog
scale_attr = getattr(QtCore.Qt, "AA_EnableHighDpiScaling", None)
@ -47,7 +47,7 @@ def open_update_window(openpype_version):
if os.getenv("OPENPYPE_HEADLESS_MODE"):
print("!!! Can't open dialog in headless mode. Exiting.")
sys.exit(1)
from Qt import QtWidgets, QtCore
from qtpy import QtWidgets, QtCore
from .update_window import UpdateWindow
scale_attr = getattr(QtCore.Qt, "AA_EnableHighDpiScaling", None)
@ -71,7 +71,7 @@ def show_message_dialog(title, message):
if os.getenv("OPENPYPE_HEADLESS_MODE"):
print("!!! Can't open dialog in headless mode. Exiting.")
sys.exit(1)
from Qt import QtWidgets, QtCore
from qtpy import QtWidgets, QtCore
from .message_dialog import MessageDialog
scale_attr = getattr(QtCore.Qt, "AA_EnableHighDpiScaling", None)

View file

@ -2,8 +2,7 @@
"""Open install dialog."""
import sys
from Qt import QtWidgets # noqa
from Qt.QtCore import Signal # noqa
from qtpy import QtWidgets
from .install_dialog import InstallDialog

View file

@ -5,9 +5,7 @@ import sys
import re
import collections
from Qt import QtCore, QtGui, QtWidgets # noqa
from Qt.QtGui import QValidator # noqa
from Qt.QtCore import QTimer # noqa
from qtpy import QtCore, QtGui, QtWidgets
from .install_thread import InstallThread
from .tools import (

View file

@ -4,7 +4,7 @@ import os
import sys
from pathlib import Path
from Qt.QtCore import QThread, Signal, QObject # noqa
from qtpy import QtCore
from .bootstrap_repos import (
BootstrapRepos,
@ -17,7 +17,7 @@ from .bootstrap_repos import (
from .tools import validate_mongo_connection
class InstallThread(QThread):
class InstallThread(QtCore.QThread):
"""Install Worker thread.
This class takes care of finding OpenPype version on user entered path
@ -28,14 +28,14 @@ class InstallThread(QThread):
user data dir.
"""
progress = Signal(int)
message = Signal((str, bool))
progress = QtCore.Signal(int)
message = QtCore.Signal((str, bool))
def __init__(self, parent=None,):
self._mongo = None
self._result = None
QThread.__init__(self, parent)
super().__init__(parent)
def result(self):
"""Result of finished installation."""

View file

@ -1,4 +1,4 @@
from Qt import QtWidgets, QtGui
from qtpy import QtWidgets, QtGui
from .tools import (
load_stylesheet,

View file

@ -1,4 +1,4 @@
from Qt import QtCore, QtGui, QtWidgets # noqa
from qtpy import QtWidgets
class NiceProgressBar(QtWidgets.QProgressBar):

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""Working thread for update."""
from Qt.QtCore import QThread, Signal, QObject # noqa
from qtpy import QtCore
from .bootstrap_repos import (
BootstrapRepos,
@ -8,7 +8,7 @@ from .bootstrap_repos import (
)
class UpdateThread(QThread):
class UpdateThread(QtCore.QThread):
"""Install Worker thread.
This class takes care of finding OpenPype version on user entered path
@ -19,13 +19,13 @@ class UpdateThread(QThread):
user data dir.
"""
progress = Signal(int)
message = Signal((str, bool))
progress = QtCore.Signal(int)
message = QtCore.Signal((str, bool))
def __init__(self, parent=None):
self._result = None
self._openpype_version = None
QThread.__init__(self, parent)
super().__init__(parent)
def set_version(self, openpype_version: OpenPypeVersion):
self._openpype_version = openpype_version

View file

@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
"""Progress window to show when OpenPype is updating/installing locally."""
import os
from qtpy import QtCore, QtGui, QtWidgets
from .update_thread import UpdateThread
from Qt import QtCore, QtGui, QtWidgets # noqa
from .bootstrap_repos import OpenPypeVersion
from .nice_progress_bar import NiceProgressBar
from .tools import load_stylesheet

View file

@ -30,8 +30,14 @@ def main(ctx):
It wraps different commands together.
"""
if ctx.invoked_subcommand is None:
ctx.invoke(tray)
# Print help if headless mode is used
if os.environ.get("OPENPYPE_HEADLESS_MODE") == "1":
print(ctx.get_help())
sys.exit(0)
else:
ctx.invoke(tray)
@main.command()

View file

@ -10,7 +10,7 @@ from wsrpc_aiohttp import (
WebSocketAsync
)
from Qt import QtCore
from qtpy import QtCore
from openpype.lib import Logger
from openpype.pipeline import legacy_io

View file

@ -7,7 +7,7 @@ import traceback
import logging
from functools import partial
from Qt import QtWidgets
from qtpy import QtWidgets
from openpype.pipeline import install_host
from openpype.modules import ModulesManager

View file

@ -1,5 +1,6 @@
import os
from Qt import QtWidgets
from qtpy import QtWidgets
import pyblish.api

View file

@ -10,7 +10,7 @@ from pathlib import Path
from types import ModuleType
from typing import Dict, List, Optional, Union
from Qt import QtWidgets, QtCore
from qtpy import QtWidgets, QtCore
import bpy
import bpy.utils.previews

View file

@ -32,11 +32,6 @@ class CreateCamera(plugin.Creator):
subset = self.data["subset"]
name = plugin.asset_name(asset, subset)
camera = bpy.data.cameras.new(subset)
camera_obj = bpy.data.objects.new(subset, camera)
instances.objects.link(camera_obj)
asset_group = bpy.data.objects.new(name=name, object_data=None)
asset_group.empty_display_type = 'SINGLE_ARROW'
instances.objects.link(asset_group)
@ -53,6 +48,11 @@ class CreateCamera(plugin.Creator):
bpy.ops.object.parent_set(keep_transform=True)
else:
plugin.deselect_all()
camera = bpy.data.cameras.new(subset)
camera_obj = bpy.data.objects.new(subset, camera)
instances.objects.link(camera_obj)
camera_obj.select_set(True)
asset_group.select_set(True)
bpy.context.view_layer.objects.active = asset_group

View file

@ -0,0 +1,82 @@
import bpy
from openpype.hosts.blender.api import plugin
def append_workfile(context, fname, do_import):
asset = context['asset']['name']
subset = context['subset']['name']
group_name = plugin.asset_name(asset, subset)
# We need to preserve the original names of the scenes, otherwise,
# if there are duplicate names in the current workfile, the imported
# scenes will be renamed by Blender to avoid conflicts.
original_scene_names = []
with bpy.data.libraries.load(fname) as (data_from, data_to):
for attr in dir(data_to):
if attr == "scenes":
for scene in data_from.scenes:
original_scene_names.append(scene)
setattr(data_to, attr, getattr(data_from, attr))
current_scene = bpy.context.scene
for scene, s_name in zip(data_to.scenes, original_scene_names):
scene.name = f"{group_name}_{s_name}"
if do_import:
collection = bpy.data.collections.new(f"{group_name}_{s_name}")
for obj in scene.objects:
collection.objects.link(obj)
current_scene.collection.children.link(collection)
for coll in scene.collection.children:
collection.children.link(coll)
class AppendBlendLoader(plugin.AssetLoader):
"""Append workfile in Blender (unmanaged)
Warning:
The loaded content will be unmanaged and is *not* visible in the
scene inventory. It's purely intended to merge content into your scene
so you could also use it as a new base.
"""
representations = ["blend"]
families = ["*"]
label = "Append Workfile"
order = 9
icon = "arrow-circle-down"
color = "#775555"
def load(self, context, name=None, namespace=None, data=None):
append_workfile(context, self.fname, False)
# We do not containerize imported content, it remains unmanaged
return
class ImportBlendLoader(plugin.AssetLoader):
"""Import workfile in the current Blender scene (unmanaged)
Warning:
The loaded content will be unmanaged and is *not* visible in the
scene inventory. It's purely intended to merge content into your scene
so you could also use it as a new base.
"""
representations = ["blend"]
families = ["*"]
label = "Import Workfile"
order = 9
icon = "arrow-circle-down"
color = "#775555"
def load(self, context, name=None, namespace=None, data=None):
append_workfile(context, self.fname, True)
# We do not containerize imported content, it remains unmanaged
return

View file

@ -48,8 +48,14 @@ class BlendLayoutLoader(plugin.AssetLoader):
bpy.data.objects.remove(obj)
def _remove_asset_and_library(self, asset_group):
if not asset_group.get(AVALON_PROPERTY):
return
libpath = asset_group.get(AVALON_PROPERTY).get('libpath')
if not libpath:
return
# Check how many assets use the same library
count = 0
for obj in bpy.data.collections.get(AVALON_CONTAINERS).all_objects:
@ -63,10 +69,12 @@ class BlendLayoutLoader(plugin.AssetLoader):
# If it is the last object to use that library, remove it
if count == 1:
library = bpy.data.libraries.get(bpy.path.basename(libpath))
bpy.data.libraries.remove(library)
if library:
bpy.data.libraries.remove(library)
def _process(
self, libpath, asset_group, group_name, asset, representation, actions
self, libpath, asset_group, group_name, asset, representation,
actions, anim_instances
):
with bpy.data.libraries.load(
libpath, link=True, relative=False
@ -140,12 +148,12 @@ class BlendLayoutLoader(plugin.AssetLoader):
elif local_obj.type == 'ARMATURE':
plugin.prepare_data(local_obj.data)
if action is not None:
if action:
if local_obj.animation_data is None:
local_obj.animation_data_create()
local_obj.animation_data.action = action
elif (local_obj.animation_data and
local_obj.animation_data.action is not None):
local_obj.animation_data.action):
plugin.prepare_data(
local_obj.animation_data.action)
@ -157,19 +165,26 @@ class BlendLayoutLoader(plugin.AssetLoader):
t.id = local_obj
elif local_obj.type == 'EMPTY':
creator_plugin = get_legacy_creator_by_name("CreateAnimation")
if not creator_plugin:
raise ValueError("Creator plugin \"CreateAnimation\" was "
"not found.")
if (not anim_instances or
(anim_instances and
local_obj.name not in anim_instances.keys())):
avalon = local_obj.get(AVALON_PROPERTY)
if avalon and avalon.get('family') == 'rig':
creator_plugin = get_legacy_creator_by_name(
"CreateAnimation")
if not creator_plugin:
raise ValueError(
"Creator plugin \"CreateAnimation\" was "
"not found.")
legacy_create(
creator_plugin,
name=local_obj.name.split(':')[-1] + "_animation",
asset=asset,
options={"useSelection": False,
"asset_group": local_obj},
data={"dependencies": representation}
)
legacy_create(
creator_plugin,
name=local_obj.name.split(':')[-1] + "_animation",
asset=asset,
options={"useSelection": False,
"asset_group": local_obj},
data={"dependencies": representation}
)
if not local_obj.get(AVALON_PROPERTY):
local_obj[AVALON_PROPERTY] = dict()
@ -272,7 +287,8 @@ class BlendLayoutLoader(plugin.AssetLoader):
avalon_container.objects.link(asset_group)
objects = self._process(
libpath, asset_group, group_name, asset, representation, None)
libpath, asset_group, group_name, asset, representation,
None, None)
for child in asset_group.children:
if child.get(AVALON_PROPERTY):
@ -352,10 +368,20 @@ class BlendLayoutLoader(plugin.AssetLoader):
return
actions = {}
anim_instances = {}
for obj in asset_group.children:
obj_meta = obj.get(AVALON_PROPERTY)
if obj_meta.get('family') == 'rig':
# Get animation instance
collections = list(obj.users_collection)
for c in collections:
avalon = c.get(AVALON_PROPERTY)
if avalon and avalon.get('family') == 'animation':
anim_instances[obj.name] = c.name
break
# Get armature's action
rig = None
for child in obj.children:
if child.type == 'ARMATURE':
@ -384,9 +410,26 @@ class BlendLayoutLoader(plugin.AssetLoader):
# If it is the last object to use that library, remove it
if count == 1:
library = bpy.data.libraries.get(bpy.path.basename(group_libpath))
bpy.data.libraries.remove(library)
if library:
bpy.data.libraries.remove(library)
self._process(str(libpath), asset_group, object_name, actions)
asset = container.get("asset_name").split("_")[0]
self._process(
str(libpath), asset_group, object_name, asset,
str(representation.get("_id")), actions, anim_instances
)
# Link the new objects to the animation collection
for inst in anim_instances.keys():
try:
obj = bpy.data.objects[inst]
bpy.data.collections[anim_instances[inst]].objects.link(obj)
except KeyError:
self.log.info(f"Object {inst} does not exist anymore.")
coll = bpy.data.collections.get(anim_instances[inst])
if (coll):
bpy.data.collections.remove(coll)
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
for child in asset_group.children:

View file

@ -1,5 +1,5 @@
import os
from Qt import QtWidgets
from qtpy import QtWidgets
from copy import deepcopy
from pprint import pformat
from openpype.tools.utils.host_tools import HostToolsHelper

View file

@ -5,10 +5,10 @@ from copy import deepcopy
from xml.etree import ElementTree as ET
import qargparse
from Qt import QtCore, QtWidgets
from qtpy import QtCore, QtWidgets
from openpype import style
from openpype.lib import Logger
from openpype.lib import Logger, StringTemplate
from openpype.pipeline import LegacyCreator, LoaderPlugin
from openpype.settings import get_current_project_settings
@ -775,6 +775,11 @@ class OpenClipSolver(flib.MediaInfoFile):
self.feed_colorspace = feed_data.get("colorspace")
self.log.debug("feed_version_name: {}".format(self.feed_version_name))
# layer rename variables
self.layer_rename_template = feed_data["layer_rename_template"]
self.layer_rename_patterns = feed_data["layer_rename_patterns"]
self.context_data = feed_data["context_data"]
# derivate other feed variables
self.feed_basename = os.path.basename(feed_path)
self.feed_dir = os.path.dirname(feed_path)
@ -813,9 +818,11 @@ class OpenClipSolver(flib.MediaInfoFile):
def _create_new_open_clip(self):
self.log.info("Building new openClip")
self.log.debug(">> self.clip_data: {}".format(self.clip_data))
for tmp_xml_track in self.clip_data.iter("track"):
# solve track (layer) name
self._rename_track_name(tmp_xml_track)
tmp_xml_feeds = tmp_xml_track.find('feeds')
tmp_xml_feeds.set('currentVersion', self.feed_version_name)
@ -850,6 +857,48 @@ class OpenClipSolver(flib.MediaInfoFile):
if uid == track_uid:
return xml_track
def _rename_track_name(self, xml_track_data):
layer_uid = xml_track_data.get("uid")
name_obj = xml_track_data.find("name")
layer_name = name_obj.text
if (
self.layer_rename_patterns
and not any(
re.search(lp_.lower(), layer_name.lower())
for lp_ in self.layer_rename_patterns
)
):
return
formating_data = self._update_formating_data(
layerName=layer_name,
layerUID=layer_uid
)
name_obj.text = StringTemplate(
self.layer_rename_template
).format(formating_data)
def _update_formating_data(self, **kwargs):
""" Updating formating data for layer rename
Attributes:
key=value (optional): will be included to formating data
as {key: value}
Returns:
dict: anatomy context data for formating
"""
self.log.debug(">> self.clip_data: {}".format(self.clip_data))
clip_name_obj = self.clip_data.find("name")
data = {
"originalBasename": clip_name_obj.text
}
# include version context data
data.update(self.context_data)
# include input kwargs data
data.update(kwargs)
return data
def _update_open_clip(self):
self.log.info("Updating openClip ..")
@ -857,11 +906,12 @@ class OpenClipSolver(flib.MediaInfoFile):
out_xml = out_xml.getroot()
self.log.debug(">> out_xml: {}".format(out_xml))
self.log.debug(">> self.clip_data: {}".format(self.clip_data))
# loop tmp tracks
updated_any = False
for tmp_xml_track in self.clip_data.iter("track"):
# solve track (layer) name
self._rename_track_name(tmp_xml_track)
# get tmp track uid
tmp_track_uid = tmp_xml_track.get("uid")
self.log.debug(">> tmp_track_uid: {}".format(tmp_track_uid))

View file

@ -1,3 +1,4 @@
from copy import deepcopy
import os
import flame
from pprint import pformat
@ -25,6 +26,14 @@ class LoadClip(opfapi.ClipLoader):
reel_name = "Loaded"
clip_name_template = "{asset}_{subset}<_{output}>"
""" Anatomy keys from version context data and dynamically added:
- {layerName} - original layer name token
- {layerUID} - original layer UID token
- {originalBasename} - original clip name taken from file
"""
layer_rename_template = "{asset}_{subset}<_{output}>"
layer_rename_patterns = []
def load(self, context, name, namespace, options):
# get flame objects
@ -38,8 +47,16 @@ class LoadClip(opfapi.ClipLoader):
version_name = version.get("name", None)
colorspace = self.get_colorspace(context)
# in case output is not in context replace key to representation
if not context["representation"]["context"].get("output"):
self.clip_name_template = self.clip_name_template.replace(
"output", "representation")
self.layer_rename_template = self.layer_rename_template.replace(
"output", "representation")
formating_data = deepcopy(context["representation"]["context"])
clip_name = StringTemplate(self.clip_name_template).format(
context["representation"]["context"])
formating_data)
# convert colorspace with ocio to flame mapping
# in imageio flame section
@ -62,6 +79,9 @@ class LoadClip(opfapi.ClipLoader):
"path": self.fname.replace("\\", "/"),
"colorspace": colorspace,
"version": "v{:0>3}".format(version_name),
"layer_rename_template": self.layer_rename_template,
"layer_rename_patterns": self.layer_rename_patterns,
"context_data": formating_data
}
self.log.debug(pformat(
loading_context

View file

@ -25,6 +25,14 @@ class LoadClipBatch(opfapi.ClipLoader):
reel_name = "OP_LoadedReel"
clip_name_template = "{batch}_{asset}_{subset}<_{output}>"
""" Anatomy keys from version context data and dynamically added:
- {layerName} - original layer name token
- {layerUID} - original layer UID token
- {originalBasename} - original clip name taken from file
"""
layer_rename_template = "{asset}_{subset}<_{output}>"
layer_rename_patterns = []
def load(self, context, name, namespace, options):
# get flame objects
@ -39,7 +47,10 @@ class LoadClipBatch(opfapi.ClipLoader):
# in case output is not in context replace key to representation
if not context["representation"]["context"].get("output"):
self.clip_name_template.replace("output", "representation")
self.clip_name_template = self.clip_name_template.replace(
"output", "representation")
self.layer_rename_template = self.layer_rename_template.replace(
"output", "representation")
formating_data = deepcopy(context["representation"]["context"])
formating_data["batch"] = self.batch.name.get_value()
@ -69,6 +80,9 @@ class LoadClipBatch(opfapi.ClipLoader):
"path": self.fname.replace("\\", "/"),
"colorspace": colorspace,
"version": "v{:0>3}".format(version_name),
"layer_rename_template": self.layer_rename_template,
"layer_rename_patterns": self.layer_rename_patterns,
"context_data": formating_data
}
self.log.debug(pformat(
loading_context

View file

@ -1,4 +1,4 @@
from Qt import QtWidgets, QtCore
from qtpy import QtWidgets, QtCore
import uiwidgets
import app_utils

View file

@ -1,4 +1,4 @@
from Qt import QtWidgets, QtCore
from qtpy import QtWidgets, QtCore
class FlameLabel(QtWidgets.QLabel):

View file

@ -1,6 +1,6 @@
from __future__ import print_function
import sys
from Qt import QtWidgets
from qtpy import QtWidgets
from pprint import pformat
import atexit

View file

@ -1,6 +1,6 @@
import sys
from Qt import QtWidgets, QtCore, QtGui
from qtpy import QtWidgets, QtCore, QtGui
from openpype.tools.utils import host_tools
from openpype.style import load_stylesheet

View file

@ -6,7 +6,7 @@ import sys
import logging
import pyblish.api
from Qt import QtCore
from qtpy import QtCore
from openpype.lib import (
Logger,

View file

@ -1,7 +1,7 @@
import os
import sys
from Qt import QtCore
from qtpy import QtCore
class PulseThread(QtCore.QThread):

View file

@ -6,10 +6,10 @@ import importlib
try:
from Qt import QtWidgets # noqa: F401
from Qt import __binding__
print(f"Qt binding: {__binding__}")
mod = importlib.import_module(__binding__)
from qtpy import API_NAME
print(f"Qt binding: {API_NAME}")
mod = importlib.import_module(API_NAME)
print(f"Qt path: {mod.__file__}")
print("Qt library found, nothing to do..")

View file

@ -3,7 +3,7 @@ import sys
import glob
import logging
from Qt import QtWidgets, QtCore
from qtpy import QtWidgets, QtCore
import qtawesome as qta

View file

@ -1,4 +1,4 @@
from Qt import QtGui, QtWidgets
from qtpy import QtGui, QtWidgets
from openpype.pipeline import InventoryAction
from openpype import style

View file

@ -1,4 +1,4 @@
from Qt import QtWidgets
from qtpy import QtWidgets
import qtawesome
from openpype.hosts.fusion.api import get_current_comp

View file

@ -14,7 +14,7 @@ import json
import signal
import time
from uuid import uuid4
from Qt import QtWidgets, QtCore, QtGui
from qtpy import QtWidgets, QtCore, QtGui
import collections
from .server import Server

View file

@ -1,7 +1,7 @@
import logging
from scriptsmenu import scriptsmenu
from Qt import QtWidgets
from qtpy import QtWidgets
log = logging.getLogger(__name__)

View file

@ -15,7 +15,11 @@ import secrets
import shutil
import hiero
from Qt import QtWidgets, QtCore, QtXml
from qtpy import QtWidgets, QtCore
try:
from PySide import QtXml
except ImportError:
from PySide2 import QtXml
from openpype.client import get_project
from openpype.settings import get_project_settings

View file

@ -43,7 +43,7 @@ def menu_install():
"""
from Qt import QtGui
from qtpy import QtGui
from . import (
publish, launch_workfiles_app, reload_config,
apply_colorspace_project, apply_colorspace_clips

View file

@ -5,7 +5,7 @@ from copy import deepcopy
import hiero
from Qt import QtWidgets, QtCore
from qtpy import QtWidgets, QtCore
import qargparse
from openpype.settings import get_current_project_settings

View file

@ -3,7 +3,7 @@ import tempfile
from pprint import pformat
import pyblish.api
from Qt.QtGui import QPixmap
from qtpy.QtGui import QPixmap
import hiero.ui

View file

@ -7,7 +7,7 @@ import contextlib
import hou # noqa
from openpype.host import HostBase, IWorkfileHost, ILoadHost, INewPublisher
from openpype.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost
import pyblish.api
@ -40,7 +40,7 @@ CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, INewPublisher):
class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
name = "houdini"
def __init__(self):

View file

@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
"""Tools to work with GLTF."""
import logging
from maya import cmds, mel # noqa
log = logging.getLogger(__name__)
_gltf_options = {
"of": str, # outputFolder
"cpr": str, # copyright
"sno": bool, # selectedNodeOnly
"sn": str, # sceneName
"glb": bool, # binary
"nbu": bool, # niceBufferURIs
"hbu": bool, # hashBufferURI
"ext": bool, # externalTextures
"ivt": int, # initialValuesTime
"acn": str, # animationClipName
"ast": int, # animationClipStartTime
"aet": int, # animationClipEndTime
"afr": float, # animationClipFrameRate
"dsa": int, # detectStepAnimations
"mpa": str, # meshPrimitiveAttributes
"bpa": str, # blendPrimitiveAttributes
"i32": bool, # force32bitIndices
"ssm": bool, # skipStandardMaterials
"eut": bool, # excludeUnusedTexcoord
"dm": bool, # defaultMaterial
"cm": bool, # colorizeMaterials
"dmy": str, # dumpMaya
"dgl": str, # dumpGLTF
"imd": str, # ignoreMeshDeformers
"ssc": bool, # skipSkinClusters
"sbs": bool, # skipBlendShapes
"rvp": bool, # redrawViewport
"vno": bool # visibleNodesOnly
}
def extract_gltf(parent_dir,
filename,
**kwargs):
"""Sets GLTF export options from data in the instance.
"""
cmds.loadPlugin('maya2glTF', quiet=True)
# load the UI to run mel command
mel.eval("maya2glTF_UI()")
parent_dir = parent_dir.replace('\\', '/')
options = {
"dsa": 1,
"glb": True
}
options.update(kwargs)
for key, value in options.copy().items():
if key not in _gltf_options:
log.warning("extract_gltf() does not support option '%s'. "
"Flag will be ignored..", key)
options.pop(key)
options.pop(value)
continue
job_args = list()
default_opt = "maya2glTF -of \"{0}\" -sn \"{1}\"".format(parent_dir, filename) # noqa
job_args.append(default_opt)
for key, value in options.items():
if isinstance(value, str):
job_args.append("-{0} \"{1}\"".format(key, value))
elif isinstance(value, bool):
if value:
job_args.append("-{0}".format(key))
else:
job_args.append("-{0} {1}".format(key, value))
job_str = " ".join(job_args)
log.info("{}".format(job_str))
mel.eval(job_str)
# close the gltf export after finish the export
gltf_UI = "maya2glTF_exporter_window"
if cmds.window(gltf_UI, q=True, exists=True):
cmds.deleteUI(gltf_UI)

View file

@ -128,13 +128,18 @@ def get_main_window():
@contextlib.contextmanager
def suspended_refresh(suspend=True):
"""Suspend viewport refreshes"""
original_state = cmds.refresh(query=True, suspend=True)
"""Suspend viewport refreshes
cmds.ogs(pause=True) is a toggle so we cant pass False.
"""
original_state = cmds.ogs(query=True, pause=True)
try:
cmds.refresh(suspend=suspend)
if suspend and not original_state:
cmds.ogs(pause=True)
yield
finally:
cmds.refresh(suspend=original_state)
if suspend and not original_state:
cmds.ogs(pause=True)
@contextlib.contextmanager
@ -3436,3 +3441,8 @@ def iter_visible_nodes_in_range(nodes, start, end):
# If no more nodes to process break the frame iterations..
if not node_dependencies:
break
def get_attribute_input(attr):
connections = cmds.listConnections(attr, plugs=True, destination=False)
return connections[0] if connections else None

View file

@ -95,21 +95,25 @@ class RenderSettings(object):
if renderer == "redshift":
self._set_redshift_settings(width, height)
mel.eval("redshiftUpdateActiveAovList")
def _set_arnold_settings(self, width, height):
"""Sets settings for Arnold."""
from mtoa.core import createOptions # noqa
from mtoa.aovs import AOVInterface # noqa
createOptions()
arnold_render_presets = self._project_settings["maya"]["RenderSettings"]["arnold_renderer"] # noqa
render_settings = self._project_settings["maya"]["RenderSettings"]
arnold_render_presets = render_settings["arnold_renderer"] # noqa
# Force resetting settings and AOV list to avoid having to deal with
# AOV checking logic, for now.
# This is a work around because the standard
# function to revert render settings does not reset AOVs list in MtoA
# Fetch current aovs in case there's any.
current_aovs = AOVInterface().getAOVs()
remove_aovs = render_settings["remove_aovs"]
if remove_aovs:
# Remove fetched AOVs
AOVInterface().removeAOVs(current_aovs)
AOVInterface().removeAOVs(current_aovs)
mel.eval("unifiedRenderGlobalsRevertToDefault")
img_ext = arnold_render_presets["image_format"]
img_prefix = arnold_render_presets["image_prefix"]
@ -118,6 +122,8 @@ class RenderSettings(object):
multi_exr = arnold_render_presets["multilayer_exr"]
additional_options = arnold_render_presets["additional_options"]
for aov in aovs:
if aov in current_aovs and not remove_aovs:
continue
AOVInterface('defaultArnoldRenderOptions').addAOV(aov)
cmds.setAttr("defaultResolution.width", width)
@ -141,12 +147,50 @@ class RenderSettings(object):
def _set_redshift_settings(self, width, height):
"""Sets settings for Redshift."""
redshift_render_presets = (
self._project_settings
["maya"]
["RenderSettings"]
["redshift_renderer"]
)
render_settings = self._project_settings["maya"]["RenderSettings"]
redshift_render_presets = render_settings["redshift_renderer"]
remove_aovs = render_settings["remove_aovs"]
all_rs_aovs = cmds.ls(type='RedshiftAOV')
if remove_aovs:
for aov in all_rs_aovs:
enabled = cmds.getAttr("{}.enabled".format(aov))
if enabled:
cmds.delete(aov)
redshift_aovs = redshift_render_presets["aov_list"]
# list all the aovs
all_rs_aovs = cmds.ls(type='RedshiftAOV')
for rs_aov in redshift_aovs:
rs_layername = rs_aov
if " " in rs_aov:
rs_renderlayer = rs_aov.replace(" ", "")
rs_layername = "rsAov_{}".format(rs_renderlayer)
else:
rs_layername = "rsAov_{}".format(rs_aov)
if rs_layername in all_rs_aovs:
continue
cmds.rsCreateAov(type=rs_aov)
# update the AOV list
mel.eval("redshiftUpdateActiveAovList")
rs_p_engine = redshift_render_presets["primary_gi_engine"]
rs_s_engine = redshift_render_presets["secondary_gi_engine"]
if int(rs_p_engine) or int(rs_s_engine) != 0:
cmds.setAttr("redshiftOptions.GIEnabled", 1)
if int(rs_p_engine) == 0:
# reset the primary GI Engine as default
cmds.setAttr("redshiftOptions.primaryGIEngine", 4)
if int(rs_s_engine) == 0:
# reset the secondary GI Engine as default
cmds.setAttr("redshiftOptions.secondaryGIEngine", 2)
else:
cmds.setAttr("redshiftOptions.GIEnabled", 0)
cmds.setAttr("redshiftOptions.primaryGIEngine", int(rs_p_engine))
cmds.setAttr("redshiftOptions.secondaryGIEngine", int(rs_s_engine))
additional_options = redshift_render_presets["additional_options"]
ext = redshift_render_presets["image_format"]
img_exts = ["iff", "exr", "tif", "png", "tga", "jpg"]
@ -163,12 +207,31 @@ class RenderSettings(object):
"""Sets important settings for Vray."""
settings = cmds.ls(type="VRaySettingsNode")
node = settings[0] if settings else cmds.createNode("VRaySettingsNode")
vray_render_presets = (
self._project_settings
["maya"]
["RenderSettings"]
["vray_renderer"]
)
render_settings = self._project_settings["maya"]["RenderSettings"]
vray_render_presets = render_settings["vray_renderer"]
# vrayRenderElement
remove_aovs = render_settings["remove_aovs"]
all_vray_aovs = cmds.ls(type='VRayRenderElement')
lightSelect_aovs = cmds.ls(type='VRayRenderElementSet')
if remove_aovs:
for aov in all_vray_aovs:
# remove all aovs except LightSelect
enabled = cmds.getAttr("{}.enabled".format(aov))
if enabled:
cmds.delete(aov)
# remove LightSelect
for light_aovs in lightSelect_aovs:
light_enabled = cmds.getAttr("{}.enabled".format(light_aovs))
if light_enabled:
cmds.delete(lightSelect_aovs)
vray_aovs = vray_render_presets["aov_list"]
for renderlayer in vray_aovs:
renderElement = "vrayAddRenderElement {}".format(renderlayer)
RE_name = mel.eval(renderElement)
# if there is more than one same render element
if RE_name.endswith("1"):
cmds.delete(RE_name)
# Set aov separator
# First we need to explicitly set the UI items in Render Settings
# because that is also what V-Ray updates to when that Render Settings

View file

@ -217,7 +217,7 @@ class ReferenceLoader(Loader):
# Need to save alembic settings and reapply, cause referencing resets
# them to incoming data.
alembic_attrs = ["speed", "offset", "cycleType"]
alembic_attrs = ["speed", "offset", "cycleType", "time"]
alembic_data = {}
if representation["name"] == "abc":
alembic_nodes = cmds.ls(
@ -226,7 +226,12 @@ class ReferenceLoader(Loader):
if alembic_nodes:
for attr in alembic_attrs:
node_attr = "{}.{}".format(alembic_nodes[0], attr)
alembic_data[attr] = cmds.getAttr(node_attr)
data = {
"input": lib.get_attribute_input(node_attr),
"value": cmds.getAttr(node_attr)
}
alembic_data[attr] = data
else:
self.log.debug("No alembic nodes found in {}".format(members))
@ -263,8 +268,19 @@ class ReferenceLoader(Loader):
"{}:*".format(namespace), type="AlembicNode"
)
if alembic_nodes:
for attr, value in alembic_data.items():
cmds.setAttr("{}.{}".format(alembic_nodes[0], attr), value)
alembic_node = alembic_nodes[0] # assume single AlembicNode
for attr, data in alembic_data.items():
node_attr = "{}.{}".format(alembic_node, attr)
input = lib.get_attribute_input(node_attr)
if data["input"]:
if data["input"] != input:
cmds.connectAttr(
data["input"], node_attr, force=True
)
else:
if input:
cmds.disconnectAttr(input, node_attr)
cmds.setAttr(node_attr, data["value"])
# Fix PLN-40 for older containers created with Avalon that had the
# `.verticesOnlySet` set to True.

View file

@ -1,5 +1,3 @@
from collections import OrderedDict
from openpype.hosts.maya.api import (
lib,
plugin
@ -9,12 +7,26 @@ from maya import cmds
class CreateAss(plugin.Creator):
"""Arnold Archive"""
"""Arnold Scene Source"""
name = "ass"
label = "Ass StandIn"
label = "Arnold Scene Source"
family = "ass"
icon = "cube"
expandProcedurals = False
motionBlur = True
motionBlurKeys = 2
motionBlurLength = 0.5
maskOptions = False
maskCamera = False
maskLight = False
maskShape = False
maskShader = False
maskOverride = False
maskDriver = False
maskFilter = False
maskColor_manager = False
maskOperator = False
def __init__(self, *args, **kwargs):
super(CreateAss, self).__init__(*args, **kwargs)
@ -22,17 +34,27 @@ class CreateAss(plugin.Creator):
# Add animation data
self.data.update(lib.collect_animation_data())
# Vertex colors with the geometry
self.data["exportSequence"] = False
self.data["expandProcedurals"] = self.expandProcedurals
self.data["motionBlur"] = self.motionBlur
self.data["motionBlurKeys"] = self.motionBlurKeys
self.data["motionBlurLength"] = self.motionBlurLength
# Masks
self.data["maskOptions"] = self.maskOptions
self.data["maskCamera"] = self.maskCamera
self.data["maskLight"] = self.maskLight
self.data["maskShape"] = self.maskShape
self.data["maskShader"] = self.maskShader
self.data["maskOverride"] = self.maskOverride
self.data["maskDriver"] = self.maskDriver
self.data["maskFilter"] = self.maskFilter
self.data["maskColor_manager"] = self.maskColor_manager
self.data["maskOperator"] = self.maskOperator
def process(self):
instance = super(CreateAss, self).process()
# data = OrderedDict(**self.data)
nodes = list()
nodes = []
if (self.options or {}).get("useSelection"):
nodes = cmds.ls(selection=True)
@ -42,7 +64,3 @@ class CreateAss(plugin.Creator):
assContent = cmds.sets(name="content_SET")
assProxy = cmds.sets(name="proxy_SET", empty=True)
cmds.sets([assContent, assProxy], forceElement=instance)
# self.log.info(data)
#
# self.data = data

View file

@ -8,3 +8,9 @@ class CreateLayout(plugin.Creator):
label = "Layout"
family = "layout"
icon = "cubes"
def __init__(self, *args, **kwargs):
super(CreateLayout, self).__init__(*args, **kwargs)
# enable this when you want to
# publish group of loaded asset
self.data["groupLoadedAssets"] = False

View file

@ -0,0 +1,35 @@
from openpype.hosts.maya.api import (
lib,
plugin
)
class CreateProxyAlembic(plugin.Creator):
"""Proxy Alembic for animated data"""
name = "proxyAbcMain"
label = "Proxy Alembic"
family = "proxyAbc"
icon = "gears"
write_color_sets = False
write_face_sets = False
def __init__(self, *args, **kwargs):
super(CreateProxyAlembic, self).__init__(*args, **kwargs)
# Add animation data
self.data.update(lib.collect_animation_data())
# Vertex colors with the geometry.
self.data["writeColorSets"] = self.write_color_sets
# Vertex colors with the geometry.
self.data["writeFaceSets"] = self.write_face_sets
# Default to exporting world-space
self.data["worldSpace"] = True
# name suffix for the bounding box
self.data["nameSuffix"] = "_BBox"
# Add options for custom attributes
self.data["attr"] = ""
self.data["attrPrefix"] = ""

View file

@ -48,3 +48,21 @@ class CreateUnrealSkeletalMesh(plugin.Creator):
cmds.sets(node, forceElement=joints_set)
else:
cmds.sets(node, forceElement=geometry_set)
# Add animation data
self.data.update(lib.collect_animation_data())
# Only renderable visible shapes
self.data["renderableOnly"] = False
# only nodes that are visible
self.data["visibleOnly"] = False
# Include parent groups
self.data["includeParentHierarchy"] = False
# Default to exporting world-space
self.data["worldSpace"] = True
# Default to suspend refresh.
self.data["refresh"] = False
# Add options for custom attributes
self.data["attr"] = ""
self.data["attrPrefix"] = ""

View file

@ -14,6 +14,7 @@ class SetFrameRangeLoader(load.LoaderPlugin):
families = ["animation",
"camera",
"proxyAbc",
"pointcache"]
representations = ["abc"]
@ -48,6 +49,7 @@ class SetFrameRangeWithHandlesLoader(load.LoaderPlugin):
families = ["animation",
"camera",
"proxyAbc",
"pointcache"]
representations = ["abc"]

View file

@ -11,7 +11,7 @@ from openpype.settings import get_project_settings
class AlembicStandinLoader(load.LoaderPlugin):
"""Load Alembic as Arnold Standin"""
families = ["animation", "model", "pointcache"]
families = ["animation", "model", "proxyAbc", "pointcache"]
representations = ["abc"]
label = "Import Alembic as Arnold Standin"

View file

@ -10,7 +10,7 @@ from openpype.settings import get_project_settings
class GpuCacheLoader(load.LoaderPlugin):
"""Load Alembic as gpuCache"""
families = ["model", "animation", "pointcache"]
families = ["model", "animation", "proxyAbc", "pointcache"]
representations = ["abc"]
label = "Import Gpu Cache"

View file

@ -16,6 +16,7 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
families = ["model",
"pointcache",
"proxyAbc",
"animation",
"mayaAscii",
"mayaScene",

View file

@ -1,4 +1,5 @@
from maya import cmds
from openpype.pipeline.publish import KnownPublishError
import pyblish.api
@ -6,6 +7,7 @@ import pyblish.api
class CollectAssData(pyblish.api.InstancePlugin):
"""Collect Ass data."""
# Offset to be after renderable camera collection.
order = pyblish.api.CollectorOrder + 0.2
label = 'Collect Ass'
families = ["ass"]
@ -23,8 +25,23 @@ class CollectAssData(pyblish.api.InstancePlugin):
instance.data['setMembers'] = members
self.log.debug('content members: {}'.format(members))
elif objset.startswith("proxy_SET"):
assert len(members) == 1, "You have multiple proxy meshes, please only use one"
if len(members) != 1:
msg = "You have multiple proxy meshes, please only use one"
raise KnownPublishError(msg)
instance.data['proxy'] = members
self.log.debug('proxy members: {}'.format(members))
# Use camera in object set if present else default to render globals
# camera.
cameras = cmds.ls(type="camera", long=True)
renderable = [c for c in cameras if cmds.getAttr("%s.renderable" % c)]
camera = renderable[0]
for node in instance.data["setMembers"]:
camera_shapes = cmds.listRelatives(
node, shapes=True, type="camera"
)
if camera_shapes:
camera = node
instance.data["camera"] = camera
self.log.debug("data: {}".format(instance.data))

View file

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
import pyblish.api
class CollectGLTF(pyblish.api.InstancePlugin):
"""Collect Assets for GLTF/GLB export."""
order = pyblish.api.CollectorOrder + 0.2
label = "Collect Asset for GLTF/GLB export"
families = ["model", "animation", "pointcache"]
def process(self, instance):
if not instance.data.get("families"):
instance.data["families"] = []
if "gltf" not in instance.data["families"]:
instance.data["families"].append("gltf")

View file

@ -1,77 +1,93 @@
import os
from maya import cmds
import arnold
from openpype.pipeline import publish
from openpype.hosts.maya.api.lib import maintained_selection
from openpype.hosts.maya.api.lib import maintained_selection, attribute_values
class ExtractAssStandin(publish.Extractor):
"""Extract the content of the instance to a ass file
"""Extract the content of the instance to a ass file"""
Things to pay attention to:
- If animation is toggled, are the frames correct
-
"""
label = "Ass Standin (.ass)"
label = "Arnold Scene Source (.ass)"
hosts = ["maya"]
families = ["ass"]
asciiAss = False
def process(self, instance):
sequence = instance.data.get("exportSequence", False)
staging_dir = self.staging_dir(instance)
filename = "{}.ass".format(instance.name)
filenames = list()
filenames = []
file_path = os.path.join(staging_dir, filename)
# Mask
mask = arnold.AI_NODE_ALL
node_types = {
"options": arnold.AI_NODE_OPTIONS,
"camera": arnold.AI_NODE_CAMERA,
"light": arnold.AI_NODE_LIGHT,
"shape": arnold.AI_NODE_SHAPE,
"shader": arnold.AI_NODE_SHADER,
"override": arnold.AI_NODE_OVERRIDE,
"driver": arnold.AI_NODE_DRIVER,
"filter": arnold.AI_NODE_FILTER,
"color_manager": arnold.AI_NODE_COLOR_MANAGER,
"operator": arnold.AI_NODE_OPERATOR
}
for key in node_types.keys():
if instance.data.get("mask" + key.title()):
mask = mask ^ node_types[key]
# Motion blur
values = {
"defaultArnoldRenderOptions.motion_blur_enable": instance.data.get(
"motionBlur", True
),
"defaultArnoldRenderOptions.motion_steps": instance.data.get(
"motionBlurKeys", 2
),
"defaultArnoldRenderOptions.motion_frames": instance.data.get(
"motionBlurLength", 0.5
)
}
# Write out .ass file
kwargs = {
"filename": file_path,
"startFrame": instance.data.get("frameStartHandle", 1),
"endFrame": instance.data.get("frameEndHandle", 1),
"frameStep": instance.data.get("step", 1),
"selected": True,
"asciiAss": self.asciiAss,
"shadowLinks": True,
"lightLinks": True,
"boundingBox": True,
"expandProcedurals": instance.data.get("expandProcedurals", False),
"camera": instance.data["camera"],
"mask": mask
}
self.log.info("Writing: '%s'" % file_path)
with maintained_selection():
self.log.info("Writing: {}".format(instance.data["setMembers"]))
cmds.select(instance.data["setMembers"], noExpand=True)
with attribute_values(values):
with maintained_selection():
self.log.info(
"Writing: {}".format(instance.data["setMembers"])
)
cmds.select(instance.data["setMembers"], noExpand=True)
if sequence:
self.log.info("Extracting ass sequence")
self.log.info(
"Extracting ass sequence with: {}".format(kwargs)
)
# Collect the start and end including handles
start = instance.data.get("frameStartHandle", 1)
end = instance.data.get("frameEndHandle", 1)
step = instance.data.get("step", 0)
exported_files = cmds.arnoldExportAss(**kwargs)
exported_files = cmds.arnoldExportAss(filename=file_path,
selected=True,
asciiAss=self.asciiAss,
shadowLinks=True,
lightLinks=True,
boundingBox=True,
startFrame=start,
endFrame=end,
frameStep=step
)
for file in exported_files:
filenames.append(os.path.split(file)[1])
self.log.info("Exported: {}".format(filenames))
else:
self.log.info("Extracting ass")
cmds.arnoldExportAss(filename=file_path,
selected=True,
asciiAss=False,
shadowLinks=True,
lightLinks=True,
boundingBox=True
)
self.log.info("Extracted {}".format(filename))
filenames = filename
optionals = [
"frameStart", "frameEnd", "step", "handles",
"handleEnd", "handleStart"
]
for key in optionals:
instance.data.pop(key, None)
if "representations" not in instance.data:
instance.data["representations"] = []
@ -79,13 +95,11 @@ class ExtractAssStandin(publish.Extractor):
representation = {
'name': 'ass',
'ext': 'ass',
'files': filenames,
"stagingDir": staging_dir
'files': filenames if len(filenames) > 1 else filenames[0],
"stagingDir": staging_dir,
'frameStart': kwargs["startFrame"]
}
if sequence:
representation['frameStart'] = start
instance.data["representations"].append(representation)
self.log.info("Extracted instance '%s' to: %s"

View file

@ -0,0 +1,65 @@
import os
from maya import cmds, mel
import pyblish.api
from openpype.pipeline import publish
from openpype.hosts.maya.api import lib
from openpype.hosts.maya.api.gltf import extract_gltf
class ExtractGLB(publish.Extractor):
order = pyblish.api.ExtractorOrder
hosts = ["maya"]
label = "Extract GLB"
families = ["gltf"]
def process(self, instance):
staging_dir = self.staging_dir(instance)
filename = "{0}.glb".format(instance.name)
path = os.path.join(staging_dir, filename)
self.log.info("Extracting GLB to: {}".format(path))
nodes = instance[:]
self.log.info("Instance: {0}".format(nodes))
start_frame = instance.data('frameStart') or \
int(cmds.playbackOptions(query=True,
animationStartTime=True))# noqa
end_frame = instance.data('frameEnd') or \
int(cmds.playbackOptions(query=True,
animationEndTime=True)) # noqa
fps = mel.eval('currentTimeUnitToFPS()')
options = {
"sno": True, # selectedNodeOnly
"nbu": True, # .bin instead of .bin0
"ast": start_frame,
"aet": end_frame,
"afr": fps,
"dsa": 1,
"acn": instance.name,
"glb": True,
"vno": True # visibleNodeOnly
}
with lib.maintained_selection():
cmds.select(nodes, hi=True, noExpand=True)
extract_gltf(staging_dir,
instance.name,
**options)
if "representations" not in instance.data:
instance.data["representations"] = []
representation = {
'name': 'glb',
'ext': 'glb',
'files': filename,
"stagingDir": staging_dir,
}
instance.data["representations"].append(representation)
self.log.info("Extract GLB successful to: {0}".format(path))

View file

@ -15,6 +15,7 @@ class ExtractLayout(publish.Extractor):
label = "Extract Layout"
hosts = ["maya"]
families = ["layout"]
project_container = "AVALON_CONTAINERS"
optional = True
def process(self, instance):
@ -33,12 +34,25 @@ class ExtractLayout(publish.Extractor):
for asset in cmds.sets(str(instance), query=True):
# Find the container
grp_name = asset.split(':')[0]
project_container = self.project_container
container_list = cmds.ls(project_container)
if len(container_list) == 0:
self.log.warning("Project container is not found!")
self.log.warning("The asset(s) may not be properly loaded after published") # noqa
continue
grp_loaded_ass = instance.data.get("groupLoadedAssets", False)
if grp_loaded_ass:
asset_list = cmds.listRelatives(asset, children=True)
for asset in asset_list:
grp_name = asset.split(':')[0]
else:
grp_name = asset.split(':')[0]
containers = cmds.ls("{}*_CON".format(grp_name))
assert len(containers) == 1, \
"More than one container found for {}".format(asset)
if len(containers) == 0:
self.log.warning("{} isn't from the loader".format(asset))
self.log.warning("It may not be properly loaded after published") # noqa
continue
container = containers[0]
representation_id = cmds.getAttr(

View file

@ -86,7 +86,8 @@ class ExtractAlembic(publish.Extractor):
start=start,
end=end))
with suspended_refresh(suspend=instance.data.get("refresh", False)):
suspend = not instance.data.get("refresh", False)
with suspended_refresh(suspend=suspend):
with maintained_selection():
cmds.select(nodes, noExpand=True)
extract_alembic(

View file

@ -0,0 +1,109 @@
import os
from maya import cmds
from openpype.pipeline import publish
from openpype.hosts.maya.api.lib import (
extract_alembic,
suspended_refresh,
maintained_selection,
iter_visible_nodes_in_range
)
class ExtractProxyAlembic(publish.Extractor):
"""Produce an alembic for bounding box geometry
"""
label = "Extract Proxy (Alembic)"
hosts = ["maya"]
families = ["proxyAbc"]
def process(self, instance):
name_suffix = instance.data.get("nameSuffix")
# Collect the start and end including handles
start = float(instance.data.get("frameStartHandle", 1))
end = float(instance.data.get("frameEndHandle", 1))
attrs = instance.data.get("attr", "").split(";")
attrs = [value for value in attrs if value.strip()]
attrs += ["cbId"]
attr_prefixes = instance.data.get("attrPrefix", "").split(";")
attr_prefixes = [value for value in attr_prefixes if value.strip()]
self.log.info("Extracting Proxy Alembic..")
dirname = self.staging_dir(instance)
filename = "{name}.abc".format(**instance.data)
path = os.path.join(dirname, filename)
proxy_root = self.create_proxy_geometry(instance,
name_suffix,
start,
end)
options = {
"step": instance.data.get("step", 1.0),
"attr": attrs,
"attrPrefix": attr_prefixes,
"writeVisibility": True,
"writeCreases": True,
"writeColorSets": instance.data.get("writeColorSets", False),
"writeFaceSets": instance.data.get("writeFaceSets", False),
"uvWrite": True,
"selection": True,
"worldSpace": instance.data.get("worldSpace", True),
"root": proxy_root
}
if int(cmds.about(version=True)) >= 2017:
# Since Maya 2017 alembic supports multiple uv sets - write them.
options["writeUVSets"] = True
with suspended_refresh():
with maintained_selection():
cmds.select(proxy_root, hi=True, noExpand=True)
extract_alembic(file=path,
startFrame=start,
endFrame=end,
**options)
if "representations" not in instance.data:
instance.data["representations"] = []
representation = {
'name': 'abc',
'ext': 'abc',
'files': filename,
"stagingDir": dirname
}
instance.data["representations"].append(representation)
instance.context.data["cleanupFullPaths"].append(path)
self.log.info("Extracted {} to {}".format(instance, dirname))
# remove the bounding box
bbox_master = cmds.ls("bbox_grp")
cmds.delete(bbox_master)
def create_proxy_geometry(self, instance, name_suffix, start, end):
nodes = instance[:]
nodes = list(iter_visible_nodes_in_range(nodes,
start=start,
end=end))
inst_selection = cmds.ls(nodes, long=True)
cmds.geomToBBox(inst_selection,
nameSuffix=name_suffix,
keepOriginal=True,
single=False,
bakeAnimation=True,
startTime=start,
endTime=end)
# create master group for bounding
# boxes as the main root
master_group = cmds.group(name="bbox_grp")
bbox_sel = cmds.ls(master_group, long=True)
self.log.debug("proxy_root: {}".format(bbox_sel))
return bbox_sel

View file

@ -105,6 +105,11 @@ class ExtractThumbnail(publish.Extractor):
pm.currentTime(refreshFrameInt - 1, edit=True)
pm.currentTime(refreshFrameInt, edit=True)
# Override transparency if requested.
transparency = instance.data.get("transparency", 0)
if transparency != 0:
preset["viewport2_options"]["transparencyAlgorithm"] = transparency
# Isolate view is requested by having objects in the set besides a
# camera.
if preset.pop("isolate_view", False) and instance.data.get("isolate"):

View file

@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
"""Create Unreal Skeletal Mesh data to be extracted as FBX."""
import os
from contextlib import contextmanager
from maya import cmds # noqa
from openpype.pipeline import publish
from openpype.hosts.maya.api.lib import (
extract_alembic,
suspended_refresh,
maintained_selection
)
@contextmanager
def renamed(original_name, renamed_name):
# type: (str, str) -> None
try:
cmds.rename(original_name, renamed_name)
yield
finally:
cmds.rename(renamed_name, original_name)
class ExtractUnrealSkeletalMeshAbc(publish.Extractor):
"""Extract Unreal Skeletal Mesh as FBX from Maya. """
label = "Extract Unreal Skeletal Mesh - Alembic"
hosts = ["maya"]
families = ["skeletalMesh"]
optional = True
def process(self, instance):
self.log.info("Extracting pointcache..")
geo = cmds.listRelatives(
instance.data.get("geometry"), allDescendents=True, fullPath=True)
joints = cmds.listRelatives(
instance.data.get("joints"), allDescendents=True, fullPath=True)
nodes = geo + joints
attrs = instance.data.get("attr", "").split(";")
attrs = [value for value in attrs if value.strip()]
attrs += ["cbId"]
attr_prefixes = instance.data.get("attrPrefix", "").split(";")
attr_prefixes = [value for value in attr_prefixes if value.strip()]
# Define output path
staging_dir = self.staging_dir(instance)
filename = "{0}.abc".format(instance.name)
path = os.path.join(staging_dir, filename)
# The export requires forward slashes because we need
# to format it into a string in a mel expression
path = path.replace('\\', '/')
self.log.info("Extracting ABC to: {0}".format(path))
self.log.info("Members: {0}".format(nodes))
self.log.info("Instance: {0}".format(instance[:]))
options = {
"step": instance.data.get("step", 1.0),
"attr": attrs,
"attrPrefix": attr_prefixes,
"writeVisibility": True,
"writeCreases": True,
"writeColorSets": instance.data.get("writeColorSets", False),
"writeFaceSets": instance.data.get("writeFaceSets", False),
"uvWrite": True,
"selection": True,
"worldSpace": instance.data.get("worldSpace", True)
}
self.log.info("Options: {}".format(options))
if int(cmds.about(version=True)) >= 2017:
# Since Maya 2017 alembic supports multiple uv sets - write them.
options["writeUVSets"] = True
if not instance.data.get("includeParentHierarchy", True):
# Set the root nodes if we don't want to include parents
# The roots are to be considered the ones that are the actual
# direct members of the set
options["root"] = instance.data.get("setMembers")
with suspended_refresh(suspend=instance.data.get("refresh", False)):
with maintained_selection():
cmds.select(nodes, noExpand=True)
extract_alembic(file=path,
# startFrame=start,
# endFrame=end,
**options)
if "representations" not in instance.data:
instance.data["representations"] = []
representation = {
'name': 'abc',
'ext': 'abc',
'files': filename,
"stagingDir": staging_dir,
}
instance.data["representations"].append(representation)
self.log.info("Extract ABC successful to: {0}".format(path))

View file

@ -21,12 +21,13 @@ def renamed(original_name, renamed_name):
cmds.rename(renamed_name, original_name)
class ExtractUnrealSkeletalMesh(publish.Extractor):
class ExtractUnrealSkeletalMeshFbx(publish.Extractor):
"""Extract Unreal Skeletal Mesh as FBX from Maya. """
order = pyblish.api.ExtractorOrder - 0.1
label = "Extract Unreal Skeletal Mesh"
label = "Extract Unreal Skeletal Mesh - FBX"
families = ["skeletalMesh"]
optional = True
def process(self, instance):
fbx_exporter = fbx.FBXExtractor(log=self.log)

View file

@ -20,7 +20,7 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin):
"""
order = ValidateContentsOrder
families = ['animation', "pointcache"]
families = ['animation', "pointcache", "proxyAbc"]
hosts = ['maya']
label = 'Animation Out Set Related Node Ids'
actions = [

View file

@ -25,6 +25,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin):
families = ["animation",
"pointcache",
"camera",
"proxyAbc",
"renderlayer",
"review",
"yeticache"]

View file

@ -28,7 +28,9 @@ class ValidateSkeletalMeshHierarchy(pyblish.api.InstancePlugin):
parent.split("|")[1] for parent in (joints_parents + geo_parents)
}
if len(set(parents_set)) != 1:
self.log.info(parents_set)
if len(set(parents_set)) > 2:
raise PublishXmlValidationError(
self,
"Multiple roots on geometry or joints."

View file

@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
import pyblish.api
from openpype.hosts.maya.api.action import (
SelectInvalidAction,
)
from openpype.pipeline.publish import (
RepairAction,
ValidateContentsOrder,
)
from maya import cmds
class ValidateSkeletalMeshTriangulated(pyblish.api.InstancePlugin):
"""Validates that the geometry has been triangulated."""
order = ValidateContentsOrder
hosts = ["maya"]
families = ["skeletalMesh"]
label = "Skeletal Mesh Triangulated"
optional = True
actions = [
SelectInvalidAction,
RepairAction
]
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError(
"The following objects needs to be triangulated: "
"{}".format(invalid))
@classmethod
def get_invalid(cls, instance):
geo = instance.data.get("geometry")
invalid = []
for obj in cmds.listRelatives(
cmds.ls(geo), allDescendents=True, fullPath=True):
n_triangles = cmds.polyEvaluate(obj, triangle=True)
n_faces = cmds.polyEvaluate(obj, face=True)
if not (isinstance(n_triangles, int) and isinstance(n_faces, int)):
continue
# We check if the number of triangles is equal to the number of
# faces for each transform node.
# If it is, the object is triangulated.
if cmds.objectType(obj, i="transform") and n_triangles != n_faces:
invalid.append(obj)
return invalid
@classmethod
def repair(cls, instance):
for node in cls.get_invalid(instance):
cmds.polyTriangulate(node)

View file

@ -10,7 +10,7 @@ from collections import OrderedDict
import clique
import nuke
from Qt import QtCore, QtWidgets
from qtpy import QtCore, QtWidgets
from openpype.client import (
get_project,
@ -81,7 +81,6 @@ class Context:
def get_main_window():
"""Acquire Nuke's main window"""
if Context.main_window is None:
from Qt import QtWidgets
top_widgets = QtWidgets.QApplication.topLevelWidgets()
name = "Foundry::UI::DockMainWindow"
@ -611,7 +610,10 @@ def get_created_node_imageio_setting_legacy(nodeclass, creator, subset):
if (
onode["subsets"]
and not any(re.search(s, subset) for s in onode["subsets"])
and not any(
re.search(s.lower(), subset.lower())
for s in onode["subsets"]
)
):
continue
@ -694,7 +696,8 @@ def get_imageio_node_override_setting(
# find matching override node
override_imageio_node = None
for onode in override_nodes:
log.info(onode)
log.debug("__ onode: {}".format(onode))
log.debug("__ subset: {}".format(subset))
if node_class not in onode["nukeNodeClass"]:
continue
@ -703,7 +706,10 @@ def get_imageio_node_override_setting(
if (
onode["subsets"]
and not any(re.search(s, subset) for s in onode["subsets"])
and not any(
re.search(s.lower(), subset.lower())
for s in onode["subsets"]
)
):
continue

View file

@ -8,7 +8,7 @@ from wsrpc_aiohttp import (
WebSocketAsync
)
from Qt import QtCore
from qtpy import QtCore
from openpype.lib import Logger
from openpype.pipeline import legacy_io

View file

@ -3,7 +3,7 @@ import sys
import contextlib
import traceback
from Qt import QtWidgets
from qtpy import QtWidgets
from openpype.lib import env_value_to_bool, Logger
from openpype.modules import ModulesManager

View file

@ -1,5 +1,7 @@
import os
from Qt import QtWidgets
from qtpy import QtWidgets
import pyblish.api
from openpype.lib import register_event_callback, Logger

View file

@ -6,7 +6,7 @@ import ctypes
import platform
import logging
from Qt import QtWidgets, QtCore, QtGui
from qtpy import QtWidgets, QtCore, QtGui
from openpype import style
from openpype.pipeline import install_host

View file

@ -207,8 +207,8 @@ class CreateRenderlayer(plugin.Creator):
)
def _ask_user_subset_override(self, instance):
from Qt import QtCore
from Qt.QtWidgets import QMessageBox
from qtpy import QtCore
from qtpy.QtWidgets import QMessageBox
title = "Subset \"{}\" already exist".format(instance["subset"])
text = (

View file

@ -2,6 +2,7 @@
import os
import logging
from typing import List
import semver
import pyblish.api
@ -21,6 +22,9 @@ import unreal # noqa
logger = logging.getLogger("openpype.hosts.unreal")
OPENPYPE_CONTAINERS = "OpenPypeContainers"
UNREAL_VERSION = semver.VersionInfo(
*os.getenv("OPENPYPE_UNREAL_VERSION").split(".")
)
HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.unreal.__file__))
PLUGINS_DIR = os.path.join(HOST_DIR, "plugins")
@ -111,7 +115,9 @@ def ls():
"""
ar = unreal.AssetRegistryHelpers.get_asset_registry()
openpype_containers = ar.get_assets_by_class("AssetContainer", True)
# UE 5.1 changed how class name is specified
class_name = ["/Script/OpenPype", "AssetContainer"] if UNREAL_VERSION.major == 5 and UNREAL_VERSION.minor > 0 else "AssetContainer" # noqa
openpype_containers = ar.get_assets_by_class(class_name, True)
# get_asset_by_class returns AssetData. To get all metadata we need to
# load asset. get_tag_values() work only on metadata registered in

View file

@ -1,5 +1,5 @@
import sys
from Qt import QtWidgets, QtCore, QtGui
from qtpy import QtWidgets, QtCore, QtGui
from openpype import (
resources,

View file

@ -150,6 +150,7 @@ class UnrealPrelaunchHook(PreLaunchHook):
engine_path=Path(engine_path)
)
self.launch_context.env["OPENPYPE_UNREAL_VERSION"] = engine_version
# Append project file to launch arguments
self.launch_context.launch_args.append(
f"\"{project_file.as_posix()}\"")

View file

@ -6,7 +6,11 @@ public class OpenPype : ModuleRules
{
public OpenPype(ReadOnlyTargetRules Target) : base(Target)
{
DefaultBuildSettings = BuildSettingsVersion.V2;
bLegacyPublicIncludePaths = false;
ShadowVariableWarningLevel = WarningLevel.Error;
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_0;
PublicIncludePaths.AddRange(
new string[] {

View file

@ -1,7 +1,7 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "AssetContainer.h"
#include "AssetRegistryModule.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Misc/PackageName.h"
#include "Engine.h"
#include "Containers/UnrealString.h"
@ -30,8 +30,8 @@ void UAssetContainer::OnAssetAdded(const FAssetData& AssetData)
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClass.ToString();
FString assetFName = AssetData.AssetClassPath.ToString();
UE_LOG(LogTemp, Log, TEXT("asset name %s"), *assetFName);
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);
@ -60,7 +60,7 @@ void UAssetContainer::OnAssetRemoved(const FAssetData& AssetData)
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClass.ToString();
FString assetFName = AssetData.AssetClassPath.ToString();
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);
@ -93,7 +93,7 @@ void UAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString&
// get asset path and class
FString assetPath = AssetData.GetFullName();
FString assetFName = AssetData.AssetClass.ToString();
FString assetFName = AssetData.AssetClassPath.ToString();
// split path
assetPath.ParseIntoArray(split, TEXT(" "), true);

View file

@ -1,10 +1,10 @@
#pragma once
#include "OpenPypePublishInstance.h"
#include "AssetRegistryModule.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetToolsModule.h"
#include "Framework/Notifications/NotificationManager.h"
#include "SNotificationList.h"
#include "Widgets/Notifications/SNotificationList.h"
//Moves all the invalid pointers to the end to prepare them for the shrinking
#define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \
@ -47,7 +47,7 @@ void UOpenPypePublishInstance::OnAssetCreated(const FAssetData& InAssetData)
if (!IsValid(Asset))
{
UE_LOG(LogAssetData, Warning, TEXT("Asset \"%s\" is not valid! Skipping the addition."),
*InAssetData.ObjectPath.ToString());
*InAssetData.GetObjectPathString());
return;
}

View file

@ -5,7 +5,7 @@
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Engine/AssetUserData.h"
#include "AssetData.h"
#include "AssetRegistry/AssetData.h"
#include "AssetContainer.generated.h"
/**

View file

@ -1,6 +1,5 @@
#pragma once
#include "EditorTutorial.h"
#include "Engine.h"
#include "OpenPypePublishInstance.generated.h"

View file

@ -50,7 +50,10 @@ def get_engine_versions(env=None):
# environment variable not set
pass
except OSError:
# specified directory doesn't exists
# specified directory doesn't exist
pass
except StopIteration:
# specified directory doesn't exist
pass
# if we've got something, terminate auto-detection process

View file

@ -0,0 +1,162 @@
# -*- coding: utf-8 -*-
"""Load Alembic Animation."""
import os
from openpype.pipeline import (
get_representation_path,
AVALON_CONTAINER_ID
)
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
import unreal # noqa
class AnimationAlembicLoader(plugin.Loader):
"""Load Unreal SkeletalMesh from Alembic"""
families = ["animation"]
label = "Import Alembic Animation"
representations = ["abc"]
icon = "cube"
color = "orange"
def get_task(self, filename, asset_dir, asset_name, replace):
task = unreal.AssetImportTask()
options = unreal.AbcImportSettings()
sm_settings = unreal.AbcStaticMeshSettings()
conversion_settings = unreal.AbcConversionSettings(
preset=unreal.AbcConversionPreset.CUSTOM,
flip_u=False, flip_v=False,
rotation=[0.0, 0.0, 0.0],
scale=[1.0, 1.0, -1.0])
task.set_editor_property('filename', filename)
task.set_editor_property('destination_path', asset_dir)
task.set_editor_property('destination_name', asset_name)
task.set_editor_property('replace_existing', replace)
task.set_editor_property('automated', True)
task.set_editor_property('save', True)
options.set_editor_property(
'import_type', unreal.AlembicImportType.SKELETAL)
options.static_mesh_settings = sm_settings
options.conversion_settings = conversion_settings
task.options = options
return task
def load(self, context, name, namespace, data):
"""Load and containerise representation into Content Browser.
This is two step process. First, import FBX to temporary path and
then call `containerise()` on it - this moves all content to new
directory and then it will create AssetContainer there and imprint it
with metadata. This will mark this path as container.
Args:
context (dict): application context
name (str): subset name
namespace (str): in Unreal this is basically path to container.
This is not passed here, so namespace is set
by `containerise()` because only then we know
real path.
data (dict): Those would be data to be imprinted. This is not used
now, data are imprinted by `containerise()`.
Returns:
list(str): list of container content
"""
# Create directory for asset and openpype container
root = "/Game/OpenPype/Assets"
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
asset_name = "{}_{}".format(asset, name)
else:
asset_name = "{}".format(name)
version = context.get('version').get('name')
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
f"{root}/{asset}/{name}_v{version:03d}", suffix="")
container_name += suffix
if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir):
unreal.EditorAssetLibrary.make_directory(asset_dir)
task = self.get_task(self.fname, asset_dir, asset_name, False)
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
asset_tools.import_asset_tasks([task])
# Create Asset Container
unreal_pipeline.create_container(
container=container_name, path=asset_dir)
data = {
"schema": "openpype:container-2.0",
"id": AVALON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": context["representation"]["_id"],
"parent": context["representation"]["parent"],
"family": context["representation"]["context"]["family"]
}
unreal_pipeline.imprint(
"{}/{}".format(asset_dir, container_name), data)
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
)
for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)
return asset_content
def update(self, container, representation):
name = container["asset_name"]
source_path = get_representation_path(representation)
destination_path = container["namespace"]
task = self.get_task(source_path, destination_path, name, True)
# do import fbx and replace existing data
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
asset_tools.import_asset_tasks([task])
container_path = f"{container['namespace']}/{container['objectName']}"
# update metadata
unreal_pipeline.imprint(
container_path,
{
"representation": str(representation["_id"]),
"parent": str(representation["parent"])
})
asset_content = unreal.EditorAssetLibrary.list_assets(
destination_path, recursive=True, include_folder=True
)
for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)
def remove(self, container):
path = container["namespace"]
parent_path = os.path.dirname(path)
unreal.EditorAssetLibrary.delete_directory(path)
asset_content = unreal.EditorAssetLibrary.list_assets(
parent_path, recursive=False
)
if len(asset_content) == 0:
unreal.EditorAssetLibrary.delete_directory(parent_path)

View file

@ -14,7 +14,7 @@ import unreal # noqa
class SkeletalMeshAlembicLoader(plugin.Loader):
"""Load Unreal SkeletalMesh from Alembic"""
families = ["pointcache"]
families = ["pointcache", "skeletalMesh"]
label = "Import Alembic Skeletal Mesh"
representations = ["abc"]
icon = "cube"

View file

@ -14,7 +14,7 @@ import unreal # noqa
class StaticMeshAlembicLoader(plugin.Loader):
"""Load Unreal StaticMesh from Alembic"""
families = ["model"]
families = ["model", "staticMesh"]
label = "Import Alembic Static Mesh"
representations = ["abc"]
icon = "cube"

View file

@ -3,6 +3,8 @@
import ast
import unreal # noqa
import pyblish.api
from openpype.hosts.unreal.api.pipeline import UNREAL_VERSION
from openpype.pipeline.publish import KnownPublishError
class CollectInstances(pyblish.api.ContextPlugin):
@ -23,8 +25,10 @@ class CollectInstances(pyblish.api.ContextPlugin):
def process(self, context):
ar = unreal.AssetRegistryHelpers.get_asset_registry()
instance_containers = ar.get_assets_by_class(
"OpenPypePublishInstance", True)
class_name = ["/Script/OpenPype",
"AssetContainer"] if UNREAL_VERSION.major == 5 and \
UNREAL_VERSION.minor > 0 else "OpenPypePublishInstance" # noqa
instance_containers = ar.get_assets_by_class(class_name, True)
for container_data in instance_containers:
asset = container_data.get_asset()
@ -32,9 +36,8 @@ class CollectInstances(pyblish.api.ContextPlugin):
data["objectName"] = container_data.asset_name
# convert to strings
data = {str(key): str(value) for (key, value) in data.items()}
assert data.get("family"), (
"instance has no family"
)
if not data.get("family"):
raise KnownPublishError("instance has no family")
# content of container
members = ast.literal_eval(data.get("members"))

View file

@ -74,22 +74,52 @@ class EventCallback(object):
"Registered callback is not callable. \"{}\""
).format(str(func)))
# Collect additional data about function
# - name
# - path
# - if expect argument or not
# Collect function name and path to file for logging
func_name = func.__name__
func_path = os.path.abspath(inspect.getfile(func))
# Get expected arguments from function spec
# - positional arguments are always preferred
expect_args = False
expect_kwargs = False
fake_event = "fake"
if hasattr(inspect, "signature"):
# Python 3 using 'Signature' object where we try to bind arg
# or kwarg. Using signature is recommended approach based on
# documentation.
sig = inspect.signature(func)
expect_args = len(sig.parameters) > 0
try:
sig.bind(fake_event)
expect_args = True
except TypeError:
pass
try:
sig.bind(event=fake_event)
expect_kwargs = True
except TypeError:
pass
else:
expect_args = len(inspect.getargspec(func)[0]) > 0
# In Python 2 'signature' is not available so 'getcallargs' is used
# - 'getcallargs' is marked as deprecated since Python 3.0
try:
inspect.getcallargs(func, fake_event)
expect_args = True
except TypeError:
pass
try:
inspect.getcallargs(func, event=fake_event)
expect_kwargs = True
except TypeError:
pass
self._func_ref = func_ref
self._func_name = func_name
self._func_path = func_path
self._expect_args = expect_args
self._expect_kwargs = expect_kwargs
self._ref_valid = func_ref is not None
self._enabled = True
@ -157,6 +187,10 @@ class EventCallback(object):
try:
if self._expect_args:
callback(event)
elif self._expect_kwargs:
callback(event=event)
else:
callback()

View file

@ -422,7 +422,7 @@ class TemplateResult(str):
cls = self.__class__
return cls(
os.path.normpath(self),
os.path.normpath(self.replace("\\", "/")),
self.template,
self.solved,
self.used_values,

View file

@ -57,7 +57,7 @@ class AvalonModule(OpenPypeModule, ITrayModule):
if not self._library_loader_imported:
return
from Qt import QtWidgets
from qtpy import QtWidgets
# Actions
action_library_loader = QtWidgets.QAction(
"Loader", tray_menu
@ -75,7 +75,7 @@ class AvalonModule(OpenPypeModule, ITrayModule):
def show_library_loader(self):
if self._library_loader_window is None:
from Qt import QtCore
from qtpy import QtCore
from openpype.tools.libraryloader import LibraryLoaderWindow
from openpype.pipeline import install_openpype_plugins

View file

@ -786,23 +786,15 @@ class ModulesManager:
).format(expected_keys, " | ".join(msg_items)))
return output
def collect_creator_plugin_paths(self, host_name):
"""Helper to collect creator plugin paths from modules.
Args:
host_name (str): For which host are creators meants.
Returns:
list: List of creator plugin paths.
"""
# Output structure
def _collect_plugin_paths(self, method_name, *args, **kwargs):
output = []
for module in self.get_enabled_modules():
# Skip module that do not inherit from `IPluginPaths`
if not isinstance(module, IPluginPaths):
continue
paths = module.get_creator_plugin_paths(host_name)
method = getattr(module, method_name)
paths = method(*args, **kwargs)
if paths:
# Convert to list if value is not list
if not isinstance(paths, (list, tuple, set)):
@ -810,6 +802,53 @@ class ModulesManager:
output.extend(paths)
return output
def collect_create_plugin_paths(self, host_name):
"""Helper to collect creator plugin paths from modules.
Args:
host_name (str): For which host are creators meant.
Returns:
list: List of creator plugin paths.
"""
return self._collect_plugin_paths(
"get_create_plugin_paths",
host_name
)
collect_creator_plugin_paths = collect_create_plugin_paths
def collect_load_plugin_paths(self, host_name):
"""Helper to collect load plugin paths from modules.
Args:
host_name (str): For which host are load plugins meant.
Returns:
list: List of load plugin paths.
"""
return self._collect_plugin_paths(
"get_load_plugin_paths",
host_name
)
def collect_publish_plugin_paths(self, host_name):
"""Helper to collect load plugin paths from modules.
Args:
host_name (str): For which host are load plugins meant.
Returns:
list: List of pyblish plugin paths.
"""
return self._collect_plugin_paths(
"get_publish_plugin_paths",
host_name
)
def get_host_module(self, host_name):
"""Find host module by host name.

View file

@ -183,7 +183,7 @@ class ClockifyModule(
# Definition of Tray menu
def tray_menu(self, parent_menu):
# Menu for Tray App
from Qt import QtWidgets
from qtpy import QtWidgets
menu = QtWidgets.QMenu("Clockify", parent_menu)
menu.setProperty("submenu", "on")

View file

@ -1,4 +1,4 @@
from Qt import QtCore, QtGui, QtWidgets
from qtpy import QtCore, QtGui, QtWidgets
from openpype import resources, style

View file

@ -188,7 +188,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
# Adding file dependencies.
if self.asset_dependencies:
dependencies = instance.context.data["fileDependencies"]
dependencies.append(context.data["currentFile"])
for dependency in dependencies:
job_info.AssetDependency += dependency
@ -299,7 +298,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
# Add export job as dependency --------------------------------------
if export_job:
job_info, _ = payload
job_info.JobDependency = export_job
job_info.JobDependencies = export_job
if instance.data.get("tileRendering"):
# Prepare tiles data
@ -436,7 +435,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
frame_assembly_job_info.ExtraInfo[0] = file_hash
frame_assembly_job_info.ExtraInfo[1] = file
frame_assembly_job_info.JobDependency = tile_job_id
frame_assembly_job_info.JobDependencies = tile_job_id
# write assembly job config files
now = datetime.now()

View file

@ -127,25 +127,17 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
"harmony": [r".*"], # for everything from AE
"celaction": [r".*"]}
enviro_filter = [
environ_job_filter = [
"OPENPYPE_METADATA_FILE"
]
environ_keys = [
"FTRACK_API_USER",
"FTRACK_API_KEY",
"FTRACK_SERVER",
"OPENPYPE_METADATA_FILE",
"AVALON_PROJECT",
"AVALON_ASSET",
"AVALON_TASK",
"AVALON_APP_NAME",
"OPENPYPE_PUBLISH_JOB"
"OPENPYPE_LOG_NO_COLORS",
"OPENPYPE_USERNAME",
"OPENPYPE_RENDER_JOB",
"OPENPYPE_PUBLISH_JOB",
"OPENPYPE_MONGO",
"OPENPYPE_VERSION",
"IS_TEST"
"OPENPYPE_VERSION"
]
# custom deadline attributes
@ -228,30 +220,43 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
instance_version = instance.data.get("version") # take this if exists
if instance_version != 1:
override_version = instance_version
output_dir = self._get_publish_folder(instance.context.data['anatomy'],
deepcopy(
instance.data["anatomyData"]),
instance.data.get("asset"),
instances[0]["subset"],
'render',
override_version)
output_dir = self._get_publish_folder(
instance.context.data['anatomy'],
deepcopy(instance.data["anatomyData"]),
instance.data.get("asset"),
instances[0]["subset"],
'render',
override_version
)
# Transfer the environment from the original job to this dependent
# job so they use the same environment
metadata_path, roothless_metadata_path = \
self._create_metadata_path(instance)
environment = job["Props"].get("Env", {})
environment["AVALON_PROJECT"] = legacy_io.Session["AVALON_PROJECT"]
environment["AVALON_ASSET"] = legacy_io.Session["AVALON_ASSET"]
environment["AVALON_TASK"] = legacy_io.Session["AVALON_TASK"]
environment["AVALON_APP_NAME"] = os.environ.get("AVALON_APP_NAME")
environment["OPENPYPE_VERSION"] = os.environ.get("OPENPYPE_VERSION")
environment["OPENPYPE_LOG_NO_COLORS"] = "1"
environment["OPENPYPE_USERNAME"] = instance.context.data["user"]
environment["OPENPYPE_PUBLISH_JOB"] = "1"
environment["OPENPYPE_RENDER_JOB"] = "0"
environment["IS_TEST"] = is_in_tests()
environment = {
"AVALON_PROJECT": legacy_io.Session["AVALON_PROJECT"],
"AVALON_ASSET": legacy_io.Session["AVALON_ASSET"],
"AVALON_TASK": legacy_io.Session["AVALON_TASK"],
"OPENPYPE_USERNAME": instance.context.data["user"],
"OPENPYPE_PUBLISH_JOB": "1",
"OPENPYPE_RENDER_JOB": "0",
"OPENPYPE_REMOTE_JOB": "0",
"OPENPYPE_LOG_NO_COLORS": "1",
"IS_TEST": str(int(is_in_tests()))
}
# add environments from self.environ_keys
for env_key in self.environ_keys:
if os.getenv(env_key):
environment[env_key] = os.environ[env_key]
# pass environment keys from self.environ_job_filter
job_environ = job["Props"].get("Env", {})
for env_j_key in self.environ_job_filter:
if job_environ.get(env_j_key):
environment[env_j_key] = job_environ[env_j_key]
# Add mongo url if it's enabled
if instance.context.data.get("deadlinePassMongoUrl"):
mongo_url = os.environ.get("OPENPYPE_MONGO")
@ -318,19 +323,15 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
if instance.data.get("suspend_publish"):
payload["JobInfo"]["InitialStatus"] = "Suspended"
index = 0
for key in environment:
if key.upper() in self.enviro_filter:
payload["JobInfo"].update(
{
"EnvironmentKeyValue%d"
% index: "{key}={value}".format(
key=key, value=environment[key]
)
}
)
index += 1
for index, (key_, value_) in enumerate(environment.items()):
payload["JobInfo"].update(
{
"EnvironmentKeyValue%d"
% index: "{key}={value}".format(
key=key_, value=value_
)
}
)
# remove secondary pool
payload["JobInfo"].pop("SecondaryPool", None)

View file

@ -1,4 +1,4 @@
from Qt import QtWidgets
from qtpy import QtWidgets
from openpype.style import load_stylesheet

View file

@ -135,9 +135,9 @@ class FirstVersionStatus(BaseEvent):
new_status = asset_version_statuses.get(found_item["status"])
if not new_status:
self.log.warning(
self.log.warning((
"AssetVersion doesn't have status `{}`."
).format(found_item["status"])
).format(found_item["status"]))
continue
try:

View file

@ -3,9 +3,9 @@ import time
import datetime
import threading
from Qt import QtCore, QtWidgets, QtGui
import ftrack_api
from qtpy import QtCore, QtWidgets, QtGui
from openpype import resources
from openpype.lib import Logger
from openpype_modules.ftrack import resolve_ftrack_url, FTRACK_MODULE_DIR

View file

@ -1,10 +1,13 @@
import os
import requests
from qtpy import QtCore, QtGui, QtWidgets
from openpype import style
from openpype_modules.ftrack.lib import credentials
from . import login_tools
from openpype import resources
from Qt import QtCore, QtGui, QtWidgets
from . import login_tools
class CredentialsDialog(QtWidgets.QDialog):

View file

@ -24,7 +24,7 @@ class OpenPypeInterface:
Child classes of OpenPypeInterface may be used as mixin in different
OpenPype modules which means they have to have implemented methods defined
in the interface. By default interface does not have any abstract parts.
in the interface. By default, interface does not have any abstract parts.
"""
pass
@ -44,40 +44,78 @@ class IPluginPaths(OpenPypeInterface):
def get_plugin_paths(self):
pass
def get_creator_plugin_paths(self, host_name):
"""Retreive creator plugin paths.
def _get_plugin_paths_by_type(self, plugin_type):
paths = self.get_plugin_paths()
if not paths or plugin_type not in paths:
return []
Give addons ability to add creator plugin paths based on host name.
paths = paths[plugin_type]
if not paths:
return []
NOTES:
- Default implementation uses 'get_plugin_paths' and always return
all creator plugins.
- Host name may help to organize plugins by host, but each creator
alsomay have host filtering.
if not isinstance(paths, (list, tuple, set)):
paths = [paths]
return paths
def get_create_plugin_paths(self, host_name):
"""Receive create plugin paths.
Give addons ability to add create plugin paths based on host name.
Notes:
Default implementation uses 'get_plugin_paths' and always return
all create plugin paths.
Args:
host_name (str): For which host are the plugins meant.
"""
paths = self.get_plugin_paths()
if not paths or "create" not in paths:
return []
if hasattr(self, "get_creator_plugin_paths"):
# TODO remove in 3.16
self.log.warning((
"DEPRECATION WARNING: Using method 'get_creator_plugin_paths'"
" which was renamed to 'get_create_plugin_paths'."
))
return self.get_creator_plugin_paths(host_name)
return self._get_plugin_paths_by_type("create")
create_paths = paths["create"]
if not create_paths:
return []
def get_load_plugin_paths(self, host_name):
"""Receive load plugin paths.
if not isinstance(create_paths, (list, tuple, set)):
create_paths = [create_paths]
return create_paths
Give addons ability to add load plugin paths based on host name.
Notes:
Default implementation uses 'get_plugin_paths' and always return
all load plugin paths.
Args:
host_name (str): For which host are the plugins meant.
"""
return self._get_plugin_paths_by_type("load")
def get_publish_plugin_paths(self, host_name):
"""Receive publish plugin paths.
Give addons ability to add publish plugin paths based on host name.
Notes:
Default implementation uses 'get_plugin_paths' and always return
all publish plugin paths.
Args:
host_name (str): For which host are the plugins meant.
"""
return self._get_plugin_paths_by_type("publish")
class ILaunchHookPaths(OpenPypeInterface):
"""Module has launch hook paths to return.
Modules does not have to inherit from this interface (changed 8.11.2022).
Module just have to have implemented 'get_launch_hook_paths' to be able use
the advantage.
Modules don't have to inherit from this interface (changed 8.11.2022).
Module just have to have implemented 'get_launch_hook_paths' to be able to
use the advantage.
Expected result is list of paths.
["path/to/launch_hooks_dir"]
@ -222,7 +260,7 @@ class ITrayAction(ITrayModule):
pass
def tray_menu(self, tray_menu):
from Qt import QtWidgets
from qtpy import QtWidgets
if self.admin_action:
menu = self.admin_submenu(tray_menu)
@ -247,7 +285,7 @@ class ITrayAction(ITrayModule):
@staticmethod
def admin_submenu(tray_menu):
if ITrayAction._admin_submenu is None:
from Qt import QtWidgets
from qtpy import QtWidgets
admin_submenu = QtWidgets.QMenu("Admin", tray_menu)
admin_submenu.menuAction().setVisible(False)
@ -279,7 +317,7 @@ class ITrayService(ITrayModule):
@staticmethod
def services_submenu(tray_menu):
if ITrayService._services_submenu is None:
from Qt import QtWidgets
from qtpy import QtWidgets
services_submenu = QtWidgets.QMenu("Services", tray_menu)
services_submenu.menuAction().setVisible(False)
@ -294,7 +332,7 @@ class ITrayService(ITrayModule):
@staticmethod
def _load_service_icons():
from Qt import QtGui
from qtpy import QtGui
ITrayService._failed_icon = QtGui.QIcon(
resources.get_resource("icons", "circle_red.png")
@ -325,7 +363,7 @@ class ITrayService(ITrayModule):
return ITrayService._failed_icon
def tray_menu(self, tray_menu):
from Qt import QtWidgets
from qtpy import QtWidgets
action = QtWidgets.QAction(
self.label,

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