@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,3682 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
|
2
|
+
import math
|
|
3
|
+
import tempfile
|
|
4
|
+
from collections.abc import Generator, Mapping, Sequence
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from os import environ
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from sys import float_info
|
|
10
|
+
from types import MappingProxyType
|
|
11
|
+
from typing import Optional, Union
|
|
12
|
+
from uuid import uuid4
|
|
13
|
+
|
|
14
|
+
import bpy
|
|
15
|
+
from bpy.types import (
|
|
16
|
+
ID,
|
|
17
|
+
Armature,
|
|
18
|
+
ArmatureModifier,
|
|
19
|
+
Bone,
|
|
20
|
+
Constraint,
|
|
21
|
+
Context,
|
|
22
|
+
CopyRotationConstraint,
|
|
23
|
+
Curve,
|
|
24
|
+
DampedTrackConstraint,
|
|
25
|
+
Image,
|
|
26
|
+
Material,
|
|
27
|
+
Mesh,
|
|
28
|
+
Node,
|
|
29
|
+
Object,
|
|
30
|
+
PoseBone,
|
|
31
|
+
ShaderNodeGroup,
|
|
32
|
+
VectorFont,
|
|
33
|
+
)
|
|
34
|
+
from mathutils import Matrix, Quaternion, Vector
|
|
35
|
+
|
|
36
|
+
from ..common import convert, safe_removal, shader
|
|
37
|
+
from ..common.char import INTERNAL_NAME_PREFIX
|
|
38
|
+
from ..common.convert import Json
|
|
39
|
+
from ..common.deep import make_json
|
|
40
|
+
from ..common.gl import GL_LINEAR, GL_REPEAT
|
|
41
|
+
from ..common.gltf import (
|
|
42
|
+
pack_glb,
|
|
43
|
+
parse_glb,
|
|
44
|
+
parse_gltf_node_matrix,
|
|
45
|
+
remove_empty_root_arrays,
|
|
46
|
+
)
|
|
47
|
+
from ..common.logger import get_logger
|
|
48
|
+
from ..common.preferences import ExportPreferencesProtocol
|
|
49
|
+
from ..common.rotation import (
|
|
50
|
+
ROTATION_MODE_EULER,
|
|
51
|
+
get_rotation_as_quaternion,
|
|
52
|
+
set_rotation_without_mode_change,
|
|
53
|
+
)
|
|
54
|
+
from ..common.shader import LegacyAddonMaterial, MmdMaterial
|
|
55
|
+
from ..common.third_party_user_extension import (
|
|
56
|
+
collect_third_party_user_extensions,
|
|
57
|
+
trigger_third_party_user_extension_hook,
|
|
58
|
+
)
|
|
59
|
+
from ..common.version import get_addon_version
|
|
60
|
+
from ..common.vrm1.human_bone import (
|
|
61
|
+
HumanBoneName,
|
|
62
|
+
HumanBoneSpecification,
|
|
63
|
+
HumanBoneSpecifications,
|
|
64
|
+
)
|
|
65
|
+
from ..common.workspace import save_workspace
|
|
66
|
+
from ..editor import search
|
|
67
|
+
from ..editor.extension_accessor import get_armature_extension, get_material_extension
|
|
68
|
+
from ..editor.mtoon1.property_group import (
|
|
69
|
+
Mtoon1SamplerPropertyGroup,
|
|
70
|
+
Mtoon1TextureInfoPropertyGroup,
|
|
71
|
+
link_or_unlink_gltf_material_nodes,
|
|
72
|
+
)
|
|
73
|
+
from ..editor.spring_bone1.handler import sort_spring_bone_joints
|
|
74
|
+
from ..editor.spring_bone1.property_group import (
|
|
75
|
+
SpringBone1JointPropertyGroup,
|
|
76
|
+
SpringBone1SpringBonePropertyGroup,
|
|
77
|
+
SpringBone1SpringPropertyGroup,
|
|
78
|
+
)
|
|
79
|
+
from ..editor.t_pose import setup_humanoid_t_pose
|
|
80
|
+
from ..editor.vrm1.property_group import (
|
|
81
|
+
Vrm1ExpressionPropertyGroup,
|
|
82
|
+
Vrm1ExpressionsPropertyGroup,
|
|
83
|
+
Vrm1FirstPersonPropertyGroup,
|
|
84
|
+
Vrm1HumanBonesPropertyGroup,
|
|
85
|
+
Vrm1HumanoidPropertyGroup,
|
|
86
|
+
Vrm1LookAtPropertyGroup,
|
|
87
|
+
Vrm1MetaPropertyGroup,
|
|
88
|
+
)
|
|
89
|
+
from ..external.io_scene_gltf2_support import (
|
|
90
|
+
ExportSceneGltfArguments,
|
|
91
|
+
export_scene_gltf,
|
|
92
|
+
image_to_image_bytes,
|
|
93
|
+
init_extras_export,
|
|
94
|
+
)
|
|
95
|
+
from .abstract_base_vrm_exporter import (
|
|
96
|
+
AbstractBaseVrmExporter,
|
|
97
|
+
FlexibleHierarchyBoneSetup,
|
|
98
|
+
assign_dict,
|
|
99
|
+
force_apply_modifiers,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
_logger = get_logger(__name__)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class Vrm1Exporter(AbstractBaseVrmExporter):
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
context: Context,
|
|
109
|
+
export_objects: Sequence[Object],
|
|
110
|
+
armature: Object,
|
|
111
|
+
export_preferences: ExportPreferencesProtocol,
|
|
112
|
+
) -> None:
|
|
113
|
+
super().__init__(context, export_objects, armature)
|
|
114
|
+
|
|
115
|
+
self._export_all_influences = (
|
|
116
|
+
export_preferences.enable_advanced_preferences
|
|
117
|
+
and export_preferences.export_all_influences
|
|
118
|
+
)
|
|
119
|
+
self._export_lights = (
|
|
120
|
+
export_preferences.enable_advanced_preferences
|
|
121
|
+
and export_preferences.export_lights
|
|
122
|
+
)
|
|
123
|
+
self._export_gltf_animations = (
|
|
124
|
+
export_preferences.enable_advanced_preferences
|
|
125
|
+
and export_preferences.export_gltf_animations
|
|
126
|
+
)
|
|
127
|
+
self._export_try_sparse_sk = (
|
|
128
|
+
export_preferences.enable_advanced_preferences
|
|
129
|
+
and export_preferences.export_try_sparse_sk
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
self._extras_main_armature_key = (
|
|
133
|
+
INTERNAL_NAME_PREFIX + self._export_id + "MainArmature"
|
|
134
|
+
)
|
|
135
|
+
self._extras_bone_name_key = INTERNAL_NAME_PREFIX + self._export_id + "BoneName"
|
|
136
|
+
self._extras_object_name_key = (
|
|
137
|
+
INTERNAL_NAME_PREFIX + self._export_id + "ObjectName"
|
|
138
|
+
)
|
|
139
|
+
self._extras_material_name_key = (
|
|
140
|
+
INTERNAL_NAME_PREFIX + self._export_id + "MaterialName"
|
|
141
|
+
)
|
|
142
|
+
self._extras_mesh_name_key = INTERNAL_NAME_PREFIX + self._export_id + "MeshName"
|
|
143
|
+
|
|
144
|
+
self._third_party_user_extensions: list[object] = []
|
|
145
|
+
self.generator_postfix = ""
|
|
146
|
+
for (
|
|
147
|
+
third_party_user_extension_name,
|
|
148
|
+
third_party_user_extension,
|
|
149
|
+
) in collect_third_party_user_extensions(context, "Vrm1ExportUserExtension"):
|
|
150
|
+
self._third_party_user_extensions.append(third_party_user_extension)
|
|
151
|
+
self.generator_postfix += " + " + third_party_user_extension_name
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def enter_overwrite_object_visibility_and_selection(
|
|
155
|
+
context: Context, export_objects: Sequence[Object]
|
|
156
|
+
) -> Mapping[str, tuple[bool, bool]]:
|
|
157
|
+
object_visibility_and_selection: dict[str, tuple[bool, bool]] = {}
|
|
158
|
+
# https://projects.blender.org/blender/blender/issues/113378
|
|
159
|
+
context.view_layer.update()
|
|
160
|
+
for obj in context.view_layer.objects:
|
|
161
|
+
object_visibility_and_selection[obj.name] = (
|
|
162
|
+
obj.hide_get(),
|
|
163
|
+
obj.select_get(),
|
|
164
|
+
)
|
|
165
|
+
enabled = obj in export_objects
|
|
166
|
+
obj.hide_set(not enabled)
|
|
167
|
+
obj.select_set(enabled)
|
|
168
|
+
return object_visibility_and_selection
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def leave_overwrite_object_visibility_and_selection(
|
|
172
|
+
context: Context,
|
|
173
|
+
object_visibility_and_selection: Mapping[str, tuple[bool, bool]],
|
|
174
|
+
) -> None:
|
|
175
|
+
for object_name, (
|
|
176
|
+
hidden,
|
|
177
|
+
selection,
|
|
178
|
+
) in reversed(list(object_visibility_and_selection.items())):
|
|
179
|
+
restore_obj = context.blend_data.objects.get(object_name)
|
|
180
|
+
if restore_obj:
|
|
181
|
+
restore_obj.hide_set(hidden)
|
|
182
|
+
restore_obj.select_set(selection)
|
|
183
|
+
|
|
184
|
+
@contextmanager
|
|
185
|
+
def overwrite_object_visibility_and_selection(self) -> Generator[None]:
|
|
186
|
+
object_visibility_and_selection = (
|
|
187
|
+
self.enter_overwrite_object_visibility_and_selection(
|
|
188
|
+
self._context, self._export_objects
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
try:
|
|
192
|
+
yield
|
|
193
|
+
# After yield, bpy's native objects may be deleted or frames may advance
|
|
194
|
+
# and become invalid. Accessing them in that state can cause crashes, so
|
|
195
|
+
# be careful not to access such potentially invalid native objects
|
|
196
|
+
# after yield
|
|
197
|
+
finally:
|
|
198
|
+
self.leave_overwrite_object_visibility_and_selection(
|
|
199
|
+
self._context, object_visibility_and_selection
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
@staticmethod
|
|
203
|
+
def enter_save_selected_mesh_compat_objects(
|
|
204
|
+
context: Context, armature: Object, original_export_objects: Sequence[Object]
|
|
205
|
+
) -> tuple[Mapping[str, str], Mapping[str, str]]:
|
|
206
|
+
backup_obj_name_to_original_obj_name: dict[str, str] = {}
|
|
207
|
+
backup_data_name_to_original_data_name: dict[str, str] = {}
|
|
208
|
+
|
|
209
|
+
# https://projects.blender.org/blender/blender/issues/113378
|
|
210
|
+
context.view_layer.update()
|
|
211
|
+
|
|
212
|
+
export_objects: list[Object] = []
|
|
213
|
+
|
|
214
|
+
# Duplicate objects.
|
|
215
|
+
# New objects are used for export
|
|
216
|
+
# Old objects are given backup names and restored after export
|
|
217
|
+
# Also, object data is given backup names
|
|
218
|
+
for obj in context.view_layer.objects:
|
|
219
|
+
if obj not in original_export_objects:
|
|
220
|
+
continue
|
|
221
|
+
if obj.type not in search.MESH_CONVERTIBLE_OBJECT_TYPES:
|
|
222
|
+
continue
|
|
223
|
+
if not obj.select_get():
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
backup_obj_name = "Backup-Object-" + uuid4().hex
|
|
227
|
+
original_obj_name = obj.name
|
|
228
|
+
backup_obj_name_to_original_obj_name[backup_obj_name] = original_obj_name
|
|
229
|
+
|
|
230
|
+
export_obj = obj.copy()
|
|
231
|
+
obj.name = backup_obj_name
|
|
232
|
+
export_obj.name = original_obj_name
|
|
233
|
+
|
|
234
|
+
export_objects.append(export_obj)
|
|
235
|
+
obj.select_set(False)
|
|
236
|
+
scene_collection_objects = context.scene.collection.objects
|
|
237
|
+
scene_collection_objects.link(export_obj)
|
|
238
|
+
export_obj.select_set(True)
|
|
239
|
+
|
|
240
|
+
obj_data = obj.data
|
|
241
|
+
if not obj_data:
|
|
242
|
+
continue
|
|
243
|
+
if obj_data.name in backup_data_name_to_original_data_name:
|
|
244
|
+
continue
|
|
245
|
+
backup_data_name = (
|
|
246
|
+
"Backup-Data-" + uuid4().hex + "-" + type(obj_data).__name__
|
|
247
|
+
)
|
|
248
|
+
backup_data_name_to_original_data_name[backup_data_name] = obj_data.name
|
|
249
|
+
obj_data.name = backup_data_name
|
|
250
|
+
|
|
251
|
+
# Duplicate object data
|
|
252
|
+
# However, if active modifiers other than Armature modifiers are attached,
|
|
253
|
+
# share the object data
|
|
254
|
+
data_to_non_sharing_objects: dict[ID, list[Object]] = {}
|
|
255
|
+
data_to_sharing_objects: dict[ID, list[Object]] = {}
|
|
256
|
+
for export_object in export_objects:
|
|
257
|
+
data = export_object.data
|
|
258
|
+
if not data:
|
|
259
|
+
continue
|
|
260
|
+
sharing = True
|
|
261
|
+
for modifier in export_object.modifiers:
|
|
262
|
+
if not modifier.show_viewport:
|
|
263
|
+
continue
|
|
264
|
+
if (
|
|
265
|
+
isinstance(modifier, ArmatureModifier)
|
|
266
|
+
and modifier.object == armature
|
|
267
|
+
):
|
|
268
|
+
continue
|
|
269
|
+
sharing = False
|
|
270
|
+
break
|
|
271
|
+
mapping = (
|
|
272
|
+
data_to_sharing_objects if sharing else data_to_non_sharing_objects
|
|
273
|
+
)
|
|
274
|
+
objs = mapping.get(data)
|
|
275
|
+
if objs is None:
|
|
276
|
+
objs = list[Object]()
|
|
277
|
+
mapping[data] = objs
|
|
278
|
+
objs.append(export_object)
|
|
279
|
+
|
|
280
|
+
for data, sharing_export_objects in data_to_sharing_objects.items():
|
|
281
|
+
original_data_name = backup_data_name_to_original_data_name.get(data.name)
|
|
282
|
+
if not original_data_name:
|
|
283
|
+
continue # This should not happen
|
|
284
|
+
export_data = data.copy()
|
|
285
|
+
export_data.name = original_data_name
|
|
286
|
+
for sharing_export_object in sharing_export_objects:
|
|
287
|
+
sharing_export_object.data = export_data
|
|
288
|
+
|
|
289
|
+
for data, non_sharing_export_objects in data_to_non_sharing_objects.items():
|
|
290
|
+
original_data_name = backup_data_name_to_original_data_name.get(data.name)
|
|
291
|
+
if not original_data_name:
|
|
292
|
+
continue # This should not happen
|
|
293
|
+
for non_sharing_export_object in non_sharing_export_objects:
|
|
294
|
+
export_data = data.copy()
|
|
295
|
+
export_data.name = original_data_name
|
|
296
|
+
non_sharing_export_object.data = export_data
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
backup_obj_name_to_original_obj_name,
|
|
300
|
+
backup_data_name_to_original_data_name,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
@staticmethod
|
|
304
|
+
def leave_save_selected_mesh_compat_objects(
|
|
305
|
+
context: Context,
|
|
306
|
+
backup_obj_name_to_original_obj_name: Mapping[str, str],
|
|
307
|
+
backup_data_name_to_original_data_name: Mapping[str, str],
|
|
308
|
+
) -> None:
|
|
309
|
+
removing_object_datum: list[ID] = []
|
|
310
|
+
|
|
311
|
+
# Delete export objects
|
|
312
|
+
for original_obj_name in reversed(
|
|
313
|
+
list(backup_obj_name_to_original_obj_name.values())
|
|
314
|
+
):
|
|
315
|
+
restored_export_obj = context.blend_data.objects.get(original_obj_name)
|
|
316
|
+
if not restored_export_obj:
|
|
317
|
+
continue
|
|
318
|
+
|
|
319
|
+
# Data may be referenced by multiple objects, so
|
|
320
|
+
# collect them here and delete them all at once later
|
|
321
|
+
restored_export_obj_data = restored_export_obj.data
|
|
322
|
+
if (
|
|
323
|
+
restored_export_obj_data
|
|
324
|
+
and restored_export_obj_data not in removing_object_datum
|
|
325
|
+
):
|
|
326
|
+
removing_object_datum.append(restored_export_obj_data)
|
|
327
|
+
|
|
328
|
+
# Even if deletion fails, randomize the name to avoid
|
|
329
|
+
# confusion with the original object
|
|
330
|
+
restored_export_obj.name = "Export-Object-" + uuid4().hex
|
|
331
|
+
if not safe_removal.remove_object(context, restored_export_obj):
|
|
332
|
+
_logger.warning(
|
|
333
|
+
'Failed to remove "%s" with %d users (temp object for "%s")',
|
|
334
|
+
restored_export_obj.name,
|
|
335
|
+
restored_export_obj.users,
|
|
336
|
+
original_obj_name,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
# Delete export object data
|
|
340
|
+
for removing_object_data in removing_object_datum:
|
|
341
|
+
# Even if deletion fails, randomize the name to avoid
|
|
342
|
+
# confusion with the original object data
|
|
343
|
+
removing_object_data.name = (
|
|
344
|
+
"Export-Data-" + uuid4().hex + "-" + type(removing_object_data).__name__
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
if removing_object_data.users:
|
|
348
|
+
_logger.warning(
|
|
349
|
+
'Failed to remove "%s" with %d users',
|
|
350
|
+
removing_object_data.name,
|
|
351
|
+
removing_object_data.users,
|
|
352
|
+
)
|
|
353
|
+
elif isinstance(removing_object_data, Mesh):
|
|
354
|
+
context.blend_data.meshes.remove(removing_object_data)
|
|
355
|
+
elif isinstance(removing_object_data, Curve):
|
|
356
|
+
context.blend_data.curves.remove(removing_object_data)
|
|
357
|
+
elif isinstance(removing_object_data, VectorFont):
|
|
358
|
+
context.blend_data.fonts.remove(removing_object_data)
|
|
359
|
+
else:
|
|
360
|
+
_logger.warning(
|
|
361
|
+
'Failed to remove "%s" with %d users. Not implemented.',
|
|
362
|
+
removing_object_data.name,
|
|
363
|
+
removing_object_data.users,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
# Restore the names of backed up objects
|
|
367
|
+
for (
|
|
368
|
+
backup_obj_name,
|
|
369
|
+
original_obj_name,
|
|
370
|
+
) in reversed(list(backup_obj_name_to_original_obj_name.items())):
|
|
371
|
+
restored_original_data_name = None
|
|
372
|
+
restored_obj = context.blend_data.objects.get(backup_obj_name)
|
|
373
|
+
if not restored_obj:
|
|
374
|
+
continue
|
|
375
|
+
restored_obj.name = original_obj_name
|
|
376
|
+
restored_obj.select_set(True)
|
|
377
|
+
|
|
378
|
+
# Restore the names of backed up object data
|
|
379
|
+
restored_obj_data = restored_obj.data
|
|
380
|
+
if not restored_obj_data:
|
|
381
|
+
continue
|
|
382
|
+
restored_original_data_name = backup_data_name_to_original_data_name.get(
|
|
383
|
+
restored_obj_data.name
|
|
384
|
+
)
|
|
385
|
+
if not restored_original_data_name:
|
|
386
|
+
continue
|
|
387
|
+
restored_obj_data.name = restored_original_data_name
|
|
388
|
+
|
|
389
|
+
@contextmanager
|
|
390
|
+
def save_selected_mesh_compat_objects(self) -> Generator[Sequence[str]]:
|
|
391
|
+
(
|
|
392
|
+
backup_obj_name_to_original_obj_name,
|
|
393
|
+
backup_data_name_to_original_data_name,
|
|
394
|
+
) = self.enter_save_selected_mesh_compat_objects(
|
|
395
|
+
self._context, self._armature, self._export_objects
|
|
396
|
+
)
|
|
397
|
+
try:
|
|
398
|
+
yield list(backup_obj_name_to_original_obj_name.values())
|
|
399
|
+
# After yield, bpy's native objects may be deleted or frames may advance
|
|
400
|
+
# and become invalid. Accessing them in that state can cause crashes, so
|
|
401
|
+
# be careful not to access such potentially invalid native objects
|
|
402
|
+
# after yield
|
|
403
|
+
finally:
|
|
404
|
+
self.leave_save_selected_mesh_compat_objects(
|
|
405
|
+
self._context,
|
|
406
|
+
backup_obj_name_to_original_obj_name,
|
|
407
|
+
backup_data_name_to_original_data_name,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
def enter_mount_skinned_mesh_parent(self, armature: Object) -> Sequence[str]:
|
|
411
|
+
mounted_object_names: list[str] = []
|
|
412
|
+
|
|
413
|
+
for obj in self._export_objects:
|
|
414
|
+
if obj.type != "MESH" or not [
|
|
415
|
+
True
|
|
416
|
+
for m in obj.modifiers
|
|
417
|
+
if isinstance(m, ArmatureModifier) and m.object == armature
|
|
418
|
+
]:
|
|
419
|
+
continue
|
|
420
|
+
|
|
421
|
+
search_obj = obj
|
|
422
|
+
while search_obj != armature:
|
|
423
|
+
if search_obj.parent:
|
|
424
|
+
search_obj = search_obj.parent
|
|
425
|
+
continue
|
|
426
|
+
mounted_object_names.append(search_obj.name)
|
|
427
|
+
matrix_world = search_obj.matrix_world.copy()
|
|
428
|
+
search_obj.parent = armature
|
|
429
|
+
search_obj.matrix_world = matrix_world
|
|
430
|
+
break
|
|
431
|
+
return mounted_object_names
|
|
432
|
+
|
|
433
|
+
def leave_mount_skinned_mesh_parent(
|
|
434
|
+
self, mounted_object_names: Sequence[str]
|
|
435
|
+
) -> None:
|
|
436
|
+
for mounted_object_name in reversed(mounted_object_names):
|
|
437
|
+
restore_obj = self._context.blend_data.objects.get(mounted_object_name)
|
|
438
|
+
if not restore_obj:
|
|
439
|
+
continue
|
|
440
|
+
matrix_world = restore_obj.matrix_world.copy()
|
|
441
|
+
restore_obj.parent = None
|
|
442
|
+
restore_obj.matrix_world = matrix_world
|
|
443
|
+
|
|
444
|
+
@contextmanager
|
|
445
|
+
def mount_skinned_mesh_parent(self) -> Generator[None]:
|
|
446
|
+
# The glTF 2.0 export processing of the addon included with Blender 3.1.2
|
|
447
|
+
# outputs invalid glb with incorrect inverseBindMatrices when all of the
|
|
448
|
+
# following conditions are met:
|
|
449
|
+
# - A mesh that is not a descendant of the armature is skinned to bones
|
|
450
|
+
# of that armature
|
|
451
|
+
# - Another mesh exists as a child of the skinned bone
|
|
452
|
+
# Therefore, make the ancestor parent of meshes that are not descendants
|
|
453
|
+
# of the armature as the armature, and restore it later
|
|
454
|
+
|
|
455
|
+
armature = self._armature
|
|
456
|
+
if not armature:
|
|
457
|
+
yield
|
|
458
|
+
return
|
|
459
|
+
|
|
460
|
+
if bpy.app.version >= (3, 2, 1):
|
|
461
|
+
yield
|
|
462
|
+
return
|
|
463
|
+
|
|
464
|
+
mounted_object_names = self.enter_mount_skinned_mesh_parent(armature)
|
|
465
|
+
try:
|
|
466
|
+
yield
|
|
467
|
+
# After yield, bpy's native objects may be deleted or frames may advance
|
|
468
|
+
# and become invalid. Accessing them in that state can cause crashes, so
|
|
469
|
+
# be careful not to access such potentially invalid native objects
|
|
470
|
+
# after yield
|
|
471
|
+
finally:
|
|
472
|
+
self.leave_mount_skinned_mesh_parent(mounted_object_names)
|
|
473
|
+
|
|
474
|
+
@classmethod
|
|
475
|
+
def create_meta_dict(
|
|
476
|
+
cls,
|
|
477
|
+
meta: Vrm1MetaPropertyGroup,
|
|
478
|
+
json_dict: dict[str, Json],
|
|
479
|
+
buffer0: bytearray,
|
|
480
|
+
image_name_to_index_dict: dict[str, int],
|
|
481
|
+
gltf2_addon_export_settings: dict[str, object],
|
|
482
|
+
) -> dict[str, Json]:
|
|
483
|
+
meta_dict: dict[str, Json] = {
|
|
484
|
+
"licenseUrl": "https://vrm.dev/licenses/1.0/",
|
|
485
|
+
"name": meta.vrm_name or "undefined",
|
|
486
|
+
"version": meta.version or "undefined",
|
|
487
|
+
"avatarPermission": meta.avatar_permission,
|
|
488
|
+
"allowExcessivelyViolentUsage": meta.allow_excessively_violent_usage,
|
|
489
|
+
"allowExcessivelySexualUsage": meta.allow_excessively_sexual_usage,
|
|
490
|
+
"commercialUsage": meta.commercial_usage,
|
|
491
|
+
"allowPoliticalOrReligiousUsage": meta.allow_political_or_religious_usage,
|
|
492
|
+
"allowAntisocialOrHateUsage": meta.allow_antisocial_or_hate_usage,
|
|
493
|
+
"creditNotation": meta.credit_notation,
|
|
494
|
+
"allowRedistribution": meta.allow_redistribution,
|
|
495
|
+
"modification": meta.modification,
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
authors = [author.value for author in meta.authors if author.value]
|
|
499
|
+
if not authors:
|
|
500
|
+
authors = ["undefined"]
|
|
501
|
+
meta_dict["authors"] = make_json(authors)
|
|
502
|
+
|
|
503
|
+
if meta.copyright_information:
|
|
504
|
+
meta_dict["copyrightInformation"] = meta.copyright_information
|
|
505
|
+
|
|
506
|
+
references: list[Json] = [
|
|
507
|
+
reference.value for reference in meta.references if reference.value
|
|
508
|
+
]
|
|
509
|
+
if references:
|
|
510
|
+
meta_dict["references"] = references
|
|
511
|
+
|
|
512
|
+
if meta.third_party_licenses:
|
|
513
|
+
meta_dict["thirdPartyLicenses"] = meta.third_party_licenses
|
|
514
|
+
|
|
515
|
+
if meta.other_license_url:
|
|
516
|
+
meta_dict["otherLicenseUrl"] = meta.other_license_url
|
|
517
|
+
|
|
518
|
+
if meta.thumbnail_image:
|
|
519
|
+
meta_dict["thumbnailImage"] = cls.find_or_create_image(
|
|
520
|
+
json_dict,
|
|
521
|
+
buffer0,
|
|
522
|
+
image_name_to_index_dict,
|
|
523
|
+
meta.thumbnail_image,
|
|
524
|
+
gltf2_addon_export_settings,
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
return meta_dict
|
|
528
|
+
|
|
529
|
+
@classmethod
|
|
530
|
+
def create_humanoid_dict(
|
|
531
|
+
cls,
|
|
532
|
+
humanoid: Vrm1HumanoidPropertyGroup,
|
|
533
|
+
bone_name_to_index_dict: Mapping[str, int],
|
|
534
|
+
) -> dict[str, Json]:
|
|
535
|
+
human_bones_dict: dict[str, Json] = {}
|
|
536
|
+
for (
|
|
537
|
+
human_bone_name,
|
|
538
|
+
human_bone,
|
|
539
|
+
) in humanoid.human_bones.human_bone_name_to_human_bone().items():
|
|
540
|
+
index = bone_name_to_index_dict.get(human_bone.node.bone_name)
|
|
541
|
+
if isinstance(index, int):
|
|
542
|
+
human_bones_dict[human_bone_name.value] = {"node": index}
|
|
543
|
+
|
|
544
|
+
return {
|
|
545
|
+
"humanBones": human_bones_dict,
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
@classmethod
|
|
549
|
+
def create_first_person_dict(
|
|
550
|
+
cls,
|
|
551
|
+
first_person: Vrm1FirstPersonPropertyGroup,
|
|
552
|
+
mesh_object_name_to_node_index_dict: Mapping[str, int],
|
|
553
|
+
) -> dict[str, Json]:
|
|
554
|
+
mesh_annotation_dicts: list[Json] = []
|
|
555
|
+
for mesh_annotation in first_person.mesh_annotations:
|
|
556
|
+
if not mesh_annotation.node or not mesh_annotation.node.mesh_object_name:
|
|
557
|
+
continue
|
|
558
|
+
node_index = mesh_object_name_to_node_index_dict.get(
|
|
559
|
+
mesh_annotation.node.mesh_object_name
|
|
560
|
+
)
|
|
561
|
+
if not isinstance(node_index, int):
|
|
562
|
+
continue
|
|
563
|
+
mesh_annotation_dicts.append(
|
|
564
|
+
{
|
|
565
|
+
"node": node_index,
|
|
566
|
+
"type": mesh_annotation.type,
|
|
567
|
+
}
|
|
568
|
+
)
|
|
569
|
+
if not mesh_annotation_dicts:
|
|
570
|
+
return {}
|
|
571
|
+
return {"meshAnnotations": mesh_annotation_dicts}
|
|
572
|
+
|
|
573
|
+
@classmethod
|
|
574
|
+
def create_look_at_dict(
|
|
575
|
+
cls,
|
|
576
|
+
look_at: Vrm1LookAtPropertyGroup,
|
|
577
|
+
) -> dict[str, Json]:
|
|
578
|
+
return {
|
|
579
|
+
"offsetFromHeadBone": list(look_at.offset_from_head_bone),
|
|
580
|
+
"type": look_at.type,
|
|
581
|
+
"rangeMapHorizontalInner": {
|
|
582
|
+
"inputMaxValue": look_at.range_map_horizontal_inner.input_max_value,
|
|
583
|
+
"outputScale": look_at.range_map_horizontal_inner.output_scale,
|
|
584
|
+
},
|
|
585
|
+
"rangeMapHorizontalOuter": {
|
|
586
|
+
"inputMaxValue": look_at.range_map_horizontal_outer.input_max_value,
|
|
587
|
+
"outputScale": look_at.range_map_horizontal_outer.output_scale,
|
|
588
|
+
},
|
|
589
|
+
"rangeMapVerticalDown": {
|
|
590
|
+
"inputMaxValue": look_at.range_map_vertical_down.input_max_value,
|
|
591
|
+
"outputScale": look_at.range_map_vertical_down.output_scale,
|
|
592
|
+
},
|
|
593
|
+
"rangeMapVerticalUp": {
|
|
594
|
+
"inputMaxValue": look_at.range_map_vertical_up.input_max_value,
|
|
595
|
+
"outputScale": look_at.range_map_vertical_up.output_scale,
|
|
596
|
+
},
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
@classmethod
|
|
600
|
+
def create_expression_dict(
|
|
601
|
+
cls,
|
|
602
|
+
expression: Vrm1ExpressionPropertyGroup,
|
|
603
|
+
mesh_object_name_to_node_index_dict: Mapping[str, int],
|
|
604
|
+
mesh_object_name_to_morph_target_names_dict: Mapping[str, list[str]],
|
|
605
|
+
material_name_to_index_dict: Mapping[str, int],
|
|
606
|
+
) -> dict[str, Json]:
|
|
607
|
+
expression_dict: dict[str, Json] = {
|
|
608
|
+
"isBinary": expression.is_binary,
|
|
609
|
+
"overrideBlink": expression.override_blink,
|
|
610
|
+
"overrideLookAt": expression.override_look_at,
|
|
611
|
+
"overrideMouth": expression.override_mouth,
|
|
612
|
+
}
|
|
613
|
+
morph_target_bind_dicts: list[Json] = []
|
|
614
|
+
for morph_target_bind in expression.morph_target_binds:
|
|
615
|
+
if (
|
|
616
|
+
not morph_target_bind.node
|
|
617
|
+
or not morph_target_bind.node.mesh_object_name
|
|
618
|
+
):
|
|
619
|
+
continue
|
|
620
|
+
node_index = mesh_object_name_to_node_index_dict.get(
|
|
621
|
+
morph_target_bind.node.mesh_object_name
|
|
622
|
+
)
|
|
623
|
+
if not isinstance(node_index, int):
|
|
624
|
+
continue
|
|
625
|
+
morph_targets = mesh_object_name_to_morph_target_names_dict.get(
|
|
626
|
+
morph_target_bind.node.mesh_object_name
|
|
627
|
+
)
|
|
628
|
+
if not isinstance(morph_targets, list):
|
|
629
|
+
continue
|
|
630
|
+
if morph_target_bind.index not in morph_targets:
|
|
631
|
+
continue
|
|
632
|
+
morph_target_bind_dicts.append(
|
|
633
|
+
{
|
|
634
|
+
"node": node_index,
|
|
635
|
+
"index": morph_targets.index(morph_target_bind.index),
|
|
636
|
+
"weight": morph_target_bind.weight,
|
|
637
|
+
}
|
|
638
|
+
)
|
|
639
|
+
if morph_target_bind_dicts:
|
|
640
|
+
expression_dict["morphTargetBinds"] = morph_target_bind_dicts
|
|
641
|
+
|
|
642
|
+
material_color_bind_dicts: list[Json] = []
|
|
643
|
+
for material_color_bind in expression.material_color_binds:
|
|
644
|
+
material_color_bind_material = material_color_bind.material
|
|
645
|
+
if (
|
|
646
|
+
not material_color_bind_material
|
|
647
|
+
or not material_color_bind_material.name
|
|
648
|
+
):
|
|
649
|
+
continue
|
|
650
|
+
material_index = material_name_to_index_dict.get(
|
|
651
|
+
material_color_bind_material.name
|
|
652
|
+
)
|
|
653
|
+
if not isinstance(material_index, int):
|
|
654
|
+
continue
|
|
655
|
+
if material_color_bind.type == "color":
|
|
656
|
+
target_value: Json = list(material_color_bind.target_value)
|
|
657
|
+
else:
|
|
658
|
+
rgb = material_color_bind.target_value_as_rgb
|
|
659
|
+
target_value = [rgb[0], rgb[1], rgb[2], 1.0]
|
|
660
|
+
material_color_bind_dicts.append(
|
|
661
|
+
{
|
|
662
|
+
"material": material_index,
|
|
663
|
+
"type": material_color_bind.type,
|
|
664
|
+
"targetValue": target_value,
|
|
665
|
+
}
|
|
666
|
+
)
|
|
667
|
+
if material_color_bind_dicts:
|
|
668
|
+
expression_dict["materialColorBinds"] = material_color_bind_dicts
|
|
669
|
+
|
|
670
|
+
texture_transform_binds: list[Json] = []
|
|
671
|
+
for texture_transform_bind in expression.texture_transform_binds:
|
|
672
|
+
texture_transform_bind_material = texture_transform_bind.material
|
|
673
|
+
if (
|
|
674
|
+
not texture_transform_bind_material
|
|
675
|
+
or not texture_transform_bind_material.name
|
|
676
|
+
):
|
|
677
|
+
continue
|
|
678
|
+
material_index = material_name_to_index_dict.get(
|
|
679
|
+
texture_transform_bind_material.name
|
|
680
|
+
)
|
|
681
|
+
if not isinstance(material_index, int):
|
|
682
|
+
continue
|
|
683
|
+
texture_transform_binds.append(
|
|
684
|
+
{
|
|
685
|
+
"material": material_index,
|
|
686
|
+
"scale": list(texture_transform_bind.scale),
|
|
687
|
+
"offset": list(texture_transform_bind.offset),
|
|
688
|
+
}
|
|
689
|
+
)
|
|
690
|
+
if texture_transform_binds:
|
|
691
|
+
expression_dict["textureTransformBinds"] = texture_transform_binds
|
|
692
|
+
|
|
693
|
+
return expression_dict
|
|
694
|
+
|
|
695
|
+
@classmethod
|
|
696
|
+
def create_expressions_dict(
|
|
697
|
+
cls,
|
|
698
|
+
expressions: Vrm1ExpressionsPropertyGroup,
|
|
699
|
+
mesh_object_name_to_node_index_dict: Mapping[str, int],
|
|
700
|
+
mesh_object_name_to_morph_target_names_dict: Mapping[str, list[str]],
|
|
701
|
+
material_name_to_index_dict: dict[str, int],
|
|
702
|
+
) -> dict[str, Json]:
|
|
703
|
+
preset_dict: dict[str, Json] = {}
|
|
704
|
+
for (
|
|
705
|
+
preset_name,
|
|
706
|
+
expression,
|
|
707
|
+
) in expressions.preset.name_to_expression_dict().items():
|
|
708
|
+
preset_dict[preset_name] = cls.create_expression_dict(
|
|
709
|
+
expression,
|
|
710
|
+
mesh_object_name_to_node_index_dict,
|
|
711
|
+
mesh_object_name_to_morph_target_names_dict,
|
|
712
|
+
material_name_to_index_dict,
|
|
713
|
+
)
|
|
714
|
+
custom_dict: dict[str, Json] = {}
|
|
715
|
+
for custom_expression in expressions.custom:
|
|
716
|
+
custom_dict[custom_expression.custom_name] = cls.create_expression_dict(
|
|
717
|
+
custom_expression,
|
|
718
|
+
mesh_object_name_to_node_index_dict,
|
|
719
|
+
mesh_object_name_to_morph_target_names_dict,
|
|
720
|
+
material_name_to_index_dict,
|
|
721
|
+
)
|
|
722
|
+
return {
|
|
723
|
+
"preset": preset_dict,
|
|
724
|
+
"custom": custom_dict,
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
@classmethod
|
|
728
|
+
def create_spring_bone_collider_dicts(
|
|
729
|
+
cls,
|
|
730
|
+
extensions_used: list[Json],
|
|
731
|
+
spring_bone: SpringBone1SpringBonePropertyGroup,
|
|
732
|
+
bone_name_to_index_dict: Mapping[str, int],
|
|
733
|
+
) -> tuple[list[Json], dict[str, int]]:
|
|
734
|
+
collider_dicts: list[Json] = []
|
|
735
|
+
collider_uuid_to_index_dict: dict[str, int] = {}
|
|
736
|
+
for collider in spring_bone.colliders:
|
|
737
|
+
if not collider.uuid:
|
|
738
|
+
continue
|
|
739
|
+
collider_dict: dict[str, Json] = {}
|
|
740
|
+
node_index = bone_name_to_index_dict.get(collider.node.bone_name)
|
|
741
|
+
if not isinstance(node_index, int):
|
|
742
|
+
continue
|
|
743
|
+
collider_dict["node"] = node_index
|
|
744
|
+
|
|
745
|
+
extended_collider = collider.extensions.vrmc_spring_bone_extended_collider
|
|
746
|
+
if collider.shape_type == collider.SHAPE_TYPE_SPHERE.identifier:
|
|
747
|
+
if not extended_collider.enabled:
|
|
748
|
+
sphere_offset: Json = list(collider.shape.sphere.offset)
|
|
749
|
+
sphere_radius = collider.shape.sphere.radius
|
|
750
|
+
elif not extended_collider.automatic_fallback_generation:
|
|
751
|
+
sphere_offset = list(collider.shape.sphere.fallback_offset)
|
|
752
|
+
sphere_radius = collider.shape.sphere.fallback_radius
|
|
753
|
+
elif (
|
|
754
|
+
extended_collider.shape_type
|
|
755
|
+
== extended_collider.SHAPE_TYPE_EXTENDED_SPHERE.identifier
|
|
756
|
+
and not extended_collider.shape.sphere.inside
|
|
757
|
+
):
|
|
758
|
+
sphere_offset = list(extended_collider.shape.sphere.offset)
|
|
759
|
+
sphere_radius = extended_collider.shape.sphere.radius
|
|
760
|
+
else:
|
|
761
|
+
sphere_offset = [0, -10000, 0]
|
|
762
|
+
sphere_radius = 0.00001
|
|
763
|
+
sphere_dict: dict[str, Json] = {
|
|
764
|
+
"offset": sphere_offset,
|
|
765
|
+
"radius": sphere_radius,
|
|
766
|
+
}
|
|
767
|
+
shape_dict: dict[str, Json] = {"sphere": sphere_dict}
|
|
768
|
+
elif collider.shape_type == collider.SHAPE_TYPE_CAPSULE.identifier:
|
|
769
|
+
if not extended_collider.enabled:
|
|
770
|
+
capsule_offset: Json = list(collider.shape.capsule.offset)
|
|
771
|
+
capsule_radius = collider.shape.capsule.radius
|
|
772
|
+
capsule_tail: Json = list(collider.shape.capsule.tail)
|
|
773
|
+
elif not extended_collider.automatic_fallback_generation:
|
|
774
|
+
capsule_offset = list(collider.shape.capsule.fallback_offset)
|
|
775
|
+
capsule_radius = collider.shape.capsule.fallback_radius
|
|
776
|
+
capsule_tail = list(collider.shape.capsule.fallback_tail)
|
|
777
|
+
elif (
|
|
778
|
+
extended_collider.shape_type
|
|
779
|
+
== extended_collider.SHAPE_TYPE_EXTENDED_CAPSULE.identifier
|
|
780
|
+
and not extended_collider.shape.capsule.inside
|
|
781
|
+
):
|
|
782
|
+
capsule_offset = list(extended_collider.shape.capsule.offset)
|
|
783
|
+
capsule_radius = extended_collider.shape.capsule.radius
|
|
784
|
+
capsule_tail = list(extended_collider.shape.capsule.tail)
|
|
785
|
+
else:
|
|
786
|
+
capsule_offset = [0, -10000, 0]
|
|
787
|
+
capsule_radius = 0.00001
|
|
788
|
+
capsule_tail = [0, -10001, 0]
|
|
789
|
+
capsule_dict: dict[str, Json] = {
|
|
790
|
+
"offset": capsule_offset,
|
|
791
|
+
"radius": capsule_radius,
|
|
792
|
+
"tail": capsule_tail,
|
|
793
|
+
}
|
|
794
|
+
shape_dict = {"capsule": capsule_dict}
|
|
795
|
+
else:
|
|
796
|
+
continue
|
|
797
|
+
|
|
798
|
+
collider_dict["shape"] = shape_dict
|
|
799
|
+
collider_uuid_to_index_dict[collider.uuid] = len(collider_dicts)
|
|
800
|
+
collider_dicts.append(collider_dict)
|
|
801
|
+
|
|
802
|
+
if not extended_collider.enabled:
|
|
803
|
+
continue
|
|
804
|
+
|
|
805
|
+
if (
|
|
806
|
+
extended_collider.shape_type
|
|
807
|
+
== extended_collider.SHAPE_TYPE_EXTENDED_SPHERE.identifier
|
|
808
|
+
):
|
|
809
|
+
extended_shape_dict: dict[str, Json] = {
|
|
810
|
+
"sphere": {
|
|
811
|
+
"offset": list(extended_collider.shape.sphere.offset),
|
|
812
|
+
"radius": extended_collider.shape.sphere.radius,
|
|
813
|
+
"inside": extended_collider.shape.sphere.inside,
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
elif (
|
|
817
|
+
extended_collider.shape_type
|
|
818
|
+
== extended_collider.SHAPE_TYPE_EXTENDED_CAPSULE.identifier
|
|
819
|
+
):
|
|
820
|
+
extended_shape_dict = {
|
|
821
|
+
"capsule": {
|
|
822
|
+
"offset": list(extended_collider.shape.capsule.offset),
|
|
823
|
+
"radius": extended_collider.shape.capsule.radius,
|
|
824
|
+
"tail": list(extended_collider.shape.capsule.tail),
|
|
825
|
+
"inside": extended_collider.shape.capsule.inside,
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
elif (
|
|
829
|
+
extended_collider.shape_type
|
|
830
|
+
== extended_collider.SHAPE_TYPE_EXTENDED_PLANE.identifier
|
|
831
|
+
):
|
|
832
|
+
extended_shape_dict = {
|
|
833
|
+
"plane": {
|
|
834
|
+
"offset": list(extended_collider.shape.plane.offset),
|
|
835
|
+
"normal": list(extended_collider.shape.plane.normal),
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
else:
|
|
839
|
+
continue
|
|
840
|
+
|
|
841
|
+
collider_dict["extensions"] = {
|
|
842
|
+
"VRMC_springBone_extended_collider": {
|
|
843
|
+
"specVersion": "1.0",
|
|
844
|
+
"shape": extended_shape_dict,
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if "VRMC_springBone_extended_collider" not in extensions_used:
|
|
849
|
+
extensions_used.append("VRMC_springBone_extended_collider")
|
|
850
|
+
|
|
851
|
+
return collider_dicts, collider_uuid_to_index_dict
|
|
852
|
+
|
|
853
|
+
@classmethod
|
|
854
|
+
def create_spring_bone_collider_group_dicts(
|
|
855
|
+
cls,
|
|
856
|
+
spring_bone: SpringBone1SpringBonePropertyGroup,
|
|
857
|
+
collider_uuid_to_index_dict: Mapping[str, int],
|
|
858
|
+
) -> tuple[list[Json], dict[str, int]]:
|
|
859
|
+
collider_group_dicts: list[Json] = []
|
|
860
|
+
collider_group_uuid_to_index_dict: dict[str, int] = {}
|
|
861
|
+
for collider_group in spring_bone.collider_groups:
|
|
862
|
+
collider_group_dict: dict[str, Json] = {"name": collider_group.vrm_name}
|
|
863
|
+
collider_indices: list[Json] = []
|
|
864
|
+
for collider_reference in collider_group.colliders:
|
|
865
|
+
collider_index = collider_uuid_to_index_dict.get(
|
|
866
|
+
collider_reference.collider_uuid
|
|
867
|
+
)
|
|
868
|
+
if not isinstance(collider_index, int):
|
|
869
|
+
continue
|
|
870
|
+
if collider_index in collider_indices:
|
|
871
|
+
continue
|
|
872
|
+
collider_indices.append(collider_index)
|
|
873
|
+
if not collider_indices:
|
|
874
|
+
continue
|
|
875
|
+
collider_group_dict["colliders"] = collider_indices
|
|
876
|
+
collider_group_uuid_to_index_dict[collider_group.uuid] = len(
|
|
877
|
+
collider_group_dicts
|
|
878
|
+
)
|
|
879
|
+
collider_group_dicts.append(collider_group_dict)
|
|
880
|
+
|
|
881
|
+
return collider_group_dicts, collider_group_uuid_to_index_dict
|
|
882
|
+
|
|
883
|
+
@classmethod
|
|
884
|
+
def create_spring_bone_spring_dicts(
|
|
885
|
+
cls,
|
|
886
|
+
extensions_used: list[Json],
|
|
887
|
+
spring_bone: SpringBone1SpringBonePropertyGroup,
|
|
888
|
+
bone_name_to_index_dict: Mapping[str, int],
|
|
889
|
+
collider_group_uuid_to_index_dict: Mapping[str, int],
|
|
890
|
+
armature: Object,
|
|
891
|
+
) -> list[Json]:
|
|
892
|
+
spring_dicts: list[Json] = []
|
|
893
|
+
armature_data = armature.data
|
|
894
|
+
if not isinstance(armature_data, Armature):
|
|
895
|
+
_logger.error("%s is not an Armature", type(armature_data))
|
|
896
|
+
return []
|
|
897
|
+
sorted_spring_and_joints: list[
|
|
898
|
+
tuple[
|
|
899
|
+
SpringBone1SpringPropertyGroup, Sequence[SpringBone1JointPropertyGroup]
|
|
900
|
+
]
|
|
901
|
+
] = [
|
|
902
|
+
(
|
|
903
|
+
spring,
|
|
904
|
+
[
|
|
905
|
+
joint
|
|
906
|
+
for joint, _ in joint_and_bones
|
|
907
|
+
if isinstance(
|
|
908
|
+
bone_name_to_index_dict.get(joint.node.bone_name), int
|
|
909
|
+
)
|
|
910
|
+
],
|
|
911
|
+
)
|
|
912
|
+
for spring in spring_bone.springs
|
|
913
|
+
for joint_and_bones in sort_spring_bone_joints(armature, spring.joints)
|
|
914
|
+
]
|
|
915
|
+
for spring, joints in sorted_spring_and_joints:
|
|
916
|
+
spring_dict: dict[str, Json] = {"name": spring.vrm_name}
|
|
917
|
+
|
|
918
|
+
first_bone: Optional[Bone] = None
|
|
919
|
+
joint_dicts: list[Json] = []
|
|
920
|
+
for joint in joints:
|
|
921
|
+
bone = armature_data.bones.get(joint.node.bone_name)
|
|
922
|
+
if not bone:
|
|
923
|
+
continue
|
|
924
|
+
if first_bone is None:
|
|
925
|
+
first_bone = bone
|
|
926
|
+
node_index = bone_name_to_index_dict.get(joint.node.bone_name)
|
|
927
|
+
if not isinstance(node_index, int):
|
|
928
|
+
continue
|
|
929
|
+
joint_dict: dict[str, Json] = {
|
|
930
|
+
"node": node_index,
|
|
931
|
+
"hitRadius": joint.hit_radius,
|
|
932
|
+
"stiffness": joint.stiffness,
|
|
933
|
+
"gravityPower": joint.gravity_power,
|
|
934
|
+
"gravityDir": [
|
|
935
|
+
joint.gravity_dir[0],
|
|
936
|
+
joint.gravity_dir[2],
|
|
937
|
+
-joint.gravity_dir[1],
|
|
938
|
+
],
|
|
939
|
+
"dragForce": joint.drag_force,
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
limit = joint.vrmc_spring_bone_limit
|
|
943
|
+
limit_parameter_dict: Optional[dict[str, Json]] = None
|
|
944
|
+
limit_type = ""
|
|
945
|
+
if limit.limit_type == limit.LIMIT_TYPE_CONE.identifier:
|
|
946
|
+
limit_parameter_dict = {"angle": limit.cone_angle}
|
|
947
|
+
limit_type = "cone"
|
|
948
|
+
elif limit.limit_type == limit.LIMIT_TYPE_HINGE.identifier:
|
|
949
|
+
limit_parameter_dict = {"angle": limit.hinge_angle}
|
|
950
|
+
limit_type = "hinge"
|
|
951
|
+
elif limit.limit_type == limit.LIMIT_TYPE_SPHERICAL.identifier:
|
|
952
|
+
limit_parameter_dict = {
|
|
953
|
+
"pitch": limit.spherical_pitch,
|
|
954
|
+
"yaw": limit.spherical_yaw,
|
|
955
|
+
}
|
|
956
|
+
limit_type = "spherical"
|
|
957
|
+
|
|
958
|
+
if limit_parameter_dict is not None and joint is not joints[-1]:
|
|
959
|
+
rotation = limit.rotation_quaternion()
|
|
960
|
+
limit_parameter_dict["rotation"] = [
|
|
961
|
+
rotation.x,
|
|
962
|
+
rotation.y,
|
|
963
|
+
rotation.z,
|
|
964
|
+
rotation.w,
|
|
965
|
+
]
|
|
966
|
+
joint_dict["extensions"] = {
|
|
967
|
+
"VRMC_springBone_limit": {
|
|
968
|
+
"specVersion": "1.0",
|
|
969
|
+
"limit": {limit_type: limit_parameter_dict},
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
if "VRMC_springBone_limit" not in extensions_used:
|
|
973
|
+
extensions_used.append("VRMC_springBone_limit")
|
|
974
|
+
|
|
975
|
+
joint_dicts.append(joint_dict)
|
|
976
|
+
|
|
977
|
+
if not joint_dicts:
|
|
978
|
+
continue
|
|
979
|
+
spring_dict["joints"] = joint_dicts
|
|
980
|
+
|
|
981
|
+
center_bone = armature_data.bones.get(spring.center.bone_name)
|
|
982
|
+
if center_bone:
|
|
983
|
+
center_bone_is_ancestor_of_first_bone = False
|
|
984
|
+
ancestor_of_first_bone = first_bone
|
|
985
|
+
while ancestor_of_first_bone:
|
|
986
|
+
if center_bone == ancestor_of_first_bone:
|
|
987
|
+
center_bone_is_ancestor_of_first_bone = True
|
|
988
|
+
break
|
|
989
|
+
ancestor_of_first_bone = ancestor_of_first_bone.parent
|
|
990
|
+
if center_bone_is_ancestor_of_first_bone:
|
|
991
|
+
center_index = bone_name_to_index_dict.get(spring.center.bone_name)
|
|
992
|
+
if isinstance(center_index, int):
|
|
993
|
+
spring_dict["center"] = center_index
|
|
994
|
+
|
|
995
|
+
collider_group_indices: list[Json] = []
|
|
996
|
+
for collider_group_reference in spring.collider_groups:
|
|
997
|
+
collider_group_index = collider_group_uuid_to_index_dict.get(
|
|
998
|
+
collider_group_reference.collider_group_uuid
|
|
999
|
+
)
|
|
1000
|
+
if isinstance(collider_group_index, int):
|
|
1001
|
+
collider_group_indices.append(collider_group_index)
|
|
1002
|
+
|
|
1003
|
+
if collider_group_indices:
|
|
1004
|
+
spring_dict["colliderGroups"] = collider_group_indices
|
|
1005
|
+
|
|
1006
|
+
spring_dicts.append(spring_dict)
|
|
1007
|
+
return spring_dicts
|
|
1008
|
+
|
|
1009
|
+
@classmethod
|
|
1010
|
+
def search_constraint_target_index(
|
|
1011
|
+
cls,
|
|
1012
|
+
constraint: Union[CopyRotationConstraint, DampedTrackConstraint],
|
|
1013
|
+
object_name_to_index_dict: Mapping[str, int],
|
|
1014
|
+
bone_name_to_index_dict: Mapping[str, int],
|
|
1015
|
+
) -> Optional[int]:
|
|
1016
|
+
target = constraint.target
|
|
1017
|
+
if not target:
|
|
1018
|
+
return None
|
|
1019
|
+
if target.type == "ARMATURE" and constraint.subtarget:
|
|
1020
|
+
return bone_name_to_index_dict.get(constraint.subtarget)
|
|
1021
|
+
return object_name_to_index_dict.get(target.name)
|
|
1022
|
+
|
|
1023
|
+
@classmethod
|
|
1024
|
+
def create_constraint_dict(
|
|
1025
|
+
cls,
|
|
1026
|
+
name: str,
|
|
1027
|
+
constraints: search.ExportConstraint,
|
|
1028
|
+
object_name_to_index_dict: Mapping[str, int],
|
|
1029
|
+
bone_name_to_index_dict: Mapping[str, int],
|
|
1030
|
+
) -> Optional[dict[str, Json]]:
|
|
1031
|
+
roll_constraint = constraints.roll_constraints.get(name)
|
|
1032
|
+
aim_constraint = constraints.aim_constraints.get(name)
|
|
1033
|
+
rotation_constraint = constraints.rotation_constraints.get(name)
|
|
1034
|
+
if roll_constraint:
|
|
1035
|
+
source_index = cls.search_constraint_target_index(
|
|
1036
|
+
roll_constraint,
|
|
1037
|
+
object_name_to_index_dict,
|
|
1038
|
+
bone_name_to_index_dict,
|
|
1039
|
+
)
|
|
1040
|
+
if isinstance(source_index, int):
|
|
1041
|
+
if roll_constraint.use_x:
|
|
1042
|
+
roll_axis = "X"
|
|
1043
|
+
elif roll_constraint.use_y:
|
|
1044
|
+
roll_axis = "Y"
|
|
1045
|
+
elif roll_constraint.use_z:
|
|
1046
|
+
roll_axis = "Z"
|
|
1047
|
+
else:
|
|
1048
|
+
message = "Unsupported roll axis"
|
|
1049
|
+
raise ValueError(message)
|
|
1050
|
+
return {
|
|
1051
|
+
"roll": {
|
|
1052
|
+
"source": source_index,
|
|
1053
|
+
"rollAxis": roll_axis,
|
|
1054
|
+
"weight": max(0.0, min(1.0, roll_constraint.influence)),
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
elif aim_constraint:
|
|
1058
|
+
source_index = cls.search_constraint_target_index(
|
|
1059
|
+
aim_constraint,
|
|
1060
|
+
object_name_to_index_dict,
|
|
1061
|
+
bone_name_to_index_dict,
|
|
1062
|
+
)
|
|
1063
|
+
if isinstance(source_index, int):
|
|
1064
|
+
return {
|
|
1065
|
+
"aim": {
|
|
1066
|
+
"source": source_index,
|
|
1067
|
+
"aimAxis": convert.BPY_TRACK_AXIS_TO_VRM_AIM_AXIS[
|
|
1068
|
+
aim_constraint.track_axis
|
|
1069
|
+
],
|
|
1070
|
+
"weight": max(0.0, min(1.0, aim_constraint.influence)),
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
elif rotation_constraint:
|
|
1074
|
+
source_index = cls.search_constraint_target_index(
|
|
1075
|
+
rotation_constraint,
|
|
1076
|
+
object_name_to_index_dict,
|
|
1077
|
+
bone_name_to_index_dict,
|
|
1078
|
+
)
|
|
1079
|
+
if isinstance(source_index, int):
|
|
1080
|
+
return {
|
|
1081
|
+
"rotation": {
|
|
1082
|
+
"source": source_index,
|
|
1083
|
+
"weight": max(0.0, min(1.0, rotation_constraint.influence)),
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
return None
|
|
1087
|
+
|
|
1088
|
+
@classmethod
|
|
1089
|
+
def create_mtoon0_khr_texture_transform(
|
|
1090
|
+
cls, node: Node, texture_input_name: str
|
|
1091
|
+
) -> Optional[dict[str, Json]]:
|
|
1092
|
+
texture_input = node.inputs.get(texture_input_name)
|
|
1093
|
+
if not texture_input:
|
|
1094
|
+
return None
|
|
1095
|
+
|
|
1096
|
+
texture_input_links = texture_input.links
|
|
1097
|
+
if not texture_input_links:
|
|
1098
|
+
return None
|
|
1099
|
+
|
|
1100
|
+
x_from_node = texture_input_links[0].from_node
|
|
1101
|
+
if not x_from_node:
|
|
1102
|
+
return None
|
|
1103
|
+
|
|
1104
|
+
x_inputs = x_from_node.inputs
|
|
1105
|
+
if not x_inputs:
|
|
1106
|
+
return None
|
|
1107
|
+
|
|
1108
|
+
x_links = x_inputs[0].links
|
|
1109
|
+
if not x_links:
|
|
1110
|
+
return None
|
|
1111
|
+
|
|
1112
|
+
uv_offset_scaling_node = x_links[0].from_node
|
|
1113
|
+
if not uv_offset_scaling_node or uv_offset_scaling_node.type != "MAPPING":
|
|
1114
|
+
return None
|
|
1115
|
+
|
|
1116
|
+
location_input = uv_offset_scaling_node.inputs.get("Location")
|
|
1117
|
+
if isinstance(location_input, shader.VECTOR_SOCKET_CLASSES):
|
|
1118
|
+
offset_x = location_input.default_value[0]
|
|
1119
|
+
offset_y = location_input.default_value[1]
|
|
1120
|
+
else:
|
|
1121
|
+
offset_x = shader.UV_GROUP_UV_OFFSET_X_DEFAULT
|
|
1122
|
+
offset_y = shader.UV_GROUP_UV_OFFSET_Y_DEFAULT
|
|
1123
|
+
|
|
1124
|
+
scale_input = uv_offset_scaling_node.inputs.get("Scale")
|
|
1125
|
+
if isinstance(scale_input, shader.VECTOR_SOCKET_CLASSES):
|
|
1126
|
+
scale_x = scale_input.default_value[0]
|
|
1127
|
+
scale_y = scale_input.default_value[1]
|
|
1128
|
+
else:
|
|
1129
|
+
scale_x = shader.UV_GROUP_UV_SCALE_X_DEFAULT
|
|
1130
|
+
scale_y = shader.UV_GROUP_UV_SCALE_Y_DEFAULT
|
|
1131
|
+
|
|
1132
|
+
if (
|
|
1133
|
+
abs(offset_x - shader.UV_GROUP_UV_OFFSET_X_DEFAULT) < float_info.epsilon
|
|
1134
|
+
and abs(offset_y - shader.UV_GROUP_UV_OFFSET_Y_DEFAULT) < float_info.epsilon
|
|
1135
|
+
and abs(scale_x - shader.UV_GROUP_UV_SCALE_X_DEFAULT) < float_info.epsilon
|
|
1136
|
+
and abs(scale_y - shader.UV_GROUP_UV_SCALE_Y_DEFAULT) < float_info.epsilon
|
|
1137
|
+
):
|
|
1138
|
+
return None
|
|
1139
|
+
|
|
1140
|
+
return {
|
|
1141
|
+
"offset": [offset_x, offset_y],
|
|
1142
|
+
"scale": [scale_x, scale_y],
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
@classmethod
|
|
1146
|
+
def find_or_create_image(
|
|
1147
|
+
cls,
|
|
1148
|
+
json_dict: dict[str, Json],
|
|
1149
|
+
buffer0: bytearray,
|
|
1150
|
+
image_name_to_index_dict: dict[str, int],
|
|
1151
|
+
image: Image,
|
|
1152
|
+
gltf2_addon_export_settings: dict[str, object],
|
|
1153
|
+
) -> int:
|
|
1154
|
+
# Check if image already exists - return early to avoid duplicating buffer data
|
|
1155
|
+
image_index = image_name_to_index_dict.get(image.name)
|
|
1156
|
+
image_dicts = json_dict.get("images")
|
|
1157
|
+
if not isinstance(image_dicts, list):
|
|
1158
|
+
image_dicts = []
|
|
1159
|
+
json_dict["images"] = image_dicts
|
|
1160
|
+
|
|
1161
|
+
if isinstance(image_index, int):
|
|
1162
|
+
if 0 <= image_index < len(image_dicts):
|
|
1163
|
+
# Image already exists, return its index without duplicating data
|
|
1164
|
+
return image_index
|
|
1165
|
+
_logger.error(
|
|
1166
|
+
"Bug: not (0 <= %d < len(images)) for %s", image_index, image.name
|
|
1167
|
+
)
|
|
1168
|
+
image_index = None
|
|
1169
|
+
|
|
1170
|
+
# Image doesn't exist yet - create new buffer view and image entry
|
|
1171
|
+
while len(buffer0) % 4:
|
|
1172
|
+
buffer0.append(0)
|
|
1173
|
+
|
|
1174
|
+
image_bytes, mime = image_to_image_bytes(image, gltf2_addon_export_settings)
|
|
1175
|
+
buffer_view_dicts = json_dict.get("bufferViews")
|
|
1176
|
+
if not isinstance(buffer_view_dicts, list):
|
|
1177
|
+
buffer_view_dicts = []
|
|
1178
|
+
json_dict["bufferViews"] = buffer_view_dicts
|
|
1179
|
+
image_buffer_view_index = len(buffer_view_dicts)
|
|
1180
|
+
buffer_view_dicts.append(
|
|
1181
|
+
{
|
|
1182
|
+
"buffer": 0,
|
|
1183
|
+
"byteOffset": len(buffer0),
|
|
1184
|
+
"byteLength": len(image_bytes),
|
|
1185
|
+
}
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1188
|
+
image_index = len(image_dicts)
|
|
1189
|
+
image_dicts.append(
|
|
1190
|
+
{
|
|
1191
|
+
"name": image.name,
|
|
1192
|
+
"bufferView": image_buffer_view_index,
|
|
1193
|
+
"mimeType": mime,
|
|
1194
|
+
}
|
|
1195
|
+
)
|
|
1196
|
+
image_name_to_index_dict[image.name] = image_index
|
|
1197
|
+
|
|
1198
|
+
buffer0.extend(image_bytes)
|
|
1199
|
+
while len(buffer0) % 4:
|
|
1200
|
+
buffer0.append(0)
|
|
1201
|
+
|
|
1202
|
+
return image_index
|
|
1203
|
+
|
|
1204
|
+
@classmethod
|
|
1205
|
+
def create_mtoon1_texture_info_dict(
|
|
1206
|
+
cls,
|
|
1207
|
+
json_dict: dict[str, Json],
|
|
1208
|
+
buffer0: bytearray,
|
|
1209
|
+
texture_info: Mtoon1TextureInfoPropertyGroup,
|
|
1210
|
+
image_name_to_index_dict: dict[str, int],
|
|
1211
|
+
gltf2_addon_export_settings: dict[str, object],
|
|
1212
|
+
) -> Optional[dict[str, Json]]:
|
|
1213
|
+
image = texture_info.index.get_connected_node_image()
|
|
1214
|
+
if not image:
|
|
1215
|
+
return None
|
|
1216
|
+
|
|
1217
|
+
image_index = cls.find_or_create_image(
|
|
1218
|
+
json_dict,
|
|
1219
|
+
buffer0,
|
|
1220
|
+
image_name_to_index_dict,
|
|
1221
|
+
image,
|
|
1222
|
+
gltf2_addon_export_settings,
|
|
1223
|
+
)
|
|
1224
|
+
|
|
1225
|
+
mag_filter = Mtoon1SamplerPropertyGroup.mag_filter_enum.identifier_to_value(
|
|
1226
|
+
texture_info.index.sampler.mag_filter,
|
|
1227
|
+
Mtoon1SamplerPropertyGroup.MAG_FILTER_DEFAULT.value,
|
|
1228
|
+
)
|
|
1229
|
+
|
|
1230
|
+
min_filter = Mtoon1SamplerPropertyGroup.min_filter_enum.identifier_to_value(
|
|
1231
|
+
texture_info.index.sampler.min_filter,
|
|
1232
|
+
Mtoon1SamplerPropertyGroup.MIN_FILTER_DEFAULT.value,
|
|
1233
|
+
)
|
|
1234
|
+
|
|
1235
|
+
wrap_s = Mtoon1SamplerPropertyGroup.wrap_enum.identifier_to_value(
|
|
1236
|
+
texture_info.index.sampler.wrap_s,
|
|
1237
|
+
Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.value,
|
|
1238
|
+
)
|
|
1239
|
+
|
|
1240
|
+
wrap_t = Mtoon1SamplerPropertyGroup.wrap_enum.identifier_to_value(
|
|
1241
|
+
texture_info.index.sampler.wrap_t,
|
|
1242
|
+
Mtoon1SamplerPropertyGroup.WRAP_DEFAULT.value,
|
|
1243
|
+
)
|
|
1244
|
+
|
|
1245
|
+
sampler_dict: dict[str, Json] = {
|
|
1246
|
+
"magFilter": mag_filter,
|
|
1247
|
+
"minFilter": min_filter,
|
|
1248
|
+
"wrapS": wrap_s,
|
|
1249
|
+
"wrapT": wrap_t,
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
sampler_dicts = json_dict.get("samplers")
|
|
1253
|
+
if not isinstance(sampler_dicts, list):
|
|
1254
|
+
sampler_dicts = []
|
|
1255
|
+
json_dict["samplers"] = sampler_dicts
|
|
1256
|
+
|
|
1257
|
+
if sampler_dict in sampler_dicts:
|
|
1258
|
+
sampler_index = sampler_dicts.index(sampler_dict)
|
|
1259
|
+
else:
|
|
1260
|
+
sampler_index = len(sampler_dicts)
|
|
1261
|
+
sampler_dicts.append(sampler_dict)
|
|
1262
|
+
|
|
1263
|
+
texture_dicts = json_dict.get("textures")
|
|
1264
|
+
if not isinstance(texture_dicts, list):
|
|
1265
|
+
texture_dicts = []
|
|
1266
|
+
json_dict["textures"] = texture_dicts
|
|
1267
|
+
|
|
1268
|
+
texture_dict: dict[str, Json] = {
|
|
1269
|
+
"sampler": sampler_index,
|
|
1270
|
+
"source": image_index,
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
if texture_dict in texture_dicts:
|
|
1274
|
+
texture_index = texture_dicts.index(texture_dict)
|
|
1275
|
+
else:
|
|
1276
|
+
texture_index = len(texture_dicts)
|
|
1277
|
+
texture_dicts.append(texture_dict)
|
|
1278
|
+
|
|
1279
|
+
khr_texture_transform = texture_info.extensions.khr_texture_transform
|
|
1280
|
+
if khr_texture_transform.is_default():
|
|
1281
|
+
return {"index": texture_index}
|
|
1282
|
+
|
|
1283
|
+
extensions_used = json_dict.get("extensionsUsed")
|
|
1284
|
+
if not isinstance(extensions_used, list):
|
|
1285
|
+
extensions_used = []
|
|
1286
|
+
json_dict["extensionsUsed"] = extensions_used
|
|
1287
|
+
|
|
1288
|
+
if "KHR_texture_transform" not in extensions_used:
|
|
1289
|
+
extensions_used.append("KHR_texture_transform")
|
|
1290
|
+
json_dict["extensionsUsed"] = extensions_used
|
|
1291
|
+
|
|
1292
|
+
khr_texture_transform_dict: dict[str, Json] = {
|
|
1293
|
+
"offset": list(khr_texture_transform.offset),
|
|
1294
|
+
"scale": list(khr_texture_transform.scale),
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
return {
|
|
1298
|
+
"index": texture_index,
|
|
1299
|
+
"extensions": {"KHR_texture_transform": khr_texture_transform_dict},
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
@classmethod
|
|
1303
|
+
def create_mtoon0_texture_info_dict(
|
|
1304
|
+
cls,
|
|
1305
|
+
context: Context,
|
|
1306
|
+
json_dict: dict[str, Json],
|
|
1307
|
+
buffer0: bytearray,
|
|
1308
|
+
node: Node,
|
|
1309
|
+
texture_input_name: str,
|
|
1310
|
+
image_name_to_index_dict: dict[str, int],
|
|
1311
|
+
gltf2_addon_export_settings: dict[str, object],
|
|
1312
|
+
) -> Optional[dict[str, Json]]:
|
|
1313
|
+
image_name_and_sampler_type = shader.get_image_name_and_sampler_type(
|
|
1314
|
+
node, texture_input_name
|
|
1315
|
+
)
|
|
1316
|
+
if image_name_and_sampler_type is None:
|
|
1317
|
+
return None
|
|
1318
|
+
|
|
1319
|
+
image_name, wrap_type, filter_type = image_name_and_sampler_type
|
|
1320
|
+
image_index = cls.find_or_create_image(
|
|
1321
|
+
json_dict,
|
|
1322
|
+
buffer0,
|
|
1323
|
+
image_name_to_index_dict,
|
|
1324
|
+
context.blend_data.images[image_name],
|
|
1325
|
+
gltf2_addon_export_settings,
|
|
1326
|
+
)
|
|
1327
|
+
|
|
1328
|
+
sampler_dict: dict[str, Json] = {
|
|
1329
|
+
"magFilter": filter_type,
|
|
1330
|
+
"minFilter": filter_type,
|
|
1331
|
+
"wrapS": wrap_type,
|
|
1332
|
+
"wrapT": wrap_type,
|
|
1333
|
+
}
|
|
1334
|
+
sampler_dicts = json_dict.get("samplers")
|
|
1335
|
+
if not isinstance(sampler_dicts, list):
|
|
1336
|
+
sampler_dicts = []
|
|
1337
|
+
json_dict["samplers"] = sampler_dicts
|
|
1338
|
+
if sampler_dict in sampler_dicts:
|
|
1339
|
+
sampler_index = sampler_dicts.index(sampler_dict)
|
|
1340
|
+
else:
|
|
1341
|
+
sampler_index = len(sampler_dicts)
|
|
1342
|
+
sampler_dicts.append(sampler_dict)
|
|
1343
|
+
|
|
1344
|
+
texture_dicts = json_dict.get("textures")
|
|
1345
|
+
if not isinstance(texture_dicts, list):
|
|
1346
|
+
texture_dicts = []
|
|
1347
|
+
json_dict["textures"] = texture_dicts
|
|
1348
|
+
|
|
1349
|
+
texture_dict: dict[str, Json] = {
|
|
1350
|
+
"sampler": sampler_index,
|
|
1351
|
+
"source": image_index,
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
if texture_dict in texture_dicts:
|
|
1355
|
+
texture_index = texture_dicts.index(texture_dict)
|
|
1356
|
+
else:
|
|
1357
|
+
texture_index = len(texture_dicts)
|
|
1358
|
+
texture_dicts.append(texture_dict)
|
|
1359
|
+
|
|
1360
|
+
khr_texture_transform_dict = cls.create_mtoon0_khr_texture_transform(
|
|
1361
|
+
node, texture_input_name
|
|
1362
|
+
)
|
|
1363
|
+
if not khr_texture_transform_dict:
|
|
1364
|
+
return {"index": texture_index}
|
|
1365
|
+
|
|
1366
|
+
extensions_used = json_dict.get("extensionsUsed")
|
|
1367
|
+
if not isinstance(extensions_used, list):
|
|
1368
|
+
extensions_used = []
|
|
1369
|
+
json_dict["extensionsUsed"] = extensions_used
|
|
1370
|
+
|
|
1371
|
+
if "KHR_texture_transform" not in extensions_used:
|
|
1372
|
+
extensions_used.append("KHR_texture_transform")
|
|
1373
|
+
json_dict["extensionsUsed"] = extensions_used
|
|
1374
|
+
|
|
1375
|
+
return {
|
|
1376
|
+
"index": texture_index,
|
|
1377
|
+
"extensions": {"KHR_texture_transform": khr_texture_transform_dict},
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
@classmethod
|
|
1381
|
+
def create_mtoon1_material_dict(
|
|
1382
|
+
cls,
|
|
1383
|
+
json_dict: dict[str, Json],
|
|
1384
|
+
buffer0: bytearray,
|
|
1385
|
+
material: Material,
|
|
1386
|
+
image_name_to_index_dict: dict[str, int],
|
|
1387
|
+
gltf2_addon_export_settings: dict[str, object],
|
|
1388
|
+
) -> dict[str, Json]:
|
|
1389
|
+
extensions_used = json_dict.get("extensionsUsed")
|
|
1390
|
+
if not isinstance(extensions_used, list):
|
|
1391
|
+
extensions_used = []
|
|
1392
|
+
json_dict["extensionsUsed"] = extensions_used
|
|
1393
|
+
|
|
1394
|
+
if "KHR_materials_unlit" not in extensions_used:
|
|
1395
|
+
extensions_used.append("KHR_materials_unlit")
|
|
1396
|
+
if "KHR_materials_emissive_strength" not in extensions_used:
|
|
1397
|
+
extensions_used.append("KHR_materials_emissive_strength")
|
|
1398
|
+
if "VRMC_materials_mtoon" not in extensions_used:
|
|
1399
|
+
extensions_used.append("VRMC_materials_mtoon")
|
|
1400
|
+
|
|
1401
|
+
# https://github.com/vrm-c/UniVRM/blob/f3479190c330ec6ecd2b40be919285aa93a53aff/Assets/VRM10/Runtime/Migration/Materials/MigrationMToonMaterial.cs
|
|
1402
|
+
mtoon_dict: dict[str, Json] = {
|
|
1403
|
+
"specVersion": "1.0",
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
gltf = get_material_extension(material).mtoon1
|
|
1407
|
+
mtoon = gltf.extensions.vrmc_materials_mtoon
|
|
1408
|
+
|
|
1409
|
+
extensions_dict: dict[str, Json] = {
|
|
1410
|
+
"KHR_materials_unlit": {},
|
|
1411
|
+
"KHR_materials_emissive_strength": {
|
|
1412
|
+
"emissiveStrength": (
|
|
1413
|
+
gltf.extensions.khr_materials_emissive_strength.emissive_strength
|
|
1414
|
+
),
|
|
1415
|
+
},
|
|
1416
|
+
"VRMC_materials_mtoon": mtoon_dict,
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
material_dict: dict[str, Json] = {"name": material.name}
|
|
1420
|
+
pbr_metallic_roughness_dict: dict[str, Json] = {}
|
|
1421
|
+
|
|
1422
|
+
material_dict["alphaMode"] = gltf.alpha_mode
|
|
1423
|
+
mtoon_dict["transparentWithZWrite"] = mtoon.transparent_with_z_write
|
|
1424
|
+
mtoon_dict["renderQueueOffsetNumber"] = mtoon.render_queue_offset_number
|
|
1425
|
+
if gltf.alpha_mode == "MASK":
|
|
1426
|
+
material_dict["alphaCutoff"] = gltf.alpha_cutoff
|
|
1427
|
+
material_dict["doubleSided"] = gltf.double_sided
|
|
1428
|
+
pbr_metallic_roughness_dict["baseColorFactor"] = list(
|
|
1429
|
+
gltf.pbr_metallic_roughness.base_color_factor
|
|
1430
|
+
)
|
|
1431
|
+
assign_dict(
|
|
1432
|
+
pbr_metallic_roughness_dict,
|
|
1433
|
+
"baseColorTexture",
|
|
1434
|
+
cls.create_mtoon1_texture_info_dict(
|
|
1435
|
+
json_dict,
|
|
1436
|
+
buffer0,
|
|
1437
|
+
gltf.pbr_metallic_roughness.base_color_texture,
|
|
1438
|
+
image_name_to_index_dict,
|
|
1439
|
+
gltf2_addon_export_settings,
|
|
1440
|
+
),
|
|
1441
|
+
)
|
|
1442
|
+
mtoon_dict["shadeColorFactor"] = list(mtoon.shade_color_factor)
|
|
1443
|
+
assign_dict(
|
|
1444
|
+
mtoon_dict,
|
|
1445
|
+
"shadeMultiplyTexture",
|
|
1446
|
+
cls.create_mtoon1_texture_info_dict(
|
|
1447
|
+
json_dict,
|
|
1448
|
+
buffer0,
|
|
1449
|
+
mtoon.shade_multiply_texture,
|
|
1450
|
+
image_name_to_index_dict,
|
|
1451
|
+
gltf2_addon_export_settings,
|
|
1452
|
+
),
|
|
1453
|
+
)
|
|
1454
|
+
assign_dict(
|
|
1455
|
+
material_dict,
|
|
1456
|
+
"normalTexture",
|
|
1457
|
+
cls.create_mtoon1_texture_info_dict(
|
|
1458
|
+
json_dict,
|
|
1459
|
+
buffer0,
|
|
1460
|
+
gltf.normal_texture,
|
|
1461
|
+
image_name_to_index_dict,
|
|
1462
|
+
gltf2_addon_export_settings,
|
|
1463
|
+
),
|
|
1464
|
+
)
|
|
1465
|
+
normal_texture_dict = material_dict.get("normalTexture")
|
|
1466
|
+
if isinstance(normal_texture_dict, dict):
|
|
1467
|
+
normal_texture_dict["scale"] = gltf.normal_texture.scale
|
|
1468
|
+
mtoon_dict["shadingShiftFactor"] = mtoon.shading_shift_factor
|
|
1469
|
+
assign_dict(
|
|
1470
|
+
mtoon_dict,
|
|
1471
|
+
"shadingShiftTexture",
|
|
1472
|
+
cls.create_mtoon1_texture_info_dict(
|
|
1473
|
+
json_dict,
|
|
1474
|
+
buffer0,
|
|
1475
|
+
mtoon.shading_shift_texture,
|
|
1476
|
+
image_name_to_index_dict,
|
|
1477
|
+
gltf2_addon_export_settings,
|
|
1478
|
+
),
|
|
1479
|
+
)
|
|
1480
|
+
shading_shift_texture_dict = mtoon_dict.get("shadingShiftTexture")
|
|
1481
|
+
if isinstance(shading_shift_texture_dict, dict):
|
|
1482
|
+
shading_shift_texture_dict["scale"] = mtoon.shading_shift_texture.scale
|
|
1483
|
+
mtoon_dict["shadingToonyFactor"] = mtoon.shading_toony_factor
|
|
1484
|
+
mtoon_dict["giEqualizationFactor"] = mtoon.gi_equalization_factor
|
|
1485
|
+
material_dict["emissiveFactor"] = list(gltf.emissive_factor)
|
|
1486
|
+
assign_dict(
|
|
1487
|
+
material_dict,
|
|
1488
|
+
"emissiveTexture",
|
|
1489
|
+
cls.create_mtoon1_texture_info_dict(
|
|
1490
|
+
json_dict,
|
|
1491
|
+
buffer0,
|
|
1492
|
+
gltf.emissive_texture,
|
|
1493
|
+
image_name_to_index_dict,
|
|
1494
|
+
gltf2_addon_export_settings,
|
|
1495
|
+
),
|
|
1496
|
+
)
|
|
1497
|
+
if assign_dict(
|
|
1498
|
+
mtoon_dict,
|
|
1499
|
+
"matcapTexture",
|
|
1500
|
+
cls.create_mtoon1_texture_info_dict(
|
|
1501
|
+
json_dict,
|
|
1502
|
+
buffer0,
|
|
1503
|
+
mtoon.matcap_texture,
|
|
1504
|
+
image_name_to_index_dict,
|
|
1505
|
+
gltf2_addon_export_settings,
|
|
1506
|
+
),
|
|
1507
|
+
):
|
|
1508
|
+
mtoon_dict["matcapFactor"] = list(mtoon.matcap_factor)
|
|
1509
|
+
|
|
1510
|
+
mtoon_dict["parametricRimColorFactor"] = list(mtoon.parametric_rim_color_factor)
|
|
1511
|
+
mtoon_dict["parametricRimFresnelPowerFactor"] = (
|
|
1512
|
+
mtoon.parametric_rim_fresnel_power_factor
|
|
1513
|
+
)
|
|
1514
|
+
mtoon_dict["parametricRimLiftFactor"] = mtoon.parametric_rim_lift_factor
|
|
1515
|
+
assign_dict(
|
|
1516
|
+
mtoon_dict,
|
|
1517
|
+
"rimMultiplyTexture",
|
|
1518
|
+
cls.create_mtoon1_texture_info_dict(
|
|
1519
|
+
json_dict,
|
|
1520
|
+
buffer0,
|
|
1521
|
+
mtoon.rim_multiply_texture,
|
|
1522
|
+
image_name_to_index_dict,
|
|
1523
|
+
gltf2_addon_export_settings,
|
|
1524
|
+
),
|
|
1525
|
+
)
|
|
1526
|
+
mtoon_dict["rimLightingMixFactor"] = mtoon.rim_lighting_mix_factor
|
|
1527
|
+
mtoon_dict["outlineWidthMode"] = mtoon.outline_width_mode
|
|
1528
|
+
mtoon_dict["outlineWidthFactor"] = mtoon.outline_width_factor
|
|
1529
|
+
assign_dict(
|
|
1530
|
+
mtoon_dict,
|
|
1531
|
+
"outlineWidthMultiplyTexture",
|
|
1532
|
+
cls.create_mtoon1_texture_info_dict(
|
|
1533
|
+
json_dict,
|
|
1534
|
+
buffer0,
|
|
1535
|
+
mtoon.outline_width_multiply_texture,
|
|
1536
|
+
image_name_to_index_dict,
|
|
1537
|
+
gltf2_addon_export_settings,
|
|
1538
|
+
),
|
|
1539
|
+
)
|
|
1540
|
+
mtoon_dict["outlineColorFactor"] = list(mtoon.outline_color_factor)
|
|
1541
|
+
mtoon_dict["outlineLightingMixFactor"] = mtoon.outline_lighting_mix_factor
|
|
1542
|
+
assign_dict(
|
|
1543
|
+
mtoon_dict,
|
|
1544
|
+
"uvAnimationMaskTexture",
|
|
1545
|
+
cls.create_mtoon1_texture_info_dict(
|
|
1546
|
+
json_dict,
|
|
1547
|
+
buffer0,
|
|
1548
|
+
mtoon.uv_animation_mask_texture,
|
|
1549
|
+
image_name_to_index_dict,
|
|
1550
|
+
gltf2_addon_export_settings,
|
|
1551
|
+
),
|
|
1552
|
+
)
|
|
1553
|
+
mtoon_dict["uvAnimationRotationSpeedFactor"] = (
|
|
1554
|
+
mtoon.uv_animation_rotation_speed_factor
|
|
1555
|
+
)
|
|
1556
|
+
mtoon_dict["uvAnimationScrollXSpeedFactor"] = (
|
|
1557
|
+
mtoon.uv_animation_scroll_x_speed_factor
|
|
1558
|
+
)
|
|
1559
|
+
mtoon_dict["uvAnimationScrollYSpeedFactor"] = (
|
|
1560
|
+
mtoon.uv_animation_scroll_y_speed_factor
|
|
1561
|
+
)
|
|
1562
|
+
|
|
1563
|
+
if pbr_metallic_roughness_dict:
|
|
1564
|
+
material_dict["pbrMetallicRoughness"] = pbr_metallic_roughness_dict
|
|
1565
|
+
|
|
1566
|
+
extensions_dict["VRMC_materials_mtoon"] = mtoon_dict
|
|
1567
|
+
material_dict["extensions"] = extensions_dict
|
|
1568
|
+
|
|
1569
|
+
return material_dict
|
|
1570
|
+
|
|
1571
|
+
@classmethod
|
|
1572
|
+
def create_legacy_gltf_material_dict(
|
|
1573
|
+
cls,
|
|
1574
|
+
context: Context,
|
|
1575
|
+
json_dict: dict[str, Json],
|
|
1576
|
+
buffer0: bytearray,
|
|
1577
|
+
material: Material,
|
|
1578
|
+
node: Node,
|
|
1579
|
+
image_name_to_index_dict: dict[str, int],
|
|
1580
|
+
gltf2_addon_export_settings: dict[str, object],
|
|
1581
|
+
) -> dict[str, Json]:
|
|
1582
|
+
material_dict: dict[str, Json] = {"name": material.name}
|
|
1583
|
+
pbr_metallic_roughness_dict: dict[str, Json] = {}
|
|
1584
|
+
|
|
1585
|
+
if material.blend_method == "OPAQUE":
|
|
1586
|
+
material_dict["alphaMode"] = "OPAQUE"
|
|
1587
|
+
elif material.blend_method == "CLIP":
|
|
1588
|
+
material_dict["alphaCutoff"] = max(0.0, min(1.0, material.alpha_threshold))
|
|
1589
|
+
material_dict["alphaMode"] = "MASK"
|
|
1590
|
+
else:
|
|
1591
|
+
material_dict["alphaMode"] = "BLEND"
|
|
1592
|
+
assign_dict(
|
|
1593
|
+
material_dict, "doubleSided", not material.use_backface_culling, False
|
|
1594
|
+
)
|
|
1595
|
+
assign_dict(
|
|
1596
|
+
pbr_metallic_roughness_dict,
|
|
1597
|
+
"baseColorFactor",
|
|
1598
|
+
shader.get_rgba_value(node, "base_Color", 0.0, 1.0),
|
|
1599
|
+
)
|
|
1600
|
+
base_color_texture_dict = cls.create_mtoon0_texture_info_dict(
|
|
1601
|
+
context,
|
|
1602
|
+
json_dict,
|
|
1603
|
+
buffer0,
|
|
1604
|
+
node,
|
|
1605
|
+
"color_texture",
|
|
1606
|
+
image_name_to_index_dict,
|
|
1607
|
+
gltf2_addon_export_settings,
|
|
1608
|
+
)
|
|
1609
|
+
assign_dict(
|
|
1610
|
+
pbr_metallic_roughness_dict, "baseColorTexture", base_color_texture_dict
|
|
1611
|
+
)
|
|
1612
|
+
|
|
1613
|
+
assign_dict(
|
|
1614
|
+
pbr_metallic_roughness_dict,
|
|
1615
|
+
"metallicFactor",
|
|
1616
|
+
shader.get_float_value(node, "metallic", 0.0, 1.0),
|
|
1617
|
+
default_value=1.0,
|
|
1618
|
+
)
|
|
1619
|
+
assign_dict(
|
|
1620
|
+
pbr_metallic_roughness_dict,
|
|
1621
|
+
"roughnessFactor",
|
|
1622
|
+
shader.get_float_value(node, "roughness", 0.0, 1.0),
|
|
1623
|
+
default_value=1.0,
|
|
1624
|
+
)
|
|
1625
|
+
assign_dict(
|
|
1626
|
+
pbr_metallic_roughness_dict,
|
|
1627
|
+
"metallicRoughnessTexture",
|
|
1628
|
+
cls.create_mtoon0_texture_info_dict(
|
|
1629
|
+
context,
|
|
1630
|
+
json_dict,
|
|
1631
|
+
buffer0,
|
|
1632
|
+
node,
|
|
1633
|
+
"emissive_texture",
|
|
1634
|
+
image_name_to_index_dict,
|
|
1635
|
+
gltf2_addon_export_settings,
|
|
1636
|
+
),
|
|
1637
|
+
)
|
|
1638
|
+
|
|
1639
|
+
normal_texture_dict = cls.create_mtoon0_texture_info_dict(
|
|
1640
|
+
context,
|
|
1641
|
+
json_dict,
|
|
1642
|
+
buffer0,
|
|
1643
|
+
node,
|
|
1644
|
+
"normal",
|
|
1645
|
+
image_name_to_index_dict,
|
|
1646
|
+
gltf2_addon_export_settings,
|
|
1647
|
+
)
|
|
1648
|
+
assign_dict(material_dict, "normalTexture", normal_texture_dict)
|
|
1649
|
+
|
|
1650
|
+
assign_dict(
|
|
1651
|
+
material_dict,
|
|
1652
|
+
"emissiveFactor",
|
|
1653
|
+
shader.get_rgb_value(node, "emissive_color", 0.0, 1.0),
|
|
1654
|
+
)
|
|
1655
|
+
assign_dict(
|
|
1656
|
+
material_dict,
|
|
1657
|
+
"emissiveTexture",
|
|
1658
|
+
cls.create_mtoon0_texture_info_dict(
|
|
1659
|
+
context,
|
|
1660
|
+
json_dict,
|
|
1661
|
+
buffer0,
|
|
1662
|
+
node,
|
|
1663
|
+
"emissive_texture",
|
|
1664
|
+
image_name_to_index_dict,
|
|
1665
|
+
gltf2_addon_export_settings,
|
|
1666
|
+
),
|
|
1667
|
+
)
|
|
1668
|
+
|
|
1669
|
+
assign_dict(
|
|
1670
|
+
material_dict,
|
|
1671
|
+
"occlusionTexture",
|
|
1672
|
+
cls.create_mtoon0_texture_info_dict(
|
|
1673
|
+
context,
|
|
1674
|
+
json_dict,
|
|
1675
|
+
buffer0,
|
|
1676
|
+
node,
|
|
1677
|
+
"occlusion_texture",
|
|
1678
|
+
image_name_to_index_dict,
|
|
1679
|
+
gltf2_addon_export_settings,
|
|
1680
|
+
),
|
|
1681
|
+
)
|
|
1682
|
+
|
|
1683
|
+
if pbr_metallic_roughness_dict:
|
|
1684
|
+
material_dict["pbrMetallicRoughness"] = pbr_metallic_roughness_dict
|
|
1685
|
+
|
|
1686
|
+
unlit_value = shader.get_float_value(node, "unlit")
|
|
1687
|
+
if unlit_value is None or unlit_value > 0.5:
|
|
1688
|
+
extensions_used = json_dict.get("extensionsUsed")
|
|
1689
|
+
if not isinstance(extensions_used, list):
|
|
1690
|
+
extensions_used = []
|
|
1691
|
+
json_dict["extensionsUsed"] = extensions_used
|
|
1692
|
+
|
|
1693
|
+
if "KHR_materials_unlit" not in extensions_used:
|
|
1694
|
+
extensions_used.append("KHR_materials_unlit")
|
|
1695
|
+
|
|
1696
|
+
material_dict["extensions"] = {"KHR_materials_unlit": {}}
|
|
1697
|
+
|
|
1698
|
+
return material_dict
|
|
1699
|
+
|
|
1700
|
+
@classmethod
|
|
1701
|
+
def create_legacy_transparent_zwrite_material_dict(
|
|
1702
|
+
cls,
|
|
1703
|
+
context: Context,
|
|
1704
|
+
json_dict: dict[str, Json],
|
|
1705
|
+
buffer0: bytearray,
|
|
1706
|
+
material: Material,
|
|
1707
|
+
node: Node,
|
|
1708
|
+
image_name_to_index_dict: dict[str, int],
|
|
1709
|
+
gltf2_addon_export_settings: dict[str, object],
|
|
1710
|
+
) -> dict[str, Json]:
|
|
1711
|
+
# https://vrm-c.github.io/UniVRM/en/implementation/transparent_zwrite.html#mtoon-unlit
|
|
1712
|
+
extensions_used = json_dict.get("extensionsUsed")
|
|
1713
|
+
if not isinstance(extensions_used, list):
|
|
1714
|
+
extensions_used = []
|
|
1715
|
+
json_dict["extensionsUsed"] = extensions_used
|
|
1716
|
+
|
|
1717
|
+
if "KHR_materials_unlit" not in extensions_used:
|
|
1718
|
+
extensions_used.append("KHR_materials_unlit")
|
|
1719
|
+
if "VRMC_materials_mtoon" not in extensions_used:
|
|
1720
|
+
extensions_used.append("VRMC_materials_mtoon")
|
|
1721
|
+
|
|
1722
|
+
mtoon_dict: dict[str, Json] = {
|
|
1723
|
+
"specVersion": "1.0",
|
|
1724
|
+
}
|
|
1725
|
+
extensions_dict: dict[str, Json] = {
|
|
1726
|
+
"KHR_materials_unlit": {},
|
|
1727
|
+
"VRMC_materials_mtoon": mtoon_dict,
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
material_dict: dict[str, Json] = {
|
|
1731
|
+
"name": material.name,
|
|
1732
|
+
"emissiveFactor": [1, 1, 1],
|
|
1733
|
+
}
|
|
1734
|
+
pbr_metallic_roughness_dict: dict[str, Json] = {
|
|
1735
|
+
"baseColorFactor": [0, 0, 0, 1],
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
material_dict["alphaMode"] = "BLEND"
|
|
1739
|
+
mtoon_dict["transparentWithZWrite"] = True
|
|
1740
|
+
mtoon_dict["renderQueueOffsetNumber"] = 0
|
|
1741
|
+
assign_dict(
|
|
1742
|
+
material_dict, "doubleSided", not material.use_backface_culling, False
|
|
1743
|
+
)
|
|
1744
|
+
base_color_texture_dict = cls.create_mtoon0_texture_info_dict(
|
|
1745
|
+
context,
|
|
1746
|
+
json_dict,
|
|
1747
|
+
buffer0,
|
|
1748
|
+
node,
|
|
1749
|
+
"Main_Texture",
|
|
1750
|
+
image_name_to_index_dict,
|
|
1751
|
+
gltf2_addon_export_settings,
|
|
1752
|
+
)
|
|
1753
|
+
assign_dict(
|
|
1754
|
+
pbr_metallic_roughness_dict, "baseColorTexture", base_color_texture_dict
|
|
1755
|
+
)
|
|
1756
|
+
if base_color_texture_dict is not None:
|
|
1757
|
+
mtoon_dict["shadeMultiplyTexture"] = base_color_texture_dict
|
|
1758
|
+
material_dict["emissiveTexture"] = base_color_texture_dict
|
|
1759
|
+
|
|
1760
|
+
if pbr_metallic_roughness_dict:
|
|
1761
|
+
material_dict["pbrMetallicRoughness"] = pbr_metallic_roughness_dict
|
|
1762
|
+
|
|
1763
|
+
extensions_dict["VRMC_materials_mtoon"] = mtoon_dict
|
|
1764
|
+
material_dict["extensions"] = extensions_dict
|
|
1765
|
+
|
|
1766
|
+
return material_dict
|
|
1767
|
+
|
|
1768
|
+
@classmethod
|
|
1769
|
+
def create_mmd_material_dict(
|
|
1770
|
+
cls,
|
|
1771
|
+
_context: Context,
|
|
1772
|
+
json_dict: dict[str, Json],
|
|
1773
|
+
buffer0: bytearray,
|
|
1774
|
+
material: Material,
|
|
1775
|
+
mmd_material: MmdMaterial,
|
|
1776
|
+
image_name_to_index_dict: dict[str, int],
|
|
1777
|
+
gltf2_addon_export_settings: dict[str, object],
|
|
1778
|
+
) -> dict[str, Json]:
|
|
1779
|
+
material_dict: dict[str, Json] = {
|
|
1780
|
+
"name": material.name,
|
|
1781
|
+
}
|
|
1782
|
+
if mmd_material.double_sided:
|
|
1783
|
+
material_dict["doubleSided"] = True
|
|
1784
|
+
if mmd_material.alpha < 1.0:
|
|
1785
|
+
material_dict["alphaMode"] = "BLEND"
|
|
1786
|
+
|
|
1787
|
+
if texture_image := mmd_material.texture:
|
|
1788
|
+
pbr_metallic_roughness_dict: dict[str, Json] = {
|
|
1789
|
+
"baseColorFactor": [0, 0, 0, mmd_material.alpha],
|
|
1790
|
+
}
|
|
1791
|
+
image_index = cls.find_or_create_image(
|
|
1792
|
+
json_dict,
|
|
1793
|
+
buffer0,
|
|
1794
|
+
image_name_to_index_dict,
|
|
1795
|
+
texture_image,
|
|
1796
|
+
gltf2_addon_export_settings,
|
|
1797
|
+
)
|
|
1798
|
+
|
|
1799
|
+
sampler_dict: dict[str, Json] = {
|
|
1800
|
+
"magFilter": GL_LINEAR,
|
|
1801
|
+
"minFilter": GL_LINEAR,
|
|
1802
|
+
"wrapS": GL_REPEAT,
|
|
1803
|
+
"wrapT": GL_REPEAT,
|
|
1804
|
+
}
|
|
1805
|
+
sampler_dicts = json_dict.get("samplers")
|
|
1806
|
+
if not isinstance(sampler_dicts, list):
|
|
1807
|
+
sampler_dicts = []
|
|
1808
|
+
json_dict["samplers"] = sampler_dicts
|
|
1809
|
+
if sampler_dict in sampler_dicts:
|
|
1810
|
+
sampler_index = sampler_dicts.index(sampler_dict)
|
|
1811
|
+
else:
|
|
1812
|
+
sampler_index = len(sampler_dicts)
|
|
1813
|
+
sampler_dicts.append(sampler_dict)
|
|
1814
|
+
|
|
1815
|
+
texture_dicts = json_dict.get("textures")
|
|
1816
|
+
if not isinstance(texture_dicts, list):
|
|
1817
|
+
texture_dicts = []
|
|
1818
|
+
json_dict["textures"] = texture_dicts
|
|
1819
|
+
|
|
1820
|
+
texture_dict: dict[str, Json] = {
|
|
1821
|
+
"sampler": sampler_index,
|
|
1822
|
+
"source": image_index,
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
if texture_dict in texture_dicts:
|
|
1826
|
+
texture_index = texture_dicts.index(texture_dict)
|
|
1827
|
+
else:
|
|
1828
|
+
texture_index = len(texture_dicts)
|
|
1829
|
+
texture_dicts.append(texture_dict)
|
|
1830
|
+
|
|
1831
|
+
texture_info: dict[str, Json] = {"index": texture_index}
|
|
1832
|
+
material_dict["emissiveTexture"] = texture_info
|
|
1833
|
+
material_dict["emissiveFactor"] = [1.0, 1.0, 1.0]
|
|
1834
|
+
else:
|
|
1835
|
+
pbr_metallic_roughness_dict = {
|
|
1836
|
+
"baseColorFactor": [*mmd_material.diffuse_color, mmd_material.alpha]
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
material_dict["pbrMetallicRoughness"] = pbr_metallic_roughness_dict
|
|
1840
|
+
return material_dict
|
|
1841
|
+
|
|
1842
|
+
@classmethod
|
|
1843
|
+
def create_mtoon_unversioned_material_dict(
|
|
1844
|
+
cls,
|
|
1845
|
+
context: Context,
|
|
1846
|
+
json_dict: dict[str, Json],
|
|
1847
|
+
buffer0: bytearray,
|
|
1848
|
+
material: Material,
|
|
1849
|
+
node: ShaderNodeGroup,
|
|
1850
|
+
image_name_to_index_dict: dict[str, int],
|
|
1851
|
+
gltf2_addon_export_settings: dict[str, object],
|
|
1852
|
+
) -> dict[str, Json]:
|
|
1853
|
+
extensions_used = json_dict.get("extensionsUsed")
|
|
1854
|
+
if not isinstance(extensions_used, list):
|
|
1855
|
+
extensions_used = []
|
|
1856
|
+
json_dict["extensionsUsed"] = extensions_used
|
|
1857
|
+
|
|
1858
|
+
if "KHR_materials_unlit" not in extensions_used:
|
|
1859
|
+
extensions_used.append("KHR_materials_unlit")
|
|
1860
|
+
if "VRMC_materials_mtoon" not in extensions_used:
|
|
1861
|
+
extensions_used.append("VRMC_materials_mtoon")
|
|
1862
|
+
|
|
1863
|
+
# https://github.com/vrm-c/UniVRM/blob/f3479190c330ec6ecd2b40be919285aa93a53aff/Assets/VRM10/Runtime/Migration/Materials/MigrationMToonMaterial.cs
|
|
1864
|
+
mtoon_dict: dict[str, Json] = {
|
|
1865
|
+
"specVersion": "1.0",
|
|
1866
|
+
}
|
|
1867
|
+
extensions_dict: dict[str, Json] = {
|
|
1868
|
+
"KHR_materials_unlit": {},
|
|
1869
|
+
"VRMC_materials_mtoon": mtoon_dict,
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
material_dict: dict[str, Json] = {"name": material.name}
|
|
1873
|
+
pbr_metallic_roughness_dict: dict[str, Json] = {}
|
|
1874
|
+
|
|
1875
|
+
if material.blend_method == "OPAQUE":
|
|
1876
|
+
material_dict["alphaMode"] = "OPAQUE"
|
|
1877
|
+
mtoon_dict["transparentWithZWrite"] = False
|
|
1878
|
+
mtoon_dict["renderQueueOffsetNumber"] = 0
|
|
1879
|
+
elif material.blend_method == "CLIP":
|
|
1880
|
+
alpha_cutoff = shader.get_float_value(node, "CutoffRate", 0, float_info.max)
|
|
1881
|
+
if alpha_cutoff is not None:
|
|
1882
|
+
material_dict["alphaCutoff"] = alpha_cutoff
|
|
1883
|
+
else:
|
|
1884
|
+
material_dict["alphaCutoff"] = 0.5
|
|
1885
|
+
material_dict["alphaMode"] = "MASK"
|
|
1886
|
+
mtoon_dict["transparentWithZWrite"] = False
|
|
1887
|
+
mtoon_dict["renderQueueOffsetNumber"] = 0
|
|
1888
|
+
else:
|
|
1889
|
+
material_dict["alphaMode"] = "BLEND"
|
|
1890
|
+
transparent_with_z_write = shader.get_float_value(
|
|
1891
|
+
node, "TransparentWithZWrite"
|
|
1892
|
+
)
|
|
1893
|
+
if (
|
|
1894
|
+
transparent_with_z_write is None
|
|
1895
|
+
or math.fabs(transparent_with_z_write) < float_info.epsilon
|
|
1896
|
+
):
|
|
1897
|
+
mtoon_dict["transparentWithZWrite"] = False
|
|
1898
|
+
else:
|
|
1899
|
+
mtoon_dict["transparentWithZWrite"] = True
|
|
1900
|
+
mtoon_dict["renderQueueOffsetNumber"] = 0
|
|
1901
|
+
assign_dict(
|
|
1902
|
+
material_dict, "doubleSided", not material.use_backface_culling, False
|
|
1903
|
+
)
|
|
1904
|
+
assign_dict(
|
|
1905
|
+
pbr_metallic_roughness_dict,
|
|
1906
|
+
"baseColorFactor",
|
|
1907
|
+
shader.get_rgba_value(node, "DiffuseColor", 0.0, 1.0),
|
|
1908
|
+
)
|
|
1909
|
+
base_color_texture_dict = cls.create_mtoon0_texture_info_dict(
|
|
1910
|
+
context,
|
|
1911
|
+
json_dict,
|
|
1912
|
+
buffer0,
|
|
1913
|
+
node,
|
|
1914
|
+
"MainTexture",
|
|
1915
|
+
image_name_to_index_dict,
|
|
1916
|
+
gltf2_addon_export_settings,
|
|
1917
|
+
)
|
|
1918
|
+
assign_dict(
|
|
1919
|
+
pbr_metallic_roughness_dict, "baseColorTexture", base_color_texture_dict
|
|
1920
|
+
)
|
|
1921
|
+
assign_dict(
|
|
1922
|
+
mtoon_dict,
|
|
1923
|
+
"shadeColorFactor",
|
|
1924
|
+
shader.get_rgb_value(node, "ShadeColor", 0.0, 1.0),
|
|
1925
|
+
)
|
|
1926
|
+
shade_multiply_texture_dict = cls.create_mtoon0_texture_info_dict(
|
|
1927
|
+
context,
|
|
1928
|
+
json_dict,
|
|
1929
|
+
buffer0,
|
|
1930
|
+
node,
|
|
1931
|
+
"ShadeTexture",
|
|
1932
|
+
image_name_to_index_dict,
|
|
1933
|
+
gltf2_addon_export_settings,
|
|
1934
|
+
)
|
|
1935
|
+
if shade_multiply_texture_dict is not None:
|
|
1936
|
+
mtoon_dict["shadeMultiplyTexture"] = shade_multiply_texture_dict
|
|
1937
|
+
elif base_color_texture_dict is not None:
|
|
1938
|
+
# https://github.com/vrm-c/UniVRM/blob/f3479190c330ec6ecd2b40be919285aa93a53aff/Assets/VRM10/Runtime/Migration/Materials/MigrationMToonMaterial.cs#L185-L204
|
|
1939
|
+
mtoon_dict["shadeMultiplyTexture"] = base_color_texture_dict
|
|
1940
|
+
normal_texture_dict = cls.create_mtoon0_texture_info_dict(
|
|
1941
|
+
context,
|
|
1942
|
+
json_dict,
|
|
1943
|
+
buffer0,
|
|
1944
|
+
node,
|
|
1945
|
+
"NormalmapTexture",
|
|
1946
|
+
image_name_to_index_dict,
|
|
1947
|
+
gltf2_addon_export_settings,
|
|
1948
|
+
)
|
|
1949
|
+
if not normal_texture_dict:
|
|
1950
|
+
normal_texture_dict = cls.create_mtoon0_texture_info_dict(
|
|
1951
|
+
context,
|
|
1952
|
+
json_dict,
|
|
1953
|
+
buffer0,
|
|
1954
|
+
node,
|
|
1955
|
+
"NomalmapTexture",
|
|
1956
|
+
image_name_to_index_dict,
|
|
1957
|
+
gltf2_addon_export_settings,
|
|
1958
|
+
)
|
|
1959
|
+
if assign_dict(
|
|
1960
|
+
material_dict, "normalTexture", normal_texture_dict
|
|
1961
|
+
) and isinstance(normal_texture_dict, dict):
|
|
1962
|
+
assign_dict(
|
|
1963
|
+
normal_texture_dict, "scale", shader.get_float_value(node, "BumpScale")
|
|
1964
|
+
)
|
|
1965
|
+
|
|
1966
|
+
shading_shift_0x = shader.get_float_value(node, "ShadeShift")
|
|
1967
|
+
if shading_shift_0x is None:
|
|
1968
|
+
shading_shift_0x = 0.0
|
|
1969
|
+
|
|
1970
|
+
shading_toony_0x = shader.get_float_value(node, "ShadeToony")
|
|
1971
|
+
if shading_toony_0x is None:
|
|
1972
|
+
shading_toony_0x = 0.0
|
|
1973
|
+
|
|
1974
|
+
mtoon_dict["shadingShiftFactor"] = convert.mtoon_shading_shift_0_to_1(
|
|
1975
|
+
shading_toony_0x, shading_shift_0x
|
|
1976
|
+
)
|
|
1977
|
+
|
|
1978
|
+
mtoon_dict["shadingToonyFactor"] = convert.mtoon_shading_toony_0_to_1(
|
|
1979
|
+
shading_toony_0x, shading_shift_0x
|
|
1980
|
+
)
|
|
1981
|
+
|
|
1982
|
+
gi_equalization_0x = shader.get_float_value(node, "IndirectLightIntensity")
|
|
1983
|
+
if gi_equalization_0x is not None:
|
|
1984
|
+
mtoon_dict["giEqualizationFactor"] = (
|
|
1985
|
+
convert.mtoon_intensity_to_gi_equalization(gi_equalization_0x)
|
|
1986
|
+
)
|
|
1987
|
+
|
|
1988
|
+
assign_dict(
|
|
1989
|
+
material_dict,
|
|
1990
|
+
"emissiveFactor",
|
|
1991
|
+
shader.get_rgb_value(node, "EmissionColor", 0.0, 1.0),
|
|
1992
|
+
)
|
|
1993
|
+
assign_dict(
|
|
1994
|
+
material_dict,
|
|
1995
|
+
"emissiveTexture",
|
|
1996
|
+
cls.create_mtoon0_texture_info_dict(
|
|
1997
|
+
context,
|
|
1998
|
+
json_dict,
|
|
1999
|
+
buffer0,
|
|
2000
|
+
node,
|
|
2001
|
+
"Emission_Texture",
|
|
2002
|
+
image_name_to_index_dict,
|
|
2003
|
+
gltf2_addon_export_settings,
|
|
2004
|
+
),
|
|
2005
|
+
)
|
|
2006
|
+
if assign_dict(
|
|
2007
|
+
mtoon_dict,
|
|
2008
|
+
"matcapTexture",
|
|
2009
|
+
cls.create_mtoon0_texture_info_dict(
|
|
2010
|
+
context,
|
|
2011
|
+
json_dict,
|
|
2012
|
+
buffer0,
|
|
2013
|
+
node,
|
|
2014
|
+
"SphereAddTexture",
|
|
2015
|
+
image_name_to_index_dict,
|
|
2016
|
+
gltf2_addon_export_settings,
|
|
2017
|
+
),
|
|
2018
|
+
):
|
|
2019
|
+
mtoon_dict["matcapFactor"] = [1, 1, 1]
|
|
2020
|
+
|
|
2021
|
+
assign_dict(
|
|
2022
|
+
mtoon_dict,
|
|
2023
|
+
"parametricRimColorFactor",
|
|
2024
|
+
shader.get_rgb_value(node, "RimColor", 0.0, 1.0),
|
|
2025
|
+
)
|
|
2026
|
+
assign_dict(
|
|
2027
|
+
mtoon_dict,
|
|
2028
|
+
"parametricRimFresnelPowerFactor",
|
|
2029
|
+
shader.get_float_value(node, "RimFresnelPower", 0.0, float_info.max),
|
|
2030
|
+
)
|
|
2031
|
+
assign_dict(
|
|
2032
|
+
mtoon_dict,
|
|
2033
|
+
"parametricRimLiftFactor",
|
|
2034
|
+
shader.get_float_value(node, "RimLift"),
|
|
2035
|
+
)
|
|
2036
|
+
assign_dict(
|
|
2037
|
+
mtoon_dict,
|
|
2038
|
+
"rimMultiplyTexture",
|
|
2039
|
+
cls.create_mtoon0_texture_info_dict(
|
|
2040
|
+
context,
|
|
2041
|
+
json_dict,
|
|
2042
|
+
buffer0,
|
|
2043
|
+
node,
|
|
2044
|
+
"RimTexture",
|
|
2045
|
+
image_name_to_index_dict,
|
|
2046
|
+
gltf2_addon_export_settings,
|
|
2047
|
+
),
|
|
2048
|
+
)
|
|
2049
|
+
|
|
2050
|
+
# https://github.com/vrm-c/UniVRM/blob/7c9919ef47a25c04100a2dcbe6a75dff49ef4857/Assets/VRM10/Runtime/Migration/Materials/MigrationMToonMaterial.cs#L287-L290
|
|
2051
|
+
mtoon_dict["rimLightingMixFactor"] = 1.0
|
|
2052
|
+
|
|
2053
|
+
centimeter_to_meter = 0.01
|
|
2054
|
+
one_hundredth = 0.01
|
|
2055
|
+
|
|
2056
|
+
outline_width_mode = shader.get_float_value(node, "OutlineWidthMode")
|
|
2057
|
+
if outline_width_mode is not None:
|
|
2058
|
+
outline_width_mode = round(outline_width_mode)
|
|
2059
|
+
else:
|
|
2060
|
+
outline_width_mode = 0
|
|
2061
|
+
|
|
2062
|
+
outline_width = shader.get_float_value(node, "OutlineWidth")
|
|
2063
|
+
if outline_width is None:
|
|
2064
|
+
outline_width = 0.0
|
|
2065
|
+
|
|
2066
|
+
if outline_width_mode == 1:
|
|
2067
|
+
mtoon_dict["outlineWidthMode"] = "worldCoordinates"
|
|
2068
|
+
mtoon_dict["outlineWidthFactor"] = max(
|
|
2069
|
+
0.0, outline_width * centimeter_to_meter
|
|
2070
|
+
)
|
|
2071
|
+
elif outline_width_mode == 2:
|
|
2072
|
+
mtoon_dict["outlineWidthMode"] = "screenCoordinates"
|
|
2073
|
+
mtoon_dict["outlineWidthFactor"] = max(
|
|
2074
|
+
0.0, outline_width * one_hundredth * 0.5
|
|
2075
|
+
)
|
|
2076
|
+
else:
|
|
2077
|
+
mtoon_dict["outlineWidthMode"] = "none"
|
|
2078
|
+
|
|
2079
|
+
assign_dict(
|
|
2080
|
+
mtoon_dict,
|
|
2081
|
+
"outlineWidthMultiplyTexture",
|
|
2082
|
+
cls.create_mtoon0_texture_info_dict(
|
|
2083
|
+
context,
|
|
2084
|
+
json_dict,
|
|
2085
|
+
buffer0,
|
|
2086
|
+
node,
|
|
2087
|
+
"OutlineWidthTexture",
|
|
2088
|
+
image_name_to_index_dict,
|
|
2089
|
+
gltf2_addon_export_settings,
|
|
2090
|
+
),
|
|
2091
|
+
)
|
|
2092
|
+
assign_dict(
|
|
2093
|
+
mtoon_dict,
|
|
2094
|
+
"outlineColorFactor",
|
|
2095
|
+
shader.get_rgb_value(node, "OutlineColor", 0.0, 1.0),
|
|
2096
|
+
)
|
|
2097
|
+
|
|
2098
|
+
outline_color_mode = shader.get_float_value(node, "OutlineColorMode")
|
|
2099
|
+
if outline_color_mode is not None:
|
|
2100
|
+
outline_color_mode = round(outline_color_mode)
|
|
2101
|
+
else:
|
|
2102
|
+
outline_color_mode = 0
|
|
2103
|
+
|
|
2104
|
+
mtoon_dict["outlineLightingMixFactor"] = 0.0
|
|
2105
|
+
if outline_color_mode == 1:
|
|
2106
|
+
assign_dict(
|
|
2107
|
+
mtoon_dict,
|
|
2108
|
+
"outlineLightingMixFactor",
|
|
2109
|
+
shader.get_float_value(node, "OutlineLightingMix"),
|
|
2110
|
+
)
|
|
2111
|
+
|
|
2112
|
+
assign_dict(
|
|
2113
|
+
mtoon_dict,
|
|
2114
|
+
"uvAnimationMaskTexture",
|
|
2115
|
+
cls.create_mtoon0_texture_info_dict(
|
|
2116
|
+
context,
|
|
2117
|
+
json_dict,
|
|
2118
|
+
buffer0,
|
|
2119
|
+
node,
|
|
2120
|
+
"UV_Animation_Mask_Texture",
|
|
2121
|
+
image_name_to_index_dict,
|
|
2122
|
+
gltf2_addon_export_settings,
|
|
2123
|
+
),
|
|
2124
|
+
)
|
|
2125
|
+
assign_dict(
|
|
2126
|
+
mtoon_dict,
|
|
2127
|
+
"uvAnimationRotationSpeedFactor",
|
|
2128
|
+
shader.get_float_value(node, "UV_Scroll_Rotation"),
|
|
2129
|
+
)
|
|
2130
|
+
assign_dict(
|
|
2131
|
+
mtoon_dict,
|
|
2132
|
+
"uvAnimationScrollXSpeedFactor",
|
|
2133
|
+
shader.get_float_value(node, "UV_Scroll_X"),
|
|
2134
|
+
)
|
|
2135
|
+
|
|
2136
|
+
invert_y = -1
|
|
2137
|
+
uv_animation_scroll_y_speed_factor = shader.get_float_value(node, "UV_Scroll_Y")
|
|
2138
|
+
if uv_animation_scroll_y_speed_factor is not None:
|
|
2139
|
+
mtoon_dict["uvAnimationScrollYSpeedFactor"] = (
|
|
2140
|
+
uv_animation_scroll_y_speed_factor * invert_y
|
|
2141
|
+
)
|
|
2142
|
+
|
|
2143
|
+
if pbr_metallic_roughness_dict:
|
|
2144
|
+
material_dict["pbrMetallicRoughness"] = pbr_metallic_roughness_dict
|
|
2145
|
+
|
|
2146
|
+
extensions_dict["VRMC_materials_mtoon"] = mtoon_dict
|
|
2147
|
+
material_dict["extensions"] = extensions_dict
|
|
2148
|
+
|
|
2149
|
+
return material_dict
|
|
2150
|
+
|
|
2151
|
+
@classmethod
|
|
2152
|
+
def save_vrm_materials(
|
|
2153
|
+
cls,
|
|
2154
|
+
context: Context,
|
|
2155
|
+
json_dict: dict[str, Json],
|
|
2156
|
+
buffer0: bytearray,
|
|
2157
|
+
material_name_to_index_dict: Mapping[str, int],
|
|
2158
|
+
image_name_to_index_dict: dict[str, int],
|
|
2159
|
+
gltf2_addon_export_settings: dict[str, object],
|
|
2160
|
+
) -> None:
|
|
2161
|
+
material_dicts = json_dict.get("materials")
|
|
2162
|
+
if not isinstance(material_dicts, list):
|
|
2163
|
+
material_dicts = []
|
|
2164
|
+
json_dict["materials"] = material_dicts
|
|
2165
|
+
|
|
2166
|
+
for material_name, index in material_name_to_index_dict.items():
|
|
2167
|
+
material = context.blend_data.materials.get(material_name)
|
|
2168
|
+
if not isinstance(material, Material) or not (
|
|
2169
|
+
0 <= index < len(material_dicts)
|
|
2170
|
+
):
|
|
2171
|
+
continue
|
|
2172
|
+
|
|
2173
|
+
if get_material_extension(material).mtoon1.enabled:
|
|
2174
|
+
material_dicts[index] = cls.create_mtoon1_material_dict(
|
|
2175
|
+
json_dict,
|
|
2176
|
+
buffer0,
|
|
2177
|
+
material,
|
|
2178
|
+
image_name_to_index_dict,
|
|
2179
|
+
gltf2_addon_export_settings,
|
|
2180
|
+
)
|
|
2181
|
+
continue
|
|
2182
|
+
|
|
2183
|
+
mmd_material = MmdMaterial.try_parse(material)
|
|
2184
|
+
if mmd_material:
|
|
2185
|
+
material_dicts[index] = cls.create_mmd_material_dict(
|
|
2186
|
+
context,
|
|
2187
|
+
json_dict,
|
|
2188
|
+
buffer0,
|
|
2189
|
+
material,
|
|
2190
|
+
mmd_material,
|
|
2191
|
+
image_name_to_index_dict,
|
|
2192
|
+
gltf2_addon_export_settings,
|
|
2193
|
+
)
|
|
2194
|
+
continue
|
|
2195
|
+
|
|
2196
|
+
# MToon_unversioned (MToon for VRM 0.0)
|
|
2197
|
+
if not (legacy_addon_material := LegacyAddonMaterial.try_parse(material)):
|
|
2198
|
+
continue
|
|
2199
|
+
if legacy_addon_material.shader_name == "MToon_unversioned":
|
|
2200
|
+
material_dicts[index] = cls.create_mtoon_unversioned_material_dict(
|
|
2201
|
+
context,
|
|
2202
|
+
json_dict,
|
|
2203
|
+
buffer0,
|
|
2204
|
+
material,
|
|
2205
|
+
legacy_addon_material.shader_node_group,
|
|
2206
|
+
image_name_to_index_dict,
|
|
2207
|
+
gltf2_addon_export_settings,
|
|
2208
|
+
)
|
|
2209
|
+
elif legacy_addon_material.shader_name == "GLTF":
|
|
2210
|
+
material_dicts[index] = cls.create_legacy_gltf_material_dict(
|
|
2211
|
+
context,
|
|
2212
|
+
json_dict,
|
|
2213
|
+
buffer0,
|
|
2214
|
+
material,
|
|
2215
|
+
legacy_addon_material.shader_node_group,
|
|
2216
|
+
image_name_to_index_dict,
|
|
2217
|
+
gltf2_addon_export_settings,
|
|
2218
|
+
)
|
|
2219
|
+
elif legacy_addon_material.shader_name == "TRANSPARENT_ZWRITE":
|
|
2220
|
+
material_dicts[index] = (
|
|
2221
|
+
cls.create_legacy_transparent_zwrite_material_dict(
|
|
2222
|
+
context,
|
|
2223
|
+
json_dict,
|
|
2224
|
+
buffer0,
|
|
2225
|
+
material,
|
|
2226
|
+
legacy_addon_material.shader_node_group,
|
|
2227
|
+
image_name_to_index_dict,
|
|
2228
|
+
gltf2_addon_export_settings,
|
|
2229
|
+
)
|
|
2230
|
+
)
|
|
2231
|
+
|
|
2232
|
+
if material_dicts:
|
|
2233
|
+
json_dict["materials"] = material_dicts
|
|
2234
|
+
|
|
2235
|
+
@staticmethod
|
|
2236
|
+
def enter_disable_mtoon1_material_nodes(context: Context) -> Sequence[str]:
|
|
2237
|
+
disabled_material_names: list[str] = []
|
|
2238
|
+
for material in context.blend_data.materials:
|
|
2239
|
+
if not material:
|
|
2240
|
+
continue
|
|
2241
|
+
if not get_material_extension(material).mtoon1.enabled:
|
|
2242
|
+
continue
|
|
2243
|
+
link_or_unlink_gltf_material_nodes(context, material, link=False)
|
|
2244
|
+
disabled_material_names.append(material.name)
|
|
2245
|
+
return disabled_material_names
|
|
2246
|
+
|
|
2247
|
+
@staticmethod
|
|
2248
|
+
def leave_disable_mtoon1_material_nodes(
|
|
2249
|
+
context: Context, disabled_material_names: Sequence[str]
|
|
2250
|
+
) -> None:
|
|
2251
|
+
for disabled_material_name in reversed(disabled_material_names):
|
|
2252
|
+
disabled_material = context.blend_data.materials.get(disabled_material_name)
|
|
2253
|
+
if not disabled_material:
|
|
2254
|
+
continue
|
|
2255
|
+
link_or_unlink_gltf_material_nodes(context, disabled_material, link=True)
|
|
2256
|
+
|
|
2257
|
+
@classmethod
|
|
2258
|
+
@contextmanager
|
|
2259
|
+
def disable_mtoon1_material_nodes(cls, context: Context) -> Generator[None]:
|
|
2260
|
+
disabled_material_names = cls.enter_disable_mtoon1_material_nodes(context)
|
|
2261
|
+
try:
|
|
2262
|
+
yield
|
|
2263
|
+
# After yield, bpy's native objects may be deleted or frames may advance
|
|
2264
|
+
# and become invalid. Accessing them in that state can cause crashes, so
|
|
2265
|
+
# be careful not to access such potentially invalid native objects
|
|
2266
|
+
# after yield
|
|
2267
|
+
finally:
|
|
2268
|
+
cls.leave_disable_mtoon1_material_nodes(context, disabled_material_names)
|
|
2269
|
+
|
|
2270
|
+
@classmethod
|
|
2271
|
+
def unassign_normal_from_mtoon_primitive_morph_target(
|
|
2272
|
+
cls,
|
|
2273
|
+
context: Context,
|
|
2274
|
+
json_dict: dict[str, Json],
|
|
2275
|
+
material_name_to_index_dict: Mapping[str, int],
|
|
2276
|
+
) -> None:
|
|
2277
|
+
mesh_dicts = json_dict.get("meshes")
|
|
2278
|
+
if not isinstance(mesh_dicts, list):
|
|
2279
|
+
return
|
|
2280
|
+
for mesh_dict in mesh_dicts:
|
|
2281
|
+
if not isinstance(mesh_dict, dict):
|
|
2282
|
+
continue
|
|
2283
|
+
primitive_dicts = mesh_dict.get("primitives")
|
|
2284
|
+
if not isinstance(primitive_dicts, list):
|
|
2285
|
+
continue
|
|
2286
|
+
for primitive_dict in primitive_dicts:
|
|
2287
|
+
if not isinstance(primitive_dict, dict):
|
|
2288
|
+
continue
|
|
2289
|
+
material_index = primitive_dict.get("material")
|
|
2290
|
+
if not isinstance(material_index, int):
|
|
2291
|
+
continue
|
|
2292
|
+
|
|
2293
|
+
skip = True
|
|
2294
|
+
for (
|
|
2295
|
+
search_material_name,
|
|
2296
|
+
search_material_index,
|
|
2297
|
+
) in material_name_to_index_dict.items():
|
|
2298
|
+
if material_index != search_material_index:
|
|
2299
|
+
continue
|
|
2300
|
+
material = context.blend_data.materials.get(search_material_name)
|
|
2301
|
+
if not material:
|
|
2302
|
+
continue
|
|
2303
|
+
mtoon1 = get_material_extension(material).mtoon1
|
|
2304
|
+
if mtoon1.export_shape_key_normals:
|
|
2305
|
+
continue
|
|
2306
|
+
if mtoon1.enabled or (
|
|
2307
|
+
(
|
|
2308
|
+
legacy_addon_material := LegacyAddonMaterial.try_parse(
|
|
2309
|
+
material
|
|
2310
|
+
)
|
|
2311
|
+
)
|
|
2312
|
+
and legacy_addon_material.shader_name == "MToon_unversioned"
|
|
2313
|
+
):
|
|
2314
|
+
skip = False
|
|
2315
|
+
break
|
|
2316
|
+
if skip:
|
|
2317
|
+
continue
|
|
2318
|
+
|
|
2319
|
+
target_dicts = primitive_dict.get("targets")
|
|
2320
|
+
if not isinstance(target_dicts, list):
|
|
2321
|
+
continue
|
|
2322
|
+
for target_dict in target_dicts:
|
|
2323
|
+
if not isinstance(target_dict, dict):
|
|
2324
|
+
continue
|
|
2325
|
+
target_dict.pop("NORMAL", None)
|
|
2326
|
+
|
|
2327
|
+
@staticmethod
|
|
2328
|
+
def enter_setup_flexible_hierarchy_bones(
|
|
2329
|
+
context: Context,
|
|
2330
|
+
armature_object: Object,
|
|
2331
|
+
export_objects: Sequence[Object],
|
|
2332
|
+
) -> Optional[FlexibleHierarchyBoneSetup]:
|
|
2333
|
+
armature_data = armature_object.data
|
|
2334
|
+
if not isinstance(armature_data, Armature):
|
|
2335
|
+
return None
|
|
2336
|
+
ext = get_armature_extension(armature_data)
|
|
2337
|
+
human_bones = ext.vrm1.humanoid.human_bones
|
|
2338
|
+
if human_bones.filter_by_human_bone_hierarchy:
|
|
2339
|
+
return None
|
|
2340
|
+
|
|
2341
|
+
assigned_human_bone_specification_to_bone_name: dict[
|
|
2342
|
+
HumanBoneSpecification, str
|
|
2343
|
+
] = {}
|
|
2344
|
+
for (
|
|
2345
|
+
human_bone_name,
|
|
2346
|
+
human_bone,
|
|
2347
|
+
) in human_bones.human_bone_name_to_human_bone().items():
|
|
2348
|
+
if not human_bone.node.bone_name:
|
|
2349
|
+
continue
|
|
2350
|
+
if human_bone.node.bone_name not in armature_data.bones:
|
|
2351
|
+
continue
|
|
2352
|
+
specification = HumanBoneSpecifications.get(human_bone_name)
|
|
2353
|
+
assigned_human_bone_specification_to_bone_name[specification] = (
|
|
2354
|
+
human_bone.node.bone_name
|
|
2355
|
+
)
|
|
2356
|
+
|
|
2357
|
+
if not assigned_human_bone_specification_to_bone_name:
|
|
2358
|
+
return None
|
|
2359
|
+
|
|
2360
|
+
child_bone_name_to_parent_bone_name = (
|
|
2361
|
+
Vrm1Exporter.create_child_bone_name_to_parent_bone_name(
|
|
2362
|
+
armature_data=armature_data,
|
|
2363
|
+
assigned_human_bone_specification_to_bone_name=(
|
|
2364
|
+
assigned_human_bone_specification_to_bone_name
|
|
2365
|
+
),
|
|
2366
|
+
all_human_bone_specifications=HumanBoneSpecifications.all_human_bones,
|
|
2367
|
+
)
|
|
2368
|
+
)
|
|
2369
|
+
if not child_bone_name_to_parent_bone_name:
|
|
2370
|
+
return None
|
|
2371
|
+
|
|
2372
|
+
bone_name_and_muted_constraint_name: list[tuple[str, str]] = []
|
|
2373
|
+
_, bone_constraints, _ = search.export_constraints(
|
|
2374
|
+
export_objects, armature_object
|
|
2375
|
+
)
|
|
2376
|
+
vrm_constraints = {
|
|
2377
|
+
vrm_constraint for _, vrm_constraint in bone_constraints.all_constraints
|
|
2378
|
+
}
|
|
2379
|
+
for pose_bone in armature_object.pose.bones:
|
|
2380
|
+
for constraint in pose_bone.constraints:
|
|
2381
|
+
if not isinstance(constraint, Constraint):
|
|
2382
|
+
continue
|
|
2383
|
+
if constraint.mute:
|
|
2384
|
+
continue
|
|
2385
|
+
if constraint in vrm_constraints:
|
|
2386
|
+
continue
|
|
2387
|
+
bone_name_and_muted_constraint_name.append(
|
|
2388
|
+
(pose_bone.name, constraint.name)
|
|
2389
|
+
)
|
|
2390
|
+
constraint.mute = True
|
|
2391
|
+
|
|
2392
|
+
return FlexibleHierarchyBoneSetup(
|
|
2393
|
+
original_bone_settings=Vrm1Exporter.reparent_edit_bones(
|
|
2394
|
+
context,
|
|
2395
|
+
armature_object,
|
|
2396
|
+
assigned_human_bone_specification_to_bone_name,
|
|
2397
|
+
child_bone_name_to_parent_bone_name,
|
|
2398
|
+
),
|
|
2399
|
+
pose_bone_name_and_muted_constraint_name=bone_name_and_muted_constraint_name,
|
|
2400
|
+
)
|
|
2401
|
+
|
|
2402
|
+
@staticmethod
|
|
2403
|
+
def enter_setup_dummy_human_bones(
|
|
2404
|
+
context: Context, armature_object: Object
|
|
2405
|
+
) -> Optional[Mapping[HumanBoneName, str]]:
|
|
2406
|
+
armature_data = armature_object.data
|
|
2407
|
+
if not isinstance(armature_data, Armature):
|
|
2408
|
+
return None
|
|
2409
|
+
ext = get_armature_extension(armature_data)
|
|
2410
|
+
human_bones = ext.vrm1.humanoid.human_bones
|
|
2411
|
+
if (
|
|
2412
|
+
not human_bones.allow_non_humanoid_rig
|
|
2413
|
+
and human_bones.bones_are_correctly_assigned()
|
|
2414
|
+
):
|
|
2415
|
+
return None
|
|
2416
|
+
|
|
2417
|
+
human_bone_name_to_human_bone = human_bones.human_bone_name_to_human_bone()
|
|
2418
|
+
human_bone_name_to_bone_name: dict[HumanBoneName, str] = {}
|
|
2419
|
+
for human_bone_name, human_bone in human_bone_name_to_human_bone.items():
|
|
2420
|
+
if not human_bone.node.bone_name:
|
|
2421
|
+
continue
|
|
2422
|
+
human_bone_name_to_bone_name[human_bone_name] = human_bone.node.bone_name
|
|
2423
|
+
human_bone.node.bone_name = ""
|
|
2424
|
+
|
|
2425
|
+
with save_workspace(context, armature_object, mode="EDIT"):
|
|
2426
|
+
hips_bone = armature_data.edit_bones.new(HumanBoneName.HIPS.value)
|
|
2427
|
+
hips_bone.head = Vector((0, 0, 0.5))
|
|
2428
|
+
hips_bone.tail = Vector((0, 1, 0.5))
|
|
2429
|
+
hips_bone_name = deepcopy(hips_bone.name)
|
|
2430
|
+
|
|
2431
|
+
right_upper_leg_bone = armature_data.edit_bones.new(
|
|
2432
|
+
HumanBoneName.RIGHT_UPPER_LEG.value
|
|
2433
|
+
)
|
|
2434
|
+
right_upper_leg_bone.head = Vector((-0.125, 0, 0.5))
|
|
2435
|
+
right_upper_leg_bone.tail = Vector((-0.125, 1, 0.5))
|
|
2436
|
+
right_upper_leg_bone.parent = hips_bone
|
|
2437
|
+
right_upper_leg_bone_name = deepcopy(right_upper_leg_bone.name)
|
|
2438
|
+
|
|
2439
|
+
right_lower_leg_bone = armature_data.edit_bones.new(
|
|
2440
|
+
HumanBoneName.RIGHT_LOWER_LEG.value
|
|
2441
|
+
)
|
|
2442
|
+
right_lower_leg_bone.head = Vector((-0.125, 0, 0.25))
|
|
2443
|
+
right_lower_leg_bone.tail = Vector((-0.125, 1, 0.25))
|
|
2444
|
+
right_lower_leg_bone.parent = right_upper_leg_bone
|
|
2445
|
+
right_lower_leg_bone_name = deepcopy(right_lower_leg_bone.name)
|
|
2446
|
+
|
|
2447
|
+
right_foot_bone = armature_data.edit_bones.new(
|
|
2448
|
+
HumanBoneName.RIGHT_FOOT.value
|
|
2449
|
+
)
|
|
2450
|
+
right_foot_bone.head = Vector((-0.125, 0, 0))
|
|
2451
|
+
right_foot_bone.tail = Vector((-0.125, 1, 0))
|
|
2452
|
+
right_foot_bone.parent = right_lower_leg_bone
|
|
2453
|
+
right_foot_bone_name = deepcopy(right_foot_bone.name)
|
|
2454
|
+
|
|
2455
|
+
left_upper_leg_bone = armature_data.edit_bones.new(
|
|
2456
|
+
HumanBoneName.LEFT_UPPER_LEG.value
|
|
2457
|
+
)
|
|
2458
|
+
left_upper_leg_bone.head = Vector((0.125, 0, 0.5))
|
|
2459
|
+
left_upper_leg_bone.tail = Vector((0.125, 1, 0.5))
|
|
2460
|
+
left_upper_leg_bone.parent = hips_bone
|
|
2461
|
+
left_upper_leg_bone_name = deepcopy(left_upper_leg_bone.name)
|
|
2462
|
+
|
|
2463
|
+
left_lower_leg_bone = armature_data.edit_bones.new(
|
|
2464
|
+
HumanBoneName.LEFT_LOWER_LEG.value
|
|
2465
|
+
)
|
|
2466
|
+
left_lower_leg_bone.head = Vector((0.125, 0, 0.25))
|
|
2467
|
+
left_lower_leg_bone.tail = Vector((0.125, 1, 0.25))
|
|
2468
|
+
left_lower_leg_bone.parent = left_upper_leg_bone
|
|
2469
|
+
left_lower_leg_bone_name = deepcopy(left_lower_leg_bone.name)
|
|
2470
|
+
|
|
2471
|
+
left_foot_bone = armature_data.edit_bones.new(HumanBoneName.LEFT_FOOT.value)
|
|
2472
|
+
left_foot_bone.head = Vector((0.125, 0, 0))
|
|
2473
|
+
left_foot_bone.tail = Vector((0.125, 1, 0))
|
|
2474
|
+
left_foot_bone.parent = left_lower_leg_bone
|
|
2475
|
+
left_foot_bone_name = deepcopy(left_foot_bone.name)
|
|
2476
|
+
|
|
2477
|
+
spine_bone = armature_data.edit_bones.new(HumanBoneName.SPINE.value)
|
|
2478
|
+
spine_bone.head = Vector((0, 0, 0.625))
|
|
2479
|
+
spine_bone.tail = Vector((0, 1, 0.625))
|
|
2480
|
+
spine_bone.parent = hips_bone
|
|
2481
|
+
spine_bone_name = deepcopy(spine_bone.name)
|
|
2482
|
+
|
|
2483
|
+
right_upper_arm_bone = armature_data.edit_bones.new(
|
|
2484
|
+
HumanBoneName.RIGHT_UPPER_ARM.value
|
|
2485
|
+
)
|
|
2486
|
+
right_upper_arm_bone.head = Vector((-0.125, 0, 0.75))
|
|
2487
|
+
right_upper_arm_bone.tail = Vector((-0.125, 1, 0.75))
|
|
2488
|
+
right_upper_arm_bone.parent = spine_bone
|
|
2489
|
+
right_upper_arm_bone_name = deepcopy(right_upper_arm_bone.name)
|
|
2490
|
+
|
|
2491
|
+
right_lower_arm_bone = armature_data.edit_bones.new(
|
|
2492
|
+
HumanBoneName.RIGHT_LOWER_ARM.value
|
|
2493
|
+
)
|
|
2494
|
+
right_lower_arm_bone.head = Vector((-0.25, 0, 0.75))
|
|
2495
|
+
right_lower_arm_bone.tail = Vector((-0.25, 1, 0.75))
|
|
2496
|
+
right_lower_arm_bone.parent = right_upper_arm_bone
|
|
2497
|
+
right_lower_arm_bone_name = deepcopy(right_lower_arm_bone.name)
|
|
2498
|
+
|
|
2499
|
+
right_hand_bone = armature_data.edit_bones.new(
|
|
2500
|
+
HumanBoneName.RIGHT_HAND.value
|
|
2501
|
+
)
|
|
2502
|
+
right_hand_bone.head = Vector((-0.375, 0, 0.75))
|
|
2503
|
+
right_hand_bone.tail = Vector((-0.375, 1, 0.75))
|
|
2504
|
+
right_hand_bone.parent = right_lower_arm_bone
|
|
2505
|
+
right_hand_bone_name = deepcopy(right_hand_bone.name)
|
|
2506
|
+
|
|
2507
|
+
left_upper_arm_bone = armature_data.edit_bones.new(
|
|
2508
|
+
HumanBoneName.LEFT_UPPER_ARM.value
|
|
2509
|
+
)
|
|
2510
|
+
left_upper_arm_bone.head = Vector((0.125, 0, 0.75))
|
|
2511
|
+
left_upper_arm_bone.tail = Vector((0.125, 1, 0.75))
|
|
2512
|
+
left_upper_arm_bone.parent = spine_bone
|
|
2513
|
+
left_upper_arm_bone_name = deepcopy(left_upper_arm_bone.name)
|
|
2514
|
+
|
|
2515
|
+
left_lower_arm_bone = armature_data.edit_bones.new(
|
|
2516
|
+
HumanBoneName.LEFT_LOWER_ARM.value
|
|
2517
|
+
)
|
|
2518
|
+
left_lower_arm_bone.head = Vector((0.25, 0, 0.75))
|
|
2519
|
+
left_lower_arm_bone.tail = Vector((0.25, 1, 0.75))
|
|
2520
|
+
left_lower_arm_bone.parent = left_upper_arm_bone
|
|
2521
|
+
left_lower_arm_bone_name = deepcopy(left_lower_arm_bone.name)
|
|
2522
|
+
|
|
2523
|
+
left_hand_bone = armature_data.edit_bones.new(HumanBoneName.LEFT_HAND.value)
|
|
2524
|
+
left_hand_bone.head = Vector((0.375, 0, 0.75))
|
|
2525
|
+
left_hand_bone.tail = Vector((0.375, 1, 0.75))
|
|
2526
|
+
left_hand_bone.parent = left_lower_arm_bone
|
|
2527
|
+
left_hand_bone_name = deepcopy(left_hand_bone.name)
|
|
2528
|
+
|
|
2529
|
+
head_bone = armature_data.edit_bones.new(HumanBoneName.HEAD.value)
|
|
2530
|
+
head_bone.head = Vector((0, 0, 0.75))
|
|
2531
|
+
head_bone.tail = Vector((0, 1, 0.75))
|
|
2532
|
+
head_bone.parent = spine_bone
|
|
2533
|
+
head_bone_name = deepcopy(head_bone.name)
|
|
2534
|
+
|
|
2535
|
+
Vrm1HumanBonesPropertyGroup.update_all_bone_name_candidates(
|
|
2536
|
+
context, armature_data.name
|
|
2537
|
+
)
|
|
2538
|
+
|
|
2539
|
+
human_bones.head.node.bone_name = head_bone_name
|
|
2540
|
+
human_bones.spine.node.bone_name = spine_bone_name
|
|
2541
|
+
human_bones.hips.node.bone_name = hips_bone_name
|
|
2542
|
+
|
|
2543
|
+
human_bones.right_upper_arm.node.bone_name = right_upper_arm_bone_name
|
|
2544
|
+
human_bones.right_lower_arm.node.bone_name = right_lower_arm_bone_name
|
|
2545
|
+
human_bones.right_hand.node.bone_name = right_hand_bone_name
|
|
2546
|
+
|
|
2547
|
+
human_bones.left_upper_arm.node.bone_name = left_upper_arm_bone_name
|
|
2548
|
+
human_bones.left_lower_arm.node.bone_name = left_lower_arm_bone_name
|
|
2549
|
+
human_bones.left_hand.node.bone_name = left_hand_bone_name
|
|
2550
|
+
|
|
2551
|
+
human_bones.right_upper_leg.node.bone_name = right_upper_leg_bone_name
|
|
2552
|
+
human_bones.right_lower_leg.node.bone_name = right_lower_leg_bone_name
|
|
2553
|
+
human_bones.right_foot.node.bone_name = right_foot_bone_name
|
|
2554
|
+
|
|
2555
|
+
human_bones.left_upper_leg.node.bone_name = left_upper_leg_bone_name
|
|
2556
|
+
human_bones.left_lower_leg.node.bone_name = left_lower_leg_bone_name
|
|
2557
|
+
human_bones.left_foot.node.bone_name = left_foot_bone_name
|
|
2558
|
+
|
|
2559
|
+
return human_bone_name_to_bone_name
|
|
2560
|
+
|
|
2561
|
+
@staticmethod
|
|
2562
|
+
def leave_setup_dummy_human_bones(
|
|
2563
|
+
context: Context,
|
|
2564
|
+
armature_object: Object,
|
|
2565
|
+
human_bone_name_to_bone_name: Mapping[HumanBoneName, str],
|
|
2566
|
+
) -> None:
|
|
2567
|
+
armature_data = armature_object.data
|
|
2568
|
+
if not isinstance(armature_data, Armature):
|
|
2569
|
+
return
|
|
2570
|
+
|
|
2571
|
+
human_bones = get_armature_extension(armature_data).vrm1.humanoid.human_bones
|
|
2572
|
+
|
|
2573
|
+
human_bone_name_to_human_bone = human_bones.human_bone_name_to_human_bone()
|
|
2574
|
+
dummy_bone_names = deepcopy(
|
|
2575
|
+
[
|
|
2576
|
+
human_bone.node.bone_name
|
|
2577
|
+
for human_bone in human_bone_name_to_human_bone.values()
|
|
2578
|
+
if human_bone.node.bone_name
|
|
2579
|
+
]
|
|
2580
|
+
)
|
|
2581
|
+
|
|
2582
|
+
for human_bone_name, human_bone in reversed(
|
|
2583
|
+
list(human_bone_name_to_human_bone.items())
|
|
2584
|
+
):
|
|
2585
|
+
bone_name = human_bone_name_to_bone_name.get(human_bone_name)
|
|
2586
|
+
if bone_name:
|
|
2587
|
+
human_bone.node.bone_name = bone_name
|
|
2588
|
+
else:
|
|
2589
|
+
human_bone.node.bone_name = ""
|
|
2590
|
+
Vrm1HumanBonesPropertyGroup.update_all_bone_name_candidates(
|
|
2591
|
+
context, armature_data.name
|
|
2592
|
+
)
|
|
2593
|
+
with save_workspace(context, armature_object, mode="EDIT"):
|
|
2594
|
+
for dummy_bone_name in reversed(dummy_bone_names):
|
|
2595
|
+
dummy_edit_bone = armature_data.edit_bones.get(dummy_bone_name)
|
|
2596
|
+
if dummy_edit_bone:
|
|
2597
|
+
armature_data.edit_bones.remove(dummy_edit_bone)
|
|
2598
|
+
|
|
2599
|
+
@classmethod
|
|
2600
|
+
@contextmanager
|
|
2601
|
+
def setup_dummy_human_bones(
|
|
2602
|
+
cls,
|
|
2603
|
+
context: Context,
|
|
2604
|
+
armature: Object,
|
|
2605
|
+
) -> Generator[None]:
|
|
2606
|
+
human_bone_name_to_bone_name = cls.enter_setup_dummy_human_bones(
|
|
2607
|
+
context,
|
|
2608
|
+
armature,
|
|
2609
|
+
)
|
|
2610
|
+
try:
|
|
2611
|
+
yield
|
|
2612
|
+
# After yield, bpy's native objects may be deleted or frames may advance
|
|
2613
|
+
# and become invalid. Accessing them in that state can cause crashes, so
|
|
2614
|
+
# be careful not to access such potentially invalid native objects
|
|
2615
|
+
# after yield
|
|
2616
|
+
finally:
|
|
2617
|
+
if human_bone_name_to_bone_name is not None:
|
|
2618
|
+
cls.leave_setup_dummy_human_bones(
|
|
2619
|
+
context,
|
|
2620
|
+
armature,
|
|
2621
|
+
human_bone_name_to_bone_name,
|
|
2622
|
+
)
|
|
2623
|
+
|
|
2624
|
+
@staticmethod
|
|
2625
|
+
def clear_constrainted_rotation(
|
|
2626
|
+
constraint: CopyRotationConstraint, object_or_bone: Union[Object, PoseBone]
|
|
2627
|
+
) -> None:
|
|
2628
|
+
euler_order = constraint.euler_order
|
|
2629
|
+
if euler_order == "AUTO":
|
|
2630
|
+
if object_or_bone.rotation_mode in ROTATION_MODE_EULER:
|
|
2631
|
+
euler_order = object_or_bone.rotation_mode
|
|
2632
|
+
else:
|
|
2633
|
+
euler_order = "XYZ"
|
|
2634
|
+
euler = get_rotation_as_quaternion(object_or_bone).to_euler(euler_order)
|
|
2635
|
+
if constraint.use_x:
|
|
2636
|
+
euler.x = 0
|
|
2637
|
+
if constraint.use_y:
|
|
2638
|
+
euler.y = 0
|
|
2639
|
+
if constraint.use_z:
|
|
2640
|
+
euler.z = 0
|
|
2641
|
+
set_rotation_without_mode_change(object_or_bone, euler.to_quaternion())
|
|
2642
|
+
|
|
2643
|
+
@staticmethod
|
|
2644
|
+
def enter_disable_constraints(
|
|
2645
|
+
context: Context, armature: Object, export_objects: Sequence[Object]
|
|
2646
|
+
) -> tuple[Sequence[tuple[str, str]], Sequence[tuple[str, str]]]:
|
|
2647
|
+
constraint: Optional[Constraint] = None
|
|
2648
|
+
|
|
2649
|
+
object_constraints, bone_constraints, _ = search.export_constraints(
|
|
2650
|
+
export_objects, armature
|
|
2651
|
+
)
|
|
2652
|
+
object_name_and_constraint_name: list[tuple[str, str]] = []
|
|
2653
|
+
for object_name, constraint in object_constraints.all_constraints:
|
|
2654
|
+
obj = context.blend_data.objects.get(object_name)
|
|
2655
|
+
if not obj:
|
|
2656
|
+
continue
|
|
2657
|
+
if isinstance(constraint, CopyRotationConstraint):
|
|
2658
|
+
Vrm1Exporter.clear_constrainted_rotation(constraint, obj)
|
|
2659
|
+
else:
|
|
2660
|
+
set_rotation_without_mode_change(obj, Quaternion())
|
|
2661
|
+
constraint.mute = True
|
|
2662
|
+
object_name_and_constraint_name.append((object_name, constraint.name))
|
|
2663
|
+
|
|
2664
|
+
bone_name_and_constraint_name: list[tuple[str, str]] = []
|
|
2665
|
+
for bone_name, constraint in bone_constraints.all_constraints:
|
|
2666
|
+
bone = armature.pose.bones.get(bone_name)
|
|
2667
|
+
if not bone:
|
|
2668
|
+
continue
|
|
2669
|
+
if isinstance(constraint, CopyRotationConstraint):
|
|
2670
|
+
Vrm1Exporter.clear_constrainted_rotation(constraint, bone)
|
|
2671
|
+
else:
|
|
2672
|
+
set_rotation_without_mode_change(bone, Quaternion())
|
|
2673
|
+
constraint.mute = True
|
|
2674
|
+
bone_name_and_constraint_name.append((bone_name, constraint.name))
|
|
2675
|
+
|
|
2676
|
+
return (object_name_and_constraint_name, bone_name_and_constraint_name)
|
|
2677
|
+
|
|
2678
|
+
@staticmethod
|
|
2679
|
+
def leave_disable_constraints(
|
|
2680
|
+
context: Context,
|
|
2681
|
+
armature: Object,
|
|
2682
|
+
object_name_and_constraint_name: Sequence[tuple[str, str]],
|
|
2683
|
+
bone_name_and_constraint_name: Sequence[tuple[str, str]],
|
|
2684
|
+
) -> None:
|
|
2685
|
+
# Consider the possibility that Object or Constraint may have been
|
|
2686
|
+
# deleted, and retrieve them again
|
|
2687
|
+
|
|
2688
|
+
for bone_name, constraint_name in reversed(list(bone_name_and_constraint_name)):
|
|
2689
|
+
bone = armature.pose.bones.get(bone_name)
|
|
2690
|
+
if not bone:
|
|
2691
|
+
continue
|
|
2692
|
+
constraint = bone.constraints.get(constraint_name)
|
|
2693
|
+
if not isinstance(constraint, Constraint):
|
|
2694
|
+
continue
|
|
2695
|
+
constraint.mute = False
|
|
2696
|
+
|
|
2697
|
+
for object_name, constraint_name in reversed(
|
|
2698
|
+
list(object_name_and_constraint_name)
|
|
2699
|
+
):
|
|
2700
|
+
obj = context.blend_data.objects.get(object_name)
|
|
2701
|
+
if not obj:
|
|
2702
|
+
continue
|
|
2703
|
+
constraint = obj.constraints.get(constraint_name)
|
|
2704
|
+
if not isinstance(constraint, Constraint):
|
|
2705
|
+
continue
|
|
2706
|
+
constraint.mute = False
|
|
2707
|
+
|
|
2708
|
+
@contextmanager
|
|
2709
|
+
def disable_constraints(self, context: Context) -> Generator[None]:
|
|
2710
|
+
object_name_and_constraint_name, bone_name_and_constraint_name = (
|
|
2711
|
+
self.enter_disable_constraints(
|
|
2712
|
+
context, self._armature, self._export_objects
|
|
2713
|
+
)
|
|
2714
|
+
)
|
|
2715
|
+
try:
|
|
2716
|
+
yield
|
|
2717
|
+
# After yield, bpy's native objects may be deleted or frames may advance
|
|
2718
|
+
# and become invalid. Accessing them in that state can cause crashes, so
|
|
2719
|
+
# be careful not to access such potentially invalid native objects
|
|
2720
|
+
# after yield
|
|
2721
|
+
finally:
|
|
2722
|
+
self.leave_disable_constraints(
|
|
2723
|
+
context,
|
|
2724
|
+
self._armature,
|
|
2725
|
+
object_name_and_constraint_name,
|
|
2726
|
+
bone_name_and_constraint_name,
|
|
2727
|
+
)
|
|
2728
|
+
|
|
2729
|
+
def enter_assign_export_custom_properties(self) -> None:
|
|
2730
|
+
self._armature[self._extras_main_armature_key] = True
|
|
2731
|
+
|
|
2732
|
+
armature_data = self._armature.data
|
|
2733
|
+
if not isinstance(armature_data, Armature):
|
|
2734
|
+
return
|
|
2735
|
+
|
|
2736
|
+
# To minimize the impact of other glTF2ExportUserExtensions,
|
|
2737
|
+
# use custom properties that seem to have less impact
|
|
2738
|
+
# to establish correspondence between Blender objects and indices.
|
|
2739
|
+
for obj in self._context.blend_data.objects:
|
|
2740
|
+
obj[self._extras_object_name_key] = obj.name
|
|
2741
|
+
for material in self._context.blend_data.materials:
|
|
2742
|
+
material[self._extras_material_name_key] = material.name
|
|
2743
|
+
for mesh in self._context.blend_data.meshes:
|
|
2744
|
+
mesh[self._extras_mesh_name_key] = mesh.name
|
|
2745
|
+
|
|
2746
|
+
# The glTF 2.0 addon comment states that it saves custom properties with
|
|
2747
|
+
# PoseBone, but it actually references custom properties of Bone.
|
|
2748
|
+
# Therefore, write to both just in case
|
|
2749
|
+
for pose_bone in self._armature.pose.bones:
|
|
2750
|
+
pose_bone[self._extras_bone_name_key] = pose_bone.name
|
|
2751
|
+
for bone in armature_data.bones:
|
|
2752
|
+
bone[self._extras_bone_name_key] = bone.name
|
|
2753
|
+
|
|
2754
|
+
def leave_assign_export_custom_properties(self) -> None:
|
|
2755
|
+
armature_data = self._armature.data
|
|
2756
|
+
if not isinstance(armature_data, Armature):
|
|
2757
|
+
return
|
|
2758
|
+
|
|
2759
|
+
for bone in reversed(armature_data.bones):
|
|
2760
|
+
bone.pop(self._extras_bone_name_key, None)
|
|
2761
|
+
for pose_bone in reversed(self._armature.pose.bones):
|
|
2762
|
+
pose_bone.pop(self._extras_bone_name_key, None)
|
|
2763
|
+
|
|
2764
|
+
for mesh in reversed(self._context.blend_data.meshes):
|
|
2765
|
+
mesh.pop(self._extras_mesh_name_key, None)
|
|
2766
|
+
for material in reversed(self._context.blend_data.materials):
|
|
2767
|
+
material.pop(self._extras_material_name_key, None)
|
|
2768
|
+
for obj in reversed(self._context.blend_data.objects):
|
|
2769
|
+
obj.pop(self._extras_object_name_key, None)
|
|
2770
|
+
|
|
2771
|
+
self._armature.pop(self._extras_main_armature_key, None)
|
|
2772
|
+
|
|
2773
|
+
@contextmanager
|
|
2774
|
+
def assign_export_custom_properties(self) -> Generator[None]:
|
|
2775
|
+
self.enter_assign_export_custom_properties()
|
|
2776
|
+
try:
|
|
2777
|
+
yield
|
|
2778
|
+
# After yield, bpy's native objects may be deleted or frames may advance
|
|
2779
|
+
# and become invalid. Accessing them in that state can cause crashes, so
|
|
2780
|
+
# be careful not to access such potentially invalid native objects
|
|
2781
|
+
# after yield
|
|
2782
|
+
finally:
|
|
2783
|
+
self.leave_assign_export_custom_properties()
|
|
2784
|
+
|
|
2785
|
+
@staticmethod
|
|
2786
|
+
def gltf_export_armature_object_remove(
|
|
2787
|
+
context: Context, mesh_object_names: Sequence[str]
|
|
2788
|
+
) -> bool:
|
|
2789
|
+
"""Return whether to enable export_armature_object_remove.
|
|
2790
|
+
|
|
2791
|
+
export_armature_object_remove is very useful and we'd like to use it,
|
|
2792
|
+
but it has many bugs, so enable it only when not affected by those bugs.
|
|
2793
|
+
"""
|
|
2794
|
+
if bpy.app.version < (4, 2):
|
|
2795
|
+
return False
|
|
2796
|
+
|
|
2797
|
+
if bpy.app.version < (4, 4):
|
|
2798
|
+
# Root bone with non-deform bones returns False
|
|
2799
|
+
# https://github.com/KhronosGroup/glTF-Blender-IO/issues/2394
|
|
2800
|
+
for selected_object in context.selected_objects:
|
|
2801
|
+
if selected_object.type != "ARMATURE":
|
|
2802
|
+
continue
|
|
2803
|
+
armature = selected_object.data
|
|
2804
|
+
if not isinstance(armature, Armature):
|
|
2805
|
+
continue
|
|
2806
|
+
for bone in armature.bones:
|
|
2807
|
+
if bone.parent:
|
|
2808
|
+
continue
|
|
2809
|
+
if not bone.use_deform:
|
|
2810
|
+
return False
|
|
2811
|
+
|
|
2812
|
+
# Return False if there are vertices with Armature modifier but no weights
|
|
2813
|
+
# https://github.com/KhronosGroup/glTF-Blender-IO/issues/2436
|
|
2814
|
+
armature_data_name_to_deform_bone_names: dict[str, set[str]] = {}
|
|
2815
|
+
for mesh_object_name in mesh_object_names:
|
|
2816
|
+
mesh_object = context.blend_data.objects.get(mesh_object_name)
|
|
2817
|
+
if not mesh_object:
|
|
2818
|
+
continue
|
|
2819
|
+
if mesh_object.type != "MESH":
|
|
2820
|
+
continue
|
|
2821
|
+
mesh = mesh_object.data
|
|
2822
|
+
if not isinstance(mesh, Mesh):
|
|
2823
|
+
continue
|
|
2824
|
+
|
|
2825
|
+
vertex_group_names_set: Optional[set[tuple[str, ...]]] = None
|
|
2826
|
+
|
|
2827
|
+
for modifier in mesh_object.modifiers:
|
|
2828
|
+
if modifier.type != "ARMATURE":
|
|
2829
|
+
continue
|
|
2830
|
+
if not isinstance(modifier, ArmatureModifier):
|
|
2831
|
+
continue
|
|
2832
|
+
if not modifier.show_viewport:
|
|
2833
|
+
continue
|
|
2834
|
+
armature_object = modifier.object
|
|
2835
|
+
if not armature_object:
|
|
2836
|
+
continue
|
|
2837
|
+
armature_data = armature_object.data
|
|
2838
|
+
if not isinstance(armature_data, Armature):
|
|
2839
|
+
continue
|
|
2840
|
+
deform_bone_names = armature_data_name_to_deform_bone_names.get(
|
|
2841
|
+
armature_data.name
|
|
2842
|
+
)
|
|
2843
|
+
if deform_bone_names is None:
|
|
2844
|
+
deform_bone_names = {
|
|
2845
|
+
bone.name for bone in armature_data.bones if bone.use_deform
|
|
2846
|
+
}
|
|
2847
|
+
armature_data_name_to_deform_bone_names[armature_data.name] = (
|
|
2848
|
+
deform_bone_names
|
|
2849
|
+
)
|
|
2850
|
+
|
|
2851
|
+
if vertex_group_names_set is None:
|
|
2852
|
+
vertex_groups = mesh_object.vertex_groups
|
|
2853
|
+
vertex_group_names_set = {
|
|
2854
|
+
tuple(
|
|
2855
|
+
vertex_groups[vertex_group_index].name
|
|
2856
|
+
for vertex_group_index in vertex_group_indices
|
|
2857
|
+
if 0 <= vertex_group_index < len(vertex_groups)
|
|
2858
|
+
)
|
|
2859
|
+
for vertex_group_indices in {
|
|
2860
|
+
tuple(
|
|
2861
|
+
sorted(
|
|
2862
|
+
{
|
|
2863
|
+
group.group
|
|
2864
|
+
for group in vertex.groups
|
|
2865
|
+
if group.weight > 0
|
|
2866
|
+
}
|
|
2867
|
+
)
|
|
2868
|
+
)
|
|
2869
|
+
for vertex in mesh.vertices
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
for vertex_group_names in vertex_group_names_set:
|
|
2874
|
+
if all(
|
|
2875
|
+
vertex_group_name not in deform_bone_names
|
|
2876
|
+
for vertex_group_name in vertex_group_names
|
|
2877
|
+
):
|
|
2878
|
+
return False
|
|
2879
|
+
|
|
2880
|
+
return True
|
|
2881
|
+
|
|
2882
|
+
def export(self) -> Optional[bytes]:
|
|
2883
|
+
init_extras_export()
|
|
2884
|
+
|
|
2885
|
+
armature_data = self._armature.data
|
|
2886
|
+
if not isinstance(armature_data, Armature):
|
|
2887
|
+
message = f"{type(armature_data)} is not an Armature"
|
|
2888
|
+
raise TypeError(message)
|
|
2889
|
+
|
|
2890
|
+
self.setup_mtoon_gltf_fallback_nodes(self._context, is_vrm0=False)
|
|
2891
|
+
|
|
2892
|
+
with (
|
|
2893
|
+
save_workspace(self._context),
|
|
2894
|
+
self.setup_flexible_hierarchy_bones(
|
|
2895
|
+
self._context, self._armature, self._export_objects
|
|
2896
|
+
),
|
|
2897
|
+
self.setup_dummy_human_bones(self._context, self._armature),
|
|
2898
|
+
self.clear_blend_shape_proxy_previews(self._context, armature_data),
|
|
2899
|
+
self.enable_deform_for_all_referenced_bones(armature_data),
|
|
2900
|
+
setup_humanoid_t_pose(self._context, self._armature),
|
|
2901
|
+
self.overwrite_object_visibility_and_selection(),
|
|
2902
|
+
):
|
|
2903
|
+
with (
|
|
2904
|
+
self.disable_constraints(self._context),
|
|
2905
|
+
self.hide_mtoon1_outline_geometry_nodes(self._context),
|
|
2906
|
+
self.disable_mtoon1_material_nodes(self._context),
|
|
2907
|
+
self.mount_skinned_mesh_parent(),
|
|
2908
|
+
self.save_selected_mesh_compat_objects() as mesh_compat_object_names,
|
|
2909
|
+
self.assign_export_custom_properties(),
|
|
2910
|
+
tempfile.TemporaryDirectory() as temp_dir,
|
|
2911
|
+
):
|
|
2912
|
+
_force_apply_modifiers_to_objects(
|
|
2913
|
+
self._context, self._armature, mesh_compat_object_names
|
|
2914
|
+
)
|
|
2915
|
+
_remove_inactive_uv_maps(self._context, mesh_compat_object_names)
|
|
2916
|
+
|
|
2917
|
+
filepath = Path(temp_dir, "out.glb")
|
|
2918
|
+
export_scene_gltf_result = export_scene_gltf(
|
|
2919
|
+
ExportSceneGltfArguments(
|
|
2920
|
+
filepath=str(filepath),
|
|
2921
|
+
check_existing=False,
|
|
2922
|
+
export_format="GLB",
|
|
2923
|
+
export_extras=True,
|
|
2924
|
+
export_def_bones=True,
|
|
2925
|
+
export_current_frame=True,
|
|
2926
|
+
use_selection=True,
|
|
2927
|
+
use_active_scene=True,
|
|
2928
|
+
export_animations=self._export_gltf_animations,
|
|
2929
|
+
export_armature_object_remove=(
|
|
2930
|
+
self.gltf_export_armature_object_remove(
|
|
2931
|
+
self._context, mesh_compat_object_names
|
|
2932
|
+
)
|
|
2933
|
+
),
|
|
2934
|
+
export_rest_position_armature=False,
|
|
2935
|
+
export_apply=False,
|
|
2936
|
+
# Models may appear incorrectly in many viewers
|
|
2937
|
+
export_all_influences=self._export_all_influences,
|
|
2938
|
+
export_lights=self._export_lights,
|
|
2939
|
+
export_try_sparse_sk=self._export_try_sparse_sk,
|
|
2940
|
+
export_vertex_color="MATERIAL",
|
|
2941
|
+
)
|
|
2942
|
+
)
|
|
2943
|
+
if export_scene_gltf_result == {"CANCELLED"}:
|
|
2944
|
+
return None
|
|
2945
|
+
if export_scene_gltf_result != {"FINISHED"}:
|
|
2946
|
+
message = (
|
|
2947
|
+
'The glTF2 Exporter has not been {"FINISHED"}'
|
|
2948
|
+
f" but {export_scene_gltf_result}"
|
|
2949
|
+
)
|
|
2950
|
+
raise AssertionError(message)
|
|
2951
|
+
extra_name_assigned_glb = filepath.read_bytes()
|
|
2952
|
+
vrm_bytes = self.add_vrm_extension_to_glb(extra_name_assigned_glb)
|
|
2953
|
+
if vrm_bytes is None:
|
|
2954
|
+
return None
|
|
2955
|
+
_logger.info("Generated VRM size: %s bytes", len(vrm_bytes))
|
|
2956
|
+
return vrm_bytes
|
|
2957
|
+
|
|
2958
|
+
def remove_exported_armature_object_before_4_2(
|
|
2959
|
+
self,
|
|
2960
|
+
json_dict: dict[str, Json],
|
|
2961
|
+
node_dicts: list[Json],
|
|
2962
|
+
armature_node_index: int,
|
|
2963
|
+
) -> None:
|
|
2964
|
+
"""Delete scene armature object if it can be deleted in Blender < 4.2.
|
|
2965
|
+
|
|
2966
|
+
This is to prevent the Armature object from being treated as a bone
|
|
2967
|
+
when re-importing in Blender. TODO: Should be handled more intelligently
|
|
2968
|
+
using skin.skeleton etc.
|
|
2969
|
+
|
|
2970
|
+
When the main armature has transforms, the current method doesn't work
|
|
2971
|
+
well with Blender 4.2.1, UniVRM 0.126.0, etc., so we don't do it.
|
|
2972
|
+
|
|
2973
|
+
When there are multiple root bones with Skin Joints registered,
|
|
2974
|
+
skin.joints requires a common root bone constraint, so we don't delete it.
|
|
2975
|
+
"""
|
|
2976
|
+
if bpy.app.version >= (4, 2):
|
|
2977
|
+
return
|
|
2978
|
+
|
|
2979
|
+
armature_world_matrix = (
|
|
2980
|
+
_find_node_world_matrix(node_dicts, armature_node_index, None) or Matrix()
|
|
2981
|
+
)
|
|
2982
|
+
|
|
2983
|
+
if not (0 <= armature_node_index < len(node_dicts)):
|
|
2984
|
+
return
|
|
2985
|
+
armature_node_dict = node_dicts[armature_node_index]
|
|
2986
|
+
if not isinstance(armature_node_dict, dict):
|
|
2987
|
+
return
|
|
2988
|
+
|
|
2989
|
+
if not is_identity_matrix(armature_world_matrix):
|
|
2990
|
+
return
|
|
2991
|
+
|
|
2992
|
+
scene_dicts = json_dict.get("scenes")
|
|
2993
|
+
if not isinstance(scene_dicts, list):
|
|
2994
|
+
return
|
|
2995
|
+
|
|
2996
|
+
all_joint_node_indices: list[Sequence[int]] = []
|
|
2997
|
+
|
|
2998
|
+
skin_dicts = json_dict.get("skins")
|
|
2999
|
+
if isinstance(skin_dicts, list):
|
|
3000
|
+
for skin_dict in skin_dicts:
|
|
3001
|
+
if not isinstance(skin_dict, dict):
|
|
3002
|
+
continue
|
|
3003
|
+
joints = skin_dict.get("joints")
|
|
3004
|
+
if not isinstance(joints, list):
|
|
3005
|
+
continue
|
|
3006
|
+
all_joint_node_indices.append(
|
|
3007
|
+
[
|
|
3008
|
+
joint_node_index
|
|
3009
|
+
for joint_node_index in joints
|
|
3010
|
+
if isinstance(joint_node_index, int)
|
|
3011
|
+
]
|
|
3012
|
+
)
|
|
3013
|
+
|
|
3014
|
+
armature_child_indices = (
|
|
3015
|
+
[child for child in children if isinstance(child, int)]
|
|
3016
|
+
if isinstance(children := armature_node_dict.get("children"), Sequence)
|
|
3017
|
+
else []
|
|
3018
|
+
)
|
|
3019
|
+
|
|
3020
|
+
for joint_node_indices in all_joint_node_indices:
|
|
3021
|
+
if len(set(joint_node_indices).intersection(armature_child_indices)) >= 2:
|
|
3022
|
+
return
|
|
3023
|
+
|
|
3024
|
+
armature_replaced = False
|
|
3025
|
+
for scene_dict in scene_dicts:
|
|
3026
|
+
if not isinstance(scene_dict, dict):
|
|
3027
|
+
continue
|
|
3028
|
+
scene_node_indices = scene_dict.get("nodes")
|
|
3029
|
+
if not isinstance(scene_node_indices, list):
|
|
3030
|
+
continue
|
|
3031
|
+
|
|
3032
|
+
# Remove ancestor nodes of that armature among nodes belonging to the scene
|
|
3033
|
+
for scene_node_index in list(scene_node_indices):
|
|
3034
|
+
if not isinstance(scene_node_index, int):
|
|
3035
|
+
continue
|
|
3036
|
+
search_scene_node_indices = [scene_node_index]
|
|
3037
|
+
while search_scene_node_indices:
|
|
3038
|
+
search_scene_node_index = search_scene_node_indices.pop()
|
|
3039
|
+
if search_scene_node_index == armature_node_index:
|
|
3040
|
+
scene_node_indices.remove(scene_node_index)
|
|
3041
|
+
break
|
|
3042
|
+
if not (0 <= search_scene_node_index < len(node_dicts)):
|
|
3043
|
+
continue
|
|
3044
|
+
search_scene_node_dict = node_dicts[search_scene_node_index]
|
|
3045
|
+
if not isinstance(search_scene_node_dict, dict):
|
|
3046
|
+
continue
|
|
3047
|
+
child_indices = search_scene_node_dict.get("children")
|
|
3048
|
+
if not isinstance(child_indices, list):
|
|
3049
|
+
continue
|
|
3050
|
+
for child_index in child_indices:
|
|
3051
|
+
if not isinstance(child_index, int):
|
|
3052
|
+
continue
|
|
3053
|
+
search_scene_node_indices.append(child_index)
|
|
3054
|
+
|
|
3055
|
+
for armature_child_index in armature_child_indices:
|
|
3056
|
+
if (
|
|
3057
|
+
not (0 <= armature_child_index < len(node_dicts))
|
|
3058
|
+
or armature_child_index in scene_node_indices
|
|
3059
|
+
):
|
|
3060
|
+
continue
|
|
3061
|
+
scene_node_indices.append(armature_child_index)
|
|
3062
|
+
if armature_replaced:
|
|
3063
|
+
continue
|
|
3064
|
+
# Apply world matrix up to main armature to its children
|
|
3065
|
+
child_node_dict = node_dicts[armature_child_index]
|
|
3066
|
+
if not isinstance(child_node_dict, dict):
|
|
3067
|
+
continue
|
|
3068
|
+
child_matrix = parse_gltf_node_matrix(child_node_dict)
|
|
3069
|
+
_set_node_matrix(
|
|
3070
|
+
child_node_dict,
|
|
3071
|
+
armature_world_matrix @ child_matrix,
|
|
3072
|
+
)
|
|
3073
|
+
armature_replaced = True
|
|
3074
|
+
|
|
3075
|
+
if armature_replaced:
|
|
3076
|
+
armature_node_dict.pop("children", None)
|
|
3077
|
+
armature_node_dict["name"] = "secondary" # Assign dummy name
|
|
3078
|
+
|
|
3079
|
+
def add_vrm_extension_to_glb(
|
|
3080
|
+
self, extra_name_assigned_glb: bytes
|
|
3081
|
+
) -> Optional[bytes]:
|
|
3082
|
+
armature_data = self._armature.data
|
|
3083
|
+
if not isinstance(armature_data, Armature):
|
|
3084
|
+
message = f"{type(armature_data)} is not an Armature"
|
|
3085
|
+
raise TypeError(message)
|
|
3086
|
+
|
|
3087
|
+
vrm = get_armature_extension(armature_data).vrm1
|
|
3088
|
+
|
|
3089
|
+
json_dict, buffer0_bytes = parse_glb(extra_name_assigned_glb)
|
|
3090
|
+
buffer0 = bytearray(buffer0_bytes)
|
|
3091
|
+
|
|
3092
|
+
bone_name_to_index_dict: dict[str, int] = {}
|
|
3093
|
+
object_name_to_index_dict: dict[str, int] = {}
|
|
3094
|
+
image_name_to_index_dict: dict[str, int] = {}
|
|
3095
|
+
mesh_object_name_to_node_index_dict: dict[str, int] = {}
|
|
3096
|
+
mesh_object_name_to_morph_target_names_dict: dict[str, list[str]] = {}
|
|
3097
|
+
|
|
3098
|
+
node_dicts = json_dict.get("nodes")
|
|
3099
|
+
if not isinstance(node_dicts, list):
|
|
3100
|
+
node_dicts = []
|
|
3101
|
+
json_dict["nodes"] = node_dicts
|
|
3102
|
+
|
|
3103
|
+
mesh_dicts = json_dict.get("meshes")
|
|
3104
|
+
if not isinstance(mesh_dicts, list):
|
|
3105
|
+
mesh_dicts = []
|
|
3106
|
+
json_dict["meshes"] = mesh_dicts
|
|
3107
|
+
|
|
3108
|
+
for node_index, node_dict in enumerate(node_dicts):
|
|
3109
|
+
if not isinstance(node_dict, dict):
|
|
3110
|
+
continue
|
|
3111
|
+
extras_dict = node_dict.get("extras")
|
|
3112
|
+
if not isinstance(extras_dict, dict):
|
|
3113
|
+
continue
|
|
3114
|
+
|
|
3115
|
+
bone_name = extras_dict.pop(self._extras_bone_name_key, None)
|
|
3116
|
+
if isinstance(bone_name, str):
|
|
3117
|
+
bone_name_to_index_dict[bone_name] = node_index
|
|
3118
|
+
|
|
3119
|
+
is_main_armature = extras_dict.pop(self._extras_main_armature_key, None)
|
|
3120
|
+
|
|
3121
|
+
object_name = extras_dict.pop(self._extras_object_name_key, None)
|
|
3122
|
+
if isinstance(object_name, str):
|
|
3123
|
+
object_name_to_index_dict[object_name] = node_index
|
|
3124
|
+
if bpy.app.version < (3, 3):
|
|
3125
|
+
is_main_armature = object_name == self._armature.name
|
|
3126
|
+
|
|
3127
|
+
if is_main_armature:
|
|
3128
|
+
if not extras_dict:
|
|
3129
|
+
node_dict.pop("extras", None)
|
|
3130
|
+
self.remove_exported_armature_object_before_4_2(
|
|
3131
|
+
json_dict, node_dicts, node_index
|
|
3132
|
+
)
|
|
3133
|
+
|
|
3134
|
+
mesh_index = node_dict.get("mesh")
|
|
3135
|
+
if (
|
|
3136
|
+
isinstance(object_name, str)
|
|
3137
|
+
and isinstance(mesh_index, int)
|
|
3138
|
+
and 0 <= mesh_index < len(mesh_dicts)
|
|
3139
|
+
):
|
|
3140
|
+
mesh_object_name_to_node_index_dict[object_name] = node_index
|
|
3141
|
+
mesh_dict = mesh_dicts[mesh_index]
|
|
3142
|
+
if isinstance(mesh_dict, dict):
|
|
3143
|
+
mesh_extras_dict = mesh_dict.get("extras")
|
|
3144
|
+
if isinstance(mesh_extras_dict, dict):
|
|
3145
|
+
target_names = mesh_extras_dict.get("targetNames")
|
|
3146
|
+
if isinstance(target_names, list):
|
|
3147
|
+
mesh_object_name_to_morph_target_names_dict[object_name] = [
|
|
3148
|
+
str(target_name) for target_name in target_names
|
|
3149
|
+
]
|
|
3150
|
+
|
|
3151
|
+
if isinstance(object_name, str) and (
|
|
3152
|
+
object_name.startswith(INTERNAL_NAME_PREFIX + "VrmAddonLinkTo")
|
|
3153
|
+
# or object_name == dummy_skinned_mesh_object_name
|
|
3154
|
+
):
|
|
3155
|
+
node_dict.clear()
|
|
3156
|
+
for child_removing_node_dict in node_dicts:
|
|
3157
|
+
if not isinstance(child_removing_node_dict, dict):
|
|
3158
|
+
continue
|
|
3159
|
+
children = child_removing_node_dict.pop("children", None)
|
|
3160
|
+
if not isinstance(children, list):
|
|
3161
|
+
continue
|
|
3162
|
+
children = [child for child in children if child != node_index]
|
|
3163
|
+
if children:
|
|
3164
|
+
child_removing_node_dict["children"] = children
|
|
3165
|
+
|
|
3166
|
+
# TODO: remove from scenes, skin joints ...
|
|
3167
|
+
|
|
3168
|
+
if not extras_dict:
|
|
3169
|
+
node_dict.pop("extras", None)
|
|
3170
|
+
|
|
3171
|
+
node_constraint_spec_version = "1.0"
|
|
3172
|
+
use_node_constraint = False
|
|
3173
|
+
object_constraints, bone_constraints, _ = search.export_constraints(
|
|
3174
|
+
self._export_objects, self._armature
|
|
3175
|
+
)
|
|
3176
|
+
|
|
3177
|
+
for object_name, node_index in object_name_to_index_dict.items():
|
|
3178
|
+
if not (0 <= node_index < len(node_dicts)):
|
|
3179
|
+
continue
|
|
3180
|
+
node_dict = node_dicts[node_index]
|
|
3181
|
+
if not isinstance(node_dict, dict):
|
|
3182
|
+
_logger.error(
|
|
3183
|
+
"gltf.nodes[%d] is invalid value: %s", node_index, node_dict
|
|
3184
|
+
)
|
|
3185
|
+
node_dict = {}
|
|
3186
|
+
node_dicts[node_index] = node_dict
|
|
3187
|
+
constraint_dict = self.create_constraint_dict(
|
|
3188
|
+
object_name,
|
|
3189
|
+
object_constraints,
|
|
3190
|
+
object_name_to_index_dict,
|
|
3191
|
+
bone_name_to_index_dict,
|
|
3192
|
+
)
|
|
3193
|
+
if constraint_dict:
|
|
3194
|
+
extensions = node_dict.get("extensions")
|
|
3195
|
+
if not isinstance(extensions, dict):
|
|
3196
|
+
node_dict["extensions"] = extensions = {}
|
|
3197
|
+
extensions["VRMC_node_constraint"] = {
|
|
3198
|
+
"specVersion": node_constraint_spec_version,
|
|
3199
|
+
"constraint": constraint_dict,
|
|
3200
|
+
}
|
|
3201
|
+
use_node_constraint = True
|
|
3202
|
+
|
|
3203
|
+
for bone_name, node_index in bone_name_to_index_dict.items():
|
|
3204
|
+
if not (0 <= node_index < len(node_dicts)):
|
|
3205
|
+
continue
|
|
3206
|
+
node_dict = node_dicts[node_index]
|
|
3207
|
+
if not isinstance(node_dict, dict):
|
|
3208
|
+
_logger.error(
|
|
3209
|
+
"gltf.nodes[%d] is invalid value: %s", node_index, node_dict
|
|
3210
|
+
)
|
|
3211
|
+
node_dict = {}
|
|
3212
|
+
node_dicts[node_index] = node_dicts
|
|
3213
|
+
constraint_dict = self.create_constraint_dict(
|
|
3214
|
+
bone_name,
|
|
3215
|
+
bone_constraints,
|
|
3216
|
+
object_name_to_index_dict,
|
|
3217
|
+
bone_name_to_index_dict,
|
|
3218
|
+
)
|
|
3219
|
+
if constraint_dict:
|
|
3220
|
+
extensions = node_dict.get("extensions")
|
|
3221
|
+
if not isinstance(extensions, dict):
|
|
3222
|
+
node_dict["extensions"] = extensions = {}
|
|
3223
|
+
extensions["VRMC_node_constraint"] = {
|
|
3224
|
+
"specVersion": node_constraint_spec_version,
|
|
3225
|
+
"constraint": constraint_dict,
|
|
3226
|
+
}
|
|
3227
|
+
use_node_constraint = True
|
|
3228
|
+
|
|
3229
|
+
material_name_to_index_dict: dict[str, int] = {}
|
|
3230
|
+
material_dicts = json_dict.get("materials")
|
|
3231
|
+
if not isinstance(material_dicts, list):
|
|
3232
|
+
material_dicts = []
|
|
3233
|
+
json_dict["materials"] = material_dicts
|
|
3234
|
+
for material_index, material_dict in enumerate(material_dicts):
|
|
3235
|
+
if not isinstance(material_dict, dict):
|
|
3236
|
+
continue
|
|
3237
|
+
extras_dict = material_dict.get("extras")
|
|
3238
|
+
if not isinstance(extras_dict, dict):
|
|
3239
|
+
continue
|
|
3240
|
+
material_name = extras_dict.pop(self._extras_material_name_key, None)
|
|
3241
|
+
if not isinstance(material_name, str):
|
|
3242
|
+
continue
|
|
3243
|
+
material_name_to_index_dict[material_name] = material_index
|
|
3244
|
+
if not extras_dict:
|
|
3245
|
+
material_dict.pop("extras", None)
|
|
3246
|
+
|
|
3247
|
+
mesh_name_to_index_dict: dict[str, int] = {}
|
|
3248
|
+
for mesh_index, mesh_dict in enumerate(mesh_dicts):
|
|
3249
|
+
if not isinstance(mesh_dict, dict):
|
|
3250
|
+
continue
|
|
3251
|
+
extras_dict = mesh_dict.get("extras")
|
|
3252
|
+
if not isinstance(extras_dict, dict):
|
|
3253
|
+
continue
|
|
3254
|
+
mesh_name = extras_dict.pop(self._extras_mesh_name_key, None)
|
|
3255
|
+
if not isinstance(mesh_name, str):
|
|
3256
|
+
continue
|
|
3257
|
+
mesh_name_to_index_dict[mesh_name] = mesh_index
|
|
3258
|
+
if not extras_dict:
|
|
3259
|
+
mesh_dict.pop("extras", None)
|
|
3260
|
+
|
|
3261
|
+
self.save_vrm_materials(
|
|
3262
|
+
self._context,
|
|
3263
|
+
json_dict,
|
|
3264
|
+
buffer0,
|
|
3265
|
+
material_name_to_index_dict,
|
|
3266
|
+
image_name_to_index_dict,
|
|
3267
|
+
self._gltf2_addon_export_settings,
|
|
3268
|
+
)
|
|
3269
|
+
self.unassign_normal_from_mtoon_primitive_morph_target(
|
|
3270
|
+
self._context, json_dict, material_name_to_index_dict
|
|
3271
|
+
)
|
|
3272
|
+
|
|
3273
|
+
extensions_used = json_dict.get("extensionsUsed")
|
|
3274
|
+
if not isinstance(extensions_used, list):
|
|
3275
|
+
extensions_used = []
|
|
3276
|
+
json_dict["extensionsUsed"] = extensions_used
|
|
3277
|
+
|
|
3278
|
+
if use_node_constraint:
|
|
3279
|
+
extensions_used.append("VRMC_node_constraint")
|
|
3280
|
+
|
|
3281
|
+
extensions = json_dict.get("extensions")
|
|
3282
|
+
if not isinstance(extensions, dict):
|
|
3283
|
+
json_dict["extensions"] = extensions = {}
|
|
3284
|
+
|
|
3285
|
+
extensions_used.append("VRMC_vrm")
|
|
3286
|
+
extensions["VRMC_vrm"] = {
|
|
3287
|
+
"specVersion": get_armature_extension(armature_data).spec_version,
|
|
3288
|
+
"meta": self.create_meta_dict(
|
|
3289
|
+
vrm.meta,
|
|
3290
|
+
json_dict,
|
|
3291
|
+
buffer0,
|
|
3292
|
+
image_name_to_index_dict,
|
|
3293
|
+
self._gltf2_addon_export_settings,
|
|
3294
|
+
),
|
|
3295
|
+
"humanoid": self.create_humanoid_dict(
|
|
3296
|
+
vrm.humanoid, bone_name_to_index_dict
|
|
3297
|
+
),
|
|
3298
|
+
"firstPerson": self.create_first_person_dict(
|
|
3299
|
+
vrm.first_person, mesh_object_name_to_node_index_dict
|
|
3300
|
+
),
|
|
3301
|
+
"lookAt": self.create_look_at_dict(vrm.look_at),
|
|
3302
|
+
"expressions": self.create_expressions_dict(
|
|
3303
|
+
vrm.expressions,
|
|
3304
|
+
mesh_object_name_to_node_index_dict,
|
|
3305
|
+
mesh_object_name_to_morph_target_names_dict,
|
|
3306
|
+
material_name_to_index_dict,
|
|
3307
|
+
),
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
spring_bone = get_armature_extension(armature_data).spring_bone1
|
|
3311
|
+
spring_bone_dict: dict[str, Json] = {}
|
|
3312
|
+
|
|
3313
|
+
(
|
|
3314
|
+
spring_bone_collider_dicts,
|
|
3315
|
+
collider_uuid_to_index_dict,
|
|
3316
|
+
) = self.create_spring_bone_collider_dicts(
|
|
3317
|
+
extensions_used, spring_bone, bone_name_to_index_dict
|
|
3318
|
+
)
|
|
3319
|
+
if spring_bone_collider_dicts:
|
|
3320
|
+
spring_bone_dict["colliders"] = spring_bone_collider_dicts
|
|
3321
|
+
|
|
3322
|
+
(
|
|
3323
|
+
spring_bone_collider_group_dicts,
|
|
3324
|
+
collider_group_uuid_to_index_dict,
|
|
3325
|
+
) = self.create_spring_bone_collider_group_dicts(
|
|
3326
|
+
spring_bone, collider_uuid_to_index_dict
|
|
3327
|
+
)
|
|
3328
|
+
if spring_bone_collider_group_dicts:
|
|
3329
|
+
spring_bone_dict["colliderGroups"] = spring_bone_collider_group_dicts
|
|
3330
|
+
|
|
3331
|
+
spring_bone_spring_dicts = self.create_spring_bone_spring_dicts(
|
|
3332
|
+
extensions_used,
|
|
3333
|
+
spring_bone,
|
|
3334
|
+
bone_name_to_index_dict,
|
|
3335
|
+
collider_group_uuid_to_index_dict,
|
|
3336
|
+
self._armature,
|
|
3337
|
+
)
|
|
3338
|
+
if spring_bone_spring_dicts:
|
|
3339
|
+
spring_bone_dict["springs"] = spring_bone_spring_dicts
|
|
3340
|
+
|
|
3341
|
+
if spring_bone_dict:
|
|
3342
|
+
extensions_used.append("VRMC_springBone")
|
|
3343
|
+
spring_bone_dict["specVersion"] = "1.0"
|
|
3344
|
+
extensions["VRMC_springBone"] = spring_bone_dict
|
|
3345
|
+
|
|
3346
|
+
json_dict["extensions"] = extensions
|
|
3347
|
+
json_dict["extensionsUsed"] = extensions_used
|
|
3348
|
+
|
|
3349
|
+
v = get_addon_version()
|
|
3350
|
+
if environ.get("BLENDER_VRM_USE_TEST_EXPORTER_VERSION") == "true":
|
|
3351
|
+
v = (999, 999, 999)
|
|
3352
|
+
|
|
3353
|
+
generator = "VRM Add-on for Blender v" + ".".join(map(str, v))
|
|
3354
|
+
|
|
3355
|
+
asset_dict = json_dict.get("asset")
|
|
3356
|
+
if not isinstance(asset_dict, dict):
|
|
3357
|
+
asset_dict = {}
|
|
3358
|
+
json_dict["asset"] = asset_dict
|
|
3359
|
+
|
|
3360
|
+
base_generator = asset_dict.get("generator")
|
|
3361
|
+
if isinstance(base_generator, str):
|
|
3362
|
+
generator += " with " + base_generator
|
|
3363
|
+
|
|
3364
|
+
generator += self.generator_postfix
|
|
3365
|
+
|
|
3366
|
+
asset_dict["generator"] = generator
|
|
3367
|
+
|
|
3368
|
+
if buffer0:
|
|
3369
|
+
buffer_dicts = json_dict.get("buffers")
|
|
3370
|
+
if not isinstance(buffer_dicts, list) or not buffer_dicts:
|
|
3371
|
+
buffer_dicts = []
|
|
3372
|
+
json_dict["buffers"] = buffer_dicts
|
|
3373
|
+
if not buffer_dicts:
|
|
3374
|
+
buffer_dicts.append({})
|
|
3375
|
+
buffer_dict = buffer_dicts[0]
|
|
3376
|
+
if not isinstance(buffer_dict, dict):
|
|
3377
|
+
buffer_dict = {}
|
|
3378
|
+
buffer_dicts[0] = buffer_dict
|
|
3379
|
+
buffer_dict["byteLength"] = len(buffer0)
|
|
3380
|
+
|
|
3381
|
+
remove_empty_root_arrays(json_dict)
|
|
3382
|
+
|
|
3383
|
+
self.trigger_pre_save_hook(
|
|
3384
|
+
json_dict,
|
|
3385
|
+
buffer0,
|
|
3386
|
+
self._armature,
|
|
3387
|
+
MappingProxyType(
|
|
3388
|
+
{
|
|
3389
|
+
index: obj
|
|
3390
|
+
for object_name, index in object_name_to_index_dict.items()
|
|
3391
|
+
if (obj := self._context.blend_data.objects.get(object_name))
|
|
3392
|
+
}
|
|
3393
|
+
),
|
|
3394
|
+
MappingProxyType(
|
|
3395
|
+
{
|
|
3396
|
+
index: bone
|
|
3397
|
+
for bone_name, index in bone_name_to_index_dict.items()
|
|
3398
|
+
if (bone := armature_data.bones.get(bone_name))
|
|
3399
|
+
}
|
|
3400
|
+
),
|
|
3401
|
+
MappingProxyType(
|
|
3402
|
+
{
|
|
3403
|
+
index: image
|
|
3404
|
+
for image_name, index in image_name_to_index_dict.items()
|
|
3405
|
+
if (image := self._context.blend_data.images.get(image_name))
|
|
3406
|
+
}
|
|
3407
|
+
),
|
|
3408
|
+
MappingProxyType(
|
|
3409
|
+
{
|
|
3410
|
+
index: material
|
|
3411
|
+
for material_name, index in material_name_to_index_dict.items()
|
|
3412
|
+
if (
|
|
3413
|
+
material := self._context.blend_data.materials.get(
|
|
3414
|
+
material_name
|
|
3415
|
+
)
|
|
3416
|
+
)
|
|
3417
|
+
}
|
|
3418
|
+
),
|
|
3419
|
+
MappingProxyType(
|
|
3420
|
+
{
|
|
3421
|
+
index: mesh
|
|
3422
|
+
for mesh_name, index in mesh_name_to_index_dict.items()
|
|
3423
|
+
if (mesh := self._context.blend_data.meshes.get(mesh_name))
|
|
3424
|
+
}
|
|
3425
|
+
),
|
|
3426
|
+
)
|
|
3427
|
+
|
|
3428
|
+
json_dict = {str(k): make_json(v) for k, v in json_dict.items()}
|
|
3429
|
+
|
|
3430
|
+
asset_dict = json_dict.get("asset")
|
|
3431
|
+
if not isinstance(asset_dict, dict):
|
|
3432
|
+
asset_dict = {}
|
|
3433
|
+
json_dict["asset"] = asset_dict
|
|
3434
|
+
asset_dict["generator"] = generator
|
|
3435
|
+
|
|
3436
|
+
if buffer0:
|
|
3437
|
+
buffer_dicts = json_dict.get("buffers")
|
|
3438
|
+
if not isinstance(buffer_dicts, list) or not buffer_dicts:
|
|
3439
|
+
buffer_dicts = []
|
|
3440
|
+
json_dict["buffers"] = buffer_dicts
|
|
3441
|
+
if not buffer_dicts:
|
|
3442
|
+
buffer_dicts.append({})
|
|
3443
|
+
buffer_dict = buffer_dicts[0]
|
|
3444
|
+
if not isinstance(buffer_dict, dict):
|
|
3445
|
+
buffer_dict = {}
|
|
3446
|
+
buffer_dicts[0] = buffer_dict
|
|
3447
|
+
buffer_dict["byteLength"] = len(buffer0)
|
|
3448
|
+
|
|
3449
|
+
return pack_glb(json_dict, buffer0)
|
|
3450
|
+
|
|
3451
|
+
def trigger_pre_save_hook(
|
|
3452
|
+
self,
|
|
3453
|
+
json_dict: dict[str, Json],
|
|
3454
|
+
buffer0: bytearray,
|
|
3455
|
+
armature_object: Object,
|
|
3456
|
+
node_index_to_object: Mapping[int, Object],
|
|
3457
|
+
node_index_to_bone: Mapping[int, Bone],
|
|
3458
|
+
image_index_to_image: Mapping[int, Image],
|
|
3459
|
+
material_index_to_material: Mapping[int, Material],
|
|
3460
|
+
mesh_index_to_mesh: Mapping[int, Mesh],
|
|
3461
|
+
) -> None:
|
|
3462
|
+
trigger_third_party_user_extension_hook(
|
|
3463
|
+
self._third_party_user_extensions,
|
|
3464
|
+
"pre_save_hook",
|
|
3465
|
+
json_dict,
|
|
3466
|
+
buffer0,
|
|
3467
|
+
armature_object,
|
|
3468
|
+
node_index_to_object,
|
|
3469
|
+
node_index_to_bone,
|
|
3470
|
+
image_index_to_image,
|
|
3471
|
+
material_index_to_material,
|
|
3472
|
+
mesh_index_to_mesh,
|
|
3473
|
+
)
|
|
3474
|
+
|
|
3475
|
+
|
|
3476
|
+
def _remove_inactive_uv_maps(
|
|
3477
|
+
context: Context, mesh_object_names: Sequence[str]
|
|
3478
|
+
) -> None:
|
|
3479
|
+
"""Remove UV maps that are not active for rendering from meshes.
|
|
3480
|
+
|
|
3481
|
+
As of UniVRM v0.131.0, only one UV map is supported, so UV maps not used for
|
|
3482
|
+
rendering are removed.
|
|
3483
|
+
"""
|
|
3484
|
+
for mesh_object_name in mesh_object_names:
|
|
3485
|
+
if not (obj := context.blend_data.objects.get(mesh_object_name)):
|
|
3486
|
+
continue
|
|
3487
|
+
if not isinstance(mesh := obj.data, Mesh):
|
|
3488
|
+
continue
|
|
3489
|
+
uv_layers = mesh.uv_layers
|
|
3490
|
+
for uv_layer_index in reversed(range(len(uv_layers))):
|
|
3491
|
+
if len(uv_layers) <= 1:
|
|
3492
|
+
break
|
|
3493
|
+
if not (0 <= uv_layer_index < len(uv_layers)):
|
|
3494
|
+
continue
|
|
3495
|
+
uv_layer = uv_layers[uv_layer_index]
|
|
3496
|
+
if uv_layer.active_render:
|
|
3497
|
+
continue
|
|
3498
|
+
uv_layers.remove(uv_layer)
|
|
3499
|
+
|
|
3500
|
+
|
|
3501
|
+
def _find_node_world_matrix(
|
|
3502
|
+
node_dicts: list[Json],
|
|
3503
|
+
target_node_index: int,
|
|
3504
|
+
parent_node_index: Optional[int],
|
|
3505
|
+
) -> Optional[Matrix]:
|
|
3506
|
+
if parent_node_index is None:
|
|
3507
|
+
all_child_indices: list[int] = []
|
|
3508
|
+
for node_dict in node_dicts:
|
|
3509
|
+
if not isinstance(node_dict, dict):
|
|
3510
|
+
continue
|
|
3511
|
+
child_node_indices = node_dict.get("children")
|
|
3512
|
+
if isinstance(child_node_indices, list):
|
|
3513
|
+
all_child_indices.extend(
|
|
3514
|
+
child_node_index
|
|
3515
|
+
for child_node_index in child_node_indices
|
|
3516
|
+
if isinstance(child_node_index, int)
|
|
3517
|
+
)
|
|
3518
|
+
for node_index in range(len(node_dicts)):
|
|
3519
|
+
if node_index in all_child_indices:
|
|
3520
|
+
continue
|
|
3521
|
+
matrix = _find_node_world_matrix(node_dicts, target_node_index, node_index)
|
|
3522
|
+
if matrix is not None:
|
|
3523
|
+
return matrix
|
|
3524
|
+
return Matrix()
|
|
3525
|
+
|
|
3526
|
+
if not (0 <= parent_node_index < len(node_dicts)):
|
|
3527
|
+
return None
|
|
3528
|
+
|
|
3529
|
+
node_dict = node_dicts[parent_node_index]
|
|
3530
|
+
if not isinstance(node_dict, dict):
|
|
3531
|
+
return None
|
|
3532
|
+
|
|
3533
|
+
parent_node_matrix = parse_gltf_node_matrix(node_dict)
|
|
3534
|
+
|
|
3535
|
+
if parent_node_index == target_node_index:
|
|
3536
|
+
return parent_node_matrix
|
|
3537
|
+
|
|
3538
|
+
child_node_indices = node_dict.get("children")
|
|
3539
|
+
if not isinstance(child_node_indices, list):
|
|
3540
|
+
return None
|
|
3541
|
+
|
|
3542
|
+
for child_node_index in child_node_indices:
|
|
3543
|
+
if not isinstance(child_node_index, int):
|
|
3544
|
+
continue
|
|
3545
|
+
child_node_matrix = _find_node_world_matrix(
|
|
3546
|
+
node_dicts, target_node_index, child_node_index
|
|
3547
|
+
)
|
|
3548
|
+
if child_node_matrix is not None:
|
|
3549
|
+
return parent_node_matrix @ child_node_matrix
|
|
3550
|
+
|
|
3551
|
+
return None
|
|
3552
|
+
|
|
3553
|
+
|
|
3554
|
+
def _set_node_matrix(node_dict: dict[str, Json], matrix: Matrix) -> None:
|
|
3555
|
+
node_dict.pop("matrix", None)
|
|
3556
|
+
location, rotation, scale = matrix.decompose()
|
|
3557
|
+
node_dict["translation"] = list(location)
|
|
3558
|
+
node_dict["rotation"] = [
|
|
3559
|
+
rotation.x,
|
|
3560
|
+
rotation.y,
|
|
3561
|
+
rotation.z,
|
|
3562
|
+
rotation.w,
|
|
3563
|
+
]
|
|
3564
|
+
node_dict["scale"] = list(scale)
|
|
3565
|
+
|
|
3566
|
+
|
|
3567
|
+
def _force_apply_modifiers_to_objects(
|
|
3568
|
+
context: Context,
|
|
3569
|
+
armature_object: Object,
|
|
3570
|
+
mesh_compatible_object_names: Sequence[str],
|
|
3571
|
+
) -> None:
|
|
3572
|
+
selected_object_names: list[str] = [
|
|
3573
|
+
obj.name for obj in context.selectable_objects if obj.select_get()
|
|
3574
|
+
]
|
|
3575
|
+
for mesh_compatible_object_name in mesh_compatible_object_names:
|
|
3576
|
+
_force_apply_modifiers_to_object(
|
|
3577
|
+
context, armature_object, mesh_compatible_object_name
|
|
3578
|
+
)
|
|
3579
|
+
for obj in context.selectable_objects:
|
|
3580
|
+
obj.select_set(obj.name in selected_object_names)
|
|
3581
|
+
|
|
3582
|
+
|
|
3583
|
+
def _force_apply_modifiers_to_object(
|
|
3584
|
+
context: Context,
|
|
3585
|
+
armature_object: Object,
|
|
3586
|
+
mesh_compatible_object_name: str,
|
|
3587
|
+
) -> None:
|
|
3588
|
+
mesh_compatible_object = context.blend_data.objects.get(mesh_compatible_object_name)
|
|
3589
|
+
if not mesh_compatible_object:
|
|
3590
|
+
return
|
|
3591
|
+
if mesh_compatible_object.type == "MESH":
|
|
3592
|
+
mesh_object: Optional[Object] = mesh_compatible_object
|
|
3593
|
+
elif mesh_compatible_object.type in search.MESH_CONVERTIBLE_OBJECT_TYPES:
|
|
3594
|
+
with save_workspace(context, mesh_compatible_object):
|
|
3595
|
+
convert_result = bpy.ops.object.convert(target="MESH")
|
|
3596
|
+
if convert_result != {"FINISHED"}:
|
|
3597
|
+
_logger.warning(
|
|
3598
|
+
"Failed to convert %s to MESH: %s",
|
|
3599
|
+
mesh_compatible_object.name,
|
|
3600
|
+
convert_result,
|
|
3601
|
+
)
|
|
3602
|
+
return
|
|
3603
|
+
mesh_object = context.blend_data.objects.get(mesh_compatible_object_name)
|
|
3604
|
+
else:
|
|
3605
|
+
return
|
|
3606
|
+
|
|
3607
|
+
if not mesh_object:
|
|
3608
|
+
return
|
|
3609
|
+
|
|
3610
|
+
original_mesh_data = mesh_object.data
|
|
3611
|
+
if not isinstance(original_mesh_data, Mesh):
|
|
3612
|
+
return
|
|
3613
|
+
|
|
3614
|
+
armature_modifier_name_to_show_render_and_show_viewport: dict[
|
|
3615
|
+
str, tuple[bool, bool]
|
|
3616
|
+
] = {}
|
|
3617
|
+
need_apply_modifiers = False
|
|
3618
|
+
for modifier in list(mesh_object.modifiers):
|
|
3619
|
+
if (
|
|
3620
|
+
not isinstance(modifier, ArmatureModifier)
|
|
3621
|
+
or modifier.object != armature_object
|
|
3622
|
+
):
|
|
3623
|
+
if modifier.show_render or modifier.show_viewport:
|
|
3624
|
+
need_apply_modifiers = True
|
|
3625
|
+
continue
|
|
3626
|
+
armature_modifier_name_to_show_render_and_show_viewport[modifier.name] = (
|
|
3627
|
+
modifier.show_render,
|
|
3628
|
+
modifier.show_viewport,
|
|
3629
|
+
)
|
|
3630
|
+
modifier.show_render = False
|
|
3631
|
+
modifier.show_viewport = False
|
|
3632
|
+
|
|
3633
|
+
try:
|
|
3634
|
+
if not need_apply_modifiers:
|
|
3635
|
+
return
|
|
3636
|
+
|
|
3637
|
+
mesh_data_name = original_mesh_data.name
|
|
3638
|
+
original_mesh_data.name = "Backup-Apply-Data-" + uuid4().hex
|
|
3639
|
+
|
|
3640
|
+
mesh_data: Optional[ID] = force_apply_modifiers(context, mesh_object)
|
|
3641
|
+
if not mesh_data:
|
|
3642
|
+
return
|
|
3643
|
+
|
|
3644
|
+
original_mesh_data.user_remap(mesh_data)
|
|
3645
|
+
if original_mesh_data.users:
|
|
3646
|
+
_logger.warning(
|
|
3647
|
+
'Failed to remove "%s" with %d users while applying modifiers',
|
|
3648
|
+
original_mesh_data.name,
|
|
3649
|
+
original_mesh_data.users,
|
|
3650
|
+
)
|
|
3651
|
+
else:
|
|
3652
|
+
context.blend_data.meshes.remove(original_mesh_data)
|
|
3653
|
+
|
|
3654
|
+
mesh_data.name = mesh_data_name
|
|
3655
|
+
finally:
|
|
3656
|
+
for modifier in list(mesh_object.modifiers):
|
|
3657
|
+
if (
|
|
3658
|
+
not isinstance(modifier, ArmatureModifier)
|
|
3659
|
+
or modifier.object != armature_object
|
|
3660
|
+
):
|
|
3661
|
+
mesh_object.modifiers.remove(modifier)
|
|
3662
|
+
continue
|
|
3663
|
+
show_render_and_show_viewport = (
|
|
3664
|
+
armature_modifier_name_to_show_render_and_show_viewport.get(
|
|
3665
|
+
modifier.name
|
|
3666
|
+
)
|
|
3667
|
+
)
|
|
3668
|
+
if show_render_and_show_viewport is None:
|
|
3669
|
+
continue
|
|
3670
|
+
modifier.show_render, modifier.show_viewport = show_render_and_show_viewport
|
|
3671
|
+
|
|
3672
|
+
|
|
3673
|
+
def is_identity_matrix(matrix: Matrix) -> bool:
|
|
3674
|
+
for row_index, row in enumerate(matrix):
|
|
3675
|
+
for column_index, value in enumerate(row):
|
|
3676
|
+
if row_index == column_index:
|
|
3677
|
+
if abs(value - 1) < float_info.epsilon:
|
|
3678
|
+
continue
|
|
3679
|
+
elif abs(value) < float_info.epsilon:
|
|
3680
|
+
continue
|
|
3681
|
+
return False
|
|
3682
|
+
return True
|