Monday, January 12, 2026

Mayaで法線を編集する (3)

法線編集の話題も最終回です。
前回は特定のオブジェクトの方向に(正確には逆方向ですが)法線を向ける話でした。こんな感じです。

全ての法線が緑の十字オブジェクトの方を向いている

ですが実際のモデリングではこの作業だけでは足りません。逆に、特定の法線の向きに合うオブジェクトの座標を決める必要もあります。つまり、二つの法線の焦点を見つけるという事です。

法線の焦点の座標を求めるには?

二つの法線はいわゆるねじれの位置にある事もありますので、数学的には「二直線の最短距離」を求める計算を使います。こちらのページにある図が分かりやすいです。
【高校数学C】ねじれの位置にある2直線間の最短距離(共通垂線) | 受験の月
正直私には計算式が全く理解出来ませんが、この図の「共通垂線」の絶対角度を求めるには初回で話したMayaのcrossコマンドが使えます。Mayaのマニュアルを参照して頂ければ分かりますが、もう見たまんまです。
Maya Creative ヘルプ | cross | Autodesk

さて、共通垂線の絶対角度を得た後はこんな感じに計算します。
下図の赤と青の直線が二つの法線の延長線、白の線が共通垂線です。


この二つの直線が属する平面を考えてみます。


この二つの平面がZ軸方向を向く様に全体を回転させます。


「回転させる」とさらっと言いましたが、これはあくまで計算上の話なのでMayaのオブジェクトとして回転させる事は出来ません。なのでどうするかというと、回転後の頂点座標を計算してその位置に各頂点を移動させます。数学的に計算するのは大変そうですが、Mayaのrotコマンドを使うとすぐに答えが出ます。
Maya Creative ヘルプ | rot | Autodesk
ちなみにX軸回転とY軸回転の二回に分けて計算します。何故かと言うとrotコマンドでは回転方向が分からないので、両直線をYZ・XZ各平面に投影した上でcrossコマンドで法線を出し、各平面の垂直軸と正負を比較して回転方向を決める必要があるからです。(一度にやる方法があるかも知れませんが…)

さて、回転させた平面をZ軸方向から見るとこんな感じです。


ここまで来れば両直線の始点と終点の座標を使い、連立一次方程式で交点が計算出来ます。Z座標は両直線の中間値にします。
最後にその座標に対して先ほどの回転を逆に実行し、最終的な焦点の座標を得ます。

エッジを加えたせいで角張った様になった見た目も…

焦点を決めて法線を加工するときれいな表示に。
(左右両サイドそれぞれで焦点を決めました。)

スクリプトはこちらです。

//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
//GetNormalCrossing
//Takahiro Nakajima 2026

//二つの機能があります。

//A)選択している二つのバーテックスの焦点の座標を取得します。
//B)選択している全てのバーテックスの法線のルートをその焦点に向けます。

//使い方:
//1)このスクリプトを実行します。ダイアログが出ます。
//2)オブジェクトの回転をリセットします。
//3)焦点を取得したい場合は、二つの頂点を選んで上のボタンを押すと
//ダイアログのカラムに焦点の座標値が入ります。
//(法線が平行な場合は0が入ります)
//4)焦点に向けたい場合は、法線を動かしたい頂点を全て選んで下のボタンを押します。

