MRが楽しい

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

Blender2.8で利用可能なpythonスクリプトを作る その72(BSDFのデフォルト値が一致するマテリアルスロットの差し替え)

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

BSDFのデフォルト値が一致するマテリアルスロットの差し替え

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

・Script_comp_material_bsdf.py

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

# プリンシプルBSDFノードで比較対象とする入力端子の名前をリストで定義する
def_comp_bsdfnode_input_list = [
    "Base Color",
    "Subsurface",
    "Subsurface Radius",
    "Subsurface Color",
    "Metallic",
    "Specular",
    "Specular Tint",
    "Roughness",
    "Anisotropic",
    "Anisotropic Rotation",
    "Sheen",
    "Sheen Tint",
    "Clearcoat",
    "Clearcoat Roughness",
    "IOR",
    "Transmission",
    "Transmission Roughness",
    "Emission",
    "Alpha",
    "Normal",
    "Clearcoat Normal",
    "Tangent",
]

# 指定したオブジェクトのマテリアルを類似マテリアルにマージする
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_bsdf(check_mat, comp_mat)

            # 比較結果をチェックする
            if comp_result:
                # マテリアルを一致したものに差し替え
                check_material_slot.material = comp_mat
                # マージ処理を終了する
                break

    return True

# 指定マテリアルのBSDFノードを比較する
def comp_material_bsdf(arg_material_one:bpy.types.Material,
  arg_material_two:bpy.types.Material) -> bool:
    """指定マテリアルのBSDFノードを比較する
    受け渡したマテリアルの出力ノードに接続されたプリシプルBSDFノードを比較する
    比較対象の入力端子のデフォルト値が有効、かつ、全て同一の場合、Trueを返す

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

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

    # マテリアルの出力ノードにプリンシプルBSDFノードが接続されているかチェックする
    if check_surface_bsdf(arg_material_one) == False:
        # プリシプルBSDF出なかった場合は処理を終了して False を返す
        return False
    
    # マテリアルの出力ノードにプリンシプルBSDFノードが接続されているかチェックする
    if check_surface_bsdf(arg_material_two) == False:
        # プリシプルBSDF出なかった場合、処理を終了して False を返す
        return False

    # プリンシプルBSDFノードを取得する
    get_node_one = get_node_linkoutput(arg_material_one)

    # プリンシプルBSDFノードを取得する
    get_node_two = get_node_linkoutput(arg_material_two)

    # 比較結果フラグ(デフォルトで一致判定)
    comp_result = True

    # 比較対象とする入力端子を全てチェックする
    for bsdfnode_inputname in def_comp_bsdfnode_input_list:

        # デフォルト値が有効なソケットの情報を取得する
        nodesocket_one = get_nodesocket_enabledefault(arg_node=get_node_one, arg_inputname=bsdfnode_inputname)
        nodesocket_two = get_nodesocket_enabledefault(arg_node=get_node_two, arg_inputname=bsdfnode_inputname)

        # デフォルト値が有効なソケット情報を取得できたか確認する
        if ((nodesocket_one == None) or (nodesocket_two == None)):
            # ソケット情報を取得できなかった場合は不一致としてチェックを終了する
            comp_result = False
            break

        # ソケットのタイプが同一か確認する
        if (type(nodesocket_one) != type(nodesocket_two)):
            # 同一でない場合は不一致としてチェックを終了する
            comp_result = False
            break

        # タイプ毎の値比較の実施済みフラグ
        checked_flg = False

        # NodeSocketFloatのソケットの比較
        if isinstance(nodesocket_one, bpy.types.NodeSocketFloat):
            # 値が一致するか比較する
            if (nodesocket_one.default_value != nodesocket_two.default_value):
                # 値が一致しない場合は不一致としてチェックを終了する
                comp_result = False
                break
            else:
                # タイプ毎の値比較の実施済みフラグを設定する
                checked_flg = True

        # NodeSocketFloatFactorのソケットの比較
        if isinstance(nodesocket_one, bpy.types.NodeSocketFloatFactor):
            # 値が一致するか比較する
            if (nodesocket_one.default_value != nodesocket_two.default_value):
                # 値が一致しない場合は不一致としてチェックを終了する
                comp_result = False
                break
            else:
                # タイプ毎の値比較の実施済みフラグを設定する
                checked_flg = True

        # NodeSocketVectorのソケットの比較
        if isinstance(nodesocket_one, bpy.types.NodeSocketVector):
            # 値が一致するか比較する
            if ((nodesocket_one.default_value[0] != nodesocket_two.default_value[0]) or
                (nodesocket_one.default_value[1] != nodesocket_two.default_value[1]) or
                (nodesocket_one.default_value[2] != nodesocket_two.default_value[2])):
                # 値が一致しない場合は不一致としてチェックを終了する
                comp_result = False
                break
            else:
                # タイプ毎の値比較の実施済みフラグを設定する
                checked_flg = True

        # NodeSocketColorのソケットの比較
        if isinstance(nodesocket_one, bpy.types.NodeSocketColor):
            # 値が一致するか比較する
            if ((nodesocket_one.default_value[0] != nodesocket_two.default_value[0]) or
                (nodesocket_one.default_value[1] != nodesocket_two.default_value[1]) or
                (nodesocket_one.default_value[2] != nodesocket_two.default_value[2]) or
                (nodesocket_one.default_value[3] != nodesocket_two.default_value[3])):
                # 値が一致しない場合は不一致としてチェックを終了する
                comp_result = False
                break
            else:
                # タイプ毎の値比較の実施済みフラグを設定する
                checked_flg = True
        
        # 値比較を実施済みか確認する
        if checked_flg == False:
            # 合致するタイプがない場合はBSDFでないと判断して不一致としてチェックを終了する
            comp_result = False
            break

    return comp_result

# 指定マテリアルのアクティブな出力ノードに接続されたノードがプリンシプル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

# 指定ノードの指定入力端子名のソケットをデフォルト値が有効な場合に取得する
def get_nodesocket_enabledefault(arg_node:bpy.types.Node, arg_inputname:str) -> bpy.types.NodeSocketStandard:
    """指定ノードの指定入力端子名のソケットをデフォルト値が有効な場合に取得する

    Args:
        arg_material (bpy.types.Material): 指定マテリアル
        arg_inputname (str): 入力端子名

    Returns:
        bpy.types.NodeSocketStandard: ノードソケット(取得失敗時 None)
    """

    # 指定名の入力端子のソケットを取得する
    # (get関数は対象が存在しない場合 None が返る)
    get_NodeSocketStandard = arg_node.inputs.get(arg_inputname)

    # 指定名の入力端子が存在するか確認する
    if get_NodeSocketStandard == None:
        # 指定名の入力端子が存在しない場合はソケットを返さない
        return None

    # ベースカラーのリンクが接続されているか確認する
    if get_NodeSocketStandard.is_linked == True:
        # リンクが設定されていればデフォルト値は無効なためソケットを返さない
        return None
    
    return get_NodeSocketStandard

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

・実行前
f:id:bluebirdofoz:20201022215708j:plain
・実行後
f:id:bluebirdofoz:20201022215718j:plain

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

Blender2.8で利用可能なpythonスクリプトを作る その70(プロジェクトまたはスクリプトフォルダの外部Pythonスクリプトを参照する)

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

プロジェクトフォルダの外部Pythonスクリプトを参照する

Blenderプロジェクトフォルダ(.blend)にあるPythonスクリプトをインポートして利用します。
プロジェクトフォルダをシステムパスに加えることで参照しています。
・Script_import_script_by_blendfolder.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_print_test

# ライブラリの関数を呼び出す
Script_print_test.method_print_test("blend_filepath : " + blend_filepath)
Script_print_test.method_print_test("blend_dirpath : " + blend_dirpath)
Script_print_test.method_print_test("Test Message")

・Script_print_test.py

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

# メッセージを表示する
def method_print_test(arg_str:str) -> bool:
    """メッセージを表示する

    Args:
        arg_str (str): 表示メッセージ

    Returns:
        bool: 実行正否
    """
    # メッセージを表示する
    print(arg_str)

    return True

f:id:bluebirdofoz:20201020230859j:plain

プロジェクトフォルダの外部Pythonスクリプトを参照する

Blenderプロジェクトフォルダ(.blend)にあるPythonスクリプトをインポートして利用します。
プロジェクトフォルダをシステムパスに加えることで参照しています。
・Script_import_script_by_scriptfolder.py

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

## エディタ実行時に追加ライブラリを参照するため
## 読み込みスクリプトのディレクトリをシステムパスに追加する
# 実行中のスクリプト名を取得
script_filename = os.path.basename(__file__)
# 読み込み中のテキストファイルリストからファイルパスを取得
script_filepath = bpy.data.texts[script_filename].filepath
# スクリプトの読み込み元のディレクトリパスを取得
script_dirpath = os.path.dirname(script_filepath)
# 読み込み元のディレクトリパスをシステムパスに追加
sys.path += [script_dirpath]

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

# ライブラリの関数を呼び出す
Script_print_test.method_print_test("script_filename : " + script_filename)
Script_print_test.method_print_test("script_filepath : " + script_filepath)
Script_print_test.method_print_test("script_dirpath : " + script_dirpath)
Script_print_test.method_print_test("Test Message")

・Script_print_test.py

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

# メッセージを表示する
def method_print_test(arg_str:str) -> bool:
    """メッセージを表示する

    Args:
        arg_str (str): 表示メッセージ

    Returns:
        bool: 実行正否
    """
    # メッセージを表示する
    print(arg_str)

    return True

f:id:bluebirdofoz:20201020230907j:plain

Blender2.8で利用可能なpythonスクリプトを作る その69(プリンシプルBSDFのベースカラー端子の情報)

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

プリンシプルBSDFのベースカラー端子の情報

プリンシプルBSDFのベースカラー端子の情報をチェックし、デフォルト値が有効な場合はベースカラー端子のデフォルト値を取得します。
スクリプトは以下のチェックを行います。
1.指定マテリアルのアクティブな出力ノードにノードが接続されているか
2.アクティブな出力ノードに接続されたノードはプリンシプルBSDFか
3.プリンシプルBSDFのベースカラー端子にリンクが貼られておらず、デフォルト値が有効か
これらのチェックが通った場合、ベースカラー端子のデフォルト値を取得します。
・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


# 関数の実行例
target_mat = bpy.data.materials.get("Red")
mat_color = get_basecolor_bsdf(target_mat)
if mat_color != None:
    print("Red:" + str(mat_color[0]) + ",Green:" + str(mat_color[1])
      + ",Blue:" + str(mat_color[2]) + ",Alpha:" + str(mat_color[3]))
else:
    print("None")

・ベースカラー有効時
f:id:bluebirdofoz:20201019231838j:plain
・デフォルト値が無効な場合
f:id:bluebirdofoz:20201019231848j:plain
・プリンシプルBSDF以外の場合
f:id:bluebirdofoz:20201019231859j:plain

Blender2.8で利用可能なpythonスクリプトを作る その68(マテリアルスロットのソートと重複削除)

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

マテリアルスロットを名前順でソートする

オブジェクトのマテリアルスロットの順序を名前でソートします。
・Script_sort_materialslot.py

# bpyインポート
import bpy

# 指定オブジェクトのマテリアルスロットをソートする
def sort_materialslot_name(arg_object:bpy.types.Object) -> bool:
    """指定オブジェクトのマテリアルスロットをソートする

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

    Returns:
        bool: 実行正否
    """

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

    # 比較継続フラグ
    change_flg = True

    # 並び替えが完了するまでループ
    while change_flg:
        # 比較継続フラグを初期化する
        change_flg = False

        # マテリアルスロットを走査する
        for num in range(len(arg_object.material_slots)-1):
            # マテリアルを取得する
            check_mat = arg_object.material_slots[num].material
            comp_mat = arg_object.material_slots[num+1].material

            # マテリアル名を比較する
            if check_mat.name > comp_mat.name:
                # 位置を入れ替える
                arg_object.active_material_index = num
                bpy.ops.object.material_slot_move(direction='DOWN')

                # 比較を継続する
                change_flg = True

    return True


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

・実行前
f:id:bluebirdofoz:20201018203829j:plain
・実行後
f:id:bluebirdofoz:20201018203838j:plain

マテリアルスロットの重複を削除する

オブジェクトに同じマテリアルを参照するマテリアルスロットがあれば削除します。
マテリアルスロットは削除すると、直上のマテリアルに割り当てが統合されるので、隣り合ったマテリアルにのみ重複チェックを実施しています。
前述のマテリアルスロットのソートと組み合わせて利用すると、全ての重複が削除されます。
・Script_delate_materialslot.py

# bpyインポート
import bpy

# マテリアルスロットの隣り合った重複を削除する
def delate_materialslot_duplicate(arg_object:bpy.types.Object) -> bool:
    """指定オブジェクトのマテリアルスロットをソートする

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

    Returns:
        bool: 実行正否
    """

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

    # 重複しているマテリアルスロットを削除する
    # 削除処理を行うので逆順に要素を追う
    for num in range(len(arg_object.material_slots)-1)[::-1]:
        # マテリアルを取得する
        check_mat = arg_object.material_slots[num].material
        comp_mat = arg_object.material_slots[num+1].material

        # マテリアル名を比較する
        if check_mat.name == comp_mat.name:
            # マテリアル名が同じならば削除する
            arg_object.active_material_index = num + 1
            bpy.ops.object.material_slot_remove()

    return True


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

・実行前
f:id:bluebirdofoz:20201018203848j:plain
・実行後
f:id:bluebirdofoz:20201018203858j:plain

空マテリアルの削除

指定のオブジェクトからマテリアルが設定されていないからマテリアルを削除します。
・Script_delate_materialslot_empty

# bpyインポート
import bpy

# 空のマテリアルスロットを削除する
def delate_materialslot_empty(arg_object:bpy.types.Object) -> bool:
    """空のマテリアルスロットを削除する

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

    Returns:
        bool: 実行正否
    """

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

    # 重複しているマテリアルスロットを削除する
    # 削除処理を行うので逆順に要素を追う
    for num in range(len(arg_object.material_slots)-1)[::-1]:
        # マテリアルを取得する
        targetmat = arg_object.material_slots[num].material

        # マテリアルが空であるか
        if targetmat == None:
            # マテリアル名が同じならば削除する
            arg_object.active_material_index = num
            bpy.ops.object.material_slot_remove()

    return True


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

・実行前
f:id:bluebirdofoz:20201021213302j:plain
・実行後
f:id:bluebirdofoz:20201021213321j:plain

UnityでGenericリグのキャラクターモデルのアニメーションを共通化できる条件を調査する

本日は Unity の小ネタ枠です。
UnityでGenericリグのキャラクターモデルのアニメーションを共通化する条件の調査結果を記事にします。

UnityでGenericリグのキャラクターモデルのアニメーションを共通化する

本記事は以下の前回記事の続きです。
bluebirdofoz.hatenablog.com

今後の3Dモデルファイル更新時に参考にするため、共通化できるリグの条件を確認します。
以下のような尻尾を振るモーションを作成し、アーマチュアオブジェクトに特定の改変が行われた際、その後もアニメーションが流用可能かチェックします。
f:id:bluebirdofoz:20201017211015j:plain

アーマチュアオブジェクトの名前変更

アーマチュアオブジェクトの名称を変更した場合です。
f:id:bluebirdofoz:20201017211027j:plain

モーションは全く動かず、アニメーションは共通化できません。
[Root node]の指定を合わせても共通化できませんでした。
f:id:bluebirdofoz:20201017211039j:plain

ボーン名の変更

一部のボーンの名称を変更した場合です。
まず、体のルートに当たる hip ボーンの名称を変更した場合です。
f:id:bluebirdofoz:20201017211049j:plain

モーションは全く動かず、アニメーションは共通化できません。
f:id:bluebirdofoz:20201017211100j:plain

ボーン名の変更(途中)

一部のボーンの名称を変更した場合です。
こちらは尻尾の途中に当たる tail_third ボーンの名称を変更した場合です。
f:id:bluebirdofoz:20201017211110j:plain

モーションは名称を変更していない tail_second ボーンまではアニメーションが再生されます。
名称を変更した tail_third ボーンから先は tail_second ボーンの動きにそのまま追従します。
f:id:bluebirdofoz:20201017211121j:plain

ボーンの追加

ボーンを追加した場合です。
尻尾の途中に当たる tail_second ボーンから追加のボーンを枝分かれで追加した場合です。
f:id:bluebirdofoz:20201017211131j:plain

モーションは問題なく共通化されて動作します。
f:id:bluebirdofoz:20201017211141j:plain

ボーンの削除

ボーンを削除した場合です。
尻尾の途中に当たる tail_third ボーンから先を削除します。
f:id:bluebirdofoz:20201017211151j:plain

ボーンを削除していない tail_second ボーンまではアニメーションが再生されます。
削除した tail_third ボーンから先は tail_second ボーンの動きにそのまま追従します。
f:id:bluebirdofoz:20201017211200j:plain

ボーンの分割

ボーンを分割した場合です。
尻尾の途中に当たる tail_first のボーンを分割して2つのボーンにしてみました。
f:id:bluebirdofoz:20201017211339j:plain

元々の tail_first ボーンまではアニメーションが再生されます。
分割で追加された tail_first.001 ボーンから先は tail_first ボーンの動きにそのまま追従します。
f:id:bluebirdofoz:20201017211351j:plain

ボーンの移動

ボーンを移動した場合です。
尻尾の途中に当たる tail_second のボーンを編集モードで移動してみました。
f:id:bluebirdofoz:20201017211400j:plain

メッシュにボーンの位置変更が反映され、その状態でアニメーションが再生されます。
f:id:bluebirdofoz:20201017211413j:plain

UnityでGenericリグのキャラクターモデルのアニメーションを共通化する

本日は Unity の小ネタ枠です。
UnityでGenericリグのキャラクターモデルのアニメーションを共通化する手順を記事にします。

リグのアニメーションの共通化

Unityでは Humanoid という共通のリグフォーマットがあり、このフォーマットに関連付けられたアバターのアニメーションは共通化することができます。
blogs.unity3d.com
bluebirdofoz.hatenablog.com

一方で Generic のリグフォーマットであっても、リグの構造が一致していれば異なるファイル間でアニメーションを共通化することができます。
作成中のモンスター型モデルでアニメーションの共通化を試してみます。
f:id:bluebirdofoz:20201016222355j:plain

Genericリグのキャラクターモデルのアニメーションの共通化

Genericリグのモデルであっても、アーマチュアの構造が一致していればメッシュオブジェクトの数が一致していなくてもアニメーションを共通化できます。
f:id:bluebirdofoz:20201016222407j:plain

アニメーションを行うメッシュとアーマチュアを含んだ以下の3Dモデルファイルを用意します。
[Optimize Game Objects]ではアーマチュアのルートボーンを指定しておきます。
f:id:bluebirdofoz:20201016222421j:plain

次に以下のようなアーマチュアとアニメーションを含んだ3Dモデルファイルを用意します。
[Optimize Game Objects]では先ほどと同様にアーマチュアのルートボーンを指定しておきます。
f:id:bluebirdofoz:20201016222430j:plain

アニメーションを共通化できているかは[Animation]タブで確認できます。
再生確認をしたい任意のアニメーションを選択し、アバターアイコンから[Other..]を選択します。
f:id:bluebirdofoz:20201016222440j:plain

プロジェクト中のゲームオブジェクト一覧が開くので、アニメーションを反映したいモデルを選択します。
f:id:bluebirdofoz:20201016222453j:plain

アニメーションのプレビューを再生し、表示されたモデルが意図通りに動けば、アニメーションが共通化できています。
f:id:bluebirdofoz:20201016222504j:plain

アニメーションの反映方法

AnimationController を新規作成し、取り込んだアニメーションを設定します。
f:id:bluebirdofoz:20201016222513j:plain

反映したいモデルの[Controller]に作成した AnimationController を設定します。
f:id:bluebirdofoz:20201016222523j:plain

シーンを再生し、反映したキャラクターモデルでアニメーションが再生されれば成功です。
f:id:bluebirdofoz:20201016222544j:plain

3Dモデル出力時の注意点

Blender でアニメーション用の3Dファイルを出力した時、上手く共通化できないケースがありました。
例えば、以下のようにファイル出力時に[アーマチュア]のみを設定した場合です。
f:id:bluebirdofoz:20201016222554j:plain

アーマチュアのみを出力した場合、アーマチュアの親オブジェクトが出力時に消えてしまい、構造が一致しなくなったためでした。
f:id:bluebirdofoz:20201016222604j:plain

この事象は例え出力時に[メッシュ]を指定していても、メッシュオブジェクトが存在しない場合は同様に発生します。
f:id:bluebirdofoz:20201016222623j:plain

以下の通り、アーマチュアの親オブジェクトが出力時に消えています。
f:id:bluebirdofoz:20201016222633j:plain

空のメッシュオブジェクトを作成し、[アーマチュア]と[メッシュ]を出力することで問題を解消できます。
f:id:bluebirdofoz:20201016222650j:plain

このモデルを取り込むと、以下の通り、アーマチュアの親オブジェクトが存在しており、階層が一致します。
f:id:bluebirdofoz:20201016222658j:plain