Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Autodesk, Inc.

from types import ModuleType

import sgtk


Expand All @@ -21,6 +23,7 @@ def init_app(self):
"""Called as the application is being initialized."""

tk_multi_breakdown2 = self.import_module("tk_multi_breakdown2")
self._flowam = tk_multi_breakdown2.flowam

# Store a reference to manager class to expose its functionality at the application level.
self._manager_class = tk_multi_breakdown2.BreakdownManager
Expand Down Expand Up @@ -141,3 +144,15 @@ def _on_dialog_close(self, dialog):
elif dialog == self._current_panel:
self.log_debug("Current panel has been closed, clearing reference.")
self._current_panel = None

@property
def flowam(self) -> ModuleType:
"""
Access to the FlowAM integration module for this app. This module provides
drop-in replacements for the standard Shotgun-based Scene Breakdown models and actions,
backed by Flow Asset Management (FlowAM).

:returns: The FlowAM integration module for this app
:rtype: :mod:`tk_multi_breakdown2.flowam`
"""
return self._flowam
1 change: 1 addition & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ jobs:
additional_repositories:
- name: tk-framework-qtwidgets
- name: tk-framework-shotgunutils
tk_core_ref: ticket/sg-43461/migrate-host-base
3 changes: 3 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ignore:
# flowam and other files not covered by unit tests
- "**python/tk_multi_breakdown2/flowam/*"
2 changes: 0 additions & 2 deletions hooks/tk-mari_scene_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Autodesk, Inc.

import os

import sgtk
from sgtk import TankError

Expand Down
8 changes: 6 additions & 2 deletions hooks/tk-maya_scene_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class BreakdownSceneOperations(HookBaseClass):
This implementation handles detection of maya references and file texture nodes.
"""

__callback_ids = []
Comment thread
chenm1adsk marked this conversation as resolved.

def scan_scene(self):
"""
The scan scene method is executed once at startup and its purpose is
Expand Down Expand Up @@ -104,7 +106,6 @@ def update(self, item):
self.logger.debug(
"File Texture %s: Updating to version %s" % (node_name, path)
)
file_name = cmds.getAttr("%s.fileTextureName" % node_name)
cmds.setAttr("%s.fileTextureName" % node_name, path, type="string")

def register_scene_change_callback(self, scene_change_callback):
Expand Down Expand Up @@ -151,4 +152,7 @@ def unregister_scene_change_callback(self):
"""Unregister the scene change callbacks by disconnecting any signals."""

for callback_id in self.__callback_ids:
OpenMaya.MSceneMessage.removeCallback(callback_id)
try:
OpenMaya.MSceneMessage.removeCallback(callback_id)
except RuntimeError:
pass
2 changes: 1 addition & 1 deletion info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ configuration:
create and execute actions.
default_value: {}

# The Flow Production Tracking fields that this app needs in order to operate correctly
# The Flow Production Tracking fields this app needs in order to operate correctly
requires_shotgun_fields:
# linked_projects.Asset is required for references in multiple Flow Production Tracking projects

Expand Down
1 change: 1 addition & 0 deletions python/tk_multi_breakdown2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Autodesk, Inc.

from . import flowam # noqa: F401
from .api import BreakdownManager

