Skip to content
Merged
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
8 changes: 8 additions & 0 deletions projects/framework-maya/extensions/maya.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,11 @@ tools:
options:
tool_configs:
- maya-setup-scene
- name: loader
action: true
menu: false # True by default
label: "Loader"
dialog_name: framework_standard_loader_dialog
options:
tool_configs:
- maya-scene-loader
58 changes: 58 additions & 0 deletions projects/framework-maya/extensions/plugins/maya_scene_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# :coding: utf-8
# :copyright: Copyright (c) 2024 ftrack
import maya.cmds as cmds

from ftrack_framework_core.plugin import BasePlugin
from ftrack_framework_core.exceptions.plugin import PluginExecutionError


class MayaSceneLoaderPlugin(BasePlugin):
name = 'maya_scene_loader'

def run(self, store):
'''
Load component to scene based on options.
'''
load_type = self.options.get('load_type')
if not load_type:
raise PluginExecutionError(
f"Invalid load_type option expected import or reference but "
f"got: {load_type}"
)

component_path = store.get('component_path')
if not component_path:
raise PluginExecutionError(f'No component path provided in store!')

if load_type == 'import':
try:
if self.options.get('namespace'):
cmds.file(
component_path,
i=True,
namespace=self.options.get('namespace'),
)
else:
cmds.file(component_path, i=True)
except RuntimeError as error:
raise PluginExecutionError(
f"Failed to import {component_path} to scene. Error: {error}"
)
elif load_type == 'reference':
try:
if self.options.get('namespace'):
cmds.file(
component_path,
r=True,
namespace=self.options.get('namespace', ''),
)
else:
cmds.file(component_path, r=True)
except RuntimeError as error:
raise PluginExecutionError(
f"Failed to reference {component_path} to scene. Error: {error}"
)

self.logger.debug(
f"Component {component_path} has been loaded to scene."
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
type: tool_config
name: maya-scene-loader
config_type: loader
compatible:
entity_types:
- FileComponent
supported_file_extensions:
- ".mb"
- ".ma"

engine:
- type: plugin
tags:
- context
plugin: resolve_entity_path
ui: show_component
- type: plugin
tags:
- loader
plugin: maya_scene_loader
ui: maya_scene_load_selector
options:
load_type: "import"
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# :coding: utf-8
# :copyright: Copyright (c) 2024 ftrack

try:
from PySide6 import QtWidgets, QtCore
except ImportError:
from PySide2 import QtWidgets, QtCore

from ftrack_framework_qt.widgets import BaseWidget


class MayaSceneLoadSelectorWidget(BaseWidget):
'''
Widget for selecting how to load a scene in Maya.
'''

name = 'maya_scene_load_selector'
ui_type = 'qt'

def __init__(
self,
event_manager,
client_id,
context_id,
plugin_config,
group_config,
on_set_plugin_option,
on_run_ui_hook,
parent=None,
):
self._import_radio = None
self._reference_radio = None
self._button_group = None
self._custom_namespace_checkbox = None
self._custom_namespace_line_edit = None

super(MayaSceneLoadSelectorWidget, self).__init__(
event_manager,
client_id,
context_id,
plugin_config,
group_config,
on_set_plugin_option,
on_run_ui_hook,
parent,
)

def pre_build_ui(self):
layout = QtWidgets.QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setAlignment(QtCore.Qt.AlignTop)
self.setLayout(layout)
self.setSizePolicy(
QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
)

def build_ui(self):
'''build function widgets.'''

# Create radio buttons
self._import_radio = QtWidgets.QRadioButton("Import")
self._reference_radio = QtWidgets.QRadioButton("Reference")

# Add radio buttons to button group to allow single selection
self._button_group = QtWidgets.QButtonGroup()
self._button_group.addButton(self._import_radio)
self._button_group.addButton(self._reference_radio)

# Add radio buttons to layout
self.layout().addWidget(self._import_radio)
self.layout().addWidget(self._reference_radio)

# Create label for checkbox
self.layout().addWidget(QtWidgets.QLabel("Options:"))

h_layout = QtWidgets.QHBoxLayout()
# Create checkbox for custom namespace
self._custom_namespace_checkbox = QtWidgets.QCheckBox(
"Enable Custom Namespace"
)

# Create line edit for custom namespace
self._custom_namespace_line_edit = QtWidgets.QLineEdit()

# Add checkbox to layout
h_layout.addWidget(self._custom_namespace_checkbox)
h_layout.addWidget(self._custom_namespace_line_edit)
self.layout().addLayout(h_layout)

def post_build_ui(self):
'''hook events'''
# Set default values
if (
self.plugin_config.get('options', {}).get('load_type')
== 'reference'
):
self._reference_radio.setChecked(True)
elif (
self.plugin_config.get('options', {}).get('load_type') == 'import'
):
self._import_radio.setChecked(True)
if self.plugin_config.get('options', {}).get('namespace'):
self._custom_namespace_checkbox.setChecked(True)
self._custom_namespace_line_edit.setText(
self.plugin_config.get('options', {}).get('namespace')
)
else:
self._custom_namespace_checkbox.setChecked(False)
self._custom_namespace_line_edit.setEnabled(False)
# set Signals
self._button_group.buttonClicked.connect(self._on_radio_button_clicked)
self._custom_namespace_checkbox.stateChanged.connect(
self._on_checkbox_state_changed
)
self._custom_namespace_line_edit.textChanged.connect(
self._on_namespace_changed
)

def _on_checkbox_state_changed(self, state):
'''Enable or disable the custom namespace line edit based on checkbox state.'''
self._custom_namespace_line_edit.setEnabled(state)
self.set_plugin_option(
'namespace', self._custom_namespace_line_edit.text()
)
if not state:
self.set_plugin_option('namespace', None)

def _on_namespace_changed(self, namespace):
'''Update the namespace option based on the line edit text.'''
if not namespace:
return
self.set_plugin_option('namespace', namespace)

def _on_radio_button_clicked(self, radio_button):
'''Toggle the custom namespace line edit based on checkbox state.'''
self.set_plugin_option('load_type', radio_button.text().lower())
2 changes: 2 additions & 0 deletions projects/framework-maya/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

## upcoming

* [new] Studio asset load capability, covering reference and import .ma and .mb scenes.
* [changed] Init; Use create_api_session utility to create the api session.
* [changed] Host, Client instance; Pass run_in_main_thread argument.
* [fix] Init; Fix on_run_tool_callback options argument.
Expand All @@ -21,6 +22,7 @@
* [fix] Launcher; Properly escaped version expressions.
* [changed] Replace Qt.py imports to PySide2 and PySide6 on widgets.


## v24.4.0
2024-04-02

Expand Down
67 changes: 49 additions & 18 deletions projects/framework-maya/source/ftrack_framework_maya/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@
get_extensions_path_from_environment,
)
from ftrack_utils.usage import set_usage_tracker, UsageTracker
from ftrack_utils.actions import register_remote_action

