*追記 コードを一部修正しました。詳しくはこの記事の『解説』へ。
シェイプグループにあるパスをシェイプレイヤーの直下にある『コンテンツ』へ移動すれば、パスポイントにtoComp(),fromComp()のエクスプレッションが使えるようになります。が、手動で移動させるとパスポイントがズレることがあります。そんなときにお使いください。
■PathMoveToRoot.jsx
以下コードをテキストにコピペして、名前を『PathMoveToRoot』拡張子を『.jsx』にすれば出来上がりです。
使い方はこのページの下のほうに書いてあります。また、その下の『重要』という項は必ずお読みください。
(function (aGbl) {
//--------------------------------------------------------------------------------------------------------------------------------------------------
function mCreateUI(aObj) {
var mPorW = (aObj instanceof Panel) ? aObj : new Window("palette", "PathMoveToRoot", undefined);
mPorW.preferredSize = [200, 200];
mPorW.margins = [10, 10, 10, 10];
mPorW.spacing = 20;
mPorW.mBt1 = mPorW.add("button { preferredSize : [-1,20] , text : 'Move' ,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.mBt1.onClick = function () {
var mAi = app.project.activeItem;
var mSl = mAi.selectedLayers[0];
var mSps = mSl.selectedProperties;
var mPath;
for (var i = 0; i < mSps.length; i++) {
if (mSps[i].matchName === "ADBE Vector Shape") {
mPath = mSps[i];
}
}
if (mPath === undefined) { return; }
app.beginUndoGroup("Move");
var mPathName = mPath.propertyGroup(1).name;
var mPathAdrsUse = mGetSelPptyAdrs(mPath);
var mPathAdrsTmp = mGetSelPptyAdrs(mPath);
mPathAdrsTmp.shift();
var mPathAdrsStr = 'var origPath = thisLayer(';
for (var i = 0; i < mPathAdrsTmp.length; i++) {
if (i !== mPathAdrsTmp.length - 1) {
if (isNaN(mPathAdrsTmp[i]) === false) {
mPathAdrsStr = mPathAdrsStr + mPathAdrsTmp[i] + ')(';
} else {
mPathAdrsStr = mPathAdrsStr + '"' + mPathAdrsTmp[i] + '")(';
}
} else {
if (isNaN(mPathAdrsTmp[i]) === false) {
mPathAdrsStr = mPathAdrsStr + mPathAdrsTmp[i] + ');';
} else {
mPathAdrsStr = mPathAdrsStr + '"' + mPathAdrsTmp[i] + '");';
}
}
}
var mExpForNewPathStr = mExpForNewPath.toString();
var mFstNum = mExpForNewPathStr.indexOf('{');
var mLstNum = mExpForNewPathStr.lastIndexOf('}');
mExpForNewPathStr = mExpForNewPathStr.slice(mFstNum + 2, mLstNum);
var mMoveToProp = mSl('ADBE Root Vectors Group');
var mNewPathProp = mMoveToProp.addProperty('ADBE Vector Shape - Group');
mNewPathProp.path.expression = mPathAdrsStr + '\n' + mExpForNewPathStr;
mNewPathProp.name = mPathName;
var mShapeValTmp = mNewPathProp.path.valueAtTime(mAi.time, false);
mNewPathProp.path.expression = "";
mNewPathProp.path.setValue(mShapeValTmp);
mSelProp = mGetPptyFromAdrs(mPathAdrsUse, 0);
mSelProp.propertyGroup(1).remove();
app.endUndoGroup();
}
//--------------------------------------------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------------------------------------------
//選択Propから [ 選択レイヤー(これのみ番号等ではなくObj)~選択Prop ] の階層別idx配列を得る関数。
function mGetSelPptyAdrs(aSp) {
var mSp = aSp;
var mAdrs = [];
//親プロパティ情報を上から入れていく。
for (var i = mSp.propertyDepth; i >= 1; i--) {
var mPropTmp = mSp.propertyGroup(i);
//一番手前のものはレイヤーなので、プロパティとしては呼べないのでレイヤーを直接入れる。
if( i === mSp.propertyDepth ){
mAdrs.push(mSp.propertyGroup(mSp.propertyDepth));
}else{
mAdrs.push(mPropTmp.propertyIndex);
}
}
//自身の情報を入れる。
mAdrs.push(mSp.propertyIndex);
return mAdrs;
}
//--------------------------------------------------------------------------------------------------------------------------------------------------
//階層別idx配列から、該当Pptyを得る(引数2は得る予定の最新深層Pptyからの上層数)。
function mGetPptyFromAdrs(aAdrs, aNum) {
var mAdrs = aAdrs;
//まずはレイヤーが直接入っている0番目を参照する。
var mSpfydPpty = aAdrs[0];
//階層を下って、選択pptyを変えていく。
for (var i = 1; i < mAdrs.length - aNum; i++) {
mSpfydPpty = mSpfydPpty(mAdrs[i]);
}
return mSpfydPpty;
}
//--------------------------------------------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------------------------------------------
function mExpForNewPath() {
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 origPoints = origPath.points();
var origInTang = origPath.inTangents();
var origOutTang = origPath.outTangents();
for (var i = 0; i < origPoints.length; i++) {
var mNewPt = mToShpLyrCoord(origPath, origPoints[i]);
var mNewIn = (mToShpLyrCoord(origPath, origPoints[i] + origInTang[i])) - mNewPt;
var mNewOut = (mToShpLyrCoord(origPath, origPoints[i] + origOutTang[i])) - mNewPt;
origPoints[i] = mNewPt;
origInTang[i] = mNewIn;
origOutTang[i] = mNewOut;
}
createPath(origPoints, origInTang, origOutTang, origPath.isClosed());
}
//--------------------------------------------------------------------------------------------------------------------------------------------------
})(this);
使い方
STEP
パスを選択する。
パス(名前が変えられる『パス 1』ではなくその下の『パス』)を選ぶ。
STEP
『Move』ボタンでパスを移動させる。
ボタンを押してパスが直下コンテンツに移動したら成功です。
*たいていは直下コンテンツに塗りや線がないので消えたように見えますが、直下に移動しています。
STEP
塗りや線を適宜移動、あるいは追加する。
塗りや線など、他のプロパティはそのままなので適宜移動してください。
シェイプグループを選んで右クリックで『グループの解除』を使うと比較的簡単です。
重要
独自作成した座標変換関数によるエクスプレッションを使用しておりますので使用は自己責任でお願い致します。
*座標変換関数エクスプレッションは処理時に一時適用するだけなので、エクスプレッションが残ることはありません。
解説
処理内容はコード内コメントをご参考ください。関連した解説はこの記事の『発展』の項に書いています。
なかなか使えるスクリプトなので、よければお使いください!