Merge remote-tracking branch 'origin/develop' into feature/OP-2795_maya-to-unreal-skeletal-meshes

This commit is contained in:
Ondřej Samohel 2022-03-21 17:35:48 +01:00
commit e67ac0cda1
No known key found for this signature in database
GPG key ID: 02376E18990A97C6
376 changed files with 9446 additions and 3658 deletions

View file

@ -43,7 +43,7 @@ jobs:
uses: heinrichreimer/github-changelog-generator-action@v2.2
with:
token: ${{ secrets.ADMIN_TOKEN }}
addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}}'
addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}'
issues: false
issuesWoLabels: false
sinceTag: "3.0.0"

View file

@ -39,7 +39,7 @@ jobs:
uses: heinrichreimer/github-changelog-generator-action@v2.2
with:
token: ${{ secrets.ADMIN_TOKEN }}
addSections: '{"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]},"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]}}'
addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}'
issues: false
issuesWoLabels: false
sinceTag: "3.0.0"
@ -81,7 +81,7 @@ jobs:
uses: heinrichreimer/github-changelog-generator-action@v2.2
with:
token: ${{ secrets.ADMIN_TOKEN }}
addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}}'
addSections: '{"documentation":{"prefix":"### 📖 Documentation","labels":["type: documentation"]},"tests":{"prefix":"### ✅ Testing","labels":["tests"]},"feature":{"prefix":"**🆕 New features**", "labels":["type: feature"]},"breaking":{"prefix":"**💥 Breaking**", "labels":["breaking"]},"enhancements":{"prefix":"**🚀 Enhancements**", "labels":["type: enhancement"]},"bugs":{"prefix":"**🐛 Bug fixes**", "labels":["type: bug"]},"deprecated":{"prefix":"**⚠️ Deprecations**", "labels":["depreciated"]}, "refactor":{"prefix":"**🔀 Refactored code**", "labels":["refactor"]}}'
issues: false
issuesWoLabels: false
sinceTag: ${{ steps.version.outputs.last_release }}

View file

