jsxファイルのあるディレクトリを読み込んで、Photoshopのパネル上から直接実行する JSXExecutor という拡張を作った。
同じような拡張である JSXLauncher と JSFLTool にインスパイアされてる。
同じような拡張があるのになぜ作ったかというと、
- JSFLToolはFlash専用の拡張
- JSXLauncherは複数のディレクトリを読み込めない
- JSXLauncherはjsx内のデバッグ用メソッドが一部動かない
ということで、2つの拡張のいいとこ取り+αな拡張が欲しかった。(※現状JSXExecutorは複数ディレクトリ対応はまだ出来てない)
インストール
Githubに書いてあるが、
- JSXExecutor.zxpを ダウンロード する
- CC・CC2014なら Adobe Extension ManagerCC 、CC2015なら ZXPInstaller を使ってインストール出来る
どのように使うか
自分のユースケースとしては、
- ちょっと試したいjsxなどを一つのディレクトリにどんどん作って貯めていく
- 開発中のjsxを各バージョンのPhotoshopから確認できる
- 業務で使うjsxを一個のディレクトリに入れてgitで管理・共有する
- プロジェクト毎に専用のjsxディレクトリを読み込む(※これは複数ディレクトリに対応後)
などを想定している。Photoshopの再起動無しでjsxを読み込めるのが、自分の様にバンバンjsxを書いて使う人にはすごく便利なんじゃないかなと思う。デバッグ用の$.write
などがきちんと動くのも便利。
実装について
この拡張はCommon Extensibility Platform(CEP)という拡張機能を利用している。詳しくは「 CEPスーパー メガ ガイド: HTML5+Node.jsでAdobeのツールを拡張する | aphall.com 」を見ていただけると一番早いと思う。
ただし、ここで書かれているのはCEP5という仕様についてで、今回はAdobe CCバージョンでも動かすために、Node.jsが使えないCEP4という仕様に基づいて制作した。(CEP5はCC2014から対応)
なので、HTMLでUIを構築し、jsとjsx間をやり取りしながら実装する形だけになっている。jsからjsxを実行するのはすべて非同期で行われるので、今回は「 q 」のDeferred(Promise)を利用してメソッドチェーンで続ける形にした。
var jsxInterface = JSXInterface.getInstance();
jsxInterface.evaluateJSX("getFileList", {scriptpath:scriptpath})
.then(function(filesObj) {
filesObj = JSON.parse(filesObj);
var scriptpath = filesObj["scriptPath"];
var files = filesObj["filepaths"];
return [scriptpath, files];
})
.spread( ...
jsからjsxの実行については、CSInterface.evalScript()というCEPのライブラリのメソッドを利用するので、それをいい感じにPromiseでラップして利用できるように、JSXInterfaceをいうクラスを作っている。これは「 Generator 」という別のPhotoshop拡張機能の仕組み内で書かれている方法をベースにした。
下調べがちょっと足りてなく、ライブラリで用意されている一部の機能も今回jsxで作ってしまったので、本当は全体的にもう少し簡単に書けると思う。
UI部分については「 Topcoat 」や「 Refills - Patterns 」を使うようにしているので、あまり手をかけていない。
デバッグ用メソッドを動かす
jsxから別のjsxを呼ぶには$.evalFile
を使えばできるが、これを使用した場合、呼ばれたjsx内の$.write
などがExtendScriptToolKitの方のコンソールにきちんと値を出力してくれない。
これをうまく動かすには、Photoshop内からjsxを参照して呼ぶのと同じ方法を使えばいい。Photoshopの裏APIを動かすActionDescripterを利用する。
function executor(args) {
var idAdobeScriptAutomationScripts = stringIDToTypeID( "AdobeScriptAutomation Scripts" );
var desc1 = new ActionDescriptor();
var idjsCt = charIDToTypeID( "jsCt" );
desc1.putPath( idjsCt, new File( args["path"] ) );
var idjsMs = charIDToTypeID( "jsMs" );
desc1.putString( idjsMs, "undefined" );
executeAction( idAdobeScriptAutomationScripts, desc1, DialogModes.NO );
return true;
}
今回はこんな感じでjsxを用意しておいて、ここに実行したいpathを渡して利用するようにした。初回の起動に多少のラグが発生するときもあるが、概ね問題なく動くはず。
まとめ
週末1日でさくっと作れるかなと思ったら意外とそれから数日かかってしまった。代わりにCEPで拡張を作るのにも大分慣れたので勉強になってよかった。これで今作っている別のjsxの各種実験が捗りそう。