try:
Expand Down
4 changes: 3 additions & 1 deletion python/tk_multi_breakdown2/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def add_update_to_specific_version_action(file_item, model, sg_data, parent=None
:rtype: QtGui.QAction
"""

if not sg_data.get("version_number"):
if sg_data.get("version_number") is None:
return

action = UpdateToSpecificVersionAction(
Expand Down Expand Up @@ -186,6 +186,7 @@ def execute(self):
index,
[self._model.FILE_ITEM_ROLE, self._model.FILE_ITEM_SG_DATA_ROLE],
)
self._model.reload()


class UpdateToSpecificVersionAction(Action):
Expand Down Expand Up @@ -226,3 +227,4 @@ def execute(self):
index,
[self._model.FILE_ITEM_ROLE, self._model.FILE_ITEM_SG_DATA_ROLE],
)
self._model.reload()
105 changes: 95 additions & 10 deletions python/tk_multi_breakdown2/api/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Autodesk, Inc.

from typing import Any, Optional

import sgtk
from tank.errors import TankHookMethodDoesNotExistError

Expand All @@ -22,6 +24,8 @@ def __init__(self, bundle):
"""Initialize the manager."""

self._bundle = bundle
_engine = sgtk.platform.current_engine()
self._flow_host = _engine.flow_host if _engine else None

@sgtk.LogManager.log_timing
def get_scene_objects(self, execute_in_main_thread=True):
Expand Down Expand Up @@ -228,22 +232,43 @@ def get_history_published_file_filters(self):

return self._bundle.get_setting("history_published_file_filters", [])

def get_latest_published_file(self, item, data_retriever=None, extra_fields=None):
def get_latest_published_file(
self,
item: FileItem,
data_retriever: Optional[Any] = None,
extra_fields: Optional[list[str]] = None,
bg_task_manager: Optional[Any] = None,
) -> Any:
"""
Get the latest available published file according to the current item context.

:param item: :class`FileItem` object we want to get the latest published file
:type item: FileItem
:param data_retreiver: If provided, the api request will be async. The default value
:param data_retriever: If provided, the api request will be async. The default value
will execute the api request synchronously.
:type data_retriever: ShotgunDataRetriever
:param bg_task_manager: Used for async execution.
:type bg_task_manager: BackgroundTaskManager

:return: The latest published file as a Flow Production Tracking entity dictionary if the request was
synchronous, else the request background task id if the request was async.
"""

is_async = data_retriever or bg_task_manager

if not item or not item.sg_data:
return None if data_retriever else {}
return None if is_async else {}

if self._bundle.context.flow_project_id and self._flow_host:
result = self._bundle.flowam.get_latest_revision(
item=item,
bg_task_manager=bg_task_manager,
)
if not is_async:
if not isinstance(result, dict):
result = {}
item.latest_published_file = result
return result

fields = self.get_published_file_fields()
if extra_fields:
Expand All @@ -267,24 +292,38 @@ def get_latest_published_file(self, item, data_retriever=None, extra_fields=None
return result

def get_published_files_for_items(
self, items, data_retriever=None, extra_fields=None
):
self,
items: list[FileItem],
data_retriever: Optional[Any] = None,
extra_fields: Optional[list[str]] = None,
bg_task_manager: Optional[Any] = None,
) -> Any:
"""
Get all published files (history) for the given items.

:param items: the list of :class`FileItem` we want to get published files for.
:type items: List[FileItem]
:param data_retreiver: If provided, the api request will be async. The default value
:param data_retriever: If provided, the api request will be async. The default value
will execute the api request synchronously.
:type data_retriever: ShotgunDataRetriever
:param bg_task_manager: Used for async execution.
:type bg_task_manager: BackgroundTaskManager

:return: If the request is async, then the request task id is returned, else the
published file data result from the api request.
:rtype: str | dict
"""

is_async = data_retriever or bg_task_manager

if not items:
return None if data_retriever else {}
return None if is_async else {}

if self._bundle.context.flow_project_id and self._flow_host:
return self._bundle.flowam.get_assets_for_items(
items=items,
bg_task_manager=bg_task_manager,
)

fields = self.get_published_file_fields()
if extra_fields:
Expand All @@ -301,7 +340,13 @@ def get_published_files_for_items(
published_file_filters=filters,
)

def get_published_file_history(self, item, extra_fields=None, data_retriever=None):
def get_published_file_history(
self,
item: FileItem,
extra_fields: Optional[list[str]] = None,
data_retriever: Optional[Any] = None,
bg_task_manager: Optional[Any] = None,
) -> Any:
"""
Get the published history for the selected item. It will gather all the published files with the same context
than the current item (project, name, task, ...)
Expand All @@ -310,9 +355,11 @@ def get_published_file_history(self, item, extra_fields=None, data_retriever=Non
:type item: FileItem
:param extra_fields: A list of Flow Production Tracking fields to append to the Flow Production Tracking query fields.
:type extra_fields: List[str]
:param data_retreiver: If provided, the api request will be async. The default value
:param data_retriever: If provided, the api request will be async. The default value
will execute the api request synchronously.
:type data_retriever: ShotgunDataRetriever
:param bg_task_manager: Used for async execution.
:type bg_task_manager: BackgroundTaskManager

:return: If the request is async, then the request task id is returned, else the
published file history.
Expand All @@ -323,7 +370,10 @@ def get_published_file_history(self, item, extra_fields=None, data_retriever=Non
return []

result = self.get_published_files_for_items(
[item], data_retriever=data_retriever, extra_fields=extra_fields
[item],
data_retriever=data_retriever,
extra_fields=extra_fields,
bg_task_manager=bg_task_manager,
)
if result and isinstance(result, list):
item.latest_published_file = result[0]
Expand All @@ -343,6 +393,27 @@ def update_to_latest_version(self, items):
if not isinstance(items, list):
items = [items]

if self._bundle.context.flow_project_id and self._flow_host:
items_to_update = self._bundle.flowam.update_to_latest(items)

# The FlowAM method performs the DCC-side update but does not update the
# Python FileItem model data. We do that here so callers always get a
# consistent, up-to-date list of updated FileItem objects regardless of
# which code path ran. A non-list return (including None) means the hook
# chose not to filter, so attempt to update all items.
if not isinstance(items_to_update, list):
items_to_update = items

updated_items = []
for item in items_to_update:
data = item.latest_published_file
if not data or not data.get("path", {}).get("local_path", None):
continue
item.sg_data = data
item.path = data["path"]["local_path"]
updated_items.append(item)
return updated_items

# First try to execute the hook method to update items in batch for performance.
try:
return self.update_items_to_latest_version(items)
Expand Down Expand Up @@ -436,6 +507,20 @@ def update_to_specific_version(self, item, sg_data):
if not sg_data or not sg_data.get("path", {}).get("local_path", None):
return False

if self._bundle.context.flow_project_id and self._flow_host:
do_update = self._bundle.flowam.update_to_revision(
item=item.to_dict(),
item_data=sg_data,
)
if do_update is None:
# Default to True if the hook return value was not explictly set
do_update = True

if do_update:
item.sg_data = sg_data
item.path = sg_data["path"]["local_path"]
return do_update

item_dict = item.to_dict()
item_dict["path"] = sg_data["path"]["local_path"]
if item_dict["extra_data"] is None:
Expand Down
9 changes: 7 additions & 2 deletions python/tk_multi_breakdown2/dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -872,8 +872,7 @@ def _show_history_item_context_menu(self, view, index, pos):
# passed in references the file history item.
if isinstance(index.model(), QtGui.QSortFilterProxyModel):
index = index.model().mapToSource(index)
history_item = index.model().itemFromIndex(index)
sg_data = history_item.get_sg_data()
sg_data = index.data(FileHistoryModel.SG_DATA_ROLE)

update_action = ActionManager.add_update_to_specific_version_action(
file_item_to_update, self._file_model, sg_data, None
Expand Down Expand Up @@ -1350,6 +1349,12 @@ def _on_update_selected_to_latest(self):
self._listen_for_events(False)
try:
ActionManager.execute_update_to_latest_action(file_items, self._file_model)
except Exception as e:
QtGui.QMessageBox.critical(
None,
"Scene Breakdown",
"Error: {}".format(e),
)
finally:
self.__executing_bulk_action = False
# Turn on event handling if it was on before
Expand Down
Loading