Blenderとか3DCGとか

スクリプト寄りです。

一つの.blendファイルでEeveeとCycles両方をレンダリングする(リンクを重視する方法)

前回の記事で紹介した、一つの.blendファイルに複数のシーンを持たせる方法を使用して

を行ってみます。
f:id:masayuki-osaka-blend:20190727184814p:plain
 Eeveeには影のみを受け取る設定が標準では存在しないため、それをCyclesで補おうというアプローチです。

 と、記事を書こうとしている最中に、既に同じことをしている動画を見つけてしまいました・・・
youtu.be なんとも間が悪い上に、自身もビューレイヤーの有効な使いかたをこれで知り、前回の記事に手を加えた次第です。
 影の調整やレンダリング設定の最適化にも触れているので、この手法をお勧めします。

さて、書くことが無くなってしまうので、ここでは敢えてシーンとオブジェクト、コレクションのリンクに拘った手法を紹介します。
 この手法はリスクを伴いますが、リンクについて考える一助になれば幸いです。

シーン共通の設定を行う

スザンヌが地面に影を落とすシチュエーションを想定します。
f:id:masayuki-osaka-blend:20190801000448p:plain

このシーンを基に、Eevee用のシーンとCycles用のシーンへ拡張していきますが、レンダー設定は各シーンで別々に管理されます。
 そのため、解像度などの共通させたいシーン設定は、最初の段階で行うことをお勧めします。今回の例では、レンダリングの背景を透明にするための設定をこの段階で行います。

f:id:masayuki-osaka-blend:20190801001832p:plain
シーンを増やした後では、シーン毎にこれを行う必要があります

シーンを増やす

シーンを増やす際、Linked Copyを選択してください。
f:id:masayuki-osaka-blend:20190802092318p:plain

 この段階で、Sceneと新しく出来たScene.001、オブジェクト等のリンク関係は図のようになります。

f:id:masayuki-osaka-blend:20190801022046p:plain
各シーンは、矢印にそって辿れるものを扱えます
★赤丸のView Layer, Scene Collectionは同じ名前で同じアイコンですが、この二種は別々の物として扱われます。

レンダラーの設定

SceneとScene.001のレンダラーをそれぞれ、EeveeとCyclesにします。
 また、それぞれのシーン名をScene.eevee, Scene.cyclesへと変えました。
f:id:masayuki-osaka-blend:20190801014711p:plain

この結果、各シーンでの関係は次のようになります。

f:id:masayuki-osaka-blend:20190801021629p:plain
Transparent, Eevee, Cyclesはレンダー設定なので、シーン別に保持されます

階層とリンク関係を変更

PlaneはCyclesでの落ち影ちとしてのみ必要なので、Eeveeのシーンから取り除きます。
 ただし、そのまま削除してしまうと両シーンから失われてしまいます。そこで、Scene.cyclesのアウトライナで、PlaneをScene Collection直下に移動させます。

f:id:masayuki-osaka-blend:20190801024247p:plain
ドラッグアンドドロップでScene Collectionへ移動させます
PlaneがScene.eeveeのScene Collectionから辿れない場所へ移ったことで、Scene.cyclesのみで取り扱えるようになりました。
f:id:masayuki-osaka-blend:20190801025556p:plain
Scene.eeveeから矢印を辿っても、Planeに辿り着けません

マテリアルとリンク関係

この段階に限らず、スザンヌにマテリアルを割り当てると両方のシーンへ反映されます。
 その理由は、マテリアルはスザンヌの下の階層に配置され、どちらのシーンからでも辿れるようになるからです。

f:id:masayuki-osaka-blend:20190802063056p:plain
マテリアルはシーンではなく、オブジェクト(にリンクしたメッシュ)へリンクします

Eevee側でのレンダリング

Scene.eeveeに切り替えてレンダリングすると、プレーンはリンクが無いため現れません。
 今回の例ではEevee側でプレーン以外をレンダリングするのが目的なので、これでスザンヌのみのレンダリング結果が得られました。
f:id:masayuki-osaka-blend:20190802064156p:plain

Cycles側でのレンダリング

Scene.cyclesに切り替えて、落ち影のみを取得するために以下の設定を行います。

  • スザンヌレンダリング結果が背景の透明と同じになるように
  • プレーンは影のみを表示する物体になるように

この2つは共にCyclesのみの機能で、Eeveeには搭載されていません。

f:id:masayuki-osaka-blend:20190802065117p:plain
スザンヌへHoldout, プレーンへShadow Catcherを設定します
これらはシーンに対してではなくオブジェクトに対しての設定です。そのため、Scene.eevee側へも影響します。
 ただし、Eeveeはこの設定を活用することができないため、Scene.eeveeではプロパティにもレンダリング結果にも反映されません。
f:id:masayuki-osaka-blend:20190802071427p:plain
Scene.eeveeからも辿れますが、レンダラーがEeveeのため意味がありません
設定後、レンダリングを行うと落ち影のみが取得できます。
f:id:masayuki-osaka-blend:20190802072512p:plain

コンポジット

前回の記事で紹介した動画では、Eeveeだけを使用して落ち影用オブジェクトを作成するため、コンポジットが不要でした。 youtu.be

ですが本記事の手法ではコンポジットが必要になります。

コンポジットエディタでRender Layerノードを複製し、それぞれScene.cycles, Scene.eeveeのレンダリング結果を表示するように設定します。

f:id:masayuki-osaka-blend:20190802073354p:plain
赤丸の所をクリックし、表示させるレンダリング結果を選択します
あとは落ち影にスザンヌを重ねると最終結果が得られます。
f:id:masayuki-osaka-blend:20190802074119p:plain

コンポジットとシーンの関係

コンポジットは、シーン毎に別々で保持されています。
 そのため例えばScene.cyclesでコンポジットを組んだ場合、Scene.eeveeに切り替えてレンダリングを行うと臨んだ最終結果が得られません。

f:id:masayuki-osaka-blend:20190802075002p:plain
Scene.cyclesでコンポジットを組んだ例
組んだコンポジットを見失ったときは、シーンを切り替えてみてください。

逆にこれを利用すれば複数のコンポジットを用意することができ、シーンを切り替えてF12を押すだけで最終イメージを選べます。
 コンポジットのノウハウを貯めたファイルをひとつ持っておくのも良いかもしれません。
f:id:masayuki-osaka-blend:20190802083851p:plain
Compositing Nodetreeというものがコンポジットの事で、シーン毎に保持されます

最後に

Blender2.8ではコレクションとビューレイヤーの登場により、シーンの切り替えはそれほど頻繁には使用しなくなるかも知れません。
 ですが取得したいレンダリング結果の種類が増えるほど、選択肢として有効になっていきます。

また、シーンに限らずリンクの概念は混乱しやすいものですが、慣れると様々な取り回しが出来るようになります。
 是非、リンクの図を思い浮かべながら色々操作してみてください。

