Skip to content

Commit

Permalink
feat: show blend file compatibility warning
Browse files Browse the repository at this point in the history
  • Loading branch information
saturday06 committed Jul 19, 2024
1 parent b5ce9e5 commit 4c61312
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 9 deletions.
16 changes: 16 additions & 0 deletions src/io_scene_vrm/common/ops/vrm.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ def model_validate(
)


def show_blend_file_vrm_addon_compatibility_warning(
execution_context: str = "EXEC_DEFAULT",
) -> set[str]:
return bpy.ops.vrm.show_blend_file_vrm_addon_compatibility_warning( # type: ignore[attr-defined, no-any-return]
execution_context,
)


def show_blend_file_compatibility_warning(
execution_context: str = "EXEC_DEFAULT",
) -> set[str]:
return bpy.ops.vrm.show_blend_file_compatibility_warning( # type: ignore[attr-defined, no-any-return]
execution_context,
)


def load_human_bone_mappings(
execution_context: str = "EXEC_DEFAULT",
/,
Expand Down
8 changes: 8 additions & 0 deletions src/io_scene_vrm/editor/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from bpy.app.handlers import persistent

from . import migration


@persistent
def load_post(_unsed: object) -> None:
migration.state.blend_file_compatibility_warning_shown = False
76 changes: 75 additions & 1 deletion src/io_scene_vrm/editor/migration.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import functools
from typing import Optional
from dataclasses import dataclass
from typing import Final, Optional

import bpy
from bpy.types import Armature, Context

from ..common import ops
from ..common.logging import get_logger
from ..common.preferences import get_preferences
from ..common.version import addon_version
Expand All @@ -24,6 +26,14 @@
logger = get_logger(__name__)


@dataclass
class State:
blend_file_compatibility_warning_shown: bool = False


state: Final = State()


def is_unnecessary(armature_data: Armature) -> bool:
ext = get_armature_extension(armature_data)
return (
Expand Down Expand Up @@ -114,6 +124,7 @@ def migrate_all_objects(
context, context.scene.name
)
mtoon1_migration.migrate(context)
validate_blend_file_version_compatibility(context)

preferences = get_preferences(context)

Expand All @@ -126,6 +137,69 @@ def migrate_all_objects(
preferences.addon_version = updated_addon_version


def validate_blend_file_version_compatibility(context: Context) -> None:
"""新しいBlenderで作成されたファイルを古いBlenderで編集しようとした場合に警告をする.
アドオンの対応バージョンの事情で新しいBlenderで編集されたファイルを古いBlenderで編集しようとし、
それによりシェイプキーが壊れるなどの報告がよく上がる。警告を出すことでユーザーに注意を促す。
"""
if not context.blend_data.filepath:
return
if not have_vrm_model(context):
return

blend_file_major_minor_version = (
context.blend_data.version[0],
context.blend_data.version[1],
)
current_major_minor_version = (bpy.app.version[0], bpy.app.version[1])
if blend_file_major_minor_version <= current_major_minor_version:
return

logger.error(
"Opening incompatible file: file_blender_version=%s running_blender_version=%s",
context.blend_data.version,
bpy.app.version,
)

if not state.blend_file_compatibility_warning_shown:
state.blend_file_compatibility_warning_shown = True
# Blender 4.2.0ではtimerで実行しないとダイアログが自動で消えるのでタイマーを使う
bpy.app.timers.register(
show_blend_file_compatibility_warning,
first_interval=0.1,
)


def show_blend_file_compatibility_warning() -> None:
ops.vrm.show_blend_file_compatibility_warning("INVOKE_DEFAULT")


def have_vrm_model(context: Context) -> bool:
for obj in context.blend_data.objects:
if obj.type != "ARMATURE":
continue
armature = obj.data
if not isinstance(armature, Armature):
continue

# https://github.com/saturday06/VRM-Addon-for-Blender/blob/2_0_3/io_scene_vrm/editor/migration.py#L372-L373
ext = get_armature_extension(armature)
if tuple(ext.addon_version) > (2, 0, 1):
return True

# https://github.com/saturday06/VRM-Addon-for-Blender/blob/0_79/importer/model_build.py#L731
humanoid_params_key = obj.get("humanoid_params")
if not isinstance(humanoid_params_key, str):
continue
# https://github.com/saturday06/VRM-Addon-for-Blender/blob/0_79/importer/model_build.py#L723-L726
if not humanoid_params_key.startswith(".json"):
continue

return True
return False


def on_change_bpy_object_name() -> None:
context = bpy.context

Expand Down
6 changes: 6 additions & 0 deletions src/io_scene_vrm/editor/mtoon1/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from ...common.logging import get_logger
from ..extension import get_material_extension
from . import migration
from .ops import VRM_OT_refresh_mtoon1_outline

logger = get_logger(__name__)
Expand Down Expand Up @@ -72,3 +73,8 @@ def save_pre(_unused: object) -> None:
@persistent
def depsgraph_update_pre(_unused: object) -> None:
trigger_update_mtoon1_outline()


@persistent
def load_post(_unsed: object) -> None:
migration.state.material_blender_4_2_warning_shown = False
27 changes: 19 additions & 8 deletions src/io_scene_vrm/editor/mtoon1/migration.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import functools
from dataclasses import dataclass
from typing import Final, Optional

import bpy
Expand Down Expand Up @@ -38,6 +39,14 @@
logger = get_logger(__name__)


@dataclass
class State:
material_blender_4_2_warning_shown: bool = False


state: Final = State()


def show_material_blender_4_2_warning_delay(material_name_lines: str) -> None:
ops.vrm.show_material_blender_4_2_warning(
"INVOKE_DEFAULT",
Expand All @@ -62,14 +71,16 @@ def migrate(context: Context) -> None:
bpy.app.version,
)

# Blender 4.2.0ではtimerで実行しないとダイアログが自動で消える
bpy.app.timers.register(
functools.partial(
show_material_blender_4_2_warning_delay,
"\n".join(blender_4_2_migrated_material_names),
),
first_interval=0.1,
)
if not state.material_blender_4_2_warning_shown:
state.material_blender_4_2_warning_shown = True
# Blender 4.2.0ではtimerで実行しないとダイアログが自動で消える
bpy.app.timers.register(
functools.partial(
show_material_blender_4_2_warning_delay,
"\n".join(blender_4_2_migrated_material_names),
),
first_interval=0.1,
)


def migrate_material(
Expand Down
82 changes: 82 additions & 0 deletions src/io_scene_vrm/editor/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from urllib.parse import urlparse

import bpy
from bpy.app.translations import pgettext
from bpy.props import StringProperty
from bpy.types import Armature, Context, Event, Mesh, Operator, UILayout
from bpy_extras.io_utils import ExportHelper, ImportHelper
Expand Down Expand Up @@ -351,6 +352,87 @@ def execute(self, _context: Context) -> set[str]:
url: str # type: ignore[no-redef]


class VRM_OT_show_blend_file_compatibility_warning(Operator):
bl_idname = "vrm.show_blend_file_compatibility_warning"
bl_label = "File Compatibility Warning"
bl_description = "Show Blend File Compatibility Warning"
bl_options: AbstractSet[str] = {"REGISTER"}

def execute(self, _context: Context) -> set[str]:
return {"FINISHED"}

def invoke(self, context: Context, _event: Event) -> set[str]:
return context.window_manager.invoke_props_dialog(self, width=500)

def draw(self, context: Context) -> None:
app_version = str(bpy.app.version[0]) + "." + str(bpy.app.version[1])
file_version = (
str(context.blend_data.version[0])
+ "."
+ str(context.blend_data.version[1])
)
column = self.layout.row(align=True).column()
text = pgettext(
"The current file is not compatible with the running Blender.\n"
+ "The file was created in Blender {file_version}, but the running Blender"
+ " version is {app_version}.\n"
+ "So it is not compatible. As a result some data may be lost or corrupted."
).format(
app_version=app_version,
file_version=file_version,
)
description_outer_column = column.column()
description_outer_column.emboss = "NONE"
description_column = description_outer_column.box().column(align=True)
for i, line in enumerate(text.splitlines()):
icon = "ERROR" if i == 0 else "NONE"
description_column.label(text=line, translate=False, icon=icon)
open_url = layout_operator(
self.layout,
VRM_OT_open_url_in_web_browser,
text="Open Documentation",
icon="URL",
)
open_url.url = "https://developer.blender.org/docs/handbook/guidelines/compatibility_handling_for_blend_files/#forward-compatibility"


class VRM_OT_show_blend_file_vrm_addon_compatibility_warning(Operator):
bl_idname = "vrm.show_blend_file_vrm_addon_compatibility_warning"
bl_label = "VRM Add-on Compatibility Warning"
bl_description = "Show Blend File and VRM Add-on Compatibility Warning"
bl_options: AbstractSet[str] = {"REGISTER"}

def execute(self, _context: Context) -> set[str]:
return {"FINISHED"}

def invoke(self, context: Context, _event: Event) -> set[str]:
return context.window_manager.invoke_props_dialog(self, width=500)

def draw(self, context: Context) -> None:
app_version = str(bpy.app.version[0]) + "." + str(bpy.app.version[1])
file_version = (
str(context.blend_data.version[0])
+ "."
+ str(context.blend_data.version[1])
)
column = self.layout.row(align=True).column()
text = pgettext(
"The current file is not compatible with the current VRM Add-on.\n"
+ "The file was created in VRM Add-on {file_version}, but the current"
+ " VRM Add-on version is {app_version}.\n"
+ "So it is not compatible. As a result some data may be lost or corrupted."
).format(
current_version=app_version,
file_version=file_version,
)
description_outer_column = column.column()
description_outer_column.emboss = "NONE"
description_column = description_outer_column.box().column(align=True)
for i, line in enumerate(text.splitlines()):
icon = "ERROR" if i == 0 else "NONE"
description_column.label(text=line, translate=False, icon=icon)


__Operator = TypeVar("__Operator", bound=Operator)


Expand Down
24 changes: 24 additions & 0 deletions src/io_scene_vrm/locale/ja_jp.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,4 +590,28 @@
+ "されるため、そのファイルはマテリアルの設定を失わずに"
+ "Blender 4.2以降でも開けるようになります。\n"
+ "影響のある可能性のあるマテリアルは次の通りです。",
(
"Operator",
"File Compatibility Warning",
): "ファイルの互換性の警告",
(
"*",
"The current file is not compatible with the running Blender.\n"
+ "The file was created in Blender {file_version}, but the running Blender"
+ " version is {app_version}.\n"
+ "So it is not compatible. As a result some data may be lost or corrupted.",
): "現在のファイルは実行中のBlenderと互換性がありません。\n"
+ "現在のファイルはBlender {file_version}で作られたファイルですが、"
+ "起動中のBlenderの\n"
+ "バージョンは{app_version}のため互換性ありません。そのため、"
+ "一部のデータが消えたり\n"
+ "壊れたりすることがあります。",
("VrmAddon", "Open Documentation"): "関連ドキュメントを開く",
(
"*",
"The current file is not compatible with the current VRM Add-on.\n"
+ "The file was created in VRM Add-on {file_version}, but the current"
+ " VRM Add-on version is {app_version}.\n"
+ "So it is not compatible. As a result some data may be lost or corrupted.",
): "VRMアドオンのバージョンが合ってないっす {file_version} vs {app_version}",
}
7 changes: 7 additions & 0 deletions src/io_scene_vrm/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from .editor import (
detail_mesh_maker,
extension,
handler,
make_armature,
make_mesh_from_bone_envelopes,
migration,
Expand Down Expand Up @@ -417,6 +418,8 @@ def save_pre(_unused: object) -> None:
ops.VRM_OT_open_url_in_web_browser,
ops.VRM_OT_save_human_bone_mappings,
ops.VRM_OT_load_human_bone_mappings,
ops.VRM_OT_show_blend_file_compatibility_warning,
ops.VRM_OT_show_blend_file_vrm_addon_compatibility_warning,
validation.VrmValidationError,
validation.WM_OT_vrm_validator,
export_scene.WM_OT_vrm_export_human_bones_assignment,
Expand Down Expand Up @@ -485,6 +488,8 @@ def register() -> None:
VIEW3D_MT_armature_add.append(panel.add_armature)
# VIEW3D_MT_mesh_add.append(panel.make_mesh)

bpy.app.handlers.load_post.append(handler.load_post)
bpy.app.handlers.load_post.append(mtoon1_handler.load_post)
bpy.app.handlers.load_post.append(load_post)
bpy.app.handlers.depsgraph_update_pre.append(
depsgraph_update_pre_once_if_load_post_is_unavailable
Expand Down Expand Up @@ -534,6 +539,8 @@ def unregister() -> None:
depsgraph_update_pre_once_if_load_post_is_unavailable
)
bpy.app.handlers.load_post.remove(load_post)
bpy.app.handlers.load_post.remove(mtoon1_handler.load_post)
bpy.app.handlers.load_post.remove(handler.load_post)

# VIEW3D_MT_mesh_add.remove(panel.make_mesh)
VIEW3D_MT_armature_add.remove(panel.add_armature)
Expand Down

0 comments on commit 4c61312

Please sign in to comment.