From 889953af3166bbc5fa2798071c53bfa7667bc644 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:18:33 +0100 Subject: [PATCH 01/68] tasks widget created model and proxy model with methods that can be overriden --- openpype/tools/utils/tasks_widget.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py index 419e77c780..699b1cf569 100644 --- a/openpype/tools/utils/tasks_widget.py +++ b/openpype/tools/utils/tasks_widget.py @@ -194,6 +194,8 @@ class TasksWidget(QtWidgets.QWidget): task_changed = QtCore.Signal() def __init__(self, dbcon, parent=None): + self._dbcon = dbcon + super(TasksWidget, self).__init__(parent) tasks_view = DeselectableTreeView(self) @@ -204,9 +206,8 @@ class TasksWidget(QtWidgets.QWidget): header_view = tasks_view.header() header_view.setSortIndicator(0, QtCore.Qt.AscendingOrder) - tasks_model = TasksModel(dbcon) - tasks_proxy = TasksProxyModel() - tasks_proxy.setSourceModel(tasks_model) + tasks_model = self._create_source_model() + tasks_proxy = self._create_proxy_model(tasks_model) tasks_view.setModel(tasks_proxy) layout = QtWidgets.QVBoxLayout(self) @@ -222,6 +223,14 @@ class TasksWidget(QtWidgets.QWidget): self._last_selected_task_name = None + def _create_source_model(self): + return TasksModel(self._dbcon) + + def _create_proxy_model(self, source_model): + proxy = TasksProxyModel() + proxy.setSourceModel(source_model) + return proxy + def refresh(self): self._tasks_model.refresh() From 33ae30d6ab25afa294ebd32aa4097bba03e8878e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:19:12 +0100 Subject: [PATCH 02/68] model and proxy model in assets widget is created in methods which may be overriden --- openpype/tools/utils/assets_widget.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index f310aafe89..1cb803c68a 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -582,11 +582,8 @@ class AssetsWidget(QtWidgets.QWidget): self.dbcon = dbcon # Tree View - model = AssetModel(dbcon=self.dbcon, parent=self) - proxy = RecursiveSortFilterProxyModel() - proxy.setSourceModel(model) - proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) - proxy.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) + model = self._create_source_model() + proxy = self._create_proxy_model(model) view = AssetsView(self) view.setModel(proxy) @@ -628,7 +625,6 @@ class AssetsWidget(QtWidgets.QWidget): selection_model.selectionChanged.connect(self._on_selection_change) refresh_btn.clicked.connect(self.refresh) current_asset_btn.clicked.connect(self.set_current_session_asset) - model.refreshed.connect(self._on_model_refresh) view.doubleClicked.connect(self.double_clicked) self._current_asset_btn = current_asset_btn @@ -639,6 +635,18 @@ class AssetsWidget(QtWidgets.QWidget): self.model_selection = {} + def _create_source_model(self): + model = AssetModel(dbcon=self.dbcon, parent=self) + model.refreshed.connect(self._on_model_refresh) + return model + + def _create_proxy_model(self, source_model): + proxy = RecursiveSortFilterProxyModel() + proxy.setSourceModel(source_model) + proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) + proxy.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) + return proxy + @property def refreshing(self): return self._model.refreshing From 4525cf1be4f48e81c51c8aa33434a7e8762a34de Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:19:51 +0100 Subject: [PATCH 03/68] filling of assets model is done in separated method --- openpype/tools/utils/assets_widget.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index 1cb803c68a..1ae560bd2b 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -401,11 +401,18 @@ class AssetModel(QtGui.QStandardItemModel): self._clear_items() return + self._fill_assets(self._doc_payload) + + self.refreshed.emit(bool(self._items_by_asset_id)) + + self._stop_fetch_thread() + + def _fill_assets(self, asset_docs): # Collect asset documents as needed asset_ids = set() asset_docs_by_id = {} asset_ids_by_parents = collections.defaultdict(set) - for asset_doc in self._doc_payload: + for asset_doc in asset_docs: asset_id = asset_doc["_id"] asset_data = asset_doc.get("data") or {} parent_id = asset_data.get("visualParent") @@ -511,10 +518,6 @@ class AssetModel(QtGui.QStandardItemModel): except Exception: pass - self.refreshed.emit(bool(self._items_by_asset_id)) - - self._stop_fetch_thread() - def _threaded_fetch(self): asset_docs = self._fetch_asset_docs() if not self._refreshing: From c19f216128f7214735cf9f864e763e1651abb5fc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:20:17 +0100 Subject: [PATCH 04/68] assets model cares on it's own if should be cleared on refresh --- openpype/tools/utils/assets_widget.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index 1ae560bd2b..9789078cb8 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -306,6 +306,8 @@ class AssetModel(QtGui.QStandardItemModel): self._items_with_color_by_id = {} self._items_by_asset_id = {} + self._last_project_name = None + @property def refreshing(self): return self._refreshing @@ -347,12 +349,11 @@ class AssetModel(QtGui.QStandardItemModel): return self.get_indexes_by_asset_ids(asset_ids) - def refresh(self, force=False, clear=False): + def refresh(self, force=False): """Refresh the data for the model. Args: force (bool): Stop currently running refresh start new refresh. - clear (bool): Clear model before refresh thread starts. """ # Skip fetch if there is already other thread fetching documents if self._refreshing: @@ -360,7 +361,13 @@ class AssetModel(QtGui.QStandardItemModel): return self.stop_refresh() - if clear: + project_name = self.dbcon.Session.get("AVALON_PROJECT") + clear_model = False + if project_name != self._last_project_name: + clear_model = True + self._last_project_name = project_name + + if clear_model: self._clear_items() # Fetch documents from mongo @@ -655,12 +662,7 @@ class AssetsWidget(QtWidgets.QWidget): return self._model.refreshing def refresh(self): - project_name = self.dbcon.Session.get("AVALON_PROJECT") - clear_model = False - if project_name != self._last_project_name: - clear_model = True - self._last_project_name = project_name - self._refresh_model(clear_model) + self._refresh_model() def stop_refresh(self): self._model.stop_refresh() @@ -706,14 +708,14 @@ class AssetsWidget(QtWidgets.QWidget): self._set_loading_state(loading=False, empty=not has_item) self.refreshed.emit() - def _refresh_model(self, clear=False): + def _refresh_model(self): # Store selection self._set_loading_state(loading=True, empty=True) # Trigger signal before refresh is called self.refresh_triggered.emit() # Refresh model - self._model.refresh(clear=clear) + self._model.refresh() def _set_loading_state(self, loading, empty): self._view.set_loading_state(loading, empty) From 06396fa038e57eb51280c1e33d58ef065e61f557 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:42:55 +0100 Subject: [PATCH 05/68] Create model for whole launcher tool --- openpype/tools/launcher/models.py | 305 +++++++++++++++++++++++++++++- 1 file changed, 303 insertions(+), 2 deletions(-) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 427475cb4b..d1927e2667 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -3,6 +3,10 @@ import copy import logging import collections +from Qt import QtCore, QtGui +from openpype.lib import ApplicationManager +from openpype.tools.utils.lib import DynamicQThread + from . import lib from .constants import ( ACTION_ROLE, @@ -11,10 +15,8 @@ from .constants import ( ACTION_ID_ROLE ) from .actions import ApplicationAction -from Qt import QtCore, QtGui from avalon.vendor import qtawesome from avalon import style, api -from openpype.lib import ApplicationManager log = logging.getLogger(__name__) @@ -223,6 +225,305 @@ class ActionModel(QtGui.QStandardItemModel): ) +class LauncherModel(QtCore.QObject): + # Refresh interval of projects + refresh_interval = 10000 + + # Signals + # Current project has changed + project_changed = QtCore.Signal(str) + # Filters has changed (any) + filters_changed = QtCore.Signal() + + # Projects were refreshed + projects_refreshed = QtCore.Signal() + + # Signals ONLY for assets model! + # - other objects should listen to asset model signals + # Asset refresh started + assets_refresh_started = QtCore.Signal() + # Assets refresh finished + assets_refreshed = QtCore.Signal() + + # Refresh timer timeout + # - give ability to tell parent window that this timer still runs + timer_timeout = QtCore.Signal() + + # Duplication from AssetsModel with "data.tasks" + _asset_projection = { + "name": 1, + "parent": 1, + "data.visualParent": 1, + "data.label": 1, + "data.icon": 1, + "data.color": 1, + "data.tasks": 1 + } + + def __init__(self, dbcon): + super(LauncherModel, self).__init__() + # Refresh timer + # - should affect only projects + refresh_timer = QtCore.QTimer() + refresh_timer.setInterval(self.refresh_interval) + refresh_timer.timeout.connect(self._on_timeout) + + self._refresh_timer = refresh_timer + + # Launcher is active + self._active = False + + # Global data + self._dbcon = dbcon + # Available project names + self._project_names = set() + + # Context data + self._asset_docs = [] + self._asset_docs_by_id = {} + self._asset_filter_data_by_id = {} + self._assignees = set() + self._task_types = set() + + # Filters + self._asset_name_filter = "" + self._assignee_filters = set() + self._task_type_filters = set() + + # Last project for which were assets queried + self._last_project_name = None + # Asset refresh thread is running + self._refreshing_assets = False + # Asset refresh thread + self._asset_refresh_thread = None + + def _on_timeout(self): + """Refresh timer timeout.""" + if self._active: + self.timer_timeout.emit() + self.refresh_projects() + + def set_active(self, active): + """Window change active state.""" + self._active = active + + def start_refresh_timer(self, trigger=False): + """Start refresh timer.""" + self._refresh_timer.start() + if trigger: + self._on_timeout() + + def stop_refresh_timer(self): + """Stop refresh timer.""" + self._refresh_timer.stop() + + @property + def project_name(self): + """Current project name.""" + return self._dbcon.Session.get("AVALON_PROJECT") + + @property + def refreshing_assets(self): + """Refreshing thread is running.""" + return self._refreshing_assets + + @property + def asset_docs(self): + """Access to asset docs.""" + return self._asset_docs + + @property + def project_names(self): + """Available project names.""" + return self._project_names + + @property + def asset_filter_data_by_id(self): + """Prepared filter data by asset id.""" + return self._asset_filter_data_by_id + + @property + def assignees(self): + """All assignees for all assets in current project.""" + return self._assignees + + @property + def task_types(self): + """All task types for all assets in current project. + + TODO: This could be maybe taken from project document where are all + task types... + """ + return self._task_types + + @property + def task_type_filters(self): + """Currently set task type filters.""" + return self._task_type_filters + + @property + def assignee_filters(self): + """Currently set assignee filters.""" + return self._assignee_filters + + @property + def asset_name_filter(self): + """Asset name filter (can be used as regex filter).""" + return self._asset_name_filter + + def get_asset_doc(self, asset_id): + """Get single asset document by id.""" + return self._asset_docs_by_id.get(asset_id) + + def set_project_name(self, project_name): + """Change project name and refresh asset documents.""" + if project_name == self.project_name: + return + self._dbcon.Session["AVALON_PROJECT"] = project_name + self.project_changed.emit(project_name) + + self.refresh_assets(force=True) + + def refresh(self): + """Trigger refresh of whole model.""" + self.refresh_projects() + self.refresh_assets(force=False) + + def refresh_projects(self): + """Refresh projects.""" + current_project = self.project_name + project_names = set() + for project_doc in self._dbcon.projects(only_active=True): + project_names.add(project_doc["name"]) + + self._project_names = project_names + self.projects_refreshed.emit() + if ( + current_project is not None + and current_project not in project_names + ): + self.set_project_name(None) + + def _set_asset_docs(self, asset_docs=None): + """Set asset documents and all related data. + + Method extract and prepare data needed for assets and tasks widget and + prepare filtering data. + """ + if asset_docs is None: + asset_docs = [] + + all_task_types = set() + all_assignees = set() + asset_docs_by_id = {} + asset_filter_data_by_id = {} + for asset_doc in asset_docs: + task_types = set() + assignees = set() + asset_id = asset_doc["_id"] + asset_docs_by_id[asset_id] = asset_doc + asset_tasks = asset_doc.get("data", {}).get("tasks") + asset_filter_data_by_id[asset_id] = { + "assignees": assignees, + "task_types": task_types + } + if not asset_tasks: + continue + + for task_data in asset_tasks.values(): + task_assignees = set() + _task_assignees = task_data.get("assignees") + if _task_assignees: + for assignee in _task_assignees: + task_assignees.add(assignee["username"]) + + task_type = task_data.get("type") + if task_assignees: + assignees |= set(task_assignees) + if task_type: + task_types.add(task_type) + + all_task_types |= task_types + all_assignees |= assignees + + self._asset_docs_by_id = asset_docs_by_id + self._asset_docs = asset_docs + self._asset_filter_data_by_id = asset_filter_data_by_id + self._assignees = all_assignees + self._task_types = all_task_types + + self.assets_refreshed.emit() + + def set_task_type_filter(self, task_types): + """Change task type filter. + + Args: + task_types (set): Set of task types that should be visible. + Pass empty set to turn filter off. + """ + self._task_type_filters = task_types + self.filters_changed.emit() + + def set_assignee_filter(self, assignees): + """Change assignees filter. + + Args: + assignees (set): Set of assignees that should be visible. + Pass empty set to turn filter off. + """ + self._assignee_filters = assignees + self.filters_changed.emit() + + def set_asset_name_filter(self, text_filter): + """Change asset name filter. + + Args: + text_filter (str): Asset name filter. Pass empty string to + turn filter off. + """ + self._asset_name_filter = text_filter + self.filters_changed.emit() + + def refresh_assets(self, force=True): + """Refresh assets.""" + self.assets_refresh_started.emit() + + if self.project_name is None: + self._set_asset_docs() + return + + if ( + not force + and self._last_project_name == self.project_name + ): + return + + self._stop_fetch_thread() + + self._refreshing_assets = True + self._last_project_name = self.project_name + self._asset_refresh_thread = DynamicQThread(self._refresh_assets) + self._asset_refresh_thread.start() + + def _stop_fetch_thread(self): + self._refreshing_assets = False + if self._asset_refresh_thread is not None: + while self._asset_refresh_thread.isRunning(): + time.sleep(0.01) + self._asset_refresh_thread = None + + def _refresh_assets(self): + asset_docs = list(self._dbcon.find( + {"type": "asset"}, + self._asset_projection + )) + time.sleep(5) + if not self._refreshing_assets: + return + self._refreshing_assets = False + self._set_asset_docs(asset_docs) + + class ProjectModel(QtGui.QStandardItemModel): """List of projects""" From 557de8c3aa51858e40145bbf9ea4b9e14d0da687 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:43:40 +0100 Subject: [PATCH 06/68] create slightly modified task model --- openpype/tools/launcher/models.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index d1927e2667..1c61bd5012 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -4,8 +4,13 @@ import logging import collections from Qt import QtCore, QtGui +from avalon.vendor import qtawesome +from avalon import style, api from openpype.lib import ApplicationManager from openpype.tools.utils.lib import DynamicQThread +from openpype.tools.utils.tasks_widget import ( + TasksModel, +) from . import lib from .constants import ( @@ -15,8 +20,6 @@ from .constants import ( ACTION_ID_ROLE ) from .actions import ApplicationAction -from avalon.vendor import qtawesome -from avalon import style, api log = logging.getLogger(__name__) @@ -524,6 +527,18 @@ class LauncherModel(QtCore.QObject): self._set_asset_docs(asset_docs) +class LauncherTaskModel(TasksModel): + def __init__(self, launcher_model, *args, **kwargs): + self._launcher_model = launcher_model + super(LauncherTaskModel, self).__init__(*args, **kwargs) + + def set_asset_id(self, asset_id): + asset_doc = None + if self._context_is_valid(): + asset_doc = self._launcher_model.get_asset_doc(asset_id) + self._set_asset(asset_doc) + + class ProjectModel(QtGui.QStandardItemModel): """List of projects""" From c874adfcb6e16e9d6bb239ea225c0184f2799849 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:47:11 +0100 Subject: [PATCH 07/68] tasks widget also can know about assignees --- openpype/tools/utils/tasks_widget.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py index 699b1cf569..204719e739 100644 --- a/openpype/tools/utils/tasks_widget.py +++ b/openpype/tools/utils/tasks_widget.py @@ -9,6 +9,7 @@ from .views import DeselectableTreeView TASK_NAME_ROLE = QtCore.Qt.UserRole + 1 TASK_TYPE_ROLE = QtCore.Qt.UserRole + 2 TASK_ORDER_ROLE = QtCore.Qt.UserRole + 3 +TASK_ASSIGNEE_ROLE = QtCore.Qt.UserRole + 4 class TasksModel(QtGui.QStandardItemModel): @@ -144,11 +145,19 @@ class TasksModel(QtGui.QStandardItemModel): task_type_icon = task_type_info.get("icon") icon = self._get_icon(task_icon, task_type_icon) + task_assignees = set() + assignees_data = task_info.get("assignees") or [] + for assignee in assignees_data: + username = assignee.get("username") + if username: + task_assignees.add(username) + label = "{} ({})".format(task_name, task_type or "type N/A") item = QtGui.QStandardItem(label) item.setData(task_name, TASK_NAME_ROLE) item.setData(task_type, TASK_TYPE_ROLE) item.setData(task_order, TASK_ORDER_ROLE) + item.setData(task_assignees, TASK_ASSIGNEE_ROLE) item.setData(icon, QtCore.Qt.DecorationRole) item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable) items.append(item) From 6d55d5d11a5342c360f912eaa8f82eefa763c78d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:49:44 +0100 Subject: [PATCH 08/68] created task proxy model with more filtering --- openpype/tools/launcher/models.py | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 1c61bd5012..c4836fb1af 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -10,6 +10,9 @@ from openpype.lib import ApplicationManager from openpype.tools.utils.lib import DynamicQThread from openpype.tools.utils.tasks_widget import ( TasksModel, + TasksProxyModel, + TASK_TYPE_ROLE, + TASK_ASSIGNEE_ROLE ) from . import lib @@ -527,6 +530,48 @@ class LauncherModel(QtCore.QObject): self._set_asset_docs(asset_docs) +class LauncherTasksProxyModel(TasksProxyModel): + """Tasks proxy model with more filtering. + + TODO: + This can be (with few modifications) used in default tasks widget too. + """ + def __init__(self, launcher_model, *args, **kwargs): + self._launcher_model = launcher_model + super(LauncherTasksProxyModel, self).__init__(*args, **kwargs) + + launcher_model.filters_changed.connect(self._on_filter_change) + + self._task_types_filter = set() + self._assignee_filter = set() + + def _on_filter_change(self): + self._task_types_filter = self._launcher_model.task_type_filters + self._assignee_filter = self._launcher_model.assignee_filters + self.invalidateFilter() + + def filterAcceptsRow(self, row, parent): + if not self._task_types_filter and not self._assignee_filter: + return True + + model = self.sourceModel() + source_index = model.index(row, self.filterKeyColumn(), parent) + if not source_index.isValid(): + return False + + # Check current index itself + if self._task_types_filter: + task_type = model.data(source_index, TASK_TYPE_ROLE) + if task_type not in self._task_types_filter: + return False + + if self._assignee_filter: + assignee = model.data(source_index, TASK_ASSIGNEE_ROLE) + if not self._assignee_filter.intersection(assignee): + return False + return True + + class LauncherTaskModel(TasksModel): def __init__(self, launcher_model, *args, **kwargs): self._launcher_model = launcher_model From e749a8ee53671b3cadb7a7e85cfbd9a76a3b0057 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:50:23 +0100 Subject: [PATCH 09/68] added modified assets model --- openpype/tools/launcher/models.py | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index c4836fb1af..13ca95fe43 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -8,6 +8,10 @@ from avalon.vendor import qtawesome from avalon import style, api from openpype.lib import ApplicationManager from openpype.tools.utils.lib import DynamicQThread +from openpype.tools.utils.assets_widget import ( + AssetModel, + ASSET_NAME_ROLE +) from openpype.tools.utils.tasks_widget import ( TasksModel, TasksProxyModel, @@ -26,6 +30,10 @@ from .actions import ApplicationAction log = logging.getLogger(__name__) +# Must be different than roles in default asset model +ASSET_TASK_TYPES_ROLE = QtCore.Qt.UserRole + 10 +ASSET_ASSIGNEE_ROLE = QtCore.Qt.UserRole + 11 + class ActionModel(QtGui.QStandardItemModel): def __init__(self, dbcon, parent=None): @@ -584,6 +592,54 @@ class LauncherTaskModel(TasksModel): self._set_asset(asset_doc) +class LauncherAssetsModel(AssetModel): + def __init__(self, launcher_model, dbcon, parent=None): + self._launcher_model = launcher_model + # Make sure that variable is available (even if is in AssetModel) + self._last_project_name = None + + super(LauncherAssetsModel, self).__init__(dbcon, parent) + + launcher_model.project_changed.connect(self._on_project_change) + launcher_model.assets_refresh_started.connect( + self._on_launcher_refresh_start + ) + launcher_model.assets_refreshed.connect(self._on_launcher_refresh) + + def _on_launcher_refresh_start(self): + self._refreshing = True + project_name = self._launcher_model.project_name + if self._last_project_name != project_name: + self._clear_items() + self._last_project_name = project_name + + def _on_launcher_refresh(self): + self._fill_assets(self._launcher_model.asset_docs) + self._refreshing = False + self.refreshed.emit(bool(self._items_by_asset_id)) + + def _fill_assets(self, *args, **kwargs): + super(LauncherAssetsModel, self)._fill_assets(*args, **kwargs) + asset_filter_data_by_id = self._launcher_model.asset_filter_data_by_id + for asset_id, item in self._items_by_asset_id.items(): + filter_data = asset_filter_data_by_id.get(asset_id) + + assignees = filter_data["assignees"] + task_types = filter_data["task_types"] + + item.setData(assignees, ASSET_ASSIGNEE_ROLE) + item.setData(task_types, ASSET_TASK_TYPES_ROLE) + + def _on_project_change(self): + self._clear_items() + + def refresh(self, *args, **kwargs): + raise ValueError("This is a bug!") + + def stop_refresh(self, *args, **kwargs): + raise ValueError("This is a bug!") + + class ProjectModel(QtGui.QStandardItemModel): """List of projects""" From 577a82ca7b67b923d64a6799e509219a9a2a758e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:50:48 +0100 Subject: [PATCH 10/68] created new recursive assets proxy with more filters --- openpype/tools/launcher/models.py | 60 +++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index 13ca95fe43..dc06ec4e28 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -1,3 +1,4 @@ +import re import uuid import copy import logging @@ -592,6 +593,65 @@ class LauncherTaskModel(TasksModel): self._set_asset(asset_doc) +class AssetRecursiveSortFilterModel(QtCore.QSortFilterProxyModel): + def __init__(self, launcher_model, *args, **kwargs): + self._launcher_model = launcher_model + + super(AssetRecursiveSortFilterModel, self).__init__(*args, **kwargs) + + launcher_model.filters_changed.connect(self._on_filter_change) + self._name_filter = "" + self._task_types_filter = set() + self._assignee_filter = set() + + def _on_filter_change(self): + self._name_filter = self._launcher_model.asset_name_filter + self._task_types_filter = self._launcher_model.task_type_filters + self._assignee_filter = self._launcher_model.assignee_filters + self.invalidateFilter() + + """Filters to the regex if any of the children matches allow parent""" + def filterAcceptsRow(self, row, parent): + if ( + not self._name_filter + and not self._task_types_filter + and not self._assignee_filter + ): + return True + + model = self.sourceModel() + source_index = model.index(row, self.filterKeyColumn(), parent) + if not source_index.isValid(): + return False + + # Check current index itself + valid = True + if self._name_filter: + name = model.data(source_index, ASSET_NAME_ROLE) + if not re.search(self._name_filter, name, re.IGNORECASE): + valid = False + + if valid and self._task_types_filter: + task_types = model.data(source_index, ASSET_TASK_TYPES_ROLE) + if not self._task_types_filter.intersection(task_types): + valid = False + + if valid and self._assignee_filter: + assignee = model.data(source_index, ASSET_ASSIGNEE_ROLE) + if not self._assignee_filter.intersection(assignee): + valid = False + + if valid: + return True + + # Check children + rows = model.rowCount(source_index) + for child_row in range(rows): + if self.filterAcceptsRow(child_row, source_index): + return True + return False + + class LauncherAssetsModel(AssetModel): def __init__(self, launcher_model, dbcon, parent=None): self._launcher_model = launcher_model From ccf3b1de219a2a8c5fe55c1e6f644e76442b08a8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:51:53 +0100 Subject: [PATCH 11/68] modified ProjectModel to use launcher model --- openpype/tools/launcher/models.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index dc06ec4e28..f518ac78a7 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -3,6 +3,7 @@ import uuid import copy import logging import collections +import time from Qt import QtCore, QtGui from avalon.vendor import qtawesome @@ -524,6 +525,7 @@ class LauncherModel(QtCore.QObject): self._refreshing_assets = False if self._asset_refresh_thread is not None: while self._asset_refresh_thread.isRunning(): + # TODO this is blocking UI should be done in a different way time.sleep(0.01) self._asset_refresh_thread = None @@ -532,7 +534,6 @@ class LauncherModel(QtCore.QObject): {"type": "asset"}, self._asset_projection )) - time.sleep(5) if not self._refreshing_assets: return self._refreshing_assets = False @@ -703,18 +704,17 @@ class LauncherAssetsModel(AssetModel): class ProjectModel(QtGui.QStandardItemModel): """List of projects""" - def __init__(self, dbcon, parent=None): + def __init__(self, launcher_model, parent=None): super(ProjectModel, self).__init__(parent=parent) - self.dbcon = dbcon + self._launcher_model = launcher_model self.project_icon = qtawesome.icon("fa.map", color="white") self._project_names = set() - def refresh(self): - project_names = set() - for project_doc in self.get_projects(): - project_names.add(project_doc["name"]) + launcher_model.projects_refreshed.connect(self._on_refresh) + def _on_refresh(self): + project_names = set(self._launcher_model.project_names) origin_project_names = set(self._project_names) self._project_names = project_names @@ -757,7 +757,3 @@ class ProjectModel(QtGui.QStandardItemModel): items.append(item) self.invisibleRootItem().insertRows(row, items) - - def get_projects(self): - return sorted(self.dbcon.projects(only_active=True), - key=lambda x: x["name"]) From 258dfbffd9e8c923a07e65cca0d3dae22d654b1f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:54:45 +0100 Subject: [PATCH 12/68] ProjectBar and ActionBar are using launcher model --- openpype/tools/launcher/widgets.py | 35 +++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py index edda8d08b5..33118e03be 100644 --- a/openpype/tools/launcher/widgets.py +++ b/openpype/tools/launcher/widgets.py @@ -4,10 +4,14 @@ import collections from Qt import QtWidgets, QtCore, QtGui from avalon.vendor import qtawesome +from openpype.tools.flickcharm import FlickCharm + from .delegates import ActionDelegate from . import lib -from .models import ActionModel -from openpype.tools.flickcharm import FlickCharm +from .models import ( + ActionModel, + ProjectModel, +) from .constants import ( ACTION_ROLE, GROUP_ROLE, @@ -20,15 +24,15 @@ from .constants import ( class ProjectBar(QtWidgets.QWidget): - def __init__(self, project_handler, parent=None): + def __init__(self, launcher_model, parent=None): super(ProjectBar, self).__init__(parent) project_combobox = QtWidgets.QComboBox(self) # Change delegate so stylysheets are applied project_delegate = QtWidgets.QStyledItemDelegate(project_combobox) project_combobox.setItemDelegate(project_delegate) - - project_combobox.setModel(project_handler.model) + model = ProjectModel(launcher_model) + project_combobox.setModel(model) project_combobox.setRootModelIndex(QtCore.QModelIndex()) layout = QtWidgets.QHBoxLayout(self) @@ -40,16 +44,17 @@ class ProjectBar(QtWidgets.QWidget): QtWidgets.QSizePolicy.Maximum ) - self.project_handler = project_handler + self._launcher_model = launcher_model self.project_delegate = project_delegate self.project_combobox = project_combobox + self._model = model # Signals self.project_combobox.currentIndexChanged.connect(self.on_index_change) - project_handler.project_changed.connect(self._on_project_change) + launcher_model.project_changed.connect(self._on_project_change) # Set current project by default if it's set. - project_name = project_handler.current_project + project_name = launcher_model.project_name if project_name: self.set_project(project_name) @@ -65,7 +70,7 @@ class ProjectBar(QtWidgets.QWidget): index = self.project_combobox.findText(project_name) if index < 0: # Try refresh combobox model - self.project_handler.refresh_model() + self._launcher_model.refresh_projects() index = self.project_combobox.findText(project_name) if index >= 0: @@ -76,7 +81,7 @@ class ProjectBar(QtWidgets.QWidget): return project_name = self.get_current_project() - self.project_handler.set_project(project_name) + self._launcher_model.set_project_name(project_name) class ActionBar(QtWidgets.QWidget): @@ -84,10 +89,10 @@ class ActionBar(QtWidgets.QWidget): action_clicked = QtCore.Signal(object) - def __init__(self, project_handler, dbcon, parent=None): + def __init__(self, launcher_model, dbcon, parent=None): super(ActionBar, self).__init__(parent) - self.project_handler = project_handler + self._launcher_model = launcher_model self.dbcon = dbcon view = QtWidgets.QListView(self) @@ -133,7 +138,7 @@ class ActionBar(QtWidgets.QWidget): self.set_row_height(1) - project_handler.projects_refreshed.connect(self._on_projects_refresh) + launcher_model.projects_refreshed.connect(self._on_projects_refresh) view.clicked.connect(self.on_clicked) def discover_actions(self): @@ -172,7 +177,7 @@ class ActionBar(QtWidgets.QWidget): def _start_animation(self, index): # Offset refresh timout - self.project_handler.start_timer() + self.launcher_model.start_refresh_timer() action_id = index.data(ACTION_ID_ROLE) item = self.model.items_by_id.get(action_id) if item: @@ -194,7 +199,7 @@ class ActionBar(QtWidgets.QWidget): return # Offset refresh timout - self.project_handler.start_timer() + self.launcher_model.start_refresh_timer() actions = index.data(ACTION_ROLE) From cde8c69cbabca6b9471cc30996b7a4a8000cfb06 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:55:12 +0100 Subject: [PATCH 13/68] modify tasks widget to use different models --- openpype/tools/launcher/widgets.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py index 33118e03be..4408219628 100644 --- a/openpype/tools/launcher/widgets.py +++ b/openpype/tools/launcher/widgets.py @@ -5,12 +5,15 @@ from Qt import QtWidgets, QtCore, QtGui from avalon.vendor import qtawesome from openpype.tools.flickcharm import FlickCharm +from openpype.tools.utils.tasks_widget import TasksWidget from .delegates import ActionDelegate from . import lib from .models import ( ActionModel, ProjectModel, + LauncherTaskModel, + LauncherTasksProxyModel ) from .constants import ( ACTION_ROLE, @@ -84,6 +87,21 @@ class ProjectBar(QtWidgets.QWidget): self._launcher_model.set_project_name(project_name) +class LauncherTaskWidget(TasksWidget): + def __init__(self, launcher_model, *args, **kwargs): + self._launcher_model = launcher_model + + super(LauncherTaskWidget, self).__init__(*args, **kwargs) + + def _create_source_model(self): + return LauncherTaskModel(self._launcher_model, self._dbcon) + + def _create_proxy_model(self, source_model): + proxy = LauncherTasksProxyModel(self._launcher_model) + proxy.setSourceModel(source_model) + return proxy + + class ActionBar(QtWidgets.QWidget): """Launcher interface""" From a232f8bc58b918e1e31741ea06c715b2ee97b992 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:55:33 +0100 Subject: [PATCH 14/68] modified assets widget to use new models nad modified what is called when --- openpype/tools/launcher/widgets.py | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py index 4408219628..397b29a1b0 100644 --- a/openpype/tools/launcher/widgets.py +++ b/openpype/tools/launcher/widgets.py @@ -5,6 +5,7 @@ from Qt import QtWidgets, QtCore, QtGui from avalon.vendor import qtawesome from openpype.tools.flickcharm import FlickCharm +from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget from openpype.tools.utils.tasks_widget import TasksWidget from .delegates import ActionDelegate @@ -12,6 +13,8 @@ from . import lib from .models import ( ActionModel, ProjectModel, + LauncherAssetsModel, + AssetRecursiveSortFilterModel, LauncherTaskModel, LauncherTasksProxyModel ) @@ -102,6 +105,54 @@ class LauncherTaskWidget(TasksWidget): return proxy +class LauncherAssetsWidget(SingleSelectAssetsWidget): + def __init__(self, launcher_model, *args, **kwargs): + self._launcher_model = launcher_model + + super(LauncherAssetsWidget, self).__init__(*args, **kwargs) + + launcher_model.assets_refresh_started.connect(self._on_refresh_start) + + self.set_current_asset_btn_visibility(False) + + def _on_refresh_start(self): + self._set_loading_state(loading=True, empty=True) + self.refresh_triggered.emit() + + @property + def refreshing(self): + return self._model.refreshing + + def refresh(self): + self._launcher_model.refresh_assets(force=True) + + def stop_refresh(self): + raise ValueError("bug stop_refresh called") + + def _refresh_model(self, clear=False): + raise ValueError("bug _refresh_model called") + + def _create_source_model(self): + model = LauncherAssetsModel(self._launcher_model, self.dbcon) + model.refreshed.connect(self._on_model_refresh) + return model + + def _create_proxy_model(self, source_model): + proxy = AssetRecursiveSortFilterModel(self._launcher_model) + proxy.setSourceModel(source_model) + proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) + proxy.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) + return proxy + + def _on_model_refresh(self, has_item): + self._proxy.sort(0) + self._set_loading_state(loading=False, empty=not has_item) + self.refreshed.emit() + + def _on_filter_text_change(self, new_text): + self._launcher_model.set_asset_name_filter(new_text) + + class ActionBar(QtWidgets.QWidget): """Launcher interface""" From 27d067fbdf66a2f61ef005236b2fadde4e758829 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:56:47 +0100 Subject: [PATCH 15/68] project panel is using launcher model --- openpype/tools/launcher/window.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/tools/launcher/window.py b/openpype/tools/launcher/window.py index a8f65894f2..9b5a21ebc2 100644 --- a/openpype/tools/launcher/window.py +++ b/openpype/tools/launcher/window.py @@ -89,15 +89,15 @@ class ProjectIconView(QtWidgets.QListView): class ProjectsPanel(QtWidgets.QWidget): """Projects Page""" - def __init__(self, project_handler, parent=None): + def __init__(self, launcher_model, parent=None): super(ProjectsPanel, self).__init__(parent=parent) view = ProjectIconView(parent=self) view.setSelectionMode(QtWidgets.QListView.NoSelection) flick = FlickCharm(parent=self) flick.activateOn(view) - - view.setModel(project_handler.model) + model = ProjectModel(launcher_model) + view.setModel(model) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) @@ -105,13 +105,14 @@ class ProjectsPanel(QtWidgets.QWidget): view.clicked.connect(self.on_clicked) + self._model = model self.view = view - self.project_handler = project_handler + self._launcher_model = launcher_model def on_clicked(self, index): if index.isValid(): project_name = index.data(QtCore.Qt.DisplayRole) - self.project_handler.set_project(project_name) + self._launcher_model.set_project_name(project_name) class AssetsPanel(QtWidgets.QWidget): @@ -119,7 +120,7 @@ class AssetsPanel(QtWidgets.QWidget): back_clicked = QtCore.Signal() session_changed = QtCore.Signal() - def __init__(self, project_handler, dbcon, parent=None): + def __init__(self, launcher_model, dbcon, parent=None): super(AssetsPanel, self).__init__(parent=parent) self.dbcon = dbcon @@ -129,7 +130,7 @@ class AssetsPanel(QtWidgets.QWidget): btn_back = QtWidgets.QPushButton(self) btn_back.setIcon(btn_back_icon) - project_bar = ProjectBar(project_handler, self) + project_bar = ProjectBar(launcher_model, self) project_bar_layout = QtWidgets.QHBoxLayout() project_bar_layout.setContentsMargins(0, 0, 0, 0) From 9b044ffd828f3c8015fdb031e67d371599c9606d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:57:07 +0100 Subject: [PATCH 16/68] use new widgets and models in launcher window --- openpype/tools/launcher/window.py | 52 ++++++++++++++++--------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/openpype/tools/launcher/window.py b/openpype/tools/launcher/window.py index 9b5a21ebc2..b5b6368865 100644 --- a/openpype/tools/launcher/window.py +++ b/openpype/tools/launcher/window.py @@ -8,17 +8,19 @@ from avalon.api import AvalonMongoDB from openpype import style from openpype.api import resources -from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget -from openpype.tools.utils.tasks_widget import TasksWidget - from avalon.vendor import qtawesome -from .models import ProjectModel -from .lib import get_action_label, ProjectHandler +from .models import ( + LauncherModel, + ProjectModel +) +from .lib import get_action_label from .widgets import ( ProjectBar, ActionBar, ActionHistory, - SlidePageWidget + SlidePageWidget, + LauncherAssetsWidget, + LauncherTaskWidget ) from openpype.tools.flickcharm import FlickCharm @@ -139,12 +141,14 @@ class AssetsPanel(QtWidgets.QWidget): project_bar_layout.addWidget(project_bar) # Assets widget - assets_widget = SingleSelectAssetsWidget(dbcon=self.dbcon, parent=self) + assets_widget = LauncherAssetsWidget( + launcher_model, dbcon=self.dbcon, parent=self + ) # Make assets view flickable assets_widget.activate_flick_charm() # Tasks widget - tasks_widget = TasksWidget(self.dbcon, self) + tasks_widget = LauncherTaskWidget(launcher_model, self.dbcon, self) # Body body = QtWidgets.QSplitter(self) @@ -166,19 +170,20 @@ class AssetsPanel(QtWidgets.QWidget): layout.addWidget(body) # signals - project_handler.project_changed.connect(self._on_project_changed) + launcher_model.project_changed.connect(self._on_project_changed) assets_widget.selection_changed.connect(self._on_asset_changed) assets_widget.refreshed.connect(self._on_asset_changed) tasks_widget.task_changed.connect(self._on_task_change) btn_back.clicked.connect(self.back_clicked) - self.project_handler = project_handler self.project_bar = project_bar self.assets_widget = assets_widget self._tasks_widget = tasks_widget self._btn_back = btn_back + self._launcher_model = launcher_model + def select_asset(self, asset_name): self.assets_widget.select_asset_by_name(asset_name) @@ -197,8 +202,6 @@ class AssetsPanel(QtWidgets.QWidget): def _on_project_changed(self): self.session_changed.emit() - self.assets_widget.refresh() - def _on_asset_changed(self): """Callback on asset selection changed @@ -251,18 +254,17 @@ class LauncherWindow(QtWidgets.QDialog): | QtCore.Qt.WindowCloseButtonHint ) - project_model = ProjectModel(self.dbcon) - project_handler = ProjectHandler(self.dbcon, project_model) + launcher_model = LauncherModel(self.dbcon) - project_panel = ProjectsPanel(project_handler) - asset_panel = AssetsPanel(project_handler, self.dbcon) + project_panel = ProjectsPanel(launcher_model) + asset_panel = AssetsPanel(launcher_model, self.dbcon) page_slider = SlidePageWidget() page_slider.addWidget(project_panel) page_slider.addWidget(asset_panel) # actions - actions_bar = ActionBar(project_handler, self.dbcon, self) + actions_bar = ActionBar(launcher_model, self.dbcon, self) # statusbar message_label = QtWidgets.QLabel(self) @@ -304,8 +306,8 @@ class LauncherWindow(QtWidgets.QDialog): # signals actions_bar.action_clicked.connect(self.on_action_clicked) action_history.trigger_history.connect(self.on_history_action) - project_handler.project_changed.connect(self.on_project_change) - project_handler.timer_timeout.connect(self._on_refresh_timeout) + launcher_model.project_changed.connect(self.on_project_change) + launcher_model.timer_timeout.connect(self._on_refresh_timeout) asset_panel.back_clicked.connect(self.on_back_clicked) asset_panel.session_changed.connect(self.on_session_changed) @@ -315,7 +317,7 @@ class LauncherWindow(QtWidgets.QDialog): self._message_timer = message_timer - self.project_handler = project_handler + self._launcher_model = launcher_model self._message_label = message_label self.project_panel = project_panel @@ -325,19 +327,19 @@ class LauncherWindow(QtWidgets.QDialog): self.page_slider = page_slider def showEvent(self, event): - self.project_handler.set_active(True) - self.project_handler.start_timer(True) + self._launcher_model.set_active(True) + self._launcher_model.start_refresh_timer(True) super(LauncherWindow, self).showEvent(event) def _on_refresh_timeout(self): # Stop timer if widget is not visible if not self.isVisible(): - self.project_handler.stop_timer() + self._launcher_model.stop_refresh_timer() def changeEvent(self, event): if event.type() == QtCore.QEvent.ActivationChange: - self.project_handler.set_active(self.isActiveWindow()) + self._launcher_model.set_active(self.isActiveWindow()) super(LauncherWindow, self).changeEvent(event) def set_page(self, page): @@ -372,7 +374,7 @@ class LauncherWindow(QtWidgets.QDialog): self.discover_actions() def on_back_clicked(self): - self.project_handler.set_project(None) + self._launcher_model.set_project_name(None) self.set_page(0) self.discover_actions() From c776ee07ba6f7e357f12ef4f53d829182130d11e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 21:57:14 +0100 Subject: [PATCH 17/68] removed unused project handler --- openpype/tools/launcher/lib.py | 71 ---------------------------------- 1 file changed, 71 deletions(-) diff --git a/openpype/tools/launcher/lib.py b/openpype/tools/launcher/lib.py index d6374f49d2..a7c686a7d0 100644 --- a/openpype/tools/launcher/lib.py +++ b/openpype/tools/launcher/lib.py @@ -23,77 +23,6 @@ ICON_CACHE = {} NOT_FOUND = type("NotFound", (object, ), {}) -class ProjectHandler(QtCore.QObject): - """Handler of project model and current project in Launcher tool. - - Helps to organize two separate widgets handling current project selection. - - It is easier to trigger project change callbacks from one place than from - multiple differect places without proper handling or sequence changes. - - Args: - dbcon(AvalonMongoDB): Mongo connection with Session. - model(ProjectModel): Object of projects model which is shared across - all widgets using projects. Arg dbcon should be used as source for - the model. - """ - # Project list will be refreshed each 10000 msecs - # - this is not part of helper implementation but should be used by widgets - # that may require reshing of projects - refresh_interval = 10000 - - # Signal emmited when project has changed - project_changed = QtCore.Signal(str) - projects_refreshed = QtCore.Signal() - timer_timeout = QtCore.Signal() - - def __init__(self, dbcon, model): - super(ProjectHandler, self).__init__() - self._active = False - # Store project model for usage - self.model = model - # Store dbcon - self.dbcon = dbcon - - self.current_project = dbcon.Session.get("AVALON_PROJECT") - - refresh_timer = QtCore.QTimer() - refresh_timer.setInterval(self.refresh_interval) - refresh_timer.timeout.connect(self._on_timeout) - - self.refresh_timer = refresh_timer - - def _on_timeout(self): - if self._active: - self.timer_timeout.emit() - self.refresh_model() - - def set_active(self, active): - self._active = active - - def start_timer(self, trigger=False): - self.refresh_timer.start() - if trigger: - self._on_timeout() - - def stop_timer(self): - self.refresh_timer.stop() - - def set_project(self, project_name): - # Change current project of this handler - self.current_project = project_name - # Change session project to take effect for other widgets using the - # dbcon object. - self.dbcon.Session["AVALON_PROJECT"] = project_name - - # Trigger change signal when everything is updated to new project - self.project_changed.emit(project_name) - - def refresh_model(self): - self.model.refresh() - self.projects_refreshed.emit() - - def get_action_icon(action): icon_name = action.icon if not icon_name: From 856a8e2b02ecb3937c354ac5eb67826dcf7d633a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 8 Feb 2022 14:22:50 +0100 Subject: [PATCH 18/68] fix imports after merge --- openpype/tools/launcher/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index ab22809f7c..eea32e1aee 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -8,8 +8,8 @@ import time import appdirs from Qt import QtCore, QtGui from avalon.vendor import qtawesome -from avalon import style, api -from openpype.lib import ApplicationManager +from avalon import api +from openpype.lib import ApplicationManager, JSONSettingRegistry from openpype.tools.utils.lib import DynamicQThread from openpype.tools.utils.assets_widget import ( AssetModel, From 4af3b1b1f997dd24e5d48391cbf2337602be403d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 11 Feb 2022 17:25:57 +0100 Subject: [PATCH 19/68] Draft implementation for search in settings --- openpype/tools/settings/settings/search.py | 137 +++++++++++++++++++++ openpype/tools/settings/settings/window.py | 25 ++++ 2 files changed, 162 insertions(+) create mode 100644 openpype/tools/settings/settings/search.py diff --git a/openpype/tools/settings/settings/search.py b/openpype/tools/settings/settings/search.py new file mode 100644 index 0000000000..3d61dd33fe --- /dev/null +++ b/openpype/tools/settings/settings/search.py @@ -0,0 +1,137 @@ +from functools import partial +import re + +from Qt import QtCore, QtWidgets +from openpype.tools.utils.models import TreeModel, Item +from openpype.tools.utils.lib import schedule + + +def get_entity_children(entity): + + if hasattr(entity, "values"): + return entity.values() + return [] + + +class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): + """Filters recursively to regex in all columns""" + + def __init__(self): + super(RecursiveSortFilterProxyModel, self).__init__() + + # Note: Recursive filtering was introduced in Qt 5.10. + self.setRecursiveFilteringEnabled(True) + + def filterAcceptsRow(self, row, parent): + + if not parent.isValid(): + return False + + regex = self.filterRegExp() + if not regex.isEmpty() and regex.isValid(): + pattern = regex.pattern() + source_model = self.sourceModel() + + # Check current index itself in all columns + for column in range(source_model.columnCount(parent)): + source_index = source_model.index(row, column, parent) + if not source_index.isValid(): + continue + + key = source_model.data(source_index, self.filterRole()) + if not key: + continue + + if re.search(pattern, key, re.IGNORECASE): + return True + + return False + + return super(RecursiveSortFilterProxyModel, + self).filterAcceptsRow(row, parent) + + +class SearchEntitiesDialog(QtWidgets.QDialog): + + path_clicked = QtCore.Signal(str) + + def __init__(self, entity, parent=None): + super(SearchEntitiesDialog, self).__init__(parent=parent) + + layout = QtWidgets.QVBoxLayout(self) + + filter_edit = QtWidgets.QLineEdit() + filter_edit.setPlaceholderText("Search..") + + model = EntityTreeModel() + proxy = RecursiveSortFilterProxyModel() + proxy.setSourceModel(model) + proxy.setDynamicSortFilter(True) + view = QtWidgets.QTreeView() + view.setModel(proxy) + + layout.addWidget(filter_edit) + layout.addWidget(view) + + filter_edit.textChanged.connect(self._on_filter_changed) + view.selectionModel().selectionChanged.connect(self.on_select) + + view.setAllColumnsShowFocus(True) + view.setSortingEnabled(True) + view.sortByColumn(1, QtCore.Qt.AscendingOrder) + + self._model = model + self._proxy = proxy + self._view = view + + # Refresh to the passed entity + model.set_root(entity) + + view.resizeColumnToContents(0) + + def _on_filter_changed(self, txt): + # Provide slight delay to filtering so user can type quickly + schedule(partial(self.on_filter_changed, txt), 250, channel="search") + + def on_filter_changed(self, txt): + self._proxy.setFilterRegExp(txt) + + # WARNING This expanding and resizing is relatively slow. + self._view.expandAll() + self._view.resizeColumnToContents(0) + + def on_select(self): + current = self._view.currentIndex() + item = current.data(EntityTreeModel.ItemRole) + self.path_clicked.emit(item["path"]) + + +class EntityTreeModel(TreeModel): + + Columns = ["trail", "label", "key", "path"] + + def add_entity(self, entity, parent=None): + + item = Item() + + # Label and key can sometimes be emtpy so we use the trail from path + # in the most left column since it's never empty + item["trail"] = entity.path.rsplit("/", 1)[-1] + item["label"] = entity.label + item["key"] = entity.key + item["path"] = entity.path + + parent.add_child(item) + + for child in get_entity_children(entity): + self.add_entity(child, parent=item) + + def set_root(self, root_entity): + self.clear() + self.beginResetModel() + + # We don't want to see the root entity so we directly add its children + for child in get_entity_children(root_entity): + self.add_entity(child, parent=self._root_item) + self.endResetModel() + diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index 411e7b5e7f..bbfba088ba 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -164,3 +164,28 @@ class MainWidget(QtWidgets.QWidget): result = dialog.exec_() if result == 1: self.trigger_restart.emit() + + def keyPressEvent(self, event): + + # todo: This might not be the cleanest place but works for prototype + if event.matches(QtGui.QKeySequence.Find): + print("Search!") + + # todo: search in all widgets (or in active)? + widget = self._header_tab_widget.currentWidget() + root_entity = widget.entity + + from .search import SearchEntitiesDialog + search = SearchEntitiesDialog(root_entity, parent=self) + search.resize(700, 500) + search.setWindowTitle("Search") + search.show() + + def on_path(path): + widget.breadcrumbs_widget.change_path(path) + + search.path_clicked.connect(on_path) + event.accept() + return + + return super(MainWidget, self).keyPressEvent(event) From 12e7f7534750b2b086a6b6428ff7bb7fe111ca39 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 12 Feb 2022 13:30:03 +0100 Subject: [PATCH 20/68] renamed search.py to search_dialog.py --- .../tools/settings/settings/{search.py => search_dialog.py} | 0 openpype/tools/settings/settings/window.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename openpype/tools/settings/settings/{search.py => search_dialog.py} (100%) diff --git a/openpype/tools/settings/settings/search.py b/openpype/tools/settings/settings/search_dialog.py similarity index 100% rename from openpype/tools/settings/settings/search.py rename to openpype/tools/settings/settings/search_dialog.py diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index bbfba088ba..55930b5d88 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -5,6 +5,7 @@ from .categories import ( ProjectWidget ) from .widgets import ShadowWidget, RestartDialog +from .search_dialog import SearchEntitiesDialog from openpype import style from openpype.lib import is_admin_password_required @@ -175,7 +176,6 @@ class MainWidget(QtWidgets.QWidget): widget = self._header_tab_widget.currentWidget() root_entity = widget.entity - from .search import SearchEntitiesDialog search = SearchEntitiesDialog(root_entity, parent=self) search.resize(700, 500) search.setWindowTitle("Search") From 61b07c356dc404437a1f4b0ebd4956af6abfaa67 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Sat, 12 Feb 2022 15:08:48 +0100 Subject: [PATCH 21/68] extended search dialog and handle reset and recreation of dialog model --- .../tools/settings/settings/categories.py | 9 + .../tools/settings/settings/search_dialog.py | 173 +++++++++++------- openpype/tools/settings/settings/window.py | 52 ++++-- 3 files changed, 157 insertions(+), 77 deletions(-) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index adbde00bf1..28e38ea51d 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -86,6 +86,8 @@ class SettingsCategoryWidget(QtWidgets.QWidget): state_changed = QtCore.Signal() saved = QtCore.Signal(QtWidgets.QWidget) restart_required_trigger = QtCore.Signal() + restart_started = QtCore.Signal() + restart_finished = QtCore.Signal() full_path_requested = QtCore.Signal(str, str) def __init__(self, user_role, parent=None): @@ -307,7 +309,12 @@ class SettingsCategoryWidget(QtWidgets.QWidget): """Change path of widget based on category full path.""" pass + def change_path(self, path): + """Change path and go to widget.""" + self.breadcrumbs_widget.change_path(path) + def set_path(self, path): + """Called from clicked widget.""" self.breadcrumbs_widget.set_path(path) def _add_developer_ui(self, footer_layout): @@ -429,6 +436,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget): self.require_restart_label.setText(value) def reset(self): + self.restart_started.emit() self.set_state(CategoryState.Working) self._on_reset_start() @@ -509,6 +517,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget): self._on_reset_crash() else: self._on_reset_success() + self.restart_finished.emit() def _on_reset_crash(self): self.save_btn.setEnabled(False) diff --git a/openpype/tools/settings/settings/search_dialog.py b/openpype/tools/settings/settings/search_dialog.py index 3d61dd33fe..3f987c0010 100644 --- a/openpype/tools/settings/settings/search_dialog.py +++ b/openpype/tools/settings/settings/search_dialog.py @@ -1,13 +1,14 @@ -from functools import partial import re +import collections -from Qt import QtCore, QtWidgets -from openpype.tools.utils.models import TreeModel, Item -from openpype.tools.utils.lib import schedule +from Qt import QtCore, QtWidgets, QtGui + +ENTITY_LABEL_ROLE = QtCore.Qt.UserRole + 1 +ENTITY_PATH_ROLE = QtCore.Qt.UserRole + 2 def get_entity_children(entity): - + # TODO find better way how to go through all children if hasattr(entity, "values"): return entity.values() return [] @@ -23,115 +24,163 @@ class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): self.setRecursiveFilteringEnabled(True) def filterAcceptsRow(self, row, parent): - if not parent.isValid(): return False regex = self.filterRegExp() if not regex.isEmpty() and regex.isValid(): pattern = regex.pattern() + compiled_regex = re.compile(pattern) source_model = self.sourceModel() # Check current index itself in all columns - for column in range(source_model.columnCount(parent)): - source_index = source_model.index(row, column, parent) - if not source_index.isValid(): - continue - - key = source_model.data(source_index, self.filterRole()) - if not key: - continue - - if re.search(pattern, key, re.IGNORECASE): - return True - + source_index = source_model.index(row, 0, parent) + if source_index.isValid(): + for role in (ENTITY_PATH_ROLE, ENTITY_LABEL_ROLE): + value = source_model.data(source_index, role) + if value and compiled_regex.search(value): + return True return False - return super(RecursiveSortFilterProxyModel, - self).filterAcceptsRow(row, parent) + return super( + RecursiveSortFilterProxyModel, self + ).filterAcceptsRow(row, parent) class SearchEntitiesDialog(QtWidgets.QDialog): - path_clicked = QtCore.Signal(str) - def __init__(self, entity, parent=None): + def __init__(self, parent): super(SearchEntitiesDialog, self).__init__(parent=parent) - layout = QtWidgets.QVBoxLayout(self) + self.setWindowTitle("Search Settings") - filter_edit = QtWidgets.QLineEdit() - filter_edit.setPlaceholderText("Search..") + filter_edit = QtWidgets.QLineEdit(self) + filter_edit.setPlaceholderText("Search...") model = EntityTreeModel() proxy = RecursiveSortFilterProxyModel() proxy.setSourceModel(model) proxy.setDynamicSortFilter(True) - view = QtWidgets.QTreeView() - view.setModel(proxy) + view = QtWidgets.QTreeView(self) + view.setAllColumnsShowFocus(True) + view.setSortingEnabled(True) + view.setModel(proxy) + model.setColumnCount(3) + + layout = QtWidgets.QVBoxLayout(self) layout.addWidget(filter_edit) layout.addWidget(view) + filter_changed_timer = QtCore.QTimer() + filter_changed_timer.setInterval(200) + + view.selectionModel().selectionChanged.connect( + self._on_selection_change + ) + filter_changed_timer.timeout.connect(self._on_filter_timer) filter_edit.textChanged.connect(self._on_filter_changed) - view.selectionModel().selectionChanged.connect(self.on_select) - - view.setAllColumnsShowFocus(True) - view.setSortingEnabled(True) - view.sortByColumn(1, QtCore.Qt.AscendingOrder) + self._filter_edit = filter_edit self._model = model self._proxy = proxy self._view = view + self._filter_changed_timer = filter_changed_timer - # Refresh to the passed entity - model.set_root(entity) + self._first_show = True - view.resizeColumnToContents(0) + def set_root_entity(self, entity): + self._model.set_root_entity(entity) + self._view.resizeColumnToContents(0) + + def showEvent(self, event): + super(SearchEntitiesDialog, self).showEvent(event) + if self._first_show: + self._first_show = False + self.resize(700, 500) def _on_filter_changed(self, txt): - # Provide slight delay to filtering so user can type quickly - schedule(partial(self.on_filter_changed, txt), 250, channel="search") + self._filter_changed_timer.start() - def on_filter_changed(self, txt): - self._proxy.setFilterRegExp(txt) + def _on_filter_timer(self): + text = self._filter_edit.text() + self._proxy.setFilterRegExp(text) # WARNING This expanding and resizing is relatively slow. self._view.expandAll() self._view.resizeColumnToContents(0) - def on_select(self): + def _on_selection_change(self): current = self._view.currentIndex() - item = current.data(EntityTreeModel.ItemRole) - self.path_clicked.emit(item["path"]) + path = current.data(ENTITY_PATH_ROLE) + self.path_clicked.emit(path) -class EntityTreeModel(TreeModel): +class EntityTreeModel(QtGui.QStandardItemModel): + def __init__(self, *args, **kwargs): + super(EntityTreeModel, self).__init__(*args, **kwargs) + self.setColumnCount(3) - Columns = ["trail", "label", "key", "path"] + def data(self, index, role=None): + if role is None: + role = QtCore.Qt.DisplayRole - def add_entity(self, entity, parent=None): + col = index.column() + if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole): + if col == 0: + pass + elif col == 1: + role = ENTITY_LABEL_ROLE + elif col == 2: + role = ENTITY_PATH_ROLE - item = Item() + if col > 0: + index = self.index(index.row(), 0, index.parent()) + return super(EntityTreeModel, self).data(index, role) - # Label and key can sometimes be emtpy so we use the trail from path - # in the most left column since it's never empty - item["trail"] = entity.path.rsplit("/", 1)[-1] - item["label"] = entity.label - item["key"] = entity.key - item["path"] = entity.path + def flags(self, index): + if index.column() > 0: + index = self.index(index.row(), 0, index.parent()) + return super(EntityTreeModel, self).flags(index) - parent.add_child(item) + def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): + if role == QtCore.Qt.DisplayRole: + if section == 0: + return "Key" + elif section == 1: + return "Label" + elif section == 2: + return "Path" + return "" + return super(EntityTreeModel, self).headerData( + section, orientation, role + ) - for child in get_entity_children(entity): - self.add_entity(child, parent=item) - - def set_root(self, root_entity): - self.clear() - self.beginResetModel() + def set_root_entity(self, root_entity): + parent = self.invisibleRootItem() + parent.removeRows(0, parent.rowCount()) + if not root_entity: + return # We don't want to see the root entity so we directly add its children - for child in get_entity_children(root_entity): - self.add_entity(child, parent=self._root_item) - self.endResetModel() + fill_queue = collections.deque() + fill_queue.append((root_entity, parent)) + cols = self.columnCount() + while fill_queue: + parent_entity, parent_item = fill_queue.popleft() + child_items = [] + for child in get_entity_children(parent_entity): + label = child.label + path = child.path + key = path.split("/")[-1] + item = QtGui.QStandardItem(key) + item.setEditable(False) + item.setData(label, ENTITY_LABEL_ROLE) + item.setData(path, ENTITY_PATH_ROLE) + item.setColumnCount(cols) + child_items.append(item) + fill_queue.append((child, item)) + if child_items: + parent_item.appendRows(child_items) diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index 55930b5d88..f6fa9a83a5 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -55,19 +55,27 @@ class MainWidget(QtWidgets.QWidget): self.setLayout(layout) + search_dialog = SearchEntitiesDialog(self) + self._shadow_widget = ShadowWidget("Working...", self) self._shadow_widget.setVisible(False) + header_tab_widget.currentChanged.connect(self._on_tab_changed) + search_dialog.path_clicked.connect(self._on_search_path_clicked) + for tab_widget in tab_widgets: tab_widget.saved.connect(self._on_tab_save) tab_widget.state_changed.connect(self._on_state_change) tab_widget.restart_required_trigger.connect( self._on_restart_required ) + tab_widget.restart_started.connect(self._on_restart_started) + tab_widget.restart_finished.connect(self._on_restart_finished) tab_widget.full_path_requested.connect(self._on_full_path_request) self._header_tab_widget = header_tab_widget self.tab_widgets = tab_widgets + self._search_dialog = search_dialog def _on_tab_save(self, source_widget): for tab_widget in self.tab_widgets: @@ -151,6 +159,21 @@ class MainWidget(QtWidgets.QWidget): for tab_widget in self.tab_widgets: tab_widget.reset() + def _update_search_dialog(self, clear=False): + if self._search_dialog.isVisible(): + entity = None + if not clear: + widget = self._header_tab_widget.currentWidget() + entity = widget.entity + self._search_dialog.set_root_entity(entity) + + def _on_tab_changed(self): + self._update_search_dialog() + + def _on_search_path_clicked(self, path): + widget = self._header_tab_widget.currentWidget() + widget.change_path(path) + def _on_restart_required(self): # Don't show dialog if there are not registered slots for # `trigger_restart` signal. @@ -166,25 +189,24 @@ class MainWidget(QtWidgets.QWidget): if result == 1: self.trigger_restart.emit() + def _on_restart_started(self): + widget = self.sender() + current_widget = self._header_tab_widget.currentWidget() + if current_widget is widget: + self._update_search_dialog(True) + + def _on_restart_finished(self): + widget = self.sender() + current_widget = self._header_tab_widget.currentWidget() + if current_widget is widget: + self._update_search_dialog() + def keyPressEvent(self, event): - - # todo: This might not be the cleanest place but works for prototype if event.matches(QtGui.QKeySequence.Find): - print("Search!") - # todo: search in all widgets (or in active)? widget = self._header_tab_widget.currentWidget() - root_entity = widget.entity - - search = SearchEntitiesDialog(root_entity, parent=self) - search.resize(700, 500) - search.setWindowTitle("Search") - search.show() - - def on_path(path): - widget.breadcrumbs_widget.change_path(path) - - search.path_clicked.connect(on_path) + self._search_dialog.show() + self._search_dialog.set_root_entity(widget.entity) event.accept() return From 806c417026b8e5ac4edb00fc7c4f1b851097ce51 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 12 Feb 2022 15:15:49 +0100 Subject: [PATCH 22/68] show publish states instead of processed plugin --- openpype/tools/pyblish_pype/control.py | 33 +++- openpype/tools/pyblish_pype/model.py | 6 +- openpype/tools/pyblish_pype/util.py | 256 ++++--------------------- openpype/tools/pyblish_pype/window.py | 39 ++-- 4 files changed, 81 insertions(+), 253 deletions(-) diff --git a/openpype/tools/pyblish_pype/control.py b/openpype/tools/pyblish_pype/control.py index 9774c8020b..0d33aa10f8 100644 --- a/openpype/tools/pyblish_pype/control.py +++ b/openpype/tools/pyblish_pype/control.py @@ -152,6 +152,8 @@ class Controller(QtCore.QObject): self.instance_toggled.connect(self._on_instance_toggled) self._main_thread_processor = MainThreadProcess() + self._current_state = "" + def reset_variables(self): self.log.debug("Resetting pyblish context variables") @@ -159,6 +161,7 @@ class Controller(QtCore.QObject): self.is_running = False self.stopped = False self.errored = False + self._current_state = "" # Active producer of pairs self.pair_generator = None @@ -167,7 +170,6 @@ class Controller(QtCore.QObject): # Orders which changes GUI # - passing collectors order disables plugin/instance toggle - self.collectors_order = None self.collect_state = 0 # - passing validators order disables validate button and gives ability @@ -176,11 +178,8 @@ class Controller(QtCore.QObject): self.validated = False # Get collectors and validators order - self.order_groups.reset() - plugin_groups = self.order_groups.groups() - plugin_groups_keys = list(plugin_groups.keys()) - self.collectors_order = plugin_groups_keys[0] - self.validators_order = self.order_groups.validation_order() + plugin_groups_keys = list(self.order_groups.groups.keys()) + self.validators_order = self.order_groups.validation_order next_group_order = None if len(plugin_groups_keys) > 1: next_group_order = plugin_groups_keys[1] @@ -191,13 +190,18 @@ class Controller(QtCore.QObject): "stop_on_validation": False, # Used? "last_plugin_order": None, - "current_group_order": self.collectors_order, + "current_group_order": plugin_groups_keys[0], "next_group_order": next_group_order, "nextOrder": None, "ordersWithError": set() } + self._set_state_by_order() self.log.debug("Reset of pyblish context variables done") + @property + def current_state(self): + return self._current_state + def presets_by_hosts(self): # Get global filters as base presets = get_project_settings(os.environ['AVALON_PROJECT']) or {} @@ -293,6 +297,7 @@ class Controller(QtCore.QObject): def on_published(self): if self.is_running: self.is_running = False + self._current_state = "Pulished" self.was_finished.emit() self._main_thread_processor.stop() @@ -355,7 +360,7 @@ class Controller(QtCore.QObject): new_current_group_order = self.processing["next_group_order"] if new_current_group_order is not None: current_next_order_found = False - for order in self.order_groups.groups().keys(): + for order in self.order_groups.groups.keys(): if current_next_order_found: new_next_group_order = order break @@ -370,6 +375,7 @@ class Controller(QtCore.QObject): if self.collect_state == 0: self.collect_state = 1 + self._current_state = "Collected" self.switch_toggleability.emit(True) self.passed_group.emit(current_group_order) yield IterationBreak("Collected") @@ -386,17 +392,20 @@ class Controller(QtCore.QObject): if not self.validated and plugin.order > self.validators_order: self.validated = True if self.processing["stop_on_validation"]: + self._current_state = "Validated" yield IterationBreak("Validated") # Stop if was stopped if self.stopped: self.stopped = False + self._current_state = "Paused" yield IterationBreak("Stopped") # check test if will stop self.processing["nextOrder"] = plugin.order message = self.test(**self.processing) if message: + self._current_state = "Paused" yield IterationBreak("Stopped due to \"{}\"".format(message)) self.processing["last_plugin_order"] = plugin.order @@ -426,6 +435,7 @@ class Controller(QtCore.QObject): # Stop if was stopped if self.stopped: self.stopped = False + self._current_state = "Paused" yield IterationBreak("Stopped") yield (plugin, instance) @@ -536,20 +546,27 @@ class Controller(QtCore.QObject): MainThreadItem(on_next) ) + def _set_state_by_order(self): + order = self.processing["current_group_order"] + self._current_state = self.order_groups.groups[order]["state"] + def collect(self): """ Iterate and process Collect plugins - load_plugins method is launched again when finished """ + self._set_state_by_order() self._main_thread_processor.process(self._start_collect) self._main_thread_processor.start() def validate(self): """ Process plugins to validations_order value.""" + self._set_state_by_order() self._main_thread_processor.process(self._start_validate) self._main_thread_processor.start() def publish(self): """ Iterate and process all remaining plugins.""" + self._set_state_by_order() self._main_thread_processor.process(self._start_publish) self._main_thread_processor.start() diff --git a/openpype/tools/pyblish_pype/model.py b/openpype/tools/pyblish_pype/model.py index bb1aff2a9a..0faadb5940 100644 --- a/openpype/tools/pyblish_pype/model.py +++ b/openpype/tools/pyblish_pype/model.py @@ -428,12 +428,12 @@ class PluginModel(QtGui.QStandardItemModel): self.clear() def append(self, plugin): - plugin_groups = self.controller.order_groups.groups() + plugin_groups = self.controller.order_groups.groups label = None order = None - for _order, _label in reversed(plugin_groups.items()): + for _order, item in reversed(plugin_groups.items()): if _order is None or plugin.order < _order: - label = _label + label = item["label"] order = _order else: break diff --git a/openpype/tools/pyblish_pype/util.py b/openpype/tools/pyblish_pype/util.py index d3d76b187c..9f3697be16 100644 --- a/openpype/tools/pyblish_pype/util.py +++ b/openpype/tools/pyblish_pype/util.py @@ -95,224 +95,44 @@ def collect_families_from_instances(instances, only_active=False): class OrderGroups: - # Validator order can be set with environment "PYBLISH_VALIDATION_ORDER" - # - this variable sets when validation button will hide and proecssing - # of validation will end with ability to continue in process - default_validation_order = pyblish.api.ValidatorOrder + 0.5 - - # Group range can be set with environment "PYBLISH_GROUP_RANGE" - default_group_range = 1 - - # Group string can be set with environment "PYBLISH_GROUP_SETTING" - default_groups = { - pyblish.api.CollectorOrder + 0.5: "Collect", - pyblish.api.ValidatorOrder + 0.5: "Validate", - pyblish.api.ExtractorOrder + 0.5: "Extract", - pyblish.api.IntegratorOrder + 0.5: "Integrate", - None: "Other" - } - - # *** This example should have same result as is `default_groups` if - # `group_range` is set to "1" - __groups_str_example__ = ( - # half of `group_range` is added to 0 because number means it is Order - "0=Collect" - # if `<` is before than it means group range is not used - # but is expected that number is already max - ",<1.5=Validate" - # "Extractor" will be used in range `<1.5; 2.5)` - ",<2.5=Extract" - ",<3.5=Integrate" - # "Other" if number is not set than all remaining plugins are in - # - in this case Other's range is <3.5; infinity) - ",Other" - ) - - _groups = None - _validation_order = None - _group_range = None - - def __init__( - self, group_str=None, group_range=None, validation_order=None - ): - super(OrderGroups, self).__init__() - # Override class methods with object methods - self.groups = self._object_groups - self.validation_order = self._object_validation_order - self.group_range = self._object_group_range - self.reset = self._object_reset - - # set - if group_range is not None: - self._group_range = self.parse_group_range( - group_range - ) - - if group_str is not None: - self._groups = self.parse_group_str( - group_str - ) - - if validation_order is not None: - self._validation_order = self.parse_validation_order( - validation_order - ) - - @staticmethod - def _groups_method(obj): - if obj._groups is None: - obj._groups = obj.parse_group_str( - group_range=obj.group_range() - ) - return obj._groups - - @staticmethod - def _reset_method(obj): - obj._groups = None - obj._validation_order = None - obj._group_range = None - - @classmethod - def reset(cls): - return cls._reset_method(cls) - - def _object_reset(self): - return self._reset_method(self) - - @classmethod - def groups(cls): - return cls._groups_method(cls) - - def _object_groups(self): - return self._groups_method(self) - - @staticmethod - def _validation_order_method(obj): - if obj._validation_order is None: - obj._validation_order = obj.parse_validation_order( - group_range=obj.group_range() - ) - return obj._validation_order - - @classmethod - def validation_order(cls): - return cls._validation_order_method(cls) - - def _object_validation_order(self): - return self._validation_order_method(self) - - @staticmethod - def _group_range_method(obj): - if obj._group_range is None: - obj._group_range = obj.parse_group_range() - return obj._group_range - - @classmethod - def group_range(cls): - return cls._group_range_method(cls) - - def _object_group_range(self): - return self._group_range_method(self) - - @staticmethod - def sort_groups(_groups_dict): - sorted_dict = collections.OrderedDict() - - # make sure won't affect any dictionary as pointer - groups_dict = copy.deepcopy(_groups_dict) - last_order = None - if None in groups_dict: - last_order = groups_dict.pop(None) - - for key in sorted(groups_dict): - sorted_dict[key] = groups_dict[key] - - if last_order is not None: - sorted_dict[None] = last_order - - return sorted_dict - - @staticmethod - def parse_group_str(groups_str=None, group_range=None): - if groups_str is None: - groups_str = os.environ.get("PYBLISH_GROUP_SETTING") - - if groups_str is None: - return OrderGroups.sort_groups(OrderGroups.default_groups) - - items = groups_str.split(",") - groups = {} - for item in items: - if "=" not in item: - order = None - label = item - else: - order, label = item.split("=") - order = order.strip() - if not order: - order = None - elif order.startswith("<"): - order = float(order.replace("<", "")) - else: - if group_range is None: - group_range = OrderGroups.default_group_range - print( - "Using default Plugin group range \"{}\".".format( - OrderGroups.default_group_range - ) - ) - order = float(order) + float(group_range) / 2 - - if order in groups: - print(( - "Order \"{}\" is registered more than once." - " Using first found." - ).format(str(order))) - continue - - groups[order] = label - - return OrderGroups.sort_groups(groups) - - @staticmethod - def parse_validation_order(validation_order_value=None, group_range=None): - if validation_order_value is None: - validation_order_value = os.environ.get("PYBLISH_VALIDATION_ORDER") - - if validation_order_value is None: - return OrderGroups.default_validation_order - - if group_range is None: - group_range = OrderGroups.default_group_range - - group_range_half = float(group_range) / 2 - - if isinstance(validation_order_value, numbers.Integral): - return validation_order_value + group_range_half - - if validation_order_value.startswith("<"): - validation_order_value = float( - validation_order_value.replace("<", "") - ) - else: - validation_order_value = ( - float(validation_order_value) - + group_range_half - ) - return validation_order_value - - @staticmethod - def parse_group_range(group_range=None): - if group_range is None: - group_range = os.environ.get("PYBLISH_GROUP_RANGE") - - if group_range is None: - return OrderGroups.default_group_range - - if isinstance(group_range, numbers.Integral): - return group_range - - return float(group_range) + validation_order = pyblish.api.ValidatorOrder + 0.5 + groups = collections.OrderedDict(( + ( + pyblish.api.CollectorOrder + 0.5, + { + "label": "Collect", + "state": "Collecting.." + } + ), + ( + pyblish.api.ValidatorOrder + 0.5, + { + "label": "Validate", + "state": "Validating.." + } + ), + ( + pyblish.api.ExtractorOrder + 0.5, + { + "label": "Extract", + "state": "Extracting.." + } + ), + ( + pyblish.api.IntegratorOrder + 0.5, + { + "label": "Integrate", + "state": "Integrating.." + } + ), + ( + None, + { + "label": "Other", + "state": "Finishing.." + } + ) + )) def env_variable_to_bool(env_key, default=False): diff --git a/openpype/tools/pyblish_pype/window.py b/openpype/tools/pyblish_pype/window.py index 7bb11745d6..576ff8a93d 100644 --- a/openpype/tools/pyblish_pype/window.py +++ b/openpype/tools/pyblish_pype/window.py @@ -300,21 +300,6 @@ class Window(QtWidgets.QDialog): info_effect = QtWidgets.QGraphicsOpacityEffect(footer_info) footer_info.setGraphicsEffect(info_effect) - on = QtCore.QPropertyAnimation(info_effect, b"opacity") - on.setDuration(0) - on.setStartValue(0) - on.setEndValue(1) - - fade = QtCore.QPropertyAnimation(info_effect, b"opacity") - fade.setDuration(500) - fade.setStartValue(1.0) - fade.setEndValue(0.0) - - animation_info_msg = QtCore.QSequentialAnimationGroup() - animation_info_msg.addAnimation(on) - animation_info_msg.addPause(2000) - animation_info_msg.addAnimation(fade) - """Setup Widgets are referred to in CSS via their object-name. We @@ -448,6 +433,8 @@ class Window(QtWidgets.QDialog): self.footer_button_validate = footer_button_validate self.footer_button_play = footer_button_play + self.footer_info = footer_info + self.overview_instance_view = overview_instance_view self.overview_plugin_view = overview_plugin_view self.plugin_model = plugin_model @@ -457,8 +444,6 @@ class Window(QtWidgets.QDialog): self.presets_button = presets_button - self.animation_info_msg = animation_info_msg - self.terminal_model = terminal_model self.terminal_proxy = terminal_proxy self.terminal_view = terminal_view @@ -984,6 +969,8 @@ class Window(QtWidgets.QDialog): self.footer_button_stop.setEnabled(True) self.footer_button_play.setEnabled(False) + self._update_state() + def on_passed_group(self, order): for group_item in self.instance_model.group_items.values(): group_index = self.instance_sort_proxy.mapFromSource( @@ -1015,6 +1002,8 @@ class Window(QtWidgets.QDialog): self.overview_plugin_view.setAnimated(False) self.overview_plugin_view.collapse(group_index) + self._update_state() + def on_was_stopped(self): self.overview_plugin_view.setAnimated(settings.Animated) errored = self.controller.errored @@ -1039,6 +1028,7 @@ class Window(QtWidgets.QDialog): and not self.controller.stopped ) self.button_suspend_logs.setEnabled(suspend_log_bool) + self._update_state() def on_was_skipped(self, plugin): plugin_item = self.plugin_model.plugin_items[plugin.id] @@ -1082,6 +1072,7 @@ class Window(QtWidgets.QDialog): ) self.update_compatibility() + self._update_state() def on_was_processed(self, result): existing_ids = set(self.instance_model.instance_items.keys()) @@ -1160,6 +1151,8 @@ class Window(QtWidgets.QDialog): self.controller.validate() + self._update_state() + def publish(self): self.info(self.tr("Preparing publish..")) self.footer_button_stop.setEnabled(True) @@ -1171,6 +1164,8 @@ class Window(QtWidgets.QDialog): self.controller.publish() + self._update_state() + def act(self, plugin_item, action): self.info("%s %s.." % (self.tr("Preparing"), action)) @@ -1285,6 +1280,9 @@ class Window(QtWidgets.QDialog): # # ------------------------------------------------------------------------- + def _update_state(self): + self.footer_info.setText(self.controller.current_state) + def info(self, message): """Print user-facing information @@ -1292,19 +1290,12 @@ class Window(QtWidgets.QDialog): message (str): Text message for the user """ - - info = self.findChild(QtWidgets.QLabel, "FooterInfo") - info.setText(message) - # Include message in terminal self.terminal_model.append([{ "label": message, "type": "info" }]) - self.animation_info_msg.stop() - self.animation_info_msg.start() - if settings.PrintInfo: # Print message to console util.u_print(message) From f574a817670cce860cf5ecf9bf93a91cde42d36f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sat, 12 Feb 2022 15:21:21 +0100 Subject: [PATCH 23/68] remove effect from label --- openpype/tools/pyblish_pype/window.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/tools/pyblish_pype/window.py b/openpype/tools/pyblish_pype/window.py index 576ff8a93d..48333e79f4 100644 --- a/openpype/tools/pyblish_pype/window.py +++ b/openpype/tools/pyblish_pype/window.py @@ -296,10 +296,6 @@ class Window(QtWidgets.QDialog): self.main_layout.setSpacing(0) self.main_layout.addWidget(main_widget) - # Display info - info_effect = QtWidgets.QGraphicsOpacityEffect(footer_info) - footer_info.setGraphicsEffect(info_effect) - """Setup Widgets are referred to in CSS via their object-name. We From a40dca7f50877fa1aa415cefa6b6ce63341a4e9f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 12 Feb 2022 23:51:48 +0100 Subject: [PATCH 24/68] Remove menu.json files - these are now loaded through settings --- openpype/hosts/maya/api/menu.json | 924 ------------- openpype/hosts/maya/api/menu_backup.json | 1567 ---------------------- 2 files changed, 2491 deletions(-) delete mode 100644 openpype/hosts/maya/api/menu.json delete mode 100644 openpype/hosts/maya/api/menu_backup.json diff --git a/openpype/hosts/maya/api/menu.json b/openpype/hosts/maya/api/menu.json deleted file mode 100644 index a2efd5233c..0000000000 --- a/openpype/hosts/maya/api/menu.json +++ /dev/null @@ -1,924 +0,0 @@ -[ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\save_scene_incremental.py", - "sourcetype": "file", - "title": "# Version Up", - "tooltip": "Incremental save with a specific format" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\open_current_folder.py", - "sourcetype": "file", - "title": "Open working folder..", - "tooltip": "Show current scene in Explorer" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\avalon\\launch_manager.py", - "sourcetype": "file", - "title": "# Project Manager", - "tooltip": "Add assets to the project" - }, - { - "type": "action", - "command": "from openpype.tools.assetcreator import app as assetcreator; assetcreator.show(context='maya')", - "sourcetype": "python", - "title": "Asset Creator", - "tooltip": "Open the Asset Creator" - }, - { - "type": "separator" - }, - { - "type": "menu", - "title": "Modeling", - "items": [ - { - "type": "action", - "command": "import easyTreezSource; reload(easyTreezSource); easyTreezSource.easyTreez()", - "sourcetype": "python", - "tags": ["modeling", "trees", "generate", "create", "plants"], - "title": "EasyTreez", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\separateMeshPerShader.py", - "sourcetype": "file", - "tags": ["modeling", "separateMeshPerShader"], - "title": "# Separate Mesh Per Shader", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\polyDetachSeparate.py", - "sourcetype": "file", - "tags": ["modeling", "poly", "detach", "separate"], - "title": "# Polygon Detach and Separate", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py", - "sourcetype": "file", - "tags": ["modeling", "select", "nth", "edge", "ui"], - "title": "# Select Every Nth Edge" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\djPFXUVs.py", - "sourcetype": "file", - "tags": ["modeling", "djPFX", "UVs"], - "title": "# dj PFX UVs", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "title": "Rigging", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\advancedSkeleton.py", - "sourcetype": "file", - "tags": [ - "rigging", - "autorigger", - "advanced", - "skeleton", - "advancedskeleton", - "file" - ], - "title": "Advanced Skeleton" - } - ] - }, - { - "type": "menu", - "title": "Shading", - "items": [ - { - "type": "menu", - "title": "# VRay", - "items": [ - { - "type": "action", - "title": "# Import Proxies", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayImportProxies.py", - "sourcetype": "file", - "tags": ["shading", "vray", "import", "proxies"], - "tooltip": "" - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "# Select All GES", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectAllGES.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "select All GES"] - }, - { - "type": "action", - "title": "# Select All GES Under Selection", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "select", "all", "GES"] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "# Selection To VRay Mesh", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "selection", "vraymesh"] - }, - { - "type": "action", - "title": "# Add VRay Round Edges Attribute", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "round edges", "attribute"] - }, - { - "type": "action", - "title": "# Add Gamma", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayAddGamma.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "add gamma"] - }, - { - "type": "separator" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py", - "sourcetype": "file", - "title": "# Select Unconnected Shader Materials", - "tags": [ - "shading", - "vray", - "select", - "vraymesh", - "materials", - "unconnected shader slots" - ], - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py", - "sourcetype": "file", - "title": "# Merge Similar VRay Mesh Materials", - "tags": [ - "shading", - "vray", - "Merge", - "VRayMesh", - "Materials" - ], - "tooltip": "" - }, - { - "type": "action", - "title": "# Create Two Sided Material", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py", - "sourcetype": "file", - "tooltip": "Creates two sided material for selected material and renames it", - "tags": ["shading", "vray", "two sided", "material"] - }, - { - "type": "action", - "title": "# Create Two Sided Material For Selected", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py", - "sourcetype": "file", - "tooltip": "Select material to create a two sided version from it", - "tags": [ - "shading", - "vray", - "Create2SidedMtlForSelectedMtl.py" - ] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "# Add OpenSubdiv Attribute", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "add", - "open subdiv", - "attribute" - ] - }, - { - "type": "action", - "title": "# Remove OpenSubdiv Attribute", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "remove", - "opensubdiv", - "attributee" - ] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "# Add Subdivision Attribute", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "addVraySubdivisionAttribute" - ] - }, - { - "type": "action", - "title": "# Remove Subdivision Attribute.py", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "remove", - "subdivision", - "attribute" - ] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "# Add Vray Object Ids", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayObjectIds.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "add", "object id"] - }, - { - "type": "action", - "title": "# Add Vray Material Ids", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "addVrayMaterialIds.py"] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "# Set Physical DOF Depth", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "physical", "DOF ", "Depth"] - }, - { - "type": "action", - "title": "# Magic Vray Proxy UI", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "magicVrayProxyUI"] - } - ] - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\pyblish\\lighting\\set_filename_prefix.py", - "sourcetype": "file", - "tags": [ - "shading", - "lookdev", - "assign", - "shaders", - "prefix", - "filename", - "render" - ], - "title": "# Set filename prefix", - "tooltip": "Set the render file name prefix." - }, - { - "type": "action", - "command": "import mayalookassigner; mayalookassigner.show()", - "sourcetype": "python", - "tags": ["shading", "look", "assign", "shaders", "auto"], - "title": "Look Manager", - "tooltip": "Open the Look Manager UI for look assignment" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\LightLinkUi.py", - "sourcetype": "file", - "tags": ["shading", "light", "link", "ui"], - "title": "# Light Link UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\vdviewer_ui.py", - "sourcetype": "file", - "tags": [ - "shading", - "look", - "vray", - "displacement", - "shaders", - "auto" - ], - "title": "# VRay Displ Viewer", - "tooltip": "Open the VRay Displacement Viewer, select and control the content of the set" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", - "sourcetype": "file", - "tags": ["shading", "CLRImage", "textures", "preview"], - "title": "# Set Texture Preview To CLRImage", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py", - "sourcetype": "file", - "tags": ["shading", "fix", "DefaultShaderSet", "Behavior"], - "title": "# Fix Default Shader Set Behavior", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py", - "sourcetype": "file", - "tags": [ - "shading", - "fix", - "Selected", - "Shapes", - "Reference", - "Assignments" - ], - "title": "# Fix Shapes Reference Assignments", - "tooltip": "Select shapes to fix the reference assignments" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\selectLambert1Members.py", - "sourcetype": "file", - "tags": ["shading", "selectLambert1Members"], - "title": "# Select Lambert1 Members", - "tooltip": "Selects all objects which have the Lambert1 shader assigned" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\selectShapesWithoutShader.py", - "sourcetype": "file", - "tags": ["shading", "selectShapesWithoutShader"], - "title": "# Select Shapes Without Shader", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py", - "sourcetype": "file", - "tags": ["shading", "fixRenderLayerOutAdjustmentErrors"], - "title": "# Fix RenderLayer Out Adjustment Errors", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\fix_renderlayer_missing_node_override.py", - "sourcetype": "file", - "tags": [ - "shading", - "renderlayer", - "missing", - "reference", - "switch", - "layer" - ], - "title": "# Fix RenderLayer Missing Referenced Nodes Overrides", - "tooltip": "" - }, - { - "type": "action", - "title": "# Image 2 Tiled EXR", - "command": "$OPENPYPE_SCRIPTS\\shading\\open_img2exr.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "exr"] - } - ] - }, - { - "type": "menu", - "title": "# Rendering", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\pyblish\\open_deadline_submission_settings.py", - "sourcetype": "file", - "tags": ["settings", "deadline", "globals", "render"], - "title": "# DL Submission Settings UI", - "tooltip": "Open the Deadline Submission Settings UI" - } - ] - }, - { - "type": "menu", - "title": "Animation", - "items": [ - { - "type": "menu", - "title": "# Attributes", - "tooltip": "", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyValues.py", - "sourcetype": "file", - "tags": ["animation", "copy", "attributes"], - "title": "# Copy Values", - "tooltip": "Copy attribute values" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyInConnections.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "connections", - "incoming" - ], - "title": "# Copy In Connections", - "tooltip": "Copy incoming connections" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyOutConnections.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "connections", - "out" - ], - "title": "# Copy Out Connections", - "tooltip": "Copy outcoming connections" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformLocal.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "local" - ], - "title": "# Copy Local Transforms", - "tooltip": "Copy local transforms" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "matrix" - ], - "title": "# Copy Matrix Transforms", - "tooltip": "Copy Matrix transforms" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformUI.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "UI" - ], - "title": "# Copy Transforms UI", - "tooltip": "Open the Copy Transforms UI" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\simpleCopyUI.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "UI", - "simple" - ], - "title": "# Simple Copy UI", - "tooltip": "Open the simple Copy Transforms UI" - } - ] - }, - { - "type": "menu", - "title": "# Optimize", - "tooltip": "Optimization scripts", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py", - "sourcetype": "file", - "tags": ["animation", "hierarchy", "toggle", "freeze"], - "title": "# Toggle Freeze Hierarchy", - "tooltip": "Freeze and unfreeze hierarchy" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py", - "sourcetype": "file", - "tags": ["animation", "nucleus", "toggle", "parallel"], - "title": "# Toggle Parallel Nucleus", - "tooltip": "Toggle parallel nucleus" - } - ] - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", - "tags": ["animation", "bake", "selection", "worldspace.py"], - "title": "# Bake Selected To Worldspace", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\timeStepper.py", - "tags": ["animation", "time", "stepper"], - "title": "# Time Stepper", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\capture_ui.py", - "tags": [ - "animation", - "capture", - "ui", - "screen", - "movie", - "image" - ], - "title": "# Capture UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\simplePlayblastUI.py", - "tags": ["animation", "simple", "playblast", "ui"], - "title": "# Simple Playblast UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\tweenMachineUI.py", - "tags": ["animation", "tween", "machine"], - "title": "# Tween Machine UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\selectAllAnimationCurves.py", - "tags": ["animation", "select", "curves"], - "title": "# Select All Animation Curves", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\pathAnimation.py", - "tags": ["animation", "path", "along"], - "title": "# Path Animation", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\offsetSelectedObjectsUI.py", - "tags": ["animation", "offsetSelectedObjectsUI.py"], - "title": "# Offset Selected Objects UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\key_amplifier_ui.py", - "tags": ["animation", "key", "amplifier"], - "title": "# Key Amplifier UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\anim_scene_optimizer.py", - "tags": ["animation", "anim_scene_optimizer.py"], - "title": "# Anim_Scene_Optimizer", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\zvParentMaster.py", - "tags": ["animation", "zvParentMaster.py"], - "title": "# ZV Parent Master", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\animLibrary.py", - "tags": ["animation", "studiolibrary.py"], - "title": "Anim Library", - "type": "action" - } - ] - }, - { - "type": "menu", - "title": "# Layout", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\alignDistributeUI.py", - "sourcetype": "file", - "tags": ["layout", "align", "Distribute", "UI"], - "title": "# Align Distribute UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\alignSimpleUI.py", - "sourcetype": "file", - "tags": ["layout", "align", "UI", "Simple"], - "title": "# Align Simple UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\center_locator.py", - "sourcetype": "file", - "tags": ["layout", "center", "locator"], - "title": "# Center Locator", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\average_locator.py", - "sourcetype": "file", - "tags": ["layout", "average", "locator"], - "title": "# Average Locator", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\selectWithinProximityUI.py", - "sourcetype": "file", - "tags": ["layout", "select", "proximity", "ui"], - "title": "# Select Within Proximity UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\dupCurveUI.py", - "sourcetype": "file", - "tags": ["layout", "Duplicate", "Curve", "UI"], - "title": "# Duplicate Curve UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\randomDeselectUI.py", - "sourcetype": "file", - "tags": ["layout", "random", "Deselect", "UI"], - "title": "# Random Deselect UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\multiReferencerUI.py", - "sourcetype": "file", - "tags": ["layout", "multi", "reference"], - "title": "# Multi Referencer UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\duplicateOffsetUI.py", - "sourcetype": "file", - "tags": ["layout", "duplicate", "offset", "UI"], - "title": "# Duplicate Offset UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\spPaint3d.py", - "sourcetype": "file", - "tags": ["layout", "spPaint3d", "paint", "tool"], - "title": "# SP Paint 3d", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\randomizeUI.py", - "sourcetype": "file", - "tags": ["layout", "randomize", "UI"], - "title": "# Randomize UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\distributeWithinObjectUI.py", - "sourcetype": "file", - "tags": ["layout", "distribute", "ObjectUI", "within"], - "title": "# Distribute Within Object UI", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "title": "# Particles", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjects.py", - "sourcetype": "file", - "tags": ["particles", "instancerToObjects"], - "title": "# Instancer To Objects", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsInstances.py", - "sourcetype": "file", - "tags": ["particles", "instancerToObjectsInstances"], - "title": "# Instancer To Objects Instances", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsInstancesWithAnimation.py", - "sourcetype": "file", - "tags": [ - "particles", - "instancerToObjectsInstancesWithAnimation" - ], - "title": "# Instancer To Objects Instances With Animation", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsWithAnimation.py", - "sourcetype": "file", - "tags": ["particles", "instancerToObjectsWithAnimation"], - "title": "# Instancer To Objects With Animation", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "title": "Cleanup", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\repair_faulty_containers.py", - "sourcetype": "file", - "tags": ["cleanup", "repair", "containers"], - "title": "# Find and Repair Containers", - "tooltip": "" - }, - { - "type": "separator" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeNamespaces.py", - "sourcetype": "file", - "tags": ["cleanup", "remove", "namespaces"], - "title": "# Remove Namespaces", - "tooltip": "Remove all namespaces" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\remove_user_defined_attributes.py", - "sourcetype": "file", - "tags": ["cleanup", "remove_user_defined_attributes"], - "title": "# Remove User Defined Attributes", - "tooltip": "Remove all user-defined attributes from all nodes" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeUnknownNodes.py", - "sourcetype": "file", - "tags": ["cleanup", "removeUnknownNodes"], - "title": "# Remove Unknown Nodes", - "tooltip": "Remove all unknown nodes" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeUnloadedReferences.py", - "sourcetype": "file", - "tags": ["cleanup", "removeUnloadedReferences"], - "title": "# Remove Unloaded References", - "tooltip": "Remove all unloaded references" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeReferencesFailedEdits.py", - "sourcetype": "file", - "tags": ["cleanup", "removeReferencesFailedEdits"], - "title": "# Remove References Failed Edits", - "tooltip": "Remove failed edits for all references" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\remove_unused_looks.py", - "sourcetype": "file", - "tags": ["cleanup", "removeUnusedLooks"], - "title": "# Remove Unused Looks", - "tooltip": "Remove all loaded yet unused Avalon look containers" - }, - { - "type": "separator" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\uniqifyNodeNames.py", - "sourcetype": "file", - "tags": ["cleanup", "uniqifyNodeNames"], - "title": "# Uniqify Node Names", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\autoRenameFileNodes.py", - "sourcetype": "file", - "tags": ["cleanup", "auto", "rename", "filenodes"], - "title": "# Auto Rename File Nodes", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\update_asset_id.py", - "sourcetype": "file", - "tags": ["cleanup", "update", "database", "asset", "id"], - "title": "# Update Asset ID", - "tooltip": "Will replace the Colorbleed ID with a new one (asset ID : Unique number)" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\ccRenameReplace.py", - "sourcetype": "file", - "tags": ["cleanup", "rename", "ui"], - "title": "Renamer", - "tooltip": "Rename UI" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\renameShapesToTransform.py", - "sourcetype": "file", - "tags": ["cleanup", "renameShapesToTransform"], - "title": "# Rename Shapes To Transform", - "tooltip": "" - } - ] - } -] diff --git a/openpype/hosts/maya/api/menu_backup.json b/openpype/hosts/maya/api/menu_backup.json deleted file mode 100644 index e2a558aedc..0000000000 --- a/openpype/hosts/maya/api/menu_backup.json +++ /dev/null @@ -1,1567 +0,0 @@ -[ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\save_scene_incremental.py", - "sourcetype": "file", - "title": "Version Up", - "tooltip": "Incremental save with a specific format" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\show_current_scene_in_explorer.py", - "sourcetype": "file", - "title": "Explore current scene..", - "tooltip": "Show current scene in Explorer" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\avalon\\launch_manager.py", - "sourcetype": "file", - "title": "Project Manager", - "tooltip": "Add assets to the project" - }, - { - "type": "separator" - }, - { - "type": "menu", - "title": "Modeling", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\duplicate_normalized.py", - "sourcetype": "file", - "tags": ["modeling", "duplicate", "normalized"], - "title": "Duplicate Normalized", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\transferUVs.py", - "sourcetype": "file", - "tags": ["modeling", "transfer", "uv"], - "title": "Transfer UVs", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\mirrorSymmetry.py", - "sourcetype": "file", - "tags": ["modeling", "mirror", "symmetry"], - "title": "Mirror Symmetry", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\selectOutlineUI.py", - "sourcetype": "file", - "tags": ["modeling", "select", "outline", "ui"], - "title": "Select Outline UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\polyDeleteOtherUVSets.py", - "sourcetype": "file", - "tags": ["modeling", "polygon", "uvset", "delete"], - "title": "Polygon Delete Other UV Sets", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\polyCombineQuick.py", - "sourcetype": "file", - "tags": ["modeling", "combine", "polygon", "quick"], - "title": "Polygon Combine Quick", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\separateMeshPerShader.py", - "sourcetype": "file", - "tags": ["modeling", "separateMeshPerShader"], - "title": "Separate Mesh Per Shader", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\polyDetachSeparate.py", - "sourcetype": "file", - "tags": ["modeling", "poly", "detach", "separate"], - "title": "Polygon Detach and Separate", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\polyRelaxVerts.py", - "sourcetype": "file", - "tags": ["modeling", "relax", "verts"], - "title": "Polygon Relax Vertices", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py", - "sourcetype": "file", - "tags": ["modeling", "select", "nth", "edge", "ui"], - "title": "Select Every Nth Edge" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\modeling\\djPFXUVs.py", - "sourcetype": "file", - "tags": ["modeling", "djPFX", "UVs"], - "title": "dj PFX UVs", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "title": "Rigging", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\addCurveBetween.py", - "sourcetype": "file", - "tags": ["rigging", "addCurveBetween", "file"], - "title": "Add Curve Between" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\averageSkinWeights.py", - "sourcetype": "file", - "tags": ["rigging", "average", "skin weights", "file"], - "title": "Average Skin Weights" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\cbSmoothSkinWeightUI.py", - "sourcetype": "file", - "tags": ["rigging", "cbSmoothSkinWeightUI", "file"], - "title": "CB Smooth Skin Weight UI" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\channelBoxManagerUI.py", - "sourcetype": "file", - "tags": ["rigging", "channelBoxManagerUI", "file"], - "title": "Channel Box Manager UI" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\characterAutorigger.py", - "sourcetype": "file", - "tags": ["rigging", "characterAutorigger", "file"], - "title": "Character Auto Rigger" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\connectUI.py", - "sourcetype": "file", - "tags": ["rigging", "connectUI", "file"], - "title": "Connect UI" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\copySkinWeightsLocal.py", - "sourcetype": "file", - "tags": ["rigging", "copySkinWeightsLocal", "file"], - "title": "Copy Skin Weights Local" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\createCenterLocator.py", - "sourcetype": "file", - "tags": ["rigging", "createCenterLocator", "file"], - "title": "Create Center Locator" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\freezeTransformToGroup.py", - "sourcetype": "file", - "tags": ["rigging", "freezeTransformToGroup", "file"], - "title": "Freeze Transform To Group" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\groupSelected.py", - "sourcetype": "file", - "tags": ["rigging", "groupSelected", "file"], - "title": "Group Selected" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\ikHandlePoleVectorLocator.py", - "sourcetype": "file", - "tags": ["rigging", "ikHandlePoleVectorLocator", "file"], - "title": "IK Handle Pole Vector Locator" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\jointOrientUI.py", - "sourcetype": "file", - "tags": ["rigging", "jointOrientUI", "file"], - "title": "Joint Orient UI" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\jointsOnCurve.py", - "sourcetype": "file", - "tags": ["rigging", "jointsOnCurve", "file"], - "title": "Joints On Curve" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py", - "sourcetype": "file", - "tags": ["rigging", "resetBindSelectedSkinJoints", "file"], - "title": "Reset Bind Selected Skin Joints" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py", - "sourcetype": "file", - "tags": [ - "rigging", - "selectSkinclusterJointsFromSelectedComponents", - "file" - ], - "title": "Select Skincluster Joints From Selected Components" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py", - "sourcetype": "file", - "tags": [ - "rigging", - "selectSkinclusterJointsFromSelectedMesh", - "file" - ], - "title": "Select Skincluster Joints From Selected Mesh" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\setJointLabels.py", - "sourcetype": "file", - "tags": ["rigging", "setJointLabels", "file"], - "title": "Set Joint Labels" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py", - "sourcetype": "file", - "tags": [ - "rigging", - "setJointOrientationFromCurrentRotation", - "file" - ], - "title": "Set Joint Orientation From Current Rotation" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py", - "sourcetype": "file", - "tags": ["rigging", "setSelectedJointsOrientationZero", "file"], - "title": "Set Selected Joints Orientation Zero" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\mirrorCurveShape.py", - "sourcetype": "file", - "tags": ["rigging", "mirrorCurveShape", "file"], - "title": "Mirror Curve Shape" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\setRotationOrderUI.py", - "sourcetype": "file", - "tags": ["rigging", "setRotationOrderUI", "file"], - "title": "Set Rotation Order UI" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\paintItNowUI.py", - "sourcetype": "file", - "tags": ["rigging", "paintItNowUI", "file"], - "title": "Paint It Now UI" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\parentScaleConstraint.py", - "sourcetype": "file", - "tags": ["rigging", "parentScaleConstraint", "file"], - "title": "Parent Scale Constraint" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\quickSetWeightsUI.py", - "sourcetype": "file", - "tags": ["rigging", "quickSetWeightsUI", "file"], - "title": "Quick Set Weights UI" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\rapidRig.py", - "sourcetype": "file", - "tags": ["rigging", "rapidRig", "file"], - "title": "Rapid Rig" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\regenerate_blendshape_targets.py", - "sourcetype": "file", - "tags": ["rigging", "regenerate_blendshape_targets", "file"], - "title": "Regenerate Blendshape Targets" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\removeRotationAxis.py", - "sourcetype": "file", - "tags": ["rigging", "removeRotationAxis", "file"], - "title": "Remove Rotation Axis" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\resetBindSelectedMeshes.py", - "sourcetype": "file", - "tags": ["rigging", "resetBindSelectedMeshes", "file"], - "title": "Reset Bind Selected Meshes" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\simpleControllerOnSelection.py", - "sourcetype": "file", - "tags": ["rigging", "simpleControllerOnSelection", "file"], - "title": "Simple Controller On Selection" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\simpleControllerOnSelectionHierarchy.py", - "sourcetype": "file", - "tags": [ - "rigging", - "simpleControllerOnSelectionHierarchy", - "file" - ], - "title": "Simple Controller On Selection Hierarchy" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\superRelativeCluster.py", - "sourcetype": "file", - "tags": ["rigging", "superRelativeCluster", "file"], - "title": "Super Relative Cluster" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\tfSmoothSkinWeight.py", - "sourcetype": "file", - "tags": ["rigging", "tfSmoothSkinWeight", "file"], - "title": "TF Smooth Skin Weight" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\toggleIntermediates.py", - "sourcetype": "file", - "tags": ["rigging", "toggleIntermediates", "file"], - "title": "Toggle Intermediates" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\toggleSegmentScaleCompensate.py", - "sourcetype": "file", - "tags": ["rigging", "toggleSegmentScaleCompensate", "file"], - "title": "Toggle Segment Scale Compensate" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\rigging\\toggleSkinclusterDeformNormals.py", - "sourcetype": "file", - "tags": ["rigging", "toggleSkinclusterDeformNormals", "file"], - "title": "Toggle Skincluster Deform Normals" - } - ] - }, - { - "type": "menu", - "title": "Shading", - "items": [ - { - "type": "menu", - "title": "VRay", - "items": [ - { - "type": "action", - "title": "Import Proxies", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayImportProxies.py", - "sourcetype": "file", - "tags": ["shading", "vray", "import", "proxies"], - "tooltip": "" - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "Select All GES", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectAllGES.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "select All GES"] - }, - { - "type": "action", - "title": "Select All GES Under Selection", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "select", "all", "GES"] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "Selection To VRay Mesh", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "selection", "vraymesh"] - }, - { - "type": "action", - "title": "Add VRay Round Edges Attribute", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "round edges", "attribute"] - }, - { - "type": "action", - "title": "Add Gamma", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayAddGamma.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "add gamma"] - }, - { - "type": "separator" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py", - "sourcetype": "file", - "title": "Select Unconnected Shader Materials", - "tags": [ - "shading", - "vray", - "select", - "vraymesh", - "materials", - "unconnected shader slots" - ], - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py", - "sourcetype": "file", - "title": "Merge Similar VRay Mesh Materials", - "tags": [ - "shading", - "vray", - "Merge", - "VRayMesh", - "Materials" - ], - "tooltip": "" - }, - { - "type": "action", - "title": "Create Two Sided Material", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py", - "sourcetype": "file", - "tooltip": "Creates two sided material for selected material and renames it", - "tags": ["shading", "vray", "two sided", "material"] - }, - { - "type": "action", - "title": "Create Two Sided Material For Selected", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py", - "sourcetype": "file", - "tooltip": "Select material to create a two sided version from it", - "tags": [ - "shading", - "vray", - "Create2SidedMtlForSelectedMtl.py" - ] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "Add OpenSubdiv Attribute", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "add", - "open subdiv", - "attribute" - ] - }, - { - "type": "action", - "title": "Remove OpenSubdiv Attribute", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "remove", - "opensubdiv", - "attributee" - ] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "Add Subdivision Attribute", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "addVraySubdivisionAttribute" - ] - }, - { - "type": "action", - "title": "Remove Subdivision Attribute.py", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "remove", - "subdivision", - "attribute" - ] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "Add Vray Object Ids", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayObjectIds.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "add", "object id"] - }, - { - "type": "action", - "title": "Add Vray Material Ids", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "addVrayMaterialIds.py"] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "Set Physical DOF Depth", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "physical", "DOF ", "Depth"] - }, - { - "type": "action", - "title": "Magic Vray Proxy UI", - "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "magicVrayProxyUI"] - } - ] - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\pyblish\\lighting\\set_filename_prefix.py", - "sourcetype": "file", - "tags": [ - "shading", - "lookdev", - "assign", - "shaders", - "prefix", - "filename", - "render" - ], - "title": "Set filename prefix", - "tooltip": "Set the render file name prefix." - }, - { - "type": "action", - "command": "import mayalookassigner; mayalookassigner.show()", - "sourcetype": "python", - "tags": ["shading", "look", "assign", "shaders", "auto"], - "title": "Look Manager", - "tooltip": "Open the Look Manager UI for look assignment" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\LightLinkUi.py", - "sourcetype": "file", - "tags": ["shading", "light", "link", "ui"], - "title": "Light Link UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\vdviewer_ui.py", - "sourcetype": "file", - "tags": [ - "shading", - "look", - "vray", - "displacement", - "shaders", - "auto" - ], - "title": "VRay Displ Viewer", - "tooltip": "Open the VRay Displacement Viewer, select and control the content of the set" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", - "sourcetype": "file", - "tags": ["shading", "CLRImage", "textures", "preview"], - "title": "Set Texture Preview To CLRImage", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py", - "sourcetype": "file", - "tags": ["shading", "fix", "DefaultShaderSet", "Behavior"], - "title": "Fix Default Shader Set Behavior", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py", - "sourcetype": "file", - "tags": [ - "shading", - "fix", - "Selected", - "Shapes", - "Reference", - "Assignments" - ], - "title": "Fix Shapes Reference Assignments", - "tooltip": "Select shapes to fix the reference assignments" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\selectLambert1Members.py", - "sourcetype": "file", - "tags": ["shading", "selectLambert1Members"], - "title": "Select Lambert1 Members", - "tooltip": "Selects all objects which have the Lambert1 shader assigned" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\selectShapesWithoutShader.py", - "sourcetype": "file", - "tags": ["shading", "selectShapesWithoutShader"], - "title": "Select Shapes Without Shader", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py", - "sourcetype": "file", - "tags": ["shading", "fixRenderLayerOutAdjustmentErrors"], - "title": "Fix RenderLayer Out Adjustment Errors", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\shading\\fix_renderlayer_missing_node_override.py", - "sourcetype": "file", - "tags": [ - "shading", - "renderlayer", - "missing", - "reference", - "switch", - "layer" - ], - "title": "Fix RenderLayer Missing Referenced Nodes Overrides", - "tooltip": "" - }, - { - "type": "action", - "title": "Image 2 Tiled EXR", - "command": "$OPENPYPE_SCRIPTS\\shading\\open_img2exr.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "exr"] - } - ] - }, - { - "type": "menu", - "title": "Rendering", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\pyblish\\open_deadline_submission_settings.py", - "sourcetype": "file", - "tags": ["settings", "deadline", "globals", "render"], - "title": "DL Submission Settings UI", - "tooltip": "Open the Deadline Submission Settings UI" - } - ] - }, - { - "type": "menu", - "title": "Animation", - "items": [ - { - "type": "menu", - "title": "Attributes", - "tooltip": "", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyValues.py", - "sourcetype": "file", - "tags": ["animation", "copy", "attributes"], - "title": "Copy Values", - "tooltip": "Copy attribute values" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyInConnections.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "connections", - "incoming" - ], - "title": "Copy In Connections", - "tooltip": "Copy incoming connections" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyOutConnections.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "connections", - "out" - ], - "title": "Copy Out Connections", - "tooltip": "Copy outcoming connections" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformLocal.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "local" - ], - "title": "Copy Local Transforms", - "tooltip": "Copy local transforms" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "matrix" - ], - "title": "Copy Matrix Transforms", - "tooltip": "Copy Matrix transforms" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformUI.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "UI" - ], - "title": "Copy Transforms UI", - "tooltip": "Open the Copy Transforms UI" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\simpleCopyUI.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "UI", - "simple" - ], - "title": "Simple Copy UI", - "tooltip": "Open the simple Copy Transforms UI" - } - ] - }, - { - "type": "menu", - "title": "Optimize", - "tooltip": "Optimization scripts", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py", - "sourcetype": "file", - "tags": ["animation", "hierarchy", "toggle", "freeze"], - "title": "Toggle Freeze Hierarchy", - "tooltip": "Freeze and unfreeze hierarchy" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py", - "sourcetype": "file", - "tags": ["animation", "nucleus", "toggle", "parallel"], - "title": "Toggle Parallel Nucleus", - "tooltip": "Toggle parallel nucleus" - } - ] - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", - "tags": ["animation", "bake", "selection", "worldspace.py"], - "title": "Bake Selected To Worldspace", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\timeStepper.py", - "tags": ["animation", "time", "stepper"], - "title": "Time Stepper", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\capture_ui.py", - "tags": [ - "animation", - "capture", - "ui", - "screen", - "movie", - "image" - ], - "title": "Capture UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\simplePlayblastUI.py", - "tags": ["animation", "simple", "playblast", "ui"], - "title": "Simple Playblast UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\tweenMachineUI.py", - "tags": ["animation", "tween", "machine"], - "title": "Tween Machine UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\selectAllAnimationCurves.py", - "tags": ["animation", "select", "curves"], - "title": "Select All Animation Curves", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\pathAnimation.py", - "tags": ["animation", "path", "along"], - "title": "Path Animation", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\offsetSelectedObjectsUI.py", - "tags": ["animation", "offsetSelectedObjectsUI.py"], - "title": "Offset Selected Objects UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\key_amplifier_ui.py", - "tags": ["animation", "key", "amplifier"], - "title": "Key Amplifier UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\anim_scene_optimizer.py", - "tags": ["animation", "anim_scene_optimizer.py"], - "title": "Anim_Scene_Optimizer", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\zvParentMaster.py", - "tags": ["animation", "zvParentMaster.py"], - "title": "ZV Parent Master", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$OPENPYPE_SCRIPTS\\animation\\poseLibrary.py", - "tags": ["animation", "poseLibrary.py"], - "title": "Pose Library", - "type": "action" - } - ] - }, - { - "type": "menu", - "title": "Layout", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\alignDistributeUI.py", - "sourcetype": "file", - "tags": ["layout", "align", "Distribute", "UI"], - "title": "Align Distribute UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\alignSimpleUI.py", - "sourcetype": "file", - "tags": ["layout", "align", "UI", "Simple"], - "title": "Align Simple UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\center_locator.py", - "sourcetype": "file", - "tags": ["layout", "center", "locator"], - "title": "Center Locator", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\average_locator.py", - "sourcetype": "file", - "tags": ["layout", "average", "locator"], - "title": "Average Locator", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\selectWithinProximityUI.py", - "sourcetype": "file", - "tags": ["layout", "select", "proximity", "ui"], - "title": "Select Within Proximity UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\dupCurveUI.py", - "sourcetype": "file", - "tags": ["layout", "Duplicate", "Curve", "UI"], - "title": "Duplicate Curve UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\randomDeselectUI.py", - "sourcetype": "file", - "tags": ["layout", "random", "Deselect", "UI"], - "title": "Random Deselect UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\multiReferencerUI.py", - "sourcetype": "file", - "tags": ["layout", "multi", "reference"], - "title": "Multi Referencer UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\duplicateOffsetUI.py", - "sourcetype": "file", - "tags": ["layout", "duplicate", "offset", "UI"], - "title": "Duplicate Offset UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\spPaint3d.py", - "sourcetype": "file", - "tags": ["layout", "spPaint3d", "paint", "tool"], - "title": "SP Paint 3d", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\randomizeUI.py", - "sourcetype": "file", - "tags": ["layout", "randomize", "UI"], - "title": "Randomize UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\layout\\distributeWithinObjectUI.py", - "sourcetype": "file", - "tags": ["layout", "distribute", "ObjectUI", "within"], - "title": "Distribute Within Object UI", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "title": "Particles", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjects.py", - "sourcetype": "file", - "tags": ["particles", "instancerToObjects"], - "title": "Instancer To Objects", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsInstances.py", - "sourcetype": "file", - "tags": ["particles", "instancerToObjectsInstances"], - "title": "Instancer To Objects Instances", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\objectsToParticlesAndInstancerCleanSource.py", - "sourcetype": "file", - "tags": [ - "particles", - "objects", - "Particles", - "Instancer", - "Clean", - "Source" - ], - "title": "Objects To Particles & Instancer - Clean Source", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\particleComponentsToLocators.py", - "sourcetype": "file", - "tags": ["particles", "components", "locators"], - "title": "Particle Components To Locators", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\objectsToParticlesAndInstancer.py", - "sourcetype": "file", - "tags": ["particles", "objects", "particles", "instancer"], - "title": "Objects To Particles And Instancer", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\spawnParticlesOnMesh.py", - "sourcetype": "file", - "tags": ["particles", "spawn", "on", "mesh"], - "title": "Spawn Particles On Mesh", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsInstancesWithAnimation.py", - "sourcetype": "file", - "tags": [ - "particles", - "instancerToObjectsInstancesWithAnimation" - ], - "title": "Instancer To Objects Instances With Animation", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\objectsToParticles.py", - "sourcetype": "file", - "tags": ["particles", "objectsToParticles"], - "title": "Objects To Particles", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\add_particle_cacheFile_attrs.py", - "sourcetype": "file", - "tags": ["particles", "add_particle_cacheFile_attrs"], - "title": "Add Particle CacheFile Attributes", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\mergeParticleSystems.py", - "sourcetype": "file", - "tags": ["particles", "mergeParticleSystems"], - "title": "Merge Particle Systems", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\particlesToLocators.py", - "sourcetype": "file", - "tags": ["particles", "particlesToLocators"], - "title": "Particles To Locators", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsWithAnimation.py", - "sourcetype": "file", - "tags": ["particles", "instancerToObjectsWithAnimation"], - "title": "Instancer To Objects With Animation", - "tooltip": "" - }, - { - "type": "separator" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\mayaReplicateHoudiniTool.py", - "sourcetype": "file", - "tags": [ - "particles", - "houdini", - "houdiniTool", - "houdiniEngine" - ], - "title": "Replicate Houdini Tool", - "tooltip": "" - }, - { - "type": "separator" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\clearInitialState.py", - "sourcetype": "file", - "tags": ["particles", "clearInitialState"], - "title": "Clear Initial State", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\particles\\killSelectedParticles.py", - "sourcetype": "file", - "tags": ["particles", "killSelectedParticles"], - "title": "Kill Selected Particles", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "title": "Yeti", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\yeti\\yeti_rig_manager.py", - "sourcetype": "file", - "tags": ["yeti", "rig", "fur", "manager"], - "title": "Open Yeti Rig Manager", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "title": "Cleanup", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\repair_faulty_containers.py", - "sourcetype": "file", - "tags": ["cleanup", "repair", "containers"], - "title": "Find and Repair Containers", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\selectByType.py", - "sourcetype": "file", - "tags": ["cleanup", "selectByType"], - "title": "Select By Type", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\selectIntermediateObjects.py", - "sourcetype": "file", - "tags": ["cleanup", "selectIntermediateObjects"], - "title": "Select Intermediate Objects", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\selectNonUniqueNames.py", - "sourcetype": "file", - "tags": ["cleanup", "select", "non unique", "names"], - "title": "Select Non Unique Names", - "tooltip": "" - }, - { - "type": "separator" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeNamespaces.py", - "sourcetype": "file", - "tags": ["cleanup", "remove", "namespaces"], - "title": "Remove Namespaces", - "tooltip": "Remove all namespaces" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\remove_user_defined_attributes.py", - "sourcetype": "file", - "tags": ["cleanup", "remove_user_defined_attributes"], - "title": "Remove User Defined Attributes", - "tooltip": "Remove all user-defined attributes from all nodes" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeUnknownNodes.py", - "sourcetype": "file", - "tags": ["cleanup", "removeUnknownNodes"], - "title": "Remove Unknown Nodes", - "tooltip": "Remove all unknown nodes" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeUnloadedReferences.py", - "sourcetype": "file", - "tags": ["cleanup", "removeUnloadedReferences"], - "title": "Remove Unloaded References", - "tooltip": "Remove all unloaded references" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeReferencesFailedEdits.py", - "sourcetype": "file", - "tags": ["cleanup", "removeReferencesFailedEdits"], - "title": "Remove References Failed Edits", - "tooltip": "Remove failed edits for all references" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\remove_unused_looks.py", - "sourcetype": "file", - "tags": ["cleanup", "removeUnusedLooks"], - "title": "Remove Unused Looks", - "tooltip": "Remove all loaded yet unused Avalon look containers" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\deleteGhostIntermediateObjects.py", - "sourcetype": "file", - "tags": ["cleanup", "deleteGhostIntermediateObjects"], - "title": "Delete Ghost Intermediate Objects", - "tooltip": "" - }, - { - "type": "separator" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\resetViewportCache.py", - "sourcetype": "file", - "tags": ["cleanup", "reset", "viewport", "cache"], - "title": "Reset Viewport Cache", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\uniqifyNodeNames.py", - "sourcetype": "file", - "tags": ["cleanup", "uniqifyNodeNames"], - "title": "Uniqify Node Names", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\autoRenameFileNodes.py", - "sourcetype": "file", - "tags": ["cleanup", "auto", "rename", "filenodes"], - "title": "Auto Rename File Nodes", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\update_asset_id.py", - "sourcetype": "file", - "tags": ["cleanup", "update", "database", "asset", "id"], - "title": "Update Asset ID", - "tooltip": "Will replace the Colorbleed ID with a new one (asset ID : Unique number)" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\colorbleedRename.py", - "sourcetype": "file", - "tags": ["cleanup", "rename", "ui"], - "title": "Colorbleed Renamer", - "tooltip": "Colorbleed Rename UI" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\renameShapesToTransform.py", - "sourcetype": "file", - "tags": ["cleanup", "renameShapesToTransform"], - "title": "Rename Shapes To Transform", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\reorderUI.py", - "sourcetype": "file", - "tags": ["cleanup", "reorderUI"], - "title": "Reorder UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\cleanup\\pastedCleaner.py", - "sourcetype": "file", - "tags": ["cleanup", "pastedCleaner"], - "title": "Pasted Cleaner", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "title": "Others", - "items": [ - { - "type": "menu", - "sourcetype": "file", - "title": "Yeti", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\yeti\\cache_selected_yeti_nodes.py", - "sourcetype": "file", - "tags": ["others", "yeti", "cache", "selected"], - "title": "Cache Selected Yeti Nodes", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "title": "Hair", - "tooltip": "", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\hair\\recolorHairCurrentCurve", - "sourcetype": "file", - "tags": ["others", "selectSoftSelection"], - "title": "Select Soft Selection", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "command": "$OPENPYPE_SCRIPTS\\others\\display", - "sourcetype": "file", - "tags": ["others", "display"], - "title": "Display", - "items": [ - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\display\\wireframeSelectedObjects.py", - "sourcetype": "file", - "tags": ["others", "wireframe", "selected", "objects"], - "title": "Wireframe Selected Objects", - "tooltip": "" - } - ] - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\archiveSceneUI.py", - "sourcetype": "file", - "tags": ["others", "archiveSceneUI"], - "title": "Archive Scene UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\getSimilarMeshes.py", - "sourcetype": "file", - "tags": ["others", "getSimilarMeshes"], - "title": "Get Similar Meshes", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\createBoundingBoxEachSelected.py", - "sourcetype": "file", - "tags": ["others", "createBoundingBoxEachSelected"], - "title": "Create BoundingBox Each Selected", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\curveFromPositionEveryFrame.py", - "sourcetype": "file", - "tags": ["others", "curveFromPositionEveryFrame"], - "title": "Curve From Position", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\instanceLeafSmartTransform.py", - "sourcetype": "file", - "tags": ["others", "instance", "leaf", "smart", "transform"], - "title": "Instance Leaf Smart Transform", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\instanceSmartTransform.py", - "sourcetype": "file", - "tags": ["others", "instance", "smart", "transform"], - "title": "Instance Smart Transform", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\randomizeUVShellsSelectedObjects.py", - "sourcetype": "file", - "tags": ["others", "randomizeUVShellsSelectedObjects"], - "title": "Randomize UV Shells", - "tooltip": "Select objects before running action" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\centerPivotGroup.py", - "sourcetype": "file", - "tags": ["others", "centerPivotGroup"], - "title": "Center Pivot Group", - "tooltip": "" - }, - { - "type": "separator" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\locatorsOnSelectedFaces.py", - "sourcetype": "file", - "tags": ["others", "locatorsOnSelectedFaces"], - "title": "Locators On Selected Faces", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\locatorsOnEdgeSelectionPrompt.py", - "sourcetype": "file", - "tags": ["others", "locatorsOnEdgeSelectionPrompt"], - "title": "Locators On Edge Selection Prompt", - "tooltip": "" - }, - { - "type": "separator" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\copyDeformers.py", - "sourcetype": "file", - "tags": ["others", "copyDeformers"], - "title": "Copy Deformers", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\selectInReferenceEditor.py", - "sourcetype": "file", - "tags": ["others", "selectInReferenceEditor"], - "title": "Select In Reference Editor", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\selectConstrainingObject.py", - "sourcetype": "file", - "tags": ["others", "selectConstrainingObject"], - "title": "Select Constraining Object", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\deformerSetRelationsUI.py", - "sourcetype": "file", - "tags": ["others", "deformerSetRelationsUI"], - "title": "Deformer Set Relations UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$OPENPYPE_SCRIPTS\\others\\recreateBaseNodesForAllLatticeNodes.py", - "sourcetype": "file", - "tags": ["others", "recreate", "base", "nodes", "lattice"], - "title": "Recreate Base Nodes For Lattice Nodes", - "tooltip": "" - } - ] - } -] From cae76ef920e581884dbff487c1c8219826006df2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 12 Feb 2022 23:56:13 +0100 Subject: [PATCH 25/68] Remove some unused functions --- openpype/hosts/maya/api/lib.py | 147 --------------------------------- 1 file changed, 147 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 1f6c8c1deb..1eab0bdfb7 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -286,153 +286,6 @@ def pairwise(iterable): return itertools.izip(a, a) -def unique(name): - assert isinstance(name, string_types), "`name` must be string" - - while cmds.objExists(name): - matches = re.findall(r"\d+$", name) - - if matches: - match = matches[-1] - name = name.rstrip(match) - number = int(match) + 1 - else: - number = 1 - - name = name + str(number) - - return name - - -def uv_from_element(element): - """Return the UV coordinate of given 'element' - - Supports components, meshes, nurbs. - - """ - - supported = ["mesh", "nurbsSurface"] - - uv = [0.5, 0.5] - - if "." not in element: - type = cmds.nodeType(element) - if type == "transform": - geometry_shape = cmds.listRelatives(element, shapes=True) - - if len(geometry_shape) >= 1: - geometry_shape = geometry_shape[0] - else: - return - - elif type in supported: - geometry_shape = element - - else: - cmds.error("Could not do what you wanted..") - return - else: - # If it is indeed a component - get the current Mesh - try: - parent = element.split(".", 1)[0] - - # Maya is funny in that when the transform of the shape - # of the component element has children, the name returned - # by that elementection is the shape. Otherwise, it is - # the transform. So lets see what type we're dealing with here. - if cmds.nodeType(parent) in supported: - geometry_shape = parent - else: - geometry_shape = cmds.listRelatives(parent, shapes=1)[0] - - if not geometry_shape: - cmds.error("Skipping %s: Could not find shape." % element) - return - - if len(cmds.ls(geometry_shape)) > 1: - cmds.warning("Multiple shapes with identical " - "names found. This might not work") - - except TypeError as e: - cmds.warning("Skipping %s: Didn't find a shape " - "for component elementection. %s" % (element, e)) - return - - try: - type = cmds.nodeType(geometry_shape) - - if type == "nurbsSurface": - # If a surfacePoint is elementected on a nurbs surface - root, u, v = element.rsplit("[", 2) - uv = [float(u[:-1]), float(v[:-1])] - - if type == "mesh": - # ----------- - # Average the U and V values - # =========== - uvs = cmds.polyListComponentConversion(element, toUV=1) - if not uvs: - cmds.warning("Couldn't derive any UV's from " - "component, reverting to default U and V") - raise TypeError - - # Flatten list of Uv's as sometimes it returns - # neighbors like this [2:3] instead of [2], [3] - flattened = [] - - for uv in uvs: - flattened.extend(cmds.ls(uv, flatten=True)) - - uvs = flattened - - sumU = 0 - sumV = 0 - for uv in uvs: - try: - u, v = cmds.polyEditUV(uv, query=True) - except Exception: - cmds.warning("Couldn't find any UV coordinated, " - "reverting to default U and V") - raise TypeError - - sumU += u - sumV += v - - averagedU = sumU / len(uvs) - averagedV = sumV / len(uvs) - - uv = [averagedU, averagedV] - except TypeError: - pass - - return uv - - -def shape_from_element(element): - """Return shape of given 'element' - - Supports components, meshes, and surfaces - - """ - - try: - # Get either shape or transform, based on element-type - node = cmds.ls(element, objectsOnly=True)[0] - except Exception: - cmds.warning("Could not find node in %s" % element) - return None - - if cmds.nodeType(node) == 'transform': - try: - return cmds.listRelatives(node, shapes=True)[0] - except Exception: - cmds.warning("Could not find shape in %s" % element) - return None - - else: - return node - - def export_alembic(nodes, file, frame_range=None, From 651ac02b3be00afa4199847cd003b51fbf955908 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 12 Feb 2022 23:57:47 +0100 Subject: [PATCH 26/68] Remove old unused function --- openpype/hosts/maya/api/lib.py | 109 --------------------------------- 1 file changed, 109 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 1eab0bdfb7..3efa625e4a 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -430,115 +430,6 @@ def imprint(node, data): cmds.setAttr(node + "." + key, value, **set_type) -def serialise_shaders(nodes): - """Generate a shader set dictionary - - Arguments: - nodes (list): Absolute paths to nodes - - Returns: - dictionary of (shader: id) pairs - - Schema: - { - "shader1": ["id1", "id2"], - "shader2": ["id3", "id1"] - } - - Example: - { - "Bazooka_Brothers01_:blinn4SG": [ - "f9520572-ac1d-11e6-b39e-3085a99791c9.f[4922:5001]", - "f9520572-ac1d-11e6-b39e-3085a99791c9.f[4587:4634]", - "f9520572-ac1d-11e6-b39e-3085a99791c9.f[1120:1567]", - "f9520572-ac1d-11e6-b39e-3085a99791c9.f[4251:4362]" - ], - "lambert2SG": [ - "f9520571-ac1d-11e6-9dbb-3085a99791c9" - ] - } - - """ - - valid_nodes = cmds.ls( - nodes, - long=True, - recursive=True, - showType=True, - objectsOnly=True, - type="transform" - ) - - meshes_by_id = {} - for mesh in valid_nodes: - shapes = cmds.listRelatives(valid_nodes[0], - shapes=True, - fullPath=True) or list() - - if shapes: - shape = shapes[0] - if not cmds.nodeType(shape): - continue - - try: - id_ = cmds.getAttr(mesh + ".mbID") - - if id_ not in meshes_by_id: - meshes_by_id[id_] = list() - - meshes_by_id[id_].append(mesh) - - except ValueError: - continue - - meshes_by_shader = dict() - for mesh in meshes_by_id.values(): - shape = cmds.listRelatives(mesh, - shapes=True, - fullPath=True) or list() - - for shader in cmds.listConnections(shape, - type="shadingEngine") or list(): - - # Objects in this group are those that haven't got - # any shaders. These are expected to be managed - # elsewhere, such as by the default model loader. - if shader == "initialShadingGroup": - continue - - if shader not in meshes_by_shader: - meshes_by_shader[shader] = list() - - shaded = cmds.sets(shader, query=True) or list() - meshes_by_shader[shader].extend(shaded) - - shader_by_id = {} - for shader, shaded in meshes_by_shader.items(): - - if shader not in shader_by_id: - shader_by_id[shader] = list() - - for mesh in shaded: - - # Enable shader assignment to faces. - name = mesh.split(".f[")[0] - - transform = name - if cmds.objectType(transform) == "mesh": - transform = cmds.listRelatives(name, parent=True)[0] - - try: - id_ = cmds.getAttr(transform + ".mbID") - shader_by_id[shader].append(mesh.replace(name, id_)) - except KeyError: - continue - - # Remove duplicates - shader_by_id[shader] = list(set(shader_by_id[shader])) - - return shader_by_id - - def lsattr(attr, value=None): """Return nodes matching `key` and `value` From 33e1687120eb47f9f9239ed8f923e7a4d5238c0e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 13 Feb 2022 00:01:06 +0100 Subject: [PATCH 27/68] Remove maya_temp_folder() function - not used in code base --- openpype/hosts/maya/api/lib.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 3efa625e4a..6396324167 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1283,15 +1283,6 @@ def extract_alembic(file, return file -def maya_temp_folder(): - scene_dir = os.path.dirname(cmds.file(query=True, sceneName=True)) - tmp_dir = os.path.abspath(os.path.join(scene_dir, "..", "tmp")) - if not os.path.isdir(tmp_dir): - os.makedirs(tmp_dir) - - return tmp_dir - - # region ID def get_id_required_nodes(referenced_nodes=False, nodes=None): """Filter out any node which are locked (reference) or readOnly From 58ac71decb4945e8855de0f90b130ce55938916d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 13 Feb 2022 00:08:13 +0100 Subject: [PATCH 28/68] Fix docstring --- openpype/hosts/maya/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 6396324167..c99c3fefdb 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2582,7 +2582,7 @@ def get_attr_in_layer(attr, layer): def fix_incompatible_containers(): - """Return whether the current scene has any outdated content""" + """Backwards compatibility: old containers to use new ReferenceLoader""" host = api.registered_host() for container in host.ls(): From c74f4e6f04361b46f97d10929514f9a56a4225d1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 13 Feb 2022 00:08:28 +0100 Subject: [PATCH 29/68] More cleanup --- openpype/hosts/maya/api/lib.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index c99c3fefdb..1e4e65578a 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2124,6 +2124,7 @@ def reset_scene_resolution(): set_scene_resolution(width, height, pixelAspect) + def set_context_settings(): """Apply the project settings from the project definition @@ -2600,10 +2601,6 @@ def fix_incompatible_containers(): "ReferenceLoader", type="string") -def _null(*args): - pass - - class shelf(): '''A simple class to build shelves in maya. Since the build method is empty, it should be extended by the derived class to build the necessary shelf From b09629397ddc4186b13602fd4b81993529f72cc5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 13 Feb 2022 00:12:35 +0100 Subject: [PATCH 30/68] Cosmetics --- openpype/hosts/maya/api/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 1e4e65578a..8e24d5ea13 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2818,7 +2818,7 @@ class RenderSetupListObserver: cmds.delete(render_layer_set_name) -class RenderSetupItemObserver(): +class RenderSetupItemObserver: """Handle changes in render setup items.""" def __init__(self, item): @@ -3054,7 +3054,7 @@ def set_colorspace(): @contextlib.contextmanager def root_parent(nodes): # type: (list) -> list - """Context manager to un-parent provided nodes and return then back.""" + """Context manager to un-parent provided nodes and return them back.""" import pymel.core as pm # noqa node_parents = [] From d9ccb5e713f7ea8548eaad0cd2cd0c40abdd7a39 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 13 Feb 2022 11:37:42 +0100 Subject: [PATCH 31/68] Add back `_null(*args)`` because it was used by `shelf` --- openpype/hosts/maya/api/lib.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 8e24d5ea13..cf59a85c9b 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2601,6 +2601,10 @@ def fix_incompatible_containers(): "ReferenceLoader", type="string") +def _null(*args): + pass + + class shelf(): '''A simple class to build shelves in maya. Since the build method is empty, it should be extended by the derived class to build the necessary shelf From df0d8fcb8719221b4f80f4793e62fe37803fcff1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 13 Feb 2022 13:25:34 +0100 Subject: [PATCH 32/68] Remove Maya 'lock' logic since it's unused in OP code base --- openpype/hosts/maya/api/__init__.py | 11 ----- openpype/hosts/maya/api/pipeline.py | 70 ----------------------------- 2 files changed, 81 deletions(-) diff --git a/openpype/hosts/maya/api/__init__.py b/openpype/hosts/maya/api/__init__.py index 9ea798e927..0eea8c4a53 100644 --- a/openpype/hosts/maya/api/__init__.py +++ b/openpype/hosts/maya/api/__init__.py @@ -10,12 +10,6 @@ from .pipeline import ( ls, containerise, - - lock, - unlock, - is_locked, - lock_ignored, - ) from .plugin import ( Creator, @@ -54,11 +48,6 @@ __all__ = [ "ls", "containerise", - "lock", - "unlock", - "is_locked", - "lock_ignored", - "Creator", "Loader", diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 476ceb840b..2c6335b87b 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -187,76 +187,6 @@ def uninstall(): menu.uninstall() -def lock(): - """Lock scene - - Add an invisible node to your Maya scene with the name of the - current file, indicating that this file is "locked" and cannot - be modified any further. - - """ - - if not cmds.objExists("lock"): - with lib.maintained_selection(): - cmds.createNode("objectSet", name="lock") - cmds.addAttr("lock", ln="basename", dataType="string") - - # Permanently hide from outliner - cmds.setAttr("lock.verticesOnlySet", True) - - fname = cmds.file(query=True, sceneName=True) - basename = os.path.basename(fname) - cmds.setAttr("lock.basename", basename, type="string") - - -def unlock(): - """Permanently unlock a locked scene - - Doesn't throw an error if scene is already unlocked. - - """ - - try: - cmds.delete("lock") - except ValueError: - pass - - -def is_locked(): - """Query whether current scene is locked""" - fname = cmds.file(query=True, sceneName=True) - basename = os.path.basename(fname) - - if self._ignore_lock: - return False - - try: - return cmds.getAttr("lock.basename") == basename - except ValueError: - return False - - -@contextlib.contextmanager -def lock_ignored(): - """Context manager for temporarily ignoring the lock of a scene - - The purpose of this function is to enable locking a scene and - saving it with the lock still in place. - - Example: - >>> with lock_ignored(): - ... pass # Do things without lock - - """ - - self._ignore_lock = True - - try: - yield - finally: - self._ignore_lock = False - - def parse_container(container): """Return the container node's full container data. From 7cfbfeefa8496de9285f5e68ab9503a6805c8e21 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 16 Feb 2022 13:47:52 +0100 Subject: [PATCH 33/68] changed prefix 'restart' to 'reset' --- openpype/tools/settings/settings/categories.py | 8 ++++---- openpype/tools/settings/settings/window.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 28e38ea51d..08d3980e24 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -86,8 +86,8 @@ class SettingsCategoryWidget(QtWidgets.QWidget): state_changed = QtCore.Signal() saved = QtCore.Signal(QtWidgets.QWidget) restart_required_trigger = QtCore.Signal() - restart_started = QtCore.Signal() - restart_finished = QtCore.Signal() + reset_started = QtCore.Signal() + reset_finished = QtCore.Signal() full_path_requested = QtCore.Signal(str, str) def __init__(self, user_role, parent=None): @@ -436,7 +436,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget): self.require_restart_label.setText(value) def reset(self): - self.restart_started.emit() + self.reset_started.emit() self.set_state(CategoryState.Working) self._on_reset_start() @@ -517,7 +517,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget): self._on_reset_crash() else: self._on_reset_success() - self.restart_finished.emit() + self.reset_finished.emit() def _on_reset_crash(self): self.save_btn.setEnabled(False) diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index f6fa9a83a5..7c0c926fdd 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -69,8 +69,8 @@ class MainWidget(QtWidgets.QWidget): tab_widget.restart_required_trigger.connect( self._on_restart_required ) - tab_widget.restart_started.connect(self._on_restart_started) - tab_widget.restart_finished.connect(self._on_restart_finished) + tab_widget.reset_started.connect(self._on_reset_started) + tab_widget.reset_started.connect(self._on_reset_finished) tab_widget.full_path_requested.connect(self._on_full_path_request) self._header_tab_widget = header_tab_widget @@ -189,13 +189,13 @@ class MainWidget(QtWidgets.QWidget): if result == 1: self.trigger_restart.emit() - def _on_restart_started(self): + def _on_reset_started(self): widget = self.sender() current_widget = self._header_tab_widget.currentWidget() if current_widget is widget: self._update_search_dialog(True) - def _on_restart_finished(self): + def _on_reset_finished(self): widget = self.sender() current_widget = self._header_tab_widget.currentWidget() if current_widget is widget: From b7d2ea23b277b6bb8a34f0a18369cb40526143bb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 16 Feb 2022 21:54:33 +0100 Subject: [PATCH 34/68] Move Houdini Save Current File to beginning of ExtractorOrder --- openpype/hosts/houdini/plugins/publish/save_scene.py | 2 +- openpype/hosts/houdini/plugins/publish/save_scene_deadline.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/plugins/publish/save_scene.py b/openpype/hosts/houdini/plugins/publish/save_scene.py index 1b12efa603..0f9e547dee 100644 --- a/openpype/hosts/houdini/plugins/publish/save_scene.py +++ b/openpype/hosts/houdini/plugins/publish/save_scene.py @@ -6,7 +6,7 @@ class SaveCurrentScene(pyblish.api.InstancePlugin): """Save current scene""" label = "Save current file" - order = pyblish.api.IntegratorOrder - 0.49 + order = pyblish.api.ExtractorOrder - 0.49 hosts = ["houdini"] families = ["usdrender", "redshift_rop"] diff --git a/openpype/hosts/houdini/plugins/publish/save_scene_deadline.py b/openpype/hosts/houdini/plugins/publish/save_scene_deadline.py index a0efd0610c..a04f6887ff 100644 --- a/openpype/hosts/houdini/plugins/publish/save_scene_deadline.py +++ b/openpype/hosts/houdini/plugins/publish/save_scene_deadline.py @@ -5,7 +5,7 @@ class SaveCurrentSceneDeadline(pyblish.api.ContextPlugin): """Save current scene""" label = "Save current file" - order = pyblish.api.IntegratorOrder - 0.49 + order = pyblish.api.ExtractorOrder - 0.49 hosts = ["houdini"] targets = ["deadline"] From 67e90668fd98114aeb715f3ff355b4c4f423a2c7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 17 Feb 2022 15:06:02 +0100 Subject: [PATCH 35/68] flame: adding `useShotName` attribute to creator ui --- .../flame/plugins/create/create_shot_clip.py | 17 ++++++++++++----- .../defaults/project_settings/flame.json | 3 ++- .../projects_schema/schema_project_flame.json | 5 +++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/flame/plugins/create/create_shot_clip.py b/openpype/hosts/flame/plugins/create/create_shot_clip.py index f055c77a89..11c00dab42 100644 --- a/openpype/hosts/flame/plugins/create/create_shot_clip.py +++ b/openpype/hosts/flame/plugins/create/create_shot_clip.py @@ -87,41 +87,48 @@ class CreateShotClip(opfapi.Creator): "target": "tag", "toolTip": "Parents folder for shot root folder, Template filled with `Hierarchy Data` section", # noqa "order": 0}, + "useShotName": { + "value": True, + "type": "QCheckBox", + "label": "Use Shot Name", + "target": "ui", + "toolTip": "Use name form Shot name clip attribute", # noqa + "order": 1}, "clipRename": { "value": False, "type": "QCheckBox", "label": "Rename clips", "target": "ui", "toolTip": "Renaming selected clips on fly", # noqa - "order": 1}, + "order": 2}, "clipName": { "value": "{sequence}{shot}", "type": "QLineEdit", "label": "Clip Name Template", "target": "ui", "toolTip": "template for creating shot namespaused for renaming (use rename: on)", # noqa - "order": 2}, + "order": 3}, "segmentIndex": { "value": True, "type": "QCheckBox", "label": "Segment index", "target": "ui", "toolTip": "Take number from segment index", # noqa - "order": 3}, + "order": 4}, "countFrom": { "value": 10, "type": "QSpinBox", "label": "Count sequence from", "target": "ui", "toolTip": "Set when the sequence number stafrom", # noqa - "order": 4}, + "order": 5}, "countSteps": { "value": 10, "type": "QSpinBox", "label": "Stepping number", "target": "ui", "toolTip": "What number is adding every new step", # noqa - "order": 5}, + "order": 6}, } }, "hierarchyData": { diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json index b601f9bcba..6fb6f55528 100644 --- a/openpype/settings/defaults/project_settings/flame.json +++ b/openpype/settings/defaults/project_settings/flame.json @@ -2,7 +2,8 @@ "create": { "CreateShotClip": { "hierarchy": "{folder}/{sequence}", - "clipRename": true, + "useShotName": true, + "clipRename": false, "clipName": "{sequence}{shot}", "segmentIndex": true, "countFrom": 10, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json index 9ef05fa832..dc88d11f61 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json @@ -28,6 +28,11 @@ "key": "hierarchy", "label": "Shot parent hierarchy" }, + { + "type": "boolean", + "key": "useShotName", + "label": "Use Shot Name" + }, { "type": "boolean", "key": "clipRename", From 8bead3c1f2460f9ca621b7e49ec3d3e5bc759564 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 17 Feb 2022 15:31:57 +0100 Subject: [PATCH 36/68] flame: shot_name used for publishing asset --- openpype/hosts/flame/api/lib.py | 1 + openpype/hosts/flame/api/plugin.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index bbb7c38119..74d9e7607a 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -527,6 +527,7 @@ def get_segment_attributes(segment): # Add timeline segment to tree clip_data = { + "shot_name": segment.shot_name.get_value(), "segment_name": segment.name.get_value(), "segment_comment": segment.comment.get_value(), "tape_name": segment.tape_name, diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index db1793cba8..ec49db1601 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -361,6 +361,7 @@ class PublishableClip: vertical_sync_default = False driving_layer_default = "" index_from_segment_default = False + use_shot_name_default = False def __init__(self, segment, **kwargs): self.rename_index = kwargs["rename_index"] @@ -376,6 +377,7 @@ class PublishableClip: # segment (clip) main attributes self.cs_name = self.clip_data["segment_name"] self.cs_index = int(self.clip_data["segment"]) + self.shot_name = self.clip_data["shot_name"] # get track name and index self.track_index = int(self.clip_data["track"]) @@ -419,18 +421,21 @@ class PublishableClip: # deal with clip name new_name = self.marker_data.pop("newClipName") - if self.rename: + if self.rename and not self.use_shot_name: # rename segment self.current_segment.name = str(new_name) self.marker_data["asset"] = str(new_name) + elif self.use_shot_name: + self.marker_data["asset"] = self.shot_name + self.marker_data["hierarchyData"]["shot"] = self.shot_name else: self.marker_data["asset"] = self.cs_name self.marker_data["hierarchyData"]["shot"] = self.cs_name if self.marker_data["heroTrack"] and self.review_layer: - self.marker_data.update({"reviewTrack": self.review_layer}) + self.marker_data["reviewTrack"] = self.review_layer else: - self.marker_data.update({"reviewTrack": None}) + self.marker_data["reviewTrack"] = None # create pype tag on track_item and add data fpipeline.imprint(self.current_segment, self.marker_data) @@ -463,6 +468,8 @@ class PublishableClip: # ui_inputs data or default values if gui was not used self.rename = self.ui_inputs.get( "clipRename", {}).get("value") or self.rename_default + self.use_shot_name = self.ui_inputs.get( + "useShotName", {}).get("value") or self.use_shot_name_default self.clip_name = self.ui_inputs.get( "clipName", {}).get("value") or self.clip_name_default self.hierarchy = self.ui_inputs.get( From b1578dcdd7103a70f9dfdaa3dc612140cc8e4b0a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 18 Feb 2022 12:22:05 +0100 Subject: [PATCH 37/68] Maya: Fix `unique_namespace` when in an namespace that is empty --- openpype/hosts/maya/api/lib.py | 50 +++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 1f6c8c1deb..20c00de110 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -206,21 +206,51 @@ def unique_namespace(namespace, format="%02d", prefix="", suffix=""): format (str, optional): Formatting of the given iteration number suffix (str, optional): Only consider namespaces with this suffix. + >>> unique_namespace("bar") + # bar01 + >>> unique_namespace(":hello") + # :hello01 + >>> unique_namespace("bar:", suffix="_NS") + # bar01_NS: + """ + def current_namespace(): + current = cmds.namespaceInfo(currentNamespace=True, + absoluteName=True) + # When inside a namespace Maya adds no trailing : + if not current.endswith(":"): + current += ":" + return current + + # Always check against the absolute namespace root + # There's no clash with :x if we're defining namespace :a:x + ROOT = ":" if namespace.startswith(":") else current_namespace() + + # Strip trailing `:` tokens since we might want to add a suffix + start = ":" if namespace.startswith(":") else "" + end = ":" if namespace.endswith(":") else "" + namespace = namespace.strip(":") + if ":" in namespace: + # Split off any nesting that we don't uniqify anyway. + parents, namespace = namespace.rsplit(":", 1) + start += parents + ":" + ROOT += start + + def exists(n): + # Check for clash with nodes and namespaces + fullpath = ROOT + n + return cmds.objExists(fullpath) or cmds.namespace(exists=fullpath) + iteration = 1 - unique = prefix + (namespace + format % iteration) + suffix + while True: + nr_namespace = namespace + format % iteration + unique = prefix + nr_namespace + suffix + + if not exists(unique): + return start + unique + end - # The `existing` set does not just contain the namespaces but *all* nodes - # within "current namespace". We need all because the namespace could - # also clash with a node name. To be truly unique and valid one needs to - # check against all. - existing = set(cmds.namespaceInfo(listNamespace=True)) - while unique in existing: iteration += 1 - unique = prefix + (namespace + format % iteration) + suffix - - return unique def read(node): From 72a3a2bdba9a49d11a6baf2b3bd4bbf2a6b30c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Marinov?= Date: Fri, 18 Feb 2022 20:44:35 +0100 Subject: [PATCH 38/68] Avoid renaming udim indexes --- openpype/plugins/publish/integrate_new.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 3d48fb92ee..486718d8c4 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -478,6 +478,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if index_frame_start is not None: dst_padding_exp = "%0{}d".format(frame_start_padding) dst_padding = dst_padding_exp % (index_frame_start + frame_number) # noqa: E501 + elif repre.get("udim"): + dst_padding = int(i) dst = "{0}{1}{2}".format( dst_head, From b8d6e2181fff35882d4496beb5ec791cd9ad0993 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 20 Feb 2022 20:17:56 +0100 Subject: [PATCH 39/68] Fix #2714 houdini open last workfile --- openpype/hooks/pre_add_last_workfile_arg.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index 653f97b3dd..922dde49bb 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -17,6 +17,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "nuke", "nukex", "hiero", + "houdini", "nukestudio", "blender", "photoshop", From ebde6ced091febcf964e4a85b13064da853b26c6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 20 Feb 2022 20:20:23 +0100 Subject: [PATCH 40/68] Fix typo in name `afftereffects` -> `aftereffects` --- openpype/hooks/pre_add_last_workfile_arg.py | 2 +- openpype/hooks/pre_copy_template_workfile.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index 653f97b3dd..89f627b37f 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -21,7 +21,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "blender", "photoshop", "tvpaint", - "afftereffects" + "aftereffects" ] def execute(self): diff --git a/openpype/hooks/pre_copy_template_workfile.py b/openpype/hooks/pre_copy_template_workfile.py index 5c56d721e8..d975c5e12a 100644 --- a/openpype/hooks/pre_copy_template_workfile.py +++ b/openpype/hooks/pre_copy_template_workfile.py @@ -19,7 +19,7 @@ class CopyTemplateWorkfile(PreLaunchHook): # Before `AddLastWorkfileToLaunchArgs` order = 0 - app_groups = ["blender", "photoshop", "tvpaint", "afftereffects"] + app_groups = ["blender", "photoshop", "tvpaint", "aftereffects"] def execute(self): """Check if can copy template for context and do it if possible. From 586f632f36a169147b379d90da3b294bd578ab70 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 20 Feb 2022 21:28:45 +0100 Subject: [PATCH 41/68] Fix typos --- openpype/hooks/pre_copy_template_workfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hooks/pre_copy_template_workfile.py b/openpype/hooks/pre_copy_template_workfile.py index d975c5e12a..dffac22ee2 100644 --- a/openpype/hooks/pre_copy_template_workfile.py +++ b/openpype/hooks/pre_copy_template_workfile.py @@ -44,7 +44,7 @@ class CopyTemplateWorkfile(PreLaunchHook): return if os.path.exists(last_workfile): - self.log.debug("Last workfile exits. Skipping {} process.".format( + self.log.debug("Last workfile exists. Skipping {} process.".format( self.__class__.__name__ )) return @@ -120,7 +120,7 @@ class CopyTemplateWorkfile(PreLaunchHook): f"Creating workfile from template: \"{template_path}\"" ) - # Copy template workfile to new destinantion + # Copy template workfile to new destination shutil.copy2( os.path.normpath(template_path), os.path.normpath(last_workfile) From c1a4204fb96d33fede2341096247a23dbce3cecd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 20 Feb 2022 22:56:07 +0100 Subject: [PATCH 42/68] Implement Reset Frame Range for Houdini --- openpype/hosts/houdini/api/lib.py | 34 +++++++++++++++++++ .../hosts/houdini/startup/MainMenuCommon.xml | 8 +++++ 2 files changed, 42 insertions(+) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 72f1c8e71f..5a087ea276 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -542,3 +542,37 @@ def maintained_selection(): if previous_selection: for node in previous_selection: node.setSelected(on=True) + + +def reset_framerange(): + """Set frame range to current asset""" + + asset_name = api.Session["AVALON_ASSET"] + asset = io.find_one({"name": asset_name, "type": "asset"}) + + frame_start = asset["data"].get("frameStart") + frame_end = asset["data"].get("frameEnd") + # Backwards compatibility + if frame_start is None or frame_end is None: + frame_start = asset["data"].get("edit_in") + frame_end = asset["data"].get("edit_out") + + if frame_start is None or frame_end is None: + log.warning("No edit information found for %s" % asset_name) + return + + handles = asset["data"].get("handles") or 0 + handle_start = asset["data"].get("handleStart") + if handle_start is None: + handle_start = handles + + handle_end = asset["data"].get("handleEnd") + if handle_end is None: + handle_end = handles + + frame_start -= int(handle_start) + frame_end += int(handle_end) + + hou.playbar.setFrameRange(frame_start, frame_end) + hou.playbar.setPlaybackRange(frame_start, frame_end) + hou.setFrame(frame_start) diff --git a/openpype/hosts/houdini/startup/MainMenuCommon.xml b/openpype/hosts/houdini/startup/MainMenuCommon.xml index b8c7f93d76..abfa3f136e 100644 --- a/openpype/hosts/houdini/startup/MainMenuCommon.xml +++ b/openpype/hosts/houdini/startup/MainMenuCommon.xml @@ -66,6 +66,14 @@ host_tools.show_workfiles(parent) ]]> + + + + + From e945775161cd86934464c62dd734ea876ea2448a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 20 Feb 2022 23:05:10 +0100 Subject: [PATCH 43/68] Set asset frame range on Houdini launch and for each new scene --- openpype/hosts/houdini/api/pipeline.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index c3dbdc5ef5..69fb236432 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -66,9 +66,9 @@ def install(): sys.path.append(hou_pythonpath) - # Set asset FPS for the empty scene directly after launch of Houdini - # so it initializes into the correct scene FPS - _set_asset_fps() + # Set asset settings for the empty scene directly after launch of Houdini + # so it initializes into the correct scene FPS, Frame Range, etc. + _set_context_settings() def uninstall(): @@ -280,17 +280,31 @@ def on_open(*args): def on_new(_): """Set project resolution and fps when create a new file""" log.info("Running callback on new..") - _set_asset_fps() + _set_context_settings() -def _set_asset_fps(): - """Set Houdini scene FPS to the default required for current asset""" +def _set_context_settings(): + """Apply the project settings from the project definition + + Settings can be overwritten by an asset if the asset.data contains + any information regarding those settings. + + Examples of settings: + fps + resolution + renderer + + Returns: + None + """ # Set new scene fps fps = get_asset_fps() print("Setting scene FPS to %i" % fps) lib.set_scene_fps(fps) + lib.reset_framerange() + def on_pyblish_instance_toggled(instance, new_value, old_value): """Toggle saver tool passthrough states on instance toggles.""" From 46d3ee4ba4fa392c4ea90f0f9db2ba238b6e9312 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 20 Feb 2022 23:41:18 +0100 Subject: [PATCH 44/68] Add todo --- openpype/hosts/houdini/api/pipeline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index 69fb236432..5280a6b60a 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -68,6 +68,7 @@ def install(): # Set asset settings for the empty scene directly after launch of Houdini # so it initializes into the correct scene FPS, Frame Range, etc. + # todo: make sure this doesn't trigger when opening with last workfile _set_context_settings() From 41d54f80c4ac8da3af8012d8fe9668dcc0889edd Mon Sep 17 00:00:00 2001 From: murphy Date: Mon, 21 Feb 2022 10:27:42 +0100 Subject: [PATCH 45/68] wrong link fix Installation of python and pyside link broken --- website/docs/artist_hosts_resolve.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/artist_hosts_resolve.md b/website/docs/artist_hosts_resolve.md index be069eea79..80e3b0e139 100644 --- a/website/docs/artist_hosts_resolve.md +++ b/website/docs/artist_hosts_resolve.md @@ -9,7 +9,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; :::warning -Before you are able to start with OpenPype tools in DaVinci Resolve, installation of its own Python 3.6 interpreter and PySide 2 has to be done. Go to [Installation of python and pyside](#installation-of-python-and-pyside) link for more information +Before you are able to start with OpenPype tools in DaVinci Resolve, installation of its own Python 3.6 interpreter and PySide 2 has to be done. Go to [Installation of python and pyside](admin_hosts_resolve#installation-of-python-and-pyside) link for more information ::: From e6177c5e7763a9bba45f83e253603b1c62e0d7fa Mon Sep 17 00:00:00 2001 From: murphy Date: Mon, 21 Feb 2022 11:30:42 +0100 Subject: [PATCH 46/68] missing .md missing .md to prevent problems with "/" on the web server --- website/docs/artist_hosts_resolve.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/artist_hosts_resolve.md b/website/docs/artist_hosts_resolve.md index 80e3b0e139..01e50c12c9 100644 --- a/website/docs/artist_hosts_resolve.md +++ b/website/docs/artist_hosts_resolve.md @@ -9,7 +9,7 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; :::warning -Before you are able to start with OpenPype tools in DaVinci Resolve, installation of its own Python 3.6 interpreter and PySide 2 has to be done. Go to [Installation of python and pyside](admin_hosts_resolve#installation-of-python-and-pyside) link for more information +Before you are able to start with OpenPype tools in DaVinci Resolve, installation of its own Python 3.6 interpreter and PySide 2 has to be done. Go to [Installation of python and pyside](admin_hosts_resolve.md#installation-of-python-and-pyside) link for more information ::: From dbcb8f63e36f8813029a7da25650bb1dfa361875 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 21 Feb 2022 13:55:24 +0100 Subject: [PATCH 47/68] OP-2642 - added subset filter to limit Slack notifications --- .../standalonepublisher/plugins/publish/collect_texture.py | 3 +++ .../modules/slack/plugins/publish/collect_slack_family.py | 7 ++++++- openpype/settings/defaults/project_settings/slack.json | 1 + .../schemas/projects_schema/schema_project_slack.json | 6 ++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py index 596a8ccfd2..d318070fbc 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py @@ -411,9 +411,11 @@ class CollectTextures(pyblish.api.ContextPlugin): Raises: ValueError - if broken 'input_naming_groups' """ + self.log.info("{} {} {}".format(name, input_naming_patterns, input_naming_groups)) for input_pattern in input_naming_patterns: for cs in color_spaces: pattern = input_pattern.replace('{color_space}', cs) + self.log.info("{} {}".format(pattern, name)) regex_result = re.findall(pattern, name) if regex_result: idx = list(input_naming_groups).index(key) @@ -424,6 +426,7 @@ class CollectTextures(pyblish.api.ContextPlugin): try: parsed_value = regex_result[0][idx] + self.log.info("par{}".format(parsed_value)) return parsed_value except IndexError: self.log.warning("Wrong index, probably " diff --git a/openpype/modules/slack/plugins/publish/collect_slack_family.py b/openpype/modules/slack/plugins/publish/collect_slack_family.py index 2110c0703b..6c965b04cd 100644 --- a/openpype/modules/slack/plugins/publish/collect_slack_family.py +++ b/openpype/modules/slack/plugins/publish/collect_slack_family.py @@ -20,18 +20,23 @@ class CollectSlackFamilies(pyblish.api.InstancePlugin): def process(self, instance): task_name = io.Session.get("AVALON_TASK") family = self.main_family_from_instance(instance) - key_values = { "families": family, "tasks": task_name, "hosts": instance.data["anatomyData"]["app"], + "subsets": instance.data["subset"] } profile = filter_profiles(self.profiles, key_values, logger=self.log) + if not profile: + self.log.info("No profile found, notification won't be send") + return + # make slack publishable if profile: + self.log.info("Found profile: {}".format(profile)) if instance.data.get('families'): instance.data['families'].append('slack') else: diff --git a/openpype/settings/defaults/project_settings/slack.json b/openpype/settings/defaults/project_settings/slack.json index 2d10bd173d..d77b8c2208 100644 --- a/openpype/settings/defaults/project_settings/slack.json +++ b/openpype/settings/defaults/project_settings/slack.json @@ -10,6 +10,7 @@ "hosts": [], "task_types": [], "tasks": [], + "subsets": [], "channel_messages": [] } ] diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json index 4e82c991e7..14814d8b01 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_slack.json @@ -69,6 +69,12 @@ "type": "list", "object_type": "text" }, + { + "key": "subsets", + "label": "Subset names", + "type": "list", + "object_type": "text" + }, { "type": "separator" }, From 43010a490ebcbe2bbbe3a882e8a10d57cf415a16 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 21 Feb 2022 15:07:00 +0100 Subject: [PATCH 48/68] Fix OpenPype not initialized on Houdini launch when opening with last workfile --- openpype/hooks/pre_add_last_workfile_arg.py | 40 +++++++++++++++++-- .../startup/scripts/openpype_launch.py | 40 +++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 openpype/hosts/houdini/startup/scripts/openpype_launch.py diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index 922dde49bb..4797b61580 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -17,7 +17,6 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "nuke", "nukex", "hiero", - "houdini", "nukestudio", "blender", "photoshop", @@ -25,7 +24,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "afftereffects" ] - def execute(self): + def get_last_workfile(self): if not self.data.get("start_last_workfile"): self.log.info("It is set to not start last workfile on start.") return @@ -39,5 +38,38 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): self.log.info("Current context does not have any workfile yet.") return - # Add path to workfile to arguments - self.launch_context.launch_args.append(last_workfile) + return last_workfile + + def execute(self): + + last_workfile = self.get_last_workfile() + if last_workfile: + # Add path to workfile to arguments + self.launch_context.launch_args.append(last_workfile) + + +class AddLastWorkfileToLaunchArgsHoudini(AddLastWorkfileToLaunchArgs): + """Add last workfile path to launch arguments - Houdini specific""" + app_groups = ["houdini"] + + def execute(self): + + last_workfile = self.get_last_workfile() + if last_workfile: + # Whenever a filepath is passed to Houdini then the startup + # scripts 123.py and houdinicore.py won't be triggered. Thus + # OpenPype will not initialize correctly. As such, whenever + # we pass a workfile we first explicitly pass a startup + # script to enforce it to run - which will load the last passed + # argument as workfile directly. + pype_root = os.environ["OPENPYPE_REPOS_ROOT"] + startup_path = os.path.join( + pype_root, "openpype", "hosts", "houdini", "startup" + ) + startup_script = os.path.join(startup_path, + "scripts", + "openpype_launch.py") + self.launch_context.launch_args.append(startup_script) + + # Add path to workfile to arguments + self.launch_context.launch_args.append(last_workfile) diff --git a/openpype/hosts/houdini/startup/scripts/openpype_launch.py b/openpype/hosts/houdini/startup/scripts/openpype_launch.py new file mode 100644 index 0000000000..1a9069dbc6 --- /dev/null +++ b/openpype/hosts/houdini/startup/scripts/openpype_launch.py @@ -0,0 +1,40 @@ +import os +import sys +import avalon.api +from openpype.hosts.houdini import api +import openpype.hosts.houdini.api.workio + +import hou + + +def is_workfile(path): + if not path: + return + + if not os.path.exists(path): + return False + + _, ext = os.path.splitext(path) + if ext in openpype.hosts.houdini.api.workio.file_extensions(): + return True + + +def main(): + print("Installing OpenPype ...") + avalon.api.install(api) + + args = sys.argv + if args and is_workfile(args[-1]): + # If the last argument is a Houdini file open it directly + workfile_path = args[-1].replace("\\", "/") + print("Opening workfile on launch: {}".format(workfile_path)) + + # We don't use `workio.open_file` because we want to explicitly ignore + # load warnings. Otherwise Houdini will fail to start if a scene load + # produces e.g. errors on missing plug-ins + hou.hipFile.load(workfile_path, + suppress_save_prompt=True, + ignore_load_warnings=True) + + +main() From 5b85dff95a323813604e927b4ed3c5428b074097 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 21 Feb 2022 15:17:58 +0100 Subject: [PATCH 49/68] OP-2642 - added subset filter to limit Slack notifications --- website/docs/module_slack.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md index 67b200259d..3a2842da63 100644 --- a/website/docs/module_slack.md +++ b/website/docs/module_slack.md @@ -47,7 +47,7 @@ It is possible to create multiple tokens and configure different scopes for them ### Profiles Profiles are used to select when to trigger notification. One or multiple profiles -could be configured, `Families`, `Task names` (regex available), `Host names` combination is needed. +could be configured, `Families`, `Task names` (regex available), `Host names`, `Subset names` (regex available) combination is needed. Eg. If I want to be notified when render is published from Maya, setting is: From 1cfdbcb2a14d36cef5bb920b5ae20f74cc476623 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 21 Feb 2022 15:55:32 +0100 Subject: [PATCH 50/68] Use pythonrc.py file for Houdini start instead of 123.py/houdinicore.py --- .../123.py => python2.7libs/pythonrc.py} | 0 .../pythonrc.py} | 0 .../startup/scripts/openpype_launch.py | 40 ------------------- 3 files changed, 40 deletions(-) rename openpype/hosts/houdini/startup/{scripts/123.py => python2.7libs/pythonrc.py} (100%) rename openpype/hosts/houdini/startup/{scripts/houdinicore.py => python3.7libs/pythonrc.py} (100%) delete mode 100644 openpype/hosts/houdini/startup/scripts/openpype_launch.py diff --git a/openpype/hosts/houdini/startup/scripts/123.py b/openpype/hosts/houdini/startup/python2.7libs/pythonrc.py similarity index 100% rename from openpype/hosts/houdini/startup/scripts/123.py rename to openpype/hosts/houdini/startup/python2.7libs/pythonrc.py diff --git a/openpype/hosts/houdini/startup/scripts/houdinicore.py b/openpype/hosts/houdini/startup/python3.7libs/pythonrc.py similarity index 100% rename from openpype/hosts/houdini/startup/scripts/houdinicore.py rename to openpype/hosts/houdini/startup/python3.7libs/pythonrc.py diff --git a/openpype/hosts/houdini/startup/scripts/openpype_launch.py b/openpype/hosts/houdini/startup/scripts/openpype_launch.py deleted file mode 100644 index 1a9069dbc6..0000000000 --- a/openpype/hosts/houdini/startup/scripts/openpype_launch.py +++ /dev/null @@ -1,40 +0,0 @@ -import os -import sys -import avalon.api -from openpype.hosts.houdini import api -import openpype.hosts.houdini.api.workio - -import hou - - -def is_workfile(path): - if not path: - return - - if not os.path.exists(path): - return False - - _, ext = os.path.splitext(path) - if ext in openpype.hosts.houdini.api.workio.file_extensions(): - return True - - -def main(): - print("Installing OpenPype ...") - avalon.api.install(api) - - args = sys.argv - if args and is_workfile(args[-1]): - # If the last argument is a Houdini file open it directly - workfile_path = args[-1].replace("\\", "/") - print("Opening workfile on launch: {}".format(workfile_path)) - - # We don't use `workio.open_file` because we want to explicitly ignore - # load warnings. Otherwise Houdini will fail to start if a scene load - # produces e.g. errors on missing plug-ins - hou.hipFile.load(workfile_path, - suppress_save_prompt=True, - ignore_load_warnings=True) - - -main() From 065c6a092fdcab6920347ed69ea0b30de474afb3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 21 Feb 2022 15:56:07 +0100 Subject: [PATCH 51/68] Revert separation of Houdini last workfile code --- openpype/hooks/pre_add_last_workfile_arg.py | 35 ++------------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index 4797b61580..caebd7d034 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -17,6 +17,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "nuke", "nukex", "hiero", + "houdini", "nukestudio", "blender", "photoshop", @@ -24,7 +25,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "afftereffects" ] - def get_last_workfile(self): + def execute(self): if not self.data.get("start_last_workfile"): self.log.info("It is set to not start last workfile on start.") return @@ -38,38 +39,6 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): self.log.info("Current context does not have any workfile yet.") return - return last_workfile - - def execute(self): - - last_workfile = self.get_last_workfile() if last_workfile: # Add path to workfile to arguments self.launch_context.launch_args.append(last_workfile) - - -class AddLastWorkfileToLaunchArgsHoudini(AddLastWorkfileToLaunchArgs): - """Add last workfile path to launch arguments - Houdini specific""" - app_groups = ["houdini"] - - def execute(self): - - last_workfile = self.get_last_workfile() - if last_workfile: - # Whenever a filepath is passed to Houdini then the startup - # scripts 123.py and houdinicore.py won't be triggered. Thus - # OpenPype will not initialize correctly. As such, whenever - # we pass a workfile we first explicitly pass a startup - # script to enforce it to run - which will load the last passed - # argument as workfile directly. - pype_root = os.environ["OPENPYPE_REPOS_ROOT"] - startup_path = os.path.join( - pype_root, "openpype", "hosts", "houdini", "startup" - ) - startup_script = os.path.join(startup_path, - "scripts", - "openpype_launch.py") - self.launch_context.launch_args.append(startup_script) - - # Add path to workfile to arguments - self.launch_context.launch_args.append(last_workfile) From c03f6ec0b1e638e5864e7ca20a725118169c24d0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 21 Feb 2022 15:56:58 +0100 Subject: [PATCH 52/68] Remove redundant if statement that didn't get reverted --- openpype/hooks/pre_add_last_workfile_arg.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py index caebd7d034..922dde49bb 100644 --- a/openpype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -39,6 +39,5 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): self.log.info("Current context does not have any workfile yet.") return - if last_workfile: - # Add path to workfile to arguments - self.launch_context.launch_args.append(last_workfile) + # Add path to workfile to arguments + self.launch_context.launch_args.append(last_workfile) From 592cf54a976f4eab3a86987adbd23249fa0aa4e7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 21 Feb 2022 16:13:53 +0100 Subject: [PATCH 53/68] OP-2642 - revert of unwanted logs --- .../standalonepublisher/plugins/publish/collect_texture.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py index d318070fbc..3b7343e685 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py @@ -411,7 +411,6 @@ class CollectTextures(pyblish.api.ContextPlugin): Raises: ValueError - if broken 'input_naming_groups' """ - self.log.info("{} {} {}".format(name, input_naming_patterns, input_naming_groups)) for input_pattern in input_naming_patterns: for cs in color_spaces: pattern = input_pattern.replace('{color_space}', cs) @@ -426,7 +425,6 @@ class CollectTextures(pyblish.api.ContextPlugin): try: parsed_value = regex_result[0][idx] - self.log.info("par{}".format(parsed_value)) return parsed_value except IndexError: self.log.warning("Wrong index, probably " From 9dd492ab236bd09f44947bed51336bd5484a4d16 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 21 Feb 2022 16:14:34 +0100 Subject: [PATCH 54/68] OP-2642 - revert of unwanted logs --- .../hosts/standalonepublisher/plugins/publish/collect_texture.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py index 3b7343e685..596a8ccfd2 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py @@ -414,7 +414,6 @@ class CollectTextures(pyblish.api.ContextPlugin): for input_pattern in input_naming_patterns: for cs in color_spaces: pattern = input_pattern.replace('{color_space}', cs) - self.log.info("{} {}".format(pattern, name)) regex_result = re.findall(pattern, name) if regex_result: idx = list(input_naming_groups).index(key) From 88a6aaee70c21f1941704d4280dddc18a663410c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 21 Feb 2022 16:15:10 +0100 Subject: [PATCH 55/68] Fix current frame not set correctly on new scene --- openpype/hosts/houdini/api/pipeline.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index 5280a6b60a..4c55de7246 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -4,6 +4,7 @@ import logging import contextlib import hou +import hdefereval import pyblish.api import avalon.api @@ -283,6 +284,15 @@ def on_new(_): log.info("Running callback on new..") _set_context_settings() + # It seems that the current frame always gets reset to frame 1 on + # new scene. So we enforce current frame to be at the start of the playbar + # with execute deferred + def _enforce_start_frame(): + start = hou.playbar.playbackRange()[0] + hou.setFrame(start) + + hdefereval.executeDeferred(_enforce_start_frame) + def _set_context_settings(): """Apply the project settings from the project definition From 823164e6c49732fa96d6960f886b66d2e1d688fb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 21 Feb 2022 16:29:57 +0100 Subject: [PATCH 56/68] Avoid on_new callback when opening a Houdini file --- openpype/hosts/houdini/api/pipeline.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index 4c55de7246..1c08e72d65 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -281,6 +281,14 @@ def on_open(*args): def on_new(_): """Set project resolution and fps when create a new file""" + + if hou.hipFile.isLoadingHipFile(): + # This event also triggers when Houdini opens a file due to the + # new event being registered to 'afterClear'. As such we can skip + # 'new' logic if the user is opening a file anyway + log.debug("Skipping on new callback due to scene being opened.") + return + log.info("Running callback on new..") _set_context_settings() From b52e511a6e3d233242fa5053434953e4b0a52f8c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 21 Feb 2022 21:03:16 +0100 Subject: [PATCH 57/68] Remove more unused code --- openpype/hosts/maya/api/__init__.py | 4 -- openpype/hosts/maya/api/lib.py | 91 ----------------------------- 2 files changed, 95 deletions(-) diff --git a/openpype/hosts/maya/api/__init__.py b/openpype/hosts/maya/api/__init__.py index 0eea8c4a53..5d76bf0f04 100644 --- a/openpype/hosts/maya/api/__init__.py +++ b/openpype/hosts/maya/api/__init__.py @@ -32,11 +32,9 @@ from .lib import ( read, apply_shaders, - without_extension, maintained_selection, suspended_refresh, - unique_name, unique_namespace, ) @@ -65,11 +63,9 @@ __all__ = [ "lsattrs", "read", - "unique_name", "unique_namespace", "apply_shaders", - "without_extension", "maintained_selection", "suspended_refresh", diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index cf59a85c9b..cd5f6ffbd8 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -154,53 +154,9 @@ def maintained_selection(): cmds.select(clear=True) -def unique_name(name, format="%02d", namespace="", prefix="", suffix=""): - """Return unique `name` - - The function takes into consideration an optional `namespace` - and `suffix`. The suffix is included in evaluating whether a - name exists - such as `name` + "_GRP" - but isn't included - in the returned value. - - If a namespace is provided, only names within that namespace - are considered when evaluating whether the name is unique. - - Arguments: - format (str, optional): The `name` is given a number, this determines - how this number is formatted. Defaults to a padding of 2. - E.g. my_name01, my_name02. - namespace (str, optional): Only consider names within this namespace. - suffix (str, optional): Only consider names with this suffix. - - Example: - >>> name = cmds.createNode("transform", name="MyName") - >>> cmds.objExists(name) - True - >>> unique = unique_name(name) - >>> cmds.objExists(unique) - False - - """ - - iteration = 1 - unique = prefix + (name + format % iteration) + suffix - - while cmds.objExists(namespace + ":" + unique): - iteration += 1 - unique = prefix + (name + format % iteration) + suffix - - if suffix: - return unique[:-len(suffix)] - - return unique - - def unique_namespace(namespace, format="%02d", prefix="", suffix=""): """Return unique namespace - Similar to :func:`unique_name` but evaluating namespaces - as opposed to object names. - Arguments: namespace (str): Name of namespace to consider format (str, optional): Formatting of the given iteration number @@ -508,17 +464,6 @@ def lsattrs(attrs): return list(matches) -@contextlib.contextmanager -def without_extension(): - """Use cmds.file with defaultExtensions=False""" - previous_setting = cmds.file(defaultExtensions=True, query=True) - try: - cmds.file(defaultExtensions=False) - yield - finally: - cmds.file(defaultExtensions=previous_setting) - - @contextlib.contextmanager def attribute_values(attr_values): """Remaps node attributes to values during context. @@ -597,26 +542,6 @@ def evaluation(mode="off"): cmds.evaluationManager(mode=original) -@contextlib.contextmanager -def no_refresh(): - """Temporarily disables Maya's UI updates - - Note: - This only disabled the main pane and will sometimes still - trigger updates in torn off panels. - - """ - - pane = _get_mel_global('gMainPane') - state = cmds.paneLayout(pane, query=True, manage=True) - cmds.paneLayout(pane, edit=True, manage=False) - - try: - yield - finally: - cmds.paneLayout(pane, edit=True, manage=state) - - @contextlib.contextmanager def empty_sets(sets, force=False): """Remove all members of the sets during the context""" @@ -1467,22 +1392,6 @@ def set_id(node, unique_id, overwrite=False): cmds.setAttr(attr, unique_id, type="string") -def remove_id(node): - """Remove the id attribute from the input node. - - Args: - node (str): The node name - - Returns: - bool: Whether an id attribute was deleted - - """ - if cmds.attributeQuery("cbId", node=node, exists=True): - cmds.deleteAttr("{}.cbId".format(node)) - return True - return False - - # endregion ID def get_reference_node(path): """ From edbd0326160c42d0831717bb26af758fb66ee6b8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 21 Feb 2022 21:05:13 +0100 Subject: [PATCH 58/68] Remove unused import --- openpype/hosts/maya/api/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index cd5f6ffbd8..1da36ff2d4 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2,7 +2,6 @@ import os import sys -import re import platform import uuid import math From 7de42ee923dabba0c534774d6367fc44df67ecf6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 21 Feb 2022 22:07:10 +0100 Subject: [PATCH 59/68] Always perform scene save in Houdini on publish --- .../houdini/plugins/publish/save_scene.py | 16 ++----------- .../plugins/publish/save_scene_deadline.py | 23 ------------------- 2 files changed, 2 insertions(+), 37 deletions(-) delete mode 100644 openpype/hosts/houdini/plugins/publish/save_scene_deadline.py diff --git a/openpype/hosts/houdini/plugins/publish/save_scene.py b/openpype/hosts/houdini/plugins/publish/save_scene.py index 0f9e547dee..fe5962fbd3 100644 --- a/openpype/hosts/houdini/plugins/publish/save_scene.py +++ b/openpype/hosts/houdini/plugins/publish/save_scene.py @@ -2,26 +2,14 @@ import pyblish.api import avalon.api -class SaveCurrentScene(pyblish.api.InstancePlugin): +class SaveCurrentScene(pyblish.api.ContextPlugin): """Save current scene""" label = "Save current file" order = pyblish.api.ExtractorOrder - 0.49 hosts = ["houdini"] - families = ["usdrender", - "redshift_rop"] - targets = ["local"] - def process(self, instance): - - # This should be a ContextPlugin, but this is a workaround - # for a bug in pyblish to run once for a family: issue #250 - context = instance.context - key = "__hasRun{}".format(self.__class__.__name__) - if context.data.get(key, False): - return - else: - context.data[key] = True + def process(self, context): # Filename must not have changed since collecting host = avalon.api.registered_host() diff --git a/openpype/hosts/houdini/plugins/publish/save_scene_deadline.py b/openpype/hosts/houdini/plugins/publish/save_scene_deadline.py deleted file mode 100644 index a04f6887ff..0000000000 --- a/openpype/hosts/houdini/plugins/publish/save_scene_deadline.py +++ /dev/null @@ -1,23 +0,0 @@ -import pyblish.api - - -class SaveCurrentSceneDeadline(pyblish.api.ContextPlugin): - """Save current scene""" - - label = "Save current file" - order = pyblish.api.ExtractorOrder - 0.49 - hosts = ["houdini"] - targets = ["deadline"] - - def process(self, context): - import hou - - assert ( - context.data["currentFile"] == hou.hipFile.path() - ), "Collected filename from current scene name." - - if hou.hipFile.hasUnsavedChanges(): - self.log.info("Saving current file..") - hou.hipFile.save(save_to_recent_files=True) - else: - self.log.debug("No unsaved changes, skipping file save..") From 1382811bc75e6302f8398af3b4d00a73685a283c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 21 Feb 2022 22:09:51 +0100 Subject: [PATCH 60/68] Remove duplicate ValidateOutputNode code --- .../plugins/publish/validate_output_node.py | 77 ------------------- 1 file changed, 77 deletions(-) delete mode 100644 openpype/hosts/houdini/plugins/publish/validate_output_node.py diff --git a/openpype/hosts/houdini/plugins/publish/validate_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_output_node.py deleted file mode 100644 index 0b60ab5c48..0000000000 --- a/openpype/hosts/houdini/plugins/publish/validate_output_node.py +++ /dev/null @@ -1,77 +0,0 @@ -import pyblish.api - - -class ValidateOutputNode(pyblish.api.InstancePlugin): - """Validate the instance SOP Output Node. - - This will ensure: - - The SOP Path is set. - - The SOP Path refers to an existing object. - - The SOP Path node is a SOP node. - - The SOP Path node has at least one input connection (has an input) - - The SOP Path has geometry data. - - """ - - order = pyblish.api.ValidatorOrder - families = ["pointcache", "vdbcache"] - hosts = ["houdini"] - label = "Validate Output Node" - - def process(self, instance): - - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError( - "Output node(s) `%s` are incorrect. " - "See plug-in log for details." % invalid - ) - - @classmethod - def get_invalid(cls, instance): - - import hou - - output_node = instance.data["output_node"] - - if output_node is None: - node = instance[0] - cls.log.error( - "SOP Output node in '%s' does not exist. " - "Ensure a valid SOP output path is set." % node.path() - ) - - return [node.path()] - - # Output node must be a Sop node. - if not isinstance(output_node, hou.SopNode): - cls.log.error( - "Output node %s is not a SOP node. " - "SOP Path must point to a SOP node, " - "instead found category type: %s" - % (output_node.path(), output_node.type().category().name()) - ) - return [output_node.path()] - - # For the sake of completeness also assert the category type - # is Sop to avoid potential edge case scenarios even though - # the isinstance check above should be stricter than this category - assert output_node.type().category().name() == "Sop", ( - "Output node %s is not of category Sop. This is a bug.." - % output_node.path() - ) - - # Check if output node has incoming connections - if not output_node.inputConnections(): - cls.log.error( - "Output node `%s` has no incoming connections" - % output_node.path() - ) - return [output_node.path()] - - # Ensure the output node has at least Geometry data - if not output_node.geometry(): - cls.log.error( - "Output node `%s` has no geometry data." % output_node.path() - ) - return [output_node.path()] From df11c4af655825df2f398062ae47b88640f09529 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Feb 2022 03:59:02 +0100 Subject: [PATCH 61/68] Improve labels with errors + fix end of validation stage --- openpype/tools/pyblish_pype/control.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/tools/pyblish_pype/control.py b/openpype/tools/pyblish_pype/control.py index 0d33aa10f8..455a338499 100644 --- a/openpype/tools/pyblish_pype/control.py +++ b/openpype/tools/pyblish_pype/control.py @@ -297,7 +297,9 @@ class Controller(QtCore.QObject): def on_published(self): if self.is_running: self.is_running = False - self._current_state = "Pulished" + self._current_state = ( + "Published" if not self.errored else "Published, with errors" + ) self.was_finished.emit() self._main_thread_processor.stop() @@ -375,7 +377,10 @@ class Controller(QtCore.QObject): if self.collect_state == 0: self.collect_state = 1 - self._current_state = "Collected" + self._current_state = ( + "Ready" if not self.errored else + "Collected, with errors" + ) self.switch_toggleability.emit(True) self.passed_group.emit(current_group_order) yield IterationBreak("Collected") @@ -383,6 +388,11 @@ class Controller(QtCore.QObject): else: self.passed_group.emit(current_group_order) if self.errored: + self._current_state = ( + "Stopped, due to errors" if not + self.processing["stop_on_validation"] else + "Validated, with errors" + ) yield IterationBreak("Last group errored") if self.collect_state == 1: @@ -392,7 +402,10 @@ class Controller(QtCore.QObject): if not self.validated and plugin.order > self.validators_order: self.validated = True if self.processing["stop_on_validation"]: - self._current_state = "Validated" + self._current_state = ( + "Validated" if not self.errored else + "Validated, with errors" + ) yield IterationBreak("Validated") # Stop if was stopped From 449bbe022c733b2e202d765e31767feae3dcfb1a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Feb 2022 11:15:25 +0100 Subject: [PATCH 62/68] fix flame version string in default settings --- openpype/settings/defaults/system_settings/applications.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index 912e2d9924..2f99200a88 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -135,7 +135,7 @@ "OPENPYPE_WIRETAP_TOOLS": "/opt/Autodesk/wiretap/tools/2021" } }, - "2021.1": { + "2021_1": { "use_python_2": true, "executables": { "windows": [], @@ -159,7 +159,7 @@ }, "__dynamic_keys_labels__": { "2021": "2021", - "2021.1": "2021.1" + "2021_1": "2021.1" } } }, From f576be7604b725b0e9db695056384cd56106c61d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Feb 2022 11:45:47 +0100 Subject: [PATCH 63/68] Fix parenting of save prompt QMessageBox - Setting the windowFlags without the original messagebox.windowFlags() was the culprit as to why the messagebox previously wouldn't show when parented. Likely because then it's missing the Dialog window flag and thus would try to embed itself into the parent UI, which you then cannot exec() (cherry picked from commit 290e2b601d0e20f4aaba356d4f053bf733de406b) --- openpype/tools/workfiles/app.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index 4b5bf07b47..27ffb768a3 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -544,10 +544,6 @@ class FilesWidget(QtWidgets.QWidget): # file on a refresh of the files model. self.auto_select_latest_modified = True - # Avoid crash in Blender and store the message box - # (setting parent doesn't work as it hides the message box) - self._messagebox = None - files_view = FilesView(self) # Create the Files model @@ -726,9 +722,9 @@ class FilesWidget(QtWidgets.QWidget): self.file_opened.emit() def save_changes_prompt(self): - self._messagebox = messagebox = QtWidgets.QMessageBox() - - messagebox.setWindowFlags(QtCore.Qt.FramelessWindowHint) + messagebox = QtWidgets.QMessageBox(parent=self) + messagebox.setWindowFlags(messagebox.windowFlags() | + QtCore.Qt.FramelessWindowHint) messagebox.setIcon(messagebox.Warning) messagebox.setWindowTitle("Unsaved Changes!") messagebox.setText( @@ -739,10 +735,6 @@ class FilesWidget(QtWidgets.QWidget): messagebox.Yes | messagebox.No | messagebox.Cancel ) - # Parenting the QMessageBox to the Widget seems to crash - # so we skip parenting and explicitly apply the stylesheet. - messagebox.setStyle(self.style()) - result = messagebox.exec_() if result == messagebox.Yes: return True From 24e22c1c598ef5eac849d68a80a8df179e182951 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Feb 2022 11:52:53 +0100 Subject: [PATCH 64/68] Refactor breadcrumbs_widget -> breadcrumbs_bar Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/tools/settings/settings/categories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 059a8b9bf8..14e25a54d8 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -383,7 +383,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget): def change_path(self, path): """Change path and go to widget.""" - self.breadcrumbs_widget.change_path(path) + self.breadcrumbs_bar.change_path(path) def set_path(self, path): """Called from clicked widget.""" From efee466761875723fd33aaa99d196b322c31312c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Feb 2022 15:05:04 +0100 Subject: [PATCH 65/68] moved sync server module one hierarchy level higher --- openpype/modules/base.py | 1 + .../{default_modules => }/sync_server/README.md | 0 .../{default_modules => }/sync_server/__init__.py | 0 .../sync_server/providers/__init__.py | 0 .../sync_server/providers/abstract_provider.py | 0 .../sync_server/providers/dropbox.py | 0 .../sync_server/providers/gdrive.py | 0 .../sync_server/providers/lib.py | 0 .../sync_server/providers/local_drive.py | 0 .../sync_server/providers/resources/dropbox.png | Bin .../sync_server/providers/resources/folder.png | Bin .../sync_server/providers/resources/gdrive.png | Bin .../sync_server/providers/resources/local_drive.png | Bin .../sync_server/providers/resources/sftp.png | Bin .../sync_server/providers/resources/studio.png | Bin .../sync_server/providers/sftp.py | 0 .../sync_server/resources/paused.png | Bin .../sync_server/resources/refresh.png | Bin .../sync_server/resources/synced.png | Bin .../sync_server/sync_server.py | 0 .../sync_server/sync_server_module.py | 0 .../{default_modules => }/sync_server/tray/app.py | 0 .../sync_server/tray/delegates.py | 0 .../{default_modules => }/sync_server/tray/lib.py | 0 .../sync_server/tray/models.py | 0 .../sync_server/tray/widgets.py | 0 .../{default_modules => }/sync_server/utils.py | 0 27 files changed, 1 insertion(+) rename openpype/modules/{default_modules => }/sync_server/README.md (100%) rename openpype/modules/{default_modules => }/sync_server/__init__.py (100%) rename openpype/modules/{default_modules => }/sync_server/providers/__init__.py (100%) rename openpype/modules/{default_modules => }/sync_server/providers/abstract_provider.py (100%) rename openpype/modules/{default_modules => }/sync_server/providers/dropbox.py (100%) rename openpype/modules/{default_modules => }/sync_server/providers/gdrive.py (100%) rename openpype/modules/{default_modules => }/sync_server/providers/lib.py (100%) rename openpype/modules/{default_modules => }/sync_server/providers/local_drive.py (100%) rename openpype/modules/{default_modules => }/sync_server/providers/resources/dropbox.png (100%) rename openpype/modules/{default_modules => }/sync_server/providers/resources/folder.png (100%) rename openpype/modules/{default_modules => }/sync_server/providers/resources/gdrive.png (100%) rename openpype/modules/{default_modules => }/sync_server/providers/resources/local_drive.png (100%) rename openpype/modules/{default_modules => }/sync_server/providers/resources/sftp.png (100%) rename openpype/modules/{default_modules => }/sync_server/providers/resources/studio.png (100%) rename openpype/modules/{default_modules => }/sync_server/providers/sftp.py (100%) rename openpype/modules/{default_modules => }/sync_server/resources/paused.png (100%) rename openpype/modules/{default_modules => }/sync_server/resources/refresh.png (100%) rename openpype/modules/{default_modules => }/sync_server/resources/synced.png (100%) rename openpype/modules/{default_modules => }/sync_server/sync_server.py (100%) rename openpype/modules/{default_modules => }/sync_server/sync_server_module.py (100%) rename openpype/modules/{default_modules => }/sync_server/tray/app.py (100%) rename openpype/modules/{default_modules => }/sync_server/tray/delegates.py (100%) rename openpype/modules/{default_modules => }/sync_server/tray/lib.py (100%) rename openpype/modules/{default_modules => }/sync_server/tray/models.py (100%) rename openpype/modules/{default_modules => }/sync_server/tray/widgets.py (100%) rename openpype/modules/{default_modules => }/sync_server/utils.py (100%) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index d566692439..437f5efdbc 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -43,6 +43,7 @@ DEFAULT_OPENPYPE_MODULES = ( "standalonepublish_action", "job_queue", "timers_manager", + "sync_server", ) diff --git a/openpype/modules/default_modules/sync_server/README.md b/openpype/modules/sync_server/README.md similarity index 100% rename from openpype/modules/default_modules/sync_server/README.md rename to openpype/modules/sync_server/README.md diff --git a/openpype/modules/default_modules/sync_server/__init__.py b/openpype/modules/sync_server/__init__.py similarity index 100% rename from openpype/modules/default_modules/sync_server/__init__.py rename to openpype/modules/sync_server/__init__.py diff --git a/openpype/modules/default_modules/sync_server/providers/__init__.py b/openpype/modules/sync_server/providers/__init__.py similarity index 100% rename from openpype/modules/default_modules/sync_server/providers/__init__.py rename to openpype/modules/sync_server/providers/__init__.py diff --git a/openpype/modules/default_modules/sync_server/providers/abstract_provider.py b/openpype/modules/sync_server/providers/abstract_provider.py similarity index 100% rename from openpype/modules/default_modules/sync_server/providers/abstract_provider.py rename to openpype/modules/sync_server/providers/abstract_provider.py diff --git a/openpype/modules/default_modules/sync_server/providers/dropbox.py b/openpype/modules/sync_server/providers/dropbox.py similarity index 100% rename from openpype/modules/default_modules/sync_server/providers/dropbox.py rename to openpype/modules/sync_server/providers/dropbox.py diff --git a/openpype/modules/default_modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py similarity index 100% rename from openpype/modules/default_modules/sync_server/providers/gdrive.py rename to openpype/modules/sync_server/providers/gdrive.py diff --git a/openpype/modules/default_modules/sync_server/providers/lib.py b/openpype/modules/sync_server/providers/lib.py similarity index 100% rename from openpype/modules/default_modules/sync_server/providers/lib.py rename to openpype/modules/sync_server/providers/lib.py diff --git a/openpype/modules/default_modules/sync_server/providers/local_drive.py b/openpype/modules/sync_server/providers/local_drive.py similarity index 100% rename from openpype/modules/default_modules/sync_server/providers/local_drive.py rename to openpype/modules/sync_server/providers/local_drive.py diff --git a/openpype/modules/default_modules/sync_server/providers/resources/dropbox.png b/openpype/modules/sync_server/providers/resources/dropbox.png similarity index 100% rename from openpype/modules/default_modules/sync_server/providers/resources/dropbox.png rename to openpype/modules/sync_server/providers/resources/dropbox.png diff --git a/openpype/modules/default_modules/sync_server/providers/resources/folder.png b/openpype/modules/sync_server/providers/resources/folder.png similarity index 100% rename from openpype/modules/default_modules/sync_server/providers/resources/folder.png rename to openpype/modules/sync_server/providers/resources/folder.png diff --git a/openpype/modules/default_modules/sync_server/providers/resources/gdrive.png b/openpype/modules/sync_server/providers/resources/gdrive.png similarity index 100% rename from openpype/modules/default_modules/sync_server/providers/resources/gdrive.png rename to openpype/modules/sync_server/providers/resources/gdrive.png diff --git a/openpype/modules/default_modules/sync_server/providers/resources/local_drive.png b/openpype/modules/sync_server/providers/resources/local_drive.png similarity index 100% rename from openpype/modules/default_modules/sync_server/providers/resources/local_drive.png rename to openpype/modules/sync_server/providers/resources/local_drive.png diff --git a/openpype/modules/default_modules/sync_server/providers/resources/sftp.png b/openpype/modules/sync_server/providers/resources/sftp.png similarity index 100% rename from openpype/modules/default_modules/sync_server/providers/resources/sftp.png rename to openpype/modules/sync_server/providers/resources/sftp.png diff --git a/openpype/modules/default_modules/sync_server/providers/resources/studio.png b/openpype/modules/sync_server/providers/resources/studio.png similarity index 100% rename from openpype/modules/default_modules/sync_server/providers/resources/studio.png rename to openpype/modules/sync_server/providers/resources/studio.png diff --git a/openpype/modules/default_modules/sync_server/providers/sftp.py b/openpype/modules/sync_server/providers/sftp.py similarity index 100% rename from openpype/modules/default_modules/sync_server/providers/sftp.py rename to openpype/modules/sync_server/providers/sftp.py diff --git a/openpype/modules/default_modules/sync_server/resources/paused.png b/openpype/modules/sync_server/resources/paused.png similarity index 100% rename from openpype/modules/default_modules/sync_server/resources/paused.png rename to openpype/modules/sync_server/resources/paused.png diff --git a/openpype/modules/default_modules/sync_server/resources/refresh.png b/openpype/modules/sync_server/resources/refresh.png similarity index 100% rename from openpype/modules/default_modules/sync_server/resources/refresh.png rename to openpype/modules/sync_server/resources/refresh.png diff --git a/openpype/modules/default_modules/sync_server/resources/synced.png b/openpype/modules/sync_server/resources/synced.png similarity index 100% rename from openpype/modules/default_modules/sync_server/resources/synced.png rename to openpype/modules/sync_server/resources/synced.png diff --git a/openpype/modules/default_modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py similarity index 100% rename from openpype/modules/default_modules/sync_server/sync_server.py rename to openpype/modules/sync_server/sync_server.py diff --git a/openpype/modules/default_modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py similarity index 100% rename from openpype/modules/default_modules/sync_server/sync_server_module.py rename to openpype/modules/sync_server/sync_server_module.py diff --git a/openpype/modules/default_modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py similarity index 100% rename from openpype/modules/default_modules/sync_server/tray/app.py rename to openpype/modules/sync_server/tray/app.py diff --git a/openpype/modules/default_modules/sync_server/tray/delegates.py b/openpype/modules/sync_server/tray/delegates.py similarity index 100% rename from openpype/modules/default_modules/sync_server/tray/delegates.py rename to openpype/modules/sync_server/tray/delegates.py diff --git a/openpype/modules/default_modules/sync_server/tray/lib.py b/openpype/modules/sync_server/tray/lib.py similarity index 100% rename from openpype/modules/default_modules/sync_server/tray/lib.py rename to openpype/modules/sync_server/tray/lib.py diff --git a/openpype/modules/default_modules/sync_server/tray/models.py b/openpype/modules/sync_server/tray/models.py similarity index 100% rename from openpype/modules/default_modules/sync_server/tray/models.py rename to openpype/modules/sync_server/tray/models.py diff --git a/openpype/modules/default_modules/sync_server/tray/widgets.py b/openpype/modules/sync_server/tray/widgets.py similarity index 100% rename from openpype/modules/default_modules/sync_server/tray/widgets.py rename to openpype/modules/sync_server/tray/widgets.py diff --git a/openpype/modules/default_modules/sync_server/utils.py b/openpype/modules/sync_server/utils.py similarity index 100% rename from openpype/modules/default_modules/sync_server/utils.py rename to openpype/modules/sync_server/utils.py From bb105209e8bc34a63e2e04bc8de42a603f3368d4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Feb 2022 15:28:05 +0100 Subject: [PATCH 66/68] Revert storing of messagebox - This is NOT done because the original crash was reproducible - but just out of pure legacy reasons for if the error might still occur. It would be worth looking into whether the crash can still be reproduced in recent Blender versions without this logic. --- openpype/tools/workfiles/app.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index 27ffb768a3..3a772a038c 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -544,6 +544,10 @@ class FilesWidget(QtWidgets.QWidget): # file on a refresh of the files model. self.auto_select_latest_modified = True + # Avoid crash in Blender and store the message box + # (setting parent doesn't work as it hides the message box) + self._messagebox = None + files_view = FilesView(self) # Create the Files model @@ -722,7 +726,7 @@ class FilesWidget(QtWidgets.QWidget): self.file_opened.emit() def save_changes_prompt(self): - messagebox = QtWidgets.QMessageBox(parent=self) + self._messagebox = messagebox = QtWidgets.QMessageBox(parent=self) messagebox.setWindowFlags(messagebox.windowFlags() | QtCore.Qt.FramelessWindowHint) messagebox.setIcon(messagebox.Warning) From 48242818e36aa41fd2637ad8543d4986b4e6a5e2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Feb 2022 16:36:36 +0100 Subject: [PATCH 67/68] fix attr name typo --- openpype/tools/launcher/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py index 7437dc1453..30e6531843 100644 --- a/openpype/tools/launcher/widgets.py +++ b/openpype/tools/launcher/widgets.py @@ -257,7 +257,7 @@ class ActionBar(QtWidgets.QWidget): def _start_animation(self, index): # Offset refresh timout - self.launcher_model.start_refresh_timer() + self._launcher_model.start_refresh_timer() action_id = index.data(ACTION_ID_ROLE) item = self.model.items_by_id.get(action_id) if item: @@ -325,7 +325,7 @@ class ActionBar(QtWidgets.QWidget): return # Offset refresh timout - self.launcher_model.start_refresh_timer() + self._launcher_model.start_refresh_timer() actions = index.data(ACTION_ROLE) From c6df75827508d4e64f48f79e6669d5f19ed78089 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 22 Feb 2022 17:18:04 +0100 Subject: [PATCH 68/68] fix filter bug --- openpype/tools/launcher/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py index c9f222a7d8..effa283318 100644 --- a/openpype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -736,7 +736,10 @@ class AssetRecursiveSortFilterModel(QtCore.QSortFilterProxyModel): valid = True if self._name_filter: name = model.data(source_index, ASSET_NAME_ROLE) - if not re.search(self._name_filter, name, re.IGNORECASE): + if ( + name is None + or not re.search(self._name_filter, name, re.IGNORECASE) + ): valid = False if valid and self._task_types_filter: