【AEスクリプト】パスのポイント番号を表示するスクリプト

追記:パス点が10個目以上ある場合に位置が合わないバグを修正しました。

パスのポイント番号をエフェクト『番号』で目視可能にし、エクスプレッションでできることの幅を広げるスクリプトです。

以下コードをテキストにコピペして、名前を『AddPathIdx』拡張子を『.jsx』にすれば出来上がりです。
使い方はこのページの下のほうに書いてあります。

(function (aGbl) {
    //--------------------------------------------------------------------------------------------------------------------------------------------------
    function mCreateUI(aObj) {
        var mPorW = (aObj instanceof Panel) ? aObj : new Window("palette", "addPathIdx", undefined);
        mPorW.preferredSize = [200, 200];
        mPorW.margins = [10, 10, 10, 10];
        mPorW.spacing = 5;

        mPorW.mBt1 = mPorW.add("button { preferredSize : [-1,20]  , text : 'Add Idx' ,alignment :  [ 'fill','top' ] }");
        mPorW.mBt2 = mPorW.add("button { preferredSize : [-1,20]  , text : 'Del Idx' ,alignment :  [ 'fill','top' ] }");

        return mPorW;
    }

    //mPnlという名でメインウインドウを作成。
    var mPnl = mCreateUI(aGbl);
    if (mPnl instanceof Window) {
        mPnl.center();
        mPnl.show();
    } else if (mPnl instanceof Panel) {
        //UIパネルの場合は以下をしないと自動レイアウトされない。
        mPnl.layout.layout(true);
    }
    //--------------------------------------------------------------------------------------------------------------------------------------------------
    //メイン処理パート。
    mPnl.mBt2.onClick = function () {
        var mAi = app.project.activeItem;
        var mSl = mAi.selectedLayers[0];

        app.beginUndoGroup("Del");
        for (var i = mSl.effect.numProperties; i >= 1; i--) {
            //もしもエフェクトに文字列"Path"が含まれていれば消す。
            if (mSl.effect(i).name.match(/Path/) !== null) {
                mSl.effect(i).remove();
            }
        }

        app.endUndoGroup();
    }
    //--------------------------------------------------------------------------------------------------------------------------------------------------
    mPnl.mBt1.onClick = function () {
        var mAi = app.project.activeItem;
        var mSl = mAi.selectedLayers[0];
        var mSps = mSl.selectedProperties;

        app.beginUndoGroup("Add");

        var mSpsTmp = [];
        //選択プロパティをパスPpty(”パス”ではなく”パス 1”)のみにする。
        for (var j = 0; j < mSps.length; j++) {
            if (mSps[j].matchName === "ADBE Vector Shape - Group") {
                mSpsTmp.push(mSps[j]);
            }
        }
        mSps = mSpsTmp;
        //-----------------------------------------------------------------------
        //作成予定エフェクトの各情報Obj配列を作る。
        //必要な情報はエクスプレッション用パスアドレス、vtx番号、パスナンバーとvtx番号を合わせた名前。
        var mEftInfoObjs = [];
        var mPathIdxSum = 0;
        for (var i = 0; i < mSps.length; i++) {
            var mPath = mSps[i];
            for (var j = 0; j < mPath.path.value.vertices.length; j++) {
                mPathIdxSum++;

                var mObj = {};
                mObj.PathNum = i;
                mObj.VtxIdx = j;
                mObj.mName = "Path" + i + " VtxIdx" + ("000" + j ).slice( -3 );

                //パスのドット記法文字列を作成する。
                var mAdrs = [];
                for (var k = mPath.propertyDepth; k >= 1; k--) {
                    var mPptyTmp = mPath.propertyGroup(k);
                    //プロパティタイプがインデックスドであれば番号を入れる。
                    if (mPptyTmp.propertyType === PropertyType.INDEXED_GROUP) {
                        mAdrs.push(mPptyTmp.propertyIndex);
                        //それ以外であれば(名前が変わらないので)名前を入れる。
                    } else if (mPptyTmp.propertyType === PropertyType.NAMED_GROUP || PropertyType.PROPERTY) {
                        mAdrs.push('"' + mPptyTmp.name + '"');
                    }
                }
                mAdrs.push(mPath.propertyIndex);
                var mAdrsStr = mAdrs.join(")(");
                mAdrsStr = "thisComp.layer(" + mAdrsStr + ").path;"
                mObj.mAdrsStr = mAdrsStr;

                //mObj.mName = mAdrsStr+ j;

                mEftInfoObjs.push(mObj);

            }
        }
        //-----------------------------------------------------------------------
        //調整用エフェクトを適用する。
        var mColEft = mSl.effect.addProperty("ADBE Color Control");
        mColEft.name = "PathVtxIdxColor";
        mColEft("ADBE Color Control-0001").setValue([1, 1, 1]);
        var mSizeEft = mSl.effect.addProperty("ADBE Slider Control");
        mSizeEft.name = "PathVtxIdxSize";
        mSizeEft("ADBE Slider Control-0001").setValue(30);
        var mPtEft = mSl.effect.addProperty("ADBE Point Control");
        mPtEft.name = "PathVtxIdxPt";
        mPtEft("ADBE Point Control-0001").setValue([0, -30]);

        //-----------------------------------------------------------------------  
        //エフェクトのひな型を作成する。
        //エフェクト『番号』を適用し、各種数値やエクスプレッションを加える。
        var mNumEft = mSl.effect.addProperty("ADBE Numbers2");
        mNumEft.name = "NumEffectForPathIdx";

        mNumEft("ADBE Numbers2-0004").setValue(0);
        mNumEft("ADBE Numbers2-0009").setValue(true);
        mNumEft("ADBE Numbers2-0008").expression = 'effect("PathVtxIdxColor")("ADBE Color Control-0001")';

        //-----------------------------------------------------------------------
        //エフェクト『番号』をパス数×idx分コピーする。
        //コピーに含めないため、選択を外す。
        for (var i = 0; i < mAi.selectedProperties.length; i++) {
            mAi.selectedProperties[i].selected = false;
        }
        for (var i = 0; i < mAi.selectedLayers.length; i++) {
            mAi.selectedLayers[i].selected = false;
        }
        //エフェクトを選択し、コピーする。
        mNumEft.selected = true;
        //もともと1つあるので、コピー数は1始まりでよい。
        for (var i = 1; i < mPathIdxSum; i++) {
            app.executeCommand(2080);
        }

        //-----------------------------------------------------------------------
        //追加したエフェクト以外を処理対象から外す。
        var mTgtEfts = [];
        for (var i = 1; i <= mSl.effect.numProperties; i++) {
            var mTgtEft = mSl.effect(i);
            //もしもエフェクトに仮文字列が含まれていれば配列に入れる。
            if (mTgtEft.name.match(/NumEffectForPathIdx/) !== null) {
                mTgtEfts.push(mTgtEft);
            }
        }

        //エフェクト群に名前、エクスプレッションを適用する。
        for (var i = 0; i < mTgtEfts.length; i++) {
            var mTgtEft = mTgtEfts[i];

            mTgtEft.name = mEftInfoObjs[i].mName;

            mTgtEft("ADBE Numbers2-0003").expression = 'Number(thisProperty.propertyGroup(1).name.slice( -3 ));'

            mTgtEft("ADBE Numbers2-0006").expression = '' +
                'var mPath = ' + mEftInfoObjs[i].mAdrsStr + '\n' +
                'var mNum = Number(thisProperty.propertyGroup(1).name.slice( -3 ));' + '\n' +
                'var mSize = effect("PathVtxIdxSize")("ADBE Slider Control-0001");' + '\n' +
                'if( mNum > mPath.points().length - 1 ){' + '\n' +
                '    0;' + '\n' +
                '}else{' + '\n' +
                '   mSize;' + '\n' +
                '}';

            var mExpTmp = mExpForNumEftPt.toString();
            mExpTmp = mCutString(mExpTmp, "{", "}");
            mExpTmp = mCutString(mExpTmp, "\n", "\n");
            mTgtEft("ADBE Numbers2-0007").expression = '' +
                'var mPath = ' + mEftInfoObjs[i].mAdrsStr + '\n' +
                mExpTmp + 'else{' + '\n' +
                'var mPt = effect("PathVtxIdxPt")("ADBE Point Control-0001");' + '\n' +
                'thisLayer.toComp( mToShpLyrCoord(mPath, mPath.points()[mNum] + mPt));' + '\n' +
                '}';

        }

        app.endUndoGroup();
    }
    //-------------------------------------------------------------------------------------------------------------------------------------------------- 
    //--------------------------------------------------------------------------------------------------------------------------------------------------     
    //エフェクト『番号』の位置に適用するエクスプレッション。関数を文字列にする用。
    function mExpForNumEftPt() {
        function mToShpLyrCoord(aPath, aTgtPt) {
            //パスパラメータの1つ上のパスプロパティを得る。
            var mProp = aPath.propertyGroup(1);

            //パスプロパティの2つ上がグループではなくレイヤーならば、
            //パスプロパティは直下コンテンツにあるのでターゲットPtをそのままリターンする。
            if (mProp.propertyGroup(2) instanceof Layer) {
                return aTgtPt;
            }

            var mTfms = [];
            for (var i = 0; i < 100; i++) {
                //mProp(パスプロパティあるいはグループ)の2つ上がグループではなくレイヤーならば検索終了。
                if (mProp.propertyGroup(2) instanceof Layer) { break; }

                //パスPropの上は隠しコンテンツ、その上はグループなので『2』でmPropをグループへ変える。
                //そしてグループのトランスフォームを得る。
                mProp = mProp.propertyGroup(2);
                mTfms.push(mProp.transform);
            }

            //先祖から順に処理するので、反転する。
            mTfms.reverse();

            //-------------------------------------------------------------------------
            //非歪位置(各トランスフォームのApのコンポ座標)を親から順に割り出して、mEachPts配列に入れていく。
            var mEachPts = [];
            for (var i = 0; i < mTfms.length; i++) {
                if (i === 0) {
                    var mXY = mTfms[0].position;
                    mEachPts.push(mXY);
                } else {
                    //子の位置は親の“原点からの”位置なので、前回算出位置から現状の親のアンカーポイントを引いて、現状ループの位置を足す。
                    mXY = mXY - mTfms[i - 1].anchorPoint + mTfms[i].position;
                    mEachPts.push(mXY);
                }
            }
            //TgtPtの非歪コンポ座標を出す。
            //ループ後のXYはラストレイヤーのアンカーポイント位置になるので、アンカーポイントを引いて原点にし、そこにTgtPtを足す。
            var mTgtXY = (mXY - mTfms[mTfms.length - 1].anchorPoint) + aTgtPt;

            //-------------------------------------------------------------------------
            //スケール、スキュー、回転を子から順に設定していく。まずは子~親順にする。
            mTfms.reverse();
            mEachPts.reverse();
            //自身が所属するトランスフォームも含めて処理するので、length分全てを回す。
            for (var i = 0; i < mTfms.length; i++) {
                //スケールパート。計算しやすいように原点からの位置にして、スケーリングして元の位置に戻す。        
                var mVecXY = mTgtXY - mEachPts[i];
                var mScldX = mVecXY[0] * (mTfms[i].scale[0] / 100);
                var mScldY = mVecXY[1] * (mTfms[i].scale[1] / 100);
                mTgtXY = [mEachPts[i][0] + mScldX, mEachPts[i][1] + mScldY];

                //スキューパート。もしもスキューが0でなければ、スキューした値を出す。
                if (mTfms[i].skew !== 0) {
                    mTgtXY = mGetSkewedPt(mEachPts[i], mTgtXY, mTfms[i].skew, mTfms[i].skewAxis);
                }

                //回転パート。親とTgtPt間の現状の角度(元々の角度もあるが、上記のスケーリングでもまた変わっている)と、
                //親が設定している角度を足したものから、その結果の位置を割り出す(現状の角度は関数内で処理されている)。
                mTgtXY = mGetRotPt(mEachPts[i], mTgtXY, mTfms[i].rotation);
            }

            return mTgtXY;


            //以下、関数内で使用している関数。
            //------------------------------------
            //2点から、基準点を中心に目標点を回転させた値を得る(現状の2点の角度は入れなくてよい)。
            function mGetRotPt(aStdPt, aTgtPt, aStdRot) {
                var mTgtPtZr = aTgtPt - aStdPt;
                var mRotRd = aStdRot * (Math.PI / 180);
                var mRstX = mTgtPtZr[0] * Math.cos(mRotRd) - mTgtPtZr[1] * Math.sin(mRotRd);
                var mRstY = mTgtPtZr[0] * Math.sin(mRotRd) + mTgtPtZr[1] * Math.cos(mRotRd);
                return [mRstX, mRstY] + aStdPt;
            }
            //------------------------------------
            function mGetSkewedPt(aOriginPt, aVtx, aRtn, aAxis) {
                //回転軸方向へスキューさせたいので、一度、回転軸に基づいてVtxを回転させて、X方向スキューで済むようにする。
                var mRtdXY = mGetRotPt(aOriginPt, aVtx, aAxis);
                //スキュー計算は原点からのもののため、原点に持ってくる。
                var mVecXY = mRtdXY - aOriginPt;
                //回転させたものをスキューする(式はX+(Y*タンジェントシータ)。グラフのY方向が数学と違うのでシータは-する。
                var mRad = (aRtn) * (Math.PI / 180);
                var mTgt = - Math.tan(mRad);
                var mSkewX = mVecXY[0] + (mVecXY[1] * mTgt);
                var mSkwdPt = [mSkewX, mVecXY[1]];
                //位置を元に戻す。
                var mVecFromOrgXY = mSkwdPt + aOriginPt;
                //回転させた方向を元に戻してリターンする。
                return mGetRotPt(aOriginPt, mVecFromOrgXY, -aAxis);
            }
        }

        //var mPath = content("シェイプ 1").content("パス 1").path;
        var mNum =Number(thisProperty.propertyGroup(1).name.slice( -3 ));
        if (mNum > mPath.points().length - 1) {
            [thisComp.width / 2, thisComp.height / 2];
        }
    }
    //-------------------------------------------------------------------------------------------------------------------------------------------------- 
    //指定した2つの文字列に挟まっている文字列を抜き出す(引数3=ラスト文字は無くてもよい)。
    //指定文字は(先頭、ラストともに)含まない。
    function mCutString(aTgtTxt, aTxtF, aTxtL) {
        var mIdx1 = aTgtTxt.indexOf(aTxtF); //+ aTxtF.length;
        if (aTxtL === undefined) {
            var mIdx2 = aTgtTxt.length - 1;
        } else {
            var mIdx2 = aTgtTxt.lastIndexOf(aTxtL);
        }
        var mRst = aTgtTxt.slice(mIdx1 + 1, mIdx2);
        return mRst;
    }
    //-------------------------------------------------------------------------------------------------------------------------------------------------- 
})(this);