//--------------------------------------------------------------------------------
global proc float[] getLinesCrossing(vector$S1,vector$E1,vector$S2,vector$E2)
{
//二直線L1とL2が最短距離になる点P1とP2の平均座標をfloat配列で返します。
//もし二直線が平行だった場合は返り値のアドレス3に1が入ります(それ以外は0)。
//L1の始点はS1、終点はE1
//L2の始点はS2、終点はE2

float $crossing[];
clear $crossing;


//二直線の法線nmlを取得
vector $V1 = $E1 - $S1;
vector $V2 = $E2 - $S2;
vector $nml = `cross $V1 $V2`;

//nmlがZ軸と一致する回転角(rad)を取得
float $angles[] = `getRotAngleForCrossingNormalToZaxis $nml`;

//二直線の始点と終点を回転させ、XY平面と平行にする
$S1 = `rotationNomalAlignToZaxis $S1 $angles 0`;
$E1 = `rotationNomalAlignToZaxis $E1 $angles 0`;
$S2 = `rotationNomalAlignToZaxis $S2 $angles 0`;
$E2 = `rotationNomalAlignToZaxis $E2 $angles 0`;

//XY平面上での交点の平均座標を取得
float $rotatedXY[] = `NKJ_GetCrossingOf2Lines $S1 $E1 $S2 $E2`;
//逆回転させて元に戻す(二直線が平行の場合はそのまま)
if($rotatedXY[2] == 1)
{
$crossing = {0,0,0,1};
}
else
{
float $crossingZ = (($S1.z) + ($S2.z)) / 2.0;
vector $crossingVec = <<$rotatedXY[0],$rotatedXY[1],$crossingZ>>;
$crossingVec = `rotationNomalAlignToZaxis $crossingVec $angles 1`;
float $corssingVeX = $crossingVec.x;
float $corssingVeY = $crossingVec.y;
float $corssingVeZ = $crossingVec.z;
$crossing = {$crossingVec.x,$crossingVec.y,$crossingVec.z, 0};
}
return $crossing;
}
//--------------------------------------------------------------------------------
global proc float[] getRotAngleForCrossingNormalToZaxis(vector$crossingNormal)
{
//$crossingNormalがZ軸と一致する回転角(rad)を返します。
//返り値のアドレス0がX回転、アドレス1がY回転になります。

//各軸のベクトル
vector $xAxis = <<1,0,0>>;
vector $yAxis = <<0,1,0>>;
vector $zAxis = <<0,0,1>>;

//$crossingNormalをX軸方向から見たベクトルを作成
float $crossingNormalY = $crossingNormal.y;
float $crossingNormalZ = $crossingNormal.z;
vector $XcrossingNormal = <<0,$crossingNormalY,$crossingNormalZ>>;

//上記ベクトルとZ軸との法線を取得し、法線がXプラスかXマイナスかで
//回転方向を決める
float $xDirection = 1.0;
vector $normalAndZaxisCrossing = `cross $XcrossingNormal $zAxis`;
if(($normalAndZaxisCrossing.x)<0.0)
{
$xDirection = (-1.0);
}
//上記ベクトルとZ軸との角度を取得し、回転方向を掛ける
float $xRot = `angle $XcrossingNormal $zAxis` *  $xDirection;
//$crossingNormalを回転させる
vector $crossingNormalXRotated = `rot $crossingNormal $xAxis $xRot`;

//回転させた$crossingNormalとZ軸との法線を取得し、法線がYプラスか
//Yマイナスかで回転方向を決める
float $yDirection=1.0;
vector $rotNormalAndZaxisCrossing = `cross $crossingNormalXRotated $zAxis`;
if(($rotNormalAndZaxisCrossing.y)<0.0)
{
$yDirection=(-1.0);
}

//回転させた$crossingNormalとZ軸との角度を取得し、回転方向を掛ける
float $yRot = `angle $crossingNormalXRotated $zAxis` *  $yDirection;

//結果を返す
float $result[] = {$xRot,$yRot};
return $result;
}
//------------------------------------------------------------
global proc vector rotationNomalAlignToZaxis(vector$crossingNormal,float$angles[],int$reverse)
{
//$reverse == 0 >> 行き
//$reverse == 1 >> 帰り

vector $result;

vector $xAxis = <<1,0,0>>;
vector $yAxis = <<0,1,0>>;
if($reverse == 0)
{
vector $crossingNormalRot1 = `rot $crossingNormal $xAxis $angles[0]`;
$result = `rot $crossingNormalRot1 $yAxis $angles[1]`;
}
else
{
vector $crossingNormalRot1 = `rot $crossingNormal $yAxis ($angles[1] * (-1.0))`;
$result = `rot $crossingNormalRot1 $xAxis ($angles[0] * (-1.0))`;
}
return $result;
}
//------------------------------------------------------------
global proc float[] NKJ_GetlinearGraphParams(float$point1[],float$point2[])
{
//point1とpoint2を通る一次方程式グラフの傾きと高さ(y=ax+bのaとb)を返します。
//もしグラフが垂直であれば、返り値のアドレス2に1が入ります(初期値は0)。

float $result[];
clear $result;

if(($point2[0] - $point1[0]) != 0.0)
{
float $a = ($point2[1] - $point1[1]) / ($point2[0] - $point1[0]);
float $b = $point1[1] - ($a * $point1[0]);
$result = {$a,$b,0};
}
else
{
$result = {0,0,1};
}

return $result;
}
//------------------------------------------------------------
global proc float[] NKJ_GetCrossingOf2Lines(vector$S1,vector$E1,vector$S2,vector$E2)
{
//二つの直線のXY平面上での交点を返します。
//もし二直線が平行な場合、返り値のアドレス2に1が入り、結果は{0,0,1}になります。


//$S1と$E1は、直線1上の二つの点のXYZ座標です。
//$S2と$E2は、直線2上の二つの点のXYZ座標です。

float $resultX = 0;
float $resultY = 0;
float $parallel = 0;

//引数から二つの直線それぞれの始点と終点のXY座標を取り出して配列に入れる
float $S1X = $S1.x;
float $S1Y = $S1.y;
float $E1X = $E1.x;
float $E1Y = $E1.y;
float $S2X = $S2.x;
float $S2Y = $S2.y;
float $E2X = $E2.x;
float $E2Y = $E2.y;

float $line1SXY[] = {$S1X,$S1Y};
float $line1EXY[] = {$E1X,$E1Y};
float $line2SXY[] = {$S2X,$S2Y};
float $line2EXY[] = {$E2X,$E2Y};

//line1とline2のaとb(傾きと高さ)を取得
float $param1[] = `NKJ_GetlinearGraphParams $line1SXY $line1EXY`;
float $param2[] = `NKJ_GetlinearGraphParams $line2SXY $line2EXY`;

//もし両方とも垂直な場合(交点無し)
if(($param1[2]==1)&&($param2[2]==1))
{
$resultX = 0;
$resultY = 0;
$parallel = 1;
}
//もし傾きが同じ場合(交点無し)
else if($param1[0] == $param2[0])
{
$resultX = 0;
$resultY = 0;
$parallel = 1;
}
//もしline1だけが垂直な場合
else if(($param1[2]==1)&&($param2[2]==0))
{
$resultX = $S1.x;
$resultY = ($param2[0] * $resultX) + $param2[1];

}
//もしline2だけが垂直な場合
else if(($param1[2]==0)&&($param2[2]==1))
{
$resultX = $S2.x;
$resultY = ($param1[0] * $resultX) + $param1[1];
}
//その他の場合
else
{
$resultX = ($param2[1] - $param1[1]) / ($param1[0] - $param2[0]);
$resultY = ($param1[0] * $resultX) + $param1[1];
}

float $result[] = {$resultX,$resultY,$parallel};
return $result;
}
//------------------------------------------------------------
global proc float[] NKJ_GetLinearGraphParamsFromVtxs(string$vtx1,string$vtx2)
{
//vtx1とvtx2を通る直線をXY平面で見た時のグラフの傾きと高さ(y=ax+bのaとb)を返します。
//もしグラフが垂直であれば、返り値のアドレス2に1が入り、配列のサイズが3になります。
float $point1[] = `xform -q -a -ws -t $vtx1`;
float $point2[] = `xform -q -a -ws -t $vtx2`;
float $result[] = `NKJ_GetlinearGraphParams $point1 $point2`;

return $result;
}
//-------------------------------------------------------
global proc NKJ_GetNormalCrossing()
{
//選択頂点を取得
string $sel[] = `ls -sl -fl -l`;
//選択頂点の座標を取得
float $vtx1Pos[] = `xform -q -a -ws -t $sel[0]`;
float $vtx2Pos[] = `xform -q -a -ws -t $sel[1]`;
//選択頂点の法線を取得
float $vtx1Nml[] = `polyNormalPerVertex -q -xyz $sel[0]`;
float $vtx2Nml[] = `polyNormalPerVertex -q -xyz $sel[1]`;

//法線先端の座標を取得
float $vtx1NmlX = $vtx1Nml[0] + $vtx1Pos[0];
float $vtx1NmlY = $vtx1Nml[1] + $vtx1Pos[1];
float $vtx1NmlZ = $vtx1Nml[2] + $vtx1Pos[2];

float $vtx2NmlX = $vtx2Nml[0] + $vtx2Pos[0];
float $vtx2NmlY = $vtx2Nml[1] + $vtx2Pos[1];
float $vtx2NmlZ = $vtx2Nml[2] + $vtx2Pos[2];

//選択頂点と法線先端の座標をベクトルにする
vector $vtx1SVec = <<$vtx1Pos[0],$vtx1Pos[1],$vtx1Pos[2]>>;
vector $vtx2SVec = <<$vtx2Pos[0],$vtx2Pos[1],$vtx2Pos[2]>>;

vector $vtx1EVec = <<$vtx1NmlX,$vtx1NmlY,$vtx1NmlZ>>;
vector $vtx2EVec = <<$vtx2NmlX,$vtx2NmlY,$vtx2NmlZ>>;
//交点の座標を取得
float $crossingPosXYZ[] = `getLinesCrossing $vtx1SVec $vtx1EVec $vtx2SVec $vtx2EVec`;
if($crossingPosXYZ[3]==1)
{
clear $crossingPosXYZ;
$crossingPosXYZ = {0,0,0,1};
}
//カラムにセット
floatField -e -v $crossingPosXYZ[0] "NKJ_GetNormalCrossing_UI|cLayout|positionXField";
floatField -e -v $crossingPosXYZ[1] "NKJ_GetNormalCrossing_UI|cLayout|positionYField";
floatField -e -v $crossingPosXYZ[2] "NKJ_GetNormalCrossing_UI|cLayout|positionZField";
}

