MRが楽しい

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

Blender2.8で利用可能なpythonスクリプトを作る その27(ブーリアンモディファイアの差分分割)

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

ブーリアンモディファイアを使って対象オブジェクトをX軸で分割してミラーを適用する

ブーリアンモディファイアを使って対象オブジェクトをX軸で分割します。
その後、ミラーモディファイアでX軸対称のオブジェクトにします。
差分を取るためのCubeオブジェクトは対象オブジェクトのバウンドボックスを元に生成します。
・get_object_center.py

# bpyインポート
import bpy
# メッシュ操作のため
import bmesh

# ブーリアンモディファイアを使って対象オブジェクトをX軸で分割する
# その後、ミラーモディファイアでX軸対称のオブジェクトとする
def apply_boolean_mirror(arg_objectname="Default") -> bool:
    """ブーリアンモディファイアを使って対象オブジェクトをX軸で分割する
    その後、ミラーモディファイアでX軸対称のオブジェクトとする

    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
    
    # ブーリアン用モディファイアの名前を指定する
    boolean_objname = "Difference"

    # 中央頂点でのブーリアン演算のエラーを防ぐため
    # 調整用のずらしと各判定しきい値を設定する
    diff_shift = 0.0001
    cleanup_threshold = diff_shift * 10
    delzero_threshold = cleanup_threshold * 2
    mirror_threshold = delzero_threshold * 2

    # 対象オブジェクトのトランスフォームを適用する
    trans_result = transform_apply_target(arg_objectname=arg_objectname)
    if trans_result != True:
        # 適用に失敗した場合は処理しない
        return False

    # ブーリアンモディファイアの差分検出用Cubeを作成する
    make_result = make_cube_difference(arg_objectname=arg_objectname, arg_makename=boolean_objname, arg_shift=diff_shift)
    if make_result != True:
        # Cube作成に失敗した場合は処理しない
        return False
    
    # ブーリアンモディファイアの差分適用を実行する
    apply_result = apply_boolean_difference(arg_objectname=arg_objectname, arg_diffname=boolean_objname)
    if apply_result != True:
        # ブーリアン作成に失敗した場合は処理しない
        return False

    # オブジェクトの孤立を削除(どの面にもつながっていない辺や頂点を削除する)を行う
    cleanup_result = cleanup_mesh_object(arg_objectname=arg_objectname, arg_threshold=cleanup_threshold)
    if cleanup_result != True:
        # 削除に失敗した場合は処理しない
        return False
    
    # X軸ゼロにある面を削除する
    delzero_result = delete_face_xzero(arg_objectname=arg_objectname, arg_threshold=delzero_threshold)
    if delzero_result != True:
        # 削除に失敗した場合は処理しない
        return False

    # X軸のミラーモディファイアを適用する
    mirror_result = apply_mirror_Xclipping(arg_objectname=arg_objectname, arg_threshold=mirror_threshold)
    if mirror_result != True:
        # ミラーに失敗した場合は処理しない
        return False

    return True

# 対象オブジェクトのトランスフォームを適用する
def transform_apply_target(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
    
    # 不要なオブジェクトを選択しないように
    # 全てのオブジェクトを走査する
    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.transform_apply(location=True, rotation=True, scale=True)

    return True

# ブーリアンモディファイアの差分検出用Cubeを作成する
def make_cube_difference(arg_objectname="Default", arg_makename="Difference", arg_shift=0.0) -> bool:
    """ブーリアンモディファイアの差分検出用Cubeを作成する

    Keyword Arguments:
        arg_objectname {str} -- 指定オブジェクト名 (default: {"Default"})
        arg_makename {str} -- 作成オブジェクト名 (default: {"Difference"})
        arg_shift {float} -- ずらし (default: {0.0})

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

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

    # 指定オブジェクトが存在するか確認する
    if selectob == None:
        # 指定オブジェクトが存在しない場合は処理しない
        return False
    
    # 指定オブジェクトの境界情報を取得する
    # オブジェクトのインタフェース
    # (https://docs.blender.org/api/current/bpy.types.Object.html)
    target_boundbox = selectob.bound_box

    # 変数の初期化
    vertcount = 0
    center_x = 0.0
    center_y = 0.0
    center_z = 0.0

    # 頂点座標の加算
    for vert_boundbox in target_boundbox:
        center_x += vert_boundbox[0]
        center_y += vert_boundbox[1]
        center_z += vert_boundbox[2]
        vertcount += 1

    # 中点の計算
    center_x = center_x / vertcount
    center_y = center_y / vertcount
    center_z = center_z / vertcount

    # 寸法の取得
    dimension_x = selectob.dimensions[0]
    dimension_y = selectob.dimensions[1]
    dimension_z = selectob.dimensions[2]

    # もし作成予定と同名のメッシュオブジェクトが既に存在していれば新規作成しない
    if arg_makename in bpy.data.objects:
        return False

    # Cubeオブジェクトを作成する
    bpy.ops.mesh.primitive_cube_add(
        size=2.0,
        location=(0.0, 0.0, 0.0),
        rotation=(0.0, 0.0, 0.0)
    )

    # 作成したオブジェクトの参照を取得する
    makeob = bpy.context.view_layer.objects.active
    
    # 作成オブジェクト名を変更する
    makeob.name = arg_makename
    
    # 作成オブジェクトの位置を中点位置に変更する
    makeob.location = (center_x, center_y, center_z)

    # 作成オブジェクトのスケールを設定する
    # (対象オブジェクトより少し大きめのサイズにする)
    makeob.scale = ((dimension_x + 1.0) / 2.0, (dimension_y + 1.0) / 2.0, (dimension_z + 1.0) / 2.0)

    # 作成オブジェクトをX軸のマイナス側に移動する
    makeob.location[0] = -1.0 * ((dimension_x + 1.0) / 2.0)

    # 中央頂点でのブーリアン演算のエラーを防ぐためのずらし量を設定する
    makeob.location[0] = makeob.location[0] + arg_shift

    return True

