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 37aaba0
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 129 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
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
240 changes: 121 additions & 119 deletions src/io_scene_vrm/importer/vrm_parser.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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":
Expand Down Expand Up @@ -55,21 +55,23 @@ 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:
keyword_map = fallback.keyword_map

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))
Expand All @@ -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:
Expand All @@ -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))
]

0 comments on commit 37aaba0

Please sign in to comment.