//-------------------------------------------------------
global proc NKJ_SetNormalCrossing()
{
//選択しているバーテックスの法線の角度を
//UIから取得した座標が焦点になる様に修正します。

//現在の選択からバーテックスリストを取得
string $vtxList[] = `ls -sl -fl -l`;
//焦点座標を取得
float $focusX = `floatField -q -v "NKJ_GetNormalCrossing_UI|cLayout|positionXField"`;
float $focusY = `floatField -q -v "NKJ_GetNormalCrossing_UI|cLayout|positionYField"`;
float $focusZ = `floatField -q -v "NKJ_GetNormalCrossing_UI|cLayout|positionZField"`;
float $locatorPos[] = {$focusX,$focusY,$focusZ};
//選択解除
select -cl;
//各バーテックスに対してループ処理
string $item;
for($item in $vtxList)
{
//バーテックスの座標を取得
float $vtxPos[] = `xform -q -a -ws -t $item`;
//法線として使う値を計算
float $x = $vtxPos[0] - $locatorPos[0];
float $y = $vtxPos[1] - $locatorPos[1];
float $z = $vtxPos[2] - $locatorPos[2];
//正規化
vector $normal = <<$x,$y,$z>>;
$normal = `unit $normal`;
//法線を設定
polyNormalPerVertex -xyz ($normal.x) ($normal.y) ($normal.z) $item;
}
}
//------------------