一つの.blendファイルに複数のシーンを持たせる

  • <2019/07/30 修正> シーン内でのUnlinkは危険なため、ビューレイヤーの使用を推奨する記述に変更いたしました。


    Cyclesとは違い、Eeveeには影のみを受け取るオブジェクト設定がありません。
     そのためEevee単体でそういったオブジェクトが必要になる場合、どのようにすると良いかという質問がTwitterの#blender質問室に挙がっていました。

    自分なりに調べていたのですが、他の方から回答に挙がったのがこちら。
    t.co
    Eevee内のノードで自作するアプローチです。すごく良いです。
     ただBlenderに限らずアプローチは色々あるもので、試したことをお蔵入りさせるのも勿体無いと思い、書き残すことにしました。

    自分のトライしたやり方は、1つのファイル内に2つの「シーン」を持たせる方法です。
     それぞれの設定を変えることでEeveeとCyclesのレンダリングを一度に行い、コンポジットで合成するというものでした。
    f:id:masayuki-osaka-blend:20190727184814p:plain
    一度のF12で、EeveeとCyclesのレンダリングが両方走ります
    これはこれで出来ることが増えるので、以下、シーンを複数持たせる方法のお話です。

    リンクについて

    まず、シーンを増やす前に、リンクについて説明します。
     オブジェクトを選択してショートカットalt + dを押すと、特殊な複製になります。この方法で増えたものは、片方の内容が変わるともう片方も影響を受けます。
    f:id:masayuki-osaka-blend:20190727185842p:plain
    スザンヌをalt+dで増やして片方を変形させようとした図
    アウトライナを見てみると、メッシュ「Suzznne」は二か所にありますが、同じ名前で同じデータの種類です。つまり同じものが二か所にリストアップされています。
    f:id:masayuki-osaka-blend:20190727191133p:plain
    赤丸のところは、実際は全く同じもの

    この関係を、「リンク」と呼びます。
    f:id:masayuki-osaka-blend:20190727202306p:plain
    2つのオブジェクトが同じメッシュにリンクしている状態

    シーンの増やし方

    シーンを増やすボタンは、ウィンドウ右上にあります。
    f:id:masayuki-osaka-blend:20190727193417p:plain
    赤丸のボタンでシーンを増やせます
    押すと挙動を四種類から選択することになりますが、それぞれ大まかに以下のような違いがあります。
    New
    なにもない、まっさらなシーンを作ります。レンダー設定など、オブジェクト以外のものも引き継がれずに初期状態と同じになります。
    Copy Settings
    レンダー設定等はコピーされますが、オブジェクト等は引き継がれません。そのため、ビューポートはまっさらになります。
    Linked Copy
    レンダー設定等はコピーされます。オブジェクト等はすべて追加前のシーンとリンクされます。次項で例を挙げます。
    Full Copy
    レンダー設定等はコピーされます。オブジェクト等はすべて複製され、別名になります。

    いずれもScene.001というシーンができ、シーンを切り替えられるようになります。
    f:id:masayuki-osaka-blend:20190727201059p:plain

    複数シーン間の関連性について

    Linked Copyでシーンを増やしたとき、片方のシーンでオブジェクトの位置を変えると、もう片方のシーンでも反映されます。マテリアルの追加や編集も同様に、両方のシーンへ影響します。
     一方で、レンダー設定は別々のものなので、片方のレンダラーを変えても、もう片方への影響はありません。
    f:id:masayuki-osaka-blend:20190727210854p:plain
    シーン設定はリンクされません
    アウトライナで表示形式を「Blender File」へ変更することで、複数シーン間でのリンクの状態をもう少し詳しく確認できます。
    f:id:masayuki-osaka-blend:20190728001138p:plain
    各オブジェクトがScene, Scene.001の両方へリンクされているのが判ります
    各オブジェクトはリンクされているため、片方のシーン内で削除(デリート)してしまうと、両方のシーンから失われます。
     もし片方のシーンのみリンクを無効化したいものがある場合、コレクションを使用して対象を管理する必要があります。

    コレクション

    コレクションは、オブジェクト等をまとめて管理するための機能を持ちます。
     フォルダに似ていますが、中に入っているのは実際のオブジェクトではなく、オブジェクトへのリンク情報に似たものだと考えてください。

    先程の図のリンク関係は次のようになります。
    f:id:masayuki-osaka-blend:20190727232524p:plain

    ★「Scene Collection」は同じ名前で同じアイコンですが、これだけは別々のものです。
    Blenderの内部では別のもとして扱われています)
     それ以外のコレクションは、これまでの説明と同じく、名前が同じであれば同一のものです。

    リンクを無効化する

    リンクを無効化するには、コレクションの横にあるチェックをオフにします。
     ただし予め、無効化したいものだけを含むコレクションを用意しておく必要があります。
    f:id:masayuki-osaka-blend:20190730225455p:plain
    シーンを増やす前にCollection 2を用意しておいてください
    チェック内容はシーンをまたいで管理は共有さないという特徴があります。
    f:id:masayuki-osaka-blend:20190730225836p:plain
    チェック内容はシーンをまたいでは共有されません。
    無効化されたものは、そのシーンのレンダリングに反映されません。

    なお厳密には、シーンがそれぞれ別のビューレイヤーというものを持ち、そのビューレイヤーがコレクションの無効化を管理しています。

    リンクを捨てる

    ★リンクを捨てる手法は危険を伴うため、上記の無効化を推奨します

    もし無効化ではなく、特定のシーンからリンクを捨てたい場合には、そのシーンのアウトライナ上で右クリックをして「Unlink」を選択します。
    f:id:masayuki-osaka-blend:20190727234529p:plain
    Scene.001からCollectionをアンリンクします
    するとCollectionへの、Scene.001からのリンクが無くなります。
    f:id:masayuki-osaka-blend:20190727235244p:plain
    リンクを失ったものは表示されません
    このとき、アンリンクする対象次第では、どのシーンからも失われてしまうので注意してください。
     たとえばCubeをアンリンクしてしまうと、どのシーンからも見えなくなります。
    f:id:masayuki-osaka-blend:20190730233816p:plain
    Scene, Scene.001の両方がCubeにたどりつけません

    リンクの追加

    シーンを増やした後で追加した物が他のシーンへ反映されないことがあります。その際はリンクの図を思い浮かべてください。
     下図は、Scene.001でスザンヌを追加した結果です。
    f:id:masayuki-osaka-blend:20190728002929p:plain
    Scene.001のScene Collection直下に作成されたため、Sceneへのリンクがありません
    この状態からスザンヌをSceneへリンクさせる方法は2つあります。
    階層で解決
    Collectionは両シーンへのリンクを持つため、Scene.001内でCollectionの階層にスザンヌを入れます。
    f:id:masayuki-osaka-blend:20190728004023p:plain
    コレクションが、中のものとリンク関係にあります
    Make Linksの実行
    Scene.001のビューポートでctrl + LからSceneへのリンクを明示します。
    f:id:masayuki-osaka-blend:20190728004627p:plain
    スザンヌを選択してctrl + L
    この場合、アウトライナとリンクは次のようになります。
    f:id:masayuki-osaka-blend:20190728005451p:plain
    両シーンのScene Collectionからリンクしています

    まとめ

  • 複数のシーンはそれぞれ、シーン設定を個別に持つことができます
  • オブジェクトとコレクションは、リンクの関係にあります
  • コレクションのうち一番上のScene Collectionだけは、同じ名前でも別々のものです
  • リンクを無効化するには、コレクション横のチェックをオフにします。
  • リンクを捨てるには、アウトライナで右クリック→Unlinkを実行します(要注意)
  • コレクションとの階層変化、またはビューポートでのctrl + Lでリンクを新たに作成できます

