【Live2D】レイヤーの一番上にまとめてかけてある効果レイヤーをそれぞれのパーツ分けしたレイヤーに適用させるスクリプト


/*******************/
(2018.09.02)
スクリプトを一部修正しました。
68行目:レイヤークリッピング
/*******************/

タイトルの通りのスクリプト作りました。コードは長いので記事のお尻に書いてあります。

元イラストがこんな感じ

 
これにpsdの一番上に効果レイヤー(画像は調整レイヤー:カラーバランス)をかけた状態がこんな感じ

 
それをこんな感じにすべてのレイヤーへクリッピングして結合をします(効果レイヤーが非表示でも印象が同じですね)。

これを自動でやってくれるスクリプトです。手作業でやるとミスがあったり、パーツ多いと大変なので作りました。
※頬の部分が適用前より少し色が弱くなっていますが、たぶん調整レイヤーの仕様です。

 

【使い方】
①psdファイルを開き、効果レイヤーをすべてレイヤーの一番上へ配置します。
②結合させたい効果レイヤーをすべて選択します。
③スクリプトを実行します。(画像ではスクリプト名をConbineLayerとしました)。Photoshopへ直接D&DしてもOKです。

 

④少し待つと画像のようなウインドウが出ます。結合させるレイヤーと確認のための画面です。問題なければ「はい」を押します。「いいえ」を押すとスクリプトを終了します。

 

⑤するともりもりと処理が走ります。

 

⑥結合処理が完了すると、完了しましたメッセージが出ます。選択していた効果レイヤーは自動で非表示状態になります。

⑦おわり!

 

こんな感じに複数レイヤーを選択して結合もできます。

 

 

【ちゅうい!】
すべてのレイヤーは表示状態にしてください。結合処理が非表示レイヤーに対してできないので、非表示状態のレイヤーがある状態でスクリプトを実行すると、こんなエラーが出るようになってます。

 
レイヤーを一つも選択していない状態でスクリプトを実行するとエラーが起きます。このエラーの回避方法はちょっとわからなかったので仕様ってことでゆるしてくだち。

 

 

【スクリプトのしくみ】
たぶん大体のイラストの描き方のレイヤー構造は
———————–
「効果レイヤー」
「線画」
「色」
———————–
みたいな感じになってると思います。

Live2D用にパーツ分けをしたpsdにすると
———————–
「効果レイヤー」
「パーツ分けレイヤー構造」
———————–
みたいな感じになりますよね。
この一番上の効果レイヤーを下のすべてのレイヤーに結合するって動作をするスクリプトって感じです。

なので効果レイヤー以外はLive2Dへインポートする準備ができてるくらいの状態のpsdがこのスクリプトにとって望ましい状態です。
※例えばLive2D_Preprocessスクリプトを使用したあとの状態

逆に特定のレイヤーのみに結合したり、一番上にない効果レイヤーを結合させるといった限定的な使い方はできません。
というかそういう用途にむけて作ってないです。

 

ということで以下がコードです。

/********/
/* main */
/********/

app.bringToFront();
if (app.documents.length ==0){
//ドキュメントが開かれていない場合は処理をしない。
}else{
//作業するドキュメントの参照
var parent = activeDocument;

// レイヤーを全取得
var allLayers = [];
getAllLayer(parent);

// 選択レイヤーを全取得
var selectedLayerList = []
getSelectedLayers(selectedLayerList);

//全レイヤーから選択レイヤーを除外する
var conbineLayerList = [];
var isVisible = true;
getConbineLayerList(conbineLayerList);

if(selectedLayerList == null || allLayers == null){
alert("結合させるレイヤーを選択してください。\n処理をキャンセルします。");
}else{
//結合先に非表示レイヤーがあると結合できないのでチェック
for(var i = 0 ; i < conbineLayerList.length ; i++){
if(!conbineLayerList[i].visible){
isVisible = false;
break;
}
}
if(!isVisible){
alert("結合先のレイヤーに非表示状態のレイヤーがあります。\n非表示レイヤーは結合が出来ないので、削除するか、表示状態にしてください。\n\n処理をキャンセルします。");
}else{
var target = "";
for(var i = 0 ; i < selectedLayerList.length ; i++){
target += selectedLayerList[i].name + " [" + (selectedLayerList.length-i) + "]\n";
}
target += "をすべてのレイヤーに結合します。([]の数字は結合順)\n\nよろしいですか?\n\n【注意!】\n結合させたい効果レイヤーはすべてレイヤーの一番上に配置してください。";

if(confirm(target)){
parent.suspendHistory("結合処理", "Conbine()");
alert("結合が完了しました。\n効果レイヤーは非表示にしてあります。");
}else{
alert("処理がキャンセルされました。");
}
}
}
}

