Blender OBJ/MTL エクスポータ(UE5モード付き)(ソースコードと実行結果)

【概要】BlenderファイルをOBJ/MTL形式でエクスポートするGUIツール。Unreal Engine 5向けの最適化機能を備え、座標系変換、スケール調整、メッシュの三角形化を自動で行う。テクスチャのコピーやマテリアルの最適化にも対応。

【目次】

  1. Python開発環境、ライブラリ類
  2. Blender OBJ/MTL エクスポータGUI版プログラム

UE5モード時は、メッシュ三角形化、座標系変換(X前方、Z上方)、スケール変換(1m=100cm)を行う。

Blender OBJ/MTL エクスポータのGUI画面

プログラム利用ガイド

1. このプログラムの利用シーン

Blenderで作成した3DモデルをUnreal Engine 5にインポートするためのソフトウェアである。座標系やスケールの変換、テクスチャファイルの整理といった作業を自動化し、エクスポート設定をGUIで指定できる。

2. 主な機能

3. 基本的な使い方

  1. 起動:

    コマンドプロンプトまたはターミナルで「python プログラムファイル名.py」を実行する。GUIウィンドウが表示される。

  2. ファイル選択:

    「Blenderファイル」の「参照」ボタンをクリックし、エクスポートしたい.blendファイルを選択する。出力先ディレクトリは自動で設定されるが、変更も可能である。

  3. オプション設定:

    必要に応じて以下のオプションを選択する。

    • UE5モード: UE5向けの座標系変換とスケール調整を行う。
    • モディファイヤ適用: 全モディファイヤを適用してエクスポートする。
    • マテリアル最適化: 同一マテリアルを統合する。
    • テクスチャコピー: テクスチャをコピーしパスを修正する。
  4. エクスポート実行:

    「エクスポート実行」ボタンをクリックする。処理状況はログエリアに表示される。

4. 便利な機能

Python開発環境、ライブラリ類

ここでは、最低限の事前準備について説明する。機械学習や深層学習を行う場合は、NVIDIA CUDA、Visual Studio、Cursorなどを追加でインストールすると便利である。これらについては別ページ https://www.kkaneko.jp/cc/dev/aiassist.htmlで詳しく解説しているので、必要に応じて参照してください。

Python 3.12 のインストール

インストール済みの場合は実行不要。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要である。

REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent --accept-source-agreements --accept-package-agreements
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul

関連する外部ページ

Python の公式ページ: https://www.python.org/

AI エディタ Windsurf のインストール

Pythonプログラムの編集・実行には、AI エディタの利用を推奨する。ここでは,Windsurfのインストールを説明する。

管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行して、Windsurfをシステム全体にインストールする。管理者権限は、wingetの--scope machineオプションでシステム全体にソフトウェアをインストールするために必要となる。

winget install --scope machine --id Codeium.Windsurf -e --silent --accept-source-agreements --accept-package-agreements

関連する外部ページ

Windsurf の公式ページ: https://windsurf.com/

必要なライブラリをシステム領域にインストール

コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する


pip install customtkinter

Blender 4 以降のインストール

コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する


winget install --scope machine --id BlenderFoundation.Blender -e

Blender OBJ/MTL エクスポータGUI版プログラム

1. 概要

このプログラムは、BlenderファイルをOBJ/MTL形式でエクスポートするGUIアプリケーションである。Unreal Engine 5(UE5)向けの座標系変換、スケール調整、メッシュ三角形化に対応し、テクスチャファイルのコピーとMTLファイル内のパス修正を自動で行う。

2. 主要技術

Blender Python API (bpy)

Blenderの機能をPythonスクリプトから制御するためのAPIである[1]。本プログラムでは、bpy.ops.wm.obj_export()関数を使用してOBJ/MTL形式のエクスポートを実行し、座標系変換やスケール調整のパラメータを指定する。

customtkinter

tkinterを拡張したGUIライブラリであり、モダンな外観のUIコンポーネントを提供する[2]。本プログラムでは、ファイル選択ダイアログ、チェックボックス、テキストボックス、ログ表示エリアの構築に使用する。

3. 技術的特徴

4. 実装の特色

本プログラムは以下の機能を備える:

5. 参考文献

[1] Blender Foundation. (2024). Blender Python API Documentation. https://docs.blender.org/api/current/