シーンを管理できればBlenderの機能をさらに有効に使えます。次回は冒頭で紹介した、EeveeとCyclesを併用してのレンダリングを、複数のシーンを用いて行ってみます。

(最初に紹介した動画の手法のほうがEeveeで完結する上、影の調整も容易なぶんスマートだと思います。)

カレントでない時間でのアトリビュート値を取得する (Maya)

本日受講したセミナー

Maya のしくみ ~ 基礎から学ぶ DG とパラレル評価、そしてキャッシュプレイバック ~ | ボーンデジタル

にて佐々木さんが最後に「公式の標準機能が、MDGContextを用いて違う時間帯の評価を行うようになったということは・・・」とおっしゃっていました。(ニュアンスは多少違うかもしれませんが) 

機能としての提供はあったが、基本機能への投入実績ができた以上、Mayaにとっての標準が新たに定義されたということです。
 大変になるなぁと思うと同時に、今後はそのあたりの内部機能が安定していくだろうと思う次第でした。

ともあれ、カレントフレーム以外でのアトリビュート値を取得する方法を紹介いたします。
 次のような条件では特に役立ちます。

  • タイムスライダを動かせば値は判る
  • でもシーンの更新に時間がかかる
  • 知りたいのはチャンネルボックスの値だけ

想定

pCube1のtranslateXについて、frame11での値を知りたい

準備

キューブを作成してtranslateXのアニメーションをつけます。 f:id:masayuki-osaka-blend:20190711214929p:plain

指定フレームでの値の取得

以下のスクリプトを実行することで、11frameでの値を取得できます。

import maya.api.OpenMaya as om2
# pCube1という名前から、apiで扱うためのリストを生成
sel = om2.MGlobal.getSelectionListByName('pCube1')
# リストから0番目(pCube1)の物体を、Dagとして取り扱える状態で取得
dag = sel.getDagPath(0)
# Dagをtransformノードとして扱えるようにする
trn = om2.MFnTransform(dag)
# transformノードのtranslateXを管理する物(プラグ)を取得
plug = trn.findPlug('tx', 0)

# 現在のfps設定を、時間を管理するクラス(MTime)から取得
fps = om2.MTime().unit
# "現在のfpsでの、11frame" に該当するタイムオブジェクトを生成
frame_11 = om2.MTime(11.0, fps)
# "現在のfpsでの、11frame" を表す、時間に関する文脈を生成
context = om2.MDGContext(frame_11)
# translateXを管理するプラグへ "現在のfpsでの、11frame" という文脈を伝え、それに応じた値をfloatで受け取る
value = plug.asFloat(context)

print(value)

この図では、コマンド実行後にタイムスライダで11frame目を表示させていますが、カレントフレームがどこであっても同じ結果が得られます。
f:id:masayuki-osaka-blend:20190711221110p:plain

カレントフレームでの値を取得

カレントフレームでの値を取得したい場合には、以下のように後半を差し替えます

import maya.api.OpenMaya as om2
# pCube1という名前から、apiで扱うためのリストを生成
sel = om2.MGlobal.getSelectionListByName('pCube1')
# リストから0番目(pCube1)の物体を、Dagとして取り扱える状態で取得
dag = sel.getDagPath(0)
# Dagをtransformノードとして扱えるようにする
trn = om2.MFnTransform(dag)
# transformノードのtranslateXを管理する物(プラグ)を取得
plug = trn.findPlug('tx', 0)
# ここまでは同じ

# "現在のfps、現在のフレーム" を表す、時間に関する文脈を生成
context = om2.MDGContext(om2.MDGContext.kNormal)
# translateXを管理するプラグへ "現在のfpsでの、11frame" という文脈を伝え、それに応じた値をfloatで受け取る
value = plug.asFloat(context)
print(value)

MDGContext.kNormal が「Normal=通常=今」を示すので、現在のfps, 現在のフレームを扱えます。
f:id:masayuki-osaka-blend:20190711222422p:plain

頂点のワールド座標を取得

メッシュシェイプに関しては、outMeshアトリビュートへのアクセスによってカレントフレーム以外の状態を取得できます。
 ただし、outMeshアトリビュートを通して取得した形状はローカル座標しか扱えません。そのため、メッシュの親ノードのワールドマトリクスを取得し、それを使用して頂点座標値の変換を行う必要があります。

import maya.api.OpenMaya as om2
# pCubeShape1という名前から、apiで扱うためのリストを生成
sel = om2.MGlobal.getSelectionListByName('pCubeShape1')
# リストから0番目(pCubeShape1)の物体を、Dagとして取り扱える状態で取得
odag = sel.getDagPath(0)
# DagをMeshとして扱えるようにする
mmesh = om2.MFnMesh(odag)
# メッシュの最終的な形(outMeshアトリビュート)を管理するプラグを取得
plug_outmesh = mmesh.findPlug('outMesh', 0)

# "現在のfpsでの、11frame" を表す、時間に関する文脈を生成
context = om2.MDGContext(om2.MTime(11, om2.MTime().unit))
# Meshの "現在のfpsでの11frame" にあたる形を取得
omeshdata = plug_outmesh.asMObject(context)
# "現在のfpsでの11frame" にあたる形をMeshとして扱えるようにする
# (カレントタイムのMeshとは別物)
time_shifted_mmesh = om2.MFnMesh(omeshdata)
# 各頂点のローカル座標を取得(outMesh経由で取得したものはワールド座標がない)
# 最後をgetPoints(om2.MSpace.kWorld)にしてもローカル座標になる
points = time_shifted_mmesh.getPoints()

# 親階層(pCube1)のworldMatrixを管理するプラグを取得
plugs_mtrx = mmesh.findPlug('parentMatrix', 0)
# parentMatrixはマルチアトリビュートなので、parentMatrix[0]のみを考慮
plug_mtrx = plugs_mtrx.elementByLogicalIndex(0)
# parentMatrix[0]のプラグから "現在のfpsでの11frame" のworldMatrixを取得
omtrxdata = plug_mtrx.asMObject(context)
# matrixとして扱えるようにする
mmtrxdata = om2.MFnMatrixData(omtrxdata)
# matrixを取得(親階層pCube1のworldMatrix)
mmtrx = mmtrxdata.matrix()

# 各頂点のローカル座標へ親階層のworldMatrixを適用
for pt in points:
    pt *= mmtrx

# 各頂点位置がワールド座標になっていることを確認
import maya.cmds as cmds
group = cmds.group(em=1, w=1)
for pt in points:
    locator = cmds.spaceLocator(p=(pt.x, pt.y, pt.z))
    cmds.parent(locator, group)

あとがき