//メイン
{
string $title = "NKJ_GetNormalCrossing";
string $titleUI = $title + "_UI";
if(`window -ex $titleUI` == 1)
{
deleteUI $titleUI;
}
window -t $title $titleUI;
columnLayout "cLayout";

text -l $title;
text -l "--------------------";
text -l "This gets a focus of 2 vtxs";
text -l "normals, or aims selected vtxs";
text -l "normals to the focus.";
text -l "1)Reset rotations.";
text -l "2) Select 2 vtxs then click upper";
text -l "button to get their focus values.";
text -l "3) Select vtxs then click lower";
text -l "button to aim them to the focus.";

text -l "--------------------";
button -w 200 -c "NKJ_GetNormalCrossing" -l "Get Nml Focus Of Selected 2 Vtxs" getCrossingButton;
floatField -w 100 -v 0.0 positionXField;
floatField -w 100 -v 0.0 positionYField;
floatField -w 100 -v 0.0 positionZField;
button -w 200 -c "NKJ_SetNormalCrossing" -l "Set Nmls On Selected Vtxs" setCrossingButton;
window -e -w 210 -h 250 $titleUI;
showWindow $titleUI;
}
//-------------------------------------------------------
//終了

Friday, January 9, 2026

Mayaで法線を編集する (2)

