本日は Blender2.79 の技術調査枠です。
Blender2.79 の Python スクリプトで3Dモデルのオブジェクトとマテリアルの結合を自動化します。
今回はマテリアルの結合にテクスチャベイクと頂点カラーベイクを利用してみます。
前回記事の続きです。
bluebirdofoz.hatenablog.com
サンプルモデル
前回と同じく3つのオブジェクトとそれぞれに同様の3つのマテリアル設定を持つモデルをサンプルとして利用します。
テクスチャベイクを利用した結合
オブジェクトの結合と、テクスチャベイクを利用したマテリアルの結合を行うスクリプトを用意しました。
結合したオブジェクトのUV展開を実施し、マテリアルのベースカラーを全てテクスチャに落とし込みます。
スクリプト
・ModelTextureMerge.py
# osインポート(ファイルパス処理のため) import os # sysインポート(引数取得のため) import sys # bpyインポート(3D処理のため) import bpy # mathインポート(角度計算のため) import math # CUI実行時のメイン関数 def main_CUI(arg_filepathlist = ["C:\\WORK\\Models\\FBXImport.fbx"]): # 必要なアドオンを有効化する(用gltf-Exporter) blender_addon_enable() for filepath in arg_filepathlist: # 処理データの有無を確認 if os.path.exists(filepath) == False: # 処理データがない場合、処理を行わない continue # 処理データのディレクトリパスを取得 target_dirpath = os.path.dirname(filepath) # 処理データのファイル名を取得 target_filename = os.path.splitext(os.path.basename(filepath))[0] # 処理データの拡張子を取得 target_fileextension = os.path.splitext(filepath)[1] print("dir:" + target_dirpath) print("name:" + target_filename) print("ext:" + target_fileextension) # シーンのクリア delete_all_objects() # ファイルの読み込み import_result = import_file( arg_filedir = target_dirpath, arg_filename = target_filename, arg_fileextension = target_fileextension ) # 読み込みの成否を確認 if import_result == False: # 読み込みに失敗した場合、処理を行わない continue # オブジェクト群に対してメッシュ結合/マテリアル結合/位置適用を実行する joinmesh_joinmat_transapply(arg_texturedir = target_dirpath) # GLB形式で3Dデータを書き出し export_result = export_file( arg_filedir = target_dirpath, arg_filename = target_filename, arg_fileextension = ".glb" ) # .blendファイルを名前を付けて保存 blendfilepath = target_dirpath + "\\" + target_filename + "_export.blend" bpy.ops.wm.save_as_mainfile(filepath = blendfilepath) print("Export blend:" + blendfilepath) # 処理結果を表示 print("Export Result:" + str(export_result)) return # GUI実行時のメイン関数 def main_GUI(): # テクスチャファイルの出力ディレクトリを取得する # (.blendファイルのディレクトリに出力する) texture_dir = get_blend_dirpath() # テクスチャファイルの出力ディレクトリが取得できなければ処理を行わない # (.blendファイル未保存時に本分岐に入る) if texture_dir == "": print("save .blend Project, plase") return # オブジェクト群に対してメッシュ結合/マテリアル結合/位置適用を実行する joinmesh_joinmat_transapply(arg_texturedir = texture_dir) return # テスト用関数 def test(): return # 必要なアドオンを有効化する # ※ scripts/addonsへのアドオンコピーが必要 # 引数 # 戻り値 def blender_addon_enable(): # gltf-Exporter アドオンの有効化 bpy.ops.wm.addon_enable(module = "io_scene_gltf2") return # オブジェクト群に対してメッシュ結合/マテリアル結合/位置適用を実行する # 引数 arg_texturedir:テクスチャ出力ディレクトリ # 戻り値 def joinmesh_joinmat_transapply(arg_texturedir = "C:\\WORK\\Texture"): # 結合後のオブジェクト名 joinob_name = "JoinObject" # テクスチャファイルの出力ディレクトリを取得する texture_dir = arg_texturedir # メッシュオブジェクトを結合する join_objects_mesh(arg_objectname = joinob_name) # マテリアルを結合する joint_material_texture(arg_objectname = joinob_name, arg_texturedir = texture_dir) # オブジェクトの位置を適用する apply_object_transform(arg_objectname = joinob_name) return # 現在のblendファイルのディレクトリパスを取得する # 戻り値 blendディレクトリパス(.blend未保存時は空文字を返却する) def get_blend_dirpath(): # blendファイルのパスを取得 filepath = bpy.data.filepath # blendファイルが未保存の場合は空文字が返る if filepath == "": # 戻り値にも空文字を返す return "" # blendフォイルのディレクトリパスを取得 dirpath = os.path.dirname(filepath) return dirpath # オブジェクト群に対して位置調整を実行する # 引数 arg_filepath:調整移動量 # 戻り値 def transmove(arg_vector = (0.0,0.0,0.0)): # 位置調整 for ob in bpy.context.scene.objects: if ob.type == 'MESH': move_vector = arg_vector add_object_transform( arg_objectname = ob.name, arg_location = move_vector ) return # 全オブジェクトの削除 # 引数 # 戻り値 def delete_all_objects(): # 全シーンオブジェクトを削除する for item in bpy.context.scene.objects: bpy.context.scene.objects.unlink(item) # 全データオブジェクトを削除する for item in bpy.data.objects: bpy.data.objects.remove(item) # 全メッシュデータを削除する for item in bpy.data.meshes: bpy.data.meshes.remove(item) # 全マテリアルデータを削除する for item in bpy.data.materials: bpy.data.materials.remove(item) return # 3Dデータをインポートする # 引数 arg_filedir:ファイルディレクトリ # arg_filename:ファイル名 # arg_fileextension:ファイル拡張子 # 戻り値 読み込み成否 def import_file( arg_filedir = "C:\\WORK\\Models", arg_filename = "ImportModel", arg_fileextension = ".fbx"): # 読み込み成否フラグ import_result = False # 拡張子を判定する if arg_fileextension == ".fbx": # FBXのインポートを実行する fbx_filepath = arg_filedir + "\\" + arg_filename + arg_fileextension print("Import FBX:" + fbx_filepath) import_file_fbx(fbx_filepath) import_result = True elif arg_fileextension == ".ifc": # IFCのインポートを実行する ifc_filepath = arg_filedir + "\\" + arg_filename+arg_fileextension print("Import IFC:" + ifc_filepath) import_file_ifc(ifc_filepath) import_result = True else: unknown_filepath = arg_filedir + "\\" + arg_filename + arg_fileextension print("Unknown file format:" + unknown_filepath) return import_result # IFCモデルをインポートする # 要IfcOpenShell(http://ifcopenshell.org/ifcblender.html) # 引数 arg_filepath:ファイルパス # 戻り値 def import_file_ifc(arg_filepath = "D:\\BlenderDir\\Script\\Import.ifc"): # ifc-Blender アドオンの有効化 bpy.ops.wm.addon_enable(module = "io_import_scene_ifc") # IFCインポート # bpy.ops.import_scene.ifc() bpy.ops.import_scene.ifc( filepath=arg_filepath, use_names=True, process_relations=False, blender_booleans=False ) return # FBXモデルをインポートする # 引数 arg_filepath:ファイルパス # 戻り値 def import_file_fbx(arg_filepath = "D:\\BlenderDir\\Script\\Import.fbx"): # FBXインポート # bpy.ops.import_scene.fbx() bpy.ops.import_scene.fbx( filepath = arg_filepath, use_manual_orientation = False, global_scale = 1.0, bake_space_transform = False, use_custom_normals = True, use_image_search = True, use_alpha_decals = False, decal_offset = 0.0, use_anim = True, anim_offset = 1.0, use_custom_props = True, use_custom_props_enum_as_string = True, ignore_leaf_bones = False, force_connect_children = False, automatic_bone_orientation = False, primary_bone_axis = 'Y', secondary_bone_axis = 'X', use_prepost_rot = True ) return # 3Dデータをエクスポートする # 引数 arg_filedir:ファイルディレクトリ # arg_filename:ファイル名 # arg_fileextension:ファイル拡張子 # 戻り値 書き出し成否 def export_file( arg_filedir = "C:\\WORK\\Models", arg_filename = "ExportModel", arg_fileextension = ".fbx"): # 書き出し成否フラグ export_result = False # 拡張子を判定する if arg_fileextension == ".fbx": fbx_filepath = arg_filedir + "\\" + arg_filename + arg_fileextension print("Export FBX:" + fbx_filepath) export_file_fbx(fbx_filepath) export_result = True elif arg_fileextension == ".glb": glb_filepath = arg_filedir + "\\" + arg_filename + arg_fileextension print("Export GLB:" + glb_filepath) export_file_glb(glb_filepath) export_result = True else: unknown_filepath = arg_filedir + "\\" + arg_filename + arg_fileextension print("Unknown file format:" + unknown_filepath) return export_result # FBXモデルをエクスポートする(アーマチュアとメッシュのみ出力) # 引数 arg_filepath:ファイルパス # 戻り値 def export_file_fbx(arg_filepath = "D:\\BlenderDir\\Script\\Export.fbx"): # FBXエクスポート # bpy.ops.export_scene.fbx() # エクスポート設定(デフォルト値) bpy.ops.export_scene.fbx( filepath = arg_filepath, version = 'BIN7400', use_selection = False, global_scale = 1.0, apply_unit_scale = True, apply_scale_options = 'FBX_SCALE_NONE', bake_space_transform = False, object_types = {'ARMATURE', 'MESH'}, use_mesh_modifiers = True, use_mesh_modifiers_render = True, mesh_smooth_type = 'OFF', use_mesh_edges = False, use_tspace = False, use_custom_props = False, add_leaf_bones = True, primary_bone_axis = 'Y', secondary_bone_axis = 'X', use_armature_deform_only = False, armature_nodetype = 'NULL', bake_anim = True, bake_anim_use_all_bones = True, bake_anim_use_nla_strips = True, bake_anim_use_all_actions = True, bake_anim_force_startend_keying = True, bake_anim_step = 1.0, bake_anim_simplify_factor = 1.0, path_mode = 'AUTO', embed_textures = False, batch_mode = 'OFF', use_batch_own_dir = True, use_metadata = True ) # 出力対象の種別 # 'EMPTY':エンプティ # 'CAMERA':カメラ # 'LAMP':ランプ # 'ARMATURE':アーマチュア # 'MESH':メッシュ # 'OTHER':その他 # パスモードの種類 # 'AUTO':自動 # 'ABSOLUTE':絶対 # 'RELATIVE':相対的 # 'MATCH':マッチ # 'STRIP':ストリップパス # 'COPY':コピー return # GLBモデルをエクスポートする # 要GLTFImporter(https://github.com/ksons/gltf-blender-importer) # 引数 arg_filepath:ファイルパス # 戻り値 def export_file_glb(arg_filepath = "D:\\BlenderDir\\Script\\Export.glb"): # GLBエクスポート # bpy.ops.export_scene.glb() # エクスポート設定(デフォルト値) bpy.ops.export_scene.glb( filepath = arg_filepath, export_copyright = '', export_embed_buffers = False, export_embed_images = False, export_strip = False, export_indices = 'UNSIGNED_INT', export_force_indices = False, export_texcoords = True, export_normals = True, export_tangents = True, export_materials = True, export_colors = True, export_cameras = False, export_camera_infinite = False, export_selected = False, export_layers = True, export_extras = False, export_yup = True, export_apply = False, export_animations = True, export_frame_range = True, export_frame_step = 1, export_move_keyframes = True, export_force_sampling = False, export_current_frame = True, export_skins = True, export_bake_skins = False, export_morph = True, export_morph_normal = True, export_morph_tangent = True, export_lights = False, export_displacement = False, will_save_settings = False ) return # 指定オブジェクトのトランスフォームを変更する # 引数 arg_objectname:指定オブジェクト名 # arg_location:指定位置値(加算) # arg_rotation:指定回転値(加算) # arg_scale:指定拡大縮小値(乗算) # 戻り値 def add_object_transform( arg_objectname = "Default", arg_location = (0,0,0), arg_rotation = (0,0,0), arg_scale = (1,1,1)): # 指定オブジェクトを取得する selectob = bpy.context.scene.objects[arg_objectname] # 位置を変更する selectob.location.x += arg_location[0] selectob.location.y += arg_location[1] selectob.location.z += arg_location[2] # 回転を変更する selectob.rotation_euler.x += 2 * math.pi / 360 * arg_rotation[0] selectob.rotation_euler.y += 2 * math.pi / 360 * arg_rotation[1] selectob.rotation_euler.z += 2 * math.pi / 360 * arg_rotation[2] # 拡大縮小を変更する selectob.scale.x *= arg_scale[0] selectob.scale.y *= arg_scale[1] selectob.scale.z *= arg_scale[2] return # メッシュオブジェクトの結合 # 引数 arg_objectname:結合オブジェクト名 # 戻り値 def join_objects_mesh(arg_objectname = ""): # シーン中の全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # オブジェクトがメッシュであるか確認する if ob.type == 'MESH': # メッシュであれば選択状態にする ob.select = True # 対象アクティブオブジェクトに切り替える # メッシュはアクティブオブジェクトに結合される bpy.context.scene.objects.active = ob else: # メッシュでなければ選択状態にしない ob.select = False # オブジェクトの結合を実行する bpy.ops.object.join() # 結合オブジェクト名が設定されているか if len(arg_objectname): # オブジェクト名が設定されていれば名前を変更する bpy.context.scene.objects.active.name = arg_objectname return # 指定オブジェクトのトランスフォームを適用する # 引数 arg_objectname:指定オブジェクト名 # arg_location:位置適用 # arg_rotation:回転適用 # arg_scale:拡大縮小適用 # 戻り値 def apply_object_transform( arg_objectname = "Default", arg_location = True, arg_rotation = True, arg_scale = True): # 他のオブジェクトに操作を適用しないよう全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 非選択状態にする ob.select = False # 指定オブジェクトを取得する selectob = bpy.context.scene.objects[arg_objectname] # 指定オブジェクトを選択状態にする selectob.select = True # トランスフォームを適用する bpy.ops.object.transform_apply( location = arg_location, rotation = arg_rotation, scale = arg_scale) # 処理を完了したら非選択状態にする selectob.select = False return # 指定オブジェクトのトランスフォームをクリアする # 引数 arg_objectname:指定オブジェクト名 # arg_location:位置クリア # arg_rotation:回転クリア # arg_scale:拡大縮小クリア # 戻り値 def clear_object_transform( arg_objectname = "Default", arg_location = True, arg_rotation = True, arg_scale = True): # 他のオブジェクトに操作を適用しないよう全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 非選択状態にする ob.select = False # 指定オブジェクトを取得する selectob = bpy.context.scene.objects[arg_objectname] # 指定オブジェクトを選択状態にする selectob.select = True # 位置をクリアする if arg_location: bpy.ops.object.location_clear(clear_delta = False) # 回転をクリアする if arg_rotation: bpy.ops.object.rotation_clear(clear_delta = False) # 拡大縮小をクリアする if arg_scale: bpy.ops.object.scale_clear(clear_delta = False) # 処理を完了したら非選択状態にする selectob.select = False return # 指定したオブジェクトのマテリアルを一つのテクスチャマテリアルで結合する # 引数 arg_objectname:指定オブジェクト名 # arg_texturedir:テクスチャ出力ディレクトリ # 戻り値 def joint_material_texture( arg_objectname = "Default", arg_texturedir = "C:\\WORK\\Texture"): # 指定オブジェクト名を設定する joinname = arg_objectname # 複製オブジェクト名を設定する duplicateame = "OptimumModel" # 結合オブジェクトを複製する duplicate_target_object(arg_objectname = joinname, arg_dupname = duplicateame) # 複製オブジェクトのマテリアルを全削除する remove_materialslot_object(arg_objectname = duplicateame) # 複製オブジェクトのUVマップを全削除する delete_UVmap_mesh(arg_objectname = duplicateame) # 複製オブジェクトのUVマップをスマートUV投影で作成する uv_smart_project(arg_objectname = duplicateame) # 画像のディレクトリパスを取得する imagedirpath = arg_texturedir # 画像のファイル名を設定する imagename = "BakeTexture" # ベイク元オブジェクトからベイク先オブジェクトへベイクを実行する bake_texture_select2active( arg_sourcename = joinname, arg_destinationname = duplicateame, arg_savefilepath = imagedirpath, arg_savefilename = imagename) # 指定オブジェクトにテクスチャを参照するマテリアルを適用する new_material_textureset( arg_objectname = duplicateame, arg_applyfilepath = imagedirpath, arg_applyfilename = imagename) # 指定オブジェクトを削除する unlink_object(arg_objectname = joinname) # 複製オブジェクトの名前を元オブジェクトの名前に変更する rename_target_object(arg_objectname = duplicateame, arg_rename = joinname) return # オブジェクトの名前を変更する # 引数 arg_objectname:指定オブジェクト名 # arg_rename:変更オブジェクト名 # 戻り値 def rename_target_object( arg_objectname = "Default", arg_rename = ""): # 変更対象オブジェクトを取得する renameob = bpy.data.objects[arg_objectname] # オブジェクトの名前を変更する renameob.name = arg_rename return # オブジェクトを複製する # 引数 arg_objectname:指定オブジェクト名 # arg_dupname:変更オブジェクト名 # 戻り値 def duplicate_target_object( arg_objectname = "Default", arg_dupname = ""): # 他のオブジェクトに操作を適用しないよう全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 非選択状態に設定する ob.select = False # 指定オブジェクトを取得する selectob = bpy.context.scene.objects[arg_objectname] # 指定オブジェクトを選択状態にする selectob.select = True # オブジェクトを複製する bpy.ops.object.duplicate_move( OBJECT_OT_duplicate = None, TRANSFORM_OT_translate = None) # 指定オブジェクトの選択状態を解除する selectob.select = False # 複製オブジェクト名が指定されている場合は名前変更 if len(arg_dupname) > 0: # 複製オブジェクトの名前を取得する duplicated_objectname = arg_objectname + ".001" # 複製オブジェクトを取得する duplicatedob = bpy.data.objects[duplicated_objectname] # 複製オブジェクトの名前を変更する duplicatedob.name = arg_dupname return # 指定オブジェクトのマテリアルスロットを全て削除する # ※ スロットの削除のため、マテリアルのデータは削除しない # 引数 arg_objectname:指定オブジェクト名 # 戻り値 def remove_materialslot_object(arg_objectname = "Default"): # 対象オブジェクトを取得する selectob = bpy.context.scene.objects[arg_objectname] # マテリアルスロットが全て削除されるまでループ for loop_index in range(len(selectob.material_slots)): # 先頭のマテリアルスロットを選択状態にする selectob.active_material_index = 0 # マテリアルスロットを削除する bpy.ops.object.material_slot_remove() return # 指定オブジェクトのUVマップを全削除する # ※ メッシュデータのUVマップを削除するため、 # 同じメッシュデータを参照するオブジェクトは影響を受ける # 引数 arg_objectname:指定オブジェクト名 # 戻り値 def delete_UVmap_mesh(arg_objectname = "Default"): # 対象オブジェクトを取得する selectob = bpy.context.scene.objects[arg_objectname] # オブジェクトがメッシュであるか確認する if selectob.type == 'MESH': # データ名を取得する dataname = selectob.data.name # オブジェクトが保持するメッシュを取得する targetmesh = bpy.data.meshes[dataname] # UVマップが全て削除されるまでループ for loop_index in range(len(targetmesh.uv_textures)): # 先頭のUVマップを選択状態にする targetmesh.uv_textures.active_index = 0 # UVマップを削除する bpy.ops.mesh.uv_texture_remove() return # 指定オブジェクトのUVマップをスマートUV投影で作成する # 引数 arg_objectname:指定オブジェクト名 # 戻り値 def uv_smart_project(arg_objectname = "Default"): # 指定オブジェクトを取得する selectob = bpy.context.scene.objects[arg_objectname] # 変更オブジェクトをアクティブに変更する bpy.context.scene.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 # ベイク元オブジェクトからベイク先オブジェクトへベイクを実行する # 引数 arg_sourcename:ベイク元オブジェクト名 # arg_destinationname:ベイク先オブジェクト名 # arg_savefiledir:ベイク画像保存ファイルディレクトリ # arg_savefilename:ベイク画像保存ファイル名 # 戻り値 def bake_texture_select2active( arg_sourcename = "Default", arg_destinationname = "Default", arg_savefilepath = "C:\\UnityChan\\Models\\UnityChanShader\\Texture", arg_savefilename = "NewTexture"): # 他のオブジェクトに操作を適用しないよう全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 非選択状態に設定する ob.select = False # ベイク先オブジェクトを取得する destinationob = bpy.context.scene.objects[arg_destinationname] # ベイク先オブジェクトをアクティブに変更する bpy.context.scene.objects.active = destinationob # 編集モードに移行する bpy.ops.object.mode_set(mode = 'EDIT', toggle = False) # 頂点を全選択した状態とする bpy.ops.mesh.select_all(action = 'SELECT') # 新規画像を作成する bakeimage = bpy.data.images.new(name = arg_savefilename, width = 1024, height = 1024, alpha = True) # 保存ファイルパスを作成する savepath = arg_savefilepath + "\\" + arg_savefilename + ".png" # 保存ファイルパスを指定する bakeimage.filepath_raw = savepath # ファイルフォーマットをPNGに設定する bakeimage.file_format = 'PNG' # 画像を一旦保存する(初期化のため) bakeimage.save() # 全てのスクリーンを走査する for area in bpy.context.screen.areas: # エリアタイプが3Dビューであるか if area.type == 'VIEW_3D': # エリアタイプをUV画像エディターに変更する area.type='IMAGE_EDITOR' # エディター内の各領域を走査する for space in area.spaces: # 領域が画像エディターの表示領域であるか if space.type == 'IMAGE_EDITOR': # 画像エディターの表示領域の作成した画像を設定する space.image = bakeimage # ベイク元オブジェクトを取得する sourceob = bpy.context.scene.objects[arg_sourcename] # ベイク元オブジェクトを選択状態にする sourceob.select = True # ベイク先オブジェクトを選択状態にする destinationob.select = True # ベイクモードを「テクスチャ」に設定する bpy.data.scenes['Scene'].render.bake_type = 'TEXTURE' # ベイク先を「選択→アクティブ」を有効に設定する bpy.context.scene.render.use_bake_selected_to_active = True # ベイク処理を実行する bpy.ops.object.bake_image() # 画像を再保存する bakeimage.save() # 全てのスクリーンを走査する for area in bpy.context.screen.areas: # エリアタイプがUV画像エディターであるか if area.type == 'IMAGE_EDITOR': # エリアタイプを3Dビューに戻す area.type = 'VIEW_3D' # ベイク先オブジェクトをオブジェクトモードに戻す bpy.ops.object.mode_set(mode = 'OBJECT', toggle = False) return # 指定オブジェクトにテクスチャを参照するマテリアルを適用する # 引数 arg_objectname:指定オブジェクト名 # arg_applyfiledir:反映画像保存ファイルディレクトリ # arg_applyfilename:反映画像保存ファイル名 # 戻り値 def new_material_textureset( arg_objectname = "Default", arg_applyfilepath = "C:\\UnityChan\\Models\\UnityChanShader\\Texture", arg_applyfilename = "NewTexture"): # 指定オブジェクトを取得する selectob = bpy.context.scene.objects[arg_objectname] # 変更オブジェクトをアクティブに変更する bpy.context.scene.objects.active = selectob # 新規マテリアルを作成する new_material = bpy.data.materials.new(arg_applyfilename) # マテリアルスロットを追加する bpy.ops.object.material_slot_add() # 作成したマテリアルスロットに新規マテリアルを設定する bpy.context.object.active_material = new_material # 新規テクスチャを作成する new_texture = bpy.data.textures.new(arg_applyfilename, type = 'IMAGE') # マテリアルにテクスチャスロットを追加する new_texture_slot = new_material.texture_slots.add() # 作成したテクスチャスロットに新規テクスチャを設定する new_texture_slot.texture = new_texture # 反映画像のファイルパスを取得する allpy_filepath = arg_applyfilepath + "\\" + arg_applyfilename + ".png" # 反映画像を読み込み apply_image = bpy.data.images.load(filepath = allpy_filepath) # 作成した新規テクスチャに画像を設定する new_texture.image = apply_image return # 指定オブジェクトを削除する # ※ 参照切りのため、メッシュデータは削除しない # 引数 arg_objectname:指定オブジェクト名 # 戻り値 def unlink_object(arg_objectname = "Default"): # 指定オブジェクトを取得する selectob=bpy.context.scene.objects[arg_objectname] # 指定オブジェクトの参照をシーンから削除する bpy.context.scene.objects.unlink(selectob) return # バックグラウンド実行時は bpy.app.background が True if bpy.app.background: # 実行指定で呼び出されているかチェック if __name__ == "__main__": # 引数を取得 args = sys.argv if 2 <= len(args): print('--------------------------- START ---------------------------') # 引数から自身のパスのみpop削除 args.pop(0) print(args) # main関数の呼び出し # バックグラウンド実行時処理 main_CUI(args) print('--------------------------- END ---------------------------') else: # GUI実行時 main_GUI()
実行結果
処理が完了すると、オブジェクトが1つに結合されます。
マテリアルも1つのテクスチャマテリアルに統合されます。
カラーを確認するため、シェーディングを[マテリアル]に変更して環境照明を設定しています。
頂点カラーベイクを利用した結合
オブジェクトの結合と、テクスチャベイクを利用したマテリアルの結合を行うスクリプトを用意しました。
結合したオブジェクトのマテリアルのベースカラーを全て頂点カラーに焼き込みます。
頂点ごとに色を焼き込むため、色の解像度を保つには頂点の細分化が必要です。
スクリプト
・ModelVertexColorMerge.py
# osインポート(ファイルパス処理のため) import os # sysインポート(引数取得のため) import sys # bpyインポート(3D処理のため) import bpy # mathインポート(角度計算のため) import math # CUI実行時のメイン関数 def main_CUI(arg_filepathlist = ["C:\\WORK\\Models\\FBXImport.fbx"]): # 必要なアドオンを有効化する(用gltf-Exporter) blender_addon_enable() for filepath in arg_filepathlist: # 処理データの有無を確認 if os.path.exists(filepath) == False: # 処理データがない場合、処理を行わない continue # 処理データのディレクトリパスを取得 target_dirpath = os.path.dirname(filepath) # 処理データのファイル名を取得 target_filename = os.path.splitext(os.path.basename(filepath))[0] # 処理データの拡張子を取得 target_fileextension = os.path.splitext(filepath)[1] print("dir:" + target_dirpath) print("name:" + target_filename) print("ext:" + target_fileextension) # シーンのクリア delete_all_objects() # ファイルの読み込み import_result = import_file( arg_filedir = target_dirpath, arg_filename = target_filename, arg_fileextension = target_fileextension ) # 読み込みの成否を確認 if import_result == False: # 読み込みに失敗した場合、処理を行わない continue # オブジェクト群に対してメッシュ結合/マテリアル結合/位置適用を実行する joinmesh_joinmat_transapply() # GLB形式で3Dデータを書き出し export_result = export_file( arg_filedir = target_dirpath, arg_filename = target_filename, arg_fileextension = ".glb" ) # .blendファイルを名前を付けて保存 blendfilepath = target_dirpath + "\\" + target_filename + "_export.blend" bpy.ops.wm.save_as_mainfile(filepath = blendfilepath) print("Export blend:" + blendfilepath) # 処理結果を表示 print("Export Result:" + str(export_result)) return # GUI実行時のメイン関数 def main_GUI(): # オブジェクト群に対してメッシュ結合/マテリアル結合/位置適用を実行する joinmesh_joinmat_transapply() return # テスト用関数 def test(): return # 必要なアドオンを有効化する # ※ scripts/addonsへのアドオンコピーが必要 # 引数 # 戻り値 def blender_addon_enable(): # gltf-Exporter アドオンの有効化 bpy.ops.wm.addon_enable(module = "io_scene_gltf2") return # オブジェクト群に対してメッシュ結合/マテリアル結合/位置適用を実行する # 引数 # 戻り値 def joinmesh_joinmat_transapply(): # 結合後のオブジェクト名 joinob_name = "JoinObject" # メッシュオブジェクトを結合する join_objects_mesh(arg_objectname = joinob_name) # マテリアルを結合する joint_material_vertexcolor(arg_objectname = joinob_name) # オブジェクトの位置を適用する apply_object_transform(arg_objectname = joinob_name) return # オブジェクト群に対して位置調整を実行する # 引数 arg_filepath:調整移動量 # 戻り値 def transmove(arg_vector = (0.0,0.0,0.0)): # 位置調整 for ob in bpy.context.scene.objects: if ob.type == 'MESH': move_vector = arg_vector add_object_transform( arg_objectname = ob.name, arg_location = move_vector ) return # 全オブジェクトの削除 # 引数 # 戻り値 def delete_all_objects(): # 全シーンオブジェクトを削除する for item in bpy.context.scene.objects: bpy.context.scene.objects.unlink(item) # 全データオブジェクトを削除する for item in bpy.data.objects: bpy.data.objects.remove(item) # 全メッシュデータを削除する for item in bpy.data.meshes: bpy.data.meshes.remove(item) # 全マテリアルデータを削除する for item in bpy.data.materials: bpy.data.materials.remove(item) return # 3Dデータをインポートする # 引数 arg_filedir:ファイルディレクトリ # arg_filename:ファイル名 # arg_fileextension:ファイル拡張子 # 戻り値 読み込み成否 def import_file( arg_filedir = "C:\\WORK\\Models", arg_filename = "ImportModel", arg_fileextension = ".fbx"): # 読み込み成否フラグ import_result = False # 拡張子を判定する if arg_fileextension == ".fbx": # FBXのインポートを実行する fbx_filepath = arg_filedir + "\\" + arg_filename + arg_fileextension print("Import FBX:" + fbx_filepath) import_file_fbx(fbx_filepath) import_result = True elif arg_fileextension == ".ifc": # IFCのインポートを実行する ifc_filepath = arg_filedir + "\\" + arg_filename+arg_fileextension print("Import IFC:" + ifc_filepath) import_file_ifc(ifc_filepath) import_result = True else: unknown_filepath = arg_filedir + "\\" + arg_filename + arg_fileextension print("Unknown file format:" + unknown_filepath) return import_result # IFCモデルをインポートする # 要IfcOpenShell(http://ifcopenshell.org/ifcblender.html) # 引数 arg_filepath:ファイルパス # 戻り値 def import_file_ifc(arg_filepath = "D:\\BlenderDir\\Script\\Import.ifc"): # ifc-Blender アドオンの有効化 bpy.ops.wm.addon_enable(module = "io_import_scene_ifc") # IFCインポート # bpy.ops.import_scene.ifc() bpy.ops.import_scene.ifc( filepath=arg_filepath, use_names=True, process_relations=False, blender_booleans=False ) return # FBXモデルをインポートする # 引数 arg_filepath:ファイルパス # 戻り値 def import_file_fbx(arg_filepath = "D:\\BlenderDir\\Script\\Import.fbx"): # FBXインポート # bpy.ops.import_scene.fbx() bpy.ops.import_scene.fbx( filepath = arg_filepath, use_manual_orientation = False, global_scale = 1.0, bake_space_transform = False, use_custom_normals = True, use_image_search = True, use_alpha_decals = False, decal_offset = 0.0, use_anim = True, anim_offset = 1.0, use_custom_props = True, use_custom_props_enum_as_string = True, ignore_leaf_bones = False, force_connect_children = False, automatic_bone_orientation = False, primary_bone_axis = 'Y', secondary_bone_axis = 'X', use_prepost_rot = True ) return # 3Dデータをエクスポートする # 引数 arg_filedir:ファイルディレクトリ # arg_filename:ファイル名 # arg_fileextension:ファイル拡張子 # 戻り値 書き出し成否 def export_file( arg_filedir = "C:\\WORK\\Models", arg_filename = "ExportModel", arg_fileextension = ".fbx"): # 書き出し成否フラグ export_result = False # 拡張子を判定する if arg_fileextension == ".fbx": fbx_filepath = arg_filedir + "\\" + arg_filename + arg_fileextension print("Export FBX:" + fbx_filepath) export_file_fbx(fbx_filepath) export_result = True elif arg_fileextension == ".glb": glb_filepath = arg_filedir + "\\" + arg_filename + arg_fileextension print("Export GLB:" + glb_filepath) export_file_glb(glb_filepath) export_result = True else: unknown_filepath = arg_filedir + "\\" + arg_filename + arg_fileextension print("Unknown file format:" + unknown_filepath) return export_result # FBXモデルをエクスポートする(アーマチュアとメッシュのみ出力) # 引数 arg_filepath:ファイルパス # 戻り値 def export_file_fbx(arg_filepath = "D:\\BlenderDir\\Script\\Export.fbx"): # FBXエクスポート # bpy.ops.export_scene.fbx() # エクスポート設定(デフォルト値) bpy.ops.export_scene.fbx( filepath = arg_filepath, version = 'BIN7400', use_selection = False, global_scale = 1.0, apply_unit_scale = True, apply_scale_options = 'FBX_SCALE_NONE', bake_space_transform = False, object_types = {'ARMATURE', 'MESH'}, use_mesh_modifiers = True, use_mesh_modifiers_render = True, mesh_smooth_type = 'OFF', use_mesh_edges = False, use_tspace = False, use_custom_props = False, add_leaf_bones = True, primary_bone_axis = 'Y', secondary_bone_axis = 'X', use_armature_deform_only = False, armature_nodetype = 'NULL', bake_anim = True, bake_anim_use_all_bones = True, bake_anim_use_nla_strips = True, bake_anim_use_all_actions = True, bake_anim_force_startend_keying = True, bake_anim_step = 1.0, bake_anim_simplify_factor = 1.0, path_mode = 'AUTO', embed_textures = False, batch_mode = 'OFF', use_batch_own_dir = True, use_metadata = True ) # 出力対象の種別 # 'EMPTY':エンプティ # 'CAMERA':カメラ # 'LAMP':ランプ # 'ARMATURE':アーマチュア # 'MESH':メッシュ # 'OTHER':その他 # パスモードの種類 # 'AUTO':自動 # 'ABSOLUTE':絶対 # 'RELATIVE':相対的 # 'MATCH':マッチ # 'STRIP':ストリップパス # 'COPY':コピー return # GLBモデルをエクスポートする # 要GLTFImporter(https://github.com/ksons/gltf-blender-importer) # 引数 arg_filepath:ファイルパス # 戻り値 def export_file_glb(arg_filepath = "D:\\BlenderDir\\Script\\Export.glb"): # GLBエクスポート # bpy.ops.export_scene.glb() # エクスポート設定(デフォルト値) bpy.ops.export_scene.glb( filepath = arg_filepath, export_copyright = '', export_embed_buffers = False, export_embed_images = False, export_strip = False, export_indices = 'UNSIGNED_INT', export_force_indices = False, export_texcoords = True, export_normals = True, export_tangents = True, export_materials = True, export_colors = True, export_cameras = False, export_camera_infinite = False, export_selected = False, export_layers = True, export_extras = False, export_yup = True, export_apply = False, export_animations = True, export_frame_range = True, export_frame_step = 1, export_move_keyframes = True, export_force_sampling = False, export_current_frame = True, export_skins = True, export_bake_skins = False, export_morph = True, export_morph_normal = True, export_morph_tangent = True, export_lights = False, export_displacement = False, will_save_settings = False ) return # 指定オブジェクトのトランスフォームを変更する # 引数 arg_objectname:指定オブジェクト名 # arg_location:指定位置値(加算) # arg_rotation:指定回転値(加算) # arg_scale:指定拡大縮小値(乗算) # 戻り値 def add_object_transform( arg_objectname = "Default", arg_location = (0,0,0), arg_rotation = (0,0,0), arg_scale = (1,1,1)): # 指定オブジェクトを取得する selectob = bpy.context.scene.objects[arg_objectname] # 位置を変更する selectob.location.x += arg_location[0] selectob.location.y += arg_location[1] selectob.location.z += arg_location[2] # 回転を変更する selectob.rotation_euler.x += 2 * math.pi / 360 * arg_rotation[0] selectob.rotation_euler.y += 2 * math.pi / 360 * arg_rotation[1] selectob.rotation_euler.z += 2 * math.pi / 360 * arg_rotation[2] # 拡大縮小を変更する selectob.scale.x *= arg_scale[0] selectob.scale.y *= arg_scale[1] selectob.scale.z *= arg_scale[2] return # メッシュオブジェクトの結合 # 引数 arg_objectname:結合オブジェクト名 # 戻り値 def join_objects_mesh(arg_objectname = ""): # シーン中の全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # オブジェクトがメッシュであるか確認する if ob.type == 'MESH': # メッシュであれば選択状態にする ob.select = True # 対象アクティブオブジェクトに切り替える # メッシュはアクティブオブジェクトに結合される bpy.context.scene.objects.active = ob else: # メッシュでなければ選択状態にしない ob.select = False # オブジェクトの結合を実行する bpy.ops.object.join() # 結合オブジェクト名が設定されているか if len(arg_objectname): # オブジェクト名が設定されていれば名前を変更する bpy.context.scene.objects.active.name = arg_objectname return # 指定オブジェクトのトランスフォームを適用する # 引数 arg_objectname:指定オブジェクト名 # arg_location:位置適用 # arg_rotation:回転適用 # arg_scale:拡大縮小適用 # 戻り値 def apply_object_transform( arg_objectname = "Default", arg_location = True, arg_rotation = True, arg_scale = True): # 他のオブジェクトに操作を適用しないよう全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 非選択状態にする ob.select = False # 指定オブジェクトを取得する selectob = bpy.context.scene.objects[arg_objectname] # 指定オブジェクトを選択状態にする selectob.select = True # トランスフォームを適用する bpy.ops.object.transform_apply( location = arg_location, rotation = arg_rotation, scale = arg_scale) # 処理を完了したら非選択状態にする selectob.select = False return # 指定オブジェクトのトランスフォームをクリアする # 引数 arg_objectname:指定オブジェクト名 # arg_location:位置クリア # arg_rotation:回転クリア # arg_scale:拡大縮小クリア # 戻り値 def clear_object_transform( arg_objectname = "Default", arg_location = True, arg_rotation = True, arg_scale = True): # 他のオブジェクトに操作を適用しないよう全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 非選択状態にする ob.select = False # 指定オブジェクトを取得する selectob = bpy.context.scene.objects[arg_objectname] # 指定オブジェクトを選択状態にする selectob.select = True # 位置をクリアする if arg_location: bpy.ops.object.location_clear(clear_delta = False) # 回転をクリアする if arg_rotation: bpy.ops.object.rotation_clear(clear_delta = False) # 拡大縮小をクリアする if arg_scale: bpy.ops.object.scale_clear(clear_delta = False) # 処理を完了したら非選択状態にする selectob.select = False return # 指定したオブジェクトのマテリアルを頂点カラーマテリアルで結合する # 引数 arg_objectname:指定オブジェクト名 # 戻り値 def joint_material_vertexcolor(arg_objectname = "Default"): # 全メッシュオブジェクトで頂点カラーのベイク bake_vertexcolor_all() # 全てのメッシュオブジェクトで頂点カラー表示 show_vertexcolor_all() return # 全メッシュオブジェクトの頂点カラーにベイク処理を行う # 引数 # 戻り値 def bake_vertexcolor_all(): # シーン中の全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 一旦全てのオブジェクトを非選択状態に設定する ob.select=False # 全シーンデータを走査する for scene in bpy.data.scenes: # [頂点カラーにベイク]を有効にする scene.render.use_bake_to_vertex_color = True # 全マテリアルデータを走査する for material in bpy.data.materials: # [陰影なし]を有効にする material.use_shadeless = True # シーン中の全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # オブジェクトがメッシュならベイク処理の対象とする if ob.type == 'MESH': # オブジェクトをアクティブにする bpy.context.scene.objects.active = ob # オブジェクトを選択状態にする ob.select = True # オブジェクトのメッシュデータを取得する obmesh = ob.data # 不要な頂点カラーを削除する for num in range(len(obmesh.vertex_colors))[::-1]: # 削除処理を行うので逆順に要素を追う obmesh.vertex_colors.active_index = num bpy.ops.mesh.vertex_color_remove() # 頂点カラーレイヤを追加する bpy.ops.mesh.vertex_color_add() # ベイクを実行する bpy.ops.object.bake_image() # オブジェクトを非選択状態に戻す ob.select = False return # 全てのメッシュオブジェクトで頂点カラー表示 # 全てのマテリアルを削除してメッシュオブジェクトに # 頂点カラー表示のマテリアルを設定し直す # 引数 # 戻り値 def show_vertexcolor_all(): # シーン中の全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 一旦全てのオブジェクトを非選択状態に設定する ob.select = False # シーン中の全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # オブジェクトがメッシュなら一旦マテリアルスロットを初期化する if ob.type == 'MESH': # オブジェクトをアクティブにする bpy.context.scene.objects.active = ob # オブジェクトを選択状態にする ob.select = True # マテリアルスロットを走査する for material_slot in ob.material_slots: # マテリアルスロットをアクティブにする ob.active_material = material_slot.material # マテリアルスロットを削除する bpy.ops.object.material_slot_remove() # 全マテリアルデータを走査する for material in bpy.data.materials: bpy.data.materials.remove(material) # 新規マテリアルの作成 bpy.ops.material.new() # 新規マテリアルの参照を取得する # (新規マテリアルはリストの終端に追加されている) vertexmaterial = bpy.data.materials[-1] # 頂点カラー表示の設定 vertexmaterial.use_vertex_color_paint = True # シーン中の全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # オブジェクトがメッシュならベイク処理の対象とする if ob.type == 'MESH': # 頂点カラーのマテリアルを設定する ob.active_material = vertexmaterial return # バックグラウンド実行時は bpy.app.background が True if bpy.app.background: # 実行指定で呼び出されているかチェック if __name__ == "__main__": # 引数を取得 args = sys.argv if 2 <= len(args): print('--------------------------- START ---------------------------') # 引数から自身のパスのみpop削除 args.pop(0) print(args) # main関数の呼び出し # バックグラウンド実行時処理 main_CUI(args) print('--------------------------- END ---------------------------') else: # GUI実行時 main_GUI()
実行結果
処理が完了すると、オブジェクトが1つに結合されます。
マテリアルも1つの頂点カラー表示のマテリアルに統合されます。
カラーを確認するため、シェーディングを[マテリアル]に変更して環境照明を設定しています。
CUIでの実行
本記事のスクリプトは以下の記事と合わせて利用すると、処理をCUIから実行することも可能です。
bluebirdofoz.hatenablog.com