Using COM always makes me feel like Mr. Yuk.
Since I had to learn a few JavaScript tricks back when I started looking into Python web development, I thought it might be worth investigating what could be done to cut out COM. It turns out that ExtendScript - the JavaScript flavor that comes with Adobe products (at least since CS 5, and I think earlier) includes a socket object which allows for TCP communication. That's not kosher in 'real' browser based JS - but ExtendScript cheats a little (that's also why it allows you to do things like hit the local file system, another JS no-no).
The Adobe docs have an example (around page 196) which shows how you can implement a chat server or a web server running inside Photoshop. That might not be practical but it provides a simple framework you can hijack to turn Photoshop into a remote procedure call server. Here's an example of a super-simple server that can be run inside Photoshop:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// requires photoshop CS5+ | |
// create a new socket | |
conn = new Socket(); | |
var keep_serving = true; | |
// sample functions. In a real application you'd have handler functions that could accept more complex inputs | |
var alrt = alert; // pop a dialog | |
var newLayer = function () { return app.activeDocument.artLayers.add(); }; // make a layer | |
var stop = function () { keep_serving = false; }; // stop the server | |
// 'register' the commands by putting them into a dictionary | |
var known_cmds = { 'alert': alrt, 'stop': stop, 'newLayer': newLayer }; | |
while (keep_serving) { | |
if (conn.listen(8789)) // ... you'd probably want to make this configurable | |
{ | |
// wait forever for a connection | |
var incoming; | |
do incoming = conn.poll(); | |
while (incoming == null); | |
// grab the next non-null communication | |
new_cmd = incoming.read(); | |
try { | |
// split the incoming message into cmd on spaces (shell style) | |
var command_text = new_cmd.split(" ", 1); | |
var args = new_cmd.slice(command_text[0].length + 1, 999).split(" "); | |
var requested = known_cmds[command_text]; | |
if (null != requested) { | |
result = requested(args); | |
incoming.writeln(result + "\nOK\n"); | |
} | |
else { | |
incoming.writeln("unknown command\nFAIL\n"); | |
} | |
} | |
catch (err) { | |
incoming.writeln(err + "FAIL\n"); | |
incoming.close(); | |
delete incoming; | |
} | |
} // end if | |
} // -- end while |
This example is very bare bones, but it is easy to extend - just create function objects and add them to known_commands dictionary and then send them over the socket. The way it's written here the commands and arguments are split on spaces (similar to the way a shell command works) -- if you need to get at them in your JS functions you can get at them using the arguments() keyword:. For serious work I'd probably use the ExtendScript XML object and send the commands and responses as xml since that gets you out of having to worry about stuff like 'what if I want to send an argument with a space in it' -- however this purpose of this excersize is just to demonstrate what's possible.
I should note that while the server is running, Photoshop is locked into the wait loop so it will not be accessible interactively -- like a Maya running a long script, the main thread is just waiting for the script to continue. For the typical 'remote control' application that's what you'd expect, but it may not answer for all purposes - so be warned.
If you're trying to talk to Photoshop from Python, it's incredibly simple:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import socket | |
HOST = '127.0.0.1' | |
PORT = 8789 | |
def send_photoshop(msg): | |
''' | |
Expects a photoshop instance running a tcp server on HOST:PORT | |
''' | |
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
conn.connect((HOST,PORT)) | |
conn.send(msg) | |
r = conn.recv(4096) | |
conn.close() | |
return r | |
send_photoshop("alert hello_from_python") #show a dialog | |
send_photoshop("newLayer") #create a layer | |
send_photoshop("stop") #stop the server | |
That's all there is to it - of course, the real problem is getting useful work done in the clunky Photoshop API -- but that's going to be the same no matter whether you talk to PS via COM or TCP/IP. Anecdotally, I've heard the PS scripts are faster in JavaScript than when using COM or AppleTalk or VB, so perhaps this method will be competitive on speed as well. For small tasks it's certainly a simpler and less irritating way to send a squirt to and from PS