UE5 python Google SpreadSheetからCSVを自動でインポートする

やあ

前回の続きだよ。
pto8913.hatenablog.com

前回はGoogle SpreadSheetから、pythonを介してデータを取ってこられるようになった。

今回はUnreal Engineのエディタでボタンを押すだけで、Google SpreadSheetからシートをcsvでダウンロードして、そのcsvと一致するDataTableを再インポートできるようにするよ。

前提条件

DataTableの名前 = "Google SpreadSheetのタイトル"_-_"シートの名前"
の形になっていること
DataTableの構造とGoogle SpreadSheetの構造が一致していること

pythonからGoogle SpreadSheetをcsvでダウンロードする

サービスアカウントのメールアドレスに共有したGoogle SpreadSheetからすべてのWorkSheetをダウンロードしてみる。

import json
import gspread
import csv
import os

ROOT_DIR = os.path.abspath(os.curdir) + "/"
SCRIPT_DIR = f'{ROOT_DIR}Scripts/'

# 前回作ったサービスアカウントのjsonファイル!
gc = gspread.service_account(f'{SCRIPT_DIR}service_account.json')

# サービスアカウントのメールアドレスに共有したSpreadSheetの一覧を取得
for row in gc.openall():
    spread_sheet = gc.open(row.title)
    for worksheet in spread_sheet.worksheets():
      filename = f'{spread_sheet.title}_-_{work_sheet.title}.csv'
      with open(f'{ダウンロードする場所}/{filename}', 'w', encoding='utf-8') as f:
          writer = csv.writer(f)
          writer.writerows(work_sheet.get_all_values())

動かすたびに全部ダウンロードするのはうれしくない!
なので特定のファイルをダウンロードする

特定のGoogle SpreadSheetから特定のWorkSheetをcsvでダウンロード

SpreadSheetのタイトルでSpreadSheetを指定 タイトルはこの部分!

spread_sheet = gc.open(SpreadSheetのタイトル)

もしくは

# https://docs.google.com/spreadsheets/d/{この部分がSpreadSheetのId}/edit#gid={この部分がWorkSheetのId}
spread_sheet = gc.open_by_key(SpreadSheetのId)
work_sheet = spreadsheet.get_worksheet_by_id(WorkSheetのId)

これでさっきみたいにcsv.writerでダウンロードする

いちいちタイトルやIdなんかを調べるのは大変だ・・・
なので次はタイトルやIdをjsonに保存して、EditorUtilityWidgetを使ってjsonをエディタでみられるようにする!

タイトルとIdのリストを作る!

jsonに保存する

こんな構造でjsonに書き込む

{
    spreadsheet1:
    {
        worksheet1: id,
        worksheet2: id
    },
    spreadsheet2:
    {
        worksheet1: id
    }
}
def RecreateSheetJson():
    titles = {}
    for row in gc.openall():
        sheets = {}
        spread_sheet = gc.open(row.title)
        for work_sheet in spread_sheet.worksheets():
            sheets[work_sheet.title] = work_sheet.id
        titles[row.title] = sheets
    with open(f'{SCRIPT_DIR}sheet.json', 'w', encoding='utf-8') as f:
        json.dump(titles, f, indent=4)

Unreal Enginepythonjsonを使えるようにする

エディタの設定からpython Developer Modeを有効にする


PluginsからJson Blueprint Utilitiesを有効にする


エディタを再起動。

Unreal Enginepythonからgspreadを呼べるようにする

Ctrl+Rcmdコマンドプロンプトを開きます
開いたらUnreal Enginepythonpip install gspreadします

"C:\Program Files\Epic Games\UE_5.0\Engine\Binaries\ThirdParty\Python3\Win64\python.exe" -m pip install gspread

EditorUtilityWidgetでjsonのリストを表示する

Content Browserを右クリックしてEditorUtilityWidgetを作成
名前はEUW_SpreadSheet_Importにしたよ

ボタンを押してpythonの関数を呼んでみる

いい感じにボタンを配置します

ボタンのOnClickedイベントを実装する

pythonのファイルから関数を呼び出す!

sheet_to_json.py

import json
import unreal
import gspread

ROOT_DIR = unreal.Paths.project_dir()

SCRIPT_DIR = f'{ROOT_DIR}Scripts/'
SHEET_JSON = f'{SCRIPT_DIR}sheet.json'

gc = gspread.service_account(f'{SCRIPT_DIR}service_account.json')

def RecreateSheetJson():
    titles = {}
    for row in gc.openall():
        sheets = {}
        SpreadSheet = gc.open(row.title)
        for WorkSheet in SpreadSheet.worksheets():
            sheets[WorkSheet.title] = WorkSheet.id
        titles[row.title] = sheets
        
    with open(SHEET_JSON, 'w', encoding='utf-8') as f:
        json.dump(titles, f, indent=4)

実行するとProjectName/Scriptsの下にsheet.jsonができるはずだよ

リストを表示するためのUIを作る

Content Browserを右クリックしてWidget Blueprintを作る
名前はWBP_SpreadSheet_WorkSheet_List

SpreadSheetのタイトルをクリックしたらWorkSheetのリストが表示できるようにする

WorkSheetを表示するためのスロットUIを作る
名前はWBP_SpreadSheet_WorkSheet_Slot


WBP_SpreadSheet_WorkSheet_Listに実装


ボタンを押したらSpreadSheetのリストが表示されるようにする

EUW_SpreadSheet_ImportWBP_SpreadSheet_WorkSheet_Listを表示するためにちょっと変更

