diff --git a/openpype/hosts/tvpaint/api/plugin.py b/openpype/hosts/tvpaint/api/plugin.py index 3eb9a5be31..e148e44a27 100644 --- a/openpype/hosts/tvpaint/api/plugin.py +++ b/openpype/hosts/tvpaint/api/plugin.py @@ -3,4 +3,17 @@ from avalon.tvpaint import pipeline class Creator(PypeCreatorMixin, pipeline.Creator): - pass + @classmethod + def get_dynamic_data(cls, *args, **kwargs): + dynamic_data = super(Creator, cls).get_dynamic_data(*args, **kwargs) + + # Change asset and name by current workfile context + workfile_context = pipeline.get_current_workfile_context() + asset_name = workfile_context.get("asset") + task_name = workfile_context.get("task") + if "asset" not in dynamic_data and asset_name: + dynamic_data["asset"] = asset_name + + if "task" not in dynamic_data and task_name: + dynamic_data["task"] = task_name + return dynamic_data diff --git a/openpype/modules/default_modules/ftrack/event_handlers_user/action_create_cust_attrs.py b/openpype/modules/default_modules/ftrack/event_handlers_user/action_create_cust_attrs.py index 3869d8ad08..0bd243ab4c 100644 --- a/openpype/modules/default_modules/ftrack/event_handlers_user/action_create_cust_attrs.py +++ b/openpype/modules/default_modules/ftrack/event_handlers_user/action_create_cust_attrs.py @@ -10,6 +10,7 @@ from openpype_modules.ftrack.lib import ( CUST_ATTR_GROUP, CUST_ATTR_TOOLS, CUST_ATTR_APPLICATIONS, + CUST_ATTR_INTENT, default_custom_attributes_definition, app_definitions_from_app_manager, @@ -431,7 +432,7 @@ class CustomAttributes(BaseAction): intent_custom_attr_data = { "label": "Intent", - "key": "intent", + "key": CUST_ATTR_INTENT, "type": "enumerator", "entity_type": "assetversion", "group": CUST_ATTR_GROUP, diff --git a/openpype/modules/default_modules/ftrack/ftrack_module.py b/openpype/modules/default_modules/ftrack/ftrack_module.py index c73f9b100d..9cbf979239 100644 --- a/openpype/modules/default_modules/ftrack/ftrack_module.py +++ b/openpype/modules/default_modules/ftrack/ftrack_module.py @@ -230,7 +230,13 @@ class FtrackModule( return import ftrack_api - from openpype_modules.ftrack.lib import get_openpype_attr + from openpype_modules.ftrack.lib import ( + get_openpype_attr, + default_custom_attributes_definition, + CUST_ATTR_TOOLS, + CUST_ATTR_APPLICATIONS, + CUST_ATTR_INTENT + ) try: session = self.create_ftrack_session() @@ -255,6 +261,15 @@ class FtrackModule( project_id = project_entity["id"] + ca_defs = default_custom_attributes_definition() + hierarchical_attrs = ca_defs.get("is_hierarchical") or {} + project_attrs = ca_defs.get("show") or {} + ca_keys = ( + set(hierarchical_attrs.keys()) + | set(project_attrs.keys()) + | {CUST_ATTR_TOOLS, CUST_ATTR_APPLICATIONS, CUST_ATTR_INTENT} + ) + cust_attr, hier_attr = get_openpype_attr(session) cust_attr_by_key = {attr["key"]: attr for attr in cust_attr} hier_attrs_by_key = {attr["key"]: attr for attr in hier_attr} @@ -262,6 +277,9 @@ class FtrackModule( failed = {} missing = {} for key, value in attributes_changes.items(): + if key not in ca_keys: + continue + configuration = hier_attrs_by_key.get(key) if not configuration: configuration = cust_attr_by_key.get(key) diff --git a/openpype/modules/default_modules/ftrack/lib/__init__.py b/openpype/modules/default_modules/ftrack/lib/__init__.py index 433a1f7881..80b4db9dd6 100644 --- a/openpype/modules/default_modules/ftrack/lib/__init__.py +++ b/openpype/modules/default_modules/ftrack/lib/__init__.py @@ -3,7 +3,8 @@ from .constants import ( CUST_ATTR_AUTO_SYNC, CUST_ATTR_GROUP, CUST_ATTR_TOOLS, - CUST_ATTR_APPLICATIONS + CUST_ATTR_APPLICATIONS, + CUST_ATTR_INTENT ) from .settings import ( get_ftrack_event_mongo_info diff --git a/openpype/modules/default_modules/ftrack/lib/constants.py b/openpype/modules/default_modules/ftrack/lib/constants.py index 73d5112e6d..e6e2013d2b 100644 --- a/openpype/modules/default_modules/ftrack/lib/constants.py +++ b/openpype/modules/default_modules/ftrack/lib/constants.py @@ -10,3 +10,5 @@ CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" CUST_ATTR_APPLICATIONS = "applications" # Environment tools custom attribute CUST_ATTR_TOOLS = "tools_env" +# Intent custom attribute name +CUST_ATTR_INTENT = "intent" diff --git a/openpype/tools/settings/settings/constants.py b/openpype/tools/settings/settings/constants.py new file mode 100644 index 0000000000..5c20bf1afe --- /dev/null +++ b/openpype/tools/settings/settings/constants.py @@ -0,0 +1,16 @@ +from Qt import QtCore + + +DEFAULT_PROJECT_LABEL = "< Default >" +PROJECT_NAME_ROLE = QtCore.Qt.UserRole + 1 +PROJECT_IS_ACTIVE_ROLE = QtCore.Qt.UserRole + 2 +PROJECT_IS_SELECTED_ROLE = QtCore.Qt.UserRole + 3 + + +__all__ = ( + "DEFAULT_PROJECT_LABEL", + + "PROJECT_NAME_ROLE", + "PROJECT_IS_ACTIVE_ROLE", + "PROJECT_IS_SELECTED_ROLE" +) diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py index a461f3e675..710884e9e5 100644 --- a/openpype/tools/settings/settings/widgets.py +++ b/openpype/tools/settings/settings/widgets.py @@ -7,6 +7,12 @@ from avalon.mongodb import ( ) from openpype.settings.lib import get_system_settings +from .constants import ( + DEFAULT_PROJECT_LABEL, + PROJECT_NAME_ROLE, + PROJECT_IS_ACTIVE_ROLE, + PROJECT_IS_SELECTED_ROLE +) class SettingsLineEdit(QtWidgets.QLineEdit): @@ -602,10 +608,63 @@ class NiceCheckbox(QtWidgets.QFrame): return super(NiceCheckbox, self).mouseReleaseEvent(event) -class ProjectListModel(QtGui.QStandardItemModel): - sort_role = QtCore.Qt.UserRole + 10 - filter_role = QtCore.Qt.UserRole + 11 - selected_role = QtCore.Qt.UserRole + 12 +class ProjectModel(QtGui.QStandardItemModel): + def __init__(self, only_active, *args, **kwargs): + super(ProjectModel, self).__init__(*args, **kwargs) + + self.dbcon = None + + self._only_active = only_active + self._default_item = None + self._items_by_name = {} + + def set_dbcon(self, dbcon): + self.dbcon = dbcon + + def refresh(self): + new_items = [] + if self._default_item is None: + item = QtGui.QStandardItem(DEFAULT_PROJECT_LABEL) + item.setData(None, PROJECT_NAME_ROLE) + item.setData(True, PROJECT_IS_ACTIVE_ROLE) + item.setData(False, PROJECT_IS_SELECTED_ROLE) + new_items.append(item) + self._default_item = item + + project_names = set() + if self.dbcon is not None: + for project_doc in self.dbcon.projects( + projection={"name": 1, "data.active": 1}, + only_active=self._only_active + ): + project_name = project_doc["name"] + project_names.add(project_name) + if project_name in self._items_by_name: + item = self._items_by_name[project_name] + else: + item = QtGui.QStandardItem(project_name) + + self._items_by_name[project_name] = item + new_items.append(item) + + is_active = project_doc.get("data", {}).get("active", True) + item.setData(project_name, PROJECT_NAME_ROLE) + item.setData(is_active, PROJECT_IS_ACTIVE_ROLE) + item.setData(False, PROJECT_IS_SELECTED_ROLE) + + if not is_active: + font = item.font() + font.setItalic(True) + item.setFont(font) + + root_item = self.invisibleRootItem() + for project_name in tuple(self._items_by_name.keys()): + if project_name not in project_names: + item = self._items_by_name.pop(project_name) + root_item.removeRow(item.row()) + + if new_items: + root_item.appendRows(new_items) class ProjectListView(QtWidgets.QListView): @@ -618,19 +677,36 @@ class ProjectListView(QtWidgets.QListView): super(ProjectListView, self).mouseReleaseEvent(event) -class ProjectListSortFilterProxy(QtCore.QSortFilterProxyModel): - +class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): def __init__(self, *args, **kwargs): - super(ProjectListSortFilterProxy, self).__init__(*args, **kwargs) + super(ProjectSortFilterProxy, self).__init__(*args, **kwargs) self._enable_filter = True + def lessThan(self, left_index, right_index): + if left_index.data(PROJECT_NAME_ROLE) is None: + return True + + if right_index.data(PROJECT_NAME_ROLE) is None: + return False + + left_is_active = left_index.data(PROJECT_IS_ACTIVE_ROLE) + right_is_active = right_index.data(PROJECT_IS_ACTIVE_ROLE) + if right_is_active == left_is_active: + return super(ProjectSortFilterProxy, self).lessThan( + left_index, right_index + ) + + if left_is_active: + return True + return False + def filterAcceptsRow(self, source_row, source_parent): if not self._enable_filter: return True index = self.sourceModel().index(source_row, 0, source_parent) is_active = bool(index.data(self.filterRole())) - is_selected = bool(index.data(ProjectListModel.selected_role)) + is_selected = bool(index.data(PROJECT_IS_SELECTED_ROLE)) return is_active or is_selected @@ -643,7 +719,6 @@ class ProjectListSortFilterProxy(QtCore.QSortFilterProxyModel): class ProjectListWidget(QtWidgets.QWidget): - default = "< Default >" project_changed = QtCore.Signal() def __init__(self, parent, only_active=False): @@ -657,13 +732,10 @@ class ProjectListWidget(QtWidgets.QWidget): label_widget = QtWidgets.QLabel("Projects") project_list = ProjectListView(self) - project_model = ProjectListModel() - project_proxy = ProjectListSortFilterProxy() - - project_proxy.setFilterRole(ProjectListModel.filter_role) - project_proxy.setSortRole(ProjectListModel.sort_role) - project_proxy.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) + project_model = ProjectModel(only_active) + project_proxy = ProjectSortFilterProxy() + project_proxy.setFilterRole(PROJECT_IS_ACTIVE_ROLE) project_proxy.setSourceModel(project_model) project_list.setModel(project_proxy) @@ -693,13 +765,14 @@ class ProjectListWidget(QtWidgets.QWidget): project_list.left_mouse_released_at.connect(self.on_item_clicked) + self._default_project_item = None + self.project_list = project_list self.project_proxy = project_proxy self.project_model = project_model self.inactive_chk = inactive_chk self.dbcon = None - self._only_active = only_active def on_item_clicked(self, new_index): new_project_name = new_index.data(QtCore.Qt.DisplayRole) @@ -746,12 +819,12 @@ class ProjectListWidget(QtWidgets.QWidget): return not self._parent.entity.has_unsaved_changes def project_name(self): - if self.current_project == self.default: + if self.current_project == DEFAULT_PROJECT_LABEL: return None return self.current_project def select_default_project(self): - self.select_project(self.default) + self.select_project(DEFAULT_PROJECT_LABEL) def select_project(self, project_name): model = self.project_model @@ -759,10 +832,10 @@ class ProjectListWidget(QtWidgets.QWidget): found_items = model.findItems(project_name) if not found_items: - found_items = model.findItems(self.default) + found_items = model.findItems(DEFAULT_PROJECT_LABEL) index = model.indexFromItem(found_items[0]) - model.setData(index, True, ProjectListModel.selected_role) + model.setData(index, True, PROJECT_IS_SELECTED_ROLE) index = proxy.mapFromSource(index) @@ -777,9 +850,6 @@ class ProjectListWidget(QtWidgets.QWidget): selected_project = index.data(QtCore.Qt.DisplayRole) break - model = self.project_model - model.clear() - mongo_url = os.environ["OPENPYPE_MONGO"] # Force uninstall of whole avalon connection if url does not match @@ -797,35 +867,8 @@ class ProjectListWidget(QtWidgets.QWidget): self.dbcon = None self.current_project = None - items = [(self.default, True)] - - if self.dbcon: - - for doc in self.dbcon.projects( - projection={"name": 1, "data.active": 1}, - only_active=self._only_active - ): - items.append( - (doc["name"], doc.get("data", {}).get("active", True)) - ) - - for project_name, is_active in items: - - row = QtGui.QStandardItem(project_name) - row.setData(is_active, ProjectListModel.filter_role) - row.setData(False, ProjectListModel.selected_role) - - if is_active: - row.setData(project_name, ProjectListModel.sort_role) - - else: - row.setData("~" + project_name, ProjectListModel.sort_role) - - font = row.font() - font.setItalic(True) - row.setFont(font) - - model.appendRow(row) + self.project_model.set_dbcon(self.dbcon) + self.project_model.refresh() self.project_proxy.sort(0)