bpy-vrm-format 4.7.4__py3-none-any.whl

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.
Files changed (154) hide show
  1. bpy_vrm_format-4.7.4.dist-info/METADATA +118 -0
  2. bpy_vrm_format-4.7.4.dist-info/RECORD +154 -0
  3. bpy_vrm_format-4.7.4.dist-info/WHEEL +4 -0
  4. io_scene_vrm/README.txt +3 -0
  5. io_scene_vrm/__init__.py +41 -0
  6. io_scene_vrm/blender_manifest.toml +28 -0
  7. io_scene_vrm/common/__init__.py +1 -0
  8. io_scene_vrm/common/animation.py +58 -0
  9. io_scene_vrm/common/blender_manifest.py +82 -0
  10. io_scene_vrm/common/char.py +16 -0
  11. io_scene_vrm/common/convert.py +287 -0
  12. io_scene_vrm/common/convert_any.py +48 -0
  13. io_scene_vrm/common/debug.py +40 -0
  14. io_scene_vrm/common/deep.py +162 -0
  15. io_scene_vrm/common/error_dialog.py +274 -0
  16. io_scene_vrm/common/fs.py +71 -0
  17. io_scene_vrm/common/gl.py +20 -0
  18. io_scene_vrm/common/gltf.py +1121 -0
  19. io_scene_vrm/common/human_bone_mapper/__init__.py +1 -0
  20. io_scene_vrm/common/human_bone_mapper/biped_mapping.py +84 -0
  21. io_scene_vrm/common/human_bone_mapper/cats_blender_plugin_fix_model_mapping.py +63 -0
  22. io_scene_vrm/common/human_bone_mapper/human_bone_mapper.py +203 -0
  23. io_scene_vrm/common/human_bone_mapper/microsoft_rocketbox_mapping.py +69 -0
  24. io_scene_vrm/common/human_bone_mapper/mixamo_mapping.py +62 -0
  25. io_scene_vrm/common/human_bone_mapper/mmd_mapping.py +115 -0
  26. io_scene_vrm/common/human_bone_mapper/mpfb_mapping.py +120 -0
  27. io_scene_vrm/common/human_bone_mapper/ready_player_me_mapping.py +65 -0
  28. io_scene_vrm/common/human_bone_mapper/reallusion_mapping.py +94 -0
  29. io_scene_vrm/common/human_bone_mapper/rigify_meta_rig_mapping.py +65 -0
  30. io_scene_vrm/common/human_bone_mapper/structure_based_mapping.py +891 -0
  31. io_scene_vrm/common/human_bone_mapper/unreal_mapping.py +62 -0
  32. io_scene_vrm/common/human_bone_mapper/vrm_addon_mapping.py +112 -0
  33. io_scene_vrm/common/human_bone_mapper/vroid_mapping.py +89 -0
  34. io_scene_vrm/common/legacy_gltf.py +16 -0
  35. io_scene_vrm/common/logger.py +84 -0
  36. io_scene_vrm/common/mtoon0.blend +0 -0
  37. io_scene_vrm/common/mtoon1.blend +0 -0
  38. io_scene_vrm/common/mtoon1_outline.blend +0 -0
  39. io_scene_vrm/common/mtoon_auto_setup.blend +0 -0
  40. io_scene_vrm/common/mtoon_unversioned.py +62 -0
  41. io_scene_vrm/common/ops/__init__.py +10 -0
  42. io_scene_vrm/common/ops/export_scene.py +67 -0
  43. io_scene_vrm/common/ops/icyp.py +53 -0
  44. io_scene_vrm/common/ops/import_scene.py +57 -0
  45. io_scene_vrm/common/ops/vrm.py +2380 -0
  46. io_scene_vrm/common/ops/wm.py +167 -0
  47. io_scene_vrm/common/preferences.py +362 -0
  48. io_scene_vrm/common/progress.py +87 -0
  49. io_scene_vrm/common/rotation.py +89 -0
  50. io_scene_vrm/common/safe_removal.py +46 -0
  51. io_scene_vrm/common/scene_watcher.py +230 -0
  52. io_scene_vrm/common/shader.py +2243 -0
  53. io_scene_vrm/common/shape_key_mapper/__init__.py +1 -0
  54. io_scene_vrm/common/shape_key_mapper/arkit_mapping.py +168 -0
  55. io_scene_vrm/common/shape_key_mapper/mmd_mapping.py +62 -0
  56. io_scene_vrm/common/shape_key_mapper/ready_player_me_mapping.py +52 -0
  57. io_scene_vrm/common/shape_key_mapper/vrchat_mapping.py +17 -0
  58. io_scene_vrm/common/third_party_user_extension.py +214 -0
  59. io_scene_vrm/common/transparent_z_write.blend +0 -0
  60. io_scene_vrm/common/version.py +161 -0
  61. io_scene_vrm/common/vrm0/__init__.py +1 -0
  62. io_scene_vrm/common/vrm0/human_bone.py +662 -0
  63. io_scene_vrm/common/vrm0/material_property.py +346 -0
  64. io_scene_vrm/common/vrm1/__init__.py +1 -0
  65. io_scene_vrm/common/vrm1/expression_preset.py +207 -0
  66. io_scene_vrm/common/vrm1/human_bone.py +822 -0
  67. io_scene_vrm/common/workspace.py +227 -0
  68. io_scene_vrm/common/writable_context.py +77 -0
  69. io_scene_vrm/editor/__init__.py +1 -0
  70. io_scene_vrm/editor/extension.py +425 -0
  71. io_scene_vrm/editor/extension_accessor.py +75 -0
  72. io_scene_vrm/editor/handler.py +84 -0
  73. io_scene_vrm/editor/khr_character/__init__.py +1 -0
  74. io_scene_vrm/editor/khr_character/panel.py +69 -0
  75. io_scene_vrm/editor/khr_character/property_group.py +27 -0
  76. io_scene_vrm/editor/khr_xmp_json_ld/__init__.py +1 -0
  77. io_scene_vrm/editor/khr_xmp_json_ld/ops.py +464 -0
  78. io_scene_vrm/editor/khr_xmp_json_ld/panel.py +136 -0
  79. io_scene_vrm/editor/khr_xmp_json_ld/property_group.py +83 -0
  80. io_scene_vrm/editor/khr_xmp_json_ld/ui_list.py +198 -0
  81. io_scene_vrm/editor/make_armature.py +709 -0
  82. io_scene_vrm/editor/menu.py +147 -0
  83. io_scene_vrm/editor/migration.py +297 -0
  84. io_scene_vrm/editor/mtoon1/__init__.py +1 -0
  85. io_scene_vrm/editor/mtoon1/handler.py +20 -0
  86. io_scene_vrm/editor/mtoon1/migration.py +536 -0
  87. io_scene_vrm/editor/mtoon1/ops.py +304 -0
  88. io_scene_vrm/editor/mtoon1/panel.py +442 -0
  89. io_scene_vrm/editor/mtoon1/property_group.py +4977 -0
  90. io_scene_vrm/editor/mtoon1/scene_watcher.py +381 -0
  91. io_scene_vrm/editor/node_constraint1/__init__.py +1 -0
  92. io_scene_vrm/editor/node_constraint1/panel.py +317 -0
  93. io_scene_vrm/editor/node_constraint1/property_group.py +25 -0
  94. io_scene_vrm/editor/ops.py +471 -0
  95. io_scene_vrm/editor/panel.py +315 -0
  96. io_scene_vrm/editor/property_group.py +888 -0
  97. io_scene_vrm/editor/search.py +682 -0
  98. io_scene_vrm/editor/spring_bone1/__init__.py +1 -0
  99. io_scene_vrm/editor/spring_bone1/handler.py +862 -0
  100. io_scene_vrm/editor/spring_bone1/menu.py +246 -0
  101. io_scene_vrm/editor/spring_bone1/migration.py +86 -0
  102. io_scene_vrm/editor/spring_bone1/ops.py +1663 -0
  103. io_scene_vrm/editor/spring_bone1/panel.py +584 -0
  104. io_scene_vrm/editor/spring_bone1/property_group.py +1355 -0
  105. io_scene_vrm/editor/spring_bone1/ui_list.py +408 -0
  106. io_scene_vrm/editor/subscription.py +123 -0
  107. io_scene_vrm/editor/t_pose.py +713 -0
  108. io_scene_vrm/editor/validation.py +1271 -0
  109. io_scene_vrm/editor/vrm0/__init__.py +1 -0
  110. io_scene_vrm/editor/vrm0/gizmo_group.py +63 -0
  111. io_scene_vrm/editor/vrm0/handler.py +23 -0
  112. io_scene_vrm/editor/vrm0/menu.py +145 -0
  113. io_scene_vrm/editor/vrm0/migration.py +761 -0
  114. io_scene_vrm/editor/vrm0/ops.py +2145 -0
  115. io_scene_vrm/editor/vrm0/panel.py +1333 -0
  116. io_scene_vrm/editor/vrm0/property_group.py +1818 -0
  117. io_scene_vrm/editor/vrm0/ui_list.py +688 -0
  118. io_scene_vrm/editor/vrm1/__init__.py +1 -0
  119. io_scene_vrm/editor/vrm1/handler.py +28 -0
  120. io_scene_vrm/editor/vrm1/menu.py +76 -0
  121. io_scene_vrm/editor/vrm1/migration.py +321 -0
  122. io_scene_vrm/editor/vrm1/ops.py +2679 -0
  123. io_scene_vrm/editor/vrm1/panel.py +1243 -0
  124. io_scene_vrm/editor/vrm1/property_group.py +2087 -0
  125. io_scene_vrm/editor/vrm1/scene_watcher.py +90 -0
  126. io_scene_vrm/editor/vrm1/ui_list.py +533 -0
  127. io_scene_vrm/exporter/__init__.py +1 -0
  128. io_scene_vrm/exporter/abstract_base_vrm_exporter.py +628 -0
  129. io_scene_vrm/exporter/export_scene.py +930 -0
  130. io_scene_vrm/exporter/gltf2_export_user_extension.py +45 -0
  131. io_scene_vrm/exporter/khr_character_exporter.py +34 -0
  132. io_scene_vrm/exporter/vrm0_exporter.py +4030 -0
  133. io_scene_vrm/exporter/vrm1_exporter.py +3653 -0
  134. io_scene_vrm/exporter/vrm_animation_exporter.py +1035 -0
  135. io_scene_vrm/external/__init__.py +1 -0
  136. io_scene_vrm/external/auto_rig_pro_support.py +3 -0
  137. io_scene_vrm/external/io_scene_gltf2_support.py +418 -0
  138. io_scene_vrm/external/rigify_support.py +1 -0
  139. io_scene_vrm/importer/__init__.py +1 -0
  140. io_scene_vrm/importer/abstract_base_vrm_importer.py +1494 -0
  141. io_scene_vrm/importer/file_handler.py +34 -0
  142. io_scene_vrm/importer/gltf2_import_user_extension.py +268 -0
  143. io_scene_vrm/importer/import_scene.py +646 -0
  144. io_scene_vrm/importer/license_validation.py +198 -0
  145. io_scene_vrm/importer/vrm0_importer.py +1730 -0
  146. io_scene_vrm/importer/vrm1_importer.py +1924 -0
  147. io_scene_vrm/importer/vrm_animation_importer.py +763 -0
  148. io_scene_vrm/importer/vrm_diff.py +316 -0
  149. io_scene_vrm/locale/__init__.py +1 -0
  150. io_scene_vrm/locale/ja_jp.py +826 -0
  151. io_scene_vrm/locale/ruff.toml +8 -0
  152. io_scene_vrm/locale/translation_dictionary.py +10 -0
  153. io_scene_vrm/locale/zh_hans.py +928 -0
  154. io_scene_vrm/registration.py +669 -0
