godot/editor/plugins/bone_map_editor_plugin.cpp

440 lines
15 KiB
C++

/*************************************************************************/
/* bone_map_editor_plugin.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "bone_map_editor_plugin.h"
#include "editor/editor_scale.h"
#include "editor/import/post_import_plugin_skeleton_renamer.h"
#include "editor/import/scene_import_settings.h"
void BoneMapperButton::fetch_textures() {
if (selected) {
set_normal_texture(get_theme_icon(SNAME("BoneMapperHandleSelected"), SNAME("EditorIcons")));
} else {
set_normal_texture(get_theme_icon(SNAME("BoneMapperHandle"), SNAME("EditorIcons")));
}
set_offset(SIDE_LEFT, 0);
set_offset(SIDE_RIGHT, 0);
set_offset(SIDE_TOP, 0);
set_offset(SIDE_BOTTOM, 0);
circle = memnew(TextureRect);
circle->set_texture(get_theme_icon(SNAME("BoneMapperHandleCircle"), SNAME("EditorIcons")));
add_child(circle);
set_state(BONE_MAP_STATE_UNSET);
}
StringName BoneMapperButton::get_profile_bone_name() const {
return profile_bone_name;
}
void BoneMapperButton::set_state(BoneMapState p_state) {
switch (p_state) {
case BONE_MAP_STATE_UNSET: {
circle->set_modulate(EditorSettings::get_singleton()->get("editors/bone_mapper/handle_colors/unset"));
} break;
case BONE_MAP_STATE_SET: {
circle->set_modulate(EditorSettings::get_singleton()->get("editors/bone_mapper/handle_colors/set"));
} break;
case BONE_MAP_STATE_ERROR: {
circle->set_modulate(EditorSettings::get_singleton()->get("editors/bone_mapper/handle_colors/error"));
} break;
default: {
} break;
}
}
void BoneMapperButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
fetch_textures();
} break;
}
}
BoneMapperButton::BoneMapperButton(const StringName p_profile_bone_name, bool p_selected) {
profile_bone_name = p_profile_bone_name;
selected = p_selected;
}
BoneMapperButton::~BoneMapperButton() {
}
void BoneMapperItem::create_editor() {
skeleton_bone_selector = memnew(EditorPropertyTextEnum);
skeleton_bone_selector->setup(skeleton_bone_names);
skeleton_bone_selector->set_label(profile_bone_name);
skeleton_bone_selector->set_selectable(false);
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);
}
void BoneMapperItem::_update_property() {
if (skeleton_bone_selector->get_edited_object() && skeleton_bone_selector->get_edited_property()) {
skeleton_bone_selector->update_property();
}
}
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);
}
void BoneMapperItem::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
create_editor();
bone_map->connect("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property));
} break;
case NOTIFICATION_EXIT_TREE: {
if (!bone_map.is_null() && bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property))) {
bone_map->disconnect("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property));
}
} break;
}
}
void BoneMapperItem::_bind_methods() {
}
BoneMapperItem::BoneMapperItem(Ref<BoneMap> &p_bone_map, PackedStringArray p_skeleton_bone_names, 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 BoneMapper::create_editor() {
profile_group_selector = memnew(EditorPropertyEnum);
profile_group_selector->set_label("Group");
profile_group_selector->set_selectable(false);
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);
bone_mapper_field = memnew(AspectRatioContainer);
bone_mapper_field->set_stretch_mode(AspectRatioContainer::STRETCH_FIT);
bone_mapper_field->set_custom_minimum_size(Vector2(0, 256.0) * EDSCALE);
bone_mapper_field->set_h_size_flags(Control::SIZE_FILL);
add_child(bone_mapper_field);
profile_bg = memnew(ColorRect);
profile_bg->set_color(Color(0, 0, 0, 1));
profile_bg->set_h_size_flags(Control::SIZE_FILL);
profile_bg->set_v_size_flags(Control::SIZE_FILL);
bone_mapper_field->add_child(profile_bg);
profile_texture = memnew(TextureRect);
profile_texture->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
profile_texture->set_ignore_texture_size(true);
profile_texture->set_h_size_flags(Control::SIZE_FILL);
profile_texture->set_v_size_flags(Control::SIZE_FILL);
bone_mapper_field->add_child(profile_texture);
mapper_item_vbox = memnew(VBoxContainer);
add_child(mapper_item_vbox);
separator = memnew(HSeparator);
add_child(separator);
recreate_items();
}
void BoneMapper::update_group_idx() {
if (!bone_map->get_profile().is_valid()) {
return;
}
PackedStringArray group_names;
int len = bone_map->get_profile()->get_group_size();
for (int i = 0; i < len; i++) {
group_names.push_back(bone_map->get_profile()->get_group_name(i));
}
if (current_group_idx >= len) {
current_group_idx = 0;
}
if (len > 0) {
profile_group_selector->setup(group_names);
profile_group_selector->update_property();
profile_group_selector->set_read_only(false);
}
}
void BoneMapper::set_current_group_idx(int p_group_idx) {
current_group_idx = p_group_idx;
recreate_editor();
}
int BoneMapper::get_current_group_idx() const {
return current_group_idx;
}
void BoneMapper::set_current_bone_idx(int p_bone_idx) {
current_bone_idx = p_bone_idx;
recreate_editor();
}
int BoneMapper::get_current_bone_idx() const {
return current_bone_idx;
}
void BoneMapper::recreate_editor() {
// Clear buttons.
int len = bone_mapper_buttons.size();
for (int i = 0; i < len; i++) {
profile_texture->remove_child(bone_mapper_buttons[i]);
memdelete(bone_mapper_buttons[i]);
}
bone_mapper_buttons.clear();
// Organize mapper items.
len = bone_mapper_items.size();
for (int i = 0; i < len; i++) {
bone_mapper_items[i]->set_visible(current_bone_idx == i);
}
Ref<SkeletonProfile> profile = bone_map->get_profile();
if (profile.is_valid()) {
SkeletonProfileHumanoid *hmn = Object::cast_to<SkeletonProfileHumanoid>(profile.ptr());
if (hmn) {
StringName hmn_group_name = profile->get_group_name(current_group_idx);
if (hmn_group_name == "Body") {
profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanBody"), SNAME("EditorIcons")));
} else if (hmn_group_name == "Face") {
profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanFace"), SNAME("EditorIcons")));
} else if (hmn_group_name == "LeftHand") {
profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanLeftHand"), SNAME("EditorIcons")));
} else if (hmn_group_name == "RightHand") {
profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanRightHand"), SNAME("EditorIcons")));
}
} else {
profile_texture->set_texture(profile->get_texture(current_group_idx));
}
} else {
profile_texture->set_texture(Ref<Texture2D>());
}
if (!profile.is_valid()) {
return;
}
for (int i = 0; i < len; i++) {
if (profile->get_group(i) == profile->get_group_name(current_group_idx)) {
BoneMapperButton *mb = memnew(BoneMapperButton(profile->get_bone_name(i), current_bone_idx == i));
mb->connect("pressed", callable_mp(this, &BoneMapper::set_current_bone_idx), varray(i), CONNECT_DEFERRED);
mb->set_h_grow_direction(GROW_DIRECTION_BOTH);
mb->set_v_grow_direction(GROW_DIRECTION_BOTH);
Vector2 vc = profile->get_handle_offset(i);
bone_mapper_buttons.push_back(mb);
profile_texture->add_child(mb);
mb->set_anchor(SIDE_LEFT, vc.x);
mb->set_anchor(SIDE_RIGHT, vc.x);
mb->set_anchor(SIDE_TOP, vc.y);
mb->set_anchor(SIDE_BOTTOM, vc.y);
}
}
_update_state();
}
void BoneMapper::clear_items() {
// Clear items.
int len = bone_mapper_items.size();
for (int i = 0; i < len; i++) {
mapper_item_vbox->remove_child(bone_mapper_items[i]);
memdelete(bone_mapper_items[i]);
}
bone_mapper_items.clear();
}
void BoneMapper::recreate_items() {
clear_items();
// Create items by profile.
Ref<SkeletonProfile> profile = bone_map->get_profile();
if (profile.is_valid()) {
PackedStringArray skeleton_bone_names;
skeleton_bone_names.push_back(String());
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();
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)));
mapper_item_vbox->add_child(bone_mapper_items[i]);
}
}
update_group_idx();
recreate_editor();
}
void BoneMapper::_update_state() {
int len = bone_mapper_buttons.size();
for (int i = 0; i < len; i++) {
StringName sbn = bone_map->get_skeleton_bone_name(bone_mapper_buttons[i]->get_profile_bone_name());
if (skeleton->find_bone(sbn) >= 0) {
if (bone_map->get_skeleton_bone_name_count(sbn) == 1) {
bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_SET);
} else {
bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_ERROR);
}
} else {
bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_UNSET);
}
}
}
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::_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);
ClassDB::bind_method(D_METHOD("set_current_bone_idx", "current_bone_idx"), &BoneMapper::set_current_bone_idx);
ClassDB::bind_method(D_METHOD("get_current_bone_idx"), &BoneMapper::get_current_bone_idx);
ADD_PROPERTY(PropertyInfo(Variant::INT, "current_group_idx"), "set_current_group_idx", "get_current_group_idx");
ADD_PROPERTY(PropertyInfo(Variant::INT, "current_bone_idx"), "set_current_bone_idx", "get_current_bone_idx");
}
void BoneMapper::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
create_editor();
bone_map->connect("bone_map_updated", callable_mp(this, &BoneMapper::_update_state));
bone_map->connect("profile_updated", callable_mp(this, &BoneMapper::recreate_items));
} break;
case NOTIFICATION_EXIT_TREE: {
clear_items();
if (!bone_map.is_null()) {
if (bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapper::_update_state))) {
bone_map->disconnect("bone_map_updated", callable_mp(this, &BoneMapper::_update_state));
}
if (bone_map->is_connected("profile_updated", callable_mp(this, &BoneMapper::recreate_items))) {
bone_map->disconnect("profile_updated", callable_mp(this, &BoneMapper::recreate_items));
}
}
}
}
}
BoneMapper::BoneMapper(Skeleton3D *p_skeleton, Ref<BoneMap> &p_bone_map) {
skeleton = p_skeleton;
bone_map = p_bone_map;
}
BoneMapper::~BoneMapper() {
}
void BoneMapEditor::create_editors() {
if (!skeleton) {
return;
}
bone_mapper = memnew(BoneMapper(skeleton, bone_map));
add_child(bone_mapper);
}
void BoneMapEditor::fetch_objects() {
// Hackey... but it may be the easist way to get a selected object from "ImporterScene".
SceneImportSettings *si = SceneImportSettings::get_singleton();
if (!si) {
return;
}
Node *selected = si->get_selected_node();
if (selected) {
Skeleton3D *sk = Object::cast_to<Skeleton3D>(selected);
if (!sk) {
return;
}
skeleton = sk;
} else {
// Editor should not exist.
skeleton = nullptr;
}
}
void BoneMapEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
fetch_objects();
create_editors();
} break;
case NOTIFICATION_EXIT_TREE: {
remove_child(bone_mapper);
bone_mapper->queue_delete();
}
}
}
BoneMapEditor::BoneMapEditor(Ref<BoneMap> &p_bone_map) {
bone_map = p_bone_map;
}
BoneMapEditor::~BoneMapEditor() {
}
bool EditorInspectorPluginBoneMap::can_handle(Object *p_object) {
return Object::cast_to<BoneMap>(p_object) != nullptr;
}
void EditorInspectorPluginBoneMap::parse_begin(Object *p_object) {
BoneMap *bm = Object::cast_to<BoneMap>(p_object);
if (!bm) {
return;
}
Ref<BoneMap> r(bm);
editor = memnew(BoneMapEditor(r));
add_custom_control(editor);
}
BoneMapEditorPlugin::BoneMapEditorPlugin() {
// Register properties in editor settings.
EDITOR_DEF("editors/bone_mapper/handle_colors/set", Color(0.1, 0.6, 0.25));
EDITOR_DEF("editors/bone_mapper/handle_colors/error", Color(0.8, 0.2, 0.2));
EDITOR_DEF("editors/bone_mapper/handle_colors/unset", Color(0.3, 0.3, 0.3));
Ref<EditorInspectorPluginBoneMap> inspector_plugin;
inspector_plugin.instantiate();
add_inspector_plugin(inspector_plugin);
Ref<PostImportPluginSkeletonRenamer> post_import_plugin_renamer;
post_import_plugin_renamer.instantiate();
add_scene_post_import_plugin(post_import_plugin_renamer);
}