function Conbine(){
/********/
/* 結合 */
/********/
// 結合先のレイヤー
for (var i = 0; i < conbineLayerList.length; i++) {
// 結合元のレイヤーを複製し、結合先のレイヤーの直上へ配置し、結合
var backCnt = selectedLayerList.length - 1;
for (var j = 0; j < selectedLayerList.length; j++) {
selectedLayerList[backCnt-j].duplicate();
//alert("backCnt:" + backCnt);
parent.activeLayer = activeDocument.layers[backCnt-j];
activeDocument.layers[backCnt-j].move(conbineLayerList[i], ElementPlacement.PLACEBEFORE);

			/*レイヤークリッピング*/
			var idGrpL = charIDToTypeID( "GrpL" );
			    var desc6 = new ActionDescriptor();
			    var idnull = charIDToTypeID( "null" );
			        var ref3 = new ActionReference();
			        var idLyr = charIDToTypeID( "Lyr " );
			        var idOrdn = charIDToTypeID( "Ordn" );
			        var idTrgt = charIDToTypeID( "Trgt" );
			        ref3.putEnumerated( idLyr, idOrdn, idTrgt );
			    desc6.putReference( idnull, ref3 );
			executeAction( idGrpL, desc6, DialogModes.NO );

parent.activeLayer.merge();
}
}

//選択レイヤーを非表示にする
for (var i = 0; i < selectedLayerList.length; i++) {
selectedLayerList[i].visible = false;
}
}

// 選択レイヤーを取得
function getSelectedLayers(selectedLayerList) {
if(parent.activeLayer){
var idGrp = stringIDToTypeID( "groupLayersEvent" );
var descGrp = new ActionDescriptor();
var refGrp = new ActionReference();
refGrp.putEnumerated(charIDToTypeID( "Lyr " ),charIDToTypeID( "Ordn" ),charIDToTypeID( "Trgt" ));
descGrp.putReference(charIDToTypeID( "null" ), refGrp );
executeAction( idGrp, descGrp, DialogModes.ALL );

for (var i = 0; i < parent.activeLayer.layers.length; i++) {
selectedLayerList.push(parent.activeLayer.layers[i])
}

Undo();
}
}

function Undo() {
executeAction(charIDToTypeID("undo"), undefined, DialogModes.NO);
}

// 取得したレイヤーから選択レイヤーを除外する
function getConbineLayerList(conbineLayerList) {
// 全アートレイヤーから
for (var i = 0; i < allLayers.length; i++) {
// 選択レイヤーを除く
for (var j = 0; j < selectedLayerList.length; j++) {
if(allLayers[i].name == selectedLayerList[j].name){
break;
}
}
if(!(j < selectedLayerList.length)){
conbineLayerList.push(allLayers[i]);
}
}
}

function getAllLayer(parent){
var ChildLayers= parent.layers;
for (var i = 0; i < ChildLayers.length; i++){
if (ChildLayers[i].typename == "LayerSet"){
//レイヤーセットの場合は再帰処理
//allLayers.push(ChildLayers[i].name);
getAllLayer(ChildLayers[i]);

}else if(ChildLayers[i].typename == "ArtLayer"){
//レイヤーを取得
allLayers.push(ChildLayers[i]);
}
}
}

個人で利用するためなので毎度のごとくデバッグはイイ感じでしかしてません。ご利用は自己責任でぇ。
Photoshop Ver:CC 2017.1.1です。他のバージョンでは動作検証してないです。ScriptingListener機能を使ったコードがあるので古すぎるバージョンだと動作しないかもしれないです。