mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Merge branch 'release/3.15.x' into bugfix/update_dialog_sizes
This commit is contained in:
commit
1453d15903
210 changed files with 2296 additions and 769 deletions
69
CHANGELOG.md
69
CHANGELOG.md
|
|
@ -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)
|
||||
|
|
|
|||
69
HISTORY.md
69
HISTORY.md
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtWidgets, QtGui
|
||||
from qtpy import QtWidgets, QtGui
|
||||
|
||||
from .tools import (
|
||||
load_stylesheet,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtCore, QtGui, QtWidgets # noqa
|
||||
from qtpy import QtWidgets
|
||||
|
||||
|
||||
class NiceProgressBar(QtWidgets.QProgressBar):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
from Qt import QtWidgets
|
||||
|
||||
from qtpy import QtWidgets
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
82
openpype/hosts/blender/plugins/load/import_workfile.py
Normal file
82
openpype/hosts/blender/plugins/load/import_workfile.py
Normal 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
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtWidgets, QtCore
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
import uiwidgets
|
||||
import app_utils
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtWidgets, QtCore
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
|
||||
class FlameLabel(QtWidgets.QLabel):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import sys
|
|||
import logging
|
||||
|
||||
import pyblish.api
|
||||
from Qt import QtCore
|
||||
from qtpy import QtCore
|
||||
|
||||
from openpype.lib import (
|
||||
Logger,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
from Qt import QtCore
|
||||
from qtpy import QtCore
|
||||
|
||||
|
||||
class PulseThread(QtCore.QThread):
|
||||
|
|
|
|||
|
|
@ -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..")
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import sys
|
|||
import glob
|
||||
import logging
|
||||
|
||||
from Qt import QtWidgets, QtCore
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
import qtawesome as qta
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtGui, QtWidgets
|
||||
from qtpy import QtGui, QtWidgets
|
||||
|
||||
from openpype.pipeline import InventoryAction
|
||||
from openpype import style
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtWidgets
|
||||
from qtpy import QtWidgets
|
||||
import qtawesome
|
||||
from openpype.hosts.fusion.api import get_current_comp
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import logging
|
||||
|
||||
from scriptsmenu import scriptsmenu
|
||||
from Qt import QtWidgets
|
||||
from qtpy import QtWidgets
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
88
openpype/hosts/maya/api/gltf.py
Normal file
88
openpype/hosts/maya/api/gltf.py
Normal 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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
35
openpype/hosts/maya/plugins/create/create_proxy_abc.py
Normal file
35
openpype/hosts/maya/plugins/create/create_proxy_abc.py
Normal 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"] = ""
|
||||
|
|
@ -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"] = ""
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
|||
|
||||
families = ["model",
|
||||
"pointcache",
|
||||
"proxyAbc",
|
||||
"animation",
|
||||
"mayaAscii",
|
||||
"mayaScene",
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
17
openpype/hosts/maya/plugins/publish/collect_gltf.py
Normal file
17
openpype/hosts/maya/plugins/publish/collect_gltf.py
Normal 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")
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
65
openpype/hosts/maya/plugins/publish/extract_gltf.py
Normal file
65
openpype/hosts/maya/plugins/publish/extract_gltf.py
Normal 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))
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
109
openpype/hosts/maya/plugins/publish/extract_proxy_abc.py
Normal file
109
openpype/hosts/maya/plugins/publish/extract_proxy_abc.py
Normal 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
|
||||
|
|
@ -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"):
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
@ -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)
|
||||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin):
|
|||
families = ["animation",
|
||||
"pointcache",
|
||||
"camera",
|
||||
"proxyAbc",
|
||||
"renderlayer",
|
||||
"review",
|
||||
"yeticache"]
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import sys
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
from qtpy import QtWidgets, QtCore, QtGui
|
||||
|
||||
from openpype import (
|
||||
resources,
|
||||
|
|
|
|||
|
|
@ -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()}\"")
|
||||
|
|
|
|||
|
|
@ -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[] {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include "EditorTutorial.h"
|
||||
#include "Engine.h"
|
||||
#include "OpenPypePublishInstance.generated.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
162
openpype/hosts/unreal/plugins/load/load_alembic_animation.py
Normal file
162
openpype/hosts/unreal/plugins/load/load_alembic_animation.py
Normal 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)
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtCore, QtGui, QtWidgets
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
from openpype import resources, style
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from Qt import QtWidgets
|
||||
from qtpy import QtWidgets
|
||||
|
||||
from openpype.style import load_stylesheet
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue