MRが楽しい

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

Blender3.0で利用可能なpythonスクリプトを作る その102(編集モード中の全オブジェクトに対して頂点グループの割合を考慮してオブジェクトをリダクションする)

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

編集モード中の全オブジェクトに対して頂点グループの割合を考慮してオブジェクトをリダクションする

編集モード中で選択中の頂点を持つ全オブジェクトに対してリダクションを行います。
このとき、オブジェクト毎に総頂点数と選択頂点数を取得して全体で一定の削減比率になるよう調整を行うようにします。

本記事では以下の2つの記事の実装を組み合わせて実現します。
bluebirdofoz.hatenablog.com
bluebirdofoz.hatenablog.com

サンプルスクリプト

上記の記事のスクリプトを組み合わせて以下のサンプルスクリプトを作成しました。
編集モード中にスクリプトを実行すると、各オブジェクトの選択中の頂点に対して decimate_ratio 変数で指定した削減比率でリダクションが行われます。
・Script_decimate_byselectvert_forallobject.py

# bpyインポート
import bpy

# 編集モード中の全オブジェクトで選択中の頂点に対して割合を考慮してオブジェクトをリダクションする
def decimate_byselectvert_forallobject(arg_decimateratio:float) -> bool:
    """編集モード中の全オブジェクトで選択中の頂点に対して割合を考慮してオブジェクトをリダクションする
    頂点を選択していないオブジェクトまたは編集モードでないオブジェクトに対して処理は行わない

    Keyword Arguments:
        arg_decimateratio {float} -- 削減比率

    Returns:
        bool -- 実行成否
    """
    
    # 現在のモードが編集モードかチェックする
    # (誤操作を防ぐため)
    if is_editmode() == False:
        return False

    # 編集対象のオブジェクトリストを作成する
    targetobject_list = []

    # シーン内の全オブジェクトを走査する
    for check_obj in bpy.context.scene.objects:
        # 編集モード中のオブジェクトを対象とする
        if is_editmode_object(check_obj) == True:
            # 編集モード中であれば頂点グループ作成の対象とする
            targetobject_list.append(check_obj)

    # 対象のオブジェクトがあったか
    if len(targetobject_list) <= 0:
        # 対象のオブジェクトが無かった場合、頂点グループは作成しない
        return True

    # アクティブオブジェクトの参照を退避する
    active_object = bpy.context.view_layer.objects.active

    # オブジェクトモードに移行する
    set_objectmode()

    # 対象のオブジェクトを全て処理する
    for target_obj in targetobject_list:
        # 選択中の頂点があれば頂点グループを作成する
        made_vertexgroup = make_vertexgroup_byselectvert(target_obj)
        # 頂点グループが作成されていれば処理を行う
        if made_vertexgroup != None:
            # 頂点グループの頂点数を考慮した削減比率を再計算する
            vertexgroup_decimeate_ratio = calculate_decimatefactor_onvertexgourp(
                target_obj, made_vertexgroup, arg_decimateratio
            )
            # リダクションを行う
            apply_decimate_vertexgroup(target_obj, made_vertexgroup, vertexgroup_decimeate_ratio)
            # 頂点グループを削除する
            remove_vertexgroup(target_obj, made_vertexgroup)

    # 対象のオブジェクトを全て処理する
    for target_obj in targetobject_list:
        # 処理対象のオブジェクトが編集モードになるよう選択状態とする
        select_object(target_obj)

    # 編集モードに戻す
    set_editmode()

    # アクティブオブジェクトを戻す
    bpy.context.view_layer.objects.active = active_object

    return True

# 選択中の頂点から新規頂点グループを作成して頂点グループを取得する
def make_vertexgroup_byselectvert(arg_object:bpy.types.Object) -> bpy.types.VertexGroup:
    """選択中の頂点から新規頂点グループを作成して頂点グループを取得する

    Keyword Arguments:
        arg_object {bpy.types.Object} -- 対象オブジェクト

    Returns:
        bpy.types.VertexGroup -- 新規頂点グループ
    """

    # 現在のモードがオブジェクトモードかチェックする
    # (VertexGroupのAddは編集モードで実行不可のため)
    if is_objectmode() == False :
        return None

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

    # インデックスリストを作成する
    index_list = []

    # 全ての頂点を走査する
    # 頂点操作のマニュアル
    # (https://docs.blender.org/api/current/bpy.types.MeshVertex.html)
    for vert in arg_object.data.vertices:
        # 選択状態か否か
        if vert.select:
            # 選択中の頂点のインデックスをリストに追加する
            index_list.append(vert.index)

    # 選択中の頂点があったか
    if len(index_list) <= 0:
        # 選択中の頂点が無かった場合、頂点グループは作成しない
        return None

    # オブジェクトに新規頂点グループを追加する
    # VertexGroupsアクセスのマニュアル
    # (https://docs.blender.org/api/current/bpy.types.VertexGroups.html)
    vertexgroup = arg_object.vertex_groups.new()

    # 選択中の頂点のインデックスを頂点グループに設定する
    # VertexGroupアクセスのマニュアル
    # (https://docs.blender.org/api/current/bpy.types.VertexGroup.html#bpy.types.VertexGroup)
    vertexgroup.add(index_list, 1.0, 'REPLACE')

    # 作成した頂点グループを返却する
    return vertexgroup

