基礎から始める3dsmax/Python(MaxPlus)プログラミング⑨

3dsmax/Pythonの連載も9回目になりました。一旦ここで3dsmaxはお休みして、8月からはMaya-Pyhtonの連載を再開します。

では今回のプログラムです。今回も長いですが、細かくコメントを入れています。元のHELPにあったプログラムはこちらです。

import MaxPlus as mp
# ------------- ピラミッドのポリゴンメッシュを作成する関数
def makePyramidMesh(mesh, size = 30.0):
 # ------------- 頂点数の指定
 mesh.SetNumVerts(4)
 # ------------- 面の指定
 mesh.SetNumFaces(4)
 # ------------- 4つの頂点座標の指定。引数sizeを利用
 mesh.SetVert(0, mp.Point3(0.0, 0.0, size*2))
 mesh.SetVert(1, mp.Point3(-size/2, -size/2, 0.0))
 mesh.SetVert(2, mp.Point3(-size/2, size/2, 0.0)) 
 mesh.SetVert(3, mp.Point3(size, 0.0, 0.0)) 
 # ------------- 4つの頂点を組み合わせてポリゴンを生成
 mesh.GetFace(0).SetVerts(0, 1, 2)
 mesh.GetFace(0).SetEdgeVisFlags(1,1,0)
 mesh.GetFace(1).SetVerts(0, 2, 3)
 mesh.GetFace(1).SetEdgeVisFlags(1,1,0)
 mesh.GetFace(2).SetVerts(0, 3, 1)
 mesh.GetFace(2).SetEdgeVisFlags(1,1,0)
 mesh.GetFace(3).SetVerts(1, 2, 3)
 mesh.GetFace(3).SetEdgeVisFlags(1,1,0)
 # -------------ジオメトリとトポロジーキャッシュを無効化して再構築
 mesh.InvalidateGeomCache()
 mesh.InvalidateTopologyCache()
# -------------メインプログラム
def main():
 # -------------ポリゴンメッシュオブジェクトの生成
 geom = mp.Factory.CreateGeomObject(mp.ClassIds.TriMeshGeometry)
 tri = mp.TriObject._CastFrom(geom)
 mesh = tri.GetMesh()
 # ------------- 上のピラミッド作成関数を実行。nodeに設定
 makePyramidMesh(mesh)
 node = mp.Factory.CreateNode(tri)
 # ------------- バーテックスカラーの設定を3つ
 mesh.ColorPerVertexMap.SetNumTextureVertices(3)
 # ------------- 3つのバーテックスカラーの色をRGBに設定
 mesh.ColorPerVertexMap.SetTextureVertex(0, mp.Point3(1, 0, 0))
 mesh.ColorPerVertexMap.SetTextureVertex(1, mp.Point3(0, 1, 0))
 mesh.ColorPerVertexMap.SetTextureVertex(2, mp.Point3(0, 0, 1))
 # ------------- 各バーテックスに3つの色を設定
 mesh.ColorPerVertexMap.SetTextureFace(0, 0, 0, 0)
 mesh.ColorPerVertexMap.SetTextureFace(1, 1, 1, 1)
 mesh.ColorPerVertexMap.SetTextureFace(2, 2, 2, 2)
 mesh.ColorPerVertexMap.SetTextureFace(3, 0 ,1, 2)
 node.VertexColorMode = True

# -------------リセットしてメインを実行
mp.FileManager.Reset(True)
main()
# -------------ファイルセーブする
fm = mp.FileManager
fm.Save("E:\Bros_test.max")
print fm.GetFileNameAndPath()

これを実行すると、このように頂点カラーが付いたピラミッドを作成します。

というわけで今回はポリゴンメッシュをPythonだけで生成する、というのをチャレンジしてみます。

