MRが楽しい

MRやVRについて学習したことを書き残す

Blender2.8で利用可能なpythonスクリプトを作る その30(頂点カラーのテクスチャベイク)

本日は Blender の技術調査枠です。
Blender2.8で利用可能なpythonスクリプトを作ります。

頂点カラーをテクスチャにベイクする

頂点カラーをテクスチャにベイクし、そのテクスチャを設定したプリンシプルBSDFのマテリアルを再設定します。
有効な頂点カラーレイヤー名を取得してエミッションカラーとして利用します。
レンダリング時に、GPUが利用可能な環境の場合は利用設定を行います。
・paint_vertexcolor_rgba.py

# bpyインポート
import bpy

# 指定オブジェクトの頂点カラーを画像テクスチャにベイクする
# その後、画像テクスチャを設定したプリンシプルBSDFのマテリアルを再設定する
def bake_vertexcolor_texture(arg_objectname="Default") -> bool:
    """指定オブジェクトの頂点カラーを画像テクスチャにベイクする
    その後、画像テクスチャを設定したプリンシプルBSDFのマテリアルを再設定する

    Keyword Arguments:
        arg_objectname {str} -- 対象オブジェクト名 (default: {"Default"})

    Returns:
        bool -- 実行正否
    """

    # 指定オブジェクトを取得する
    # (get関数は対象が存在しない場合 None が返る)
    selectob = bpy.data.objects.get(arg_objectname)

    # 指定オブジェクトが存在するか確認する
    if selectob == None:
        # 指定オブジェクトが存在しない場合は処理しない
        return False
    
    # 指定オブジェクトがメッシュオブジェクトか確認する
    if selectob.type != 'MESH':
        # メッシュオブジェクトでない場合は処理しない
        return False

    # オブジェクトをアクティブにする
    bpy.context.view_layer.objects.active = selectob
    
    # 各種ノードの参照名を定義する
    texturenodename = "ShaderNodeTexImage"
    attrnodename = "Attribute"
    emissionnodename = "Emission"

    # オブジェクトの選択状態をクリアする
    object_select_clear()

    # スマートUV展開を実行する
    uv_result = project_uv_smart(arg_objectname=arg_objectname)
    if uv_result != True:
        # UV作成に失敗した場合は処理しない
        return False

    # 指定オブジェクトの全マテリアルを削除する
    delmat_result = delete_material_all(arg_objectname=arg_objectname)
    if delmat_result != True:
        # マテリアル削除に失敗した場合は処理しない
        return False
    
    # 頂点カラーレイヤー名を取得する
    vertexcolor_name = get_vertexcolor_active(arg_objectname=arg_objectname) 
    if len(vertexcolor_name) < 1:
        # 名称取得に失敗した場合は処理しない
        return False

    # 指定オブジェクトに頂点カラーとエミッションを使ったシンプルなマテリアルを作成する
    addmat_result = add_material_vertexemission(arg_objectname=arg_objectname, arg_vertexcolorname=vertexcolor_name)
    if addmat_result != True:
        # マテリアル追加に失敗した場合は処理しない
        return False
    
    # テクスチャ用の画像ノードを追加してメッシュオブジェクトをベイクする
    # その後、画像ノードをカラーとして接続する
    bake_result = bake_texture_object(
        arg_objectname=arg_objectname,
        arg_addnodename=texturenodename,
        arg_texturesize=1024,
        arg_GPUuse=True
    )
    if bake_result != True:
        # テクスチャ作成に失敗した場合は処理しない
        return False

    # 不要なノード(属性ノード)を削除する
    delete_node_target(arg_objectname=arg_objectname, arg_deletenodename=attrnodename)
    
    # 不要なノード(エミッションノード)を削除する
    delete_node_target(arg_objectname=arg_objectname, arg_deletenodename=emissionnodename)

    return True

# 全てのオブジェクトを非選択状態にする
def object_select_clear():
    """全てのオブジェクトを非選択状態にする

    Returns:
        Bool -- 実行正否
    """
    
    # 全てのオブジェクトを走査する
    for ob in bpy.context.scene.objects:
        # 非選択状態に設定する
        ob.select_set(False)

    return True

