diff --git a/app.py b/app.py index 3d3b4ec..79eb06f 100644 --- a/app.py +++ b/app.py @@ -31,14 +31,19 @@ def init_app(self): # Use the Launchers defined in the tk_multi_launchapp payload # to do all of the heavy lifting for this app app_payload = self.import_module("tk_multi_launchapp") - if self.get_setting("use_software_entity"): - # For zero config type setups - self._launcher = app_payload.SoftwareEntityLauncher() - else: - # For traditional setups - self._launcher = app_payload.SingleConfigLauncher() - - # Register the appropriate DCC launch commands + # if self.get_setting("use_software_entity"): + # # For zero config type setups + # self._launcher = app_payload.SoftwareEntityLauncher() + # else: + # # For traditional setups + # self._launcher = app_payload.SingleConfigLauncher() + + self._launcher = self.execute_hook_method( + "hook_launcher", "init", base_class=app_payload.BaseLauncher + ) + + # # Register the appropriate DCC launch commands + self._launcher.register_launch_commands() def launch_from_path_and_context(self, path, context, version=None): diff --git a/python/tk_multi_launchapp/single_config_launcher.py b/hooks/single_config_launcher.py similarity index 85% rename from python/tk_multi_launchapp/single_config_launcher.py rename to hooks/single_config_launcher.py index 60dcf7f..eb368dc 100644 --- a/python/tk_multi_launchapp/single_config_launcher.py +++ b/hooks/single_config_launcher.py @@ -13,27 +13,30 @@ import sgtk from sgtk import TankError -from .base_launcher import BaseLauncher +HookBaseClass = sgtk.get_hook_baseclass() -class SingleConfigLauncher(BaseLauncher): +class SingleConfigLauncher(HookBaseClass): """ Launches a DCC based on traditional configuration settings. """ - def __init__(self): + def init(self): """ Initialize base class and member values """ - BaseLauncher.__init__(self) + + super(SingleConfigLauncher, self).init() # Store required information to launch the app as members. - self._app_path = self._tk_app.get_setting("%s_path" % self._platform_name, "") - self._app_args = self._tk_app.get_setting("%s_args" % self._platform_name, "") - self._app_menu_name = self._tk_app.get_setting("menu_name") - self._app_engine = self._tk_app.get_setting("engine") - self._app_group = self._tk_app.get_setting("group") - self._is_group_default = self._tk_app.get_setting("group_default") + self._app_path = self.parent.get_setting("%s_path" % self._platform_name, "") + self._app_args = self.parent.get_setting("%s_args" % self._platform_name, "") + self._app_menu_name = self.parent.get_setting("menu_name") + self._app_engine = self.parent.get_setting("engine") + self._app_group = self.parent.get_setting("group") + self._is_group_default = self.parent.get_setting("group_default") + + return self def register_launch_commands(self): """ @@ -46,11 +49,11 @@ def register_launch_commands(self): return # get icon value, replacing tokens if needed - app_icon = self._tk_app.get_setting("icon") + app_icon = self.parent.get_setting("icon") if app_icon.startswith("{target_engine}"): if self._app_engine: engine_path = sgtk.platform.get_engine_path( - self._app_engine, self._tk_app.sgtk, self._tk_app.context + self._app_engine, self.parent.sgtk, self.parent.context ) if engine_path: app_icon = app_icon.replace("{target_engine}", engine_path, 1) @@ -65,7 +68,7 @@ def register_launch_commands(self): app_icon = "" if app_icon.startswith("{config_path}"): - config_path = self._tk_app.sgtk.pipeline_configuration.get_config_location() + config_path = self.parent.sgtk.pipeline_configuration.get_config_location() if not config_path: raise TankError( "No pipeline configuration path found for '{config_path}' replacement." @@ -76,13 +79,13 @@ def register_launch_commands(self): app_icon = app_icon.replace("/", os.path.sep) # Initialize per version - app_versions = self._tk_app.get_setting("versions") or [] + app_versions = self.parent.get_setting("versions") or [] if app_versions: # If a list of versions has been specified, the "group_default" configuration # setting is invalid because it cannot be applied to each generated command. # Set the group default to the highest version in the list instead. sorted_versions = self._sort_versions(app_versions) - self._tk_app.log_debug( + self.parent.log_debug( "Unable to apply group '%s' group_default value to list of DCC versions : %s. " "Setting group '%s' default to highest version '%s' instead." % ( @@ -131,7 +134,7 @@ def launch_from_path(self, path, version=None): :param path: File path DCC should open after launch. :param version: (Optional) Specific version of DCC to launch. """ - context = self._tk_app.sgtk.context_from_path(path) + context = self.parent.sgtk.context_from_path(path) self._launch_app( self._app_menu_name, self._app_engine, diff --git a/python/tk_multi_launchapp/software_entity_launcher.py b/hooks/software_entity_launcher.py similarity index 90% rename from python/tk_multi_launchapp/software_entity_launcher.py rename to hooks/software_entity_launcher.py index ce20d1e..f5de9a7 100644 --- a/python/tk_multi_launchapp/software_entity_launcher.py +++ b/hooks/software_entity_launcher.py @@ -13,10 +13,10 @@ import sgtk -from .base_launcher import BaseLauncher +HookBaseClass = sgtk.get_hook_baseclass() -class SoftwareEntityLauncher(BaseLauncher): +class SoftwareEntityLauncher(HookBaseClass): """ Launches a DCC application based on site Software entity entries. @@ -68,8 +68,8 @@ def register_launch_commands(self): for sw_entity in sw_entities: - self._tk_app.log_debug("-" * 20) - self._tk_app.log_debug( + self.parent.log_debug("-" * 20) + self.parent.log_debug( "Parsing Software entity for launch commands:\n%s" % pprint.pformat(sw_entity, indent=4) ) @@ -107,12 +107,12 @@ def register_launch_commands(self): ): # all paths are none - we are in automatic mode - self._tk_app.log_debug("All path fields are None. Automatic mode.") + self.parent.log_debug("All path fields are None. Automatic mode.") # make sure we have an engine defined when running in automatic mode # the engine implements the software discovery logic and is therefore required if engine_str is None: - self._tk_app.log_debug( + self.parent.log_debug( "No engine set. Skipping this software entity." ) continue @@ -135,13 +135,13 @@ def register_launch_commands(self): else: # one or more path fields are not none. This means manual mode. - self._tk_app.log_debug( + self.parent.log_debug( "One or more path fields are not None. Manual mode." ) if sw_entity[app_path_field] is None: # manual mode but nothing to do for our os - self._tk_app.log_debug( + self.parent.log_debug( "No path defined for current platform (field %s) - skipping." % app_path_field ) @@ -178,7 +178,7 @@ def launch_from_path(self, path, version=None): :param version: (Optional) Specific version of DCC to launch. """ # This functionality is not supported for Software entities. - self._tk_app.log_error( + self.parent.log_error( "launch_from_path() is not supported by SoftwareEntityLauncher. " "Please register individual application launch commands in your " "Project's configuration to use this functionality." @@ -194,7 +194,7 @@ def launch_from_path_and_context(self, path, context, version=None): :param version: (Optional) Specific version of DCC to launch. """ # This functionality is not supported for Software entities. - self._tk_app.log_error( + self.parent.log_error( "launch_from_path_and_context() is not supported by " "SoftwareEntityLauncher. Please register individual application " "launch commands in your Project's configuration to use this " @@ -214,12 +214,12 @@ def _get_sg_software_entities(self): # check that software entity is supported if self.__get_sg_server_version() < (7, 2, 0): - self._tk_app.log_warning( + self.parent.log_warning( "Your version of SG does not support Software entity based launching." ) return [] - scan_all_projects = self._tk_app.get_setting("scan_all_projects") or False + scan_all_projects = self.parent.get_setting("scan_all_projects") or False # Determine the information to retrieve from Shotgun # Use filters to retrieve Software entities that match specified @@ -237,7 +237,7 @@ def _get_sg_software_entities(self): # Next handle Project restrictions. Always include Software entities # that have no Project restrictions. project_filters = [["projects", "is", None]] - current_project = self._tk_app.context.project + current_project = self.parent.context.project if current_project: # If a Project is defined in the current context, retrieve # Software entities that have either no Project restrictions OR @@ -253,7 +253,7 @@ def _get_sg_software_entities(self): # Now Group and User restrictions. Always retrieve Software entities # that have no Group or User restrictions. - current_user = self._tk_app.context.user + current_user = self.parent.context.user # No user restriction filter. user_group_filter = ["user_restrictions", "is", None] @@ -302,19 +302,19 @@ def _get_sg_software_entities(self): ] # Add any user defined fields to the list of fields we should request. - sw_fields += self._tk_app.get_setting("software_entity_extra_fields") + sw_fields += self.parent.get_setting("software_entity_extra_fields") # Log the resolved filter. - self._tk_app.log_debug( + self.parent.log_debug( "Searching for Software entities matching filters:\n%s" % (pprint.pformat(sw_filters, indent=4),) ) - sw_entities = self._tk_app.shotgun.find("Software", sw_filters, sw_fields) + sw_entities = self.parent.shotgun.find("Software", sw_filters, sw_fields) if not sw_entities: # No Entities found matching filters, nothing to do. - self._tk_app.log_debug("No matching SG Software entities found.") + self.parent.log_debug("No matching SG Software entities found.") else: - self._tk_app.log_debug( + self.parent.log_debug( "Got software data from ShotGrid:\n%s" % pprint.pformat(sw_entities) ) @@ -356,14 +356,14 @@ def _scan_for_software_and_register( """ # No application path was specified, triggering "auto discovery" mode. Attempt to # find relevant application path(s) from the engine launcher. - self._tk_app.log_debug( + self.parent.log_debug( "Attempting to auto discover software for %s." % engine_str ) software_versions = self._scan_for_software( engine_str, dcc_versions, dcc_products ) - self._tk_app.log_debug( + self.parent.log_debug( "Scan detected %d software versions" % len(software_versions) ) @@ -375,15 +375,15 @@ def _scan_for_software_and_register( if len(sorted_versions) > 1 and is_group_default: # there is more than one match and we have requested that this is the # group default. In this case make the highest version the group default. - self._tk_app.log_debug( + self.parent.log_debug( "Multiple matches for the group default. Will use the highest version " "number as the default." ) for software_version in software_versions: # run before launch hook - self._tk_app.log_debug("Running before register command hook...") - launch_engine_str = self._tk_app.execute_hook_method( + self.parent.log_debug("Running before register command hook...") + launch_engine_str = self.parent.execute_hook_method( "hook_before_register_command", "determine_engine_instance_name", software_version=software_version, @@ -400,7 +400,7 @@ def _scan_for_software_and_register( # is determined by the hook. We need to check for the same possible # issue here since the requested engine instance has changed. if launch_engine_str != engine_str: - self._tk_app.logger.debug( + self.parent.logger.debug( "The before_register_command hook changed the engine instance " "to be %s.", launch_engine_str, @@ -410,10 +410,10 @@ def _scan_for_software_and_register( # We don't need the returned env and descriptor. We only # care whether it raises or not. sgtk.platform.engine.get_env_and_descriptor_for_engine( - launch_engine_str, self._tk_app.sgtk, self._tk_app.context + launch_engine_str, self.parent.sgtk, self.parent.context ) except sgtk.platform.TankMissingEngineError: - self._tk_app.logger.debug( + self.parent.logger.debug( "The engine instance requested by before_register_command (%s) " "does not exist in the current environment. The launcher will " "not be registered as a result.", @@ -423,8 +423,8 @@ def _scan_for_software_and_register( # We need to check to see if the engine instance associated with # the launch command is something we've been configured to skip. - if launch_engine_str in self._tk_app.get_setting("skip_engine_instances"): - self._tk_app.logger.debug( + if launch_engine_str in self.parent.get_setting("skip_engine_instances"): + self.parent.logger.debug( "The %s engine instance has been configured to be skipped by way " "of the skip_engine_instances app setting. The launcher command " "for %r will not be registered.", @@ -506,7 +506,7 @@ def _manual_register( if len(sorted_versions) > 1 and is_group_default: # there is more than one match and we have requested that this is the # group default. In this case make the highest version the group default. - self._tk_app.log_debug( + self.parent.log_debug( "Multiple matches for the group default. Will use the highest version " "number as the default." ) @@ -558,24 +558,24 @@ def _extract_thumbnail(self, entity_type, entity_id, sg_thumb_url): :param sg_thumb_url: The thumbnail url for the given record :returns: path to local image """ - self._tk_app.log_debug( + self.parent.log_debug( "Attempting to extract high res thumbnail from %s %s" % (entity_type, entity_id) ) default_thumbnail_location = os.path.join( - self._tk_app.disk_location, "icon_256.png" + self.parent.disk_location, "icon_256.png" ) if sg_thumb_url is None: - self._tk_app.log_debug( + self.parent.log_debug( "No thumbnail is set in ShotGrid. Falling back on default." ) # use the launch app icon return default_thumbnail_location - if not self._tk_app.engine.has_ui: - self._tk_app.log_debug( + if not self.parent.engine.has_ui: + self.parent.log_debug( "Runtime environment does not have Qt. Skipping extraction." ) # use the launch app icon @@ -584,26 +584,25 @@ def _extract_thumbnail(self, entity_type, entity_id, sg_thumb_url): # all good to go - download the target icon # Import sgutils after ui has been confirmed because it has dependencies on Qt. - shotgun_data = sgtk.platform.import_framework( - "tk-framework-shotgunutils", "shotgun_data" - ) + fw = self.load_framework("tk-framework-shotgunutils_v5.x.x") + shotgun_data = fw.import_module("shotgun_data") # Download the Software thumbnail source from Shotgun and cache for reuse. - self._tk_app.log_debug( + self.parent.log_debug( "Downloading app icon from %s %s ..." % (entity_type, entity_id) ) try: icon_path = shotgun_data.ShotgunDataRetriever.download_thumbnail_source( - entity_type, entity_id, self._tk_app + entity_type, entity_id, self.parent ) except Exception: - self._tk_app.logger.exception( + self.parent.logger.exception( "There was a problem downloading the thumbnail:" ) return default_thumbnail_location else: - self._tk_app.log_debug("...download complete: %s" % icon_path) + self.parent.log_debug("...download complete: %s" % icon_path) return icon_path @@ -629,18 +628,18 @@ def _scan_for_software(self, engine, versions, products): """ # First try to construct the engine launcher for the specified engine. try: - self._tk_app.log_debug("Initializing engine launcher for %s." % engine) + self.parent.log_debug("Initializing engine launcher for %s." % engine) engine_launcher = sgtk.platform.create_engine_launcher( - self._tk_app.sgtk, self._tk_app.context, engine, versions, products + self.parent.sgtk, self.parent.context, engine, versions, products ) if not engine_launcher: - self._tk_app.log_debug( + self.parent.log_debug( "Toolkit engine %s does not support scanning for local DCC " "applications." % engine ) return [] except Exception as e: - self._tk_app.log_debug( + self.parent.log_debug( "Unable to construct engine launcher for %s. Cannot determine " "corresponding DCC application information:\n%s" % (engine, e) ) @@ -648,12 +647,12 @@ def _scan_for_software(self, engine, versions, products): # Next try to scan for available applications for this engine. try: - self._tk_app.log_debug( + self.parent.log_debug( "Scanning for Toolkit engine %s local applications." % engine ) software_versions = engine_launcher.scan_software() except Exception as e: - self._tk_app.log_warning( + self.parent.log_warning( "Caught unexpected error scanning for DCC applications corresponding " "to Toolkit engine %s:\n%s\n%s" % (engine, e, traceback.format_exc()) ) @@ -668,7 +667,7 @@ def __get_sg_server_version(self): :returns: Tuple of (major, minor, patch) versions. """ - sg_major_ver = self._tk_app.shotgun.server_info["version"][0] - sg_minor_ver = self._tk_app.shotgun.server_info["version"][1] - sg_patch_ver = self._tk_app.shotgun.server_info["version"][2] + sg_major_ver = self.parent.shotgun.server_info["version"][0] + sg_minor_ver = self.parent.shotgun.server_info["version"][1] + sg_patch_ver = self.parent.shotgun.server_info["version"][2] return sg_major_ver, sg_minor_ver, sg_patch_ver diff --git a/info.yml b/info.yml index 71589e6..c038f40 100644 --- a/info.yml +++ b/info.yml @@ -10,33 +10,26 @@ # Metadata defining the behaviour and requirements for this app - # expected fields in the configuration file for this app configuration: - - use_software_entity: - type: bool - description: Whether to use ShotGrid Software Entities to register - launch commands for available DCCs. - default_value: false - software_entity_extra_fields: type: list allows_empty: True values: type: str default_value: [] - description: "A list of Software entity field code names that should be fetched and - and passed to the hooks as part of the Software entity dictionary. - The main fields are already fetched and provided by default, but this - allows custom fields to be specified without needing to perform a second - look up in ShotGrid." + description: + "A list of Software entity field code names that should be fetched and + and passed to the hooks as part of the Software entity dictionary. + The main fields are already fetched and provided by default, but this + allows custom fields to be specified without needing to perform a second + look up in ShotGrid." scan_all_projects: type: bool description: When true, and when use_software_entity is true, launchers - will be registered for all projects instead of only the - current environment's project. + will be registered for all projects instead of only the + current environment's project. default_value: false menu_name: @@ -45,15 +38,16 @@ configuration: default_value: "" icon: - type: str - description: "The path to the icon to appear for the application. If the - value starts with '{target_engine}' then the remainder of the - path will be relative to the root of the engine's install. If - the value starts with '{config_path}' then the remainder of the - path will be relative to the root of the configuration. The - path should use forward slashes which will be replaced with - the correct operating system separator when used." - default_value: "{target_engine}/icon_256.png" + type: str + description: + "The path to the icon to appear for the application. If the + value starts with '{target_engine}' then the remainder of the + path will be relative to the root of the engine's install. If + the value starts with '{config_path}' then the remainder of the + path will be relative to the root of the configuration. The + path should use forward slashes which will be replaced with + the correct operating system separator when used." + default_value: "{target_engine}/icon_256.png" # Path information for multiple platforms windows_path: @@ -88,10 +82,11 @@ configuration: engine: type: str - description: "The name of the ShotGrid engine to start. This is typically the application - name prefixed with tk, e.g. tk-maya, tk-nuke, tk-photoshop etc. If you set - this to an empty string, no toolkit engine will be started, meaning that - you can launch applications that do not have toolkit engines set up." + description: + "The name of the ShotGrid engine to start. This is typically the application + name prefixed with tk, e.g. tk-maya, tk-nuke, tk-photoshop etc. If you set + this to an empty string, no toolkit engine will be started, meaning that + you can launch applications that do not have toolkit engines set up." default_value: "" group: @@ -101,33 +96,35 @@ configuration: group_default: type: bool - description: "Boolean value indicating whether this command should represent the group as - a whole. Setting this value to True indicates that this is the command to run and - display in applications that show a single button for each named group." + description: + "Boolean value indicating whether this command should represent the group as + a whole. Setting this value to True indicates that this is the command to run and + display in applications that show a single button for each named group." default_value: False defer_keyword: type: str default_value: "" - description: "Advanced parameter. This allows for advanced customization around deferred folder creation. - Deferred folder creation allows for the creation of partial subfolder structures depending - on a specific keyword (see main documentation for details). Before an app is launched, folders - are automatically created and by default (e.g. if you leave this setting as null), - the launch app will pass the name of the engine as the deferred folder creation keyword. - This makes it easy to set up deferred rules in your folder creation config for tk-maya, - tk-nuke etc. However, if you for example wanted to set up specific deferred folder structures - for Nuke and Nuke X (both running the nuke engine), you need a finer granularity. This setting - can then be used to override the default behaviour of just passing the engine name. Instead, you - can pass any string into the deferred folder creation (for example 'nuke' and 'nuke_x' in the case - above). This parameter is also useful if you want to use deferred folder creation in - conjunction with launching of apps which do not have a toolkit engine defined - for these - app launch instances, the engine setting is left blank, and therefore no engine name is passed - into the deferred folder creation. In such cases you can utilize this parameter to control - the deferred folder creation. - - You can specify multiple keywords using a comma as a delimiter (eg. 'nuke, nuke_x'). - This will trigger folder creation for folders in your schema that have any - of these values in their deferred folder creation setting." + description: + "Advanced parameter. This allows for advanced customization around deferred folder creation. + Deferred folder creation allows for the creation of partial subfolder structures depending + on a specific keyword (see main documentation for details). Before an app is launched, folders + are automatically created and by default (e.g. if you leave this setting as null), + the launch app will pass the name of the engine as the deferred folder creation keyword. + This makes it easy to set up deferred rules in your folder creation config for tk-maya, + tk-nuke etc. However, if you for example wanted to set up specific deferred folder structures + for Nuke and Nuke X (both running the nuke engine), you need a finer granularity. This setting + can then be used to override the default behaviour of just passing the engine name. Instead, you + can pass any string into the deferred folder creation (for example 'nuke' and 'nuke_x' in the case + above). This parameter is also useful if you want to use deferred folder creation in + conjunction with launching of apps which do not have a toolkit engine defined - for these + app launch instances, the engine setting is left blank, and therefore no engine name is passed + into the deferred folder creation. In such cases you can utilize this parameter to control + the deferred folder creation. + + You can specify multiple keywords using a comma as a delimiter (eg. 'nuke, nuke_x'). + This will trigger folder creation for folders in your schema that have any + of these values in their deferred folder creation setting." versions: type: list @@ -135,65 +132,78 @@ configuration: values: type: str default_value: [] - description: "A list of strings that will be used to substitute for the {version} token - in the values for the other settings for the instance of this app. For - example, a value of ['2012', '2013', '2014'] for this setting and a value of - 'Launch Maya {version}' for menu_name would result in the following command - menu names being registered: Launch Maya 2012, Launch Maya 2013, Launch - Maya 2014. The first version in the list will be considered the 'default' - version if the engine running the app supports the concept. You can use pieces - of the version if you wrap the parts in parenthesis like ['(7.0)v3', '(8.0)v1']. - These pieces of the version string are available in the other settings via the - {v0}, {v1}, {v2}, ... replacement tokens." + description: + "A list of strings that will be used to substitute for the {version} token + in the values for the other settings for the instance of this app. For + example, a value of ['2012', '2013', '2014'] for this setting and a value of + 'Launch Maya {version}' for menu_name would result in the following command + menu names being registered: Launch Maya 2012, Launch Maya 2013, Launch + Maya 2014. The first version in the list will be considered the 'default' + version if the engine running the app supports the concept. You can use pieces + of the version if you wrap the parts in parenthesis like ['(7.0)v3', '(8.0)v1']. + These pieces of the version string are available in the other settings via the + {v0}, {v1}, {v2}, ... replacement tokens." skip_engine_instances: type: list allows_empty: True values: - type: str + type: str default_value: [] - description: "A list of string names of engine instances to skip registering Software - launcher commands for. For example, a value of ['tk-nukestudio'] for this - setting would stop any Software entity launcher commands being registered - with the engine that reference the 'tk-nukestudio' engine instance in that - environment. This can be used to allow for an engine instance to be configured - for a given environment without a launcher existing for it. This is useful - in the case of Nuke Studio, where we want to allow for context changes into - certain environments, but we only want the application to be launchable from - a project environment. This setting only has an impact on Software entity - launchers." + description: + "A list of string names of engine instances to skip registering Software + launcher commands for. For example, a value of ['tk-nukestudio'] for this + setting would stop any Software entity launcher commands being registered + with the engine that reference the 'tk-nukestudio' engine instance in that + environment. This can be used to allow for an engine instance to be configured + for a given environment without a launcher existing for it. This is useful + in the case of Nuke Studio, where we want to allow for context changes into + certain environments, but we only want the application to be launchable from + a project environment. This setting only has an impact on Software entity + launchers." extra: type: dict - description: "ShotGrid engine specific extra values. These are defined per ShotGrid engine. - Please look in the app documentation for more details." + description: + "ShotGrid engine specific extra values. These are defined per ShotGrid engine. + Please look in the app documentation for more details." default_value: {} hook_app_launch: type: hook - default_value: app_launch - description: "Called to launch the application. This hook contains the code that does - the actual execution of the launch command and parameters. If you have - a custom launcher system in your studio, it can be handy to override - Tank's default launch behaviour." + default_value: "{self}/app_launch.py" + description: + "Called to launch the application. This hook contains the code that does + the actual execution of the launch command and parameters. If you have + a custom launcher system in your studio, it can be handy to override + Tank's default launch behaviour." hook_before_app_launch: type: hook - default_value: before_app_launch - description: "This hook is called just before the hook_app_launch is used and can be - useful if you don't want to modify the way applications are being launched - (which is advanced usage and can be done by overriding the app_launch hook), - but merely want to modify the environment before app launch. You may want - to add additional pipeline paths, APIs or other things to the setup, or - specify additional scripts etc to run." + default_value: "{self}/before_app_launch.py" + description: + "This hook is called just before the hook_app_launch is used and can be + useful if you don't want to modify the way applications are being launched + (which is advanced usage and can be done by overriding the app_launch hook), + but merely want to modify the environment before app launch. You may want + to add additional pipeline paths, APIs or other things to the setup, or + specify additional scripts etc to run." hook_before_register_command: type: hook - default_value: before_register_command - description: "This hook is called just before launcher command registration occurs. - it can be used to alter the engine instance name associated with the - launcher should that be required. This hook's methods are only called - when Software entity launchers are being used." + default_value: "{self}/before_register_command.py" + description: + "This hook is called just before launcher command registration occurs. + it can be used to alter the engine instance name associated with the + launcher should that be required. This hook's methods are only called + when Software entity launchers are being used." + + hook_launcher: + type: hook + default_value: "{self}/single_config_launcher.py" + description: + "This hook controls the method by which applications are collected + and launched." # the Shotgun fields that this app needs in order to operate correctly requires_shotgun_fields: @@ -211,4 +221,8 @@ requires_engine_version: supported_engines: frameworks: - - {"name": "tk-framework-shotgunutils", "version": "v5.x.x", "minimum_version": "v5.1.0"} + - { + "name": "tk-framework-shotgunutils", + "version": "v5.x.x", + "minimum_version": "v5.1.0", + } diff --git a/python/tk_multi_launchapp/__init__.py b/python/tk_multi_launchapp/__init__.py index c79c9c5..6f06f0c 100644 --- a/python/tk_multi_launchapp/__init__.py +++ b/python/tk_multi_launchapp/__init__.py @@ -8,5 +8,4 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .single_config_launcher import SingleConfigLauncher -from .software_entity_launcher import SoftwareEntityLauncher +from .base_launcher import BaseLauncher diff --git a/python/tk_multi_launchapp/base_launcher.py b/python/tk_multi_launchapp/base_launcher.py index 3caebdc..881ee68 100644 --- a/python/tk_multi_launchapp/base_launcher.py +++ b/python/tk_multi_launchapp/base_launcher.py @@ -19,8 +19,10 @@ from .util import clear_dll_directory, restore_dll_directory from .prepare_apps import prepare_launch_for_engine +HookBaseClass = sgtk.get_hook_baseclass() -class BaseLauncher(object): + +class BaseLauncher(HookBaseClass): """ Functionality to register engine commands that launch DCC applications, as well as the business logic to perform the launch. @@ -29,12 +31,10 @@ class BaseLauncher(object): of sources. """ - def __init__(self): + def init(self): """ Initialize members """ - # Retrieve the TK Application from the current bundle - self._tk_app = sgtk.platform.current_bundle() # Store the current platform value self._platform_name = ( @@ -45,6 +45,8 @@ def __init__(self): else "windows" ) + return self + def _register_launch_command( self, app_menu_name, @@ -111,7 +113,7 @@ def _register_launch_command( "shotgun_publishedfile", "shotgun_version", ] - if self._tk_app.engine.environment.get("name") not in skip_environments: + if self.parent.engine.environment.get("name") not in skip_environments: properties = { "title": menu_name, "short_name": command_name, @@ -138,11 +140,11 @@ def launch_version(*args, **kwargs): **kwargs ) - self._tk_app.log_debug( + self.parent.log_debug( "Registering command %s to launch %s with args %s for engine %s" % (command_name, app_path, app_args, app_engine) ) - self._tk_app.engine.register_command( + self.parent.engine.register_command( command_name, launch_version, properties ) @@ -199,8 +201,8 @@ def _launch_app( version_string = get_clean_version_string(version) # run before launch hook - self._tk_app.log_debug("Running before app launch hook...") - self._tk_app.execute_hook( + self.parent.log_debug("Running before app launch hook...") + self.parent.execute_hook( "hook_before_app_launch", app_path=app_path, app_args=app_args, @@ -217,10 +219,10 @@ def _launch_app( dll_directory_cache = clear_dll_directory() try: # Launch the application - self._tk_app.log_debug( + self.parent.log_debug( "Launching executable '%s' with args '%s'" % (app_path, app_args) ) - result = self._tk_app.execute_hook( + result = self.parent.execute_hook( "hook_app_launch", app_path=app_path, app_args=app_args, @@ -233,35 +235,35 @@ def _launch_app( finally: restore_dll_directory(dll_directory_cache) - self._tk_app.log_debug("Hook tried to launch '%s'" % launch_cmd) + self.parent.log_debug("Hook tried to launch '%s'" % launch_cmd) if return_code != 0: # some special logic here to decide how to display failure feedback if app_engine == "tk-shotgun": # for the shotgun engine, use the log info in order to # get the proper html formatting - self._tk_app.log_info( + self.parent.log_info( "Failed to launch application! " "This is most likely because the path is not set correctly." "The command that was used to attempt to launch is '%s'. " "

