MRが楽しい

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

Blender2.8で利用可能なpythonスクリプトを作る その71(類似マテリアルスロットの差し替え)

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

類似マテリアルスロットの差し替え

マテリアルスロットに登録されているマテリアル同士を比較し、類似するマテリアルがあれば差し替えます。
スクリプトでは以下の条件で類似マテリアルを判断しています。
1.指定マテリアルのアクティブな出力ノードにノードが接続されているか
2.アクティブな出力ノードに接続されたノードはプリンシプルBSDFか
3.プリンシプルBSDFのベースカラー端子にリンクが貼られておらず、デフォルト値が有効か
4.デフォルト値が有効な場合、そのカラー値が一致すれば類似と判断する

スクリプトBlenderプロジェクトフォルダにある2つのPythonスクリプトを参照しています。
・Script_material_marge_object.py

# 各種ライブラリインポート
import sys, os
import bpy

## エディタ実行時に追加ライブラリを参照するため
## 読み込みスクリプトのディレクトリをシステムパスに追加する
# Blenderプロジェクトのファイルパスを取得
blend_filepath = bpy.data.filepath
# Blenderプロジェクトの読み込み元のディレクトリパスを取得
blend_dirpath = os.path.dirname(blend_filepath)
# 読み込み元のディレクトリパスをシステムパスに追加
sys.path += [blend_dirpath]

# 追加ライブラリのインポート
import Script_check_surface_bsdf
import Script_get_basecolor_bsdf


# 指定したオブジェクトのマテリアルを類似マテリアルにマージする
def material_marge_object(arg_object:bpy.types.Object) -> bool:
    """指定したオブジェクトのマテリアルを類似マテリアルにマージする

    Args:
        arg_object (bpy.types.Object): 指定オブジェクト

    Returns:
        bool: 実行正否
    """

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

    # オブジェクトのマテリアルスロットを走査する
    for check_material_slot in arg_object.material_slots:
        # スロットのマテリアルを取得
        check_mat = check_material_slot.material
        # 全マテリアルデータを走査する
        for comp_material_slot in arg_object.material_slots:
            # スロットのマテリアルを取得
            comp_mat = comp_material_slot.material
            # 一致マテリアルを検出する前に自身がリストに出た場合
            # マージ処理を行わない
            if check_mat.name == comp_mat.name:
                break

            # マテリアルの比較処理
            comp_result = comp_material_color(check_mat, comp_mat)

            # ディフューズ色を比較
            if comp_result:
                # マテリアルを一致したものに差し替え
                check_material_slot.material = comp_mat
                # マージ処理を終了する
                break

    return True

# 指定マテリアルのカラー情報を比較する
def comp_material_color(arg_material_one:bpy.types.Material,
  arg_material_two:bpy.types.Material) -> bool:
    """指定マテリアルのカラー情報を比較する
    受け渡したマテリアルの出力ノードに接続されたプリシプルBSDFノードのベースカラーを比較する
    比較したベースカラーが同一の場合、Trueを返す

    Args:
        arg_material_one (bpy.types.Material): 比較マテリアル1
        arg_material_two (bpy.types.Material): 比較マテリアル2

    Returns:
        bool: 比較結果(一致:True)
    """

    # 比較結果フラグ
    comp_result = False

    # カラー情報の変数を用意する
    material_nodesocketcolor_one = None
    material_nodesocketcolor_two = None

    # マテリアルの出力ノードにプリンシプルBSDFノードが接続されているかチェックする
    if Script_check_surface_bsdf.check_surface_bsdf(arg_material_one):
        # プリンシプルBSDFノードのベースカラーの値を取得する
        material_nodesocketcolor_one = Script_get_basecolor_bsdf.get_basecolor_bsdf(arg_material_one)
    else:
        # プリシプルBSDF出なかった場合は処理を終了して False を返す
        return False
    
    # マテリアルの出力ノードにプリンシプルBSDFノードが接続されているかチェックする
    if Script_check_surface_bsdf.check_surface_bsdf(arg_material_two):
        # プリンシプルBSDFノードのベースカラーの値を取得する
        material_nodesocketcolor_two = Script_get_basecolor_bsdf.get_basecolor_bsdf(arg_material_two)
    else:
        # プリシプルBSDF出なかった場合、処理を終了して False を返す
        return False
    
    # ベースカラーの値が取得できたかチェックする
    if ((material_nodesocketcolor_one == None) or
        (material_nodesocketcolor_two == None)):
        # 値が取得できなかった場合は処理を終了して False を返す
        return False

    # カラーソケットの各値が一致するかチェックする
    if ((material_nodesocketcolor_one[0] == material_nodesocketcolor_two[0]) and
        (material_nodesocketcolor_one[1] == material_nodesocketcolor_two[1]) and
        (material_nodesocketcolor_one[2] == material_nodesocketcolor_two[2]) and
        (material_nodesocketcolor_one[3] == material_nodesocketcolor_two[3])):
        # 全ての値が一致する場合は一致と見なす
        comp_result = True

    return comp_result