# スマートUV展開を実行する(デフォルト設定)
def project_uv_smart(arg_objectname="Default") -> bool:
    """スマートUV展開を実行する(デフォルト設定)

    Keyword Arguments:
        arg_objectname {str} -- 対象オブジェクト名 (default: {'Default'})

    Returns:
        Bool -- 実行正否
    """

    # 指定オブジェクトを取得する
    # (get関数は対象が存在しない場合 None が返る)
    selectob = bpy.data.objects.get(arg_objectname)

    # 指定オブジェクトが存在するか確認する
    if selectob == None:
        # 指定オブジェクトが存在しない場合は処理しない
        return False
    
    # 不要なオブジェクトを選択しないように
    # 全てのオブジェクトを走査する
    for ob in bpy.context.scene.objects:
        # 非選択状態に設定する
        ob.select_set(False)
    
    # オブジェクトを選択状態にする
    selectob.select_set(True)
 
    # 対象オブジェクトをアクティブに変更する
    bpy.context.view_layer.objects.active = selectob

    # 編集モードに移行する
    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
    
    # 頂点を全選択した状態とする
    bpy.ops.mesh.select_all(action='SELECT')
    
    # デフォルト設定のスマートUV展開を実行する
    # 角度制限:66,島の余白:0,エリアウェイト:0,アスペクト比の補正:True,UV境界に合わせる:True
    bpy.ops.uv.smart_project(angle_limit=66, island_margin=0, 
      user_area_weight=0, use_aspect=True, stretch_to_bounds=True)
    
    # オブジェクトモードに移行する
    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
    
    return True

# 指定オブジェクトの全マテリアルを削除する
def delete_material_all(arg_objectname="Default") -> bool:
    """指定オブジェクトに新規マテリアルを作成する

    Keyword Arguments:
        arg_objectname {str} -- 対象オブジェクト名 (default: {"Default"})

    Returns:
        Bool -- 実行正否
    """

    # 指定オブジェクトを取得する
    # (get関数は対象が存在しない場合 None が返る)
    selectob = bpy.data.objects.get(arg_objectname)

    # 指定オブジェクトが存在するか確認する
    if selectob == None:
        # 指定オブジェクトが存在しない場合は処理しない
        return False
    
    # 指定オブジェクトがメッシュオブジェクトか確認する
    if selectob.type != 'MESH':
        # メッシュオブジェクトでない場合は処理しない
        return False

    # オブジェクトをアクティブにする
    bpy.context.view_layer.objects.active = selectob

    # マテリアルスロットを走査する
    for material_slot in selectob.material_slots:

        # マテリアルスロットをアクティブにする
        selectob.active_material = material_slot.material

        # マテリアルスロットを削除する
        bpy.ops.object.material_slot_remove()
    
    return True
# レンダリング時に有効な頂点カラーレイヤーの名前を取得する
def get_vertexcolor_active(arg_objectname="Default") -> str:
    """レンダリング時に有効な頂点カラーレイヤーの名前を取得する

    Keyword Arguments:
        arg_objectname {str} -- 対象オブジェクト名 (default: {"Default"})

    Returns:
        str -- レンダリング時に有効な頂点カラーレイヤーの名前(取得失敗は空文字)
    """

    # 指定オブジェクトを取得する
    # (get関数は対象が存在しない場合 None が返る)
    selectob = bpy.data.objects.get(arg_objectname)

    # 指定オブジェクトが存在するか確認する
    if selectob == None:
        # 指定オブジェクトが存在しない場合は処理しない
        return ""
    
    # 指定オブジェクトがメッシュオブジェクトか確認する
    if selectob.type != 'MESH':
        # メッシュオブジェクトでない場合は処理しない
        return ""

    # オブジェクトをアクティブにする
    bpy.context.view_layer.objects.active = selectob

    # 対象オブジェクトのメッシュデータを取得する
    # メッシュデータのインタフェース
    # (https://docs.blender.org/api/current/bpy.types.Mesh.html)
    meshdata = selectob.data

    # 頂点カラーレイヤーのリストを取得する
    # 頂点カラーレイヤーのリストのインタフェース
    # (https://docs.blender.org/api/current/bpy.types.LoopColors.html)
    vertexcolors = meshdata.vertex_colors

    # 変数の初期化
    render_index = -1

    # 頂点カラーレイヤーを走査する
    for index in range(0, len(vertexcolors)):
        # レンダリング時の有効無効をチェックする
        if vertexcolors[index].active_render == True:
            # レンダリング時に有効な頂点カラーレイヤーのインデックスを取得する
            render_index = index

    # 有効なインデックスが取得できたか確認する
    if render_index == -1:
        # 取得できなかった場合は空文字を返す
        return ""

    # レンダリング時に有効な頂点カラーレイヤーの参照を取得する
    active_vertexcolor = vertexcolors[render_index]

    return active_vertexcolor.name

