Merge remote-tracking branch 'upstream/develop' into fusion_integration_v2

# Conflicts:
#	openpype/scripts/fusion_switch_shot.py
This commit is contained in:
Roy Nieterau 2022-03-17 12:20:05 +01:00
commit 4c81d1d0b2
898 changed files with 57420 additions and 9022 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 }}

11
.gitmodules vendored
View file

@ -1,12 +1,3 @@
[submodule "repos/avalon-core"]
path = repos/avalon-core
url = https://github.com/pypeclub/avalon-core.git
[submodule "repos/avalon-unreal-integration"]
path = repos/avalon-unreal-integration
url = https://github.com/pypeclub/avalon-unreal-integration.git
[submodule "openpype/modules/default_modules/ftrack/python2_vendor/arrow"]
path = openpype/modules/default_modules/ftrack/python2_vendor/arrow
url = https://github.com/arrow-py/arrow.git
[submodule "openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api"]
path = openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api
url = https://bitbucket.org/ftrack/ftrack-python-api.git
url = https://github.com/pypeclub/avalon-core.git

View file

@ -1,121 +1,89 @@
# Changelog
## [3.9.0-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD)
## [3.9.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.2...HEAD)
**Deprecated:**
- Loader: Remove default family states for hosts from code [\#2706](https://github.com/pypeclub/OpenPype/pull/2706)
### 📖 Documentation
- Update docusaurus to latest version [\#2760](https://github.com/pypeclub/OpenPype/pull/2760)
- documentation: add example to `repack-version` command [\#2669](https://github.com/pypeclub/OpenPype/pull/2669)
**🆕 New features**
- Flame: loading clips to reels [\#2622](https://github.com/pypeclub/OpenPype/pull/2622)
**🚀 Enhancements**
- Pyblish Pype: Remove redundant new line in installed fonts printing [\#2758](https://github.com/pypeclub/OpenPype/pull/2758)
- Flame: adding validator source clip [\#2746](https://github.com/pypeclub/OpenPype/pull/2746)
- Work Files: Preserve subversion comment of current filename by default [\#2734](https://github.com/pypeclub/OpenPype/pull/2734)
- Ftrack: Disable ftrack module by default [\#2732](https://github.com/pypeclub/OpenPype/pull/2732)
- Project Manager: Disable add task, add asset and save button when not in a project [\#2727](https://github.com/pypeclub/OpenPype/pull/2727)
- dropbox handle big file [\#2718](https://github.com/pypeclub/OpenPype/pull/2718)
- Fusion Move PR: Minor tweaks to Fusion integration [\#2716](https://github.com/pypeclub/OpenPype/pull/2716)
- Nuke: prerender with review knob [\#2691](https://github.com/pypeclub/OpenPype/pull/2691)
- Maya configurable unit validator [\#2680](https://github.com/pypeclub/OpenPype/pull/2680)
- General: Add settings for CleanUpFarm and disable the plugin by default [\#2679](https://github.com/pypeclub/OpenPype/pull/2679)
- Project Manager: Only allow scroll wheel edits when spinbox is active [\#2678](https://github.com/pypeclub/OpenPype/pull/2678)
- Ftrack: Sync description to assets [\#2670](https://github.com/pypeclub/OpenPype/pull/2670)
- General: FFmpeg conversion also check attribute string length [\#2635](https://github.com/pypeclub/OpenPype/pull/2635)
- Global: adding studio name/code to anatomy template formatting data [\#2630](https://github.com/pypeclub/OpenPype/pull/2630)
- Houdini: Load Arnold .ass procedurals into Houdini [\#2606](https://github.com/pypeclub/OpenPype/pull/2606)
- Deadline: Simplify GlobalJobPreLoad logic [\#2605](https://github.com/pypeclub/OpenPype/pull/2605)
- Houdini: Implement Arnold .ass standin extraction from Houdini \(also support .ass.gz\) [\#2603](https://github.com/pypeclub/OpenPype/pull/2603)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.0...HEAD)
**🐛 Bug fixes**
- 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)
- Loader UI: Multiple asset selection and underline colors fixed [\#2731](https://github.com/pypeclub/OpenPype/pull/2731)
- General: Fix loading of unused chars in xml format [\#2729](https://github.com/pypeclub/OpenPype/pull/2729)
- TVPaint: Set objectName with members [\#2725](https://github.com/pypeclub/OpenPype/pull/2725)
- General: Don't use 'objectName' from loaded references [\#2715](https://github.com/pypeclub/OpenPype/pull/2715)
- Settings: Studio Project anatomy is queried using right keys [\#2711](https://github.com/pypeclub/OpenPype/pull/2711)
- Local Settings: Additional applications don't break UI [\#2710](https://github.com/pypeclub/OpenPype/pull/2710)
- Houdini: Fix refactor of Houdini host move for CreateArnoldAss [\#2704](https://github.com/pypeclub/OpenPype/pull/2704)
- LookAssigner: Fix imports after moving code to OpenPype repository [\#2701](https://github.com/pypeclub/OpenPype/pull/2701)
- Multiple hosts: unify menu style across hosts [\#2693](https://github.com/pypeclub/OpenPype/pull/2693)
- Maya Redshift fixes [\#2692](https://github.com/pypeclub/OpenPype/pull/2692)
- Maya: fix fps validation popup [\#2685](https://github.com/pypeclub/OpenPype/pull/2685)
- Houdini Explicitly collect correct frame name even in case of single frame render when `frameStart` is provided [\#2676](https://github.com/pypeclub/OpenPype/pull/2676)
- hiero: fix effect collector name and order [\#2673](https://github.com/pypeclub/OpenPype/pull/2673)
- Maya: Fix menu callbacks [\#2671](https://github.com/pypeclub/OpenPype/pull/2671)
- Launcher: Fix access to 'data' attribute on actions [\#2659](https://github.com/pypeclub/OpenPype/pull/2659)
- Houdini: fix usd family in loader and integrators [\#2631](https://github.com/pypeclub/OpenPype/pull/2631)
- 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)
**Merged pull requests:**
**🔀 Refactored code**
- Harmony: Rendering in Deadline didn't work in other machines than submitter [\#2754](https://github.com/pypeclub/OpenPype/pull/2754)
- Maya: set Deadline job/batch name to original source workfile name instead of published workfile [\#2733](https://github.com/pypeclub/OpenPype/pull/2733)
- Fusion: Moved implementation into OpenPype [\#2713](https://github.com/pypeclub/OpenPype/pull/2713)
- TVPaint: Plugin build without dependencies [\#2705](https://github.com/pypeclub/OpenPype/pull/2705)
- Webpublisher: Photoshop create a beauty png [\#2689](https://github.com/pypeclub/OpenPype/pull/2689)
- Ftrack: Hierarchical attributes are queried properly [\#2682](https://github.com/pypeclub/OpenPype/pull/2682)
- Maya: Add Validate Frame Range settings [\#2661](https://github.com/pypeclub/OpenPype/pull/2661)
- Harmony: move to Openpype [\#2657](https://github.com/pypeclub/OpenPype/pull/2657)
- General: Show applications without integration in project [\#2656](https://github.com/pypeclub/OpenPype/pull/2656)
- Maya: cleanup duplicate rendersetup code [\#2642](https://github.com/pypeclub/OpenPype/pull/2642)
- General: Reduce style usage to OpenPype repository [\#2889](https://github.com/pypeclub/OpenPype/pull/2889)
## [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)
**🚀 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)
- 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)
- Ftrack: Job killer with missing user [\#2819](https://github.com/pypeclub/OpenPype/pull/2819)
- 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)
- General: Extract template formatting from anatomy [\#2766](https://github.com/pypeclub/OpenPype/pull/2766)
## [3.8.2](https://github.com/pypeclub/OpenPype/tree/3.8.2) (2022-02-07)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.8.2-nightly.3...3.8.2)
### 📖 Documentation
- Cosmetics: Fix common typos in openpype/website [\#2617](https://github.com/pypeclub/OpenPype/pull/2617)
**🚀 Enhancements**
- General: Project backup tools [\#2629](https://github.com/pypeclub/OpenPype/pull/2629)
- nuke: adding clear button to write nodes [\#2627](https://github.com/pypeclub/OpenPype/pull/2627)
- Ftrack: Family to Asset type mapping is in settings [\#2602](https://github.com/pypeclub/OpenPype/pull/2602)
**🐛 Bug fixes**
- Fix pulling of cx\_freeze 6.10 [\#2628](https://github.com/pypeclub/OpenPype/pull/2628)
**Merged pull requests:**
- Docker: enhance dockerfiles with metadata, fix pyenv initialization [\#2647](https://github.com/pypeclub/OpenPype/pull/2647)
- WebPublisher: fix instance duplicates [\#2641](https://github.com/pypeclub/OpenPype/pull/2641)
- Fix - safer pulling of task name for webpublishing from PS [\#2613](https://github.com/pypeclub/OpenPype/pull/2613)
## [3.8.1](https://github.com/pypeclub/OpenPype/tree/3.8.1) (2022-02-01)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.8.1-nightly.3...3.8.1)
**🚀 Enhancements**
- Webpublisher: Thumbnail extractor [\#2600](https://github.com/pypeclub/OpenPype/pull/2600)
**🐛 Bug fixes**
- Release/3.8.0 [\#2619](https://github.com/pypeclub/OpenPype/pull/2619)
- hotfix: OIIO tool path - add extension on windows [\#2618](https://github.com/pypeclub/OpenPype/pull/2618)
- Settings: Enum does not store empty string if has single item to select [\#2615](https://github.com/pypeclub/OpenPype/pull/2615)
**Merged pull requests:**
- Bump pillow from 8.4.0 to 9.0.0 [\#2595](https://github.com/pypeclub/OpenPype/pull/2595)
## [3.8.0](https://github.com/pypeclub/OpenPype/tree/3.8.0) (2022-01-24)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.8.0-nightly.7...3.8.0)

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

@ -42,6 +42,12 @@ def standalonepublisher():
PypeCommands().launch_standalone_publisher()
@main.command()
def traypublisher():
"""Show new OpenPype Standalone publisher UI."""
PypeCommands().launch_traypublisher()
@main.command()
@click.option("-d", "--debug",
is_flag=True, help=("Run pype tray in debug mode"))
@ -371,10 +377,15 @@ def run(script):
"--app_variant",
help="Provide specific app variant for test, empty for latest",
default=None)
def runtests(folder, mark, pyargs, test_data_folder, persist, app_variant):
@click.option("-t",
"--timeout",
help="Provide specific timeout value for test case",
default=None)
def runtests(folder, mark, pyargs, test_data_folder, persist, app_variant,
timeout):
"""Run all automatic tests after proper initialization via start.py"""
PypeCommands().run_tests(folder, mark, pyargs, test_data_folder,
persist, app_variant)
persist, app_variant, timeout)
@main.command()

View file

@ -17,12 +17,13 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook):
"nuke",
"nukex",
"hiero",
"houdini",
"nukestudio",
"fusion",
"blender",
"photoshop",
"tvpaint",
"afftereffects"
"aftereffects"
]
def execute(self):

View file

@ -19,7 +19,7 @@ class CopyTemplateWorkfile(PreLaunchHook):
# Before `AddLastWorkfileToLaunchArgs`
order = 0
app_groups = ["blender", "photoshop", "tvpaint", "afftereffects"]
app_groups = ["blender", "photoshop", "tvpaint", "aftereffects"]
def execute(self):
"""Check if can copy template for context and do it if possible.
@ -44,7 +44,7 @@ class CopyTemplateWorkfile(PreLaunchHook):
return
if os.path.exists(last_workfile):
self.log.debug("Last workfile exits. Skipping {} process.".format(
self.log.debug("Last workfile exists. Skipping {} process.".format(
self.__class__.__name__
))
return
@ -120,7 +120,7 @@ class CopyTemplateWorkfile(PreLaunchHook):
f"Creating workfile from template: \"{template_path}\""
)
# Copy template workfile to new destinantion
# Copy template workfile to new destination
shutil.copy2(
os.path.normpath(template_path),
os.path.normpath(last_workfile)

View file

@ -2,7 +2,7 @@ from openpype.api import Anatomy
from openpype.lib import (
PreLaunchHook,
EnvironmentPrepData,
prepare_host_environments,
prepare_app_environments,
prepare_context_environments
)
@ -14,14 +14,6 @@ class GlobalHostDataHook(PreLaunchHook):
def execute(self):
"""Prepare global objects to `data` that will be used for sure."""
if not self.application.is_host:
self.log.info(
"Skipped hook {}. Application is not marked as host.".format(
self.__class__.__name__
)
)
return
self.prepare_global_data()
if not self.data.get("asset_doc"):
@ -49,7 +41,7 @@ class GlobalHostDataHook(PreLaunchHook):
"log": self.log
})
prepare_host_environments(temp_data, self.launch_context.env_group)
prepare_app_environments(temp_data, self.launch_context.env_group)
prepare_context_environments(temp_data)
temp_data.pop("log")

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'...")
@ -202,13 +212,10 @@ def reload_pipeline(*args):
avalon.api.uninstall()
for module in (
"avalon.io",
"avalon.lib",
"avalon.pipeline",
"avalon.tools.creator.app",
"avalon.tools.manager.app",
"avalon.api",
"avalon.tools",
"avalon.io",
"avalon.lib",
"avalon.pipeline",
"avalon.api",
):
module = importlib.import_module(module)
importlib.reload(module)

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

@ -527,6 +527,7 @@ def get_segment_attributes(segment):
# Add timeline segment to tree
clip_data = {
"shot_name": segment.shot_name.get_value(),
"segment_name": segment.name.get_value(),
"segment_comment": segment.comment.get_value(),
"tape_name": segment.tape_name,

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"]
@ -361,6 +364,7 @@ class PublishableClip:
vertical_sync_default = False
driving_layer_default = ""
index_from_segment_default = False
use_shot_name_default = False
def __init__(self, segment, **kwargs):
self.rename_index = kwargs["rename_index"]
@ -376,6 +380,7 @@ class PublishableClip:
# segment (clip) main attributes
self.cs_name = self.clip_data["segment_name"]
self.cs_index = int(self.clip_data["segment"])
self.shot_name = self.clip_data["shot_name"]
# get track name and index
self.track_index = int(self.clip_data["track"])
@ -419,18 +424,21 @@ class PublishableClip:
# deal with clip name
new_name = self.marker_data.pop("newClipName")
if self.rename:
if self.rename and not self.use_shot_name:
# rename segment
self.current_segment.name = str(new_name)
self.marker_data["asset"] = str(new_name)
elif self.use_shot_name:
self.marker_data["asset"] = self.shot_name
self.marker_data["hierarchyData"]["shot"] = self.shot_name
else:
self.marker_data["asset"] = self.cs_name
self.marker_data["hierarchyData"]["shot"] = self.cs_name
if self.marker_data["heroTrack"] and self.review_layer:
self.marker_data.update({"reviewTrack": self.review_layer})
self.marker_data["reviewTrack"] = self.review_layer
else:
self.marker_data.update({"reviewTrack": None})
self.marker_data["reviewTrack"] = None
# create pype tag on track_item and add data
fpipeline.imprint(self.current_segment, self.marker_data)
@ -463,6 +471,8 @@ class PublishableClip:
# ui_inputs data or default values if gui was not used
self.rename = self.ui_inputs.get(
"clipRename", {}).get("value") or self.rename_default
self.use_shot_name = self.ui_inputs.get(
"useShotName", {}).get("value") or self.use_shot_name_default
self.clip_name = self.ui_inputs.get(
"clipName", {}).get("value") or self.clip_name_default
self.hierarchy = self.ui_inputs.get(
@ -652,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

@ -87,41 +87,48 @@ class CreateShotClip(opfapi.Creator):
"target": "tag",
"toolTip": "Parents folder for shot root folder, Template filled with `Hierarchy Data` section", # noqa
"order": 0},
"useShotName": {
"value": True,
"type": "QCheckBox",
"label": "Use Shot Name",
"target": "ui",
"toolTip": "Use name form Shot name clip attribute", # noqa
"order": 1},
"clipRename": {
"value": False,
"type": "QCheckBox",
"label": "Rename clips",
"target": "ui",
"toolTip": "Renaming selected clips on fly", # noqa
"order": 1},
"order": 2},
"clipName": {
"value": "{sequence}{shot}",
"type": "QLineEdit",
"label": "Clip Name Template",
"target": "ui",
"toolTip": "template for creating shot namespaused for renaming (use rename: on)", # noqa
"order": 2},
"order": 3},
"segmentIndex": {
"value": True,
"type": "QCheckBox",
"label": "Segment index",
"target": "ui",
"toolTip": "Take number from segment index", # noqa
"order": 3},
"order": 4},
"countFrom": {
"value": 10,
"type": "QSpinBox",
"label": "Count sequence from",
"target": "ui",
"toolTip": "Set when the sequence number stafrom", # noqa
"order": 4},
"order": 5},
"countSteps": {
"value": 10,
"type": "QSpinBox",
"label": "Stepping number",
"target": "ui",
"toolTip": "What number is adding every new step", # noqa
"order": 5},
"order": 6},
}
},
"hierarchyData": {

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

@ -22,6 +22,7 @@ class ExtractSubsetResources(openpype.api.Extractor):
"ext": "jpg",
"xml_preset_file": "Jpeg (8-bit).xml",
"xml_preset_dir": "",
"export_type": "File Sequence",
"colorspace_out": "Output - sRGB",
"representation_add_range": False,
"representation_tags": ["thumbnail"]
@ -30,6 +31,7 @@ class ExtractSubsetResources(openpype.api.Extractor):
"ext": "mov",
"xml_preset_file": "Apple iPad (1920x1080).xml",
"xml_preset_dir": "",
"export_type": "Movie",
"colorspace_out": "Output - Rec.709",
"representation_add_range": True,
"representation_tags": [
@ -54,21 +56,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 +93,52 @@ 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"]
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
})
# 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 +161,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 +178,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 +189,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 +217,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 +309,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

@ -29,7 +29,7 @@
<fileType>Jpeg</fileType>
<codec>923688</codec>
<codecProfile></codecProfile>
<namePattern>&lt;segment name&gt;</namePattern>
<namePattern>&lt;shot name&gt;</namePattern>
<compressionQuality>100</compressionQuality>
<transferCharacteristic>2</transferCharacteristic>
<colorimetricSpecification>4</colorimetricSpecification>

View file

@ -27,7 +27,7 @@
</sequence>
<movie>
<fileType>QuickTime</fileType>
<namePattern>&lt;segment name&gt;</namePattern>
<namePattern>&lt;shot name&gt;</namePattern>
<yuvHeadroom>0</yuvHeadroom>
<yuvColourSpace>PCS_709</yuvColourSpace>
<operationalPattern>None</operationalPattern>
@ -43,7 +43,7 @@
<targetVersion>2021</targetVersion>
<pathSuffix>/profiles/.33622016/HDTV_720p_8Mbits.cdxprof</pathSuffix>
</codecProfile>
<namePattern>&lt;segment name&gt;_&lt;video codec&gt;</namePattern>
<namePattern>&lt;shot name&gt;_&lt;video codec&gt;</namePattern>
<compressionQuality>50</compressionQuality>
<transferCharacteristic>2</transferCharacteristic>
<colorimetricSpecification>4</colorimetricSpecification>

View file

@ -8,7 +8,7 @@ PLUGIN_DIR = os.path.dirname(os.path.dirname(__file__))
EXPORT_PRESETS_DIR = os.path.join(PLUGIN_DIR, "export_preset")
CONFIG_DIR = os.path.join(os.path.expanduser(
"~/.openpype"), "openpype_flame_to_ftrack")
"~/.openpype"), "openpype_babypublisher")
@contextmanager

View file

@ -360,6 +360,8 @@ class FtrackComponentCreator:
class FtrackEntityOperator:
existing_tasks = []
def __init__(self, session, project_entity):
self.session = session
self.project_entity = project_entity
@ -392,10 +394,7 @@ class FtrackEntityOperator:
query = '{} where name is "{}" and project_id is "{}"'.format(
type, name, self.project_entity["id"])
try:
entity = session.query(query).one()
except Exception:
entity = None
entity = session.query(query).first()
# if entity doesnt exist then create one
if not entity:
@ -430,10 +429,21 @@ class FtrackEntityOperator:
return parents
def create_task(self, task_type, task_types, parent):
existing_task = [
_exising_tasks = [
child for child in parent['children']
if child.entity_type.lower() == 'task'
if child['name'].lower() in task_type.lower()
]
# add task into existing tasks if they are not already there
for _t in _exising_tasks:
if _t in self.existing_tasks:
continue
self.existing_tasks.append(_t)
existing_task = [
task for task in self.existing_tasks
if task['name'].lower() in task_type.lower()
if task['parent'] == parent
]
if existing_task:
@ -445,4 +455,5 @@ class FtrackEntityOperator:
})
task["type"] = task_types[task_type]
self.existing_tasks.append(task)
return task

View file

@ -1,4 +1,4 @@
from PySide2 import QtWidgets, QtCore
from Qt import QtWidgets, QtCore
import uiwidgets
import app_utils
@ -33,11 +33,12 @@ class MainWindow(QtWidgets.QWidget):
self.panel_class.clear_temp_data()
self.panel_class.close()
clear_inner_modules()
ftrack_lib.FtrackEntityOperator.existing_tasks = []
# now the panel can be closed
event.accept()
class FlameToFtrackPanel(object):
class FlameBabyPublisherPanel(object):
session = None
temp_data_dir = None
processed_components = []
@ -78,7 +79,7 @@ class FlameToFtrackPanel(object):
# creating ui
self.window.setMinimumSize(1500, 600)
self.window.setWindowTitle('Sequence Shots to Ftrack')
self.window.setWindowTitle('OpenPype: Baby-publisher')
self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
self.window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.window.setFocusPolicy(QtCore.Qt.StrongFocus)
@ -469,10 +470,14 @@ class FlameToFtrackPanel(object):
for sequence in self.selection:
frame_rate = float(str(sequence.frame_rate)[:-4])
for ver in sequence.versions:
for tracks in ver.tracks:
for segment in tracks.segments:
for track in ver.tracks:
if len(track.segments) == 0 and track.hidden:
continue
for segment in track.segments:
print(segment.attributes)
if str(segment.name)[1:-1] == "":
if segment.name.get_value() == "":
continue
if segment.hidden.get_value() is True:
continue
# get clip frame duration
record_duration = str(segment.record_duration)[1:-1]
@ -492,11 +497,11 @@ class FlameToFtrackPanel(object):
# Add timeline segment to tree
QtWidgets.QTreeWidgetItem(self.tree, [
str(sequence.name)[1:-1], # seq
str(segment.name)[1:-1], # shot
sequence.name.get_value(), # seq name
segment.shot_name.get_value(), # shot name
str(clip_duration), # clip duration
shot_description, # shot description
str(segment.comment)[1:-1] # task description
segment.comment.get_value() # task description
]).setFlags(
QtCore.Qt.ItemIsEditable
| QtCore.Qt.ItemIsEnabled

View file

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

View file

@ -16,10 +16,11 @@ def flame_panel_executor(selection):
if "panel_app" in sys.modules.keys():
print("panel_app module is already loaded")
del sys.modules["panel_app"]
import panel_app
reload(panel_app) # noqa
print("panel_app module removed from sys.modules")
import panel_app
panel_app.FlameToFtrackPanel(selection)
panel_app.FlameBabyPublisherPanel(selection)
def scope_sequence(selection):
@ -30,7 +31,7 @@ def scope_sequence(selection):
def get_media_panel_custom_ui_actions():
return [
{
"name": "OpenPype: Ftrack",
"name": "OpenPype: Baby-publisher",
"actions": [
{
"name": "Create Shots",

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
from openpype.api import (
get_asset
@ -167,7 +167,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

@ -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

@ -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

@ -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

@ -361,7 +361,7 @@ def zip_and_move(source, destination):
log.debug(f"Saved '{source}' to '{destination}'")
def show(module_name):
def show(tool_name):
"""Call show on "module_name".
This allows to make a QApplication ahead of time and always "exec_" to
@ -375,13 +375,6 @@ def show(module_name):
# requests to be received properly.
time.sleep(1)
# Get tool name from module name
# TODO this is for backwards compatibility not sure if `TB_sceneOpened.js`
# is automatically updated.
# Previous javascript sent 'module_name' which contained whole tool import
# string e.g. "avalon.tools.workfiles" now it should be only "workfiles"
tool_name = module_name.split(".")[-1]
kwargs = {}
if tool_name == "loader":
kwargs["use_context"] = True

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 .lib import (
sync_avalon_data_to_workfile,
launch_workfiles_app,
selection_changed_timeline,
before_project_save
before_project_save,
register_event_callback
)
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

@ -24,8 +24,7 @@ from .lib import (
lsattrs,
read,
maintained_selection,
unique_name
maintained_selection
)
@ -51,8 +50,7 @@ __all__ = [
"lsattrs",
"read",
"maintained_selection",
"unique_name"
"maintained_selection"
]
# Backwards API compatibility

View file

@ -99,65 +99,6 @@ def get_id_required_nodes():
return list(nodes)
def get_additional_data(container):
"""Not implemented yet!"""
return container
def set_parameter_callback(node, parameter, language, callback):
"""Link a callback to a parameter of a node
Args:
node(hou.Node): instance of the nodee
parameter(str): name of the parameter
language(str): name of the language, e.g.: python
callback(str): command which needs to be triggered
Returns:
None
"""
template_grp = node.parmTemplateGroup()
template = template_grp.find(parameter)
if not template:
return
script_language = (hou.scriptLanguage.Python if language == "python" else
hou.scriptLanguage.Hscript)
template.setScriptCallbackLanguage(script_language)
template.setScriptCallback(callback)
template.setTags({"script_callback": callback,
"script_callback_language": language.lower()})
# Replace the existing template with the adjusted one
template_grp.replace(parameter, template)
node.setParmTemplateGroup(template_grp)
def set_parameter_callbacks(node, parameter_callbacks):
"""Set callbacks for multiple parameters of a node
Args:
node(hou.Node): instance of a hou.Node
parameter_callbacks(dict): collection of parameter and callback data
example: {"active" :
{"language": "python",
"callback": "print('hello world)'"}
}
Returns:
None
"""
for parameter, data in parameter_callbacks.items():
language = data["language"]
callback = data["callback"]
set_parameter_callback(node, parameter, language, callback)
def get_output_parameter(node):
"""Return the render output parameter name of the given node
@ -189,19 +130,6 @@ def get_output_parameter(node):
raise TypeError("Node type '%s' not supported" % node_type)
@contextmanager
def attribute_values(node, data):
previous_attrs = {key: node.parm(key).eval() for key in data.keys()}
try:
node.setParms(data)
yield
except Exception as exc:
pass
finally:
node.setParms(previous_attrs)
def set_scene_fps(fps):
hou.setFps(fps)
@ -349,10 +277,6 @@ def render_rop(ropnode):
raise RuntimeError("Render failed: {0}".format(exc))
def children_as_string(node):
return [c.name() for c in node.children()]
def imprint(node, data):
"""Store attributes with value on a node
@ -473,53 +397,6 @@ def read(node):
parameter in node.spareParms()}
def unique_name(name, format="%03d", namespace="", prefix="", suffix="",
separator="_"):
"""Return unique `name`
The function takes into consideration an optional `namespace`
and `suffix`. The suffix is included in evaluating whether a
name exists - such as `name` + "_GRP" - but isn't included
in the returned value.
If a namespace is provided, only names within that namespace
are considered when evaluating whether the name is unique.
Arguments:
format (str, optional): The `name` is given a number, this determines
how this number is formatted. Defaults to a padding of 2.
E.g. my_name01, my_name02.
namespace (str, optional): Only consider names within this namespace.
suffix (str, optional): Only consider names with this suffix.
Example:
>>> name = hou.node("/obj").createNode("geo", name="MyName")
>>> assert hou.node("/obj/MyName")
True
>>> unique = unique_name(name)
>>> assert hou.node("/obj/{}".format(unique))
False
"""
iteration = 1
parts = [prefix, name, format % iteration, suffix]
if namespace:
parts.insert(0, namespace)
unique = separator.join(parts)
children = children_as_string(hou.node("/obj"))
while unique in children:
iteration += 1
unique = separator.join(parts)
if suffix:
return unique[:-len(suffix)]
return unique
@contextmanager
def maintained_selection():
"""Maintain selection during context
@ -542,3 +419,37 @@ def maintained_selection():
if previous_selection:
for node in previous_selection:
node.setSelected(on=True)
def reset_framerange():
"""Set frame range to current asset"""
asset_name = api.Session["AVALON_ASSET"]
asset = io.find_one({"name": asset_name, "type": "asset"})
frame_start = asset["data"].get("frameStart")
frame_end = asset["data"].get("frameEnd")
# Backwards compatibility
if frame_start is None or frame_end is None:
frame_start = asset["data"].get("edit_in")
frame_end = asset["data"].get("edit_out")
if frame_start is None or frame_end is None:
log.warning("No edit information found for %s" % asset_name)
return
handles = asset["data"].get("handles") or 0
handle_start = asset["data"].get("handleStart")
if handle_start is None:
handle_start = handles
handle_end = asset["data"].get("handleEnd")
if handle_end is None:
handle_end = handles
frame_start -= int(handle_start)
frame_end += int(handle_end)
hou.playbar.setFrameRange(frame_start, frame_end)
hou.playbar.setPlaybackRange(frame_start, frame_end)
hou.setFrame(frame_start)

View file

@ -4,17 +4,24 @@ import logging
import contextlib
import hou
import hdefereval
import pyblish.api
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
@ -46,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
@ -66,9 +73,10 @@ def install():
sys.path.append(hou_pythonpath)
# Set asset FPS for the empty scene directly after launch of Houdini
# so it initializes into the correct scene FPS
_set_asset_fps()
# Set asset settings for the empty scene directly after launch of Houdini
# so it initializes into the correct scene FPS, Frame Range, etc.
# todo: make sure this doesn't trigger when opening with last workfile
_set_context_settings()
def uninstall():
@ -99,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():
@ -227,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..")
@ -240,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..")
@ -277,20 +285,51 @@ 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():
# This event also triggers when Houdini opens a file due to the
# new event being registered to 'afterClear'. As such we can skip
# 'new' logic if the user is opening a file anyway
log.debug("Skipping on new callback due to scene being opened.")
return
log.info("Running callback on new..")
_set_asset_fps()
_set_context_settings()
# It seems that the current frame always gets reset to frame 1 on
# new scene. So we enforce current frame to be at the start of the playbar
# with execute deferred
def _enforce_start_frame():
start = hou.playbar.playbackRange()[0]
hou.setFrame(start)
hdefereval.executeDeferred(_enforce_start_frame)
def _set_asset_fps():
"""Set Houdini scene FPS to the default required for current asset"""
def _set_context_settings():
"""Apply the project settings from the project definition
Settings can be overwritten by an asset if the asset.data contains
any information regarding those settings.
Examples of settings:
fps
resolution
renderer
Returns:
None
"""
# Set new scene fps
fps = get_asset_fps()
print("Setting scene FPS to %i" % fps)
lib.set_scene_fps(fps)
lib.reset_framerange()
def on_pyblish_instance_toggled(instance, new_value, old_value):
"""Toggle saver tool passthrough states on instance toggles."""

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 = [
@ -29,8 +29,8 @@ class SetFrameRangeLoader(api.Loader):
version = context["version"]
version_data = version.get("data", {})
start = version_data.get("startFrame", None)
end = version_data.get("endFrame", None)
start = version_data.get("frameStart", None)
end = version_data.get("frameEnd", None)
if start is None or end is None:
print(
@ -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 = [
@ -67,8 +67,8 @@ class SetFrameRangeWithHandlesLoader(api.Loader):
version = context["version"]
version_data = version.get("data", {})
start = version_data.get("startFrame", None)
end = version_data.get("endFrame", None)
start = version_data.get("frameStart", None)
end = version_data.get("frameEnd", None)
if start is None or end is None:
print(
@ -78,9 +78,8 @@ class SetFrameRangeWithHandlesLoader(api.Loader):
return
# Include handles
handles = version_data.get("handles", 0)
start -= handles
end += handles
start -= version_data.get("handleStart", 0)
end += version_data.get("handleEnd", 0)
hou.playbar.setFrameRange(start, end)
hou.playbar.setPlaybackRange(start, end)

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

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