[2] TomSchimansky. (2024). CustomTkinter Documentation. https://customtkinter.tomschimansky.com/documentation/

ソースコード


# Blender OBJ/MTL エクスポータ GUI版
# 特徴技術名: Blender Python API (bpy)
# 出典: Blender Foundation (2024). Blender Python API Documentation. https://docs.blender.org/api/current/
# 特徴機能: bpy.ops.wm.obj_export()によるパラメータ制御を伴うOBJ/MTL形式エクスポート。座標系変換、スケール調整、メッシュ三角形化などの制御が可能
# 学習済みモデル: 使用なし
# 方式設計:
#   - 関連利用技術:
#     - customtkinter: UIを提供するtkinterの拡張ライブラリ。ファイル選択ダイアログ、オプション設定、リアルタイムログ表示を実現
#     - subprocess: Blenderをバックグラウンドプロセスとして実行し、標準出力をリアルタイムで取得
#     - threading: GUI応答性維持のための非同期処理実装
#     - pathlib/os/shutil: ファイルシステム操作とテクスチャファイルのコピー処理
#   - 入力と出力: 入力: Blenderファイル(.blend)、出力: OBJ/MTLファイルとテクスチャ
#   - 処理手順:
#     1. GUIでオプション設定とファイル選択
#     2. Blenderをバックグラウンドプロセスで起動
#     3. bpyを使用してBlenderファイルを開く
#     4. 指定オプションに基づきモディファイヤ適用、マテリアル最適化を実行
#     5. bpy.ops.wm.obj_export()でOBJ/MTL形式にエクスポート(UE5モード時は座標系変換とスケール調整)
#     6. テクスチャのコピーとMTLファイルのパス修正
#   - 前処理、後処理:
#     - 前処理: モディファイヤの自動適用(全オブジェクト対象、エクスポート時のみ)、重複マテリアルの検出と統合
#     - 後処理: MTLファイル内のmap_Kdパスをファイル名のみに修正(UE5互換性向上)、texturesフォルダへのテクスチャ自動コピー
#   - 追加処理: UE5モード時のメッシュ三角形化、座標系変換(X前方、Z上方)、スケール変換(1m=100cm)
#   - 調整を必要とする設定値: なし(GUIで全オプションを動的に設定可能)
# 将来方策: なし(全設定はGUIで調整可能)
# その他の重要事項:
#   - Blender 4.5以降が必要
#   - 元のBlenderファイルは変更されない(エクスポート時のみの適用)
#   - GUI起動: python <Python プログラムファイル名>
# 前準備:
#   - Blenderのインストール(4.5以降推奨)
#   - Blenderの実行パスをシステムのPATH環境変数に追加(推奨)
#   - pip install customtkinter

import os
import shutil
import customtkinter as ctk
from tkinter import filedialog, messagebox
import threading
import sys
import tempfile
import subprocess
import platform

# 設定定数
WINDOW_WIDTH = 800  # GUIウィンドウの幅
WINDOW_HEIGHT = 700  # GUIウィンドウの高さ
BLENDER_VERSIONS = ['4.5', '4.2', '4.1', '4.0']  # サポートするBlenderバージョン
TEXTURE_FOLDER_NAME = 'textures'  # テクスチャ保存フォルダ名
UE5_SCALE = 100.0  # UE5向けスケール変換値(1m = 100cm)

# GUI設定定数
LOG_HEIGHT = 300  # ログ表示エリアの高さ(ピクセル)
ENTRY_WIDTH = 400  # 入力フィールドの幅(ピクセル)
PADDING_X = 10  # 水平方向のパディング
PADDING_Y = 5  # 垂直方向のパディング

# CustomTkinterの設定
ctk.set_appearance_mode('system')  # システムの設定に従う(ライト/ダーク)
ctk.set_default_color_theme('blue')  # カラーテーマ

# グローバル変数(GUI用)
root = None
log_text = None
blend_path = None
output_dir = None
ue5_mode = None
apply_mods = None
opt_materials = None
copy_tex = None


def find_blender_executable():
    """Blenderの実行可能ファイルを探す"""
    # まずPATHから探す
    if shutil.which('blender'):
        return 'blender'

    # Windows用の共通パスを定義
    paths = []
    for ver in BLENDER_VERSIONS:
        paths.append(f'C:\\Program Files\\Blender Foundation\\Blender {ver}\\blender.exe')
    paths.extend([
        r'C:\Program Files\Blender Foundation\Blender\blender.exe',
        r'C:\Program Files (x86)\Blender Foundation\Blender\blender.exe',
    ])

    for path in paths:
        if os.path.exists(path):
            return path

    return None