3行名:def makePyramidMeshで、モデリングをしています。引数sizeで大きさを決めています。
5行目と7行目、mesh.SetNumVerts(4)mesh.SetNumFaces(4)で頂点数とフェイスの数を4つづつと定義します。
9~12行目でmesh.SetVert(X,Y,Z)という命令でポリゴンの頂点(バーテックス)0~3の座標をXYZを設定します。以下の図とプログラムを見比べてください。

14,16,18,20行のmesh.GetFace(ポリゴン番号).SetVerts(頂点番号, 頂点番号, 頂点番号)でポリゴン生成します。

15,17,19,21行のmesh.GetFace(ポリゴン番号).SetEdgeVisFlags(1,1,0)でそのエッジの表示をONにしています。3dsmaxはご存知の通り、全て3角ですが、4角以上のポリゴンにエッジを非表示にできます。

23,24行でジオメトリとトポロジーキャッシュを無効化して再構築しておくことで、モデルが正しく表示されます。

26行目からメインプログラムです。
28~30は毎回おなじみのジオメトリ作成の一連の流れです。違うのは、CreateGeomObject(mp.ClassIds.TriMeshGeometry)の最後のTriMeshGeometryで三角形ポリゴン生成する設定です。

32、33行目でmakePyramidMesh関数を実行します。
35行目から頂点カラーを設定します。ポリゴンに色を付ける基本機能です。これに本来はライティングと組み合わせて、シェーディングを行います。

35行目でmesh.ColorPerVertexMap.SetNumTextureVertices(3)で頂点カラーを3つ定義します。
37~39行目のmesh.ColorPerVertexMap.SetTextureVertex(頂点カラー番号, mp.Point3(赤, 青,緑))で色を定義しています。RGBは0~1で設定します。

41~44行のmesh.ColorPerVertexMap.SetTextureFace(ポリゴン番号, 1つめの頂点カラー番号, 2つめの頂点カラー番号, 3つめの頂点カラー番号)と定義しています。1つのポリゴンに3つ頂点があり、定義した色をそれぞれ割り当てます。0~2までは3つを同じにしたので、1の面が同じ色で塗りつぶしますが、そこの面は3つの頂点をRGBで塗ったので間は補完され、ぐらーでーションになります。


48、49行でシーンリセットして、メインを実行です。
51~53行目で、このシーンを自動でファイルセーブします。同時にファイルのパスを表示します。
fm = mp.FileManager
fm.Save(“E:\Bros_test.max”)
print fm.GetFileNameAndPath()


これも便利ですので、ぜひ活用ください。

出来上がったオブジェクトはちょっと変な性質を持っています。

この物体はモディファイアが追加できません。

編集可能メッシュに変換すると、何でもできるようになります。

しかし、レンダリングできません。法線モディファイアを入れてやればOKです。
今回は簡単な例でしたが、3dsmaxやMayaなど3DCGの根本的な仕組みの理解にもなりますし、CSVなど頂点情報を外部から読み込んでモデル生成なども可能でしょう。

UnrealEngine4.20の新機能とUnrealStudio その2

前回の続きです。その際はまだ4.20が正式リリースしてませんでした。
UnrealStudioも正式バージョンアップして、前回の新しいテンプレートのマニュアルができてました。

残念ながら本日時点では、日本語版はありません。
前回発見できなかった機能がありました。
スペースキーを押しっぱなしにしすると、メニューが出ます。

「X-ray Mode」といい、「Apply X-ray」でレントゲン写真みたいな表示になります。
「Isolate X-ray」で、選んだ部品以外がレントゲン写真になり、そのパーツだけを良く確認できます。

「Remove X-ray」で元の戻ります。

もうひとつ、アウトライナで「ProductViewer_Collector」という Actorを選んで、詳細の「Navigation Mode」をFlyingModeにできます。
これにより、いつものUE4のマウス操作やWASDキー、カーソルキーでの操作が可能です。
しかし、先ほどのレントゲンやパーツの移動などの機能がありません。


