Skip to content
Draft
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
85 changes: 85 additions & 0 deletions plugins/FlowToolkitAliasPlugin.py
Original file line number Diff line number Diff line change
@@ -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
36 changes: 35 additions & 1 deletion python/tk_alias/menu_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down Expand Up @@ -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()
Expand Down
25 changes: 19 additions & 6 deletions startup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down