Skip to content

Commit

Permalink
chore: immutable parser logic
Browse files Browse the repository at this point in the history
  • Loading branch information
saturday06 committed Oct 2, 2024
1 parent 38bb608 commit baa0391
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 260 deletions.
3 changes: 2 additions & 1 deletion src/io_scene_vrm/common/deep.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import difflib
import math
from collections.abc import Mapping
from json import dumps as json_dumps
from typing import Union

Expand Down Expand Up @@ -41,7 +42,7 @@ def make_json(v: object) -> Json:


def get(
json: Json,
json: Union[Json, Mapping[str, Json]],
attrs: list[Union[int, str]],
default: Json = None,
) -> Json:
Expand Down
10 changes: 5 additions & 5 deletions src/io_scene_vrm/importer/abstract_base_vrm_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@ def import_vrm(self) -> None:
self.use_fake_user_for_thumbnail()
progress.update(0.4)
if (
self.parse_result.vrm1_extension
or self.parse_result.vrm0_extension
self.parse_result.vrm1_extension_dict
or self.parse_result.vrm0_extension_dict
):
self.make_materials(progress.partial_progress(0.9))
if (
self.parse_result.vrm1_extension
or self.parse_result.vrm0_extension
self.parse_result.vrm1_extension_dict
or self.parse_result.vrm0_extension_dict
):
self.load_gltf_extensions()
progress.update(0.92)
Expand Down Expand Up @@ -187,7 +187,7 @@ def use_fake_user_for_thumbnail(self) -> None:
# のインデックスになっている
# https://github.com/vrm-c/UniVRM/blob/v0.67.0/Assets/VRM/Runtime/IO/VRMImporterself.context.cs#L308
json_texture_index = deep.get(
self.parse_result.vrm0_extension, ["meta", "texture"]
self.parse_result.vrm0_extension_dict, ["meta", "texture"]
)
if not isinstance(json_texture_index, int):
return
Expand Down
239 changes: 228 additions & 11 deletions src/io_scene_vrm/importer/import_scene.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from collections.abc import Mapping, Sequence
from collections.abc import Set as AbstractSet
from dataclasses import dataclass
from os import environ
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional

import bpy
from bpy.app.translations import pgettext
Expand All @@ -17,7 +19,9 @@
)
from bpy_extras.io_utils import ImportHelper

