From 26d491724b354b5514a40c3766e6b1549b8ff103 Mon Sep 17 00:00:00 2001 From: Isamu Mogi Date: Wed, 2 Oct 2024 23:33:12 +0900 Subject: [PATCH] chore: immutable parser logic --- src/io_scene_vrm/common/deep.py | 3 +- .../importer/abstract_base_vrm_importer.py | 10 +- src/io_scene_vrm/importer/vrm0_importer.py | 4 +- src/io_scene_vrm/importer/vrm1_importer.py | 4 +- src/io_scene_vrm/importer/vrm_parser.py | 240 +++++++++--------- 5 files changed, 132 insertions(+), 129 deletions(-) diff --git a/src/io_scene_vrm/common/deep.py b/src/io_scene_vrm/common/deep.py index 0c0f10a49..4eb94e2db 100644 --- a/src/io_scene_vrm/common/deep.py +++ b/src/io_scene_vrm/common/deep.py @@ -1,5 +1,6 @@ import difflib import math +from collections.abc import Mapping from json import dumps as json_dumps from typing import Union @@ -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: diff --git a/src/io_scene_vrm/importer/abstract_base_vrm_importer.py b/src/io_scene_vrm/importer/abstract_base_vrm_importer.py index 6fde573ec..4f7c98e57 100644 --- a/src/io_scene_vrm/importer/abstract_base_vrm_importer.py +++ b/src/io_scene_vrm/importer/abstract_base_vrm_importer.py @@ -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) @@ -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 diff --git a/src/io_scene_vrm/importer/vrm0_importer.py b/src/io_scene_vrm/importer/vrm0_importer.py index f67c01f03..d85d2748d 100644 --- a/src/io_scene_vrm/importer/vrm0_importer.py +++ b/src/io_scene_vrm/importer/vrm0_importer.py @@ -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() @@ -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"] diff --git a/src/io_scene_vrm/importer/vrm1_importer.py b/src/io_scene_vrm/importer/vrm1_importer.py index 05f7f02a0..63a8facc4 100644 --- a/src/io_scene_vrm/importer/vrm1_importer.py +++ b/src/io_scene_vrm/importer/vrm1_importer.py @@ -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(): @@ -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() diff --git a/src/io_scene_vrm/importer/vrm_parser.py b/src/io_scene_vrm/importer/vrm_parser.py index d922bd565..cec0a1556 100644 --- a/src/io_scene_vrm/importer/vrm_parser.py +++ b/src/io_scene_vrm/importer/vrm_parser.py @@ -1,10 +1,10 @@ # SPDX-License-Identifier: MIT # SPDX-FileCopyrightText: 2018 iCyP -import sys -from dataclasses import dataclass, field +from collections.abc import Mapping, Sequence +from dataclasses import dataclass from pathlib import Path -from typing import Optional, Union +from typing import Optional from ..common import deep from ..common.convert import Json @@ -20,11 +20,11 @@ class Vrm0MaterialProperty: name: str shader: str render_queue: Optional[int] - keyword_map: dict[str, bool] - tag_map: dict[str, str] - float_properties: dict[str, float] - vector_properties: dict[str, list[float]] - texture_properties: dict[str, 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": @@ -55,7 +55,7 @@ def create(json_dict: Json) -> "Vrm0MaterialProperty": raw_keyword_map = json_dict.get("keywordMap") if isinstance(raw_keyword_map, dict): - keyword_map = { + keyword_map: Mapping[str, bool] = { k: v for k, v in raw_keyword_map.items() if isinstance(v, bool) } else: @@ -63,13 +63,15 @@ def create(json_dict: Json) -> "Vrm0MaterialProperty": raw_tag_map = json_dict.get("tagMap") if isinstance(raw_tag_map, dict): - tag_map = {k: v for k, v in raw_tag_map.items() if isinstance(v, str)} + 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 = { + float_properties: Mapping[str, float] = { k: float(v) for k, v in raw_float_properties.items() if isinstance(v, (float, int)) @@ -79,25 +81,21 @@ def create(json_dict: Json) -> "Vrm0MaterialProperty": raw_vector_properties = json_dict.get("vectorProperties") if isinstance(raw_vector_properties, dict): - vector_properties: dict[str, list[float]] = {} - for k, v in raw_vector_properties.items(): - if not isinstance(v, list): - continue - float_v: list[float] = [] - ok = True - for e in v: - if not isinstance(e, (float, int)): - ok = False - break - float_v.append(float(e)) - if ok: - vector_properties[k] = float_v + 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 = { + texture_properties: Mapping[str, int] = { k: v for k, v in raw_texture_properties.items() if isinstance(v, int) } else: @@ -115,125 +113,129 @@ def create(json_dict: Json) -> "Vrm0MaterialProperty": ) -@dataclass -class ImageProperties: - name: str +@dataclass(frozen=True) +class ParseResult: filepath: Path - filetype: str + 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] -@dataclass -class ParseResult: - filepath: Path - json_dict: dict[str, Json] = field(default_factory=dict) - spec_version_number: tuple[int, int] = (0, 0) - spec_version_str: str = "0.0" - spec_version_is_stable: bool = True - vrm0_extension: dict[str, Json] = field(init=False, default_factory=dict) - vrm1_extension: dict[str, Json] = field(init=False, default_factory=dict) - hips_node_index: Optional[int] = None - image_properties: list[ImageProperties] = field(init=False, default_factory=list) - vrm0_material_properties: list[Vrm0MaterialProperty] = field( - init=False, default_factory=list - ) - skins_joints_list: list[list[int]] = field(init=False, default_factory=list) - skins_root_node_list: list[int] = field(init=False, default_factory=list) - - -@dataclass +@dataclass(frozen=True) class VrmParser: filepath: Path extract_textures_into_folder: bool make_new_texture_folder: bool license_validation: bool - decoded_binary: list[list[Union[int, float, list[int], list[float]]]] = field( - init=False, default_factory=list - ) - json_dict: dict[str, Json] = field(init=False, default_factory=dict) def parse(self) -> ParseResult: json_dict, _ = parse_glb(self.filepath.read_bytes()) - self.json_dict = json_dict if self.license_validation: - validate_license(self.json_dict) - - parse_result = ParseResult(filepath=self.filepath, json_dict=self.json_dict) - self.vrm_extension_read(parse_result) - self.material_read(parse_result) - - return parse_result - - def vrm_extension_read(self, parse_result: ParseResult) -> None: - vrm1_dict = deep.get(self.json_dict, ["extensions", "VRMC_vrm"]) - if isinstance(vrm1_dict, dict): - self.vrm1_extension_read(parse_result, vrm1_dict) - return + 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, + ) = self.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, + ) = self.read_vrm0_extension(vrm0_extension_dict) + vrm0_material_properties = self.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=self.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, + ) - vrm0_dict = deep.get(self.json_dict, ["extensions", "VRM"]) - if isinstance(vrm0_dict, dict): - self.vrm0_extension_read(parse_result, vrm0_dict) + def read_vrm0_extension( + self, 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 - def vrm0_extension_read( - self, parse_result: ParseResult, vrm0_dict: dict[str, Json] - ) -> None: spec_version = vrm0_dict.get("specVersion") if isinstance(spec_version, str): - parse_result.spec_version_str = spec_version - parse_result.vrm0_extension = vrm0_dict + spec_version_str = spec_version human_bones = deep.get(vrm0_dict, ["humanoid", "humanBones"], []) - if not isinstance(human_bones, list): - message = "No human bones" - raise TypeError(message) - - hips_node_index: Optional[int] = None - 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 - - if not isinstance(hips_node_index, int): - logger.warning("No hips bone index found") - return - - parse_result.hips_node_index = hips_node_index - - def vrm1_extension_read( - self, parse_result: ParseResult, vrm1_dict: dict[str, Json] - ) -> None: - parse_result.vrm1_extension = vrm1_dict - parse_result.spec_version_number = (1, 0) - parse_result.spec_version_is_stable = False + 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( + self, 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): - logger.warning("No hips bone index found") - return - parse_result.hips_node_index = hips_node_index + hips_node_index = None + return ( + spec_version_number, + spec_version_str, + spec_version_is_stable, + hips_node_index, + ) - def material_read(self, parse_result: ParseResult) -> None: - material_dicts = self.json_dict.get("materials") + def read_vrm0_material_properties( + self, json_dict: dict[str, Json] + ) -> Sequence[Vrm0MaterialProperty]: + material_dicts = json_dict.get("materials") if not isinstance(material_dicts, list): - return - for index in range(len(material_dicts)): - parse_result.vrm0_material_properties.append( - Vrm0MaterialProperty.create( - deep.get( - self.json_dict, - ["extensions", "VRM", "materialProperties", index], - ) + return [] + return [ + Vrm0MaterialProperty.create( + deep.get( + json_dict, + ["extensions", "VRM", "materialProperties", index], ) ) - - -if __name__ == "__main__": - VrmParser( - Path(sys.argv[1]), - extract_textures_into_folder=True, - make_new_texture_folder=True, - license_validation=True, - ).parse() + for index in range(len(material_dicts)) + ]