Skip to content

Commit f014b8e

Browse files
authored
feat: Backlog/framework loader maya (#558)
1 parent a6f6c7f commit f014b8e

File tree

7 files changed

+286
-18
lines changed

7 files changed

+286
-18
lines changed

projects/framework-maya/extensions/maya.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,11 @@ tools:
2626
options:
2727
tool_configs:
2828
- maya-setup-scene
29+
- name: loader
30+
action: true
31+
menu: false # True by default
32+
label: "Loader"
33+
dialog_name: framework_standard_loader_dialog
34+
options:
35+
tool_configs:
36+
- maya-scene-loader
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# :coding: utf-8
2+
# :copyright: Copyright (c) 2024 ftrack
3+
import maya.cmds as cmds
4+
5+
from ftrack_framework_core.plugin import BasePlugin
6+
from ftrack_framework_core.exceptions.plugin import PluginExecutionError
7+
8+
9+
class MayaSceneLoaderPlugin(BasePlugin):
10+
name = 'maya_scene_loader'
11+
12+
def run(self, store):
13+
'''
14+
Load component to scene based on options.
15+
'''
16+
load_type = self.options.get('load_type')
17+
if not load_type:
18+
raise PluginExecutionError(
19+
f"Invalid load_type option expected import or reference but "
20+
f"got: {load_type}"
21+
)
22+
23+
component_path = store.get('component_path')
24+
if not component_path:
25+
raise PluginExecutionError(f'No component path provided in store!')
26+
27+
if load_type == 'import':
28+
try:
29+
if self.options.get('namespace'):
30+
cmds.file(
31+
component_path,
32+
i=True,
33+
namespace=self.options.get('namespace'),
34+
)
35+
else:
36+
cmds.file(component_path, i=True)
37+
except RuntimeError as error:
38+
raise PluginExecutionError(
39+
f"Failed to import {component_path} to scene. Error: {error}"
40+
)
41+
elif load_type == 'reference':
42+
try:
43+
if self.options.get('namespace'):
44+
cmds.file(
45+
component_path,
46+
r=True,
47+
namespace=self.options.get('namespace', ''),
48+
)
49+
else:
50+
cmds.file(component_path, r=True)
51+
except RuntimeError as error:
52+
raise PluginExecutionError(
53+
f"Failed to reference {component_path} to scene. Error: {error}"
54+
)
55+
56+
self.logger.debug(
57+
f"Component {component_path} has been loaded to scene."
58+
)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
type: tool_config
2+
name: maya-scene-loader
3+
config_type: loader
4+
compatible:
5+
entity_types:
6+
- FileComponent
7+
supported_file_extensions:
8+
- ".mb"
9+
- ".ma"
10+
11+
engine:
12+
- type: plugin
13+
tags:
14+
- context
15+
plugin: resolve_entity_path
16+
ui: show_component
17+
- type: plugin
18+
tags:
19+
- loader
20+
plugin: maya_scene_loader
21+
ui: maya_scene_load_selector
22+
options:
23+
load_type: "import"
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# :coding: utf-8
2+
# :copyright: Copyright (c) 2024 ftrack
3+
4+
try:
5+
from PySide6 import QtWidgets, QtCore
6+
except ImportError:
7+
from PySide2 import QtWidgets, QtCore
8+
9+
from ftrack_framework_qt.widgets import BaseWidget
10+
11+
12+
class MayaSceneLoadSelectorWidget(BaseWidget):
13+
'''
14+
Widget for selecting how to load a scene in Maya.
15+
'''
16+
17+
name = 'maya_scene_load_selector'
18+
ui_type = 'qt'
19+
20+
def __init__(
21+
self,
22+
event_manager,
23+
client_id,
24+
context_id,
25+
plugin_config,
26+
group_config,
27+
on_set_plugin_option,
28+
on_run_ui_hook,
29+
parent=None,
30+
):
31+
self._import_radio = None
32+
self._reference_radio = None
33+
self._button_group = None
34+
self._custom_namespace_checkbox = None
35+
self._custom_namespace_line_edit = None
36+
37+
super(MayaSceneLoadSelectorWidget, self).__init__(
38+
event_manager,
39+
client_id,
40+
context_id,
41+
plugin_config,
42+
group_config,
43+
on_set_plugin_option,
44+
on_run_ui_hook,
45+
parent,
46+
)
47+
48+
def pre_build_ui(self):
49+
layout = QtWidgets.QVBoxLayout()
50+
layout.setContentsMargins(0, 0, 0, 0)
51+
layout.setAlignment(QtCore.Qt.AlignTop)
52+
self.setLayout(layout)
53+
self.setSizePolicy(
54+
QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed
55+
)
56+
57+
def build_ui(self):
58+
'''build function widgets.'''
59+
60+
# Create radio buttons
61+
self._import_radio = QtWidgets.QRadioButton("Import")
62+
self._reference_radio = QtWidgets.QRadioButton("Reference")
63+
64+
# Add radio buttons to button group to allow single selection
65+
self._button_group = QtWidgets.QButtonGroup()
66+
self._button_group.addButton(self._import_radio)
67+
self._button_group.addButton(self._reference_radio)
68+
69+
# Add radio buttons to layout
70+
self.layout().addWidget(self._import_radio)
71+
self.layout().addWidget(self._reference_radio)
72+
73+
# Create label for checkbox
74+
self.layout().addWidget(QtWidgets.QLabel("Options:"))
75+
76+
h_layout = QtWidgets.QHBoxLayout()
77+
# Create checkbox for custom namespace
78+
self._custom_namespace_checkbox = QtWidgets.QCheckBox(
79+
"Enable Custom Namespace"
80+
)
81+
82+
# Create line edit for custom namespace
83+
self._custom_namespace_line_edit = QtWidgets.QLineEdit()
84+
85+
# Add checkbox to layout
86+
h_layout.addWidget(self._custom_namespace_checkbox)
87+
h_layout.addWidget(self._custom_namespace_line_edit)
88+
self.layout().addLayout(h_layout)
89+
90+
def post_build_ui(self):
91+
'''hook events'''
92+
# Set default values
93+
if (
94+
self.plugin_config.get('options', {}).get('load_type')
95+
== 'reference'
96+
):
97+
self._reference_radio.setChecked(True)
98+
elif (
99+
self.plugin_config.get('options', {}).get('load_type') == 'import'
100+
):
101+
self._import_radio.setChecked(True)
102+
if self.plugin_config.get('options', {}).get('namespace'):
103+
self._custom_namespace_checkbox.setChecked(True)
104+
self._custom_namespace_line_edit.setText(
105+
self.plugin_config.get('options', {}).get('namespace')
106+
)
107+
else:
108+
self._custom_namespace_checkbox.setChecked(False)
109+
self._custom_namespace_line_edit.setEnabled(False)
110+
# set Signals
111+
self._button_group.buttonClicked.connect(self._on_radio_button_clicked)
112+
self._custom_namespace_checkbox.stateChanged.connect(
113+
self._on_checkbox_state_changed
114+
)
115+
self._custom_namespace_line_edit.textChanged.connect(
116+
self._on_namespace_changed
117+
)
118+
119+
def _on_checkbox_state_changed(self, state):
120+
'''Enable or disable the custom namespace line edit based on checkbox state.'''
121+
self._custom_namespace_line_edit.setEnabled(state)
122+
self.set_plugin_option(
123+
'namespace', self._custom_namespace_line_edit.text()
124+
)
125+
if not state:
126+
self.set_plugin_option('namespace', None)
127+
128+
def _on_namespace_changed(self, namespace):
129+
'''Update the namespace option based on the line edit text.'''
130+
if not namespace:
131+
return
132+
self.set_plugin_option('namespace', namespace)
133+
134+
def _on_radio_button_clicked(self, radio_button):
135+
'''Toggle the custom namespace line edit based on checkbox state.'''
136+
self.set_plugin_option('load_type', radio_button.text().lower())

projects/framework-maya/release_notes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
## upcoming
55

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

25+
2426
## v24.4.0
2527
2024-04-02
2628

projects/framework-maya/source/ftrack_framework_maya/__init__.py

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,15 @@
2121
get_extensions_path_from_environment,
2222
)
2323
from ftrack_utils.usage import set_usage_tracker, UsageTracker
24+
from ftrack_utils.actions import register_remote_action
2425

