add BoneFlipper for flipping bone poses

This commit is contained in:
2025-07-02 21:07:14 +10:00
parent 32c9e1db9b
commit 74e8222894
4 changed files with 120 additions and 9 deletions

View File

@@ -93,7 +93,7 @@ func _ready() -> void:
continue
for bone_name in bone_to_flatten.bone_names:
var bone_idx := _skeleton.find_bone(bone_name)
assert(bone_idx != -1, bone_name + " missing!")
assert(bone_idx != -1, str(self) + ": bone " + bone_name + " missing!")
func _process_modification() -> void:

View File

@@ -0,0 +1,93 @@
@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 == "_bones_to_flip" or property.name == "_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 or !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()
pose_rot.z = -pose_rot.z
pose_rot.y = -pose_rot.y
pose.basis = Basis.from_euler(pose_rot)
_skeleton.set_bone_pose(bone_idx, pose)

View File

@@ -0,0 +1 @@
uid://bmbbkpkqtugwy