Click here to learn " "more about how to setup your app launch configuration." - % (launch_cmd, self._tk_app.HELP_DOC_URL) + % (launch_cmd, self.parent.HELP_DOC_URL) ) - elif self._tk_app.engine.has_ui: + elif self.parent.engine.has_ui: # got UI support. Launch dialog with nice message from ..not_found_dialog import show_path_error_dialog - show_path_error_dialog(self._tk_app, launch_cmd) + show_path_error_dialog(self.parent, launch_cmd) else: # traditional non-ui environment without any html support. - self._tk_app.log_error( + self.parent.log_error( "Failed to launch application! This is most likely because " "the path is not set correctly. The command that was used " "to attempt to launch is '%s'. To learn more about how to " "set up your app launch configuration, see the following " - "documentation: %s" % (launch_cmd, self._tk_app.HELP_DOC_URL) + "documentation: %s" % (launch_cmd, self.parent.HELP_DOC_URL) ) else: @@ -299,12 +301,12 @@ def _register_event_log(self, menu_name, app_engine, ctx, command_executed): launch the DCC. """ meta = {} - meta["core"] = self._tk_app.sgtk.version + meta["core"] = self.parent.sgtk.version meta["engine"] = "%s %s" % ( - self._tk_app.engine.name, - self._tk_app.engine.version, + self.parent.engine.name, + self.parent.engine.version, ) - meta["app"] = "%s %s" % (self._tk_app.name, self._tk_app.version) + meta["app"] = "%s %s" % (self.parent.name, self.parent.version) meta["launched_engine"] = app_engine meta["command"] = command_executed # In Python 3 and certain flavors of Python 2, the sys.platform value under Linux @@ -320,9 +322,9 @@ def _register_event_log(self, menu_name, app_engine, ctx, command_executed): ) if ctx.task: meta["task"] = ctx.task["id"] - desc = "%s %s: %s" % (self._tk_app.name, self._tk_app.version, menu_name) + desc = "%s %s: %s" % (self.parent.name, self.parent.version, menu_name) sgtk.util.create_event_log_entry( - self._tk_app.sgtk, ctx, "Toolkit_App_Startup", desc, meta + self.parent.sgtk, ctx, "Toolkit_App_Startup", desc, meta ) def _launch_callback( @@ -351,26 +353,26 @@ def _launch_callback( this launch command. """ # Verify a Project is defined in the context. - if self._tk_app.context.project is None: + if self.parent.context.project is None: raise TankError( "Your context does not have a project defined. Cannot continue." ) # Extract an entity type and id from the context. - entity_type = self._tk_app.context.project["type"] - entity_id = self._tk_app.context.project["id"] + entity_type = self.parent.context.project["type"] + entity_id = self.parent.context.project["id"] # if there is an entity then that takes precedence - if self._tk_app.context.entity: - entity_type = self._tk_app.context.entity["type"] - entity_id = self._tk_app.context.entity["id"] + if self.parent.context.entity: + entity_type = self.parent.context.entity["type"] + entity_id = self.parent.context.entity["id"] # and if there is a task that is even better - if self._tk_app.context.task: - entity_type = self._tk_app.context.task["type"] - entity_id = self._tk_app.context.task["id"] + if self.parent.context.task: + entity_type = self.parent.context.task["type"] + entity_id = self.parent.context.task["id"] - if len(self._tk_app.sgtk.roots) == 0: + if len(self.parent.sgtk.roots) == 0: # configuration doesn't have any filesystem roots defined - self._tk_app.log_debug( + self.parent.log_debug( "Configuration does not have any filesystem roots defined. " "Skipping folder creation." ) @@ -379,13 +381,13 @@ def _launch_callback( # Do the folder creation. If there is a specific defer keyword, # this takes precedence. Otherwise, use the engine name for the # DCC application by default. - defer_keyword = self._tk_app.get_setting("defer_keyword") or app_engine + defer_keyword = self.parent.get_setting("defer_keyword") or app_engine try: - self._tk_app.log_debug( + self.parent.log_debug( "Creating folders for %s %s. Defer keyword: '%s'" % (entity_type, entity_id, defer_keyword) ) - self._tk_app.sgtk.create_filesystem_structure( + self.parent.sgtk.create_filesystem_structure( entity_type, entity_id, engine=defer_keyword ) except sgtk.TankError as err: @@ -399,7 +401,7 @@ def _launch_callback( app_engine, app_path, app_args, - self._tk_app.context, + self.parent.context, version, file_to_open, software_entity,