summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRĂ©mi Verschelde <remi@verschelde.fr>2022-08-23 18:16:15 +0200
committerGitHub <noreply@github.com>2022-08-23 18:16:15 +0200
commit5c5bc21195eac1ce8a80d726d391b004adf44247 (patch)
tree02115fb08c881f692739f95af3e225af6f269588
parentbbe8d2b03efdda9e0fa5484d618e2be576f52b13 (diff)
parentc7e4eeb8a47b78e65dd35b506b559e592ad71e90 (diff)
Merge pull request #63854 from TokageItLab/auto-bone-mapping
-rw-r--r--editor/plugins/bone_map_editor_plugin.cpp931
-rw-r--r--editor/plugins/bone_map_editor_plugin.h65
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.cpp1
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.h1
-rw-r--r--scene/resources/bone_map.cpp12
-rw-r--r--scene/resources/bone_map.h4
-rw-r--r--scene/resources/skeleton_profile.cpp2
7 files changed, 983 insertions, 33 deletions
diff --git a/editor/plugins/bone_map_editor_plugin.cpp b/editor/plugins/bone_map_editor_plugin.cpp
index 70775c1ee2..5db9249af1 100644
--- a/editor/plugins/bone_map_editor_plugin.cpp
+++ b/editor/plugins/bone_map_editor_plugin.cpp
@@ -47,6 +47,9 @@ void BoneMapperButton::fetch_textures() {
set_offset(SIDE_TOP, 0);
set_offset(SIDE_BOTTOM, 0);
+ // Hack to avoid handle color darkening...
+ set_modulate(EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(4.25, 4.25, 4.25));
+
circle = memnew(TextureRect);
circle->set_texture(get_theme_icon(SNAME("BoneMapperHandleCircle"), SNAME("EditorIcons")));
add_child(circle);
@@ -98,14 +101,24 @@ BoneMapperButton::~BoneMapperButton() {
}
void BoneMapperItem::create_editor() {
- skeleton_bone_selector = memnew(EditorPropertyTextEnum);
- skeleton_bone_selector->setup(skeleton_bone_names, false, true);
+ HBoxContainer *hbox = memnew(HBoxContainer);
+ add_child(hbox);
+
+ skeleton_bone_selector = memnew(EditorPropertyText);
skeleton_bone_selector->set_label(profile_bone_name);
skeleton_bone_selector->set_selectable(false);
+ skeleton_bone_selector->set_h_size_flags(SIZE_EXPAND_FILL);
skeleton_bone_selector->set_object_and_property(bone_map.ptr(), "bone_map/" + String(profile_bone_name));
skeleton_bone_selector->update_property();
skeleton_bone_selector->connect("property_changed", callable_mp(this, &BoneMapperItem::_value_changed));
- add_child(skeleton_bone_selector);
+ hbox->add_child(skeleton_bone_selector);
+
+ picker_button = memnew(Button);
+ picker_button->set_icon(get_theme_icon(SNAME("ClassList"), SNAME("EditorIcons")));
+ picker_button->connect("pressed", callable_mp(this, &BoneMapperItem::_open_picker));
+ hbox->add_child(picker_button);
+
+ add_child(memnew(HSeparator));
}
void BoneMapperItem::_update_property() {
@@ -114,6 +127,10 @@ void BoneMapperItem::_update_property() {
}
}
+void BoneMapperItem::_open_picker() {
+ emit_signal(SNAME("pick"), profile_bone_name);
+}
+
void BoneMapperItem::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
bone_map->set(p_property, p_value);
}
@@ -133,25 +150,153 @@ void BoneMapperItem::_notification(int p_what) {
}
void BoneMapperItem::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("pick", PropertyInfo(Variant::STRING_NAME, "profile_bone_name")));
}
-BoneMapperItem::BoneMapperItem(Ref<BoneMap> &p_bone_map, PackedStringArray p_skeleton_bone_names, const StringName &p_profile_bone_name) {
+BoneMapperItem::BoneMapperItem(Ref<BoneMap> &p_bone_map, const StringName &p_profile_bone_name) {
bone_map = p_bone_map;
- skeleton_bone_names = p_skeleton_bone_names;
profile_bone_name = p_profile_bone_name;
}
BoneMapperItem::~BoneMapperItem() {
}
+void BonePicker::create_editors() {
+ set_title(TTR("Bone Picker:"));
+
+ VBoxContainer *vbox = memnew(VBoxContainer);
+ add_child(vbox);
+
+ bones = memnew(Tree);
+ bones->set_select_mode(Tree::SELECT_SINGLE);
+ bones->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ bones->set_hide_root(true);
+ bones->connect("item_activated", callable_mp(this, &BonePicker::_confirm));
+ vbox->add_child(bones);
+
+ create_bones_tree(skeleton);
+}
+
+void BonePicker::create_bones_tree(Skeleton3D *p_skeleton) {
+ bones->clear();
+
+ if (!p_skeleton) {
+ return;
+ }
+
+ TreeItem *root = bones->create_item();
+
+ HashMap<int, TreeItem *> items;
+
+ items.insert(-1, root);
+
+ Ref<Texture> bone_icon = get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons"));
+
+ Vector<int> bones_to_process = p_skeleton->get_parentless_bones();
+ bool is_first = true;
+ while (bones_to_process.size() > 0) {
+ int current_bone_idx = bones_to_process[0];
+ bones_to_process.erase(current_bone_idx);
+
+ Vector<int> current_bone_child_bones = p_skeleton->get_bone_children(current_bone_idx);
+ int child_bone_size = current_bone_child_bones.size();
+ for (int i = 0; i < child_bone_size; i++) {
+ bones_to_process.push_back(current_bone_child_bones[i]);
+ }
+
+ const int parent_idx = p_skeleton->get_bone_parent(current_bone_idx);
+ TreeItem *parent_item = items.find(parent_idx)->value;
+
+ TreeItem *joint_item = bones->create_item(parent_item);
+ items.insert(current_bone_idx, joint_item);
+
+ joint_item->set_text(0, p_skeleton->get_bone_name(current_bone_idx));
+ joint_item->set_icon(0, bone_icon);
+ joint_item->set_selectable(0, true);
+ joint_item->set_metadata(0, "bones/" + itos(current_bone_idx));
+ if (is_first) {
+ is_first = false;
+ } else {
+ joint_item->set_collapsed(true);
+ }
+ }
+}
+
+void BonePicker::_confirm() {
+ _ok_pressed();
+}
+
+void BonePicker::popup_bones_tree(const Size2i &p_minsize) {
+ popup_centered(p_minsize);
+}
+
+bool BonePicker::has_selected_bone() {
+ TreeItem *selected = bones->get_selected();
+ if (!selected) {
+ return false;
+ }
+ return true;
+}
+
+StringName BonePicker::get_selected_bone() {
+ TreeItem *selected = bones->get_selected();
+ if (!selected) {
+ return StringName();
+ }
+ return selected->get_text(0);
+}
+
+void BonePicker::_bind_methods() {
+}
+
+void BonePicker::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ create_editors();
+ } break;
+ }
+}
+
+BonePicker::BonePicker(Skeleton3D *p_skeleton) {
+ skeleton = p_skeleton;
+}
+
+BonePicker::~BonePicker() {
+}
+
void BoneMapper::create_editor() {
+ // Create Bone picker.
+ picker = memnew(BonePicker(skeleton));
+ picker->connect("confirmed", callable_mp(this, &BoneMapper::_apply_picker_selection));
+ add_child(picker, false, INTERNAL_MODE_FRONT);
+
+ profile_selector = memnew(EditorPropertyResource);
+ profile_selector->setup(bone_map.ptr(), "profile", "SkeletonProfile");
+ profile_selector->set_label("Profile");
+ profile_selector->set_selectable(false);
+ profile_selector->set_object_and_property(bone_map.ptr(), "profile");
+ profile_selector->update_property();
+ profile_selector->connect("property_changed", callable_mp(this, &BoneMapper::_profile_changed));
+ add_child(profile_selector);
+ add_child(memnew(HSeparator));
+
+ HBoxContainer *group_hbox = memnew(HBoxContainer);
+ add_child(group_hbox);
+
profile_group_selector = memnew(EditorPropertyEnum);
profile_group_selector->set_label("Group");
profile_group_selector->set_selectable(false);
+ profile_group_selector->set_h_size_flags(SIZE_EXPAND_FILL);
profile_group_selector->set_object_and_property(this, "current_group_idx");
profile_group_selector->update_property();
profile_group_selector->connect("property_changed", callable_mp(this, &BoneMapper::_value_changed));
- add_child(profile_group_selector);
+ group_hbox->add_child(profile_group_selector);
+
+ clear_mapping_button = memnew(Button);
+ clear_mapping_button->set_icon(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")));
+ clear_mapping_button->set_tooltip(TTR("Clear mappings in current group."));
+ clear_mapping_button->connect("pressed", callable_mp(this, &BoneMapper::_clear_mapping_current_group));
+ group_hbox->add_child(clear_mapping_button);
bone_mapper_field = memnew(AspectRatioContainer);
bone_mapper_field->set_stretch_mode(AspectRatioContainer::STRETCH_FIT);
@@ -175,9 +320,6 @@ void BoneMapper::create_editor() {
mapper_item_vbox = memnew(VBoxContainer);
add_child(mapper_item_vbox);
- separator = memnew(HSeparator);
- add_child(separator);
-
recreate_items();
}
@@ -201,6 +343,18 @@ void BoneMapper::update_group_idx() {
}
}
+void BoneMapper::_pick_bone(const StringName &p_bone_name) {
+ picker_key_name = p_bone_name;
+ picker->popup_bones_tree(Size2(500, 500) * EDSCALE);
+}
+
+void BoneMapper::_apply_picker_selection() {
+ if (!picker->has_selected_bone()) {
+ return;
+ }
+ bone_map->set_skeleton_bone_name(picker_key_name, picker->get_selected_bone());
+}
+
void BoneMapper::set_current_group_idx(int p_group_idx) {
current_group_idx = p_group_idx;
recreate_editor();
@@ -282,6 +436,7 @@ void BoneMapper::clear_items() {
// Clear items.
int len = bone_mapper_items.size();
for (int i = 0; i < len; i++) {
+ bone_mapper_items[i]->disconnect("pick", callable_mp(this, &BoneMapper::_pick_bone));
mapper_item_vbox->remove_child(bone_mapper_items[i]);
memdelete(bone_mapper_items[i]);
}
@@ -293,16 +448,11 @@ void BoneMapper::recreate_items() {
// Create items by profile.
Ref<SkeletonProfile> profile = bone_map->get_profile();
if (profile.is_valid()) {
- PackedStringArray skeleton_bone_names;
- int len = skeleton->get_bone_count();
- for (int i = 0; i < len; i++) {
- skeleton_bone_names.push_back(skeleton->get_bone_name(i));
- }
-
- len = profile->get_bone_size();
+ int len = profile->get_bone_size();
for (int i = 0; i < len; i++) {
StringName bn = profile->get_bone_name(i);
- bone_mapper_items.append(memnew(BoneMapperItem(bone_map, skeleton_bone_names, bn)));
+ bone_mapper_items.append(memnew(BoneMapperItem(bone_map, bn)));
+ bone_mapper_items[i]->connect("pick", callable_mp(this, &BoneMapper::_pick_bone), CONNECT_DEFERRED);
mapper_item_vbox->add_child(bone_mapper_items[i]);
}
}
@@ -363,11 +513,754 @@ void BoneMapper::_update_state() {
}
}
+void BoneMapper::_clear_mapping_current_group() {
+ if (bone_map.is_valid()) {
+ Ref<SkeletonProfile> profile = bone_map->get_profile();
+ if (profile.is_valid() && profile->get_group_size() > 0) {
+ int len = profile->get_bone_size();
+ for (int i = 0; i < len; i++) {
+ if (profile->get_group(i) == profile->get_group_name(current_group_idx)) {
+ bone_map->_set_skeleton_bone_name(profile->get_bone_name(i), StringName());
+ }
+ }
+ recreate_items();
+ }
+ }
+}
+
+#ifdef MODULE_REGEX_ENABLED
+int BoneMapper::search_bone_by_name(Skeleton3D *p_skeleton, Vector<String> p_picklist, BoneSegregation p_segregation, int p_parent, int p_child, int p_children_count) {
+ // There may be multiple candidates hit by existing the subsidiary bone.
+ // The one with the shortest name is probably the original.
+ LocalVector<String> hit_list;
+ String shortest = "";
+
+ for (int word_idx = 0; word_idx < p_picklist.size(); word_idx++) {
+ RegEx re = RegEx(p_picklist[word_idx]);
+ if (p_child == -1) {
+ Vector<int> bones_to_process = p_parent == -1 ? p_skeleton->get_parentless_bones() : p_skeleton->get_bone_children(p_parent);
+ while (bones_to_process.size() > 0) {
+ int idx = bones_to_process[0];
+ bones_to_process.erase(idx);
+ Vector<int> children = p_skeleton->get_bone_children(idx);
+ for (int i = 0; i < children.size(); i++) {
+ bones_to_process.push_back(children[i]);
+ }
+
+ if (p_children_count == 0 && children.size() > 0) {
+ continue;
+ }
+ if (p_children_count > 0 && children.size() < p_children_count) {
+ continue;
+ }
+
+ String bn = skeleton->get_bone_name(idx);
+ if (!re.search(bn.to_lower()).is_null() && guess_bone_segregation(bn) == p_segregation) {
+ hit_list.push_back(bn);
+ }
+ }
+
+ if (hit_list.size() > 0) {
+ shortest = hit_list[0];
+ for (uint32_t i = 0; i < hit_list.size(); i++) {
+ if (hit_list[i].length() < shortest.length()) {
+ shortest = hit_list[i]; // Prioritize parent.
+ }
+ }
+ }
+ } else {
+ int idx = skeleton->get_bone_parent(p_child);
+ while (idx != p_parent && idx >= 0) {
+ Vector<int> children = p_skeleton->get_bone_children(idx);
+ if (p_children_count == 0 && children.size() > 0) {
+ continue;
+ }
+ if (p_children_count > 0 && children.size() < p_children_count) {
+ continue;
+ }
+
+ String bn = skeleton->get_bone_name(idx);
+ if (!re.search(bn.to_lower()).is_null() && guess_bone_segregation(bn) == p_segregation) {
+ hit_list.push_back(bn);
+ }
+ idx = skeleton->get_bone_parent(idx);
+ }
+
+ if (hit_list.size() > 0) {
+ shortest = hit_list[0];
+ for (uint32_t i = 0; i < hit_list.size(); i++) {
+ if (hit_list[i].length() <= shortest.length()) {
+ shortest = hit_list[i]; // Prioritize parent.
+ }
+ }
+ }
+ }
+
+ if (shortest != "") {
+ break;
+ }
+ }
+
+ if (shortest == "") {
+ return -1;
+ }
+
+ return skeleton->find_bone(shortest);
+}
+
+BoneMapper::BoneSegregation BoneMapper::guess_bone_segregation(String p_bone_name) {
+ String fixed_bn = p_bone_name.camelcase_to_underscore().to_lower();
+
+ LocalVector<String> left_words;
+ left_words.push_back("(?<![a-zA-Z])left");
+ left_words.push_back("(?<![a-zA-Z0-9])l(?![a-zA-Z0-9])");
+
+ LocalVector<String> right_words;
+ right_words.push_back("(?<![a-zA-Z])right");
+ right_words.push_back("(?<![a-zA-Z0-9])r(?![a-zA-Z0-9])");
+
+ for (uint32_t i = 0; i < left_words.size(); i++) {
+ RegEx re_l = RegEx(left_words[i]);
+ if (!re_l.search(fixed_bn).is_null()) {
+ return BONE_SEGREGATION_LEFT;
+ }
+ RegEx re_r = RegEx(right_words[i]);
+ if (!re_r.search(fixed_bn).is_null()) {
+ return BONE_SEGREGATION_RIGHT;
+ }
+ }
+
+ return BONE_SEGREGATION_NONE;
+}
+
+void BoneMapper::_run_auto_mapping() {
+ auto_mapping_process(bone_map);
+ recreate_items();
+}
+
+void BoneMapper::auto_mapping_process(Ref<BoneMap> &p_bone_map) {
+ WARN_PRINT("Run auto mapping.");
+
+ int bone_idx = -1;
+ Vector<String> picklist; // Use Vector<String> because match words have priority.
+ Vector<int> search_path;
+
+ // 1. Guess Hips
+ picklist.push_back("hip");
+ picklist.push_back("pelvis");
+ picklist.push_back("waist");
+ picklist.push_back("torso");
+ int hips = search_bone_by_name(skeleton, picklist);
+ if (hips == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess Hips. Abort auto mapping.");
+ return; // If there is no Hips, we cannot guess bone after then.
+ } else {
+ p_bone_map->_set_skeleton_bone_name("Hips", skeleton->get_bone_name(hips));
+ }
+ picklist.clear();
+
+ // 2. Guess Root
+ bone_idx = skeleton->get_bone_parent(hips);
+ while (bone_idx >= 0) {
+ search_path.push_back(bone_idx);
+ bone_idx = skeleton->get_bone_parent(bone_idx);
+ }
+ if (search_path.size() == 0) {
+ bone_idx = -1;
+ } else if (search_path.size() == 1) {
+ bone_idx = search_path[0]; // It is only one bone which can be root.
+ } else {
+ bool found = false;
+ for (int i = 0; i < search_path.size(); i++) {
+ RegEx re = RegEx("root");
+ if (!re.search(skeleton->get_bone_name(search_path[i]).to_lower()).is_null()) {
+ bone_idx = search_path[i]; // Name match is preferred.
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ for (int i = 0; i < search_path.size(); i++) {
+ if (Vector3(0, 0, 0).is_equal_approx(skeleton->get_bone_global_rest(search_path[i]).origin)) {
+ bone_idx = search_path[i]; // The bone existing at the origin is appropriate as a root.
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ bone_idx = search_path[search_path.size() - 1]; // Ambiguous, but most parental bone selected.
+ }
+ }
+ if (bone_idx == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess Root."); // Root is not required, so continue.
+ } else {
+ p_bone_map->_set_skeleton_bone_name("Root", skeleton->get_bone_name(bone_idx));
+ }
+ bone_idx = -1;
+ search_path.clear();
+
+ // 3. Guess Neck
+ picklist.push_back("neck");
+ picklist.push_back("head"); // For no neck model.
+ picklist.push_back("face"); // Same above.
+ int neck = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, hips);
+ picklist.clear();
+
+ // 4. Guess Head
+ picklist.push_back("head");
+ picklist.push_back("face");
+ int head = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck);
+ if (head == -1) {
+ search_path = skeleton->get_bone_children(neck);
+ if (search_path.size() == 1) {
+ head = search_path[0]; // Maybe only one child of the Neck is Head.
+ }
+ }
+ if (head == -1) {
+ if (neck != -1) {
+ head = neck; // The head animation should have more movement.
+ neck = -1;
+ p_bone_map->_set_skeleton_bone_name("Head", skeleton->get_bone_name(head));
+ } else {
+ WARN_PRINT("Auto Mapping couldn't guess Neck or Head."); // Continued for guessing on the other bones. But abort when guessing spines step.
+ }
+ } else {
+ p_bone_map->_set_skeleton_bone_name("Neck", skeleton->get_bone_name(neck));
+ p_bone_map->_set_skeleton_bone_name("Head", skeleton->get_bone_name(head));
+ }
+ picklist.clear();
+ search_path.clear();
+
+ int neck_or_head = neck != -1 ? neck : (head != -1 ? head : -1);
+ if (neck_or_head != -1) {
+ // 4-1. Guess Eyes
+ picklist.push_back("eye(?!.*(brow|lash|lid))");
+ bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, neck_or_head);
+ if (bone_idx == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess LeftEye.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("LeftEye", skeleton->get_bone_name(bone_idx));
+ }
+ bone_idx = -1;
+
+ bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, neck_or_head);
+ if (bone_idx == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess RightEye.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("RightEye", skeleton->get_bone_name(bone_idx));
+ }
+ bone_idx = -1;
+ picklist.clear();
+
+ // 4-2. Guess Jaw
+ picklist.push_back("jaw");
+ bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_NONE, neck_or_head);
+ if (bone_idx == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess Jaw.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("Jaw", skeleton->get_bone_name(bone_idx));
+ }
+ bone_idx = -1;
+ picklist.clear();
+ }
+
+ // 5. Guess Foots
+ picklist.push_back("foot");
+ picklist.push_back("ankle");
+ int left_foot = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips);
+ if (left_foot == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess LeftFoot.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("LeftFoot", skeleton->get_bone_name(left_foot));
+ }
+ int right_foot = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips);
+ if (right_foot == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess RightFoot.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("RightFoot", skeleton->get_bone_name(right_foot));
+ }
+ picklist.clear();
+
+ // 5-1. Guess LowerLegs
+ picklist.push_back("(low|under).*leg");
+ picklist.push_back("knee");
+ picklist.push_back("shin");
+ picklist.push_back("calf");
+ picklist.push_back("leg");
+ int left_lower_leg = -1;
+ if (left_foot != -1) {
+ left_lower_leg = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_foot);
+ }
+ if (left_lower_leg == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess LeftLowerLeg.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("LeftLowerLeg", skeleton->get_bone_name(left_lower_leg));
+ }
+ int right_lower_leg = -1;
+ if (right_foot != -1) {
+ right_lower_leg = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_foot);
+ }
+ if (right_lower_leg == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess RightLowerLeg.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("RightLowerLeg", skeleton->get_bone_name(right_lower_leg));
+ }
+ picklist.clear();
+
+ // 5-2. Guess UpperLegs
+ picklist.push_back("up.*leg");
+ picklist.push_back("thigh");
+ picklist.push_back("leg");
+ if (left_lower_leg != -1) {
+ bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_lower_leg);
+ }
+ if (bone_idx == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess LeftUpperLeg.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("LeftUpperLeg", skeleton->get_bone_name(bone_idx));
+ }
+ bone_idx = -1;
+ if (right_lower_leg != -1) {
+ bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_lower_leg);
+ }
+ if (bone_idx == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess RightUpperLeg.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("RightUpperLeg", skeleton->get_bone_name(bone_idx));
+ }
+ bone_idx = -1;
+ picklist.clear();
+
+ // 5-3. Guess Toes
+ picklist.push_back("toe");
+ picklist.push_back("ball");
+ if (left_foot != -1) {
+ bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_foot);
+ if (bone_idx == -1) {
+ search_path = skeleton->get_bone_children(left_foot);
+ if (search_path.size() == 1) {
+ bone_idx = search_path[0]; // Maybe only one child of the Foot is Toes.
+ }
+ search_path.clear();
+ }
+ }
+ if (bone_idx == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess LeftToes.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("LeftToes", skeleton->get_bone_name(bone_idx));
+ }
+ bone_idx = -1;
+ if (right_foot != -1) {
+ bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_foot);
+ if (bone_idx == -1) {
+ search_path = skeleton->get_bone_children(right_foot);
+ if (search_path.size() == 1) {
+ bone_idx = search_path[0]; // Maybe only one child of the Foot is Toes.
+ }
+ search_path.clear();
+ }
+ }
+ if (bone_idx == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess RightToes.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("RightToes", skeleton->get_bone_name(bone_idx));
+ }
+ bone_idx = -1;
+ picklist.clear();
+
+ // 6. Guess Hands
+ picklist.push_back("hand");
+ picklist.push_back("wrist");
+ picklist.push_back("palm");
+ picklist.push_back("fingers");
+ int left_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, -1, 5);
+ if (left_hand_or_palm == -1) {
+ // Ambiguous, but try again for fewer finger models.
+ left_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips);
+ }
+ int left_hand = left_hand_or_palm; // Check for the presence of a wrist, since bones with five children may be palmar.
+ while (left_hand != -1) {
+ bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips, left_hand);
+ if (bone_idx == -1) {
+ break;
+ }
+ left_hand = bone_idx;
+ }
+ if (left_hand == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess LeftHand.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("LeftHand", skeleton->get_bone_name(left_hand));
+ }
+ bone_idx = -1;
+ int right_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, -1, 5);
+ if (right_hand_or_palm == -1) {
+ // Ambiguous, but try again for fewer finger models.
+ right_hand_or_palm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips);
+ }
+ int right_hand = right_hand_or_palm;
+ while (right_hand != -1) {
+ bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips, right_hand);
+ if (bone_idx == -1) {
+ break;
+ }
+ right_hand = bone_idx;
+ }
+ if (right_hand == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess RightHand.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("RightHand", skeleton->get_bone_name(right_hand));
+ }
+ bone_idx = -1;
+ picklist.clear();
+
+ // 6-1. Guess Finger
+ bool named_finger_is_found = false;
+ LocalVector<String> fingers;
+ fingers.push_back("thumb|pollex");
+ fingers.push_back("index|fore");
+ fingers.push_back("middle");
+ fingers.push_back("ring");
+ fingers.push_back("little|pinkie|pinky");
+ if (left_hand_or_palm != -1) {
+ LocalVector<LocalVector<String>> left_fingers_map;
+ left_fingers_map.resize(5);
+ left_fingers_map[0].push_back("LeftThumbMetacarpal");
+ left_fingers_map[0].push_back("LeftThumbProximal");
+ left_fingers_map[0].push_back("LeftThumbDistal");
+ left_fingers_map[1].push_back("LeftIndexProximal");
+ left_fingers_map[1].push_back("LeftIndexIntermediate");
+ left_fingers_map[1].push_back("LeftIndexDistal");
+ left_fingers_map[2].push_back("LeftMiddleProximal");
+ left_fingers_map[2].push_back("LeftMiddleIntermediate");
+ left_fingers_map[2].push_back("LeftMiddleDistal");
+ left_fingers_map[3].push_back("LeftRingProximal");
+ left_fingers_map[3].push_back("LeftRingIntermediate");
+ left_fingers_map[3].push_back("LeftRingDistal");
+ left_fingers_map[4].push_back("LeftLittleProximal");
+ left_fingers_map[4].push_back("LeftLittleIntermediate");
+ left_fingers_map[4].push_back("LeftLittleDistal");
+ for (int i = 0; i < 5; i++) {
+ picklist.push_back(fingers[i]);
+ int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_hand_or_palm, -1, 0);
+ if (finger != -1) {
+ while (finger != left_hand_or_palm && finger >= 0) {
+ search_path.push_back(finger);
+ finger = skeleton->get_bone_parent(finger);
+ }
+ search_path.reverse();
+ if (search_path.size() == 1) {
+ p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
+ named_finger_is_found = true;
+ } else if (search_path.size() == 2) {
+ p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
+ p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
+ named_finger_is_found = true;
+ } else if (search_path.size() >= 3) {
+ search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone.
+ p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
+ p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
+ p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][2], skeleton->get_bone_name(search_path[2]));
+ named_finger_is_found = true;
+ }
+ }
+ picklist.clear();
+ search_path.clear();
+ }
+
+ // It is a bit corner case, but possibly the finger names are sequentially numbered...
+ if (!named_finger_is_found) {
+ picklist.push_back("finger");
+ RegEx finger_re = RegEx("finger");
+ search_path = skeleton->get_bone_children(left_hand_or_palm);
+ Vector<String> finger_names;
+ for (int i = 0; i < search_path.size(); i++) {
+ String bn = skeleton->get_bone_name(search_path[i]);
+ if (!finger_re.search(bn.to_lower()).is_null()) {
+ finger_names.push_back(bn);
+ }
+ }
+ finger_names.sort(); // Order by lexicographic, normal use cases never have more than 10 fingers in one hand.
+ search_path.clear();
+ for (int i = 0; i < finger_names.size(); i++) {
+ if (i >= 5) {
+ break;
+ }
+ int finger_root = skeleton->find_bone(finger_names[i]);
+ int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, finger_root, -1, 0);
+ if (finger != -1) {
+ while (finger != finger_root && finger >= 0) {
+ search_path.push_back(finger);
+ finger = skeleton->get_bone_parent(finger);
+ }
+ }
+ search_path.push_back(finger_root);
+ search_path.reverse();
+ if (search_path.size() == 1) {
+ p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
+ } else if (search_path.size() == 2) {
+ p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
+ p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
+ } else if (search_path.size() >= 3) {
+ search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone.
+ p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
+ p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
+ p_bone_map->_set_skeleton_bone_name(left_fingers_map[i][2], skeleton->get_bone_name(search_path[2]));
+ }
+ search_path.clear();
+ }
+ picklist.clear();
+ }
+ }
+ named_finger_is_found = false;
+ if (right_hand_or_palm != -1) {
+ LocalVector<LocalVector<String>> right_fingers_map;
+ right_fingers_map.resize(5);
+ right_fingers_map[0].push_back("RightThumbMetacarpal");
+ right_fingers_map[0].push_back("RightThumbProximal");
+ right_fingers_map[0].push_back("RightThumbDistal");
+ right_fingers_map[1].push_back("RightIndexProximal");
+ right_fingers_map[1].push_back("RightIndexIntermediate");
+ right_fingers_map[1].push_back("RightIndexDistal");
+ right_fingers_map[2].push_back("RightMiddleProximal");
+ right_fingers_map[2].push_back("RightMiddleIntermediate");
+ right_fingers_map[2].push_back("RightMiddleDistal");
+ right_fingers_map[3].push_back("RightRingProximal");
+ right_fingers_map[3].push_back("RightRingIntermediate");
+ right_fingers_map[3].push_back("RightRingDistal");
+ right_fingers_map[4].push_back("RightLittleProximal");
+ right_fingers_map[4].push_back("RightLittleIntermediate");
+ right_fingers_map[4].push_back("RightLittleDistal");
+ for (int i = 0; i < 5; i++) {
+ picklist.push_back(fingers[i]);
+ int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_hand_or_palm, -1, 0);
+ if (finger != -1) {
+ while (finger != right_hand_or_palm && finger >= 0) {
+ search_path.push_back(finger);
+ finger = skeleton->get_bone_parent(finger);
+ }
+ search_path.reverse();
+ if (search_path.size() == 1) {
+ p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
+ named_finger_is_found = true;
+ } else if (search_path.size() == 2) {
+ p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
+ p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
+ named_finger_is_found = true;
+ } else if (search_path.size() >= 3) {
+ search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone.
+ p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
+ p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
+ p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][2], skeleton->get_bone_name(search_path[2]));
+ named_finger_is_found = true;
+ }
+ }
+ picklist.clear();
+ search_path.clear();
+ }
+
+ // It is a bit corner case, but possibly the finger names are sequentially numbered...
+ if (!named_finger_is_found) {
+ picklist.push_back("finger");
+ RegEx finger_re = RegEx("finger");
+ search_path = skeleton->get_bone_children(right_hand_or_palm);
+ Vector<String> finger_names;
+ for (int i = 0; i < search_path.size(); i++) {
+ String bn = skeleton->get_bone_name(search_path[i]);
+ if (!finger_re.search(bn.to_lower()).is_null()) {
+ finger_names.push_back(bn);
+ }
+ }
+ finger_names.sort(); // Order by lexicographic, normal use cases never have more than 10 fingers in one hand.
+ search_path.clear();
+ for (int i = 0; i < finger_names.size(); i++) {
+ if (i >= 5) {
+ break;
+ }
+ int finger_root = skeleton->find_bone(finger_names[i]);
+ int finger = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, finger_root, -1, 0);
+ if (finger != -1) {
+ while (finger != finger_root && finger >= 0) {
+ search_path.push_back(finger);
+ finger = skeleton->get_bone_parent(finger);
+ }
+ }
+ search_path.push_back(finger_root);
+ search_path.reverse();
+ if (search_path.size() == 1) {
+ p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
+ } else if (search_path.size() == 2) {
+ p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
+ p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
+ } else if (search_path.size() >= 3) {
+ search_path = search_path.slice(-3); // Eliminate the possibility of carpal bone.
+ p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][0], skeleton->get_bone_name(search_path[0]));
+ p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][1], skeleton->get_bone_name(search_path[1]));
+ p_bone_map->_set_skeleton_bone_name(right_fingers_map[i][2], skeleton->get_bone_name(search_path[2]));
+ }
+ search_path.clear();
+ }
+ picklist.clear();
+ }
+ }
+
+ // 7. Guess Arms
+ picklist.push_back("shoulder");
+ picklist.push_back("clavicle");
+ picklist.push_back("collar");
+ int left_shoulder = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, hips);
+ if (left_shoulder == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess LeftShoulder.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("LeftShoulder", skeleton->get_bone_name(left_shoulder));
+ }
+ int right_shoulder = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, hips);
+ if (right_shoulder == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess RightShoulder.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("RightShoulder", skeleton->get_bone_name(right_shoulder));
+ }
+ picklist.clear();
+
+ // 7-1. Guess LowerArms
+ picklist.push_back("(low|fore).*arm");
+ picklist.push_back("elbow");
+ picklist.push_back("arm");
+ int left_lower_arm = -1;
+ if (left_shoulder != -1 && left_hand_or_palm != -1) {
+ left_lower_arm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_shoulder, left_hand_or_palm);
+ }
+ if (left_lower_arm == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess LeftLowerArm.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("LeftLowerArm", skeleton->get_bone_name(left_lower_arm));
+ }
+ int right_lower_arm = -1;
+ if (right_shoulder != -1 && right_hand_or_palm != -1) {
+ right_lower_arm = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_shoulder, right_hand_or_palm);
+ }
+ if (right_lower_arm == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess RightLowerArm.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("RightLowerArm", skeleton->get_bone_name(right_lower_arm));
+ }
+ picklist.clear();
+
+ // 7-2. Guess UpperArms
+ picklist.push_back("up.*arm");
+ picklist.push_back("arm");
+ if (left_shoulder != -1 && left_lower_arm != -1) {
+ bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_LEFT, left_shoulder, left_lower_arm);
+ }
+ if (bone_idx == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess LeftUpperArm.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("LeftUpperArm", skeleton->get_bone_name(bone_idx));
+ }
+ bone_idx = -1;
+ if (right_shoulder != -1 && right_lower_arm != -1) {
+ bone_idx = search_bone_by_name(skeleton, picklist, BONE_SEGREGATION_RIGHT, right_shoulder, right_lower_arm);
+ }
+ if (bone_idx == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess RightUpperArm.");
+ } else {
+ p_bone_map->_set_skeleton_bone_name("RightUpperArm", skeleton->get_bone_name(bone_idx));
+ }
+ bone_idx = -1;
+ picklist.clear();
+
+ // 8. Guess UpperChest or Chest
+ if (neck_or_head == -1) {
+ return; // Abort.
+ }
+ int chest_or_upper_chest = skeleton->get_bone_parent(neck_or_head);
+ bool is_appropriate = true;
+ if (left_shoulder != -1) {
+ bone_idx = skeleton->get_bone_parent(left_shoulder);
+ bool detect = false;
+ while (bone_idx != hips && bone_idx >= 0) {
+ if (bone_idx == chest_or_upper_chest) {
+ detect = true;
+ break;
+ }
+ bone_idx = skeleton->get_bone_parent(bone_idx);
+ }
+ if (!detect) {
+ is_appropriate = false;
+ }
+ bone_idx = -1;
+ }
+ if (right_shoulder != -1) {
+ bone_idx = skeleton->get_bone_parent(right_shoulder);
+ bool detect = false;
+ while (bone_idx != hips && bone_idx >= 0) {
+ if (bone_idx == chest_or_upper_chest) {
+ detect = true;
+ break;
+ }
+ bone_idx = skeleton->get_bone_parent(bone_idx);
+ }
+ if (!detect) {
+ is_appropriate = false;
+ }
+ bone_idx = -1;
+ }
+ if (!is_appropriate) {
+ if (skeleton->get_bone_parent(left_shoulder) == skeleton->get_bone_parent(right_shoulder)) {
+ chest_or_upper_chest = skeleton->get_bone_parent(left_shoulder);
+ } else {
+ chest_or_upper_chest = -1;
+ }
+ }
+ if (chest_or_upper_chest == -1) {
+ WARN_PRINT("Auto Mapping couldn't guess Chest or UpperChest. Abort auto mapping.");
+ return; // Will be not able to guess Spines.
+ }
+
+ // 9. Guess Spines
+ bone_idx = skeleton->get_bone_parent(chest_or_upper_chest);
+ while (bone_idx != hips && bone_idx >= 0) {
+ search_path.push_back(bone_idx);
+ bone_idx = skeleton->get_bone_parent(bone_idx);
+ }
+ search_path.reverse();
+ if (search_path.size() == 0) {
+ p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(chest_or_upper_chest)); // Maybe chibi model...?
+ } else if (search_path.size() == 1) {
+ p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(search_path[0]));
+ p_bone_map->_set_skeleton_bone_name("Chest", skeleton->get_bone_name(chest_or_upper_chest));
+ } else if (search_path.size() >= 2) {
+ p_bone_map->_set_skeleton_bone_name("Spine", skeleton->get_bone_name(search_path[0]));
+ p_bone_map->_set_skeleton_bone_name("Chest", skeleton->get_bone_name(search_path[search_path.size() - 1])); // Probably UppeChest's parent is appropriate.
+ p_bone_map->_set_skeleton_bone_name("UpperChest", skeleton->get_bone_name(chest_or_upper_chest));
+ }
+ bone_idx = -1;
+ search_path.clear();
+
+ WARN_PRINT("Finish auto mapping.");
+}
+#endif // MODULE_REGEX_ENABLED
+
void BoneMapper::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
set(p_property, p_value);
recreate_editor();
}
+void BoneMapper::_profile_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
+ bone_map->set(p_property, p_value);
+
+ // Run auto mapping when setting SkeletonProfileHumanoid by GUI Editor.
+ Ref<SkeletonProfile> profile = bone_map->get_profile();
+ if (profile.is_valid()) {
+ SkeletonProfileHumanoid *hmn = Object::cast_to<SkeletonProfileHumanoid>(profile.ptr());
+ if (hmn) {
+#ifdef MODULE_REGEX_ENABLED
+ _run_auto_mapping();
+#endif // MODULE_REGEX_ENABLED
+ }
+ }
+}
+
void BoneMapper::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_current_group_idx", "current_group_idx"), &BoneMapper::set_current_group_idx);
ClassDB::bind_method(D_METHOD("get_current_group_idx"), &BoneMapper::get_current_group_idx);
@@ -444,10 +1337,6 @@ void BoneMapEditor::_notification(int p_what) {
create_editors();
} break;
case NOTIFICATION_EXIT_TREE: {
- if (bone_mapper) {
- remove_child(bone_mapper);
- bone_mapper->queue_delete();
- }
skeleton = nullptr;
} break;
}
diff --git a/editor/plugins/bone_map_editor_plugin.h b/editor/plugins/bone_map_editor_plugin.h
index 339547ea10..0541ce6eac 100644
--- a/editor/plugins/bone_map_editor_plugin.h
+++ b/editor/plugins/bone_map_editor_plugin.h
@@ -34,6 +34,12 @@
#include "editor/editor_node.h"
#include "editor/editor_plugin.h"
#include "editor/editor_properties.h"
+
+#include "modules/modules_enabled.gen.h" // For regex.
+#ifdef MODULE_REGEX_ENABLED
+#include "modules/regex/regex.h"
+#endif
+
#include "scene/3d/skeleton_3d.h"
#include "scene/gui/color_rect.h"
#include "scene/gui/dialogs.h"
@@ -79,12 +85,13 @@ class BoneMapperItem : public VBoxContainer {
int button_id = -1;
StringName profile_bone_name;
- PackedStringArray skeleton_bone_names;
Ref<BoneMap> bone_map;
- EditorPropertyTextEnum *skeleton_bone_selector;
+ EditorPropertyText *skeleton_bone_selector;
+ Button *picker_button;
void _update_property();
+ void _open_picker();
protected:
void _notification(int p_what);
@@ -95,20 +102,49 @@ protected:
public:
void assign_button_id(int p_button_id);
- BoneMapperItem(Ref<BoneMap> &p_bone_map, PackedStringArray p_skeleton_bone_names, const StringName &p_profile_bone_name = StringName());
+ BoneMapperItem(Ref<BoneMap> &p_bone_map, const StringName &p_profile_bone_name = StringName());
~BoneMapperItem();
};
+class BonePicker : public AcceptDialog {
+ GDCLASS(BonePicker, AcceptDialog);
+
+ Skeleton3D *skeleton = nullptr;
+ Tree *bones = nullptr;
+
+public:
+ void popup_bones_tree(const Size2i &p_minsize = Size2i());
+ bool has_selected_bone();
+ StringName get_selected_bone();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+ void _confirm();
+
+private:
+ void create_editors();
+ void create_bones_tree(Skeleton3D *p_skeleton);
+
+public:
+ BonePicker(Skeleton3D *p_skeleton);
+ ~BonePicker();
+};
+
class BoneMapper : public VBoxContainer {
GDCLASS(BoneMapper, VBoxContainer);
Skeleton3D *skeleton;
Ref<BoneMap> bone_map;
+ EditorPropertyResource *profile_selector;
+
Vector<BoneMapperItem *> bone_mapper_items;
+ Button *clear_mapping_button;
+
VBoxContainer *mapper_item_vbox;
- HSeparator *separator;
int current_group_idx = 0;
int current_bone_idx = -1;
@@ -126,10 +162,31 @@ class BoneMapper : public VBoxContainer {
void update_group_idx();
void _update_state();
+ /* Bone picker */
+ BonePicker *picker = nullptr;
+ StringName picker_key_name;
+ void _pick_bone(const StringName &p_bone_name);
+ void _apply_picker_selection();
+ void _clear_mapping_current_group();
+
+#ifdef MODULE_REGEX_ENABLED
+ /* For auto mapping */
+ enum BoneSegregation {
+ BONE_SEGREGATION_NONE,
+ BONE_SEGREGATION_LEFT,
+ BONE_SEGREGATION_RIGHT
+ };
+ int search_bone_by_name(Skeleton3D *p_skeleton, Vector<String> p_picklist, BoneSegregation p_segregation = BONE_SEGREGATION_NONE, int p_parent = -1, int p_child = -1, int p_children_count = -1);
+ BoneSegregation guess_bone_segregation(String p_bone_name);
+ void auto_mapping_process(Ref<BoneMap> &p_bone_map);
+ void _run_auto_mapping();
+#endif // MODULE_REGEX_ENABLED
+
protected:
void _notification(int p_what);
static void _bind_methods();
virtual void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing);
+ virtual void _profile_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing);
public:
void set_current_group_idx(int p_group_idx);
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index 1e4ef217f0..c25f2bb25c 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -31,7 +31,6 @@
#include "skeleton_3d_editor_plugin.h"
#include "core/io/resource_saver.h"
-#include "editor/editor_file_dialog.h"
#include "editor/editor_node.h"
#include "editor/editor_properties.h"
#include "editor/editor_scale.h"
diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h
index f51d4e60e8..9747ed8374 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.h
+++ b/editor/plugins/skeleton_3d_editor_plugin.h
@@ -31,6 +31,7 @@
#ifndef SKELETON_3D_EDITOR_PLUGIN_H
#define SKELETON_3D_EDITOR_PLUGIN_H
+#include "editor/editor_file_dialog.h"
#include "editor/editor_plugin.h"
#include "editor/editor_properties.h"
#include "node_3d_editor_plugin.h"
diff --git a/scene/resources/bone_map.cpp b/scene/resources/bone_map.cpp
index f4697a09b8..dfaf82f36a 100644
--- a/scene/resources/bone_map.cpp
+++ b/scene/resources/bone_map.cpp
@@ -82,9 +82,13 @@ StringName BoneMap::get_skeleton_bone_name(StringName p_profile_bone_name) const
return bone_map.get(p_profile_bone_name);
}
-void BoneMap::set_skeleton_bone_name(StringName p_profile_bone_name, const StringName p_skeleton_bone_name) {
+void BoneMap::_set_skeleton_bone_name(StringName p_profile_bone_name, const StringName p_skeleton_bone_name) {
ERR_FAIL_COND(!bone_map.has(p_profile_bone_name));
bone_map.insert(p_profile_bone_name, p_skeleton_bone_name);
+}
+
+void BoneMap::set_skeleton_bone_name(StringName p_profile_bone_name, const StringName p_skeleton_bone_name) {
+ _set_skeleton_bone_name(p_profile_bone_name, p_skeleton_bone_name);
emit_signal("bone_map_updated");
}
@@ -167,8 +171,10 @@ void BoneMap::_bind_methods() {
ADD_SIGNAL(MethodInfo("profile_updated"));
}
-void BoneMap::_validate_property(PropertyInfo &p_property) const {
- //
+void BoneMap::_validate_property(PropertyInfo &property) const {
+ if (property.name == "bonemap" || property.name == "profile") {
+ property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
}
BoneMap::BoneMap() {
diff --git a/scene/resources/bone_map.h b/scene/resources/bone_map.h
index e1bb571df9..a07a776e27 100644
--- a/scene/resources/bone_map.h
+++ b/scene/resources/bone_map.h
@@ -50,9 +50,6 @@ protected:
static void _bind_methods();
public:
- int get_profile_type() const;
- void set_profile_type(const int p_profile_type);
-
Ref<SkeletonProfile> get_profile() const;
void set_profile(const Ref<SkeletonProfile> &p_profile);
@@ -60,6 +57,7 @@ public:
StringName get_skeleton_bone_name(StringName p_profile_bone_name) const;
void set_skeleton_bone_name(StringName p_profile_bone_name, const StringName p_skeleton_bone_name);
+ void _set_skeleton_bone_name(StringName p_profile_bone_name, const StringName p_skeleton_bone_name); // Avoid to emit signal for editor.
StringName find_profile_bone_name(StringName p_skeleton_bone_name) const;
diff --git a/scene/resources/skeleton_profile.cpp b/scene/resources/skeleton_profile.cpp
index 875d2dcff7..1367ea86dd 100644
--- a/scene/resources/skeleton_profile.cpp
+++ b/scene/resources/skeleton_profile.cpp
@@ -506,7 +506,7 @@ SkeletonProfileHumanoid::SkeletonProfileHumanoid() {
bones.write[5].reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.1, 0);
bones.write[5].handle_offset = Vector2(0.5, 0.23);
bones.write[5].group = "Body";
- bones.write[5].require = true;
+ bones.write[5].require = false;
bones.write[6].bone_name = "Head";
bones.write[6].bone_parent = "Neck";