Source code for python.tk_houdini.bootstrap

# Copyright (c) 2013 Shotgun Software Inc.
#
# CONFIDENTIAL AND PROPRIETARY
#
# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
# Source Code License included in this distribution package. See LICENSE.
# By accessing, using, copying or modifying this work you indicate your
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.

import os
import shutil
import sys
import tempfile
import traceback

################################################################################
# constants defining environment variables used during bootstrap

# Name of the env variable that stores the temp directory where .xml files are
# written to defined menus, shelves and python panels for the SG integration.
[docs]g_temp_env = "TK_HOUDINI_TMP"
# Name of the env variable that stores the serialized context used durring # classit toolkit bootstrap
[docs]g_sgtk_context_env = "TANK_CONTEXT"
# Name of the env variable that stores the name the engine instance name for # classic toolkit bootstrap
[docs]g_sgtk_engine_env = "TANK_ENGINE"
################################################################################ # methods for bootstrapping toolkit within houdini
[docs]def bootstrap(tank, context): """ Interface for older versions of tk-multi-launchapp. This is deprecated and now replaced with the ``startup.py`` file and ``SoftwareLauncher`` interface. Prepares the environment for a tk-houdini bootstrap. This method is called directly from the tk-multi-launchapp. """ # get the necessary environment variable for launch env = get_classic_startup_env() # update the environment with the classic startup vars os.environ.update(env)
[docs]def bootstrap_classic(): """ This method initiates the classic toolkit bootstrap. This is accomplished by extracting engine and context information from the environment and then calling sgtk.platform.start_engine(). """ try: import sgtk except ImportError: bootstrap_exception("Failed to import 'sgtk'!") return # ensure the engine name and context are defined in the environment for env_var in [g_sgtk_context_env, g_sgtk_engine_env]: if env_var not in os.environ: bootstrap_exception( "Toolkit bootstrap is missing a required env variable: %s" % (env_var) ) return # extract the engine and context from the environment engine_name = os.environ.get(g_sgtk_engine_env) try: context = sgtk.context.deserialize(os.environ.get(g_sgtk_context_env)) except Exception as e: bootstrap_exception( "Toolkit bootstrap failed to extract the current context from the " "environment! The Shotgun integration will be disabled. Details: " "%s" % (e,) ) return # now do the classic engine startup try: engine = sgtk.platform.start_engine(engine_name, context.sgtk, context) except Exception as e: bootstrap_exception("Toolkit bootstrap failed to start the engine: %s" % (e,)) return # clean env vars. note, we don't clean the temp dir env variable since it is # used by the engine to know where to write the shelf/menu .xml files for env_var in [g_sgtk_context_env, g_sgtk_engine_env]: if env_var in os.environ: del os.environ[env_var]
[docs]def bootstrap_exception(error_msg): """ Shows an error message if there is a problem during bootstrap. """ # this file can be imported before houdini is up and running, but this # method should only be called during a houdini session import hou # get a full stack trace details = traceback.format_exc() if hou.isUIAvailable(): # we have a UI, show the error in a popup dialog hou.ui.displayMessage(error_msg, details=details) else: # no UI, just print to stdout print(error_msg) print(details)
################################################################################ # utility methods for populating the environment prior to bootstrap
[docs]def get_classic_startup_env(): """ Returns a dict of key/value pairs representing the environment variables needed to launch houdini and startup toolkit in classic mode. """ # Add the classic startup directory for the engine (2 levels up from this # file). This directory, in the engine root, includes the pythonrc.py files # within python2.6libs and python2.7libs directories once on HOUDINI_PATH, # houdini will execute the appropriate file at startup startup_path = os.path.join( os.path.dirname(__file__), "..", "..", "classic_startup" ) startup_path = os.path.normpath(startup_path) # update the houdini path with the engine startup path return _get_env([startup_path])
[docs]def get_plugin_startup_env(plugin_names): """ Returns a dict of key/value pairs representing the environment variables needed to launch houdini with the supplied plugins. """ # for each plugin provided, construct a list of corresponding plugin startup # paths. startup_paths = [] for plugin_name in plugin_names: # Each plugin should have the standard pythonX.Xlibs/pythonrc.py folders # at the top-level which houdini will execute at startup time. plugin_startup_path = os.path.join( os.path.dirname(__file__), "..", "..", "plugins", plugin_name, ) plugin_startup_path = os.path.normpath(plugin_startup_path) # if the path exists, add it to the list of startup paths if os.path.exists(plugin_startup_path): startup_paths.append(plugin_startup_path) return _get_env(startup_paths)
################################################################################ # helper methods for the methods above
[docs]def _get_env(startup_paths): """ This method represents the common behavior for preparing an environment for houdini launch. It populates and returns a dictionary with all the necessary keys for starting the engine. The supplied startup paths will differ based on the startup mode (classic or plugin). """ env = {} # setup a path for the engine to write out its menu file tk_houdini_temp_dir = tempfile.mkdtemp(prefix="tk-houdini") # set env var to point engine at temp path env[g_temp_env] = tk_houdini_temp_dir # This allows Qt to load, but I think it makes Houdini unstable... env["OBJC_DISABLE_GC"] = "YES" # construct the houdini path. this isn't as simple as prepending these paths # since we have to account for some legacy behavior and houdini weirdness # when it comes to the expected path separator. see the _build_houdini_path # method for additional details. try: # supply a single list of startup paths which is the temp directory plus # the supplied startup paths env["HOUDINI_PATH"] = _build_houdini_path([tk_houdini_temp_dir] + startup_paths) except: # had an error, clean up the tmp dir shutil.rmtree(tk_houdini_temp_dir) raise return env
[docs]def _build_houdini_path(startup_paths): """ Given some paths, construct an updated houdini path. This method preserves the existing HOUDINI_PATH and prepends the supplied startup paths. It also appends the special `&` default path if it is not already included. """ hou_path_str = os.environ.get("HOUDINI_PATH") # default to using the OS-specific path separator. windows should always use # semicolon since colon is the drive separator. path_sep = os.pathsep if hou_path_str: # It turns out Houdini allows HOUDINI_PATH to be separated by semicolons # on any OS, so the tk engine has always supported and assumed # semicolons regardless of the current OS. Some clients on POSIX OSs # however, who define HOUDINI_PATH in their env prior to tk engine # bootstrap, use colons as the path separator. This is completely valid # and matches the POSIX convention. if sys.platform is not "win32": # for non-windows OS, see if semicolon is in use if ";" in hou_path_str: # already using semi-colons, continue using semicolons. this # will allow clients relying on the legacy engine behavior to # continue without making any changes. path_sep = ";" hou_path_str = hou_path_str.rstrip(path_sep) hou_paths = hou_path_str.split(path_sep) else: hou_paths = [] # paths to prepend that are not already in the houdini path. these paths # include the temp directory which is typically where the engine will write # .xml files for menu/shelf/panel definitions. in addition, startup paths # are added depending on the startup mode. in classic mode, this will be the # engine's root level startup directory which includes code to start the # engine. in plugin mode, this will be a list of paths for each plugin's # startup path. prepend_paths = startup_paths prepend_paths = [p for p in prepend_paths if not p in hou_paths] new_paths = prepend_paths new_paths.extend(hou_paths) # append the ampersand if it's not already in the paths if not "&" in hou_paths: new_paths.append("&") return path_sep.join(new_paths)