# 対象オブジェクトにブーリアンモディファイアの差分を適用する
# 差分に用いたオブジェクトは削除する
def apply_boolean_difference(arg_objectname="Default", arg_diffname="Difference") -> bool:
    """対象オブジェクトにブーリアンモディファイアの差分を適用する
    差分に用いたオブジェクトは削除する

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

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

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

    # 差分オブジェクトが存在するか確認する
    if diffob == None:
        # 差分オブジェクトが存在しない場合は処理しない
        return False
    
    # 指定オブジェクトを取得する
    # (get関数は対象が存在しない場合 None が返る)
    selectob = bpy.data.objects.get(arg_objectname)

    # 指定オブジェクトが存在するか確認する
    if selectob == None:
        # 指定オブジェクトが存在しない場合は処理しない
        return False
    
    # 指定オブジェクトをアクティブに変更する
    bpy.context.view_layer.objects.active = selectob
    
    # 「ブーリアン」モディファイアを追加する
    # ブーリアンモディファイアのインタフェース
    # (https://docs.blender.org/api/current/bpy.types.BooleanModifier.html)
    bpy.ops.object.modifier_add(type='BOOLEAN')

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

    # 演算方法に[差分]を指定する
    boolean_modifier.operation = 'DIFFERENCE'

    # 統合対象にオブジェクトを指定する
    boolean_modifier.object = diffob

    # 重複の敷居値を指定する
    boolean_modifier.double_threshold = 0.000001

    # 「ブーリアン」モディファイアを適用する
    bpy.ops.object.modifier_apply(apply_as='DATA',modifier=boolean_modifier.name)

    # 統合したオブジェクトを削除する
    bpy.data.objects.remove(diffob)

    return True

# オブジェクトに以下のクリーンアップを実行
# ・大きさ0を融解:面積が0の面を削除し、1つの頂点にまとめる
# ・孤立を削除:どの面にもつながっていない辺や頂点を削除する
# ・重複頂点を削除:重複している頂点を1つの頂点にまとめる
def cleanup_mesh_object(arg_objectname="Default", arg_threshold=0.0001) -> bool:
    """オブジェクトにクリーンアップを実行
    ・大きさ0を融解:面積が0の面を削除し、1つの頂点にまとめる
    ・孤立を削除:どの面にもつながっていない辺や頂点を削除する
    ・重複頂点を削除:重複している頂点を1つの頂点にまとめる
    
    Keyword Arguments:
        arg_objectname {str} -- 指定オブジェクト名 (default: {"Default"})
        arg_threshold {float} -- 結合しきい値 (default: {0.0001})

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

    # 他のオブジェクトに操作を適用しないよう全てのオブジェクトを走査する
    for ob in bpy.context.scene.objects:
        # 非選択状態に設定する
        ob.select_set(False)

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

    # 指定オブジェクトが存在するか確認する
    if targetob == None:
        # 指定オブジェクトが存在しない場合は処理しない
        return False

    # 変更オブジェクトをアクティブに変更する
    bpy.context.view_layer.objects.active = targetob
  
    # 編集モードに移行する
    bpy.ops.object.mode_set(mode='EDIT', toggle=False)

    # 頂点を全選択した状態とする
    bpy.ops.mesh.select_all(action='SELECT') 

    # 大きさ0を融解(結合距離 0.01)
    bpy.ops.mesh.dissolve_degenerate(threshold=0.01)

    # 変更を反映するため再び頂点を全選択
    bpy.ops.mesh.select_all(action='SELECT') 

    # 孤立を削除(頂点、辺のみ)
    bpy.ops.mesh.delete_loose(use_verts=True, use_edges=True, use_faces=False)

    # 孤立を削除で全選択が解除されるので再び頂点を全選択
    bpy.ops.mesh.select_all() 

    # 重複頂点を削除(結合距離、非選択部の結合無効)
    bpy.ops.mesh.remove_doubles(threshold=arg_threshold, use_unselected=False)

    # オブジェクトモードに移行する
    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

    return True