# オブジェクトから指定の頂点グループを削除する
def remove_vertexgroup(arg_object:bpy.types.Object, arg_vertexgroup:bpy.types.VertexGroup) -> bool:
    """オブジェクトから指定の頂点グループを削除する

    Keyword Arguments:
        arg_object {bpy.types.Object} -- 対象オブジェクト
        arg_vertexgroup {bpy.types.VertexGroup} -- 指定の頂点グループ

    Returns:
        bool -- 実行成否
    """
    
    # 指定オブジェクトに指定の頂点グループが含まれるかチェックする
    check_vertexgroup = arg_object.vertex_groups.get(arg_vertexgroup.name)
    if check_vertexgroup == None:
        return False
    
    # 頂点グループを削除する
    arg_object.vertex_groups.remove(check_vertexgroup)

    # 実行成否を返却する
    return True

# 指定のオブジェクトを選択状態にする
def select_object(arg_object:bpy.types.Object) -> bool:
    """指定のオブジェクトを選択状態にする

    Keyword Arguments:
        arg_object {bpy.types.Object} -- 対象オブジェクト

    Returns:
        bool -- 実行成否
    """
    
    # 指定のオブジェクトを選択状態にする
    arg_object.select_set(True)

    # 実行成否を返却する
    return True

# 頂点グループを指定してポリゴン数削減モディファイアを適用する
def apply_decimate_vertexgroup(
    arg_object:bpy.types.Object,
    arg_vertexgroup:bpy.types.VertexGroup,
    arg_decimateratio:float) -> bool:
    """頂点グループを指定してポリゴン数削減モディファイアを適用する

    Keyword Arguments:
        arg_object (bpy.types.Object) -- 指定オブジェクト
        arg_vertexgroup {bpy.types.VertexGroup} -- 指定の頂点グループ
        arg_decimateratio {float} -- 削減比率

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

    # 指定オブジェクトに指定の頂点グループが含まれるかチェックする
    check_vertexgroup = arg_object.vertex_groups.get(arg_vertexgroup.name)
    if check_vertexgroup == None:
        return False
    
    # 指定オブジェクトがメッシュか確認する
    if arg_object.type != 'MESH':
        # メッシュが存在しない場合は処理しない
        return False

    # 変更オブジェクトをアクティブに変更する
    bpy.context.view_layer.objects.active = arg_object
    
    # 「ポリゴン数削減」モディファイアを追加する
    # モディファイア追加の種類とマニュアル
    # (https://docs.blender.org/api/current/bpy.ops.object.html#bpy.ops.object.gpencil_modifier_add)
    # ポリゴン数削減モディファイアのインタフェース
    # (https://docs.blender.org/api/current/bpy.types.DecimateModifier.html)
    bpy.ops.object.modifier_add(type='DECIMATE')

    # 追加されたモディファイアを取得する
    decimate_modifier = arg_object.modifiers[-1]

    # 削減のタイプを COLLAPSE に指定する
    decimate_modifier.decimate_type = 'COLLAPSE'

    # 削減の比率を設定する
    decimate_modifier.ratio = arg_decimateratio

    # 頂点グループを指定する
    decimate_modifier.vertex_group = check_vertexgroup.name

    # 「ポリゴン数削減」モディファイアを適用する
    bpy.ops.object.modifier_apply(modifier=decimate_modifier.name)
    
    # 実行成否を返却する
    return True

# 頂点グループの頂点数に対応する削減比率を再計算する
def calculate_decimatefactor_onvertexgourp(
    arg_object:bpy.types.Object,
    arg_vertexgroup:bpy.types.VertexGroup,
    arg_decimateratio:float) -> float:
    """頂点グループの頂点数に対応する削減比率を再計算する

    Keyword Arguments:
        arg_object {bpy.types.Object} -- 指定オブジェクト
        arg_vertexgroup {bpy.types.VertexGroup} -- 指定の頂点グループ
        arg_decimateratio {float} -- 削減比率

    Returns:
        float -- 再計算後の削減比率
    """

    # 指定オブジェクトに指定の頂点グループが含まれるかチェックする
    check_vertexgroup = arg_object.vertex_groups.get(arg_vertexgroup.name)
    if check_vertexgroup == None:
        return 1.0
    
    # 指定オブジェクトがメッシュか確認する
    if arg_object.type != 'MESH':
        # メッシュが存在しない場合は処理しない
        return arg_decimateratio

    # 頂点グループの参照から頂点グループに所属している頂点数を算出つする
    vertexgroup_count = count_weight_vertexgroup(arg_object, check_vertexgroup)
    # 全体の頂点数を確認する
    vertex_count = count_data_vertex(arg_object)
    # 頂点グループの割合を算出する
    vertexgroup_ratio = vertexgroup_count / vertex_count
    # 頂点グループの割合を考慮して削減比率を再計算する
    vertexgroup_decimateratio = 1.0 - ((1.0 - arg_decimateratio) * vertexgroup_ratio)

    return vertexgroup_decimateratio

# 指定のオブジェクトの頂点の数を取得する
def count_data_vertex(arg_object:bpy.types.Object) -> int:
    """指定のオブジェクトの頂点の数を取得する

    Keyword Arguments:
        arg_object {bpy.types.Object} -- 指定オブジェクト

    Returns:
        int -- 頂点数
    """

    # データに保持されている頂点の数を返却する
    return len(arg_object.data.vertices)

# 指定の頂点グループに所属している頂点の数を取得する(頂点グループの参照から算出)
def count_weight_vertexgroup(
    arg_object:bpy.types.Object,
     arg_vertexgroup:bpy.types.VertexGroup) -> int:
    """指定の頂点グループに所属している頂点の数を取得する(頂点グループの参照から算出)

    Keyword Arguments:
        arg_object {bpy.types.Object} -- 指定オブジェクト
        arg_vertexgroup {bpy.types.VertexGroup} -- 指定の頂点グループ

    Returns:
        int -- 頂点グループに含まれる頂点数
    """

    # 指定オブジェクトに指定の頂点グループが含まれるかチェックする
    check_vertexgroup = arg_object.vertex_groups.get(arg_vertexgroup.name)
    if check_vertexgroup == None:
        return 0
    
    # カウント用変数
    result_count = 0

    # 指定オブジェクトの全頂点を走査する
    for vert in arg_object.data.vertices:
        try:
            # ウェイト情報を参照できるならカウントアップする
            check_vertexgroup.weight(vert.index)
            result_count = result_count + 1
        except RuntimeError:
            # 頂点グループに所属していない場合はエラーが発生するのでパスする
            pass

    # カウント結果を返却する
    return result_count

# 指定のオブジェクトが編集モードかチェックする
def is_editmode_object(arg_object:bpy.types.Object) -> bool:
    """指定のオブジェクトが編集モードかチェックする
    
    Keyword Arguments:
        arg_object {bpy.types.Object} -- 対象オブジェクト

    Returns:
        bool -- 編集モードか否か
    """

    # オブジェクト毎のモードをチェックする
    # (https://docs.blender.org/api/current/bpy.types.Object.html#bpy.types.Object.mode)
    return ('EDIT' == arg_object.mode)

# オブジェクトモードに移行する
def set_objectmode():
    """オブジェクトモードに移行する

    Keyword Arguments:

    Returns:
    """

    # オブジェクトモードに移行する
    # モード切替のマニュアル
    # (https://docs.blender.org/api/current/bpy.ops.object.html#bpy.ops.object.mode_set)
    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
    return

# 現在のモードがオブジェクトモードかチェックする
def is_objectmode() -> bool:
    """現在のモードがオブジェクトモードかチェックする

    Keyword Arguments:

    Returns:
        bool -- オブジェクトモードか否か
    """

    # 現在のモードをチェックする
    # (https://docs.blender.org/api/current/bpy.context.html#bpy.context.mode)
    return ('OBJECT' == bpy.context.mode)

# 編集モードに移行する
def set_editmode():
    """編集モードに移行する

    Keyword Arguments:

    Returns:
    """

    # 編集モードに移行する
    # モード切替のマニュアル
    # (https://docs.blender.org/api/current/bpy.ops.object.html#bpy.ops.object.mode_set)
    bpy.ops.object.mode_set(mode='EDIT', toggle=False)
    return

# 現在のモードが編集モードかチェックする
def is_editmode() -> bool:
    """現在のモードが編集モードかチェックする

    Keyword Arguments:

    Returns:
        bool -- 編集モードか否か
    """

    # 現在のモードをチェックする
    # (https://docs.blender.org/api/current/bpy.context.html#bpy.context.mode)
    return ('EDIT_MESH' == bpy.context.mode)

# 関数の実行例
# 頂点の削減比率を指定する
decimate_ratio = 0.5
# 編集モード中の全てのオブジェクトに対して選択中の頂点から新規頂点グループを作成する
decimate_byselectvert_forallobject(decimate_ratio)

・実行例1
f:id:bluebirdofoz:20220213030350j:plain
f:id:bluebirdofoz:20220213030400j:plain

・実行例2
f:id:bluebirdofoz:20220213030440j:plain
f:id:bluebirdofoz:20220213030449j:plain

・実行例3
f:id:bluebirdofoz:20220213030457j:plain
f:id:bluebirdofoz:20220213030506j:plain

・実行例4
f:id:bluebirdofoz:20220213030516j:plain
f:id:bluebirdofoz:20220213030524j:plain