見出し画像

【b3d_mkn】blenderPython ノード自動化周りの知見

自分用のメモ書きにざっと書いておきます

とりあえず開発は一旦置いておこう!って思ったんですが、どうにも必要そうだったので久しぶりに書きましたスクリプト

さて背景どうしよう・仕様策定

実は前々から発想はあったのです、面倒だったし後回しにした方が良いと思ったからやらなかっただけで、でも実際に必要だと思ったので前の着想を元に仕様を引きました

①購入したり配布されてるアセットは大体テクスチャがついてるので
 元々のシェーダー(プリンシバルBSDFとか自前ノードグループとか)のBaseColorからテクスチャが繋がってるノードを千切って、kiryToonShaderのBaseColorに接続しなおす

②色味の微調整とかマスクとかの機能が後々必要になると思うので
 下記記事であらかじめ作ってあるマテリアル一括操作ツールを拡張する事で背景操作も出来るようにする(それ用のHSVやミックスカラーノードも自動で接続するようにする)
【b3d_mkn】自分で作ったpythonアドオン概念まとめ②マテリアル一括操作ツール【blender/pyside】|makeInuFilm (note.com)

③特定の命名がついたコレクション内に入ってるオブジェクトのマテリアルを一括で置き換えれるようにする(ここはこの記事では書いて無いです)

そんな感じでコードの記載例に行きたいと思います

本題:マテリアルノードの繋ぎ方等の一例

import bpy


#ファイルのパスの指定
dirpath = bpy.data.filepath.split("\\",1)[0] 
filepath = dirpath + "\\tools\\Scripts\\temp\\" + "TEMPnodeGroup.blend"

#kirytoonShaderを探して、blenderシーン内に存在しない場合はアペンドして呼び出す
## bpy.ops.wm.append() でも出来るけどちょっと重いので下記の書き方をしています
if bpy.data.node_groups.get("KiryToonShade_Eevee"):
    with bpy.data.libraries.load(filepath,link=False,relative=False) as (data_from, data_to):
        data_to.node_groups = ["KiryToonShader_Eevee"]

#material_slots[0] = 割り当てられてるマテリアルの一番最初を取得
##大体の配布アセットとか購入アセットは複数マテリアルではないので割り切ってる
###必要があればfor文で回せばヨシ!

main_material = bpy.context.object.material_slots[0].material
#ここは空グループの生成
new_group = main_material.node_tree.nodes.new("ShaderNodeGroup")
#空グループに何を割り当てるか
new_group.node_tree = bpy.data.node_groups['KiryToonShader_Eevee']
#名前
new_group.name = "KTS_BG"
#シェーダーエディター内のロケーション設定
new_group.location = 580,520

#HSVノードの生成
new_hsv = main_material.node_tree.nodes.new("ShaderNodeHueSaturation")
new_hsv.name = "BG_HSV"
new_hsv.location = 220,378
#同じ、影用
new_sdwhsv = main_material.node_tree.nodes.new("ShaderNodeHueSaturation")
new_sdwhsv.name = "BG_SHSV"
new_sdwhsv.location = 196,227
#影用のオフセット
new_offsethsv = main_material.node_tree.nodes.new("ShaderNodeHueSaturation")
new_offsethsv.name = "BG_OFHSV"
new_offsethsv.location = 409,276
#マスクノード(EEVEEから生成する際のカラーマスク機能)
new_msk = main_material.node_tree.nodes.new("ShaderNodeMix")
new_msk.name = "BG_MSK"
new_msk.data_type = "RGBA"
new_msk.location = 780,436
new_msk = main_material.node_tree.nodes["BG_MSK"]
#kiryToonShaderから放射ノードに繋ぐ(マスク機能をつける為の応急策)
new_emission = main_material.node_tree.nodes.new("ShaderNodeEmission")
new_emission.name = "BG_EMI"
new_emission.location = 980, 426

#ノードを繋げる前準備 inputs outputsを変数に格納
new_group_ina = new_group.inputs["BaseColor"]
new_group_inb = new_group.inputs["ShadowColor"]
new_group_out = new_group.outputs["Color"]

new_hsv_in = new_hsv.inputs["Color"]
new_hsv_out = new_hsv.outputs["Color"]

new_sdwhsv_in = new_sdwhsv.inputs["Color"]
new_sdwhsv_out = new_sdwhsv.outputs["Color"]

new_offsethsv_in = new_offsethsv.inputs["Color"]
new_offsethsv_out = new_offsethsv.outputs["Color"]
new_offsethsv.inputs["Value"].default_value = 0.8

###ここはほんとに罠、後で後述###
new_msk_in = new_msk.inputs[6]
new_msk.inputs["Factor"].default_value = 0
new_msk_out = new_msk.outputs[2]
new_emission_in = new_emission.inputs["Color"]
new_emission_out = new_emission.outputs[0]

#マテリアル出力ノードの存在をチェックしてない場合は生成
if main_material.node_tree.nodes.get("ShaderNodeOutputMaterial"):
    material_ot = main_material.node_tree.nodes["ShaderNodeOutputMaterial"]
    material_in = material_ot.inputs["Surface"]
else:
    material_ot = main_material.node_tree.nodes.new("ShaderNodeOutputMaterial")
    material_in = material_ot.inputs["Surface"]
material_ot.location = 1200,420

#大体テクスチャは0番にあるものがベースカラーであると信じて(後で手動指定すればいいし)
##0番めの画像テクスチャノードをget なかったら生成
tex = [i for i in main_material.node_tree.nodes if i.type == "TEX_IMAGE"]
if tex:
    firsttex_out = tex[0].outputs["Color"]
