diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py
index dacfd338bb..ea61dc4785 100644
--- a/openpype/hosts/hiero/api/pipeline.py
+++ b/openpype/hosts/hiero/api/pipeline.py
@@ -251,7 +251,6 @@ def reload_config():
import importlib
for module in (
- "openpype.api",
"openpype.hosts.hiero.lib",
"openpype.hosts.hiero.menu",
"openpype.hosts.hiero.tags"
diff --git a/openpype/hosts/hiero/plugins/publish/integrate_version_up_workfile.py b/openpype/hosts/hiero/plugins/publish/integrate_version_up_workfile.py
index 934e7112fa..6ccbe955f2 100644
--- a/openpype/hosts/hiero/plugins/publish/integrate_version_up_workfile.py
+++ b/openpype/hosts/hiero/plugins/publish/integrate_version_up_workfile.py
@@ -1,5 +1,6 @@
from pyblish import api
-import openpype.api as pype
+
+from openpype.lib import version_up
class IntegrateVersionUpWorkfile(api.ContextPlugin):
@@ -15,7 +16,7 @@ class IntegrateVersionUpWorkfile(api.ContextPlugin):
def process(self, context):
project = context.data["activeProject"]
path = context.data.get("currentFile")
- new_path = pype.version_up(path)
+ new_path = version_up(path)
if project:
project.saveAs(new_path)
diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py
index 7db420f6af..c343c635fa 100644
--- a/openpype/hosts/nuke/api/pipeline.py
+++ b/openpype/hosts/nuke/api/pipeline.py
@@ -66,7 +66,6 @@ def reload_config():
"""
for module in (
- "openpype.api",
"openpype.hosts.nuke.api.actions",
"openpype.hosts.nuke.api.menu",
"openpype.hosts.nuke.api.plugin",
diff --git a/openpype/hosts/nuke/plugins/publish/precollect_workfile.py b/openpype/hosts/nuke/plugins/publish/precollect_workfile.py
index 822f405a6f..316c651b66 100644
--- a/openpype/hosts/nuke/plugins/publish/precollect_workfile.py
+++ b/openpype/hosts/nuke/plugins/publish/precollect_workfile.py
@@ -3,7 +3,8 @@ import os
import nuke
import pyblish.api
-import openpype.api as pype
+
+from openpype.lib import get_version_from_path
from openpype.hosts.nuke.api.lib import (
add_publish_knob,
get_avalon_knob_data
@@ -74,7 +75,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin):
"fps": root['fps'].value(),
"currentFile": current_file,
- "version": int(pype.get_version_from_path(current_file)),
+ "version": int(get_version_from_path(current_file)),
"host": pyblish.api.current_host(),
"hostVersion": nuke.NUKE_VERSION_STRING
diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py
index 89c25389cb..2cb5a8729f 100644
--- a/openpype/hosts/traypublisher/api/plugin.py
+++ b/openpype/hosts/traypublisher/api/plugin.py
@@ -17,11 +17,27 @@ from openpype.lib.transcoding import IMAGE_EXTENSIONS, VIDEO_EXTENSIONS
REVIEW_EXTENSIONS = IMAGE_EXTENSIONS + VIDEO_EXTENSIONS
+def _cache_and_get_instances(creator):
+ """Cache instances in shared data.
+
+ Args:
+ creator (Creator): Plugin which would like to get instances from host.
+
+ Returns:
+ List[Dict[str, Any]]: Cached instances list from host implementation.
+ """
+
+ shared_key = "openpype.traypublisher.instances"
+ if shared_key not in creator.collection_shared_data:
+ creator.collection_shared_data[shared_key] = list_instances()
+ return creator.collection_shared_data[shared_key]
+
+
class HiddenTrayPublishCreator(HiddenCreator):
host_name = "traypublisher"
def collect_instances(self):
- for instance_data in list_instances():
+ for instance_data in _cache_and_get_instances():
creator_id = instance_data.get("creator_identifier")
if creator_id == self.identifier:
instance = CreatedInstance.from_existing(
@@ -58,7 +74,7 @@ class TrayPublishCreator(Creator):
host_name = "traypublisher"
def collect_instances(self):
- for instance_data in list_instances():
+ for instance_data in _cache_and_get_instances():
creator_id = instance_data.get("creator_identifier")
if creator_id == self.identifier:
instance = CreatedInstance.from_existing(
diff --git a/openpype/hosts/unreal/api/__init__.py b/openpype/hosts/unreal/api/__init__.py
index 870982f5f9..3f96d8ac6f 100644
--- a/openpype/hosts/unreal/api/__init__.py
+++ b/openpype/hosts/unreal/api/__init__.py
@@ -1,10 +1,8 @@
# -*- coding: utf-8 -*-
"""Unreal Editor OpenPype host API."""
-from .plugin import (
- Loader,
- Creator
-)
+from .plugin import Loader
+
from .pipeline import (
install,
uninstall,
@@ -25,7 +23,6 @@ from .pipeline import (
__all__ = [
"install",
"uninstall",
- "Creator",
"Loader",
"ls",
"publish",
diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py
index d8d2f2420d..6fc00cb71c 100644
--- a/openpype/hosts/unreal/api/plugin.py
+++ b/openpype/hosts/unreal/api/plugin.py
@@ -1,16 +1,7 @@
# -*- coding: utf-8 -*-
from abc import ABC
-from openpype.pipeline import (
- LegacyCreator,
- LoaderPlugin,
-)
-
-
-class Creator(LegacyCreator):
- """This serves as skeleton for future OpenPype specific functionality"""
- defaults = ['Main']
- maintain_selection = False
+from openpype.pipeline import LoaderPlugin
class Loader(LoaderPlugin, ABC):
diff --git a/openpype/hosts/unreal/plugins/create/create_camera.py b/openpype/hosts/unreal/plugins/create/create_camera.py
index 2842900834..bf1489d688 100644
--- a/openpype/hosts/unreal/plugins/create/create_camera.py
+++ b/openpype/hosts/unreal/plugins/create/create_camera.py
@@ -2,11 +2,11 @@ import unreal
from unreal import EditorAssetLibrary as eal
from unreal import EditorLevelLibrary as ell
-from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api.pipeline import instantiate
+from openpype.pipeline import LegacyCreator
-class CreateCamera(plugin.Creator):
+class CreateCamera(LegacyCreator):
"""Layout output for character rigs"""
name = "layoutMain"
diff --git a/openpype/hosts/unreal/plugins/create/create_layout.py b/openpype/hosts/unreal/plugins/create/create_layout.py
index 5fef08ce2a..c1067b00d9 100644
--- a/openpype/hosts/unreal/plugins/create/create_layout.py
+++ b/openpype/hosts/unreal/plugins/create/create_layout.py
@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
from unreal import EditorLevelLibrary
-from openpype.hosts.unreal.api import plugin
+from openpype.pipeline import LegacyCreator
from openpype.hosts.unreal.api.pipeline import instantiate
-class CreateLayout(plugin.Creator):
+class CreateLayout(LegacyCreator):
"""Layout output for character rigs."""
name = "layoutMain"
diff --git a/openpype/hosts/unreal/plugins/create/create_look.py b/openpype/hosts/unreal/plugins/create/create_look.py
index 12f6b70ae6..4abf3f6095 100644
--- a/openpype/hosts/unreal/plugins/create/create_look.py
+++ b/openpype/hosts/unreal/plugins/create/create_look.py
@@ -2,9 +2,10 @@
"""Create look in Unreal."""
import unreal # noqa
from openpype.hosts.unreal.api import pipeline, plugin
+from openpype.pipeline import LegacyCreator
-class CreateLook(plugin.Creator):
+class CreateLook(LegacyCreator):
"""Shader connections defining shape look."""
name = "unrealLook"
diff --git a/openpype/hosts/unreal/plugins/create/create_render.py b/openpype/hosts/unreal/plugins/create/create_render.py
index 950799cc10..a85d17421b 100644
--- a/openpype/hosts/unreal/plugins/create/create_render.py
+++ b/openpype/hosts/unreal/plugins/create/create_render.py
@@ -1,10 +1,10 @@
import unreal
from openpype.hosts.unreal.api import pipeline
-from openpype.hosts.unreal.api.plugin import Creator
+from openpype.pipeline import LegacyCreator
-class CreateRender(Creator):
+class CreateRender(LegacyCreator):
"""Create instance for sequence for rendering"""
name = "unrealRender"
diff --git a/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py b/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py
index 601c2fae06..45d517d27d 100644
--- a/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py
+++ b/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py
@@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
"""Create Static Meshes as FBX geometry."""
import unreal # noqa
-from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api.pipeline import (
instantiate,
)
+from openpype.pipeline import LegacyCreator
-class CreateStaticMeshFBX(plugin.Creator):
+class CreateStaticMeshFBX(LegacyCreator):
"""Static FBX geometry."""
name = "unrealStaticMeshMain"
diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py
index 2dfdfc142f..c6362ee4c3 100644
--- a/openpype/pipeline/create/context.py
+++ b/openpype/pipeline/create/context.py
@@ -30,6 +30,11 @@ from .creator_plugins import (
UpdateData = collections.namedtuple("UpdateData", ["instance", "changes"])
+class UnavailableSharedData(Exception):
+ """Shared data are not available at the moment when are accessed."""
+ pass
+
+
class ImmutableKeyError(TypeError):
"""Accessed key is immutable so does not allow changes or removements."""
@@ -999,6 +1004,9 @@ class CreateContext:
self._bulk_counter = 0
self._bulk_instances_to_process = []
+ # Shared data across creators during collection phase
+ self._collection_shared_data = None
+
# Trigger reset if was enabled
if reset:
self.reset(discover_publish_plugins)
@@ -1054,6 +1062,9 @@ class CreateContext:
All changes will be lost if were not saved explicitely.
"""
+
+ self.reset_preparation()
+
self.reset_avalon_context()
self.reset_plugins(discover_publish_plugins)
self.reset_context_data()
@@ -1062,6 +1073,20 @@ class CreateContext:
self.reset_instances()
self.execute_autocreators()
+ self.reset_finalization()
+
+ def reset_preparation(self):
+ """Prepare attributes that must be prepared/cleaned before reset."""
+
+ # Give ability to store shared data for collection phase
+ self._collection_shared_data = {}
+
+ def reset_finalization(self):
+ """Cleanup of attributes after reset."""
+
+ # Stop access to collection shared data
+ self._collection_shared_data = None
+
def reset_avalon_context(self):
"""Give ability to reset avalon context.
@@ -1626,3 +1651,20 @@ class CreateContext:
if not plugin.__instanceEnabled__:
plugins.append(plugin)
return plugins
+
+ @property
+ def collection_shared_data(self):
+ """Access to shared data that can be used during creator's collection.
+
+ Retruns:
+ Dict[str, Any]: Shared data.
+
+ Raises:
+ UnavailableSharedData: When called out of collection phase.
+ """
+
+ if self._collection_shared_data is None:
+ raise UnavailableSharedData(
+ "Accessed Collection shared data out of collection phase"
+ )
+ return self._collection_shared_data
diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py
index 05ba8902aa..97ee94c449 100644
--- a/openpype/pipeline/create/creator_plugins.py
+++ b/openpype/pipeline/create/creator_plugins.py
@@ -6,6 +6,7 @@ from abc import (
abstractmethod,
abstractproperty
)
+
import six
from openpype.settings import get_system_settings, get_project_settings
@@ -323,6 +324,19 @@ class BaseCreator:
return self.instance_attr_defs
+ @property
+ def collection_shared_data(self):
+ """Access to shared data that can be used during creator's collection.
+
+ Retruns:
+ Dict[str, Any]: Shared data.
+
+ Raises:
+ UnavailableSharedData: When called out of collection phase.
+ """
+
+ return self.create_context.collection_shared_data
+
class Creator(BaseCreator):
"""Creator that has more information for artist to show in UI.
diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py
index 280d2cf0a0..1c732bf3a7 100644
--- a/openpype/tools/publisher/control.py
+++ b/openpype/tools/publisher/control.py
@@ -1674,6 +1674,8 @@ class PublisherController(BasePublisherController):
self.host_is_valid = self._create_context.host_is_valid
+ self._create_context.reset_preparation()
+
# Reset avalon context
self._create_context.reset_avalon_context()
@@ -1684,6 +1686,8 @@ class PublisherController(BasePublisherController):
self._reset_publish()
self._reset_instances()
+ self._create_context.reset_finalization()
+
self._emit_event("controller.reset.finished")
self.emit_card_message("Refreshed..")
diff --git a/website/docs/dev_publishing.md b/website/docs/dev_publishing.md
index 7a6082a517..135f6cd985 100644
--- a/website/docs/dev_publishing.md
+++ b/website/docs/dev_publishing.md
@@ -47,10 +47,14 @@ Context discovers creator and publish plugins. Trigger collections of existing i
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.
-#### 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.
+During reset are re-cached Creator plugins, re-collected instances, refreshed host context and more. Object of `CreateContext` supply shared data during the reset. They can be used by creators to share same data needed during collection phase or during creation for autocreators.
-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}/{asset name}/{task name}"`.
+#### Required functions in host implementation
+It is recommended to use `HostBase` class (`from openpype.host import HostBase`) as base for host implementation with combination of `IPublishHost` interface (`from openpype.host import IPublishHost`). These abstract classes should guide you to fill missing attributes and methods.
+
+To sum them and in case host implementation is inheriting `HostBase` the 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.
+
+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}/{asset name}/{task name}"` (this is default implementation in `HostBase`).
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
@@ -68,6 +72,9 @@ Main responsibility of create plugin is to create, update, collect and remove in
#### *BaseCreator*
Base implementation of creator plugin. It is not recommended to use this class as base for production plugins but rather use one of **HiddenCreator**, **AutoCreator** and **Creator** variants.
+**Access to shared data**
+Functions to work with "Collection shared data" can be used during reset phase of `CreateContext`. Creators can cache there data that are common for them. For example list of nodes in scene. Methods are implemented on `CreateContext` but their usage is primarily for Create plugins as nothing else should use it. Each creator can access `collection_shared_data` attribute which is a dictionary where shared data can be stored.
+
**Abstractions**
- **`family`** (class attr) - Tells what kind of instance will be created.
```python