def log(message):
    """ログメッセージを表示"""
    if root and log_text:
        root.after(0, lambda: log_text.insert('end', message) or log_text.see('end'))


def browse_blend_file():
    """Blenderファイルを選択"""
    filename = filedialog.askopenfilename(
        title='Blenderファイルを選択',
        filetypes=[('Blender files', '*.blend'), ('All files', '*.*')]
    )
    if filename:
        blend_path.set(filename)
        # デフォルトの出力ディレクトリを設定
        if not output_dir.get():
            output_dir.set(os.path.dirname(filename))


def browse_output_dir():
    """出力ディレクトリを選択"""
    dirname = filedialog.askdirectory(title='出力ディレクトリを選択')
    if dirname:
        output_dir.set(dirname)


def export_thread():
    """エクスポート処理(別スレッド)"""
    try:
        # Blenderの実行可能ファイルを探す
        blender_path = find_blender_executable()
        if not blender_path:
            log('\nエラー: Blenderが見つかりません\n')
            log('以下のいずれかの方法で解決してください:\n')
            log('1. BlenderをPATH環境変数に追加\n')
            log('2. Blenderを標準的な場所にインストール\n')
            messagebox.showerror('エラー', 'Blenderが見つかりません。\nBlenderがインストールされていることを確認してください。')
            return

        # 出力ディレクトリの処理
        output_directory = output_dir.get() if output_dir.get() else os.path.dirname(blend_path.get())

        # Blender内で実行するスクリプトを作成
        script_content = f'''# -*- coding: utf-8 -*-
import bpy
import os
import shutil

UE5_SCALE = {UE5_SCALE}
TEXTURE_FOLDER_NAME = '{TEXTURE_FOLDER_NAME}'

# 設定値
blend_path = r'{blend_path.get()}'
output_dir = r'{output_directory}'
ue5_mode = {ue5_mode.get()}
apply_mods = {apply_mods.get()}
opt_materials = {opt_materials.get()}
copy_tex = {copy_tex.get()}

try:
    # Blenderファイルを開く
    bpy.ops.wm.open_mainfile(filepath=blend_path)

    # 出力パスの設定
    blend_name = os.path.splitext(os.path.basename(blend_path))[0]

    # 出力パスを絶対パスに変換
    output_dir = os.path.abspath(output_dir)
    output_path = os.path.join(output_dir, blend_name + '.obj')

    # モディファイヤの適用
    if apply_mods:
        try:
            # オブジェクトモードに切り替え
            if bpy.context.mode != 'OBJECT':
                bpy.ops.object.mode_set(mode='OBJECT')

            # 全オブジェクトを走査してモディファイヤを適用
            for obj in bpy.data.objects:
                if obj.type == 'MESH' and obj.modifiers:
                    # オブジェクトを選択してアクティブに設定
                    bpy.ops.object.select_all(action='DESELECT')
                    obj.select_set(True)
                    bpy.context.view_layer.objects.active = obj

                    # モディファイヤをリストにコピー(削除時のインデックス変更対策)
                    modifiers_to_apply = [mod.name for mod in obj.modifiers]

                    for modifier_name in modifiers_to_apply:
                        try:
                            if modifier_name in obj.modifiers:
                                bpy.ops.object.modifier_apply(modifier=modifier_name)
                                print(f'モディファイヤ適用: {{obj.name}}.{{modifier_name}}')
                        except Exception as e:
                            print(f'モディファイヤ {{modifier_name}} の適用に失敗: {{str(e)}}')
        except Exception as e:
            print(f'モディファイヤ適用中にエラーが発生: {{str(e)}}')

    # マテリアルの最適化
    if opt_materials:
        try:
            materials = {{}}
            material_map = {{}}

            for mat in bpy.data.materials:
                if mat.use_nodes:
                    # マテリアルの主要プロパティをキーとして使用
                    key = (
                        round(mat.diffuse_color[0], 3),
                        round(mat.diffuse_color[1], 3),
                        round(mat.diffuse_color[2], 3),
                        round(mat.metallic, 3),
                        round(mat.roughness, 3)
                    )
                    if key in materials:
                        material_map[mat.name] = materials[key]
                        print(f'マテリアル統合: {{mat.name}} -> {{materials[key]}}')
                    else:
                        materials[key] = mat.name
                        material_map[mat.name] = mat.name

            # オブジェクトのマテリアルを統合されたものに置き換え
            for obj in bpy.data.objects:
                if obj.type == 'MESH' and obj.data.materials:
                    for i, mat in enumerate(obj.data.materials):
                        if mat and mat.name in material_map:
                            new_mat_name = material_map[mat.name]
                            if new_mat_name != mat.name:
                                obj.data.materials[i] = bpy.data.materials[new_mat_name]
        except Exception as e:
            print(f'マテリアル最適化中にエラーが発生: {{str(e)}}')

    # 全オブジェクトを選択
    bpy.ops.object.select_all(action='SELECT')

    # OBJエクスポート設定
    print(f'エクスポート開始: {{output_path}}')
    if ue5_mode:
        # UE5向け設定:座標系変換とスケール調整
        bpy.ops.wm.obj_export(
            filepath=output_path,
            export_selected_objects=False,
            apply_modifiers=False,  # モディファイヤは事前に適用済み
            export_uv=True,
            export_normals=True,
            export_colors=True,
            export_materials=True,
            export_triangulated_mesh=True,
            export_object_groups=True,
            export_material_groups=True,
            forward_axis='X',
            up_axis='Z',
            global_scale=UE5_SCALE
        )
        print(f'UE5モード: 座標系変換(X前方,Z上方), スケール={{UE5_SCALE}}, 三角形化')
    else:
        # 標準設定
        bpy.ops.wm.obj_export(
            filepath=output_path,
            export_selected_objects=False,
            apply_modifiers=False,  # モディファイヤは事前に適用済み
            export_uv=True,
            export_normals=True,
            export_colors=True,
            export_materials=True,
            export_triangulated_mesh=False,
            export_object_groups=True,
            export_material_groups=True,
            forward_axis='NEGATIVE_Z',
            up_axis='Y',
            global_scale=1.0
        )
        print(f'標準モード: Blenderデフォルト座標系')

    # テクスチャのコピーとMTLファイルの修正
    if copy_tex:
        mtl_path = output_path.replace('.obj', '.mtl')
        if os.path.exists(mtl_path):
            try:
                # texturesフォルダ作成
                textures_dir = os.path.join(output_dir, TEXTURE_FOLDER_NAME)
                os.makedirs(textures_dir, exist_ok=True)
                print(f'テクスチャフォルダ作成: {{textures_dir}}')

                # MTLファイルを読み込んで修正
                with open(mtl_path, 'r', encoding='utf-8') as f:
                    lines = f.readlines()

                new_lines = []
                texture_count = 0
                for line in lines:
                    if line.strip().startswith('map_'):
                        parts = line.split(None, 1)
                        if len(parts) == 2:
                            texture_path = parts[1].strip()

                            # Windowsパスのバックスラッシュをスラッシュに変換
                            texture_path = texture_path.replace('\\\\', '/')

                            # テクスチャファイルの絶対パス取得
                            if not os.path.isabs(texture_path):
                                # 相対パスの場合、Blenderファイルのディレクトリを基準とする
                                texture_path = os.path.join(os.path.dirname(blend_path), texture_path)

                            texture_path = os.path.normpath(texture_path)
                            tex_name = os.path.basename(texture_path)

                            # テクスチャファイルが存在する場合のみコピー
                            if os.path.exists(texture_path) and os.path.isfile(texture_path):
                                dest_path = os.path.join(textures_dir, tex_name)
                                try:
                                    shutil.copy2(texture_path, dest_path)
                                    print(f'テクスチャコピー成功: {{tex_name}}')
                                    texture_count += 1
                                except Exception as e:
                                    print(f'テクスチャコピー失敗 {{tex_name}}: {{str(e)}}')
                            else:
                                print(f'テクスチャファイルが見つかりません: {{texture_path}}')

                            # MTLファイル内のパスを相対パスに変更(スラッシュを使用)
                            new_line = f'{{parts[0]}} {{TEXTURE_FOLDER_NAME}}/{{tex_name}}\\n'
                            new_lines.append(new_line)
                        else:
                            new_lines.append(line)
                    else:
                        new_lines.append(line)

                # MTLファイルを上書き
                with open(mtl_path, 'w', encoding='utf-8') as f:
                    f.writelines(new_lines)

                print(f'MTLファイル更新完了: {{texture_count}}個のテクスチャを処理')

            except Exception as e:
                print(f'テクスチャ処理中にエラーが発生: {{str(e)}}')

    mode_str = 'UE5モード' if ue5_mode else '標準モード'
    print(f'\\nエクスポート完了({{mode_str}})')
    print(f'OBJファイル: {{output_path}}')
    print(f'MTLファイル: {{output_path.replace(".obj", ".mtl")}}')

    # 出力ファイルサイズを表示(MB単位)
    if os.path.exists(output_path):
        obj_size = os.path.getsize(output_path) / (1000 * 1000)  # MB単位に修正
        print(f'OBJファイルサイズ: {{obj_size:.2f}} MB')

except Exception as e:
    print(f'エクスポート中にエラーが発生しました: {{str(e)}}')
    import traceback
    traceback.print_exc()
'''

        # 一時スクリプトファイル作成
        with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False, encoding='utf-8') as f:
            f.write(script_content)
            temp_script = f.name

        # Blenderをバックグラウンドで実行
        cmd = [
            blender_path,
            '--background',
            '--python', temp_script
        ]

        process = subprocess.Popen(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            universal_newlines=True,
            encoding='utf-8'
        )

        # 出力をリアルタイムで表示
        for line in process.stdout:
            log(line)

        process.wait()

        # 一時ファイル削除
        try:
            os.unlink(temp_script)
        except OSError as e:
            log(f'一時ファイル削除失敗: {e}\n')

        if process.returncode == 0:
            log('\nエクスポート完了!\n')
            messagebox.showinfo('完了', 'エクスポートが完了しました')
        else:
            log('\nエラーが発生しました\n')
            messagebox.showerror('エラー', 'エクスポート中にエラーが発生しました')

    except Exception as e:
        log(f'\nエラー: {str(e)}\n')
        messagebox.showerror('エラー', str(e))


