diff --git a/com.shotgunsoftware.basic.adobecc.version b/com.shotgunsoftware.basic.adobecc.version
new file mode 100644
index 0000000..95e94cd
--- /dev/null
+++ b/com.shotgunsoftware.basic.adobecc.version
@@ -0,0 +1 @@
+v0.0.1
\ No newline at end of file
diff --git a/com.shotgunsoftware.basic.adobecc.zxp b/com.shotgunsoftware.basic.adobecc.zxp
new file mode 100644
index 0000000..c0a9a45
Binary files /dev/null and b/com.shotgunsoftware.basic.adobecc.zxp differ
diff --git a/developer/build_extension.py b/developer/build_extension.py
new file mode 100755
index 0000000..54fb4e0
--- /dev/null
+++ b/developer/build_extension.py
@@ -0,0 +1,417 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2016 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 argparse
+import os
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+
+# global placeholder for when we import sgtk
+sgtk = None
+logger = None
+
+# bundle cache dir name within the built plugin
+BUNDLE_CACHE_DIR = "bundle_cache"
+
+# the build script located in the core repo
+CORE_BUILD_SCRIPT = os.path.join("developer", "build_plugin.py")
+
+# timestamp url for signing the extension.
+# see: http://www.davidebarranca.com/2014/05/html-panels-tips-10-packaging-zxp-installers/
+TIMESTAMP_URL = "https://timestamp.geotrust.com/tsa"
+
+
+def main():
+ """
+ Wraps all the steps to build and sign the extension
+ """
+
+ # parse and validate the command line args
+ args = _validate_args(_parse_args())
+
+ # first step is to build the plugin
+ _build_plugin(args)
+
+ # remove the bundle cache unless specified
+ if not args["bundle_cache"]:
+ _remove_bundle_cache(args)
+
+ # add a version file to the built plugin
+ _write_version_file(args)
+
+ # sign the newly built plugin and create the .ZXP file
+ if args["sign"]:
+ _sign_plugin(args)
+ else:
+ logger.warning(
+ "Not signing the built plugin. This build can not be released!"
+ )
+
+ logger.info("Build successful.")
+
+
+def _build_plugin(args):
+ """
+ First step is to build the plugin itself.
+
+ Adds the extension output dir to the args dict.
+ """
+
+ # construct the full extension output directory
+ plugin_build_dir = os.path.abspath(
+ os.path.join(
+ args["output_dir"],
+ args["extension_name"]
+ )
+ )
+
+ # the full plugin build script command
+ command = "{py} {build_script} '{plugin_dir}' '{plugin_build_dir}'".format(
+ py=sys.executable,
+ build_script=os.path.join(args["core"], CORE_BUILD_SCRIPT),
+ plugin_dir=args["plugin_dir"],
+ plugin_build_dir=plugin_build_dir,
+ )
+
+ # execute the build script
+ logger.info("Plugin build command: %s" % (command,))
+ logger.info("Executing plugin build command...")
+ status = subprocess.call(shlex.split(command))
+
+ # check the return status
+ if status:
+ logger.error("Error building the plugin.")
+ raise Exception("There was a problem building the plugin.")
+
+ # add the full ext dir path to the args dict
+ args["plugin_build_dir"] = plugin_build_dir
+ logger.info("Built plugin: %s" % (args["plugin_build_dir"],))
+
+
+def _parse_args():
+ """
+ Define the parser and return the parsed args.
+ """
+
+ parser = argparse.ArgumentParser(
+ description="Build and package an Adobe extension for the " +
+ "engine. This includes signing the extension with the " +
+ "supplied certificate. The extension will be built " +
+ "in the engine repo unless an output directory is " +
+ "specified."
+ )
+
+ parser.add_argument(
+ "--core", "-c",
+ metavar="/path/to/tk-core",
+ help="The path to tk-core to use when building the toolkit plugin.",
+ required=True,
+ )
+
+ parser.add_argument(
+ "--plugin_name", "-p",
+ metavar="name",
+ help="The name of the engine plugin to build. Ex: 'basic'.",
+ required=True,
+ )
+
+ parser.add_argument(
+ "--extension_name", "-e",
+ metavar="name",
+ help="The name of the output extension. Ex: " +
+ "'com.shotgunsoftware.basic.photoshopcc'",
+ required=True,
+ )
+
+ parser.add_argument(
+ "--sign", "-s",
+ nargs=3,
+ metavar=("/path/to/ZXPSignCmd", "/path/to/certificate", "password"),
+ help="If supplied, sign the build extension. Requires 3 arguments: " +
+ "the path to the 'ZXPSignCmd', the certificate and the password." +
+ "Note, the ZXPSignCmd can be downloaded here: " +
+ "http://labs.adobe.com/downloads/extensionbuilder3.html",
+ )
+
+ parser.add_argument(
+ "--bundle_cache", "-b",
+ action="store_true",
+ help="If supplied, include the 'bundle_cache' directory in the build " +
+ "plugin. If not, it is removed after the build.",
+ )
+
+ parser.add_argument(
+ "--version", "-v",
+ metavar="v#.#.#",
+ help="The version to attached to the built plugin. If not specified, " +
+ "the version will be set to 'dev' and will override any version " +
+ "of the extension at launch/install time. The current version " +
+ "can be found in the .version file that lives next to the " +
+ "existing .zxp file."
+ )
+
+ parser.add_argument(
+ "--output_dir", "-o",
+ metavar="/path/to/output/extension",
+ help="If supplied, output the built extension here. If not supplied, " +
+ "the extension will be built in the engine directory at the top " +
+ "level.",
+ )
+
+ return parser.parse_args()
+
+def _remove_bundle_cache(args):
+ """
+ Remove the built extensions bundle cache.
+ """
+
+ logger.info("Removing bundle cache from built extension...")
+ bundle_cache_dir = os.path.join(
+ args["plugin_build_dir"],
+ BUNDLE_CACHE_DIR
+ )
+ try:
+ shutil.rmtree(bundle_cache_dir)
+ except Exception, e:
+ logger.warning("Failed to remove bundle cache from extension.")
+
+
+def _sign_plugin(args):
+ """
+ Sign the built plugin (creates .zxp) and cleanup the build directory.
+ """
+
+ # extension_path is same as the plugin build dir with the .zxp extension
+ extension_path = "%s.zxp" % (args["plugin_build_dir"],)
+
+ # remove the existing build file
+ if os.path.exists(extension_path):
+ from sgtk.util.filesystem import safe_delete_file
+ safe_delete_file(extension_path)
+
+ (sign_command, certificate_path, certificate_pwd) = args["sign"]
+
+ cmd_str = "{zxp_sign} -sign {plugin_build_dir} {extension_path} {cert} " + \
+ "{cert_pwd} -tsa {tsa}"
+ command = cmd_str.format(
+ zxp_sign=sign_command,
+ plugin_build_dir=args["plugin_build_dir"],
+ extension_path=extension_path,
+ cert=certificate_path,
+ cert_pwd=certificate_pwd,
+ tsa=TIMESTAMP_URL,
+ )
+
+ # execute the build script
+ logger.info("Signing extension command: %s" % (command,))
+ logger.info("Signing the extension...")
+ status = subprocess.call(shlex.split(command))
+
+ # check the return status
+ if status:
+ logger.error("Error signing the extension.")
+ raise Exception("There was a problem signing the extension.")
+
+ # clean up the plugin build directory
+ try:
+ shutil.rmtree(args["plugin_build_dir"])
+ except Exception, e:
+ logger.warning("Failed to remove plugin build dir.")
+
+ # add the signed extension path to the args
+ args["extension_path"] = extension_path
+ logger.info("Signed extension: %s" % (args["extension_path"],))
+
+def _validate_args(args):
+ """
+ Validate the parsed args. Will raise if there are errors.
+
+ Sets up the logger if core can be imported.
+
+ Adds some additional values based on supplied args including the engine
+ directory, plugin path, etc.
+
+ Returns a dictionary of the parsed arguments of the following form:
+
+ {
+ 'core': '/path/to/tk-core',
+ 'extension_name': 'extesion.name.here',
+ 'bundle_cache': True,
+ 'plugin_name': 'plugin_name',
+ 'sign': ['/path/to/ZXPSignCmd', '/path/to/cert', 'cert_password'],
+ 'version': 'v1.0.0',
+ 'output_dir': '/path/to/output/dir',
+ 'engine_dir': '/path/to/the/engine/repo',
+ 'plugin_dir': '/path/to/the/engine/plugin',
+ }
+ """
+
+ # convert the args namespace to a dict
+ args = vars(args)
+
+ # ensure core path exists and build script is there
+ if not os.path.exists(args["core"]):
+ raise Exception(
+ "Supplied core path does not exist: %s" % (args["core"],)
+ )
+
+ # make sure we can import core
+ try:
+ sgtk_dir = os.path.join(args["core"], "python")
+ sys.path.insert(0, sgtk_dir) # make sure this one is found first
+ import sgtk as imported_sgtk
+ global sgtk
+ sgtk = imported_sgtk
+ except Exception, e:
+ raise Exception("Error import supplied core: %s" % (e,))
+
+ # setup the logger for use from here on out
+ try:
+ # set up std toolkit logging to file
+ sgtk.LogManager().initialize_base_file_handler("build_plugin")
+
+ # set up output of all sgtk log messages to stdout
+ sgtk.LogManager().initialize_custom_handler()
+
+ global logger
+ logger = sgtk.LogManager.get_logger("build_extension")
+
+ except Exception, e:
+ raise Exception("Error creating toolkit logger: %s" % (e,))
+
+ logger.info("Validating command line arguments...")
+
+ # ensure the core plugin build script exists
+ logger.info("Finding plugin build script...")
+ build_script = os.path.join(args["core"], CORE_BUILD_SCRIPT)
+ if not os.path.exists(build_script):
+ raise Exception(
+ "Could not find plugin build script in supplied core: %s" %
+ (build_script,)
+ )
+
+ # ensure the extension name is valid
+ logger.info("Ensuring valid plugin & extension build names...")
+ from sgtk.util.filesystem import create_valid_filename
+ args["extension_name"] = create_valid_filename(args["extension_name"])
+ logger.info("Extension name: %s" % (args["extension_name"]))
+
+ # make sure version is valid
+ logger.info("Verifying supplied version...")
+ if args["version"]:
+ if not re.match(r"^v\d+\.\d+\.\d+$", args["version"]):
+ raise Exception(
+ "Supplied version doesn't match the format 'v#.#.#'. Supplied: %s" %
+ (args["version"],)
+ )
+ else:
+ args["version"] = "dev"
+
+ # if signing requested, validate those args
+ if args["sign"]:
+
+ logger.info("Verifying 'ZXPSignCmd` path...")
+ if not os.path.exists(args["sign"][0]):
+ raise Exception(
+ "The supplied 'ZXPSignCmd' does not exist. Supplied path: %s " %
+ (args["sign"][0],)
+ )
+
+ logger.info("Verifying certificate path...")
+ if not os.path.exists(args["sign"][1]):
+ raise Exception(
+ "The supplied certificate does not exist. Supplied path: %s " %
+ (args["sign"][1],)
+ )
+
+ # get the full path to the engine repo
+ logger.info("Populating the engine directory...")
+ args["engine_dir"] = os.path.abspath(
+ os.path.join(
+ os.path.dirname(__file__),
+ os.pardir
+ )
+ )
+
+ # ensure the plugin can be found in the engine
+ logger.info("Validating plugin name...")
+ plugin_dir = os.path.join(
+ args["engine_dir"],
+ "plugins",
+ args["plugin_name"]
+ )
+ if not os.path.exists(plugin_dir):
+ raise Exception(
+ "Could not find plugin '%s' in engine." % (args["plugin_name"],)
+ )
+ args["plugin_dir"] = plugin_dir
+
+ # if output dir defined, ensure it exists. populate args with engine dir
+ # if not.
+ logger.info("Determining output directory...")
+ if args["output_dir"]:
+ if not os.path.exists(args["output_dir"]):
+ from sgtk.util.filesystem import ensure_folder_exists
+ ensure_folder_exists(args["output_dir"])
+ else:
+ args["output_dir"] = args["engine_dir"]
+
+ # return the validate args
+ logger.info("Command line arguments validated.")
+ return args
+
+
+def _write_version_file(args):
+ """
+ Write a file to the built plugin directory containing the specified version.
+
+ Also write the file to the top-level of the engine repo to make it possible
+ to easily compare during install.
+ """
+
+ # the file to create with the specified version
+ bundle_version_file_path = os.path.join(
+ args["plugin_build_dir"],
+ "%s.%s" % (args["extension_name"], "version")
+ )
+
+ # write the file
+ logger.info("Writing build version info file...")
+ with open(bundle_version_file_path, "w") as bundle_version_file:
+ bundle_version_file.write(args["version"])
+
+ engine_file_path = os.path.join(
+ args["engine_dir"],
+ "%s.%s" % (args["extension_name"], "version"),
+ )
+
+ # write the file
+ logger.info("Writing build version info file...")
+ with open(engine_file_path, "w") as engine_version_file:
+ engine_version_file.write(args["version"])
+
+
+if __name__ == "__main__":
+
+ exit_code = 1
+ try:
+ exit_code = main()
+ except Exception, e:
+ print "ERROR: %s" % (e,)
+ else:
+ logger.info("Extension successfully built!")
+
+ sys.exit(exit_code)
+
diff --git a/engine.py b/engine.py
index 476cd89..ff77e03 100644
--- a/engine.py
+++ b/engine.py
@@ -287,9 +287,9 @@ def _handle_active_document_change(self, active_document_path):
return
if active_document_path:
- self.log_debug("New active document is %s" % active_document_path)
+ self.logger.debug("New active document is %s" % active_document_path)
else:
- self.log_debug(
+ self.logger.debug(
"New active document check failed. This is likely due to the "
"new active document being in an unsaved state."
)
@@ -304,15 +304,18 @@ def _handle_active_document_change(self, active_document_path):
previous_context=self.context,
)
except Exception:
- # TODO: We need to set the panel to a disabled state the way
- # we do the Shotgun menu in Nuke Studio.
- self.log_debug(
+ self.logger.debug(
"Unable to determine context from path. Not changing context."
)
+ # clear the context finding task ids so that any tasks that
+ # finish won't send data to js.
+ self.__context_find_uid = None
+ self.__context_thumb_uid = None
+ self.adobe.send_unknown_context()
return
if not context.project:
- self.log_debug(
+ self.logger.debug(
"New context doesn't have a Project entity. Not changing "
"context."
)
@@ -321,6 +324,7 @@ def _handle_active_document_change(self, active_document_path):
self._CONTEXT_CACHE[active_document_path] = context
if context != self.context:
+ self.adobe.context_about_to_change()
sgtk.platform.change_context(context)
def _handle_command(self, uid):
diff --git a/plugins/README.md b/plugins/README.md
new file mode 100644
index 0000000..30dd70d
--- /dev/null
+++ b/plugins/README.md
@@ -0,0 +1,261 @@
+# Extension Development
+
+This section is designed for developers and covers how to setup, build, and test
+the Shotgun Photoshop CC extension.
+
+For more information about Adobe CEP extensions, here are some useful resources:
+
+* [CEP Resources (Github)](https://github.com/Adobe-CEP/CEP-Resources)
+* [David Berranca's Blog](http://www.davidebarranca.com/)
+* [A Short Guide to HTML5 Extensions (Adobe)](http://www.adobe.com/devnet/creativesuite/articles/a-short-guide-to-HTML5-extensions.html)
+
+## Building & Testing
+
+Building the extension requires a little bit of setup. The following sections
+cover the prerequisites and various scenarios for building and testing the
+extension.
+
+### OS-specific CEP extensions install directories:
+
+All Adobe extensions live in a single directory. The directory is specific to
+the current OS. Here are the directories for Windows and OS X. It is important
+to be able to access these directories in order to monitor and manually clean up
+extensions during development should something go awry.
+
+```shell
+# Windows
+> C:\Users\[user name]\AppData\Roaming\Adobe\CEP\extensions\
+
+# OS X
+> ~/Library/Application Support/Adobe/CEP/extensions/
+```
+
+### Build Script
+
+Because the SG-Adobe integration is built via the **Toolkit as a Plugin** framework,
+you'll need to rebuild the SG plugin into an Adobe extension as you make changes
+to the code.
+
+In order to build the extension, you'll need to run the included
+`developer/build_extension.py` script. This is a wrapper around the
+`build_plugin.py` from core (`v0.18.27` or later).
+
+Building the extension requires running the `build_extension.py` script with a
+local copy of tk-core, supplying it with options about where and how to build
+the output extension.
+
+The arguments for the build script look like this:
+
+```bash
+> ./build_extension.py --help
+usage: build_extension.py [-h] --core /path/to/tk-core --plugin_name name
+ --extension_name name
+ [--sign /path/to/ZXPSignCmd /path/to/certificate password]
+ [--bundle_cache]
+ [--version v#.#.#]
+ [--output_dir /path/to/output/extension]
+
+Build and package an Adobe extension for the engine. This includes
+signing the extension with the supplied certificate. The extension will be
+built in the engine repo unless an output directory is specified.
+
+optional arguments:
+ -h, --help show this help message and exit
+ --core /path/to/tk-core, -c /path/to/tk-core
+ The path to tk-core to use when building the toolkit
+ plugin.
+ --plugin_name name, -p name
+ The name of the engine plugin to build. Ex: 'basic'.
+ --extension_name name, -e name
+ The name of the output extension. Ex:
+ 'com.shotgunsoftware.basic.adobecc'
+ --sign /path/to/ZXPSignCmd /path/to/certificate password, -s /path/to/ZXPSignCmd /path/to/certificate password
+ If supplied, sign the build extension. Requires 3
+ arguments: the path to the 'ZXPSignCmd', the
+ certificate and the password.Note, the ZXPSignCmd can
+ be downloaded here:
+ http://labs.adobe.com/downloads/extensionbuilder3.html
+ --bundle_cache, -b If supplied, include the 'bundle_cache' directory in
+ the build plugin. If not, it is removed after the
+ build.
+ --version v#.#.#, -v v#.#.#
+ The version to attached to the built plugin. If not
+ specified, the version will be set to 'dev' and will
+ override any version of the extension at
+ launch/install time. The current version can be found
+ in the .version file that lives next to the existing
+ .zxp file.
+ --output_dir /path/to/output/extension, -o /path/to/output/extension
+ If supplied, output the built extension here. If not
+ supplied, the extension will be built in the engine
+ directory at the top level.
+```
+
+There are quite a few options for the script. The sections below will outline
+how to use the build script for different testing scenarios.
+
+### PlayerDebugMode
+
+If you plan to do local development and testing with an unsigned/uncertified
+extensions, you will need to set some OS-specific preferences:
+
+As per the Adobe docs:
+
+> Applications will normally not load an extension unless it is
+> cryptographically signed. However, during development we want to be able to
+> quickly test an extension without having to sign it. To turn on debug mode:
+>
+> * On Mac, open the file `~/Library/Preferences/com.adobe.CSXS.7.plist` and add a
+> row with key `PlayerDebugMode`, of type `String`, and value `1`.
+> * On Windows, open the registry key `HKEY_CURRENT_USER/Software/Adobe/CSXS.7`
+> and add a key named `PlayerDebugMode`, of type `String`, and value `1`
+>
+> You should only need to do this once.
+
+### Local testing without signing
+
+It is possible to build and test the extension without bundling it into a `.zxp`
+file (what Adobe wants for its Add-ons site). This scenario will simply run the
+underlying SG plugin build into a directory of your choosing.
+
+To test with this setup, set an environment variable:
+`SHOTGUN_ADOBECC_DISABLE_AUTO_INSTALL`. In the classic toolkit startup (web,
+Desktop, tk-shell) this will prevent the `.zxp` packaged with the engine from
+being installed automatically.
+
+Now you can build the extension directly to the CEP extensions directory. Here's
+an example of the command run from the top level of the engine repo:
+
+```
+python developer/build_extension.py -c ../tk-core -p basic -e com.shotgunsoftware.basic.adobecc -o "/Users/josh/Library/Application Support/Adobe/CEP/extensions/"
+```
+
+Argument breakdown:
+
+* `-c /path/to/core`: The local path to the `tk-core` to use for building the plugin
+* `-p plugin_name': The engine plugin to build. Plugins found in the engine's `plugins` directory.
+* `-e extension_name`: The output name of the adobe extension to build. This should match the
+`ExtensionBundleId` found in the extensions manifest (`CSXS/manifest.xml`).
+* `-o output_dir`: The output directory to write the built extension. Here we're
+writing directly to the installed CEP extensions directory.
+
+Repeat this command as you make changes to the plugin code to update the installed
+extension. NOTE: Older versions of the core `build_plugin.py` script will leave a
+backup directory in the CEP extensions directory, next to the build. You will likely
+want to make sure these are cleaned up after each build to avoid confusion and prevent
+issues inside the Adobe product.
+
+### Local testing with signing
+
+You can also build a signed extension that mimics the final build. This workflow
+involves building a `.zxp` file that can be auto-installed and updated as you
+launch toolkit (via web, Desktop, or tk-shell).
+
+First, make sure you do **NOT** have the `SHOTGUN_ADOBECC_DISABLE_AUTO_INSTALL`
+set in your environment. We will build the `.zxp` and allow the auto install to
+run. This env variable prevents that.
+
+Next, clean out any previous direct builds (see previous section) from the CEP
+install directory. The first toolkit startup will install the `.zxp` automatically.
+
+Finally, download the `ZXPSignCmd` tool to sign the built extension. You can find
+it [here](http://labs.adobe.com/downloads/extensionbuilder3.html).
+
+Now you can build the `.zxp` file with the extension build script. Here's an
+example command run from the top level of the engine repo:
+
+```
+python developer/build_extension.py -c ../tk-core -p basic -e com.shotgunsoftware.basic.adobecc -s ../ZXPSignCmd ~/Documents/certs/my_certificate.p12 my_cert_password
+```
+
+The first few arguments are identical to the previous example. The additional
+arguments are:
+
+* `-s ../ZXPSignCmd`: The path to the `ZXPSignCmd` tool that you previously
+downloaded
+* `~/Documents/certs/my_certificate.p12`: The second argument to the `-s` (sign)
+option is the path to certificate to use to sign the extension.
+* `my_cert_password`: The third argument to the `-s` option is the password for
+the certificate.
+
+Since the `-o` (output directory) option was not supplied, the `.zxp` file will
+be build and overwrite the `.zxp` file in the engine. The built extension file
+will look like this:
+
+```
+com.shotgunsoftware.basic.adobecc.version
+com.shotgunsoftware.basic.adobecc.zxp
+```
+
+Notice the `.version` file. This file is used by the toolkit startup logic to
+know when to auto install the extension. When the `-v` (version) flag is not
+specified, this file will contain the string `dev` which tells the engine
+startup code to always install/update this extension. This makes the developer
+round trip from extension build to engine startup straight forward. A copy of
+this `.version` file is also included in the `.zxp` bundle so that when it is
+unpacked within the CEP extensions directory, it can be easily compared against
+what is in the engine for the non `dev` case.
+
+If you're worried about overwriting the `.zxp` file bundled with the engine,
+remember you can always use `git checkout` to discard your changes.
+
+### Building for Release
+
+Building for release is almost identical to the previous example with the
+exception of specifying an actual version. To do that, you simple add an additional
+argument to the build command:
+
+```
+python developer/build_extension.py -c ../tk-core -p basic -e com.shotgunsoftware.basic.adobecc -s ../ZXPSignCmd ~/Documents/certs/my_certificate.p12 my_cert_password -v v0.0.1
+```
+
+The results of the command will look like this:
+
+```
+com.shotgunsoftware.basic.adobecc.version
+com.shotgunsoftware.basic.adobecc.zxp
+```
+
+Unlike the previous section, the `.version` file will now include the string
+`v0.0.1`. As bug fixes and features are added to the extension, and it is rebuilt
+with higher version numbers, the engine startup code will use this file to
+compare and determine if the user's installed version requires an update.
+
+### Testing
+
+The `basic` plugin/extension has a flyout menu with options useful for testing
+and debugging. To enable these, set the environment variable `SHOTGUN_ADOBECC_DEVELOPER`.
+
+
+* **Chrome Console...** - Requires Chrome as the default browser. Opens a Chrome
+console connected to the Adobe extensions.
+* **Reload Shotgun Extension** - Reloads the extension, including restarting the
+external python process.
+
+### Configuration repo
+
+If you're doing development on the `tk-photoshop` engine and launching it
+via the Adobe CC launcher (Toolkit as a plugin), you'll need to clone the config
+repo so that you can change it to point to your development repo(s). The config
+used by the engine is the [tk-config-basic](https://github.com/shotgunsoftware/tk-config-basic)
+repo. Obviously, you'll also need to clone the repos for the bundle's you're
+doing development on as well.
+
+The base configuration is defined in the extension's `info.yml`. You will need
+to change the descriptor arguments to point to your development repository. Here
+is an example:
+
+```yaml
+base_configuration:
+ #type: app_store
+ #name: tk-config-pluginbasic
+
+ type: dev
+ path: /path/to/my/dev/config/repo
+```
+
+Once you're pointing to your configuration development repo, you can change the
+bundle descriptors in the configuration's environment files to point to the
+development repos for the bundles you're working on.
+
+
diff --git a/plugins/basic/README.md b/plugins/basic/README.md
deleted file mode 100644
index 4516b93..0000000
--- a/plugins/basic/README.md
+++ /dev/null
@@ -1,134 +0,0 @@
-# Extension Development
-
-This section is designed for developers and covers how to setup, build, and test
-the Adobe CC extension.
-
-The entire extension source lives here in the `tk-photoshopcc` engine in the
-`extensions/basic` folder. This includes all of the pieces required by Adobe to
-run as a CEP extension as well as the configuration and logic specific to
-bootstrapping and running Shotgun Toolkit in a python process.
-
-For more information about Adobe CEP extensions, here are some useful resources:
-
-* [CEP Resources (Github)](https://github.com/Adobe-CEP/CEP-Resources)
-* [David Berranca's Blog](http://www.davidebarranca.com/)
-* [A Short Guide to HTML5 Extensions (Adobe)](http://www.adobe.com/devnet/creativesuite/articles/a-short-guide-to-HTML5-extensions.html)
-
-## Setup
-
-Before you can build and test the extension locally, you need to setup your
-environment to test.
-
-### PlayerDebugMode
-
-As per the Adobe docs:
-
-> Applications will normally not load an extension unless it is
-> cryptographically signed. However, during development we want to be able to
-> quickly test an extension without having to sign it. To turn on debug mode:
->
-> * On Mac, open the file `~/Library/Preferences/com.adobe.CSXS.7.plist` and add a
-> row with key `PlayerDebugMode`, of type `String`, and value `1`.
-> * On Windows, open the registry key `HKEY_CURRENT_USER/Software/Adobe/CSXS.7`
-> and add a key named `PlayerDebugMode`, of type `String`, and value `1`
->
-> You should only need to do this once.
-
-### Development Repositories
-
-If you're doing development on the `tk-photoshopcc` engine itself, or an app or
-framework being loaded by the engine, you'll need to clone the config repo so
-that you can make changes to it. The config used by the engine is the
-[tk-config-pluginbasic](https://github.com/shotgunsoftware/tk-config-pluginbasic)
-repo. Obviously, you'll also need to clone the repos for the bundle's you're
-doing development on.
-
-The base configuration is defined in the extension's `info.yml`. You will need
-to change the descriptor arguments to point to your development repository. Here
-is an example:
-
-```yaml
-base_configuration:
- #type: app_store
- #name: tk-config-pluginbasic
-
- type: dev
- path: /path/to/my/dev/config/repo
-```
-
-Once you're pointing to your configuration development repo, you can change the
-bundle descriptors in the configuration's environment files to point to the
-development repos for the bundles you're working on.
-
-### Core Development and Testing
-
-In order to bootstrap into a specific core, you'll need to alter the descriptor
-in the extension's `config/core/core_api.yml` file. Simply change the descriptor
-arguments in that file to point to a local clone of `tk-core` or to a specific
-branch or tag in github.
-
-## Building
-
-In order to build the extension, you'll need to have a local copy of core that
-has the `developer/build_plugin.py` script available (`v0.18.27` or later
-preferably).
-
-Building the extension requires running the `build_plugin.py` script from your
-local core, supplying it with a source path (the path to the extension itself
-in your development repo) and a destination path (the path where you want to
-write the extension).
-
-Here is an example of building the extension:
-
-```shell
-> cd ~/dev
-> python tk-core/developer/build_plugin.py tk-photoshopcc/extensions/basic my_extension
-```
-
-The above command will write the built extension to the `~/dev/my_extension`
-directory. In order for the extension to be picked up, it will need to live in
-the CEP extensions directory:
-
-```shell
-# windows
-> C:\Users\[user name]\AppData\Roaming\Adobe\CEP\extensions\
-
-# osx
-> ~/Library/Application Support/Adobe/CEP/extensions/
-```
-
-You can have the `build_plugin.py` script write directly to the extensions
-directory, just be aware that the script will create a backup copy in the
-same location if the destination folder already exists. Alternatively, you can
-create a symlink in the extensions directory back to the build destination path
-in your development area.
-
-## Testing
-
-Once you've followed the steps above, the extension should be available the
-next time you startup Photoshop. If you do not see it, double check the steps
-above.
-
-The extension has a flyout menu with 2 options useful for testing and debugging:
-
-* **Debug Console...** - Opens the default browser and displays
-information about the extension and a clickable link to debug it.
-* **Reload** - Reloads the extension, including restarting the
-external python process.
-
-As you make changes in your code, you will need to rebuild the extension using
-the steps above. You can use the **Reload** menu item to reload the extension
-after it has been rebuilt.
-
-# Building the Plugin for Release
-
-Coming soon...
-
-## Clean up dev descriptors
-
-# TODO:
-* consider the case where you have the officially released extension installed
-system wide, but you also want to do local development. Revisit once we've done
-the initial release for beta testing.
-* consider all the side effects of reloading the extension. does it actually
-handle reconnecting properly. do we get a completely fresh environment?
diff --git a/plugins/basic/css/style.css b/plugins/basic/css/style.css
index 80dedae..a79ab5c 100644
--- a/plugins/basic/css/style.css
+++ b/plugins/basic/css/style.css
@@ -61,10 +61,12 @@ a:link.sg_value_link:hover {
}
/* indicator for external links in context header */
+/*
a:link.sg_value_link:after {
content: url(../images/external_link.png);
margin-left: 4px;
}
+*/
/* white on hover */
a:hover {
@@ -109,13 +111,12 @@ a:active {
/* the first image that shows up when panel is loading */
#loading_img {
- position: absolute;
- top: 50%;
- left: 50%;
- width: 270px;
- height: 193px;
- margin-top: -96px;
- margin-left: -135px;
+ position: fixed;
+ margin: auto;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
}
/* error display */
@@ -176,26 +177,26 @@ div.sg_error {
/* grayscale images in favorites until hovered */
.sg_panel_command_img {
- -webkit-filter: grayscale(1);
- width: 26px;
- height: 26px;
+ -webkit-filter: grayscale(1) brightness(.75);
+ width: 19px;
+ height: 19px;
}
.sg_panel_command_other_img {
- width: 26px;
- height: 26px;
+ width: 19px;
+ height: 19px;
}
.sg_panel_command_img:hover {
- -webkit-filter: grayscale(0);
+ -webkit-filter: grayscale(0) brightness(1);
}
/* regular command button */
.sg_command_button {
display: inline-block;
border-radius: 3px;
- margin: 4px;
- padding: 2px;
+ margin: 1px;
+ padding: 4px;
border: 1px solid rgba(0, 0, 0, 0);
}
@@ -218,10 +219,11 @@ div.sg_error {
/* a single command div */
.sg_panel_command {
border-bottom: 1px solid #373737;
- height: 34px;
- padding-left: 18px;
+ height: 32px;
+ padding-left: 16px;
padding-right: 18px;
- -webkit-filter: grayscale(1);
+ padding-top: 5px;
+ -webkit-filter: grayscale(1) brightness(.75);
color: #D7D7D7;
}
@@ -236,7 +238,7 @@ div.sg_error {
.sg_panel_command:hover {
background-color: #6B6B6B;
color: white;
- -webkit-filter: grayscale(0);
+ -webkit-filter: grayscale(0) brightness(1);
}
.sg_panel_command:hover a:link {
@@ -370,6 +372,13 @@ div.sg_error {
display: none;
}
+#sg_panel_console_tag {
+ display: inline;
+ color: #373737;
+ border-right: 1px solid #373737;
+ padding-right: 3px;
+}
+
#sg_panel_console_header {
position: fixed;
top: 0px;
@@ -396,20 +405,57 @@ div.sg_error {
#sg_log_message {
color: #D7D7D7;
display: inline;
+ padding-left: 3px;
}
#sg_log_message_debug {
color: #18A7E3;
display: inline;
+ padding-left: 3px;
}
#sg_log_message_warn {
color: rgb(226, 159, 74);
display: inline;
+ padding-left: 3px;
}
#sg_log_message_error {
color: rgb(226, 74, 74);
display: inline;
+ padding-left: 3px;
+}
+
+#sg_unknown_context_table {
+ width: 100%;
+ padding: 10px;
+ vertical-align: top;
+}
+
+#sg_unknown_context_thumbnail {
+ display: table-cell;
+ vertical-align: top;
+ text-align: center;
}
+#sg_unknown_context_title {
+ text-align: left;
+ vertical-align: top;
+ color: #D7D7D7;
+ padding-left: 8px;
+ padding-top: 0;
+ width: 100%;
+ flex: 1;
+ display: inline-block;
+}
+
+#sg_unknown_context_details{
+ text-align: left;
+ vertical-align: top;
+ color: #777777;
+ padding-left: 8px;
+ padding-top: 0px;
+ width: 100%;
+ flex: 1;
+ display: inline-block;
+}
diff --git a/plugins/basic/images/sg_logo.png b/plugins/basic/images/sg_logo.png
new file mode 100644
index 0000000..2a4fefb
Binary files /dev/null and b/plugins/basic/images/sg_logo.png differ
diff --git a/plugins/basic/images/sg_logo_loading.png b/plugins/basic/images/sg_logo_loading.png
deleted file mode 100644
index 306c3fc..0000000
Binary files a/plugins/basic/images/sg_logo_loading.png and /dev/null differ
diff --git a/plugins/basic/images/sg_logo_with_text.png b/plugins/basic/images/sg_logo_with_text.png
new file mode 100644
index 0000000..d2e0d18
Binary files /dev/null and b/plugins/basic/images/sg_logo_with_text.png differ
diff --git a/plugins/basic/js/shotgun/constants.js b/plugins/basic/js/shotgun/constants.js
index 2e89123..8a7815a 100644
--- a/plugins/basic/js/shotgun/constants.js
+++ b/plugins/basic/js/shotgun/constants.js
@@ -36,12 +36,14 @@ sg_constants.product_info = {
// Photoshop
PHSP: {
+ display_name: "Photoshop",
tk_engine_name: "tk-photoshop",
debug_url: "http://localhost:45216"
},
// Photoshop alt
PHXS: {
+ display_name: "Photoshop",
tk_engine_name: "tk-photoshop",
debug_url: "http://localhost:45217"
},
@@ -49,6 +51,7 @@ sg_constants.product_info = {
// After Effects
AEFT: {
+ display_name: "After Effects",
tk_engine_name: "tk-aftereffects",
debug_url: "http://localhost:45218"
},
@@ -56,6 +59,7 @@ sg_constants.product_info = {
// Premiere Pro
PPRO: {
+ display_name: "Premiere Pro",
tk_engine_name: "tk-premiere",
debug_url: "http://localhost:45219"
}
@@ -84,7 +88,8 @@ sg_constants.panel_div_ids = {
sg_constants.python_error_codes = {
EXIT_STATUS_CLEAN: 0,
EXIT_STATUS_ERROR: 100,
- EXIT_STATUS_NO_PYSIDE: 101
+ EXIT_STATUS_NO_PYSIDE: 101,
+ EXIT_STATUS_PYTHON_FAIL: 102
};
// External link to pyside docs
diff --git a/plugins/basic/js/shotgun/manager.js b/plugins/basic/js/shotgun/manager.js
index 5ed97fc..25015aa 100644
--- a/plugins/basic/js/shotgun/manager.js
+++ b/plugins/basic/js/shotgun/manager.js
@@ -112,8 +112,12 @@ sg_manager.Manager = new function() {
);
} catch (error) {
+
+ const error_lines = error.stack.split(/\r?\n/);
+ const stack_err_msg = error_lines[0];
+
const message = "There was an unexpected error during startup of " +
- "the Adobe Shotgun integration.";
+ "the Adobe Shotgun integration:
" + stack_err_msg;
// log the error in the event that the panel has started and the
// user can click the console
@@ -144,26 +148,6 @@ sg_manager.Manager = new function() {
self.shutdown();
};
- this.set_commands = function(commands) {
- // emits the current commands update for listeners to respond to
- sg_manager.UPDATE_COMMANDS.emit(commands);
- };
-
- this.set_context_fields = function(context_fields) {
- // emits the current context fields update for listeners to respond to
- sg_manager.UPDATE_CONTEXT_FIELDS.emit(context_fields);
- };
-
- this.set_context_thumbnail = function(context_thumbnail) {
- // emits the current context thumbnail update for listeners to respond to
- sg_manager.UPDATE_CONTEXT_THUMBNAIL.emit(context_thumbnail);
- };
-
- this.context_about_to_change = function() {
- // emits the context about to change signal for listeners to respond to
- sg_manager.CONTEXT_ABOUT_TO_CHANGE.emit();
- };
-
this.shutdown = function() {
// Ensure all the manager's components are shutdown properly
//
@@ -287,8 +271,7 @@ sg_manager.Manager = new function() {
sg_logging.debug("Spawning child process... ");
sg_logging.debug("Python executable: " + python_exe_path);
sg_logging.debug("Current working directory: " + plugin_python_path);
- sg_logging.debug("Executing command: ");
- sg_logging.debug(" " +
+ sg_logging.debug("Executing command: " +
[
python_exe_path,
plugin_bootstrap_py,
@@ -322,15 +305,17 @@ sg_manager.Manager = new function() {
throw error;
}
- sg_logging.debug("Child process spawned! PID: " + self.python_process.pid)
+ self.python_process.on("error", function(error) {
+ sg_logging.error("Python process error: " + error);
+ });
// log stdout from python process
- self.python_process.stdout.on("data", function (data) {
+ self.python_process.stdout.on("data", function(data) {
sg_logging.python(data.toString());
});
// log stderr from python process
- self.python_process.stderr.on("data", function (data) {
+ self.python_process.stderr.on("data", function(data) {
sg_logging.python(data.toString());
});
@@ -367,9 +352,6 @@ sg_manager.Manager = new function() {
const _get_open_port = function(port_found_callback) {
// Find an open port and send it to the supplied callback
- // TODO: allow specification of an explicit port to use for debugging
- // perhaps something that is exposed during the build process?
-
// https://nodejs.org/api/http.html#http_class_http_server
const http = require('http');
@@ -519,6 +501,8 @@ sg_manager.Manager = new function() {
// setup listeners for any events that need to be processed by the
// manager
+ sg_logging.debug("Setting up event listeners...");
+
// ---- Events from the panel
sg_panel.REQUEST_MANAGER_RELOAD.connect(_reload);
@@ -556,6 +540,8 @@ sg_manager.Manager = new function() {
// Keep an eye on the active document.
setInterval(_active_document_check, active_document_interval);
+
+ sg_logging.debug("Event listeners created.");
};
const _emit_python_critical_error = function(error) {
diff --git a/plugins/basic/js/shotgun/manager_events.js b/plugins/basic/js/shotgun/manager_events.js
index 3e72966..afad54d 100644
--- a/plugins/basic/js/shotgun/manager_events.js
+++ b/plugins/basic/js/shotgun/manager_events.js
@@ -22,6 +22,9 @@ sg_event.create_event(sg_manager, "UPDATE_CONTEXT_DISPLAY");
// typically as an async response to a REQUEST_STATE event from the panel
sg_event.create_event(sg_manager, "UPDATE_CONTEXT_THUMBNAIL");
+// sent when the python side cannot determine a context for the current document
+sg_event.create_event(sg_manager, "UNKNOWN_CONTEXT");
+
// sent just before a context change from python
sg_event.create_event(sg_manager, "CONTEXT_ABOUT_TO_CHANGE");
diff --git a/plugins/basic/js/shotgun/panel.js b/plugins/basic/js/shotgun/panel.js
index 56b6f05..34bbae9 100644
--- a/plugins/basic/js/shotgun/panel.js
+++ b/plugins/basic/js/shotgun/panel.js
@@ -52,6 +52,8 @@ sg_panel.Panel = new function() {
// clears any state for the current context.
_context_thumbnail_data = undefined;
+ _build_flyout_menu([]);
+
};
this.set_panel_loading_state = function() {
@@ -63,7 +65,7 @@ sg_panel.Panel = new function() {
_show_header(false);
_set_contents(
- "
");
+ "
");
_show_info(true);
_set_info(
@@ -71,6 +73,47 @@ sg_panel.Panel = new function() {
);
};
+ this.set_unknown_context_state = function() {
+ // Clears the panel's contents and displays a message that it is disabled
+
+ this.clear();
+
+ _set_bg_color("#222222");
+
+ _show_header(false);
+ _clear_messages();
+
+ var app_name = _cs_interface.getHostEnvironment().appName;
+ const app_display_name = sg_constants.product_info[app_name].display_name;
+
+ _set_contents(
+ "
" +
+ "" +
+ "" +
+ " " +
+ " | " +
+ "" +
+ "" +
+ "" +
+ "" +
+ "Integration Disabled" +
+ " | " +
+ " " +
+ "" +
+ "" +
+ "The currently active file can't be associated with a " +
+ "Shotgun context. Try switching to another file or " +
+ "restarting " + app_display_name + "." +
+ " | " +
+ " " +
+ " " +
+ " | " +
+ "
" +
+ "
"
+ );
+
+ };
+
this.set_context_loading_state = function() {
// clears the current context and displays it as loading a new one.
@@ -195,6 +238,15 @@ sg_panel.Panel = new function() {
// TODO: display error in the panel
}
+ // Here we send the "AppOnline" event strictly in the event of a manual
+ // reload/restart of the SG extension. For initial launch of PS, this
+ // will be a no-op since the manager will already be running. For the
+ // reload scenario, this is the jumpstart that the manager requires to
+ // start up.
+ const event_type = "com.adobe.csxs.events.AppOnline";
+ var event = new CSEvent(event_type, "APPLICATION");
+ event.extensionId = _cs_interface.getExtensionID();
+ _cs_interface.dispatchEvent(event);
};
this.on_unload = function() {
@@ -351,22 +403,18 @@ sg_panel.Panel = new function() {
commands_html +=
"" +
- "
" +
- "" +
- "" +
- "" +
- "" +
+ "