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,