else:
    firsttex_int = main_material.node_tree.nodes.new("ShaderNodeTexImage")
    firsttex_out = firsttex_int.outputs["Color"]

#後は繋ぐノード    
main_material.node_tree.links.new(firsttex_out,new_hsv_in)
main_material.node_tree.links.new(firsttex_out,new_sdwhsv_in)
main_material.node_tree.links.new(new_sdwhsv_out,new_offsethsv_in)
main_material.node_tree.links.new(new_hsv_out,new_group_ina)
main_material.node_tree.links.new(new_offsethsv_out,new_group_inb)

main_material.node_tree.links.new(new_group_out,new_msk_in)
main_material.node_tree.links.new(new_msk_out,new_emission_in)
main_material.node_tree.links.new(new_emission_out,material_in)

これをただ走らせてもファイルパスの問題があるのでエラーが起こります、あくまで参考にして頂ければと思います

bpy.context.object.material_slots[0].material

これは選択してアクティブになってるオブジェクトのマテリアルスロットの先頭にあるマテリアルデータを取得する呪文です

bpy.context.object.material_slots[0].material.node_tree.nodes.new()

こうすると各ノードを生成する事ができますnew()の中を
.new("ShaderNodeHueSaturation")とすれば HSVカラーが生成できます

上の記事の8割はそれで構成されてるので、とりあえずこの書き方か

bpy.context.object.material_slots[0].material.node_tree.nodes["NodeNAME"]

という形でノードに割り当てられている名前かインデックスを入れれば、既に存在しているノードを取得出来るので、試してみてください

main_material = bpy.context.object.material_slots[0].material
new_hsv = main_material.node_tree.nodes.new("ShaderNodeHueSaturation")
new_hsv_in = new_hsv.inputs["Color"]
new_hsv_out = new_hsv.outputs["Color"]

こうすると、HSVを生成した後に HSVにあるinputsとoutputsのソケットを取得できます

赤丸の部分を取得してます
main_material = bpy.context.object.material_slots[0].material
main_material.node_tree.links.new(output,input)

お互いのinとoutのソケットを取得した後に
.links.new(output側,input側) という書き方をすれば繋げる事が出来ます

さて、実行してみれば勝手にノードが生成&繋がってると思います

後はこれをコレクション内のオブジェクトか、選択してるオブジェクトに適用するかで書き換えれば、背景をトゥーン調に一括変更が出来るという事ですね

重要な罠について

new_msk = main_material.node_tree.nodes.new("ShaderNodeMix")
new_msk.name = "BG_MSK"
new_msk.data_type = "RGBA"

###ここはほんとに罠、後で後述###
new_msk_in = new_msk.inputs[6]
new_msk.inputs["Factor"].default_value = 0
new_msk_out = new_msk.outputs[2]
new_emission_in = new_emission.inputs["Color"]
new_emission_out = new_emission.outputs[0]

これはミックスノードで起こる罠です(何でこんな仕様なんと突っ込みたくはなるし多分今後アップデートする時にまた仕様変わるんだろうなぁ……****)
さて、特に罠だと思った所はinputs[6] outputs[2]です

こいつを見てくれ、どう思う?

本来見た目上ではinputs[1] ,outputs[0]なんだ しかし実際には[6],[2]
これは簡単に言えば

ここが主な原因である

つまり[[係数],[A],[B],[係数],[A],[B],[係数],[A],[B]]
inputsのソケットはこういうデータの持ち方になっていて、indexをしっかり与えないと接続してくれないのである、なんとstringで指定してもindex[1]になる、困っちゃうよね
outputsの結果に関しても[結果],[結果],[結果] なのでindex[2]
多分他のノードもこういう仕様になってしまったと思われるので、注意する必要がありますね

余談:何故必要と思ったか

はじめに
キャラルック周りは個人的に(現実的な事情も加味した上で)納得しているが、背景回りに関してまだ答えが見つかってなかった、という前提がある
前回記事で書きました
まずはバウンシングボールから【CGアニメーション】|makeInuFilm (note.com)

これでざっくり50秒、blenderから書き出しまでやってみた
やってみた結果やっぱり背景だけなんか違う というザックリした感覚
を覚えてしまったわけです、以前まで、自分のキャラとか開発してた時は

背景は別にアセット買っておいとけばまぁ良いんじゃないか というテキトーなライブ感で生きていたのだが、やっぱりダメでありました

そりゃそうだろ的な感想は当然あると思うが、 一人ってのはどうしても限界がある、複数の目に晒されないと気付かない事というのは一杯ある、限界が狭いのが一人の弱点なわけです

当然一人の限界を伸ばす努力として
・CGで生産性の高い個人用のキャラデザを考える
・それを運用するためのリグや流用ツールの開発・自動化

これらに3年かけたわけだけども、別に一人で出来ると驕ってた訳ではなくて、出身がCGの専門とかじゃないとか、SNSであんま知己を増やす活動をしなかったとか、そういった過程から生まれる結果として一人しかいなかったからそうせざるを得なかった所が痛い所なわけであります

さて、申し開きは程ほどに
そんな稚拙な作り方をしているわけで、その稚拙さを自分で気付くためには恥ずかしい事にどんどん手を動かして公開するしかないという事ですね

そんなわけで背景どうしようかなという問題はまだまだ続くと思います、一人ではなぁ!

この記事が気に入ったらサポートをしてみませんか?