def export():
    """エクスポート実行"""
    if not blend_path.get():
        messagebox.showerror('エラー', 'Blenderファイルを選択してください')
        return

    # ログクリア
    log_text.delete('1.0', 'end')

    # ガイダンス表示
    log('========================================\n')
    log('Blender OBJ/MTL エクスポータ\n')
    log('========================================\n')
    log('エクスポート設定:\n')
    log(f'  入力ファイル: {os.path.basename(blend_path.get())}\n')
    log(f'  出力先: {output_dir.get() or os.path.dirname(blend_path.get())}\n')
    log(f'  UE5モード: {"有効" if ue5_mode.get() else "無効"}\n')
    log(f'  モディファイヤ適用: {"有効" if apply_mods.get() else "無効"}\n')
    log(f'  マテリアル最適化: {"有効" if opt_materials.get() else "無効"}\n')
    log(f'  テクスチャコピー: {"有効" if copy_tex.get() else "無効"}\n')
    log('========================================\n')
    log('処理を開始します...\n\n')

    # 別スレッドで実行
    thread = threading.Thread(target=export_thread)
    thread.daemon = True
    thread.start()


def create_gui():
    """GUI作成"""
    global root, log_text, blend_path, output_dir, ue5_mode, apply_mods, opt_materials, copy_tex

    print("========================================")
    print("Blender OBJ/MTL エクスポータ GUI版")
    print("========================================")
    print("概要: BlenderファイルをOBJ/MTL形式でエクスポート")
    print("操作方法:")
    print("  1. Blenderファイルを選択")
    print("  2. 必要に応じてオプションを設定")
    print("  3. エクスポート実行ボタンをクリック")
    print("注意事項:")
    print("  - Blender 4.5以降が必要です")
    print("  - 元のBlenderファイルは変更されません")
    print("========================================")

    root = ctk.CTk()
    root.title('Blender OBJ/MTL エクスポータ')
    root.geometry(f'{WINDOW_WIDTH}x{WINDOW_HEIGHT}')

    # 変数初期化
    blend_path = ctk.StringVar()
    output_dir = ctk.StringVar()
    ue5_mode = ctk.BooleanVar(value=True)
    apply_mods = ctk.BooleanVar(value=False)
    opt_materials = ctk.BooleanVar(value=False)
    copy_tex = ctk.BooleanVar(value=False)

    # メインフレーム
    main_frame = ctk.CTkScrollableFrame(root)
    main_frame.pack(fill='both', expand=True, padx=PADDING_X, pady=PADDING_Y)

    # プログラム概要
    o_frame = ctk.CTkFrame(main_frame)
    o_frame.pack(fill='x', padx=PADDING_X, pady=PADDING_Y)

    ctk.CTkLabel(o_frame, text='プログラム概要', font=('', 16, 'bold')).pack(pady=(10, 5))

    overview_text = '''このプログラムは、BlenderファイルをOBJ/MTL形式でエクスポートします。
Unreal Engine 5向けの最適化機能を備えており、座標系変換、スケール調整、
メッシュの三角形化などを自動で行います。'''

    ctk.CTkLabel(o_frame, text=overview_text, justify='left').pack(padx=20, pady=(5, 10))

    # 利用上の注意点
    n_frame = ctk.CTkFrame(main_frame)
    n_frame.pack(fill='x', padx=PADDING_X, pady=PADDING_Y)

    ctk.CTkLabel(n_frame, text='利用上の注意点', font=('', 16, 'bold')).pack(pady=(10, 5))

    notice_text = '''• Blender 4.5以降が必要です
• UE5モードではメッシュが自動的に三角形化されます
• テクスチャコピー機能使用時は、MTLファイルが自動修正されます
• モディファイヤ適用は元に戻せません(元ファイルは変更されません)'''

    ctk.CTkLabel(n_frame, text=notice_text, justify='left').pack(padx=20, pady=(5, 10))

    # ファイル選択
    file_frame = ctk.CTkFrame(main_frame)
    file_frame.pack(fill='x', padx=PADDING_X, pady=PADDING_Y)

    ctk.CTkLabel(file_frame, text='ファイル設定', font=('', 16, 'bold')).pack(pady=(10, 5))

    # 入力ファイル
    input_frame = ctk.CTkFrame(file_frame)
    input_frame.pack(fill='x', padx=20, pady=5)

    ctk.CTkLabel(input_frame, text='Blenderファイル:').pack(side='left', padx=(10, 5))
    ctk.CTkEntry(input_frame, textvariable=blend_path, width=ENTRY_WIDTH).pack(side='left', padx=5)
    ctk.CTkButton(input_frame, text='参照', command=browse_blend_file, width=80).pack(side='left', padx=(5, 10))

    # 出力ディレクトリ
    output_frame = ctk.CTkFrame(file_frame)
    output_frame.pack(fill='x', padx=20, pady=(5, 10))

    ctk.CTkLabel(output_frame, text='出力ディレクトリ:').pack(side='left', padx=(10, 5))
    ctk.CTkEntry(output_frame, textvariable=output_dir, width=ENTRY_WIDTH).pack(side='left', padx=5)
    ctk.CTkButton(output_frame, text='参照', command=browse_output_dir, width=80).pack(side='left', padx=(5, 10))

    # オプション設定
    opt_frame = ctk.CTkFrame(main_frame)
    opt_frame.pack(fill='x', padx=PADDING_X, pady=PADDING_Y)

    ctk.CTkLabel(opt_frame, text='エクスポートオプション', font=('', 16, 'bold')).pack(pady=(10, 5))

    # オプションのコンテナ
    options_container = ctk.CTkFrame(opt_frame)
    options_container.pack(fill='x', padx=20, pady=(5, 10))

    # UE5モード
    ue5_frame = ctk.CTkFrame(options_container)
    ue5_frame.pack(fill='x', pady=5)
    ctk.CTkCheckBox(ue5_frame, text='UE5モード', variable=ue5_mode).pack(side='left', padx=(10, 20))
    ctk.CTkLabel(ue5_frame, text='座標系とスケールをUE5向けに変換、メッシュを三角形化',
                 text_color='gray').pack(side='left')

    # モディファイヤ適用
    mod_frame = ctk.CTkFrame(options_container)
    mod_frame.pack(fill='x', pady=5)
    ctk.CTkCheckBox(mod_frame, text='モディファイヤ適用', variable=apply_mods).pack(side='left', padx=(10, 20))
    ctk.CTkLabel(mod_frame, text='全モディファイヤを適用してエクスポート(元ファイルは変更されません)',
                 text_color='gray').pack(side='left')

    # マテリアル最適化
    mat_frame = ctk.CTkFrame(options_container)
    mat_frame.pack(fill='x', pady=5)
    ctk.CTkCheckBox(mat_frame, text='マテリアル最適化', variable=opt_materials).pack(side='left', padx=(10, 20))
    ctk.CTkLabel(mat_frame, text='同一設定のマテリアルを統合して出力',
                 text_color='gray').pack(side='left')

    # テクスチャコピー
    tex_frame = ctk.CTkFrame(options_container)
    tex_frame.pack(fill='x', pady=5)
    ctk.CTkCheckBox(tex_frame, text='テクスチャコピー', variable=copy_tex).pack(side='left', padx=(10, 20))
    ctk.CTkLabel(tex_frame, text='使用テクスチャをtexturesフォルダにコピーし、パスを相対化',
                 text_color='gray').pack(side='left')

    # 実行ボタン
    ctk.CTkButton(root, text='エクスポート実行', command=export, height=40).pack(pady=PADDING_Y)

    # ログ出力エリア
    log_frame = ctk.CTkFrame(root)
    log_frame.pack(fill='both', expand=True, padx=PADDING_X, pady=PADDING_Y)

    ctk.CTkLabel(log_frame, text='実行ログ', font=('', 14, 'bold')).pack(pady=(5, 0))

    log_text = ctk.CTkTextbox(log_frame, height=LOG_HEIGHT)
    log_text.pack(fill='both', expand=True, padx=5, pady=5)

    root.mainloop()