EUW_SpreadSheet_Importに実装


これでボタンを押したら、サービスアカウントのメールアドレスに共有したGoogle SpreadSheetが表示されるはずだよ

リストをクリックしたらSpreadSheetとWorkSheetの名前を指定できるようにする

WBP_SpreadSheet_WorkSheet_SlotOnClickedイベントを作成

WBP_SpreadSheet_WorkSheet_ListOnClicked イベントを作成

WBP_SpreadSheet_WorkSheet_ListOnExpansionChanged イベントを作成

WBP_SpreadSheet_WorkSheet_Slotのクリックイベントにバインド

EUW_SpreadSheet_Importをちょっと変更

EUW_SpreadSheet_Importにイベントを追加

WBP_SpreadSheet_WorkSheet_Listのイベントをバインド

これでボタンを押したら選択できるようになったはずだよ。

ボタンを押して再インポートできるようにする

sheet_to_json.py

import json
import unreal
import gspread
import csv
import os

ROOT_DIR = unreal.Paths.project_dir()

SCRIPT_DIR = f'{ROOT_DIR}Scripts/'
SHEET_JSON = f'{SCRIPT_DIR}sheet.json'

DATATABLE_DIR = f'{ROOT_DIR}DataTableSource/'
if not os.path.exists(DATATABLE_DIR):
    os.makedirs(DATATABLE_DIR, exist_ok=True)

gc = gspread.service_account(f'{SCRIPT_DIR}service_account.json')

def RecreateSheetJson():
    titles = {}
    for row in gc.openall():
        sheets = {}
        SpreadSheet = gc.open(row.title)
        for WorkSheet in SpreadSheet.worksheets():
            sheets[WorkSheet.title] = WorkSheet.id
        titles[row.title] = sheets
        
    with open(SHEET_JSON, 'w', encoding='utf-8') as f:
        json.dump(titles, f, indent=4)

def GetFileName(SpreadSheetTitle: str, WorkSheetTitle: str) -> str:
    return f'{SpreadSheetTitle}_-_{WorkSheetTitle}'

def GetDownloadName(DownloadDir: str, SpreadSheetTitle: str, WorkSheetTitle: str) -> str:
    return f'{DownloadDir}{GetFileName(SpreadSheetTitle,WorkSheetTitle)}.csv'

def DownloadWorkSheet(DownloadDir: str, SpreadSheetTitle: str, WorkSheetTitle: str) -> bool:
    with open(SHEET_JSON, 'r', encoding='utf-8') as f:
        Sheet_Json = json.load(f)
    if Sheet_Json[SpreadSheetTitle]:
        SpreadSheet = gc.open(SpreadSheetTitle)
        if not SpreadSheet:
            return False

        WorkSheet_Id = Sheet_Json[SpreadSheetTitle][WorkSheetTitle]
        WorkSheet = SpreadSheet.get_worksheet_by_id(WorkSheet_Id)
        if not WorkSheet:
            return False
        
        DOWNLOAD_NAME = GetDownloadName(DownloadDir, SpreadSheetTitle, WorkSheetTitle)
        with open(DOWNLOAD_NAME, 'w', newline='', encoding='utf-8') as f:
            writer = csv.writer(f)
            writer.writerows(WorkSheet.get_all_values())
            return True
    return False


def ReimportWorkSheet(EditorDataTableDir: str, SpreadSheetTitle: str, WorkSheetTitle: str, IsReDownload: bool = True) -> bool:
    DOWNLOAD_NAME = GetDownloadName(DATATABLE_DIR, SpreadSheetTitle, WorkSheetTitle)
    if IsReDownload:
        if not DownloadWorkSheet(DATATABLE_DIR, SpreadSheetTitle, WorkSheetTitle):
            return False

    if not os.path.exists(DOWNLOAD_NAME):
        if not DownloadWorkSheet(DATATABLE_DIR, SpreadSheetTitle, WorkSheetTitle):
            return False
 
    FILE_NAME = GetFileName(SpreadSheetTitle,WorkSheetTitle)
    if os.path.exists(DOWNLOAD_NAME):
        DTPaths = unreal.EditorAssetLibrary.list_assets(EditorDataTableDir)
        for DTPath in DTPaths:
            if FILE_NAME in DTPath:
                task = unreal.AssetImportTask()
                task.filename = DOWNLOAD_NAME
                task.destination_path = EditorDataTableDir
                task.replace_existing = True
                task.automated = True
                task.save = False

                csv_factory = unreal.CSVImportFactory()
                Obj = unreal.EditorAssetLibrary.load_asset(DTPath)
                DataTable = unreal.DataTable.cast(Obj)
                row_struct = DataTable.get_editor_property("row_struct")
                csv_factory.automated_import_settings.import_row_struct = row_struct

                task.factory = csv_factory

                asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
                asset_tools.import_asset_tasks([task])
                return True
    return False

def ReimportAllWorkSheet(EditorDataTableDir: str, SpreadSheetTitle: str, IsReDownload: bool = True) -> bool:
    SpreadSheet = gc.open(SpreadSheetTitle)
    for WorkSheet in SpreadSheet.worksheets():
        ReimportWorkSheet(EditorDataTableDir, SpreadSheetTitle, WorkSheet.title, IsReDownload)
    return True

EUW_SpreadSheet_Importにボタンを追加

OnClickedイベントを作成

これでボタンを押したら指定したGoogle SpreadSheetを再インポートできるよ

完全版

プラグインにした

github.com