@@ -0,0 +1,287 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
2
+ import math
3
+ from collections.abc import Iterator, Mapping, Sequence
4
+ from sys import float_info
5
+ from typing import Final, Optional, Union
6
+
7
+ from . import convert_any
8
+ from .logger import get_logger
9
+
10
+ _logger = get_logger(__name__)
11
+
12
+ BPY_TRACK_AXIS_TO_VRM_AIM_AXIS: Final[Mapping[str, str]] = {
13
+ "TRACK_X": "PositiveX",
14
+ "TRACK_Y": "PositiveY",
15
+ "TRACK_Z": "PositiveZ",
16
+ "TRACK_NEGATIVE_X": "NegativeX",
17
+ "TRACK_NEGATIVE_Y": "NegativeY",
18
+ "TRACK_NEGATIVE_Z": "NegativeZ",
19
+ }
20
+
21
+ VRM_AIM_AXIS_TO_BPY_TRACK_AXIS: Final[Mapping[str, str]] = {
22
+ v: k for k, v in BPY_TRACK_AXIS_TO_VRM_AIM_AXIS.items()
23
+ }
24
+
25
+
26
+ Json = Union[
27
+ None,
28
+ bool,
29
+ int,
30
+ float,
31
+ str,
32
+ list["Json"],
33
+ dict[str, "Json"],
34
+ ]
35
+
36
+ JsonView = Union[
37
+ None,
38
+ bool,
39
+ int,
40
+ float,
41
+ str,
42
+ Sequence["JsonView"],
43
+ Mapping[str, "JsonView"],
44
+ ]
45
+
46
+
47
+ def iterator_or_none(v: object) -> Optional[Iterator[object]]:
48
+ try:
49
+ # "isinstance(v, Iterable)" doesn't work.
50
+ # https://github.com/python/cpython/blob/3.9/Doc/library/collections.abc.rst?plain=1#L126-L127
51
+ iterator = iter(v) # type: ignore[call-overload]
52
+ except TypeError:
53
+ return None
54
+ return convert_any.iterator_to_object_iterator(iterator)
55
+
56
+
57
+ def sequence_or_none(
58
+ sequence_object: object,
59
+ ) -> Optional[Sequence[object]]:
60
+ iterator = iterator_or_none(sequence_object)
61
+ if iterator is None:
62
+ return None
63
+ return list(iterator)
64
+
65
+
66
+ def mapping_or_none(
67
+ mapping_object: object,
68
+ ) -> Optional[Mapping[object, object]]:
69
+ return convert_any.mapping_to_object_mapping(mapping_object)
70
+
71
+
72
+ def vrm_json_vector3_to_tuple(
73
+ value: Json,
74
+ ) -> Optional[tuple[float, float, float]]:
75
+ if not isinstance(value, dict):
76
+ return None
77
+ x = float_or(value.get("x"), 0.0)
78
+ y = float_or(value.get("y"), 0.0)
79
+ z = float_or(value.get("z"), 0.0)
80
+ return (x, y, z)
81
+
82
+
83
+ def vrm_json_curve_to_list(curve: Json) -> Optional[list[float]]:
84
+ if not isinstance(curve, list):
85
+ return None
86
+ values: list[float] = [float_or(v, 0.0) for v in curve]
87
+ while len(values) < 8:
88
+ values.append(0)
89
+ while len(values) > 8:
90
+ values.pop()
91
+ return values
92
+
93
+
94
+ def vrm_json_array_to_float_vector(
95
+ input_values: Json, defaults: list[float]
96
+ ) -> list[float]:
97
+ if not isinstance(input_values, list):
98
+ return defaults
99
+
100
+ output_values: list[float] = []
101
+ for index, default in enumerate(defaults):
102
+ if index >= len(input_values):
103
+ output_values.append(default)
104
+ continue
105
+
106
+ input_value = float_or(input_values[index], default)
107
+ output_values.append(input_value)
108
+
109
+ return output_values
110
+
111
+
112
+ def mtoon_shading_toony_1_to_0(shading_toony: float, shading_shift: float) -> float:
113
+ base = 2 - shading_toony + shading_shift
114
+ if abs(base) < float_info.epsilon:
115
+ # https://github.com/Santarh/MToon/blob/43f02fe8c9b19c3cf9f3238003b7d8b332933833/MToon/Resources/Shaders/MToon.shader#L17
116
+ return 0.9
117
+ return max(0, min((shading_toony + shading_shift) / base, 1))
118
+
119
+
120
+ def mtoon_shading_shift_1_to_0(shading_toony: float, shading_shift: float) -> float:
121
+ return max(-1, min(shading_toony - shading_shift - 1, 1))
122
+
123
+
124
+ def mtoon_gi_equalization_to_intensity(gi_equalization: float) -> float:
125
+ return max(0, min(1.0, 1.0 - gi_equalization))
126
+
127
+
128
+ # https://github.com/vrm-c/UniVRM/blob/f3479190c330ec6ecd2b40be919285aa93a53aff/Assets/VRMShaders/VRM10/MToon10/Runtime/MToon10Migrator.cs#L10-L19
129
+ def mtoon_shading_toony_0_to_1(
130
+ shading_toony_0x: float, shading_shift_0x: float
131
+ ) -> float:
132
+ (range_min, range_max) = _get_shading_range_0x(shading_toony_0x, shading_shift_0x)
133
+ return max(0, min(1.0, (2.0 - (range_max - range_min)) * 0.5))
134
+
135
+
136
+ # https://github.com/vrm-c/UniVRM/blob/f3479190c330ec6ecd2b40be919285aa93a53aff/Assets/VRMShaders/VRM10/MToon10/Runtime/MToon10Migrator.cs#L21-L30
137
+ def mtoon_shading_shift_0_to_1(
138
+ shading_toony_0x: float, shading_shift_0x: float
139
+ ) -> float:
140
+ (range_min, range_max) = _get_shading_range_0x(shading_toony_0x, shading_shift_0x)
141
+ return max(-1, min(1.0, ((range_max + range_min) * 0.5 * -1)))
142
+
143
+
144
+ # https://github.com/vrm-c/UniVRM/blob/f3479190c330ec6ecd2b40be919285aa93a53aff/Assets/VRMShaders/VRM10/MToon10/Runtime/MToon10Migrator.cs#L32-L38
145
+ def mtoon_intensity_to_gi_equalization(gi_intensity_0x: float) -> float:
146
+ return max(0, min(1.0, 1.0 - gi_intensity_0x))
147
+
148
+
149
+ # https://github.com/vrm-c/UniVRM/blob/f3479190c330ec6ecd2b40be919285aa93a53aff/Assets/VRMShaders/VRM10/MToon10/Runtime/MToon10Migrator.cs#L40-L46
150
+ def _get_shading_range_0x(
151
+ shading_toony_0x: float, shading_shift_0x: float
152
+ ) -> tuple[float, float]:
153
+ range_min = shading_shift_0x
154
+ range_max = (1 - shading_toony_0x) + shading_toony_0x * shading_shift_0x
155
+ return (range_min, range_max)
156
+
157
+
158
+ def float_or_none(
159
+ v: object,
160
+ min_value: float = -float_info.max,
161
+ max_value: float = float_info.max,
162
+ ) -> Optional[float]:
163
+ if isinstance(v, float):
164
+ if math.isnan(v):
165
+ return None
166
+ return max(min_value, min(v, max_value))
167
+
168
+ if isinstance(v, bool):
169
+ return max(min_value, min(1.0 if v else 0.0, max_value))
170
+
171
+ if isinstance(v, int):
172
+ return max(min_value, min(float(v), max_value))
173
+
174
+ return None
175
+
176
+
177
+ def float_or(
178
+ v: object,
179
+ default: float,
180
+ min_value: float = -float_info.max,
181
+ max_value: float = float_info.max,
182
+ ) -> float:
183
+ float_or_none_value = float_or_none(v, min_value, max_value)
184
+ if float_or_none_value is not None:
185
+ return float_or_none_value
186
+
187
+ if min_value <= default <= max_value:
188
+ return default
189
+
190
+ _logger.warning(
191
+ "Default float value %s is out of range [%s, %s]", default, min_value, max_value
192
+ )
193
+
194
+ return max(min_value, min(default, max_value))
195
+
196
+
197
+ def float4_or_none(value4: object) -> Optional[tuple[float, float, float, float]]:
198
+ value4_iterator = iterator_or_none(value4)
199
+ if value4_iterator is None:
200
+ return None
201
+
202
+ result: list[float] = []
203
+ for value in value4_iterator:
204
+ if len(result) == 4:
205
+ return None
206
+ float_value = float_or_none(value)
207
+ if float_value is None:
208
+ return None
209
+ result.append(float_value)
210
+ if len(result) != 4:
211
+ return None
212
+ return (result[0], result[1], result[2], result[3])
213
+
214
+
215
+ def float4_or(
216
+ value4: object, default: tuple[float, float, float, float]
217
+ ) -> tuple[float, float, float, float]:
218
+ float4 = float4_or_none(value4)
219
+ return float4 if float4 is not None else default
220
+
221
+
222
+ def float3_or_none(value3: object) -> Optional[tuple[float, float, float]]:
223
+ value3_iterator = iterator_or_none(value3)
224
+ if value3_iterator is None:
225
+ return None
226
+
227
+ result: list[float] = []
228
+ for value in value3_iterator:
229
+ if len(result) == 3:
230
+ return None
231
+ float_value = float_or_none(value)
232
+ if float_value is None:
233
+ return None
234
+ result.append(float_value)
235
+ if len(result) != 3:
236
+ return None
237
+ return (result[0], result[1], result[2])
238
+
239
+
240
+ def float3_or(
241
+ value3: object, default: tuple[float, float, float]
242
+ ) -> tuple[float, float, float]:
243
+ float3 = float3_or_none(value3)
244
+ return float3 if float3 is not None else default
245
+
246
+
247
+ def float2_or_none(value2: object) -> Optional[tuple[float, float]]:
248
+ value2_iterator = iterator_or_none(value2)
249
+ if value2_iterator is None:
250
+ return None
251
+ result: list[float] = []
252
+ for value in value2_iterator:
253
+ if len(result) == 2:
254
+ return None
255
+ float_value = float_or_none(value)
256
+ if float_value is None:
257
+ return None
258
+ result.append(float_value)
259
+ if len(result) != 2:
260
+ return None
261
+ return (result[0], result[1])
262
+
263
+
264
+ def axis_blender_to_gltf(vector3: Sequence[float]) -> tuple[float, float, float]:
265
+ return (
266
+ -vector3[0],
267
+ vector3[2],
268
+ vector3[1],
269
+ )
270
+
271
+
272
+ def linear_to_srgb(
273
+ non_color: Sequence[float],
274
+ ) -> Sequence[float]:
275
+ return [
276
+ math.pow(channel_value, 1.0 / 2.2) if channel_index < 3 else channel_value
277
+ for channel_index, channel_value in enumerate(non_color)
278
+ ]
279
+
280
+
281
+ def srgb_to_linear(
282
+ srgb_color: Sequence[float],
283
+ ) -> Sequence[float]:
284
+ return [
285
+ math.pow(channel_value, 2.2) if channel_index < 3 else channel_value
286
+ for channel_index, channel_value in enumerate(srgb_color)
287
+ ]
@@ -0,0 +1,48 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
2
+ """A module provides a function to convert a variable of type Any to a concrete type.
3
+
4
+ Inevitably, variables of type Any may occur, and such variables cannot be handled
5
+ in type checkers in strict mode. Any is allowed only in the module here.
6
+ """
7
+
8
+ import sys
9
+ from collections.abc import Iterator, Mapping
10
+ from typing import (
11
+ Any, # Any is allowed only in the module here.
12
+ Optional,
13
+ )
14
+
15
+
16
+ def to_object( # type: ignore[explicit-any]
17
+ any_object: Any, # noqa: ANN401 # Any is allowed only in the module here.
18
+ ) -> object:
19
+ # Interpret Unknown as object
20
+ # https://github.com/microsoft/pyright/issues/3650
21
+ if not isinstance(any_object, object):
22
+ if sys.version_info >= (3, 11):
23
+ from typing import assert_never
24
+
25
+ assert_never(any_object)
26
+ raise TypeError
27
+ return any_object
28
+
29
+
30
+ def iterator_to_object_iterator( # type: ignore[explicit-any]
31
+ any_iterator: Any, # noqa: ANN401 # Any is allowed only in the module here.
32
+ ) -> Optional[Iterator[object]]:
33
+ any_iterator_without_partial_type_narrowing = any_iterator
34
+ if not isinstance(any_iterator, Iterator):
35
+ return None
36
+ return map(to_object, any_iterator_without_partial_type_narrowing)
37
+
38
+
39
+ def mapping_to_object_mapping( # type: ignore[explicit-any]
40
+ any_mapping: Any, # noqa: ANN401 # Any is allowed only in the module here.
41
+ ) -> Optional[Mapping[object, object]]:
42
+ any_mapping_without_partial_type_narrowing = any_mapping
43
+ if not isinstance(any_mapping, Mapping):
44
+ return None
45
+ return {
46
+ to_object(k): to_object(v)
47
+ for k, v in any_mapping_without_partial_type_narrowing.items()
48
+ }
@@ -0,0 +1,40 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
2
+ import math
3
+ from collections.abc import Sequence
4
+ from typing import Union
5
+
6
+ from mathutils import Matrix, Quaternion, Vector
7
+
8
+
9
+ def dump(v: Union[Matrix, Vector, Quaternion, float]) -> str:
10
+ if isinstance(v, (float, int)):
11
+ return str(v)
12
+
13
+ if isinstance(v, Matrix):
14
+ t, r, s = v.decompose()
15
+ return f"Matrix(T={dump(t)},R={dump(r)},S={dump(s)})"
16
+
17
+ if isinstance(v, Vector):
18
+ return f"({v.x:.3f},{v.y:.3f},{v.z:.3f})"
19
+
20
+ x, y, z = (round(math.degrees(xyz)) for xyz in v.to_euler()[:])
21
+ return f"Euler({x},{y},{z})"
22
+
23
+
24
+ def assert_vector3_equals(
25
+ expected: Vector, actual: Sequence[float], message: str
26
+ ) -> None:
27
+ if len(actual) != 3:
28
+ message = f"actual length is not 3: {actual}"
29
+ raise AssertionError(message)
30
+
31
+ threshold = 0.0001
32
+ if abs(expected[0] - actual[0]) > threshold:
33
+ message = f"{message}: {tuple(expected)} is different from {tuple(actual)}"
34
+ raise AssertionError(message)
35
+ if abs(expected[1] - actual[1]) > threshold:
36
+ message = f"{message}: {tuple(expected)} is different from {tuple(actual)}"
37
+ raise AssertionError(message)
38
+ if abs(expected[2] - actual[2]) > threshold:
39
+ message = f"{message}: {tuple(expected)} is different from {tuple(actual)}"
40
+ raise AssertionError(message)
@@ -0,0 +1,162 @@
1
+ # SPDX-License-Identifier: MIT OR GPL-3.0-or-later
2
+ import difflib
3
+ import math
4
+ from collections.abc import Mapping
5
+ from json import dumps as json_dumps
6
+ from types import MappingProxyType
7
+
8
+ from . import convert
9
+ from .convert import Json, JsonView
10
+ from .logger import get_logger
11
+
12
+ _logger = get_logger(__name__)
13
+
14
+
15
+ def make_json(v: object) -> Json:
16
+ if v is None:
17
+ return None
18
+ if isinstance(v, int):
19
+ return v
20
+ if isinstance(v, float):
21
+ if not math.isfinite(v):
22
+ _logger.warning("%s %s is non-finite value", v, type(v))
23
+ return 0.0
24
+ return v
25
+ if isinstance(v, bool):
26
+ return v
27
+ if isinstance(v, str):
28
+ return v
29
+
30
+ mapping = convert.mapping_or_none(v)
31
+ if mapping is not None:
32
+ dict_result: dict[str, Json] = {}
33
+ for key, value in mapping.items():
34
+ if isinstance(key, str):
35
+ dict_result[key] = make_json(value)
36
+ continue
37
+ _logger.warning("%s %s is unrecognized type for dict key", key, type(key))
38
+ return dict_result
39
+
40
+ iterator = convert.iterator_or_none(v)
41
+ if iterator is not None:
42
+ return [make_json(x) for x in iterator]
43
+
44
+ _logger.warning("%s %s is unrecognized type", v, type(v))
45
+ return None
46
+
47
+
48
+ def make_json_view(v: object) -> JsonView:
49
+ json = make_json(v)
50
+ if isinstance(json, dict):
51
+ return MappingProxyType(
52
+ {key: make_json_view(value) for key, value in json.items()}
53
+ )
54
+ if isinstance(json, list):
55
+ return tuple(make_json_view(x) for x in json)
56
+ return json
57
+
58
+
59
+ def make_json_dict(v: Mapping[str, object]) -> dict[str, Json]:
60
+ return {key: make_json(value) for key, value in v.items()}
61
+
62
+
63
+ def diff(
64
+ left: Json,
65
+ right: Json,
66
+ float_tolerance: float = 0,
67
+ path: str = "",
68
+ ) -> list[str]:
69
+ if isinstance(left, list):
70
+ if not isinstance(right, list):
71
+ return [f"{path}: left is list but right is {type(right)}"]
72
+ if len(left) != len(right):
73
+ result = [
74
+ f"{path}: left length is {len(left)} but right length is {len(right)}"
75
+ ]
76
+
77
+ right_json_str = json_dumps(right, indent=4, sort_keys=True)
78
+ right_json_lines = right_json_str.splitlines(keepends=True)
79
+ left_json_str = json_dumps(left, indent=4, sort_keys=True)
80
+ left_json_lines = left_json_str.splitlines(keepends=True)
81
+
82
+ if len(right_json_lines) > 1000 or len(left_json_lines) > 1000:
83
+ return result
84
+
85
+ unified_diff = [
86
+ line.rstrip()
87
+ for line in difflib.unified_diff(
88
+ right_json_lines,
89
+ left_json_lines,
90
+ f"{path}/right",
91
+ f"{path}/left",
92
+ )
93
+ ]
94
+ if len(unified_diff) > 1000:
95
+ return result
96
+ return result + unified_diff
97
+ diffs: list[str] = []
98
+ for i, (left_child, right_child) in enumerate(zip(left, right)):
99
+ diffs.extend(diff(left_child, right_child, float_tolerance, f"{path}[{i}]"))
100
+ return diffs
101
+
102
+ if isinstance(left, dict):
103
+ if not isinstance(right, dict):
104
+ return [f"{path}: left is dict but right is {type(right)}"]
105
+ diffs = []
106
+ for key in sorted(left.keys() | right.keys()):
107
+ if key not in left:
108
+ diffs.append(f'{path}: {key} not in left. right["{key}"]={right[key]}')
109
+ continue
110
+ if key not in right:
111
+ diffs.append(f'{path}: {key} not in right, left["{key}"]={left[key]}')
112
+ continue
113
+ diffs.extend(
114
+ diff(left[key], right[key], float_tolerance, f'{path}["{key}"]')
115
+ )
116
+ return diffs
117
+
118
+ if isinstance(left, bool):
119
+ if not isinstance(right, bool):
120
+ return [f"{path}: left is bool but right is {type(right)}"]
121
+ if left != right:
122
+ return [f"{path}: left is {left} but right is {right}"]
123
+ return []
124
+
125
+ if isinstance(left, str):
126
+ if not isinstance(right, str):
127
+ return [f"{path}: left is str but right is {type(right)}"]
128
+ if left != right:
129
+ return [f'{path}: left is "{left}" but right is "{right}"']
130
+ return []
131
+
132
+ if isinstance(left, int) and isinstance(right, int):
133
+ if left != right:
134
+ return [f"{path}: left is {left} but right is {right}"]
135
+ return []
136
+
137
+ if isinstance(left, (int, float)) and isinstance(right, (int, float)):
138
+ if (isinstance(left, float) and not math.isfinite(left)) or (
139
+ isinstance(right, float) and not math.isfinite(right)
140
+ ):
141
+ return [
142
+ f"{path}: left is {left}"
143
+ + f" but right is {right}, They are not comparable numbers"
144
+ ]
145
+
146
+ error = math.fabs(float(left) - float(right))
147
+ if error > float_tolerance:
148
+ return [
149
+ f"{path}: left is {float(left):20.17f}"
150
+ + f" but right is {float(right):20.17f}, error={error:19.17f}"
151
+ ]
152
+ return []
153
+
154
+ if left is None:
155
+ if right is None:
156
+ return []
157
+ return [f"{path}: left is None but right is {type(right)}"]
158
+ if right is None:
159
+ return [f"{path}: left is {type(left)} but right is None"]
160
+
161
+ message = f"{path}: unexpected type left={type(left)} right={type(right)}"
162
+ raise ValueError(message)