def __gather_node(channels: typing.Tuple[bpy.types.FCurve], blender_object: bpy.types.Object, export_settings, bake_bone: typing.Union[str, None], driver_obj ) -> 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) if blender_object.type == "ARMATURE": # TODO: get joint from fcurve data_path and gather_joint if bake_bone is not None: blender_bone = blender_object.pose.bones[bake_bone] else: 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: obj = blender_object.proxy if blender_object.proxy else blender_object return gltf2_blender_gather_joints.gather_joint(obj, 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]: obj = blender_object.proxy if blender_object.proxy else blender_object return gltf2_blender_gather_joints.gather_jointb(obj, blender_bone, export_settings) return gltf2_blender_gather_nodes.gather_node(blender_object, blender_object.library.name if blender_object.library else None, None, None, export_settings)
def gather_joint(blender_object, blender_bone, export_settings): """ Generate a glTF2 node from a blender bone, as joints in glTF2 are simply nodes. :param blender_bone: a blender PoseBone :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))) # extract bone transform if blender_bone.parent is None: correction_matrix_local = gltf2_blender_math.multiply(axis_basis_change, blender_bone.bone.matrix_local) else: correction_matrix_local = gltf2_blender_math.multiply( blender_bone.parent.bone.matrix_local.inverted(), blender_bone.bone.matrix_local) matrix_basis = blender_bone.matrix_basis trans, rot, sca = gltf2_blender_extract.decompose_transition( gltf2_blender_math.multiply(correction_matrix_local, matrix_basis), export_settings) translation, rotation, scale = (None, None, None) if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0: translation = [trans[0], trans[1], trans[2]] if rot[0] != 1.0 or rot[1] != 0.0 or rot[2] != 0.0 or rot[3] != 0.0: rotation = [rot[1], rot[2], rot[3], rot[0]] if sca[0] != 1.0 or sca[1] != 1.0 or sca[2] != 1.0: scale = [sca[0], sca[1], sca[2]] # 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)) # finally add to the joints array containing all the joints in the hierarchy return gltf2_io.Node( camera=None, children=children, extensions=None, extras=__gather_extras(blender_bone, export_settings), matrix=None, mesh=None, name=blender_bone.name, rotation=rotation, scale=scale, skin=None, translation=translation, weights=None )
def gather_joint(blender_object, blender_bone, export_settings): """ Generate a glTF2 node from a blender bone, as joints in glTF2 are simply nodes. :param blender_bone: a blender PoseBone :param export_settings: the settings for this export :return: a glTF2 node (acting as a joint) """ translation, rotation, scale = __gather_trans_rot_scale( 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)) # finally add to the joints array containing all the joints in the hierarchy node = gltf2_io.Node(camera=None, children=children, extensions=None, extras=__gather_extras(blender_bone, export_settings), matrix=None, mesh=None, name=blender_bone.name, rotation=rotation, scale=scale, skin=None, translation=translation, weights=None) node.__blender_data = ('BONE', blender_object, blender_bone.name) export_user_extensions('gather_joint_hook', export_settings, node, blender_bone) return node
def __gather_children(blender_object, blender_scene, export_settings): children = [] # 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 child_object = _child_object.proxy if _child_object.proxy else _child_object node = gather_node( child_object, child_object.library.name if child_object.library else None, blender_scene, None, export_settings) if node is not None: children.append(node) # 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 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) # blender bones if blender_object.type == "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 ] def find_parent_joint(joints, name): for joint in joints: if joint.name == name: return joint parent_joint = find_parent_joint(joint.children, name) if parent_joint: return parent_joint return None for child in direct_bone_children: # find parent joint parent_joint = find_parent_joint(root_joints, child.parent_bone) if not parent_joint: continue child_node = gather_node(child, None, None, None, 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 = gltf2_blender_math.multiply(child.matrix_parent_inverse, child.matrix_basis) mat = gltf2_blender_math.multiply(mat, axis_basis_change) _, rot_quat, _ = mat.decompose() child_node.rotation = [ rot_quat[1], rot_quat[2], rot_quat[3], rot_quat[0] ] # fix translation (in blender bone's tail is the origin for children) trans, _, _ = child.matrix_local.decompose() if trans is None: trans = [0, 0, 0] # bones go down their local y axis if blender_bone.matrix.to_scale()[1] >= 1e-6: bone_tail = [ 0, blender_bone.length / blender_bone.matrix.to_scale()[1], 0 ] else: bone_tail = [0, 0, 0] # If scale is 0, tail == head child_node.translation = [ trans[idx] + bone_tail[idx] for idx in range(3) ] parent_joint.children.append(child_node) return children
def gather_joint(blender_object, blender_bone, export_settings): """ Generate a glTF2 node from a blender bone, as joints in glTF2 are simply nodes. :param blender_bone: a blender PoseBone :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))) # extract bone transform if blender_bone.parent is None: correction_matrix_local = axis_basis_change @ blender_bone.bone.matrix_local else: correction_matrix_local = ( blender_bone.parent.bone.matrix_local.inverted_safe() @ blender_bone.bone.matrix_local ) if (blender_bone.bone.use_inherit_rotation == False or blender_bone.bone.inherit_scale != "FULL") and blender_bone.parent != None: rest_mat = (blender_bone.parent.bone.matrix_local.inverted_safe() @ blender_bone.bone.matrix_local) matrix_basis = (rest_mat.inverted_safe() @ blender_bone.parent.matrix.inverted_safe() @ blender_bone.matrix) else: matrix_basis = blender_bone.matrix matrix_basis = blender_object.convert_space(pose_bone=blender_bone, matrix=matrix_basis, from_space='POSE', to_space='LOCAL') trans, rot, sca = (correction_matrix_local @ matrix_basis).decompose() 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]] # 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)) # finally add to the joints array containing all the joints in the hierarchy node = gltf2_io.Node( camera=None, children=children, extensions=None, extras=__gather_extras(blender_bone, export_settings), matrix=None, mesh=None, name=blender_bone.name, rotation=rotation, scale=scale, skin=None, translation=translation, weights=None ) export_user_extensions('gather_joint_hook', export_settings, node, blender_bone) return node
def gather_animation_channels( blender_action: bpy.types.Action, blender_object: bpy.types.Object, export_settings) -> typing.List[gltf2_io.AnimationChannel]: channels = [] # First calculate range of animation for baking # This is need if user set 'Force sampling' and in case we need to bake bake_range_start = None bake_range_end = None groups = __get_channel_groups(blender_action, blender_object, export_settings) # Note: channels has some None items only for SK if some SK are not animated for chans in groups: ranges = [channel.range() for channel in chans if channel is not None] if bake_range_start is None: bake_range_start = min([ channel.range()[0] for channel in chans if channel is not None ]) else: bake_range_start = min( bake_range_start, min([ channel.range()[0] for channel in chans if channel is not None ])) if bake_range_end is None: bake_range_end = max([ channel.range()[1] for channel in chans if channel is not None ]) else: bake_range_end = max( bake_range_end, max([ channel.range()[1] for channel in chans if channel is not None ])) if blender_object.type == "ARMATURE" and export_settings[ 'gltf_force_sampling'] is True: # We have to store sampled animation data for every deformation bones # Check that there are some anim in this action if bake_range_start is None: return [] # 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 ] for bone in bones_to_be_animated: for p in ["location", "rotation_quaternion", "scale"]: channel = __gather_animation_channel( (), blender_object, export_settings, bone.name, p, bake_range_start, bake_range_end, blender_action.name) channels.append(channel) else: for channel_group in __get_channel_groups(blender_action, blender_object, export_settings): channel_group_sorted = __get_channel_group_sorted( channel_group, blender_object) channel = __gather_animation_channel( channel_group_sorted, blender_object, export_settings, None, None, bake_range_start, bake_range_end, blender_action.name) if channel is not None: channels.append(channel) return channels
def gather_animation_channels(blender_action: bpy.types.Action, blender_object: bpy.types.Object, export_settings ) -> typing.List[gltf2_io.AnimationChannel]: channels = [] # First calculate range of animation for baking # This is need if user set 'Force sampling' and in case we need to bake bake_range_start = None bake_range_end = None groups = __get_channel_groups(blender_action, blender_object, export_settings) # Note: channels has some None items only for SK if some SK are not animated for chans in groups: ranges = [channel.range() for channel in chans if channel is not None] if bake_range_start is None: bake_range_start = min([channel.range()[0] for channel in chans if channel is not None]) else: bake_range_start = min(bake_range_start, min([channel.range()[0] for channel in chans if channel is not None])) if bake_range_end is None: bake_range_end = max([channel.range()[1] for channel in chans if channel is not None]) else: bake_range_end = max(bake_range_end, max([channel.range()[1] for channel in chans if channel is not None])) if blender_object.type == "ARMATURE" and export_settings['gltf_force_sampling'] is True: # We have to store sampled animation data for every deformation bones # Check that there are some anim in this action if bake_range_start is None: return [] # 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] for bone in bones_to_be_animated: for p in ["location", "rotation_quaternion", "scale"]: channel = __gather_animation_channel( (), blender_object, export_settings, bone.name, p, bake_range_start, bake_range_end, blender_action.name, None) channels.append(channel) # Retrieve animation on armature object itself, if any fcurves_armature = __gather_armature_object_channel_groups(blender_action, blender_object, export_settings) for channel_group in fcurves_armature: # No need to sort on armature, that can't have SK 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, blender_action.name, None) 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( fcurves, blender_object, export_settings, None, None, bake_range_start, bake_range_end, blender_action.name, obj) channels.append(channel) else: 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, export_settings, None, None, bake_range_start, bake_range_end, blender_action.name, None) if channel is not None: channels.append(channel) # resetting driver caches gltf2_blender_gather_drivers.get_sk_driver_values.reset_cache() gltf2_blender_gather_drivers.get_sk_drivers.reset_cache() return channels
def __gather_children(blender_object, blender_scene, export_settings): children = [] # 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 child_object = _child_object.proxy if _child_object.proxy else _child_object node = gather_node( child_object, child_object.library.name if child_object.library else None, blender_scene, None, export_settings) if node is not None: children.append(node) # 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 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) # blender bones if blender_object.type == "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 ] def find_parent_joint(joints, name): for joint in joints: if joint.name == name: return joint parent_joint = find_parent_joint(joint.children, name) if parent_joint: return parent_joint return None for child in direct_bone_children: # find parent joint parent_joint = find_parent_joint(root_joints, child.parent_bone) if not parent_joint: continue child_node = gather_node(child, None, None, None, export_settings) if child_node is None: continue parent_joint.children.append(child_node) return children