from ftrack_utils.session import create_api_session

from ftrack_framework_maya.utils import dock_maya_right, run_in_main_thread
from ftrack_framework_maya.utils import (
dock_maya_right,
run_in_main_thread,
get_maya_session_identifier,
)


# Evaluate version and log package version
Expand Down Expand Up @@ -106,6 +111,31 @@ def on_run_tool_callback(
)


@run_in_main_thread
def on_subscribe_action_tool_callback(
client_instance,
tool_name,
label,
dialog_name=None,
options=None,
):
register_remote_action(
session=client_instance.session,
action_name=tool_name,
label=label,
subscriber_id=client_instance.id,
launch_callback=client_instance.on_launch_action_callback,
discover_callback=functools.partial(
client_instance.on_discover_action_callback,
tool_name,
label,
dialog_name,
options,
get_maya_session_identifier,
),
)


def bootstrap_integration(framework_extensions_path):
'''
Initialise Maya Framework integration
Expand Down Expand Up @@ -194,11 +224,13 @@ def bootstrap_integration(framework_extensions_path):
# Register tools into ftrack menu
for tool in dcc_config['tools']:
run_on = tool.get("run_on")
action = tool.get("action")
on_menu = tool.get("menu", True)
label = tool.get('label') or tool.get('name')
if on_menu:
cmds.menuItem(
parent=ftrack_menu,
label=tool['label'],
label=label,
command=(
functools.partial(
on_run_tool_callback,
Expand All @@ -210,22 +242,21 @@ def bootstrap_integration(framework_extensions_path):
),
image=":/{}.png".format(tool['icon']),
)
if run_on:
if run_on == "startup":
# Execute startup tool-configs
on_run_tool_callback(
client_instance,
tool.get('name'),
tool.get('dialog_name'),
tool['options'],
)
else:
logger.error(
f"Unsupported run_on value: {run_on} tool section of the "
f"tool {tool.get('name')} on the tool config file: "
f"{dcc_config['name']}. \n Currently supported values:"
f" [startup]"
)
if run_on == "startup":
on_run_tool_callback(
client_instance,
tool.get('name'),
tool.get('dialog_name'),
tool['options'],
)
if action:
on_subscribe_action_tool_callback(
client_instance,
tool.get('name'),
label,
tool.get('dialog_name'),
tool['options'],
)

return client_instance

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import threading
from functools import wraps
import socket

import maya.cmds as cmds
import maya.utils as maya_utils
Expand All @@ -16,6 +17,15 @@
from PySide2 import QtWidgets, QtCore


def get_maya_session_identifier():
computer_name = socket.gethostname()
# Get the Maya scene name
scene_name = cmds.file(q=True, sceneName=True, shortName=True)
identifier = f"{scene_name}_Maya_{computer_name}"

return identifier


# Dock widget in Maya
def dock_maya_right(widget):
'''Dock *widget* to the right side of Maya.'''
Expand Down