# 関数の実行例
target_obj = bpy.data.objects.get("Cube")
material_marge_object(target_obj)

・Script_check_surface_bsdf.py

# bpyインポート
import bpy

# 指定マテリアルのアクティブな出力ノードに接続されたノードがプリンシプルBSDFかチェックする
def check_surface_bsdf(arg_material:bpy.types.Material) -> bool:
    """指定マテリアルのアクティブな出力ノードに接続されたノードがプリンシプルBSDFかチェックする

    Args:
        arg_material (bpy.types.Material): 指定マテリアル

    Returns:
        bool: プリンシプルBSDFが接続されているか
    """

    # マテリアルのノードを有効化する
    use_material_node(arg_material=arg_material)

    # アクティブな出力ノードに接続されたノードを取得する
    get_node = get_node_linkoutput(arg_material=arg_material)

    # ノードが取得できたか確認する
    if get_node == None:
        # サーフェスノードが存在しない場合はFalseを返す
        return False

    # ノードの種類がプリンシプルBSDFかチェックして結果を返す
    isBSDF = check_isnode_bsdf(get_node)

    return isBSDF


# アクティブな出力ノードに接続されたノードを取得する
def get_node_linkoutput(arg_material:bpy.types.Material) -> bpy.types.Node:
    """アクティブな出力ノードに接続されたノードを取得する

    Args:
        arg_material (bpy.types.Material): 指定マテリアル

    Returns:
        bpy.types.Node: アクティブな出力ノードに接続されたノード
    """

    # 参照の保存用変数
    name_mapping = {}

    # ノード操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.Node.html)
    # ノードリスト操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.Nodes.html)
    # ノードツリー操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.NodeTree.html)

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

    # 出力ノードを取得する変数
    output_node = None

    # 出力ノードの操作マニュアル
    # (https://docs.blender.org/api/current/bpy.types.ShaderNodeOutputMaterial.html)

    # 全ノードを走査する
    for check_node in mat_nodes:
        # ノードタイプを取得する
        node_idname = check_node.bl_idname

        # ノードタイプが出力ノードか確認する
        if node_idname == 'ShaderNodeOutputMaterial':
            # アクティブな出力ノードのフラグを取得する
            is_activeoutput = check_node.is_active_output

            # アクティブな出力ノードかチェックする
            if is_activeoutput == True:
                # アクティブな出力ノードなら保持する
                output_node = check_node

    # 出力ノードが取得できたか確認する
    if output_node == None:
        # 出力ノードが存在しない場合は処理しない
        return None
    
    # ノードソケット操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.NodeSocket.html)

    # 出力ノードのサーフェス入力(1番目の入力)のリンクを確認する
    surface_input = output_node.inputs[0]

    # リンクが接続されているか確認する
    if surface_input.is_linked == False:
        # 出力ノードにサーフェスノードが接続されていない場合は処理しない
        return None

    # リンク操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.NodeLink.html#bpy.types.NodeLink)

    # リンクの一覧を取得する
    mat_links = arg_material.node_tree.links

    # 接続元ノードを取得する変数
    surface_node = None

    # リンクを走査する
    for check_link in mat_links:
        # 接続先が出力ノードのサーフェス入力か確認する
        if check_link.to_socket == surface_input:
            # リンクの接続元ノードを取得する
            surface_node = check_link.from_node
    
    # 接続元ノードが取得できたか確認する
    if surface_node == None:
        # 接続元ノードが存在しない場合は処理しない
        return None
    
    # 接続元となっているサーフェスノードを返却する
    return_node = surface_node

    return return_node