# メイン処理
if __name__ == '__main__':
    create_gui()

実験・研究スキルの基礎:Blender OBJ/MTLエクスポートで学ぶ3Dデータ変換実験

1. 実験・研究のスキル構成要素

実験や研究を行うには、以下の5つの構成要素を理解する必要がある。

1.1 実験用データ

このプログラムではBlenderファイル(.blend形式)が実験用データである。3Dモデル、マテリアル、テクスチャを含むファイルを使用する。

1.2 実験計画

何を明らかにするために実験を行うのかを定める。

計画例:

1.3 プログラム

実験を実施するためのツールである。このプログラムはBlender Python API(bpy)とcustomtkinterを使用している。

1.4 プログラムの機能

このプログラムは4つのオプションで3Dデータのエクスポートを制御する。

入力パラメータ:

出力情報:

オプションの動作:

1.5 検証(結果の確認と考察)

プログラムの実行結果を観察し、オプションの影響を考察する。

基本認識:

観察のポイント:

2. 間違いの原因と対処方法

2.1 プログラムのミス(人為的エラー)

「Blenderが見つかりません」エラーが表示される

GUIが表示されない

エクスポート処理が完了しない

2.2 期待と異なる結果が出る場合

UE5でインポートしたモデルの向きがおかしい

UE5でインポートしたモデルのサイズが合わない