@ -1,67 +1,117 @@
# Changelog
## [3.9.0-nightly.5](https://github.com/pypeclub/OpenPype/tree/HEAD)
## [3.9.2-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.2...HEAD)
**Deprecated:**
- Houdini: Remove unused code [\#2779](https://github.com/pypeclub/OpenPype/pull/2779)
### 📖 Documentation
- Documentation: fixed broken links [\#2799](https://github.com/pypeclub/OpenPype/pull/2799)
- Documentation: broken link fix [\#2785](https://github.com/pypeclub/OpenPype/pull/2785)
- Documentation: link fixes [\#2772](https://github.com/pypeclub/OpenPype/pull/2772)
- Update docusaurus to latest version [\#2760](https://github.com/pypeclub/OpenPype/pull/2760)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.1...HEAD)
**🚀 Enhancements**
- General: Color dialog UI fixes [\#2817](https://github.com/pypeclub/OpenPype/pull/2817)
- General: Set context environments for non host applications [\#2803](https://github.com/pypeclub/OpenPype/pull/2803)
- Tray publisher: New Tray Publisher host \(beta\) [\#2778](https://github.com/pypeclub/OpenPype/pull/2778)
- Houdini: Implement Reset Frame Range [\#2770](https://github.com/pypeclub/OpenPype/pull/2770)
- Pyblish Pype: Remove redundant new line in installed fonts printing [\#2758](https://github.com/pypeclub/OpenPype/pull/2758)
- Flame: use Shot Name on segment for asset name [\#2751](https://github.com/pypeclub/OpenPype/pull/2751)
- Flame: adding validator source clip [\#2746](https://github.com/pypeclub/OpenPype/pull/2746)
- Ftrack: Disable ftrack module by default [\#2732](https://github.com/pypeclub/OpenPype/pull/2732)
- RoyalRender: Minor enhancements [\#2700](https://github.com/pypeclub/OpenPype/pull/2700)
- CI: change the version bump logic [\#2919](https://github.com/pypeclub/OpenPype/pull/2919)
- Deadline: Add headless argument [\#2916](https://github.com/pypeclub/OpenPype/pull/2916)
- Ftrack: Fill workfile in custom attribute [\#2906](https://github.com/pypeclub/OpenPype/pull/2906)
- Settings UI: Add simple tooltips for settings entities [\#2901](https://github.com/pypeclub/OpenPype/pull/2901)
**🐛 Bug fixes**
- Ftrack: Missing Ftrack id after editorial publish [\#2905](https://github.com/pypeclub/OpenPype/pull/2905)
- AfterEffects: Fix rendering for single frame in DL [\#2875](https://github.com/pypeclub/OpenPype/pull/2875)
**🔀 Refactored code**
- General: Move formatting and workfile functions [\#2914](https://github.com/pypeclub/OpenPype/pull/2914)
## [3.9.1](https://github.com/pypeclub/OpenPype/tree/3.9.1) (2022-03-18)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.9.1-nightly.3...3.9.1)
**🚀 Enhancements**
- General: Change how OPENPYPE\_DEBUG value is handled [\#2907](https://github.com/pypeclub/OpenPype/pull/2907)
- nuke: imageio adding ocio config version 1.2 [\#2897](https://github.com/pypeclub/OpenPype/pull/2897)
- Flame: support for comment with xml attribute overrides [\#2892](https://github.com/pypeclub/OpenPype/pull/2892)
- Nuke: ExtractReviewSlate can handle more codes and profiles [\#2879](https://github.com/pypeclub/OpenPype/pull/2879)
- Flame: sequence used for reference video [\#2869](https://github.com/pypeclub/OpenPype/pull/2869)
**🐛 Bug fixes**
- General: Fix use of Anatomy roots [\#2904](https://github.com/pypeclub/OpenPype/pull/2904)
- Fixing gap detection in extract review [\#2902](https://github.com/pypeclub/OpenPype/pull/2902)
- Pyblish Pype - ensure current state is correct when entering new group order [\#2899](https://github.com/pypeclub/OpenPype/pull/2899)
- SceneInventory: Fix import of load function [\#2894](https://github.com/pypeclub/OpenPype/pull/2894)
- Harmony - fixed creator issue [\#2891](https://github.com/pypeclub/OpenPype/pull/2891)
- General: Remove forgotten use of avalon Creator [\#2885](https://github.com/pypeclub/OpenPype/pull/2885)
- General: Avoid circular import [\#2884](https://github.com/pypeclub/OpenPype/pull/2884)
- Fixes for attaching loaded containers \(\#2837\) [\#2874](https://github.com/pypeclub/OpenPype/pull/2874)
**🔀 Refactored code**
- General: Reduce style usage to OpenPype repository [\#2889](https://github.com/pypeclub/OpenPype/pull/2889)
- General: Move loader logic from avalon to openpype [\#2886](https://github.com/pypeclub/OpenPype/pull/2886)
## [3.9.0](https://github.com/pypeclub/OpenPype/tree/3.9.0) (2022-03-14)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.9.0-nightly.9...3.9.0)
**Deprecated:**
- AssetCreator: Remove the tool [\#2845](https://github.com/pypeclub/OpenPype/pull/2845)
### 📖 Documentation
- Documentation: Change Photoshop & AfterEffects plugin path [\#2878](https://github.com/pypeclub/OpenPype/pull/2878)
**🚀 Enhancements**
- General: Subset name filtering in ExtractReview outpus [\#2872](https://github.com/pypeclub/OpenPype/pull/2872)
- NewPublisher: Descriptions and Icons in creator dialog [\#2867](https://github.com/pypeclub/OpenPype/pull/2867)
- NewPublisher: Changing task on publishing instance [\#2863](https://github.com/pypeclub/OpenPype/pull/2863)
- TrayPublisher: Choose project widget is more clear [\#2859](https://github.com/pypeclub/OpenPype/pull/2859)
- New: Validation exceptions [\#2841](https://github.com/pypeclub/OpenPype/pull/2841)
- Maya: add loaded containers to published instance [\#2837](https://github.com/pypeclub/OpenPype/pull/2837)
- Ftrack: Can sync fps as string [\#2836](https://github.com/pypeclub/OpenPype/pull/2836)
- General: Custom function for find executable [\#2822](https://github.com/pypeclub/OpenPype/pull/2822)
- General: Color dialog UI fixes [\#2817](https://github.com/pypeclub/OpenPype/pull/2817)
- global: letter box calculated on output as last process [\#2812](https://github.com/pypeclub/OpenPype/pull/2812)
- Nuke: adding Reformat to baking mov plugin [\#2811](https://github.com/pypeclub/OpenPype/pull/2811)
- Manager: Update all to latest button [\#2805](https://github.com/pypeclub/OpenPype/pull/2805)
**🐛 Bug fixes**
- General: Missing time function [\#2877](https://github.com/pypeclub/OpenPype/pull/2877)
- Deadline: Fix plugin name for tile assemble [\#2868](https://github.com/pypeclub/OpenPype/pull/2868)
- Nuke: gizmo precollect fix [\#2866](https://github.com/pypeclub/OpenPype/pull/2866)
- General: Fix hardlink for windows [\#2864](https://github.com/pypeclub/OpenPype/pull/2864)
- General: ffmpeg was crashing on slate merge [\#2860](https://github.com/pypeclub/OpenPype/pull/2860)
- WebPublisher: Video file was published with one too many frame [\#2858](https://github.com/pypeclub/OpenPype/pull/2858)
- New Publisher: Error dialog got right styles [\#2857](https://github.com/pypeclub/OpenPype/pull/2857)
- General: Fix getattr clalback on dynamic modules [\#2855](https://github.com/pypeclub/OpenPype/pull/2855)
- Nuke: slate resolution to input video resolution [\#2853](https://github.com/pypeclub/OpenPype/pull/2853)
- WebPublisher: Fix username stored in DB [\#2852](https://github.com/pypeclub/OpenPype/pull/2852)
- WebPublisher: Fix wrong number of frames for video file [\#2851](https://github.com/pypeclub/OpenPype/pull/2851)
- Nuke: Fix family test in validate\_write\_legacy to work with stillImage [\#2847](https://github.com/pypeclub/OpenPype/pull/2847)
- Nuke: fix multiple baking profile farm publishing [\#2842](https://github.com/pypeclub/OpenPype/pull/2842)
- Blender: Fixed parameters for FBX export of the camera [\#2840](https://github.com/pypeclub/OpenPype/pull/2840)
- Maya: Stop creation of reviews for Cryptomattes [\#2832](https://github.com/pypeclub/OpenPype/pull/2832)
- Deadline: Remove recreated event [\#2828](https://github.com/pypeclub/OpenPype/pull/2828)
- Deadline: Added missing events folder [\#2827](https://github.com/pypeclub/OpenPype/pull/2827)
- Maya: Deformer node ids validation plugin [\#2826](https://github.com/pypeclub/OpenPype/pull/2826)
- Settings: Missing document with OP versions may break start of OpenPype [\#2825](https://github.com/pypeclub/OpenPype/pull/2825)
- Deadline: more detailed temp file name for environment json [\#2824](https://github.com/pypeclub/OpenPype/pull/2824)
- General: Host name was formed from obsolete code [\#2821](https://github.com/pypeclub/OpenPype/pull/2821)
- Settings UI: Fix "Apply from" action [\#2820](https://github.com/pypeclub/OpenPype/pull/2820)
- Settings UI: Search case sensitivity [\#2810](https://github.com/pypeclub/OpenPype/pull/2810)
- Flame Babypublisher optimalization [\#2806](https://github.com/pypeclub/OpenPype/pull/2806)
- resolve: fixing fusion module loading [\#2802](https://github.com/pypeclub/OpenPype/pull/2802)
- Flame: Fix version string in default settings [\#2783](https://github.com/pypeclub/OpenPype/pull/2783)
- After Effects: Fix typo in name `afftereffects` -\> `aftereffects` [\#2768](https://github.com/pypeclub/OpenPype/pull/2768)
- Avoid renaming udim indexes [\#2765](https://github.com/pypeclub/OpenPype/pull/2765)
- Maya: Fix `unique\_namespace` when in an namespace that is empty [\#2759](https://github.com/pypeclub/OpenPype/pull/2759)
- Loader UI: Fix right click in representation widget [\#2757](https://github.com/pypeclub/OpenPype/pull/2757)
- Aftereffects 2022 and Deadline [\#2748](https://github.com/pypeclub/OpenPype/pull/2748)
- Flame: bunch of bugs [\#2745](https://github.com/pypeclub/OpenPype/pull/2745)
- Maya: Save current scene on workfile publish [\#2744](https://github.com/pypeclub/OpenPype/pull/2744)
- Version Up: Preserve parts of filename after version number \(like subversion\) on version\_up [\#2741](https://github.com/pypeclub/OpenPype/pull/2741)
- Maya: Remove some unused code [\#2709](https://github.com/pypeclub/OpenPype/pull/2709)
**Merged pull requests:**
- Move Unreal Implementation to OpenPype [\#2823](https://github.com/pypeclub/OpenPype/pull/2823)
- Ftrack: Job killer with missing user [\#2819](https://github.com/pypeclub/OpenPype/pull/2819)
- Ftrack: Unset task ids from asset versions before tasks are removed [\#2800](https://github.com/pypeclub/OpenPype/pull/2800)
- Slack: fail gracefully if slack exception [\#2798](https://github.com/pypeclub/OpenPype/pull/2798)
- Ftrack: Moved module one hierarchy level higher [\#2792](https://github.com/pypeclub/OpenPype/pull/2792)
- SyncServer: Moved module one hierarchy level higher [\#2791](https://github.com/pypeclub/OpenPype/pull/2791)
- Royal render: Move module one hierarchy level higher [\#2790](https://github.com/pypeclub/OpenPype/pull/2790)
- Deadline: Move module one hierarchy level higher [\#2789](https://github.com/pypeclub/OpenPype/pull/2789)
- Houdini: Remove duplicate ValidateOutputNode plug-in [\#2780](https://github.com/pypeclub/OpenPype/pull/2780)
- Slack: Added regex for filtering on subset names [\#2775](https://github.com/pypeclub/OpenPype/pull/2775)
- Houdini: Fix open last workfile [\#2767](https://github.com/pypeclub/OpenPype/pull/2767)
- General: Extract template formatting from anatomy [\#2766](https://github.com/pypeclub/OpenPype/pull/2766)
- Harmony: Rendering in Deadline didn't work in other machines than submitter [\#2754](https://github.com/pypeclub/OpenPype/pull/2754)
- Houdini: Move Houdini Save Current File to beginning of ExtractorOrder [\#2747](https://github.com/pypeclub/OpenPype/pull/2747)
- Maya: set Deadline job/batch name to original source workfile name instead of published workfile [\#2733](https://github.com/pypeclub/OpenPype/pull/2733)
- Nuke: Use AVALON\_APP to get value for "app" key [\#2818](https://github.com/pypeclub/OpenPype/pull/2818)
- StandalonePublisher: use dynamic groups in subset names [\#2816](https://github.com/pypeclub/OpenPype/pull/2816)
**🔀 Refactored code**
- Refactor: move webserver tool to openpype [\#2876](https://github.com/pypeclub/OpenPype/pull/2876)
- General: Move create logic from avalon to OpenPype [\#2854](https://github.com/pypeclub/OpenPype/pull/2854)
- General: Add vendors from avalon [\#2848](https://github.com/pypeclub/OpenPype/pull/2848)
- General: Basic event system [\#2846](https://github.com/pypeclub/OpenPype/pull/2846)
- General: Move change context functions [\#2839](https://github.com/pypeclub/OpenPype/pull/2839)
- Tools: Don't use avalon tools code [\#2829](https://github.com/pypeclub/OpenPype/pull/2829)
- Move Unreal Implementation to OpenPype [\#2823](https://github.com/pypeclub/OpenPype/pull/2823)
## [3.8.2](https://github.com/pypeclub/OpenPype/tree/3.8.2) (2022-02-07)

View file

@ -10,7 +10,8 @@ from .lib import (
Anatomy,
filter_pyblish_plugins,
set_plugin_attributes_from_settings,
change_timer_to_current_context
change_timer_to_current_context,
register_event_callback,
)
pyblish = avalon = _original_discover = None
@ -58,10 +59,15 @@ def patched_discover(superclass):
"""
# run original discover and get plugins
plugins = _original_discover(superclass)
filtered_plugins = [
plugin
for plugin in plugins
if issubclass(plugin, superclass)
]
set_plugin_attributes_from_settings(plugins, superclass)
set_plugin_attributes_from_settings(filtered_plugins, superclass)
return plugins
return filtered_plugins
@import_wrapper
@ -69,6 +75,10 @@ def install():
"""Install Pype to Avalon."""
from pyblish.lib import MessageHandler
from openpype.modules import load_modules
from openpype.pipeline import (
LegacyCreator,
register_loader_plugin_path,
)
from avalon import pipeline
# Make sure modules are loaded
@ -84,7 +94,7 @@ def install():
log.info("Registering global plug-ins..")
pyblish.register_plugin_path(PUBLISH_PATH)
pyblish.register_discovery_filter(filter_pyblish_plugins)
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
register_loader_plugin_path(LOAD_PATH)
project_name = os.environ.get("AVALON_PROJECT")
@ -112,8 +122,8 @@ def install():
continue
pyblish.register_plugin_path(path)
avalon.register_plugin_path(avalon.Loader, path)
avalon.register_plugin_path(avalon.Creator, path)
register_loader_plugin_path(path)
avalon.register_plugin_path(LegacyCreator, path)
avalon.register_plugin_path(avalon.InventoryAction, path)
# apply monkey patched discover to original one
@ -122,20 +132,22 @@ def install():
avalon.discover = patched_discover
pipeline.discover = patched_discover
avalon.on("taskChanged", _on_task_change)
register_event_callback("taskChanged", _on_task_change)
def _on_task_change(*args):
def _on_task_change():
change_timer_to_current_context()
@import_wrapper
def uninstall():
"""Uninstall Pype from Avalon."""
from openpype.pipeline import deregister_loader_plugin_path
log.info("Deregistering global plug-ins..")
pyblish.deregister_plugin_path(PUBLISH_PATH)
pyblish.deregister_discovery_filter(filter_pyblish_plugins)
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
deregister_loader_plugin_path(LOAD_PATH)
log.info("Global plug-ins unregistred")
# restore original discover

View file

@ -45,9 +45,6 @@ from .lib.avalon_context import (
from . import resources
from .plugin import (
PypeCreatorMixin,
Creator,
Extractor,
ValidatePipelineOrder,
@ -89,9 +86,6 @@ __all__ = [
# Resources
"resources",
# Pype creator mixin
"PypeCreatorMixin",
"Creator",
# plugin classes
"Extractor",
# ordering

View file

@ -101,7 +101,7 @@ def eventserver(debug,
on linux and window service).
"""
if debug:
os.environ['OPENPYPE_DEBUG'] = "3"
os.environ["OPENPYPE_DEBUG"] = "1"
PypeCommands().launch_eventservercli(
ftrack_url,
@ -128,7 +128,7 @@ def webpublisherwebserver(debug, executable, upload_dir, host=None, port=None):
Expect "pype.club" user created on Ftrack.
"""
if debug:
os.environ['OPENPYPE_DEBUG'] = "3"
os.environ["OPENPYPE_DEBUG"] = "1"
PypeCommands().launch_webpublisher_webservercli(
upload_dir=upload_dir,
@ -176,7 +176,7 @@ def publish(debug, paths, targets, gui):
More than one path is allowed.
"""
if debug:
os.environ['OPENPYPE_DEBUG'] = '3'
os.environ["OPENPYPE_DEBUG"] = "1"
PypeCommands.publish(list(paths), targets, gui)
@ -195,7 +195,7 @@ def remotepublishfromapp(debug, project, path, host, user=None, targets=None):
More than one path is allowed.
"""
if debug:
os.environ['OPENPYPE_DEBUG'] = '3'
os.environ["OPENPYPE_DEBUG"] = "1"
PypeCommands.remotepublishfromapp(
project, path, host, user, targets=targets
)
@ -215,7 +215,7 @@ def remotepublish(debug, project, path, user=None, targets=None):
More than one path is allowed.
"""
if debug:
os.environ['OPENPYPE_DEBUG'] = '3'
os.environ["OPENPYPE_DEBUG"] = "1"
PypeCommands.remotepublish(project, path, user, targets=targets)
@ -240,7 +240,7 @@ def texturecopy(debug, project, asset, path):
Nothing is written to database.
"""
if debug:
os.environ['OPENPYPE_DEBUG'] = '3'
os.environ["OPENPYPE_DEBUG"] = "1"
PypeCommands().texture_copy(project, asset, path)
@ -409,7 +409,7 @@ def syncserver(debug, active_site):
var OPENPYPE_LOCAL_ID set to 'active_site'.
"""
if debug:
os.environ['OPENPYPE_DEBUG'] = '3'
os.environ["OPENPYPE_DEBUG"] = "1"
PypeCommands().syncserver(active_site)

View file

@ -15,7 +15,7 @@ from Qt import QtCore
from openpype.tools.utils import host_tools
from avalon import api
from avalon.tools.webserver.app import WebServerTool
from openpype.tools.adobe_webserver.app import WebServerTool
from .ws_stub import AfterEffectsServerStub

View file

@ -9,7 +9,13 @@ from avalon import io, pipeline
from openpype import lib
from openpype.api import Logger
from openpype.pipeline import (
LegacyCreator,
register_loader_plugin_path,
deregister_loader_plugin_path,
)
import openpype.hosts.aftereffects
from openpype.lib import register_event_callback
from .launch_logic import get_stub
@ -65,21 +71,21 @@ def install():
pyblish.api.register_host("aftereffects")
pyblish.api.register_plugin_path(PUBLISH_PATH)
avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH)
register_loader_plugin_path(LOAD_PATH)
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
log.info(PUBLISH_PATH)
pyblish.api.register_callback(
"instanceToggled", on_pyblish_instance_toggled
)
avalon.api.on("application.launched", application_launch)
register_event_callback("application.launched", application_launch)
def uninstall():
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH)
deregister_loader_plugin_path(LOAD_PATH)
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
def on_pyblish_instance_toggled(instance, old_value, new_value):

View file

@ -1,9 +1,8 @@
import avalon.api
from openpype.pipeline import LoaderPlugin
from .launch_logic import get_stub
class AfterEffectsLoader(avalon.api.Loader):
class AfterEffectsLoader(LoaderPlugin):
@staticmethod
def get_stub():
return get_stub()

View file

@ -8,7 +8,7 @@ import logging
import attr
from wsrpc_aiohttp import WebSocketAsync
from avalon.tools.webserver.app import WebServerTool
from openpype.tools.adobe_webserver.app import WebServerTool
@attr.s

View file

@ -1,13 +1,12 @@
from avalon.api import CreatorError
import openpype.api
from openpype.pipeline import create
from openpype.pipeline import CreatorError
from openpype.hosts.aftereffects.api import (
get_stub,
list_instances
)
class CreateRender(openpype.api.Creator):
class CreateRender(create.LegacyCreator):
"""Render folder for publish.
Creates subsets in format 'familyTaskSubsetname',

View file

@ -1,11 +1,10 @@
import re
import avalon.api
from openpype.lib import (
get_background_layers,
get_unique_layer_name
)
from openpype.pipeline import get_representation_path
from openpype.hosts.aftereffects.api import (
AfterEffectsLoader,
containerise
@ -78,7 +77,7 @@ class BackgroundLoader(AfterEffectsLoader):
else: # switching version - keep same name
comp_name = container["namespace"]
path = avalon.api.get_representation_path(representation)
path = get_representation_path(representation)
layers = get_background_layers(path)
comp = stub.reload_background(container["members"][1],

View file

@ -1,8 +1,8 @@
import re
import avalon.api
from openpype import lib
from openpype.pipeline import get_representation_path
from openpype.hosts.aftereffects.api import (
AfterEffectsLoader,
containerise
@ -92,7 +92,7 @@ class FileLoader(AfterEffectsLoader):
"{}_{}".format(context["asset"], context["subset"]))
else: # switching version - keep same name
layer_name = container["namespace"]
path = avalon.api.get_representation_path(representation)
path = get_representation_path(representation)
# with aftereffects.maintained_selection(): # TODO
stub.replace_item(layer.id, path, stub.LOADED_ICON + layer_name)
stub.imprint(

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<error id="main">
<title>Subset context</title>
<description>
## Invalid subset context
Context of the given subset doesn't match your current scene.
### How to repair?
You can fix this with "repair" button on the right.
</description>
<detail>
### __Detailed Info__ (optional)
This might happen if you are reuse old workfile and open it in different context.
(Eg. you created subset "renderCompositingDefault" from asset "Robot' in "your_project_Robot_compositing.aep", now you opened this workfile in a context "Sloth" but existing subset for "Robot" asset stayed in the workfile.)
</detail>
</error>
</root>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<error id="main">
<title>Scene setting</title>
<description>
## Invalid scene setting found
One of the settings in a scene doesn't match to asset settings in database.
{invalid_setting_str}
### How to repair?
Change values for {invalid_keys_str} in the scene OR change them in the asset database if they are wrong there.
</description>
<detail>
### __Detailed Info__ (optional)
This error is shown when for example resolution in the scene doesn't match to resolution set on the asset in the database.
Either value in the database or in the scene is wrong.
</detail>
</error>
<error id="file_not_found">
<title>Scene file doesn't exist</title>
<description>
## Scene file doesn't exist
Collected scene {scene_url} doesn't exist.
### How to repair?
Re-save file, start publish from the beginning again.
</description>
</error>
</root>

View file

@ -1,6 +1,7 @@
from avalon import api
import pyblish.api
import openpype.api
from openpype.pipeline import PublishXmlValidationError
from openpype.hosts.aftereffects.api import get_stub
@ -53,9 +54,8 @@ class ValidateInstanceAsset(pyblish.api.InstancePlugin):
current_asset = api.Session["AVALON_ASSET"]
msg = (
f"Instance asset {instance_asset} is not the same "
f"as current context {current_asset}. PLEASE DO:\n"
f"Repair with 'A' action to use '{current_asset}'.\n"
f"If that's not correct value, close workfile and "
f"reopen via Workfiles!"
f"as current context {current_asset}."
)
assert instance_asset == current_asset, msg
if instance_asset != current_asset:
raise PublishXmlValidationError(self, msg)

View file

@ -5,6 +5,7 @@ import re
import pyblish.api
from openpype.pipeline import PublishXmlValidationError
from openpype.hosts.aftereffects.api import get_asset_settings
@ -99,12 +100,14 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin):
self.log.info("current_settings:: {}".format(current_settings))
invalid_settings = []
invalid_keys = set()
for key, value in expected_settings.items():
if value != current_settings[key]:
invalid_settings.append(
"{} expected: {} found: {}".format(key, value,
current_settings[key])
)
invalid_keys.add(key)
if ((expected_settings.get("handleStart")
or expected_settings.get("handleEnd"))
@ -116,7 +119,27 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin):
msg = "Found invalid settings:\n{}".format(
"\n".join(invalid_settings)
)
assert not invalid_settings, msg
assert os.path.exists(instance.data.get("source")), (
"Scene file not found (saved under wrong name)"
)
if invalid_settings:
invalid_keys_str = ",".join(invalid_keys)
break_str = "<br/>"
invalid_setting_str = "<b>Found invalid settings:</b><br/>{}".\
format(break_str.join(invalid_settings))
formatting_data = {
"invalid_setting_str": invalid_setting_str,
"invalid_keys_str": invalid_keys_str
}
raise PublishXmlValidationError(self, msg,
formatting_data=formatting_data)
if not os.path.exists(instance.data.get("source")):
scene_url = instance.data.get("source")
msg = "Scene file {} not found (saved under wrong name)".format(
scene_url
)
formatting_data = {
"scene_url": scene_url
}
raise PublishXmlValidationError(self, msg, key="file_not_found",
formatting_data=formatting_data)

View file

@ -14,7 +14,16 @@ import avalon.api
from avalon import io, schema
from avalon.pipeline import AVALON_CONTAINER_ID
from openpype.pipeline import (
LegacyCreator,
register_loader_plugin_path,
deregister_loader_plugin_path,
)
from openpype.api import Logger
from openpype.lib import (
register_event_callback,
emit_event
)
import openpype.hosts.blender
HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.blender.__file__))
@ -45,13 +54,14 @@ def install():
pyblish.api.register_host("blender")
pyblish.api.register_plugin_path(str(PUBLISH_PATH))
avalon.api.register_plugin_path(avalon.api.Loader, str(LOAD_PATH))
avalon.api.register_plugin_path(avalon.api.Creator, str(CREATE_PATH))
register_loader_plugin_path(str(LOAD_PATH))
avalon.api.register_plugin_path(LegacyCreator, str(CREATE_PATH))
lib.append_user_scripts()
avalon.api.on("new", on_new)
avalon.api.on("open", on_open)
register_event_callback("new", on_new)
register_event_callback("open", on_open)
_register_callbacks()
_register_events()
@ -66,8 +76,8 @@ def uninstall():
pyblish.api.deregister_host("blender")
pyblish.api.deregister_plugin_path(str(PUBLISH_PATH))
avalon.api.deregister_plugin_path(avalon.api.Loader, str(LOAD_PATH))
avalon.api.deregister_plugin_path(avalon.api.Creator, str(CREATE_PATH))
deregister_loader_plugin_path(str(LOAD_PATH))
avalon.api.deregister_plugin_path(LegacyCreator, str(CREATE_PATH))
if not IS_HEADLESS:
ops.unregister()
@ -113,22 +123,22 @@ def set_start_end_frames():
scene.render.resolution_y = resolution_y
def on_new(arg1, arg2):
def on_new():
set_start_end_frames()
def on_open(arg1, arg2):
def on_open():
set_start_end_frames()
@bpy.app.handlers.persistent
def _on_save_pre(*args):
avalon.api.emit("before_save", args)
emit_event("before.save")
@bpy.app.handlers.persistent
def _on_save_post(*args):
avalon.api.emit("save", args)
emit_event("save")
@bpy.app.handlers.persistent
@ -136,9 +146,9 @@ def _on_load_post(*args):
# Detect new file or opening an existing file
if bpy.data.filepath:
# Likely this was an open operation since it has a filepath
avalon.api.emit("open", args)
emit_event("open")
else:
avalon.api.emit("new", args)
emit_event("new")
ops.OpenFileCacher.post_load()
@ -169,7 +179,7 @@ def _register_callbacks():
log.info("Installed event handler _on_load_post...")
def _on_task_changed(*args):
def _on_task_changed():
"""Callback for when the task in the context is changed."""
# TODO (jasper): Blender has no concept of projects or workspace.
@ -186,7 +196,7 @@ def _on_task_changed(*args):
def _register_events():
"""Install callbacks for specific events."""
avalon.api.on("taskChanged", _on_task_changed)
register_event_callback("taskChanged", _on_task_changed)
log.info("Installed event callback for 'taskChanged'...")

View file

@ -5,8 +5,10 @@ from typing import Dict, List, Optional
import bpy
import avalon.api
from openpype.api import PypeCreatorMixin
from openpype.pipeline import (
LegacyCreator,
LoaderPlugin,
)
from .pipeline import AVALON_CONTAINERS
from .ops import (
MainThreadItem,
@ -129,7 +131,7 @@ def deselect_all():
bpy.context.view_layer.objects.active = active
class Creator(PypeCreatorMixin, avalon.api.Creator):
class Creator(LegacyCreator):
"""Base class for Creator plug-ins."""
defaults = ['Main']
@ -145,13 +147,13 @@ class Creator(PypeCreatorMixin, avalon.api.Creator):
return collection
class Loader(avalon.api.Loader):
class Loader(LoaderPlugin):
"""Base class for Loader plug-ins."""
hosts = ["blender"]
class AssetLoader(avalon.api.Loader):
class AssetLoader(LoaderPlugin):
"""A basic AssetLoader for Blender
This will implement the basic logic for linking/appending assets

View file

@ -6,7 +6,7 @@ from typing import Dict, List, Optional
import bpy
from avalon import api
from openpype.pipeline import get_representation_path
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
AVALON_PROPERTY,
@ -178,7 +178,7 @@ class CacheModelLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
libpath = Path(api.get_representation_path(representation))
libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(

View file

@ -5,9 +5,13 @@ from pathlib import Path
from pprint import pformat
from typing import Dict, List, Optional
from avalon import api, blender
import bpy
from openpype.pipeline import get_representation_path
import openpype.hosts.blender.api.plugin
from openpype.hosts.blender.api.pipeline import (
containerise_existing,
AVALON_PROPERTY,
)
logger = logging.getLogger("openpype").getChild("blender").getChild("load_action")
@ -49,7 +53,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader):
container = bpy.data.collections.new(lib_container)
container.name = container_name
blender.pipeline.containerise_existing(
containerise_existing(
container,
name,
namespace,
@ -57,8 +61,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader):
self.__class__.__name__,
)
container_metadata = container.get(
blender.pipeline.AVALON_PROPERTY)
container_metadata = container.get(AVALON_PROPERTY)
container_metadata["libpath"] = libpath
container_metadata["lib_container"] = lib_container
@ -90,16 +93,16 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader):
anim_data.action.make_local()
if not obj.get(blender.pipeline.AVALON_PROPERTY):
if not obj.get(AVALON_PROPERTY):
obj[blender.pipeline.AVALON_PROPERTY] = dict()
obj[AVALON_PROPERTY] = dict()
avalon_info = obj[blender.pipeline.AVALON_PROPERTY]
avalon_info = obj[AVALON_PROPERTY]
avalon_info.update({"container_name": container_name})
objects_list.append(obj)
animation_container.pop(blender.pipeline.AVALON_PROPERTY)
animation_container.pop(AVALON_PROPERTY)
# Save the list of objects in the metadata container
container_metadata["objects"] = objects_list
@ -128,7 +131,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader):
container["objectName"]
)
libpath = Path(api.get_representation_path(representation))
libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
logger.info(
@ -153,8 +156,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader):
f"Unsupported file: {libpath}"
)
collection_metadata = collection.get(
blender.pipeline.AVALON_PROPERTY)
collection_metadata = collection.get(AVALON_PROPERTY)
collection_libpath = collection_metadata["libpath"]
normalized_collection_libpath = (
@ -225,16 +227,16 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader):
strip.action = anim_data.action
strip.action_frame_end = anim_data.action.frame_range[1]
if not obj.get(blender.pipeline.AVALON_PROPERTY):
if not obj.get(AVALON_PROPERTY):
obj[blender.pipeline.AVALON_PROPERTY] = dict()
obj[AVALON_PROPERTY] = dict()
avalon_info = obj[blender.pipeline.AVALON_PROPERTY]
avalon_info = obj[AVALON_PROPERTY]
avalon_info.update({"container_name": collection.name})
objects_list.append(obj)
anim_container.pop(blender.pipeline.AVALON_PROPERTY)
anim_container.pop(AVALON_PROPERTY)
# Save the list of objects in the metadata container
collection_metadata["objects"] = objects_list
@ -266,8 +268,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader):
"Nested collections are not supported."
)
collection_metadata = collection.get(
blender.pipeline.AVALON_PROPERTY)
collection_metadata = collection.get(AVALON_PROPERTY)
objects = collection_metadata["objects"]
lib_container = collection_metadata["lib_container"]

View file

@ -6,7 +6,7 @@ from typing import Dict, List, Optional
import bpy
from avalon import api
from openpype.pipeline import get_representation_path
from openpype.hosts.blender.api import plugin
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
@ -102,7 +102,7 @@ class AudioLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
libpath = Path(api.get_representation_path(representation))
libpath = Path(get_representation_path(representation))
self.log.info(
"Container: %s\nRepresentation: %s",

View file

@ -7,7 +7,7 @@ from typing import Dict, List, Optional
import bpy
from avalon import api
from openpype.pipeline import get_representation_path
from openpype.hosts.blender.api import plugin
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
@ -155,7 +155,7 @@ class BlendCameraLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
libpath = Path(api.get_representation_path(representation))
libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(

View file

@ -6,7 +6,7 @@ from typing import Dict, List, Optional
import bpy
from avalon import api
from openpype.pipeline import get_representation_path
from openpype.hosts.blender.api import plugin, lib
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
@ -143,7 +143,7 @@ class FbxCameraLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
libpath = Path(api.get_representation_path(representation))
libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(

View file

@ -6,7 +6,7 @@ from typing import Dict, List, Optional
import bpy
from avalon import api
from openpype.pipeline import get_representation_path
from openpype.hosts.blender.api import plugin, lib
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
@ -187,7 +187,7 @@ class FbxModelLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
libpath = Path(api.get_representation_path(representation))
libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(

View file

@ -6,8 +6,11 @@ from typing import Dict, List, Optional
import bpy
from avalon import api
from openpype import lib
from openpype.pipeline import (
legacy_create,
get_representation_path,
)
from openpype.hosts.blender.api import plugin
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
@ -159,7 +162,7 @@ class BlendLayoutLoader(plugin.AssetLoader):
raise ValueError("Creator plugin \"CreateAnimation\" was "
"not found.")
api.create(
legacy_create(
creator_plugin,
name=local_obj.name.split(':')[-1] + "_animation",
asset=asset,
@ -308,7 +311,7 @@ class BlendLayoutLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
libpath = Path(api.get_representation_path(representation))
libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(

View file

@ -7,8 +7,13 @@ from typing import Dict, Optional
import bpy
from avalon import api
from openpype import lib
from openpype.pipeline import (
discover_loader_plugins,
remove_container,
load_container,
get_representation_path,
loaders_from_representation,
)
from openpype.hosts.blender.api.pipeline import (
AVALON_INSTANCES,
AVALON_CONTAINERS,
@ -34,7 +39,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
objects = list(asset_group.children)
for obj in objects:
api.remove(obj.get(AVALON_PROPERTY))
remove_container(obj.get(AVALON_PROPERTY))
def _remove_animation_instances(self, asset_group):
instances = bpy.data.collections.get(AVALON_INSTANCES)
@ -67,13 +72,13 @@ class JsonLayoutLoader(plugin.AssetLoader):
with open(libpath, "r") as fp:
data = json.load(fp)
all_loaders = api.discover(api.Loader)
all_loaders = discover_loader_plugins()
for element in data:
reference = element.get('reference')
family = element.get('family')
loaders = api.loaders_from_representation(all_loaders, reference)
loaders = loaders_from_representation(all_loaders, reference)
loader = self._get_loader(loaders, family)
if not loader:
@ -103,7 +108,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
# at this time it will not return anything. The assets will be
# loaded in the next Blender cycle, so we use the options to
# set the transform, parent and assign the action, if there is one.
api.load(
load_container(
loader,
reference,
namespace=instance_name,
@ -118,7 +123,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
# raise ValueError("Creator plugin \"CreateCamera\" was "
# "not found.")
# api.create(
# legacy_create(
# creator_plugin,
# name="camera",
# # name=f"{unique_number}_{subset}_animation",
@ -189,7 +194,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
libpath = Path(api.get_representation_path(representation))
libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(

View file

@ -8,7 +8,7 @@ import os
import json
import bpy
from avalon import api
from openpype.pipeline import get_representation_path
from openpype.hosts.blender.api import plugin
from openpype.hosts.blender.api.pipeline import (
containerise_existing,
@ -140,7 +140,7 @@ class BlendLookLoader(plugin.AssetLoader):
def update(self, container: Dict, representation: Dict):
collection = bpy.data.collections.get(container["objectName"])
libpath = Path(api.get_representation_path(representation))
libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(

View file

@ -6,7 +6,7 @@ from typing import Dict, List, Optional
import bpy
from avalon import api
from openpype.pipeline import get_representation_path
from openpype.hosts.blender.api import plugin
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
@ -195,7 +195,7 @@ class BlendModelLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
libpath = Path(api.get_representation_path(representation))
libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(

View file

@ -6,10 +6,15 @@ from typing import Dict, List, Optional
import bpy
from avalon import api
from avalon.blender import lib as avalon_lib
from openpype import lib
from openpype.hosts.blender.api import plugin
from openpype.pipeline import (
legacy_create,
get_representation_path,
)
from openpype.hosts.blender.api import (
plugin,
get_selection,
)
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
AVALON_PROPERTY,
@ -248,7 +253,7 @@ class BlendRigLoader(plugin.AssetLoader):
animation_asset = options.get('animation_asset')
api.create(
legacy_create(
creator_plugin,
name=namespace + "_animation",
# name=f"{unique_number}_{subset}_animation",
@ -262,7 +267,7 @@ class BlendRigLoader(plugin.AssetLoader):
if anim_file:
bpy.ops.import_scene.fbx(filepath=anim_file, anim_offset=0.0)
imported = avalon_lib.get_selection()
imported = get_selection()
armature = [
o for o in asset_group.children if o.type == 'ARMATURE'][0]
@ -306,7 +311,7 @@ class BlendRigLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
libpath = Path(api.get_representation_path(representation))
libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(

View file

@ -50,6 +50,10 @@ class ExtractCamera(api.Extractor):
filepath=filepath,
use_active_collection=False,
use_selection=True,
bake_anim_use_nla_strips=False,
bake_anim_use_all_actions=False,
add_leaf_bones=False,
armature_nodetype='ROOT',
object_types={'CAMERA'},
bake_anim_simplify_factor=0.0
)

View file

@ -1,9 +1,9 @@
import os
import re
import json
import getpass
from avalon.vendor import requests
import re
import requests
import pyblish.api

View file

@ -68,7 +68,8 @@ from .workio import (
)
from .render_utils import (
export_clip,
get_preset_path_by_xml_name
get_preset_path_by_xml_name,
modify_preset_file
)
__all__ = [
@ -140,5 +141,6 @@ __all__ = [
# render utils
"export_clip",
"get_preset_path_by_xml_name"
"get_preset_path_by_xml_name",
"modify_preset_file"
]

View file

@ -7,6 +7,11 @@ from avalon import api as avalon
from avalon.pipeline import AVALON_CONTAINER_ID
from pyblish import api as pyblish
from openpype.api import Logger
from openpype.pipeline import (
LegacyCreator,
register_loader_plugin_path,
deregister_loader_plugin_path,
)
from .lib import (
set_segment_data_marker,
set_publish_attribute,
@ -32,8 +37,8 @@ def install():
pyblish.register_host("flame")
pyblish.register_plugin_path(PUBLISH_PATH)
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
register_loader_plugin_path(LOAD_PATH)
avalon.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
log.info("OpenPype Flame plug-ins registred ...")
@ -47,8 +52,8 @@ def uninstall():
log.info("Deregistering Flame plug-ins..")
pyblish.deregister_plugin_path(PUBLISH_PATH)
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH)
deregister_loader_plugin_path(LOAD_PATH)
avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH)
avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
# register callback for switching publishable

View file

@ -2,13 +2,16 @@ import os
import re
import shutil
import sys
from avalon.vendor import qargparse
from xml.etree import ElementTree as ET
import six
import qargparse
from Qt import QtWidgets, QtCore
import openpype.api as openpype
from openpype.pipeline import (
LegacyCreator,
LoaderPlugin,
)
from openpype import style
import avalon.api as avalon
from . import (
lib as flib,
pipeline as fpipeline,
@ -299,7 +302,7 @@ class Spacer(QtWidgets.QWidget):
self.setLayout(layout)
class Creator(openpype.Creator):
class Creator(LegacyCreator):
"""Creator class wrapper
"""
clip_color = constants.COLOR_MAP["purple"]
@ -659,7 +662,7 @@ class PublishableClip:
# Publishing plugin functions
# Loader plugin functions
class ClipLoader(avalon.Loader):
class ClipLoader(LoaderPlugin):
"""A basic clip loader for Flame
This will implement the basic behavior for a loader to inherit from that

View file

@ -1,4 +1,5 @@
import os
from xml.etree import ElementTree as ET
def export_clip(export_path, clip, preset_path, **kwargs):
@ -123,3 +124,29 @@ def get_preset_path_by_xml_name(xml_preset_name):
# if nothing found then return False
return False
def modify_preset_file(xml_path, staging_dir, data):
"""Modify xml preset with input data
Args:
xml_path (str ): path for input xml preset
staging_dir (str): staging dir path
data (dict): data where key is xmlTag and value as string
Returns:
str: _description_
"""
# create temp path
dirname, basename = os.path.split(xml_path)
temp_path = os.path.join(staging_dir, basename)
# change xml following data keys
with open(xml_path, "r") as datafile:
tree = ET.parse(datafile)
for key, value in data.items():
for element in tree.findall(".//{}".format(key)):
element.text = str(value)
tree.write(temp_path)
return temp_path

View file

@ -420,13 +420,20 @@ class WireTapCom(object):
RuntimeError: Not able to set colorspace policy
"""
color_policy = color_policy or "Legacy"
# check if the colour policy in custom dir
if not os.path.exists(color_policy):
color_policy = "/syncolor/policies/Autodesk/{}".format(
color_policy)
# create arguments
project_colorspace_cmd = [
os.path.join(
self.wiretap_tools_dir,
"wiretap_duplicate_node"
),
"-s",
"/syncolor/policies/Autodesk/{}".format(color_policy),
color_policy,
"-n",
"/projects/{}/syncolor".format(project_name)
]

View file

@ -73,7 +73,7 @@ class FlamePrelaunch(PreLaunchHook):
"FrameWidth": int(width),
"FrameHeight": int(height),
"AspectRatio": float((width / height) * _db_p_data["pixelAspect"]),
"FrameRate": "{} fps".format(fps),
"FrameRate": self._get_flame_fps(fps),
"FrameDepth": str(imageio_flame["project"]["frameDepth"]),
"FieldDominance": str(imageio_flame["project"]["fieldDominance"])
}
@ -101,6 +101,28 @@ class FlamePrelaunch(PreLaunchHook):
self.launch_context.launch_args.extend(app_arguments)
def _get_flame_fps(self, fps_num):
fps_table = {
float(23.976): "23.976 fps",
int(25): "25 fps",
int(24): "24 fps",
float(29.97): "29.97 fps DF",
int(30): "30 fps",
int(50): "50 fps",
float(59.94): "59.94 fps DF",
int(60): "60 fps"
}
match_key = min(fps_table.keys(), key=lambda x: abs(x - fps_num))
try:
return fps_table[match_key]
except KeyError as msg:
raise KeyError((
"Missing FPS key in conversion table. "
"Following keys are available: {}".format(fps_table.keys())
)) from msg
def _add_pythonpath(self):
pythonpath = self.launch_context.env.get("PYTHONPATH")

View file

@ -172,7 +172,7 @@ class LoadClip(opfapi.ClipLoader):
# version_name = version.get("name", None)
# colorspace = version_data.get("colorspace", None)
# object_name = "{}_{}".format(name, namespace)
# file = api.get_representation_path(representation).replace("\\", "/")
# file = get_representation_path(representation).replace("\\", "/")
# clip = track_item.source()
# # reconnect media to new path

View file

@ -1,3 +1,4 @@
import re
import pyblish
import openpype
import openpype.hosts.flame.api as opfapi
@ -6,6 +7,10 @@ from openpype.hosts.flame.otio import flame_export
# # developer reload modules
from pprint import pformat
# constatns
NUM_PATERN = re.compile(r"([0-9\.]+)")
TXT_PATERN = re.compile(r"([a-zA-Z]+)")
class CollectTimelineInstances(pyblish.api.ContextPlugin):
"""Collect all Timeline segment selection."""
@ -16,6 +21,16 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
audio_track_items = []
# TODO: add to settings
# settings
xml_preset_attrs_from_comments = {
"width": "number",
"height": "number",
"pixelRatio": "float",
"resizeType": "string",
"resizeFilter": "string"
}
def process(self, context):
project = context.data["flameProject"]
sequence = context.data["flameSequence"]
@ -26,6 +41,10 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
# process all sellected
with opfapi.maintained_segment_selection(sequence) as segments:
for segment in segments:
comment_attributes = self._get_comment_attributes(segment)
self.log.debug("_ comment_attributes: {}".format(
pformat(comment_attributes)))
clip_data = opfapi.get_segment_attributes(segment)
clip_name = clip_data["segment_name"]
self.log.debug("clip_name: {}".format(clip_name))
@ -101,6 +120,9 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
# add resolution
self._get_resolution_to_data(inst_data, context)
# add comment attributes if any
inst_data.update(comment_attributes)
# create instance
instance = context.create_instance(**inst_data)
@ -126,6 +148,94 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
if marker_data.get("reviewTrack") is not None:
instance.data["reviewAudio"] = True
def _get_comment_attributes(self, segment):
comment = segment.comment.get_value()
# try to find attributes
attributes = {
"xml_overrides": {
"pixelRatio": 1.00}
}
# search for `:`
for split in self._split_comments(comment):
# make sure we ignore if not `:` in key
if ":" not in split:
continue
self._get_xml_preset_attrs(
attributes, split)
# add xml overides resolution to instance data
xml_overrides = attributes["xml_overrides"]
if xml_overrides.get("width"):
attributes.update({
"resolutionWidth": xml_overrides["width"],
"resolutionHeight": xml_overrides["height"],
"pixelAspect": xml_overrides["pixelRatio"]
})
return attributes
def _get_xml_preset_attrs(self, attributes, split):
# split to key and value
key, value = split.split(":")
for a_name, a_type in self.xml_preset_attrs_from_comments.items():
# exclude all not related attributes
if a_name.lower() not in key.lower():
continue
# get pattern defined by type
pattern = TXT_PATERN
if a_type in ("number" , "float"):
pattern = NUM_PATERN
res_goup = pattern.findall(value)
# raise if nothing is found as it is not correctly defined
if not res_goup:
raise ValueError((
"Value for `{}` attribute is not "
"set correctly: `{}`").format(a_name, split))
if "string" in a_type:
_value = res_goup[0]
if "float" in a_type:
_value = float(res_goup[0])
if "number" in a_type:
_value = int(res_goup[0])
attributes["xml_overrides"][a_name] = _value
# condition for resolution in key
if "resolution" in key.lower():
res_goup = NUM_PATERN.findall(value)
# check if axpect was also defined
# 1920x1080x1.5
aspect = res_goup[2] if len(res_goup) > 2 else 1
width = int(res_goup[0])
height = int(res_goup[1])
pixel_ratio = float(aspect)
attributes["xml_overrides"].update({
"width": width,
"height": height,
"pixelRatio": pixel_ratio
})
def _split_comments(self, comment_string):
# first split comment by comma
split_comments = []
if "," in comment_string:
split_comments.extend(comment_string.split(","))
elif ";" in comment_string:
split_comments.extend(comment_string.split(";"))
else:
split_comments.append(comment_string)
return split_comments
def _get_head_tail(self, clip_data, first_frame):
# calculate head and tail with forward compatibility
head = clip_data.get("segment_head")

View file

@ -1,6 +1,7 @@
import os
from pprint import pformat
from copy import deepcopy
import pyblish.api
import openpype.api
from openpype.hosts.flame import api as opfapi
@ -22,6 +23,8 @@ class ExtractSubsetResources(openpype.api.Extractor):
"ext": "jpg",
"xml_preset_file": "Jpeg (8-bit).xml",
"xml_preset_dir": "",
"export_type": "File Sequence",
"ignore_comment_attrs": True,
"colorspace_out": "Output - sRGB",
"representation_add_range": False,
"representation_tags": ["thumbnail"]
@ -30,6 +33,8 @@ class ExtractSubsetResources(openpype.api.Extractor):
"ext": "mov",
"xml_preset_file": "Apple iPad (1920x1080).xml",
"xml_preset_dir": "",
"export_type": "Movie",
"ignore_comment_attrs": True,
"colorspace_out": "Output - Rec.709",
"representation_add_range": True,
"representation_tags": [
@ -54,21 +59,35 @@ class ExtractSubsetResources(openpype.api.Extractor):
):
instance.data["representations"] = []
frame_start = instance.data["frameStart"]
handle_start = instance.data["handleStart"]
frame_start_handle = frame_start - handle_start
source_first_frame = instance.data["sourceFirstFrame"]
source_start_handles = instance.data["sourceStartH"]
source_end_handles = instance.data["sourceEndH"]
source_duration_handles = (
source_end_handles - source_start_handles) + 1
# flame objects
segment = instance.data["item"]
sequence_clip = instance.context.data["flameSequence"]
clip_data = instance.data["flameSourceClip"]
clip = clip_data["PyClip"]
in_mark = (source_start_handles - source_first_frame) + 1
out_mark = in_mark + source_duration_handles
# segment's parent track name
s_track_name = segment.parent.name.get_value()
# get configured workfile frame start/end (handles excluded)
frame_start = instance.data["frameStart"]
# get media source first frame
source_first_frame = instance.data["sourceFirstFrame"]
# get timeline in/out of segment
clip_in = instance.data["clipIn"]
clip_out = instance.data["clipOut"]
# get handles value - take only the max from both
handle_start = instance.data["handleStart"]
handle_end = instance.data["handleStart"]
handles = max(handle_start, handle_end)
# get media source range with handles
source_end_handles = instance.data["sourceEndH"]
source_start_handles = instance.data["sourceStartH"]
source_end_handles = instance.data["sourceEndH"]
# create staging dir path
staging_dir = self.staging_dir(instance)
# add default preset type for thumbnail and reviewable video
@ -77,15 +96,61 @@ class ExtractSubsetResources(openpype.api.Extractor):
export_presets = deepcopy(self.default_presets)
export_presets.update(self.export_presets_mapping)
# with maintained duplication loop all presets
with opfapi.maintained_object_duplication(clip) as duplclip:
# loop all preset names and
for unique_name, preset_config in export_presets.items():
# loop all preset names and
for unique_name, preset_config in export_presets.items():
modify_xml_data = {}
# get all presets attributes
preset_file = preset_config["xml_preset_file"]
preset_dir = preset_config["xml_preset_dir"]
export_type = preset_config["export_type"]
repre_tags = preset_config["representation_tags"]
ignore_comment_attrs = preset_config["ignore_comment_attrs"]
color_out = preset_config["colorspace_out"]
# get frame range with handles for representation range
frame_start_handle = frame_start - handle_start
source_duration_handles = (
source_end_handles - source_start_handles) + 1
# define in/out marks
in_mark = (source_start_handles - source_first_frame) + 1
out_mark = in_mark + source_duration_handles
# by default export source clips
exporting_clip = clip
if export_type == "Sequence Publish":
# change export clip to sequence
exporting_clip = sequence_clip
# change in/out marks to timeline in/out
in_mark = clip_in
out_mark = clip_out
# add xml tags modifications
modify_xml_data.update({
"exportHandles": True,
"nbHandles": handles,
"startFrame": frame_start
})
if not ignore_comment_attrs:
# add any xml overrides collected form segment.comment
modify_xml_data.update(instance.data["xml_overrides"])
self.log.debug("__ modify_xml_data: {}".format(pformat(
modify_xml_data
)))
# with maintained duplication loop all presets
with opfapi.maintained_object_duplication(
exporting_clip) as duplclip:
kwargs = {}
preset_file = preset_config["xml_preset_file"]
preset_dir = preset_config["xml_preset_dir"]
repre_tags = preset_config["representation_tags"]
color_out = preset_config["colorspace_out"]
if export_type == "Sequence Publish":
# only keep visible layer where instance segment is child
self.hide_other_tracks(duplclip, s_track_name)
# validate xml preset file is filled
if preset_file == "":
@ -108,10 +173,13 @@ class ExtractSubsetResources(openpype.api.Extractor):
)
# create preset path
preset_path = str(os.path.join(
preset_orig_xml_path = str(os.path.join(
preset_dir, preset_file
))
preset_path = opfapi.modify_preset_file(
preset_orig_xml_path, staging_dir, modify_xml_data)
# define kwargs based on preset type
if "thumbnail" in unique_name:
kwargs["thumb_frame_number"] = in_mark + (
@ -122,6 +190,7 @@ class ExtractSubsetResources(openpype.api.Extractor):
"out_mark": out_mark
})
# get and make export dir paths
export_dir_path = str(os.path.join(
staging_dir, unique_name
))
@ -132,6 +201,7 @@ class ExtractSubsetResources(openpype.api.Extractor):
export_dir_path, duplclip, preset_path, **kwargs)
extension = preset_config["ext"]
# create representation data
representation_data = {
"name": unique_name,
@ -159,7 +229,12 @@ class ExtractSubsetResources(openpype.api.Extractor):
# add files to represetation but add
# imagesequence as list
if (
"movie_file" in preset_path
# first check if path in files is not mov extension
[
f for f in files
if os.path.splitext(f)[-1] == ".mov"
]
# then try if thumbnail is not in unique name
or unique_name == "thumbnail"
):
representation_data["files"] = files.pop()
@ -246,3 +321,19 @@ class ExtractSubsetResources(openpype.api.Extractor):
)
return new_stage_dir, new_files_list
def hide_other_tracks(self, sequence_clip, track_name):
"""Helper method used only if sequence clip is used
Args:
sequence_clip (flame.Clip): sequence clip
track_name (str): track name
"""
# create otio tracks and clips
for ver in sequence_clip.versions:
for track in ver.tracks:
if len(track.segments) == 0 and track.hidden:
continue
if track.name.get_value() != track_name:
track.hidden = True

View file

@ -5,8 +5,8 @@ import contextlib
from Qt import QtGui
import avalon.api
from avalon import io
from openpype.pipeline import switch_container
from .pipeline import get_current_comp, comp_lock_and_undo_chunk
self = sys.modules[__name__]
@ -142,7 +142,7 @@ def switch_item(container,
assert representation, ("Could not find representation in the database "
"with the name '%s'" % representation_name)
avalon.api.switch(container, representation)
switch_container(container, representation)
return representation

View file

@ -11,6 +11,11 @@ import avalon.api
from avalon.pipeline import AVALON_CONTAINER_ID
from openpype.api import Logger
from openpype.pipeline import (
LegacyCreator,
register_loader_plugin_path,
deregister_loader_plugin_path,
)
import openpype.hosts.fusion
log = Logger().get_logger(__name__)
@ -62,8 +67,8 @@ def install():
pyblish.api.register_plugin_path(PUBLISH_PATH)
log.info("Registering Fusion plug-ins..")
avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH)
register_loader_plugin_path(LOAD_PATH)
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH)
pyblish.api.register_callback(
@ -86,8 +91,8 @@ def uninstall():
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
log.info("Deregistering Fusion plug-ins..")
avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH)
deregister_loader_plugin_path(LOAD_PATH)
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
avalon.api.deregister_plugin_path(
avalon.api.InventoryAction, INVENTORY_PATH
)

View file

@ -1,13 +1,13 @@
import os
import openpype.api
from openpype.pipeline import create
from openpype.hosts.fusion.api import (
get_current_comp,
comp_lock_and_undo_chunk
)
class CreateOpenEXRSaver(openpype.api.Creator):
class CreateOpenEXRSaver(create.LegacyCreator):
name = "openexrDefault"
label = "Create OpenEXR Saver"

View file

@ -2,10 +2,10 @@
"""
from avalon import api
from openpype.pipeline import load
class FusionSetFrameRangeLoader(api.Loader):
class FusionSetFrameRangeLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["animation",
@ -39,7 +39,7 @@ class FusionSetFrameRangeLoader(api.Loader):
lib.update_frame_range(start, end)
class FusionSetFrameRangeWithHandlesLoader(api.Loader):
class FusionSetFrameRangeWithHandlesLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["animation",

View file

@ -1,8 +1,12 @@
import os
import contextlib
from avalon import api, io
from avalon import io
from openpype.pipeline import (
load,
get_representation_path,
)
from openpype.hosts.fusion.api import (
imprint_container,
get_current_comp,
@ -117,7 +121,7 @@ def loader_shift(loader, frame, relative=True):
return int(shift)
class FusionLoadSequence(api.Loader):
class FusionLoadSequence(load.LoaderPlugin):
"""Load image sequence into Fusion"""
families = ["imagesequence", "review", "render"]
@ -204,7 +208,7 @@ class FusionLoadSequence(api.Loader):
assert tool.ID == "Loader", "Must be Loader"
comp = tool.Comp()
root = os.path.dirname(api.get_representation_path(representation))
root = os.path.dirname(get_representation_path(representation))
path = self._get_first_image(root)
# Get start frame from version data

View file

@ -5,11 +5,12 @@ import logging
# Pipeline imports
import avalon.api
from avalon import io, pipeline
from avalon import io
from openpype.lib import version_up
from openpype.hosts.fusion import api
from openpype.hosts.fusion.api import lib
from openpype.lib.avalon_context import get_workdir_from_session
log = logging.getLogger("Update Slap Comp")
@ -44,16 +45,6 @@ def _format_version_folder(folder):
return version_folder
def _get_work_folder(session):
"""Convenience function to get the work folder path of the current asset"""
# Get new filename, create path based on asset and work template
template_work = self._project["config"]["template"]["work"]
work_path = pipeline._format_work_template(template_work, session)
return os.path.normpath(work_path)
def _get_fusion_instance():
fusion = getattr(sys.modules["__main__"], "fusion", None)
if fusion is None:
@ -72,7 +63,7 @@ def _format_filepath(session):
asset = session["AVALON_ASSET"]
# Save updated slap comp
work_path = _get_work_folder(session)
work_path = get_workdir_from_session(session)
walk_to_dir = os.path.join(work_path, "scenes", "slapcomp")
slapcomp_dir = os.path.abspath(walk_to_dir)
@ -112,7 +103,7 @@ def _update_savers(comp, session):
None
"""
new_work = _get_work_folder(session)
new_work = get_workdir_from_session(session)
renders = os.path.join(new_work, "renders")
version_folder = _format_version_folder(renders)
renders_version = os.path.join(renders, version_folder)

View file

@ -1,5 +1,5 @@
from Qt import QtWidgets
from avalon.vendor import qtawesome
import qtawesome
from openpype.hosts.fusion.api import get_current_comp

View file

@ -5,11 +5,12 @@ import logging
from Qt import QtWidgets, QtCore
import avalon.api
from avalon import io, pipeline
from avalon.vendor import qtawesome as qta
from avalon import io
import qtawesome as qta
from openpype import style
from openpype.hosts.fusion import api
from openpype.lib.avalon_context import get_workdir_from_session
log = logging.getLogger("Fusion Switch Shot")
@ -123,7 +124,7 @@ class App(QtWidgets.QWidget):
def _on_open_from_dir(self):
start_dir = self._get_context_directory()
start_dir = get_workdir_from_session()
comp_file, _ = QtWidgets.QFileDialog.getOpenFileName(
self, "Choose comp", start_dir)
@ -157,17 +158,6 @@ class App(QtWidgets.QWidget):
import colorbleed.scripts.fusion_switch_shot as switch_shot
switch_shot.switch(asset_name=asset, filepath=file_name, new=True)
def _get_context_directory(self):
project = io.find_one({"type": "project",
"name": avalon.api.Session["AVALON_PROJECT"]},
projection={"config": True})
template = project["config"]["template"]["work"]
dir = pipeline._format_work_template(template, avalon.api.Session)
return dir
def collect_slap_comps(self, directory):
items = glob.glob("{}/*.comp".format(directory))
return items

View file

@ -575,7 +575,7 @@ replace_files = """function %s_replace_files(args)
""" % (signature, signature)
class ImageSequenceLoader(api.Loader):
class ImageSequenceLoader(load.LoaderPlugin):
"""Load images
Stores the imported asset in a container named after the asset.
"""

View file

@ -272,8 +272,8 @@ function Client() {
app.avalonClient.send(
{
'module': 'avalon.api',
'method': 'emit',
'module': 'openpype.lib',
'method': 'emit_event',
'args': ['application.launched']
}, false);
};

View file

@ -9,6 +9,12 @@ import avalon.api
from avalon.pipeline import AVALON_CONTAINER_ID
from openpype import lib
from openpype.lib import register_event_callback
from openpype.pipeline import (
LegacyCreator,
register_loader_plugin_path,
deregister_loader_plugin_path,
)
import openpype.hosts.harmony
import openpype.hosts.harmony.api as harmony
@ -129,7 +135,7 @@ def check_inventory():
harmony.send({"function": "PypeHarmony.message", "args": msg})
def application_launch():
def application_launch(event):
"""Event that is executed after Harmony is launched."""
# FIXME: This is breaking server <-> client communication.
# It is now moved so it it manually called.
@ -178,8 +184,8 @@ def install():
pyblish.api.register_host("harmony")
pyblish.api.register_plugin_path(PUBLISH_PATH)
avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH)
register_loader_plugin_path(LOAD_PATH)
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
log.info(PUBLISH_PATH)
# Register callbacks.
@ -187,13 +193,13 @@ def install():
"instanceToggled", on_pyblish_instance_toggled
)
avalon.api.on("application.launched", application_launch)
register_event_callback("application.launched", application_launch)
def uninstall():
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH)
deregister_loader_plugin_path(LOAD_PATH)
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
def on_pyblish_instance_toggled(instance, old_value, new_value):

View file

@ -1,9 +1,8 @@
import avalon.api
from openpype.api import PypeCreatorMixin
from openpype.pipeline import LegacyCreator
import openpype.hosts.harmony.api as harmony
class Creator(PypeCreatorMixin, avalon.api.Creator):
class Creator(LegacyCreator):
"""Creator plugin to create instances in Harmony.
By default a Composite node is created to support any number of nodes in

View file

@ -1,4 +1,7 @@
from avalon import api
from openpype.pipeline import (
load,
get_representation_path,
)
import openpype.hosts.harmony.api as harmony
sig = harmony.signature()
@ -29,7 +32,7 @@ function %s(args)
""" % (sig, sig)
class ImportAudioLoader(api.Loader):
class ImportAudioLoader(load.LoaderPlugin):
"""Import audio."""
families = ["shot", "audio"]
@ -37,7 +40,7 @@ class ImportAudioLoader(api.Loader):
label = "Import Audio"
def load(self, context, name=None, namespace=None, data=None):
wav_file = api.get_representation_path(context["representation"])
wav_file = get_representation_path(context["representation"])
harmony.send(
{"function": func, "args": [context["subset"]["name"], wav_file]}
)

View file

@ -1,7 +1,10 @@
import os
import json
from avalon import api
from openpype.pipeline import (
load,
get_representation_path,
)
import openpype.hosts.harmony.api as harmony
import openpype.lib
@ -226,7 +229,7 @@ replace_files
"""
class BackgroundLoader(api.Loader):
class BackgroundLoader(load.LoaderPlugin):
"""Load images
Stores the imported asset in a container named after the asset.
"""
@ -278,7 +281,7 @@ class BackgroundLoader(api.Loader):
def update(self, container, representation):
path = api.get_representation_path(representation)
path = get_representation_path(representation)
with open(path) as json_file:
data = json.load(json_file)
@ -297,7 +300,7 @@ class BackgroundLoader(api.Loader):
bg_folder = os.path.dirname(path)
path = api.get_representation_path(representation)
path = get_representation_path(representation)
print(container)

View file

@ -6,12 +6,15 @@ from pathlib import Path
import clique
from avalon import api
from openpype.pipeline import (
load,
get_representation_path,
)
import openpype.hosts.harmony.api as harmony
import openpype.lib
class ImageSequenceLoader(api.Loader):
class ImageSequenceLoader(load.LoaderPlugin):
"""Load image sequences.
Stores the imported asset in a container named after the asset.
@ -79,7 +82,7 @@ class ImageSequenceLoader(api.Loader):
self_name = self.__class__.__name__
node = container.get("nodes").pop()
path = api.get_representation_path(representation)
path = get_representation_path(representation)
collections, remainder = clique.assemble(
os.listdir(os.path.dirname(path))
)

View file

@ -1,11 +1,14 @@
import os
import shutil
from avalon import api
from openpype.pipeline import (
load,
get_representation_path,
)
import openpype.hosts.harmony.api as harmony
class ImportPaletteLoader(api.Loader):
class ImportPaletteLoader(load.LoaderPlugin):
"""Import palettes."""
families = ["palette", "harmony.palette"]
@ -31,7 +34,7 @@ class ImportPaletteLoader(api.Loader):
scene_path = harmony.send(
{"function": "scene.currentProjectPath"}
)["result"]
src = api.get_representation_path(representation)
src = get_representation_path(representation)
dst = os.path.join(
scene_path,
"palette-library",

View file

@ -6,12 +6,15 @@ import os
import shutil
import uuid
from avalon import api
from openpype.pipeline import (
load,
get_representation_path,
)
import openpype.hosts.harmony.api as harmony
import openpype.lib
class TemplateLoader(api.Loader):
class TemplateLoader(load.LoaderPlugin):
"""Load Harmony template as container.
.. todo::
@ -38,7 +41,7 @@ class TemplateLoader(api.Loader):
# Load template.
self_name = self.__class__.__name__
temp_dir = tempfile.mkdtemp()
zip_file = api.get_representation_path(context["representation"])
zip_file = get_representation_path(context["representation"])
template_path = os.path.join(temp_dir, "temp.tpl")
with zipfile.ZipFile(zip_file, "r") as zip_ref:
zip_ref.extractall(template_path)

View file

@ -3,11 +3,14 @@ import zipfile
import os
import shutil
from avalon import api
from openpype.pipeline import (
load,
get_representation_path,
)
import openpype.hosts.harmony.api as harmony
class ImportTemplateLoader(api.Loader):
class ImportTemplateLoader(load.LoaderPlugin):
"""Import templates."""
families = ["harmony.template", "workfile"]
@ -17,7 +20,7 @@ class ImportTemplateLoader(api.Loader):
def load(self, context, name=None, namespace=None, data=None):
# Import template.
temp_dir = tempfile.mkdtemp()
zip_file = api.get_representation_path(context["representation"])
zip_file = get_representation_path(context["representation"])
template_path = os.path.join(temp_dir, "temp.tpl")
with zipfile.ZipFile(zip_file, "r") as zip_ref:
zip_ref.extractall(template_path)

View file

@ -5,6 +5,7 @@ from pathlib import Path
import attr
from avalon import api
from openpype.lib import get_formatted_current_time
import openpype.lib.abstract_collect_render
import openpype.hosts.harmony.api as harmony
from openpype.lib.abstract_collect_render import RenderInstance
@ -138,7 +139,7 @@ class CollectFarmRender(openpype.lib.abstract_collect_render.
render_instance = HarmonyRenderInstance(
version=version,
time=api.time(),
time=get_formatted_current_time(),
source=context.data["currentFile"],
label=node.split("/")[1],
subset=subset_name,

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<error id="main">
<title>Missing audio file</title>
<description>
## Cannot locate linked audio file
Audio file at {audio_url} cannot be found.
### How to repair?
Copy audio file to the highlighted location or remove audio link in the workfile.
</description>
</error>
</root>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<error id="main">
<title>Subset context</title>
<description>
## Invalid subset context
Asset name found '{found}' in subsets, expected '{expected}'.
### How to repair?
You can fix this with `Repair` button on the right. This will use '{expected}' asset name and overwrite '{found}' asset name in scene metadata.
After that restart `Publish` with a `Reload button`.
If this is unwanted, close workfile and open again, that way different asset value would be used for context information.
</description>
<detail>
### __Detailed Info__ (optional)
This might happen if you are reuse old workfile and open it in different context.
(Eg. you created subset "renderCompositingDefault" from asset "Robot' in "your_project_Robot_compositing.aep", now you opened this workfile in a context "Sloth" but existing subset for "Robot" asset stayed in the workfile.)
</detail>
</error>
</root>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<root>
<error id="main">
<title>Scene setting</title>
<description>
## Invalid scene setting found
One of the settings in a scene doesn't match to asset settings in database.
{invalid_setting_str}
### How to repair?
Change values for {invalid_keys_str} in the scene OR change them in the asset database if they are wrong there.
</description>
<detail>
### __Detailed Info__ (optional)
This error is shown when for example resolution in the scene doesn't match to resolution set on the asset in the database.
Either value in the database or in the scene is wrong.
</detail>
</error>
<error id="file_not_found">
<title>Scene file doesn't exist</title>
<description>
## Scene file doesn't exist
Collected scene {scene_url} doesn't exist.
### How to repair?
Re-save file, start publish from the beginning again.
</description>
</error>
</root>

View file

@ -4,6 +4,8 @@ import pyblish.api
import openpype.hosts.harmony.api as harmony
from openpype.pipeline import PublishXmlValidationError
class ValidateAudio(pyblish.api.InstancePlugin):
"""Ensures that there is an audio file in the scene.
@ -42,4 +44,9 @@ class ValidateAudio(pyblish.api.InstancePlugin):
msg = "You are missing audio file:\n{}".format(audio_path)
assert os.path.isfile(audio_path), msg
formatting_data = {
"audio_url": audio_path
}
if os.path.isfile(audio_path):
raise PublishXmlValidationError(self, msg,
formatting_data=formatting_data)

View file

@ -2,6 +2,7 @@ import os
import pyblish.api
import openpype.api
from openpype.pipeline import PublishXmlValidationError
import openpype.hosts.harmony.api as harmony
@ -45,4 +46,11 @@ class ValidateInstance(pyblish.api.InstancePlugin):
"Instance asset is not the same as current asset:"
f"\nInstance: {instance_asset}\nCurrent: {current_asset}"
)
assert instance_asset == current_asset, msg
formatting_data = {
"found": instance_asset,
"expected": current_asset
}
if instance_asset != current_asset:
raise PublishXmlValidationError(self, msg,
formatting_data=formatting_data)

View file

@ -7,7 +7,7 @@ import re
import pyblish.api
import openpype.hosts.harmony.api as harmony
import openpype.hosts.harmony
from openpype.pipeline import PublishXmlValidationError
class ValidateSceneSettingsRepair(pyblish.api.Action):
@ -19,12 +19,12 @@ class ValidateSceneSettingsRepair(pyblish.api.Action):
def process(self, context, plugin):
"""Repair action entry point."""
expected = openpype.hosts.harmony.api.get_asset_settings()
expected = harmony.get_asset_settings()
asset_settings = _update_frames(dict.copy(expected))
asset_settings["frameStart"] = 1
asset_settings["frameEnd"] = asset_settings["frameEnd"] + \
asset_settings["handleEnd"]
openpype.hosts.harmony.api.set_scene_settings(asset_settings)
harmony.set_scene_settings(asset_settings)
if not os.path.exists(context.data["scenePath"]):
self.log.info("correcting scene name")
scene_dir = os.path.dirname(context.data["currentFile"])
@ -55,7 +55,7 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin):
def process(self, instance):
"""Plugin entry point."""
expected_settings = openpype.hosts.harmony.api.get_asset_settings()
expected_settings = harmony.get_asset_settings()
self.log.info("scene settings from DB:".format(expected_settings))
expected_settings = _update_frames(dict.copy(expected_settings))
@ -102,13 +102,13 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin):
self.log.debug("current scene settings {}".format(current_settings))
invalid_settings = []
invalid_keys = set()
for key, value in expected_settings.items():
if value != current_settings[key]:
invalid_settings.append({
"name": key,
"expected": value,
"current": current_settings[key]
})
invalid_settings.append(
"{} expected: {} found: {}".format(key, value,
current_settings[key]))
invalid_keys.add(key)
if ((expected_settings["handleStart"]
or expected_settings["handleEnd"])
@ -120,10 +120,30 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin):
msg = "Found invalid settings:\n{}".format(
json.dumps(invalid_settings, sort_keys=True, indent=4)
)
assert not invalid_settings, msg
assert os.path.exists(instance.context.data.get("scenePath")), (
"Scene file not found (saved under wrong name)"
)
if invalid_settings:
invalid_keys_str = ",".join(invalid_keys)
break_str = "<br/>"
invalid_setting_str = "<b>Found invalid settings:</b><br/>{}".\
format(break_str.join(invalid_settings))
formatting_data = {
"invalid_setting_str": invalid_setting_str,
"invalid_keys_str": invalid_keys_str
}
raise PublishXmlValidationError(self, msg,
formatting_data=formatting_data)
scene_url = instance.context.data.get("scenePath")
if not os.path.exists(scene_url):
msg = "Scene file {} not found (saved under wrong name)".format(
scene_url
)
formatting_data = {
"scene_url": scene_url
}
raise PublishXmlValidationError(self, msg, key="file_not_found",
formatting_data=formatting_data)
def _update_frames(expected_settings):

View file

@ -1,12 +1,12 @@
import os
import hiero.core.events
import avalon.api as avalon
from openpype.api import Logger
from openpype.lib import register_event_callback
from .lib import (
sync_avalon_data_to_workfile,
launch_workfiles_app,
selection_changed_timeline,
before_project_save
before_project_save,
)
from .tags import add_tags_to_workfile
from .menu import update_menu_task_label
@ -126,5 +126,5 @@ def register_events():
"""
# if task changed then change notext of hiero
avalon.on("taskChanged", update_menu_task_label)
register_event_callback("taskChanged", update_menu_task_label)
log.info("Installed event callback for 'taskChanged'..")

View file

@ -14,7 +14,7 @@ self = sys.modules[__name__]
self._change_context_menu = None
def update_menu_task_label(*args):
def update_menu_task_label():
"""Update the task label in Avalon menu to current session"""
object_name = self._change_context_menu

View file

@ -9,6 +9,11 @@ from avalon import api as avalon
from avalon import schema
from pyblish import api as pyblish
from openpype.api import Logger
from openpype.pipeline import (
LegacyCreator,
register_loader_plugin_path,
deregister_loader_plugin_path,
)
from openpype.tools.utils import host_tools
from . import lib, menu, events
@ -44,8 +49,8 @@ def install():
log.info("Registering Hiero plug-ins..")
pyblish.register_host("hiero")
pyblish.register_plugin_path(PUBLISH_PATH)
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
register_loader_plugin_path(LOAD_PATH)
avalon.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
# register callback for switching publishable
@ -66,8 +71,8 @@ def uninstall():
log.info("Deregistering Hiero plug-ins..")
pyblish.deregister_host("hiero")
pyblish.deregister_plugin_path(PUBLISH_PATH)
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH)
deregister_loader_plugin_path(LOAD_PATH)
avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH)
# register callback for switching publishable
pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled)

View file

@ -1,13 +1,16 @@
import re
import os
import hiero
from Qt import QtWidgets, QtCore
from avalon.vendor import qargparse
import avalon.api as avalon
import openpype.api as openpype
from . import lib
import re
from copy import deepcopy
import hiero
from Qt import QtWidgets, QtCore
import qargparse
import openpype.api as openpype
from openpype.pipeline import LoaderPlugin, LegacyCreator
from . import lib
log = openpype.Logger().get_logger(__name__)
@ -303,7 +306,7 @@ def get_reference_node_parents(ref):
return parents
class SequenceLoader(avalon.Loader):
class SequenceLoader(LoaderPlugin):
"""A basic SequenceLoader for Resolve
This will implement the basic behavior for a loader to inherit from that
@ -589,7 +592,7 @@ class ClipLoader:
return track_item
class Creator(openpype.Creator):
class Creator(LegacyCreator):
"""Creator class wrapper
"""
clip_color = "Purple"

View file

@ -1,4 +1,5 @@
from avalon import io, api
from avalon import io
from openpype.pipeline import get_representation_path
import openpype.hosts.hiero.api as phiero
# from openpype.hosts.hiero.api import plugin, lib
# reload(lib)
@ -112,7 +113,7 @@ class LoadClip(phiero.SequenceLoader):
version_name = version.get("name", None)
colorspace = version_data.get("colorspace", None)
object_name = "{}_{}".format(name, namespace)
file = api.get_representation_path(representation).replace("\\", "/")
file = get_representation_path(representation).replace("\\", "/")
clip = track_item.source()
# reconnect media to new path

View file

@ -11,11 +11,17 @@ import avalon.api
from avalon.pipeline import AVALON_CONTAINER_ID
from avalon.lib import find_submodule
from openpype.pipeline import (
LegacyCreator,
register_loader_plugin_path,
)
import openpype.hosts.houdini
from openpype.hosts.houdini.api import lib
from openpype.lib import (
any_outdated
register_event_callback,
emit_event,
any_outdated,
)
from .lib import get_asset_fps
@ -47,15 +53,15 @@ def install():
pyblish.api.register_host("hpython")
pyblish.api.register_plugin_path(PUBLISH_PATH)
avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH)
register_loader_plugin_path(LOAD_PATH)
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
log.info("Installing callbacks ... ")
# avalon.on("init", on_init)
avalon.api.before("save", before_save)
avalon.api.on("save", on_save)
avalon.api.on("open", on_open)
avalon.api.on("new", on_new)
# register_event_callback("init", on_init)
register_event_callback("before.save", before_save)
register_event_callback("save", on_save)
register_event_callback("open", on_open)
register_event_callback("new", on_new)
pyblish.api.register_callback(
"instanceToggled", on_pyblish_instance_toggled
@ -101,13 +107,13 @@ def _register_callbacks():
def on_file_event_callback(event):
if event == hou.hipFileEventType.AfterLoad:
avalon.api.emit("open", [event])
emit_event("open")
elif event == hou.hipFileEventType.AfterSave:
avalon.api.emit("save", [event])
emit_event("save")
elif event == hou.hipFileEventType.BeforeSave:
avalon.api.emit("before_save", [event])
emit_event("before.save")
elif event == hou.hipFileEventType.AfterClear:
avalon.api.emit("new", [event])
emit_event("new")
def get_main_window():
@ -229,11 +235,11 @@ def ls():
yield data
def before_save(*args):
def before_save():
return lib.validate_fps()
def on_save(*args):
def on_save():
log.info("Running callback on save..")
@ -242,7 +248,7 @@ def on_save(*args):
lib.set_id(node, new_id, overwrite=False)
def on_open(*args):
def on_open():
if not hou.isUIAvailable():
log.debug("Batch mode detected, ignoring `on_open` callbacks..")
@ -279,7 +285,7 @@ def on_open(*args):
dialog.show()
def on_new(_):
def on_new():
"""Set project resolution and fps when create a new file"""
if hou.hipFile.isLoadingHipFile():

View file

@ -2,11 +2,12 @@
"""Houdini specific Avalon/Pyblish plugin definitions."""
import sys
import six
import avalon.api
from avalon.api import CreatorError
import hou
from openpype.api import PypeCreatorMixin
from openpype.pipeline import (
CreatorError,
LegacyCreator
)
from .lib import imprint
@ -14,7 +15,7 @@ class OpenPypeCreatorError(CreatorError):
pass
class Creator(PypeCreatorMixin, avalon.api.Creator):
class Creator(LegacyCreator):
"""Creator plugin to create instances in Houdini
To support the wide range of node types for render output (Alembic, VDB,

View file

@ -2,10 +2,10 @@
"""
from avalon import api
from openpype.pipeline import load
class SetFrameRangeLoader(api.Loader):
class SetFrameRangeLoader(load.LoaderPlugin):
"""Set Houdini frame range"""
families = [
@ -43,7 +43,7 @@ class SetFrameRangeLoader(api.Loader):
hou.playbar.setPlaybackRange(start, end)
class SetFrameRangeWithHandlesLoader(api.Loader):
class SetFrameRangeWithHandlesLoader(load.LoaderPlugin):
"""Set Maya frame range including pre- and post-handles"""
families = [

View file

@ -1,10 +1,12 @@
import os
from avalon import api
from openpype.pipeline import (
load,
get_representation_path,
)
from openpype.hosts.houdini.api import pipeline
class AbcLoader(api.Loader):
class AbcLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["model", "animation", "pointcache", "gpuCache"]
@ -90,7 +92,7 @@ class AbcLoader(api.Loader):
return
# Update the file path
file_path = api.get_representation_path(representation)
file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
alembic_node.setParms({"fileName": file_path})

View file

@ -1,11 +1,15 @@
import os
from avalon import api
from avalon.houdini import pipeline
import clique
from openpype.pipeline import (
load,
get_representation_path,
)
from openpype.hosts.houdini.api import pipeline
class AssLoader(api.Loader):
class AssLoader(load.LoaderPlugin):
"""Load .ass with Arnold Procedural"""
families = ["ass"]
@ -88,7 +92,7 @@ class AssLoader(api.Loader):
def update(self, container, representation):
# Update the file path
file_path = api.get_representation_path(representation)
file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
procedural = container["node"]

View file

@ -1,4 +1,7 @@
from avalon import api
from openpype.pipeline import (
load,
get_representation_path,
)
from openpype.hosts.houdini.api import pipeline
@ -74,7 +77,7 @@ def transfer_non_default_values(src, dest, ignore=None):
dest_parm.setFromParm(parm)
class CameraLoader(api.Loader):
class CameraLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["camera"]
@ -129,7 +132,7 @@ class CameraLoader(api.Loader):
node = container["node"]
# Update the file path
file_path = api.get_representation_path(representation)
file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
# Update attributes

View file

@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
from avalon import api
import os
from openpype.pipeline import (
load,
get_representation_path,
)
from openpype.hosts.houdini.api import pipeline
class HdaLoader(api.Loader):
class HdaLoader(load.LoaderPlugin):
"""Load Houdini Digital Asset file."""
families = ["hda"]
@ -15,7 +18,6 @@ class HdaLoader(api.Loader):
color = "orange"
def load(self, context, name=None, namespace=None, data=None):
import os
import hou
# Format file name, Houdini only wants forward slashes
@ -49,7 +51,7 @@ class HdaLoader(api.Loader):
import hou
hda_node = container["node"]
file_path = api.get_representation_path(representation)
file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
hou.hda.installFile(file_path)
defs = hda_node.type().allInstalledDefinitions()

View file

@ -1,6 +1,9 @@
import os
from avalon import api
from openpype.pipeline import (
load,
get_representation_path,
)
from openpype.hosts.houdini.api import lib, pipeline
import hou
@ -37,7 +40,7 @@ def get_image_avalon_container():
return image_container
class ImageLoader(api.Loader):
class ImageLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["colorbleed.imagesequence"]
@ -87,7 +90,7 @@ class ImageLoader(api.Loader):
node = container["node"]
# Update the file path
file_path = api.get_representation_path(representation)
file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
file_path = self._get_file_sequence(file_path)

View file

@ -1,8 +1,11 @@
from avalon import api
from openpype.pipeline import (
load,
get_representation_path,
)
from openpype.hosts.houdini.api import lib, pipeline
class USDSublayerLoader(api.Loader):
class USDSublayerLoader(load.LoaderPlugin):
"""Sublayer USD file in Solaris"""
families = [
@ -57,7 +60,7 @@ class USDSublayerLoader(api.Loader):
node = container["node"]
# Update the file path
file_path = api.get_representation_path(representation)
file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
# Update attributes

View file

@ -1,8 +1,11 @@
from avalon import api
from openpype.pipeline import (
load,
get_representation_path,
)
from openpype.hosts.houdini.api import lib, pipeline
class USDReferenceLoader(api.Loader):
class USDReferenceLoader(load.LoaderPlugin):
"""Reference USD file in Solaris"""
families = [
@ -57,7 +60,7 @@ class USDReferenceLoader(api.Loader):
node = container["node"]
# Update the file path
file_path = api.get_representation_path(representation)
file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
# Update attributes

View file

@ -1,11 +1,14 @@
import os
import re
from avalon import api
from openpype.pipeline import (
load,
get_representation_path,
)
from openpype.hosts.houdini.api import pipeline
class VdbLoader(api.Loader):
class VdbLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["vdbcache"]
@ -96,7 +99,7 @@ class VdbLoader(api.Loader):
return
# Update the file path
file_path = api.get_representation_path(representation)
file_path = get_representation_path(representation)
file_path = self.format_path(file_path)
file_node.setParms({"fileName": file_path})

View file

@ -1,7 +1,7 @@
from avalon import api
from openpype.pipeline import load
class ShowInUsdview(api.Loader):
class ShowInUsdview(load.LoaderPlugin):
"""Open USD file in usdview"""
families = ["colorbleed.usd"]

View file

@ -7,6 +7,7 @@ from collections import deque
import pyblish.api
import openpype.api
from openpype.pipeline import get_representation_path
import openpype.hosts.houdini.api.usd as hou_usdlib
from openpype.hosts.houdini.api.lib import render_rop
@ -308,7 +309,7 @@ class ExtractUSDLayered(openpype.api.Extractor):
self.log.debug("No existing representation..")
return False
old_file = api.get_representation_path(representation)
old_file = get_representation_path(representation)
if not os.path.exists(old_file):
return False

View file

@ -17,10 +17,16 @@ import bson
from maya import cmds, mel
import maya.api.OpenMaya as om
from avalon import api, io, pipeline
from avalon import api, io
from openpype import lib
from openpype.api import get_anatomy_settings
from openpype.pipeline import (
discover_loader_plugins,
loaders_from_representation,
get_representation_path,
load_container,
)
from .commands import reset_frame_range
@ -1580,21 +1586,21 @@ def assign_look_by_version(nodes, version_id):
log.info("Using look for the first time ..")
# Load file
loaders = api.loaders_from_representation(api.discover(api.Loader),
representation_id)
_loaders = discover_loader_plugins()
loaders = loaders_from_representation(_loaders, representation_id)
Loader = next((i for i in loaders if i.__name__ == "LookLoader"), None)
if Loader is None:
raise RuntimeError("Could not find LookLoader, this is a bug")
# Reference the look file
with maintained_selection():
container_node = pipeline.load(Loader, look_representation)
container_node = load_container(Loader, look_representation)
# Get container members
shader_nodes = get_container_members(container_node)
# Load relationships
shader_relation = api.get_representation_path(json_representation)
shader_relation = get_representation_path(json_representation)
with open(shader_relation, "r") as f:
relationships = json.load(f)
@ -1931,18 +1937,26 @@ def remove_other_uv_sets(mesh):
cmds.removeMultiInstance(attr, b=True)
def get_id_from_history(node):
def get_id_from_sibling(node, history_only=True):
"""Return first node id in the history chain that matches this node.
The nodes in history must be of the exact same node type and must be
parented under the same parent.
Optionally, if no matching node is found from the history, all the
siblings of the node that are of the same type are checked.
Additionally to having the same parent, the sibling must be marked as
'intermediate object'.
Args:
node (str): node to retrieve the
node (str): node to retrieve the history from
history_only (bool): if True and if nothing found in history,
look for an 'intermediate object' in all the node's siblings
of same type
Returns:
str or None: The id from the node in history or None when no id found
on any valid nodes in the history.
str or None: The id from the sibling node or None when no id found
on any valid nodes in the history or siblings.
"""
@ -1971,6 +1985,45 @@ def get_id_from_history(node):
if _id:
return _id
if not history_only:
# Get siblings of same type
similar_nodes = cmds.listRelatives(parent,
type=node_type,
fullPath=True)
similar_nodes = cmds.ls(similar_nodes, exactType=node_type, long=True)
# Exclude itself
similar_nodes = [x for x in similar_nodes if x != node]
# Get all unique ids from siblings in order since
# we consistently take the first one found
sibling_ids = OrderedDict()
for similar_node in similar_nodes:
# Check if "intermediate object"
if not cmds.getAttr(similar_node + ".intermediateObject"):
continue
_id = get_id(similar_node)
if not _id:
continue
if _id in sibling_ids:
sibling_ids[_id].append(similar_node)
else:
sibling_ids[_id] = [similar_node]
if sibling_ids:
first_id, found_nodes = next(iter(sibling_ids.items()))
# Log a warning if we've found multiple unique ids
if len(sibling_ids) > 1:
log.warning(("Found more than 1 intermediate shape with"
" unique id for '{}'. Using id of first"
" found: '{}'".format(node, found_nodes[0])))
return first_id
# Project settings
def set_scene_fps(fps, update=True):

View file

@ -2,7 +2,6 @@ import os
import sys
import errno
import logging
import contextlib
from maya import utils, cmds, OpenMaya
import maya.api.OpenMaya as om
@ -15,8 +14,17 @@ from avalon.pipeline import AVALON_CONTAINER_ID
import openpype.hosts.maya
from openpype.tools.utils import host_tools
from openpype.lib import any_outdated
from openpype.lib import (
any_outdated,
register_event_callback,
emit_event
)
from openpype.lib.path_tools import HostDirmap
from openpype.pipeline import (
LegacyCreator,
register_loader_plugin_path,
deregister_loader_plugin_path,
)
from openpype.hosts.maya.lib import copy_workspace_mel
from . import menu, lib
@ -49,13 +57,13 @@ def install():
pyblish.api.register_host("mayapy")
pyblish.api.register_host("maya")
avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH)
register_loader_plugin_path(LOAD_PATH)
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH)
log.info(PUBLISH_PATH)
log.info("Installing callbacks ... ")
avalon.api.on("init", on_init)
register_event_callback("init", on_init)
# Callbacks below are not required for headless mode, the `init` however
# is important to load referenced Alembics correctly at rendertime.
@ -69,12 +77,12 @@ def install():
menu.install()
avalon.api.on("save", on_save)
avalon.api.on("open", on_open)
avalon.api.on("new", on_new)
avalon.api.before("save", on_before_save)
avalon.api.on("taskChanged", on_task_changed)
avalon.api.on("before.workfile.save", before_workfile_save)
register_event_callback("save", on_save)
register_event_callback("open", on_open)
register_event_callback("new", on_new)
register_event_callback("before.save", on_before_save)
register_event_callback("taskChanged", on_task_changed)
register_event_callback("workfile.save.before", before_workfile_save)
def _set_project():
@ -137,7 +145,7 @@ def _register_callbacks():
def _on_maya_initialized(*args):
avalon.api.emit("init", args)
emit_event("init")
if cmds.about(batch=True):
log.warning("Running batch mode ...")
@ -148,15 +156,15 @@ def _on_maya_initialized(*args):
def _on_scene_new(*args):
avalon.api.emit("new", args)
emit_event("new")
def _on_scene_save(*args):
avalon.api.emit("save", args)
emit_event("save")
def _on_scene_open(*args):
avalon.api.emit("open", args)
emit_event("open")
def _before_scene_save(return_code, client_data):
@ -166,7 +174,10 @@ def _before_scene_save(return_code, client_data):
# in order to block the operation.
OpenMaya.MScriptUtil.setBool(return_code, True)
avalon.api.emit("before_save", [return_code, client_data])
emit_event(
"before.save",
{"return_code": return_code}
)
def uninstall():
@ -175,8 +186,8 @@ def uninstall():
pyblish.api.deregister_host("mayapy")
pyblish.api.deregister_host("maya")
avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH)
deregister_loader_plugin_path(LOAD_PATH)
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
avalon.api.deregister_plugin_path(
avalon.api.InventoryAction, INVENTORY_PATH
)
@ -343,7 +354,7 @@ def containerise(name,
return container
def on_init(_):
def on_init():
log.info("Running callback on init..")
def safe_deferred(fn):
@ -384,12 +395,12 @@ def on_init(_):
safe_deferred(override_toolbox_ui)
def on_before_save(return_code, _):
def on_before_save():
"""Run validation for scene's FPS prior to saving"""
return lib.validate_fps()
def on_save(_):
def on_save():
"""Automatically add IDs to new nodes
Any transform of a mesh, without an existing ID, is given one
@ -407,7 +418,7 @@ def on_save(_):
lib.set_id(node, new_id, overwrite=False)
def on_open(_):
def on_open():
"""On scene open let's assume the containers have changed."""
from Qt import QtWidgets
@ -455,7 +466,7 @@ def on_open(_):
dialog.show()
def on_new(_):
def on_new():
"""Set project resolution and fps when create a new file"""
log.info("Running callback on new..")
with lib.suspended_refresh():
@ -471,7 +482,7 @@ def on_new(_):
lib.set_context_settings()
def on_task_changed(*args):
def on_task_changed():
"""Wrapped function of app initialize and maya's on task changed"""
# Run
menu.update_menu_task_label()
@ -509,7 +520,7 @@ def on_task_changed(*args):
def before_workfile_save(event):
workdir_path = event.workdir_path
workdir_path = event["workdir_path"]
if workdir_path:
copy_workspace_mel(workdir_path)

View file

@ -2,9 +2,14 @@ import os
from maya import cmds
from avalon import api
from avalon.vendor import qargparse
from openpype.api import PypeCreatorMixin
import qargparse
from avalon.pipeline import AVALON_CONTAINER_ID
from openpype.pipeline import (
LegacyCreator,
LoaderPlugin,
get_representation_path,
)
from .pipeline import containerise
from . import lib
@ -77,7 +82,7 @@ def get_reference_node_parents(ref):
return parents
class Creator(PypeCreatorMixin, api.Creator):
class Creator(LegacyCreator):
defaults = ['Main']
def process(self):
@ -93,7 +98,7 @@ class Creator(PypeCreatorMixin, api.Creator):
return instance
class Loader(api.Loader):
class Loader(LoaderPlugin):
hosts = ["maya"]
@ -168,16 +173,18 @@ class ReferenceLoader(Loader):
return
ref_node = get_reference_node(nodes, self.log)
loaded_containers.append(containerise(
container = containerise(
name=name,
namespace=namespace,
nodes=[ref_node],
context=context,
loader=self.__class__.__name__
))
)
loaded_containers.append(container)
self._organize_containers(nodes, container)
c += 1
namespace = None
return loaded_containers
def process_reference(self, context, name, namespace, data):
@ -190,7 +197,7 @@ class ReferenceLoader(Loader):
node = container["objectName"]
path = api.get_representation_path(representation)
path = get_representation_path(representation)
# Get reference node from container members
members = get_container_members(node)
@ -243,6 +250,8 @@ class ReferenceLoader(Loader):
self.log.warning("Ignoring file read error:\n%s", exc)
self._organize_containers(content, container["objectName"])
# Reapply alembic settings.
if representation["name"] == "abc" and alembic_data:
alembic_nodes = cmds.ls(
@ -280,7 +289,6 @@ class ReferenceLoader(Loader):
to remove from scene.
"""
from maya import cmds
node = container["objectName"]
@ -310,3 +318,14 @@ class ReferenceLoader(Loader):
deleteNamespaceContent=True)
except RuntimeError:
pass
@staticmethod
def _organize_containers(nodes, container):
# type: (list, str) -> None
"""Put containers in loaded data to correct hierarchy."""
for node in nodes:
id_attr = "{}.id".format(node)
if not cmds.attributeQuery("id", node=node, exists=True):
continue
if cmds.getAttr(id_attr) == AVALON_CONTAINER_ID:
cmds.sets(node, forceElement=container)

View file

@ -8,7 +8,15 @@ import copy
import six
from maya import cmds
from avalon import api, io
from avalon import io
from openpype.pipeline import (
discover_loader_plugins,
loaders_from_representation,
load_container,
update_container,
remove_container,
get_representation_path,
)
from openpype.hosts.maya.api.lib import (
matrix_equals,
unique_namespace
@ -120,12 +128,13 @@ def load_package(filepath, name, namespace=None):
root = "{}:{}".format(namespace, name)
containers = []
all_loaders = api.discover(api.Loader)
all_loaders = discover_loader_plugins()
for representation_id, instances in data.items():
# Find the compatible loaders
loaders = api.loaders_from_representation(all_loaders,
representation_id)
loaders = loaders_from_representation(
all_loaders, representation_id
)
for instance in instances:
container = _add(instance=instance,
@ -180,9 +189,11 @@ def _add(instance, representation_id, loaders, namespace, root="|"):
instance['loader'], instance)
raise RuntimeError("Loader is missing.")
container = api.load(Loader,
representation_id,
namespace=instance['namespace'])
container = load_container(
Loader,
representation_id,
namespace=instance['namespace']
)
# Get the root from the loaded container
loaded_root = get_container_transforms({"objectName": container},
@ -320,13 +331,13 @@ def update_package(set_container, representation):
"type": "representation"
})
current_file = api.get_representation_path(current_representation)
current_file = get_representation_path(current_representation)
assert current_file.endswith(".json")
with open(current_file, "r") as fp:
current_data = json.load(fp)
# Load the new package data
new_file = api.get_representation_path(representation)
new_file = get_representation_path(representation)
assert new_file.endswith(".json")
with open(new_file, "r") as fp:
new_data = json.load(fp)
@ -460,12 +471,12 @@ def update_scene(set_container, containers, current_data, new_data, new_file):
# considered as new element and added afterwards.
processed_containers.pop()
processed_namespaces.remove(container_ns)
api.remove(container)
remove_container(container)
continue
# Check whether the conversion can be done by the Loader.
# They *must* use the same asset, subset and Loader for
# `api.update` to make sense.
# `update_container` to make sense.
old = io.find_one({
"_id": io.ObjectId(representation_current)
})
@ -479,20 +490,21 @@ def update_scene(set_container, containers, current_data, new_data, new_file):
continue
new_version = new["context"]["version"]
api.update(container, version=new_version)
update_container(container, version=new_version)
else:
# Remove this container because it's not in the new data
log.warning("Removing content: %s", container_ns)
api.remove(container)
remove_container(container)
# Add new assets
all_loaders = api.discover(api.Loader)
all_loaders = discover_loader_plugins()
for representation_id, instances in new_data.items():
# Find the compatible loaders
loaders = api.loaders_from_representation(all_loaders,
representation_id)
loaders = loaders_from_representation(
all_loaders, representation_id
)
for instance in instances:
# Already processed in update functionality
@ -517,7 +529,7 @@ def update_scene(set_container, containers, current_data, new_data, new_file):
def compare_representations(old, new):
"""Check if the old representation given can be updated
Due to limitations of the `api.update` function we cannot allow
Due to limitations of the `update_container` function we cannot allow
differences in the following data:
* Representation name (extension)

View file

@ -19,9 +19,9 @@ from openpype.api import (
get_project_settings,
get_asset)
from openpype.modules import ModulesManager
from openpype.pipeline import CreatorError
from avalon.api import Session
from avalon.api import CreatorError
class CreateRender(plugin.Creator):

View file

@ -19,10 +19,10 @@ from openpype.api import (
get_project_settings
)
from openpype.pipeline import CreatorError
from openpype.modules import ModulesManager
from avalon.api import Session
from avalon.api import CreatorError
class CreateVRayScene(plugin.Creator):

View file

@ -1,5 +1,9 @@
import json
from avalon import api, io, pipeline
from avalon import api, io
from openpype.pipeline import (
get_representation_context,
get_representation_path_from_context,
)
from openpype.hosts.maya.api.lib import (
maintained_selection,
apply_shaders
@ -73,11 +77,11 @@ class ImportModelRender(api.InventoryAction):
"name": self.look_data_type,
})
context = pipeline.get_representation_context(look_repr["_id"])
maya_file = pipeline.get_representation_path_from_context(context)
context = get_representation_context(look_repr["_id"])
maya_file = get_representation_path_from_context(context)
context = pipeline.get_representation_context(json_repr["_id"])
json_file = pipeline.get_representation_path_from_context(context)
context = get_representation_context(json_repr["_id"])
json_file = get_representation_path_from_context(context)
# Import the look file
with maintained_selection():

View file

@ -2,14 +2,14 @@
"""
from avalon import api
from openpype.pipeline import load
from openpype.hosts.maya.api.lib import (
maintained_selection,
unique_namespace
)
class SetFrameRangeLoader(api.Loader):
class SetFrameRangeLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["animation",
@ -43,7 +43,7 @@ class SetFrameRangeLoader(api.Loader):
animationEndTime=end)
class SetFrameRangeWithHandlesLoader(api.Loader):
class SetFrameRangeWithHandlesLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["animation",
@ -81,7 +81,7 @@ class SetFrameRangeWithHandlesLoader(api.Loader):
animationEndTime=end)
class ImportMayaLoader(api.Loader):
class ImportMayaLoader(load.LoaderPlugin):
"""Import action for Maya (unmanaged)
Warning:

View file

@ -1,8 +1,11 @@
import os
import clique
from avalon import api
from openpype.api import get_project_settings
from openpype.pipeline import (
load,
get_representation_path
)
import openpype.hosts.maya.api.plugin
from openpype.hosts.maya.api.plugin import get_reference_node
from openpype.hosts.maya.api.lib import (
@ -106,7 +109,7 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
node = container["objectName"]
representation["context"].pop("frame", None)
path = api.get_representation_path(representation)
path = get_representation_path(representation)
print(path)
# path = self.fname
print(self.fname)
@ -164,7 +167,7 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
type="string")
class AssStandinLoader(api.Loader):
class AssStandinLoader(load.LoaderPlugin):
"""Load .ASS file as standin"""
families = ["ass"]
@ -240,7 +243,7 @@ class AssStandinLoader(api.Loader):
import pymel.core as pm
path = api.get_representation_path(representation)
path = get_representation_path(representation)
files_in_path = os.listdir(os.path.split(path)[0])
sequence = 0

View file

@ -1,7 +1,10 @@
from avalon import api
from openpype.pipeline import (
load,
remove_container
)
class AssemblyLoader(api.Loader):
class AssemblyLoader(load.LoaderPlugin):
families = ["assembly"]
representations = ["json"]
@ -48,13 +51,11 @@ class AssemblyLoader(api.Loader):
def update(self, container, representation):
from openpype import setdress
return setdress.update_package(container,
representation)
return setdress.update_package(container, representation)
def remove(self, container):
"""Remove all sub containers"""
from avalon import api
from openpype import setdress
import maya.cmds as cmds
@ -63,7 +64,7 @@ class AssemblyLoader(api.Loader):
for member_container in member_containers:
self.log.info("Removing container %s",
member_container['objectName'])
api.remove(member_container)
remove_container(member_container)
# Remove alembic hierarchy reference
# TODO: Check whether removing all contained references is safe enough

View file

@ -1,10 +1,14 @@
from maya import cmds, mel
from avalon import api, io
from avalon import io
from openpype.pipeline import (
load,
get_representation_path
)
from openpype.hosts.maya.api.pipeline import containerise
from openpype.hosts.maya.api.lib import unique_namespace
class AudioLoader(api.Loader):
class AudioLoader(load.LoaderPlugin):
"""Specific loader of audio."""
families = ["audio"]
@ -51,7 +55,7 @@ class AudioLoader(api.Loader):
assert audio_node is not None, "Audio node not found."
path = api.get_representation_path(representation)
path = get_representation_path(representation)
audio_node.filename.set(path)
cmds.setAttr(
container["objectName"] + ".representation",

View file

@ -1,9 +1,13 @@
import os
from avalon import api
from openpype.pipeline import (
load,
get_representation_path
)
from openpype.api import get_project_settings
class GpuCacheLoader(api.Loader):
class GpuCacheLoader(load.LoaderPlugin):
"""Load model Alembic as gpuCache"""
families = ["model"]
@ -73,7 +77,7 @@ class GpuCacheLoader(api.Loader):
import maya.cmds as cmds
path = api.get_representation_path(representation)
path = get_representation_path(representation)
# Update the cache
members = cmds.sets(container['objectName'], query=True)

View file

@ -1,6 +1,10 @@
from Qt import QtWidgets, QtCore
from avalon import api, io
from avalon import io
from openpype.pipeline import (
load,
get_representation_path
)
from openpype.hosts.maya.api.pipeline import containerise
from openpype.hosts.maya.api.lib import unique_namespace
@ -74,7 +78,7 @@ class CameraWindow(QtWidgets.QDialog):
self.close()
class ImagePlaneLoader(api.Loader):
class ImagePlaneLoader(load.LoaderPlugin):
"""Specific loader of plate for image planes on selected camera."""
families = ["image", "plate", "render"]
@ -203,7 +207,7 @@ class ImagePlaneLoader(api.Loader):
assert image_plane_shape is not None, "Image plane not found."
path = api.get_representation_path(representation)
path = get_representation_path(representation)
image_plane_shape.imageName.set(path)
cmds.setAttr(
container["objectName"] + ".representation",

View file

@ -5,7 +5,8 @@ from collections import defaultdict
from Qt import QtWidgets
from avalon import api, io
from avalon import io
from openpype.pipeline import get_representation_path
import openpype.hosts.maya.api.plugin
from openpype.hosts.maya.api import lib
from openpype.widgets.message_window import ScrollMessageBox
@ -77,7 +78,7 @@ class LookLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
})
# Load relationships
shader_relation = api.get_representation_path(json_representation)
shader_relation = get_representation_path(json_representation)
with open(shader_relation, "r") as f:
json_data = json.load(f)

View file

@ -1,8 +1,8 @@
from avalon import api
from maya import mel
from openpype.pipeline import load
class MatchmoveLoader(api.Loader):
class MatchmoveLoader(load.LoaderPlugin):
"""
This will run matchmove script to create track in scene.

View file

@ -5,8 +5,11 @@ import clique
import maya.cmds as cmds
from avalon import api
from openpype.api import get_project_settings
from openpype.pipeline import (
load,
get_representation_path
)
from openpype.hosts.maya.api.lib import (
namespaced,
maintained_selection,
@ -15,7 +18,7 @@ from openpype.hosts.maya.api.lib import (
from openpype.hosts.maya.api.pipeline import containerise
class RedshiftProxyLoader(api.Loader):
class RedshiftProxyLoader(load.LoaderPlugin):
"""Load Redshift proxy"""
families = ["redshiftproxy"]
@ -78,7 +81,7 @@ class RedshiftProxyLoader(api.Loader):
rs_meshes = cmds.ls(members, type="RedshiftProxyMesh")
assert rs_meshes, "Cannot find RedshiftProxyMesh in container"
filename = api.get_representation_path(representation)
filename = get_representation_path(representation)
for rs_mesh in rs_meshes:
cmds.setAttr("{}.fileName".format(rs_mesh),

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