多少無理してでも参加した甲斐のある、とても濃密なセミナーでした。
 そして、Mayaは「とことん突き詰める人」向きなのだと改めて思ったり。
そうでなければ?Evaluation ToolkitではなくFix Toolkitが無難なラインで提供されていたと思います。

久しぶりにプロ向けのセミナーに参加した事もあり、失礼もあったかと思いますが、引き続きセミナーにアンテナを張っていきたい、と思った次第です。

Blender, Maya両対応のハードウェアレンダリングツールを公開いたしました。

複数のシーンへハードウェアレンダリングを行うツールを公開いたしました。
http://maya2blender.xsrv.jp/tools/hardware_render_animations/v010/document/index.html

f:id:masayuki-osaka-blend:20190615000457g:plain
Maya2019 (sp0)とBlenderは2.79で動作確認しております。(Blender2.80は未対応)
なお、Blender側は別途PySide2の環境が必要となります。
詳しくは上記リンクをご覧ください。


Blenderに関して2.80への対応を行うほか、PySide2に頼らないバージョンを作成する予定で、Mayaに関してはバックグラウンド実行を実装したいところです。

本ツールは、『同じパッケージでMayaへもBlenderへも対応する』をコンセプトに作成しました。コードをご覧いただければ、MayaでのコマンドとBlenderでのコマンドを見比べられて面白いかもしれません。

f:id:masayuki-osaka-blend:20190615000658p:plain
ツール毎に方言がある所だけをそれぞれの言葉で話させ、それ以外は共通の処理を行う作りになっています。


PySide2はHoudini, MotionBuilderにも備わっているので、同じアプローチでそれらのツールへも拡張できそうです。
そのための練習として作成しましたが、実際に拡張する予定はありません。

スクリプトの技量はまだまだですが、コツコツやっていこうと思います。

Blenderでワールドマトリクスからオブジェクトの軸方向を取得

2.79.6での検証です。2.8はまだ・・・
また、ボーンの扱いは特殊らしい?ので、未検証です。

取得対象

移動と回転をさせたキューブについて、各軸の方向を取得する方法の紹介です。

f:id:masayuki-osaka-blend:20190108134008p:plain
確認し易くするために、Axis(とName)をオンにしています

ワールドマトリクスとは

オブジェクトに親子関係がある場合、各オブジェクトのlocation, rotation, scaleへは、親との相対的な値が入っています。それに対し、親子関係を無視した(親子関係を解消した場合に相当する)状態を表すものをワールドマトリクスといいます。これを利用して、オブジェクトのXYZ軸がどの方向を向いているのかが取得できます。
 ワールドマトリクスを取得するには以下のコードを実行します("Cube"の例)。

import bpy
# Cubeのオブジェクトを取得
obj = bpy.data.objects["Cube"]
# Cubeのワールドマトリクスを取得
matrix_world = obj.matrix_world
# 取得したマトリクスを表示
print(matrix_world)

表示結果はシステムコンソールに表示されるので、確認するにはToggle System Console(システムコンソール切替)を押しておく必要があります。
f:id:masayuki-osaka-blend:20190108172751p:plain

マトリクスの読み方

f:id:masayuki-osaka-blend:20190108172353p:plain
Blenderのマトリクスは、左端の縦(一列目)がX軸の方向を表します。同様に二列目がY軸、三列目がZ軸の方向を表します。
 それぞれ上から順に、原点から見た各軸のx, y, zの座標を示します。 f:id:masayuki-osaka-blend:20190108180351p:plain
例えばこのキューブのX軸の向きは、原点から見て
  x=0.7272, y=-0.6838, z=-0.0602
の方向を向いたベクトルと並行だということになります。

なお、四列目は位置のワールド座標を表しており、
  x=1.5527, y=-0.5653, z=3.1395
の場所にこのキューブがあることを示しています。

マトリクスの一番下の行は、とりあえず無視してください。

プログラム上で取得しやすくする

マトリクスは横の一行ごとに抽出することができ、 以下のコードを実行すると各列を抜き取れます。

import bpy
obj = bpy.data.objects['Cube']
matrix_world = obj.matrix_world
print(matrix_world)
print(matrix_world[0])  # 最初の行を取得して表示
print(matrix_world[1])  # 2行目を表示
print(matrix_world[2])  # 3行目を表示
print(matrix_world[3])  # 最後の行を表示

f:id:masayuki-osaka-blend:20190108191309p:plain
ところが、Blenderでは各軸の情報が縦に並んでいるため、この方法では軸の情報が取得できません。 例えば二行目は、各軸の向きと位置のうち、y座標を並べたものが表示されてしまいます。
f:id:masayuki-osaka-blend:20190108210418p:plain
そこで以下のコードで、マトリクスの縦と横を入れ替えます

import bpy
obj = bpy.data.objects['Cube']
matrix_world = obj.matrix_world
print('---matrix_world---')
print(matrix_world)
# 縦と横を入れ替える(transpose)
transposed_matrix = matrix_world.transposed()
print('---transposed---')
print(transposed_matrix)  # 縦と横を入れ替えたマトリクスを表示

f:id:masayuki-osaka-blend:20190108223844p:plain
各軸の情報が横に並ぶようになります


 今回は軸の方向を調べるのが目的なので、四列目と四行目は不要になります。次のコードは縦横を入れ替えたマトリクスを三行三列だけにして、各行を表示させています。

import bpy
obj = bpy.data.objects['Cube']
matrix_world = obj.matrix_world
# 縦と横を入れ替える(transpose)
transposed_matrix = matrix_world.transposed()
print('---transposed---')
print(transposed_matrix)  # 縦と横を入れ替えたマトリクスを表示
# 3行3列に絞る(4行目、4列目を捨てる)
transposed_3x3 = transposed_matrix.to_3x3()
print('---transposed 3x3---')
print(transposed_3x3)  # 3行3列にしたマトリクスを表示
# 各行を表示
print(transposed_3x3[0])  # X軸の方向を表示
print(transposed_3x3[1])  # Y軸の方向を表示
print(transposed_3x3[2])  # Z軸の方向を表示

f:id:masayuki-osaka-blend:20190108225645p:plain
これで各軸の方向を取得することができました。

確認する

各軸の向きが取得できたので、その向きがあっているかを確認してみます。
 下の図は、3つのEmptyを作成して名前を変え、取得した座標を各locationへ入力、それを原点のEmptyの子供にしたものです。
f:id:masayuki-osaka-blend:20190108233123p:plain
原点の親EmptyをCubeと同じ場所にすることで、Cubeの座標表示と各ロケータの位置が揃うことが確認できます。
f:id:masayuki-osaka-blend:20190108233856p:plain

あとがき

カメラの向いている方向を取得する必要があり、調べた副産物を記事にしてみました。

 カメラの視線方向は取得したZ軸の方向に対して、各xyz座標値に-1を掛けた向き(=Z軸のマイナスの向き)になります。
 今回の例では、"Cube"の代わりに"Camera"だったとして、取得したZ軸の向きが
 x=-0.2276, y=-0.3230, z=0.9186