from ..common import ops, version
from ..common import deep, ops, version
from ..common.convert import Json
from ..common.gltf import parse_glb
from ..common.logging import get_logger
from ..common.preferences import (
ImportPreferencesProtocol,
Expand All @@ -31,11 +35,10 @@
from ..editor.ops import VRM_OT_open_url_in_web_browser, layout_operator
from ..editor.property_group import CollectionPropertyProtocol, StringPropertyGroup
from .abstract_base_vrm_importer import AbstractBaseVrmImporter
from .license_validation import LicenseConfirmationRequiredError
from .license_validation import LicenseConfirmationRequiredError, validate_license
from .vrm0_importer import Vrm0Importer
from .vrm1_importer import Vrm1Importer
from .vrm_animation_importer import VrmAnimationImporter
from .vrm_parser import VrmParser

logger = get_logger(__name__)

Expand Down Expand Up @@ -324,13 +327,7 @@ def import_vrm(
*,
license_validation: bool,
) -> set[str]:
parse_result = VrmParser(
filepath,
preferences.extract_textures_into_folder,
preferences.make_new_texture_folder,
license_validation=license_validation,
).parse()

parse_result = parse_vrm_json(filepath, license_validation=license_validation)
if parse_result.spec_version_number >= (1,):
vrm_importer: AbstractBaseVrmImporter = Vrm1Importer(
context,
Expand Down Expand Up @@ -528,3 +525,223 @@ def draw(self, context: Context) -> None:
armature_object_name_candidates: CollectionPropertyProtocol[ # type: ignore[no-redef]
StringPropertyGroup
]


@dataclass(frozen=True)
class Vrm0MaterialProperty:
name: str
shader: str
render_queue: Optional[int]
keyword_map: Mapping[str, bool]
tag_map: Mapping[str, str]
float_properties: Mapping[str, float]
vector_properties: Mapping[str, Sequence[float]]
texture_properties: Mapping[str, int]

@staticmethod
def create(json_dict: Json) -> "Vrm0MaterialProperty":
fallback = Vrm0MaterialProperty(
name="Undefined",
shader="VRM_USE_GLTFSHADER",
render_queue=None,
keyword_map={},
tag_map={},
float_properties={},
vector_properties={},
texture_properties={},
)
if not isinstance(json_dict, dict):
return fallback

name = json_dict.get("name")
if not isinstance(name, str):
name = fallback.name

shader = json_dict.get("shader")
if not isinstance(shader, str):
shader = fallback.shader

render_queue = json_dict.get("renderQueue")
if not isinstance(render_queue, int):
render_queue = fallback.render_queue

raw_keyword_map = json_dict.get("keywordMap")
if isinstance(raw_keyword_map, dict):
keyword_map: Mapping[str, bool] = {
k: v for k, v in raw_keyword_map.items() if isinstance(v, bool)
}
else:
keyword_map = fallback.keyword_map

raw_tag_map = json_dict.get("tagMap")
if isinstance(raw_tag_map, dict):
tag_map: Mapping[str, str] = {
k: v for k, v in raw_tag_map.items() if isinstance(v, str)
}
else:
tag_map = fallback.tag_map

raw_float_properties = json_dict.get("floatProperties")
if isinstance(raw_float_properties, dict):
float_properties: Mapping[str, float] = {
k: float(v)
for k, v in raw_float_properties.items()
if isinstance(v, (float, int))
}
else:
float_properties = fallback.float_properties

raw_vector_properties = json_dict.get("vectorProperties")
if isinstance(raw_vector_properties, dict):
vector_properties: Mapping[str, Sequence[float]] = {
k: [
vector_element
for vector_element in vector
if isinstance(vector_element, (float, int))
]
for k, vector in raw_vector_properties.items()
if isinstance(vector, list)
}
else:
vector_properties = fallback.vector_properties

raw_texture_properties = json_dict.get("textureProperties")
if isinstance(raw_texture_properties, dict):
texture_properties: Mapping[str, int] = {
k: v for k, v in raw_texture_properties.items() if isinstance(v, int)
}
else:
texture_properties = fallback.texture_properties

return Vrm0MaterialProperty(
name=name,
shader=shader,
render_queue=render_queue,
keyword_map=keyword_map,
tag_map=tag_map,
float_properties=float_properties,
vector_properties=vector_properties,
texture_properties=texture_properties,
)


@dataclass(frozen=True)
class ParseResult:
filepath: Path
json_dict: Mapping[str, Json]
spec_version_number: tuple[int, int]
spec_version_str: str
spec_version_is_stable: bool
vrm0_extension_dict: Mapping[str, Json]
vrm1_extension_dict: Mapping[str, Json]
hips_node_index: Optional[int]
vrm0_material_properties: Sequence[Vrm0MaterialProperty]


def parse_vrm_json(filepath: Path, *, license_validation: bool) -> ParseResult:
json_dict, _ = parse_glb(filepath.read_bytes())

if license_validation:
validate_license(json_dict)

vrm1_extension_dict = deep.get(json_dict, ["extensions", "VRMC_vrm"])
vrm0_extension_dict = deep.get(json_dict, ["extensions", "VRM"])
vrm0_material_properties: Sequence[Vrm0MaterialProperty] = []
if isinstance(vrm1_extension_dict, dict):
(
spec_version_number,
spec_version_str,
spec_version_is_stable,
hips_node_index,
) = read_vrm1_extension(vrm1_extension_dict)
vrm0_extension_dict = {}
elif isinstance(vrm0_extension_dict, dict):
(
spec_version_number,
spec_version_str,
spec_version_is_stable,
hips_node_index,
) = read_vrm0_extension(vrm0_extension_dict)
vrm0_material_properties = read_vrm0_material_properties(json_dict)
vrm1_extension_dict = {}
else:
spec_version_number = (0, 0)
spec_version_str = "0.0"
spec_version_is_stable = True
hips_node_index = None
vrm0_extension_dict = {}
vrm1_extension_dict = {}

return ParseResult(
filepath=filepath,
json_dict=json_dict,
spec_version_number=spec_version_number,
spec_version_str=spec_version_str,
spec_version_is_stable=spec_version_is_stable,
vrm0_extension_dict=vrm0_extension_dict,
vrm1_extension_dict=vrm1_extension_dict,
hips_node_index=hips_node_index,
vrm0_material_properties=vrm0_material_properties,
)


def read_vrm0_extension(
vrm0_dict: dict[str, Json],
) -> tuple[tuple[int, int], str, bool, Optional[int]]:
spec_version_number = (0, 0)
spec_version_str = "0.0"
spec_version_is_stable = True
hips_node_index = None

spec_version = vrm0_dict.get("specVersion")
if isinstance(spec_version, str):
spec_version_str = spec_version

human_bones = deep.get(vrm0_dict, ["humanoid", "humanBones"], [])
if isinstance(human_bones, list):
for human_bone in human_bones:
if isinstance(human_bone, dict) and human_bone.get("bone") == "hips":
index = human_bone.get("node")
if isinstance(index, int):
hips_node_index = index

return (
spec_version_number,
spec_version_str,
spec_version_is_stable,
hips_node_index,
)


def read_vrm1_extension(
vrm1_dict: dict[str, Json],
) -> tuple[tuple[int, int], str, bool, Optional[int]]:
spec_version_number = (1, 0)
spec_version_str = "1.0"
spec_version_is_stable = True
hips_node_index = deep.get(vrm1_dict, ["humanoid", "humanBones", "hips", "node"])
if not isinstance(hips_node_index, int):
hips_node_index = None
return (
spec_version_number,
spec_version_str,
spec_version_is_stable,
hips_node_index,
)


def read_vrm0_material_properties(
json_dict: dict[str, Json],
) -> Sequence[Vrm0MaterialProperty]:
material_dicts = json_dict.get("materials")
if not isinstance(material_dicts, list):
return []
return [
Vrm0MaterialProperty.create(
deep.get(
json_dict,
["extensions", "VRM", "materialProperties", index],
)
)
for index in range(len(material_dicts))
]
4 changes: 2 additions & 2 deletions src/io_scene_vrm/importer/vrm0_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ def load_gltf_extensions(self) -> None:
if self.parse_result.spec_version_number >= (1, 0):
return

vrm0_extension = self.parse_result.vrm0_extension
vrm0_extension = self.parse_result.vrm0_extension_dict

addon_extension.addon_version = addon_version()

Expand Down Expand Up @@ -1021,7 +1021,7 @@ def load_vrm0_secondary_animation(

def find_vrm0_bone_node_indices(self) -> list[int]:
result: list[int] = []
vrm0_dict = self.parse_result.vrm0_extension
vrm0_dict = self.parse_result.vrm0_extension_dict

first_person_bone_index = deep.get(
vrm0_dict, ["firstPerson", "firstPersonBone"]
Expand Down
4 changes: 2 additions & 2 deletions src/io_scene_vrm/importer/vrm1_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ def make_materials(self, progress: PartialProgress) -> None:

def find_vrm1_bone_node_indices(self) -> list[int]:
result: list[int] = []
vrm1_dict = self.parse_result.vrm1_extension
vrm1_dict = self.parse_result.vrm1_extension_dict
human_bones_dict = deep.get(vrm1_dict, ["humanoid", "humanBones"])
if isinstance(human_bones_dict, dict):
for human_bone_dict in human_bones_dict.values():
Expand Down Expand Up @@ -835,7 +835,7 @@ def load_gltf_extensions(self) -> None:
vrm1 = addon_extension.vrm1

addon_extension.spec_version = addon_extension.SPEC_VERSION_VRM1
vrm1_extension_dict = self.parse_result.vrm1_extension
vrm1_extension_dict = self.parse_result.vrm1_extension_dict

addon_extension.addon_version = addon_version()

Expand Down
Loading

0 comments on commit baa0391

Please sign in to comment.