def get_sk_drivers(blender_armature): drivers = [] for child in blender_armature.children: if not child.data: continue # child.data can be an armature - which has no shapekeys if not hasattr(child.data, 'shape_keys'): continue if not child.data.shape_keys: continue if not child.data.shape_keys.animation_data: continue if not child.data.shape_keys.animation_data.drivers: continue if len(child.data.shape_keys.animation_data.drivers) <= 0: continue shapekeys_idx = {} cpt_sk = 0 for sk in child.data.shape_keys.key_blocks: if sk == sk.relative_key: continue if sk.mute is True: continue shapekeys_idx[sk.name] = cpt_sk cpt_sk += 1 # Note: channels will have some None items only for SK if some SK are not animated idx_channel_mapping = [] all_sorted_channels = [] for sk_c in child.data.shape_keys.animation_data.drivers: # Check if driver is valid. If not, ignore this driver channel try: # Check if driver is valid. # Try/Except is no more a suffisant check, starting with version Blender 3.0, # Blender crashs when trying to resolve path on invalid driver if not sk_c.is_valid: continue sk_name = child.data.shape_keys.path_resolve( get_target_object_path(sk_c.data_path)).name except: continue # Do not take into account this driver if corresponding SK is disabled if child.data.shape_keys.key_blocks[sk_name].mute is True: continue idx = shapekeys_idx[sk_name] idx_channel_mapping.append((shapekeys_idx[sk_name], sk_c)) existing_idx = dict(idx_channel_mapping) for i in range(0, cpt_sk): if i not in existing_idx.keys(): all_sorted_channels.append(None) else: all_sorted_channels.append(existing_idx[i]) if len(all_sorted_channels) > 0: drivers.append((child, tuple(all_sorted_channels))) return tuple(drivers)
def get_sk_driver_values(blender_object, frame, fcurves): sk_values = [] 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) return tuple(sk_values)
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) return tuple(sk_values)
def get_sk_drivers(blender_armature): drivers = [] for child in blender_armature.children: if not child.data: continue # child.data can be an armature - which has no shapekeys if not hasattr(child.data, 'shape_keys'): continue if not child.data.shape_keys: continue if not child.data.shape_keys.animation_data: continue if not child.data.shape_keys.animation_data.drivers: continue if len(child.data.shape_keys.animation_data.drivers) <= 0: continue shapekeys_idx = {} cpt_sk = 0 for sk in child.data.shape_keys.key_blocks: if sk == sk.relative_key: continue if sk.mute is True: continue shapekeys_idx[sk.name] = cpt_sk cpt_sk += 1 # Note: channels will have some None items only for SK if some SK are not animated idx_channel_mapping = [] all_sorted_channels = [] for sk_c in child.data.shape_keys.animation_data.drivers: # Check if driver is valid. If not, ignore this driver channel try: sk_name = child.data.shape_keys.path_resolve( get_target_object_path(sk_c.data_path)).name except: continue idx = shapekeys_idx[sk_name] idx_channel_mapping.append((shapekeys_idx[sk_name], sk_c)) existing_idx = dict(idx_channel_mapping) for i in range(0, cpt_sk): if i not in existing_idx.keys(): all_sorted_channels.append(None) else: all_sorted_channels.append(existing_idx[i]) drivers.append((child, tuple(all_sorted_channels))) return tuple(drivers)
def __gather_non_keyed_values(channels: typing.Tuple[bpy.types.FCurve], blender_object: bpy.types.Object, 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, export_settings ) -> typing.Tuple[typing.Optional[float]]: non_keyed_values = [] 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 if None not in channels: # classic case for object TRS or bone TRS # Or if all morph target are animated if driver_obj is not None: # driver of SK return tuple([None] * len(channels)) if bake_channel is None: target = channels[0].data_path.split('.')[-1] else: target = bake_channel if target == "value": # All morph targets are animated return tuple([None] * len(channels)) indices = [c.array_index for c in channels] indices.sort() length = { "delta_location": 3, "delta_rotation_euler": 3, "location": 3, "rotation_axis_angle": 4, "rotation_euler": 3, "rotation_quaternion": 4, "scale": 3, "value": len(channels) }.get(target) if length is None: # This is not a known target return () for i in range(0, length): if bake_channel is not None: non_keyed_values.append({ "delta_location" : obj.delta_location, "delta_rotation_euler" : obj.delta_rotation_euler, "location" : obj.location, "rotation_axis_angle" : obj.rotation_axis_angle, "rotation_euler" : obj.rotation_euler, "rotation_quaternion" : obj.rotation_quaternion, "scale" : obj.scale }[target][i]) elif i in indices: non_keyed_values.append(None) else: if blender_object_if_armature is None: non_keyed_values.append({ "delta_location" : obj.delta_location, "delta_rotation_euler" : obj.delta_rotation_euler, "location" : obj.location, "rotation_axis_angle" : obj.rotation_axis_angle, "rotation_euler" : obj.rotation_euler, "rotation_quaternion" : obj.rotation_quaternion, "scale" : obj.scale }[target][i]) else: # TODO, this is not working if the action is not active (NLA case for example) trans, rot, scale = pose_bone_if_armature.matrix_basis.decompose() non_keyed_values.append({ "location": trans, "rotation_axis_angle": rot, "rotation_euler": rot, "rotation_quaternion": rot, "scale": scale }[target][i]) return tuple(non_keyed_values) else: # We are in case of morph target, where all targets are not animated # So channels has some None items first_channel = [c for c in channels if c is not None][0] object_path = get_target_object_path(first_channel.data_path) if object_path: shapekeys_idx = {} cpt_sk = 0 for sk in obj.data.shape_keys.key_blocks: if sk == sk.relative_key: continue if sk.mute is True: continue shapekeys_idx[cpt_sk] = sk.name cpt_sk += 1 for idx_c, channel in enumerate(channels): if channel is None: non_keyed_values.append(obj.data.shape_keys.key_blocks[shapekeys_idx[idx_c]].value) else: non_keyed_values.append(None) return tuple(non_keyed_values)
def __gather_output(channels: typing.Tuple[bpy.types.FCurve], parent_inverse, blender_object_if_armature: typing.Optional[bpy.types.Object], non_keyed_values: typing.Tuple[typing.Optional[float]], bake_bone: typing.Union[str, None], bake_channel: typing.Union[str, None], bake_range_start, bake_range_end, action_name, driver_obj, export_settings ) -> gltf2_io.Accessor: """Gather the data of the keyframes.""" keyframes = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(blender_object_if_armature, channels, non_keyed_values, bake_bone, bake_channel, bake_range_start, bake_range_end, action_name, driver_obj, export_settings) 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 is_yup = export_settings[gltf2_blender_export_keys.YUP] # bone animations need to be handled differently as they are in a different coordinate system if bake_bone is None: object_path = get_target_object_path(target_datapath) else: object_path = None is_armature_animation = bake_bone is not None or (blender_object_if_armature is not None and object_path != "") if is_armature_animation: if bake_bone is None: bone = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature, object_path) else: bone = blender_object_if_armature.pose.bones[bake_bone] if isinstance(bone, bpy.types.PoseBone): if bone.parent is None: 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 @ bone.bone.matrix_local else: correction_matrix_local = ( bone.parent.bone.matrix_local.inverted() @ bone.bone.matrix_local ) transform = correction_matrix_local else: transform = mathutils.Matrix.Identity(4) else: transform = parent_inverse values = [] for keyframe in keyframes: # Transform the data and build gltf control points value = gltf2_blender_math.transform(keyframe.value, target_datapath, transform) 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) 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 if not isinstance(value, list): in_tangent = value - in_tangent else: in_tangent = [value[i] - in_tangent[i] for i in range(len(value))] keyframe_value = gltf2_blender_math.mathutils_to_gltf(in_tangent) + keyframe_value # append 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) 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 if not isinstance(value, list): out_tangent = value - out_tangent else: out_tangent = [value[i] - out_tangent[i] for i in range(len(value))] keyframe_value = keyframe_value + gltf2_blender_math.mathutils_to_gltf(out_tangent) # append values += keyframe_value # store the keyframe data in a binary buffer component_type = gltf2_io_constants.ComponentType.Float if get_target_property_name(target_datapath) == "value": # channels with 'weight' targets must have scalar accessors data_type = gltf2_io_constants.DataType.Scalar else: data_type = gltf2_io_constants.DataType.vec_type_from_num(len(keyframes[0].value)) return gltf2_io.Accessor( buffer_view=gltf2_io_binary_data.BinaryData.from_list(values, component_type), byte_offset=None, component_type=component_type, count=len(values) // gltf2_io_constants.DataType.num_elements(data_type), extensions=None, extras=None, max=None, min=None, name=None, normalized=None, sparse=None, type=data_type )
def __gather_output(channels: typing.Tuple[bpy.types.FCurve], parent_inverse, blender_object_if_armature: typing.Optional[ bpy.types.Object], export_settings) -> gltf2_io.Accessor: """Gather the data of the keyframes.""" keyframes = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes( channels, export_settings) target_datapath = channels[0].data_path transform = parent_inverse is_yup = export_settings[gltf2_blender_export_keys.YUP] object_path = get_target_object_path(target_datapath) is_armature_animation = blender_object_if_armature is not None and object_path != "" if is_armature_animation: bone = blender_object_if_armature.path_resolve(object_path) if isinstance(bone, bpy.types.PoseBone): if bone.parent is not None: parent_transform = bone.parent.bone.matrix_local transform = gltf2_blender_math.multiply( transform, parent_transform.inverted()) # if not is_yup: # transform = gltf2_blender_math.multiply(transform, gltf2_blender_math.to_zup()) else: # only apply the y-up conversion to root bones, as child bones already are in the y-up space if is_yup: transform = gltf2_blender_math.multiply( transform, gltf2_blender_math.to_yup()) local_transform = bone.bone.matrix_local transform = gltf2_blender_math.multiply(transform, local_transform) values = [] for keyframe in keyframes: # Transform the data and extract value = gltf2_blender_math.transform(keyframe.value, target_datapath, transform) 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: in_tangent = gltf2_blender_math.transform(keyframe.in_tangent, target_datapath, transform) if is_yup and blender_object_if_armature is None: in_tangent = gltf2_blender_math.swizzle_yup( in_tangent, target_datapath) keyframe_value = gltf2_blender_math.mathutils_to_gltf( in_tangent) + keyframe_value if keyframe.out_tangent is not None: out_tangent = gltf2_blender_math.transform(keyframe.out_tangent, target_datapath, transform) if is_yup and blender_object_if_armature is None: out_tangent = gltf2_blender_math.swizzle_yup( out_tangent, target_datapath) keyframe_value = keyframe_value + gltf2_blender_math.mathutils_to_gltf( out_tangent) values += keyframe_value component_type = gltf2_io_constants.ComponentType.Float if get_target_property_name(target_datapath) == "value": # channels with 'weight' targets must have scalar accessors data_type = gltf2_io_constants.DataType.Scalar else: data_type = gltf2_io_constants.DataType.vec_type_from_num( len(keyframes[0].value)) return gltf2_io.Accessor( buffer_view=gltf2_io_binary_data.BinaryData.from_list( values, component_type), byte_offset=None, component_type=component_type, count=len(values) // gltf2_io_constants.DataType.num_elements(data_type), extensions=None, extras=None, max=None, min=None, name=None, normalized=None, sparse=None, type=data_type)
def __gather_output(channels: typing.Tuple[bpy.types.FCurve], parent_inverse, blender_object_if_armature: typing.Optional[bpy.types.Object], export_settings ) -> gltf2_io.Accessor: """Gather the data of the keyframes.""" keyframes = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(blender_object_if_armature, channels, export_settings) target_datapath = channels[0].data_path transform = mathutils.Matrix.Identity(4) is_yup = export_settings[gltf2_blender_export_keys.YUP] # bone animations need to be handled differently as they are in a different coordinate system object_path = get_target_object_path(target_datapath) is_armature_animation = blender_object_if_armature is not None and object_path != "" if is_armature_animation: bone = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature, object_path) if isinstance(bone, bpy.types.PoseBone): if bone.parent is None: 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 = gltf2_blender_math.multiply(axis_basis_change, bone.bone.matrix_local) else: correction_matrix_local = gltf2_blender_math.multiply( bone.parent.bone.matrix_local.inverted(), bone.bone.matrix_local) transform = correction_matrix_local values = [] for keyframe in keyframes: # Transform the data and build gltf control points value = gltf2_blender_math.transform(keyframe.value, target_datapath, transform) 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) 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 in_tangent = value - in_tangent if not isinstance(value, list) else [value[0] - in_tangent[0]] keyframe_value = gltf2_blender_math.mathutils_to_gltf(in_tangent) + keyframe_value # append 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) 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 out_tangent = value - out_tangent if not isinstance(value, list) else [value[0] - out_tangent[0]] keyframe_value = keyframe_value + gltf2_blender_math.mathutils_to_gltf(out_tangent) # append values += keyframe_value # store the keyframe data in a binary buffer component_type = gltf2_io_constants.ComponentType.Float if get_target_property_name(target_datapath) == "value": # channels with 'weight' targets must have scalar accessors data_type = gltf2_io_constants.DataType.Scalar else: data_type = gltf2_io_constants.DataType.vec_type_from_num(len(keyframes[0].value)) return gltf2_io.Accessor( buffer_view=gltf2_io_binary_data.BinaryData.from_list(values, component_type), byte_offset=None, component_type=component_type, count=len(values) // gltf2_io_constants.DataType.num_elements(data_type), extensions=None, extras=None, max=None, min=None, name=None, normalized=None, sparse=None, type=data_type )