でしたので、カメラの視線方向は
 x=0.2276, y=0.3230, z=-0.9186
となります。

 Mayaでは「カメラの視線方向を取得」というそのものな命令があるのですが、Blenderでは見当たらなかったので入り用でしたらご参照ください。(ひょっとしたらあるのかも・・・)

 あと、Mayaでワールドマトリクスを取得した場合、今回の「縦と横を入れ替えたマトリクス」と同等のものが取得されます。Blenderのように各軸の向きを縦並びにするケースをcolumn-major order、Mayaのように横並びにするものをrow-major orderと呼ぶそうで、アプリケーションによって違いがあるようです。
 カメラの視線方向はBlenderもMayaも「マイナスZ方向」なのは変わりません。なぜマイナスなのだろう・・・

Blenderでマクロを作る (Edit, Object Mode用マクロの例)

PhotoShopでは「アクション」という、既存機能をまとめて一発で実行できるようにする仕組みがあります。

Mayaでもそういった仕組みは作れますが、既存機能の登録に関して少しスクリプトの知識が必要になります。

  そして、Blenderの場合はもっと面倒だったりします。

  ここでは、メッシュエディットモードでの例をもとに、既存機能をまとめて実行するための方法を紹介します。

<本例でのシチュエーション>

『エディットモードでエッジを一つ選択。それを起点に一列飛ばしでループエッジを除去したい。』

これを実現するために、以下を一つにまとめて実行できるようにします。

  1. エッジ選択をしている状態をスタートとして、リング選択をする (Select > Edge Rings)
  2. 選択を一つ飛ばしにする (Select > Checker Deselect)
  3. 選択状態をもとにループ選択へ変換する (Select > Edge Loops)
  4. 選択してあるものを除去する (Mesh > Delete > Dissolve Edges)

f:id:masayuki-osaka-blend:20180118220613p:plain

<大まかな流れ>

以下の順序でアドオンを作成します。

  1. メニューから各既存機能を実行し、それに対応するスクリプトでのコマンドを見つける
  2. 見つけたコマンドを使ってアドオンをつくる(テンプレート使用)
  3. そのアドオンを登録する

<下準備>

InfoパネルとText Editorパネルを表示

まず、Blenderのレイアウトを変更し、下図のようにします。
「ctrl+右矢印」を何回か押すと早いかと思います。
f:id:masayuki-osaka-blend:20180107112830p:plain

  • Infoパネルは、操作の履歴が表示されます。右クリックで行の選択の on/off ができます。
  • Text Editorパネルは、スクリプトやメモを書いて保存するために使います。
  • Python Consoleパネルは今回、出番がありません。


今回の例では、

  1. 3DViewで一つずつ既存機能を実行
  2. Infoパネル表示された実行コマンドをコピー
  3. Text Editorパネルへペースト

という流れでアドオンを作成します。

<既存機能を実行してコマンドを得る>

今回の例では、一つのエッジを選択したあと、マクロを実行するという想定です。
まず、エッジを一つ選択してください。
f:id:masayuki-osaka-blend:20180114192055p:plain

これから既存コマンドを一つずつ実行しますが、何のコマンドが実行されたかを判りやすくするため、Infoパネルを綺麗にします。Infoパネル上でショートカット「A」→「X」でこれまでのコマンド履歴が削除されます。
f:id:masayuki-osaka-blend:20180114193249p:plain

選択をリング状に変換するため、Select > Edge Ring を実行します。
EdgeRingを実行した途端、Infoパネルに一行追加される事に注目してください。
f:id:masayuki-osaka-blend:20180114194715p:plain

この増えた行が、Edge Ringsのコマンドです。引き続き

  • 選択を一つ飛ばしにする (Select > Checker Deselect)
  • 選択をもとにループ選択をする (Select > Edge Loops)
  • 選択してあるものを除去する (Mesh > Delete > Dissolve Edges)

を実行すると、下図のように合計4個のコマンドが表示されます。
f:id:masayuki-osaka-blend:20180114200352p:plain

このコマンドは後に使用するので、コピーしてメモ帳などに控えてください。
f:id:masayuki-osaka-blend:20180114201820p:plain

<アドオンの作成>

1、コードのペースト

アドオンを1から作るのは骨がおれるので、ほとんどの部分を流用します。
Text Editorで新規テキストを作成し、下のコードを張り付けてください。

※ペースト用コードを表示

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####


bl_info = {
    "name": "My Addon",             # addon's name
    "description": "This is ...",   # addon's description
    "category": "Mesh",             # addon's category
}


class My_macro:

    # 1 to register in the menu. Otherwise 0.
    in_menu = 1

    # name in the menu
    menu_name = "Menu Name"

    # tooltip
    tooltip = "This is my macro"

    # command name. Only lowercase, number, and "_" are available.
    program_name = "my_command"

    # If you register shortcut now, select from the below list.
    shortcut_key = "NONE"

    # If you don't register shortcut, here is meaningless.
    shortcut_shift = 1      # If you hold down the Shift, 1. Otherwise 0.
    shortcut_ctrl = 1       # If you hold down the Ctrl, 0. Otherwise 0.
    shortcut_alt = 1        # If you hold down the Alt, 0. Otherwise 0.


    @staticmethod
    def my_commands():
        '''Paste commands here.'''
        # Align the head of commands with this line.
        
        
        
        