使い方

STEP
パスを選択する。

パス(『パス』ではなく『パス 1』とか)を選ぶ。同レイヤー内であれば複数選択可能です。

STEP
『Add』ボタンでポイント番号を追加。

エフェクト『番号』のダイアログが出るのでOKにして下さい。
するとエフェクト『番号』が複数追加され、ポイント番号となります。
文字の大きさや位置、色は同じく追加される制御エフェクトで調整可能。
パスポイントを動かせば数字も追従します。

STEP
ポイント番号を目視してエクスプレッションを作る。

例えばヌルに『あるパス.points()[ポイント番号]』といったエクスプレッションを打てば
そのポイントの位置を得られます。
(ただし、シェイプのトランスフォーム基準での位置座標なので変換が必要です。くわしくはこちら

また、パスのエクスプレッション欄は使っていないので、パス自身にも自由にエクスプレッションを作成できます。

*エクスプレッションでポイントが増えた場合、自動で数字を追加してくれる機能はありません。
エフェクト『番号』の名前がパス別に”Path+数字”になっているので「これかな?」という名前のエフェクト『番号』を増やせば追加が可能です。パス番号よりエフェクトが増えてしまってもエクスプレッションエラーにならないようにしてあるので、気軽に増やすことができます。

STEP
『Del』ボタンで追加したエフェクトのみを削除。

あんまりないとは思いますが、シェイプレイヤーにすでに別エフェクトが適用されていた場合、Delボタンを押せばこのスクリプトで追加したエフェクトだけを削除できます。
(正確に言えば、”Path~”という名前が付いているエフェクトが全て削除されます)

*通常操作でエフェクトを全削除しても問題なしです。

使いどころ

AEの標準機能ではスクリプトでもパス番号を得られないので、せっかく色々あるシェイプパス系のメソッドがなかなか使いづらいですね。ということで、目視スクリプトを作ってみました。
CuttanaNirでは選択パス番号を直接得る機能を実現、実装しています!)

ポイント番号さえ得られればシェイプパス系の操作はほぼ全てできます。目視可能となったので、直観的にパスのエクスプレッションを作成できるのでは、と思います!
ちなみに、ユーザーにポイント番号を入力させる形でパス関連スクリプトを作ることも可能ですね。

解説

処理内容はコード内コメントをご参考ください。概要としては、
パスポイントの総数は簡単に得られるので、その数だけエフェクト『番号』を追加する。
エフェクト『番号』の名前を連番にしておく。
連番を参照して各パスポイントの位置、実際に表示する番号を得る。
エフェクト『番号』はスクリプトでも、どうしても最初にダイアログが出てしまうので、1つだけ作ってあとはコピーで済ます。そのため、for文の形がちょっとわかりにくくなっている。

といった具合です。

よかったらシェアしてね!
  • URLをコピーしました!