# X軸ゼロにある面を削除する
def delete_face_xzero(arg_objectname="Default", arg_threshold=0.0001) -> bool:
    """X軸ゼロにある面を削除する

    Keyword Arguments:
        arg_objectname {str} -- 対象オブジェクト名 (default: {"Default"})
        arg_threshold {float} -- 選択しきい値 (default: {0.0001})

    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

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

    # メッシュデータを取得する
    # メッシュアクセスのマニュアル
    # (https://docs.blender.org/api/current/bmesh.types.html?highlight=bmedge#bmesh.types.BMesh)
    meshdata = bmesh.from_edit_mesh(selectob.data)

    # 選択モードを面選択モードにする
    meshdata.select_mode = {'VERT'}

    # 一度選択を全てクリアする
    bpy.ops.mesh.select_all(action='DESELECT')

    # 頂点を走査する
    # 頂点アクセスのマニュアル
    # (https://docs.blender.org/api/current/bmesh.types.html?highlight=bmedge#bmesh.types.BMVert)
    for v in meshdata.verts:
        # X軸の値がしきい値以下の頂点を取得する
        if v.co.x < arg_threshold:
            # 頂点を選択状態にする
            v.select_set(True)
        else:
            # 頂点を非選択状態にする
            v.select_set(False)

    # 辺または面の選択状態を更新する
    # (頂点の選択による面選択を有効化するため)
    meshdata.select_flush_mode()

    # 断面の面を削除する
    bpy.ops.mesh.delete(type='FACE')
    
    # オブジェクトモードに移行する
    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

    return True

# X軸のミラーモディファイアを適用する
def apply_mirror_Xclipping(arg_objectname="Default", arg_threshold=0.0001) -> bool:
    """対象オブジェクトにブーリアンモディファイアの差分を適用する

    Keyword Arguments:
        arg_objectname {str} -- 対象オブジェクト名 (default: {"Default"})
        arg_threshold {float} -- 結合しきい値 (default: {0.0001})

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

    # 指定オブジェクトが存在するか確認する
    if selectob == None:
        # 指定オブジェクトが存在しない場合は処理しない
        return False
    
    # 指定オブジェクトをアクティブに変更する
    bpy.context.view_layer.objects.active = selectob

    # 「ミラー」モディファイアを追加する
    # ミラーモディファイアのインタフェース
    # (https://docs.blender.org/api/current/bpy.types.MirrorModifier.html)
    bpy.ops.object.modifier_add(type='MIRROR')
  
    # 作成モディファイアを取得する
    mirror_modifier = selectob.modifiers[-1]
  
    # X軸でミラー
    mirror_modifier.use_axis[0] = True
    mirror_modifier.use_axis[1] = False
    mirror_modifier.use_axis[2] = False
  
    # オブション設定(結合,クリッピング,頂点グループ)
    mirror_modifier.use_mirror_merge = True
    mirror_modifier.use_clip = True
    mirror_modifier.use_mirror_vertex_groups = True
  
    # 結合距離
    mirror_modifier.merge_threshold = arg_threshold

    # 「ミラー」モディファイアを適用する
    bpy.ops.object.modifier_apply(apply_as='DATA',modifier=mirror_modifier.name)

    return True

# オブジェクトのバウンドボックスから中点を計算する
def get_object_center(arg_objectname="Default") -> list:
    """オブジェクトのバウンドボックスから中点を計算する

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

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

    # 指定オブジェクトが存在するか確認する
    if selectob == None:
        # 指定オブジェクトが存在しない場合は処理しない
        return [0.0, 0.0, 0.0]
    
    # 指定オブジェクトの境界情報を取得する
    # オブジェクトのインタフェース
    # (https://docs.blender.org/api/current/bpy.types.Object.html)
    target_boundbox = selectob.bound_box

    # 変数の初期化
    vertcount = 0
    center_x = 0.0
    center_y = 0.0
    center_z = 0.0

    # 頂点座標の加算
    for vert_boundbox in target_boundbox:
        center_x += vert_boundbox[0]
        center_y += vert_boundbox[1]
        center_z += vert_boundbox[2]
        vertcount += 1

    # 中点の計算
    center_x = center_x / vertcount
    center_y = center_y / vertcount
    center_z = center_z / vertcount

    return [center_x, center_y, center_z]

# 関数の実行例
apply_boolean_mirror(
    arg_objectname="Combo"
)

f:id:bluebirdofoz:20200518155531j:plain

以前作成した「X軸がマイナス値の頂点を削除してミラーモディファイアを設定する」スクリプトの違いは、X軸の中央にブーリアンモディファイアが頂点を作成する点です。
X軸の中央に頂点がないオブジェクトでも左右対称なオブジェクトに変換できます。
bluebirdofoz.hatenablog.com