Copy link to clipboard
Copied
I have been messing with BridgeTalk lately and came upon a situation where my onResult techniques were really too cumbersome to do what I wanted in a succinct way: batch process something that repeatedly does stuff in Photoshop and then goes back to Illustrator.
My technique for BT caters to my desire of being able to send a single file to a customer via email, and it would have every thing it needs included embedded images and all of the necessary code. The way I send a BT message is:
So this all used to work and works fine now, but it makes certain things more cumbersome such as when you want to keep a running log of a batch process.
The one problem with BrigeTalk.send() is that it is asynchronous and if you have a loop that pushes all results via an onResult function to a log, the calling function would return right away and be already done by the time the Photoshop part comes into play, returning an empty log.
Ok, well that's fine, just pass the log too, and keep passing this 'snowball' object back and forth. But what if I have a 'smart' log that has methods like a custom "toJSON()" method that edits the object for sending to other places? Maybe we can use toSource() on it and it would preserve the methods, I don't know, but what I do know is I don't want to be passing around objects which intuitively are meant to stay put in one spot and just get updated.
Now it was time to check out the BridgeTalk docs and the .pump() method, and I finally happened upon xbytor's famous code that motion graphics and Photoshop pros have been using for years and years: https://www.ps-scripts.com/viewtopic.php?t=11379
So I applied it and came up with the test script below, it appears to solve a lot of my issues.
Notes:
The following script has a variable 'log' that gets items added to it. This variable stays in its easily-managed spot and isn't passed around among BridgeTalk messages, which is just great.
eval() & toSource() vs JSON: the cool thing about toSource() is that it can preserve some things that JSON crashes with. But there's the concern for security: an object that may have been tampered with in some obscure way could be injected into the eval() statement to cause malicious deeds. Therefore I think now that using toSource and eval is ok when passing around objects you've made or native items, but when there's a UI or external data from somewhere it's better to have JSON.parse().
Even without a UI or external data, I'm not sure if the following hacks would still make toSource less-secure:
Anyway, supposing they aren't an issue, here is the snippet:
#target illustrator
// no #targetengine required? The sendSynch prototype seems to take care of it.
function test (argsObjAsSource) {
/**
* @typedef LaunchScriptCategories
* {"PsItemProcess"} PS_ITEM_PROCESS
*/
/**
* @typedef LaunchScriptArguments
* {LaunchScriptCategories[keyof LaunchScriptCategories]} launchScript
* {object} data
*/
// XBYTOR's code.
// Send a synchronous message. The result is returned.
// If a result doesn't come back in 'timeout' seconds, undefined is returned.
BridgeTalk.prototype.sendSynch = function (timeout) {
var self = this;
self.onResult = function (res) {
this.result = res.body;
this.complete = true;
}
self.complete = false;
self.send();
if (timeout) {
for (var i = 0; i < timeout; i++) {
BridgeTalk.pump(); // process any outstanding messages
if (!self.complete) {
$.sleep(1000);
} else {
break;
}
}
}
var res = self.result;
self.result = self.complete = self.onResult = undefined;
return res;
};
function bridgeTalkEncode(txt) {
/* thanks to Bob Stucky */
txt = encodeURIComponent(txt);
txt = txt.replace(/\r/, "%0d");
txt = txt.replace(/\n/, "%0a");
txt = txt.replace(/\\/, "%5c");
txt = txt.replace(/'/g, "%27");
return txt.replace(/"/g, "%22");
};
/**
* {"photoshop" | "illustrator"} targetApp
* {Function} scriptCode
* {LaunchScriptArguments} args
* {number} [timeout] - Number of seconds.
*/
function sendSyncBT (targetApp, scriptCode, args, timeout) {
if (timeout == undefined) {
timeout = 500;
}
var bt = new BridgeTalk();
bt.target = targetApp;
var argStr = "(" + args.toSource() + ")";
var btMsg = "var scp = '" + bridgeTalkEncode(scriptCode.toString() + "\n" + scriptCode.name + argStr) + "';\n";
btMsg += "var scpDecoded = decodeURI( scp );\n";
btMsg += "res = eval( scpDecoded );";
bt.body = btMsg;
return bt.sendSynch(timeout);
};
// ================================================= Script-Scope variables ================================================= //
var batchInstructions = [
{ prop1 : "A", prop2 : 0 },
{ prop1 : "B", prop2 : 1 },
{ prop1 : "C", prop2 : 2 },
];
var log = [];
// ==================================================== Script Functions ==================================================== //
function psItemProcess (instrObj) {
alert("Prop 1 is:\n" + instrObj.prop1);
return instrObj.prop2;
};
function batchProcess (batchInstructions) {
var thisItem;
var launchInstructions;
var thisResult;
for (var i = 0; i < batchInstructions.length; i++) {
thisItem = batchInstructions[i];
/** {LaunchScriptArguments} */
launchInstructions = ({
"launchScript" : "PsItemProcess",
"data" : thisItem,
});
thisResult = sendSyncBT("photoshop", test, launchInstructions);
log.push(thisResult);
}
return log;
};
// ====================================================== Script Entry ====================================================== //
if (typeof (argsObjAsSource) != "undefined") {
/** {LaunchScriptArguments} */
var argsObj;
try {
argsObj = eval(argsObjAsSource);
} catch (e) {
alert(e);
return;
}
if (argsObj.launchScript == "PsItemProcess") {
/* [Ps process] this entire script has been passed into Photoshop, and this argument decides which part is executed
(this helps with code re-use, even among common functions used by different applications) */
return psItemProcess(argsObj.data);
/* [/Ps process] this return will output the data kept whole via the sendSynch() prototype of BridgeTalk. */
}
} else {
// This is the initial Illustrator entry portion, perform initial check here.
if (app.documents.length == 0) {
alert("No documents are open.");
return;
}
// synchronous execution, maked BridgeTalk easy to use by fitting into various procedures and maintaining original data of calling script.
var result = batchProcess(batchInstructions);
BridgeTalk.bringToFront("illustrator"); // otherwise they have to manually click Illustrator in dock to bring it back as active application.
alert(app.activeDocument.artboards[0].name); // Still have access to DOM - not so with regular BT-message and onResult().
alert(result.toSource());
}
};
test();
Since this works with alerts (wouldn't it be embarrassing if it turned out this only worked with alerts?), I think I'll use this technique in all my procedural scripts now. There's really no need to do async BT messages other than when creating a ScriptUI palette script or something that take a while to do but you still want them to move around in the Illustrator document or a ScriptUI modal dialog. In my case the dialogs are usually dismissed before a batch process starts and there's been no instance of where it was covenient, necessary or required to send a message with hopes of using onResult but let users still do things in the document - usually the document is being processed in some way and I don't need the users to be able to touch it in any way.
Ok, so this is an example of a sync BT message, courtesy of the famous xbytor who does or did a lot of very advanced Photoshop stuff. For all who have BT issues, this should be quite a treat!
Copy link to clipboard
Copied
I think I may have been onto something when saying it could have turned out to work only with alerts!
Here's what I found:
So, before with onResult I had no problems but I hated my code and the concept of passing around objects that shouldn't really be dynamic like that. I was afraid that the whole thing wasn't working, all this after I made changes to my code and didn't commit them or back them up, and I also restarted the computer just in case, for sure erasing my previous progress forever. Luckily after trial & error ( my favorite thing ), I came to conclude that without bringing to front in specific cases, the timeouts somehow mis-align to put Illustrator in a state of deadlock: Photoshop really had to be brought to front for all the stuff to work. I noticed this at first due to my Photoshop process actually starting up when I force-quit Illustrator.
With onResult, none of this was an issue and you actually have more freedom to click about while the other app was busy. This new synch method locks down both applications, which is what I want, but it doesn't look pretty (Windows: Illustrator is grayed out and black boxes appear to cover all the UI panels, dock icon hover app image splits into multiple panel thumbnails, Photoshop is more normal and nothing much happens there except you can't click on anything which is fine) and it looks like your computer is suffering some critical error while all this is going on. Maybe I can stick in some progress bar and incremental redraw() to make it look like it's not exploding.