テクスチャが表示されない

出力ファイルサイズが予想より大きい

モディファイヤの効果が反映されていない

3. 実験レポートのサンプル

エクスポートオプションが出力ファイルに与える影響の調査

実験目的:

各エクスポートオプションがOBJファイルのサイズと品質に与える影響を測定し、用途に応じた設定を見つける。

実験計画:

同一のBlenderファイルを使用し、オプションの組み合わせを変えてエクスポートを行い、結果を比較する。

実験方法:

プログラムを実行し、以下の基準で評価する:

実験結果:

UE5モード モディファイヤ適用 マテリアル最適化 OBJサイズ(MB) マテリアル数 備考
OFF OFF OFF xxxx xxxx 基準値
ON OFF OFF xxxx xxxx 三角形化による増加
OFF ON OFF xxxx xxxx モディファイヤ反映
OFF OFF ON xxxx xxxx マテリアル統合
ON ON ON xxxx xxxx 全オプション有効

考察:

結論:

(例文)UE5へのインポートを目的とする場合は、UE5モードを有効にすることで座標系とスケールの問題を回避できた。ファイルサイズを抑えたい場合はマテリアル最適化が有効であった。モディファイヤ適用は見た目の再現に必要だが、ファイルサイズ増加とのトレードオフがある。用途に応じてオプションを選択する必要性が確認できた。