'''Available characters can be used as shortcut
"NONE",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",

"SEMI_COLON", "PERIOD", "COMMA", "QUOTE", "ACCENT_GRAVE", "MINUS", "PLUS",
"SLASH", "BACK_SLASH", "EQUAL", "LEFT_BRACKET", "RIGHT_BRACKET",

"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
"F13", "F14", "F15", "F16", "F17", "F18", "F19",

"ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE",

"NUMPAD_0", "NUMPAD_1", "NUMPAD_2", "NUMPAD_3", "NUMPAD_4", "NUMPAD_5",
"NUMPAD_6", "NUMPAD_7", "NUMPAD_8", "NUMPAD_9",
"NUMPAD_PERIOD", "NUMPAD_SLASH", "NUMPAD_ASTERIX", "NUMPAD_MINUS",
"NUMPAD_ENTER", "NUMPAD_PLUS",

"ESC", "TAB", "RET", "SPACE", "LINE_FEED", "BACK_SPACE", "DEL", "PAUSE",
"INSERT", "HOME", "PAGE_UP", "PAGE_DOWN", "END",

"LEFT_ARROW", "DOWN_ARROW", "RIGHT_ARROW", "UP_ARROW",

"LEFTMOUSE", "MIDDLEMOUSE", "RIGHTMOUSE", "BUTTON4MOUSE", "BUTTON5MOUSE",
"BUTTON6MOUSE", "BUTTON7MOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE",
"WHEELINMOUSE", "WHEELOUTMOUSE", "MOUSEROTATE", "MOUSEMOVE",
"INBETWEEN_MOUSEMOVE", "ACTIONMOUSE", "SELECTMOUSE",

"TRACKPADPAN", "TRACKPADZOOM", "PEN", "ERASER", "EVT_TWEAK_L", "EVT_TWEAK_M",
"EVT_TWEAK_R", "EVT_TWEAK_A", "EVT_TWEAK_S", "LEFT_CTRL", "LEFT_ALT",
"LEFT_SHIFT", "RIGHT_ALT", "RIGHT_CTRL", "RIGHT_SHIFT", "OSKEY", "GRLESS",
"MEDIA_PLAY", "MEDIA_STOP", "MEDIA_FIRST", "MEDIA_LAST", "TEXTINPUT",
"WINDOW_DEACTIVATE", "TIMER", "TIMER0", "TIMER1", "TIMER2", "TIMER_JOBS",
"TIMER_AUTOSAVE", "TIMER_REPORT", "TIMERREGION", "NDOF_MOTION",
"NDOF_BUTTON_MENU", "NDOF_BUTTON_FIT", "NDOF_BUTTON_TOP", "NDOF_BUTTON_BOTTOM",
"NDOF_BUTTON_LEFT", "NDOF_BUTTON_RIGHT", "NDOF_BUTTON_FRONT",
"NDOF_BUTTON_BACK", "NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO2",
"NDOF_BUTTON_ROLL_CW", "NDOF_BUTTON_ROLL_CCW", "NDOF_BUTTON_SPIN_CW",
"NDOF_BUTTON_SPIN_CCW", "NDOF_BUTTON_TILT_CW", "NDOF_BUTTON_TILT_CCW",
"NDOF_BUTTON_ROTATE", "NDOF_BUTTON_PANZOOM", "NDOF_BUTTON_DOMINANT",
"NDOF_BUTTON_PLUS", "NDOF_BUTTON_MINUS", "NDOF_BUTTON_ESC", "NDOF_BUTTON_ALT",
"NDOF_BUTTON_SHIFT", "NDOF_BUTTON_CTRL", "NDOF_BUTTON_1", "NDOF_BUTTON_2",
"NDOF_BUTTON_3", "NDOF_BUTTON_4", "NDOF_BUTTON_5", "NDOF_BUTTON_6",
"NDOF_BUTTON_7", "NDOF_BUTTON_8", "NDOF_BUTTON_9", "NDOF_BUTTON_10",
"NDOF_BUTTON_A", "NDOF_BUTTON_B", "NDOF_BUTTON_C"
'''


# Don't edit under this line.

import bpy


class MyTool(bpy.types.Operator):

    __doc__ = My_macro.tooltip

    bl_idname = "mesh.{}".format(My_macro.program_name)
    bl_label = My_macro.menu_name
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        My_macro.my_commands()
        return {'FINISHED'}


# store keymaps here to access after registration
addon_keymaps = []


def menu_func(self, context):
    self.layout.operator(MyTool.bl_idname)


def register():
    bpy.utils.register_class(MyTool)

    # register menu
    if My_macro.in_menu:
        bpy.types.VIEW3D_MT_edit_mesh.append(menu_func)

    if My_macro.shortcut_key != 'NONE':
        # handle the keymap
        wm = bpy.context.window_manager
        km = wm.keyconfigs.addon.keymaps.new(name='Mesh', space_type='EMPTY')
        kmi = km.keymap_items.new(MyTool.bl_idname,
                                  My_macro.shortcut_key,
                                  'PRESS',
                                  ctrl=My_macro.shortcut_ctrl,
                                  shift=My_macro.shortcut_shift,
                                  alt=My_macro.shortcut_alt
                                  )
        addon_keymaps.append(km)


def unregister():
    bpy.utils.unregister_class(MyTool)

    # unregister menu
    if My_macro.in_menu:
        bpy.types.VIEW3D_MT_edit_mesh.remove(menu_func)

    # handle the keymap
    wm = bpy.context.window_manager
    for km in addon_keymaps:
        wm.keyconfigs.addon.keymaps.remove(km)
    # clear the list
    del addon_keymaps[:]


if __name__ == "__main__":
    register()


f:id:masayuki-osaka-blend:20180115011931p:plain
(文字の色が黒一色であったり、行番号が非表示の時は、上図のボタンで修正すると見易くなります)

続いて54行目以降に、先ほどメモ帳などへ控えたコマンドを張り付けます。
このとき、行の頭を53行目に揃えてください。

f:id:masayuki-osaka-blend:20180114213808p:plain

2、アドオン名などの変更

21行目から47行目にかけて、アドオンに関する情報を直接書き替えます。
(以下のアドオン登録後の画像は、まだ手元での確認はできません。後の項で出来るようになります)

<アドオンを見つけ易くするための情報>
  • 21行目 "name": アドオンの名前
  • 22行目 "description": アドオンの説明
  • 23行目 "category": アドオンを探す際のカテゴリ

f:id:masayuki-osaka-blend:20180115005326p:plain

<登録するメニューの内容>
  • 30行目 in_menu: メニューに追加するか(しないなら数字の0)
  • 33行目 menu_name: メニューに表示する際の名前
  • 36行目 tooltip: メニュー上での説明書き

f:id:masayuki-osaka-blend:20180115005512p:plain

<プログラム上でのコマンド名>(半角英数字と"_"のみで記入)
  • 39行目 program_name: Blenderがこのプログラムを実行するためのコマンド名
    (あまりシンプルな名前では既存のコマンドとかぶってしまう場合があります)

f:id:masayuki-osaka-blend:20180115005544p:plain

<ショートカット情報>

メニューを右クリックしてのショートカット設定など、後で登録するなら変更不要です。
アドオンの読み込み時に、自動的にショートカットも決まるようにしたい場合に変更してください。

  • 42行目 shortcut_key: 押すボタン(Shift, Ctrl, Altは除く)
      (60行目前後から書いてある、沢山の候補から選択して記入)
  • 45行目 shortcut_shift: Shiftキーと一緒に押すか(押さないなら数字の0)
  • 46行目 shortcut_ctrl: Ctrlキーと一緒に押すか(押さないなら数字の0)
  • 47行目 shortcut_alt: Altキーと一緒に押すか(押さないなら数字の0)

f:id:masayuki-osaka-blend:20180115010729p:plain
この図の例では、shift+Ctrl+右マウスクリックで実行されます。

3、コードを保存

アドオンとしてBlenderに読み込ませるために、これまで編集したスクリプトを保存します。
Text Editor のメニューから Text > Save As で、任意の場所に保存してください。

拡張子は .py とする必要があります。

C:\Program Files\ 以下など、管理者権限が必要な場所への保存はエラーになることがあります。
デスクトップなどに保存することをお勧めします。

f:id:masayuki-osaka-blend:20180118090453p:plain
今回の例では、ファイル名を「mesh_dissolve_skip_loops.py」としました。

4、アドオンとして登録

保存したファイルを以下のフォルダに移動させてください。

<Blenderのフォルダ>\<バージョン番号>\scripts\addons

f:id:masayuki-osaka-blend:20180118091612p:plain

これでアドオンとして読み込めます。Blender User PreferencesでRefresh ボタンを押すか、Blenderを再起動すると一覧に表示されますので、作成したアドオンを有効にしてください。
ここでアドオンが見つからない場合、スクリプトの記述や保存場所の間違いが考えられます。

