HOME

Maneki Pipeline

ゲーム開発のためのプロシージャルワークフロー

AI生成した3Dモデルのパイプライン④:一覧資料の作成

AI生成したモデルをゲームエンジンで利用できるようなパイプラインのプロトを作る。 プロトタイプということでコードはあまり整理していません。一例としてご覧ください。

前回の記事

AI生成した3Dモデルのパイプライン③:コンバートパイプラインのプロトタイプ
https://www.procedural.jp/articles/b2v9og0110q4

今回は前回記事の最後で端折ったディレクトリ以下の情報をスプレッドシートにまとめている下り。

資料化したい情報をjsonとして書き出す

Houdini上でPythonを追加し出力したいアトリビュートを追加したい。
ひとまず元メッシュのポリゴン数と変換後のメッシュを保存する想定でjsonを出力する。

元メッシュ読み込み後にwrangleを追加し、ポリゴン数をdetailアトリビュートに追加。

i@source_primnum = @numprim;
元モデルのポリゴン数を参照

rop fbxの前にも同様にポリゴン数カウントを追加

i@fix_primnum = @numprim;
変換後モデルのポリゴン数を参照

rop_fbxの前に Pythonノードを追加し、json出力用のコードを追加

import json

setting_node = hou.node("../SETTING")
setting_geo = setting_node.geometry()

source_model_node = hou.node("../RAW_MODEL")
source_model_geo = source_model_node.geometry()

fix_model_node = hou.node("../FIX_MODEL")
fix_model_geo = fix_model_node.geometry()

detail = {
    "source_primnum": source_model_geo.attribValue("source_primnum"),
    "fix_primnum": fix_model_geo.attribValue("fix_primnum")}

export_path = setting_geo.attribValue("export_detail_json_file_path")
with open(export_path, 'w') as f:
    json.dump(detail, f)

処理内の情報をjsonとして保存するためのPythonノード

コンバート時にノード内の情報を出力するための準備ができた。

openpyxlで一覧資料を作成

環境にopenpyxlと画像を追加したいのでpillowを追加し、

環境にopenpyxlとpillowを追加

コードを実行(Houdiniの外で実行しています)

"""
作成した画像ファイルとjsonデータをまとめた表を作成する
"""
import os
import json

import openpyxl
from openpyxl import styles
from openpyxl.utils import get_column_letter
from openpyxl.drawing import image

def create_summary_sheet(root_directory_path: str):
    """画像ファイルとdetail.jsonをまとめたExcelシートを作成する

    Args:
        root_directory_path (str): ルートディレクトリのパス
    """
    workbook = openpyxl.Workbook()
    sheet = workbook.active

    # ヘッダー行の作成とスタイルの適用
    headers = ["名前", "生成モデル", "変換モデル", "生成モデルポリゴン数", "変換モデルポリゴン数"]
    sheet.append(headers)
    header_font = styles.Font(bold=True)
    header_fill = styles.PatternFill(start_color="AFCDAF", end_color="AFCDAF", fill_type="solid")
    for col_num, header in enumerate(headers, 1):
        cell = sheet.cell(row=1, column=col_num, value=header)
        cell.font = header_font
        cell.fill = header_fill

    # 罫線のスタイル定義
    thin_border = styles.Border(right=styles.Side(style="thin", color="000000"))
    thick_border = styles.Border(bottom=styles.Side(style="thick", color="000000"))

    # 0行目と1行目の間に太い線を引く (ヘッダー下)
    for col_num in range(1, len(headers) + 1):
        sheet.cell(row=1, column=col_num).border = thick_border

    # ヘッダーの固定
    sheet.freeze_panes = "A2"

    row_num = 2  # データ書き込み開始行

    # ルートフォルダ内の各ディレクトリを走査
    for model_dir_name in os.listdir(root_directory_path):
        model_dir_path = os.path.join(root_directory_path, model_dir_name)

        # モデルディレクトリであるか確認
        if os.path.isdir(model_dir_path):
            image_files = sorted([f for f in os.listdir(model_dir_path)
                                  if f.lower().endswith((".png", ".jpg", ".jpeg"))])
            detail_file = os.path.join(model_dir_path, "detail.json")
            detail_data = {}

            # 0列目の色調を変更
            first_col_cell = sheet.cell(row=row_num, column=1, value=model_dir_name)
            first_col_fill = styles.PatternFill(
                start_color="AFAFCD", end_color="AFAFCD", fill_type="solid") # Light Gray
            first_col_cell.fill = first_col_fill

            # 0列目と1列目の間に細い線を引く
            sheet.cell(row=row_num, column=1).border = thin_border

            # detail.json が存在する場合
            if os.path.exists(detail_file):
                with open(detail_file, "r", encoding="utf-8") as f:
                    try:
                        detail_data = json.load(f)
                    except Exception as e:
                        print(f"エラーが発生しました: {e}")
            elif os.path.exists(detail_file.replace(".json", ".JSON")):
                detail_file_upper = detail_file.replace(".json", ".JSON")
                with open(detail_file_upper, "r", encoding="utf-8") as f:
                    try:
                        detail_data = json.load(f)
                    except Exception as e:
                        print(f"エラーが発生しました: {e}")
            else:
                # detail.json が存在していない
                pass

            # 画像を最大2つまで同じ行に追加
            for i, image_filename in enumerate(image_files[:2]):
                image_path = os.path.join(model_dir_path, image_filename)
                col_num = 2 + i  # B列から配置

                try:
                    img = image.Image(image_path)
                    target_width = 120
                    target_height = 90
                    img.width = target_width
                    img.height = target_height
                    anchor_cell = get_column_letter(col_num) + str(row_num)
                    sheet.add_image(img, anchor_cell)
                    sheet.row_dimensions[row_num].height = target_height * 1.2
                    col_letter = get_column_letter(col_num)
                    sheet.column_dimensions[col_letter].width = target_width / 7.0
                except Exception as e:
                    print(f"エラーが発生しました: {e}")

            # detail.json の特定のキーの値を書き込み
            source_polygon_num = detail_data.get("source_primnum", "")
            converted_polygon_num = detail_data.get("fix_primnum", "")
            sheet.cell(row=row_num, column=4, value=source_polygon_num)
            sheet.cell(row=row_num, column=5, value=converted_polygon_num)

            row_num += 1

    # 列幅の自動調整 (モデルディレクトリ名と特定キーの列)
    sheet.column_dimensions["A"].auto_size = True
    sheet.column_dimensions["D"].auto_size = True
    sheet.column_dimensions["E"].auto_size = True

    # ワークブックを保存
    output_excel_file = os.path.join(root_directory_path, "image_detail_summary.xlsx")
    workbook.save(output_excel_file)

# 実行例
root = "D:/resource/meshyai/lib"
create_summary_sheet(root)

スプレッドシートが生成されました(自分の家の環境にはExcelが無いのでGoogleSpreadSheetで確認しています)

生成されたシートをGoogleスプレッドシートで確認


仕組みのベースのうち、スプレッドシート作成の部分についてです。
実パイプラインに落とし込んでいくにあたり、必要なアトリビュートなどの情報を追加します。

fish_ball

プロシージャル魚類