mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 00:44:52 +01:00
OP-2766 - merge develop
This commit is contained in:
commit
516e86f4e0
81 changed files with 1408 additions and 658 deletions
19
CHANGELOG.md
19
CHANGELOG.md
|
|
@ -1,6 +1,6 @@
|
|||
# Changelog
|
||||
|
||||
## [3.9.2-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
## [3.9.2-nightly.3](https://github.com/pypeclub/OpenPype/tree/HEAD)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.1...HEAD)
|
||||
|
||||
|
|
@ -8,11 +8,16 @@
|
|||
|
||||
- Docs: Added MongoDB requirements [\#2951](https://github.com/pypeclub/OpenPype/pull/2951)
|
||||
|
||||
**🆕 New features**
|
||||
|
||||
- Multiverse: First PR [\#2908](https://github.com/pypeclub/OpenPype/pull/2908)
|
||||
|
||||
**🚀 Enhancements**
|
||||
|
||||
- Slack: Added configurable maximum file size of review upload to Slack [\#2945](https://github.com/pypeclub/OpenPype/pull/2945)
|
||||
- NewPublisher: Prepared implementation of optional pyblish plugin [\#2943](https://github.com/pypeclub/OpenPype/pull/2943)
|
||||
- Workfiles: Open published workfiles [\#2925](https://github.com/pypeclub/OpenPype/pull/2925)
|
||||
- General: Default modules loaded dynamically [\#2923](https://github.com/pypeclub/OpenPype/pull/2923)
|
||||
- CI: change the version bump logic [\#2919](https://github.com/pypeclub/OpenPype/pull/2919)
|
||||
- Deadline: Add headless argument [\#2916](https://github.com/pypeclub/OpenPype/pull/2916)
|
||||
- Nuke: Add no-audio Tag [\#2911](https://github.com/pypeclub/OpenPype/pull/2911)
|
||||
|
|
@ -22,16 +27,21 @@
|
|||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- Slack: Added default for review\_upload\_limit for Slack [\#2965](https://github.com/pypeclub/OpenPype/pull/2965)
|
||||
- Settings: Conditional dictionary avoid invalid logs [\#2956](https://github.com/pypeclub/OpenPype/pull/2956)
|
||||
- LogViewer: Don't refresh on initialization [\#2949](https://github.com/pypeclub/OpenPype/pull/2949)
|
||||
- nuke: python3 compatibility issue with `iteritems` [\#2948](https://github.com/pypeclub/OpenPype/pull/2948)
|
||||
- General: anatomy data with correct task short key [\#2947](https://github.com/pypeclub/OpenPype/pull/2947)
|
||||
- SceneInventory: Fix imports in UI [\#2944](https://github.com/pypeclub/OpenPype/pull/2944)
|
||||
- Slack: add generic exception [\#2941](https://github.com/pypeclub/OpenPype/pull/2941)
|
||||
- General: Python specific vendor paths on env injection [\#2939](https://github.com/pypeclub/OpenPype/pull/2939)
|
||||
- General: More fail safe delete old versions [\#2936](https://github.com/pypeclub/OpenPype/pull/2936)
|
||||
- Settings UI: Collapsed of collapsible wrapper works as expected [\#2934](https://github.com/pypeclub/OpenPype/pull/2934)
|
||||
- Maya: Do not pass `set` to maya commands \(fixes support for older maya versions\) [\#2932](https://github.com/pypeclub/OpenPype/pull/2932)
|
||||
- General: Don't print log record on OSError [\#2926](https://github.com/pypeclub/OpenPype/pull/2926)
|
||||
- Hiero: Fix import of 'register\_event\_callback' [\#2924](https://github.com/pypeclub/OpenPype/pull/2924)
|
||||
- Ftrack: Missing Ftrack id after editorial publish [\#2905](https://github.com/pypeclub/OpenPype/pull/2905)
|
||||
- AfterEffects: Fix rendering for single frame in DL [\#2875](https://github.com/pypeclub/OpenPype/pull/2875)
|
||||
|
||||
**🔀 Refactored code**
|
||||
|
||||
|
|
@ -43,7 +53,7 @@
|
|||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Maya: Do not pass `set` to maya commands \(fixes support for older maya versions\) [\#2932](https://github.com/pypeclub/OpenPype/pull/2932)
|
||||
- Maya - added transparency into review creator [\#2952](https://github.com/pypeclub/OpenPype/pull/2952)
|
||||
|
||||
## [3.9.1](https://github.com/pypeclub/OpenPype/tree/3.9.1) (2022-03-18)
|
||||
|
||||
|
|
@ -96,14 +106,10 @@
|
|||
- 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)
|
||||
|
||||
**🐛 Bug fixes**
|
||||
|
||||
- General: Missing time function [\#2877](https://github.com/pypeclub/OpenPype/pull/2877)
|
||||
- AfterEffects: Fix rendering for single frame in DL [\#2875](https://github.com/pypeclub/OpenPype/pull/2875)
|
||||
- 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)
|
||||
|
|
@ -126,7 +132,6 @@
|
|||
- 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**
|
||||
|
||||
|
|
|
|||
|
|
@ -2,20 +2,16 @@
|
|||
"""Pype module."""
|
||||
import os
|
||||
import platform
|
||||
import functools
|
||||
import logging
|
||||
|
||||
from .settings import get_project_settings
|
||||
from .lib import (
|
||||
Anatomy,
|
||||
filter_pyblish_plugins,
|
||||
set_plugin_attributes_from_settings,
|
||||
change_timer_to_current_context,
|
||||
register_event_callback,
|
||||
)
|
||||
|
||||
pyblish = avalon = _original_discover = None
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
@ -27,60 +23,17 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
|||
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
||||
|
||||
|
||||
def import_wrapper(func):
|
||||
"""Wrap module imports to specific functions."""
|
||||
@functools.wraps(func)
|
||||
def decorated(*args, **kwargs):
|
||||
global pyblish
|
||||
global avalon
|
||||
global _original_discover
|
||||
if pyblish is None:
|
||||
from pyblish import api as pyblish
|
||||
from avalon import api as avalon
|
||||
|
||||
# we are monkey patching `avalon.api.discover()` to allow us to
|
||||
# load plugin presets on plugins being discovered by avalon.
|
||||
# Little bit of hacking, but it allows us to add out own features
|
||||
# without need to modify upstream code.
|
||||
|
||||
_original_discover = avalon.discover
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return decorated
|
||||
|
||||
|
||||
@import_wrapper
|
||||
def patched_discover(superclass):
|
||||
"""Patch `avalon.api.discover()`.
|
||||
|
||||
Monkey patched version of :func:`avalon.api.discover()`. It allows
|
||||
us to load presets on plugins being discovered.
|
||||
"""
|
||||
# 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(filtered_plugins, superclass)
|
||||
|
||||
return filtered_plugins
|
||||
|
||||
|
||||
@import_wrapper
|
||||
def install():
|
||||
"""Install Pype to Avalon."""
|
||||
"""Install OpenPype to Avalon."""
|
||||
import avalon.api
|
||||
import pyblish.api
|
||||
from pyblish.lib import MessageHandler
|
||||
from openpype.modules import load_modules
|
||||
from openpype.pipeline import (
|
||||
LegacyCreator,
|
||||
register_loader_plugin_path,
|
||||
register_inventory_action,
|
||||
register_creator_plugin_path,
|
||||
)
|
||||
from avalon import pipeline
|
||||
|
||||
# Make sure modules are loaded
|
||||
load_modules()
|
||||
|
|
@ -93,8 +46,8 @@ def install():
|
|||
MessageHandler.emit = modified_emit
|
||||
|
||||
log.info("Registering global plug-ins..")
|
||||
pyblish.register_plugin_path(PUBLISH_PATH)
|
||||
pyblish.register_discovery_filter(filter_pyblish_plugins)
|
||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
pyblish.api.register_discovery_filter(filter_pyblish_plugins)
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
|
||||
project_name = os.environ.get("AVALON_PROJECT")
|
||||
|
|
@ -103,7 +56,7 @@ def install():
|
|||
if project_name:
|
||||
anatomy = Anatomy(project_name)
|
||||
anatomy.set_root_environments()
|
||||
avalon.register_root(anatomy.roots)
|
||||
avalon.api.register_root(anatomy.roots)
|
||||
|
||||
project_settings = get_project_settings(project_name)
|
||||
platform_name = platform.system().lower()
|
||||
|
|
@ -122,17 +75,14 @@ def install():
|
|||
if not path or not os.path.exists(path):
|
||||
continue
|
||||
|
||||
pyblish.register_plugin_path(path)
|
||||
pyblish.api.register_plugin_path(path)
|
||||
register_loader_plugin_path(path)
|
||||
avalon.register_plugin_path(LegacyCreator, path)
|
||||
register_creator_plugin_path(path)
|
||||
register_inventory_action(path)
|
||||
|
||||
# apply monkey patched discover to original one
|
||||
log.info("Patching discovery")
|
||||
|
||||
avalon.discover = patched_discover
|
||||
pipeline.discover = patched_discover
|
||||
|
||||
register_event_callback("taskChanged", _on_task_change)
|
||||
|
||||
|
||||
|
|
@ -140,16 +90,13 @@ def _on_task_change():
|
|||
change_timer_to_current_context()
|
||||
|
||||
|
||||
@import_wrapper
|
||||
def uninstall():
|
||||
"""Uninstall Pype from Avalon."""
|
||||
import pyblish.api
|
||||
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)
|
||||
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
|
||||
pyblish.api.deregister_discovery_filter(filter_pyblish_plugins)
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
log.info("Global plug-ins unregistred")
|
||||
|
||||
# restore original discover
|
||||
avalon.discover = _original_discover
|
||||
|
|
|
|||
|
|
@ -5,15 +5,15 @@ from Qt import QtWidgets
|
|||
from bson.objectid import ObjectId
|
||||
|
||||
import pyblish.api
|
||||
import avalon.api
|
||||
from avalon import io
|
||||
|
||||
from openpype import lib
|
||||
from openpype.api import Logger
|
||||
from openpype.pipeline import (
|
||||
LegacyCreator,
|
||||
register_loader_plugin_path,
|
||||
register_creator_plugin_path,
|
||||
deregister_loader_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
import openpype.hosts.aftereffects
|
||||
|
|
@ -73,7 +73,7 @@ def install():
|
|||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
log.info(PUBLISH_PATH)
|
||||
|
||||
pyblish.api.register_callback(
|
||||
|
|
@ -86,7 +86,7 @@ def install():
|
|||
def uninstall():
|
||||
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
deregister_creator_plugin_path(CREATE_PATH)
|
||||
|
||||
|
||||
def on_pyblish_instance_toggled(instance, old_value, new_value):
|
||||
|
|
|
|||
|
|
@ -5,14 +5,6 @@ from openpype.pipeline import HOST_WORKFILE_EXTENSIONS
|
|||
from .launch_logic import get_stub
|
||||
|
||||
|
||||
def _active_document():
|
||||
document_name = get_stub().get_active_document_name()
|
||||
if not document_name:
|
||||
return None
|
||||
|
||||
return document_name
|
||||
|
||||
|
||||
def file_extensions():
|
||||
return HOST_WORKFILE_EXTENSIONS["aftereffects"]
|
||||
|
||||
|
|
@ -39,7 +31,8 @@ def current_file():
|
|||
full_name = get_stub().get_active_document_full_name()
|
||||
if full_name and full_name != "null":
|
||||
return os.path.normpath(full_name).replace("\\", "/")
|
||||
except Exception:
|
||||
except ValueError:
|
||||
print("Nothing opened")
|
||||
pass
|
||||
|
||||
return None
|
||||
|
|
@ -47,3 +40,15 @@ def current_file():
|
|||
|
||||
def work_root(session):
|
||||
return os.path.normpath(session["AVALON_WORKDIR"]).replace("\\", "/")
|
||||
|
||||
|
||||
def _active_document():
|
||||
# TODO merge with current_file - even in extension
|
||||
document_name = None
|
||||
try:
|
||||
document_name = get_stub().get_active_document_name()
|
||||
except ValueError:
|
||||
print("Nothing opened")
|
||||
pass
|
||||
|
||||
return document_name
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
from openpype.pipeline import create
|
||||
from openpype.pipeline import CreatorError
|
||||
from openpype.pipeline import (
|
||||
CreatorError,
|
||||
LegacyCreator
|
||||
)
|
||||
from openpype.hosts.aftereffects.api import (
|
||||
get_stub,
|
||||
list_instances
|
||||
)
|
||||
|
||||
|
||||
class CreateRender(create.LegacyCreator):
|
||||
class CreateRender(LegacyCreator):
|
||||
"""Render folder for publish.
|
||||
|
||||
Creates subsets in format 'familyTaskSubsetname',
|
||||
|
|
|
|||
|
|
@ -38,7 +38,13 @@ class CollectWorkfile(pyblish.api.ContextPlugin):
|
|||
|
||||
# workfile instance
|
||||
family = "workfile"
|
||||
subset = family + task.capitalize()
|
||||
subset = get_subset_name(
|
||||
family,
|
||||
"",
|
||||
task,
|
||||
context.data["assetEntity"]["_id"],
|
||||
host_name="photoshop"
|
||||
)
|
||||
# Create instance
|
||||
instance = context.create_instance(subset)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,10 @@ import avalon.api
|
|||
from avalon import io, schema
|
||||
|
||||
from openpype.pipeline import (
|
||||
LegacyCreator,
|
||||
register_loader_plugin_path,
|
||||
register_creator_plugin_path,
|
||||
deregister_loader_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
from openpype.api import Logger
|
||||
|
|
@ -54,7 +55,7 @@ def install():
|
|||
pyblish.api.register_plugin_path(str(PUBLISH_PATH))
|
||||
|
||||
register_loader_plugin_path(str(LOAD_PATH))
|
||||
avalon.api.register_plugin_path(LegacyCreator, str(CREATE_PATH))
|
||||
register_creator_plugin_path(str(CREATE_PATH))
|
||||
|
||||
lib.append_user_scripts()
|
||||
|
||||
|
|
@ -76,7 +77,7 @@ def uninstall():
|
|||
pyblish.api.deregister_plugin_path(str(PUBLISH_PATH))
|
||||
|
||||
deregister_loader_plugin_path(str(LOAD_PATH))
|
||||
avalon.api.deregister_plugin_path(LegacyCreator, str(CREATE_PATH))
|
||||
deregister_creator_plugin_path(str(CREATE_PATH))
|
||||
|
||||
if not IS_HEADLESS:
|
||||
ops.unregister()
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ log = Logger.get_logger(__name__)
|
|||
|
||||
FRAME_PATTERN = re.compile(r"[\._](\d+)[\.]")
|
||||
|
||||
|
||||
class CTX:
|
||||
# singleton used for passing data between api modules
|
||||
app_framework = None
|
||||
|
|
@ -538,9 +539,17 @@ def get_segment_attributes(segment):
|
|||
|
||||
# head and tail with forward compatibility
|
||||
if segment.head:
|
||||
clip_data["segment_head"] = int(segment.head)
|
||||
# `infinite` can be also returned
|
||||
if isinstance(segment.head, str):
|
||||
clip_data["segment_head"] = 0
|
||||
else:
|
||||
clip_data["segment_head"] = int(segment.head)
|
||||
if segment.tail:
|
||||
clip_data["segment_tail"] = int(segment.tail)
|
||||
# `infinite` can be also returned
|
||||
if isinstance(segment.tail, str):
|
||||
clip_data["segment_tail"] = 0
|
||||
else:
|
||||
clip_data["segment_tail"] = int(segment.tail)
|
||||
|
||||
# add all available shot tokens
|
||||
shot_tokens = _get_shot_tokens_values(segment, [
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ Basic avalon integration
|
|||
"""
|
||||
import os
|
||||
import contextlib
|
||||
from avalon import api as avalon
|
||||
from pyblish import api as pyblish
|
||||
|
||||
from openpype.api import Logger
|
||||
from openpype.pipeline import (
|
||||
LegacyCreator,
|
||||
register_loader_plugin_path,
|
||||
register_creator_plugin_path,
|
||||
deregister_loader_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
from .lib import (
|
||||
|
|
@ -37,7 +37,7 @@ def install():
|
|||
pyblish.register_host("flame")
|
||||
pyblish.register_plugin_path(PUBLISH_PATH)
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
avalon.register_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
log.info("OpenPype Flame plug-ins registred ...")
|
||||
|
||||
# register callback for switching publishable
|
||||
|
|
@ -52,7 +52,7 @@ def uninstall():
|
|||
log.info("Deregistering Flame plug-ins..")
|
||||
pyblish.deregister_plugin_path(PUBLISH_PATH)
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
deregister_creator_plugin_path(CREATE_PATH)
|
||||
|
||||
# register callback for switching publishable
|
||||
pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled)
|
||||
|
|
|
|||
|
|
@ -422,7 +422,13 @@ class WireTapCom(object):
|
|||
color_policy = color_policy or "Legacy"
|
||||
|
||||
# check if the colour policy in custom dir
|
||||
if not os.path.exists(color_policy):
|
||||
if "/" in color_policy:
|
||||
# if unlikelly full path was used make it redundant
|
||||
color_policy = color_policy.replace("/syncolor/policies/", "")
|
||||
# expecting input is `Shared/NameOfPolicy`
|
||||
color_policy = "/syncolor/policies/{}".format(
|
||||
color_policy)
|
||||
else:
|
||||
color_policy = "/syncolor/policies/Autodesk/{}".format(
|
||||
color_policy)
|
||||
|
||||
|
|
|
|||
|
|
@ -34,119 +34,125 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
|
|||
def process(self, context):
|
||||
project = context.data["flameProject"]
|
||||
sequence = context.data["flameSequence"]
|
||||
selected_segments = context.data["flameSelectedSegments"]
|
||||
self.log.debug("__ selected_segments: {}".format(selected_segments))
|
||||
|
||||
self.otio_timeline = context.data["otioTimeline"]
|
||||
self.clips_in_reels = opfapi.get_clips_in_reels(project)
|
||||
self.fps = context.data["fps"]
|
||||
|
||||
# process all sellected
|
||||
with opfapi.maintained_segment_selection(sequence) as segments:
|
||||
for segment in segments:
|
||||
comment_attributes = self._get_comment_attributes(segment)
|
||||
self.log.debug("_ comment_attributes: {}".format(
|
||||
pformat(comment_attributes)))
|
||||
for segment in selected_segments:
|
||||
# get openpype tag data
|
||||
marker_data = opfapi.get_segment_data_marker(segment)
|
||||
self.log.debug("__ marker_data: {}".format(
|
||||
pformat(marker_data)))
|
||||
|
||||
clip_data = opfapi.get_segment_attributes(segment)
|
||||
clip_name = clip_data["segment_name"]
|
||||
self.log.debug("clip_name: {}".format(clip_name))
|
||||
if not marker_data:
|
||||
continue
|
||||
|
||||
# get openpype tag data
|
||||
marker_data = opfapi.get_segment_data_marker(segment)
|
||||
self.log.debug("__ marker_data: {}".format(
|
||||
pformat(marker_data)))
|
||||
if marker_data.get("id") != "pyblish.avalon.instance":
|
||||
continue
|
||||
|
||||
if not marker_data:
|
||||
continue
|
||||
self.log.debug("__ segment.name: {}".format(
|
||||
segment.name
|
||||
))
|
||||
|
||||
if marker_data.get("id") != "pyblish.avalon.instance":
|
||||
continue
|
||||
comment_attributes = self._get_comment_attributes(segment)
|
||||
|
||||
# get file path
|
||||
file_path = clip_data["fpath"]
|
||||
self.log.debug("_ comment_attributes: {}".format(
|
||||
pformat(comment_attributes)))
|
||||
|
||||
# get source clip
|
||||
source_clip = self._get_reel_clip(file_path)
|
||||
clip_data = opfapi.get_segment_attributes(segment)
|
||||
clip_name = clip_data["segment_name"]
|
||||
self.log.debug("clip_name: {}".format(clip_name))
|
||||
|
||||
first_frame = opfapi.get_frame_from_filename(file_path) or 0
|
||||
# get file path
|
||||
file_path = clip_data["fpath"]
|
||||
|
||||
head, tail = self._get_head_tail(clip_data, first_frame)
|
||||
# get source clip
|
||||
source_clip = self._get_reel_clip(file_path)
|
||||
|
||||
# solve handles length
|
||||
marker_data["handleStart"] = min(
|
||||
marker_data["handleStart"], head)
|
||||
marker_data["handleEnd"] = min(
|
||||
marker_data["handleEnd"], tail)
|
||||
first_frame = opfapi.get_frame_from_filename(file_path) or 0
|
||||
|
||||
with_audio = bool(marker_data.pop("audio"))
|
||||
head, tail = self._get_head_tail(clip_data, first_frame)
|
||||
|
||||
# add marker data to instance data
|
||||
inst_data = dict(marker_data.items())
|
||||
# solve handles length
|
||||
marker_data["handleStart"] = min(
|
||||
marker_data["handleStart"], head)
|
||||
marker_data["handleEnd"] = min(
|
||||
marker_data["handleEnd"], tail)
|
||||
|
||||
asset = marker_data["asset"]
|
||||
subset = marker_data["subset"]
|
||||
with_audio = bool(marker_data.pop("audio"))
|
||||
|
||||
# insert family into families
|
||||
family = marker_data["family"]
|
||||
families = [str(f) for f in marker_data["families"]]
|
||||
families.insert(0, str(family))
|
||||
# add marker data to instance data
|
||||
inst_data = dict(marker_data.items())
|
||||
|
||||
# form label
|
||||
label = asset
|
||||
if asset != clip_name:
|
||||
label += " ({})".format(clip_name)
|
||||
label += " {}".format(subset)
|
||||
label += " {}".format("[" + ", ".join(families) + "]")
|
||||
asset = marker_data["asset"]
|
||||
subset = marker_data["subset"]
|
||||
|
||||
inst_data.update({
|
||||
"name": "{}_{}".format(asset, subset),
|
||||
"label": label,
|
||||
"asset": asset,
|
||||
"item": segment,
|
||||
"families": families,
|
||||
"publish": marker_data["publish"],
|
||||
"fps": self.fps,
|
||||
"flameSourceClip": source_clip,
|
||||
"sourceFirstFrame": int(first_frame),
|
||||
"path": file_path
|
||||
})
|
||||
# insert family into families
|
||||
family = marker_data["family"]
|
||||
families = [str(f) for f in marker_data["families"]]
|
||||
families.insert(0, str(family))
|
||||
|
||||
# get otio clip data
|
||||
otio_data = self._get_otio_clip_instance_data(clip_data) or {}
|
||||
self.log.debug("__ otio_data: {}".format(pformat(otio_data)))
|
||||
# form label
|
||||
label = asset
|
||||
if asset != clip_name:
|
||||
label += " ({})".format(clip_name)
|
||||
label += " {} [{}]".format(subset, ", ".join(families))
|
||||
|
||||
# add to instance data
|
||||
inst_data.update(otio_data)
|
||||
self.log.debug("__ inst_data: {}".format(pformat(inst_data)))
|
||||
inst_data.update({
|
||||
"name": "{}_{}".format(asset, subset),
|
||||
"label": label,
|
||||
"asset": asset,
|
||||
"item": segment,
|
||||
"families": families,
|
||||
"publish": marker_data["publish"],
|
||||
"fps": self.fps,
|
||||
"flameSourceClip": source_clip,
|
||||
"sourceFirstFrame": int(first_frame),
|
||||
"path": file_path
|
||||
})
|
||||
|
||||
# add resolution
|
||||
self._get_resolution_to_data(inst_data, context)
|
||||
# get otio clip data
|
||||
otio_data = self._get_otio_clip_instance_data(clip_data) or {}
|
||||
self.log.debug("__ otio_data: {}".format(pformat(otio_data)))
|
||||
|
||||
# add comment attributes if any
|
||||
inst_data.update(comment_attributes)
|
||||
# add to instance data
|
||||
inst_data.update(otio_data)
|
||||
self.log.debug("__ inst_data: {}".format(pformat(inst_data)))
|
||||
|
||||
# create instance
|
||||
instance = context.create_instance(**inst_data)
|
||||
# add resolution
|
||||
self._get_resolution_to_data(inst_data, context)
|
||||
|
||||
# add colorspace data
|
||||
instance.data.update({
|
||||
"versionData": {
|
||||
"colorspace": clip_data["colour_space"],
|
||||
}
|
||||
})
|
||||
# add comment attributes if any
|
||||
inst_data.update(comment_attributes)
|
||||
|
||||
# create shot instance for shot attributes create/update
|
||||
self._create_shot_instance(context, clip_name, **inst_data)
|
||||
# create instance
|
||||
instance = context.create_instance(**inst_data)
|
||||
|
||||
self.log.info("Creating instance: {}".format(instance))
|
||||
self.log.info(
|
||||
"_ instance.data: {}".format(pformat(instance.data)))
|
||||
# add colorspace data
|
||||
instance.data.update({
|
||||
"versionData": {
|
||||
"colorspace": clip_data["colour_space"],
|
||||
}
|
||||
})
|
||||
|
||||
if not with_audio:
|
||||
continue
|
||||
# create shot instance for shot attributes create/update
|
||||
self._create_shot_instance(context, clip_name, **inst_data)
|
||||
|
||||
# add audioReview attribute to plate instance data
|
||||
# if reviewTrack is on
|
||||
if marker_data.get("reviewTrack") is not None:
|
||||
instance.data["reviewAudio"] = True
|
||||
self.log.info("Creating instance: {}".format(instance))
|
||||
self.log.info(
|
||||
"_ instance.data: {}".format(pformat(instance.data)))
|
||||
|
||||
if not with_audio:
|
||||
continue
|
||||
|
||||
# add audioReview attribute to plate instance data
|
||||
# if reviewTrack is on
|
||||
if marker_data.get("reviewTrack") is not None:
|
||||
instance.data["reviewAudio"] = True
|
||||
|
||||
def _get_comment_attributes(self, segment):
|
||||
comment = segment.comment.get_value()
|
||||
|
|
@ -188,7 +194,7 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
|
|||
|
||||
# get pattern defined by type
|
||||
pattern = TXT_PATERN
|
||||
if a_type in ("number" , "float"):
|
||||
if a_type in ("number", "float"):
|
||||
pattern = NUM_PATERN
|
||||
|
||||
res_goup = pattern.findall(value)
|
||||
|
|
|
|||
|
|
@ -31,27 +31,28 @@ class CollecTimelineOTIO(pyblish.api.ContextPlugin):
|
|||
)
|
||||
|
||||
# adding otio timeline to context
|
||||
with opfapi.maintained_segment_selection(sequence):
|
||||
with opfapi.maintained_segment_selection(sequence) as selected_seg:
|
||||
otio_timeline = flame_export.create_otio_timeline(sequence)
|
||||
|
||||
instance_data = {
|
||||
"name": subset_name,
|
||||
"asset": asset_doc["name"],
|
||||
"subset": subset_name,
|
||||
"family": "workfile"
|
||||
}
|
||||
instance_data = {
|
||||
"name": subset_name,
|
||||
"asset": asset_doc["name"],
|
||||
"subset": subset_name,
|
||||
"family": "workfile"
|
||||
}
|
||||
|
||||
# create instance with workfile
|
||||
instance = context.create_instance(**instance_data)
|
||||
self.log.info("Creating instance: {}".format(instance))
|
||||
# create instance with workfile
|
||||
instance = context.create_instance(**instance_data)
|
||||
self.log.info("Creating instance: {}".format(instance))
|
||||
|
||||
# update context with main project attributes
|
||||
context.data.update({
|
||||
"flameProject": project,
|
||||
"flameSequence": sequence,
|
||||
"otioTimeline": otio_timeline,
|
||||
"currentFile": "Flame/{}/{}".format(
|
||||
project.name, sequence.name
|
||||
),
|
||||
"fps": float(str(sequence.frame_rate)[:-4])
|
||||
})
|
||||
# update context with main project attributes
|
||||
context.data.update({
|
||||
"flameProject": project,
|
||||
"flameSequence": sequence,
|
||||
"otioTimeline": otio_timeline,
|
||||
"currentFile": "Flame/{}/{}".format(
|
||||
project.name, sequence.name
|
||||
),
|
||||
"flameSelectedSegments": selected_seg,
|
||||
"fps": float(str(sequence.frame_rate)[:-4])
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ import logging
|
|||
import contextlib
|
||||
|
||||
import pyblish.api
|
||||
import avalon.api
|
||||
|
||||
from openpype.api import Logger
|
||||
from openpype.pipeline import (
|
||||
LegacyCreator,
|
||||
register_loader_plugin_path,
|
||||
deregister_loader_plugin_path,
|
||||
register_creator_plugin_path,
|
||||
register_inventory_action_path,
|
||||
deregister_loader_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
deregister_inventory_action_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
|
|
@ -70,7 +70,7 @@ def install():
|
|||
log.info("Registering Fusion plug-ins..")
|
||||
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
register_inventory_action_path(INVENTORY_PATH)
|
||||
|
||||
pyblish.api.register_callback(
|
||||
|
|
@ -94,7 +94,7 @@ def uninstall():
|
|||
log.info("Deregistering Fusion plug-ins..")
|
||||
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
deregister_creator_plugin_path(CREATE_PATH)
|
||||
deregister_inventory_action_path(INVENTORY_PATH)
|
||||
|
||||
pyblish.api.deregister_callback(
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import os
|
||||
|
||||
from openpype.pipeline import create
|
||||
from openpype.pipeline import LegacyCreator
|
||||
from openpype.hosts.fusion.api import (
|
||||
get_current_comp,
|
||||
comp_lock_and_undo_chunk
|
||||
)
|
||||
|
||||
|
||||
class CreateOpenEXRSaver(create.LegacyCreator):
|
||||
class CreateOpenEXRSaver(LegacyCreator):
|
||||
|
||||
name = "openexrDefault"
|
||||
label = "Create OpenEXR Saver"
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ from bson.objectid import ObjectId
|
|||
import pyblish.api
|
||||
|
||||
from avalon import io
|
||||
import avalon.api
|
||||
|
||||
from openpype import lib
|
||||
from openpype.lib import register_event_callback
|
||||
from openpype.pipeline import (
|
||||
LegacyCreator,
|
||||
register_loader_plugin_path,
|
||||
register_creator_plugin_path,
|
||||
deregister_loader_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
import openpype.hosts.harmony
|
||||
|
|
@ -108,9 +108,8 @@ def check_inventory():
|
|||
if not lib.any_outdated():
|
||||
return
|
||||
|
||||
host = avalon.api.registered_host()
|
||||
outdated_containers = []
|
||||
for container in host.ls():
|
||||
for container in ls():
|
||||
representation = container['representation']
|
||||
representation_doc = io.find_one(
|
||||
{
|
||||
|
|
@ -186,7 +185,7 @@ def install():
|
|||
pyblish.api.register_host("harmony")
|
||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
log.info(PUBLISH_PATH)
|
||||
|
||||
# Register callbacks.
|
||||
|
|
@ -200,7 +199,7 @@ def install():
|
|||
def uninstall():
|
||||
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
deregister_creator_plugin_path(CREATE_PATH)
|
||||
|
||||
|
||||
def on_pyblish_instance_toggled(instance, old_value, new_value):
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import os
|
|||
import contextlib
|
||||
from collections import OrderedDict
|
||||
|
||||
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_creator_plugin_path,
|
||||
register_loader_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
deregister_loader_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
|
|
@ -50,7 +50,7 @@ def install():
|
|||
pyblish.register_host("hiero")
|
||||
pyblish.register_plugin_path(PUBLISH_PATH)
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
avalon.register_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
|
||||
# register callback for switching publishable
|
||||
pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled)
|
||||
|
|
@ -71,7 +71,7 @@ def uninstall():
|
|||
pyblish.deregister_host("hiero")
|
||||
pyblish.deregister_plugin_path(PUBLISH_PATH)
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
deregister_creator_plugin_path(CREATE_PATH)
|
||||
|
||||
# register callback for switching publishable
|
||||
pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import avalon.api
|
|||
from avalon.lib import find_submodule
|
||||
|
||||
from openpype.pipeline import (
|
||||
LegacyCreator,
|
||||
register_creator_plugin_path,
|
||||
register_loader_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
|
|
@ -54,7 +54,7 @@ def install():
|
|||
|
||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
|
||||
log.info("Installing callbacks ... ")
|
||||
# register_event_callback("init", on_init)
|
||||
|
|
|
|||
|
|
@ -23,8 +23,10 @@ from openpype.pipeline import (
|
|||
LegacyCreator,
|
||||
register_loader_plugin_path,
|
||||
register_inventory_action_path,
|
||||
register_creator_plugin_path,
|
||||
deregister_loader_plugin_path,
|
||||
deregister_inventory_action_path,
|
||||
deregister_creator_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
from openpype.hosts.maya.lib import copy_workspace_mel
|
||||
|
|
@ -60,7 +62,7 @@ def install():
|
|||
pyblish.api.register_host("maya")
|
||||
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
register_inventory_action_path(INVENTORY_PATH)
|
||||
log.info(PUBLISH_PATH)
|
||||
|
||||
|
|
@ -189,7 +191,7 @@ def uninstall():
|
|||
pyblish.api.deregister_host("maya")
|
||||
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
deregister_creator_plugin_path(CREATE_PATH)
|
||||
deregister_inventory_action_path(INVENTORY_PATH)
|
||||
|
||||
menu.uninstall()
|
||||
|
|
|
|||
|
|
@ -22,4 +22,6 @@ class CreateLook(plugin.Creator):
|
|||
self.data["maketx"] = self.make_tx
|
||||
|
||||
# Enable users to force a copy.
|
||||
# - on Windows is "forceCopy" always changed to `True` because of
|
||||
# windows implementation of hardlinks
|
||||
self.data["forceCopy"] = False
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import os
|
|||
import sys
|
||||
import json
|
||||
import tempfile
|
||||
import platform
|
||||
import contextlib
|
||||
import subprocess
|
||||
from collections import OrderedDict
|
||||
|
|
@ -334,7 +335,14 @@ class ExtractLook(openpype.api.Extractor):
|
|||
transfers = []
|
||||
hardlinks = []
|
||||
hashes = {}
|
||||
force_copy = instance.data.get("forceCopy", False)
|
||||
# Temporary fix to NOT create hardlinks on windows machines
|
||||
if platform.system().lower() == "windows":
|
||||
self.log.info(
|
||||
"Forcing copy instead of hardlink due to issues on Windows..."
|
||||
)
|
||||
force_copy = True
|
||||
else:
|
||||
force_copy = instance.data.get("forceCopy", False)
|
||||
|
||||
for filepath in files_metadata:
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ from openpype.tools.utils import host_tools
|
|||
from openpype.lib.path_tools import HostDirmap
|
||||
from openpype.settings import get_project_settings
|
||||
from openpype.modules import ModulesManager
|
||||
from openpype.pipeline import discover_legacy_creator_plugins
|
||||
|
||||
from .workio import (
|
||||
save_file,
|
||||
|
|
@ -1902,7 +1903,7 @@ def recreate_instance(origin_node, avalon_data=None):
|
|||
# create new node
|
||||
# get appropriate plugin class
|
||||
creator_plugin = None
|
||||
for Creator in api.discover(api.Creator):
|
||||
for Creator in discover_legacy_creator_plugins():
|
||||
if Creator.__name__ == data["creator"]:
|
||||
creator_plugin = Creator
|
||||
break
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ from collections import OrderedDict
|
|||
import nuke
|
||||
|
||||
import pyblish.api
|
||||
import avalon.api
|
||||
|
||||
import openpype
|
||||
from openpype.api import (
|
||||
|
|
@ -15,10 +14,11 @@ from openpype.api import (
|
|||
)
|
||||
from openpype.lib import register_event_callback
|
||||
from openpype.pipeline import (
|
||||
LegacyCreator,
|
||||
register_loader_plugin_path,
|
||||
register_creator_plugin_path,
|
||||
register_inventory_action_path,
|
||||
deregister_loader_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
deregister_inventory_action_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
|
|
@ -106,7 +106,7 @@ def install():
|
|||
log.info("Registering Nuke plug-ins..")
|
||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
register_inventory_action_path(INVENTORY_PATH)
|
||||
|
||||
# Register Avalon event for workfiles loading.
|
||||
|
|
@ -132,7 +132,7 @@ def uninstall():
|
|||
pyblish.deregister_host("nuke")
|
||||
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
deregister_creator_plugin_path(CREATE_PATH)
|
||||
deregister_inventory_action_path(INVENTORY_PATH)
|
||||
|
||||
pyblish.api.deregister_callback(
|
||||
|
|
|
|||
|
|
@ -450,6 +450,7 @@ class ExporterReviewMov(ExporterReview):
|
|||
|
||||
def generate_mov(self, farm=False, **kwargs):
|
||||
self.publish_on_farm = farm
|
||||
read_raw = kwargs["read_raw"]
|
||||
reformat_node_add = kwargs["reformat_node_add"]
|
||||
reformat_node_config = kwargs["reformat_node_config"]
|
||||
bake_viewer_process = kwargs["bake_viewer_process"]
|
||||
|
|
@ -484,6 +485,9 @@ class ExporterReviewMov(ExporterReview):
|
|||
r_node["origlast"].setValue(self.last_frame)
|
||||
r_node["colorspace"].setValue(self.write_colorspace)
|
||||
|
||||
if read_raw:
|
||||
r_node["raw"].setValue(1)
|
||||
|
||||
# connect
|
||||
self._temp_nodes[subset].append(r_node)
|
||||
self.previous_node = r_node
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import os
|
||||
import toml
|
||||
|
||||
import nuke
|
||||
|
||||
from avalon import api
|
||||
import pyblish.api
|
||||
import openpype.api
|
||||
from openpype.pipeline import discover_creator_plugins
|
||||
from openpype.hosts.nuke.api.lib import get_avalon_knob_data
|
||||
|
||||
|
||||
|
|
@ -79,7 +78,7 @@ class ValidateWriteLegacy(pyblish.api.InstancePlugin):
|
|||
|
||||
# get appropriate plugin class
|
||||
creator_plugin = None
|
||||
for Creator in api.discover(api.Creator):
|
||||
for Creator in discover_creator_plugins():
|
||||
if Creator.__name__ != Create_name:
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ from openpype.pipeline import (
|
|||
BaseCreator,
|
||||
LegacyCreator,
|
||||
register_loader_plugin_path,
|
||||
register_creator_plugin_path,
|
||||
deregister_loader_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
import openpype.hosts.photoshop
|
||||
|
|
@ -90,7 +92,7 @@ def install():
|
|||
def uninstall():
|
||||
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
deregister_creator_plugin_path(CREATE_PATH)
|
||||
|
||||
|
||||
def ls():
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class CollectReview(pyblish.api.ContextPlugin):
|
|||
"""
|
||||
|
||||
label = "Collect Review"
|
||||
order = pyblish.api.CollectorOrder
|
||||
label = "Review"
|
||||
hosts = ["photoshop"]
|
||||
order = pyblish.api.CollectorOrder + 0.1
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,17 @@ Basic avalon integration
|
|||
import os
|
||||
import contextlib
|
||||
from collections import OrderedDict
|
||||
from avalon import api as avalon
|
||||
from avalon import schema
|
||||
|
||||
from pyblish import api as pyblish
|
||||
|
||||
from avalon import schema
|
||||
|
||||
from openpype.api import Logger
|
||||
from openpype.pipeline import (
|
||||
LegacyCreator,
|
||||
register_loader_plugin_path,
|
||||
register_creator_plugin_path,
|
||||
deregister_loader_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
from . import lib
|
||||
|
|
@ -46,7 +49,7 @@ def install():
|
|||
log.info("Registering DaVinci Resovle plug-ins..")
|
||||
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
avalon.register_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
|
||||
# register callback for switching publishable
|
||||
pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled)
|
||||
|
|
@ -70,7 +73,7 @@ def uninstall():
|
|||
log.info("Deregistering DaVinci Resovle plug-ins..")
|
||||
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
deregister_creator_plugin_path(CREATE_PATH)
|
||||
|
||||
# register callback for switching publishable
|
||||
pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class MyAutoCreator(AutoCreator):
|
|||
def update_instances(self, update_list):
|
||||
pipeline.update_instances(update_list)
|
||||
|
||||
def create(self, options=None):
|
||||
def create(self):
|
||||
existing_instance = None
|
||||
for instance in self.create_context.instances:
|
||||
if instance.family == self.family:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from openpype.hosts.traypublisher.api import pipeline
|
||||
from openpype.lib import FileDef
|
||||
from openpype.pipeline import (
|
||||
Creator,
|
||||
CreatedInstance,
|
||||
lib
|
||||
CreatedInstance
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ class WorkfileCreator(Creator):
|
|||
|
||||
def get_instance_attr_defs(self):
|
||||
output = [
|
||||
lib.FileDef(
|
||||
FileDef(
|
||||
"filepath",
|
||||
folders=False,
|
||||
extensions=self.extensions,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from openpype.pipeline import PublishValidationError
|
|||
class ValidateWorkfilePath(pyblish.api.InstancePlugin):
|
||||
"""Validate existence of workfile instance existence."""
|
||||
|
||||
label = "Collect Workfile"
|
||||
label = "Validate Workfile"
|
||||
order = pyblish.api.ValidatorOrder - 0.49
|
||||
families = ["workfile"]
|
||||
hosts = ["traypublisher"]
|
||||
|
|
|
|||
|
|
@ -15,9 +15,10 @@ from openpype.hosts import tvpaint
|
|||
from openpype.api import get_current_project_settings
|
||||
from openpype.lib import register_event_callback
|
||||
from openpype.pipeline import (
|
||||
LegacyCreator,
|
||||
register_loader_plugin_path,
|
||||
register_creator_plugin_path,
|
||||
deregister_loader_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -82,7 +83,7 @@ def install():
|
|||
pyblish.api.register_host("tvpaint")
|
||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
|
||||
registered_callbacks = (
|
||||
pyblish.api.registered_callbacks().get("instanceToggled") or []
|
||||
|
|
@ -104,7 +105,7 @@ def uninstall():
|
|||
pyblish.api.deregister_host("tvpaint")
|
||||
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
|
||||
deregister_loader_plugin_path(LOAD_PATH)
|
||||
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
|
||||
deregister_creator_plugin_path(CREATE_PATH)
|
||||
|
||||
|
||||
def containerise(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
"""Plugin converting png files from ExtractSequence into exrs.
|
||||
|
||||
Requires:
|
||||
ExtractSequence - source of PNG
|
||||
ExtractReview - review was already created so we can convert to any exr
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
|
||||
import pyblish.api
|
||||
from openpype.lib import (
|
||||
get_oiio_tools_path,
|
||||
run_subprocess,
|
||||
)
|
||||
from openpype.pipeline import KnownPublishError
|
||||
|
||||
|
||||
class ExtractConvertToEXR(pyblish.api.InstancePlugin):
|
||||
# Offset to get after ExtractSequence plugin.
|
||||
order = pyblish.api.ExtractorOrder + 0.1
|
||||
label = "Extract Sequence EXR"
|
||||
hosts = ["tvpaint"]
|
||||
families = ["render"]
|
||||
|
||||
enabled = False
|
||||
|
||||
# Replace source PNG files or just add
|
||||
replace_pngs = True
|
||||
# EXR compression
|
||||
exr_compression = "ZIP"
|
||||
|
||||
def process(self, instance):
|
||||
repres = instance.data.get("representations")
|
||||
if not repres:
|
||||
return
|
||||
|
||||
oiio_path = get_oiio_tools_path()
|
||||
# Raise an exception when oiiotool is not available
|
||||
# - this can currently happen on MacOS machines
|
||||
if not os.path.exists(oiio_path):
|
||||
KnownPublishError(
|
||||
"OpenImageIO tool is not available on this machine."
|
||||
)
|
||||
|
||||
new_repres = []
|
||||
for repre in repres:
|
||||
if repre["name"] != "png":
|
||||
continue
|
||||
|
||||
self.log.info(
|
||||
"Processing representation: {}".format(
|
||||
json.dumps(repre, sort_keys=True, indent=4)
|
||||
)
|
||||
)
|
||||
|
||||
src_filepaths = set()
|
||||
new_filenames = []
|
||||
for src_filename in repre["files"]:
|
||||
dst_filename = os.path.splitext(src_filename)[0] + ".exr"
|
||||
new_filenames.append(dst_filename)
|
||||
|
||||
src_filepath = os.path.join(repre["stagingDir"], src_filename)
|
||||
dst_filepath = os.path.join(repre["stagingDir"], dst_filename)
|
||||
|
||||
src_filepaths.add(src_filepath)
|
||||
|
||||
args = [
|
||||
oiio_path, src_filepath,
|
||||
"--compression", self.exr_compression,
|
||||
# TODO how to define color conversion?
|
||||
"--colorconvert", "sRGB", "linear",
|
||||
"-o", dst_filepath
|
||||
]
|
||||
run_subprocess(args)
|
||||
|
||||
new_repres.append(
|
||||
{
|
||||
"name": "exr",
|
||||
"ext": "exr",
|
||||
"files": new_filenames,
|
||||
"stagingDir": repre["stagingDir"],
|
||||
"tags": list(repre["tags"])
|
||||
}
|
||||
)
|
||||
|
||||
if self.replace_pngs:
|
||||
instance.data["representations"].remove(repre)
|
||||
|
||||
for filepath in src_filepaths:
|
||||
instance.context.data["cleanupFullPaths"].append(filepath)
|
||||
|
||||
instance.data["representations"].extend(new_repres)
|
||||
self.log.info(
|
||||
"Representations: {}".format(
|
||||
json.dumps(
|
||||
instance.data["representations"], sort_keys=True, indent=4
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -12,7 +12,6 @@ from openpype.hosts.tvpaint.lib import (
|
|||
fill_reference_frames,
|
||||
composite_rendered_layers,
|
||||
rename_filepaths_by_frame_start,
|
||||
composite_images
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ import pyblish.api
|
|||
from avalon import api
|
||||
|
||||
from openpype.pipeline import (
|
||||
LegacyCreator,
|
||||
register_loader_plugin_path,
|
||||
register_creator_plugin_path,
|
||||
deregister_loader_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
from openpype.tools.utils import host_tools
|
||||
|
|
@ -49,7 +50,7 @@ def install():
|
|||
logger.info("installing OpenPype for Unreal")
|
||||
pyblish.api.register_plugin_path(str(PUBLISH_PATH))
|
||||
register_loader_plugin_path(str(LOAD_PATH))
|
||||
api.register_plugin_path(LegacyCreator, str(CREATE_PATH))
|
||||
register_creator_plugin_path(str(CREATE_PATH))
|
||||
_register_callbacks()
|
||||
_register_events()
|
||||
|
||||
|
|
@ -58,7 +59,7 @@ def uninstall():
|
|||
"""Uninstall Unreal configuration for Avalon."""
|
||||
pyblish.api.deregister_plugin_path(str(PUBLISH_PATH))
|
||||
deregister_loader_plugin_path(str(LOAD_PATH))
|
||||
api.deregister_plugin_path(LegacyCreator, str(CREATE_PATH))
|
||||
deregister_creator_plugin_path(str(CREATE_PATH))
|
||||
|
||||
|
||||
def _register_callbacks():
|
||||
|
|
|
|||
|
|
@ -211,6 +211,7 @@ class ApplicationGroup:
|
|||
data (dict): Group defying data loaded from settings.
|
||||
manager (ApplicationManager): Manager that created the group.
|
||||
"""
|
||||
|
||||
def __init__(self, name, data, manager):
|
||||
self.name = name
|
||||
self.manager = manager
|
||||
|
|
@ -374,6 +375,7 @@ class ApplicationManager:
|
|||
will always use these values. Gives ability to create manager
|
||||
using different settings.
|
||||
"""
|
||||
|
||||
def __init__(self, system_settings=None):
|
||||
self.log = PypeLogger.get_logger(self.__class__.__name__)
|
||||
|
||||
|
|
@ -530,13 +532,13 @@ class EnvironmentToolGroup:
|
|||
variants = data.get("variants") or {}
|
||||
label_by_key = variants.pop(M_DYNAMIC_KEY_LABEL, {})
|
||||
variants_by_name = {}
|
||||
for variant_name, variant_env in variants.items():
|
||||
for variant_name, variant_data in variants.items():
|
||||
if variant_name in METADATA_KEYS:
|
||||
continue
|
||||
|
||||
variant_label = label_by_key.get(variant_name) or variant_name
|
||||
tool = EnvironmentTool(
|
||||
variant_name, variant_label, variant_env, self
|
||||
variant_name, variant_label, variant_data, self
|
||||
)
|
||||
variants_by_name[variant_name] = tool
|
||||
self.variants = variants_by_name
|
||||
|
|
@ -560,15 +562,30 @@ class EnvironmentTool:
|
|||
|
||||
Args:
|
||||
name (str): Name of the tool.
|
||||
environment (dict): Variant environments.
|
||||
variant_data (dict): Variant data with environments and
|
||||
host and app variant filters.
|
||||
group (str): Name of group which wraps tool.
|
||||
"""
|
||||
|
||||
def __init__(self, name, label, environment, group):
|
||||
def __init__(self, name, label, variant_data, group):
|
||||
# Backwards compatibility 3.9.1 - 3.9.2
|
||||
# - 'variant_data' contained only environments but contain also host
|
||||
# and application variant filters
|
||||
host_names = variant_data.get("host_names", [])
|
||||
app_variants = variant_data.get("app_variants", [])
|
||||
|
||||
if "environment" in variant_data:
|
||||
environment = variant_data["environment"]
|
||||
else:
|
||||
environment = variant_data
|
||||
|
||||
self.host_names = host_names
|
||||
self.app_variants = app_variants
|
||||
self.name = name
|
||||
self.variant_label = label
|
||||
self.label = " ".join((group.label, label))
|
||||
self.group = group
|
||||
|
||||
self._environment = environment
|
||||
self.full_name = "/".join((group.name, name))
|
||||
|
||||
|
|
@ -579,6 +596,19 @@ class EnvironmentTool:
|
|||
def environment(self):
|
||||
return copy.deepcopy(self._environment)
|
||||
|
||||
def is_valid_for_app(self, app):
|
||||
"""Is tool valid for application.
|
||||
|
||||
Args:
|
||||
app (Application): Application for which are prepared environments.
|
||||
"""
|
||||
if self.app_variants and app.full_name not in self.app_variants:
|
||||
return False
|
||||
|
||||
if self.host_names and app.host_name not in self.host_names:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class ApplicationExecutable:
|
||||
"""Representation of executable loaded from settings."""
|
||||
|
|
@ -1384,7 +1414,7 @@ def prepare_app_environments(data, env_group=None, implementation_envs=True):
|
|||
# Make sure each tool group can be added only once
|
||||
for key in asset_doc["data"].get("tools_env") or []:
|
||||
tool = app.manager.tools.get(key)
|
||||
if not tool:
|
||||
if not tool or not tool.is_valid_for_app(app):
|
||||
continue
|
||||
groups_by_name[tool.group.name] = tool.group
|
||||
tool_by_group_name[tool.group.name][tool.name] = tool
|
||||
|
|
|
|||
|
|
@ -1604,13 +1604,13 @@ def get_creator_by_name(creator_name, case_sensitive=False):
|
|||
Returns:
|
||||
Creator: Return first matching plugin or `None`.
|
||||
"""
|
||||
from openpype.pipeline import LegacyCreator
|
||||
from openpype.pipeline import discover_legacy_creator_plugins
|
||||
|
||||
# Lower input creator name if is not case sensitive
|
||||
if not case_sensitive:
|
||||
creator_name = creator_name.lower()
|
||||
|
||||
for creator_plugin in avalon.api.discover(LegacyCreator):
|
||||
for creator_plugin in discover_legacy_creator_plugins():
|
||||
_creator_name = creator_plugin.__name__
|
||||
|
||||
# Lower creator plugin name if is not case sensitive
|
||||
|
|
@ -1965,6 +1965,7 @@ def get_last_workfile(
|
|||
data.pop("comment", None)
|
||||
if not data.get("ext"):
|
||||
data["ext"] = extensions[0]
|
||||
data["ext"] = data["ext"].replace('.', '')
|
||||
filename = StringTemplate.format_strict_template(file_template, data)
|
||||
|
||||
if full_path:
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ import importlib
|
|||
import inspect
|
||||
import logging
|
||||
|
||||
import six
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
|
||||
def import_filepath(filepath, module_name=None):
|
||||
|
|
@ -28,7 +29,7 @@ def import_filepath(filepath, module_name=None):
|
|||
# Prepare module object where content of file will be parsed
|
||||
module = types.ModuleType(module_name)
|
||||
|
||||
if PY3:
|
||||
if six.PY3:
|
||||
# Use loader so module has full specs
|
||||
module_loader = importlib.machinery.SourceFileLoader(
|
||||
module_name, filepath
|
||||
|
|
@ -38,7 +39,7 @@ def import_filepath(filepath, module_name=None):
|
|||
# Execute module code and store content to module
|
||||
with open(filepath) as _stream:
|
||||
# Execute content and store it to module object
|
||||
exec(_stream.read(), module.__dict__)
|
||||
six.exec_(_stream.read(), module.__dict__)
|
||||
|
||||
module.__file__ = filepath
|
||||
return module
|
||||
|
|
@ -129,20 +130,12 @@ def classes_from_module(superclass, module):
|
|||
for name in dir(module):
|
||||
# It could be anything at this point
|
||||
obj = getattr(module, name)
|
||||
if not inspect.isclass(obj):
|
||||
if not inspect.isclass(obj) or obj is superclass:
|
||||
continue
|
||||
|
||||
# These are subclassed from nothing, not even `object`
|
||||
if not len(obj.__bases__) > 0:
|
||||
continue
|
||||
if issubclass(obj, superclass):
|
||||
classes.append(obj)
|
||||
|
||||
# Use string comparison rather than `issubclass`
|
||||
# in order to support reloading of this module.
|
||||
bases = recursive_bases_from_class(obj)
|
||||
if not any(base.__name__ == superclass.__name__ for base in bases):
|
||||
continue
|
||||
|
||||
classes.append(obj)
|
||||
return classes
|
||||
|
||||
|
||||
|
|
@ -228,7 +221,7 @@ def import_module_from_dirpath(dirpath, folder_name, dst_module_name=None):
|
|||
dst_module_name(str): Parent module name under which can be loaded
|
||||
module added.
|
||||
"""
|
||||
if PY3:
|
||||
if six.PY3:
|
||||
module = _import_module_from_dirpath_py3(
|
||||
dirpath, folder_name, dst_module_name
|
||||
)
|
||||
|
|
|
|||
|
|
@ -478,8 +478,14 @@ def convert_for_ffmpeg(
|
|||
oiio_cmd.extend(["--eraseattrib", attr_name])
|
||||
|
||||
# Add last argument - path to output
|
||||
base_file_name = os.path.basename(first_input_path)
|
||||
output_path = os.path.join(output_dir, base_file_name)
|
||||
if is_sequence:
|
||||
ext = os.path.splitext(first_input_path)[1]
|
||||
base_filename = "tmp.%{:0>2}d{}".format(
|
||||
len(str(input_frame_end)), ext
|
||||
)
|
||||
else:
|
||||
base_filename = os.path.basename(first_input_path)
|
||||
output_path = os.path.join(output_dir, base_filename)
|
||||
oiio_cmd.extend([
|
||||
"-o", output_path
|
||||
])
|
||||
|
|
|
|||
|
|
@ -286,21 +286,6 @@ def from_dict_to_set(data, is_project):
|
|||
return result
|
||||
|
||||
|
||||
def get_avalon_project_template(project_name):
|
||||
"""Get avalon template
|
||||
Args:
|
||||
project_name: (string)
|
||||
Returns:
|
||||
dictionary with templates
|
||||
"""
|
||||
templates = Anatomy(project_name).templates
|
||||
return {
|
||||
"workfile": templates["avalon"]["workfile"],
|
||||
"work": templates["avalon"]["work"],
|
||||
"publish": templates["avalon"]["publish"]
|
||||
}
|
||||
|
||||
|
||||
def get_project_apps(in_app_list):
|
||||
""" Application definitions for app name.
|
||||
|
||||
|
|
|
|||
|
|
@ -27,11 +27,11 @@ class LogsWindow(QtWidgets.QWidget):
|
|||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
self._frist_show = True
|
||||
self._first_show = True
|
||||
|
||||
def showEvent(self, event):
|
||||
super(LogsWindow, self).showEvent(event)
|
||||
|
||||
if self._frist_show:
|
||||
self._frist_show = False
|
||||
if self._first_show:
|
||||
self._first_show = False
|
||||
self.logs_widget.refresh()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ from .create import (
|
|||
|
||||
LegacyCreator,
|
||||
legacy_create,
|
||||
|
||||
discover_creator_plugins,
|
||||
discover_legacy_creator_plugins,
|
||||
register_creator_plugin,
|
||||
deregister_creator_plugin,
|
||||
register_creator_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
)
|
||||
|
||||
from .load import (
|
||||
|
|
@ -80,6 +87,13 @@ __all__ = (
|
|||
"LegacyCreator",
|
||||
"legacy_create",
|
||||
|
||||
"discover_creator_plugins",
|
||||
"discover_legacy_creator_plugins",
|
||||
"register_creator_plugin",
|
||||
"deregister_creator_plugin",
|
||||
"register_creator_plugin_path",
|
||||
"deregister_creator_plugin_path",
|
||||
|
||||
# --- Load ---
|
||||
"HeroVersionType",
|
||||
"IncompatibleLoaderError",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
import logging
|
||||
from openpype.pipeline.plugin_discover import (
|
||||
discover,
|
||||
register_plugin,
|
||||
register_plugin_path,
|
||||
deregister_plugin,
|
||||
deregister_plugin_path
|
||||
)
|
||||
|
||||
|
||||
class LauncherAction(object):
|
||||
|
|
@ -90,28 +97,20 @@ class InventoryAction(object):
|
|||
|
||||
# Launcher action
|
||||
def discover_launcher_actions():
|
||||
import avalon.api
|
||||
|
||||
return avalon.api.discover(LauncherAction)
|
||||
return discover(LauncherAction)
|
||||
|
||||
|
||||
def register_launcher_action(plugin):
|
||||
import avalon.api
|
||||
|
||||
return avalon.api.register_plugin(LauncherAction, plugin)
|
||||
return register_plugin(LauncherAction, plugin)
|
||||
|
||||
|
||||
def register_launcher_action_path(path):
|
||||
import avalon.api
|
||||
|
||||
return avalon.api.register_plugin_path(LauncherAction, path)
|
||||
return register_plugin_path(LauncherAction, path)
|
||||
|
||||
|
||||
# Inventory action
|
||||
def discover_inventory_actions():
|
||||
import avalon.api
|
||||
|
||||
actions = avalon.api.discover(InventoryAction)
|
||||
actions = discover(InventoryAction)
|
||||
filtered_actions = []
|
||||
for action in actions:
|
||||
if action is not InventoryAction:
|
||||
|
|
@ -121,24 +120,16 @@ def discover_inventory_actions():
|
|||
|
||||
|
||||
def register_inventory_action(plugin):
|
||||
import avalon.api
|
||||
|
||||
return avalon.api.register_plugin(InventoryAction, plugin)
|
||||
return register_plugin(InventoryAction, plugin)
|
||||
|
||||
|
||||
def deregister_inventory_action(plugin):
|
||||
import avalon.api
|
||||
|
||||
avalon.api.deregister_plugin(InventoryAction, plugin)
|
||||
deregister_plugin(InventoryAction, plugin)
|
||||
|
||||
|
||||
def register_inventory_action_path(path):
|
||||
import avalon.api
|
||||
|
||||
return avalon.api.register_plugin_path(InventoryAction, path)
|
||||
return register_plugin_path(InventoryAction, path)
|
||||
|
||||
|
||||
def deregister_inventory_action_path(path):
|
||||
import avalon.api
|
||||
|
||||
return avalon.api.deregister_plugin_path(InventoryAction, path)
|
||||
return deregister_plugin_path(InventoryAction, path)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,14 @@ from .creator_plugins import (
|
|||
|
||||
BaseCreator,
|
||||
Creator,
|
||||
AutoCreator
|
||||
AutoCreator,
|
||||
|
||||
discover_creator_plugins,
|
||||
discover_legacy_creator_plugins,
|
||||
register_creator_plugin,
|
||||
deregister_creator_plugin,
|
||||
register_creator_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
)
|
||||
|
||||
from .context import (
|
||||
|
|
@ -29,6 +36,13 @@ __all__ = (
|
|||
"Creator",
|
||||
"AutoCreator",
|
||||
|
||||
"discover_creator_plugins",
|
||||
"discover_legacy_creator_plugins",
|
||||
"register_creator_plugin",
|
||||
"deregister_creator_plugin",
|
||||
"register_creator_plugin_path",
|
||||
"deregister_creator_plugin_path",
|
||||
|
||||
"CreatedInstance",
|
||||
"CreateContext",
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ from contextlib import contextmanager
|
|||
from .creator_plugins import (
|
||||
BaseCreator,
|
||||
Creator,
|
||||
AutoCreator
|
||||
AutoCreator,
|
||||
discover_creator_plugins,
|
||||
)
|
||||
|
||||
from openpype.api import (
|
||||
|
|
@ -845,7 +846,7 @@ class CreateContext:
|
|||
creators = {}
|
||||
autocreators = {}
|
||||
manual_creators = {}
|
||||
for creator_class in avalon.api.discover(BaseCreator):
|
||||
for creator_class in discover_creator_plugins():
|
||||
if inspect.isabstract(creator_class):
|
||||
self.log.info(
|
||||
"Skipping abstract Creator {}".format(str(creator_class))
|
||||
|
|
|
|||
|
|
@ -8,7 +8,19 @@ from abc import (
|
|||
)
|
||||
import six
|
||||
|
||||
from openpype.lib import get_subset_name_with_asset_doc
|
||||
from openpype.lib import (
|
||||
get_subset_name_with_asset_doc,
|
||||
set_plugin_attributes_from_settings,
|
||||
)
|
||||
from openpype.pipeline.plugin_discover import (
|
||||
discover,
|
||||
register_plugin,
|
||||
register_plugin_path,
|
||||
deregister_plugin,
|
||||
deregister_plugin_path
|
||||
)
|
||||
|
||||
from .legacy_create import LegacyCreator
|
||||
|
||||
|
||||
class CreatorError(Exception):
|
||||
|
|
@ -329,6 +341,43 @@ class AutoCreator(BaseCreator):
|
|||
|
||||
Can be used e.g. for `workfile`.
|
||||
"""
|
||||
|
||||
def remove_instances(self, instances):
|
||||
"""Skip removement."""
|
||||
pass
|
||||
|
||||
|
||||
def discover_creator_plugins():
|
||||
return discover(BaseCreator)
|
||||
|
||||
|
||||
def discover_legacy_creator_plugins():
|
||||
plugins = discover(LegacyCreator)
|
||||
set_plugin_attributes_from_settings(plugins, LegacyCreator)
|
||||
return plugins
|
||||
|
||||
|
||||
def register_creator_plugin(plugin):
|
||||
if issubclass(plugin, BaseCreator):
|
||||
register_plugin(BaseCreator, plugin)
|
||||
|
||||
elif issubclass(plugin, LegacyCreator):
|
||||
register_plugin(LegacyCreator, plugin)
|
||||
|
||||
|
||||
def deregister_creator_plugin(plugin):
|
||||
if issubclass(plugin, BaseCreator):
|
||||
deregister_plugin(BaseCreator, plugin)
|
||||
|
||||
elif issubclass(plugin, LegacyCreator):
|
||||
deregister_plugin(LegacyCreator, plugin)
|
||||
|
||||
|
||||
def register_creator_plugin_path(path):
|
||||
register_plugin_path(BaseCreator, path)
|
||||
register_plugin_path(LegacyCreator, path)
|
||||
|
||||
|
||||
def deregister_creator_plugin_path(path):
|
||||
deregister_plugin_path(BaseCreator, path)
|
||||
deregister_plugin_path(LegacyCreator, path)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import logging
|
||||
|
||||
from openpype.lib import set_plugin_attributes_from_settings
|
||||
from openpype.pipeline.plugin_discover import (
|
||||
discover,
|
||||
register_plugin,
|
||||
register_plugin_path,
|
||||
deregister_plugin,
|
||||
deregister_plugin_path
|
||||
)
|
||||
from .utils import get_representation_path_from_context
|
||||
|
||||
|
||||
|
|
@ -102,30 +110,22 @@ class SubsetLoaderPlugin(LoaderPlugin):
|
|||
|
||||
|
||||
def discover_loader_plugins():
|
||||
import avalon.api
|
||||
|
||||
return avalon.api.discover(LoaderPlugin)
|
||||
plugins = discover(LoaderPlugin)
|
||||
set_plugin_attributes_from_settings(plugins, LoaderPlugin)
|
||||
return plugins
|
||||
|
||||
|
||||
def register_loader_plugin(plugin):
|
||||
import avalon.api
|
||||
|
||||
return avalon.api.register_plugin(LoaderPlugin, plugin)
|
||||
|
||||
|
||||
def deregister_loader_plugin_path(path):
|
||||
import avalon.api
|
||||
|
||||
avalon.api.deregister_plugin_path(LoaderPlugin, path)
|
||||
|
||||
|
||||
def register_loader_plugin_path(path):
|
||||
import avalon.api
|
||||
|
||||
return avalon.api.register_plugin_path(LoaderPlugin, path)
|
||||
return register_plugin(LoaderPlugin, plugin)
|
||||
|
||||
|
||||
def deregister_loader_plugin(plugin):
|
||||
import avalon.api
|
||||
deregister_plugin(LoaderPlugin, plugin)
|
||||
|
||||
avalon.api.deregister_plugin(LoaderPlugin, plugin)
|
||||
|
||||
def deregister_loader_plugin_path(path):
|
||||
deregister_plugin_path(LoaderPlugin, path)
|
||||
|
||||
|
||||
def register_loader_plugin_path(path):
|
||||
return register_plugin_path(LoaderPlugin, path)
|
||||
|
|
|
|||
298
openpype/pipeline/plugin_discover.py
Normal file
298
openpype/pipeline/plugin_discover.py
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
import os
|
||||
import inspect
|
||||
import traceback
|
||||
|
||||
from openpype.api import Logger
|
||||
from openpype.lib.python_module_tools import (
|
||||
modules_from_path,
|
||||
classes_from_module,
|
||||
)
|
||||
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
|
||||
class DiscoverResult:
|
||||
"""Result of Plug-ins discovery of a single superclass type.
|
||||
|
||||
Stores discovered, duplicated, ignored and abstract plugins and file paths
|
||||
which crashed on execution of file.
|
||||
"""
|
||||
|
||||
def __init__(self, superclass):
|
||||
self.superclass = superclass
|
||||
self.plugins = []
|
||||
self.crashed_file_paths = {}
|
||||
self.duplicated_plugins = []
|
||||
self.abstract_plugins = []
|
||||
self.ignored_plugins = set()
|
||||
# Store loaded modules to keep them in memory
|
||||
self._modules = set()
|
||||
|
||||
def __iter__(self):
|
||||
for plugin in self.plugins:
|
||||
yield plugin
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.plugins[item]
|
||||
|
||||
def __setitem__(self, item, value):
|
||||
self.plugins[item] = value
|
||||
|
||||
def add_module(self, module):
|
||||
"""Add dynamically loaded python module to keep it in memory."""
|
||||
self._modules.add(module)
|
||||
|
||||
def get_report(self, only_errors=True, exc_info=True, full_report=False):
|
||||
lines = []
|
||||
if not only_errors:
|
||||
# Successfully discovered plugins
|
||||
if self.plugins or full_report:
|
||||
lines.append(
|
||||
"*** Discovered {} plugins".format(len(self.plugins))
|
||||
)
|
||||
for cls in self.plugins:
|
||||
lines.append("- {}".format(cls.__class__.__name__))
|
||||
|
||||
# Plugin that were defined to be ignored
|
||||
if self.ignored_plugins or full_report:
|
||||
lines.append("*** Ignored plugins {}".format(len(
|
||||
self.ignored_plugins
|
||||
)))
|
||||
for cls in self.ignored_plugins:
|
||||
lines.append("- {}".format(cls.__class__.__name__))
|
||||
|
||||
# Abstract classes
|
||||
if self.abstract_plugins or full_report:
|
||||
lines.append("*** Discovered {} abstract plugins".format(len(
|
||||
self.abstract_plugins
|
||||
)))
|
||||
for cls in self.abstract_plugins:
|
||||
lines.append("- {}".format(cls.__class__.__name__))
|
||||
|
||||
# Abstract classes
|
||||
if self.duplicated_plugins or full_report:
|
||||
lines.append("*** There were {} duplicated plugins".format(len(
|
||||
self.duplicated_plugins
|
||||
)))
|
||||
for cls in self.duplicated_plugins:
|
||||
lines.append("- {}".format(cls.__class__.__name__))
|
||||
|
||||
if self.crashed_file_paths or full_report:
|
||||
lines.append("*** Failed to load {} files".format(len(
|
||||
self.crashed_file_paths
|
||||
)))
|
||||
for path, exc_info_args in self.crashed_file_paths.items():
|
||||
lines.append("- {}".format(path))
|
||||
if exc_info:
|
||||
lines.append(10 * "*")
|
||||
lines.extend(traceback.format_exception(*exc_info_args))
|
||||
lines.append(10 * "*")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def log_report(self, only_errors=True, exc_info=True):
|
||||
report = self.get_report(only_errors, exc_info)
|
||||
if report:
|
||||
log.info(report)
|
||||
|
||||
|
||||
class PluginDiscoverContext(object):
|
||||
"""Store and discover registered types nad registered paths to types.
|
||||
|
||||
Keeps in memory all registered types and their paths. Paths are dynamically
|
||||
loaded on discover so different discover calls won't return the same
|
||||
class objects even if were loaded from same file.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._registered_plugins = {}
|
||||
self._registered_plugin_paths = {}
|
||||
self._last_discovered_plugins = {}
|
||||
# Store the last result to memory
|
||||
self._last_discovered_results = {}
|
||||
|
||||
def get_last_discovered_plugins(self, superclass):
|
||||
"""Access last discovered plugin by a subperclass.
|
||||
|
||||
Returns:
|
||||
None: When superclass was not discovered yet.
|
||||
list: Lastly discovered plugins of the superclass.
|
||||
"""
|
||||
|
||||
return self._last_discovered_plugins.get(superclass)
|
||||
|
||||
def discover(
|
||||
self,
|
||||
superclass,
|
||||
allow_duplicates=True,
|
||||
ignore_classes=None,
|
||||
return_report=False
|
||||
):
|
||||
"""Find and return subclasses of `superclass`
|
||||
|
||||
Args:
|
||||
superclass (type): Class which determines discovered subclasses.
|
||||
allow_duplicates (bool): Validate class name duplications.
|
||||
ignore_classes (list): List of classes that will be ignored
|
||||
and not added to result.
|
||||
|
||||
Returns:
|
||||
DiscoverResult: Object holding succesfully discovered plugins,
|
||||
ignored plugins, plugins with missing abstract implementation
|
||||
and duplicated plugin.
|
||||
"""
|
||||
|
||||
if not ignore_classes:
|
||||
ignore_classes = []
|
||||
|
||||
result = DiscoverResult(superclass)
|
||||
plugin_names = set()
|
||||
registered_classes = self._registered_plugins.get(superclass) or []
|
||||
registered_paths = self._registered_plugin_paths.get(superclass) or []
|
||||
for cls in registered_classes:
|
||||
if cls is superclass or cls in ignore_classes:
|
||||
result.ignored_plugins.add(cls)
|
||||
continue
|
||||
|
||||
if inspect.isabstract(cls):
|
||||
result.abstract_plugins.append(cls)
|
||||
continue
|
||||
|
||||
class_name = cls.__name__
|
||||
if class_name in plugin_names:
|
||||
result.duplicated_plugins.append(cls)
|
||||
continue
|
||||
plugin_names.add(class_name)
|
||||
result.plugins.append(cls)
|
||||
|
||||
# Include plug-ins from registered paths
|
||||
for path in registered_paths:
|
||||
modules, crashed = modules_from_path(path)
|
||||
for item in crashed:
|
||||
filepath, exc_info = item
|
||||
result.crashed_file_paths[filepath] = exc_info
|
||||
|
||||
for item in modules:
|
||||
filepath, module = item
|
||||
result.add_module(module)
|
||||
for cls in classes_from_module(superclass, module):
|
||||
if cls is superclass or cls in ignore_classes:
|
||||
result.ignored_plugins.add(cls)
|
||||
continue
|
||||
|
||||
if inspect.isabstract(cls):
|
||||
result.abstract_plugins.append(cls)
|
||||
continue
|
||||
|
||||
if not allow_duplicates:
|
||||
class_name = cls.__name__
|
||||
if class_name in plugin_names:
|
||||
result.duplicated_plugins.append(cls)
|
||||
continue
|
||||
plugin_names.add(class_name)
|
||||
|
||||
result.plugins.append(cls)
|
||||
|
||||
# Store in memory last result to keep in memory loaded modules
|
||||
self._last_discovered_results[superclass] = result
|
||||
self._last_discovered_plugins[superclass] = list(
|
||||
result.plugins
|
||||
)
|
||||
result.log_report()
|
||||
if return_report:
|
||||
return result
|
||||
return result.plugins
|
||||
|
||||
def register_plugin(self, superclass, cls):
|
||||
"""Register a directory containing plug-ins of type `superclass`
|
||||
|
||||
Arguments:
|
||||
superclass (type): Superclass of plug-in
|
||||
cls (object): Subclass of `superclass`
|
||||
"""
|
||||
|
||||
if superclass not in self._registered_plugins:
|
||||
self._registered_plugins[superclass] = list()
|
||||
|
||||
if cls not in self._registered_plugins[superclass]:
|
||||
self._registered_plugins[superclass].append(cls)
|
||||
|
||||
def register_plugin_path(self, superclass, path):
|
||||
"""Register a directory of one or more plug-ins
|
||||
|
||||
Arguments:
|
||||
superclass (type): Superclass of plug-ins to look for during
|
||||
discovery
|
||||
path (str): Absolute path to directory in which to discover
|
||||
plug-ins
|
||||
"""
|
||||
|
||||
if superclass not in self._registered_plugin_paths:
|
||||
self._registered_plugin_paths[superclass] = list()
|
||||
|
||||
path = os.path.normpath(path)
|
||||
if path not in self._registered_plugin_paths[superclass]:
|
||||
self._registered_plugin_paths[superclass].append(path)
|
||||
|
||||
def registered_plugin_paths(self):
|
||||
"""Return all currently registered plug-in paths"""
|
||||
# Return shallow copy so we the original data can't be changed
|
||||
return {
|
||||
superclass: paths[:]
|
||||
for superclass, paths in self._registered_plugin_paths.items()
|
||||
}
|
||||
|
||||
def deregister_plugin(self, superclass, plugin):
|
||||
"""Opposite of `register_plugin()`"""
|
||||
if superclass in self._registered_plugins:
|
||||
self._registered_plugins[superclass].remove(plugin)
|
||||
|
||||
def deregister_plugin_path(self, superclass, path):
|
||||
"""Opposite of `register_plugin_path()`"""
|
||||
self._registered_plugin_paths[superclass].remove(path)
|
||||
|
||||
|
||||
class _GlobalDiscover:
|
||||
"""Access to global object of PluginDiscoverContext.
|
||||
|
||||
Using singleton object to register/deregister plugins and plugin paths
|
||||
and then discover them by superclass.
|
||||
"""
|
||||
|
||||
_context = None
|
||||
|
||||
@classmethod
|
||||
def get_context(cls):
|
||||
if cls._context is None:
|
||||
cls._context = PluginDiscoverContext()
|
||||
return cls._context
|
||||
|
||||
|
||||
def discover(superclass, allow_duplicates=True):
|
||||
context = _GlobalDiscover.get_context()
|
||||
return context.discover(superclass, allow_duplicates)
|
||||
|
||||
|
||||
def get_last_discovered_plugins(superclass):
|
||||
context = _GlobalDiscover.get_context()
|
||||
return context.get_last_discovered_plugins(superclass)
|
||||
|
||||
|
||||
def register_plugin(superclass, cls):
|
||||
context = _GlobalDiscover.get_context()
|
||||
context.register_plugin(superclass, cls)
|
||||
|
||||
|
||||
def register_plugin_path(superclass, path):
|
||||
context = _GlobalDiscover.get_context()
|
||||
context.register_plugin_path(superclass, path)
|
||||
|
||||
|
||||
def deregister_plugin(superclass, cls):
|
||||
context = _GlobalDiscover.get_context()
|
||||
context.deregister_plugin(superclass, cls)
|
||||
|
||||
|
||||
def deregister_plugin_path(superclass, path):
|
||||
context = _GlobalDiscover.get_context()
|
||||
context.deregister_plugin_path(superclass, path)
|
||||
|
|
@ -2,6 +2,11 @@ import os
|
|||
import copy
|
||||
import logging
|
||||
|
||||
from .plugin_discover import (
|
||||
discover,
|
||||
register_plugin,
|
||||
register_plugin_path,
|
||||
)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
@ -126,21 +131,15 @@ class BinaryThumbnail(ThumbnailResolver):
|
|||
|
||||
# Thumbnail resolvers
|
||||
def discover_thumbnail_resolvers():
|
||||
import avalon.api
|
||||
|
||||
return avalon.api.discover(ThumbnailResolver)
|
||||
return discover(ThumbnailResolver)
|
||||
|
||||
|
||||
def register_thumbnail_resolver(plugin):
|
||||
import avalon.api
|
||||
|
||||
return avalon.api.register_plugin(ThumbnailResolver, plugin)
|
||||
register_plugin(ThumbnailResolver, plugin)
|
||||
|
||||
|
||||
def register_thumbnail_resolver_path(path):
|
||||
import avalon.api
|
||||
|
||||
return avalon.api.register_plugin_path(ThumbnailResolver, path)
|
||||
register_plugin_path(ThumbnailResolver, path)
|
||||
|
||||
|
||||
register_thumbnail_resolver(TemplateResolver)
|
||||
|
|
|
|||
21
openpype/plugins/publish/collect_cleanup_keys.py
Normal file
21
openpype/plugins/publish/collect_cleanup_keys.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
Requires:
|
||||
None
|
||||
Provides:
|
||||
context
|
||||
- cleanupFullPaths (list)
|
||||
- cleanupEmptyDirs (list)
|
||||
"""
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CollectCleanupKeys(pyblish.api.ContextPlugin):
|
||||
"""Prepare keys for 'ExplicitCleanUp' plugin."""
|
||||
|
||||
label = "Collect Cleanup Keys"
|
||||
order = pyblish.api.CollectorOrder
|
||||
|
||||
def process(self, context):
|
||||
context.data["cleanupFullPaths"] = []
|
||||
context.data["cleanupEmptyDirs"] = []
|
||||
|
|
@ -7,8 +7,12 @@ import shutil
|
|||
from bson.objectid import ObjectId
|
||||
from pymongo import InsertOne, ReplaceOne
|
||||
import pyblish.api
|
||||
|
||||
from avalon import api, io, schema
|
||||
from openpype.lib import create_hard_link
|
||||
from openpype.lib import (
|
||||
create_hard_link,
|
||||
filter_profiles
|
||||
)
|
||||
|
||||
|
||||
class IntegrateHeroVersion(pyblish.api.InstancePlugin):
|
||||
|
|
@ -17,7 +21,9 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin):
|
|||
order = pyblish.api.IntegratorOrder + 0.1
|
||||
|
||||
optional = True
|
||||
active = True
|
||||
|
||||
# Families are modified using settings
|
||||
families = [
|
||||
"model",
|
||||
"rig",
|
||||
|
|
@ -33,11 +39,13 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin):
|
|||
"project", "asset", "task", "subset", "representation",
|
||||
"family", "hierarchy", "task", "username"
|
||||
]
|
||||
# TODO add family filtering
|
||||
# QUESTION/TODO this process should happen on server if crashed due to
|
||||
# permissions error on files (files were used or user didn't have perms)
|
||||
# *but all other plugins must be sucessfully completed
|
||||
|
||||
template_name_profiles = []
|
||||
_default_template_name = "hero"
|
||||
|
||||
def process(self, instance):
|
||||
self.log.debug(
|
||||
"--- Integration of Hero version for subset `{}` begins.".format(
|
||||
|
|
@ -51,27 +59,35 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin):
|
|||
)
|
||||
return
|
||||
|
||||
project_name = api.Session["AVALON_PROJECT"]
|
||||
template_key = self._get_template_key(instance)
|
||||
|
||||
# TODO raise error if Hero not set?
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
if "hero" not in anatomy.templates:
|
||||
self.log.warning("!!! Anatomy does not have set `hero` key!")
|
||||
return
|
||||
|
||||
if "path" not in anatomy.templates["hero"]:
|
||||
project_name = api.Session["AVALON_PROJECT"]
|
||||
if template_key not in anatomy.templates:
|
||||
self.log.warning((
|
||||
"!!! There is not set `path` template in `hero` anatomy"
|
||||
" for project \"{}\"."
|
||||
).format(project_name))
|
||||
"!!! Anatomy of project \"{}\" does not have set"
|
||||
" \"{}\" template key!"
|
||||
).format(project_name, template_key))
|
||||
return
|
||||
|
||||
hero_template = anatomy.templates["hero"]["path"]
|
||||
if "path" not in anatomy.templates[template_key]:
|
||||
self.log.warning((
|
||||
"!!! There is not set \"path\" template in \"{}\" anatomy"
|
||||
" for project \"{}\"."
|
||||
).format(template_key, project_name))
|
||||
return
|
||||
|
||||
hero_template = anatomy.templates[template_key]["path"]
|
||||
self.log.debug("`hero` template check was successful. `{}`".format(
|
||||
hero_template
|
||||
))
|
||||
|
||||
hero_publish_dir = self.get_publish_dir(instance)
|
||||
self.integrate_instance(instance, template_key, hero_template)
|
||||
|
||||
def integrate_instance(self, instance, template_key, hero_template):
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
published_repres = instance.data["published_representations"]
|
||||
hero_publish_dir = self.get_publish_dir(instance, template_key)
|
||||
|
||||
src_version_entity = instance.data.get("versionEntity")
|
||||
filtered_repre_ids = []
|
||||
|
|
@ -271,12 +287,12 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin):
|
|||
continue
|
||||
|
||||
# Prepare anatomy data
|
||||
anatomy_data = repre_info["anatomy_data"]
|
||||
anatomy_data = copy.deepcopy(repre_info["anatomy_data"])
|
||||
anatomy_data.pop("version", None)
|
||||
|
||||
# Get filled path to repre context
|
||||
anatomy_filled = anatomy.format(anatomy_data)
|
||||
template_filled = anatomy_filled["hero"]["path"]
|
||||
template_filled = anatomy_filled[template_key]["path"]
|
||||
|
||||
repre_data = {
|
||||
"path": str(template_filled),
|
||||
|
|
@ -308,11 +324,11 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin):
|
|||
collections, remainders = clique.assemble(published_files)
|
||||
if remainders or not collections or len(collections) > 1:
|
||||
raise Exception((
|
||||
"Integrity error. Files of published representation "
|
||||
"is combination of frame collections and single files."
|
||||
"Collections: `{}` Single files: `{}`"
|
||||
).format(str(collections),
|
||||
str(remainders)))
|
||||
"Integrity error. Files of published"
|
||||
" representation is combination of frame"
|
||||
" collections and single files. Collections:"
|
||||
" `{}` Single files: `{}`"
|
||||
).format(str(collections), str(remainders)))
|
||||
|
||||
src_col = collections[0]
|
||||
|
||||
|
|
@ -320,13 +336,10 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin):
|
|||
frame_splitter = "_-_FRAME_SPLIT_-_"
|
||||
anatomy_data["frame"] = frame_splitter
|
||||
_anatomy_filled = anatomy.format(anatomy_data)
|
||||
_template_filled = _anatomy_filled["hero"]["path"]
|
||||
_template_filled = _anatomy_filled[template_key]["path"]
|
||||
head, tail = _template_filled.split(frame_splitter)
|
||||
padding = int(
|
||||
anatomy.templates["render"].get(
|
||||
"frame_padding",
|
||||
anatomy.templates["render"].get("padding")
|
||||
)
|
||||
anatomy.templates[template_key]["frame_padding"]
|
||||
)
|
||||
|
||||
dst_col = clique.Collection(
|
||||
|
|
@ -444,6 +457,8 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin):
|
|||
backup_hero_publish_dir is not None and
|
||||
os.path.exists(backup_hero_publish_dir)
|
||||
):
|
||||
if os.path.exists(hero_publish_dir):
|
||||
shutil.rmtree(hero_publish_dir)
|
||||
os.rename(backup_hero_publish_dir, hero_publish_dir)
|
||||
self.log.error((
|
||||
"!!! Creating of hero version failed."
|
||||
|
|
@ -466,13 +481,13 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin):
|
|||
files.append(_path)
|
||||
return files
|
||||
|
||||
def get_publish_dir(self, instance):
|
||||
def get_publish_dir(self, instance, template_key):
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
template_data = copy.deepcopy(instance.data["anatomyData"])
|
||||
|
||||
if "folder" in anatomy.templates["hero"]:
|
||||
if "folder" in anatomy.templates[template_key]:
|
||||
anatomy_filled = anatomy.format(template_data)
|
||||
publish_folder = anatomy_filled["hero"]["folder"]
|
||||
publish_folder = anatomy_filled[template_key]["folder"]
|
||||
else:
|
||||
# This is for cases of Deprecated anatomy without `folder`
|
||||
# TODO remove when all clients have solved this issue
|
||||
|
|
@ -489,7 +504,7 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin):
|
|||
" key underneath `publish` (in global of for project `{}`)."
|
||||
).format(project_name))
|
||||
|
||||
file_path = anatomy_filled["hero"]["path"]
|
||||
file_path = anatomy_filled[template_key]["path"]
|
||||
# Directory
|
||||
publish_folder = os.path.dirname(file_path)
|
||||
|
||||
|
|
@ -499,6 +514,38 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin):
|
|||
|
||||
return publish_folder
|
||||
|
||||
def _get_template_key(self, instance):
|
||||
anatomy_data = instance.data["anatomyData"]
|
||||
task_data = anatomy_data.get("task") or {}
|
||||
task_name = task_data.get("name")
|
||||
task_type = task_data.get("type")
|
||||
host_name = instance.context.data["hostName"]
|
||||
# TODO raise error if Hero not set?
|
||||
family = self.main_family_from_instance(instance)
|
||||
key_values = {
|
||||
"families": family,
|
||||
"task_names": task_name,
|
||||
"task_types": task_type,
|
||||
"hosts": host_name
|
||||
}
|
||||
profile = filter_profiles(
|
||||
self.template_name_profiles,
|
||||
key_values,
|
||||
logger=self.log
|
||||
)
|
||||
if profile:
|
||||
template_name = profile["template_name"]
|
||||
else:
|
||||
template_name = self._default_template_name
|
||||
return template_name
|
||||
|
||||
def main_family_from_instance(self, instance):
|
||||
"""Returns main family of entered instance."""
|
||||
family = instance.data.get("family")
|
||||
if not family:
|
||||
family = instance.data["families"][0]
|
||||
return family
|
||||
|
||||
def copy_file(self, src_path, dst_path):
|
||||
# TODO check drives if are the same to check if cas hardlink
|
||||
dirname = os.path.dirname(dst_path)
|
||||
|
|
@ -564,22 +611,16 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin):
|
|||
src_file (string) - original file path
|
||||
dst_file (string) - hero file path
|
||||
"""
|
||||
_, rootless = anatomy.find_root_template_from_path(
|
||||
dst_file
|
||||
)
|
||||
_, rtls_src = anatomy.find_root_template_from_path(
|
||||
src_file
|
||||
)
|
||||
_, rootless = anatomy.find_root_template_from_path(dst_file)
|
||||
_, rtls_src = anatomy.find_root_template_from_path(src_file)
|
||||
return path.replace(rtls_src, rootless)
|
||||
|
||||
def _update_hash(self, hash, src_file_name, dst_file):
|
||||
"""
|
||||
Updates hash value with proper hero name
|
||||
"""
|
||||
src_file_name = self._get_name_without_ext(
|
||||
src_file_name)
|
||||
hero_file_name = self._get_name_without_ext(
|
||||
dst_file)
|
||||
src_file_name = self._get_name_without_ext(src_file_name)
|
||||
hero_file_name = self._get_name_without_ext(dst_file)
|
||||
return hash.replace(src_file_name, hero_file_name)
|
||||
|
||||
def _get_name_without_ext(self, value):
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ M_ENVIRONMENT_KEY = "__environment_keys__"
|
|||
# Metadata key for storing dynamic created labels
|
||||
M_DYNAMIC_KEY_LABEL = "__dynamic_keys_labels__"
|
||||
|
||||
METADATA_KEYS = (
|
||||
METADATA_KEYS = frozenset([
|
||||
M_OVERRIDDEN_KEY,
|
||||
M_ENVIRONMENT_KEY,
|
||||
M_DYNAMIC_KEY_LABEL
|
||||
)
|
||||
])
|
||||
|
||||
# Keys where studio's system overrides are stored
|
||||
GLOBAL_SETTINGS_KEY = "global_settings"
|
||||
|
|
|
|||
|
|
@ -185,8 +185,8 @@
|
|||
"linux": []
|
||||
},
|
||||
"renderSpace": "ACEScg",
|
||||
"viewName": "ACES 1.0 SDR-video",
|
||||
"displayName": "sRGB"
|
||||
"displayName": "sRGB",
|
||||
"viewName": "ACES 1.0 SDR-video"
|
||||
},
|
||||
"colorManagementPreference": {
|
||||
"configFilePath": {
|
||||
|
|
|
|||
|
|
@ -15,33 +15,6 @@
|
|||
"deadline"
|
||||
]
|
||||
},
|
||||
"ProcessSubmittedJobOnFarm": {
|
||||
"enabled": true,
|
||||
"deadline_department": "",
|
||||
"deadline_pool": "",
|
||||
"deadline_group": "",
|
||||
"deadline_chunk_size": 1,
|
||||
"deadline_priority": 50,
|
||||
"publishing_script": "",
|
||||
"skip_integration_repre_list": [],
|
||||
"aov_filter": {
|
||||
"maya": [
|
||||
".+(?:\\.|_)([Bb]eauty)(?:\\.|_).*"
|
||||
],
|
||||
"nuke": [
|
||||
".*"
|
||||
],
|
||||
"aftereffects": [
|
||||
".*"
|
||||
],
|
||||
"celaction": [
|
||||
".*"
|
||||
],
|
||||
"harmony": [
|
||||
".*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"MayaSubmitDeadline": {
|
||||
"enabled": true,
|
||||
"optional": false,
|
||||
|
|
@ -95,6 +68,33 @@
|
|||
"group": "",
|
||||
"department": "",
|
||||
"multiprocess": true
|
||||
},
|
||||
"ProcessSubmittedJobOnFarm": {
|
||||
"enabled": true,
|
||||
"deadline_department": "",
|
||||
"deadline_pool": "",
|
||||
"deadline_group": "",
|
||||
"deadline_chunk_size": 1,
|
||||
"deadline_priority": 50,
|
||||
"publishing_script": "",
|
||||
"skip_integration_repre_list": [],
|
||||
"aov_filter": {
|
||||
"maya": [
|
||||
".+(?:\\.|_)([Bb]eauty)(?:\\.|_).*"
|
||||
],
|
||||
"nuke": [
|
||||
".*"
|
||||
],
|
||||
"aftereffects": [
|
||||
".*"
|
||||
],
|
||||
"celaction": [
|
||||
".*"
|
||||
],
|
||||
"harmony": [
|
||||
".*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,20 +33,6 @@
|
|||
"enabled": false,
|
||||
"profiles": []
|
||||
},
|
||||
"IntegrateHeroVersion": {
|
||||
"enabled": true,
|
||||
"optional": true,
|
||||
"families": [
|
||||
"model",
|
||||
"rig",
|
||||
"look",
|
||||
"pointcache",
|
||||
"animation",
|
||||
"setdress",
|
||||
"layout",
|
||||
"mayaScene"
|
||||
]
|
||||
},
|
||||
"ExtractJpegEXR": {
|
||||
"enabled": true,
|
||||
"ffmpeg_args": {
|
||||
|
|
@ -204,6 +190,22 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"IntegrateHeroVersion": {
|
||||
"enabled": true,
|
||||
"optional": true,
|
||||
"active": true,
|
||||
"families": [
|
||||
"model",
|
||||
"rig",
|
||||
"look",
|
||||
"pointcache",
|
||||
"animation",
|
||||
"setdress",
|
||||
"layout",
|
||||
"mayaScene"
|
||||
],
|
||||
"template_name_profiles": []
|
||||
},
|
||||
"CleanUp": {
|
||||
"paterns": [],
|
||||
"remove_temp_renders": false
|
||||
|
|
|
|||
|
|
@ -119,11 +119,10 @@
|
|||
"families": [],
|
||||
"sebsets": []
|
||||
},
|
||||
"extension": "mov",
|
||||
"read_raw": false,
|
||||
"viewer_process_override": "",
|
||||
"bake_viewer_process": true,
|
||||
"bake_viewer_input_process": true,
|
||||
"add_tags": [],
|
||||
"reformat_node_add": false,
|
||||
"reformat_node_config": [
|
||||
{
|
||||
|
|
@ -151,7 +150,9 @@
|
|||
"name": "pbb",
|
||||
"value": false
|
||||
}
|
||||
]
|
||||
],
|
||||
"extension": "mov",
|
||||
"add_tags": []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"active": true
|
||||
},
|
||||
"ValidateNaming": {
|
||||
"invalid_chars": "[ \\\\/+\\*\\?\\(\\)\\[\\]\\{\\}:,]",
|
||||
"invalid_chars": "[ \\\\/+\\*\\?\\(\\)\\[\\]\\{\\}:,;]",
|
||||
"replace_char": "_"
|
||||
},
|
||||
"ExtractImage": {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@
|
|||
"enabled": true,
|
||||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ExtractConvertToEXR": {
|
||||
"enabled": false,
|
||||
"replace_pngs": true,
|
||||
"exr_compression": "ZIP"
|
||||
}
|
||||
},
|
||||
"load": {
|
||||
|
|
|
|||
|
|
@ -25,10 +25,18 @@
|
|||
},
|
||||
"variants": {
|
||||
"3-2": {
|
||||
"MTOA_VERSION": "3.2"
|
||||
"host_names": [],
|
||||
"app_variants": [],
|
||||
"environment": {
|
||||
"MTOA_VERSION": "3.2"
|
||||
}
|
||||
},
|
||||
"3-1": {
|
||||
"MTOA_VERSION": "3.1"
|
||||
"host_names": [],
|
||||
"app_variants": [],
|
||||
"environment": {
|
||||
"MTOA_VERSION": "3.1"
|
||||
}
|
||||
},
|
||||
"__dynamic_keys_labels__": {
|
||||
"3-2": "3.2",
|
||||
|
|
|
|||
|
|
@ -78,6 +78,47 @@
|
|||
"docstring": "Validate if shot on instances metadata is same as workfiles shot"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"key": "ExtractConvertToEXR",
|
||||
"label": "Extract Convert To EXR",
|
||||
"is_group": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"label": "<b>WARNING:</b> This plugin does not work on MacOS (using OIIO tool)."
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "replace_pngs",
|
||||
"label": "Replace source PNG"
|
||||
},
|
||||
{
|
||||
"type": "enum",
|
||||
"key": "exr_compression",
|
||||
"label": "EXR Compression",
|
||||
"multiselection": false,
|
||||
"enum_items": [
|
||||
{"ZIP": "ZIP"},
|
||||
{"ZIPS": "ZIPS"},
|
||||
{"DWAA": "DWAA"},
|
||||
{"DWAB": "DWAB"},
|
||||
{"PIZ": "PIZ"},
|
||||
{"RLE": "RLE"},
|
||||
{"PXR24": "PXR24"},
|
||||
{"B44": "B44"},
|
||||
{"B44A": "B44A"},
|
||||
{"none": "None"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -122,32 +122,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"key": "IntegrateHeroVersion",
|
||||
"label": "IntegrateHeroVersion",
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "optional",
|
||||
"label": "Optional"
|
||||
},
|
||||
{
|
||||
"key": "families",
|
||||
"label": "Families",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
|
|
@ -652,6 +626,80 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"key": "IntegrateHeroVersion",
|
||||
"label": "IntegrateHeroVersion",
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "optional",
|
||||
"label": "Optional"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "active",
|
||||
"label": "Active"
|
||||
},
|
||||
{
|
||||
"key": "families",
|
||||
"label": "Families",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "template_name_profiles",
|
||||
"label": "Template name profiles",
|
||||
"use_label_wrap": true,
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"key": "families",
|
||||
"label": "Families",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "hosts-enum",
|
||||
"key": "hosts",
|
||||
"label": "Hosts",
|
||||
"multiselection": true
|
||||
},
|
||||
{
|
||||
"key": "task_types",
|
||||
"label": "Task types",
|
||||
"type": "task-types-enum"
|
||||
},
|
||||
{
|
||||
"key": "task_names",
|
||||
"label": "Task names",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "template_name",
|
||||
"label": "Template name",
|
||||
"tooltip": "Name of template from Anatomy templates"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
|
|
|
|||
|
|
@ -208,9 +208,10 @@
|
|||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "extension",
|
||||
"label": "File extension"
|
||||
"type": "boolean",
|
||||
"key": "read_raw",
|
||||
"label": "Read colorspace RAW",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
|
|
@ -227,12 +228,6 @@
|
|||
"key": "bake_viewer_input_process",
|
||||
"label": "Bake Viewer Input Process (LUTs)"
|
||||
},
|
||||
{
|
||||
"key": "add_tags",
|
||||
"label": "Add additional tags to representations",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
|
|
@ -246,7 +241,7 @@
|
|||
"type": "collapsible-wrap",
|
||||
"label": "Reformat Node Knobs",
|
||||
"collapsible": true,
|
||||
"collapsed": false,
|
||||
"collapsed": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "list",
|
||||
|
|
@ -347,6 +342,20 @@
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "extension",
|
||||
"label": "Write node file type"
|
||||
},
|
||||
{
|
||||
"key": "add_tags",
|
||||
"label": "Add additional tags to representations",
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
[
|
||||
{
|
||||
"type": "list-strict",
|
||||
"key": "{name}",
|
||||
"label": "{label}",
|
||||
"object_types": [
|
||||
{
|
||||
"label": "Red",
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"decimal": 3
|
||||
},
|
||||
{
|
||||
"label": "Green",
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"decimal": 3
|
||||
},
|
||||
{
|
||||
"label": "Blue",
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"decimal": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -25,7 +25,30 @@
|
|||
"key": "variants",
|
||||
"collapsible_key": true,
|
||||
"object_type": {
|
||||
"type": "raw-json"
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"key": "host_names",
|
||||
"label": "Hosts",
|
||||
"type": "hosts-enum",
|
||||
"multiselection": true
|
||||
},
|
||||
{
|
||||
"key": "app_variants",
|
||||
"label": "Applications",
|
||||
"type": "apps-enum",
|
||||
"multiselection": true,
|
||||
"tooltip": "Applications are not \"live\" and may require to Save and refresh settings UI to update values."
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"key": "environment",
|
||||
"label": "Environments",
|
||||
"type": "raw-json"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -265,11 +265,43 @@ def save_project_anatomy(project_name, anatomy_data):
|
|||
raise SaveWarningExc(warnings)
|
||||
|
||||
|
||||
def _system_settings_backwards_compatible_conversion(studio_overrides):
|
||||
# Backwards compatibility of tools 3.9.1 - 3.9.2 to keep
|
||||
# "tools" environments
|
||||
if (
|
||||
"tools" in studio_overrides
|
||||
and "tool_groups" in studio_overrides["tools"]
|
||||
):
|
||||
tool_groups = studio_overrides["tools"]["tool_groups"]
|
||||
for tool_group, group_value in tool_groups.items():
|
||||
if tool_group in METADATA_KEYS:
|
||||
continue
|
||||
|
||||
variants = group_value.get("variants")
|
||||
if not variants:
|
||||
continue
|
||||
|
||||
for key in set(variants.keys()):
|
||||
if key in METADATA_KEYS:
|
||||
continue
|
||||
|
||||
variant_value = variants[key]
|
||||
if "environment" not in variant_value:
|
||||
variants[key] = {
|
||||
"environment": variant_value
|
||||
}
|
||||
|
||||
|
||||
@require_handler
|
||||
def get_studio_system_settings_overrides(return_version=False):
|
||||
return _SETTINGS_HANDLER.get_studio_system_settings_overrides(
|
||||
output = _SETTINGS_HANDLER.get_studio_system_settings_overrides(
|
||||
return_version
|
||||
)
|
||||
value = output
|
||||
if return_version:
|
||||
value, version = output
|
||||
_system_settings_backwards_compatible_conversion(value)
|
||||
return output
|
||||
|
||||
|
||||
@require_handler
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import avalon.api as api
|
||||
import openpype
|
||||
from openpype.pipeline import LegacyCreator
|
||||
from openpype.pipeline import (
|
||||
LegacyCreator,
|
||||
register_creator_plugin,
|
||||
discover_creator_plugins,
|
||||
)
|
||||
|
||||
|
||||
class MyTestCreator(LegacyCreator):
|
||||
|
|
@ -27,8 +31,8 @@ def test_avalon_plugin_presets(monkeypatch, printer):
|
|||
|
||||
openpype.install()
|
||||
api.register_host(Test())
|
||||
api.register_plugin(LegacyCreator, MyTestCreator)
|
||||
plugins = api.discover(LegacyCreator)
|
||||
register_creator_plugin(MyTestCreator)
|
||||
plugins = discover_creator_plugins()
|
||||
printer("Test if we got our test plugin")
|
||||
assert MyTestCreator in plugins
|
||||
for p in plugins:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import uuid
|
||||
from Qt import QtGui, QtCore
|
||||
|
||||
from avalon import api
|
||||
from openpype.pipeline import LegacyCreator
|
||||
from openpype.pipeline import discover_legacy_creator_plugins
|
||||
|
||||
from . constants import (
|
||||
FAMILY_ROLE,
|
||||
|
|
@ -22,7 +21,7 @@ class CreatorsModel(QtGui.QStandardItemModel):
|
|||
self._creators_by_id = {}
|
||||
|
||||
items = []
|
||||
creators = api.discover(LegacyCreator)
|
||||
creators = discover_legacy_creator_plugins()
|
||||
for creator in creators:
|
||||
item_id = str(uuid.uuid4())
|
||||
self._creators_by_id[item_id] = creator
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ class CreateDialog(QtWidgets.QDialog):
|
|||
create_btn.setEnabled(False)
|
||||
|
||||
form_layout = QtWidgets.QFormLayout()
|
||||
form_layout.addRow("Name:", variant_layout)
|
||||
form_layout.addRow("Variant:", variant_layout)
|
||||
form_layout.addRow("Subset:", subset_name_input)
|
||||
|
||||
mid_widget = QtWidgets.QWidget(self)
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@ class CompleterView(QtWidgets.QListView):
|
|||
QtCore.Qt.FramelessWindowHint
|
||||
| QtCore.Qt.Tool
|
||||
)
|
||||
|
||||
# Open the widget unactivated
|
||||
self.setAttribute(QtCore.Qt.WA_ShowWithoutActivating)
|
||||
delegate = QtWidgets.QStyledItemDelegate()
|
||||
self.setItemDelegate(delegate)
|
||||
|
||||
|
|
@ -225,10 +228,18 @@ class SettingsLineEdit(PlaceholderLineEdit):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super(SettingsLineEdit, self).__init__(*args, **kwargs)
|
||||
|
||||
self._completer = None
|
||||
# Timer which will get started on focus in and stopped on focus out
|
||||
# - callback checks if line edit or completer have focus
|
||||
# and hide completer if not
|
||||
focus_timer = QtCore.QTimer()
|
||||
focus_timer.setInterval(50)
|
||||
|
||||
focus_timer.timeout.connect(self._on_focus_timer)
|
||||
self.textChanged.connect(self._on_text_change)
|
||||
|
||||
self._completer = None
|
||||
self._focus_timer = focus_timer
|
||||
|
||||
def _on_text_change(self, text):
|
||||
if self._completer is not None:
|
||||
self._completer.set_text_filter(text)
|
||||
|
|
@ -240,19 +251,19 @@ class SettingsLineEdit(PlaceholderLineEdit):
|
|||
new_point = self.mapToGlobal(point)
|
||||
self._completer.move(new_point)
|
||||
|
||||
def _on_focus_timer(self):
|
||||
if not self.hasFocus() and not self._completer.hasFocus():
|
||||
self._completer.hide()
|
||||
self._focus_timer.stop()
|
||||
|
||||
def focusInEvent(self, event):
|
||||
super(SettingsLineEdit, self).focusInEvent(event)
|
||||
self.focused_in.emit()
|
||||
|
||||
if self._completer is None:
|
||||
return
|
||||
self._completer.show()
|
||||
self._update_completer()
|
||||
|
||||
def focusOutEvent(self, event):
|
||||
super(SettingsLineEdit, self).focusOutEvent(event)
|
||||
if self._completer is not None:
|
||||
self._completer.hide()
|
||||
self._focus_timer.start()
|
||||
self._completer.show()
|
||||
self._update_completer()
|
||||
|
||||
def paintEvent(self, event):
|
||||
super(SettingsLineEdit, self).paintEvent(event)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ from openpype.lib import filter_profiles
|
|||
from openpype.style import get_objected_colors
|
||||
from openpype.resources import get_image_path
|
||||
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
|
||||
def center_window(window):
|
||||
"""Move window to center of it's screen."""
|
||||
|
|
@ -111,13 +113,23 @@ def get_qta_icon_by_name_and_color(icon_name, icon_color):
|
|||
variants.append("{0}.{1}".format(key, icon_name))
|
||||
|
||||
icon = None
|
||||
used_variant = None
|
||||
for variant in variants:
|
||||
try:
|
||||
icon = qtawesome.icon(variant, color=icon_color)
|
||||
used_variant = variant
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if used_variant is None:
|
||||
log.info("Didn't find icon \"{}\"".format(icon_name))
|
||||
|
||||
elif used_variant != icon_name:
|
||||
log.debug("Icon \"{}\" was not found \"{}\" is used instead".format(
|
||||
icon_name, used_variant
|
||||
))
|
||||
|
||||
SharedObjects.icons[full_icon_name] = icon
|
||||
return icon
|
||||
|
||||
|
|
@ -140,8 +152,8 @@ def get_asset_icon_name(asset_doc, has_children=True):
|
|||
return icon_name
|
||||
|
||||
if has_children:
|
||||
return "folder"
|
||||
return "folder-o"
|
||||
return "fa.folder"
|
||||
return "fa.folder-o"
|
||||
|
||||
|
||||
def get_asset_icon_color(asset_doc):
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.9.2-nightly.2"
|
||||
__version__ = "3.9.2-nightly.3"
|
||||
|
|
|
|||
|
|
@ -641,5 +641,6 @@ class SingleFileWidget(QtWidgets.QWidget):
|
|||
filepaths.append(filepath)
|
||||
# TODO filter check
|
||||
if len(filepaths) == 1:
|
||||
self.set_value(filepaths[0], False)
|
||||
self._filepath_input.setText(filepaths[0])
|
||||
|
||||
event.accept()
|
||||
|
|
|
|||
84
poetry.lock
generated
84
poetry.lock
generated
|
|
@ -680,15 +680,8 @@ category = "main"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
attrs = ">=17.4.0"
|
||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||
pyrsistent = ">=0.14.0"
|
||||
six = ">=1.11.0"
|
||||
|
||||
[package.extras]
|
||||
format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"]
|
||||
format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"]
|
||||
format = ["rfc3987", "strict-rfc3339", "webcolors"]
|
||||
|
||||
[[package]]
|
||||
name = "keyring"
|
||||
|
|
@ -784,7 +777,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
|||
|
||||
[[package]]
|
||||
name = "paramiko"
|
||||
version = "2.9.2"
|
||||
version = "2.10.1"
|
||||
description = "SSH2 protocol library"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
|
@ -794,6 +787,7 @@ python-versions = "*"
|
|||
bcrypt = ">=3.1.3"
|
||||
cryptography = ">=2.5"
|
||||
pynacl = ">=1.0.1"
|
||||
six = "*"
|
||||
|
||||
[package.extras]
|
||||
all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"]
|
||||
|
|
@ -1087,14 +1081,6 @@ category = "main"
|
|||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "pyrsistent"
|
||||
version = "0.18.1"
|
||||
description = "Persistent/Functional/Immutable data structures"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "pysftp"
|
||||
version = "0.2.9"
|
||||
|
|
@ -1633,7 +1619,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "3.7.*"
|
||||
content-hash = "2f78d48a6aad2d8a88b7dd7f31a76d907bec9fb65f0086fba6b6d2e1605f0f88"
|
||||
content-hash = "b02313c8255a1897b0f0617ad4884a5943696c363512921aab1cb2dd8f4fdbe0"
|
||||
|
||||
[metadata.files]
|
||||
acre = []
|
||||
|
|
@ -2171,12 +2157,28 @@ log4mongo = [
|
|||
{file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"},
|
||||
]
|
||||
markupsafe = [
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
|
||||
|
|
@ -2185,14 +2187,27 @@ markupsafe = [
|
|||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
|
||||
|
|
@ -2202,6 +2217,12 @@ markupsafe = [
|
|||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
|
||||
{file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
|
||||
|
|
@ -2277,8 +2298,8 @@ packaging = [
|
|||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
||||
]
|
||||
paramiko = [
|
||||
{file = "paramiko-2.9.2-py2.py3-none-any.whl", hash = "sha256:04097dbd96871691cdb34c13db1883066b8a13a0df2afd4cb0a92221f51c2603"},
|
||||
{file = "paramiko-2.9.2.tar.gz", hash = "sha256:944a9e5dbdd413ab6c7951ea46b0ab40713235a9c4c5ca81cfe45c6f14fa677b"},
|
||||
{file = "paramiko-2.10.1-py2.py3-none-any.whl", hash = "sha256:f6cbd3e1204abfdbcd40b3ecbc9d32f04027cd3080fe666245e21e7540ccfc1b"},
|
||||
{file = "paramiko-2.10.1.tar.gz", hash = "sha256:443f4da23ec24e9a9c0ea54017829c282abdda1d57110bf229360775ccd27a31"},
|
||||
]
|
||||
parso = [
|
||||
{file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
|
||||
|
|
@ -2598,29 +2619,6 @@ pyparsing = [
|
|||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
||||
]
|
||||
pyrsistent = [
|
||||
{file = "pyrsistent-0.18.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1"},
|
||||
{file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26"},
|
||||
{file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e"},
|
||||
{file = "pyrsistent-0.18.1-cp310-cp310-win32.whl", hash = "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6"},
|
||||
{file = "pyrsistent-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec"},
|
||||
{file = "pyrsistent-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b"},
|
||||
{file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc"},
|
||||
{file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22"},
|
||||
{file = "pyrsistent-0.18.1-cp37-cp37m-win32.whl", hash = "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8"},
|
||||
{file = "pyrsistent-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286"},
|
||||
{file = "pyrsistent-0.18.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6"},
|
||||
{file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec"},
|
||||
{file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c"},
|
||||
{file = "pyrsistent-0.18.1-cp38-cp38-win32.whl", hash = "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca"},
|
||||
{file = "pyrsistent-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a"},
|
||||
{file = "pyrsistent-0.18.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5"},
|
||||
{file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045"},
|
||||
{file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c"},
|
||||
{file = "pyrsistent-0.18.1-cp39-cp39-win32.whl", hash = "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc"},
|
||||
{file = "pyrsistent-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07"},
|
||||
{file = "pyrsistent-0.18.1.tar.gz", hash = "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96"},
|
||||
]
|
||||
pysftp = [
|
||||
{file = "pysftp-0.2.9.tar.gz", hash = "sha256:fbf55a802e74d663673400acd92d5373c1c7ee94d765b428d9f977567ac4854a"},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "OpenPype"
|
||||
version = "3.9.2-nightly.2" # OpenPype
|
||||
version = "3.9.2-nightly.3" # OpenPype
|
||||
description = "Open VFX and Animation pipeline with support."
|
||||
authors = ["OpenPype Team <info@openpype.io>"]
|
||||
license = "MIT License"
|
||||
|
|
|
|||
3
setup.py
3
setup.py
|
|
@ -187,5 +187,6 @@ setup(
|
|||
"build_dir": (openpype_root / "docs" / "build").as_posix()
|
||||
}
|
||||
},
|
||||
executables=executables
|
||||
executables=executables,
|
||||
packages=[]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -180,6 +180,9 @@ $out = & "$($env:POETRY_HOME)\bin\poetry" run python setup.py build 2>&1
|
|||
Set-Content -Path "$($openpype_root)\build\build.log" -Value $out
|
||||
if ($LASTEXITCODE -ne 0)
|
||||
{
|
||||
Write-Host "------------------------------------------" -ForegroundColor Red
|
||||
Get-Content "$($openpype_root)\build\build.log"
|
||||
Write-Host "------------------------------------------" -ForegroundColor Red
|
||||
Write-Host "!!! " -NoNewLine -ForegroundColor Red
|
||||
Write-Host "Build failed. Check the log: " -NoNewline
|
||||
Write-Host ".\build\build.log" -ForegroundColor Yellow
|
||||
|
|
|
|||
|
|
@ -185,9 +185,9 @@ if [ "$disable_submodule_update" == 1 ]; then
|
|||
fi
|
||||
echo -e "${BIGreen}>>>${RST} Building ..."
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
"$POETRY_HOME/bin/poetry" run python "$openpype_root/setup.py" build &> "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return 1; }
|
||||
"$POETRY_HOME/bin/poetry" run python "$openpype_root/setup.py" build &> "$openpype_root/build/build.log" || { echo -e "${BIRed}------------------------------------------${RST}"; cat "$openpype_root/build/build.log"; echo -e "${BIRed}------------------------------------------${RST}"; echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return 1; }
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
"$POETRY_HOME/bin/poetry" run python "$openpype_root/setup.py" bdist_mac &> "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return 1; }
|
||||
"$POETRY_HOME/bin/poetry" run python "$openpype_root/setup.py" bdist_mac &> "$openpype_root/build/build.log" || { echo -e "${BIRed}------------------------------------------${RST}"; cat "$openpype_root/build/build.log"; echo -e "${BIRed}------------------------------------------${RST}"; echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return 1; }
|
||||
fi
|
||||
"$POETRY_HOME/bin/poetry" run python "$openpype_root/tools/build_dependencies.py"
|
||||
|
||||
|
|
|
|||
BIN
website/docs/assets/publisher_card_view.png
Normal file
BIN
website/docs/assets/publisher_card_view.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
BIN
website/docs/assets/publisher_create_dialog.png
Normal file
BIN
website/docs/assets/publisher_create_dialog.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
BIN
website/docs/assets/publisher_list_view.png
Normal file
BIN
website/docs/assets/publisher_list_view.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
|
|
@ -2,19 +2,22 @@
|
|||
id: dev_publishing
|
||||
title: Publishing
|
||||
sidebar_label: Publishing
|
||||
toc_max_heading_level: 4
|
||||
---
|
||||
|
||||
Publishing workflow consist of 2 parts:
|
||||
- Creation - Mark what will be published and how.
|
||||
- Publishing - Use data from creation to go through pyblish process.
|
||||
Publishing workflow consists of 2 parts:
|
||||
- Creating - Mark what will be published and how.
|
||||
- Publishing - Use data from Creating to go through the pyblish process.
|
||||
|
||||
OpenPype is using [pyblish](https://pyblish.com/) for publishing process. OpenPype a little bit extend and modify few functions mainly for reports and UI purposes. The main differences are that OpenPype's publish UI allows to enable/disable instances or plugins during creation part instead of in publishing part and has limited plugin actions only for failed validation plugins.
|
||||
OpenPype is using [pyblish](https://pyblish.com/) for the publishing process. OpenPype extends and modifies its few functions a bit, mainly for reports and UI purposes. The main differences are that OpenPype's publish UI allows to enable/disable instances or plugins during Creating part instead of in the publishing part and has limited plugin actions only for failed validation plugins.
|
||||
|
||||
# Creation
|
||||
Concept of creation does not have to "create" anything but prepare and store metadata about an "instance" (becomes a subset after publish process). Created instance always has `family` which defines what kind of data will be published, best example is `workfile` family. Storing of metadata is host specific and may be even a Creator plugin specific. In most of hosts are metadata stored to workfile (Maya scene, Nuke script, etc.) to an item or a node the same way so consistency of host implementation is kept, but some features may require different approach that is the reason why it is creator plugin responsibility. Storing the metadata to workfile gives ability to keep values so artist does not have to do create and set what should be published and how over and over.
|
||||
## **Creating**
|
||||
|
||||
## Created instance
|
||||
Objected representation of created instance metadata defined by class **CreatedInstance**. Has access to **CreateContext** and **BaseCreator** that initialized the object. Is dictionary like object with few immutable keys (marked with start `*` in table). The immutable keys are set by creator plugin or create context on initialization and thei values can't change. Instance can have more arbitrary data, for example ids of nodes in scene but keep in mind that some keys are reserved.
|
||||
Concept of Creating does not have to "create" anything yet, but prepare and store metadata about an "instance" (becomes a subset after the publish process). Created instance always has `family` which defines what kind of data will be published, the best example is `workfile` family. Storing of metadata is host specific and may be even a Creator plugin specific. Most hosts are storing metadata into a workfile (Maya scene, Nuke script, etc.) to an item or a node the same way as regular Pyblish instances, so consistency of host implementation is kept, but some features may require a different approach that is the reason why it is creator plugin responsibility. Storing the metadata to the workfile persists values, so the artist does not have to create and set what should be published and how over and over.
|
||||
|
||||
### Created instance
|
||||
|
||||
Objected representation of created instance metadata defined by class **CreatedInstance**. Has access to **CreateContext** and **BaseCreator** that initialized the object. Is a dictionary-like object with few immutable keys (marked with start `*` in table). The immutable keys are set by the creator plugin or create context on initialization and their values can't change. Instance can have more arbitrary data, for example ids of nodes in scene but keep in mind that some keys are reserved.
|
||||
|
||||
| Key | Type | Description |
|
||||
|---|---|---|
|
||||
|
|
@ -22,33 +25,34 @@ Objected representation of created instance metadata defined by class **CreatedI
|
|||
| *instance_id | str | Unique ID of instance. Set automatically on instance creation using `str(uuid.uuid4())` |
|
||||
| *family | str | Instance's family representing type defined by creator plugin. |
|
||||
| *creator_identifier | str | Identifier of creator that collected/created the instance. |
|
||||
| *creator_attributes | dict | Dictionary of attributes that are defined by creator plugin (`get_instance_attr_defs`). |
|
||||
| *creator_attributes | dict | Dictionary of attributes that are defined by the creator plugin (`get_instance_attr_defs`). |
|
||||
| *publish_attributes | dict | Dictionary of attributes that are defined by publish plugins. |
|
||||
| variant | str | Variant is entered by artist on creation and may affect **subset**. |
|
||||
| subset | str | Name of instance. This name will be used as subset name during publishing. Can be changed on context change or variant change. |
|
||||
| active | bool | Is instance active and will be published or not. |
|
||||
| variant | str | Variant is entered by the artist on creation and may affect **subset**. |
|
||||
| subset | str | Name of instance. This name will be used as a subset name during publishing. Can be changed on context change or variant change. |
|
||||
| active | bool | Is the instance active and will be published or not. |
|
||||
| asset | str | Name of asset in which context was created. |
|
||||
| task | str | Name of task in which context was created. Can be set to `None`. |
|
||||
|
||||
:::note
|
||||
Task should not be required until subset name template expect it.
|
||||
Task should not be required until the subset name template expects it.
|
||||
:::
|
||||
|
||||
object of **CreatedInstance** has method **data_to_store** which returns dictionary that can be parsed to json string. This method will return all data related to instance so can be re-created using `CreatedInstance.from_existing(data)`.
|
||||
object of **CreatedInstance** has method **data_to_store** which returns a dictionary that can be parsed to a json string. This method will return all data related to the instance so it can be re-created using `CreatedInstance.from_existing(data)`.
|
||||
|
||||
## Create context
|
||||
Controller and wrapper around creation is `CreateContext` which cares about loading of plugins needed for creation. And validates required functions in host implementation.
|
||||
#### *Create context* {#category-doc-link}
|
||||
|
||||
Context discovers creator and publish plugins. Trigger collections of existing instances on creators and trigger creation itself. Also keeps in mind instance objects by their ids.
|
||||
Controller and wrapper around Creating is `CreateContext` which cares about loading of plugins needed for Creating. And validates required functions in host implementation.
|
||||
|
||||
Creator plugins can call **creator_adds_instance** or **creator_removed_instance** to add/remove instance but these methods are not meant to be called directly out of creator. The reason is that is creator's responsibility to remove metadata or decide if should remove the instance.
|
||||
Context discovers creator and publish plugins. Trigger collections of existing instances on creators and trigger Creating itself. Also it keeps in mind instance objects by their ids.
|
||||
|
||||
### Required functions in host implementation
|
||||
Host implementation **must** have implemented **get_context_data** and **update_context_data**. These two functions are needed to store metadata that are not related to any instane but are needed for creation and publishing process. Right now are there stored data about enabled/disabled optional publish plugins. When data are not stored and loaded properly reset of publishing will cause that they will be set to default value. Similar to instance data can be context data also parsed to json string.
|
||||
Creator plugins can call **creator_adds_instance** or **creator_removed_instance** to add/remove instances but these methods are not meant to be called directly out of the creator. The reason is that it is the creator's responsibility to remove metadata or decide if it should remove the instance.
|
||||
|
||||
There are also few optional functions. For UI purposes it is possible to implement **get_context_title** which can return string showed in UI as a title. Output string may contain html tags. It is recommended to return context path (it will be created function this purposes) in this order `"{project name}/{asset hierarchy}/<b>{asset name}</b>/{task name}"`.
|
||||
#### Required functions in host implementation
|
||||
Host implementation **must** implement **get_context_data** and **update_context_data**. These two functions are needed to store metadata that are not related to any instance but are needed for Creating and publishing process. Right now only data about enabled/disabled optional publish plugins is stored there. When data is not stored and loaded properly, reset of publishing will cause that they will be set to default value. Context data also parsed to json string similarly as instance data.
|
||||
|
||||
Another optional function is **get_current_context**. This function is handy in hosts where is possible to open multiple workfiles in one process so using global context variables is not relevant because artist can switch between opened workfiles without being acknowledged. When function is not implemented or won't return right keys the global context is used.
|
||||
There are also few optional functions. For UI purposes it is possible to implement **get_context_title** which can return a string shown in UI as a title. Output string may contain html tags. It is recommended to return context path (it will be created function this purposes) in this order `"{project name}/{asset hierarchy}/<b>{asset name}</b>/{task name}"`.
|
||||
|
||||
Another optional function is **get_current_context**. This function is handy in hosts where it is possible to open multiple workfiles in one process so using global context variables is not relevant because artists can switch between opened workfiles without being acknowledged. When a function is not implemented or won't return the right keys the global context is used.
|
||||
```json
|
||||
# Expected keys in output
|
||||
{
|
||||
|
|
@ -58,10 +62,10 @@ Another optional function is **get_current_context**. This function is handy in
|
|||
}
|
||||
```
|
||||
|
||||
## Create plugin
|
||||
### Create plugin
|
||||
Main responsibility of create plugin is to create, update, collect and remove instance metadata and propagate changes to create context. Has access to **CreateContext** (`self.create_context`) that discovered the plugin so has also access to other creators and instances. Create plugins have a lot of responsibility so it is recommended to implement common code per host.
|
||||
|
||||
### BaseCreator
|
||||
#### *BaseCreator*
|
||||
Base implementation of creator plugin. It is not recommended to use this class as base for production plugins but rather use one of **AutoCreator** and **Creator** variants.
|
||||
|
||||
**Abstractions**
|
||||
|
|
@ -71,7 +75,7 @@ class WorkfileCreator(Creator):
|
|||
family = "workfile"
|
||||
```
|
||||
|
||||
- **`collect_instances`** (method) - Collect already existing instances from workfile and add them to create context. This method is called on initialization or reset of **CreateContext**. Each creator is responsible to find it's instances metadata, convert them to **CreatedInstance** object and add the to create context (`self._add_instance_to_context(instnace_obj)`).
|
||||
- **`collect_instances`** (method) - Collect already existing instances from the workfile and add them to create context. This method is called on initialization or reset of **CreateContext**. Each creator is responsible to find its instance metadata, convert them to **CreatedInstance** object and add them to create context (`self._add_instance_to_context(instnace_obj)`).
|
||||
```python
|
||||
def collect_instances(self):
|
||||
# Using 'pipeline.list_instances' is just example how to get existing instances from scene
|
||||
|
|
@ -88,7 +92,7 @@ def collect_instances(self):
|
|||
self._add_instance_to_context(instance)
|
||||
```
|
||||
|
||||
- **`create`** (method) - Create new object of **CreatedInstance** store it's metadata to workfile and add the instance into create context. Failed creation should raise **CreatorError** if happens error that can artist fix or give him some useful information. Trigger and implementation differs for **Creator** and **AutoCreator**.
|
||||
- **`create`** (method) - Create a new object of **CreatedInstance** store its metadata to the workfile and add the instance into the created context. Failed Creating should raise **CreatorError** if an error happens that artists can fix or give them some useful information. Triggers and implementation differs for **Creator** and **AutoCreator**.
|
||||
|
||||
- **`update_instances`** (method) - Update data of instances. Receives tuple with **instance** and **changes**.
|
||||
```python
|
||||
|
|
@ -141,8 +145,8 @@ When host implementation use universal way how to store and load instances you s
|
|||
|
||||
**Optional implementations**
|
||||
|
||||
- **`enabled`** (attr) - Boolean if creator plugin is enabled and used.
|
||||
- **`identifier`** (class attr) - Consistent unique string identifier of the creator plugin. Is used to identify source plugin of existing instances. There can't be 2 creator plugins with same identifier. Default implementation returns `family` attribute.
|
||||
- **`enabled`** (attr) - Boolean if the creator plugin is enabled and used.
|
||||
- **`identifier`** (class attr) - Consistent unique string identifier of the creator plugin. Is used to identify source plugin of existing instances. There can't be 2 creator plugins with the same identifier. Default implementation returns `family` attribute.
|
||||
```python
|
||||
class RenderLayerCreator(Creator):
|
||||
family = "render"
|
||||
|
|
@ -154,13 +158,13 @@ class RenderPassCreator(Creator):
|
|||
identifier = "render_pass"
|
||||
```
|
||||
|
||||
- **`label`** (attr) - String label of creator plugin which will showed in UI, `identifier` is used when not set. It should be possible to use html tags.
|
||||
- **`label`** (attr) - String label of creator plugin which will show up in UI, `identifier` is used when not set. It should be possible to use html tags.
|
||||
```python
|
||||
class RenderLayerCreator(Creator):
|
||||
label = "Render Layer"
|
||||
```
|
||||
|
||||
- **`get_icon`** (attr) - Icon of creator and it's instances. Value can be a path to image file, full name of qtawesome icon, `QPixmap` or `QIcon`. For complex cases or cases when `Qt` objects are returned it is recommended to override `get_icon` method and handle the logic or import `Qt` inside the method to not break headless usage of creator plugin. For list of qtawesome icons check qtawesome github repository (look for used version in pyproject.toml). Default implementation return **icon** attribute.
|
||||
- **`get_icon`** (attr) - Icon of creator and its instances. Value can be a path to an image file, full name of qtawesome icon, `QPixmap` or `QIcon`. For complex cases or cases when `Qt` objects are returned it is recommended to override `get_icon` method and handle the logic or import `Qt` inside the method to not break headless usage of creator plugin. For list of qtawesome icons check qtawesome github repository (look for the used version in pyproject.toml). Default implementation return **icon** attribute.
|
||||
- **`icon`** (method) - Attribute for default implementation of **get_icon**.
|
||||
```python
|
||||
class RenderLayerCreator(Creator):
|
||||
|
|
@ -168,7 +172,7 @@ class RenderLayerCreator(Creator):
|
|||
icon = "fa5.building"
|
||||
```
|
||||
|
||||
- **`get_instance_attr_defs`** (method) - Attribute definitions of instance. Creator can define attribute values with default values for each instance. These attributes may affect how will be instance processed during publishing. Attribute defiitions can be used from `openpype.pipeline.lib.attribute_definitions` (NOTE: Will be moved to `openpype.lib.attribute_definitions` soon). Attribute definitions define basic type of values for different cases e.g. boolean, number, string, enumerator, etc. Default implementations returns **instance_attr_defs**.
|
||||
- **`get_instance_attr_defs`** (method) - Attribute definitions of instance. Creator can define attribute values with default values for each instance. These attributes may affect how instances will be instance processed during publishing. Attribute defiitions can be used from `openpype.pipeline.lib.attribute_definitions` (NOTE: Will be moved to `openpype.lib.attribute_definitions` soon). Attribute definitions define basic types of values for different cases e.g. boolean, number, string, enumerator, etc. Default implementation returns **instance_attr_defs**.
|
||||
- **`instance_attr_defs`** (attr) - Attribute for default implementation of **get_instance_attr_defs**.
|
||||
|
||||
```python
|
||||
|
|
@ -190,16 +194,16 @@ class RenderLayerCreator(Creator):
|
|||
]
|
||||
```
|
||||
|
||||
- **`get_subset_name`** (method) - Calculate subset name based on passed data. Data can be extended using `get_dynamic_data` method. Default implementation is using `get_subset_name` from `openpype.lib` which is recommended.
|
||||
- **`get_subset_name`** (method) - Calculate subset name based on passed data. Data can be extended using the `get_dynamic_data` method. Default implementation is using `get_subset_name` from `openpype.lib` which is recommended.
|
||||
|
||||
- **`get_dynamic_data`** (method) - Can be used to extend data for subset template which may be required in some cases.
|
||||
- **`get_dynamic_data`** (method) - Can be used to extend data for subset templates which may be required in some cases.
|
||||
|
||||
|
||||
### AutoCreator
|
||||
Creator that is triggered on reset of create context. Can be used for families that are expected to be created automatically without artist interaction (e.g. **workfile**). Method `create` is triggered after collecting of all creators.
|
||||
#### *AutoCreator*
|
||||
Creator that is triggered on reset of create context. Can be used for families that are expected to be created automatically without artist interaction (e.g. **workfile**). Method `create` is triggered after collecting all creators.
|
||||
|
||||
:::important
|
||||
**AutoCreator** has implemented **remove_instances** to do nothing as removing of auto created instances would lead to create new instance immediately or on refresh.
|
||||
**AutoCreator** has implemented **remove_instances** to do nothing as removing of auto created instances would lead to creating new instance immediately or on refresh.
|
||||
:::
|
||||
|
||||
```python
|
||||
|
|
@ -268,14 +272,11 @@ def create(self):
|
|||
existing_instance["task"] = task_name
|
||||
```
|
||||
|
||||
### Creator
|
||||
Implementation of creator plugin that is triggered manually by artist in UI (or by code). Has extended options for UI purposes than **AutoCreator** and **create** method expect more arguments.
|
||||
|
||||
**Abstractions**
|
||||
- **`create`** (method) - Code where creation of metadata
|
||||
#### *Creator*
|
||||
Implementation of creator plugin that is triggered manually by the artist in UI (or by code). Has extended options for UI purposes than **AutoCreator** and **create** method expect more arguments.
|
||||
|
||||
**Optional implementations**
|
||||
- **`create_allow_context_change`** (class attr) - Allow to set context in UI before creation. Some creator may not allow it or their logic would not use the context selection (e.g. bulk creators). Is set to `True` but default.
|
||||
- **`create_allow_context_change`** (class attr) - Allow to set context in UI before Creating. Some creators may not allow it or their logic would not use the context selection (e.g. bulk creators). Is set to `True` but default.
|
||||
```python
|
||||
class BulkRenderCreator(Creator):
|
||||
create_allow_context_change = False
|
||||
|
|
@ -286,7 +287,7 @@ class BulkRenderCreator(Creator):
|
|||
- **`get_default_variant`** (method) - Returns default variant that is prefilled in UI (value does not have to be in default variants). By default returns **default_variant** attribute. If returns `None` then UI logic will take first item from **get_default_variants** if there is any otherwise **"Main"** is used.
|
||||
- **`default_variant`** (attr) - Attribute for default implementation of **get_default_variant**.
|
||||
|
||||
- **`get_description`** (method) - Returns short string description of creator. Returns **description** attribute by default.
|
||||
- **`get_description`** (method) - Returns a short string description of the creator. Returns **description** attribute by default.
|
||||
- **`description`** (attr) - Attribute for default implementation of **get_description**.
|
||||
|
||||
- **`get_detailed_description`** (method) - Returns detailed string description of creator. Can contain markdown. Returns **detailed_description** attribute by default.
|
||||
|
|
@ -391,39 +392,39 @@ class CreateRender(Creator):
|
|||
self._add_instance_to_context(new_instance)
|
||||
```
|
||||
|
||||
# Publish
|
||||
## Exceptions
|
||||
## **Publish**
|
||||
### Exceptions
|
||||
OpenPype define few specific exceptions that should be used in publish plugins.
|
||||
|
||||
### Validation exception
|
||||
Validation plugins should raise `PublishValidationError` to show to an artist what's wrong and give him actions to fix it. The exception says that error happened in plugin can be fixed by artist himself (with or without action on plugin). Any other errors will stop publishing immediately. Exception `PublishValidationError` raised after validation order has same effect as any other exception.
|
||||
#### *Validation exception*
|
||||
Validation plugins should raise `PublishValidationError` to show to an artist what's wrong and give him actions to fix it. The exception says that errors in the plugin can be fixed by the artist himself (with or without action on plugin). Any other errors will stop publishing immediately. The exception `PublishValidationError` raised after validation order has the same effect as any other exception.
|
||||
|
||||
Exception `PublishValidationError` expects 4 arguments:
|
||||
- **message** Which is not used in UI but for headless publishing.
|
||||
- **title** Short description of error (2-5 words). Title is used for grouping of exceptions per plugin.
|
||||
- **description** Detailed description of happened issue where markdown and html can be used.
|
||||
- **detail** Is optional to give even more detailed information for advanced users. At this moment is detail showed under description but it is in plan to have detail in collapsible widget.
|
||||
- **description** Detailed description of the issue where markdown and html can be used.
|
||||
- **detail** Is optional to give even more detailed information for advanced users. At this moment the detail is shown directly under description but it is in plan to have detail in a collapsible widget.
|
||||
|
||||
Extended version is `PublishXmlValidationError` which uses xml files with stored descriptions. This helps to avoid having huge markdown texts inside code. The exception has 4 arguments:
|
||||
- **plugin** The plugin object which raises the exception to find it's related xml file.
|
||||
- **plugin** The plugin object which raises the exception to find its related xml file.
|
||||
- **message** Exception message for publishing without UI or different pyblish UI.
|
||||
- **key** Optional argument says which error from xml is used as validation plugin may raise error with different messages based on the current errors. Default is **"main"**.
|
||||
- **key** Optional argument says which error from xml is used as a validation plugin may raise error with different messages based on the current errors. Default is **"main"**.
|
||||
- **formatting_data** Optional dictionary to format data in the error. This is used to fill detailed description with data from the publishing so artist can get more precise information.
|
||||
|
||||
**Where and how to create xml file**
|
||||
|
||||
Xml files for `PublishXmlValidationError` must be located in **./help** subfolder next to plugin and the filename must match the filename of plugin.
|
||||
Xml files for `PublishXmlValidationError` must be located in **./help** subfolder next to the plugin and the filename must match the filename of the plugin.
|
||||
```
|
||||
# File location related to plugin file
|
||||
└ publish
|
||||
├ help
|
||||
│ ├ validate_scene.xml
|
||||
│ └ ...
|
||||
├ validate_scene.py
|
||||
└ ...
|
||||
├ help
|
||||
│ ├ validate_scene.xml
|
||||
│ └ ...
|
||||
├ validate_scene.py
|
||||
└ ...
|
||||
```
|
||||
|
||||
Xml file content has **<root>** node which may contain any amount of **<error>** nodes, but each of them must have **id** attribute with unique value. That is then used for **key**. Each error must have **<title>** and **<description>** and **<detail>**. Text content may contain python formatting keys that can be filled when exception is raised.
|
||||
Xml file content has **<root>** node which may contain any amount of **<error>** nodes, but each of them must have **id** attribute with unique value. That is then used for **key**. Each error must have **<title>** and **<description>** and **<detail>**. Text content may contain python formatting keys that can be filled when an exception is raised.
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
|
|
@ -435,7 +436,7 @@ Context of the given subset doesn't match your current scene.
|
|||
|
||||
### How to repair?
|
||||
|
||||
Yout can fix this with "Repair" button on the right. This will use '{expected_asset}' asset name and overwrite '{found_asset}' asset name in scene metadata.
|
||||
You can fix this with the "Repair" button on the right. This will use '{expected_asset}' asset name and overwrite '{found_asset}' asset name in scene metadata.
|
||||
|
||||
After that restart publishing with Reload button.
|
||||
</description>
|
||||
|
|
@ -449,20 +450,24 @@ or the scene file was copy pasted from different context.
|
|||
</root>
|
||||
```
|
||||
|
||||
### Known errors
|
||||
When there is a known error that can't be fixed by user (e.g. can't connect to deadline service, etc.) `KnownPublishError` should be raise. The only difference is that it's message is shown in UI to artist otherwise a neutral message without context is shown.
|
||||
#### *Known errors*
|
||||
When there is a known error that can't be fixed by the user (e.g. can't connect to deadline service, etc.) `KnownPublishError` should be raised. The only difference is that its message is shown in UI to the artist otherwise a neutral message without context is shown.
|
||||
|
||||
## Plugin extension
|
||||
Publish plugins can be extended by additional logic when inherits from `OpenPypePyblishPluginMixin` which can be used as mixin (additional inheritance of class). Publish plugins that inherit from this mixin can define attributes that will be shown in **CreatedInstance**. One of most important usages is to be able turn on/off optional plugins.
|
||||
### Plugin extension
|
||||
Publish plugins can be extended by additional logic when inheriting from `OpenPypePyblishPluginMixin` which can be used as mixin (additional inheritance of class). Publish plugins that inherit from this mixin can define attributes that will be shown in **CreatedInstance**. One of the most important usages is to be able turn on/off optional plugins.
|
||||
|
||||
Attributes are defined by return value of `get_attribute_defs` method. Attribute definitions are for families defined in plugin's `families` attribute if it's instance plugin or for whole context if it's context plugin. To convert existing values (or to remove legacy values) can be re-implemented `convert_attribute_values`. Default implementation just converts the values to right types.
|
||||
Attributes are defined by the return value of `get_attribute_defs` method. Attribute definitions are for families defined in plugin's `families` attribute if it's instance plugin or for whole context if it's context plugin. To convert existing values (or to remove legacy values) can be re-implemented `convert_attribute_values`. Default implementation just converts the values to right types.
|
||||
|
||||
:::important
|
||||
Values of publish attributes from created instance are never removed automatically so implementing of this method is best way to remove legacy data or convert them to new data structure.
|
||||
:::Important
|
||||
Values of publish attributes from created instance are never removed automatically so implementing this method is the best way to remove legacy data or convert them to new data structure.
|
||||
:::
|
||||
|
||||
Possible attribute definitions can be found in `openpype/pipeline/lib/attribute_definitions.py`.
|
||||
|
||||
<details>
|
||||
<summary>Example plugin</summary>
|
||||
<p>
|
||||
|
||||
```python
|
||||
import pyblish.api
|
||||
from openpype.pipeline import (
|
||||
|
|
@ -496,16 +501,13 @@ class MyExtendedPlugin(
|
|||
if not self.optional:
|
||||
return True
|
||||
|
||||
# Attribute values are stored by class names
|
||||
# - for those purposes was implemented 'get_attr_values_from_data'
|
||||
# to help with accessing it
|
||||
attribute_values = self.get_attr_values_from_data(context.data)
|
||||
# Get 'process' key
|
||||
process_value = (
|
||||
context.data
|
||||
.get("publish_attributes", {})
|
||||
# Attribute values are stored by class names
|
||||
.get(self.__class__.__name__, {})
|
||||
# Access the key
|
||||
.get("process")
|
||||
)
|
||||
if process_value or process_value is None:
|
||||
process_value = attribute_values.get("process")
|
||||
if process_value is None or process_value:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
@ -515,3 +517,32 @@ class MyExtendedPlugin(
|
|||
# Do plugin logic
|
||||
...
|
||||
```
|
||||
</p>
|
||||
</details>
|
||||
|
||||
## **UI examples**
|
||||
### Main publish window
|
||||
Main window of publisher shows instances and their values, collected by creators.
|
||||
|
||||
**Card view**
|
||||

|
||||
**List view**
|
||||

|
||||
|
||||
#### *Instances views*
|
||||
List of instances always contains an `Options` item which is used to show attributes of context plugins. Values from the item are saved and loaded using [host implementation](#required-functions-in-host-implementation) **get_context_data** and **update_context_data**. Instances are grouped by family and can be shown in card view (single selection) or list view (multi selection).
|
||||
|
||||
Instance view has at the bottom 3 buttons. Plus sign opens [create dialog](#create-dialog), bin removes selected instances and stripes swap card and list view.
|
||||
|
||||
#### *Context options*
|
||||
It is possible to change variant or asset and task context of instances at the top part but all changes there must be confirmed. Confirmation will trigger recalculation of subset names and all new data are stored to instances.
|
||||
|
||||
#### *Create attributes*
|
||||
Instance attributes display all created attributes of all selected instances. All attributes that have the same definition are grouped into one input and are visually indicated if values are not the same for selected instances. In most cases have **< Multiselection >** placeholder.
|
||||
|
||||
#### *Publish attributes*
|
||||
Publish attributes work the same way as create attributes but the source of attribute definitions are pyblish plugins. Attributes are filtered based on families of selected instances and families defined in the pyblish plugin.
|
||||
|
||||
### Create dialog
|
||||

|
||||
Create dialog is used by artist to create new instances in a context. The context selection can be enabled/disabled by changing `create_allow_context_change` on [creator plugin](#creator). In the middle part the artist selects what will be created and what variant it is. On the right side is information about the selected creator and its pre-create attributes. There is also a question mark button which extends the window and displays more detailed information about the creator.
|
||||
|
|
@ -5125,9 +5125,9 @@ minimatch@^3.0.4:
|
|||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
mkdirp@^0.5.5:
|
||||
version "0.5.5"
|
||||
|
|
@ -5207,9 +5207,9 @@ node-fetch@2.6.7:
|
|||
whatwg-url "^5.0.0"
|
||||
|
||||
node-forge@^1.2.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c"
|
||||
integrity sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.0.tgz#37a874ea723855f37db091e6c186e5b67a01d4b2"
|
||||
integrity sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==
|
||||
|
||||
node-releases@^2.0.1:
|
||||
version "2.0.2"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue