diff --git a/build_resources.yml b/build_resources.yml
index c61ccc79..f4c09422 100644
--- a/build_resources.yml
+++ b/build_resources.yml
@@ -5,6 +5,8 @@
- ui_src: resources
ui_files:
+ - build_asset_dialog
+ - build_template_dialog
- dialog
- open_publish_form
- widget_publish_history
diff --git a/hooks/tk-houdini_actions.py b/hooks/tk-houdini_actions.py
index 7fd03649..be5e6698 100644
--- a/hooks/tk-houdini_actions.py
+++ b/hooks/tk-houdini_actions.py
@@ -240,8 +240,8 @@ def execute_action(self, name, params, sg_publish_data, am_base_obj=None):
# -----------------------
# FlowAM specific actions
# -----------------------
- use_medm_data = app.get_setting("use_medm_data", False)
- if use_medm_data:
+ enable_flowam = app.get_setting("enable_flowam", False)
+ if enable_flowam:
if name == "open":
am_base_obj._do_open(sg_publish_data)
diff --git a/hooks/tk-maya_actions.py b/hooks/tk-maya_actions.py
index f54ffea0..88f8b82a 100644
--- a/hooks/tk-maya_actions.py
+++ b/hooks/tk-maya_actions.py
@@ -288,8 +288,8 @@ def execute_action(self, name, params, sg_publish_data, am_base_obj=None):
# -----------------------
# FlowAM specific actions
# -----------------------
- use_medm_data = app.get_setting("use_medm_data", False)
- if use_medm_data:
+ enable_flowam = app.get_setting("enable_flowam", False)
+ if enable_flowam:
if name == "reference_am":
am_base_obj._create_reference_am(sg_publish_data)
diff --git a/hooks/tk-nuke_actions.py b/hooks/tk-nuke_actions.py
index 36c7cf28..96842e93 100644
--- a/hooks/tk-nuke_actions.py
+++ b/hooks/tk-nuke_actions.py
@@ -247,8 +247,8 @@ def execute_action(self, name, params, sg_publish_data, am_base_obj=None):
# -----------------------
# FlowAM specific actions
# -----------------------
- use_medm_data = app.get_setting("use_medm_data", False)
- if use_medm_data:
+ enable_flowam = app.get_setting("enable_flowam", False)
+ if enable_flowam:
if name == "build_new_script":
am_base_obj._build_new_scene(sg_publish_data)
diff --git a/info.yml b/info.yml
index 3dad9364..6e530f5d 100644
--- a/info.yml
+++ b/info.yml
@@ -33,10 +33,10 @@ configuration:
Published File Types). The legacy Published File Type filter widget
cannot be used in combination with the Filter menu.
- use_medm_data:
+ enable_flowam:
type: bool
default_value: false
- description: Set to True to use MEDM (Flow Asset Management) data instead of Shotgun
+ description: Set to True to use Flow Asset Management data instead of Shotgun
data. When enabled, the loader fetches publish data from the Flow Asset
Management system. Requires tk-framework-flowam to be configured in the
environment.
diff --git a/python/tk_multi_loader/api/manager.py b/python/tk_multi_loader/api/manager.py
index 152f0fd1..32bf1237 100644
--- a/python/tk_multi_loader/api/manager.py
+++ b/python/tk_multi_loader/api/manager.py
@@ -353,7 +353,7 @@ def _fix_timestamp(sg_data):
def get_am_base_obj(self) -> "FlowAMActions | None":
""" """
- if sgtk.platform.current_bundle().get_setting("use_medm_data", False):
+ if sgtk.platform.current_bundle().get_setting("enable_flowam", False):
from ..medm import FlowAMActions
return FlowAMActions()
diff --git a/python/tk_multi_loader/build_asset_dialog.py b/python/tk_multi_loader/build_asset_dialog.py
new file mode 100644
index 00000000..4628d30a
--- /dev/null
+++ b/python/tk_multi_loader/build_asset_dialog.py
@@ -0,0 +1,214 @@
+# Copyright (c) 2026 Shotgun Software Inc.
+#
+# CONFIDENTIAL AND PROPRIETARY
+#
+# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
+# Source Code License included in this distribution package. See LICENSE.
+# By accessing, using, copying or modifying this work you indicate your
+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
+# not expressly granted therein are reserved by Shotgun Software Inc.
+
+from __future__ import annotations # needed for Houdini support
+
+import sgtk
+from sgtk.platform.qt import QtGui
+
+from .medm.template_queries import get_template_pipeline_steps, get_templates
+from .ui.build_asset_dialog import Ui_BuildAssetDialog
+
+# Toolkit logger
+logger = sgtk.LogManager.get_logger(__name__)
+
+
+class BuildAssetDialog(QtGui.QDialog):
+ """
+ Custom dialog for building new asset.
+ Presents options for building from new scene, current scene, or template.
+ Dynamically updates available templates based on selected pipeline step.
+ """
+
+ def __init__(
+ self,
+ project_id: str,
+ parent: QtGui.QWidget | None = None,
+ pipeline_step: str | None = None,
+ ) -> None:
+ """
+ Initializes the BuildAssetDialog.
+
+ Args:
+ project_id: Id of AM project to be queried.
+ parent: The parent QWidget for this dialog, default to None.
+ pipeline_step: Preselected pipeline step. If provided, it is stored in
+ self.step and used to configure the pipeline_step_combo_box.
+ When a value is given, the combobox is populated
+ with this single option and then disabled. If None,
+ the combobox is populated with the full list of available
+ pipeline steps and remains enabled.
+
+ Notes
+ -----
+ The presence of pipeline_step determines both the contents
+ and the enabled state of the pipeline_step_combo_box.
+ """
+ super().__init__(parent)
+
+ _flow = sgtk.platform.import_framework("tk-framework-flowam", "flow")
+ _FlowError = _flow.FlowError
+ _Project = _flow.data.Project
+ self._CreateMode = _flow.asset_management.CreateMode
+ self._get_template_source_path = _flow.asset_management.get_template_source_path
+
+ # Query the project entity
+ try:
+ self.project = _Project(project_id)
+ except _FlowError as exc:
+ raise ValueError(f"Project not found: {project_id}") from exc
+
+ self.build = None
+ self.step = pipeline_step
+ self.template = None
+ # Maps of entity names to entity objects
+ self.pipeline_steps = {}
+ self.templates = {}
+ # Template source path based on user's selection
+ self.template_source_path = ""
+
+ self.ui = Ui_BuildAssetDialog()
+ self.ui.setupUi(self)
+
+ ok_button = self.ui.build_button_box.button(
+ QtGui.QDialogButtonBox.StandardButton.Ok
+ )
+ ok_button.setText("Build")
+
+ # Populate combo box from options list
+ self.ui.build_mode_combo_box.addItems(
+ [
+ self._CreateMode.NEW.value,
+ self._CreateMode.CURRENT.value,
+ self._CreateMode.TEMPLATE.value,
+ ]
+ )
+
+ self.ui.build_mode_combo_box.currentTextChanged.connect(
+ self.on_build_option_changed
+ )
+ self.ui.pipeline_step_combo_box.currentTextChanged.connect(
+ self.on_pipeline_step_changed
+ )
+ self.ui.templateWidget.hide()
+ self.setMinimumHeight(0)
+ self.setMinimumWidth(310)
+ self.adjustSize()
+
+ # Stub Utilities
+ def get_pipeline_steps(self) -> list[str]:
+ """
+ Returns a list of pipeline steps provided by the Flow AM framework.
+
+ Returns:
+ list[str]: A list of pipeline step names.
+ """
+ pipeline_steps = get_template_pipeline_steps(self.project)
+ pipeline_step_names = []
+ for pipeline_step in pipeline_steps:
+ self.pipeline_steps[pipeline_step.name] = pipeline_step
+ pipeline_step_names.append(pipeline_step.name)
+ return pipeline_step_names
+
+ def get_pipeline_step_templates(self, step: str) -> list[str]:
+ """
+ Returns a list of templates for a given pipeline step.
+
+ Args:
+ step (str): The name of the pipeline step.
+
+ Returns:
+ list[str]: A list of template names for the specified pipeline step.
+ """
+ pipeline_step = self.pipeline_steps[step]
+ templates = get_templates(pipeline_step)
+ template_names = []
+ for template in templates:
+ self.templates[template.name] = template
+ template_names.append(template.name)
+ return template_names
+
+ # Slots
+ def on_build_option_changed(self, text: str) -> None:
+ """
+ Handles changes to the build option selection.
+
+ Args:
+ text (str): The new build option selected.
+ """
+ is_template_mode = self._CreateMode(text) == self._CreateMode.TEMPLATE
+
+ self.setUpdatesEnabled(False)
+ self.ui.templateWidget.setVisible(is_template_mode)
+ self.ui.pipeline_step_combo_box.clear()
+
+ if is_template_mode and self.step:
+ self.ui.pipeline_step_combo_box.addItems([self.step])
+ self.ui.pipeline_step_combo_box.setCurrentText(self.step)
+
+ elif is_template_mode:
+ self.ui.pipeline_step_combo_box.addItems(self.get_pipeline_steps())
+
+ else:
+ self.ui.templates_combo_box.clear()
+ self.setMinimumSize(0, 0)
+ self.step = None
+ self.template = None
+ self.template_source_path = ""
+ self.templates = {}
+
+ self.layout().activate()
+ self.resize(self.width(), self.sizeHint().height())
+ self.setUpdatesEnabled(True)
+
+ def on_pipeline_step_changed(self, step: str) -> None:
+ """
+ Handles changes to the pipeline step selection.
+
+ Args:
+ step (str): The new pipeline step selected.
+
+ Returns:
+ None.
+ """
+ if not step:
+ return
+
+ self.ui.templates_combo_box.clear()
+ self.ui.templates_combo_box.addItems(self.get_pipeline_step_templates(step))
+
+ def on_build_clicked(self) -> None:
+ """
+ Handles the build button click event.
+
+ Returns:
+ None.
+ """
+ self.build = self._CreateMode(self.ui.build_mode_combo_box.currentText())
+
+ if self.build == self._CreateMode.TEMPLATE:
+ self.step = self.ui.pipeline_step_combo_box.currentText()
+ self.template = self.ui.templates_combo_box.currentText()
+ if self.template and self.template in self.templates:
+ template = self.templates[self.template]
+ self.template_source_path = self._get_template_source_path(template)
+ else:
+ self.step = None
+ self.template = None
+ self.template_source_path = ""
+
+ logger.info(
+ f"Building {self.build} from {self.step} using template {self.template}"
+ )
+
+ def accept(self) -> None:
+ """Override accept to ensure dialog closes properly."""
+ self.on_build_clicked()
+ super().accept()
diff --git a/python/tk_multi_loader/build_template_dialog.py b/python/tk_multi_loader/build_template_dialog.py
new file mode 100644
index 00000000..079e8970
--- /dev/null
+++ b/python/tk_multi_loader/build_template_dialog.py
@@ -0,0 +1,137 @@
+# Copyright (c) 2026 Shotgun Software Inc.
+#
+# CONFIDENTIAL AND PROPRIETARY
+#
+# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
+# Source Code License included in this distribution package. See LICENSE.
+# By accessing, using, copying or modifying this work you indicate your
+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
+# not expressly granted therein are reserved by Shotgun Software Inc.
+
+from __future__ import annotations # needed for Houdini support
+
+import sgtk
+from sgtk.platform.qt import QtGui
+
+from .medm.template_queries import find_template_pipeline_step, get_templates
+from .ui.build_template_dialog import Ui_BuildTemplateDialog
+
+# Toolkit logger
+logger = sgtk.LogManager.get_logger(__name__)
+
+
+class BuildTemplateDialog(QtGui.QDialog):
+ """
+ Custom dialog for selecting a template to build an asset from.
+ """
+
+ def __init__(
+ self,
+ project_id: str,
+ pipeline_steps: list[str] | None = None,
+ parent: QtGui.QWidget | None = None,
+ ) -> None:
+ super().__init__(parent)
+
+ _flow = sgtk.platform.import_framework("tk-framework-flowam", "flow")
+ _FlowError = _flow.FlowError
+ _Project = _flow.data.Project
+ self._CreateMode = _flow.asset_management.CreateMode
+
+ # Query the project entity
+ try:
+ self.project = _Project(project_id)
+ except _FlowError as exc:
+ raise ValueError(f"Project not found: {project_id}") from exc
+
+ if not pipeline_steps:
+ raise ValueError("Pipeline steps must be provided to populate the dialog.")
+
+ self.step = None
+ self.template = None
+ self.description = None
+ self.mode = None
+
+ self.ui = Ui_BuildTemplateDialog()
+ self.ui.setupUi(self)
+
+ self.ui.build_mode_combo_box.addItems(
+ [self._CreateMode.NEW.value, self._CreateMode.CURRENT.value]
+ )
+
+ self.ui.pipeline_step_combo_box.addItems(pipeline_steps)
+
+ # Obtain the button to disconnect the default slot to avoid to close the
+ # dialog if the validation fails
+ self.ok_button = self.ui.build_template_button_box.button(
+ QtGui.QDialogButtonBox.Ok
+ )
+ self.ok_button.clicked.disconnect()
+ self.ok_button.clicked.connect(self.on_build_template_clicked)
+
+ self.ui.template_name_line_edit.textChanged.connect(
+ self._update_ok_button_state
+ )
+ self._update_ok_button_state()
+
+ def _update_ok_button_state(self) -> None:
+ """Enable OK button only when required fields are filled."""
+ has_template = bool(self.ui.template_name_line_edit.text().strip())
+ self.ok_button.setEnabled(has_template)
+
+ def on_build_template_clicked(self) -> None:
+ """
+ Handler for when the build template button is clicked.
+ Gathers input data.
+ """
+ self.mode = self._CreateMode(self.ui.build_mode_combo_box.currentText())
+ self.step = self.ui.pipeline_step_combo_box.currentText()
+ self.template = self.ui.template_name_line_edit.text().strip()
+ self.description = self.ui.description_text_edit.toPlainText()
+
+ template_name_validation_msg = self.__validate_template_name(self.template)
+ if template_name_validation_msg:
+ QtGui.QMessageBox.warning(
+ self,
+ template_name_validation_msg["title"],
+ template_name_validation_msg["message"],
+ )
+ return
+
+ self.accept()
+
+ def __validate_template_name(self, template_name: str) -> dict[str, str] | None:
+ """
+ Validates the template name provided by the user.
+ Ensures the name is not empty and does not already exist for the
+ selected pipeline step.
+
+ Args:
+ template_name (str): The name of the template to validate.
+
+ Returns:
+ dict[str, str] | None: A dictionary with 'title' and 'message'
+ keys if validation fails, otherwise None.
+ """
+ if not template_name:
+ return {
+ "title": "Input Error",
+ "message": "Template name cannot be empty.",
+ }
+
+ pipeline_step = find_template_pipeline_step(self.project, self.step)
+ if not pipeline_step:
+ return None
+
+ available_templates = get_templates(pipeline_step)
+ available_template_names = [template.name for template in available_templates]
+ if template_name in available_template_names:
+ return {
+ "title": "Duplicate Template Name",
+ "message": (
+ f"A template with the name '{template_name}' already exists. "
+ "Please choose a different name."
+ ),
+ }
+
+ return None
diff --git a/python/tk_multi_loader/dialog.py b/python/tk_multi_loader/dialog.py
index 4475f0e0..1a43db7d 100644
--- a/python/tk_multi_loader/dialog.py
+++ b/python/tk_multi_loader/dialog.py
@@ -97,7 +97,7 @@ def __init__(self, action_manager, parent=None):
# Hold a reference to the current animation to prevent GC mid-run
self._current_animation = None
- # MEDM tree view - only created when use_medm_data is enabled
+ # FlowAM tree view - only created when enable_flowam is enabled
self._medm_tree_view = None
# The loader app can be invoked from other applications with a custom
@@ -166,18 +166,18 @@ def __init__(self, action_manager, parent=None):
self._publish_history_model = SgPublishHistoryModel(self, self._task_manager)
- # MEDM objects are only instantiated when use_medm_data is enabled.
+ # FlowAM objects are only instantiated when enable_flowam is enabled.
# tk-framework-flowam is required by these classes but is not available
- # in all environments (e.g. CI). Keeping these as None when MEDM is
+ # in all environments (e.g. CI). Keeping these as None when FlowAM is
# disabled prevents a hard startup failure in those environments.
self._medm_cache = None
self._medm_thumbnail_service = None
self._medm_history_model = None
- if sgtk.platform.current_bundle().get_setting("use_medm_data", False):
+ if sgtk.platform.current_bundle().get_setting("enable_flowam", False):
self._medm_cache = MedmSharedCache()
self._medm_thumbnail_service = MedmThumbnailService(self._medm_cache, self)
- # MEDM history model for MEDM publish items
+ # FlowAM history model for FlowAM publish items
self._medm_history_model = MedmPublishHistoryModel(
self, self._task_manager, self._medm_cache, self._medm_thumbnail_service
)
@@ -377,15 +377,15 @@ def __init__(self, action_manager, parent=None):
# Set up filtering
app = sgtk.platform.current_bundle()
- use_medm = app.get_setting("use_medm_data", False)
+ enable_flowam = app.get_setting("enable_flowam", False)
if app.get_setting("use_legacy_published_file_type_filter", False):
# Hide the Filter menu button.
# The legacy filter functionality is always set up, since the filter menu still
# requires some of that functionality.
self._filter_menu = None
self.ui.filter_menu_btn.hide()
- elif use_medm:
- # Disable filter menu for MEDM mode - it expects ShotgunModel data
+ elif enable_flowam:
+ # Disable filter menu for Flow Asset Management mode - it expects ShotgunModel data
self._filter_menu = None
self.ui.filter_menu_btn.hide()
else:
@@ -429,8 +429,8 @@ def __init__(self, action_manager, parent=None):
self._load_entity_presets()
- # Set up the MEDM tree panel when Flow Asset Management is enabled
- if use_medm:
+ # Set up the FlowAM tree panel when Flow Asset Management is enabled
+ if enable_flowam:
self._setup_medm_tree_panel()
#################################################
@@ -585,7 +585,7 @@ def closeEvent(self, event):
shotgun_globals.unregister_bg_task_manager(self._task_manager)
self._task_manager.shut_down()
- # Shut down the MEDM thumbnail service if it is running
+ # Shut down the FlowAM thumbnail service if it is running
if self._medm_thumbnail_service is not None:
self._medm_thumbnail_service.destroy()
@@ -682,19 +682,21 @@ def _content_size_hint():
view.model().modelReset.connect(self._update_history_view_height)
def _update_history_view_height(self) -> None:
- """Notify the layout that the history view's ideal size has changed."""
- self.ui.history_view.updateGeometry()
+ """Resize history_view to exactly fit its content (capped at max)."""
+ view = self.ui.history_view
+ model = view.model()
+ if model and model.rowCount() > 0:
+ row_h = view.sizeHintForRow(0)
+ content_h = row_h * model.rowCount() + 4
+ view.setMaximumHeight(min(content_h, self._history_view_max_height))
+ else:
+ view.setMaximumHeight(0)
+ view.updateGeometry()
def _on_details_button_toggled(self, checked: bool) -> None:
"""
Triggers a show/hide of the details header with an animation.
"""
- content_height = 0
- try:
- content_height = self.ui.details_header.sizeHint().height()
- except Exception:
- pass
-
if (
self._current_animation
and self._current_animation.state() == QtCore.QAbstractAnimation.Running
@@ -706,12 +708,16 @@ def _on_details_button_toggled(self, checked: bool) -> None:
animation.setEasingCurve(QtCore.QEasingCurve.InOutQuad)
if checked:
+ # Release the maximumHeight constraint so the label sizes itself
+ # to its content via the Preferred size policy.
animation.setStartValue(0)
- animation.setEndValue(content_height)
+ animation.setEndValue(16777215)
else:
- animation.setStartValue(content_height)
+ # Collapse from the actual rendered height, not a stale sizeHint.
+ animation.setStartValue(self.ui.details_header.height())
animation.setEndValue(0)
+ animation.finished.connect(lambda: setattr(self, "_current_animation", None))
self._current_animation = animation
animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
@@ -760,7 +766,7 @@ def _on_history_selection(self, selected, deselected):
if pixmap.isNull():
pixmap = None
- # For MEDM data, fall back to the publish thumbnail role
+ # For FlowAM data, fall back to the publish thumbnail role
if not pixmap:
publish_thumb = item.data(SgPublishHistoryModel.PUBLISH_THUMB_ROLE)
if (
@@ -1175,7 +1181,7 @@ def __clear_publish_history(pixmap):
sg_data = item.get_sg_data()
# Route to the correct history model.
- # MEDM data is identified by _medm_asset or _medm_draft keys.
+ # FlowAM data is identified by _medm_asset or _medm_draft keys.
if self._medm_history_model is not None and (
sg_data.get("_medm_asset") is not None
or sg_data.get("_medm_draft") is not None
@@ -1207,7 +1213,7 @@ def _on_detail_version_playback(self):
def _refresh_current_history_model(self) -> None:
"""
- Refresh the currently active history model (either SG or MEDM).
+ Refresh the currently active history model (either SG or FlowAM).
"""
current_source = self._publish_history_proxy.sourceModel()
if current_source == self._medm_history_model:
@@ -2236,7 +2242,7 @@ def _on_treeview_item_selected(self):
selected_item = self._get_selected_entity()
- # Clear MEDM tree selection when a classic entity tree item is selected,
+ # Clear FlowAM tree selection when a classic entity tree item is selected,
# and restore the Shotgun publish model as the source
if self._medm_tree_view is not None:
self._medm_tree_view.selectionModel().clearSelection()
@@ -2439,7 +2445,7 @@ def _populate_entity_breadcrumbs(self, selected_item):
def _setup_medm_tree_panel(self) -> None:
"""
- Set up the MEDM tree view panel as the left-most panel in the splitter.
+ Set up the FlowAM tree view panel as the left-most panel in the splitter.
This panel shows the Flow Asset Management hierarchy.
"""
medm_panel = QtGui.QWidget()
@@ -2488,10 +2494,10 @@ def _setup_medm_tree_panel(self) -> None:
self._on_medm_tree_selection_changed
)
- # Insert the MEDM panel as the first (left-most) widget in the splitter
+ # Insert the FlowAM panel as the first (left-most) widget in the splitter
self.ui.splitter.insertWidget(0, medm_panel)
- # Adjust stretch factors: [MEDM, classic-left, middle, right]
+ # Adjust stretch factors: [FlowAM, classic-left, middle, right]
self.ui.splitter.setStretchFactor(0, 2)
self.ui.splitter.setStretchFactor(1, 3)
self.ui.splitter.setStretchFactor(2, 7)
@@ -2504,8 +2510,8 @@ def _on_medm_tree_selection_changed(
self, selected: QtGui.QItemSelection, deselected: QtGui.QItemSelection
) -> None:
"""
- Called when selection changes in the MEDM tree view. Updates the
- publish view to show publishes for the selected MEDM entity.
+ Called when selection changes in the FlowAM tree view. Updates the
+ publish view to show publishes for the selected FlowAM entity.
"""
app = sgtk.platform.current_bundle()
@@ -2523,19 +2529,19 @@ def _on_medm_tree_selection_changed(
item = self._medm_entity_model.itemFromIndex(index)
if item:
- # Switch to MEDM publish model
+ # Switch to FlowAM publish model
self._publish_proxy_model.setSourceModel(self._medm_publish_model)
- # Clear type filters - MEDM items don't use SG publish types
+ # Clear type filters - FlowAM items don't use SG publish types
self._publish_proxy_model.set_filter_by_type_ids(None, True)
- # Load publishes for the selected MEDM asset
+ # Load publishes for the selected FlowAM asset
self._medm_publish_model.load_data(item)
# Re-evaluate all proxy filter items
self._publish_proxy_model.invalidateFilter()
else:
- app.log_warning("MEDM: Could not get item from index")
+ app.log_warning("FlowAM: Could not get item from index")
################################################################################################
diff --git a/python/tk_multi_loader/medm/__init__.py b/python/tk_multi_loader/medm/__init__.py
index 8ea17348..d5e0a887 100644
--- a/python/tk_multi_loader/medm/__init__.py
+++ b/python/tk_multi_loader/medm/__init__.py
@@ -8,9 +8,9 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-"""MEDM (Flow Asset Management) integration models for the Loader app.
+"""FlowAM integration models for the Loader app.
-This package provides Qt models that back the loader when ``use_medm_data``
+This package provides Qt models that back the loader when ``enable_flowam``
is enabled in the app configuration. All models share a single
:class:`~medm.shared_cache.MedmSharedCache` and
:class:`~medm.thumbnail_service.MedmThumbnailService` instance injected by
@@ -22,6 +22,11 @@
from .latestpublish_model import MedmLatestPublishModel
from .publishhistory_model import MedmPublishHistoryModel
from .shared_cache import MedmSharedCache
+from .template_queries import (
+ find_template_pipeline_step,
+ get_template_pipeline_steps,
+ get_templates,
+)
from .thumbnail_service import MedmThumbnailService
__all__ = [
@@ -31,4 +36,7 @@
"MedmPublishHistoryModel",
"MedmSharedCache",
"MedmThumbnailService",
+ "find_template_pipeline_step",
+ "get_template_pipeline_steps",
+ "get_templates",
]
diff --git a/python/tk_multi_loader/medm/entity_model.py b/python/tk_multi_loader/medm/entity_model.py
index 93c9ac5c..ebc4ecd9 100644
--- a/python/tk_multi_loader/medm/entity_model.py
+++ b/python/tk_multi_loader/medm/entity_model.py
@@ -8,9 +8,9 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-"""MEDM Asset Model - Tree model for MEDM asset hierarchy
+"""FlowAM Asset Model - Tree model for FlowAM asset hierarchy
-This module provides a tree model that displays MEDM assets
+This module provides a tree model that displays FlowAM assets
in the left-hand tree view of the loader.
Children are loaded lazily: only the immediate children of the current project
@@ -41,8 +41,8 @@
class MedmEntityModel(QtGui.QStandardItemModel):
"""
- Tree model that displays MEDM assets in a hierarchical structure.
- This replaces SgEntityModel for MEDM data sources.
+ Tree model that displays FlowAM assets in a hierarchical structure.
+ This replaces SgEntityModel for FlowAM data sources.
Uses lazy loading: only the project's immediate children are fetched at
startup. Deeper levels are fetched when the user expands a node.
@@ -53,7 +53,7 @@ class MedmEntityModel(QtGui.QStandardItemModel):
SG_ASSOCIATED_FIELD_ROLE = QtCore.Qt.UserRole + 2
ASSET_ROLE = (
QtCore.Qt.UserRole + 200
- ) # Stores MEDM Asset object (shared with all MEDM models)
+ ) # Stores FlowAM Asset object (shared with all FlowAM models)
# Lazy-loading bookkeeping role: True once children have been fetched for a node.
CHILDREN_LOADED_ROLE = QtCore.Qt.UserRole + 201
@@ -142,7 +142,7 @@ def canFetchMore(self, parent: QtCore.QModelIndex) -> bool:
return not item.data(self.CHILDREN_LOADED_ROLE)
def fetchMore(self, parent: QtCore.QModelIndex) -> None:
- """Load the immediate children of *parent* from the MEDM API (or cache)."""
+ """Load the immediate children of *parent* from the FlowAM API (or cache)."""
if not parent.isValid():
return
item = self.itemFromIndex(parent)
@@ -176,21 +176,21 @@ def item_from_entity(
Returns a QStandardItem based on entity type and entity id.
**OVERRIDE:** This method overrides the ShotgunModel.item_from_entity() interface
- to provide MEDM-compatible implementation. The original ShotgunModel version uses
- an internal data handler (_data_handler.get_uid_from_entity_id), but MEDM models
+ to provide FlowAM-compatible implementation. The original ShotgunModel version uses
+ an internal data handler (_data_handler.get_uid_from_entity_id), but FlowAM models
store data in a tree structure requiring recursive search.
**Implementation Differences from ShotgunModel.item_from_entity:**
- Original: Uses flat data handler lookup (uid-based)
- This override: Performs recursive tree search through QStandardItem hierarchy
- Original: Validates entity_type matches model's __entity_type
- - This override: Ignores entity_type (MEDM uses unified Asset model)
+ - This override: Ignores entity_type (FlowAM uses unified Asset model)
**Note:** Method name preserved for API compatibility with dialog.py which expects
all entity models (SgEntityModel, SgHierarchyModel, MedmEntityModel) to implement
this interface. Called by dialog._get_item_from_entity() for navigation/selection.
- :param entity_type: Shotgun entity type (ignored in MEDM implementation)
+ :param entity_type: Shotgun entity type (ignored in FlowAM implementation)
:param entity_id: Entity ID to search for (compared against SG_DATA_ROLE["id"])
:returns: :class:`~PySide.QtGui.QStandardItem` or None if not found
"""
@@ -213,12 +213,12 @@ def get_cached_children(self, asset: Asset) -> List[Asset]:
"""
Return child :class:`Asset` objects for *asset*.
- Uses the internal cache when available; otherwise fetches from the MEDM
+ Uses the internal cache when available; otherwise fetches from the FlowAM
API and stores the result. This is the single entry-point that both
the tree's ``fetchMore`` and :class:`MedmLatestPublishModel` use, so
that a drill-down never fetches the same level twice.
- :param asset: Parent MEDM Asset whose children are needed.
+ :param asset: Parent FlowAM Asset whose children are needed.
:returns: List of child Asset objects (may be empty).
"""
return self._fetch_and_cache_children(asset)
@@ -229,18 +229,18 @@ def get_cached_children(self, asset: Asset) -> List[Asset]:
def _initialize_project(self) -> None:
"""
- Initialize and cache the MEDM Project object.
+ Initialize and cache the FlowAM Project object.
Called during __init__ to fail fast if project is unavailable.
"""
try:
session_project = self._flow_module.data.get_session_project()
self._project = self._flow_module.data.Project(session_project.id)
self._app.log_debug(
- f"MEDM Entity: Initialized project '{self._project.name}'"
+ f"FlowAM Entity: Initialized project '{self._project.name}'"
)
except Exception as e:
self._app.log_error(
- f"MEDM Entity: Failed to initialize project: {type(e).__name__}: {e}. "
+ f"FlowAM Entity: Failed to initialize project: {type(e).__name__}: {e}. "
"Entity tree will not be loaded."
)
self._project = None
@@ -273,11 +273,11 @@ def _get_structural_type_ids(self) -> set:
self._structural_type_ids = {folder_id, pipeline_step_id}
self._app.log_debug(
- f"MEDM Entity: structural type IDs = {self._structural_type_ids}"
+ f"FlowAM Entity: structural type IDs = {self._structural_type_ids}"
)
except self._flow_module.FlowError as e:
self._app.log_warning(
- f"MEDM Entity: could not resolve structural type IDs ({e}); "
+ f"FlowAM Entity: could not resolve structural type IDs ({e}); "
"non-structural assets without structural descendants will be hidden."
)
self._structural_type_ids = set()
@@ -301,7 +301,7 @@ def _is_tree_node(self, asset: Asset) -> bool:
Assets that satisfy none of the above are pure leaf items that belong
only in the centre-panel publish list, not in the tree.
- :param asset: MEDM ``Asset`` to test.
+ :param asset: FlowAM ``Asset`` to test.
:returns: ``True`` if the asset should appear in the tree.
"""
if _is_structural_asset_util(asset, self._flow_module):
@@ -322,7 +322,7 @@ def _icon_for_asset(self, asset: Asset) -> QtGui.QIcon:
* **Everything else** (workfiles, generic assets, ...) -> binary/data
icon, reflecting that the item holds or organises file data.
- :param asset: MEDM ``Asset`` to pick an icon for.
+ :param asset: FlowAM ``Asset`` to pick an icon for.
:returns: A :class:`QtGui.QIcon` instance.
"""
return (
@@ -333,18 +333,20 @@ def _icon_for_asset(self, asset: Asset) -> QtGui.QIcon:
def _load_medm_assets(self) -> None:
"""
- Load the first level of MEDM assets (project's immediate children).
+ Load the first level of FlowAM assets (project's immediate children).
Called asynchronously after a short delay to show the loading spinner.
"""
if self._project is None:
self._app.log_warning(
- "MEDM Entity: Cannot load assets - project not initialized"
+ "FlowAM Entity: Cannot load assets - project not initialized"
)
self.data_refresh_fail.emit("Project not initialized")
return
try:
- self._app.log_debug("MEDM: Loading entity tree (project children only)...")
+ self._app.log_debug(
+ "FlowAM: Loading entity tree (project children only)..."
+ )
count = 0
for asset in self._project.iterate_children():
@@ -353,13 +355,13 @@ def _load_medm_assets(self) -> None:
count += 1
self._app.log_debug(
- f"MEDM: Entity tree loaded successfully. Loaded {count} root assets"
+ f"FlowAM: Entity tree loaded successfully. Loaded {count} root assets"
)
self.cache_loaded.emit()
self.data_refreshed.emit(True)
except Exception as e:
- self._app.log_error(f"Failed to load MEDM data: {e}")
+ self._app.log_error(f"Failed to load FlowAM data: {e}")
import traceback
self._app.log_debug(traceback.format_exc())
@@ -375,7 +377,7 @@ def _add_asset_item(
reports ``True`` and Qt draws an expand arrow until the user actually
drills in.
- :param asset: The MEDM Asset to add.
+ :param asset: The FlowAM Asset to add.
:param parent_item: Parent item, or ``None`` for root level.
:returns: The newly created item.
"""
@@ -428,17 +430,19 @@ def _load_children_for_item(self, item: QtGui.QStandardItem) -> None:
for child_asset in tree_children:
self._add_asset_item(child_asset, item)
self._app.log_debug(
- f"MEDM: Loaded {len(tree_children)}/{len(children)} children for '{asset.name}' "
+ f"FlowAM: Loaded {len(tree_children)}/{len(children)} children for '{asset.name}' "
f"(non-structural leaf children hidden from tree)"
)
except Exception as e:
- self._app.log_debug(f"MEDM: Could not get children for '{asset.name}': {e}")
+ self._app.log_debug(
+ f"FlowAM: Could not get children for '{asset.name}': {e}"
+ )
def _fetch_and_cache_children(self, asset: Asset) -> List[Asset]:
"""
Return child assets for *asset*, fetching from the API only on the
first call and caching the result in the shared cache for subsequent
- lookups by any MEDM model.
+ lookups by any FlowAM model.
"""
if asset.id in self._cache.children:
return self._cache.children[asset.id]
diff --git a/python/tk_multi_loader/medm/flowam_actions.py b/python/tk_multi_loader/medm/flowam_actions.py
index ff2c69a9..22a871a9 100644
--- a/python/tk_multi_loader/medm/flowam_actions.py
+++ b/python/tk_multi_loader/medm/flowam_actions.py
@@ -17,6 +17,8 @@
from sgtk import TankError
from sgtk.platform.qt import QtGui
+from ..build_asset_dialog import BuildAssetDialog
+from ..build_template_dialog import BuildTemplateDialog
from ..constants import DRAFT_VERSION_IDENTIFIER
@@ -127,7 +129,6 @@ def _build_new_scene(self, sg_publish_data: dict) -> None:
:param sg_publish_data: Shotgun data dictionary with all the standard publish fields.
"""
- flow_ui_module = self.load_framework("tk-framework-flowam", "ui")
parent_window = self._get_dialog_parent()
sg_flow_am_id = self._app.context.project.get("sg_flow_am_id")
# Get the pipeline step from the task
@@ -135,7 +136,7 @@ def _build_new_scene(self, sg_publish_data: dict) -> None:
task_id = task.get("id") if task else None
task_pipeline_step = self._get_task_pipeline_step(task_id) if task_id else None
# Open the build scene dialog
- build_scene_dialog = flow_ui_module.BuildAssetDialog(
+ build_scene_dialog = BuildAssetDialog(
project_id=sg_flow_am_id,
parent=parent_window,
pipeline_step=task_pipeline_step,
@@ -334,15 +335,12 @@ def _build_new_template(self, sg_publish_data: dict) -> None:
:param sg_publish_data: Shotgun data dictionary with all the standard publish fields.
"""
- flow_am_fw = self.load_framework("tk-framework-flowam")
- flow_ui_module = flow_am_fw.import_module("ui")
-
# Get the sg_flow_am_id from the Project
sg_flow_am_id = self._get_flowam_id()
parent_window = self._get_dialog_parent()
- build_template_dialog = flow_ui_module.BuildTemplateDialog(
+ build_template_dialog = BuildTemplateDialog(
sg_flow_am_id, self._get_pipeline_steps(), parent_window
)
build_template_dialog.accepted.connect(
diff --git a/python/tk_multi_loader/medm/latestpublish_model.py b/python/tk_multi_loader/medm/latestpublish_model.py
index 3c29b8a3..09caedc0 100644
--- a/python/tk_multi_loader/medm/latestpublish_model.py
+++ b/python/tk_multi_loader/medm/latestpublish_model.py
@@ -8,10 +8,10 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-"""MEDM Latest Publish Model - Replacement for SgLatestPublishModel
+"""FlowAM Latest Publish Model - Replacement for SgLatestPublishModel
This module provides a drop-in replacement for SgLatestPublishModel that uses
-Flow Asset Management (MEDM) data instead of Shotgun data.
+Flow Asset Management (FlowAM) data instead of Shotgun data.
"""
from __future__ import annotations
@@ -41,7 +41,7 @@ class MedmLatestPublishModel(QtGui.QStandardItemModel):
Model which handles the main spreadsheet view which displays the latest version of all
publishes from Flow Asset Management.
- This is a drop-in replacement for SgLatestPublishModel that uses MEDM data.
+ This is a drop-in replacement for SgLatestPublishModel that uses FlowAM data.
"""
# Sentinel key used inside cache.drafts to store the list returned by
@@ -56,12 +56,12 @@ class MedmLatestPublishModel(QtGui.QStandardItemModel):
PUBLISH_TYPE_NAME_ROLE = QtCore.Qt.UserRole + 104
SEARCHABLE_NAME = QtCore.Qt.UserRole + 105
- # Additional MEDM-specific roles
+ # Additional FlowAM-specific roles
SG_DATA_ROLE = QtCore.Qt.UserRole + 1 # To maintain compatibility with ShotgunModel
SG_ASSOCIATED_FIELD_ROLE = QtCore.Qt.UserRole + 2
ASSET_ROLE = (
QtCore.Qt.UserRole + 200
- ) # Stores MEDM Asset object (shared with all MEDM models)
+ ) # Stores FlowAM Asset object (shared with all FlowAM models)
DRAFT_ROLE = (
QtCore.Qt.UserRole + 202
) # Stores DraftInfo for draft rows (shared with history model)
@@ -85,13 +85,13 @@ def __init__(
thumbnail_service: Optional[MedmThumbnailService] = None,
):
"""
- Model which represents the latest publishes for an entity from MEDM.
+ Model which represents the latest publishes for an entity from FlowAM.
:param parent: Parent QObject
:param publish_type_model: Model for tracking publish types
:param bg_task_manager: Background task manager (kept for API compatibility)
:param cache: Shared :class:`MedmSharedCache`. When provided all data
- caches (children, drafts, publish types) are shared with other MEDM
+ caches (children, drafts, publish types) are shared with other FlowAM
models so no duplicate API calls are made. When *None* a private
cache is created for standalone use.
:param thumbnail_service: Shared :class:`MedmThumbnailService` instance.
@@ -119,7 +119,7 @@ def __init__(
self._current_entity = None
self._project_id = 0
- self._project_name = "MEDM Project"
+ self._project_name = "FlowAM Project"
self._initialize_project_info()
# -------------------------------------------------------------------------
@@ -135,7 +135,7 @@ def destroy(self) -> None:
def load_data(self, item: Optional[QtGui.QStandardItem]) -> None:
"""
Clears the model and sets it up for the selected asset from left treeview panel.
- Loads data from MEDM instead of Shotgun.
+ Loads data from FlowAM instead of Shotgun.
:param item: Selected item in the treeview, None if nothing is selected.
"""
@@ -179,7 +179,7 @@ def _initialize_project_info(self) -> None:
self._project_name = self._app.context.project["name"]
except Exception as e:
self._app.log_warning(
- f"MEDM LatestPublish: Failed to initialize project info: {type(e).__name__}: {e}. "
+ f"FlowAM LatestPublish: Failed to initialize project info: {type(e).__name__}: {e}. "
"Using default project values. This may indicate the session project was not "
"initialized or the project ID is invalid."
)
@@ -200,10 +200,10 @@ def _populate_model_from_selected_item(
asset = self._extract_asset_from_tree_item(selected_item)
if asset is None:
- self._app.log_warning("MEDM: Could not extract asset from selected item")
+ self._app.log_warning("FlowAM: Could not extract asset from selected item")
return
- self._app.log_debug(f"MEDM: Asset extracted: {asset.name}")
+ self._app.log_debug(f"FlowAM: Asset extracted: {asset.name}")
children_asset_sg_dicts = self._fetch_asset_children(asset)
@@ -218,14 +218,14 @@ def _populate_model_from_selected_item(
children_asset_sg_dicts = [self._asset_to_sg_dict(asset)]
except Exception as e:
self._app.log_warning(
- f"MEDM: Could not convert leaf asset '{asset.name}' to sg_dict: {e}"
+ f"FlowAM: Could not convert leaf asset '{asset.name}' to sg_dict: {e}"
)
# Keep a reference to the raw asset so we can still fetch its
# drafts below.
leaf_asset_fallback = asset
self._app.log_debug(
- f"MEDM: Fetched {len(children_asset_sg_dicts)} latest version dicts from children"
+ f"FlowAM: Fetched {len(children_asset_sg_dicts)} latest version dicts from children"
)
assets_for_draft_lookup = [
@@ -248,7 +248,7 @@ def _populate_model_from_selected_item(
self._cache.drafts[child_asset.id] = raw_drafts
except Exception as e:
self._app.log_debug(
- f"MEDM: Could not fetch drafts for '{child_asset.name}': {e}"
+ f"FlowAM: Could not fetch drafts for '{child_asset.name}': {e}"
)
continue
@@ -257,12 +257,12 @@ def _populate_model_from_selected_item(
try:
draft_dicts.append(self._draft_to_sg_dict(draft_info, child_asset))
self._app.log_debug(
- f"MEDM: Found draft '{getattr(draft_info, 'name', '?')}' "
+ f"FlowAM: Found draft '{getattr(draft_info, 'name', '?')}' "
f"for asset '{child_asset.name}'"
)
except Exception as e:
self._app.log_warning(
- f"MEDM: Could not convert draft '{getattr(draft_info, 'name', '?')}' "
+ f"FlowAM: Could not convert draft '{getattr(draft_info, 'name', '?')}' "
f"for '{child_asset.name}': {e}"
)
@@ -302,7 +302,7 @@ def _populate_model_from_selected_item(
draft_count += 1
self._app.log_debug(
- f"MEDM: center panel now has {self.rowCount()} items "
+ f"FlowAM: center panel now has {self.rowCount()} items "
f"({draft_count} draft(s), {published_count} published)"
)
@@ -313,10 +313,10 @@ def _extract_asset_from_tree_item(
self, item: QtGui.QStandardItem
) -> Optional[Asset]:
"""
- Extract the MEDM Asset object from a tree view QStandardItem.
+ Extract the FlowAM Asset object from a tree view QStandardItem.
:param item: The QStandardItem from the entity tree (left panel)
- :returns: MEDM Asset object or None if not found
+ :returns: FlowAM Asset object or None if not found
"""
# Both MedmEntityModel and MedmLatestPublishModel use ASSET_ROLE = Qt.UserRole + 200
asset = item.data(self.ASSET_ROLE)
@@ -338,7 +338,7 @@ def _fetch_asset_children(self, asset: Asset) -> List[Dict[str, Any]]:
a tree node never duplicates an API call that was already made when the
node was expanded by :class:`MedmEntityModel` (or vice-versa).
- :param asset: The selected MEDM Asset in MEDM treeview
+ :param asset: The selected FlowAM Asset in FlowAM treeview
:returns: List of sg_data dictionaries representing each non-structural child asset
"""
children_asset_sg_dicts = []
@@ -353,39 +353,39 @@ def _fetch_asset_children(self, asset: Asset) -> List[Dict[str, Any]]:
for child_asset in child_assets:
if _is_structural_asset_util(child_asset, self._flow_module):
self._app.log_debug(
- f"MEDM: Skipping structural asset '{child_asset.name}' from center panel"
+ f"FlowAM: Skipping structural asset '{child_asset.name}' from center panel"
)
continue
try:
asset_dict = self._asset_to_sg_dict(child_asset)
children_asset_sg_dicts.append(asset_dict)
self._app.log_debug(
- f"MEDM: Added asset '{child_asset.name}' with latest version "
+ f"FlowAM: Added asset '{child_asset.name}' with latest version "
f"v{child_asset.version_number}"
)
except Exception as e:
self._app.log_warning(
- f"MEDM: Error processing child asset '{child_asset.name}': {e}"
+ f"FlowAM: Error processing child asset '{child_asset.name}': {e}"
)
continue
except Exception as e:
- self._app.log_warning(f"MEDM: Error fetching asset children: {e}")
+ self._app.log_warning(f"FlowAM: Error fetching asset children: {e}")
self._app.log_debug(
- f"MEDM: Loaded {len(children_asset_sg_dicts)} children assets for asset '{asset.name}'"
+ f"FlowAM: Loaded {len(children_asset_sg_dicts)} children assets for asset '{asset.name}'"
)
return children_asset_sg_dicts
def _asset_to_sg_dict(self, asset: Asset) -> Dict[str, Any]:
"""
- Convert an MEDM Asset to a Shotgun-compatible dictionary.
+ Convert an FlowAM Asset to a Shotgun-compatible dictionary.
- :param asset: The MEDM Asset
+ :param asset: The FlowAM Asset
:returns: Dictionary with Shotgun-compatible fields
"""
sg_publish_type_id = None
- sg_publish_type_code = "MEDM Asset"
+ sg_publish_type_code = "FlowAM Asset"
medm_type_ids = asset.type_ids
if len(medm_type_ids) > 0:
@@ -404,7 +404,7 @@ def _asset_to_sg_dict(self, asset: Asset) -> Dict[str, Any]:
"created_by": {
"type": "HumanUser",
"id": 1,
- "name": asset.created_by or "MEDM User",
+ "name": asset.created_by or "FlowAM User",
},
"entity": {"type": "Asset", "id": None, "name": asset.name},
"project": {
@@ -448,12 +448,12 @@ def _draft_to_sg_dict(
:param draft_info: DraftInfo returned by asset_management.get_asset_drafts()
(CheckoutDraftInfo) or get_drafts() (NewDraftInfo).
- :param asset: The MEDM Asset the draft belongs to. May be ``None`` for
+ :param asset: The FlowAM Asset the draft belongs to. May be ``None`` for
``NewDraftInfo`` entries whose parent asset has not been published yet.
:returns: sg_data dictionary compatible with action hooks and center panel UI.
"""
sg_publish_type_id = None
- sg_publish_type_code = "MEDM Asset"
+ sg_publish_type_code = "FlowAM Asset"
# Prefer the published asset's type_ids; fall back to the draft's own
# type_ids for NewDraftInfo where no published asset exists yet.
@@ -506,7 +506,7 @@ def _fetch_new_draft_items_for_parent(
matches *parent_asset_id*.
These represent brand-new assets that exist only on disk and have never
- been published to MEDM. Because they have no published asset record
+ been published to FlowAM. Because they have no published asset record
they are invisible to ``asset.iterate_children()`` and must be surfaced
via :func:`~flow.asset_management.get_drafts`.
@@ -524,7 +524,7 @@ def _fetch_new_draft_items_for_parent(
draft_type="new"
)
except Exception as e:
- self._app.log_warning(f"MEDM: Could not fetch new-asset drafts: {e}")
+ self._app.log_warning(f"FlowAM: Could not fetch new-asset drafts: {e}")
all_new_drafts = []
self._cache.drafts[self._NEW_DRAFTS_CACHE_KEY] = all_new_drafts
else:
@@ -537,13 +537,13 @@ def _fetch_new_draft_items_for_parent(
try:
draft_sg_dict = self._draft_to_sg_dict(draft_info, asset=None)
self._app.log_debug(
- f"MEDM: Found new-asset draft '{getattr(draft_info, 'name', '?')}' "
+ f"FlowAM: Found new-asset draft '{getattr(draft_info, 'name', '?')}' "
f"under parent '{parent_asset_id}'"
)
result.append(draft_sg_dict)
except Exception as e:
self._app.log_warning(
- f"MEDM: Could not convert new-asset draft "
+ f"FlowAM: Could not convert new-asset draft "
f"'{getattr(draft_info, 'name', '?')}': {e}"
)
return result
@@ -559,7 +559,7 @@ def _add_sg_dict_as_qt_item(self, sg_item: Dict[str, Any]) -> None:
The QStandardItem stores multiple pieces of data in custom Qt roles:
- SG_DATA_ROLE: Full Shotgun-compatible dict for backwards compatibility
- - ASSET_ROLE: Original MEDM Asset object (from "_medm_asset" key)
+ - ASSET_ROLE: Original FlowAM Asset object (from "_medm_asset" key)
- TYPE_ID_ROLE: Publish type ID for filtering
- PUBLISH_TYPE_NAME_ROLE: Publish type name for display
- SEARCHABLE_NAME: Name for search/filter operations
@@ -568,7 +568,7 @@ def _add_sg_dict_as_qt_item(self, sg_item: Dict[str, Any]) -> None:
center panel's publish view.
:param sg_item: Shotgun-compatible dictionary created by _asset_to_sg_dict().
- Must contain "_medm_asset" key with the original MEDM Asset object.
+ Must contain "_medm_asset" key with the original FlowAM Asset object.
"""
qt_item = QtGui.QStandardItem(sg_item.get("code", "Unnamed"))
@@ -601,7 +601,7 @@ def get_sg_data():
self.appendRow(qt_item)
self._app.log_debug(
- f"MEDM: Added item '{qt_item.text()}' to model (row count: {self.rowCount()})"
+ f"FlowAM: Added item '{qt_item.text()}' to model (row count: {self.rowCount()})"
)
def _set_tooltip(self, item: QtGui.QStandardItem, sg_item: Dict[str, Any]) -> None:
@@ -641,7 +641,7 @@ def _resolve_and_download_thumbnail(
on the main thread once the image bytes are available.
:param qt_item: The QStandardItem to set the thumbnail on.
- :param revision_id: MEDM AssetRevision ID whose thumbnail is needed.
+ :param revision_id: FlowAM AssetRevision ID whose thumbnail is needed.
"""
self._thumbnail_service.request(qt_item, revision_id, self._apply_thumbnail)
diff --git a/python/tk_multi_loader/medm/publishhistory_model.py b/python/tk_multi_loader/medm/publishhistory_model.py
index dea5110a..f2d7c880 100644
--- a/python/tk_multi_loader/medm/publishhistory_model.py
+++ b/python/tk_multi_loader/medm/publishhistory_model.py
@@ -9,10 +9,10 @@
# not expressly granted therein are reserved by Shotgun Software Inc.
"""
-MEDM Publish History Model - Shows all revisions for a selected MEDM entity.
+FlowAM Publish History Model - Shows all revisions for a selected FlowAM entity.
This module provides a model that displays all revisions (version history) for
-a selected MEDM entity, similar to SgPublishHistoryModel for Shotgun data.
+a selected FlowAM entity, similar to SgPublishHistoryModel for Shotgun data.
"""
from __future__ import annotations
@@ -45,9 +45,9 @@
class MedmPublishHistoryModel(QtGui.QStandardItemModel):
"""
- Model that displays the version history (all revisions) for an MEDM entity.
+ Model that displays the version history (all revisions) for an FlowAM entity.
- This is the MEDM equivalent of SgPublishHistoryModel.
+ This is the FlowAM equivalent of SgPublishHistoryModel.
"""
# Custom roles - matching SgPublishHistoryModel interface
@@ -55,12 +55,12 @@ class MedmPublishHistoryModel(QtGui.QStandardItemModel):
PUBLISH_THUMB_ROLE = QtCore.Qt.UserRole + 102
FULL_IMAGE_PATH_ROLE = QtCore.Qt.UserRole + 103
- # MEDM-specific roles
+ # FlowAM-specific roles
SG_DATA_ROLE = QtCore.Qt.UserRole + 1 # To maintain compatibility with ShotgunModel
ASSET_ROLE = (
QtCore.Qt.UserRole + 200
- ) # Stores MEDM Asset object (shared with all MEDM models)
- VERSION_ROLE = QtCore.Qt.UserRole + 201 # Stores MEDM AssetVersion object
+ ) # Stores FlowAM Asset object (shared with all FlowAM models)
+ VERSION_ROLE = QtCore.Qt.UserRole + 201 # Stores FlowAM AssetVersion object
DRAFT_ROLE = QtCore.Qt.UserRole + 202 # Stores DraftInfo for draft rows
# Signals for compatibility with ShotgunModelOverlayWidget
@@ -83,7 +83,7 @@ def __init__(
:param parent: Parent QObject
:param bg_task_manager: Background task manager (kept for API compatibility)
:param cache: Shared :class:`MedmSharedCache`. When provided all data
- caches (drafts, versions, publish types) are shared with other MEDM
+ caches (drafts, versions, publish types) are shared with other FlowAM
models. When *None* a private cache is created for standalone use.
:param thumbnail_service: Shared :class:`MedmThumbnailService` instance.
When provided thumbnail downloads are shared with other models.
@@ -106,7 +106,7 @@ def __init__(
self._current_sg_data = None
self._project_id = 0
- self._project_name = "MEDM Project"
+ self._project_name = "FlowAM Project"
self._initialize_project_info()
# -------------------------------------------------------------------------
@@ -128,7 +128,7 @@ def load_data(self, sg_data: Dict[str, Any]) -> None:
selected in the center panel, ordered by version number (newest first).
:param sg_data: Shotgun-compatible data dict from center panel selection.
- Must contain "_medm_asset" field with the MEDM Asset.
+ Must contain "_medm_asset" field with the FlowAM Asset.
"""
self.clear()
self._current_sg_data = sg_data
@@ -147,7 +147,7 @@ def load_data(self, sg_data: Dict[str, Any]) -> None:
self.data_refreshed.emit(True)
else:
self._app.log_warning(
- "MEDM History: No asset found in selected publish asset sg_data dict"
+ "FlowAM History: No asset found in selected publish asset sg_data dict"
)
self.data_refresh_fail.emit("No asset found in selection")
return
@@ -164,7 +164,7 @@ def load_data(self, sg_data: Dict[str, Any]) -> None:
self._add_version_as_qt_item(asset_version, medm_asset)
self._app.log_debug(
- f"MEDM History: Loaded {len(versions)} published versions"
+ f"FlowAM History: Loaded {len(versions)} published versions"
)
try:
@@ -179,18 +179,18 @@ def load_data(self, sg_data: Dict[str, Any]) -> None:
self._add_draft_as_qt_item(draft_info, medm_asset)
if drafts:
self._app.log_debug(
- f"MEDM History: Added {len(drafts)} draft(s) for asset '{medm_asset.name}'"
+ f"FlowAM History: Added {len(drafts)} draft(s) for asset '{medm_asset.name}'"
)
except Exception as e:
# Drafts are optional - a failure here should not prevent published
# versions from being shown.
- self._app.log_warning(f"MEDM History: Could not fetch drafts: {e}")
+ self._app.log_warning(f"FlowAM History: Could not fetch drafts: {e}")
self.cache_loaded.emit()
self.data_refreshed.emit(True)
except Exception as e:
- self._app.log_error(f"MEDM History: Error loading versions: {e}")
+ self._app.log_error(f"FlowAM History: Error loading versions: {e}")
self.data_refresh_fail.emit(str(e))
def async_refresh(self) -> None:
@@ -207,7 +207,7 @@ def async_refresh(self) -> None:
self.data_refreshed.emit(True)
def hard_refresh(self) -> None:
- """Force refresh of data (same as async_refresh for MEDM)."""
+ """Force refresh of data (same as async_refresh for FlowAM)."""
self.async_refresh()
# -------------------------------------------------------------------------
@@ -221,7 +221,7 @@ def _initialize_project_info(self) -> None:
self._project_name = self._app.context.project["name"]
except Exception as e:
self._app.log_warning(
- f"MEDM History: Failed to initialize project info: {type(e).__name__}: {e}. "
+ f"FlowAM History: Failed to initialize project info: {type(e).__name__}: {e}. "
"Using default project values. This may indicate the session project was not "
"initialized or the project ID is invalid."
)
@@ -232,8 +232,8 @@ def _add_version_as_qt_item(
"""
Convert an AssetVersion to a QStandardItem and add it to the history model.
- :param asset_version: The MEDM AssetVersion to add
- :param asset: The parent MEDM Asset
+ :param asset_version: The FlowAM AssetVersion to add
+ :param asset: The parent FlowAM Asset
"""
version_number = asset_version.version_number
@@ -262,20 +262,20 @@ def get_sg_data():
qt_item.get_sg_data = get_sg_data
self.appendRow(qt_item)
- self._app.log_debug(f"MEDM History: Added version v{version_number}")
+ self._app.log_debug(f"FlowAM History: Added version v{version_number}")
def _version_to_sg_dict(
self, version: AssetVersion, asset: Asset
) -> Dict[str, Any]:
"""
- Convert a MEDM AssetVersion to Shotgun-compatible dictionary.
+ Convert a FlowAM AssetVersion to Shotgun-compatible dictionary.
- :param version: The MEDM AssetVersion
- :param asset: The parent MEDM Asset
+ :param version: The FlowAM AssetVersion
+ :param asset: The parent FlowAM Asset
:returns: sg_data dictionary compatible with Shotgun UI
"""
sg_publish_type_id = None
- sg_publish_type_code = "MEDM Asset"
+ sg_publish_type_code = "FlowAM Asset"
medm_type_ids = asset.type_ids
if medm_type_ids:
@@ -297,7 +297,7 @@ def _version_to_sg_dict(
"created_by": {
"type": "HumanUser",
"id": None,
- "name": version.created_by or "MEDM User",
+ "name": version.created_by or "FlowAM User",
},
"entity": {
"type": "Asset",
@@ -339,11 +339,11 @@ def _draft_to_sg_dict(
:param draft_info: DraftInfo object returned by asset_management.get_asset_drafts()
or get_drafts(). May be CheckoutDraftInfo or NewDraftInfo.
- :param asset: The parent MEDM Asset (may be None for NewDraftInfo)
+ :param asset: The parent FlowAM Asset (may be None for NewDraftInfo)
:returns: sg_data dictionary compatible with action hooks and Shotgun UI
"""
sg_publish_type_id = None
- sg_publish_type_code = "MEDM Asset"
+ sg_publish_type_code = "FlowAM Asset"
# Prefer the published asset's type_ids; fall back to the draft's own
# type_ids for NewDraftInfo where no published asset exists yet.
@@ -380,7 +380,7 @@ def _add_draft_as_qt_item(
NewDraftInfo (new asset not yet published).
:param draft_info: DraftInfo object (CheckoutDraftInfo or NewDraftInfo)
- :param asset: The parent MEDM Asset (may be None for NewDraftInfo)
+ :param asset: The parent FlowAM Asset (may be None for NewDraftInfo)
"""
draft_type = getattr(draft_info, "draft_type", "unknown")
@@ -414,7 +414,7 @@ def get_sg_data():
# Prepend so drafts always appear above all published versions.
self.insertRow(0, qt_item)
self._app.log_debug(
- f"MEDM History: Added {draft_type} draft '{draft_info.name}' "
+ f"FlowAM History: Added {draft_type} draft '{draft_info.name}' "
f"(draft_id={draft_info.draft_id})"
)
@@ -432,7 +432,7 @@ def _resolve_and_download_thumbnail(
on the main thread once the image bytes are available.
:param qt_item: The QStandardItem to set the thumbnail on.
- :param revision_id: MEDM AssetRevision ID whose thumbnail is needed.
+ :param revision_id: FlowAM AssetRevision ID whose thumbnail is needed.
"""
self._thumbnail_service.request(qt_item, revision_id, self._apply_thumbnail)
diff --git a/python/tk_multi_loader/medm/shared_cache.py b/python/tk_multi_loader/medm/shared_cache.py
index ea9831f5..08c22a0c 100644
--- a/python/tk_multi_loader/medm/shared_cache.py
+++ b/python/tk_multi_loader/medm/shared_cache.py
@@ -9,14 +9,14 @@
# not expressly granted therein are reserved by Shotgun Software Inc.
"""
-Shared cache container for all MEDM model data.
+Shared cache container for all FlowAM model data.
A single :class:`MedmSharedCache` instance is created by the dialog and
-injected into every MEDM model. Centralising the dictionaries here means:
+injected into every FlowAM model. Centralising the dictionaries here means:
* No duplicate API calls when the same data is needed by more than one model
(e.g. drafts used by both the latest-publish and history panels).
-* A single place to inspect or clear all MEDM state.
+* A single place to inspect or clear all FlowAM state.
* Refresh semantics are explicit: :meth:`clear_on_refresh` drops only the
data that can change between user-triggered refreshes; immutable data
(thumbnail images, publish-type mappings) is preserved.
@@ -31,7 +31,7 @@
@dataclasses.dataclass
class MedmSharedCache:
"""
- Central store for all dictionaries shared across MEDM models.
+ Central store for all dictionaries shared across FlowAM models.
All values default to empty dicts so the object can be created with
``MedmSharedCache()`` and immediately passed to model constructors.
diff --git a/python/tk_multi_loader/medm/template_queries.py b/python/tk_multi_loader/medm/template_queries.py
new file mode 100644
index 00000000..fa99e1fa
--- /dev/null
+++ b/python/tk_multi_loader/medm/template_queries.py
@@ -0,0 +1,67 @@
+# Copyright (c) 2026 Shotgun Software Inc.
+#
+# CONFIDENTIAL AND PROPRIETARY
+#
+# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
+# Source Code License included in this distribution package. See LICENSE.
+# By accessing, using, copying or modifying this work you indicate your
+# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
+# not expressly granted therein are reserved by Shotgun Software Inc.
+
+"""Template query helpers for Flow Asset Management.
+
+These functions mirror the template-browsing API exposed by the
+``tk-framework-flowam`` ``flow.asset_management`` module, but load the
+framework lazily so callers do not need a direct framework reference.
+"""
+
+from __future__ import annotations
+
+from typing import Any, Optional
+
+import sgtk
+
+logger = sgtk.platform.get_logger(__name__)
+
+
+def get_template_pipeline_steps(project: Any) -> list[Any]:
+ """Return pipeline steps available under the Templates folder.
+
+ :param project: Flow AM ``Project`` instance to query.
+ :returns: List of pipeline step ``Asset`` objects found under the
+ Templates folder, or an empty list when the folder is absent.
+ """
+ _flow = sgtk.platform.import_framework("tk-framework-flowam", "flow")
+ _am = _flow.asset_management
+ template_folder = project.find_child(_am.TEMPLATE_FOLDER)
+ if not template_folder:
+ return []
+ pipeline_step_type_id = _flow.schema.get_schema_id(_am.PIPELINE_STEP_TYPE)
+ return template_folder.find_children(type_id=pipeline_step_type_id)
+
+
+def get_templates(pipeline_step: Any) -> list[Any]:
+ """Return template assets available under a given pipeline step.
+
+ :param pipeline_step: Flow AM ``Asset`` representing a pipeline step.
+ :returns: List of template ``Asset`` objects under the pipeline step.
+ """
+ _flow = sgtk.platform.import_framework("tk-framework-flowam", "flow")
+ _am = _flow.asset_management
+ template_type_id = _flow.schema.get_schema_id(_am.TEMPLATE_TYPE)
+ return pipeline_step.find_children(type_id=template_type_id)
+
+
+def find_template_pipeline_step(project: Any, pipeline_step_name: str) -> Optional[Any]:
+ """Find a pipeline step by name under the Templates folder.
+
+ :param project: Flow AM ``Project`` instance to query.
+ :param pipeline_step_name: Name of the pipeline step to look for.
+ :returns: The matching pipeline step ``Asset``, or ``None`` if not found.
+ """
+ _flow = sgtk.platform.import_framework("tk-framework-flowam", "flow")
+ _am = _flow.asset_management
+ template_folder = project.find_child(_am.TEMPLATE_FOLDER)
+ if not template_folder:
+ return None
+ return template_folder.find_child(pipeline_step_name)
diff --git a/python/tk_multi_loader/medm/thumbnail_service.py b/python/tk_multi_loader/medm/thumbnail_service.py
index 40a43e7d..3c69f441 100644
--- a/python/tk_multi_loader/medm/thumbnail_service.py
+++ b/python/tk_multi_loader/medm/thumbnail_service.py
@@ -9,15 +9,15 @@
# not expressly granted therein are reserved by Shotgun Software Inc.
"""
-MEDM Thumbnail Service - shared thumbnail URL resolver and image downloader.
+FlowAM Thumbnail Service - shared thumbnail URL resolver and image downloader.
-A single instance is created per dialog session and injected into every MEDM
-model that needs thumbnails. Because MEDM revision IDs are immutable (a given
+A single instance is created per dialog session and injected into every FlowAM
+model that needs thumbnails. Because FlowAM revision IDs are immutable (a given
ID always resolves to the same URL and pixel data), both caches are kept for
the entire session and are never evicted on refresh.
The URL and data caches are owned by the :class:`~medm.shared_cache.MedmSharedCache`
-passed at construction time, so all MEDM state lives in one place.
+passed at construction time, so all FlowAM state lives in one place.
"""
from __future__ import annotations
@@ -37,10 +37,10 @@
class MedmThumbnailService(QtCore.QObject):
"""
Session-scoped thumbnail URL resolver and image downloader shared across
- all MEDM model instances (MedmLatestPublishModel, MedmPublishHistoryModel).
+ all FlowAM model instances (MedmLatestPublishModel, MedmPublishHistoryModel).
The URL and image-data caches are provided externally via
- :class:`~medm.shared_cache.MedmSharedCache` so all MEDM caches live in a
+ :class:`~medm.shared_cache.MedmSharedCache` so all FlowAM caches live in a
single, inspectable location. The service itself owns only the operational
state: a timer, a pending-work queue, and the background threads.
@@ -101,7 +101,7 @@ def request(
Otherwise a daemon thread resolves the URL and downloads the image.
:param qt_item: ``QStandardItem`` whose icon should be updated.
- :param revision_id: MEDM ``AssetRevision`` ID to look up.
+ :param revision_id: FlowAM ``AssetRevision`` ID to look up.
:param callback: ``callable(qt_item, image_data: bytes)`` that will be
invoked on the **main thread** once the image bytes are available.
The callback is responsible for converting bytes to a ``QPixmap``
@@ -154,7 +154,7 @@ def _resolve_and_fetch(
url = self._flow_module.asset_management.get_thumbnail_url(revision_id)
except Exception as exc:
self._app.log_debug(
- f"MEDM ThumbnailService: URL resolve failed for {revision_id}: {exc}"
+ f"FlowAM ThumbnailService: URL resolve failed for {revision_id}: {exc}"
)
self._url_cache[revision_id] = url
@@ -178,7 +178,7 @@ def _resolve_and_fetch(
) as response:
if response.status != 200:
self._app.log_debug(
- f"MEDM ThumbnailService: HTTP {response.status} for {revision_id}"
+ f"FlowAM ThumbnailService: HTTP {response.status} for {revision_id}"
)
return
image_data = response.read(self._MAX_IMAGE_BYTES)
@@ -192,7 +192,7 @@ def _resolve_and_fetch(
except Exception as exc:
self._app.log_debug(
- f"MEDM ThumbnailService: Download failed: {type(exc).__name__}: {exc}"
+ f"FlowAM ThumbnailService: Download failed: {type(exc).__name__}: {exc}"
)
def _process_pending(self) -> None:
@@ -205,5 +205,5 @@ def _process_pending(self) -> None:
callback(qt_item, image_data)
except Exception as exc:
self._app.log_debug(
- f"MEDM ThumbnailService: Callback error: {type(exc).__name__}: {exc}"
+ f"FlowAM ThumbnailService: Callback error: {type(exc).__name__}: {exc}"
)
diff --git a/python/tk_multi_loader/medm/utils.py b/python/tk_multi_loader/medm/utils.py
index db2bd318..2f1c5b02 100644
--- a/python/tk_multi_loader/medm/utils.py
+++ b/python/tk_multi_loader/medm/utils.py
@@ -8,7 +8,7 @@
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
-"""Shared utility helpers for MEDM models.
+"""Shared utility helpers for FlowAM models.
Functions here are intentionally free of Qt and SGTK framework imports so
they stay lightweight and easy to unit-test.
@@ -23,7 +23,7 @@
def is_structural_asset(asset: Any, flow_module: Any) -> bool:
- """Return ``True`` when *asset* is a structural container in the MEDM hierarchy.
+ """Return ``True`` when *asset* is a structural container in the FlowAM hierarchy.
An asset is considered structural — and therefore belongs only in the
left-hand tree, never in the centre-panel publish list — when **any** of
@@ -37,7 +37,7 @@ def is_structural_asset(asset: Any, flow_module: Any) -> bool:
schema error never surfaces as a visible crash; the safe default is
``False`` (treat the asset as publishable).
- :param asset: MEDM ``Asset`` object to test.
+ :param asset: FlowAM ``Asset`` object to test.
:param flow_module: The ``flow`` framework module imported via
``sgtk.platform.import_framework("tk-framework-flowam", "flow")``.
:returns: ``True`` if the asset is a structural container.
@@ -149,7 +149,7 @@ def build_draft_sg_dict(
:param draft_info: ``DraftInfo`` instance (``CheckoutDraftInfo`` or
``NewDraftInfo``).
- :param asset: MEDM ``Asset`` the draft belongs to. May be ``None`` for
+ :param asset: FlowAM ``Asset`` the draft belongs to. May be ``None`` for
``NewDraftInfo`` entries surfaced by the history model.
:param context: ``sgtk.Context`` used to resolve the current user.
:param project_id: ShotGrid project ID.
@@ -203,7 +203,7 @@ def resolve_publish_type(
flow_module: Any,
app: Any,
) -> Tuple[Optional[int], str]:
- """Resolve a MEDM schema type ID to a ``(sg_publish_type_id, display_name)`` pair.
+ """Resolve a FlowAM schema type ID to a ``(sg_publish_type_id, display_name)`` pair.
Resolution order:
@@ -211,7 +211,7 @@ def resolve_publish_type(
2. ShotGrid ``PublishedFileType`` lookup by display name (real ID).
3. ``None`` fallback when no SG record exists (item will bypass type filter).
- :param medm_type_id_str: MEDM schema type ID string.
+ :param medm_type_id_str: FlowAM schema type ID string.
:param cache: :class:`MedmSharedCache` instance whose ``publish_types``
dict is used for caching.
:param flow_module: The ``flow`` framework module imported via
@@ -229,7 +229,7 @@ def resolve_publish_type(
display_name = schema_name
except Exception as e:
app.log_debug(
- f"MEDM: Could not get schema display name for '{medm_type_id_str}': {e}"
+ f"FlowAM: Could not get schema display name for '{medm_type_id_str}': {e}"
)
sg_publish_type_id = None
@@ -242,17 +242,17 @@ def resolve_publish_type(
if pft:
sg_publish_type_id = pft["id"]
app.log_debug(
- f"MEDM: Resolved PublishedFileType '{display_name}' "
+ f"FlowAM: Resolved PublishedFileType '{display_name}' "
f"-> SG id={sg_publish_type_id}"
)
else:
app.log_debug(
- f"MEDM: No SG PublishedFileType found for '{display_name}', "
+ f"FlowAM: No SG PublishedFileType found for '{display_name}', "
f"item will bypass type filter"
)
except Exception as e:
app.log_debug(
- f"MEDM: Could not look up PublishedFileType for '{display_name}': {e}"
+ f"FlowAM: Could not look up PublishedFileType for '{display_name}': {e}"
)
result: Tuple[Optional[int], str] = (sg_publish_type_id, display_name)
diff --git a/python/tk_multi_loader/ui/build_asset_dialog.py b/python/tk_multi_loader/ui/build_asset_dialog.py
new file mode 100644
index 00000000..cb31577c
--- /dev/null
+++ b/python/tk_multi_loader/ui/build_asset_dialog.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'build_asset_dialog.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from tank.platform.qt import QtCore
+for name, cls in QtCore.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
+
+from tank.platform.qt import QtGui
+for name, cls in QtGui.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
+
+
+class Ui_BuildAssetDialog(object):
+ def setupUi(self, BuildAssetDialog):
+ if not BuildAssetDialog.objectName():
+ BuildAssetDialog.setObjectName(u"BuildAssetDialog")
+ BuildAssetDialog.resize(310, 190)
+ sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(BuildAssetDialog.sizePolicy().hasHeightForWidth())
+ BuildAssetDialog.setSizePolicy(sizePolicy)
+ self.verticalLayout_3 = QVBoxLayout(BuildAssetDialog)
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
+ self.verticalLayout = QVBoxLayout()
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setSizeConstraint(QLayout.SetMinimumSize)
+ self.verticalLayout.setContentsMargins(9, 9, 9, 9)
+ self.label = QLabel(BuildAssetDialog)
+ self.label.setObjectName(u"label")
+
+ self.verticalLayout.addWidget(self.label)
+
+ self.build_mode_combo_box = QComboBox(BuildAssetDialog)
+ self.build_mode_combo_box.setObjectName(u"build_mode_combo_box")
+
+ self.verticalLayout.addWidget(self.build_mode_combo_box)
+
+ self.templateWidget = QWidget(BuildAssetDialog)
+ self.templateWidget.setObjectName(u"templateWidget")
+ sizePolicy.setHeightForWidth(self.templateWidget.sizePolicy().hasHeightForWidth())
+ self.templateWidget.setSizePolicy(sizePolicy)
+ self.verticalLayout_2 = QVBoxLayout(self.templateWidget)
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
+ self.verticalLayout_2.setContentsMargins(0, 0, 0, 9)
+ self.pipeline_step_label = QLabel(self.templateWidget)
+ self.pipeline_step_label.setObjectName(u"pipeline_step_label")
+ self.pipeline_step_label.setEnabled(True)
+
+ self.verticalLayout_2.addWidget(self.pipeline_step_label)
+
+ self.pipeline_step_combo_box = QComboBox(self.templateWidget)
+ self.pipeline_step_combo_box.setObjectName(u"pipeline_step_combo_box")
+ self.pipeline_step_combo_box.setEnabled(True)
+
+ self.verticalLayout_2.addWidget(self.pipeline_step_combo_box)
+
+ self.templates_label = QLabel(self.templateWidget)
+ self.templates_label.setObjectName(u"templates_label")
+ self.templates_label.setEnabled(True)
+ self.templates_label.setMinimumSize(QSize(41, 0))
+
+ self.verticalLayout_2.addWidget(self.templates_label)
+
+ self.templates_combo_box = QComboBox(self.templateWidget)
+ self.templates_combo_box.setObjectName(u"templates_combo_box")
+ self.templates_combo_box.setEnabled(True)
+
+ self.verticalLayout_2.addWidget(self.templates_combo_box)
+
+ self.verticalLayout.addWidget(self.templateWidget)
+
+ self.build_button_box = QDialogButtonBox(BuildAssetDialog)
+ self.build_button_box.setObjectName(u"build_button_box")
+ self.build_button_box.setOrientation(Qt.Horizontal)
+ self.build_button_box.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
+
+ self.verticalLayout.addWidget(self.build_button_box)
+
+ self.verticalLayout_3.addLayout(self.verticalLayout)
+
+ self.retranslateUi(BuildAssetDialog)
+ self.build_button_box.accepted.connect(BuildAssetDialog.accept)
+ self.build_button_box.rejected.connect(BuildAssetDialog.reject)
+
+ QMetaObject.connectSlotsByName(BuildAssetDialog)
+ # setupUi
+
+ def retranslateUi(self, BuildAssetDialog):
+ BuildAssetDialog.setWindowTitle(QCoreApplication.translate("BuildAssetDialog", u"Build New Scene", None))
+ self.label.setText(QCoreApplication.translate("BuildAssetDialog", u"Build from", None))
+ self.pipeline_step_label.setText(QCoreApplication.translate("BuildAssetDialog", u"Pipeline Step", None))
+ self.templates_label.setText(QCoreApplication.translate("BuildAssetDialog", u"Templates", None))
+ # retranslateUi
diff --git a/python/tk_multi_loader/ui/build_template_dialog.py b/python/tk_multi_loader/ui/build_template_dialog.py
new file mode 100644
index 00000000..e0fd20e2
--- /dev/null
+++ b/python/tk_multi_loader/ui/build_template_dialog.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'build_template_dialog.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from tank.platform.qt import QtCore
+for name, cls in QtCore.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
+
+from tank.platform.qt import QtGui
+for name, cls in QtGui.__dict__.items():
+ if isinstance(cls, type): globals()[name] = cls
+
+
+class Ui_BuildTemplateDialog(object):
+ def setupUi(self, BuildTemplateDialog):
+ if not BuildTemplateDialog.objectName():
+ BuildTemplateDialog.setObjectName(u"BuildTemplateDialog")
+ BuildTemplateDialog.resize(296, 416)
+ self.widget = QWidget(BuildTemplateDialog)
+ self.widget.setObjectName(u"widget")
+ self.widget.setGeometry(QRect(20, 22, 258, 379))
+ self.verticalLayout = QVBoxLayout(self.widget)
+ self.verticalLayout.setObjectName(u"verticalLayout")
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.build_mode_label = QLabel(self.widget)
+ self.build_mode_label.setObjectName(u"build_mode_label")
+
+ self.verticalLayout.addWidget(self.build_mode_label)
+
+ self.build_mode_combo_box = QComboBox(self.widget)
+ self.build_mode_combo_box.setObjectName(u"build_mode_combo_box")
+
+ self.verticalLayout.addWidget(self.build_mode_combo_box)
+
+ self.pipeline_step_label = QLabel(self.widget)
+ self.pipeline_step_label.setObjectName(u"pipeline_step_label")
+
+ self.verticalLayout.addWidget(self.pipeline_step_label)
+
+ self.pipeline_step_combo_box = QComboBox(self.widget)
+ self.pipeline_step_combo_box.setObjectName(u"pipeline_step_combo_box")
+
+ self.verticalLayout.addWidget(self.pipeline_step_combo_box)
+
+ self.template_name_label = QLabel(self.widget)
+ self.template_name_label.setObjectName(u"template_name_label")
+
+ self.verticalLayout.addWidget(self.template_name_label)
+
+ self.template_name_line_edit = QLineEdit(self.widget)
+ self.template_name_line_edit.setObjectName(u"template_name_line_edit")
+
+ self.verticalLayout.addWidget(self.template_name_line_edit)
+
+ self.horizontalLayout = QHBoxLayout()
+ self.horizontalLayout.setSpacing(0)
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
+ self.horizontalLayout.setSizeConstraint(QLayout.SetDefaultConstraint)
+ self.description_label = QLabel(self.widget)
+ self.description_label.setObjectName(u"description_label")
+ sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.description_label.sizePolicy().hasHeightForWidth())
+ self.description_label.setSizePolicy(sizePolicy)
+
+ self.horizontalLayout.addWidget(self.description_label)
+
+ self.optional_label = QLabel(self.widget)
+ self.optional_label.setObjectName(u"optional_label")
+ self.optional_label.setEnabled(False)
+
+ self.horizontalLayout.addWidget(self.optional_label)
+
+ self.verticalLayout.addLayout(self.horizontalLayout)
+
+ self.description_text_edit = QTextEdit(self.widget)
+ self.description_text_edit.setObjectName(u"description_text_edit")
+
+ self.verticalLayout.addWidget(self.description_text_edit)
+
+ self.build_template_button_box = QDialogButtonBox(self.widget)
+ self.build_template_button_box.setObjectName(u"build_template_button_box")
+ self.build_template_button_box.setOrientation(Qt.Horizontal)
+ self.build_template_button_box.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
+
+ self.verticalLayout.addWidget(self.build_template_button_box)
+
+ self.retranslateUi(BuildTemplateDialog)
+ self.build_template_button_box.accepted.connect(BuildTemplateDialog.accept)
+ self.build_template_button_box.rejected.connect(BuildTemplateDialog.reject)
+
+ QMetaObject.connectSlotsByName(BuildTemplateDialog)
+ # setupUi
+
+ def retranslateUi(self, BuildTemplateDialog):
+ BuildTemplateDialog.setWindowTitle(QCoreApplication.translate("BuildTemplateDialog", u"Build Template", None))
+ self.build_mode_label.setText(QCoreApplication.translate("BuildTemplateDialog", u"Build Mode", None))
+ self.pipeline_step_label.setText(QCoreApplication.translate("BuildTemplateDialog", u"Pipeline Step", None))
+ self.template_name_label.setText(QCoreApplication.translate("BuildTemplateDialog", u"Template Name", None))
+ self.description_label.setText(QCoreApplication.translate("BuildTemplateDialog", u"Description", None))
+ self.optional_label.setText(QCoreApplication.translate("BuildTemplateDialog", u" (optional)", None))
+ self.description_text_edit.setPlaceholderText(QCoreApplication.translate("BuildTemplateDialog", u"Add a brief description...", None))
+ # retranslateUi
diff --git a/python/tk_multi_loader/ui/dialog.py b/python/tk_multi_loader/ui/dialog.py
index c60ba44d..75f375b3 100644
--- a/python/tk_multi_loader/ui/dialog.py
+++ b/python/tk_multi_loader/ui/dialog.py
@@ -420,12 +420,11 @@ def setupUi(self, Dialog):
self.verticalLayout_6.setObjectName(u"verticalLayout_6")
self.history_view = QListView(self.details)
self.history_view.setObjectName(u"history_view")
- sizePolicy8 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
+ sizePolicy8 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
sizePolicy8.setHorizontalStretch(0)
sizePolicy8.setVerticalStretch(0)
sizePolicy8.setHeightForWidth(self.history_view.sizePolicy().hasHeightForWidth())
self.history_view.setSizePolicy(sizePolicy8)
- self.history_view.setMinimumSize(QSize(0, 350))
self.history_view.setAutoFillBackground(False)
self.history_view.setProperty("showDropIndicator", True)
self.history_view.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
@@ -439,10 +438,6 @@ def setupUi(self, Dialog):
self.details_widget.setObjectName(u"details_widget")
self.verticalLayout_4 = QVBoxLayout(self.details_widget)
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
- self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
-
- self.verticalLayout_4.addItem(self.verticalSpacer)
-
self.details_button = QToolButton(self.details_widget)
self.details_button.setObjectName(u"details_button")
sizePolicy9 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
@@ -461,7 +456,7 @@ def setupUi(self, Dialog):
self.details_header = QLabel(self.details_widget)
self.details_header.setObjectName(u"details_header")
- sizePolicy10 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
+ sizePolicy10 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
sizePolicy10.setHorizontalStretch(0)
sizePolicy10.setVerticalStretch(0)
sizePolicy10.setHeightForWidth(self.details_header.sizePolicy().hasHeightForWidth())
@@ -473,6 +468,10 @@ def setupUi(self, Dialog):
self.verticalLayout_6.addWidget(self.details_widget)
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ self.verticalLayout_6.addItem(self.verticalSpacer)
+
self.verticalLayout_3.addLayout(self.verticalLayout_6)
self.horizontalLayout_3 = QHBoxLayout()
diff --git a/python/tk_multi_loader/ui/resources_rc.py b/python/tk_multi_loader/ui/resources_rc.py
index 715559cd..0dce447c 100644
--- a/python/tk_multi_loader/ui/resources_rc.py
+++ b/python/tk_multi_loader/ui/resources_rc.py
@@ -29455,7 +29455,7 @@
\x00\x00\x01\x16\x00\x00\x00\x00\x00\x01\x00\x01X\x0b\
\x00\x00\x01\x9eP\xa9\xfa5\
\x00\x00\x06\x02\x00\x00\x00\x00\x00\x01\x00\x05\xdb\xad\
-\x00\x00\x01\x9c\xe7\x9b\x13P\
+\x00\x00\x01\x9e\xad\x95\x98\xff\
\x00\x00\x03\xd4\x00\x00\x00\x00\x00\x01\x00\x04\xa4\x0a\
\x00\x00\x01\x9eP\xa9\xfaA\
\x00\x00\x07\xd2\x00\x00\x00\x00\x00\x01\x00\x07\x0d\xa6\
@@ -29467,7 +29467,7 @@
\x00\x00\x02\xd6\x00\x00\x00\x00\x00\x01\x00\x04{\xca\
\x00\x00\x01\x9eP\xa9\xfa\xc5\
\x00\x00\x05J\x00\x00\x00\x00\x00\x01\x00\x05\xc5\x9e\
-\x00\x00\x01\x9c\xe7\x9b\x13\x12\
+\x00\x00\x01\x9e\xad\x95\x98\xea\
\x00\x00\x05\xcc\x00\x00\x00\x00\x00\x01\x00\x05\xd8\xe4\
\x00\x00\x01\x9eP\xa9\xfb\xca\
\x00\x00\x07d\x00\x00\x00\x00\x00\x01\x00\x06\xb1\x7f\
@@ -29479,9 +29479,9 @@
\x00\x00\x03\x02\x00\x00\x00\x00\x00\x01\x00\x04\x95\xda\
\x00\x00\x01\x9eP\xa9\xf9\xd1\
\x00\x00\x06P\x00\x00\x00\x00\x00\x01\x00\x05\xdf\xc0\
-\x00\x00\x01\x9c\xe7\x9b\x13\x1c\
+\x00\x00\x01\x9e\xad\x95\x98\xf1\
\x00\x00\x04\x06\x00\x00\x00\x00\x00\x01\x00\x04\xa60\
-\x00\x00\x01\x9c\xe7\x9b\x13L\
+\x00\x00\x01\x9e\xad\x95\x98\xfa\
\x00\x00\x08\x00\x00\x00\x00\x00\x00\x01\x00\x07\x18\xce\
\x00\x00\x01\x9eP\xa9\xfaY\
\x00\x00\x04\xac\x00\x00\x00\x00\x00\x01\x00\x05\xb2\x1c\
@@ -29489,7 +29489,7 @@
\x00\x00\x01p\x00\x00\x00\x00\x00\x01\x00\x01\x5c\x22\
\x00\x00\x01\x9eP\xa9\xfa\xee\
\x00\x00\x07\x1e\x00\x00\x00\x00\x00\x01\x00\x06\xa1\x17\
-\x00\x00\x01\x9c\xe7\x9b\x13F\
+\x00\x00\x01\x9e\xad\x95\x98\xf6\
\x00\x00\x01\xa0\x00\x00\x00\x00\x00\x01\x00\x01bR\
\x00\x00\x01\x9eP\xa9\xfbR\
\x00\x00\x02 \x00\x00\x00\x00\x00\x01\x00\x02\x0e\xf4\
@@ -29525,11 +29525,11 @@
\x00\x00\x06t\x00\x00\x00\x00\x00\x01\x00\x05\xea\x8f\
\x00\x00\x01\x9eP\xa9\xfb\x09\
\x00\x00\x00\xec\x00\x00\x00\x00\x00\x01\x00\x00A\xcb\
-\x00\x00\x01\x9c\xe7\x9b\x12\xfa\
+\x00\x00\x01\x9e\xad\x95\x98\xe4\
\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x15N\
\x00\x00\x01\x9eP\xa9\xfa\x8c\
\x00\x00\x03\xb4\x00\x00\x00\x00\x00\x01\x00\x04\xa1\xfb\
-\x00\x00\x01\x9c\xe7\x9b\x13T\
+\x00\x00\x01\x9e\xad\x95\x99\x04\
\x00\x00\x01\xec\x00\x00\x00\x00\x00\x01\x00\x02\x0c\x85\
\x00\x00\x01\x9eP\xa9\xfa \
\x00\x00\x03\x18\x00\x00\x00\x00\x00\x01\x00\x04\x9a\x8e\
diff --git a/resources/build_asset_dialog.ui b/resources/build_asset_dialog.ui
new file mode 100644
index 00000000..5ba666f3
--- /dev/null
+++ b/resources/build_asset_dialog.ui
@@ -0,0 +1,163 @@
+
+
+ BuildAssetDialog
+
+
+
+ 0
+ 0
+ 310
+ 190
+
+
+
+
+ 0
+ 0
+
+
+
+ Build New Scene
+
+
+ -
+
+
+ QLayout::SetMinimumSize
+
+
+ 9
+
+
+ 9
+
+
+ 9
+
+
+ 9
+
+
-
+
+
+ Build from
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 9
+
+
-
+
+
+ true
+
+
+ Pipeline Step
+
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
+ true
+
+
+
+ 41
+ 0
+
+
+
+ Templates
+
+
+
+ -
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+
+
+ build_button_box
+ accepted()
+ BuildAssetDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ build_button_box
+ rejected()
+ BuildAssetDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/resources/build_template_dialog.ui b/resources/build_template_dialog.ui
new file mode 100644
index 00000000..9a7a8a4d
--- /dev/null
+++ b/resources/build_template_dialog.ui
@@ -0,0 +1,144 @@
+
+
+ BuildTemplateDialog
+
+
+
+ 0
+ 0
+ 296
+ 416
+
+
+
+ Build Template
+
+
+
+
+ 20
+ 22
+ 258
+ 379
+
+
+
+ -
+
+
+ Build Mode
+
+
+
+ -
+
+
+ -
+
+
+ Pipeline Step
+
+
+
+ -
+
+
+ -
+
+
+ Template Name
+
+
+
+ -
+
+
+ -
+
+
+ 0
+
+
+ QLayout::SetDefaultConstraint
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Description
+
+
+
+ -
+
+
+ false
+
+
+ (optional)
+
+
+
+
+
+ -
+
+
+ Add a brief description...
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+
+ build_template_button_box
+ accepted()
+ BuildTemplateDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ build_template_button_box
+ rejected()
+ BuildTemplateDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/resources/dialog.ui b/resources/dialog.ui
index 735a5742..ed88dc68 100644
--- a/resources/dialog.ui
+++ b/resources/dialog.ui
@@ -832,17 +832,11 @@ background-image: url(:/res/right_arrow_pressed.png);
-
-
+
0
0
-
-
- 0
- 350
-
-
history_view
@@ -869,19 +863,6 @@ background-image: url(:/res/right_arrow_pressed.png);
-
-
-
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
@@ -916,7 +897,7 @@ background-image: url(:/res/right_arrow_pressed.png);
-
-
+
0
0
@@ -935,6 +916,19 @@ background-image: url(:/res/right_arrow_pressed.png);
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
-