引き続き法線についてです。

前回、スムースシェーディングでローポリ感を軽減する話をしました。
こんな感じですね。

カクカクした見た目が…

スムースシェードでハイポリ風に。
ポリゴン数は同じです。

ところで、このローポリ球の一部のポリゴン数を増やしたらどうなるでしょうか?
赤道付近にエッジを足し、球の輪郭に沿うように少し膨らませます。




ポリゴン数を増やした部分が段差の様な見た目になってしまいました…。
加工前と後の法線を比較してみましょう。


加工部分の法線を延長してみると、球の中心からずれているのがわかります。


ずれている法線の角度を修正し、延長線が球の中心を通る様にすれば段差の様な見た目は解消されます。
今回は、任意のオブジェクトの方角に法線を傾けるMELを作成し修正してみました。
球の中心に別のオブジェクト(ロケータ)を配置し、とりあえず全部の法線に対して実行。


段差状に見えていた箇所がきれいになりました。

普通ならガタつくこの様な形状も滑らかに出来ます。


MELはこちらです。次回も法線の話です。

//------------------
//------------------
global proc NKJ_AimSelectedVtxsNormalsToSelectedObj()
{
//選択しているバーテックスの法線の角度を、根本側が
//別のオブジェクトの方向を正しく向く様に修正します。
//(つまり、法線の根本方向の延長線上に、焦点となる
//オブジェクトが存在する様になります。)

//使用方法:
//1)このスクリプトを実行します。ダイアログが出ます。
//2)法線角度を修正したいオブジェクトの回転をリセットします。
//3)法線角度を修正したいバーテックスを全て選択します。
//4)焦点にしたい別のオブジェクトをOutlinerで追加選択します。
//5)ダイアログのボタンをクリックすると処理が行われます。

//------------------
//現在の選択からバーテックスリストを取得
string $vtxList[] = `ls -sl -fl -l -typ "float3"`;
//現在の選択からエイム対象オブジェクト(transform)を取得
string $locatorList[] = `ls -sl -l -typ "transform"`;
//ロケータの座標を取得
float $locatorPos[] = `xform -q -a -ws -t $locatorList[0]`;
//選択解除
select -cl;
//各バーテックスに対してループ処理
string $item;
for($item in $vtxList)
{
//バーテックスの座標を取得
float $vtxPos[] = `xform -q -a -ws -t $item`;
//法線として使う値を計算
float $x = $vtxPos[0] - $locatorPos[0];
float $y = $vtxPos[1] - $locatorPos[1];
float $z = $vtxPos[2] - $locatorPos[2];
//正規化
vector $normal = <<$x,$y,$z>>;
$normal = `unit $normal`;
//法線を設定
polyNormalPerVertex -xyz ($normal.x) ($normal.y) ($normal.z) $item;
}
}
//------------------
//メイン
{
string $title = "NKJ_AimNormalsToObj";
string $titleUI = $title + "_UI";
if(`window -ex $titleUI` == 1)
{
deleteUI $titleUI;
}
window -t $title $titleUI;
columnLayout "cLayout";

text -l $title;
text -l "--------------------";
text -l "This aims selected vertices normals";
text -l "roots to another object.";
text -l "1)Reset object rotations.";
text -l "1)Select vertices you want to edit.";
text -l "2)Add-select another object";
text -l "in Outliner.";
text -l "3)Click a button below.";
text -l "--------------------";
button -w 200 -c NKJ_AimSelectedVtxsNormalsToSelectedObj "Aim";
window -e -w 210 -h 140 $titleUI;
showWindow $titleUI;
}
//-------------------------------------------------------
//終了

Thursday, January 8, 2026