# 対象マテリアルのノードを有効化する
def use_material_node(arg_material:bpy.types.Material):
    """対象マテリアルのノードを有効化する

    Args:
        arg_material (bpy.types.Material): 対象マテリアル
    """

    # マテリアル操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.Material.html)

    # ノードが無効な場合、有効化する
    if arg_material.use_nodes == False:
        arg_material.use_nodes = True

    return


# 指定ノードがプリンシプルBSDFかチェックする
def check_isnode_bsdf(arg_node:bpy.types.Node) -> bool:
    """指定ノードがプリンシプルBSDFかチェックする

    Args:
        arg_node (bpy.types.Node): 指定ノード

    Returns:
        bool: プリンシプルBSDFか否か
    """

    # チェック結果
    isBSDF = False

    # ノードタイプを取得する
    node_idname = arg_node.bl_idname

    # ノードタイプがプリンシプルBSDFノードか確認する
    if node_idname == 'ShaderNodeBsdfPrincipled':
        # プリンシプルBSDFならTrueを返す
        isBSDF = True

    return isBSDF

・Script_get_basecolor_bsdf.py

# bpyインポート
import bpy

# 指定マテリアルのアクティブな出力ノードに接続されたプリンシプルBSDFのベースカラーを取得する
def get_basecolor_bsdf(arg_material:bpy.types.Material) -> bpy.types.NodeSocketColor:
    """指定マテリアルのアクティブな出力ノードに接続されたプリンシプルBSDFのベースカラーを取得する

    Args:
        arg_material (bpy.types.Material): 指定マテリアル

    Returns:
        bpy.types.NodeSocketColor: ベースカラー(取得失敗時 None)
    """

    # カラーの取得変数を初期化する
    getBaseColor = None

    # マテリアルのノードを有効化する
    use_material_node(arg_material=arg_material)

    # アクティブな出力ノードに接続されたノードを取得する
    get_node = get_node_linkoutput(arg_material=arg_material)

    # ノードが取得できたか確認する
    if get_node == None:
        # サーフェスノードが存在しない場合はFalseを返す
        return False

    # ノードの種類がプリンシプルBSDFかチェックして結果を返す
    isBSDF = check_isnode_bsdf(get_node)

    # プリンスプルBSDFか確認する
    if isBSDF == True:
        # プリンシプルBSDFならベースカラーにリンクが貼られているかチェックする
        isCheckedLink = check_link_bsdf_basecolor(get_node)

        # リンクが貼られていたか確認する
        if isCheckedLink == False:
            # リンクが貼られていないならベースカラー値を取得する
            getBaseColor = get_value_bsdf_basecolor(get_node)

    return getBaseColor