# 指定オブジェクトに頂点カラーとエミッションを使ったシンプルなマテリアルを作成する
# (予め不要なノードは削除しておくこと)
def add_material_vertexemission(arg_objectname="Default",
  arg_materialname="DefaultMaterial", arg_vertexcolorname="Col") -> bool:
    """指定オブジェクトに頂点カラーとエミッションを使ったシンプルなマテリアルを作成する
    (予め不要なノードは削除しておくこと)

    Keyword Arguments:
        arg_objectname {str} -- 対象オブジェクト名 (default: {"Default"})
        arg_materialname {str} -- 追加マテリアル名 (default: {"NewMaterial"})
        arg_vertexcolorname {str} -- 頂点カラーレイヤー名 (default: {"Col"})

    Returns:
        Bool -- 実行正否
    """

    # 指定オブジェクトを取得する
    # (get関数は対象が存在しない場合 None が返る)
    selectob = bpy.data.objects.get(arg_objectname)

    # 指定オブジェクトが存在するか確認する
    if selectob == None:
        # 指定オブジェクトが存在しない場合は処理しない
        return False
    
    # 指定オブジェクトがメッシュオブジェクトか確認する
    if selectob.type != 'MESH':
        # メッシュオブジェクトでない場合は処理しない
        return False

    # オブジェクトをアクティブにする
    bpy.context.view_layer.objects.active = selectob

    # 新規マテリアルを作成する
    newmaterial = bpy.data.materials.new(arg_materialname)

    # マテリアルスロットを追加する
    bpy.ops.object.material_slot_add()

    # 追加したマテリアルスロットに新規マテリアルを設定する
    selectob.active_material = newmaterial

    # ノードを使用する
    newmaterial.use_nodes = True

    # 新規マテリアルのノード参照を取得する
    mat_nodes = newmaterial.node_tree.nodes

    # マテリアル内の全ノードを走査する
    for delete_node in mat_nodes:
        # 一旦全てのノードを削除する
        mat_nodes.remove(delete_node)

    # 属性ノードを追加する
    attr_node = mat_nodes.new(type="ShaderNodeAttribute")

    # 属性ノードのアトリビュート名を頂点カラーレイヤー名に変更する
    attr_node.attribute_name = arg_vertexcolorname

    # エミッションノードを追加する
    emit_node = mat_nodes.new(type="ShaderNodeEmission")

    # 出力ノードを追加する
    output_node = mat_nodes.new(type="ShaderNodeOutputMaterial")

    # ターゲットマテリアルのノードリンク参照を取得する
    mat_link = newmaterial.node_tree.links

    # 属性ノードのカラーと放射ノードのカラーを接続する
    mat_link.new(attr_node.outputs[0], emit_node.inputs[0])

    # 放射ノードの放射と出力ノードのサーフェスを接続する
    mat_link.new(emit_node.outputs[0], output_node.inputs[0])

    return True