Mayaで法線を編集する (1)

 ローポリモデルでは、スムースシェードで角を滑らかに見せる事が一般的に行われます。ポリゴン数を増やさずにローポリ感を無くすのに有効な手段です。こちらはMayaで作ったモデルです。


スムースシェード無し。見るからにローポリ。


スムースシェード有り。ローポリ感が軽減される。

しかし、この「スムースシェード有り」のモデル、何だかちょっとダルい感じがしませんか?よくよく見ると、平面の部分が奥に行くに従ってちょっと暗くなっています。完全な平面ではなく、ごくわずかに丸みを帯びている様な…



その理由は3Dソフトの自動計算にあります。角を滑らかにする処理に平面の四辺が影響を受け、四辺の見た目が少し外向きになってしまっているのです。

こちらの図の緑色の線は、それぞれの地点の表示計算上の向きを表しています。


横から見ると一目瞭然です。形状的には垂直・水平な面が、表示計算上はわずかに外側に向いています。


この緑色の線は「法線」と呼ばれます。この「法線」を各平面に対して垂直にしてやれば、平面がきちんと平面に見える様になるはずです。
Mayaには法線の向きを数値入力出来る機能がありますので、それを使って修正してみる事にします。

法線の値はXYZのベクトルで指定します。MayaはY軸が垂直軸なので、例えば上の面の法線であれば、
X=0.0
Y=1.0
Z=0.0
という数値にします。六面全てで同様に法線の値を設定します。


結果です。ほんの少しの差ですが、平面の丸み感が無くなり印象がピシッとしました。


さて、上の例では修正する面が水平・垂直だったので法線の値の指定もXYZ = 0・1・0みたいな切りの良い数値に出来ましたが、これが下の図みたいな形だったらどうでしょうか?


この面に設定するべき法線の値を調べるには…?

こういう場合は、下図の二つのベクトルの外積を求めます。ベクトルの外積はベクトルが為す面と垂直なので、それが求める法線となります。


ベクトルの外積を計算するというのは何だか難しそうですが、Mayaの場合はcrossというコマンドで即座に答えが出ます。中学レベルの数学すら心もと無い私でも安心です。
Mayaヘルプの解説ページ

後は二つのベクトルの値です。根本の一頂点と先端の二頂点の座標が分かれば二つのベクトルが作れるので、まずはこの面に属する頂点の名前を調べます。

ここからは自動化を見据えてMELスクリプトを使っていきます。
まず、面をMayaで選択しておいてlsコマンドで面の名前を調べます。


面の名前が分かったので、その面に属する頂点の名前を調べます。
polyInfoコマンドを使います。
ここで、Mayaをお使いの方は「面から頂点名を取得するならpolyListComponentConversionの方が扱いが楽じゃないの?」と疑問に思われるかもしれません。
確かにpolyInfoコマンドだと文字列から数字を取り出したりしなければならず面倒なのですが、これを使うには理由があり、polyListComponentConversionだと頂点の並び順が調べられないのです。
頂点の正しい並び順が分からないとcrossコマンドに渡すベクトルの順番が逆になり、計算した法線が反対を向く可能性があります。なのでpolyInfoコマンドを使う必要があるのです。

さて、頂点の番号が分かった所で座標を調べます。Xformコマンドです。
必要な三頂点の座標を調べ、矢印の先端にあたる座標から根本の座標を引いてベクトルにします。こうして得られた二つのベクトルを、最初に述べたcrossコマンドに渡します。
引数としてはvector型を使います。これは三個のfloatで構成される変数型です。
これで法線ベクトルが計算出来ました。

これを、対象となる面に属する全頂点の法線として設定します。polyNormalPerVertexコマンドを使います。
引数はfloat型で渡すので、.x・.y・.zという形で三個のfloatをvector変数から取り出して使います。ちなみに上記の例の様に括弧に入れてやらないとエラーになります。一度float型変数に入れてから渡すのもありです。

という事で、法線が面に垂直にセット出来ました。


ここまでの手順をスクリプトにまとめました。
スクリプト本体はこちらです。次回も法線加工の話です…

