diff --git a/plugins/FlowToolkitAliasPlugin.py b/plugins/FlowToolkitAliasPlugin.py new file mode 100644 index 00000000..28542d1c --- /dev/null +++ b/plugins/FlowToolkitAliasPlugin.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import os +import sys + +# Add the framework path to sys.path for Alias's embedded Python interpreter +_fw_path = os.environ.get("TK_FRAMEWORK_ALIAS_PYTHON_PATH") +print(f"TK_FRAMEWORK_ALIAS_PYTHON_PATH: {_fw_path}") +if _fw_path and _fw_path not in sys.path: + sys.path.insert(0, _fw_path) + +try: + import alias_api as alpy +except ImportError: + import alias_api_d as alpy + +from tk_framework_alias.server import AliasBridge + +PLUGIN_VERSION = "dev" + +_alias_bridge: AliasBridge | None = None + + +@alpy.plugin( + name="Flow PT for Alias", + description="Flow Production Tracking Toolkit integration for Alias", + author="Autodesk", + version="0.1", +) +class FlowToolkitAliasPlugin: + """Register the Flow Production Tracking Toolkit plugin with Alias.""" + + +@alpy.momentary_tool(keep_active_tool=True, attribute_string="fptalias") +class FlowToolkitTool: + """Placeholder tool required by Alias plugin parser.""" + + @alpy.on_activate + def on_activate(self) -> None: + alpy.log_to_prompt("Flow Production Tracking Toolkit for Alias activated") + + +@alpy.plugin_init +def initialize(): + """Start the AliasBridge server and bootstrap the client application.""" + global _alias_bridge + + _alias_bridge = AliasBridge() + + hostname = os.environ.get("ALIAS_PLUGIN_CLIENT_SIO_HOSTNAME") + port_str = os.environ.get("ALIAS_PLUGIN_CLIENT_SIO_PORT") + port = int(port_str) if port_str else -1 + + max_retries = 0 if (hostname and port >= 0) else -1 + + if not _alias_bridge.start_server(hostname, port, max_retries): + print("Failed to start Alias communication") + return + + print("Running Alias Python API server") + + client_name = os.environ.get("ALIAS_PLUGIN_CLIENT_NAME", "plugin-client") + + client_info = { + "plugin_version": PLUGIN_VERSION, + "alias_version": getattr(alpy, "__version__", "unknown"), + "python_version": sys.version, + } + + if _alias_bridge.bootstrap_client(client_name, client_info): + print(f"Starting client '{client_name}'...") + + +@alpy.plugin_exit +def deinit(): + """Stop the AliasBridge server.""" + global _alias_bridge + + if _alias_bridge: + try: + _alias_bridge.stop_server() + except Exception as exc: + print(str(exc)) + + _alias_bridge = None diff --git a/python/tk_alias/menu_generation.py b/python/tk_alias/menu_generation.py index 1ed9468b..156c0a05 100644 --- a/python/tk_alias/menu_generation.py +++ b/python/tk_alias/menu_generation.py @@ -14,6 +14,34 @@ from sgtk.util import is_windows, is_macos, is_linux +class _AliasMenuCompat: + """Compatibility wrapper for Alias 2027+ menu API. + + Provides the same interface as the old Menu class (add_menu, add_command, + clean, remove) but routes calls to the new standalone API functions. + The new API uses string-based parent references instead of object handles. + """ + + def __init__(self, alias_py, name): + self._alias_py = alias_py + self._name = name + + def add_menu(self, text): + self._alias_py._alpy_make_submenu(self._name, text) + return text + + def add_command(self, name, callback, parent=None, add_separator=False): + parent_name = parent if isinstance(parent, str) else self._name + on_submenu = parent is not None + self._alias_py._alpy_make_menu_item(name, parent_name, on_submenu, callback) + + def clean(self): + return None + + def remove(self): + return None + + class AliasMenuGenerator(object): """Menu handling for Alias.""" @@ -65,7 +93,13 @@ def build(self): if self.alias_menu is None: # First, create the Flow Production Tracking menu in Alias. - self.__alias_menu = self.engine.alias_py.Menu(self.menu_name) + if hasattr(self.engine.alias_py, "Menu"): + self.__alias_menu = self.engine.alias_py.Menu(self.menu_name) + else: + self.engine.alias_py._alpy_make_main_menu(self.menu_name) + self.__alias_menu = _AliasMenuCompat( + self.engine.alias_py, self.menu_name + ) else: # Make sure we're starting with a fresh menu self.clean_menu() diff --git a/startup.py b/startup.py index 64f1a1df..5109c24c 100644 --- a/startup.py +++ b/startup.py @@ -127,6 +127,10 @@ def prepare_launch(self, exec_path, args, file_to_open=None): required_env.update(plugin_env) required_env["PYTHONPATH"] = os.environ["PYTHONPATH"] + # Pass the framework path explicitly for the embedded Python plugin, + # which cannot safely use the full PYTHONPATH (stdlib version conflicts). + required_env["TK_FRAMEWORK_ALIAS_PYTHON_PATH"] = framework_python_path + # Prepare the launch environment with variables required by the # classic bootstrap approach. required_env["SGTK_ENGINE"] = self.engine_name @@ -141,6 +145,13 @@ def prepare_launch(self, exec_path, args, file_to_open=None): if file_to_open: required_env["SGTK_FILE_TO_OPEN"] = file_to_open + if not plugin_file_path: + # No C++ plugin, use our Python plugin + required_env["ALIAS_INTERNAL_PYTHON_SCRIPT_FOLDER"] = os.path.join( + self.disk_location, "plugins" + ) + required_env["ALIAS_DEBUG_CONSOLE"] = os.environ.get("TK_DEBUG", "0") + # Get the launch app path and args app_path, app_args = self.__prepare_launch_args( args, code_name, plugin_file_path, exec_path, server_python_exe @@ -342,7 +353,7 @@ def __prepare_launch_args( :type args: str :parm code_name: The Alias code name. :type code_name: str - :parm plugin_file_path: The file path to the .lst file used to auto load plugins. + :parm plugin_file_path: The file path to the .lst file used to auto load C++ plugins. :type plugin_file_path: str :param alias_exe: The file path to the Alias executable. :type alias_exe: str @@ -363,15 +374,17 @@ def __prepare_launch_args( app_args += " " + code_name_flags if python_exe is None: - # Launching Alias application directly - add the plugin file path to the Alias cmd - # line args to auto-load the plugin. - app_args += ' -P "{0}'.format(plugin_file_path) - app_args += '"' + # Launching Alias application directly + if plugin_file_path: + # Add the C++ plugin file path to the Alias cmd line args to auto-load the plugin. + app_args += ' -P "{0}'.format(plugin_file_path) + app_args += '"' app_path = alias_exe else: # Launching Alias indirectly to ensure the Alias Plugin uses a specific Python # version - wrap the command line to launch Alias with the given python executable - app_args += f' -P \\"{plugin_file_path}\\"' + if plugin_file_path: + app_args += f' -P \\"{plugin_file_path}\\"' python_args = f'import os;os.system(r\'start /B \\"App\\" \\"{alias_exe}\\" {app_args}\')' app_args = f'-c "{python_args}"' app_path = python_exe