上の図の一番下に「VR-Previwe」のスイッチがあります。これでViveやOculusなどのコントローラーでパーツの移動や視点テレポート移動が使えます。
※「Unreal Studio Product Viewer Template」サイトより画像リンク引用

では、サンプルではなくオリジナルのパーツを使いたい場合はどうすればいいでしょうか?

テストとして、Coneのポリゴンメッシュをコンテンツブラウザにインポートします。もちろんDataSmithでCADデータや3dsmaxデータでもOKです。
注意点は、「ProductViewer」フォルダ内に入れないとダメです。この中のサブフォルダなどを作って階層化も問題ありません。

レベルに出したら、UE4を普段からお使いの方はお分かりかと思いますが「ムーバブル」設定します。

ProductViewerCollectorを再度選択します。先ほどモード切り替えの上の項目に、「IntactiveRoot」「TeleportSurfaceRoot」があり、「+」で追加できます。
「IntactiveRoot」は、分解したりレントゲンなどの機能を付ける親のActorです。
「TeleportSurfaceRoot」は、VRの際にテレポートする床の設定です。

「IntactiveRoot」をアウトライナから探し、Coneをドラッグして親子付けします。

これだけの設定で、このテンプレートの機能を使うことができるようになります。

では、実際にCADデータを使ってみましょう。

これはAutodesk Inventorのチュートリアルのデータです。
新しいプロジェクトを起動します。

コンテンツブラウザから「新追加」から、「…コンテンツパック」を選択

プロジェクトのテンプレートを追加します。

ImportCADを実行

CADデータのここではアセンブリファイルを読み込みます。関連するパーツは全て読み込まれます。

ここでポリゴンリダクションの設定などを行いますが、今回はそのまま。

こんな感に変換しています。以前もお話しましたが、軸はY=90を入れないとだめです。
当たり前ですが、車輪など複数あるパーツはインスタンスで、コンテンツブラウザには1つしかありません。また、CADの軸の拘束を配慮して、回転するもののピボットは回転軸に正確に合っています。

これをMayaにFBXで書き出してみました。

1万ポリゴンと余裕でVRで安心して使えるポリゴンになっています。ピボットが正しくなっているのが解ります。

ProductViewerCollectorをレベルに出します。

このテンプレートでは、PlayerStartを使いません。削除してOKです。
ProductViewerCollectorの位置がカメラになり、X+がカメラの前になるようです。

各パーツをムーバブルにします。

「IntactiveRoot」をインポートしたCADデータの最上階層にして、
「TeleportSurfaceRoot」を初めからある床にします。

なんと、これで完成です!以下、完成動画をぜひご覧ください。

基礎から始める3dsmax/Python(MaxPlus)プログラミング⑧

今週はこんなプログラムです。長いですが、コメントを詳細にしました。

import random, math, MaxPlus as mp
#-------------------- 0.0~1.0の範囲のfloat型の乱数を返す
def rnd():
 return random.random()
#-------------------- -3.14+乱数(0~1)*2*3.14=-3.14~13.14
def rndAngle():
 return -math.pi + (rnd() * 2 * math.pi)
#-------------------- クォータニオンの軸と角度をランダムに決める
def rndQuat():
 return mp.Quat(rnd(), rnd(), rnd(), rndAngle())
#-------------------- (0~1)*100-50=-50~50
def rndDist():
 return rnd() * 100.0 - 50.0
#--------------------座標のX,Yをランダムに
def rndPosition():
 return mp.Point3(rndDist(), rndDist(), 0)
#-------------------- (0~1)*2+1=1~3倍
def rndScaleAmount():
 return rnd() * 2.0 + 0.1
#--------------------XYZをランダムスケール
def rndScale():
 return mp.Point3(rndScaleAmount(), rndScaleAmount(), rndScaleAmount())
#-------------------- 移動、回転、スケールをランダムに
def randomTransformNodes(nodes):
 for n in nodes:
  n.Scaling = rndScale()
  n.Position = rndPosition()
  n.Rotation = rndQuat()