f:id:masayuki-osaka-blend:20180118093218p:plain

<アドオンの確認>

エッジ選択をして、実際にアドオンが機能するかを確認します。

  • コード30行目の in_menu を1にしていた場合、Meshメニューの一番上に追加されます。
  • ショートカットを登録した場合は、そちらも機能しているかを確認してください。

f:id:masayuki-osaka-blend:20180118221633p:plain

<その他>

以上がひと通りの説明になります。以下は補足です。

日本語

 コードの21行目から47行目にかけて書き換えましたが、下図の5か所は日本語でも構いません。ただし、Text Editorは日本語で書き込むことが出来ないため、メモ帳などからコピー&ペーストする必要があります。
 また、インターフェースを日本語にしていないと正しく表示されません。
f:id:masayuki-osaka-blend:20180118225611p:plain

オブジェクトモード版

 今回の例ではエディットからスタートするアドオンを説明しましたが、オブジェクトモードを起点とする場合は以下のコードをお使いください。

※オブジェクトモード起点のコードを表示

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####


bl_info = {
    "name": "My Addon",             # addon's name
    "description": "This is ...",   # addon's description
    "category": "Object",             # addon's category
}


class My_macro:

    # 1 to register in the menu. Otherwise 0.
    in_menu = 1

    # name in the menu
    menu_name = "Menu Name"

    # tooltip
    tooltip = "This is my macro"

    # command name. Only lowercase, number, and "_" are available.
    program_name = "my_command"

    # If you register shortcut now, select from the below list.
    shortcut_key = "NONE"

    # If you don't register shortcut, here is meaningless.
    shortcut_shift = 1      # If you hold down the Shift, 1. Otherwise 0.
    shortcut_ctrl = 1       # If you hold down the Ctrl, 0. Otherwise 0.
    shortcut_alt = 1        # If you hold down the Alt, 0. Otherwise 0.


    @staticmethod
    def my_commands():
        '''Paste commands here.'''
        # Align the head of commands with this line.
        
        
        
        



'''Available characters can be used as shortcut
"NONE",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",

"SEMI_COLON", "PERIOD", "COMMA", "QUOTE", "ACCENT_GRAVE", "MINUS", "PLUS",
"SLASH", "BACK_SLASH", "EQUAL", "LEFT_BRACKET", "RIGHT_BRACKET",

"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
"F13", "F14", "F15", "F16", "F17", "F18", "F19",

"ZERO", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE",

"NUMPAD_0", "NUMPAD_1", "NUMPAD_2", "NUMPAD_3", "NUMPAD_4", "NUMPAD_5",
"NUMPAD_6", "NUMPAD_7", "NUMPAD_8", "NUMPAD_9",
"NUMPAD_PERIOD", "NUMPAD_SLASH", "NUMPAD_ASTERIX", "NUMPAD_MINUS",
"NUMPAD_ENTER", "NUMPAD_PLUS",

"ESC", "TAB", "RET", "SPACE", "LINE_FEED", "BACK_SPACE", "DEL", "PAUSE",
"INSERT", "HOME", "PAGE_UP", "PAGE_DOWN", "END",

"LEFT_ARROW", "DOWN_ARROW", "RIGHT_ARROW", "UP_ARROW",

"LEFTMOUSE", "MIDDLEMOUSE", "RIGHTMOUSE", "BUTTON4MOUSE", "BUTTON5MOUSE",
"BUTTON6MOUSE", "BUTTON7MOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE",
"WHEELINMOUSE", "WHEELOUTMOUSE", "MOUSEROTATE", "MOUSEMOVE",
"INBETWEEN_MOUSEMOVE", "ACTIONMOUSE", "SELECTMOUSE",

"TRACKPADPAN", "TRACKPADZOOM", "PEN", "ERASER", "EVT_TWEAK_L", "EVT_TWEAK_M",
"EVT_TWEAK_R", "EVT_TWEAK_A", "EVT_TWEAK_S", "LEFT_CTRL", "LEFT_ALT",
"LEFT_SHIFT", "RIGHT_ALT", "RIGHT_CTRL", "RIGHT_SHIFT", "OSKEY", "GRLESS",
"MEDIA_PLAY", "MEDIA_STOP", "MEDIA_FIRST", "MEDIA_LAST", "TEXTINPUT",
"WINDOW_DEACTIVATE", "TIMER", "TIMER0", "TIMER1", "TIMER2", "TIMER_JOBS",
"TIMER_AUTOSAVE", "TIMER_REPORT", "TIMERREGION", "NDOF_MOTION",
"NDOF_BUTTON_MENU", "NDOF_BUTTON_FIT", "NDOF_BUTTON_TOP", "NDOF_BUTTON_BOTTOM",
"NDOF_BUTTON_LEFT", "NDOF_BUTTON_RIGHT", "NDOF_BUTTON_FRONT",
"NDOF_BUTTON_BACK", "NDOF_BUTTON_ISO1", "NDOF_BUTTON_ISO2",
"NDOF_BUTTON_ROLL_CW", "NDOF_BUTTON_ROLL_CCW", "NDOF_BUTTON_SPIN_CW",
"NDOF_BUTTON_SPIN_CCW", "NDOF_BUTTON_TILT_CW", "NDOF_BUTTON_TILT_CCW",
"NDOF_BUTTON_ROTATE", "NDOF_BUTTON_PANZOOM", "NDOF_BUTTON_DOMINANT",
"NDOF_BUTTON_PLUS", "NDOF_BUTTON_MINUS", "NDOF_BUTTON_ESC", "NDOF_BUTTON_ALT",
"NDOF_BUTTON_SHIFT", "NDOF_BUTTON_CTRL", "NDOF_BUTTON_1", "NDOF_BUTTON_2",
"NDOF_BUTTON_3", "NDOF_BUTTON_4", "NDOF_BUTTON_5", "NDOF_BUTTON_6",
"NDOF_BUTTON_7", "NDOF_BUTTON_8", "NDOF_BUTTON_9", "NDOF_BUTTON_10",
"NDOF_BUTTON_A", "NDOF_BUTTON_B", "NDOF_BUTTON_C"
'''


# Don't edit under this line.

import bpy


class MyTool(bpy.types.Operator):

    __doc__ = My_macro.tooltip

    bl_idname = "object.{}".format(My_macro.program_name)
    bl_label = My_macro.menu_name
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        My_macro.my_commands()
        return {'FINISHED'}


# store keymaps here to access after registration
addon_keymaps = []


def menu_func(self, context):
    self.layout.operator(MyTool.bl_idname)


def register():
    bpy.utils.register_class(MyTool)

    # register menu
    if My_macro.in_menu:
        bpy.types.VIEW3D_MT_object.append(menu_func)

    if My_macro.shortcut_key != 'NONE':
        # handle the keymap
        wm = bpy.context.window_manager
        km = wm.keyconfigs.addon.keymaps.new(name='Object', space_type='EMPTY')
        kmi = km.keymap_items.new(MyTool.bl_idname,
                                  My_macro.shortcut_key,
                                  'PRESS',
                                  ctrl=My_macro.shortcut_ctrl,
                                  shift=My_macro.shortcut_shift,
                                  alt=My_macro.shortcut_alt
                                  )
        addon_keymaps.append(km)