//------------------------------------------------------------------
//NKJ_MakeSelectedFacesLookFlat.mel
//Takahiro Nakajima 2025

//概要:
//スムースシェードが掛かったオブジェクトで、任意のフェイスの各頂点の
//法線を並行にし、そのフェイスが完全に平面に見える様にします。

//使用方法:
//1)このMELスクリプトを実行します。
//ダイアログが開きます。
//2)処理対象のオブジェクトの回転値をリセットします。
//3)処理対象のフェイスを全て選択します。
//4)ダイアログのボタンを押すと処理が実行されます。

//-------------------------------------------------------
//-------------------------------------------------------

global proc vector NKJ_GetVtxPosVector(string$vtx)
{
//バーテックスの座標をvectorで返します。
//-----------------
//バーテックスの絶対座標を取得
float $vtxPos[] = `xform -q -ws -t $vtx`;
//座標をvectorにする
vector $result = <<$vtxPos[0],$vtxPos[1],$vtxPos[2]>>;
return $result;
}
//-------------------------------------------------------
global proc vector NKJ_GetFaceNormalVector(string$face)
{
//$faceの法線を取得します。
//-----------------
//$faceからオブジェクト名を作成
string $tempList[];
clear $tempList;
tokenize $face "." $tempList;
string $objName = $tempList[0];

//$faceのバーテックス番号を並び順で取得
string $vtxNums[] = `polyInfo -fv $face`;
string $vtxNumList[];
clear $vtxNumList;
tokenize $vtxNums[0] " " $vtxNumList;

//バーテックス名を3個並び順で作成
string $vtxA = $objName + ".vtx[" +  $vtxNumList[2] + "]";
string $vtxB = $objName + ".vtx[" +  $vtxNumList[3] + "]";
string $vtxC = $objName + ".vtx[" +  $vtxNumList[4] + "]";

//上記各バーテックスの絶対座標をvectorで取得
vector $vtxAPos = `NKJ_GetVtxPosVector $vtxA`;
vector $vtxBPos = `NKJ_GetVtxPosVector $vtxB`;
vector $vtxCPos = `NKJ_GetVtxPosVector $vtxC`;

//ベクトルABとベクトルACを作成
vector $vecAB = $vtxBPos - $vtxAPos;
vector $vecAC = $vtxCPos - $vtxAPos;

//ベクトルABとベクトルACの法線を取得し単位ベクトルにする
vector $normal = `cross $vecAB $vecAC`;
$normal = `unit $normal`;

return $normal;
}
//-------------------------------------------------------
global proc NKJ_AlignVtxNormalToFaceNormal(string$face)
{
//$faceの各バーテックスの法線を$faceの法線に合わせます。
//-----------------
//$faceの法線を取得
vector$faceNormal = `NKJ_GetFaceNormalVector $face`;
float $normalX = $faceNormal.x;
float $normalY = $faceNormal.y;
float $normalZ = $faceNormal.z;
//$faceの全頂点を取得
string $vtxList[] = `polyListComponentConversion -tv $face`;
$vtxList = `ls -fl $vtxList`;
//各頂点の法線をセット
string $item;
for($item in $vtxList)
{
polyNormalPerVertex -e -xyz $normalX $normalY $normalZ $item;
}
}
//-------------------------------------------------------
global proc NKJ_AlignVtxNormalToSelectedFaceNormals()
{
//選択している全フェイスで、バーテックスの法線をフェイスの法線に合わせます。
//実行後は選択が解除されます。
string $sel[] = `ls -sl -fl -l`;
select -cl;
string $item;
for($item in $sel)
{
NKJ_AlignVtxNormalToFaceNormal $item;
}
}
//-------------------------------------------------------
//メイン
{
string $title = "NKJ_MakeSelectedFacesLookFlat";
string $titleUI = $title + "_UI";
if(`window -ex $titleUI` == 1)
{
deleteUI $titleUI;
}
window -t $title $titleUI;
columnLayout "cLayout";

text -l $title;
text -l "--------------------";
text -l "This makes faces look flat";
text -l "with keepig their soft edges.";
text -l "1)Reset your object's rotations.";
text -l "2)Select faces you want to edit.";
text -l "3)Click a button below.";
text -l "--------------------";
button -w 200 -c NKJ_AlignVtxNormalToSelectedFaceNormals "Make Selected Faces Look Flat";
window -e -w 210 -h 140 $titleUI;
showWindow $titleUI;
}
//-------------------------------------------------------
//終了