2526
from ftrack_utils.session import create_api_session
2627

27-
from ftrack_framework_maya.utils import dock_maya_right, run_in_main_thread
28+
from ftrack_framework_maya.utils import (
29+
dock_maya_right,
30+
run_in_main_thread,
31+
get_maya_session_identifier,
32+
)
2833

2934

3035
# Evaluate version and log package version
@@ -106,6 +111,31 @@ def on_run_tool_callback(
106111
)
107112

108113

114+
@run_in_main_thread
115+
def on_subscribe_action_tool_callback(
116+
client_instance,
117+
tool_name,
118+
label,
119+
dialog_name=None,
120+
options=None,
121+
):
122+
register_remote_action(
123+
session=client_instance.session,
124+
action_name=tool_name,
125+
label=label,
126+
subscriber_id=client_instance.id,
127+
launch_callback=client_instance.on_launch_action_callback,
128+
discover_callback=functools.partial(
129+
client_instance.on_discover_action_callback,
130+
tool_name,
131+
label,
132+
dialog_name,
133+
options,
134+
get_maya_session_identifier,
135+
),
136+
)
137+
138+
109139
def bootstrap_integration(framework_extensions_path):
110140
'''
111141
Initialise Maya Framework integration
@@ -194,11 +224,13 @@ def bootstrap_integration(framework_extensions_path):
194224
# Register tools into ftrack menu
195225
for tool in dcc_config['tools']:
196226
run_on = tool.get("run_on")
227+
action = tool.get("action")
197228
on_menu = tool.get("menu", True)
229+
label = tool.get('label') or tool.get('name')
198230
if on_menu:
199231
cmds.menuItem(
200232
parent=ftrack_menu,
201-
label=tool['label'],
233+
label=label,
202234
command=(
203235
functools.partial(
204236
on_run_tool_callback,
@@ -210,22 +242,21 @@ def bootstrap_integration(framework_extensions_path):
210242
),
211243
image=":/{}.png".format(tool['icon']),
212244
)
213-
if run_on:
214-
if run_on == "startup":
215-
# Execute startup tool-configs
216-
on_run_tool_callback(
217-
client_instance,
218-
tool.get('name'),
219-
tool.get('dialog_name'),
220-
tool['options'],
221-
)
222-
else:
223-
logger.error(
224-
f"Unsupported run_on value: {run_on} tool section of the "
225-
f"tool {tool.get('name')} on the tool config file: "
226-
f"{dcc_config['name']}. \n Currently supported values:"
227-
f" [startup]"
228-
)
245+
if run_on == "startup":
246+
on_run_tool_callback(
247+
client_instance,
248+
tool.get('name'),
249+
tool.get('dialog_name'),
250+
tool['options'],
251+
)
252+
if action:
253+
on_subscribe_action_tool_callback(
254+
client_instance,
255+
tool.get('name'),
256+
label,
257+
tool.get('dialog_name'),
258+
tool['options'],
259+
)
229260

230261
return client_instance
231262

projects/framework-maya/source/ftrack_framework_maya/utils/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import threading
55
from functools import wraps
6+
import socket
67

78
import maya.cmds as cmds
89
import maya.utils as maya_utils
@@ -16,6 +17,15 @@
1617
from PySide2 import QtWidgets, QtCore
1718

1819

20+
def get_maya_session_identifier():
21+
computer_name = socket.gethostname()
22+
# Get the Maya scene name
23+
scene_name = cmds.file(q=True, sceneName=True, shortName=True)
24+
identifier = f"{scene_name}_Maya_{computer_name}"
25+
26+
return identifier
27+
28+
1929
# Dock widget in Maya
2030
def dock_maya_right(widget):
2131
'''Dock *widget* to the right side of Maya.'''

0 commit comments

Comments
 (0)