@volter/blender-engine 0.1.148 → 0.1.149
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +16 -0
- package/README.md +4 -11
- package/browser/addons/vrm/LICENSE_(OPTION1)_MIT.txt +22 -0
- package/browser/addons/vrm/LICENSE_(OPTION2)_GPL-3_0.txt +674 -0
- package/browser/addons/vrm/LICENSE_MAIN.txt +1 -0
- package/browser/addons/vrm/README.txt +3 -0
- package/browser/addons/vrm/__init__.py +41 -0
- package/browser/addons/vrm/blender_manifest.toml +16 -0
- package/browser/addons/vrm/common/__init__.py +1 -0
- package/browser/addons/vrm/common/animation.py +58 -0
- package/browser/addons/vrm/common/blender_manifest.py +82 -0
- package/browser/addons/vrm/common/char.py +16 -0
- package/browser/addons/vrm/common/convert.py +287 -0
- package/browser/addons/vrm/common/convert_any.py +48 -0
- package/browser/addons/vrm/common/debug.py +40 -0
- package/browser/addons/vrm/common/deep.py +162 -0
- package/browser/addons/vrm/common/error_dialog.py +274 -0
- package/browser/addons/vrm/common/fs.py +71 -0
- package/browser/addons/vrm/common/gl.py +20 -0
- package/browser/addons/vrm/common/gltf.py +1149 -0
- package/browser/addons/vrm/common/human_bone_mapper/__init__.py +1 -0
- package/browser/addons/vrm/common/human_bone_mapper/biped_mapping.py +84 -0
- package/browser/addons/vrm/common/human_bone_mapper/cats_blender_plugin_fix_model_mapping.py +63 -0
- package/browser/addons/vrm/common/human_bone_mapper/human_bone_mapper.py +203 -0
- package/browser/addons/vrm/common/human_bone_mapper/microsoft_rocketbox_mapping.py +69 -0
- package/browser/addons/vrm/common/human_bone_mapper/mixamo_mapping.py +62 -0
- package/browser/addons/vrm/common/human_bone_mapper/mmd_mapping.py +115 -0
- package/browser/addons/vrm/common/human_bone_mapper/mpfb_mapping.py +120 -0
- package/browser/addons/vrm/common/human_bone_mapper/ready_player_me_mapping.py +65 -0
- package/browser/addons/vrm/common/human_bone_mapper/reallusion_mapping.py +94 -0
- package/browser/addons/vrm/common/human_bone_mapper/rigify_meta_rig_mapping.py +65 -0
- package/browser/addons/vrm/common/human_bone_mapper/structure_based_mapping.py +891 -0
- package/browser/addons/vrm/common/human_bone_mapper/unreal_mapping.py +62 -0
- package/browser/addons/vrm/common/human_bone_mapper/vrm_addon_mapping.py +112 -0
- package/browser/addons/vrm/common/human_bone_mapper/vroid_mapping.py +89 -0
- package/browser/addons/vrm/common/legacy_gltf.py +16 -0
- package/browser/addons/vrm/common/logger.py +84 -0
- package/browser/addons/vrm/common/mtoon0.blend +0 -0
- package/browser/addons/vrm/common/mtoon1.blend +0 -0
- package/browser/addons/vrm/common/mtoon1_outline.blend +0 -0
- package/browser/addons/vrm/common/mtoon_auto_setup.blend +0 -0
- package/browser/addons/vrm/common/mtoon_unversioned.py +62 -0
- package/browser/addons/vrm/common/ops/__init__.py +10 -0
- package/browser/addons/vrm/common/ops/export_scene.py +67 -0
- package/browser/addons/vrm/common/ops/icyp.py +53 -0
- package/browser/addons/vrm/common/ops/import_scene.py +57 -0
- package/browser/addons/vrm/common/ops/vrm.py +2380 -0
- package/browser/addons/vrm/common/ops/wm.py +167 -0
- package/browser/addons/vrm/common/preferences.py +362 -0
- package/browser/addons/vrm/common/progress.py +87 -0
- package/browser/addons/vrm/common/rotation.py +89 -0
- package/browser/addons/vrm/common/safe_removal.py +46 -0
- package/browser/addons/vrm/common/scene_watcher.py +230 -0
- package/browser/addons/vrm/common/shader.py +2243 -0
- package/browser/addons/vrm/common/shape_key_mapper/__init__.py +1 -0
- package/browser/addons/vrm/common/shape_key_mapper/arkit_mapping.py +168 -0
- package/browser/addons/vrm/common/shape_key_mapper/mmd_mapping.py +62 -0
- package/browser/addons/vrm/common/shape_key_mapper/ready_player_me_mapping.py +52 -0
- package/browser/addons/vrm/common/shape_key_mapper/vrchat_mapping.py +17 -0
- package/browser/addons/vrm/common/third_party_user_extension.py +214 -0
- package/browser/addons/vrm/common/transparent_z_write.blend +0 -0
- package/browser/addons/vrm/common/version.py +161 -0
- package/browser/addons/vrm/common/vrm0/__init__.py +1 -0
- package/browser/addons/vrm/common/vrm0/human_bone.py +662 -0
- package/browser/addons/vrm/common/vrm0/material_property.py +346 -0
- package/browser/addons/vrm/common/vrm1/__init__.py +1 -0
- package/browser/addons/vrm/common/vrm1/expression_preset.py +207 -0
- package/browser/addons/vrm/common/vrm1/human_bone.py +822 -0
- package/browser/addons/vrm/common/workspace.py +227 -0
- package/browser/addons/vrm/common/writable_context.py +77 -0
- package/browser/addons/vrm/documentation/en-us/animation/index.md +63 -0
- package/browser/addons/vrm/documentation/en-us/create-humanoid-vrm-from-scratch/index.md +131 -0
- package/browser/addons/vrm/documentation/en-us/create-simple-vrm-from-scratch/index.md +30 -0
- package/browser/addons/vrm/documentation/en-us/development/index.md +136 -0
- package/browser/addons/vrm/documentation/en-us/index.md +41 -0
- package/browser/addons/vrm/documentation/en-us/installation/index.md +152 -0
- package/browser/addons/vrm/documentation/en-us/material-mtoon/index.md +122 -0
- package/browser/addons/vrm/documentation/en-us/material-pbr/index.md +94 -0
- package/browser/addons/vrm/documentation/en-us/scripting-api/index.md +391 -0
- package/browser/addons/vrm/documentation/en-us/ui/export_scene.vrm/index.md +57 -0
- package/browser/addons/vrm/documentation/en-us/ui/export_scene.vrma/index.md +17 -0
- package/browser/addons/vrm/documentation/en-us/ui/import_scene.vrm/index.md +61 -0
- package/browser/addons/vrm/documentation/en-us/ui/import_scene.vrma/index.md +13 -0
- package/browser/addons/vrm/documentation/ja-jp/animation/index.md +57 -0
- package/browser/addons/vrm/documentation/ja-jp/create-humanoid-vrm-from-scratch/index.md +129 -0
- package/browser/addons/vrm/documentation/ja-jp/create-simple-vrm-from-scratch/index.md +29 -0
- package/browser/addons/vrm/documentation/ja-jp/development/index.md +137 -0
- package/browser/addons/vrm/documentation/ja-jp/index.md +37 -0
- package/browser/addons/vrm/documentation/ja-jp/installation/index.md +150 -0
- package/browser/addons/vrm/documentation/ja-jp/material-mtoon/index.md +113 -0
- package/browser/addons/vrm/documentation/ja-jp/material-pbr/index.md +90 -0
- package/browser/addons/vrm/documentation/ja-jp/scripting-api/index.md +389 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/export_scene.vrm/index.md +55 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/export_scene.vrma/index.md +19 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/import_scene.vrm/index.md +56 -0
- package/browser/addons/vrm/documentation/ja-jp/ui/import_scene.vrma/index.md +14 -0
- package/browser/addons/vrm/editor/__init__.py +1 -0
- package/browser/addons/vrm/editor/extension.py +425 -0
- package/browser/addons/vrm/editor/extension_accessor.py +75 -0
- package/browser/addons/vrm/editor/handler.py +84 -0
- package/browser/addons/vrm/editor/khr_character/__init__.py +1 -0
- package/browser/addons/vrm/editor/khr_character/panel.py +69 -0
- package/browser/addons/vrm/editor/khr_character/property_group.py +27 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/__init__.py +1 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/ops.py +464 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/panel.py +136 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/property_group.py +83 -0
- package/browser/addons/vrm/editor/khr_xmp_json_ld/ui_list.py +198 -0
- package/browser/addons/vrm/editor/make_armature.py +709 -0
- package/browser/addons/vrm/editor/menu.py +147 -0
- package/browser/addons/vrm/editor/migration.py +297 -0
- package/browser/addons/vrm/editor/mtoon1/__init__.py +1 -0
- package/browser/addons/vrm/editor/mtoon1/handler.py +20 -0
- package/browser/addons/vrm/editor/mtoon1/migration.py +536 -0
- package/browser/addons/vrm/editor/mtoon1/ops.py +304 -0
- package/browser/addons/vrm/editor/mtoon1/panel.py +442 -0
- package/browser/addons/vrm/editor/mtoon1/property_group.py +4977 -0
- package/browser/addons/vrm/editor/mtoon1/scene_watcher.py +381 -0
- package/browser/addons/vrm/editor/node_constraint1/__init__.py +1 -0
- package/browser/addons/vrm/editor/node_constraint1/panel.py +317 -0
- package/browser/addons/vrm/editor/node_constraint1/property_group.py +25 -0
- package/browser/addons/vrm/editor/ops.py +471 -0
- package/browser/addons/vrm/editor/panel.py +315 -0
- package/browser/addons/vrm/editor/property_group.py +888 -0
- package/browser/addons/vrm/editor/search.py +682 -0
- package/browser/addons/vrm/editor/spring_bone1/__init__.py +1 -0
- package/browser/addons/vrm/editor/spring_bone1/handler.py +994 -0
- package/browser/addons/vrm/editor/spring_bone1/menu.py +246 -0
- package/browser/addons/vrm/editor/spring_bone1/migration.py +86 -0
- package/browser/addons/vrm/editor/spring_bone1/ops.py +1679 -0
- package/browser/addons/vrm/editor/spring_bone1/panel.py +605 -0
- package/browser/addons/vrm/editor/spring_bone1/property_group.py +1457 -0
- package/browser/addons/vrm/editor/spring_bone1/ui_list.py +408 -0
- package/browser/addons/vrm/editor/subscription.py +123 -0
- package/browser/addons/vrm/editor/t_pose.py +713 -0
- package/browser/addons/vrm/editor/validation.py +1271 -0
- package/browser/addons/vrm/editor/vrm0/__init__.py +1 -0
- package/browser/addons/vrm/editor/vrm0/gizmo_group.py +63 -0
- package/browser/addons/vrm/editor/vrm0/handler.py +23 -0
- package/browser/addons/vrm/editor/vrm0/menu.py +145 -0
- package/browser/addons/vrm/editor/vrm0/migration.py +761 -0
- package/browser/addons/vrm/editor/vrm0/ops.py +2145 -0
- package/browser/addons/vrm/editor/vrm0/panel.py +1333 -0
- package/browser/addons/vrm/editor/vrm0/property_group.py +1818 -0
- package/browser/addons/vrm/editor/vrm0/ui_list.py +688 -0
- package/browser/addons/vrm/editor/vrm1/__init__.py +1 -0
- package/browser/addons/vrm/editor/vrm1/handler.py +28 -0
- package/browser/addons/vrm/editor/vrm1/menu.py +76 -0
- package/browser/addons/vrm/editor/vrm1/migration.py +321 -0
- package/browser/addons/vrm/editor/vrm1/ops.py +2679 -0
- package/browser/addons/vrm/editor/vrm1/panel.py +1243 -0
- package/browser/addons/vrm/editor/vrm1/property_group.py +2087 -0
- package/browser/addons/vrm/editor/vrm1/scene_watcher.py +90 -0
- package/browser/addons/vrm/editor/vrm1/ui_list.py +533 -0
- package/browser/addons/vrm/exporter/__init__.py +1 -0
- package/browser/addons/vrm/exporter/abstract_base_vrm_exporter.py +628 -0
- package/browser/addons/vrm/exporter/export_scene.py +930 -0
- package/browser/addons/vrm/exporter/gltf2_export_user_extension.py +45 -0
- package/browser/addons/vrm/exporter/khr_character_exporter.py +34 -0
- package/browser/addons/vrm/exporter/vrm0_exporter.py +4020 -0
- package/browser/addons/vrm/exporter/vrm1_exporter.py +3682 -0
- package/browser/addons/vrm/exporter/vrm_animation_exporter.py +1035 -0
- package/browser/addons/vrm/external/__init__.py +1 -0
- package/browser/addons/vrm/external/auto_rig_pro_support.py +3 -0
- package/browser/addons/vrm/external/io_scene_gltf2_support.py +418 -0
- package/browser/addons/vrm/external/rigify_support.py +1 -0
- package/browser/addons/vrm/importer/__init__.py +1 -0
- package/browser/addons/vrm/importer/abstract_base_vrm_importer.py +1548 -0
- package/browser/addons/vrm/importer/file_handler.py +34 -0
- package/browser/addons/vrm/importer/gltf2_import_user_extension.py +268 -0
- package/browser/addons/vrm/importer/import_scene.py +646 -0
- package/browser/addons/vrm/importer/license_validation.py +198 -0
- package/browser/addons/vrm/importer/vrm0_importer.py +1723 -0
- package/browser/addons/vrm/importer/vrm1_importer.py +2014 -0
- package/browser/addons/vrm/importer/vrm_animation_importer.py +763 -0
- package/browser/addons/vrm/importer/vrm_diff.py +316 -0
- package/browser/addons/vrm/locale/__init__.py +1 -0
- package/browser/addons/vrm/locale/ja_jp.py +826 -0
- package/browser/addons/vrm/locale/ruff.toml +8 -0
- package/browser/addons/vrm/locale/translation_dictionary.py +10 -0
- package/browser/addons/vrm/locale/zh_hans.py +928 -0
- package/browser/addons/vrm/registration.py +670 -0
- package/browser/addons/vrm.json +18 -0
- package/browser/blender-emscripten-engine.mts +60 -128
- package/browser/blender-engine.mts +20 -186
- package/browser/blender-wali-engine.mts +35 -81
- package/browser/describe-thrown.mts +3 -6
- package/browser/document-chunks.mts +4 -16
- package/browser/gltf_export.py +3161 -0
- package/browser/heap-growth.mts +23 -0
- package/browser/index.ts +2 -0
- package/browser/protocol.ts +119 -35
- package/browser/pull-job.mts +2 -10
- package/browser/rna.ts +190 -73
- package/browser/runtime.ts +197 -82
- package/browser/session-addons.mts +33 -0
- package/browser/session-frame.mts +5 -24
- package/browser/session.py +1338 -1427
- package/browser/three/attach-presenter.ts +11 -39
- package/browser/three/blender-base64.ts +1 -36
- package/browser/three/blender-corner-normals.ts +1 -23
- package/browser/three/blender-display-shaders.json +1 -1
- package/browser/three/blender-display-shaders.lut +0 -0
- package/browser/three/blender-display-transform.ts +2 -1
- package/browser/three/blender-frame-columns.ts +5 -13
- package/browser/three/blender-graph-material.ts +18 -3
- package/browser/three/blender-node-graph.ts +10 -34
- package/browser/three/blender-physical-material.ts +2 -31
- package/browser/three/blender-runtime-armature.ts +7 -60
- package/browser/three/blender-runtime-frame.ts +9 -23
- package/browser/three/blender-runtime-geometry.ts +22 -26
- package/browser/three/blender-runtime-lighting.ts +48 -178
- package/browser/three/blender-runtime-shadows.ts +187 -1
- package/browser/three/blender-runtime-skeleton.ts +53 -1
- package/browser/three/blender-runtime-view.ts +505 -283
- package/browser/three/blender-runtime-weights.ts +9 -72
- package/browser/three/blender-sky.ts +13 -33
- package/browser/three/blender-triangulate.ts +7 -35
- package/browser/three/blender-viewport-sampling.ts +235 -0
- package/browser/three/blender-workbench-material.ts +25 -29
- package/browser/three/blender-workbench-outline.ts +199 -0
- package/browser/three/blender-world-volume.ts +62 -10
- package/browser/three/presenter.ts +10 -26
- package/browser/three/release.ts +5 -21
- package/browser/three/sky-precompute-worker.ts +1 -18
- package/browser/three/sky-worker.ts +5 -23
- package/browser/three/world-field-sampler.ts +12 -26
- package/browser/volter_three.py +9 -105
- package/browser/worker.ts +156 -151
- package/package.json +5 -2
- package/wasm/BUNDLE.json +17 -17
- package/wasm/DEPENDENCY-LICENSES.txt +14 -0
- package/wasm/blender_browser.data.br +0 -0
- package/wasm/blender_browser.js +1 -1
- package/wasm/blender_browser.wasm.br +0 -0
- package/browser/frame-transfer.mts +0 -19
- package/browser/three/agx-base-srgb.lut +0 -0
- package/browser/three/agx-look-medium-high-contrast.lut +0 -0
- package/browser/three/agx-look-punchy.lut +0 -0
- package/browser/three/blender-agx.ts +0 -235
- package/browser/three/blender-display-lut.ts +0 -145
- package/browser/three/blender-filmic.ts +0 -49
- package/browser/three/blender-standard.ts +0 -68
- package/browser/three/blender-world-visibility.ts +0 -278
- package/browser/three/filmic-srgb.lut +0 -0
|
@@ -0,0 +1,1548 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
|
2
|
+
# SPDX-FileCopyrightText: 2018 iCyP
|
|
3
|
+
|
|
4
|
+
import base64
|
|
5
|
+
import contextlib
|
|
6
|
+
import functools
|
|
7
|
+
import math
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import secrets
|
|
11
|
+
import shutil
|
|
12
|
+
import string
|
|
13
|
+
import struct
|
|
14
|
+
import tempfile
|
|
15
|
+
import uuid
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from collections.abc import Generator, Mapping, Sequence
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Optional
|
|
21
|
+
|
|
22
|
+
import bpy
|
|
23
|
+
from bpy.path import clean_name
|
|
24
|
+
from bpy.types import (
|
|
25
|
+
Armature,
|
|
26
|
+
Context,
|
|
27
|
+
Image,
|
|
28
|
+
Material,
|
|
29
|
+
Mesh,
|
|
30
|
+
Object,
|
|
31
|
+
SpaceView3D,
|
|
32
|
+
)
|
|
33
|
+
from mathutils import Euler, Matrix
|
|
34
|
+
|
|
35
|
+
from ..common import safe_removal, shader
|
|
36
|
+
from ..common.convert import Json, sequence_or_none
|
|
37
|
+
from ..common.deep import make_json
|
|
38
|
+
from ..common.fs import (
|
|
39
|
+
create_unique_indexed_directory_path,
|
|
40
|
+
create_unique_indexed_file_path,
|
|
41
|
+
)
|
|
42
|
+
from ..common.gl import GL_FLOAT, GL_LINEAR, GL_REPEAT, GL_UNSIGNED_SHORT
|
|
43
|
+
from ..common.gltf import (
|
|
44
|
+
FLOAT_NEGATIVE_MAX,
|
|
45
|
+
FLOAT_POSITIVE_MAX,
|
|
46
|
+
merge_duplicate_vertex_skinning_weights,
|
|
47
|
+
pack_glb,
|
|
48
|
+
parse_glb,
|
|
49
|
+
read_buffer_view_as_bytes,
|
|
50
|
+
)
|
|
51
|
+
from ..common.logger import get_logger
|
|
52
|
+
from ..common.preferences import ImportPreferencesProtocol
|
|
53
|
+
from ..common.progress import PartialProgress, create_progress
|
|
54
|
+
from ..common.rotation import (
|
|
55
|
+
get_rotation_as_quaternion,
|
|
56
|
+
set_rotation_without_mode_change,
|
|
57
|
+
)
|
|
58
|
+
from ..common.workspace import save_workspace
|
|
59
|
+
from ..editor.extension_accessor import get_armature_extension
|
|
60
|
+
from ..external.io_scene_gltf2_support import (
|
|
61
|
+
ImportSceneGltfArguments,
|
|
62
|
+
import_scene_gltf,
|
|
63
|
+
)
|
|
64
|
+
from .gltf2_import_user_extension import glTF2ImportUserExtension
|
|
65
|
+
from .license_validation import validate_license
|
|
66
|
+
|
|
67
|
+
_logger = get_logger(__name__)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class ParseResult:
|
|
72
|
+
filepath: Path
|
|
73
|
+
json_dict: Mapping[str, Json]
|
|
74
|
+
spec_version_number: tuple[int, int]
|
|
75
|
+
spec_version_str: str
|
|
76
|
+
spec_version_is_stable: bool
|
|
77
|
+
vrm0_extension_dict: Mapping[str, Json]
|
|
78
|
+
vrm1_extension_dict: Mapping[str, Json]
|
|
79
|
+
hips_node_index: Optional[int]
|
|
80
|
+
bin_chunk: bytes
|
|
81
|
+
|
|
82
|
+
def load_thumbnail_image(self, context: Context) -> Optional[str]:
|
|
83
|
+
image_index = None
|
|
84
|
+
|
|
85
|
+
if isinstance(meta_dict := self.vrm1_extension_dict.get("meta"), dict):
|
|
86
|
+
image_index = meta_dict.get("thumbnailImage")
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
not isinstance(image_index, int)
|
|
90
|
+
and isinstance(meta_dict := self.vrm0_extension_dict.get("meta"), dict)
|
|
91
|
+
and isinstance(texture_index := meta_dict.get("texture"), int)
|
|
92
|
+
and isinstance(texture_dicts := self.json_dict.get("textures"), list)
|
|
93
|
+
and 0 <= texture_index < len(texture_dicts)
|
|
94
|
+
and isinstance(texture_dict := texture_dicts[texture_index], dict)
|
|
95
|
+
):
|
|
96
|
+
image_index = texture_dict.get("source")
|
|
97
|
+
|
|
98
|
+
if not isinstance(image_index, int):
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
image_dicts = self.json_dict.get("images")
|
|
102
|
+
if not isinstance(image_dicts, list):
|
|
103
|
+
return None
|
|
104
|
+
if not (0 <= image_index < len(image_dicts)):
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
image_dict = image_dicts[image_index]
|
|
108
|
+
if not isinstance(image_dict, dict):
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
image_uri = image_dict.get("uri")
|
|
112
|
+
if isinstance(image_uri, str):
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
buffer_dicts = self.json_dict.get("buffers")
|
|
116
|
+
if not isinstance(buffer_dicts, list):
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
buffer_view_index = image_dict.get("bufferView")
|
|
120
|
+
if not isinstance(buffer_view_index, int):
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
buffer_view_dicts = self.json_dict.get("bufferViews")
|
|
124
|
+
if not isinstance(buffer_view_dicts, list):
|
|
125
|
+
return None
|
|
126
|
+
if not (0 <= buffer_view_index < len(buffer_view_dicts)):
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
buffer_view_dict = buffer_view_dicts[buffer_view_index]
|
|
130
|
+
if not isinstance(buffer_view_dict, dict):
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
image_bytes = read_buffer_view_as_bytes(
|
|
134
|
+
buffer_view_dict, buffer_dicts, self.bin_chunk
|
|
135
|
+
)
|
|
136
|
+
if not image_bytes:
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
mime_type = image_dict.get("mimeType")
|
|
140
|
+
if mime_type == "image/jpeg":
|
|
141
|
+
suffix = ".jpg"
|
|
142
|
+
elif mime_type == "image/png":
|
|
143
|
+
suffix = ".png"
|
|
144
|
+
else:
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
temp_dir = tempfile.TemporaryDirectory()
|
|
148
|
+
try:
|
|
149
|
+
temp_file_path = Path(temp_dir.name) / f"thumbnail{suffix}"
|
|
150
|
+
temp_file_path.write_bytes(image_bytes)
|
|
151
|
+
image = context.blend_data.images.load(str(temp_file_path))
|
|
152
|
+
image.name = "vrm-thumbnail-" + uuid.uuid4().hex
|
|
153
|
+
image.pack()
|
|
154
|
+
finally:
|
|
155
|
+
with contextlib.suppress(OSError):
|
|
156
|
+
temp_dir.cleanup()
|
|
157
|
+
return image.name
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class AbstractBaseVrmImporter(ABC):
|
|
161
|
+
def __init__(
|
|
162
|
+
self,
|
|
163
|
+
context: Context,
|
|
164
|
+
parse_result: ParseResult,
|
|
165
|
+
preferences: ImportPreferencesProtocol,
|
|
166
|
+
) -> None:
|
|
167
|
+
self._context = context
|
|
168
|
+
self._parse_result = parse_result
|
|
169
|
+
self._preferences = preferences
|
|
170
|
+
|
|
171
|
+
self._meshes: dict[int, Mesh] = {}
|
|
172
|
+
self._images: dict[int, Image] = {}
|
|
173
|
+
self._armature: Optional[Object] = None
|
|
174
|
+
self._bone_names: dict[int, str] = {}
|
|
175
|
+
self._materials: dict[int, Material] = {}
|
|
176
|
+
self._bone_child_object_world_matrices: dict[str, Matrix] = {}
|
|
177
|
+
self._import_id = glTF2ImportUserExtension.update_current_import_id()
|
|
178
|
+
self._temp_object_name_count = 0
|
|
179
|
+
self._object_names: dict[int, str] = {}
|
|
180
|
+
self._imported_object_names: Optional[list[str]] = None
|
|
181
|
+
|
|
182
|
+
@abstractmethod
|
|
183
|
+
def load_materials(self, progress: PartialProgress) -> None:
|
|
184
|
+
pass
|
|
185
|
+
|
|
186
|
+
@abstractmethod
|
|
187
|
+
def load_gltf_extensions(self) -> None:
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
@abstractmethod
|
|
191
|
+
def find_vrm_bone_node_indices(self) -> list[int]:
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
def import_vrm(self) -> None:
|
|
195
|
+
try:
|
|
196
|
+
with create_progress(self._context) as progress:
|
|
197
|
+
with save_workspace(self._context):
|
|
198
|
+
progress.update(0.1)
|
|
199
|
+
self.import_gltf2_with_indices()
|
|
200
|
+
progress.update(0.4)
|
|
201
|
+
if (
|
|
202
|
+
self._parse_result.vrm1_extension_dict
|
|
203
|
+
or self._parse_result.vrm0_extension_dict
|
|
204
|
+
):
|
|
205
|
+
self.load_materials(progress.partial_progress(0.9))
|
|
206
|
+
if (
|
|
207
|
+
self._parse_result.vrm1_extension_dict
|
|
208
|
+
or self._parse_result.vrm0_extension_dict
|
|
209
|
+
):
|
|
210
|
+
self.load_gltf_extensions()
|
|
211
|
+
progress.update(0.92)
|
|
212
|
+
self.setup_viewport()
|
|
213
|
+
progress.update(0.94)
|
|
214
|
+
self._context.view_layer.update()
|
|
215
|
+
progress.update(0.96)
|
|
216
|
+
|
|
217
|
+
# Texture extraction occurs. During this process, .blend file saving
|
|
218
|
+
# may occur and save callbacks may run, so be careful not to apply
|
|
219
|
+
# those callbacks to incompletely imported VRM data
|
|
220
|
+
if self._preferences.extract_textures_into_folder:
|
|
221
|
+
self.extract_textures(repack=False)
|
|
222
|
+
elif bpy.app.version < (3, 1):
|
|
223
|
+
self.extract_textures(repack=True)
|
|
224
|
+
else:
|
|
225
|
+
self.assign_packed_image_filepaths()
|
|
226
|
+
|
|
227
|
+
self.save_t_pose_action()
|
|
228
|
+
progress.update(0.97)
|
|
229
|
+
self.setup_object_selection_and_activation()
|
|
230
|
+
progress.update(0.98)
|
|
231
|
+
finally:
|
|
232
|
+
glTF2ImportUserExtension.clear_current_import_id()
|
|
233
|
+
|
|
234
|
+
self.on_post_import()
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def armature_data(self) -> Armature:
|
|
238
|
+
if not self._armature:
|
|
239
|
+
message = "armature is not set"
|
|
240
|
+
raise AssertionError(message)
|
|
241
|
+
armature_data = self._armature.data
|
|
242
|
+
if not isinstance(armature_data, Armature):
|
|
243
|
+
message = f"{type(armature_data)} is not an Armature"
|
|
244
|
+
raise TypeError(message)
|
|
245
|
+
return armature_data
|
|
246
|
+
|
|
247
|
+
@staticmethod
|
|
248
|
+
def enter_save_bone_child_object_transforms(
|
|
249
|
+
context: Context, armature_object: Object
|
|
250
|
+
) -> Optional[Mapping[str, Matrix]]:
|
|
251
|
+
armature_data = armature_object.data
|
|
252
|
+
if not isinstance(armature_data, Armature):
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
# Save the world matrices of child objects of bones before editing
|
|
256
|
+
context.view_layer.update()
|
|
257
|
+
bone_child_object_world_matrices: dict[str, Matrix] = {}
|
|
258
|
+
for obj in context.blend_data.objects:
|
|
259
|
+
if (
|
|
260
|
+
obj.parent_type == "BONE"
|
|
261
|
+
and obj.parent == armature_object
|
|
262
|
+
and obj.parent_bone in armature_data.bones
|
|
263
|
+
):
|
|
264
|
+
bone_child_object_world_matrices[obj.name] = obj.matrix_world.copy()
|
|
265
|
+
|
|
266
|
+
return bone_child_object_world_matrices
|
|
267
|
+
|
|
268
|
+
@staticmethod
|
|
269
|
+
def leave_save_bone_child_object_transforms(
|
|
270
|
+
context: Context,
|
|
271
|
+
armature_object: Object,
|
|
272
|
+
bone_child_object_world_matrices: Mapping[str, Matrix],
|
|
273
|
+
) -> None:
|
|
274
|
+
armature_data = armature_object.data
|
|
275
|
+
if not isinstance(armature_data, Armature):
|
|
276
|
+
message = f"{type(armature_data)} is not an Armature"
|
|
277
|
+
raise TypeError(message)
|
|
278
|
+
# Restore the world matrices of child objects of bones before editing
|
|
279
|
+
context.view_layer.update()
|
|
280
|
+
for name, matrix_world in reversed(
|
|
281
|
+
list(bone_child_object_world_matrices.items())
|
|
282
|
+
):
|
|
283
|
+
restore_obj = context.blend_data.objects.get(name)
|
|
284
|
+
if (
|
|
285
|
+
restore_obj
|
|
286
|
+
and restore_obj.parent_type == "BONE"
|
|
287
|
+
and restore_obj.parent == armature_object
|
|
288
|
+
and restore_obj.parent_bone in armature_data.bones
|
|
289
|
+
):
|
|
290
|
+
restore_obj.matrix_world = matrix_world.copy()
|
|
291
|
+
context.view_layer.update()
|
|
292
|
+
|
|
293
|
+
@staticmethod
|
|
294
|
+
@contextlib.contextmanager
|
|
295
|
+
def save_bone_child_object_transforms(
|
|
296
|
+
context: Context, armature: Object
|
|
297
|
+
) -> Generator[Armature]:
|
|
298
|
+
bone_child_object_world_matrices = (
|
|
299
|
+
AbstractBaseVrmImporter.enter_save_bone_child_object_transforms(
|
|
300
|
+
context, armature
|
|
301
|
+
)
|
|
302
|
+
)
|
|
303
|
+
try:
|
|
304
|
+
with save_workspace(context, armature, mode="EDIT"):
|
|
305
|
+
armature_data = armature.data
|
|
306
|
+
if not isinstance(armature_data, Armature):
|
|
307
|
+
message = f"{type(armature_data)} is not an Armature"
|
|
308
|
+
raise TypeError(message)
|
|
309
|
+
yield armature_data
|
|
310
|
+
# After yield, bpy native objects may be deleted or frames may advance
|
|
311
|
+
# making them invalid. Accessing them in this state can cause crashes,
|
|
312
|
+
# so be careful not to access such native objects after yield
|
|
313
|
+
finally:
|
|
314
|
+
if bone_child_object_world_matrices is not None:
|
|
315
|
+
AbstractBaseVrmImporter.leave_save_bone_child_object_transforms(
|
|
316
|
+
context, armature, bone_child_object_world_matrices
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
@staticmethod
|
|
320
|
+
def reset_material(material: Material) -> None:
|
|
321
|
+
if bpy.app.version < (5, 0, 0) and not material.use_nodes:
|
|
322
|
+
material.use_nodes = True
|
|
323
|
+
shader.clear_node_tree(material.node_tree)
|
|
324
|
+
if material.alpha_threshold != 0.5:
|
|
325
|
+
material.alpha_threshold = 0.5
|
|
326
|
+
if material.blend_method != "OPAQUE":
|
|
327
|
+
material.blend_method = "OPAQUE"
|
|
328
|
+
if bpy.app.version < (4, 3) and material.shadow_method != "OPAQUE":
|
|
329
|
+
material.shadow_method = "OPAQUE"
|
|
330
|
+
if material.use_backface_culling:
|
|
331
|
+
material.use_backface_culling = False
|
|
332
|
+
if material.show_transparent_back:
|
|
333
|
+
material.show_transparent_back = False
|
|
334
|
+
node_tree = material.node_tree
|
|
335
|
+
if node_tree:
|
|
336
|
+
node_tree.nodes.new("ShaderNodeOutputMaterial")
|
|
337
|
+
else:
|
|
338
|
+
_logger.error("No node tree for material %s", material.name)
|
|
339
|
+
|
|
340
|
+
def assign_packed_image_filepaths(self) -> None:
|
|
341
|
+
# Assign image filepath for fbx export
|
|
342
|
+
for image in self._images.values():
|
|
343
|
+
if image.packed_file is None:
|
|
344
|
+
continue
|
|
345
|
+
if image.filepath:
|
|
346
|
+
continue
|
|
347
|
+
image_name = Path(image.filepath_from_user()).stem
|
|
348
|
+
if not image_name:
|
|
349
|
+
image_name = clean_name(image.name)
|
|
350
|
+
image_type = image.file_format.lower()
|
|
351
|
+
if bpy.app.version >= (3, 4):
|
|
352
|
+
image.filepath_raw = f"//textures{os.sep}{image_name}.{image_type}"
|
|
353
|
+
else:
|
|
354
|
+
image.filepath_raw = f"//{image_name}.{image_type}"
|
|
355
|
+
|
|
356
|
+
def extract_textures(self, *, repack: bool) -> None:
|
|
357
|
+
"""Extract textures to a folder as files.
|
|
358
|
+
|
|
359
|
+
In Blender 3.1 and later, texture extraction requires saving the .blend file.
|
|
360
|
+
Therefore, the file is saved, but be careful that file save callbacks may run
|
|
361
|
+
during this process.
|
|
362
|
+
"""
|
|
363
|
+
dir_path = self._parse_result.filepath.with_suffix(".vrm.textures").absolute()
|
|
364
|
+
if self._preferences.make_new_texture_folder or repack:
|
|
365
|
+
dir_path = create_unique_indexed_directory_path(dir_path)
|
|
366
|
+
dir_path.mkdir(parents=True, exist_ok=True)
|
|
367
|
+
|
|
368
|
+
if bpy.app.version >= (3, 1) and not self._context.blend_data.filepath:
|
|
369
|
+
temp_blend_path = None
|
|
370
|
+
for _ in range(10000):
|
|
371
|
+
image_suffix = (
|
|
372
|
+
".temp"
|
|
373
|
+
+ "".join(secrets.choice(string.digits) for _ in range(15))
|
|
374
|
+
+ ".blend"
|
|
375
|
+
)
|
|
376
|
+
temp_blend_path = self._parse_result.filepath.with_suffix(image_suffix)
|
|
377
|
+
if not temp_blend_path.exists():
|
|
378
|
+
break
|
|
379
|
+
if temp_blend_path is not None:
|
|
380
|
+
bpy.ops.wm.save_as_mainfile(filepath=str(temp_blend_path))
|
|
381
|
+
|
|
382
|
+
for image_index, image in self._images.items():
|
|
383
|
+
image_original_file_path = Path(image.filepath_from_user())
|
|
384
|
+
image_path_stem = image_original_file_path.stem
|
|
385
|
+
if image_path_stem:
|
|
386
|
+
legacy_image_name_prefix = self._import_id + "Image"
|
|
387
|
+
if image_path_stem.startswith(legacy_image_name_prefix):
|
|
388
|
+
image_path_stem = re.sub(
|
|
389
|
+
r"^\d+_",
|
|
390
|
+
"",
|
|
391
|
+
image_path_stem[len(legacy_image_name_prefix) :],
|
|
392
|
+
)
|
|
393
|
+
if not image_path_stem:
|
|
394
|
+
image_path_stem = image.name
|
|
395
|
+
image_path_stem = Path(clean_name(image_path_stem[:100])).stem
|
|
396
|
+
if not image_path_stem:
|
|
397
|
+
image_path_stem = f"Image_{image_index}"
|
|
398
|
+
|
|
399
|
+
original_image_path_suffix = image_original_file_path.suffix
|
|
400
|
+
detected_image_path_suffix = "." + image.file_format.lower()
|
|
401
|
+
if original_image_path_suffix.lower() != detected_image_path_suffix and (
|
|
402
|
+
original_image_path_suffix.lower(),
|
|
403
|
+
detected_image_path_suffix,
|
|
404
|
+
) != (".jpg", ".jpeg"):
|
|
405
|
+
image_path_suffix = detected_image_path_suffix
|
|
406
|
+
else:
|
|
407
|
+
image_path_suffix = original_image_path_suffix
|
|
408
|
+
|
|
409
|
+
image_path = dir_path / (image_path_stem + image_path_suffix)
|
|
410
|
+
|
|
411
|
+
try:
|
|
412
|
+
image.unpack(method="WRITE_ORIGINAL")
|
|
413
|
+
except RuntimeError:
|
|
414
|
+
_logger.exception("Failed to unpack %s", image.name)
|
|
415
|
+
continue
|
|
416
|
+
|
|
417
|
+
image_unpacked_path_str = image.filepath_from_user()
|
|
418
|
+
if not image_unpacked_path_str:
|
|
419
|
+
continue
|
|
420
|
+
image_unpacked_file_path = Path(image_unpacked_path_str)
|
|
421
|
+
if not image_unpacked_file_path.exists():
|
|
422
|
+
continue
|
|
423
|
+
|
|
424
|
+
image_bytes = image_unpacked_file_path.read_bytes()
|
|
425
|
+
with contextlib.suppress(OSError):
|
|
426
|
+
image_unpacked_file_path.unlink()
|
|
427
|
+
image_path = create_unique_indexed_file_path(image_path, image_bytes)
|
|
428
|
+
if image.filepath != str(image_path):
|
|
429
|
+
image.filepath = str(image_path)
|
|
430
|
+
image.reload()
|
|
431
|
+
if repack:
|
|
432
|
+
image.pack()
|
|
433
|
+
|
|
434
|
+
if repack:
|
|
435
|
+
shutil.rmtree(dir_path, ignore_errors=True)
|
|
436
|
+
|
|
437
|
+
# Be careful not to let bones proliferate by repeating VRM re-import.
|
|
438
|
+
# Things to pay special attention to:
|
|
439
|
+
# - Root bones
|
|
440
|
+
# - Bones that have meshes parented to them
|
|
441
|
+
def find_retain_node_indices(self, scene_dict: dict[str, Json]) -> list[int]:
|
|
442
|
+
scene_node_index_jsons = scene_dict.get("nodes")
|
|
443
|
+
if not isinstance(scene_node_index_jsons, list):
|
|
444
|
+
return []
|
|
445
|
+
scene_node_indices = [
|
|
446
|
+
index for index in scene_node_index_jsons if isinstance(index, int)
|
|
447
|
+
]
|
|
448
|
+
json_dict = self._parse_result.json_dict
|
|
449
|
+
node_dict_jsons = json_dict.get("nodes")
|
|
450
|
+
if not isinstance(node_dict_jsons, list):
|
|
451
|
+
return []
|
|
452
|
+
node_dicts = [
|
|
453
|
+
node_dict for node_dict in node_dict_jsons if isinstance(node_dict, dict)
|
|
454
|
+
]
|
|
455
|
+
skin_dict_jsons = json_dict.get("skins")
|
|
456
|
+
if not isinstance(skin_dict_jsons, list):
|
|
457
|
+
skin_dict_jsons = []
|
|
458
|
+
skin_dicts = [
|
|
459
|
+
skin_dict for skin_dict in skin_dict_jsons if isinstance(skin_dict, dict)
|
|
460
|
+
]
|
|
461
|
+
|
|
462
|
+
bone_node_indices = self.find_vrm_bone_node_indices()
|
|
463
|
+
|
|
464
|
+
# Collect all nodes in the scene node tree where the hips bone exists.
|
|
465
|
+
# Also treat the root node of that tree as a bone.
|
|
466
|
+
all_scene_node_indices: list[int] = []
|
|
467
|
+
hips_found = False
|
|
468
|
+
for scene_node_index in scene_node_indices:
|
|
469
|
+
all_scene_node_indices.clear()
|
|
470
|
+
|
|
471
|
+
search_scene_node_indices = [scene_node_index]
|
|
472
|
+
while search_scene_node_indices:
|
|
473
|
+
search_scene_node_index = search_scene_node_indices.pop()
|
|
474
|
+
if search_scene_node_index == self._parse_result.hips_node_index:
|
|
475
|
+
bone_node_indices.append(scene_node_index)
|
|
476
|
+
hips_found = True
|
|
477
|
+
if not (0 <= search_scene_node_index < len(node_dicts)):
|
|
478
|
+
continue
|
|
479
|
+
node_dict = node_dicts[search_scene_node_index]
|
|
480
|
+
all_scene_node_indices.append(search_scene_node_index)
|
|
481
|
+
child_indices = node_dict.get("children")
|
|
482
|
+
if not isinstance(child_indices, list):
|
|
483
|
+
continue
|
|
484
|
+
for child_index in child_indices:
|
|
485
|
+
if not isinstance(child_index, int):
|
|
486
|
+
continue
|
|
487
|
+
if child_index in all_scene_node_indices:
|
|
488
|
+
# Avoid recursive nodes
|
|
489
|
+
continue
|
|
490
|
+
search_scene_node_indices.append(child_index)
|
|
491
|
+
if hips_found:
|
|
492
|
+
break
|
|
493
|
+
if not hips_found:
|
|
494
|
+
return []
|
|
495
|
+
|
|
496
|
+
all_scene_node_indices = list(dict.fromkeys(all_scene_node_indices)) # Distinct
|
|
497
|
+
|
|
498
|
+
# Also treat indices registered in skin as bones
|
|
499
|
+
for node_index in all_scene_node_indices:
|
|
500
|
+
if not (0 <= node_index < len(node_dicts)):
|
|
501
|
+
continue
|
|
502
|
+
node_dict = node_dicts[node_index]
|
|
503
|
+
skin_index = node_dict.get("skin")
|
|
504
|
+
if not isinstance(skin_index, int) or not (
|
|
505
|
+
0 <= skin_index < len(skin_dicts)
|
|
506
|
+
):
|
|
507
|
+
continue
|
|
508
|
+
skin_dict = skin_dicts[skin_index]
|
|
509
|
+
skeleton_index = skin_dict.get("skeleton")
|
|
510
|
+
if isinstance(skeleton_index, int):
|
|
511
|
+
bone_node_indices.append(skeleton_index)
|
|
512
|
+
joint_indices = skin_dict.get("joints")
|
|
513
|
+
if isinstance(joint_indices, list):
|
|
514
|
+
for joint_index in joint_indices:
|
|
515
|
+
if isinstance(joint_index, int):
|
|
516
|
+
bone_node_indices.append(joint_index)
|
|
517
|
+
|
|
518
|
+
# Remove bone indices that are not in the scene node index
|
|
519
|
+
for bone_node_index in list(bone_node_indices):
|
|
520
|
+
if bone_node_index not in all_scene_node_indices:
|
|
521
|
+
bone_node_indices.remove(bone_node_index)
|
|
522
|
+
|
|
523
|
+
# Add children from currently found bone nodes until hitting mesh nodes
|
|
524
|
+
search_bone_node_indices = list(bone_node_indices)
|
|
525
|
+
while search_bone_node_indices:
|
|
526
|
+
search_bone_node_index = search_bone_node_indices.pop()
|
|
527
|
+
if not (0 <= search_bone_node_index < len(node_dicts)):
|
|
528
|
+
continue
|
|
529
|
+
node_dict = node_dicts[search_bone_node_index]
|
|
530
|
+
if isinstance(node_dict.get("mesh"), int):
|
|
531
|
+
continue
|
|
532
|
+
|
|
533
|
+
bone_node_indices.append(search_bone_node_index)
|
|
534
|
+
|
|
535
|
+
child_indices = node_dict.get("children")
|
|
536
|
+
if not isinstance(child_indices, list):
|
|
537
|
+
continue
|
|
538
|
+
for child_index in child_indices:
|
|
539
|
+
if not isinstance(child_index, int):
|
|
540
|
+
continue
|
|
541
|
+
if child_index in bone_node_indices:
|
|
542
|
+
continue
|
|
543
|
+
search_bone_node_indices.append(child_index)
|
|
544
|
+
|
|
545
|
+
# If a mesh node has bone nodes as children,
|
|
546
|
+
# treat that mesh node as a bone too
|
|
547
|
+
bone_node_indices.extend(
|
|
548
|
+
functools.reduce(
|
|
549
|
+
lambda left, right: left + right,
|
|
550
|
+
[
|
|
551
|
+
self.find_middle_bone_indices(
|
|
552
|
+
node_dicts, bone_node_indices, bone_node_index, []
|
|
553
|
+
)
|
|
554
|
+
for bone_node_index in bone_node_indices
|
|
555
|
+
],
|
|
556
|
+
list[int](),
|
|
557
|
+
)
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
return list(dict.fromkeys(bone_node_indices)) # Distinct
|
|
561
|
+
|
|
562
|
+
@classmethod
|
|
563
|
+
def find_middle_bone_indices(
|
|
564
|
+
cls,
|
|
565
|
+
node_dicts: list[dict[str, Json]],
|
|
566
|
+
bone_node_indices: list[int],
|
|
567
|
+
bone_node_index: int,
|
|
568
|
+
middle_bone_node_indices: list[int],
|
|
569
|
+
) -> list[int]:
|
|
570
|
+
if not (0 <= bone_node_index < len(node_dicts)):
|
|
571
|
+
return []
|
|
572
|
+
if bone_node_index in middle_bone_node_indices:
|
|
573
|
+
return []
|
|
574
|
+
node_dict = node_dicts[bone_node_index]
|
|
575
|
+
child_indices = node_dict.get("children")
|
|
576
|
+
if not isinstance(child_indices, list):
|
|
577
|
+
return []
|
|
578
|
+
|
|
579
|
+
result: list[int] = []
|
|
580
|
+
for child_index in child_indices:
|
|
581
|
+
if not isinstance(child_index, int):
|
|
582
|
+
continue
|
|
583
|
+
if not (0 <= child_index < len(node_dicts)):
|
|
584
|
+
continue
|
|
585
|
+
if child_index in bone_node_indices:
|
|
586
|
+
result.extend(middle_bone_node_indices)
|
|
587
|
+
continue
|
|
588
|
+
result.extend(
|
|
589
|
+
cls.find_middle_bone_indices(
|
|
590
|
+
node_dicts,
|
|
591
|
+
bone_node_indices,
|
|
592
|
+
child_index,
|
|
593
|
+
[*middle_bone_node_indices, bone_node_index],
|
|
594
|
+
)
|
|
595
|
+
)
|
|
596
|
+
return result
|
|
597
|
+
|
|
598
|
+
def import_gltf2_with_indices(self) -> None:
|
|
599
|
+
json_dict, buffer0_bytes = parse_glb(self._parse_result.filepath.read_bytes())
|
|
600
|
+
|
|
601
|
+
node_dicts = json_dict.get("nodes")
|
|
602
|
+
if not isinstance(node_dicts, list):
|
|
603
|
+
node_dicts = []
|
|
604
|
+
json_dict["nodes"] = node_dicts
|
|
605
|
+
|
|
606
|
+
material_dicts = json_dict.get("materials")
|
|
607
|
+
if not isinstance(material_dicts, list):
|
|
608
|
+
material_dicts = []
|
|
609
|
+
json_dict["materials"] = material_dicts
|
|
610
|
+
|
|
611
|
+
mesh_dicts = json_dict.get("meshes")
|
|
612
|
+
if not isinstance(mesh_dicts, list):
|
|
613
|
+
mesh_dicts = []
|
|
614
|
+
json_dict["meshes"] = mesh_dicts
|
|
615
|
+
|
|
616
|
+
scene_dicts = json_dict.get("scenes")
|
|
617
|
+
if not isinstance(scene_dicts, list):
|
|
618
|
+
scene_dicts = []
|
|
619
|
+
json_dict["scenes"] = scene_dicts
|
|
620
|
+
|
|
621
|
+
accessor_dicts = json_dict.get("accessors")
|
|
622
|
+
if not isinstance(accessor_dicts, list):
|
|
623
|
+
accessor_dicts = []
|
|
624
|
+
json_dict["accessors"] = accessor_dicts
|
|
625
|
+
|
|
626
|
+
extras_node_index_key = self._import_id + "Nodes"
|
|
627
|
+
extras_material_index_key = self._import_id + "Materials"
|
|
628
|
+
extras_mesh_index_key = self._import_id + "Meshes"
|
|
629
|
+
extras_mesh_node_indices_key = self._import_id + "MeshNodes"
|
|
630
|
+
|
|
631
|
+
for value_dicts, extras_index_key in (
|
|
632
|
+
(node_dicts, extras_node_index_key),
|
|
633
|
+
(material_dicts, extras_material_index_key),
|
|
634
|
+
(mesh_dicts, extras_mesh_index_key),
|
|
635
|
+
):
|
|
636
|
+
for index, value_dict in enumerate(value_dicts):
|
|
637
|
+
if not isinstance(value_dict, dict):
|
|
638
|
+
continue
|
|
639
|
+
|
|
640
|
+
extras_dict = value_dict.get("extras")
|
|
641
|
+
if not isinstance(extras_dict, dict):
|
|
642
|
+
extras_dict = {}
|
|
643
|
+
value_dict["extras"] = extras_dict
|
|
644
|
+
extras_dict.update({extras_index_key: index})
|
|
645
|
+
|
|
646
|
+
for mesh_index, mesh_dict in enumerate(mesh_dicts):
|
|
647
|
+
if not isinstance(mesh_dict, dict):
|
|
648
|
+
continue
|
|
649
|
+
node_indices = [
|
|
650
|
+
node_index
|
|
651
|
+
for node_index, node_dict in enumerate(node_dicts)
|
|
652
|
+
if isinstance(node_dict, dict) and node_dict.get("mesh") == mesh_index
|
|
653
|
+
]
|
|
654
|
+
if not node_indices:
|
|
655
|
+
continue
|
|
656
|
+
extras_dict = mesh_dict.get("extras")
|
|
657
|
+
if not isinstance(extras_dict, dict):
|
|
658
|
+
extras_dict = {}
|
|
659
|
+
mesh_dict["extras"] = extras_dict
|
|
660
|
+
extras_dict.update({extras_mesh_node_indices_key: make_json(node_indices)})
|
|
661
|
+
|
|
662
|
+
legacy_image_name_prefix = self._import_id + "Image"
|
|
663
|
+
image_dicts = json_dict.get("images")
|
|
664
|
+
if isinstance(image_dicts, list):
|
|
665
|
+
for image_index, image_dict in enumerate(image_dicts):
|
|
666
|
+
texture_dicts = json_dict.get("textures")
|
|
667
|
+
if not isinstance(texture_dicts, list) or not [
|
|
668
|
+
True
|
|
669
|
+
for texture_dict in texture_dicts
|
|
670
|
+
if isinstance(texture_dict, dict)
|
|
671
|
+
and texture_dict.get("source") == image_index
|
|
672
|
+
]:
|
|
673
|
+
sampler_dicts = json_dict.get("samplers")
|
|
674
|
+
if not isinstance(sampler_dicts, list):
|
|
675
|
+
sampler_dicts = []
|
|
676
|
+
json_dict["samplers"] = sampler_dicts
|
|
677
|
+
sampler_index = len(sampler_dicts)
|
|
678
|
+
sampler_dicts.append(
|
|
679
|
+
{
|
|
680
|
+
"magFilter": GL_LINEAR,
|
|
681
|
+
"minFilter": GL_LINEAR,
|
|
682
|
+
"wrapS": GL_REPEAT,
|
|
683
|
+
"wrapT": GL_REPEAT,
|
|
684
|
+
}
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
if not isinstance(texture_dicts, list):
|
|
688
|
+
texture_dicts = []
|
|
689
|
+
json_dict["textures"] = texture_dicts
|
|
690
|
+
texture_dicts.append(
|
|
691
|
+
{
|
|
692
|
+
"sampler": sampler_index,
|
|
693
|
+
"source": image_index,
|
|
694
|
+
}
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
if bpy.app.version < (3, 1):
|
|
698
|
+
if not isinstance(image_dict, dict):
|
|
699
|
+
continue
|
|
700
|
+
name = image_dict.get("name")
|
|
701
|
+
if not isinstance(name, str) or not name:
|
|
702
|
+
# https://github.com/KhronosGroup/glTF-Blender-IO/blob/709630548cdc184af6ea50b2ff3ddc5450bc0af3/addons/io_scene_gltf2/blender/imp/gltf2_blender_image.py#L54
|
|
703
|
+
name = f"Image_{image_index}"
|
|
704
|
+
image_dict["name"] = (
|
|
705
|
+
legacy_image_name_prefix + str(image_index) + "_" + name
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
for mesh_dict in mesh_dicts:
|
|
709
|
+
if not isinstance(mesh_dict, dict):
|
|
710
|
+
continue
|
|
711
|
+
mesh_extras_dict = mesh_dict.get("extras")
|
|
712
|
+
if not isinstance(mesh_extras_dict, dict):
|
|
713
|
+
mesh_extras_dict = {}
|
|
714
|
+
mesh_dict["extras"] = mesh_extras_dict
|
|
715
|
+
mesh_target_names = mesh_extras_dict.get("targetNames")
|
|
716
|
+
if isinstance(mesh_target_names, list):
|
|
717
|
+
continue
|
|
718
|
+
primitive_dicts = mesh_dict.get("primitives")
|
|
719
|
+
if not isinstance(primitive_dicts, list):
|
|
720
|
+
continue
|
|
721
|
+
for primitive_dict in primitive_dicts:
|
|
722
|
+
if not isinstance(primitive_dict, dict):
|
|
723
|
+
continue
|
|
724
|
+
primitive_extras_dict = primitive_dict.get("extras")
|
|
725
|
+
if not isinstance(primitive_extras_dict, dict):
|
|
726
|
+
continue
|
|
727
|
+
primitive_target_names = primitive_extras_dict.get("targetNames")
|
|
728
|
+
if not isinstance(primitive_target_names, list):
|
|
729
|
+
continue
|
|
730
|
+
mesh_extras_dict["targetNames"] = primitive_target_names
|
|
731
|
+
break
|
|
732
|
+
|
|
733
|
+
texture_dicts = json_dict.get("textures")
|
|
734
|
+
if isinstance(texture_dicts, list) and texture_dicts:
|
|
735
|
+
primitive_dicts = list[Json]()
|
|
736
|
+
|
|
737
|
+
for texture_index, _ in enumerate(texture_dicts):
|
|
738
|
+
buffer_dicts = json_dict.get("buffers")
|
|
739
|
+
if not isinstance(buffer_dicts, list):
|
|
740
|
+
buffer_dicts = []
|
|
741
|
+
json_dict["buffers"] = buffer_dicts
|
|
742
|
+
position_buffer_index = len(buffer_dicts)
|
|
743
|
+
position_buffer_bytes = struct.pack(
|
|
744
|
+
"<9f", 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0
|
|
745
|
+
)
|
|
746
|
+
buffer_dicts.append(
|
|
747
|
+
{
|
|
748
|
+
"uri": "data:application/gltf-buffer;base64,"
|
|
749
|
+
+ base64.b64encode(position_buffer_bytes).decode("ascii"),
|
|
750
|
+
"byteLength": len(position_buffer_bytes),
|
|
751
|
+
}
|
|
752
|
+
)
|
|
753
|
+
texcoord_buffer_index = len(buffer_dicts)
|
|
754
|
+
texcoord_buffer_bytes = struct.pack("<6f", 0.0, 0.0, 1.0, 0.0, 0.0, 1.0)
|
|
755
|
+
buffer_dicts.append(
|
|
756
|
+
{
|
|
757
|
+
"uri": "data:application/gltf-buffer;base64,"
|
|
758
|
+
+ base64.b64encode(texcoord_buffer_bytes).decode("ascii"),
|
|
759
|
+
"byteLength": len(texcoord_buffer_bytes),
|
|
760
|
+
}
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
buffer_view_dicts = json_dict.get("bufferViews")
|
|
764
|
+
if not isinstance(buffer_view_dicts, list):
|
|
765
|
+
buffer_view_dicts = []
|
|
766
|
+
json_dict["bufferViews"] = buffer_view_dicts
|
|
767
|
+
position_buffer_view_index = len(buffer_view_dicts)
|
|
768
|
+
buffer_view_dicts.append(
|
|
769
|
+
{
|
|
770
|
+
"buffer": position_buffer_index,
|
|
771
|
+
"byteLength": len(position_buffer_bytes),
|
|
772
|
+
}
|
|
773
|
+
)
|
|
774
|
+
texcoord_buffer_view_index = len(buffer_view_dicts)
|
|
775
|
+
buffer_view_dicts.append(
|
|
776
|
+
{
|
|
777
|
+
"buffer": texcoord_buffer_index,
|
|
778
|
+
"byteLength": len(texcoord_buffer_bytes),
|
|
779
|
+
}
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
accessor_dicts = json_dict.get("accessors")
|
|
783
|
+
if not isinstance(accessor_dicts, list):
|
|
784
|
+
accessor_dicts = []
|
|
785
|
+
json_dict["accessors"] = accessor_dicts
|
|
786
|
+
position_accessors_index = len(accessor_dicts)
|
|
787
|
+
accessor_dicts.append(
|
|
788
|
+
{
|
|
789
|
+
"bufferView": position_buffer_view_index,
|
|
790
|
+
"type": "VEC3",
|
|
791
|
+
"componentType": GL_FLOAT,
|
|
792
|
+
"count": 3,
|
|
793
|
+
"min": [0, 0, 0],
|
|
794
|
+
"max": [1, 1, 0],
|
|
795
|
+
}
|
|
796
|
+
)
|
|
797
|
+
texcoord_accessors_index = len(accessor_dicts)
|
|
798
|
+
accessor_dicts.append(
|
|
799
|
+
{
|
|
800
|
+
"bufferView": texcoord_buffer_view_index,
|
|
801
|
+
"type": "VEC2",
|
|
802
|
+
"componentType": GL_FLOAT,
|
|
803
|
+
"count": 3,
|
|
804
|
+
}
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
tex_material_index = len(material_dicts)
|
|
808
|
+
material_dicts.append(
|
|
809
|
+
{
|
|
810
|
+
"name": self.temp_object_name(),
|
|
811
|
+
"emissiveTexture": {"index": texture_index},
|
|
812
|
+
}
|
|
813
|
+
)
|
|
814
|
+
primitive_dicts.append(
|
|
815
|
+
{
|
|
816
|
+
"attributes": {
|
|
817
|
+
"POSITION": position_accessors_index,
|
|
818
|
+
"TEXCOORD_0": texcoord_accessors_index,
|
|
819
|
+
},
|
|
820
|
+
"material": tex_material_index,
|
|
821
|
+
}
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
tex_mesh_index = len(mesh_dicts)
|
|
825
|
+
mesh_dicts.append(
|
|
826
|
+
{"name": self.temp_object_name(), "primitives": primitive_dicts}
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
tex_node_index = len(node_dicts)
|
|
830
|
+
node_dicts.append({"name": self.temp_object_name(), "mesh": tex_mesh_index})
|
|
831
|
+
|
|
832
|
+
scene_dicts.append(
|
|
833
|
+
{"name": self.temp_object_name(), "nodes": [tex_node_index]}
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
for scene_dict in scene_dicts:
|
|
837
|
+
if not isinstance(scene_dict, dict):
|
|
838
|
+
continue
|
|
839
|
+
retain_node_indices = self.find_retain_node_indices(scene_dict)
|
|
840
|
+
if not retain_node_indices:
|
|
841
|
+
continue
|
|
842
|
+
|
|
843
|
+
buffer_dicts = json_dict.get("buffers")
|
|
844
|
+
if not isinstance(buffer_dicts, list):
|
|
845
|
+
buffer_dicts = []
|
|
846
|
+
json_dict["buffers"] = buffer_dicts
|
|
847
|
+
position_buffer_index = len(buffer_dicts)
|
|
848
|
+
position_buffer_bytes = struct.pack(
|
|
849
|
+
"<9f", 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0
|
|
850
|
+
)
|
|
851
|
+
buffer_dicts.append(
|
|
852
|
+
{
|
|
853
|
+
"uri": "data:application/gltf-buffer;base64,"
|
|
854
|
+
+ base64.b64encode(position_buffer_bytes).decode("ascii"),
|
|
855
|
+
"byteLength": len(position_buffer_bytes),
|
|
856
|
+
}
|
|
857
|
+
)
|
|
858
|
+
joints_buffer_index = len(buffer_dicts)
|
|
859
|
+
joints_buffer_bytes = struct.pack(
|
|
860
|
+
"<12H", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
861
|
+
)
|
|
862
|
+
buffer_dicts.append(
|
|
863
|
+
{
|
|
864
|
+
"uri": "data:application/gltf-buffer;base64,"
|
|
865
|
+
+ base64.b64encode(joints_buffer_bytes).decode("ascii"),
|
|
866
|
+
"byteLength": len(joints_buffer_bytes),
|
|
867
|
+
}
|
|
868
|
+
)
|
|
869
|
+
weights_buffer_index = len(buffer_dicts)
|
|
870
|
+
weights_buffer_bytes = struct.pack(
|
|
871
|
+
"<12f", 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0
|
|
872
|
+
)
|
|
873
|
+
buffer_dicts.append(
|
|
874
|
+
{
|
|
875
|
+
"uri": "data:application/gltf-buffer;base64,"
|
|
876
|
+
+ base64.b64encode(weights_buffer_bytes).decode("ascii"),
|
|
877
|
+
"byteLength": len(weights_buffer_bytes),
|
|
878
|
+
}
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
buffer_view_dicts = json_dict.get("bufferViews")
|
|
882
|
+
if not isinstance(buffer_view_dicts, list):
|
|
883
|
+
buffer_view_dicts = []
|
|
884
|
+
json_dict["bufferViews"] = buffer_view_dicts
|
|
885
|
+
position_buffer_view_index = len(buffer_view_dicts)
|
|
886
|
+
buffer_view_dicts.append(
|
|
887
|
+
{
|
|
888
|
+
"buffer": position_buffer_index,
|
|
889
|
+
"byteLength": len(position_buffer_bytes),
|
|
890
|
+
}
|
|
891
|
+
)
|
|
892
|
+
joints_buffer_view_index = len(buffer_view_dicts)
|
|
893
|
+
buffer_view_dicts.append(
|
|
894
|
+
{
|
|
895
|
+
"buffer": joints_buffer_index,
|
|
896
|
+
"byteLength": len(joints_buffer_bytes),
|
|
897
|
+
}
|
|
898
|
+
)
|
|
899
|
+
weights_buffer_view_index = len(buffer_view_dicts)
|
|
900
|
+
buffer_view_dicts.append(
|
|
901
|
+
{
|
|
902
|
+
"buffer": weights_buffer_index,
|
|
903
|
+
"byteLength": len(weights_buffer_bytes),
|
|
904
|
+
}
|
|
905
|
+
)
|
|
906
|
+
|
|
907
|
+
accessor_dicts = json_dict.get("accessors")
|
|
908
|
+
if not isinstance(accessor_dicts, list):
|
|
909
|
+
accessor_dicts = []
|
|
910
|
+
json_dict["accessors"] = accessor_dicts
|
|
911
|
+
position_accessors_index = len(accessor_dicts)
|
|
912
|
+
accessor_dicts.append(
|
|
913
|
+
{
|
|
914
|
+
"bufferView": position_buffer_view_index,
|
|
915
|
+
"type": "VEC3",
|
|
916
|
+
"componentType": GL_FLOAT,
|
|
917
|
+
"count": 3,
|
|
918
|
+
"min": [0, 0, 0],
|
|
919
|
+
"max": [1, 1, 0],
|
|
920
|
+
}
|
|
921
|
+
)
|
|
922
|
+
joints_accessors_index = len(accessor_dicts)
|
|
923
|
+
accessor_dicts.append(
|
|
924
|
+
{
|
|
925
|
+
"bufferView": joints_buffer_view_index,
|
|
926
|
+
"type": "VEC4",
|
|
927
|
+
"componentType": GL_UNSIGNED_SHORT,
|
|
928
|
+
"count": 3,
|
|
929
|
+
}
|
|
930
|
+
)
|
|
931
|
+
weights_accessors_index = len(accessor_dicts)
|
|
932
|
+
accessor_dicts.append(
|
|
933
|
+
{
|
|
934
|
+
"bufferView": weights_buffer_view_index,
|
|
935
|
+
"type": "VEC4",
|
|
936
|
+
"componentType": GL_FLOAT,
|
|
937
|
+
"count": 3,
|
|
938
|
+
}
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
primitive_dicts = [
|
|
942
|
+
{
|
|
943
|
+
"attributes": {
|
|
944
|
+
"POSITION": position_accessors_index,
|
|
945
|
+
"JOINTS_0": joints_accessors_index,
|
|
946
|
+
"WEIGHTS_0": weights_accessors_index,
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
]
|
|
950
|
+
|
|
951
|
+
mesh_dicts = json_dict.get("meshes")
|
|
952
|
+
if not isinstance(mesh_dicts, list):
|
|
953
|
+
mesh_dicts = []
|
|
954
|
+
json_dict["meshes"] = mesh_dicts
|
|
955
|
+
skin_mesh_index = len(mesh_dicts)
|
|
956
|
+
mesh_dicts.append(
|
|
957
|
+
make_json(
|
|
958
|
+
{"name": self.temp_object_name(), "primitives": primitive_dicts}
|
|
959
|
+
)
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
skin_dicts = json_dict.get("skins")
|
|
963
|
+
if not isinstance(skin_dicts, list):
|
|
964
|
+
skin_dicts = []
|
|
965
|
+
json_dict["skins"] = skin_dicts
|
|
966
|
+
skin_index = len(skin_dicts)
|
|
967
|
+
skin_dicts.append({"joints": list(retain_node_indices)})
|
|
968
|
+
|
|
969
|
+
node_dicts = json_dict.get("nodes")
|
|
970
|
+
if not isinstance(node_dicts, list):
|
|
971
|
+
node_dicts = []
|
|
972
|
+
json_dict["nodes"] = node_dicts
|
|
973
|
+
skin_node_index = len(node_dicts)
|
|
974
|
+
node_dicts.append(
|
|
975
|
+
{
|
|
976
|
+
"name": self.temp_object_name(),
|
|
977
|
+
"mesh": skin_mesh_index,
|
|
978
|
+
"skin": skin_index,
|
|
979
|
+
}
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
scene_nodes = scene_dict.get("nodes")
|
|
983
|
+
if not isinstance(scene_nodes, list):
|
|
984
|
+
scene_nodes = []
|
|
985
|
+
scene_dict["nodes"] = scene_nodes
|
|
986
|
+
|
|
987
|
+
scene_nodes.append(skin_node_index)
|
|
988
|
+
|
|
989
|
+
# Prevent errors when extensions not supported by the glTF 2.0 add-on
|
|
990
|
+
# are included in "extensionsRequired"
|
|
991
|
+
extensions_required = json_dict.get("extensionsRequired")
|
|
992
|
+
if isinstance(extensions_required, list):
|
|
993
|
+
for supported_extension in (
|
|
994
|
+
"VRM",
|
|
995
|
+
"VRMC_vrm",
|
|
996
|
+
"VRMC_springBone",
|
|
997
|
+
"VRMC_node_constraint",
|
|
998
|
+
"VRMC_materials_mtoon",
|
|
999
|
+
"VRMC_materials_hdr_emissiveMultiplier",
|
|
1000
|
+
):
|
|
1001
|
+
while supported_extension in extensions_required:
|
|
1002
|
+
extensions_required.remove(supported_extension)
|
|
1003
|
+
|
|
1004
|
+
# Unfortunately such VRMs exist.
|
|
1005
|
+
for accessor_dict in accessor_dicts:
|
|
1006
|
+
if not isinstance(accessor_dict, dict):
|
|
1007
|
+
continue
|
|
1008
|
+
|
|
1009
|
+
max_values = accessor_dict.get("max")
|
|
1010
|
+
if isinstance(max_values, list):
|
|
1011
|
+
for i, max_value in enumerate(list(max_values)):
|
|
1012
|
+
if not isinstance(max_value, (float, int)) or math.isnan(max_value):
|
|
1013
|
+
max_values[i] = FLOAT_POSITIVE_MAX
|
|
1014
|
+
elif math.isinf(max_value):
|
|
1015
|
+
max_values[i] = (
|
|
1016
|
+
FLOAT_POSITIVE_MAX if max_value > 0 else FLOAT_NEGATIVE_MAX
|
|
1017
|
+
)
|
|
1018
|
+
|
|
1019
|
+
min_values = accessor_dict.get("min")
|
|
1020
|
+
if isinstance(min_values, list):
|
|
1021
|
+
for i, min_value in enumerate(list(min_values)):
|
|
1022
|
+
if not isinstance(min_value, (float, int)) or math.isnan(min_value):
|
|
1023
|
+
min_values[i] = FLOAT_NEGATIVE_MAX
|
|
1024
|
+
elif math.isinf(min_value):
|
|
1025
|
+
min_values[i] = (
|
|
1026
|
+
FLOAT_POSITIVE_MAX if min_value > 0 else FLOAT_NEGATIVE_MAX
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
merge_duplicate_vertex_skinning_weights(json_dict, buffer0_bytes)
|
|
1030
|
+
|
|
1031
|
+
if self._parse_result.spec_version_number < (1, 0):
|
|
1032
|
+
bone_heuristic = "FORTUNE"
|
|
1033
|
+
else:
|
|
1034
|
+
bone_heuristic = "BLENDER"
|
|
1035
|
+
full_vrm_import_success = False
|
|
1036
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
1037
|
+
indexed_vrm_filepath = Path(temp_dir, "indexed.vrm")
|
|
1038
|
+
indexed_vrm_filepath.write_bytes(pack_glb(json_dict, buffer0_bytes))
|
|
1039
|
+
try:
|
|
1040
|
+
import_scene_gltf(
|
|
1041
|
+
ImportSceneGltfArguments(
|
|
1042
|
+
filepath=str(indexed_vrm_filepath),
|
|
1043
|
+
import_pack_images=True,
|
|
1044
|
+
bone_heuristic=bone_heuristic,
|
|
1045
|
+
guess_original_bind_pose=False,
|
|
1046
|
+
disable_bone_shape=True,
|
|
1047
|
+
import_scene_as_collection=False,
|
|
1048
|
+
)
|
|
1049
|
+
)
|
|
1050
|
+
full_vrm_import_success = True
|
|
1051
|
+
except RuntimeError:
|
|
1052
|
+
_logger.exception(
|
|
1053
|
+
'Failed to import "%s" generated from "%s" using glTF 2.0 Add-on',
|
|
1054
|
+
indexed_vrm_filepath,
|
|
1055
|
+
self._parse_result.filepath,
|
|
1056
|
+
)
|
|
1057
|
+
self.cleanup_gltf2_with_indices()
|
|
1058
|
+
if not full_vrm_import_success:
|
|
1059
|
+
# Some VRMs have broken animations.
|
|
1060
|
+
# https://github.com/vrm-c/UniVRM/issues/1522
|
|
1061
|
+
# https://github.com/saturday06/VRM-Addon-for-Blender/issues/58
|
|
1062
|
+
json_dict.pop("animations", None)
|
|
1063
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
1064
|
+
indexed_vrm_filepath = Path(temp_dir, "indexed.vrm")
|
|
1065
|
+
indexed_vrm_filepath.write_bytes(pack_glb(json_dict, buffer0_bytes))
|
|
1066
|
+
try:
|
|
1067
|
+
import_scene_gltf(
|
|
1068
|
+
ImportSceneGltfArguments(
|
|
1069
|
+
filepath=str(indexed_vrm_filepath),
|
|
1070
|
+
import_pack_images=True,
|
|
1071
|
+
bone_heuristic=bone_heuristic,
|
|
1072
|
+
guess_original_bind_pose=False,
|
|
1073
|
+
disable_bone_shape=True,
|
|
1074
|
+
import_scene_as_collection=False,
|
|
1075
|
+
)
|
|
1076
|
+
)
|
|
1077
|
+
except RuntimeError:
|
|
1078
|
+
_logger.exception(
|
|
1079
|
+
'Failed to import "%s" generated from "%s"'
|
|
1080
|
+
" using glTF 2.0 Add-on without animations key",
|
|
1081
|
+
indexed_vrm_filepath,
|
|
1082
|
+
self._parse_result.filepath,
|
|
1083
|
+
)
|
|
1084
|
+
self.cleanup_gltf2_with_indices()
|
|
1085
|
+
raise
|
|
1086
|
+
# Save the selection state of objects immediately after glTF import
|
|
1087
|
+
self._imported_object_names = [
|
|
1088
|
+
selected_object.name for selected_object in self._context.selected_objects
|
|
1089
|
+
]
|
|
1090
|
+
|
|
1091
|
+
for obj in self._context.blend_data.objects:
|
|
1092
|
+
node_index = obj.pop(extras_node_index_key, None)
|
|
1093
|
+
if isinstance(node_index, int):
|
|
1094
|
+
self._object_names[node_index] = obj.name
|
|
1095
|
+
data = obj.data
|
|
1096
|
+
if isinstance(data, Mesh):
|
|
1097
|
+
data.pop(extras_node_index_key, None)
|
|
1098
|
+
|
|
1099
|
+
if not isinstance(data, Armature):
|
|
1100
|
+
continue
|
|
1101
|
+
|
|
1102
|
+
for pose_bone in obj.pose.bones:
|
|
1103
|
+
pose_bone.pop(extras_node_index_key, None)
|
|
1104
|
+
|
|
1105
|
+
for bone_name, bone in data.bones.items():
|
|
1106
|
+
bone_node_index = bone.pop(extras_node_index_key, None)
|
|
1107
|
+
if not isinstance(bone_node_index, int):
|
|
1108
|
+
continue
|
|
1109
|
+
if 0 <= bone_node_index < len(node_dicts):
|
|
1110
|
+
node_dict = node_dicts[bone_node_index]
|
|
1111
|
+
if isinstance(node_dict, dict):
|
|
1112
|
+
node_dict["name"] = bone_name
|
|
1113
|
+
self._bone_names[bone_node_index] = bone_name
|
|
1114
|
+
if (
|
|
1115
|
+
self._armature is not None
|
|
1116
|
+
or bone_node_index != self._parse_result.hips_node_index
|
|
1117
|
+
):
|
|
1118
|
+
continue
|
|
1119
|
+
|
|
1120
|
+
ext = get_armature_extension(data)
|
|
1121
|
+
vrm0 = ext.vrm0
|
|
1122
|
+
vrm1 = ext.vrm1
|
|
1123
|
+
spring_bone1 = ext.spring_bone1
|
|
1124
|
+
vrm0.humanoid.initial_automatic_bone_assignment = False
|
|
1125
|
+
vrm1.humanoid.human_bones.initial_automatic_bone_assignment = False
|
|
1126
|
+
if self._parse_result.spec_version_number >= (1, 0):
|
|
1127
|
+
vrm1.expressions.initial_automatic_expression_assignment = False
|
|
1128
|
+
spring_bone1.initial_automatic_spring_bone_assignment = False
|
|
1129
|
+
vrm0.humanoid.pose = vrm0.humanoid.POSE_CURRENT_POSE.identifier
|
|
1130
|
+
vrm1.humanoid.pose = vrm1.humanoid.POSE_CURRENT_POSE.identifier
|
|
1131
|
+
self._armature = obj
|
|
1132
|
+
|
|
1133
|
+
if (
|
|
1134
|
+
(armature := self._armature) is not None
|
|
1135
|
+
and isinstance(armature.data, Armature)
|
|
1136
|
+
and self._parse_result.spec_version_number < (1, 0)
|
|
1137
|
+
):
|
|
1138
|
+
bone_name_to_roll: dict[str, float] = {}
|
|
1139
|
+
with save_workspace(self._context, armature, mode="EDIT"):
|
|
1140
|
+
for edit_bone in armature.data.edit_bones:
|
|
1141
|
+
bone_name_to_roll[edit_bone.name] = edit_bone.roll
|
|
1142
|
+
|
|
1143
|
+
with save_workspace(self._context, armature):
|
|
1144
|
+
armature_rotation = get_rotation_as_quaternion(armature)
|
|
1145
|
+
armature_rotation.rotate(Euler((0.0, 0.0, math.pi), "XYZ"))
|
|
1146
|
+
set_rotation_without_mode_change(armature, armature_rotation)
|
|
1147
|
+
|
|
1148
|
+
bpy.ops.object.select_all(action="DESELECT")
|
|
1149
|
+
armature.select_set(True)
|
|
1150
|
+
bpy.ops.object.transform_apply(
|
|
1151
|
+
location=False, rotation=True, scale=False, properties=False
|
|
1152
|
+
)
|
|
1153
|
+
|
|
1154
|
+
with self.save_bone_child_object_transforms(self._context, armature):
|
|
1155
|
+
edit_bones = [
|
|
1156
|
+
edit_bone
|
|
1157
|
+
for edit_bone in armature.data.edit_bones
|
|
1158
|
+
if not edit_bone.parent
|
|
1159
|
+
]
|
|
1160
|
+
while edit_bones:
|
|
1161
|
+
edit_bone = edit_bones.pop(0)
|
|
1162
|
+
roll = bone_name_to_roll.get(edit_bone.name)
|
|
1163
|
+
if roll is not None:
|
|
1164
|
+
edit_bone.roll = roll
|
|
1165
|
+
edit_bones.extend(edit_bone.children)
|
|
1166
|
+
|
|
1167
|
+
for material in self._context.blend_data.materials:
|
|
1168
|
+
if self.is_temp_object_name(material.name):
|
|
1169
|
+
continue
|
|
1170
|
+
material_index = material.pop(extras_material_index_key, None)
|
|
1171
|
+
if isinstance(material_index, int):
|
|
1172
|
+
self._materials[material_index] = material
|
|
1173
|
+
|
|
1174
|
+
for mesh in self._context.blend_data.meshes:
|
|
1175
|
+
custom_mesh_index = mesh.pop(extras_mesh_index_key, None)
|
|
1176
|
+
custom_mesh_node_indices: Optional[Sequence[object]] = sequence_or_none(
|
|
1177
|
+
mesh.pop(extras_mesh_node_indices_key, None)
|
|
1178
|
+
)
|
|
1179
|
+
if not isinstance(custom_mesh_index, int):
|
|
1180
|
+
continue
|
|
1181
|
+
self._meshes[custom_mesh_index] = mesh
|
|
1182
|
+
|
|
1183
|
+
# In some cases, it may not be possible to retrieve the node index
|
|
1184
|
+
# corresponding to an object. If the mesh object that owns the mesh data
|
|
1185
|
+
# does not have a node index assigned to it, assign the array of node
|
|
1186
|
+
# indices registered in the mesh data.
|
|
1187
|
+
if not custom_mesh_node_indices:
|
|
1188
|
+
continue
|
|
1189
|
+
self._restore_object_names_from_mesh_node_indices(
|
|
1190
|
+
mesh, custom_mesh_node_indices, len(node_dicts)
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
for image in list(self._context.blend_data.images):
|
|
1194
|
+
custom_image_index = image.get(self._import_id)
|
|
1195
|
+
if not isinstance(custom_image_index, int) and image.name.startswith(
|
|
1196
|
+
legacy_image_name_prefix
|
|
1197
|
+
):
|
|
1198
|
+
custom_image_index_str = "".join(
|
|
1199
|
+
image.name.split(legacy_image_name_prefix)[1:]
|
|
1200
|
+
).split("_", maxsplit=1)[0]
|
|
1201
|
+
with contextlib.suppress(ValueError):
|
|
1202
|
+
custom_image_index = int(custom_image_index_str)
|
|
1203
|
+
if not isinstance(custom_image_index, int):
|
|
1204
|
+
continue
|
|
1205
|
+
image_dicts = json_dict.get("images")
|
|
1206
|
+
if isinstance(image_dicts, list) and 0 <= custom_image_index < len(
|
|
1207
|
+
image_dicts
|
|
1208
|
+
):
|
|
1209
|
+
# image.name may be automatically shortened during import, so
|
|
1210
|
+
# restore it from the json value
|
|
1211
|
+
image_dict = image_dicts[custom_image_index]
|
|
1212
|
+
indexed_image_name = None
|
|
1213
|
+
|
|
1214
|
+
if isinstance(image_dict, dict):
|
|
1215
|
+
indexed_image_name = image_dict.get("name")
|
|
1216
|
+
|
|
1217
|
+
if isinstance(indexed_image_name, str):
|
|
1218
|
+
if indexed_image_name.startswith(legacy_image_name_prefix):
|
|
1219
|
+
indexed_image_name = "_".join(indexed_image_name.split("_")[1:])
|
|
1220
|
+
else:
|
|
1221
|
+
indexed_image_name = None
|
|
1222
|
+
|
|
1223
|
+
if indexed_image_name:
|
|
1224
|
+
image.name = indexed_image_name
|
|
1225
|
+
else:
|
|
1226
|
+
# https://github.com/KhronosGroup/glTF-Blender-IO/blob/709630548cdc184af6ea50b2ff3ddc5450bc0af3/addons/io_scene_gltf2/blender/imp/gltf2_blender_image.py#L54
|
|
1227
|
+
image.name = f"Image_{custom_image_index}"
|
|
1228
|
+
|
|
1229
|
+
else:
|
|
1230
|
+
image.name = "_".join(image.name.split("_")[1:])
|
|
1231
|
+
|
|
1232
|
+
self._images[custom_image_index] = image
|
|
1233
|
+
|
|
1234
|
+
if self._context.object is not None and self._context.object.mode == "EDIT":
|
|
1235
|
+
bpy.ops.object.mode_set(mode="OBJECT")
|
|
1236
|
+
|
|
1237
|
+
for scene in list(self._context.blend_data.scenes):
|
|
1238
|
+
if not self.is_temp_object_name(scene.name):
|
|
1239
|
+
continue
|
|
1240
|
+
if bpy.app.version >= (3, 2) and scene.use_extra_user:
|
|
1241
|
+
scene.use_extra_user = False
|
|
1242
|
+
if scene.users:
|
|
1243
|
+
_logger.warning(
|
|
1244
|
+
'Failed to remove "%s" with %d users while removing temp scenes',
|
|
1245
|
+
scene.name,
|
|
1246
|
+
scene.users,
|
|
1247
|
+
)
|
|
1248
|
+
else:
|
|
1249
|
+
self._context.blend_data.scenes.remove(scene)
|
|
1250
|
+
|
|
1251
|
+
for obj in list(self._context.blend_data.objects):
|
|
1252
|
+
if not self.is_temp_object_name(obj.name):
|
|
1253
|
+
continue
|
|
1254
|
+
if not safe_removal.remove_object(self._context, obj):
|
|
1255
|
+
_logger.warning(
|
|
1256
|
+
'Failed to remove "%s" with %d users while removing temp objects',
|
|
1257
|
+
obj.name,
|
|
1258
|
+
obj.users,
|
|
1259
|
+
)
|
|
1260
|
+
|
|
1261
|
+
for mesh in list(self._context.blend_data.meshes):
|
|
1262
|
+
if not self.is_temp_object_name(mesh.name):
|
|
1263
|
+
continue
|
|
1264
|
+
if mesh.users:
|
|
1265
|
+
_logger.warning(
|
|
1266
|
+
'Failed to remove "%s" with %d users while removing temp meshes',
|
|
1267
|
+
mesh.name,
|
|
1268
|
+
mesh.users,
|
|
1269
|
+
)
|
|
1270
|
+
else:
|
|
1271
|
+
self._context.blend_data.meshes.remove(mesh)
|
|
1272
|
+
|
|
1273
|
+
for material in list(self._context.blend_data.materials):
|
|
1274
|
+
if not self.is_temp_object_name(material.name):
|
|
1275
|
+
continue
|
|
1276
|
+
if material.users:
|
|
1277
|
+
_logger.warning(
|
|
1278
|
+
'Failed to remove "%s" with %d users while removing temp materials',
|
|
1279
|
+
material.name,
|
|
1280
|
+
material.users,
|
|
1281
|
+
)
|
|
1282
|
+
else:
|
|
1283
|
+
self._context.blend_data.materials.remove(material)
|
|
1284
|
+
|
|
1285
|
+
if self._armature is None:
|
|
1286
|
+
_logger.warning("Failed to read VRM Humanoid")
|
|
1287
|
+
|
|
1288
|
+
def _restore_object_names_from_mesh_node_indices(
|
|
1289
|
+
self,
|
|
1290
|
+
mesh: Mesh,
|
|
1291
|
+
node_indices: Sequence[object],
|
|
1292
|
+
node_count: int,
|
|
1293
|
+
) -> None:
|
|
1294
|
+
unassigned_object_names: list[str] = [
|
|
1295
|
+
obj.name
|
|
1296
|
+
for obj in self._context.blend_data.objects
|
|
1297
|
+
if obj.type == "MESH" and obj.data == mesh
|
|
1298
|
+
]
|
|
1299
|
+
unassigned_node_indices: list[int] = []
|
|
1300
|
+
for node_index in node_indices:
|
|
1301
|
+
if not isinstance(node_index, int):
|
|
1302
|
+
continue
|
|
1303
|
+
if not (0 <= node_index < node_count):
|
|
1304
|
+
continue
|
|
1305
|
+
if (
|
|
1306
|
+
(object_name := self._object_names.get(node_index))
|
|
1307
|
+
and (obj := self._context.blend_data.objects.get(object_name))
|
|
1308
|
+
and obj.name in unassigned_object_names
|
|
1309
|
+
):
|
|
1310
|
+
unassigned_object_names.remove(obj.name)
|
|
1311
|
+
else:
|
|
1312
|
+
unassigned_node_indices.append(node_index)
|
|
1313
|
+
if not unassigned_object_names:
|
|
1314
|
+
return
|
|
1315
|
+
# Link unassigned items to one another. However, it is very difficult to
|
|
1316
|
+
# establish completely accurate links.
|
|
1317
|
+
for index, node_index in enumerate(unassigned_node_indices):
|
|
1318
|
+
self._object_names[node_index] = unassigned_object_names[
|
|
1319
|
+
index % len(unassigned_object_names)
|
|
1320
|
+
]
|
|
1321
|
+
|
|
1322
|
+
def cleanup_gltf2_with_indices(self) -> None:
|
|
1323
|
+
with save_workspace(self._context):
|
|
1324
|
+
meshes_key = self._import_id + "Meshes"
|
|
1325
|
+
nodes_key = self._import_id + "Nodes"
|
|
1326
|
+
remove_objs: list[Object] = []
|
|
1327
|
+
for obj in list(self._context.scene.collection.objects):
|
|
1328
|
+
if isinstance(armature_data := obj.data, Armature):
|
|
1329
|
+
for bone in armature_data.bones:
|
|
1330
|
+
if nodes_key in bone:
|
|
1331
|
+
remove_objs.append(obj)
|
|
1332
|
+
break
|
|
1333
|
+
continue
|
|
1334
|
+
|
|
1335
|
+
if isinstance(mesh_data := obj.data, Mesh) and (
|
|
1336
|
+
nodes_key in mesh_data
|
|
1337
|
+
or meshes_key in mesh_data
|
|
1338
|
+
or self.is_temp_object_name(mesh_data.name)
|
|
1339
|
+
):
|
|
1340
|
+
remove_objs.append(obj)
|
|
1341
|
+
continue
|
|
1342
|
+
|
|
1343
|
+
if (
|
|
1344
|
+
nodes_key in obj
|
|
1345
|
+
or meshes_key in obj
|
|
1346
|
+
or self.is_temp_object_name(obj.name)
|
|
1347
|
+
):
|
|
1348
|
+
remove_objs.append(obj)
|
|
1349
|
+
|
|
1350
|
+
for obj in remove_objs:
|
|
1351
|
+
safe_removal.remove_object(self._context, obj)
|
|
1352
|
+
|
|
1353
|
+
def temp_object_name(self) -> str:
|
|
1354
|
+
self._temp_object_name_count += 1
|
|
1355
|
+
return f"{self._import_id}Temp_{self._temp_object_name_count}_"
|
|
1356
|
+
|
|
1357
|
+
def is_temp_object_name(self, name: str) -> bool:
|
|
1358
|
+
return name.startswith(f"{self._import_id}Temp_")
|
|
1359
|
+
|
|
1360
|
+
def setup_viewport(self) -> None:
|
|
1361
|
+
if self._armature:
|
|
1362
|
+
if self._preferences.set_armature_display_to_wire:
|
|
1363
|
+
self._armature.display_type = "WIRE"
|
|
1364
|
+
if self._preferences.set_armature_display_to_show_in_front:
|
|
1365
|
+
self._armature.show_in_front = True
|
|
1366
|
+
if self._preferences.set_armature_bone_shape_to_default:
|
|
1367
|
+
for bone in self._armature.pose.bones:
|
|
1368
|
+
bone.custom_shape = None
|
|
1369
|
+
|
|
1370
|
+
if self._preferences.set_view_transform_to_standard_on_import:
|
|
1371
|
+
# https://github.com/saturday06/VRM-Addon-for-Blender/issues/336#issuecomment-1760729404
|
|
1372
|
+
view_settings = self._context.scene.view_settings
|
|
1373
|
+
try:
|
|
1374
|
+
view_settings.view_transform = "Standard"
|
|
1375
|
+
except TypeError:
|
|
1376
|
+
_logger.exception(
|
|
1377
|
+
'scene.view_settings.view_transform doesn\'t support "Standard".'
|
|
1378
|
+
)
|
|
1379
|
+
|
|
1380
|
+
if self._preferences.set_shading_type_to_material_on_import:
|
|
1381
|
+
screen = self._context.screen
|
|
1382
|
+
if screen:
|
|
1383
|
+
for area in screen.areas:
|
|
1384
|
+
for space in area.spaces:
|
|
1385
|
+
if space.type == "VIEW_3D" and isinstance(space, SpaceView3D):
|
|
1386
|
+
space.shading.type = "MATERIAL"
|
|
1387
|
+
|
|
1388
|
+
def save_t_pose_action(self) -> None:
|
|
1389
|
+
if self._armature is None:
|
|
1390
|
+
return
|
|
1391
|
+
|
|
1392
|
+
t_pose_action = self._context.blend_data.actions.new(name="T-Pose")
|
|
1393
|
+
t_pose_action.use_fake_user = True
|
|
1394
|
+
animation_data_created = False
|
|
1395
|
+
if not self._armature.animation_data:
|
|
1396
|
+
self._armature.animation_data_create()
|
|
1397
|
+
animation_data_created = True
|
|
1398
|
+
if not self._armature.animation_data:
|
|
1399
|
+
message = "armature.animation_data is None"
|
|
1400
|
+
raise ValueError(message)
|
|
1401
|
+
original_action = self._armature.animation_data.action
|
|
1402
|
+
self._armature.animation_data.action = t_pose_action
|
|
1403
|
+
|
|
1404
|
+
for bone in self._armature.pose.bones:
|
|
1405
|
+
for data_path in (
|
|
1406
|
+
"location",
|
|
1407
|
+
"rotation_quaternion",
|
|
1408
|
+
"rotation_axis_angle",
|
|
1409
|
+
"rotation_euler",
|
|
1410
|
+
"scale",
|
|
1411
|
+
):
|
|
1412
|
+
bone.keyframe_insert(data_path=data_path, frame=1)
|
|
1413
|
+
|
|
1414
|
+
ext = get_armature_extension(self.armature_data)
|
|
1415
|
+
ext.vrm0.humanoid.pose = ext.vrm0.humanoid.POSE_CUSTOM_POSE.identifier
|
|
1416
|
+
ext.vrm1.humanoid.pose = ext.vrm1.humanoid.POSE_CUSTOM_POSE.identifier
|
|
1417
|
+
ext.vrm0.humanoid.pose_library = t_pose_action
|
|
1418
|
+
ext.vrm1.humanoid.pose_library = t_pose_action
|
|
1419
|
+
if animation_data_created:
|
|
1420
|
+
self._armature.animation_data_clear()
|
|
1421
|
+
else:
|
|
1422
|
+
self._armature.animation_data.action = original_action
|
|
1423
|
+
|
|
1424
|
+
def setup_object_selection_and_activation(self) -> None:
|
|
1425
|
+
active_object = self._context.view_layer.objects.active
|
|
1426
|
+
if active_object and active_object.mode != "OBJECT":
|
|
1427
|
+
bpy.ops.object.mode_set(mode="OBJECT")
|
|
1428
|
+
if self._imported_object_names is not None:
|
|
1429
|
+
bpy.ops.object.select_all(action="DESELECT")
|
|
1430
|
+
for obj in self._context.selectable_objects:
|
|
1431
|
+
if obj.name in self._imported_object_names:
|
|
1432
|
+
obj.select_set(True)
|
|
1433
|
+
if self._armature is not None:
|
|
1434
|
+
self._context.view_layer.objects.active = self._armature
|
|
1435
|
+
|
|
1436
|
+
@abstractmethod
|
|
1437
|
+
def on_post_import(self) -> None:
|
|
1438
|
+
pass
|
|
1439
|
+
|
|
1440
|
+
|
|
1441
|
+
def parse_vrm_json(filepath: Path, *, license_validation: bool) -> ParseResult:
|
|
1442
|
+
json_dict, bin_chunk = parse_glb(filepath.read_bytes())
|
|
1443
|
+
|
|
1444
|
+
extensions_dict = json_dict.get("extensions")
|
|
1445
|
+
if isinstance(extensions_dict, dict):
|
|
1446
|
+
vrm1_extension_dict = extensions_dict.get("VRMC_vrm")
|
|
1447
|
+
vrm0_extension_dict = extensions_dict.get("VRM")
|
|
1448
|
+
else:
|
|
1449
|
+
vrm1_extension_dict = None
|
|
1450
|
+
vrm0_extension_dict = None
|
|
1451
|
+
|
|
1452
|
+
if isinstance(vrm1_extension_dict, dict):
|
|
1453
|
+
(
|
|
1454
|
+
spec_version_number,
|
|
1455
|
+
spec_version_str,
|
|
1456
|
+
spec_version_is_stable,
|
|
1457
|
+
hips_node_index,
|
|
1458
|
+
) = _read_vrm1_extension(vrm1_extension_dict)
|
|
1459
|
+
vrm0_extension_dict = {}
|
|
1460
|
+
elif isinstance(vrm0_extension_dict, dict):
|
|
1461
|
+
(
|
|
1462
|
+
spec_version_number,
|
|
1463
|
+
spec_version_str,
|
|
1464
|
+
spec_version_is_stable,
|
|
1465
|
+
hips_node_index,
|
|
1466
|
+
) = _read_vrm0_extension(vrm0_extension_dict)
|
|
1467
|
+
vrm1_extension_dict = {}
|
|
1468
|
+
else:
|
|
1469
|
+
spec_version_number = (0, 0)
|
|
1470
|
+
spec_version_str = "0.0"
|
|
1471
|
+
spec_version_is_stable = True
|
|
1472
|
+
hips_node_index = None
|
|
1473
|
+
vrm0_extension_dict = {}
|
|
1474
|
+
vrm1_extension_dict = {}
|
|
1475
|
+
|
|
1476
|
+
if license_validation:
|
|
1477
|
+
validate_license(json_dict, spec_version_number)
|
|
1478
|
+
|
|
1479
|
+
return ParseResult(
|
|
1480
|
+
filepath=filepath,
|
|
1481
|
+
json_dict=json_dict,
|
|
1482
|
+
spec_version_number=spec_version_number,
|
|
1483
|
+
spec_version_str=spec_version_str,
|
|
1484
|
+
spec_version_is_stable=spec_version_is_stable,
|
|
1485
|
+
vrm0_extension_dict=vrm0_extension_dict,
|
|
1486
|
+
vrm1_extension_dict=vrm1_extension_dict,
|
|
1487
|
+
hips_node_index=hips_node_index,
|
|
1488
|
+
bin_chunk=bin_chunk,
|
|
1489
|
+
)
|
|
1490
|
+
|
|
1491
|
+
|
|
1492
|
+
def _read_vrm0_extension(
|
|
1493
|
+
vrm0_dict: dict[str, Json],
|
|
1494
|
+
) -> tuple[tuple[int, int], str, bool, Optional[int]]:
|
|
1495
|
+
spec_version_number = (0, 0)
|
|
1496
|
+
spec_version_str = "0.0"
|
|
1497
|
+
spec_version_is_stable = True
|
|
1498
|
+
hips_node_index = None
|
|
1499
|
+
|
|
1500
|
+
spec_version = vrm0_dict.get("specVersion")
|
|
1501
|
+
if isinstance(spec_version, str):
|
|
1502
|
+
spec_version_str = spec_version
|
|
1503
|
+
|
|
1504
|
+
humanoid_dict = vrm0_dict.get("humanoid")
|
|
1505
|
+
if isinstance(humanoid_dict, dict):
|
|
1506
|
+
human_bone_dicts = humanoid_dict.get("humanBones")
|
|
1507
|
+
if isinstance(human_bone_dicts, list):
|
|
1508
|
+
for human_bone_dict in human_bone_dicts:
|
|
1509
|
+
if not isinstance(human_bone_dict, dict):
|
|
1510
|
+
continue
|
|
1511
|
+
if human_bone_dict.get("bone") != "hips":
|
|
1512
|
+
continue
|
|
1513
|
+
node_index = human_bone_dict.get("node")
|
|
1514
|
+
if isinstance(node_index, int):
|
|
1515
|
+
hips_node_index = node_index
|
|
1516
|
+
|
|
1517
|
+
return (
|
|
1518
|
+
spec_version_number,
|
|
1519
|
+
spec_version_str,
|
|
1520
|
+
spec_version_is_stable,
|
|
1521
|
+
hips_node_index,
|
|
1522
|
+
)
|
|
1523
|
+
|
|
1524
|
+
|
|
1525
|
+
def _read_vrm1_extension(
|
|
1526
|
+
vrm1_dict: dict[str, Json],
|
|
1527
|
+
) -> tuple[tuple[int, int], str, bool, Optional[int]]:
|
|
1528
|
+
spec_version_number = (1, 0)
|
|
1529
|
+
spec_version_str = "1.0"
|
|
1530
|
+
spec_version_is_stable = True
|
|
1531
|
+
hips_node_index = None
|
|
1532
|
+
|
|
1533
|
+
humanoid_dict = vrm1_dict.get("humanoid")
|
|
1534
|
+
if isinstance(humanoid_dict, dict):
|
|
1535
|
+
human_bones_dict = humanoid_dict.get("humanBones")
|
|
1536
|
+
if isinstance(human_bones_dict, dict):
|
|
1537
|
+
hips_dict = human_bones_dict.get("hips")
|
|
1538
|
+
if isinstance(hips_dict, dict):
|
|
1539
|
+
node_index = hips_dict.get("node")
|
|
1540
|
+
if isinstance(node_index, int):
|
|
1541
|
+
hips_node_index = node_index
|
|
1542
|
+
|
|
1543
|
+
return (
|
|
1544
|
+
spec_version_number,
|
|
1545
|
+
spec_version_str,
|
|
1546
|
+
spec_version_is_stable,
|
|
1547
|
+
hips_node_index,
|
|
1548
|
+
)
|