-1

Custom Action Menu Items And Python

The goal:

To create a Custom Action Menu to be used to trigger Python script. Before the script is run or executed it receives some data value from Shotgun.

The journey.

1. First of all, since everything in Shotgun is happening via Web Browser there must be some steps taken to make sure a web browser knows what to do.

From what I could understand (I am VFX artist and not a web programmer)  it is impractical to type PYTHON command (or any other command you can run using OS Command Prompt) into a web browser's URL Address field and to expect Firefox or Chrome to execute it. To make Firefox or any other web browser "aware" of any command there is a need to set up a so-called CUSTOM PROTOCOL HANDLER. While the term "Custom protocol handler" is quite annoying and difficult to remember it is a quite simple by the definition. If you wouldn't mind I would like to take a chance to try to explain the concept behind of it to those who like me desperately tries to understand what you wrongly call "a documentation" (which in a reality is just a computer geeks chat recorded in a form of online forum).

But let's get back to CUSTOM PROTOCOL HANDLER.

An important part of  Windows Operation System is a so-called Registry. Registry functions are quite wide. And most of the Windows users don't even know about its existence. As a matter of fact the Registry is hidden by default. You have to know some geeky commands to be able to open the application that would let you edit the Registry's values. As I am aware there are two such commands: "regedit" and "regedit32". You can run both applications (both very similar to each other while "regedit32" is more capable then regedit) by going to Windows Start button and click Run. Then just type "regedit" or "regedit32".

As you could be able to see from RegEdit the Registry is an endless data sheet of different internal to Windows operation system values. Most of the data is beyond of human mind to understand. But there are some values that make sense. Those are for example the associations of the file types with the software applications. It is possible to manually tweak the Registry to break *.PDF file association with Acrobat Reader and open PDFs with any other application of choice (not that another application will open it. A double clicking a PDF file in a file browser will start the application that is being defined in Registry as a default application to open PDF document). Needless to say since Registry is hidden from the user by default (so they wouldn't mess with it) Microsoft has supplied the user with a number of tools to modify the values in Registry indirectly. One of the ways to modify the Registry values is to write a simple text file (in Notepad for example) and  to save it with the *.reg file extension. Double clicking such *.reg file will make Windows attempt to make the changes to Registry. If *.reg file script file was filled properly (with a proper syntax) the result of the *.reg file execution are the updated values in Registry.

While defining a CUSTOM PROTOCOL HANDLER we would want to use Windows Registry as well. We choose any word we like to be used as a "protocol". Then we associate or "link" this word of choice to the executable program file. The syntax for the registry script can be observed in RV Player rvlink.reg scrip file.

Here is the syntax:

 Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\rvlink]
@="URL:RV Protocol"
"URL Protocol"=""
[HKEY_CLASSES_ROOT\rvlink\DefaultIcon]
@="rv.exe,1"
[HKEY_CLASSES_ROOT\rvlink\shell]
[HKEY_CLASSES_ROOT\rvlink\shell\open]
[HKEY_CLASSES_ROOT\rvlink\shell\open\command]
@="\"C:\\Program Files\\Tweak\\RV-3.12.12-64\\bin\\rv.exe\" \"%1\""

The second line in this script is what defines the PROTOCOL name: [HKEY_CLASSES_ROOT\rvlink]

 

In this particular case the name of protocol is defined as "rvlink"

The last line of the *.reg script defines the file path to the executable program file (in this case it is rv.exe):

@="\"C:\\Program Files\\Tweak\\RV-3.12.12-64\\bin\\rv.exe\" \"%1\""

 

All you need to do is to double click the *.reg script file. And the Registry will be updated.

Now any Web browser will look for C:\Program Files\Tweak\RV-3.12.12-64\bin\rv.exe executable as soon as beginning of