Thursday, August 29, 2024

画面をキャプチャーして反転表示する


紙に絵を描いた後に裏から透かしてチェックするというのは良くあるやり方ですが、同じ事を3Dソフトでやりたい場合はスクリーンショットを撮ってペイントソフト等で反転させる必要があります。もっと簡単にする為にPythonスクリプトを書いてみました。

以下のサイトを参考にさせて頂きました。
Pythonの画像処理ライブラリPillow(PIL)の使い方 | note.nkmk.me
【完全版】たった10ステップ!PythonのPillowライブラリで画像処理をマスターしよう | ちょこっとプロ! (chocottopro.com)
PythonでPillow(PIL)モジュールを使用しスクリーンショットを撮る | Men of Letters(メン・オブ・レターズ) – 論理的思考/業務改善/プログラミング (kazuuu.net)
Python, Pillowで画像の一部をトリミング(切り出し/切り抜き) | note.nkmk.me

以下、Windows11での手順です。
まずPythonをインストールし、続いてPillowというライブラリをインストールします。
次に新規テキストファイルにこちらのスクリプトを記入し、拡張子.pyでセーブします。

from PIL import Image
from PIL import ImageGrab
pic = ImageGrab.grab()
pic = pic.transpose(Image.FLIP_LEFT_RIGHT)
pic = pic.crop((500,100,1420,980))
pic.show()

そのファイルをダブルクリックするとWindows PowerShellで実行され、左右反転したスクリーンキャプチャー画像が表示されます。


このスクリプトではクロップされた画像になりますが、クロップが必要無い場合は5行目を削除します。
なお、このままだとスクリーンキャプチャーにWindows PowerShell自体のウィンドウが映り込んでしまうので、邪魔にならない様にWindows PowerShellの設定で起動サイズを小さくし、軌道位置を固定しておきます。(もっと良いやり方がありそうですが…)



YouTubeチャンネル更新

SculpLand - YouTube

Sunday, May 23, 2021

SFの箱を作る

「マンガの肉」の様にポピュラーではないが、「SFの箱」も結構多くの人に共通認識的なイメージがあると思う。あちこちに補強の溝とか凹みとかが入っているああいう奴、あれを作ってみる。

まずCGソフトでモデリングする。

UVを展開し、A4の紙数枚に分けてプリントアウト・切り抜き。

完成。B5幅位の大きさになった。

内箱も作ったので物が収納出来る。大体A6サイズ。各部の凹みを避けた結果かなり容積が小さくなってしまった。

ライトを一つにして撮影するとSF感アップ!

Tuesday, January 12, 2021

ノートを作る

自作は楽しい。という事でノート作り。

材料は普通紙、厚紙、糸。
普通紙15枚ほどを二つ折りにする。
糸を通す切り込みを開ける。倍力ニッパを使うと簡単だ。
穴では無いので見た目は少々良くないが、実用上は全く問題無い。
次に、そこから一枚だけ外す。
外した紙を厚紙と重ねる。
切り込みを通して線を描く。
紙を外す。切れ目の箇所に線が入った。
二つ折りする。
折り目と線が交差する所にピンで穴を開ける。
ひっくり返して確認する。折り目と穴が余りずれていなければOK。
厚紙と普通紙を全部重ねる。
ここから綴じ針で縫っていく。先端はとがっていなくてもいいので、やすりで丸めておくと余計な気を使わなくて済む。
中央から始める。
中央>端>中央>逆の端>中央の順で縫っていく。糸の端は、数回縫っておけば結ぶ必要は無い。
端を切って完成。
表紙のマークはデザインというより実用的な意味で必要だ。無地だと手に取る前に向きが確認しにくいのだ。
落書き。
自作のノートに落書きするのは楽しい。