#-------------------- オブジェクト作成 ここでforで繰り返し作成
def createNodes(obj, cnt):
 return [mp.Factory.CreateNode(obj) for i in range(cnt)]
#-------------------- メイン関数
def main():
 box = mp.Factory.CreateGeomObject(mp.ClassIds.Box) 
 box.ParameterBlock.Length.Value = 10.0
 box.ParameterBlock.Height.Value = 10.0
 box.ParameterBlock.Width.Value = 10.0
 nodes = createNodes(box, 25)
 randomTransformNodes(nodes)
#-------------------- ジーンを初期化して実行
mp.FileManager.Reset(True)
main()

では、早速実行してみましょう。

実行の度に違うガレキの山ができます。これをアタッチして、岩のテクスチャを貼れば背景に使えそうですね。

26行目のn.Scaling = rndScale()28行目のn.Rotation = rndQuat()を削除すると・・・

28行目の削除だけだと・・・
26行目の削除だけだと・・・

今回はランダム関数の塊です。位置、回転、スケールをランダムにして配置しています。

プログラムの書き方の例でもあります。複雑になって解らなくなるより、今回のように1行しかない関数を集めて作る方法をお勧めします。最初から解説します。
import random, math, MaxPlus as mp
これは過去には3行で
import random
import math
import MaxPlus as mp

と書いていましたが、このように書いてもOKです。
def rnd():で0.0~1.0の範囲のfloat型の乱数を作ります。
return random.random()は引数自体にランダムな数字を作るPythonの関数を入れて短く書いています。今回のプログラムは全てこの方法です。

次のrndAngle():が解りにくいかもしれません

return -math.pi + (rnd() * 2 * math.pi)
math.piは3.14の円周率です。rnd() は0.0~1.0で変化する乱数です。難しい場合、最少と最大の値を入れてみるといいです。

最少:0・・・-3.14+0*2*3.14=-3.14
最大:1・・・-3.14+1*2*3.14=3.14
といことは、-3.14~3.14に変化する値になります。
クオータニオンは「ラジアン」で角度を計算しますから、3.14=180度ですから、-180から180度に回転できるので、1周しますね。
以下、同様です。
13行目 return rnd() * 100.0 – 50.0 ・・・-50~50=XYの位置
19行目 return rnd() * 2.0 + 0.1 ・・・1~3=スケール

今回は関数が連動しています。
rnd()→rndAngle()→rndQuat()→randomTransformNodes(nodes):→main():

rnd()→rndPosition()→randomTransformNodes(nodes):→main():


rnd()→rndScaleAmount()→rndScale()→randomTransformNodes(nodes):→main():

createNodes(obj, cnt)→main()
落ち着いて、プログラムの流れを読み取ってください。
一番今回特殊なのは、30,31行です。
createNodes(obj, cnt):
return [mp.Factory.CreateNode(obj) for i in range(cnt)]
この1行でBOXを作成して、引数のcntをforで繰り返しているのです。
ちょっと癖のある書き方ですが、プログラミングはこのように様々な人の工夫を参考にすると早く覚えることができます。
注意なのは、createNodes(obj, cnt)mp.Factory.CreateNode(obj)は全然別物です。この連載は基本AutodeskのHelpにあるサンプルを改良してチュートリアルを作っていますが、これは間違えやすい悪い例です。mp.Factory.CreateNodeは3dsmaxの命令ですから変更できないので、できれば関数の名前を間違えにくいものにしましょう。

def main()は、Boxを定義して大きさを決め、コピー数を決めてrandomTransformNodes関数を呼んでいます。これも難しくないですね。

createNodes(box, 25)でBox25個がnodesに入ります。
randomTransformNodes(nodes)で25個の位置、回転、スケールを変更します。for命令で繰り返す必要はありません。

範囲を-500~500、コピー数を500にしてみました。

さらに距離5000で1万個です。自由に変化を付けることができます。

今回はここまでです。