# テクスチャ用の画像ノードを追加してメッシュオブジェクトをベイクする
# その後、画像ノードをカラーとして接続する
def bake_texture_object(arg_objectname="Default",
  arg_addnodename="ShaderNodeTexImage",
  arg_texturesize=2048,
  arg_GPUuse=False):
    """テクスチャ用の画像ノードを追加してメッシュオブジェクトをベイクする
    その後、画像ノードをカラーとして接続する

    Keyword Arguments:
        arg_objectname {str} -- 対象オブジェクト名 (default: {"Default"})
        arg_addnodename {str} -- 追加ノード名 (default: {"ShaderNodeTexImage"})
        arg_texturesize {int} -- テクスチャサイズ (default: {2048})
        arg_GPUuse {bool} -- GPU利用有無 (default: {False})

    Returns:
        Bool -- 実行正否
    """

    # 指定オブジェクトを取得する
    # (get関数は対象が存在しない場合 None が返る)
    selectob = bpy.data.objects.get(arg_objectname)

    # 指定オブジェクトが存在するか確認する
    if selectob == None:
        # 指定オブジェクトが存在しない場合は処理しない
        return False
    
    # 指定オブジェクトがメッシュオブジェクトか確認する
    if selectob.type != 'MESH':
        # メッシュオブジェクトでない場合は処理しない
        return False

    # オブジェクトをアクティブにする
    bpy.context.view_layer.objects.active = selectob

    # マテリアルスロットを走査する
    if len(selectob.material_slots) == 0:
        # マテリアルスロットが設定されていなければ処理しない
        return False

    # 処理対象のマテリアルを取得する
    target_mat = selectob.material_slots[0].material

    # ターゲットマテリアルのノード参照を取得
    mat_nodes = target_mat.node_tree.nodes

    # テクスチャノードの追加
    texture_node = mat_nodes.new(type="ShaderNodeTexImage")

    # テクスチャノードをアクティブにする
    mat_nodes.active = texture_node

    # 新規画像を作成する
    bakeimage = bpy.data.images.new(
        name="BakeTexture",
        width=arg_texturesize,
        height=arg_texturesize,
        alpha=True
    )

    # テクスチャノードに新規画像を設定する
    texture_node.image = bakeimage

    # レンダリングエンジンを CYCLES に切り替える
    bpy.data.scenes["Scene"].render.engine = 'CYCLES'

    # GPUの利用有無を確認する
    if arg_GPUuse == True:
        # 利用設定ならGPUの設定を行う
        bpy.data.scenes["Scene"].cycles.device = 'GPU'
        # CUDAを選択する
        bpy.context.preferences.addons['cycles'].preferences.compute_device_type = 'CUDA'
        # デバイスの一覧を取得する
        for devices in bpy.context.preferences.addons['cycles'].preferences.get_devices():
            for device in devices:
                # デバイスタイプがCUDAならば利用対象とする
                if device.type == 'CUDA':
                    print("利用可能なGPUを検出しました:" + device.name)
                    device.use = True

    # ベイクの余白を設定
    bpy.data.scenes["Scene"].render.bake_margin = 2

    # 放射タイプのレンダリングを実行する
    bpy.ops.object.bake(type='EMIT')

    # ノードリンクの取得
    mat_link = target_mat.node_tree.links

    # プリンシプルBSDFノードを追加する
    bsdf_node = mat_nodes.new(type="ShaderNodeBsdfPrincipled")

    # 出力ノードを追加する
    output_node = mat_nodes["Material Output"]

    # テクスチャノードのカラーとプリンシプルBSDFノードのベースカラーを接続する
    mat_link.new(texture_node.outputs[0], bsdf_node.inputs[0])
    
    # 放射ノードの放射と出力ノードのサーフェスを接続する
    mat_link.new(bsdf_node.outputs[0], output_node.inputs[0])

    return True

# 対象オブジェクトの先頭マテリアルの指定ノードを削除する
def delete_node_target(arg_objectname="Default", arg_deletenodename="DeleteNode"):
    """対象オブジェクトの先頭マテリアルの指定ノードを削除する

    Keyword Arguments:
        arg_objectname {str} -- 対象オブジェクト名 (default: {"Default"})
        arg_deletenodename {str} -- 削除ノード名 (default: {"DeleteNode"})

    Returns:
        Bool -- 実行正否
    """

    # 指定オブジェクトを取得する
    # (get関数は対象が存在しない場合 None が返る)
    selectob = bpy.data.objects.get(arg_objectname)

    # 指定オブジェクトが存在するか確認する
    if selectob == None:
        # 指定オブジェクトが存在しない場合は処理しない
        return False
    
    # 指定オブジェクトがメッシュオブジェクトか確認する
    if selectob.type != 'MESH':
        # メッシュオブジェクトでない場合は処理しない
        return False

    # オブジェクトをアクティブにする
    bpy.context.view_layer.objects.active = selectob

    # マテリアルスロットを走査する
    if len(selectob.material_slots) == 0:
        # マテリアルスロットが設定されていなければ処理しない
        return False

    # 処理対象のマテリアルを取得する
    target_mat = selectob.material_slots[0].material

    # ターゲットマテリアルのノード参照を取得
    mat_nodes = target_mat.node_tree.nodes

    # 削除ノードの取得
    delete_node = mat_nodes[arg_deletenodename]

    # ノードを削除する
    mat_nodes.remove(delete_node)

    return True


# 関数の実行例
bake_vertexcolor_texture(arg_objectname="Sphere")

f:id:bluebirdofoz:20200520215716j:plain
f:id:bluebirdofoz:20200520215726j:plain