@tool class_name BoneFlipper extends SkeletonModifier3D @export var flip: bool = false @export_enum(" ") var _bones_to_flip: Array[String] = [] @export var _flip_all_bones: bool = true @export_enum(" ") var _bones_to_exclude: Array[String] = [] @export var _bone_prefixes_to_exclude: Array[String] = [] var _skeleton: Skeleton3D func _validate_property(property: Dictionary) -> void: if property.name in ["_bones_to_flip", "_bones_to_exclude"]: if _skeleton: property.hint = PROPERTY_HINT_TYPE_STRING property.hint_string = ( "%d/%d:%s" % [ TYPE_STRING, PROPERTY_HINT_ENUM, _skeleton.get_concatenated_bone_names() ] ) func _ready() -> void: _skeleton = get_skeleton() assert(_skeleton, str(self) + ": _skeleton missing!") for bone_name in _bones_to_flip: var bone_idx := _skeleton.find_bone(bone_name) assert(bone_idx != -1, str(self) + ": bone " + bone_name + " missing!") for bone_name in _bones_to_exclude: var bone_idx := _skeleton.find_bone(bone_name) assert(bone_idx != -1, str(self) + ": bone " + bone_name + " missing!") func _process_modification() -> void: if !_skeleton: return if !flip: for bone_idx in range(_skeleton.get_bone_count()): _skeleton.set_bone_pose(bone_idx, _skeleton.get_bone_pose(bone_idx)) return if _flip_all_bones: for bone_idx in range(_skeleton.get_bone_count()): _flip_bone_if_not_excluded(bone_idx) else: for bone_name in _bones_to_flip: var bone_idx := _skeleton.find_bone(bone_name) if bone_idx == -1: continue _flip_bone(bone_idx, bone_name) func _flip_bone_if_not_excluded(bone_idx: int) -> void: var bone_name := _skeleton.get_bone_name(bone_idx) if bone_name in _bones_to_exclude: return for bone_prefix in _bone_prefixes_to_exclude: if bone_prefix and bone_name.begins_with(bone_prefix): return _flip_bone(bone_idx, bone_name) func _flip_bone(bone_idx: int, bone_name: String) -> void: var pose: Transform3D = _skeleton.get_bone_pose(bone_idx) if bone_name.ends_with("_R") or (bone_name.ends_with("_L") and !_flip_all_bones): var other_side_bone_name: String = ( bone_name.replace("_R", "_L") if bone_name.ends_with("_R") else bone_name.replace("_L", "_R") ) var other_side_bone_idx := _skeleton.find_bone(other_side_bone_name) if other_side_bone_idx == -1: return var other_side_pose: Transform3D = _skeleton.get_bone_pose(other_side_bone_idx) _mirror_bone_transform(bone_idx, other_side_pose) _mirror_bone_transform(other_side_bone_idx, pose) elif !bone_name.ends_with("_R") and !bone_name.ends_with("_L"): _mirror_bone_transform(bone_idx, pose) func _mirror_bone_transform(bone_idx: int, pose: Transform3D) -> void: pose.origin.x = -pose.origin.x var pose_rot := pose.basis.get_euler() var pose_scale := pose.basis.get_scale() pose_rot.y = -pose_rot.y pose_rot.z = -pose_rot.z pose.basis = Basis.from_euler(pose_rot) pose.basis = pose.basis.scaled(pose_scale) _skeleton.set_bone_pose(bone_idx, pose)