From 65672ccafdd362be171bd48c01c01a8349ac2089 Mon Sep 17 00:00:00 2001
From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Date: Thu, 14 Aug 2025 17:15:07 +0200
Subject: [PATCH 1/6] removed legacy create tool
---
client/ayon_core/tools/creator/model.py | 2 -
client/ayon_core/tools/creator/window.py | 1 -
client/ayon_core/tools/utils/host_tools.py | 60 +++++-----------------
3 files changed, 14 insertions(+), 49 deletions(-)
diff --git a/client/ayon_core/tools/creator/model.py b/client/ayon_core/tools/creator/model.py
index bf6c7380a1..16d24cc8bc 100644
--- a/client/ayon_core/tools/creator/model.py
+++ b/client/ayon_core/tools/creator/model.py
@@ -1,8 +1,6 @@
import uuid
from qtpy import QtGui, QtCore
-from ayon_core.pipeline import discover_legacy_creator_plugins
-
from . constants import (
PRODUCT_TYPE_ROLE,
ITEM_ID_ROLE
diff --git a/client/ayon_core/tools/creator/window.py b/client/ayon_core/tools/creator/window.py
index 5d1c0a272a..fe8ee86dcf 100644
--- a/client/ayon_core/tools/creator/window.py
+++ b/client/ayon_core/tools/creator/window.py
@@ -15,7 +15,6 @@ from ayon_core.pipeline import (
)
from ayon_core.pipeline.create import (
PRODUCT_NAME_ALLOWED_SYMBOLS,
- legacy_create,
CreatorError,
)
diff --git a/client/ayon_core/tools/utils/host_tools.py b/client/ayon_core/tools/utils/host_tools.py
index 3d356555f3..bfd008925b 100644
--- a/client/ayon_core/tools/utils/host_tools.py
+++ b/client/ayon_core/tools/utils/host_tools.py
@@ -31,7 +31,6 @@ class HostToolsHelper:
# Prepare attributes for all tools
self._workfiles_tool = None
self._loader_tool = None
- self._creator_tool = None
self._publisher_tool = None
self._subset_manager_tool = None
self._scene_inventory_tool = None
@@ -96,27 +95,6 @@ class HostToolsHelper:
loader_tool.refresh()
- def get_creator_tool(self, parent):
- """Create, cache and return creator tool window."""
- if self._creator_tool is None:
- from ayon_core.tools.creator import CreatorWindow
-
- creator_window = CreatorWindow(parent=parent or self._parent)
- self._creator_tool = creator_window
-
- return self._creator_tool
-
- def show_creator(self, parent=None):
- """Show tool to create new instantes for publishing."""
- with qt_app_context():
- creator_tool = self.get_creator_tool(parent)
- creator_tool.refresh()
- creator_tool.show()
-
- # Pull window to the front.
- creator_tool.raise_()
- creator_tool.activateWindow()
-
def get_subset_manager_tool(self, parent):
"""Create, cache and return subset manager tool window."""
if self._subset_manager_tool is None:
@@ -261,35 +239,32 @@ class HostToolsHelper:
if tool_name == "workfiles":
return self.get_workfiles_tool(parent, *args, **kwargs)
- elif tool_name == "loader":
+ if tool_name == "loader":
return self.get_loader_tool(parent, *args, **kwargs)
- elif tool_name == "libraryloader":
+ if tool_name == "libraryloader":
return self.get_library_loader_tool(parent, *args, **kwargs)
- elif tool_name == "creator":
- return self.get_creator_tool(parent, *args, **kwargs)
-
- elif tool_name == "subsetmanager":
+ if tool_name == "subsetmanager":
return self.get_subset_manager_tool(parent, *args, **kwargs)
- elif tool_name == "sceneinventory":
+ if tool_name == "sceneinventory":
return self.get_scene_inventory_tool(parent, *args, **kwargs)
- elif tool_name == "publish":
- self.log.info("Can't return publish tool window.")
-
- # "new" publisher
- elif tool_name == "publisher":
+ if tool_name == "publisher":
return self.get_publisher_tool(parent, *args, **kwargs)
- elif tool_name == "experimental_tools":
+ if tool_name == "experimental_tools":
return self.get_experimental_tools_dialog(parent, *args, **kwargs)
- else:
- self.log.warning(
- "Can't show unknown tool name: \"{}\"".format(tool_name)
- )
+ if tool_name == "publish":
+ self.log.info("Can't return publish tool window.")
+ return None
+
+ self.log.warning(
+ "Can't show unknown tool name: \"{}\"".format(tool_name)
+ )
+ return None
def show_tool_by_name(self, tool_name, parent=None, *args, **kwargs):
"""Show tool by it's name.
@@ -305,9 +280,6 @@ class HostToolsHelper:
elif tool_name == "libraryloader":
self.show_library_loader(parent, *args, **kwargs)
- elif tool_name == "creator":
- self.show_creator(parent, *args, **kwargs)
-
elif tool_name == "subsetmanager":
self.show_subset_manager(parent, *args, **kwargs)
@@ -379,10 +351,6 @@ def show_library_loader(parent=None):
_SingletonPoint.show_tool_by_name("libraryloader", parent)
-def show_creator(parent=None):
- _SingletonPoint.show_tool_by_name("creator", parent)
-
-
def show_subset_manager(parent=None):
_SingletonPoint.show_tool_by_name("subsetmanager", parent)
From 81d30462e26d4ae05f853c06fe23c00f7ba5946f Mon Sep 17 00:00:00 2001
From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Date: Thu, 14 Aug 2025 17:15:31 +0200
Subject: [PATCH 2/6] removed legacy create and related functions
---
client/ayon_core/pipeline/__init__.py | 8 -
client/ayon_core/pipeline/create/__init__.py | 14 --
.../pipeline/create/creator_plugins.py | 58 -----
.../pipeline/create/legacy_create.py | 216 ------------------
4 files changed, 296 deletions(-)
delete mode 100644 client/ayon_core/pipeline/create/legacy_create.py
diff --git a/client/ayon_core/pipeline/__init__.py b/client/ayon_core/pipeline/__init__.py
index 137736c302..65ad55d06e 100644
--- a/client/ayon_core/pipeline/__init__.py
+++ b/client/ayon_core/pipeline/__init__.py
@@ -19,9 +19,6 @@ from .create import (
CreatedInstance,
CreatorError,
- LegacyCreator,
- legacy_create,
-
discover_creator_plugins,
discover_legacy_creator_plugins,
register_creator_plugin,
@@ -141,12 +138,7 @@ __all__ = (
"CreatorError",
- # - legacy creation
- "LegacyCreator",
- "legacy_create",
-
"discover_creator_plugins",
- "discover_legacy_creator_plugins",
"register_creator_plugin",
"deregister_creator_plugin",
"register_creator_plugin_path",
diff --git a/client/ayon_core/pipeline/create/__init__.py b/client/ayon_core/pipeline/create/__init__.py
index ced43528eb..2f076b63f6 100644
--- a/client/ayon_core/pipeline/create/__init__.py
+++ b/client/ayon_core/pipeline/create/__init__.py
@@ -44,9 +44,6 @@ from .creator_plugins import (
AutoCreator,
HiddenCreator,
- discover_legacy_creator_plugins,
- get_legacy_creator_by_name,
-
discover_creator_plugins,
register_creator_plugin,
deregister_creator_plugin,
@@ -58,11 +55,6 @@ from .creator_plugins import (
from .context import CreateContext
-from .legacy_create import (
- LegacyCreator,
- legacy_create,
-)
-
__all__ = (
"PRODUCT_NAME_ALLOWED_SYMBOLS",
@@ -105,9 +97,6 @@ __all__ = (
"AutoCreator",
"HiddenCreator",
- "discover_legacy_creator_plugins",
- "get_legacy_creator_by_name",
-
"discover_creator_plugins",
"register_creator_plugin",
"deregister_creator_plugin",
@@ -117,7 +106,4 @@ __all__ = (
"cache_and_get_instances",
"CreateContext",
-
- "LegacyCreator",
- "legacy_create",
)
diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py
index cbc06145fb..b890704649 100644
--- a/client/ayon_core/pipeline/create/creator_plugins.py
+++ b/client/ayon_core/pipeline/create/creator_plugins.py
@@ -20,7 +20,6 @@ from ayon_core.pipeline.staging_dir import get_staging_dir_info, StagingDir
from .constants import DEFAULT_VARIANT_VALUE
from .product_name import get_product_name
from .utils import get_next_versions_for_instances
-from .legacy_create import LegacyCreator
from .structures import CreatedInstance
if TYPE_CHECKING:
@@ -975,62 +974,10 @@ def discover_convertor_plugins(*args, **kwargs):
return discover(ProductConvertorPlugin, *args, **kwargs)
-def discover_legacy_creator_plugins():
- from ayon_core.pipeline import get_current_project_name
-
- log = Logger.get_logger("CreatorDiscover")
-
- plugins = discover(LegacyCreator)
- project_name = get_current_project_name()
- project_settings = get_project_settings(project_name)
- for plugin in plugins:
- try:
- plugin.apply_settings(project_settings)
- except Exception:
- log.warning(
- "Failed to apply settings to creator {}".format(
- plugin.__name__
- ),
- exc_info=True
- )
- return plugins
-
-
-def get_legacy_creator_by_name(creator_name, case_sensitive=False):
- """Find creator plugin by name.
-
- Args:
- creator_name (str): Name of creator class that should be returned.
- case_sensitive (bool): Match of creator plugin name is case sensitive.
- Set to `False` by default.
-
- Returns:
- Creator: Return first matching plugin or `None`.
- """
-
- # Lower input creator name if is not case sensitive
- if not case_sensitive:
- creator_name = creator_name.lower()
-
- for creator_plugin in discover_legacy_creator_plugins():
- _creator_name = creator_plugin.__name__
-
- # Lower creator plugin name if is not case sensitive
- if not case_sensitive:
- _creator_name = _creator_name.lower()
-
- if _creator_name == creator_name:
- return creator_plugin
- return None
-
-
def register_creator_plugin(plugin):
if issubclass(plugin, BaseCreator):
register_plugin(BaseCreator, plugin)
- elif issubclass(plugin, LegacyCreator):
- register_plugin(LegacyCreator, plugin)
-
elif issubclass(plugin, ProductConvertorPlugin):
register_plugin(ProductConvertorPlugin, plugin)
@@ -1039,22 +986,17 @@ def deregister_creator_plugin(plugin):
if issubclass(plugin, BaseCreator):
deregister_plugin(BaseCreator, plugin)
- elif issubclass(plugin, LegacyCreator):
- deregister_plugin(LegacyCreator, plugin)
-
elif issubclass(plugin, ProductConvertorPlugin):
deregister_plugin(ProductConvertorPlugin, plugin)
def register_creator_plugin_path(path):
register_plugin_path(BaseCreator, path)
- register_plugin_path(LegacyCreator, path)
register_plugin_path(ProductConvertorPlugin, path)
def deregister_creator_plugin_path(path):
deregister_plugin_path(BaseCreator, path)
- deregister_plugin_path(LegacyCreator, path)
deregister_plugin_path(ProductConvertorPlugin, path)
diff --git a/client/ayon_core/pipeline/create/legacy_create.py b/client/ayon_core/pipeline/create/legacy_create.py
deleted file mode 100644
index f6427d9bd1..0000000000
--- a/client/ayon_core/pipeline/create/legacy_create.py
+++ /dev/null
@@ -1,216 +0,0 @@
-"""Create workflow moved from avalon-core repository.
-
-Renamed classes and functions
-- 'Creator' -> 'LegacyCreator'
-- 'create' -> 'legacy_create'
-"""
-
-import os
-import logging
-import collections
-
-from ayon_core.pipeline.constants import AYON_INSTANCE_ID
-
-from .product_name import get_product_name
-
-
-class LegacyCreator:
- """Determine how assets are created"""
- label = None
- product_type = None
- defaults = None
- maintain_selection = True
- enabled = True
-
- dynamic_product_name_keys = []
-
- log = logging.getLogger("LegacyCreator")
- log.propagate = True
-
- def __init__(self, name, folder_path, options=None, data=None):
- self.name = name # For backwards compatibility
- self.options = options
-
- # Default data
- self.data = collections.OrderedDict()
- # TODO use 'AYON_INSTANCE_ID' when all hosts support it
- self.data["id"] = AYON_INSTANCE_ID
- self.data["productType"] = self.product_type
- self.data["folderPath"] = folder_path
- self.data["productName"] = name
- self.data["active"] = True
-
- self.data.update(data or {})
-
- @classmethod
- def apply_settings(cls, project_settings):
- """Apply AYON settings to a plugin class."""
-
- host_name = os.environ.get("AYON_HOST_NAME")
- plugin_type = "create"
- plugin_type_settings = (
- project_settings
- .get(host_name, {})
- .get(plugin_type, {})
- )
- global_type_settings = (
- project_settings
- .get("core", {})
- .get(plugin_type, {})
- )
- if not global_type_settings and not plugin_type_settings:
- return
-
- plugin_name = cls.__name__
-
- plugin_settings = None
- # Look for plugin settings in host specific settings
- if plugin_name in plugin_type_settings:
- plugin_settings = plugin_type_settings[plugin_name]
-
- # Look for plugin settings in global settings
- elif plugin_name in global_type_settings:
- plugin_settings = global_type_settings[plugin_name]
-
- if not plugin_settings:
- return
-
- cls.log.debug(">>> We have preset for {}".format(plugin_name))
- for option, value in plugin_settings.items():
- if option == "enabled" and value is False:
- cls.log.debug(" - is disabled by preset")
- else:
- cls.log.debug(" - setting `{}`: `{}`".format(option, value))
- setattr(cls, option, value)
-
- def process(self):
- pass
-
- @classmethod
- def get_dynamic_data(
- cls, project_name, folder_entity, task_entity, variant, host_name
- ):
- """Return dynamic data for current Creator plugin.
-
- By default return keys from `dynamic_product_name_keys` attribute
- as mapping to keep formatted template unchanged.
-
- ```
- dynamic_product_name_keys = ["my_key"]
- ---
- output = {
- "my_key": "{my_key}"
- }
- ```
-
- Dynamic keys may override default Creator keys (productType, task,
- folderPath, ...) but do it wisely if you need.
-
- All of keys will be converted into 3 variants unchanged, capitalized
- and all upper letters. Because of that are all keys lowered.
-
- This method can be modified to prefill some values just keep in mind it
- is class method.
-
- Args:
- project_name (str): Context's project name.
- folder_entity (dict[str, Any]): Folder entity.
- task_entity (dict[str, Any]): Task entity.
- variant (str): What is entered by user in creator tool.
- host_name (str): Name of host.
-
- Returns:
- dict: Fill data for product name template.
- """
- dynamic_data = {}
- for key in cls.dynamic_product_name_keys:
- key = key.lower()
- dynamic_data[key] = "{" + key + "}"
- return dynamic_data
-
- @classmethod
- def get_product_name(
- cls, project_name, folder_entity, task_entity, variant, host_name=None
- ):
- """Return product name created with entered arguments.
-
- Logic extracted from Creator tool. This method should give ability
- to get product name without the tool.
-
- TODO: Maybe change `variant` variable.
-
- By default is output concatenated product type with variant.
-
- Args:
- project_name (str): Context's project name.
- folder_entity (dict[str, Any]): Folder entity.
- task_entity (dict[str, Any]): Task entity.
- variant (str): What is entered by user in creator tool.
- host_name (str): Name of host.
-
- Returns:
- str: Formatted product name with entered arguments. Should match
- config's logic.
- """
-
- dynamic_data = cls.get_dynamic_data(
- project_name, folder_entity, task_entity, variant, host_name
- )
- task_name = task_type = None
- if task_entity:
- task_name = task_entity["name"]
- task_type = task_entity["taskType"]
- return get_product_name(
- project_name,
- task_name,
- task_type,
- host_name,
- cls.product_type,
- variant,
- dynamic_data=dynamic_data
- )
-
-
-def legacy_create(
- Creator, product_name, folder_path, options=None, data=None
-):
- """Create a new instance
-
- Associate nodes with a product name and type. These nodes are later
- validated, according to their `product type`, and integrated into the
- shared environment, relative their `productName`.
-
- Data relative each product type, along with default data, are imprinted
- into the resulting objectSet. This data is later used by extractors
- and finally asset browsers to help identify the origin of the asset.
-
- Arguments:
- Creator (Creator): Class of creator.
- product_name (str): Name of product.
- folder_path (str): Folder path.
- options (dict, optional): Additional options from GUI.
- data (dict, optional): Additional data from GUI.
-
- Raises:
- NameError on `productName` already exists
- KeyError on invalid dynamic property
- RuntimeError on host error
-
- Returns:
- Name of instance
-
- """
- from ayon_core.pipeline import registered_host
-
- host = registered_host()
- plugin = Creator(product_name, folder_path, options, data)
-
- if plugin.maintain_selection is True:
- with host.maintained_selection():
- print("Running %s with maintained selection" % plugin)
- instance = plugin.process()
- return instance
-
- print("Running %s" % plugin)
- instance = plugin.process()
- return instance
From 027f148b102d464e8097173f4ff365cb0fa25125 Mon Sep 17 00:00:00 2001
From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Date: Thu, 14 Aug 2025 17:16:11 +0200
Subject: [PATCH 3/6] remove legacy creators logic from template builder
---
.../workfile/workfile_template_builder.py | 44 ++++---------------
1 file changed, 9 insertions(+), 35 deletions(-)
diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py
index e2add99752..37f76a2268 100644
--- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py
+++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py
@@ -54,7 +54,6 @@ from ayon_core.pipeline.plugin_discover import (
)
from ayon_core.pipeline.create import (
- discover_legacy_creator_plugins,
CreateContext,
HiddenCreator,
)
@@ -131,7 +130,6 @@ class AbstractTemplateBuilder(ABC):
"""
_log = None
- use_legacy_creators = False
def __init__(self, host):
# Get host name
@@ -321,19 +319,6 @@ class AbstractTemplateBuilder(ABC):
return list(get_folders(project_name, folder_ids=linked_folder_ids))
- def _collect_legacy_creators(self):
- creators_by_name = {}
- for creator in discover_legacy_creator_plugins():
- if not creator.enabled:
- continue
- creator_name = creator.__name__
- if creator_name in creators_by_name:
- raise KeyError(
- "Duplicated creator name {} !".format(creator_name)
- )
- creators_by_name[creator_name] = creator
- self._creators_by_name = creators_by_name
-
def _collect_creators(self):
self._creators_by_name = {
identifier: creator
@@ -345,10 +330,7 @@ class AbstractTemplateBuilder(ABC):
def get_creators_by_name(self):
if self._creators_by_name is None:
- if self.use_legacy_creators:
- self._collect_legacy_creators()
- else:
- self._collect_creators()
+ self._collect_creators()
return self._creators_by_name
@@ -1938,8 +1920,6 @@ class PlaceholderCreateMixin(object):
pre_create_data (dict): dictionary of configuration from Creator
configuration in UI
"""
-
- legacy_create = self.builder.use_legacy_creators
creator_name = placeholder.data["creator"]
create_variant = placeholder.data["create_variant"]
active = placeholder.data.get("active")
@@ -1979,20 +1959,14 @@ class PlaceholderCreateMixin(object):
# compile product name from variant
try:
- if legacy_create:
- creator_instance = creator_plugin(
- product_name,
- folder_path
- ).process()
- else:
- creator_instance = self.builder.create_context.create(
- creator_plugin.identifier,
- create_variant,
- folder_entity,
- task_entity,
- pre_create_data=pre_create_data,
- active=active
- )
+ creator_instance = self.builder.create_context.create(
+ creator_plugin.identifier,
+ create_variant,
+ folder_entity,
+ task_entity,
+ pre_create_data=pre_create_data,
+ active=active
+ )
except: # noqa: E722
failed = True
From 7b0d54e7a8a9842fd905e77fe7d8026a78a35b4c Mon Sep 17 00:00:00 2001
From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Date: Thu, 14 Aug 2025 17:25:15 +0200
Subject: [PATCH 4/6] remove creator tool
Now really remove it...
---
client/ayon_core/tools/creator/__init__.py | 9 -
client/ayon_core/tools/creator/constants.py | 8 -
client/ayon_core/tools/creator/model.py | 59 ---
client/ayon_core/tools/creator/widgets.py | 275 -----------
client/ayon_core/tools/creator/window.py | 507 --------------------
5 files changed, 858 deletions(-)
delete mode 100644 client/ayon_core/tools/creator/__init__.py
delete mode 100644 client/ayon_core/tools/creator/constants.py
delete mode 100644 client/ayon_core/tools/creator/model.py
delete mode 100644 client/ayon_core/tools/creator/widgets.py
delete mode 100644 client/ayon_core/tools/creator/window.py
diff --git a/client/ayon_core/tools/creator/__init__.py b/client/ayon_core/tools/creator/__init__.py
deleted file mode 100644
index 585b8bdf80..0000000000
--- a/client/ayon_core/tools/creator/__init__.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from .window import (
- show,
- CreatorWindow
-)
-
-__all__ = (
- "show",
- "CreatorWindow"
-)
diff --git a/client/ayon_core/tools/creator/constants.py b/client/ayon_core/tools/creator/constants.py
deleted file mode 100644
index ec555fbe9c..0000000000
--- a/client/ayon_core/tools/creator/constants.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from qtpy import QtCore
-
-
-PRODUCT_TYPE_ROLE = QtCore.Qt.UserRole + 1
-ITEM_ID_ROLE = QtCore.Qt.UserRole + 2
-
-SEPARATOR = "---"
-SEPARATORS = {"---", "---separator---"}
diff --git a/client/ayon_core/tools/creator/model.py b/client/ayon_core/tools/creator/model.py
deleted file mode 100644
index 16d24cc8bc..0000000000
--- a/client/ayon_core/tools/creator/model.py
+++ /dev/null
@@ -1,59 +0,0 @@
-import uuid
-from qtpy import QtGui, QtCore
-
-from . constants import (
- PRODUCT_TYPE_ROLE,
- ITEM_ID_ROLE
-)
-
-
-class CreatorsModel(QtGui.QStandardItemModel):
- def __init__(self, *args, **kwargs):
- super(CreatorsModel, self).__init__(*args, **kwargs)
-
- self._creators_by_id = {}
-
- def reset(self):
- # TODO change to refresh when clearing is not needed
- self.clear()
- self._creators_by_id = {}
-
- items = []
- creators = discover_legacy_creator_plugins()
- for creator in creators:
- if not creator.enabled:
- continue
- item_id = str(uuid.uuid4())
- self._creators_by_id[item_id] = creator
-
- label = creator.label or creator.product_type
- item = QtGui.QStandardItem(label)
- item.setEditable(False)
- item.setData(item_id, ITEM_ID_ROLE)
- item.setData(creator.product_type, PRODUCT_TYPE_ROLE)
- items.append(item)
-
- if not items:
- item = QtGui.QStandardItem("No registered create plugins")
- item.setEnabled(False)
- item.setData(False, QtCore.Qt.ItemIsEnabled)
- items.append(item)
-
- items.sort(key=lambda item: item.text())
- self.invisibleRootItem().appendRows(items)
-
- def get_creator_by_id(self, item_id):
- return self._creators_by_id.get(item_id)
-
- def get_indexes_by_product_type(self, product_type):
- indexes = []
- for row in range(self.rowCount()):
- index = self.index(row, 0)
- item_id = index.data(ITEM_ID_ROLE)
- creator_plugin = self._creators_by_id.get(item_id)
- if creator_plugin and (
- creator_plugin.label.lower() == product_type.lower()
- or creator_plugin.product_type.lower() == product_type.lower()
- ):
- indexes.append(index)
- return indexes
diff --git a/client/ayon_core/tools/creator/widgets.py b/client/ayon_core/tools/creator/widgets.py
deleted file mode 100644
index bbc6848e6c..0000000000
--- a/client/ayon_core/tools/creator/widgets.py
+++ /dev/null
@@ -1,275 +0,0 @@
-import re
-import inspect
-
-from qtpy import QtWidgets, QtCore, QtGui
-
-import qtawesome
-
-from ayon_core.pipeline.create import PRODUCT_NAME_ALLOWED_SYMBOLS
-from ayon_core.tools.utils import ErrorMessageBox
-
-if hasattr(QtGui, "QRegularExpressionValidator"):
- RegularExpressionValidatorClass = QtGui.QRegularExpressionValidator
- RegularExpressionClass = QtCore.QRegularExpression
-else:
- RegularExpressionValidatorClass = QtGui.QRegExpValidator
- RegularExpressionClass = QtCore.QRegExp
-
-
-class CreateErrorMessageBox(ErrorMessageBox):
- def __init__(
- self,
- product_type,
- product_name,
- folder_path,
- exc_msg,
- formatted_traceback,
- parent
- ):
- self._product_type = product_type
- self._product_name = product_name
- self._folder_path = folder_path
- self._exc_msg = exc_msg
- self._formatted_traceback = formatted_traceback
- super(CreateErrorMessageBox, self).__init__("Creation failed", parent)
-
- def _create_top_widget(self, parent_widget):
- label_widget = QtWidgets.QLabel(parent_widget)
- label_widget.setText(
- "Failed to create"
- )
- return label_widget
-
- def _get_report_data(self):
- report_message = (
- "Failed to create Product: \"{product_name}\""
- " Type: \"{product_type}\""
- " in Folder: \"{folder_path}\""
- "\n\nError: {message}"
- ).format(
- product_name=self._product_name,
- product_type=self._product_type,
- folder_path=self._folder_path,
- message=self._exc_msg
- )
- if self._formatted_traceback:
- report_message += "\n\n{}".format(self._formatted_traceback)
- return [report_message]
-
- def _create_content(self, content_layout):
- item_name_template = (
- "{}: {{}}
"
- "{}: {{}}
"
- "{}: {{}}
"
- ).format(
- "Product type",
- "Product name",
- "Folder"
- )
- exc_msg_template = "{}"
-
- line = self._create_line()
- content_layout.addWidget(line)
-
- item_name_widget = QtWidgets.QLabel(self)
- item_name_widget.setText(
- item_name_template.format(
- self._product_type, self._product_name, self._folder_path
- )
- )
- content_layout.addWidget(item_name_widget)
-
- message_label_widget = QtWidgets.QLabel(self)
- message_label_widget.setText(
- exc_msg_template.format(self.convert_text_for_html(self._exc_msg))
- )
- content_layout.addWidget(message_label_widget)
-
- if self._formatted_traceback:
- line_widget = self._create_line()
- tb_widget = self._create_traceback_widget(
- self._formatted_traceback
- )
- content_layout.addWidget(line_widget)
- content_layout.addWidget(tb_widget)
-
-
-class ProductNameValidator(RegularExpressionValidatorClass):
- invalid = QtCore.Signal(set)
- pattern = "^[{}]*$".format(PRODUCT_NAME_ALLOWED_SYMBOLS)
-
- def __init__(self):
- reg = RegularExpressionClass(self.pattern)
- super(ProductNameValidator, self).__init__(reg)
-
- def validate(self, text, pos):
- results = super(ProductNameValidator, self).validate(text, pos)
- if results[0] == RegularExpressionValidatorClass.Invalid:
- self.invalid.emit(self.invalid_chars(text))
- return results
-
- def invalid_chars(self, text):
- invalid = set()
- re_valid = re.compile(self.pattern)
- for char in text:
- if char == " ":
- invalid.add("' '")
- continue
- if not re_valid.match(char):
- invalid.add(char)
- return invalid
-
-
-class VariantLineEdit(QtWidgets.QLineEdit):
- report = QtCore.Signal(str)
- colors = {
- "empty": (QtGui.QColor("#78879b"), ""),
- "exists": (QtGui.QColor("#4E76BB"), "border-color: #4E76BB;"),
- "new": (QtGui.QColor("#7AAB8F"), "border-color: #7AAB8F;"),
- }
-
- def __init__(self, *args, **kwargs):
- super(VariantLineEdit, self).__init__(*args, **kwargs)
-
- validator = ProductNameValidator()
- self.setValidator(validator)
- self.setToolTip("Only alphanumeric characters (A-Z a-z 0-9), "
- "'_' and '.' are allowed.")
-
- self._status_color = self.colors["empty"][0]
-
- anim = QtCore.QPropertyAnimation()
- anim.setTargetObject(self)
- anim.setPropertyName(b"status_color")
- anim.setEasingCurve(QtCore.QEasingCurve.InCubic)
- anim.setDuration(300)
- anim.setStartValue(QtGui.QColor("#C84747")) # `Invalid` status color
- self.animation = anim
-
- validator.invalid.connect(self.on_invalid)
-
- def on_invalid(self, invalid):
- message = "Invalid character: %s" % ", ".join(invalid)
- self.report.emit(message)
- self.animation.stop()
- self.animation.start()
-
- def as_empty(self):
- self._set_border("empty")
- self.report.emit("Empty product name ..")
-
- def as_exists(self):
- self._set_border("exists")
- self.report.emit("Existing product, appending next version.")
-
- def as_new(self):
- self._set_border("new")
- self.report.emit("New product, creating first version.")
-
- def _set_border(self, status):
- qcolor, style = self.colors[status]
- self.animation.setEndValue(qcolor)
- self.setStyleSheet(style)
-
- def _get_status_color(self):
- return self._status_color
-
- def _set_status_color(self, color):
- self._status_color = color
- self.setStyleSheet("border-color: %s;" % color.name())
-
- status_color = QtCore.Property(
- QtGui.QColor, _get_status_color, _set_status_color
- )
-
-
-class ProductTypeDescriptionWidget(QtWidgets.QWidget):
- """A product type description widget.
-
- Shows a product type icon, name and a help description.
- Used in creator header.
-
- _______________________
- | ____ |
- | |icon| PRODUCT TYPE |
- | |____| help |
- |_______________________|
-
- """
-
- SIZE = 35
-
- def __init__(self, parent=None):
- super(ProductTypeDescriptionWidget, self).__init__(parent=parent)
-
- icon_label = QtWidgets.QLabel(self)
- icon_label.setSizePolicy(
- QtWidgets.QSizePolicy.Maximum,
- QtWidgets.QSizePolicy.Maximum
- )
-
- # Add 4 pixel padding to avoid icon being cut off
- icon_label.setFixedWidth(self.SIZE + 4)
- icon_label.setFixedHeight(self.SIZE + 4)
-
- label_layout = QtWidgets.QVBoxLayout()
- label_layout.setSpacing(0)
-
- product_type_label = QtWidgets.QLabel(self)
- product_type_label.setObjectName("CreatorProductTypeLabel")
- product_type_label.setAlignment(
- QtCore.Qt.AlignBottom | QtCore.Qt.AlignLeft
- )
-
- help_label = QtWidgets.QLabel(self)
- help_label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)
-
- label_layout.addWidget(product_type_label)
- label_layout.addWidget(help_label)
-
- layout = QtWidgets.QHBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(5)
- layout.addWidget(icon_label)
- layout.addLayout(label_layout)
-
- self._help_label = help_label
- self._product_type_label = product_type_label
- self._icon_label = icon_label
-
- def set_item(self, creator_plugin):
- """Update elements to display information of a product type item.
-
- Args:
- creator_plugin (dict): A product type item as registered with
- name, help and icon.
-
- Returns:
- None
-
- """
- if not creator_plugin:
- self._icon_label.setPixmap(None)
- self._product_type_label.setText("")
- self._help_label.setText("")
- return
-
- # Support a font-awesome icon
- icon_name = getattr(creator_plugin, "icon", None) or "info-circle"
- try:
- icon = qtawesome.icon("fa.{}".format(icon_name), color="white")
- pixmap = icon.pixmap(self.SIZE, self.SIZE)
- except Exception:
- print("BUG: Couldn't load icon \"fa.{}\"".format(str(icon_name)))
- # Create transparent pixmap
- pixmap = QtGui.QPixmap()
- pixmap.fill(QtCore.Qt.transparent)
- pixmap = pixmap.scaled(self.SIZE, self.SIZE)
-
- # Parse a clean line from the Creator's docstring
- docstring = inspect.getdoc(creator_plugin)
- creator_help = docstring.splitlines()[0] if docstring else ""
-
- self._icon_label.setPixmap(pixmap)
- self._product_type_label.setText(creator_plugin.product_type)
- self._help_label.setText(creator_help)
diff --git a/client/ayon_core/tools/creator/window.py b/client/ayon_core/tools/creator/window.py
deleted file mode 100644
index fe8ee86dcf..0000000000
--- a/client/ayon_core/tools/creator/window.py
+++ /dev/null
@@ -1,507 +0,0 @@
-import sys
-import traceback
-import re
-
-import ayon_api
-from qtpy import QtWidgets, QtCore
-
-from ayon_core import style
-from ayon_core.settings import get_current_project_settings
-from ayon_core.tools.utils.lib import qt_app_context
-from ayon_core.pipeline import (
- get_current_project_name,
- get_current_folder_path,
- get_current_task_name,
-)
-from ayon_core.pipeline.create import (
- PRODUCT_NAME_ALLOWED_SYMBOLS,
- CreatorError,
-)
-
-from .model import CreatorsModel
-from .widgets import (
- CreateErrorMessageBox,
- VariantLineEdit,
- ProductTypeDescriptionWidget
-)
-from .constants import (
- ITEM_ID_ROLE,
- SEPARATOR,
- SEPARATORS
-)
-
-module = sys.modules[__name__]
-module.window = None
-
-
-class CreatorWindow(QtWidgets.QDialog):
- def __init__(self, parent=None):
- super(CreatorWindow, self).__init__(parent)
- self.setWindowTitle("Instance Creator")
- self.setFocusPolicy(QtCore.Qt.StrongFocus)
- if not parent:
- self.setWindowFlags(
- self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint
- )
-
- creator_info = ProductTypeDescriptionWidget(self)
-
- creators_model = CreatorsModel()
-
- creators_proxy = QtCore.QSortFilterProxyModel()
- creators_proxy.setSourceModel(creators_model)
-
- creators_view = QtWidgets.QListView(self)
- creators_view.setObjectName("CreatorsView")
- creators_view.setModel(creators_proxy)
-
- folder_path_input = QtWidgets.QLineEdit(self)
- variant_input = VariantLineEdit(self)
- product_name_input = QtWidgets.QLineEdit(self)
- product_name_input.setEnabled(False)
-
- variants_btn = QtWidgets.QPushButton()
- variants_btn.setFixedWidth(18)
- variants_menu = QtWidgets.QMenu(variants_btn)
- variants_btn.setMenu(variants_menu)
-
- name_layout = QtWidgets.QHBoxLayout()
- name_layout.addWidget(variant_input)
- name_layout.addWidget(variants_btn)
- name_layout.setSpacing(3)
- name_layout.setContentsMargins(0, 0, 0, 0)
-
- body_layout = QtWidgets.QVBoxLayout()
- body_layout.setContentsMargins(0, 0, 0, 0)
-
- body_layout.addWidget(creator_info, 0)
- body_layout.addWidget(QtWidgets.QLabel("Product type", self), 0)
- body_layout.addWidget(creators_view, 1)
- body_layout.addWidget(QtWidgets.QLabel("Folder path", self), 0)
- body_layout.addWidget(folder_path_input, 0)
- body_layout.addWidget(QtWidgets.QLabel("Product name", self), 0)
- body_layout.addLayout(name_layout, 0)
- body_layout.addWidget(product_name_input, 0)
-
- useselection_chk = QtWidgets.QCheckBox("Use selection", self)
- useselection_chk.setCheckState(QtCore.Qt.Checked)
-
- create_btn = QtWidgets.QPushButton("Create", self)
- # Need to store error_msg to prevent garbage collection
- msg_label = QtWidgets.QLabel(self)
-
- footer_layout = QtWidgets.QVBoxLayout()
- footer_layout.addWidget(create_btn, 0)
- footer_layout.addWidget(msg_label, 0)
- footer_layout.setContentsMargins(0, 0, 0, 0)
-
- layout = QtWidgets.QVBoxLayout(self)
- layout.addLayout(body_layout, 1)
- layout.addWidget(useselection_chk, 0, QtCore.Qt.AlignLeft)
- layout.addLayout(footer_layout, 0)
-
- msg_timer = QtCore.QTimer()
- msg_timer.setSingleShot(True)
- msg_timer.setInterval(5000)
-
- validation_timer = QtCore.QTimer()
- validation_timer.setSingleShot(True)
- validation_timer.setInterval(300)
-
- msg_timer.timeout.connect(self._on_msg_timer)
- validation_timer.timeout.connect(self._on_validation_timer)
-
- create_btn.clicked.connect(self._on_create)
- variant_input.returnPressed.connect(self._on_create)
- variant_input.textChanged.connect(self._on_data_changed)
- variant_input.report.connect(self.echo)
- folder_path_input.textChanged.connect(self._on_data_changed)
- creators_view.selectionModel().currentChanged.connect(
- self._on_selection_changed
- )
-
- # Store valid states and
- self._is_valid = False
- create_btn.setEnabled(self._is_valid)
-
- self._first_show = True
-
- # Message dialog when something goes wrong during creation
- self._message_dialog = None
-
- self._creator_info = creator_info
- self._create_btn = create_btn
- self._useselection_chk = useselection_chk
- self._variant_input = variant_input
- self._product_name_input = product_name_input
- self._folder_path_input = folder_path_input
-
- self._creators_model = creators_model
- self._creators_proxy = creators_proxy
- self._creators_view = creators_view
-
- self._variants_btn = variants_btn
- self._variants_menu = variants_menu
-
- self._msg_label = msg_label
-
- self._validation_timer = validation_timer
- self._msg_timer = msg_timer
-
- # Defaults
- self.resize(300, 500)
- variant_input.setFocus()
-
- def _set_valid_state(self, valid):
- if self._is_valid == valid:
- return
- self._is_valid = valid
- self._create_btn.setEnabled(valid)
-
- def _build_menu(self, default_names=None):
- """Create optional predefined variants.
-
- Args:
- default_names(list): all predefined names
-
- Returns:
- None
- """
- if not default_names:
- default_names = []
-
- menu = self._variants_menu
- button = self._variants_btn
-
- # Get and destroy the action group
- group = button.findChild(QtWidgets.QActionGroup)
- if group:
- group.deleteLater()
-
- state = any(default_names)
- button.setEnabled(state)
- if state is False:
- return
-
- # Build new action group
- group = QtWidgets.QActionGroup(button)
- for name in default_names:
- if name in SEPARATORS:
- menu.addSeparator()
- continue
- action = group.addAction(name)
- menu.addAction(action)
-
- group.triggered.connect(self._on_action_clicked)
-
- def _on_action_clicked(self, action):
- self._variant_input.setText(action.text())
-
- def _on_data_changed(self, *args):
- # Set invalid state until it's reconfirmed to be valid by the
- # scheduled callback so any form of creation is held back until
- # valid again
- self._set_valid_state(False)
-
- self._validation_timer.start()
-
- def _on_validation_timer(self):
- index = self._creators_view.currentIndex()
- item_id = index.data(ITEM_ID_ROLE)
- creator_plugin = self._creators_model.get_creator_by_id(item_id)
- user_input_text = self._variant_input.text()
- folder_path = self._folder_path_input.text()
-
- # Early exit if no folder path
- if not folder_path:
- self._build_menu()
- self.echo("Folder is required ..")
- self._set_valid_state(False)
- return
-
- project_name = get_current_project_name()
- folder_entity = None
- if creator_plugin:
- # Get the folder from the database which match with the name
- folder_entity = ayon_api.get_folder_by_path(
- project_name, folder_path, fields={"id"}
- )
-
- # Get plugin
- if not folder_entity or not creator_plugin:
- self._build_menu()
-
- if not creator_plugin:
- self.echo("No registered product types ..")
- else:
- self.echo("Folder '{}' not found ..".format(folder_path))
- self._set_valid_state(False)
- return
-
- folder_id = folder_entity["id"]
-
- task_name = get_current_task_name()
- task_entity = ayon_api.get_task_by_name(
- project_name, folder_id, task_name
- )
-
- # Calculate product name with Creator plugin
- product_name = creator_plugin.get_product_name(
- project_name, folder_entity, task_entity, user_input_text
- )
- # Force replacement of prohibited symbols
- # QUESTION should Creator care about this and here should be only
- # validated with schema regex?
-
- # Allow curly brackets in product name for dynamic keys
- curly_left = "__cbl__"
- curly_right = "__cbr__"
- tmp_product_name = (
- product_name
- .replace("{", curly_left)
- .replace("}", curly_right)
- )
- # Replace prohibited symbols
- tmp_product_name = re.sub(
- "[^{}]+".format(PRODUCT_NAME_ALLOWED_SYMBOLS),
- "",
- tmp_product_name
- )
- product_name = (
- tmp_product_name
- .replace(curly_left, "{")
- .replace(curly_right, "}")
- )
- self._product_name_input.setText(product_name)
-
- # Get all products of the current folder
- product_entities = ayon_api.get_products(
- project_name, folder_ids={folder_id}, fields={"name"}
- )
- existing_product_names = {
- product_entity["name"]
- for product_entity in product_entities
- }
- existing_product_names_low = set(
- _name.lower()
- for _name in existing_product_names
- )
-
- # Defaults to dropdown
- defaults = []
- # Check if Creator plugin has set defaults
- if (
- creator_plugin.defaults
- and isinstance(creator_plugin.defaults, (list, tuple, set))
- ):
- defaults = list(creator_plugin.defaults)
-
- # Replace
- compare_regex = re.compile(re.sub(
- user_input_text, "(.+)", product_name, flags=re.IGNORECASE
- ))
- variant_hints = set()
- if user_input_text:
- for _name in existing_product_names:
- _result = compare_regex.search(_name)
- if _result:
- variant_hints |= set(_result.groups())
-
- if variant_hints:
- if defaults:
- defaults.append(SEPARATOR)
- defaults.extend(variant_hints)
- self._build_menu(defaults)
-
- # Indicate product existence
- if not user_input_text:
- self._variant_input.as_empty()
- elif product_name.lower() in existing_product_names_low:
- # validate existence of product name with lowered text
- # - "renderMain" vs. "rensermain" mean same path item for
- # windows
- self._variant_input.as_exists()
- else:
- self._variant_input.as_new()
-
- # Update the valid state
- valid = product_name.strip() != ""
-
- self._set_valid_state(valid)
-
- def _on_selection_changed(self, old_idx, new_idx):
- index = self._creators_view.currentIndex()
- item_id = index.data(ITEM_ID_ROLE)
-
- creator_plugin = self._creators_model.get_creator_by_id(item_id)
-
- self._creator_info.set_item(creator_plugin)
-
- if creator_plugin is None:
- return
-
- default = None
- if hasattr(creator_plugin, "get_default_variant"):
- default = creator_plugin.get_default_variant()
-
- if not default:
- if (
- creator_plugin.defaults
- and isinstance(creator_plugin.defaults, list)
- ):
- default = creator_plugin.defaults[0]
- else:
- default = "Default"
-
- self._variant_input.setText(default)
-
- self._on_data_changed()
-
- def keyPressEvent(self, event):
- """Custom keyPressEvent.
-
- Override keyPressEvent to do nothing so that Maya's panels won't
- take focus when pressing "SHIFT" whilst mouse is over viewport or
- outliner. This way users don't accidentally perform Maya commands
- whilst trying to name an instance.
-
- """
- pass
-
- def showEvent(self, event):
- super(CreatorWindow, self).showEvent(event)
- if self._first_show:
- self._first_show = False
- self.setStyleSheet(style.load_stylesheet())
-
- def refresh(self):
- self._folder_path_input.setText(get_current_folder_path())
-
- self._creators_model.reset()
-
- product_types_smart_select = (
- get_current_project_settings()
- ["core"]
- ["tools"]
- ["creator"]
- ["product_types_smart_select"]
- )
- current_index = None
- product_type = None
- task_name = get_current_task_name() or None
- lowered_task_name = task_name.lower()
- if task_name:
- for smart_item in product_types_smart_select:
- _low_task_names = {
- name.lower() for name in smart_item["task_names"]
- }
- for _task_name in _low_task_names:
- if _task_name in lowered_task_name:
- product_type = smart_item["name"]
- break
- if product_type:
- break
-
- if product_type:
- indexes = self._creators_model.get_indexes_by_product_type(
- product_type
- )
- if indexes:
- index = indexes[0]
- current_index = self._creators_proxy.mapFromSource(index)
-
- if current_index is None or not current_index.isValid():
- current_index = self._creators_proxy.index(0, 0)
-
- self._creators_view.setCurrentIndex(current_index)
-
- def _on_create(self):
- # Do not allow creation in an invalid state
- if not self._is_valid:
- return
-
- index = self._creators_view.currentIndex()
- item_id = index.data(ITEM_ID_ROLE)
- creator_plugin = self._creators_model.get_creator_by_id(item_id)
- if creator_plugin is None:
- return
-
- product_name = self._product_name_input.text()
- folder_path = self._folder_path_input.text()
- use_selection = self._useselection_chk.isChecked()
-
- variant = self._variant_input.text()
-
- error_info = None
- try:
- legacy_create(
- creator_plugin,
- product_name,
- folder_path,
- options={"useSelection": use_selection},
- data={"variant": variant}
- )
-
- except CreatorError as exc:
- self.echo("Creator error: {}".format(str(exc)))
- error_info = (str(exc), None)
-
- except Exception as exc:
- self.echo("Program error: %s" % str(exc))
-
- exc_type, exc_value, exc_traceback = sys.exc_info()
- formatted_traceback = "".join(traceback.format_exception(
- exc_type, exc_value, exc_traceback
- ))
- error_info = (str(exc), formatted_traceback)
-
- if error_info:
- box = CreateErrorMessageBox(
- creator_plugin.product_type,
- product_name,
- folder_path,
- *error_info,
- parent=self
- )
- box.show()
- # Store dialog so is not garbage collected before is shown
- self._message_dialog = box
-
- else:
- self.echo("Created %s .." % product_name)
-
- def _on_msg_timer(self):
- self._msg_label.setText("")
-
- def echo(self, message):
- self._msg_label.setText(str(message))
- self._msg_timer.start()
-
-
-def show(parent=None):
- """Display product creator GUI
-
- Arguments:
- debug (bool, optional): Run loader in debug-mode,
- defaults to False
- parent (QtCore.QObject, optional): When provided parent the interface
- to this QObject.
-
- """
-
- try:
- module.window.close()
- del module.window
- except (AttributeError, RuntimeError):
- pass
-
- with qt_app_context():
- window = CreatorWindow(parent)
- window.refresh()
- window.show()
-
- module.window = window
-
- # Pull window to the front.
- module.window.raise_()
- module.window.activateWindow()
From 7856ee98fef309a4404d37275631f7903864140c Mon Sep 17 00:00:00 2001
From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Date: Thu, 14 Aug 2025 17:25:22 +0200
Subject: [PATCH 5/6] remove import
---
client/ayon_core/pipeline/__init__.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/client/ayon_core/pipeline/__init__.py b/client/ayon_core/pipeline/__init__.py
index 65ad55d06e..f2ec952cd6 100644
--- a/client/ayon_core/pipeline/__init__.py
+++ b/client/ayon_core/pipeline/__init__.py
@@ -20,7 +20,6 @@ from .create import (
CreatorError,
discover_creator_plugins,
- discover_legacy_creator_plugins,
register_creator_plugin,
deregister_creator_plugin,
register_creator_plugin_path,
From 8314d83a0d52adbef475e63dffdc85cc51f47e45 Mon Sep 17 00:00:00 2001
From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com>
Date: Thu, 14 Aug 2025 17:26:00 +0200
Subject: [PATCH 6/6] remove unused import
---
client/ayon_core/pipeline/create/creator_plugins.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py
index b890704649..7573589b82 100644
--- a/client/ayon_core/pipeline/create/creator_plugins.py
+++ b/client/ayon_core/pipeline/create/creator_plugins.py
@@ -6,7 +6,6 @@ from typing import TYPE_CHECKING, Optional, Dict, Any
from abc import ABC, abstractmethod
-from ayon_core.settings import get_project_settings
from ayon_core.lib import Logger, get_version_from_path
from ayon_core.pipeline.plugin_discover import (
discover,