From e4d09e38d7ea526c424f6be8f60b0eb03ce9523d Mon Sep 17 00:00:00 2001 From: Stacey Oue Date: Fri, 8 May 2026 18:16:12 -0400 Subject: [PATCH 1/2] Udpate start up for Alias 2027.1 support. Add python plugin for 2027.1 --- plugins/FlowToolkitAliasPlugin.py | 91 +++++++++++++++++++++++++++++++ plugins/temp/flow_toolkit.py | 50 +++++++++++++++++ startup.py | 27 +++++++-- 3 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 plugins/FlowToolkitAliasPlugin.py create mode 100644 plugins/temp/flow_toolkit.py diff --git a/plugins/FlowToolkitAliasPlugin.py b/plugins/FlowToolkitAliasPlugin.py new file mode 100644 index 00000000..19cc19f3 --- /dev/null +++ b/plugins/FlowToolkitAliasPlugin.py @@ -0,0 +1,91 @@ +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 Toolkit", + description="Flow Production Tracking integration for Alias", + author="Autodesk", + version="0.1", +) +class FlowToolkitAliasPlugin: + """Register the Flow Toolkit plugin with Alias.""" + + +@alpy.continuous_tool(menu=alpy.palette.PICK) +class FlowToolkitTool: + """Placeholder tool required by Alias plugin parser.""" + + @alpy.tool_init( + manipulates_pick_list=False, coordinate_type=alpy.COORDINATE_ABSOLUTE + ) + def on_tool_begin(self): + pass + + @alpy.tool_cleanup + def on_tool_end(self): + pass + + +@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/plugins/temp/flow_toolkit.py b/plugins/temp/flow_toolkit.py new file mode 100644 index 00000000..04222a15 --- /dev/null +++ b/plugins/temp/flow_toolkit.py @@ -0,0 +1,50 @@ +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) + +import alias_api as alpy + +from tk_framework_alias.server import AliasBridge + +PLUGIN_VERSION = "dev" + + +def start(): + """Start the AliasBridge server and bootstrap the client application.""" + + 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 None + + 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}'...") + + return alias_bridge + + +alias_bridge = start() diff --git a/startup.py b/startup.py index 64f1a1df..d5ca9331 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,15 @@ 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" + ) + # FIXME default to 0 + alias_debug = os.environ.get("TK_ALIAS_DEBUG_CONSOLE", "1") + required_env["ALIAS_DEBUG_CONSOLE"] = alias_debug + # 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 +355,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 +376,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 From e4ff24e25c951d7a97df65a083a810fea5a50ad0 Mon Sep 17 00:00:00 2001 From: Stacey Oue Date: Fri, 8 May 2026 18:36:08 -0400 Subject: [PATCH 2/2] wip: fix for Alias 2027.1 python api (need to revisit) --- plugins/FlowToolkitAliasPlugin.py | 20 +++++------- plugins/temp/flow_toolkit.py | 50 ------------------------------ python/tk_alias/menu_generation.py | 36 ++++++++++++++++++++- startup.py | 4 +-- 4 files changed, 43 insertions(+), 67 deletions(-) delete mode 100644 plugins/temp/flow_toolkit.py diff --git a/plugins/FlowToolkitAliasPlugin.py b/plugins/FlowToolkitAliasPlugin.py index 19cc19f3..28542d1c 100644 --- a/plugins/FlowToolkitAliasPlugin.py +++ b/plugins/FlowToolkitAliasPlugin.py @@ -22,28 +22,22 @@ @alpy.plugin( - name="Flow Toolkit", - description="Flow Production Tracking integration for Alias", + name="Flow PT for Alias", + description="Flow Production Tracking Toolkit integration for Alias", author="Autodesk", version="0.1", ) class FlowToolkitAliasPlugin: - """Register the Flow Toolkit plugin with Alias.""" + """Register the Flow Production Tracking Toolkit plugin with Alias.""" -@alpy.continuous_tool(menu=alpy.palette.PICK) +@alpy.momentary_tool(keep_active_tool=True, attribute_string="fptalias") class FlowToolkitTool: """Placeholder tool required by Alias plugin parser.""" - @alpy.tool_init( - manipulates_pick_list=False, coordinate_type=alpy.COORDINATE_ABSOLUTE - ) - def on_tool_begin(self): - pass - - @alpy.tool_cleanup - def on_tool_end(self): - pass + @alpy.on_activate + def on_activate(self) -> None: + alpy.log_to_prompt("Flow Production Tracking Toolkit for Alias activated") @alpy.plugin_init diff --git a/plugins/temp/flow_toolkit.py b/plugins/temp/flow_toolkit.py deleted file mode 100644 index 04222a15..00000000 --- a/plugins/temp/flow_toolkit.py +++ /dev/null @@ -1,50 +0,0 @@ -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) - -import alias_api as alpy - -from tk_framework_alias.server import AliasBridge - -PLUGIN_VERSION = "dev" - - -def start(): - """Start the AliasBridge server and bootstrap the client application.""" - - 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 None - - 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}'...") - - return alias_bridge - - -alias_bridge = start() 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 d5ca9331..5109c24c 100644 --- a/startup.py +++ b/startup.py @@ -150,9 +150,7 @@ def prepare_launch(self, exec_path, args, file_to_open=None): required_env["ALIAS_INTERNAL_PYTHON_SCRIPT_FOLDER"] = os.path.join( self.disk_location, "plugins" ) - # FIXME default to 0 - alias_debug = os.environ.get("TK_ALIAS_DEBUG_CONSOLE", "1") - required_env["ALIAS_DEBUG_CONSOLE"] = alias_debug + 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(