2019年12月20日金曜日

画像素材の自動更新アドオン

Blenderのテクスチャを他のソフトで作成している場合に
画像の更新のために手動で更新をするのも煩わしいので
データの更新を検出して再読み込みをさせるアドオンを作成しました
その使用法とPhotoshopの便利な機能「画像アセット」について解説したいと思います

Photoshopの「画像アセット」

Photoshopにはレイヤにつけた名前に従って自動的に画像を書き出す機能があります。

例えば グループレイヤに01.png 02.pngと名前をつけておくと
PSDの保存されている場所にPSDの名前に-assetsとついたフォルダが作られて
それぞれのグループを統合した画像で 01.png 02.png といった感じに出力されます

PSDのレイヤを編集するとその都度変更が自動出力されます
Web用の機能なので 3Dのテクスチャ用で出力できる種類はPNGのみですが
自動的に出力されるのは便利かと思います。
ファイルの名前の付け方によっては 画像を縮小したりもできます

Blenderの自動更新アドオン

さて、冒頭に話をした自動更新アドオンです。
Blenderに読み込んでいる画像ファイルの更新を検出して再読み込みします
他にも同様な機能なものはあるようですが、ミニマムな構成にしてみました。
長いデータなので boothにダウンロード用ファイルを登録しました
bl_info = {
    "name" : "AutoReloadImage",
    "author" : "Yukimituki",
    "description" : "",
    "blender" : (2, 80, 0),
    "version" : (0, 0, 1),
    "location" : "",
    "warning" : "",
    "category" : "Image"
}

import bpy
import os
from bpy.types import (
        PropertyGroup,
        AddonPreferences,
        )

class IMAGE_PT_AutoReload(bpy.types.Panel):
    bl_idname = "autoreload.panel"
    bl_label = "Reload Images"
    bl_space_type = "IMAGE_EDITOR"
    bl_region_type = "UI"
    bl_category = "Image"
    # 描画の定義
    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.prop(context.scene, "imageassets_autoreload_time")
        row = layout.row()
        row.prop(context.scene, "imageassets_autoreload_active")
        row = layout.row()
        row.operator("image.reloadbutton")


class ModalTimerOperator(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.modal_timer_operator"
    bl_label = "Modal Timer Operator"

    _timer = None
    asset_dic = {}
    def __init__(self):
        asset_dic = self.asset_dic
        self.asset_dic = set_asset_dic(asset_dic)

    def modal(self, context, event):
        #check ImageEditor panel value
        if not context.scene.imageassets_autoreload_active:
            context.window_manager.event_timer_remove(self._timer)
            return {'FINISHED'}

        if event.type == 'TIMER':
            asset_dic = self.asset_dic
            reload_assets(asset_dic)
            self.asset_dic = set_asset_dic(asset_dic)
        return {'PASS_THROUGH'}

    def execute(self, context):
        wm = context.window_manager
        reload_cycle = context.scene.imageassets_autoreload_time
        self._timer = wm.event_timer_add(reload_cycle, window=context.window)
        wm.modal_handler_add(self)
        self.asset_dic = set_asset_dic({})
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)
#手動リロード処理
class OBJECT_OT_reloadbutton(bpy.types.Operator):
    bl_idname = "image.reloadbutton"
    bl_label = "Manually update"
    def execute(self, context):
        for img in bpy.data.images:
            #only single image file
            if img.type != 'IMAGE': continue
            img.reload()
            set_asset_dic({})
        return {'FINISHED'}
#自動リロード処理
def reload_assets(asset_dic):
    for image_obj in asset_dic:
        try:
            #only single image file
            if image_obj.type != 'IMAGE': continue
            image_path = bpy.path.abspath(image_obj.filepath)
            #チェック時にファイル保存が重なると一時的にファイルが認識できないため
            if os.path.lexists(image_path) == False: continue
            #更新時刻の取得 get modefied time
            mod_time = os.stat(image_path).st_mtime_ns
            if mod_time != asset_dic[image_obj]:
                image_obj.reload()
                asset_dic[image_obj] = mod_time
                print("reload:%s" % image_obj.name )
        except:pass
#ファイル内のイメージデータブロックの情報を記録
def set_asset_dic(asset_dic):
    if len(bpy.data.images) == len(asset_dic): return(asset_dic)
    for img in bpy.data.images:
        if img.type != 'IMAGE':
            image_path = bpy.path.abspath(img.filepath)
            #get modefied time as nanosec(int)
            asset_dic[img] = os.stat(image_path).st_mtime_ns
        else:asset_dic[img] = 0
    return(asset_dic)

def update_bool_func(self, context):
    if context.scene.imageassets_autoreload_active:
        bpy.ops.wm.modal_timer_operator()

classes = (
    ModalTimerOperator,
    IMAGE_PT_AutoReload,
    OBJECT_OT_reloadbutton
    )
#アドオンとして機能させるための情報の登録
def register():
    #properties
    Scene = bpy.types.Scene
    props = bpy.props
    Scene.imageassets_autoreload_active = props.BoolProperty(name="Auto Reload", description="", default=False, update = update_bool_func)
    Scene.imageassets_autoreload_time = props.IntProperty(name="Reload Cycle",description="", default=10 )
    #classes
    for cls in classes:
        bpy.utils.register_class(cls)
#アドオンを停止させた時に情報の削除
def unregister():
    #properties
    Scene = bpy.types.Scene
    props = bpy.props
    del Scene.imageassets_autoreload_active
    del Scene.imageassets_autoreload_time
    #classes
    for cls in classes:
        bpy.utils.unregister_class(cls)

if __name__ == "__main__":
    register()

(Blender2.8用ですが5行目の "blender" : (2, 80, 0),の数字を(2,29,0)に書き換えることで2.79でも動作します )
アドオンとして読み込むと
画像エディッタやUVエディッタの画像タブに「Reload Images」の項目が出ます
Auto ReloadのチェックボックスをオンにするとReload Cycleの秒数毎にファイルをチェックしますManually updateのボタンで手動で更新することもできます。

先にPhotoshopの話をしましたが 画像ファイルの更新日時を検出する動作ですので
画像を作成するソフトは何でも構いません。

ゲームのモデル等 テクスチャを描く作業をしている人お役に立てれば幸いです。

1 件のコメント:

  1. このコメントはブログの管理者によって削除されました。

    返信削除