URL address typed into URL Address fields starts with "rvlink" (in place where you usually use "http"

So, for example, if you instead of typing into the URL field of Web Browser:

http://www.google.com

you type:

rvlink://www.google.com

the Web Browser assumes that RV player needs to be used to open (or to resolve) the "www.google.com" address.

RV player (rv.exe) is opened. But nothing is being played back since www.google.com is not the path to the movie file or the image sequence.

But if you type into the URL Address field something like:

rvlink://C:/Users/theUser/Documents/maya/projects/fluid_render/images/frame_01.1.png

RV player reads an image file frame_01.1.png from the local drive from the location specified in file path:

C:/Users/theUser/Documents/maya/projects/fluid_render/images

 

From playing with RV player and "rvlink" Custom Protocol Handler I assume that a Web Browser

processes the URL address after it is typed into URL field (or if URL link was clicked) in two parts.

First the Web Browser determinates  what application is used with supplied Custom Protocol Handler that starts the

URL address typed into the URL field. If it HTTP the web browser proceeds recolving the URL address by itself.

If the Protocol handler is ITMS then a Web Browser via Operation System (using Registry data values and its associations) opens iTunes and the rest of URL address (everything followed after Protocol Handler) is sent or forwarded to iTunes to be processed or resolved.

Same with RVLINK Protocol Handler which will instruct OS to open rv.exe file path to which is defined in Registry.

And so on.

Now excited by all of these I have created a Custom Handler Protocol for Python.exe. I called this protocol as Python_Protocol.

I have uploaded the python file to a web server with the URL address: www.myCoolWebSite.com/my_python_script.py

Then I typed into the web browser URL address field:

Python_Protocol://www.myCoolWebSite.com/my_python_script.py 

and hit enter. And nothing happened. All I saw was a blink of the Python window with some error message I couldn't read since it was so fast.

Needless to say that if I run the very same script from a normal command prompt using a "traditional" appoach:

python c:/my_python_script.py

the script runs just fine.

This is a half of the battle which I didn't win yet.  I don't understand how Shotgun would pass the data values I need to Python?

Let's imagine I have a Custom Action Menu in Shotgun which when clicked runs a Python script from a web location such as:

www.myCoolWebSite.com/my_python_script.py

As I understand all that would happen here (if it works at all) is that Python will be started, the my_python_script.py script will be downloaded from

www.myCoolWebSite.com  and the script will be executed or run. That's it. I don't see here how Shotgun or when Shotgun is passing the arguments to Python (such as file path to image sequence or the name of the shot the Action Menu Item was executed). Please explain the mechanics of it. When and how Shotgun is controlled to pass the data to the variables defined in Python Script? Or what would be the way to make sure Python is pulling the data from the shot from where the Custom Action Menu was executed? What makes Shotgun to send the data to Python Variables declared in python.py script what is downloaded and run from a web location and run by python.exe? How Shotgun passes the data from Shotgun is remaining to be unclear.

 

 

6 comments

  • 0
    Avatar
    Support

    Hi Pasha,

     

    It looks like you've spent a good bit of time on this.  I can see how it would be frustrating to get stuck at the end.   If you are looking to use custom protocols to launch an external application, we have some docs on that here that might make things clearer: https://support.shotgunsoftware.com/entries/86754-launching-external-applications-using-custom-protocols-rock-instead-of-http  (or if you're working in a MacOS environment, there's some great info here: https://support.shotgunsoftware.com/entries/127152)

     

    The Action Menu Item won't trigger a Python script directly.  The Action Menu Item will do an HTML POST call to an external site.   We often see Action Menu Items used in conjunction with internal web servers, for example.  ActionMenuItems are generally POST requests (if the link is an http link) that send a bunch of information about the current page to "a target" script.  The web server will take the parameters in the POST request, fire up the script that's been specified as a target, and give it the parameters to process.  The target script can take actions locally (eg processing files) or automate further actions on the Shotgun server (via the API).  

     

    Hope that helps.  Feel free to let us know if any of that isn't clear.

     

    Thanks,

    -Rebecca

     

     

     

  • 0
    Avatar
    Sputniks

    Hi Rebecca, Thanks for the explanation. I still would like to know more about what happens under the hood.
    Let me outline it here.

    1. The user clicks the Custom Action Menu with the Python script sitting on the web server at the address:
    http://www.domain.com/my_Python_script.py
    2. The Custom Action Menu when clicked does so-called "HTML POST call/request" to my_Python_script.py
    HTML Post call has the following format:
    http://www.sputnik.shotgunstudio.com/page/1324#Shot_860_shot_01?firstname=bob&lastname=smith

    The symbol ? indicates the part where HTML Post call (request) starts.
    Data fields are sent in a syntax like this:
    String Key Name is to the left of the equal sign
    The Value of the string is to the right of the equal sign. Example:
    firstname=bob

    3. Parsing.
    Here is where I still don't understand. What part of my Python script that is hosted on a web server
    ...my Python script to which Action Menu sends Post call/request... what part of the Python code in my script is capturing or receiving the data sent out of Shotgun into the string array?

    Would you please write me a little example in Python based on this scenario:

    Let's say my Action Menu sends this Post Call:
    http://www.sputnik.shotgunstudio.com/page/1324#Shot_860_shot_01?firstname=bob&lastname=smith

    Now would you please write a quick Python script that would show me a syntax on how to get
    the Post Call and store it into the string variable. So the that example Python script prints out to the screen output:

    First Name is Bob
    Last Name is Smith

  • 0
    Avatar
    Tom Stratton

    Hey Sputniks - did you ever get the solution you needed for this? I'm wondering the same thing and thought you might save me some tinkering time :-)

     

    Thanks

     

    Tom

  • 0
    Avatar
    Steven Parker

    Hey Sputniks/Tom, maybe I can help clarify a bit of this as we're beginning to integrate using this feature in a big way.

    When a user clicks an entry in the list of some type in Shotgun, you can bind an ActionMenuItem to that click. What happens here is the Shotgun website makes a web request to whatever web address you've configured it to go to. It will hand over some variables to your web server using a web method called "POST". This is a special way of sending data between two web pages.

    It's up to the web server's script to parse those variables, and each programming language will do this differently. In fact, there's even several ways in python depending on your python technology (wsgi, mod_python, cgi, etc) as well as your python web stack (pylons, django, twisted,etc). However, generically, the URL you've given can be easily parsed.

    Example URL: http://www.sputnik.shotgunstudio.com/page/1324#Shot_860_shot_01?firstname=bob&lastname=smith processed with the Turbogears 2.1 web framework...

    def _default(self, *args, **kw):
        first_name = kw.get('firstname', None)
        last_name = kw.get('lastname', None)

    Some explanation...your web framework will parse the URL, and eventually decide to call this default function. The first parts of the URL are passed as *args, and everything after the question mark will come in as a dictionary in **kw. (See http://www.technovelty.org/code/python/asterisk.html on an explanation of *args and **kw). I'm simply getting a value from the dict known as 'kw' by using the 'get' method. I'm also supplying a None default parameter in case they didn't give me a firstname in the post URL.

    Once again, this is all highly dependent on your python framework you're using, and Turbogears is sort of the lead hammer solution to this simple example. If I were to build something small, I'd probably start with twisted.

    Good luck!
    -VooDoo

  • 0
    Avatar
    Cathy Blanco

    Sputniks, I've been messing around with this as well, and here's some Python code (adapted from a script I'm working on to fit your parameters, not terribly Pythonic in style and untested, so don't sue me if it doesn't work perfectly as-is):

    import sys

    FILE = open("/tmp/output", "w", 0)

    # If you've set up your protocol handler correctly, then when it is invoked, the full URL that was entered in the browser's location box is

    # put in sys.argv[1] for you to use

    url = sys.argv[1]

    pieces = url.split("/")

    parseThis = pieces.pop()    # the part you want to process is the last part of the url

    (throwThisAway, processThis) = parseThis.split("?")     # get rid of everything before the '?'

    parts = processThis.split("&")    # we're getting close now....

    for part in parts:

        (key, value) = part.split("=")

        if key == 'firstname':

            FILE.write("First Name is %s\n", value)

        elif key == 'lastname':

            FILE.write("Last Name is %s\n", value)

    FILE.close()

  • 0
    Avatar
    Tom Stratton

    Hi - 

    question/feature request: It would be nice if we could specify some additional parameters on the URL that gets called.

    It would be nice to specify how the pop-up window looks as it opens (size, placement, etc.) without having to use onload() calls

    Also, a most common use case is to have the script compute/update something in SG on the calling page - the current gymnastics that are required to get the page to refresh are different for different pages - if we could have the current page URL passed with the rest of the parameters so we could reload that would be great.

    Tom

Please sign in to leave a comment.