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
This commit is contained in:
parent
842c215b74
commit
782f8585f4
Notes:
blender-bot
2023-02-14 18:52:20 +01:00
Referenced by issue #95050, Python Traceback when exporting to GLTF Referenced by issue #78459, glTF Exporter won't properly export linked collections if 'Selected objects' option is toggled
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
# 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_
|
||||
# Do not manage not exported objects
|
||||
if vtree.nodes[obj_uuid].node is None:
|
||||
continue
|
||||
|
||||
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
|
||||
|
|
|
@ -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_joints.gather_joint_vnode(export_settings['vtree'].nodes[obj_uuid].bones[blender_bone.name], 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 export_settings['vtree'].nodes[obj_uuid].node
|
||||
|
||||
|
||||
def __gather_path(channels: typing.Tuple[bpy.types.FCurve],
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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"]:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,83 +6,134 @@ import bpy
|
|||
from io_scene_gltf2.blender.exp import gltf2_blender_get
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
Decorate the cache gather functions results.
|
||||
return cached_by_key(key=default_key)(func)
|
||||
|
||||
def objectcache(func):
|
||||
|
||||
def reset_cache_objectcache():
|
||||
func.__objectcache = {}
|
||||
|
||||
func.reset_cache = reset_cache_objectcache
|
||||
|
||||
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:
|
||||
"""
|
||||
@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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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')
|
||||
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)
|
||||
|
||||
trans, rot, sca = (correction_matrix_local @ matrix_basis).decompose()
|
||||
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):
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -13,137 +13,49 @@ 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)
|
||||
|
||||
vnode.node = node
|
||||
|
||||
if node.skin is not None:
|
||||
vnode.skin = skin
|
||||
|
||||
return 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 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
|
||||
|
||||
|
||||
def __gather_camera(blender_object, export_settings):
|
||||
if blender_object.type != 'CAMERA':
|
||||
return None
|
||||
|
@ -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
|
||||
mat = vtree.nodes[vtree.nodes[child].parent_bone_uuid].matrix_world.inverted_safe() @ vtree.nodes[child].matrix_world
|
||||
loc, rot_quat, scale = mat.decompose()
|
||||
|
||||
_, rot_quat, _ = mat.decompose()
|
||||
child_node.rotation = [rot_quat[1], rot_quat[2], rot_quat[3], rot_quat[0]]
|
||||
trans = __convert_swizzle_location(loc, export_settings)
|
||||
rot = __convert_swizzle_rotation(rot_quat, export_settings)
|
||||
sca = __convert_swizzle_scale(scale, export_settings)
|
||||
|
||||
# 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)]
|
||||
|
||||
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]:
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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]
|
||||
def __gather_joints(armature_uuid, export_settings):
|
||||
|
||||
# joints is a flat list containing all nodes belonging to the skin
|
||||
joints = []
|
||||
blender_armature_object = export_settings['vtree'].nodes[armature_uuid].blender_object
|
||||
|
||||
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))
|
||||
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]
|
||||
|
||||
for joint in root_joints:
|
||||
__collect_joints(joint)
|
||||
# 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
|
||||
|
|
|
@ -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
|
||||
|
Loading…
Reference in New Issue