def unregister():
    bpy.utils.unregister_class(MyTool)

    # unregister menu
    if My_macro.in_menu:
        bpy.types.VIEW3D_MT_object.remove(menu_func)

    # handle the keymap
    wm = bpy.context.window_manager
    for km in addon_keymaps:
        wm.keyconfigs.addon.keymaps.remove(km)
    # clear the list
    del addon_keymaps[:]


if __name__ == "__main__":
    register()

<謝辞>

 本記事は、稲村JIN氏のツイートから着想を得て記述致しました。マクロ作成を体験するにあたり、過不足ないテーマをご提供頂いたことを一方的ながら感謝申し上げます。
 氏の作品はライティングが特に魅力的で、それはマジシャンゆえなのだろうと推測します。私は「バックボーンは作品を底上げする」と信じておりますが、それは間違いでないと感じさせてくださる、そんなかたです。
 いろんな形で、いろんなかたが関わっていく。それぞれ個性を重んじて。素敵な事だと思います。

以上、謝辞に替えて本記事の締めとさせて頂きます。

Blenderミートアップイベント BlendxJP (レイアウトとプリビズについて思うこと)

2016年8月20日。とんでもない神イベントに参加できました。

 

【BlendxJP】

 

既にいろんな方がブログや twitter で感想を記述されています。そのあたりは

Blenderミートアップイベント BlendxJP (#blendxjp ) - Togetterまとめ

BlendxJP後夜祭 - Togetterまとめ

を追って頂ければ。

 

今回、初めて blog を書きたい衝動にかられ、アカウントを取りました。

 

それはある登壇者の発表が自分にとって、考えさせられる内容だったからです。なのでイベント全体ではなく、一部に絞った記事になります。

 

酒向大輔 氏。

 

作画監督として、アニメーターとして、活躍なさっているかたです。発表の内容は『アニメーション制作現場での、レイアウトにおける Blender の活用事例』でした。

 

実は、何年か前に仕事でお世話になったことがありました。

そして前にも後にも、酒向氏以上に解りやすく論理的に、現場で教育して下さったかたには出会っておりません。

「画力の訓練と、構成力の知識」に関して、氏自身が非常に深いものをお持ちなのは間違いなく、特に後者が、酒向氏の師としての魅力を数段引き上げているのを実感しております。

なにせ、終始「なるほど!」しかない現場でしたから。

 

本当に短い間のご縁でしたが、この場を借りて改めてお礼を申し上げます。

そしてこれからも宜しくお願い致します。

 

 

さて、私情はともかく本題。

 先述のご縁の際に酒向氏は初めて Blender を触られ、その後に色々あっての今回の登壇となったわけですが、氏が「レイアウト」とおっしゃった内容に衝撃を受けました。

 

3DCG 業界未経験のかたに説明しますと、この業界でのアニメーション制作を行う企業は、「アニメ系」「ゲーム系」「映像系」と大きく別れ、それぞれでノウハウが大きく違ったりします。

・「ゲーム系」:CG はコンピュータの演算によるもの、という事を重要視する。プログラムに強い。

・「映像系」:3DCG ツールを使って魅力的な絵を作る事を重視する。美術性やリアル性が高い。

・「アニメ系」:セルアニメ、セルルックアニメ、ジャパニメーションのために 3DCG を利用する。萌える。燃える。

ざっくりとこんな感じで。異論はあるでしょうが、私見はこんな感じです。

あと、「遊技機系」という言葉もありますが、上記全てにまたがってくるので、今回の分類からは外します。

 

 私は今、そのうちの「映像系」に見を置いています。この分野では、ずっと前から大切と認識されつつ、大手以外は中々踏み出せない工程というものが存在しています。

・「プリビズ」

プリ・ビジュアライゼーション。細かなアニメーションを付ける前に作られる動画を指します。

カメラの設定、キャラクターやオブジェクトの位置や動き、カット切り替わりのタイミングを決めるものです。

モデルは大雑把な仮のものを使うのですが、ハリウッド映画ではこのプリビズを俳優に見せて「このように動いて欲しい」というオーダーをする例もあると聞きます。また、カメラマンはプリビズに記述された設定に従って実写素材を撮ったりもします。

つまり、動きのレイアウトを早めの段階で確定させるのです。

 

 シン・ゴジラでは

『シン・ゴジラ』白組によるCGメイキング映像 - YouTube

の 1:51~ がそれに当たります。是非ご覧ください。

 

  中々踏み出せない、と書きましたが理由があります。「映像系」にとってプリビズの導入は、あらたに作業時間をプラスして行わなくてはならないからです。

 

一方、酒向氏に昨日このお話をした際、氏は「プリビズ」という言葉をご存知でありませんでした。

氏にとっての「レイアウト」は映像系でいう所のプリビズにあたり、氏は 3DCG の導入にあたり、自然とプリビズを作成していらっしゃったのです。また、それについて「手書きが 3DCG に変わっただけですよ」とおっしゃっていました。

 

この意味を自分なりに考えました。以下考察です。

 

 手書きでレイアウトを切るということは、時間がかかる上に責任重大で、後になっての切り直しは一大事です。

分業のなかで、レイアウト→原画→動画というように、責任の度合いと制作の順序が同じ流れに乗っているのは、長年をかけて築き上げたシステムだからでしょう。

 

一方、 業界として若い 3DCG 企業の中小手では、この流れが混在することがよくあります。特に 3DCG ツールはアニメーションの補完が自動的であるため、初心者でも一連の動きが作れてしまいます。

そして、手書きでの原画やレイアウトにあたる業務も、経験の浅い者が請け負うワークフローになりがちな罠が待ち受けています。

結果的に、絵コンテをそのまま立体にしただけのレイアウトが出来上がったり、また、絵コンテをもとに発想を広げたレイアウトを切る権限が与えられない、という状態になりがちです。当然、後々にレイアウトからリセットするリスクも高まります。

 

本来は豊富な経験と知識が必要な「レイアウト」という作業を、3DCG 業界は近年になって「プリビズ」という言葉で認識し、作業量を増やす必要が生まれたと感じているのではないでしょうか。

 

それらの重要性と、費やす時間をずっと認識していた手書き業界では、プリビズは「単純にコンピュータに筆をかえただけ」で済む便利な手段になります。裏を返せば、それを新しいものとして認識しなくてはならない 3DCG 業界は、今やっと「レイアウト」の本来の重要性を認識しているのでしょう。

 

もちろんこれから、3DCG 業界でもプリビズは普及してゆくでしょうし、レイアウトもどんどん魅力的で独創的なものになってゆくと思われます。

それに期待しつつ。自分も頑張る次第です。

 

 

とっても長くなりましたが、今回のイベントで最も衝撃的な気付きでしたので、つらつらと書いてしまいました。

 

他にも素敵なことだらけで、本当に神イベントでした。

また、参加します。その時にはまた宜しくお願いします、なのです!