Skip to content

Commit

Permalink
feat: show progress for heavy task
Browse files Browse the repository at this point in the history
  • Loading branch information
saturday06 committed Jul 21, 2024
1 parent c4075eb commit 25f2c7f
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 64 deletions.
78 changes: 78 additions & 0 deletions src/io_scene_vrm/common/progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import math
from collections.abc import Iterator
from contextlib import contextmanager
from typing import Final, Optional
from uuid import uuid4

from bpy.types import Context

from .logging import get_logger

logger = get_logger(__name__)


class PartialProgress:
def __init__(
self, progress: "Progress", partial_start_ratio: float, partial_end_ratio: float
) -> None:
self.progress: Final = progress
self.partial_start_ratio: Final = partial_start_ratio
self.partial_end_ratio: Final = partial_end_ratio

def update(self, ratio: float) -> None:
ratio = min(max(0.0, ratio), 1.0)
self.progress.update(
self.partial_start_ratio
+ ratio * (self.partial_end_ratio - self.partial_start_ratio)
)


class Progress:
active_progress_uuid: Optional[str] = None

def __init__(self, context: Context, *, show_progress: bool) -> None:
self.context: Final = context
self.show_progress: Final = show_progress
self.uuid: Final = uuid4().hex
self.last_ratio = 0.0

def partial_progress(self, partial_end_ratio: float) -> PartialProgress:
partial_end_ratio = min(max(0.0, partial_end_ratio), 1.0)
return PartialProgress(self, self.last_ratio, partial_end_ratio)

def update(self, ratio: float) -> None:
ratio = min(max(0.0, ratio), 1.0)
self.last_ratio = ratio
if self.active_progress_uuid != self.uuid:
logger.error(
"Progress.update() called from different progress active=%s self=%s",
self.active_progress_uuid,
self.uuid,
)
return

if not self.show_progress:
return

# マウスカーソルが四桁の数値になり、0から9999までの値が表示できる領域がある
# しかし、進捗率をそのまま0から9999の数値に変換すると、下二桁の数値が頻繁に
# ラウンドトリップし進捗状況が分かりにくくなる。そのため、下二桁の表示領域
# だけを利用し0から99の数値で進捗率を表示する
self.context.window_manager.progress_update(math.floor(ratio * 99))


@contextmanager
def create_progress(
context: Context, *, show_progress: bool = True
) -> Iterator[Progress]:
saved_progress_uuid = Progress.active_progress_uuid
try:
if show_progress and Progress.active_progress_uuid is None:
context.window_manager.progress_begin(0, 9999)
progress = Progress(context, show_progress=show_progress)
Progress.active_progress_uuid = progress.uuid
yield progress
finally:
Progress.active_progress_uuid = saved_progress_uuid
if show_progress and Progress.active_progress_uuid is None:
context.window_manager.progress_end()
7 changes: 5 additions & 2 deletions src/io_scene_vrm/editor/migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ def migrate(context: Optional[Context], armature_object_name: str) -> bool:


def migrate_all_objects(
context: Context, *, skip_non_migrated_armatures: bool = False
context: Context,
*,
skip_non_migrated_armatures: bool = False,
show_progress: bool = False,
) -> None:
for obj in context.blend_data.objects:
if obj.type == "ARMATURE":
Expand All @@ -124,7 +127,7 @@ def migrate_all_objects(
VrmAddonSceneExtensionPropertyGroup.update_vrm0_material_property_names(
context, context.scene.name
)
mtoon1_migration.migrate(context)
mtoon1_migration.migrate(context, show_progress=show_progress)
validate_blend_file_compatibility(context)
validate_blend_file_addon_compatibility(context)

Expand Down
20 changes: 14 additions & 6 deletions src/io_scene_vrm/editor/mtoon1/migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from ...common import convert, ops, shader, version
from ...common.gl import GL_LINEAR, GL_NEAREST
from ...common.logging import get_logger
from ...common.progress import create_progress
from ..extension import get_material_extension
from .property_group import (
GL_LINEAR_IMAGE_INTERPOLATIONS,
Expand Down Expand Up @@ -54,12 +55,17 @@ def show_material_blender_4_2_warning_delay(material_name_lines: str) -> None:
)


def migrate(context: Context) -> None:
def migrate(context: Context, *, show_progress: bool = False) -> None:
blender_4_2_migrated_material_names: list[str] = []
for material in context.blend_data.materials:
if not material:
continue
migrate_material(context, material, blender_4_2_migrated_material_names)

with create_progress(context, show_progress=show_progress) as progress:
for material_index, material in enumerate(context.blend_data.materials):
if not material:
continue
migrate_material(context, material, blender_4_2_migrated_material_names)
progress.update(float(material_index) / len(context.blend_data.materials))
progress.update(1)

if (
blender_4_2_migrated_material_names
and tuple(context.blend_data.version) < (4, 2)
Expand Down Expand Up @@ -416,7 +422,9 @@ def migrate_material(
)

typed_mtoon1.setup_drivers()
typed_mtoon1.addon_version = version.addon_version()
updated_addon_version = version.addon_version()
if tuple(typed_mtoon1.addon_version) != updated_addon_version:
typed_mtoon1.addon_version = updated_addon_version


def backup_texture_info(texture_info: object) -> Optional[TextureInfoBackup]:
Expand Down
71 changes: 34 additions & 37 deletions src/io_scene_vrm/exporter/vrm0_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
from ..common.legacy_gltf import TEXTURE_INPUT_NAMES
from ..common.logging import get_logger
from ..common.mtoon_unversioned import MtoonUnversioned
from ..common.progress import PartialProgress, create_progress
from ..common.version import addon_version
from ..common.vrm0.human_bone import HumanBoneSpecifications
from ..common.workspace import save_workspace
Expand Down Expand Up @@ -119,42 +120,35 @@ def armature_data(self) -> Armature:
return armature_data

def export_vrm(self) -> Optional[bytes]:
wm = self.context.window_manager
wm.progress_begin(0, 8)

self.setup_mtoon_gltf_fallback_nodes(self.context, is_vrm0=True)

try:
with (
save_workspace(self.context),
self.setup_armature(),
self.clear_blend_shape_proxy_previews(self.armature_data),
self.hide_mtoon1_outline_geometry_nodes(self.context),
self.setup_pose(
self.armature,
self.armature_data,
get_armature_extension(self.armature_data).vrm0.humanoid,
),
):
wm.progress_update(1)
self.image_to_bin()
wm.progress_update(2)
self.make_scene_node_skin_dicts()
wm.progress_update(3)
self.material_to_dict()
wm.progress_update(4)
# 内部でcontext.view_layer.objects.active = meshをするので復元する
with save_workspace(self.context):
self.mesh_to_bin_and_dict()
wm.progress_update(5)
self.json_dict["scene"] = 0
self.gltf_meta_to_dict()
wm.progress_update(6)
self.vrm_meta_to_dict() # colliderとかmetaとか....
wm.progress_update(7)
self.pack()
finally:
wm.progress_end()
with (
create_progress(self.context) as progress,
save_workspace(self.context),
self.setup_armature(),
self.clear_blend_shape_proxy_previews(self.armature_data),
self.hide_mtoon1_outline_geometry_nodes(self.context),
self.setup_pose(
self.armature,
self.armature_data,
get_armature_extension(self.armature_data).vrm0.humanoid,
),
):
self.setup_mtoon_gltf_fallback_nodes(self.context, is_vrm0=True)
progress.update(0.1)
self.image_to_bin()
progress.update(0.2)
self.make_scene_node_skin_dicts()
progress.update(0.3)
self.material_to_dict()
progress.update(0.4)
# 内部でcontext.view_layer.objects.active = meshをするので復元する
with save_workspace(self.context):
self.mesh_to_bin_and_dict(progress.partial_progress(0.8))
self.json_dict["scene"] = 0
self.gltf_meta_to_dict()
progress.update(0.9)
self.vrm_meta_to_dict() # colliderとかmetaとか....
progress.update(1)
self.pack()
return self.result

@staticmethod
Expand Down Expand Up @@ -2112,7 +2106,7 @@ def tessface_fan(
)
return polys

def mesh_to_bin_and_dict(self) -> None:
def mesh_to_bin_and_dict(self, progress: PartialProgress) -> None:
mesh_dicts = self.json_dict.get("meshes")
if not isinstance(mesh_dicts, list):
mesh_dicts = []
Expand Down Expand Up @@ -2699,6 +2693,9 @@ def mesh_to_bin_and_dict(self) -> None:
mesh_dicts.append(mesh_dict)
bm.free()

progress.update(float(mesh_index) / len(meshes))
progress.update(1)

def exporter_name(self) -> str:
v = addon_version()
if environ.get("BLENDER_VRM_USE_TEST_EXPORTER_VERSION") == "true":
Expand Down
31 changes: 15 additions & 16 deletions src/io_scene_vrm/importer/abstract_base_vrm_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from ..common.gltf import FLOAT_NEGATIVE_MAX, FLOAT_POSITIVE_MAX, pack_glb, parse_glb
from ..common.logging import get_logger
from ..common.preferences import ImportPreferencesProtocol
from ..common.progress import PartialProgress, create_progress
from ..common.workspace import save_workspace
from ..editor.extension import get_armature_extension
from .gltf2_addon_importer_user_extension import Gltf2AddonImporterUserExtension
Expand Down Expand Up @@ -72,7 +73,7 @@ def __init__(
self.mesh_object_names: dict[int, str] = {}

@abstractmethod
def make_materials(self) -> None:
def make_materials(self, progress: PartialProgress) -> None:
pass

@abstractmethod
Expand All @@ -84,34 +85,32 @@ def find_vrm_bone_node_indices(self) -> list[int]:
pass

def import_vrm(self) -> None:
wm = self.context.window_manager
wm.progress_begin(0, 7)
try:
with save_workspace(self.context):
wm.progress_update(1)
with (
create_progress(self.context) as progress,
save_workspace(self.context),
):
progress.update(0.1)
self.import_gltf2_with_indices()
wm.progress_update(2)
progress.update(0.2)
if self.preferences.extract_textures_into_folder:
self.extract_textures(repack=False)
elif bpy.app.version < (3, 1):
self.extract_textures(repack=True)
else:
self.assign_packed_image_filepaths()
wm.progress_update(3)
progress.update(0.3)
self.use_fake_user_for_thumbnail()
wm.progress_update(4)
progress.update(0.4)
if self.parse_result.vrm1_extension or self.parse_result.vrm0_extension:
self.make_materials()
wm.progress_update(5)
self.make_materials(progress.partial_progress(0.9))
if self.parse_result.vrm1_extension or self.parse_result.vrm0_extension:
self.load_gltf_extensions()
wm.progress_update(6)
self.viewport_setup()
self.viewport_setup()
self.context.view_layer.update()
progress.update(1)
finally:
try:
Gltf2AddonImporterUserExtension.clear_current_import_id()
finally:
wm.progress_end()
Gltf2AddonImporterUserExtension.clear_current_import_id()

@property
def armature_data(self) -> Armature:
Expand Down
8 changes: 7 additions & 1 deletion src/io_scene_vrm/importer/vrm0_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from ..common import convert, deep, shader
from ..common.convert import Json
from ..common.logging import get_logger
from ..common.progress import PartialProgress
from ..common.version import addon_version
from ..common.vrm0.human_bone import HumanBoneName, HumanBoneSpecifications
from ..editor import make_armature, migration
Expand Down Expand Up @@ -49,7 +50,7 @@


class Vrm0Importer(AbstractBaseVrmImporter):
def make_materials(self) -> None:
def make_materials(self, progress: PartialProgress) -> None:
shader_to_build_method = {
"VRM/MToon": self.build_material_from_mtoon0,
"VRM/UnlitTransparentZWrite": self.build_material_from_transparent_z_write,
Expand All @@ -69,6 +70,11 @@ def make_materials(self) -> None:

self.reset_material(material)
build_method(material, material_property)
progress.update(
float(index) / len(self.parse_result.vrm0_material_properties)
)

progress.update(1)

def assign_mtoon0_texture(
self,
Expand Down
6 changes: 5 additions & 1 deletion src/io_scene_vrm/importer/vrm1_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from ..common import convert, deep, ops, shader
from ..common.convert import Json
from ..common.logging import get_logger
from ..common.progress import PartialProgress
from ..common.version import addon_version
from ..common.vrm1 import human_bone as vrm1_human_bone
from ..common.vrm1.human_bone import HumanBoneName, HumanBoneSpecifications
Expand Down Expand Up @@ -363,13 +364,16 @@ def make_mtoon1_material(
uv_animation_scroll_y_speed_factor
)

def make_materials(self) -> None:
def make_materials(self, progress: PartialProgress) -> None:
material_dicts = self.parse_result.json_dict.get("materials")
if not isinstance(material_dicts, list):
progress.update(1)
return
for index, material_dict in enumerate(material_dicts):
if isinstance(material_dict, dict):
self.make_mtoon1_material(index, material_dict)
progress.update(float(index) / len(material_dicts))
progress.update(1)

def find_vrm1_bone_node_indices(self) -> list[int]:
result: list[int] = []
Expand Down
2 changes: 1 addition & 1 deletion src/io_scene_vrm/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
def setup(*, load_post: bool) -> None:
context = bpy.context
shader.add_shaders(context)
migration.migrate_all_objects(context)
migration.migrate_all_objects(context, show_progress=True)
mtoon1_property_group.setup_drivers(context)
subscription.setup_subscription(load_post=load_post)

Expand Down

0 comments on commit 25f2c7f

Please sign in to comment.