# アクティブな出力ノードに接続されたノードを取得する
def get_node_linkoutput(arg_material:bpy.types.Material) -> bpy.types.Node:
    """アクティブな出力ノードに接続されたノードを取得する

    Args:
        arg_material (bpy.types.Material): 指定マテリアル

    Returns:
        bpy.types.Node: アクティブな出力ノードに接続されたノード
    """

    # 参照の保存用変数
    name_mapping = {}

    # ノード操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.Node.html)
    # ノードリスト操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.Nodes.html)
    # ノードツリー操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.NodeTree.html)

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

    # 出力ノードを取得する変数
    output_node = None

    # 出力ノードの操作マニュアル
    # (https://docs.blender.org/api/current/bpy.types.ShaderNodeOutputMaterial.html)

    # 全ノードを走査する
    for check_node in mat_nodes:
        # ノードタイプを取得する
        node_idname = check_node.bl_idname

        # ノードタイプが出力ノードか確認する
        if node_idname == 'ShaderNodeOutputMaterial':
            # アクティブな出力ノードのフラグを取得する
            is_activeoutput = check_node.is_active_output

            # アクティブな出力ノードかチェックする
            if is_activeoutput == True:
                # アクティブな出力ノードなら保持する
                output_node = check_node

    # 出力ノードが取得できたか確認する
    if output_node == None:
        # 出力ノードが存在しない場合は処理しない
        return None
    
    # ノードソケット操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.NodeSocket.html)

    # 出力ノードのサーフェス入力(1番目の入力)のリンクを確認する
    surface_input = output_node.inputs[0]

    # リンクが接続されているか確認する
    if surface_input.is_linked == False:
        # 出力ノードにサーフェスノードが接続されていない場合は処理しない
        return None

    # リンク操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.NodeLink.html#bpy.types.NodeLink)

    # リンクの一覧を取得する
    mat_links = arg_material.node_tree.links

    # 接続元ノードを取得する変数
    surface_node = None

    # リンクを走査する
    for check_link in mat_links:
        # 接続先が出力ノードのサーフェス入力か確認する
        if check_link.to_socket == surface_input:
            # リンクの接続元ノードを取得する
            surface_node = check_link.from_node
    
    # 接続元ノードが取得できたか確認する
    if surface_node == None:
        # 接続元ノードが存在しない場合は処理しない
        return None
    
    # 接続元となっているサーフェスノードを返却する
    return_node = surface_node

    return return_node

# 対象マテリアルのノードを有効化する
def use_material_node(arg_material:bpy.types.Material):
    """対象マテリアルのノードを有効化する

    Args:
        arg_material (bpy.types.Material): 対象マテリアル
    """

    # マテリアル操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.Material.html)

    # ノードが無効な場合、有効化する
    if arg_material.use_nodes == False:
        arg_material.use_nodes = True

    return

# 指定ノードがプリンシプルBSDFかチェックする
def check_isnode_bsdf(arg_node:bpy.types.Node) -> bool:
    """指定ノードがプリンシプルBSDFかチェックする

    Args:
        arg_node (bpy.types.Node): 指定ノード

    Returns:
        bool: プリンシプルBSDFか否か
    """

    # チェック結果
    isBSDF = False

    # ノードタイプを取得する
    node_idname = arg_node.bl_idname

    # ノードタイプがプリンシプルBSDFノードか確認する
    if node_idname == 'ShaderNodeBsdfPrincipled':
        # プリンシプルBSDFならTrueを返す
        isBSDF = True

    return isBSDF


# 指定ノードのプリンシプルBSDFのベースカラーにリンクが設定されているかチェックする
def check_link_bsdf_basecolor(arg_node:bpy.types.Node) -> bool:
    """指定ノードのプリンシプルBSDFのベースカラーにリンクが設定されているかチェックする

    Args:
        arg_node (bpy.types.Node): 指定ノード

    Returns:
        bool: リンクの有無
    """

    # チェック結果
    isLinked = False

    # ベースカラーのリンクが接続されているか確認する
    if arg_node.inputs["Base Color"].is_linked == True:
        # リンクが設定されていればTrueを返す
        isLinked = True
    
    return isLinked

# 指定ノードのプリンシプルBSDFのベースカラー値を取得する
def get_value_bsdf_basecolor(arg_node:bpy.types.Node) -> bpy.types.NodeSocketColor:
    """指定ノードのプリンシプルBSDFのベースカラー値を取得する

    Args:
        arg_node (bpy.types.Node): 指定ノード

    Returns:
        bpy.types.NodeSocketColor: ベースカラー値
    """

    # ベースカラーのデフォルトカラーを取得する
    # ノードソケットカラー操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.NodeSocketColor.html)
    basecolor_value = arg_node.inputs["Base Color"].default_value

    return basecolor_value

・実行前
f:id:bluebirdofoz:20201021213958j:plain
・実行後
f:id:bluebirdofoz:20201021214008j:plain

参考ページ

以前記事にした以下のスクリプトを参考にしています。
bluebirdofoz.hatenablog.com
bluebirdofoz.hatenablog.com
bluebirdofoz.hatenablog.com