Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Duroure <julien.duroure@gmail.com>2022-03-02 23:36:18 +0300
committerJulien Duroure <julien.duroure@gmail.com>2022-03-02 23:36:18 +0300
commit782f8585f4cc131a7043269ed5ccb14a36742e3d (patch)
tree82393e682232debc23256b1da81d573295c1968e
parent842c215b746f7e14f9299fa8ae50d2fecd870a92 (diff)
glTF exporter: Big refactoring
- precompute tree before export - manage collections / instances / linked - use custom cache to avoid name collision - animations are baked from world matrix More info on https://github.com/KhronosGroup/glTF-Blender-IO
-rwxr-xr-xio_scene_gltf2/__init__.py4
-rwxr-xr-xio_scene_gltf2/blender/com/gltf2_blender_math.py18
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_export_keys.py1
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_extract.py8
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather.py52
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py35
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py131
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py196
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py101
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py78
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py184
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py18
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py78
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py10
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py71
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py329
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py49
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py143
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py375
19 files changed, 1244 insertions, 637 deletions
diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
index a672be22..1b3c0cc2 100755
--- a/io_scene_gltf2/__init__.py
+++ b/io_scene_gltf2/__init__.py
@@ -4,7 +4,7 @@
bl_info = {
'name': 'glTF 2.0 format',
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
- "version": (3, 2, 7),
+ "version": (3, 2, 8),
'blender': (3, 1, 0),
'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0',
@@ -879,6 +879,8 @@ class GLTF_PT_export_animation_export(bpy.types.Panel):
row = layout.row()
row.active = operator.export_force_sampling
row.prop(operator, 'export_def_bones')
+ if operator.export_force_sampling is False and operator.export_def_bones is True:
+ layout.label(text="Export only deformation bones is not possible when not sampling animation")
class GLTF_PT_export_animation_shapekeys(bpy.types.Panel):
diff --git a/io_scene_gltf2/blender/com/gltf2_blender_math.py b/io_scene_gltf2/blender/com/gltf2_blender_math.py
index 0498e8f8..d2a018a8 100755
--- a/io_scene_gltf2/blender/com/gltf2_blender_math.py
+++ b/io_scene_gltf2/blender/com/gltf2_blender_math.py
@@ -98,7 +98,7 @@ def swizzle_yup_value(value: typing.Any) -> typing.Any:
return value
-def transform(v: typing.Union[Vector, Quaternion], data_path: str, transform: Matrix = Matrix.Identity(4)) -> typing \
+def transform(v: typing.Union[Vector, Quaternion], data_path: str, transform: Matrix = Matrix.Identity(4), need_rotation_correction: bool = False) -> typing \
.Union[Vector, Quaternion]:
"""Manage transformations."""
target = get_target_property_name(data_path)
@@ -116,25 +116,31 @@ def transform(v: typing.Union[Vector, Quaternion], data_path: str, transform: Ma
if transform_func is None:
raise RuntimeError("Cannot transform values at {}".format(data_path))
- return transform_func(v, transform)
+ return transform_func(v, transform, need_rotation_correction)
-def transform_location(location: Vector, transform: Matrix = Matrix.Identity(4)) -> Vector:
+def transform_location(location: Vector, transform: Matrix = Matrix.Identity(4), need_rotation_correction:bool = False) -> Vector:
"""Transform location."""
+ correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
m = Matrix.Translation(location)
+ if need_rotation_correction:
+ m @= correction.to_matrix().to_4x4()
m = transform @ m
return m.to_translation()
-def transform_rotation(rotation: Quaternion, transform: Matrix = Matrix.Identity(4)) -> Quaternion:
+def transform_rotation(rotation: Quaternion, transform: Matrix = Matrix.Identity(4), need_rotation_correction: bool = False) -> Quaternion:
"""Transform rotation."""
rotation.normalize()
+ correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
m = rotation.to_matrix().to_4x4()
+ if need_rotation_correction:
+ m @= correction.to_matrix().to_4x4()
m = transform @ m
return m.to_quaternion()
-def transform_scale(scale: Vector, transform: Matrix = Matrix.Identity(4)) -> Vector:
+def transform_scale(scale: Vector, transform: Matrix = Matrix.Identity(4), need_rotation_correction: bool = False) -> Vector:
"""Transform scale."""
m = Matrix.Identity(4)
m[0][0] = scale.x
@@ -145,7 +151,7 @@ def transform_scale(scale: Vector, transform: Matrix = Matrix.Identity(4)) -> Ve
return m.to_scale()
-def transform_value(value: Vector, _: Matrix = Matrix.Identity(4)) -> Vector:
+def transform_value(value: Vector, _: Matrix = Matrix.Identity(4), need_rotation_correction: bool = False) -> Vector:
"""Transform value."""
return value
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py b/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
index 61a9f5bf..812db3f9 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_export_keys.py
@@ -19,6 +19,7 @@ VISIBLE = 'gltf_visible'
RENDERABLE = 'gltf_renderable'
ACTIVE_COLLECTION = 'gltf_active_collection'
SKINS = 'gltf_skins'
+DEF_BONES_ONLY = 'gltf_def_bones'
DISPLACEMENT = 'gltf_displacement'
FORCE_SAMPLING = 'gltf_force_sampling'
FRAME_RANGE = 'gltf_frame_range'
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
index f5b69f13..d81bd706 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_extract.py
@@ -9,10 +9,14 @@ from ...io.com.gltf2_io_debug import print_console
from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins
-def extract_primitives(glTF, blender_mesh, library, blender_object, blender_vertex_groups, modifiers, export_settings):
+def extract_primitives(blender_mesh, uuid_for_skined_data, blender_vertex_groups, modifiers, export_settings):
"""Extract primitives from a mesh."""
print_console('INFO', 'Extracting primitive: ' + blender_mesh.name)
+ blender_object = None
+ if uuid_for_skined_data:
+ blender_object = export_settings['vtree'].nodes[uuid_for_skined_data].blender_object
+
use_normals = export_settings[gltf2_blender_export_keys.NORMALS]
if use_normals:
blender_mesh.calc_normals_split()
@@ -57,7 +61,7 @@ def extract_primitives(glTF, blender_mesh, library, blender_object, blender_vert
armature = None
if armature:
- skin = gltf2_blender_gather_skins.gather_skin(armature, export_settings)
+ skin = gltf2_blender_gather_skins.gather_skin(export_settings['vtree'].nodes[uuid_for_skined_data].armature, export_settings)
if not skin:
armature = None
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
index 31c0fa62..f515da8c 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather.py
@@ -7,10 +7,12 @@ from io_scene_gltf2.io.com import gltf2_io
from io_scene_gltf2.io.com.gltf2_io_debug import print_console
from io_scene_gltf2.blender.exp import gltf2_blender_gather_nodes
from io_scene_gltf2.blender.exp import gltf2_blender_gather_animations
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_animation_sampler_keyframes
from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
from ..com.gltf2_blender_extras import generate_extras
from io_scene_gltf2.blender.exp import gltf2_blender_export_keys
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_tree
def gather_gltf2(export_settings):
@@ -22,12 +24,18 @@ def gather_gltf2(export_settings):
scenes = []
animations = [] # unfortunately animations in gltf2 are just as 'root' as scenes.
active_scene = None
+ store_user_scene = bpy.context.scene
for blender_scene in bpy.data.scenes:
scenes.append(__gather_scene(blender_scene, export_settings))
if export_settings[gltf2_blender_export_keys.ANIMATIONS]:
+ # resetting object cache
+ gltf2_blender_gather_animation_sampler_keyframes.get_object_matrix.reset_cache()
animations += __gather_animations(blender_scene, export_settings)
if bpy.context.scene.name == blender_scene.name:
active_scene = len(scenes) -1
+
+ # restore user scene
+ bpy.context.window.scene = store_user_scene
return active_scene, scenes, animations
@@ -40,14 +48,25 @@ def __gather_scene(blender_scene, export_settings):
nodes=[]
)
- for blender_object in blender_scene.objects:
- if blender_object.parent is None:
- node = gltf2_blender_gather_nodes.gather_node(
- blender_object,
- blender_object.library.name if blender_object.library else None,
- blender_scene, None, export_settings)
- if node is not None:
- scene.nodes.append(node)
+
+ vtree = gltf2_blender_gather_tree.VExportTree(export_settings)
+ vtree.construct(blender_scene)
+ vtree.search_missing_armature() # In case armature are no parented correctly
+
+ export_user_extensions('vtree_before_filter_hook', export_settings, vtree)
+
+ # Now, we can filter tree if needed
+ vtree.filter()
+
+ export_user_extensions('vtree_after_filter_hook', export_settings, vtree)
+
+ export_settings['vtree'] = vtree
+
+ for r in [vtree.nodes[r] for r in vtree.roots]:
+ node = gltf2_blender_gather_nodes.gather_node(
+ r, export_settings)
+ if node is not None:
+ scene.nodes.append(node)
export_user_extensions('gather_scene_hook', export_settings, scene, blender_scene)
@@ -58,15 +77,16 @@ def __gather_animations(blender_scene, export_settings):
animations = []
merged_tracks = {}
- for blender_object in blender_scene.objects:
+ vtree = export_settings['vtree']
+ for obj_uuid in vtree.get_all_objects():
+ blender_object = vtree.nodes[obj_uuid].blender_object
+
+ # Do not manage not exported objects
+ if vtree.nodes[obj_uuid].node is None:
+ continue
- # First check if this object is exported or not. Do not export animation of not exported object
- obj_node = gltf2_blender_gather_nodes.gather_node(blender_object,
- blender_object.library.name if blender_object.library else None,
- blender_scene, None, export_settings)
- if obj_node is not None:
- animations_, merged_tracks = gltf2_blender_gather_animations.gather_animations(blender_object, merged_tracks, len(animations), export_settings)
- animations += animations_
+ animations_, merged_tracks = gltf2_blender_gather_animations.gather_animations(obj_uuid, merged_tracks, len(animations), export_settings)
+ animations += animations_
if export_settings['gltf_nla_strips'] is False:
# Fake an animation with all animations of the scene
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py
index 928fa14a..0e542de8 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channel_target.py
@@ -12,18 +12,20 @@ from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
@cached
-def gather_animation_channel_target(channels: typing.Tuple[bpy.types.FCurve],
- blender_object: bpy.types.Object,
+def gather_animation_channel_target(obj_uuid: int,
+ channels: typing.Tuple[bpy.types.FCurve],
bake_bone: typing.Union[str, None],
bake_channel: typing.Union[str, None],
- driver_obj,
+ driver_obj_uuid,
export_settings
) -> gltf2_io.AnimationChannelTarget:
+ blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
+
animation_channel_target = gltf2_io.AnimationChannelTarget(
extensions=__gather_extensions(channels, blender_object, export_settings, bake_bone),
extras=__gather_extras(channels, blender_object, export_settings, bake_bone),
- node=__gather_node(channels, blender_object, export_settings, bake_bone, driver_obj),
+ node=__gather_node(channels, obj_uuid, export_settings, bake_bone, driver_obj_uuid),
path=__gather_path(channels, blender_object, export_settings, bake_bone, bake_channel)
)
@@ -54,16 +56,16 @@ def __gather_extras(channels: typing.Tuple[bpy.types.FCurve],
def __gather_node(channels: typing.Tuple[bpy.types.FCurve],
- blender_object: bpy.types.Object,
+ obj_uuid: str,
export_settings,
bake_bone: typing.Union[str, None],
- driver_obj
+ driver_obj_uuid
) -> gltf2_io.Node:
- if driver_obj is not None:
- return gltf2_blender_gather_nodes.gather_node(driver_obj,
- driver_obj.library.name if driver_obj.library else None,
- None, None, export_settings)
+ blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
+
+ if driver_obj_uuid is not None:
+ return export_settings['vtree'].nodes[driver_obj_uuid].node
if blender_object.type == "ARMATURE":
# TODO: get joint from fcurve data_path and gather_joint
@@ -74,16 +76,9 @@ def __gather_node(channels: typing.Tuple[bpy.types.FCurve],
blender_bone = blender_object.path_resolve(channels[0].data_path.rsplit('.', 1)[0])
if isinstance(blender_bone, bpy.types.PoseBone):
- if export_settings["gltf_def_bones"] is False:
- return gltf2_blender_gather_joints.gather_joint(blender_object, blender_bone, export_settings)
- else:
- bones, _, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_object)
- if blender_bone.name in [b.name for b in bones]:
- return gltf2_blender_gather_joints.gather_joint(blender_object, blender_bone, export_settings)
-
- return gltf2_blender_gather_nodes.gather_node(blender_object,
- blender_object.library.name if blender_object.library else None,
- None, None, export_settings)
+ return gltf2_blender_gather_joints.gather_joint_vnode(export_settings['vtree'].nodes[obj_uuid].bones[blender_bone.name], export_settings)
+
+ return export_settings['vtree'].nodes[obj_uuid].node
def __gather_path(channels: typing.Tuple[bpy.types.FCurve],
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py
index 4c79092c..87ef7c13 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_channels.py
@@ -15,15 +15,18 @@ from io_scene_gltf2.blender.exp import gltf2_blender_get
from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins
from io_scene_gltf2.blender.exp import gltf2_blender_gather_drivers
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_tree import VExportNode
+from . import gltf2_blender_export_keys
@cached
-def gather_animation_channels(blender_action: bpy.types.Action,
- blender_object: bpy.types.Object,
+def gather_animation_channels(obj_uuid: int,
+ blender_action: bpy.types.Action,
export_settings
) -> typing.List[gltf2_io.AnimationChannel]:
channels = []
+ blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
# First calculate range of animation for baking
# This is need if user set 'Force sampling' and in case we need to bake
@@ -59,11 +62,8 @@ def gather_animation_channels(blender_action: bpy.types.Action,
# Then bake all bones
bones_to_be_animated = []
- if export_settings["gltf_def_bones"] is False:
- bones_to_be_animated = blender_object.data.bones
- else:
- bones_to_be_animated, _, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_object)
- bones_to_be_animated = [blender_object.pose.bones[b.name] for b in bones_to_be_animated]
+ bones_uuid = export_settings["vtree"].get_all_bones(obj_uuid)
+ bones_to_be_animated = [blender_object.pose.bones[export_settings["vtree"].nodes[b].blender_bone.name] for b in bones_uuid]
list_of_animated_bone_channels = []
for channel_group in __get_channel_groups(blender_action, blender_object, export_settings):
@@ -72,9 +72,9 @@ def gather_animation_channels(blender_action: bpy.types.Action,
for bone in bones_to_be_animated:
for p in ["location", "rotation_quaternion", "scale"]:
- channel = __gather_animation_channel(
+ channel = gather_animation_channel(
+ obj_uuid,
(),
- blender_object,
export_settings,
bone.name,
p,
@@ -95,17 +95,17 @@ def gather_animation_channels(blender_action: bpy.types.Action,
if len(channel_group) == 0:
# Only errors on channels, ignoring
continue
- channel = __gather_animation_channel(channel_group, blender_object, export_settings, None, None, bake_range_start, bake_range_end, force_range, blender_action.name, None, True)
+ channel = gather_animation_channel(obj_uuid, channel_group, export_settings, None, None, bake_range_start, bake_range_end, force_range, blender_action.name, None, True)
if channel is not None:
channels.append(channel)
# Retrieve channels for drivers, if needed
- drivers_to_manage = gltf2_blender_gather_drivers.get_sk_drivers(blender_object)
- for obj, fcurves in drivers_to_manage:
- channel = __gather_animation_channel(
+ drivers_to_manage = gltf2_blender_gather_drivers.get_sk_drivers(obj_uuid, export_settings)
+ for obj_driver_uuid, fcurves in drivers_to_manage:
+ channel = gather_animation_channel(
+ obj_uuid,
fcurves,
- blender_object,
export_settings,
None,
None,
@@ -113,31 +113,77 @@ def gather_animation_channels(blender_action: bpy.types.Action,
bake_range_end,
force_range,
blender_action.name,
- obj,
- False)
+ obj_driver_uuid,
+ True)
if channel is not None:
channels.append(channel)
else:
+ done_paths = []
for channel_group in __get_channel_groups(blender_action, blender_object, export_settings):
channel_group_sorted = __get_channel_group_sorted(channel_group, blender_object)
if len(channel_group_sorted) == 0:
# Only errors on channels, ignoring
continue
- channel = __gather_animation_channel(
- channel_group_sorted,
- blender_object,
+ channel = gather_animation_channel(
+ obj_uuid,
+ channel_group_sorted,
+ export_settings,
+ None,
+ None,
+ bake_range_start,
+ bake_range_end,
+ force_range,
+ blender_action.name,
+ None,
+ True
+ )
+ if channel is not None:
+ channels.append(channel)
+
+ # Store already done channel path
+ target = [c for c in channel_group_sorted if c is not None][0].data_path.split('.')[-1]
+ path = {
+ "delta_location": "location",
+ "delta_rotation_euler": "rotation_quaternion",
+ "location": "location",
+ "rotation_axis_angle": "rotation_quaternion",
+ "rotation_euler": "rotation_quaternion",
+ "rotation_quaternion": "rotation_quaternion",
+ "scale": "scale",
+ "value": "weights"
+ }.get(target)
+ if path is not None:
+ done_paths.append(path)
+ done_paths = list(set(done_paths))
+
+ if export_settings['gltf_selected'] is True and export_settings['vtree'].tree_troncated is True:
+ start_frame = min([v[0] for v in [a.frame_range for a in bpy.data.actions]])
+ end_frame = max([v[1] for v in [a.frame_range for a in bpy.data.actions]])
+ to_be_done = ['location', 'rotation_quaternion', 'scale']
+ to_be_done = [c for c in to_be_done if c not in done_paths]
+
+ # In case of weight action, do nothing.
+ # If there is only weight --> TRS is already managed at first
+ if not (len(done_paths) == 1 and 'weights' in done_paths):
+ for p in to_be_done:
+ channel = gather_animation_channel(
+ obj_uuid,
+ (),
export_settings,
None,
- None,
- bake_range_start,
- bake_range_end,
+ p,
+ start_frame,
+ end_frame,
force_range,
blender_action.name,
None,
- False)
- if channel is not None:
- channels.append(channel)
+ False #If Object is not animated, don't keep animation for this channel
+ )
+
+ if channel is not None:
+ channels.append(channel)
+
# resetting driver caches
@@ -198,8 +244,9 @@ def __get_channel_group_sorted(channels: typing.Tuple[bpy.types.FCurve], blender
# if not shapekeys, stay in same order, because order doesn't matter
return channels
-def __gather_animation_channel(channels: typing.Tuple[bpy.types.FCurve],
- blender_object: bpy.types.Object,
+# This function can be called directly from gather_animation in case of bake animation (non animated selected object)
+def gather_animation_channel(obj_uuid: str,
+ channels: typing.Tuple[bpy.types.FCurve],
export_settings,
bake_bone: typing.Union[str, None],
bake_channel: typing.Union[str, None],
@@ -207,15 +254,18 @@ def __gather_animation_channel(channels: typing.Tuple[bpy.types.FCurve],
bake_range_end,
force_range: bool,
action_name: str,
- driver_obj,
+ driver_obj_uuid,
node_channel_is_animated: bool
) -> typing.Union[gltf2_io.AnimationChannel, None]:
+
+ blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
+
if not __filter_animation_channel(channels, blender_object, export_settings):
return None
- __target= __gather_target(channels, blender_object, export_settings, bake_bone, bake_channel, driver_obj)
+ __target= __gather_target(obj_uuid, channels, export_settings, bake_bone, bake_channel, driver_obj_uuid)
if __target.path is not None:
- sampler = __gather_sampler(channels, blender_object, export_settings, bake_bone, bake_channel, bake_range_start, bake_range_end, force_range, action_name, driver_obj, node_channel_is_animated)
+ sampler = __gather_sampler(channels, obj_uuid, export_settings, bake_bone, bake_channel, bake_range_start, bake_range_end, force_range, action_name, driver_obj_uuid, node_channel_is_animated)
if sampler is None:
# After check, no need to animate this node for this channel
@@ -268,7 +318,7 @@ def __gather_extras(channels: typing.Tuple[bpy.types.FCurve],
def __gather_sampler(channels: typing.Tuple[bpy.types.FCurve],
- blender_object: bpy.types.Object,
+ obj_uuid: str,
export_settings,
bake_bone: typing.Union[str, None],
bake_channel: typing.Union[str, None],
@@ -276,33 +326,38 @@ def __gather_sampler(channels: typing.Tuple[bpy.types.FCurve],
bake_range_end,
force_range: bool,
action_name,
- driver_obj,
+ driver_obj_uuid,
node_channel_is_animated: bool
) -> gltf2_io.AnimationSampler:
+
+ need_rotation_correction = (export_settings[gltf2_blender_export_keys.CAMERAS] and export_settings['vtree'].nodes[obj_uuid].blender_type == VExportNode.CAMERA) or \
+ (export_settings[gltf2_blender_export_keys.LIGHTS] and export_settings['vtree'].nodes[obj_uuid].blender_type == VExportNode.LIGHT)
+
return gltf2_blender_gather_animation_samplers.gather_animation_sampler(
channels,
- blender_object,
+ obj_uuid,
bake_bone,
bake_channel,
bake_range_start,
bake_range_end,
force_range,
action_name,
- driver_obj,
+ driver_obj_uuid,
node_channel_is_animated,
+ need_rotation_correction,
export_settings
)
-def __gather_target(channels: typing.Tuple[bpy.types.FCurve],
- blender_object: bpy.types.Object,
+def __gather_target(obj_uuid: str,
+ channels: typing.Tuple[bpy.types.FCurve],
export_settings,
bake_bone: typing.Union[str, None],
bake_channel: typing.Union[str, None],
- driver_obj
+ driver_obj_uuid
) -> gltf2_io.AnimationChannelTarget:
return gltf2_blender_gather_animation_channel_target.gather_animation_channel_target(
- channels, blender_object, bake_bone, bake_channel, driver_obj, export_settings)
+ obj_uuid, channels, bake_bone, bake_channel, driver_obj_uuid, export_settings)
def __get_channel_groups(blender_action: bpy.types.Action, blender_object: bpy.types.Object, export_settings):
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py
index d24db395..cd836682 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py
@@ -5,12 +5,13 @@ import bpy
import mathutils
import typing
-from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached, bonecache
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached, bonecache, objectcache
from io_scene_gltf2.blender.com import gltf2_blender_math
from io_scene_gltf2.blender.exp import gltf2_blender_get
from io_scene_gltf2.blender.exp.gltf2_blender_gather_drivers import get_sk_drivers, get_sk_driver_values
from . import gltf2_blender_export_keys
from io_scene_gltf2.io.com import gltf2_io_debug
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_tree import VExportNode
import numpy as np
@@ -95,6 +96,10 @@ class Keyframe:
def value(self, value: typing.List[float]):
self.__value = self.__set_indexed(value)
+ @value.setter
+ def value_total(self, value: typing.List[float]):
+ self.__value = value
+
@property
def in_tangent(self) -> typing.Union[mathutils.Vector, mathutils.Euler, mathutils.Quaternion, typing.List[float]]:
if self.__in_tangent is None:
@@ -120,9 +125,75 @@ class Keyframe:
self.__out_tangent = self.__set_indexed(value)
+@objectcache
+def get_object_matrix(blender_obj_uuid: str,
+ action_name: str,
+ bake_range_start: int,
+ bake_range_end: int,
+ current_frame: int,
+ step: int,
+ export_settings
+ ):
+
+ data = {}
+
+ # TODO : bake_range_start & bake_range_end are no more needed here
+ # Because we bake, we don't know exactly the frame range,
+ # So using min / max of all actions
+
+ start_frame = min([v[0] for v in [a.frame_range for a in bpy.data.actions]])
+ end_frame = max([v[1] for v in [a.frame_range for a in bpy.data.actions]])
+
+ frame = start_frame
+ while frame <= end_frame:
+ bpy.context.scene.frame_set(int(frame))
+
+ for obj_uuid in [uid for (uid, n) in export_settings['vtree'].nodes.items() if n.blender_type not in [VExportNode.BONE]]:
+ blender_obj = export_settings['vtree'].nodes[obj_uuid].blender_object
+
+ # if this object is not animated, do not skip :
+ # We need this object too in case of bake
+
+ # calculate local matrix
+ if export_settings['vtree'].nodes[obj_uuid].parent_uuid is None:
+ parent_mat = mathutils.Matrix.Identity(4).freeze()
+ else:
+ if export_settings['vtree'].nodes[export_settings['vtree'].nodes[obj_uuid].parent_uuid].blender_type not in [VExportNode.BONE]:
+ parent_mat = export_settings['vtree'].nodes[export_settings['vtree'].nodes[obj_uuid].parent_uuid].blender_object.matrix_world
+ else:
+ # Object animated is parented to a bone
+ blender_bone = export_settings['vtree'].nodes[export_settings['vtree'].nodes[obj_uuid].parent_bone_uuid].blender_bone
+ armature_object = export_settings['vtree'].nodes[export_settings['vtree'].nodes[export_settings['vtree'].nodes[obj_uuid].parent_bone_uuid].armature].blender_object
+ axis_basis_change = mathutils.Matrix(
+ ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
+
+ parent_mat = armature_object.matrix_world @ blender_bone.matrix @ axis_basis_change
+
+ #For object inside collection (at root), matrix world is already expressed regarding collection parent
+ if export_settings['vtree'].nodes[obj_uuid].parent_uuid is not None and export_settings['vtree'].nodes[export_settings['vtree'].nodes[obj_uuid].parent_uuid].blender_type == VExportNode.COLLECTION:
+ parent_mat = mathutils.Matrix.Identity(4).freeze()
+
+ mat = parent_mat.inverted_safe() @ blender_obj.matrix_world
+
+ if obj_uuid not in data.keys():
+ data[obj_uuid] = {}
+
+ if blender_obj.animation_data and blender_obj.animation_data.action:
+ if blender_obj.animation_data.action.name not in data[obj_uuid].keys():
+ data[obj_uuid][blender_obj.animation_data.action.name] = {}
+ data[obj_uuid][blender_obj.animation_data.action.name][frame] = mat
+ else:
+ # case of baking selected object.
+ # There is no animation, so use uuid of object as key
+ if obj_uuid not in data[obj_uuid].keys():
+ data[obj_uuid][obj_uuid] = {}
+ data[obj_uuid][obj_uuid][frame] = mat
+
+ frame += step
+ return data
@bonecache
-def get_bone_matrix(blender_object_if_armature: typing.Optional[bpy.types.Object],
+def get_bone_matrix(blender_obj_uuid_if_armature: typing.Optional[str],
channels: typing.Tuple[bpy.types.FCurve],
bake_bone: typing.Union[str, None],
bake_channel: typing.Union[str, None],
@@ -130,9 +201,11 @@ def get_bone_matrix(blender_object_if_armature: typing.Optional[bpy.types.Object
bake_range_end,
action_name: str,
current_frame: int,
- step: int
+ step: int,
+ export_settings
):
+ blender_object_if_armature = export_settings['vtree'].nodes[blender_obj_uuid_if_armature].blender_object if blender_obj_uuid_if_armature is not None else None
data = {}
# Always using bake_range, because some bones may need to be baked,
@@ -145,35 +218,40 @@ def get_bone_matrix(blender_object_if_armature: typing.Optional[bpy.types.Object
frame = start_frame
while frame <= end_frame:
data[frame] = {}
- # we need to bake in the constraints
bpy.context.scene.frame_set(int(frame))
- for pbone in blender_object_if_armature.pose.bones:
- if bake_bone is None:
- matrix = pbone.matrix_basis.copy()
+ bones = export_settings['vtree'].get_all_bones(blender_obj_uuid_if_armature)
+
+ for bone_uuid in bones:
+ blender_bone = export_settings['vtree'].nodes[bone_uuid].blender_bone
+
+ if export_settings['vtree'].nodes[bone_uuid].parent_uuid is not None and export_settings['vtree'].nodes[export_settings['vtree'].nodes[bone_uuid].parent_uuid].blender_type == VExportNode.BONE:
+ blender_bone_parent = export_settings['vtree'].nodes[export_settings['vtree'].nodes[bone_uuid].parent_uuid].blender_bone
+ rest_mat = blender_bone_parent.bone.matrix_local.inverted_safe() @ blender_bone.bone.matrix_local
+ matrix = rest_mat.inverted_safe() @ blender_bone_parent.matrix.inverted_safe() @ blender_bone.matrix
else:
- if (pbone.bone.use_inherit_rotation == False or pbone.bone.inherit_scale != "FULL") and pbone.parent != None:
- rest_mat = (pbone.parent.bone.matrix_local.inverted_safe() @ pbone.bone.matrix_local)
- matrix = (rest_mat.inverted_safe() @ pbone.parent.matrix.inverted_safe() @ pbone.matrix)
+ if blender_bone.parent is None:
+ matrix = blender_bone.bone.matrix_local.inverted_safe() @ blender_bone.matrix
else:
- matrix = pbone.matrix
- matrix = blender_object_if_armature.convert_space(pose_bone=pbone, matrix=matrix, from_space='POSE', to_space='LOCAL')
-
+ # Bone has a parent, but in export, after filter, is at root of armature
+ matrix = blender_bone.matrix.copy()
- data[frame][pbone.name] = matrix
+ data[frame][blender_bone.name] = matrix
# If some drivers must be evaluated, do it here, to avoid to have to change frame by frame later
- drivers_to_manage = get_sk_drivers(blender_object_if_armature)
- for dr_obj, dr_fcurves in drivers_to_manage:
- vals = get_sk_driver_values(dr_obj, frame, dr_fcurves)
+ drivers_to_manage = get_sk_drivers(blender_obj_uuid_if_armature, export_settings)
+ for dr_obj_uuid, dr_fcurves in drivers_to_manage:
+ vals = get_sk_driver_values(dr_obj_uuid, frame, dr_fcurves, export_settings)
frame += step
return data
# cache for performance reasons
+# This function is called 2 times, for input (timing) and output (key values)
@cached
-def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Object],
+def gather_keyframes(blender_obj_uuid: str,
+ is_armature: bool,
channels: typing.Tuple[bpy.types.FCurve],
non_keyed_values: typing.Tuple[typing.Optional[float]],
bake_bone: typing.Union[str, None],
@@ -182,32 +260,40 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec
bake_range_end,
force_range: bool,
action_name: str,
- driver_obj,
+ driver_obj_uuid,
node_channel_is_animated: bool,
export_settings
- ) -> typing.List[Keyframe]:
+ ) -> typing.Tuple[typing.List[Keyframe], bool]:
"""Convert the blender action groups' fcurves to keyframes for use in glTF."""
+
+ blender_object_if_armature = export_settings['vtree'].nodes[blender_obj_uuid].blender_object if is_armature is True is not None else None
+ blender_obj_uuid_if_armature = blender_obj_uuid if is_armature is True else None
+
if force_range is True:
start_frame = bake_range_start
end_frame = bake_range_end
else:
- if bake_bone is None and driver_obj is None:
+ if bake_bone is None and driver_obj_uuid is None:
# Find the start and end of the whole action group
# Note: channels has some None items only for SK if some SK are not animated
ranges = [channel.range() for channel in channels if channel is not None]
- start_frame = min([channel.range()[0] for channel in channels if channel is not None])
- end_frame = max([channel.range()[1] for channel in channels if channel is not None])
+ if len(channels) != 0:
+ start_frame = min([channel.range()[0] for channel in channels if channel is not None])
+ end_frame = max([channel.range()[1] for channel in channels if channel is not None])
+ else:
+ start_frame = bake_range_start
+ end_frame = bake_range_end
else:
start_frame = bake_range_start
end_frame = bake_range_end
keyframes = []
- if needs_baking(blender_object_if_armature, channels, export_settings):
+ baking_is_needed = needs_baking(blender_object_if_armature, channels, export_settings)
+ if baking_is_needed:
# Bake the animation, by evaluating the animation for all frames
- # TODO: maybe baking can also be done with FCurve.convert_to_samples
- if blender_object_if_armature is not None and driver_obj is None:
+ if blender_object_if_armature is not None and driver_obj_uuid is None:
if bake_bone is None:
pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature,
channels[0].data_path)
@@ -224,7 +310,7 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec
if isinstance(pose_bone_if_armature, bpy.types.PoseBone):
mat = get_bone_matrix(
- blender_object_if_armature,
+ blender_obj_uuid_if_armature,
channels,
bake_bone,
bake_channel,
@@ -232,7 +318,8 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec
bake_range_end,
action_name,
frame,
- step
+ step,
+ export_settings
)
trans, rot, scale = mat.decompose()
@@ -248,12 +335,36 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec
"scale": scale
}[target_property]
else:
- if driver_obj is None:
- # Note: channels has some None items only for SK if some SK are not animated
- key.value = [c.evaluate(frame) for c in channels if c is not None]
- complete_key(key, non_keyed_values)
+ if driver_obj_uuid is None:
+ # If channel is TRS, we bake from world matrix, else this is SK
+ if len(channels) != 0:
+ target = [c for c in channels if c is not None][0].data_path.split('.')[-1]
+ else:
+ target = bake_channel
+ if target == "value": #SK
+ # Note: channels has some None items only for SK if some SK are not animated
+ key.value = [c.evaluate(frame) for c in channels if c is not None]
+ complete_key(key, non_keyed_values)
+ else:
+
+ mat = get_object_matrix(blender_obj_uuid,
+ action_name,
+ bake_range_start,
+ bake_range_end,
+ frame,
+ step,
+ export_settings)
+
+ trans, rot, sca = mat.decompose()
+ key.value_total = {
+ "location": trans,
+ "rotation_axis_angle": [rot.to_axis_angle()[1], rot.to_axis_angle()[0][0], rot.to_axis_angle()[0][1], rot.to_axis_angle()[0][2]],
+ "rotation_euler": rot.to_euler(),
+ "rotation_quaternion": rot,
+ "scale": sca
+ }[target]
else:
- key.value = get_sk_driver_values(driver_obj, frame, channels)
+ key.value = get_sk_driver_values(driver_obj_uuid, frame, channels, export_settings)
complete_key(key, non_keyed_values)
keyframes.append(key)
frame += step
@@ -307,7 +418,7 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec
keyframes.append(key)
if not export_settings[gltf2_blender_export_keys.OPTIMIZE_ANIMS]:
- return keyframes
+ return (keyframes, baking_is_needed)
# For armature only
# Check if all values are the same
@@ -319,17 +430,20 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec
if node_channel_is_animated is True: # fcurve on this bone for this property
# Keep animation, but keep only 2 keyframes if data are not changing
- return [keyframes[0], keyframes[-1]] if cst is True and len(keyframes) >= 2 else keyframes
+ return ([keyframes[0], keyframes[-1]], baking_is_needed) if cst is True and len(keyframes) >= 2 else (keyframes, baking_is_needed)
else: # bone is not animated (no fcurve)
# Not keeping if not changing property
- return None if cst is True else keyframes
+ return (None, baking_is_needed) if cst is True else (keyframes, baking_is_needed)
else:
# For objects, if all values are the same, we keep only first and last
cst = fcurve_is_constant(keyframes)
- return [keyframes[0], keyframes[-1]] if cst is True and len(keyframes) >= 2 else keyframes
-
+ if node_channel_is_animated is True:
+ return ([keyframes[0], keyframes[-1]], baking_is_needed) if cst is True and len(keyframes) >= 2 else (keyframes, baking_is_needed)
+ else:
+ # baked object (selected but not animated)
+ return (None, baking_is_needed) if cst is True else (keyframes, baking_is_needed)
- return keyframes
+ return (keyframes, baking_is_needed)
def fcurve_is_constant(keyframes):
@@ -374,6 +488,10 @@ def needs_baking(blender_object_if_armature: typing.Optional[bpy.types.Object],
if export_settings[gltf2_blender_export_keys.FORCE_SAMPLING]:
return True
+ # If tree is troncated, sampling is forced
+ if export_settings['vtree'].tree_troncated is True:
+ return True
+
# Sampling due to unsupported interpolation
interpolation = [c for c in channels if c is not None][0].keyframe_points[0].interpolation
if interpolation not in ["BEZIER", "LINEAR", "CONSTANT"]:
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py
index b3cc9d30..143fccea 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py
@@ -3,6 +3,7 @@
import typing
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_tree import VExportNode
import bpy
import mathutils
@@ -21,20 +22,23 @@ from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extension
@cached
def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve],
- blender_object: bpy.types.Object,
+ obj_uuid: str,
bake_bone: typing.Union[str, None],
bake_channel: typing.Union[str, None],
bake_range_start,
bake_range_end,
force_range: bool,
action_name: str,
- driver_obj,
+ driver_obj_uuid,
node_channel_is_animated: bool,
+ need_rotation_correction,
export_settings
) -> gltf2_io.AnimationSampler:
- blender_object_if_armature = blender_object if blender_object.type == "ARMATURE" else None
- if blender_object_if_armature is not None and driver_obj is None:
+ blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
+ is_armature = True if blender_object.type == "ARMATURE" else False
+ blender_object_if_armature = blender_object if is_armature is True else None
+ if is_armature is True and driver_obj_uuid is None:
if bake_bone is None:
pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature,
channels[0].data_path)
@@ -45,15 +49,15 @@ def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve],
non_keyed_values = __gather_non_keyed_values(channels, blender_object,
blender_object_if_armature, pose_bone_if_armature,
bake_channel,
- driver_obj,
+ driver_obj_uuid,
export_settings)
if blender_object.parent is not None:
matrix_parent_inverse = blender_object.matrix_parent_inverse.copy().freeze()
else:
matrix_parent_inverse = mathutils.Matrix.Identity(4).freeze()
- input = __gather_input(channels, blender_object_if_armature, non_keyed_values,
- bake_bone, bake_channel, bake_range_start, bake_range_end, force_range, action_name, driver_obj, node_channel_is_animated, export_settings)
+ input = __gather_input(channels, obj_uuid, is_armature, non_keyed_values,
+ bake_bone, bake_channel, bake_range_start, bake_range_end, force_range, action_name, driver_obj_uuid, node_channel_is_animated, export_settings)
if input is None:
# After check, no need to animate this node for this channel
@@ -66,7 +70,8 @@ def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve],
interpolation=__gather_interpolation(channels, blender_object_if_armature, export_settings, bake_bone, bake_channel),
output=__gather_output(channels,
matrix_parent_inverse,
- blender_object_if_armature,
+ obj_uuid,
+ is_armature,
non_keyed_values,
bake_bone,
bake_channel,
@@ -74,8 +79,9 @@ def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve],
bake_range_end,
force_range,
action_name,
- driver_obj,
+ driver_obj_uuid,
node_channel_is_animated,
+ need_rotation_correction,
export_settings)
)
@@ -97,12 +103,13 @@ def __gather_non_keyed_values(channels: typing.Tuple[bpy.types.FCurve],
blender_object_if_armature: typing.Optional[bpy.types.Object],
pose_bone_if_armature: typing.Optional[bpy.types.PoseBone],
bake_channel: typing.Union[str, None],
- driver_obj,
+ driver_obj_uuid,
export_settings
) -> typing.Tuple[typing.Optional[float]]:
non_keyed_values = []
+ driver_obj = export_settings['vtree'].nodes[driver_obj_uuid].blender_object if driver_obj_uuid is not None else None
obj = blender_object if driver_obj is None else driver_obj
# Note: channels has some None items only for SK if some SK are not animated
@@ -217,10 +224,10 @@ def __gather_extras(channels: typing.Tuple[bpy.types.FCurve],
) -> typing.Any:
return None
-
@cached
def __gather_input(channels: typing.Tuple[bpy.types.FCurve],
- blender_object_if_armature: typing.Optional[bpy.types.Object],
+ blender_obj_uuid: str,
+ is_armature: bool,
non_keyed_values: typing.Tuple[typing.Optional[float]],
bake_bone: typing.Union[str, None],
bake_channel: typing.Union[str, None],
@@ -228,12 +235,13 @@ def __gather_input(channels: typing.Tuple[bpy.types.FCurve],
bake_range_end,
force_range: bool,
action_name,
- driver_obj,
+ driver_obj_uuid,
node_channel_is_animated: bool,
export_settings
) -> gltf2_io.Accessor:
"""Gather the key time codes."""
- keyframes = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(blender_object_if_armature,
+ keyframes, is_baked = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(blender_obj_uuid,
+ is_armature,
channels,
non_keyed_values,
bake_bone,
@@ -242,7 +250,7 @@ def __gather_input(channels: typing.Tuple[bpy.types.FCurve],
bake_range_end,
force_range,
action_name,
- driver_obj,
+ driver_obj_uuid,
node_channel_is_animated,
export_settings)
if keyframes is None:
@@ -277,14 +285,15 @@ def __gather_interpolation(channels: typing.Tuple[bpy.types.FCurve],
# TODO: check if the bone was animated with CONSTANT
return 'LINEAR'
else:
- max_keyframes = max([len(ch.keyframe_points) for ch in channels if ch is not None])
- # If only single keyframe revert to STEP
- if max_keyframes < 2:
- return 'STEP'
+ if len(channels) != 0: # channels can be empty when baking object (non animated selected object)
+ max_keyframes = max([len(ch.keyframe_points) for ch in channels if ch is not None])
+ # If only single keyframe revert to STEP
+ if max_keyframes < 2:
+ return 'STEP'
- # If all keyframes are CONSTANT, we can use STEP.
- if all(all(k.interpolation == 'CONSTANT' for k in c.keyframe_points) for c in channels if c is not None):
- return 'STEP'
+ # If all keyframes are CONSTANT, we can use STEP.
+ if all(all(k.interpolation == 'CONSTANT' for k in c.keyframe_points) for c in channels if c is not None):
+ return 'STEP'
# Otherwise, sampled keyframes use LINEAR interpolation.
return 'LINEAR'
@@ -304,7 +313,8 @@ def __gather_interpolation(channels: typing.Tuple[bpy.types.FCurve],
@cached
def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
parent_inverse,
- blender_object_if_armature: typing.Optional[bpy.types.Object],
+ blender_obj_uuid: str,
+ is_armature: bool,
non_keyed_values: typing.Tuple[typing.Optional[float]],
bake_bone: typing.Union[str, None],
bake_channel: typing.Union[str, None],
@@ -314,10 +324,12 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
action_name,
driver_obj,
node_channel_is_animated: bool,
+ need_rotation_correction: bool,
export_settings
) -> gltf2_io.Accessor:
"""Gather the data of the keyframes."""
- keyframes = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(blender_object_if_armature,
+ keyframes, is_baked = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(blender_obj_uuid,
+ is_armature,
channels,
non_keyed_values,
bake_bone,
@@ -329,10 +341,19 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
driver_obj,
node_channel_is_animated,
export_settings)
+
+ if is_baked is True:
+ parent_inverse = mathutils.Matrix.Identity(4).freeze()
+
+ blender_object_if_armature = export_settings['vtree'].nodes[blender_obj_uuid].blender_object if is_armature is True else None
+
if bake_bone is not None:
target_datapath = "pose.bones['" + bake_bone + "']." + bake_channel
else:
- target_datapath = [c for c in channels if c is not None][0].data_path
+ if len(channels) != 0: # channels can be empty when baking object (non animated selected object)
+ target_datapath = [c for c in channels if c is not None][0].data_path
+ else:
+ target_datapath = bake_channel
is_yup = export_settings[gltf2_blender_export_keys.YUP]
@@ -355,6 +376,7 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
bone = blender_object_if_armature.pose.bones[bake_bone]
if isinstance(bone, bpy.types.PoseBone):
if bone.parent is None:
+ # bone at root of armature
axis_basis_change = mathutils.Matrix.Identity(4)
if export_settings[gltf2_blender_export_keys.YUP]:
axis_basis_change = mathutils.Matrix(
@@ -364,10 +386,25 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
(0.0, 0.0, 0.0, 1.0)))
correction_matrix_local = axis_basis_change @ bone.bone.matrix_local
else:
- correction_matrix_local = (
- bone.parent.bone.matrix_local.inverted_safe() @
- bone.bone.matrix_local
- )
+ # Bone is not at root of armature
+ # There are 2 cases :
+ parent_uuid = export_settings['vtree'].nodes[export_settings['vtree'].nodes[blender_obj_uuid].bones[bone.name]].parent_uuid
+ if parent_uuid is not None and export_settings['vtree'].nodes[parent_uuid].blender_type == VExportNode.BONE:
+ # export bone is not at root of armature neither
+ correction_matrix_local = (
+ bone.parent.bone.matrix_local.inverted_safe() @
+ bone.bone.matrix_local
+ )
+ else:
+ # exported bone (after filter) is at root of armature
+ axis_basis_change = mathutils.Matrix.Identity(4)
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ axis_basis_change = mathutils.Matrix(
+ ((1.0, 0.0, 0.0, 0.0),
+ (0.0, 0.0, 1.0, 0.0),
+ (0.0, -1.0, 0.0, 0.0),
+ (0.0, 0.0, 0.0, 1.0)))
+ correction_matrix_local = axis_basis_change
transform = correction_matrix_local
else:
@@ -378,14 +415,14 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
values = []
for keyframe in keyframes:
# Transform the data and build gltf control points
- value = gltf2_blender_math.transform(keyframe.value, target_datapath, transform)
+ value = gltf2_blender_math.transform(keyframe.value, target_datapath, transform, need_rotation_correction)
if is_yup and not is_armature_animation:
value = gltf2_blender_math.swizzle_yup(value, target_datapath)
keyframe_value = gltf2_blender_math.mathutils_to_gltf(value)
if keyframe.in_tangent is not None:
# we can directly transform the tangent as it currently is represented by a control point
- in_tangent = gltf2_blender_math.transform(keyframe.in_tangent, target_datapath, transform)
+ in_tangent = gltf2_blender_math.transform(keyframe.in_tangent, target_datapath, transform, need_rotation_correction)
if is_yup and blender_object_if_armature is None:
in_tangent = gltf2_blender_math.swizzle_yup(in_tangent, target_datapath)
# the tangent in glTF is relative to the keyframe value
@@ -397,7 +434,7 @@ def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
if keyframe.out_tangent is not None:
# we can directly transform the tangent as it currently is represented by a control point
- out_tangent = gltf2_blender_math.transform(keyframe.out_tangent, target_datapath, transform)
+ out_tangent = gltf2_blender_math.transform(keyframe.out_tangent, target_datapath, transform, need_rotation_correction)
if is_yup and blender_object_if_armature is None:
out_tangent = gltf2_blender_math.swizzle_yup(out_tangent, target_datapath)
# the tangent in glTF is relative to the keyframe value
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py
index 39f09d52..828d1955 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animations.py
@@ -11,7 +11,36 @@ from ..com.gltf2_blender_extras import generate_extras
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
-def gather_animations(blender_object: bpy.types.Object,
+def __gather_channels_baked(obj_uuid, export_settings):
+ channels = []
+
+ # If no animation in file, no need to bake
+ if len(bpy.data.actions) == 0:
+ return None
+
+ start_frame = min([v[0] for v in [a.frame_range for a in bpy.data.actions]])
+ end_frame = max([v[1] for v in [a.frame_range for a in bpy.data.actions]])
+
+ for p in ["location", "rotation_quaternion", "scale"]:
+ channel = gltf2_blender_gather_animation_channels.gather_animation_channel(
+ obj_uuid,
+ (),
+ export_settings,
+ None,
+ p,
+ start_frame,
+ end_frame,
+ False,
+ obj_uuid, # Use obj uuid as action name for caching
+ None,
+ False #If Object is not animated, don't keep animation for this channel
+ )
+ if channel is not None:
+ channels.append(channel)
+
+ return channels if len(channels) > 0 else None
+
+def gather_animations( obj_uuid: int,
tracks: typing.Dict[str, typing.List[int]],
offset: int,
export_settings) -> typing.Tuple[typing.List[gltf2_io.Animation], typing.Dict[str, typing.List[int]]]:
@@ -24,11 +53,29 @@ def gather_animations(blender_object: bpy.types.Object,
"""
animations = []
+ blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
+
# Collect all 'actions' affecting this object. There is a direct mapping between blender actions and glTF animations
blender_actions = __get_blender_actions(blender_object, export_settings)
- # save the current active action of the object, if any
- # We will restore it after export
+ if len([a for a in blender_actions if a[2] == "OBJECT"]) == 0:
+ # No TRS animation are found for this object.
+ # But we need to bake, in case we export selection
+ if export_settings['gltf_selected'] is True and blender_object.type != "ARMATURE":
+ channels = __gather_channels_baked(obj_uuid, export_settings)
+ if channels is not None:
+ animation = gltf2_io.Animation(
+ channels=channels,
+ extensions=None, # as other animations
+ extras=None, # Because there is no animation to get extras from
+ name=blender_object.name, # Use object name as animation name
+ samplers=[]
+ )
+
+ __link_samplers(animation, export_settings)
+ if animation is not None:
+ animations.append(animation)
+
current_action = None
if blender_object.animation_data and blender_object.animation_data.action:
current_action = blender_object.animation_data.action
@@ -63,7 +110,7 @@ def gather_animations(blender_object: bpy.types.Object,
# No need to set active shapekeys animations, this is needed for bone baking
- animation = __gather_animation(blender_action, blender_object, export_settings)
+ animation = __gather_animation(obj_uuid, blender_action, export_settings)
if animation is not None:
animations.append(animation)
@@ -91,21 +138,24 @@ def gather_animations(blender_object: bpy.types.Object,
return animations, tracks
-def __gather_animation(blender_action: bpy.types.Action,
- blender_object: bpy.types.Object,
- export_settings
+def __gather_animation( obj_uuid: int,
+ blender_action: bpy.types.Action,
+ export_settings
) -> typing.Optional[gltf2_io.Animation]:
+
+ blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
+
if not __filter_animation(blender_action, blender_object, export_settings):
return None
name = __gather_name(blender_action, blender_object, export_settings)
try:
animation = gltf2_io.Animation(
- channels=__gather_channels(blender_action, blender_object, export_settings),
+ channels=__gather_channels(obj_uuid, blender_action, export_settings),
extensions=__gather_extensions(blender_action, blender_object, export_settings),
extras=__gather_extras(blender_action, blender_object, export_settings),
name=name,
- samplers=__gather_samplers(blender_action, blender_object, export_settings)
+ samplers=__gather_samplers(obj_uuid, blender_action, export_settings)
)
except RuntimeError as error:
print_console("WARNING", "Animation '{}' could not be exported. Cause: {}".format(name, error))
@@ -134,12 +184,12 @@ def __filter_animation(blender_action: bpy.types.Action,
return True
-def __gather_channels(blender_action: bpy.types.Action,
- blender_object: bpy.types.Object,
+def __gather_channels(obj_uuid: int,
+ blender_action: bpy.types.Action,
export_settings
) -> typing.List[gltf2_io.AnimationChannel]:
return gltf2_blender_gather_animation_channels.gather_animation_channels(
- blender_action, blender_object, export_settings)
+ obj_uuid, blender_action, export_settings)
def __gather_extensions(blender_action: bpy.types.Action,
@@ -166,8 +216,8 @@ def __gather_name(blender_action: bpy.types.Action,
return blender_action.name
-def __gather_samplers(blender_action: bpy.types.Action,
- blender_object: bpy.types.Object,
+def __gather_samplers(obj_uuid: str,
+ blender_action: bpy.types.Action,
export_settings
) -> typing.List[gltf2_io.AnimationSampler]:
# We need to gather the samplers after gathering all channels --> populate this list in __link_samplers
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py
index 7e49ac02..4f95431c 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_cache.py
@@ -6,83 +6,134 @@ import bpy
from io_scene_gltf2.blender.exp import gltf2_blender_get
-def cached(func):
+def cached_by_key(key):
+ """
+ Decorates functions whose result should be cached. Use it like:
+ @cached_by_key(key=...)
+ def func(..., export_settings):
+ ...
+ The decorated function, func, must always take an "export_settings" arg
+ (the cache is stored here).
+ The key argument to the decorator is a function that computes the key to
+ cache on. It is passed all the arguments to func.
"""
- Decorate the cache gather functions results.
+ def inner(func):
+ @functools.wraps(func)
+ def wrapper_cached(*args, **kwargs):
+ if kwargs.get("export_settings"):
+ export_settings = kwargs["export_settings"]
+ else:
+ export_settings = args[-1]
+
+ cache_key = key(*args, **kwargs)
+
+ # invalidate cache if export settings have changed
+ if not hasattr(func, "__export_settings") or export_settings != func.__export_settings:
+ func.__cache = {}
+ func.__export_settings = export_settings
+ # use or fill cache
+ if cache_key in func.__cache:
+ return func.__cache[cache_key]
+ else:
+ result = func(*args, **kwargs)
+ func.__cache[cache_key] = result
+ return result
+
+ return wrapper_cached
+
+ return inner
- The gather function is only executed if its result isn't in the cache yet
- :param func: the function to be decorated. It will have a static __cache member afterwards
- :return:
+
+def default_key(*args, **kwargs):
+ """
+ Default cache key for @cached functions.
+ Cache on all arguments (except export_settings).
"""
+ assert len(args) >= 2 and 0 <= len(kwargs) <= 1, "Wrong signature for cached function"
+ cache_key_args = args
+ # make a shallow copy of the keyword arguments so that 'export_settings' can be removed
+ cache_key_kwargs = dict(kwargs)
+ if kwargs.get("export_settings"):
+ del cache_key_kwargs["export_settings"]
+ else:
+ cache_key_args = args[:-1]
+
+ cache_key = ()
+ for i in cache_key_args:
+ cache_key += (i,)
+ for i in cache_key_kwargs.values():
+ cache_key += (i,)
+
+ return cache_key
+
+
+def cached(func):
+ return cached_by_key(key=default_key)(func)
+
+def objectcache(func):
+
+ def reset_cache_objectcache():
+ func.__objectcache = {}
+
+ func.reset_cache = reset_cache_objectcache
+
@functools.wraps(func)
- def wrapper_cached(*args, **kwargs):
- assert len(args) >= 2 and 0 <= len(kwargs) <= 1, "Wrong signature for cached function"
+ def wrapper_objectcache(*args, **kwargs):
cache_key_args = args
- # make a shallow copy of the keyword arguments so that 'export_settings' can be removed
- cache_key_kwargs = dict(kwargs)
- if kwargs.get("export_settings"):
- export_settings = kwargs["export_settings"]
- # 'export_settings' should not be cached
- del cache_key_kwargs["export_settings"]
- else:
- export_settings = args[-1]
- cache_key_args = args[:-1]
+ cache_key_args = args[:-1]
- __by_name = [bpy.types.Object, bpy.types.Scene, bpy.types.Material, bpy.types.Action, bpy.types.Mesh, bpy.types.PoseBone]
+ if not hasattr(func, "__objectcache"):
+ func.reset_cache()
- # we make a tuple from the function arguments so that they can be used as a key to the cache
- cache_key = ()
- for i in cache_key_args:
- if type(i) in __by_name:
- cache_key += (i.name,)
- else:
- cache_key += (i,)
- for i in cache_key_kwargs.values():
- if type(i) in __by_name:
- cache_key += (i.name,)
- else:
- cache_key += (i,)
-
- # invalidate cache if export settings have changed
- if not hasattr(func, "__export_settings") or export_settings != func.__export_settings:
- func.__cache = {}
- func.__export_settings = export_settings
- # use or fill cache
- if cache_key in func.__cache:
- return func.__cache[cache_key]
- else:
+ # object is not cached yet
+ if cache_key_args[0] not in func.__objectcache.keys():
result = func(*args)
- func.__cache[cache_key] = result
- return result
- return wrapper_cached
+ func.__objectcache = result
+ return result[cache_key_args[0]][cache_key_args[1]][cache_key_args[4]]
+ # object is in cache, but not this action
+ # We need to keep other actions
+ elif cache_key_args[1] not in func.__objectcache[cache_key_args[0]].keys():
+ result = func(*args)
+ func.__objectcache[cache_key_args[0]][cache_key_args[1]] = result[cache_key_args[0]][cache_key_args[1]]
+ return result[cache_key_args[0]][cache_key_args[1]][cache_key_args[4]]
+ # all is already cached
+ else:
+ return func.__objectcache[cache_key_args[0]][cache_key_args[1]][cache_key_args[4]]
+ return wrapper_objectcache
def bonecache(func):
def reset_cache_bonecache():
func.__current_action_name = None
- func.__current_armature_name = None
+ func.__current_armature_uuid = None
func.__bonecache = {}
func.reset_cache = reset_cache_bonecache
@functools.wraps(func)
def wrapper_bonecache(*args, **kwargs):
- if args[2] is None:
- pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(args[0],
- args[1][0].data_path)
+
+ armature = args[-1]['vtree'].nodes[args[0]].blender_object
+
+ cache_key_args = args
+ cache_key_args = args[:-1]
+
+ if cache_key_args[2] is None:
+ pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(armature,
+ cache_key_args[1][0].data_path)
else:
- pose_bone_if_armature = args[0].pose.bones[args[2]]
+ pose_bone_if_armature = armature.pose.bones[cache_key_args[2]]
if not hasattr(func, "__current_action_name"):
func.reset_cache()
- if args[6] != func.__current_action_name or args[0] != func.__current_armature_name:
+ if cache_key_args[6] != func.__current_action_name or cache_key_args[0] != func.__current_armature_uuid:
result = func(*args)
func.__bonecache = result
- func.__current_action_name = args[6]
- func.__current_armature_name = args[0]
- return result[args[7]][pose_bone_if_armature.name]
+ func.__current_action_name = cache_key_args[6]
+ func.__current_armature_uuid = cache_key_args[0]
+ return result[cache_key_args[7]][pose_bone_if_armature.name]
else:
- return func.__bonecache[args[7]][pose_bone_if_armature.name]
+ return func.__bonecache[cache_key_args[7]][pose_bone_if_armature.name]
return wrapper_bonecache
# TODO: replace "cached" with "unique" in all cases where the caching is functional and not only for performance reasons
@@ -92,23 +143,27 @@ unique = cached
def skdriverdiscovercache(func):
def reset_cache_skdriverdiscovercache():
- func.__current_armature_name = None
+ func.__current_armature_uuid = None
func.__skdriverdiscover = {}
func.reset_cache = reset_cache_skdriverdiscovercache
@functools.wraps(func)
def wrapper_skdriverdiscover(*args, **kwargs):
- if not hasattr(func, "__current_armature_name") or func.__current_armature_name is None:
+
+ cache_key_args = args
+ cache_key_args = args[:-1]
+
+ if not hasattr(func, "__current_armature_uuid") or func.__current_armature_uuid is None:
func.reset_cache()
- if args[0] != func.__current_armature_name:
+ if cache_key_args[0] != func.__current_armature_uuid:
result = func(*args)
- func.__skdriverdiscover[args[0]] = result
- func.__current_armature_name = args[0]
+ func.__skdriverdiscover[cache_key_args[0]] = result
+ func.__current_armature_uuid = cache_key_args[0]
return result
else:
- return func.__skdriverdiscover[args[0]]
+ return func.__skdriverdiscover[cache_key_args[0]]
return wrapper_skdriverdiscover
def skdrivervalues(func):
@@ -123,12 +178,17 @@ def skdrivervalues(func):
if not hasattr(func, "__skdrivervalues") or func.__skdrivervalues is None:
func.reset_cache()
- if args[0].name not in func.__skdrivervalues.keys():
- func.__skdrivervalues[args[0].name] = {}
- if args[1] not in func.__skdrivervalues[args[0].name]:
+ armature = args[-1]['vtree'].nodes[args[0]].blender_object
+
+ cache_key_args = args
+ cache_key_args = args[:-1]
+
+ if armature.name not in func.__skdrivervalues.keys():
+ func.__skdrivervalues[armature.name] = {}
+ if cache_key_args[1] not in func.__skdrivervalues[armature.name]:
vals = func(*args)
- func.__skdrivervalues[args[0].name][args[1]] = vals
+ func.__skdrivervalues[armature.name][cache_key_args[1]] = vals
return vals
else:
- return func.__skdrivervalues[args[0].name][args[1]]
+ return func.__skdrivervalues[armature.name][cache_key_args[1]]
return wrapper_skdrivervalues
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py
index 1f82c2b3..4e77f60e 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_drivers.py
@@ -5,13 +5,20 @@
from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import skdriverdiscovercache, skdrivervalues
from io_scene_gltf2.blender.com.gltf2_blender_data_path import get_target_object_path
-
@skdriverdiscovercache
-def get_sk_drivers(blender_armature):
+def get_sk_drivers(blender_armature_uuid, export_settings):
+
+ blender_armature = export_settings['vtree'].nodes[blender_armature_uuid].blender_object
drivers = []
- for child in blender_armature.children:
+ for child_uuid in export_settings['vtree'].nodes[blender_armature_uuid].children:
+
+ if export_settings['vtree'].nodes[child_uuid].blender_type == "BONE":
+ continue
+
+ child = export_settings['vtree'].nodes[child_uuid].blender_object
+
if not child.data:
continue
# child.data can be an armature - which has no shapekeys
@@ -63,13 +70,14 @@ def get_sk_drivers(blender_armature):
all_sorted_channels.append(existing_idx[i])
if len(all_sorted_channels) > 0:
- drivers.append((child, tuple(all_sorted_channels)))
+ drivers.append((child_uuid, tuple(all_sorted_channels)))
return tuple(drivers)
@skdrivervalues
-def get_sk_driver_values(blender_object, frame, fcurves):
+def get_sk_driver_values(blender_object_uuid, frame, fcurves, export_settings):
sk_values = []
+ blender_object = export_settings['vtree'].nodes[blender_object_uuid].blender_object
for f in [f for f in fcurves if f is not None]:
sk_values.append(blender_object.data.shape_keys.path_resolve(get_target_object_path(f.data_path)).value)
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py
index ffc1231b..f4fd6c51 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_joints.py
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright 2018-2021 The glTF-Blender-IO authors.
-import mathutils
+from mathutils import Matrix, Quaternion, Vector
from . import gltf2_blender_export_keys
from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
@@ -9,9 +9,40 @@ from io_scene_gltf2.io.com import gltf2_io
from io_scene_gltf2.blender.exp import gltf2_blender_gather_skins
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
from ..com.gltf2_blender_extras import generate_extras
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_tree
+
+
+
+# TODO these 3 functions move to shared file
+def __convert_swizzle_location(loc, export_settings):
+ """Convert a location from Blender coordinate system to glTF coordinate system."""
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ return Vector((loc[0], loc[2], -loc[1]))
+ else:
+ return Vector((loc[0], loc[1], loc[2]))
+
+
+def __convert_swizzle_rotation(rot, export_settings):
+ """
+ Convert a quaternion rotation from Blender coordinate system to glTF coordinate system.
+
+ 'w' is still at first position.
+ """
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ return Quaternion((rot[0], rot[1], rot[3], -rot[2]))
+ else:
+ return Quaternion((rot[0], rot[1], rot[2], rot[3]))
+
+
+def __convert_swizzle_scale(scale, export_settings):
+ """Convert a scale from Blender coordinate system to glTF coordinate system."""
+ if export_settings[gltf2_blender_export_keys.YUP]:
+ return Vector((scale[0], scale[2], scale[1]))
+ else:
+ return Vector((scale[0], scale[1], scale[2]))
@cached
-def gather_joint(blender_object, blender_bone, export_settings):
+def gather_joint_vnode(vnode, export_settings):
"""
Generate a glTF2 node from a blender bone, as joints in glTF2 are simply nodes.
@@ -19,28 +50,19 @@ def gather_joint(blender_object, blender_bone, export_settings):
:param export_settings: the settings for this export
:return: a glTF2 node (acting as a joint)
"""
- axis_basis_change = mathutils.Matrix.Identity(4)
- if export_settings[gltf2_blender_export_keys.YUP]:
- axis_basis_change = mathutils.Matrix(
- ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
+ vtree = export_settings['vtree']
+ blender_object = vtree.nodes[vnode].blender_object
+ blender_bone = vtree.nodes[vnode].blender_bone
- # extract bone transform
- if blender_bone.parent is None:
- correction_matrix_local = axis_basis_change @ blender_bone.bone.matrix_local
- else:
- correction_matrix_local = (
- blender_bone.parent.bone.matrix_local.inverted_safe() @
- blender_bone.bone.matrix_local
- )
-
- if (blender_bone.bone.use_inherit_rotation == False or blender_bone.bone.inherit_scale != "FULL") and blender_bone.parent != None:
- rest_mat = (blender_bone.parent.bone.matrix_local.inverted_safe() @ blender_bone.bone.matrix_local)
- matrix_basis = (rest_mat.inverted_safe() @ blender_bone.parent.matrix.inverted_safe() @ blender_bone.matrix)
- else:
- matrix_basis = blender_bone.matrix
- matrix_basis = blender_object.convert_space(pose_bone=blender_bone, matrix=matrix_basis, from_space='POSE', to_space='LOCAL')
- trans, rot, sca = (correction_matrix_local @ matrix_basis).decompose()
+ mat = vtree.nodes[vtree.nodes[vnode].parent_uuid].matrix_world.inverted_safe() @ vtree.nodes[vnode].matrix_world
+
+ trans, rot, sca = mat.decompose()
+
+ trans = __convert_swizzle_location(trans, export_settings)
+ rot = __convert_swizzle_rotation(rot, export_settings)
+ sca = __convert_swizzle_scale(sca, export_settings)
+
translation, rotation, scale = (None, None, None)
if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0:
translation = [trans[0], trans[1], trans[2]]
@@ -52,14 +74,8 @@ def gather_joint(blender_object, blender_bone, export_settings):
# traverse into children
children = []
- if export_settings["gltf_def_bones"] is False:
- for bone in blender_bone.children:
- children.append(gather_joint(blender_object, bone, export_settings))
- else:
- _, children_, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_bone.id_data)
- if blender_bone.name in children_.keys():
- for bone in children_[blender_bone.name]:
- children.append(gather_joint(blender_object, blender_bone.id_data.pose.bones[bone], export_settings))
+ for bone_uuid in [c for c in vtree.nodes[vnode].children if vtree.nodes[c].blender_type == gltf2_blender_gather_tree.VExportNode.BONE]:
+ children.append(gather_joint_vnode(bone_uuid, export_settings))
# finally add to the joints array containing all the joints in the hierarchy
node = gltf2_io.Node(
@@ -79,6 +95,8 @@ def gather_joint(blender_object, blender_bone, export_settings):
export_user_extensions('gather_joint_hook', export_settings, node, blender_bone)
+ vtree.nodes[vnode].node = node
+
return node
def __gather_extras(blender_bone, export_settings):
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
index 2d83b0dd..845024e5 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
@@ -3,7 +3,7 @@
import bpy
-from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached, cached_by_key
from io_scene_gltf2.io.com import gltf2_io
from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture_info, gltf2_blender_export_keys
@@ -16,8 +16,14 @@ from io_scene_gltf2.blender.exp import gltf2_blender_get
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
from io_scene_gltf2.io.com.gltf2_io_debug import print_console
-
@cached
+def get_material_cache_key(blender_material, export_settings):
+ # Use id of material
+ # Do not use bpy.types that can be unhashable
+ # Do not use material name, that can be not unique (when linked)
+ return ((id(blender_material),))
+
+@cached_by_key(key=get_material_cache_key)
def gather_material(blender_material, export_settings):
"""
Gather the material used by the blender primitive.
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py
index ac90e4cd..c8987127 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_mesh.py
@@ -4,7 +4,7 @@
import bpy
from typing import Optional, Dict, List, Any, Tuple
from .gltf2_blender_export_keys import MORPH
-from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached, cached_by_key
from io_scene_gltf2.io.com import gltf2_io
from io_scene_gltf2.blender.exp import gltf2_blender_gather_primitives
from ..com.gltf2_blender_extras import generate_extras
@@ -13,30 +13,64 @@ from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extension
@cached
+def get_mesh_cache_key(blender_mesh,
+ blender_object,
+ vertex_groups,
+ modifiers,
+ skip_filter,
+ materials,
+ original_mesh,
+ export_settings):
+ # Use id of original mesh
+ # Do not use bpy.types that can be unhashable
+ # Do not use mesh name, that can be not unique (when linked)
+
+ # If materials are not exported, no need to cache by material
+ if export_settings['gltf_materials'] is None:
+ mats = None
+ else:
+ mats = tuple(id(m) if m is not None else None for m in materials)
+
+ # TODO check what is really needed for modifiers
+
+ mesh_to_id_cache = blender_mesh if original_mesh is None else original_mesh
+ return (
+ (id(mesh_to_id_cache),),
+ (modifiers,),
+ (skip_filter,), #TODO to check if still needed
+ mats
+ )
+
+@cached_by_key(key=get_mesh_cache_key)
def gather_mesh(blender_mesh: bpy.types.Mesh,
- library: Optional[str],
- blender_object: Optional[bpy.types.Object],
+ uuid_for_skined_data,
vertex_groups: Optional[bpy.types.VertexGroups],
modifiers: Optional[bpy.types.ObjectModifiers],
skip_filter: bool,
- material_names: Tuple[str],
+ materials: Tuple[bpy.types.Material],
+ original_mesh: bpy.types.Mesh,
export_settings
) -> Optional[gltf2_io.Mesh]:
- if not skip_filter and not __filter_mesh(blender_mesh, library, vertex_groups, modifiers, export_settings):
+ if not skip_filter and not __filter_mesh(blender_mesh, vertex_groups, modifiers, export_settings):
return None
mesh = gltf2_io.Mesh(
- extensions=__gather_extensions(blender_mesh, library, vertex_groups, modifiers, export_settings),
- extras=__gather_extras(blender_mesh, library, vertex_groups, modifiers, export_settings),
- name=__gather_name(blender_mesh, library, vertex_groups, modifiers, export_settings),
- weights=__gather_weights(blender_mesh, library, vertex_groups, modifiers, export_settings),
- primitives=__gather_primitives(blender_mesh, library, blender_object, vertex_groups, modifiers, material_names, export_settings),
+ extensions=__gather_extensions(blender_mesh, vertex_groups, modifiers, export_settings),
+ extras=__gather_extras(blender_mesh, vertex_groups, modifiers, export_settings),
+ name=__gather_name(blender_mesh, vertex_groups, modifiers, export_settings),
+ weights=__gather_weights(blender_mesh, vertex_groups, modifiers, export_settings),
+ primitives=__gather_primitives(blender_mesh, uuid_for_skined_data, vertex_groups, modifiers, materials, export_settings),
)
if len(mesh.primitives) == 0:
print_console("WARNING", "Mesh '{}' has no primitives and will be omitted.".format(mesh.name))
return None
+ blender_object = None
+ if uuid_for_skined_data:
+ blender_object = export_settings['vtree'].nodes[uuid_for_skined_data].blender_object
+
+
export_user_extensions('gather_mesh_hook',
export_settings,
mesh,
@@ -45,13 +79,12 @@ def gather_mesh(blender_mesh: bpy.types.Mesh,
vertex_groups,
modifiers,
skip_filter,
- material_names)
+ materials)
return mesh
def __filter_mesh(blender_mesh: bpy.types.Mesh,
- library: Optional[str],
vertex_groups: Optional[bpy.types.VertexGroups],
modifiers: Optional[bpy.types.ObjectModifiers],
export_settings
@@ -63,7 +96,6 @@ def __filter_mesh(blender_mesh: bpy.types.Mesh,
def __gather_extensions(blender_mesh: bpy.types.Mesh,
- library: Optional[str],
vertex_groups: Optional[bpy.types.VertexGroups],
modifiers: Optional[bpy.types.ObjectModifiers],
export_settings
@@ -72,7 +104,6 @@ def __gather_extensions(blender_mesh: bpy.types.Mesh,
def __gather_extras(blender_mesh: bpy.types.Mesh,
- library: Optional[str],
vertex_groups: Optional[bpy.types.VertexGroups],
modifiers: Optional[bpy.types.ObjectModifiers],
export_settings
@@ -100,7 +131,6 @@ def __gather_extras(blender_mesh: bpy.types.Mesh,
def __gather_name(blender_mesh: bpy.types.Mesh,
- library: Optional[str],
vertex_groups: Optional[bpy.types.VertexGroups],
modifiers: Optional[bpy.types.ObjectModifiers],
export_settings
@@ -109,24 +139,21 @@ def __gather_name(blender_mesh: bpy.types.Mesh,
def __gather_primitives(blender_mesh: bpy.types.Mesh,
- library: Optional[str],
- blender_object: Optional[bpy.types.Object],
+ uuid_for_skined_data,
vertex_groups: Optional[bpy.types.VertexGroups],
modifiers: Optional[bpy.types.ObjectModifiers],
- material_names: Tuple[str],
+ materials: Tuple[bpy.types.Material],
export_settings
) -> List[gltf2_io.MeshPrimitive]:
return gltf2_blender_gather_primitives.gather_primitives(blender_mesh,
- library,
- blender_object,
+ uuid_for_skined_data,
vertex_groups,
modifiers,
- material_names,
+ materials,
export_settings)
def __gather_weights(blender_mesh: bpy.types.Mesh,
- library: Optional[str],
vertex_groups: Optional[bpy.types.VertexGroups],
modifiers: Optional[bpy.types.ObjectModifiers],
export_settings
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py
index a69f5d58..25784960 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_nodes.py
@@ -13,135 +13,47 @@ from io_scene_gltf2.blender.exp import gltf2_blender_gather_cameras
from io_scene_gltf2.blender.exp import gltf2_blender_gather_mesh
from io_scene_gltf2.blender.exp import gltf2_blender_gather_joints
from io_scene_gltf2.blender.exp import gltf2_blender_gather_lights
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_tree import VExportNode
from ..com.gltf2_blender_extras import generate_extras
from io_scene_gltf2.io.com import gltf2_io
from io_scene_gltf2.io.com import gltf2_io_extensions
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
from io_scene_gltf2.io.com.gltf2_io_debug import print_console
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_tree
-def gather_node(blender_object, library, blender_scene, dupli_object_parent, export_settings):
- # custom cache to avoid cache miss when called from animation
- # with blender_scene=None
-
- # invalidate cache if export settings have changed
- if not hasattr(gather_node, "__export_settings") or export_settings != gather_node.__export_settings:
- gather_node.__cache = {}
- gather_node.__export_settings = export_settings
-
- if blender_scene is None and (blender_object.name, library) in gather_node.__cache:
- return gather_node.__cache[(blender_object.name, library)]
-
- node = __gather_node(blender_object, library, blender_scene, dupli_object_parent, export_settings)
- gather_node.__cache[(blender_object.name, library)] = node
- return node
-
-@cached
-def __gather_node(blender_object, library, blender_scene, dupli_object_parent, export_settings):
- children, only_bone_children = __gather_children(blender_object, blender_scene, export_settings)
-
- camera = None
- mesh = None
- skin = None
- weights = None
-
- # If blender_scene is None, we are coming from animation export
- # Check to know if object is exported is already done, so we don't check
- # again if object is instanced in scene : this check was already done when exporting object itself
- if not __filter_node(blender_object, blender_scene, export_settings):
- if children:
- # This node should be filtered out, but has un-filtered children present.
- # So, export this node, excluding its camera, mesh, skin, and weights.
- # The transformations and animations on this node will have visible effects on children.
-
- # Armature always have children node(s) (that are bone(s))
- # We have to check if children are only bones or not for armatures
- if blender_object.type == "ARMATURE" and only_bone_children is True:
- return None
-
- pass
- else:
- # This node is filtered out, and has no un-filtered children or descendants.
- return None
- else:
- # This node is being fully exported.
- camera = __gather_camera(blender_object, export_settings)
- mesh = __gather_mesh(blender_object, library, export_settings)
- skin = __gather_skin(blender_object, export_settings)
- weights = __gather_weights(blender_object, export_settings)
+def gather_node(vnode, export_settings):
+ blender_object = vnode.blender_object
+ skin = __gather_skin(vnode, blender_object, export_settings)
node = gltf2_io.Node(
- camera=camera,
- children=children,
+ camera=__gather_camera(blender_object, export_settings),
+ children=__gather_children(vnode, blender_object, export_settings),
extensions=__gather_extensions(blender_object, export_settings),
extras=__gather_extras(blender_object, export_settings),
matrix=__gather_matrix(blender_object, export_settings),
- mesh=mesh,
+ mesh=__gather_mesh(vnode, blender_object, export_settings),
name=__gather_name(blender_object, export_settings),
rotation=None,
scale=None,
skin=skin,
translation=None,
- weights=weights
+ weights=__gather_weights(blender_object, export_settings)
)
# If node mesh is skined, transforms should be ignored at import, so no need to set them here
if node.skin is None:
- node.translation, node.rotation, node.scale = __gather_trans_rot_scale(blender_object, export_settings)
+ node.translation, node.rotation, node.scale = __gather_trans_rot_scale(vnode, export_settings)
- if export_settings[gltf2_blender_export_keys.YUP]:
- # Checking node.extensions is making sure that the type of lamp is managed, and will be exported
- if blender_object.type == 'LIGHT' and export_settings[gltf2_blender_export_keys.LIGHTS] and node.extensions:
- correction_node = __get_correction_node(blender_object, export_settings)
- correction_node.extensions = {"KHR_lights_punctual": node.extensions["KHR_lights_punctual"]}
- del node.extensions["KHR_lights_punctual"]
- node.children.append(correction_node)
- if blender_object.type == 'CAMERA' and export_settings[gltf2_blender_export_keys.CAMERAS]:
- correction_node = __get_correction_node(blender_object, export_settings)
- correction_node.camera = node.camera
- node.children.append(correction_node)
- node.camera = None
export_user_extensions('gather_node_hook', export_settings, node, blender_object)
- return node
-
+ vnode.node = node
-def __filter_node(blender_object, blender_scene, export_settings):
- if blender_object.users == 0:
- return False
- if blender_scene is not None:
- instanced = any([blender_object.name in layer.objects for layer in blender_scene.view_layers])
- if instanced is False:
- # Check if object is from a linked collection
- if any([blender_object.name in coll.objects for coll in bpy.data.collections if coll.library is not None]):
- pass
- else:
- # Not instanced, not linked -> We don't keep this object
- return False
- if export_settings[gltf2_blender_export_keys.SELECTED] and blender_object.select_get() is False:
- return False
+ if node.skin is not None:
+ vnode.skin = skin
- if export_settings[gltf2_blender_export_keys.VISIBLE] and blender_object.visible_get() is False:
- return False
-
- # render_get() doesn't exist, so unfortunately this won't take into account the Collection settings
- if export_settings[gltf2_blender_export_keys.RENDERABLE] and blender_object.hide_render is True:
- return False
-
- if export_settings[gltf2_blender_export_keys.ACTIVE_COLLECTION]:
- found = any(x == blender_object for x in bpy.context.collection.all_objects)
-
- if not found:
- return False
-
- if blender_object.type == 'LIGHT':
- return export_settings[gltf2_blender_export_keys.LIGHTS]
-
- if blender_object.type == 'CAMERA':
- return export_settings[gltf2_blender_export_keys.CAMERAS]
-
- return True
+ return node
def __gather_camera(blender_object, export_settings):
@@ -151,54 +63,35 @@ def __gather_camera(blender_object, export_settings):
return gltf2_blender_gather_cameras.gather_camera(blender_object.data, export_settings)
-def __gather_children(blender_object, blender_scene, export_settings):
+def __gather_children(vnode, blender_object, export_settings):
children = []
- only_bone_children = True # True by default, will be set to False if needed
- # standard children
- for child_object in blender_object.children:
- if child_object.parent_bone:
- # this is handled further down,
- # as the object should be a child of the specific bone,
- # not the Armature object
- continue
-
- node = gather_node(child_object,
- child_object.library.name if child_object.library else None,
- blender_scene, None, export_settings)
+
+ vtree = export_settings['vtree']
+
+ # Standard Children / Collection
+ for c in [vtree.nodes[c] for c in vnode.children if vtree.nodes[c].blender_type != gltf2_blender_gather_tree.VExportNode.BONE]:
+ node = gather_node(c, export_settings)
if node is not None:
children.append(node)
- only_bone_children = False
- # blender dupli objects
- if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection:
- for dupli_object in blender_object.instance_collection.objects:
- if dupli_object.parent is not None:
- continue
- if dupli_object.type == "ARMATURE":
- continue # There is probably a proxy (no more existing)
- node = gather_node(dupli_object,
- dupli_object.library.name if dupli_object.library else None,
- blender_scene, blender_object.name, export_settings)
- if node is not None:
- children.append(node)
- only_bone_children = False
-
- # blender bones
- if blender_object.type == "ARMATURE":
+
+
+ # Armature --> Retrieve Blender bones
+ if vnode.blender_type == gltf2_blender_gather_tree.VExportNode.ARMATURE:
root_joints = []
- if export_settings["gltf_def_bones"] is False:
- bones = blender_object.pose.bones
- else:
- bones, _, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_object)
- bones = [blender_object.pose.bones[b.name] for b in bones]
- for blender_bone in bones:
- if not blender_bone.parent:
- joint = gltf2_blender_gather_joints.gather_joint(blender_object, blender_bone, export_settings)
- children.append(joint)
- root_joints.append(joint)
- # handle objects directly parented to bones
- direct_bone_children = [child for child in blender_object.children if child.parent_bone]
- if len(direct_bone_children) != 0:
- only_bone_children = False
+
+ all_armature_children = vnode.children
+ root_bones_uuid = [c for c in all_armature_children if export_settings['vtree'].nodes[c].blender_type == VExportNode.BONE]
+ for bone_uuid in root_bones_uuid:
+ joint = gltf2_blender_gather_joints.gather_joint_vnode(bone_uuid, export_settings)
+ children.append(joint)
+ root_joints.append(joint)
+
+ # Object parented to bones
+ direct_bone_children = []
+ for n in [vtree.nodes[i] for i in vtree.get_all_bones(vnode.uuid)]:
+ direct_bone_children.extend([c for c in n.children if vtree.nodes[c].blender_type != gltf2_blender_gather_tree.VExportNode.BONE])
+
+
def find_parent_joint(joints, name):
for joint in joints:
if joint.name == name:
@@ -207,44 +100,40 @@ def __gather_children(blender_object, blender_scene, export_settings):
if parent_joint:
return parent_joint
return None
- for child in direct_bone_children:
+
+ for child in direct_bone_children: # List of object that are parented to bones
# find parent joint
- parent_joint = find_parent_joint(root_joints, child.parent_bone)
+ parent_joint = find_parent_joint(root_joints, vtree.nodes[child].blender_object.parent_bone)
if not parent_joint:
continue
- child_node = gather_node(child, None, blender_scene, None, export_settings)
+ child_node = gather_node(vtree.nodes[child], export_settings)
if child_node is None:
continue
blender_bone = blender_object.pose.bones[parent_joint.name]
- # fix rotation
- if export_settings[gltf2_blender_export_keys.YUP]:
- rot = child_node.rotation
- if rot is None:
- rot = [0, 0, 0, 1]
-
- rot_quat = Quaternion(rot)
- axis_basis_change = Matrix(
- ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, -1.0, 0.0), (0.0, 1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
- mat = child.matrix_parent_inverse @ child.matrix_basis
- mat = mat @ axis_basis_change
-
- _, rot_quat, _ = mat.decompose()
- child_node.rotation = [rot_quat[1], rot_quat[2], rot_quat[3], rot_quat[0]]
-
- # fix translation (in blender bone's tail is the origin for children)
- trans, _, _ = child.matrix_local.decompose()
- if trans is None:
- trans = [0, 0, 0]
- # bones go down their local y axis
- if blender_bone.matrix.to_scale()[1] >= 1e-6:
- bone_tail = [0, blender_bone.length / blender_bone.matrix.to_scale()[1], 0]
- else:
- bone_tail = [0,0,0] # If scale is 0, tail == head
- child_node.translation = [trans[idx] + bone_tail[idx] for idx in range(3)]
+
+ mat = vtree.nodes[vtree.nodes[child].parent_bone_uuid].matrix_world.inverted_safe() @ vtree.nodes[child].matrix_world
+ loc, rot_quat, scale = mat.decompose()
+
+ trans = __convert_swizzle_location(loc, export_settings)
+ rot = __convert_swizzle_rotation(rot_quat, export_settings)
+ sca = __convert_swizzle_scale(scale, export_settings)
+
+
+ translation, rotation, scale = (None, None, None)
+ if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0:
+ translation = [trans[0], trans[1], trans[2]]
+ if rot[0] != 1.0 or rot[1] != 0.0 or rot[2] != 0.0 or rot[3] != 0.0:
+ rotation = [rot[1], rot[2], rot[3], rot[0]]
+ if sca[0] != 1.0 or sca[1] != 1.0 or sca[2] != 1.0:
+ scale = [sca[0], sca[1], sca[2]]
+
+ child_node.translation = translation
+ child_node.rotation = rotation
+ child_node.scale = scale
parent_joint.children.append(child_node)
- return children, only_bone_children
+ return children
def __gather_extensions(blender_object, export_settings):
@@ -283,13 +172,17 @@ def __gather_matrix(blender_object, export_settings):
return []
-def __gather_mesh(blender_object, library, export_settings):
+def __gather_mesh(vnode, blender_object, export_settings):
if blender_object.type in ['CURVE', 'SURFACE', 'FONT']:
- return __gather_mesh_from_nonmesh(blender_object, library, export_settings)
+ return __gather_mesh_from_nonmesh(blender_object, export_settings)
if blender_object.type != "MESH":
return None
+ # For duplis instancer, when show is off -> export as empty
+ if vnode.force_as_empty is True:
+ return None
+
# Be sure that object is valid (no NaN for example)
blender_object.data.validate()
@@ -301,6 +194,8 @@ def __gather_mesh(blender_object, library, export_settings):
if len(modifiers) == 0:
modifiers = None
+ # TODO for objects without any modifiers, we can keep original mesh_data
+ # It will instance mesh in glTF
if export_settings[gltf2_blender_export_keys.APPLY]:
armature_modifiers = {}
if export_settings[gltf2_blender_export_keys.SKINS]:
@@ -335,24 +230,23 @@ def __gather_mesh(blender_object, library, export_settings):
modifiers = None
materials = tuple(ms.material for ms in blender_object.material_slots)
- material_names = tuple(None if mat is None else mat.name for mat in materials)
# retrieve armature
# Because mesh data will be transforms to skeleton space,
# we can't instantiate multiple object at different location, skined by same armature
- blender_object_for_skined_data = None
+ uuid_for_skined_data = None
if export_settings[gltf2_blender_export_keys.SKINS]:
for idx, modifier in enumerate(blender_object.modifiers):
if modifier.type == 'ARMATURE':
- blender_object_for_skined_data = blender_object
+ uuid_for_skined_data = vnode.uuid
result = gltf2_blender_gather_mesh.gather_mesh(blender_mesh,
- library,
- blender_object_for_skined_data,
+ uuid_for_skined_data,
vertex_groups,
modifiers,
skip_filter,
- material_names,
+ materials,
+ None,
export_settings)
if export_settings[gltf2_blender_export_keys.APPLY]:
@@ -361,7 +255,7 @@ def __gather_mesh(blender_object, library, export_settings):
return result
-def __gather_mesh_from_nonmesh(blender_object, library, export_settings):
+def __gather_mesh_from_nonmesh(blender_object, export_settings):
"""Handles curves, surfaces, text, etc."""
needs_to_mesh_clear = False
try:
@@ -387,18 +281,18 @@ def __gather_mesh_from_nonmesh(blender_object, library, export_settings):
needs_to_mesh_clear = True
skip_filter = True
- material_names = tuple([ms.material.name for ms in blender_object.material_slots if ms.material is not None])
+ materials = tuple([ms.material for ms in blender_object.material_slots if ms.material is not None])
vertex_groups = None
modifiers = None
blender_object_for_skined_data = None
result = gltf2_blender_gather_mesh.gather_mesh(blender_mesh,
- library,
blender_object_for_skined_data,
vertex_groups,
modifiers,
skip_filter,
- material_names,
+ materials,
+ blender_object.data,
export_settings)
finally:
@@ -411,33 +305,15 @@ def __gather_mesh_from_nonmesh(blender_object, library, export_settings):
def __gather_name(blender_object, export_settings):
return blender_object.name
-
-def __gather_trans_rot_scale(blender_object, export_settings):
- if blender_object.matrix_parent_inverse == Matrix.Identity(4):
- trans = blender_object.location
-
- if blender_object.rotation_mode in ['QUATERNION', 'AXIS_ANGLE']:
- rot = blender_object.rotation_quaternion
- else:
- rot = blender_object.rotation_euler.to_quaternion()
-
- sca = blender_object.scale
+def __gather_trans_rot_scale(vnode, export_settings):
+ if vnode.parent_uuid is None:
+ # No parent, so matrix is world matrix
+ trans, rot, sca = vnode.matrix_world.decompose()
else:
- # matrix_local = matrix_parent_inverse*location*rotation*scale
- # Decomposing matrix_local gives less accuracy, but is needed if matrix_parent_inverse is not the identity.
+ # calculate local matrix
+ trans, rot, sca = (export_settings['vtree'].nodes[vnode.parent_uuid].matrix_world.inverted_safe() @ vnode.matrix_world).decompose()
- if blender_object.matrix_local[3][3] != 0.0:
- trans, rot, sca = blender_object.matrix_local.decompose()
- else:
- # Some really weird cases, scale is null (if parent is null when evaluation is done)
- print_console('WARNING', 'Some nodes are 0 scaled during evaluation. Result can be wrong')
- trans = blender_object.location
- if blender_object.rotation_mode in ['QUATERNION', 'AXIS_ANGLE']:
- rot = blender_object.rotation_quaternion
- else:
- rot = blender_object.rotation_euler.to_quaternion()
- sca = blender_object.scale
# make sure the rotation is normalized
rot.normalize()
@@ -446,9 +322,9 @@ def __gather_trans_rot_scale(blender_object, export_settings):
rot = __convert_swizzle_rotation(rot, export_settings)
sca = __convert_swizzle_scale(sca, export_settings)
- if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection:
+ if vnode.blender_object.instance_type == 'COLLECTION' and vnode.blender_object.instance_collection:
offset = -__convert_swizzle_location(
- blender_object.instance_collection.instance_offset, export_settings)
+ vnode.blender_object.instance_collection.instance_offset, export_settings)
s = Matrix.Diagonal(sca).to_4x4()
r = rot.to_matrix().to_4x4()
@@ -473,8 +349,7 @@ def __gather_trans_rot_scale(blender_object, export_settings):
scale = [sca[0], sca[1], sca[2]]
return translation, rotation, scale
-
-def __gather_skin(blender_object, export_settings):
+def __gather_skin(vnode, blender_object, export_settings):
modifiers = {m.type: m for m in blender_object.modifiers}
if "ARMATURE" not in modifiers or modifiers["ARMATURE"].object is None:
return None
@@ -501,34 +376,12 @@ def __gather_skin(blender_object, export_settings):
return None
# Skins and meshes must be in the same glTF node, which is different from how blender handles armatures
- return gltf2_blender_gather_skins.gather_skin(modifiers["ARMATURE"].object, export_settings)
+ return gltf2_blender_gather_skins.gather_skin(vnode.armature, export_settings)
def __gather_weights(blender_object, export_settings):
return None
-
-def __get_correction_node(blender_object, export_settings):
- correction_quaternion = __convert_swizzle_rotation(
- Quaternion((1.0, 0.0, 0.0), math.radians(-90.0)), export_settings)
- correction_quaternion = [correction_quaternion[1], correction_quaternion[2],
- correction_quaternion[3], correction_quaternion[0]]
- return gltf2_io.Node(
- camera=None,
- children=[],
- extensions=None,
- extras=None,
- matrix=None,
- mesh=None,
- name=blender_object.name + '_Orientation',
- rotation=correction_quaternion,
- scale=None,
- skin=None,
- translation=None,
- weights=None
- )
-
-
def __convert_swizzle_location(loc, export_settings):
"""Convert a location from Blender coordinate system to glTF coordinate system."""
if export_settings[gltf2_blender_export_keys.YUP]:
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
index 82ff7f66..9e5ce648 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
@@ -7,7 +7,7 @@ import numpy as np
from .gltf2_blender_export_keys import NORMALS, MORPH_NORMAL, TANGENTS, MORPH_TANGENT, MORPH
-from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached, cached_by_key
from io_scene_gltf2.blender.exp import gltf2_blender_extract
from io_scene_gltf2.blender.exp import gltf2_blender_gather_accessors
from io_scene_gltf2.blender.exp import gltf2_blender_gather_primitive_attributes
@@ -20,13 +20,34 @@ from io_scene_gltf2.io.com.gltf2_io_debug import print_console
@cached
+def get_primitive_cache_key(
+ blender_mesh,
+ blender_object,
+ vertex_groups,
+ modifiers,
+ materials,
+ export_settings):
+
+ # Use id of mesh
+ # Do not use bpy.types that can be unhashable
+ # Do not use mesh name, that can be not unique (when linked)
+
+ # TODO check what is really needed for modifiers
+
+ return (
+ (id(blender_mesh),),
+ (modifiers,),
+ tuple(id(m) if m is not None else None for m in materials)
+ )
+
+
+@cached_by_key(key=get_primitive_cache_key)
def gather_primitives(
blender_mesh: bpy.types.Mesh,
- library: Optional[str],
- blender_object: Optional[bpy.types.Object],
+ uuid_for_skined_data,
vertex_groups: Optional[bpy.types.VertexGroups],
modifiers: Optional[bpy.types.ObjectModifiers],
- material_names: Tuple[str],
+ materials: Tuple[bpy.types.Material],
export_settings
) -> List[gltf2_io.MeshPrimitive]:
"""
@@ -36,7 +57,7 @@ def gather_primitives(
"""
primitives = []
- blender_primitives = __gather_cache_primitives(blender_mesh, library, blender_object,
+ blender_primitives = __gather_cache_primitives(blender_mesh, uuid_for_skined_data,
vertex_groups, modifiers, export_settings)
for internal_primitive in blender_primitives:
@@ -45,14 +66,13 @@ def gather_primitives(
if export_settings['gltf_materials'] == "EXPORT" and material_idx is not None:
blender_material = None
- if material_names:
- i = material_idx if material_idx < len(material_names) else -1
- material_name = material_names[i]
- if material_name is not None:
- blender_material = bpy.data.materials[material_name]
- if blender_material is not None:
+ mat = None
+ if materials:
+ i = material_idx if material_idx < len(materials) else -1
+ mat = materials[i]
+ if mat is not None:
material = gltf2_blender_gather_materials.gather_material(
- blender_material,
+ mat,
export_settings,
)
@@ -72,8 +92,7 @@ def gather_primitives(
@cached
def __gather_cache_primitives(
blender_mesh: bpy.types.Mesh,
- library: Optional[str],
- blender_object: Optional[bpy.types.Object],
+ uuid_for_skined_data,
vertex_groups: Optional[bpy.types.VertexGroups],
modifiers: Optional[bpy.types.ObjectModifiers],
export_settings
@@ -84,7 +103,7 @@ def __gather_cache_primitives(
primitives = []
blender_primitives = gltf2_blender_extract.extract_primitives(
- None, blender_mesh, library, blender_object, vertex_groups, modifiers, export_settings)
+ blender_mesh, uuid_for_skined_data, vertex_groups, modifiers, export_settings)
for internal_primitive in blender_primitives:
primitive = {
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py
index e5534c5a..136d654d 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_skins.py
@@ -10,10 +10,12 @@ from io_scene_gltf2.io.com import gltf2_io_constants
from io_scene_gltf2.blender.exp import gltf2_blender_gather_accessors
from io_scene_gltf2.blender.exp import gltf2_blender_gather_joints
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
+from io_scene_gltf2.blender.exp import gltf2_blender_gather_tree
+from io_scene_gltf2.blender.exp.gltf2_blender_gather_tree import VExportNode
@cached
-def gather_skin(blender_object, export_settings):
+def gather_skin(armature_uuid, export_settings):
"""
Gather armatures, bones etc into a glTF2 skin object.
@@ -21,78 +23,70 @@ def gather_skin(blender_object, export_settings):
:param export_settings:
:return: a glTF2 skin object
"""
- if not __filter_skin(blender_object, export_settings):
+
+ blender_armature_object = export_settings['vtree'].nodes[armature_uuid].blender_object
+
+ if not __filter_skin(blender_armature_object, export_settings):
return None
skin = gltf2_io.Skin(
- extensions=__gather_extensions(blender_object, export_settings),
- extras=__gather_extras(blender_object, export_settings),
- inverse_bind_matrices=__gather_inverse_bind_matrices(blender_object, export_settings),
- joints=__gather_joints(blender_object, export_settings),
- name=__gather_name(blender_object, export_settings),
- skeleton=__gather_skeleton(blender_object, export_settings)
+ extensions=__gather_extensions(blender_armature_object, export_settings),
+ extras=__gather_extras(blender_armature_object, export_settings),
+ inverse_bind_matrices=__gather_inverse_bind_matrices(armature_uuid, export_settings),
+ joints=__gather_joints(armature_uuid, export_settings),
+ name=__gather_name(blender_armature_object, export_settings),
+ skeleton=__gather_skeleton(blender_armature_object, export_settings)
)
- export_user_extensions('gather_skin_hook', export_settings, skin, blender_object)
+ # If armature is not exported, joints will be empty.
+ # Do not construct skin in that case
+ if len(skin.joints) == 0:
+ return None
+
+ export_user_extensions('gather_skin_hook', export_settings, skin, blender_armature_object)
return skin
-def __filter_skin(blender_object, export_settings):
+def __filter_skin(blender_armature_object, export_settings):
if not export_settings[gltf2_blender_export_keys.SKINS]:
return False
- if blender_object.type != 'ARMATURE' or len(blender_object.pose.bones) == 0:
+ if blender_armature_object.type != 'ARMATURE' or len(blender_armature_object.pose.bones) == 0:
return False
return True
-def __gather_extensions(blender_object, export_settings):
+def __gather_extensions(blender_armature_object, export_settings):
return None
-def __gather_extras(blender_object, export_settings):
+def __gather_extras(blender_armature_object, export_settings):
return None
-def __gather_inverse_bind_matrices(blender_object, export_settings):
+def __gather_inverse_bind_matrices(armature_uuid, export_settings):
+
+ blender_armature_object = export_settings['vtree'].nodes[armature_uuid].blender_object
+
axis_basis_change = mathutils.Matrix.Identity(4)
if export_settings[gltf2_blender_export_keys.YUP]:
axis_basis_change = mathutils.Matrix(
((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
- if export_settings['gltf_def_bones'] is False:
- # build the hierarchy of nodes out of the bones
- root_bones = []
- for blender_bone in blender_object.pose.bones:
- if not blender_bone.parent:
- root_bones.append(blender_bone)
- else:
- _, children_, root_bones = get_bone_tree(None, blender_object)
-
- matrices = []
-
- # traverse the matrices in the same order as the joints and compute the inverse bind matrix
+ bones_uuid = export_settings['vtree'].get_all_bones(armature_uuid)
def __collect_matrices(bone):
inverse_bind_matrix = (
axis_basis_change @
(
- blender_object.matrix_world @
+ blender_armature_object.matrix_world @
bone.bone.matrix_local
)
).inverted_safe()
matrices.append(inverse_bind_matrix)
- if export_settings['gltf_def_bones'] is False:
- for child in bone.children:
- __collect_matrices(child)
- else:
- if bone.name in children_.keys():
- for child in children_[bone.name]:
- __collect_matrices(blender_object.pose.bones[child])
-
- # start with the "root" bones and recurse into children, in the same ordering as the how joints are gathered
- for root_bone in root_bones:
- __collect_matrices(root_bone)
+ matrices = []
+ for b in bones_uuid:
+ __collect_matrices(blender_armature_object.pose.bones[export_settings['vtree'].nodes[b].blender_bone.name])
# flatten the matrices
inverse_matrices = []
@@ -113,67 +107,26 @@ def __gather_inverse_bind_matrices(blender_object, export_settings):
)
-def __gather_joints(blender_object, export_settings):
- root_joints = []
- if export_settings['gltf_def_bones'] is False:
- # build the hierarchy of nodes out of the bones
- for blender_bone in blender_object.pose.bones:
- if not blender_bone.parent:
- root_joints.append(gltf2_blender_gather_joints.gather_joint(blender_object, blender_bone, export_settings))
- else:
- _, children_, root_joints = get_bone_tree(None, blender_object)
- root_joints = [gltf2_blender_gather_joints.gather_joint(blender_object, i, export_settings) for i in root_joints]
-
- # joints is a flat list containing all nodes belonging to the skin
- joints = []
-
- def __collect_joints(node):
- joints.append(node)
- if export_settings['gltf_def_bones'] is False:
- for child in node.children:
- __collect_joints(child)
- else:
- if node.name in children_.keys():
- for child in children_[node.name]:
- __collect_joints(gltf2_blender_gather_joints.gather_joint(blender_object, blender_object.pose.bones[child], export_settings))
-
- for joint in root_joints:
- __collect_joints(joint)
+def __gather_joints(armature_uuid, export_settings):
+
+ blender_armature_object = export_settings['vtree'].nodes[armature_uuid].blender_object
+
+ all_armature_children = export_settings['vtree'].nodes[armature_uuid].children
+ root_bones_uuid = [c for c in all_armature_children if export_settings['vtree'].nodes[c].blender_type == VExportNode.BONE]
+ # Create bone nodes
+ for root_bone_uuid in root_bones_uuid:
+ gltf2_blender_gather_joints.gather_joint_vnode(root_bone_uuid, export_settings)
+
+ bones_uuid = export_settings['vtree'].get_all_bones(armature_uuid)
+ joints = [export_settings['vtree'].nodes[b].node for b in bones_uuid]
return joints
-def __gather_name(blender_object, export_settings):
- return blender_object.name
+def __gather_name(blender_armature_object, export_settings):
+ return blender_armature_object.name
-def __gather_skeleton(blender_object, export_settings):
+def __gather_skeleton(blender_armature_object, export_settings):
# In the future support the result of https://github.com/KhronosGroup/glTF/pull/1195
- return None # gltf2_blender_gather_nodes.gather_node(blender_object, blender_scene, export_settings)
-
-@cached
-def get_bone_tree(blender_dummy, blender_object):
-
- bones = []
- children = {}
- root_bones = []
-
- def get_parent(bone):
- bones.append(bone.name)
- if bone.parent is not None:
- if bone.parent.name not in children.keys():
- children[bone.parent.name] = []
- children[bone.parent.name].append(bone.name)
- get_parent(bone.parent)
- else:
- root_bones.append(bone.name)
-
- for bone in [b for b in blender_object.data.bones if b.use_deform is True]:
- get_parent(bone)
-
- # remove duplicates
- for k, v in children.items():
- children[k] = list(set(v))
- list_ = list(set(bones))
- root_ = list(set(root_bones))
- return [blender_object.data.bones[b] for b in list_], children, [blender_object.pose.bones[b] for b in root_]
+ return None
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
new file mode 100644
index 00000000..7a5ce2be
--- /dev/null
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_tree.py
@@ -0,0 +1,375 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright 2021 The glTF-Blender-IO authors.
+
+import bpy
+import uuid
+
+from . import gltf2_blender_export_keys
+from mathutils import Quaternion, Matrix
+
+class VExportNode:
+
+ OBJECT = 1
+ ARMATURE = 2
+ BONE = 3
+ LIGHT = 4
+ CAMERA = 5
+ COLLECTION = 6
+
+ # Parent type, to be set on child regarding its parent
+ NO_PARENT = 54
+ PARENT_OBJECT = 50
+ PARENT_BONE = 51
+ PARENT_BONE_RELATIVE = 52
+ PARENT_ROOT_BONE = 53
+ PARENT_BONE_BONE = 55
+
+
+ def __init__(self):
+ self.children = []
+ self.blender_type = None
+ self.world_matrix = None
+ self.parent_type = None
+
+ self.blender_object = None
+ self.blender_bone = None
+
+ self.force_as_empty = False # Used for instancer display
+
+ # Only for bone/bone and object parented to bone
+ self.parent_bone_uuid = None
+
+ # Only for bones
+ self.use_deform = None
+
+ # Only for armature
+ self.bones = {}
+
+ # For deformed object
+ self.armature = None # for deformed object and for bone
+ self.skin = None
+
+ # glTF
+ self.node = None
+
+ def add_child(self, uuid):
+ self.children.append(uuid)
+
+ def set_world_matrix(self, matrix):
+ self.world_matrix = matrix
+
+ def set_blender_data(self, blender_object, blender_bone):
+ self.blender_object = blender_object
+ self.blender_bone = blender_bone
+
+ def recursive_display(self, tree, mode):
+ if mode == "simple":
+ for c in self.children:
+ print(self.blender_object.name, "/", self.blender_bone.name if self.blender_bone else "", "-->", tree.nodes[c].blender_object.name, "/", tree.nodes[c].blender_bone.name if tree.nodes[c].blender_bone else "" )
+ tree.nodes[c].recursive_display(tree, mode)
+
+class VExportTree:
+ def __init__(self, export_settings):
+ self.nodes = {}
+ self.roots = []
+
+ self.export_settings = export_settings
+
+ self.tree_troncated = False
+
+ def add_node(self, node):
+ self.nodes[node.uuid] = node
+
+ def add_children(self, uuid_parent, uuid_child):
+ self.nodes[uuid_parent].add_child(uuid_child)
+
+ def construct(self, blender_scene):
+ bpy.context.window.scene = blender_scene
+ depsgraph = bpy.context.evaluated_depsgraph_get()
+
+ for blender_object in [obj.original for obj in depsgraph.scene_eval.objects if obj.parent is None]:
+ self.recursive_node_traverse(blender_object, None, None, Matrix.Identity(4))
+
+ def recursive_node_traverse(self, blender_object, blender_bone, parent_uuid, parent_coll_matrix_world, armature_uuid=None, dupli_world_matrix=None):
+ node = VExportNode()
+ node.uuid = str(uuid.uuid4())
+ node.parent_uuid = parent_uuid
+ node.set_blender_data(blender_object, blender_bone)
+
+ # add to parent if needed
+ if parent_uuid is not None:
+ self.add_children(parent_uuid, node.uuid)
+ else:
+ self.roots.append(node.uuid)
+
+ # Set blender type
+ if blender_bone is not None:
+ node.blender_type = VExportNode.BONE
+ self.nodes[armature_uuid].bones[blender_bone.name] = node.uuid
+ node.use_deform = blender_bone.id_data.data.bones[blender_bone.name].use_deform
+ elif blender_object.type == "ARMATURE":
+ node.blender_type = VExportNode.ARMATURE
+ elif blender_object.type == "CAMERA":
+ node.blender_type = VExportNode.CAMERA
+ elif blender_object.type == "LIGHT":
+ node.blender_type = VExportNode.LIGHT
+ elif blender_object.instance_type == "COLLECTION":
+ node.blender_type = VExportNode.COLLECTION
+ else:
+ node.blender_type = VExportNode.OBJECT
+
+ # For meshes with armature modifier (parent is armature), keep armature uuid
+ if node.blender_type == VExportNode.OBJECT:
+ modifiers = {m.type: m for m in blender_object.modifiers}
+ if "ARMATURE" in modifiers and modifiers["ARMATURE"].object is not None:
+ if parent_uuid is None or not self.nodes[parent_uuid].blender_type == VExportNode.ARMATURE:
+ # correct workflow is to parent skinned mesh to armature, but ...
+ # all users don't use correct workflow
+ print("WARNING: Armature must be the parent of skinned mesh")
+ print("Armature is selected by its name, but may be false in case of instances")
+ # Search an armature by name, and use the first found
+ # This will be done after all objects are setup
+ node.armature_needed = modifiers["ARMATURE"].object.name
+ else:
+ node.armature = parent_uuid
+
+ # For bones, store uuid of armature
+ if blender_bone is not None:
+ node.armature = armature_uuid
+
+ # for bone/bone parenting, store parent, this will help armature tree management
+ if parent_uuid is not None and self.nodes[parent_uuid].blender_type == VExportNode.BONE and node.blender_type == VExportNode.BONE:
+ node.parent_bone_uuid = parent_uuid
+
+
+ # Objects parented to bone
+ if parent_uuid is not None and self.nodes[parent_uuid].blender_type == VExportNode.BONE and node.blender_type != VExportNode.BONE:
+ node.parent_bone_uuid = parent_uuid
+
+ # World Matrix
+ # Store World Matrix for objects
+ if dupli_world_matrix is not None:
+ node.matrix_world = dupli_world_matrix
+ elif node.blender_type in [VExportNode.OBJECT, VExportNode.COLLECTION, VExportNode.ARMATURE, VExportNode.CAMERA, VExportNode.LIGHT]:
+ # Matrix World of object is expressed based on collection instance objects are
+ # So real world matrix is collection world_matrix @ "world_matrix" of object
+ node.matrix_world = parent_coll_matrix_world @ blender_object.matrix_world.copy()
+ if node.blender_type == VExportNode.CAMERA and self.export_settings[gltf2_blender_export_keys.CAMERAS]:
+ correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
+ node.matrix_world @= correction.to_matrix().to_4x4()
+ elif node.blender_type == VExportNode.LIGHT and self.export_settings[gltf2_blender_export_keys.LIGHTS]:
+ correction = Quaternion((2**0.5/2, -2**0.5/2, 0.0, 0.0))
+ node.matrix_world @= correction.to_matrix().to_4x4()
+ elif node.blender_type == VExportNode.BONE:
+ node.matrix_world = self.nodes[node.armature].matrix_world @ blender_bone.matrix
+ axis_basis_change = Matrix(
+ ((1.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0)))
+ node.matrix_world = node.matrix_world @ axis_basis_change
+
+ # Force empty ?
+ # For duplis, if instancer is not display, we should create an empty
+ if blender_object.is_instancer is True and blender_object.show_instancer_for_render is False:
+ node.force_as_empty = True
+
+ # Storing this node
+ self.add_node(node)
+
+ ###### Manage children ######
+
+ # standard children
+ if blender_bone is None and blender_object.is_instancer is False:
+ for child_object in blender_object.children:
+ if child_object.parent_bone:
+ # Object parented to bones
+ # Will be manage later
+ continue
+ else:
+ # Classic parenting
+ self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world)
+
+ # Collections
+ if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection:
+ for dupli_object in blender_object.instance_collection.objects:
+ if dupli_object.parent is not None:
+ continue
+ self.recursive_node_traverse(dupli_object, None, node.uuid, node.matrix_world)
+
+ # Armature : children are bones with no parent
+ if blender_object.type == "ARMATURE" and blender_bone is None:
+ for b in [b for b in blender_object.pose.bones if b.parent is None]:
+ self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, node.uuid)
+
+ # Bones
+ if blender_object.type == "ARMATURE" and blender_bone is not None:
+ for b in blender_bone.children:
+ self.recursive_node_traverse(blender_object, b, node.uuid, parent_coll_matrix_world, armature_uuid)
+
+ # Object parented to bone
+ if blender_bone is not None:
+ for child_object in [c for c in blender_object.children if c.parent_bone is not None and c.parent_bone == blender_bone.name]:
+ self.recursive_node_traverse(child_object, None, node.uuid, parent_coll_matrix_world)
+
+ # Duplis
+ if blender_object.is_instancer is True and blender_object.instance_type != 'COLLECTION':
+ depsgraph = bpy.context.evaluated_depsgraph_get()
+ for (dupl, mat) in [(dup.object.original, dup.matrix_world.copy()) for dup in depsgraph.object_instances if dup.parent and id(dup.parent.original) == id(blender_object)]:
+ self.recursive_node_traverse(dupl, None, node.uuid, parent_coll_matrix_world, dupli_world_matrix=mat)
+
+ def get_all_objects(self):
+ return [n.uuid for n in self.nodes.values() if n.blender_type != VExportNode.BONE]
+
+ def get_all_bones(self, uuid): #For armatue Only
+ if self.nodes[uuid].blender_type == VExportNode.ARMATURE:
+ def recursive_get_all_bones(uuid):
+ total = []
+ if self.nodes[uuid].blender_type == VExportNode.BONE:
+ total.append(uuid)
+ for child_uuid in self.nodes[uuid].children:
+ total.extend(recursive_get_all_bones(child_uuid))
+
+ return total
+
+ tot = []
+ for c_uuid in self.nodes[uuid].children:
+ tot.extend(recursive_get_all_bones(c_uuid))
+ return tot
+ else:
+ return []
+
+ def display(self, mode):
+ if mode == "simple":
+ for n in self.roots:
+ print("Root", self.nodes[n].blender_object.name, "/", self.nodes[n].blender_bone.name if self.nodes[n].blender_bone else "" )
+ self.nodes[n].recursive_display(self, mode)
+
+
+ def filter_tag(self):
+ roots = self.roots.copy()
+ for r in roots:
+ self.recursive_filter_tag(r, None)
+
+ def filter_perform(self):
+ roots = self.roots.copy()
+ for r in roots:
+ self.recursive_filter(r, None) # Root, so no parent
+
+ def filter(self):
+ self.filter_tag()
+ self.filter_perform()
+
+
+ def recursive_filter_tag(self, uuid, parent_keep_tag):
+ # parent_keep_tag is for collection instance
+ # some properties (selection, visibility, renderability)
+ # are defined at collection level, and we need to use these values
+ # for all objects of the collection instance.
+ # But some properties (camera, lamp ...) are not defined at collection level
+ if parent_keep_tag is None:
+ self.nodes[uuid].keep_tag = self.node_filter_not_inheritable_is_kept(uuid) and self.node_filter_inheritable_is_kept(uuid)
+ elif parent_keep_tag is True:
+ self.nodes[uuid].keep_tag = self.node_filter_not_inheritable_is_kept(uuid)
+ elif parent_keep_tag is False:
+ self.nodes[uuid].keep_tag = False
+ else:
+ print("This should not happen!")
+
+ for child in self.nodes[uuid].children:
+ if self.nodes[uuid].blender_type == VExportNode.COLLECTION:
+ self.recursive_filter_tag(child, self.nodes[uuid].keep_tag)
+ else:
+ self.recursive_filter_tag(child, parent_keep_tag)
+
+ def recursive_filter(self, uuid, parent_kept_uuid):
+ children = self.nodes[uuid].children.copy()
+
+ new_parent_kept_uuid = None
+ if self.nodes[uuid].keep_tag is False:
+ new_parent_kept_uuid = parent_kept_uuid
+ # Need to modify tree
+ if self.nodes[uuid].parent_uuid is not None:
+ self.nodes[self.nodes[uuid].parent_uuid].children.remove(uuid)
+ else:
+ # Remove from root
+ self.roots.remove(uuid)
+ else:
+ new_parent_kept_uuid = uuid
+
+ # If parent_uuid is not parent_kept_uuid, we need to modify children list of parent_kept_uuid
+ if parent_kept_uuid != self.nodes[uuid].parent_uuid and parent_kept_uuid is not None:
+ self.tree_troncated = True
+ self.nodes[parent_kept_uuid].children.append(uuid)
+
+ # If parent_kept_uuid is None, and parent_uuid was not, add to root list
+ if self.nodes[uuid].parent_uuid is not None and parent_kept_uuid is None:
+ self.tree_troncated = True
+ self.roots.append(uuid)
+
+ # Modify parent uuid
+ self.nodes[uuid].parent_uuid = parent_kept_uuid
+
+ for child in children:
+ self.recursive_filter(child, new_parent_kept_uuid)
+
+
+ def node_filter_not_inheritable_is_kept(self, uuid):
+ # Export Camera or not
+ if self.nodes[uuid].blender_type == VExportNode.CAMERA:
+ if self.export_settings[gltf2_blender_export_keys.CAMERAS] is False:
+ return False
+
+ # Export Lamp or not
+ if self.nodes[uuid].blender_type == VExportNode.LIGHT:
+ if self.export_settings[gltf2_blender_export_keys.LIGHTS] is False:
+ return False
+
+ # Export deform bones only
+ if self.nodes[uuid].blender_type == VExportNode.BONE:
+ if self.export_settings['gltf_def_bones'] is True and self.nodes[uuid].use_deform is False:
+ # Check if bone has some objected parented to bone. We need to keep it in that case, even if this is not a def bone
+ if len([c for c in self.nodes[uuid].children if self.nodes[c].blender_type != VExportNode.BONE]) != 0:
+ return True
+ return False
+
+ return True
+
+ def node_filter_inheritable_is_kept(self, uuid):
+
+ if self.export_settings[gltf2_blender_export_keys.SELECTED] and self.nodes[uuid].blender_object.select_get() is False:
+ return False
+
+ if self.export_settings[gltf2_blender_export_keys.VISIBLE]:
+ # The eye in outliner (object)
+ if self.nodes[uuid].blender_object.visible_get() is False:
+ return False
+
+ # The screen in outliner (object)
+ if self.nodes[uuid].blender_object.hide_viewport is True:
+ return False
+
+ # The screen in outliner (collections)
+ if all([c.hide_viewport for c in self.nodes[uuid].blender_object.users_collection]):
+ return False
+
+ # The camera in outliner (object)
+ if self.export_settings[gltf2_blender_export_keys.RENDERABLE]:
+ if self.nodes[uuid].blender_object.hide_render is True:
+ return False
+
+ # The camera in outliner (collections)
+ if all([c.hide_render for c in self.nodes[uuid].blender_object.users_collection]):
+ return False
+
+ if self.export_settings[gltf2_blender_export_keys.ACTIVE_COLLECTION]:
+ found = any(x == self.nodes[uuid].blender_object for x in bpy.context.collection.all_objects)
+ if not found:
+ return False
+
+ return True
+
+ def search_missing_armature(self):
+ for n in [n for n in self.nodes.values() if hasattr(n, "armature_needed") is True]:
+ candidates = [i for i in self.nodes.values() if i.blender_type == VExportNode.ARMATURE and i.blender_object.name == n.armature_needed]
+ if len(candidates) > 0:
+ n.armature = candidates[0].uuid
+ del n.armature_needed
+ \ No newline at end of file