Add per-bone meta to Skeleton3D
Individual bones are not represented as `Node`s in Godot, in order to support meta functionality for them the skeleton has to carry the information similarly to how other per-bone properties are handled. - Also adds support for GLTF import/export
This commit is contained in:
parent
6daa6a8513
commit
0468bea899
|
@ -99,6 +99,21 @@
|
|||
Returns the global rest transform for [param bone_idx].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_meta" qualifiers="const">
|
||||
<return type="Variant" />
|
||||
<param index="0" name="bone_idx" type="int" />
|
||||
<param index="1" name="key" type="StringName" />
|
||||
<description>
|
||||
Returns bone metadata for [param bone_idx] with [param key].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_meta_list" qualifiers="const">
|
||||
<return type="StringName[]" />
|
||||
<param index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Returns a list of all metadata keys for [param bone_idx].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_name" qualifiers="const">
|
||||
<return type="String" />
|
||||
<param index="0" name="bone_idx" type="int" />
|
||||
|
@ -171,6 +186,14 @@
|
|||
Use for invalidating caches in IK solvers and other nodes which process bones.
|
||||
</description>
|
||||
</method>
|
||||
<method name="has_bone_meta" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<param index="0" name="bone_idx" type="int" />
|
||||
<param index="1" name="key" type="StringName" />
|
||||
<description>
|
||||
Returns whether there exists any bone metadata for [param bone_idx] with key [param key].
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_bone_enabled" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<param index="0" name="bone_idx" type="int" />
|
||||
|
@ -263,6 +286,15 @@
|
|||
[b]Note:[/b] The pose transform needs to be a global pose! To convert a world transform from a [Node3D] to a global bone pose, multiply the [method Transform3D.affine_inverse] of the node's [member Node3D.global_transform] by the desired world transform.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_meta">
|
||||
<return type="void" />
|
||||
<param index="0" name="bone_idx" type="int" />
|
||||
<param index="1" name="key" type="StringName" />
|
||||
<param index="2" name="value" type="Variant" />
|
||||
<description>
|
||||
Sets bone metadata for [param bone_idx], will set the [param key] meta to [param value].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_name">
|
||||
<return type="void" />
|
||||
<param index="0" name="bone_idx" type="int" />
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/**************************************************************************/
|
||||
/* add_metadata_dialog.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "add_metadata_dialog.h"
|
||||
|
||||
AddMetadataDialog::AddMetadataDialog() {
|
||||
VBoxContainer *vbc = memnew(VBoxContainer);
|
||||
add_child(vbc);
|
||||
|
||||
HBoxContainer *hbc = memnew(HBoxContainer);
|
||||
vbc->add_child(hbc);
|
||||
hbc->add_child(memnew(Label(TTR("Name:"))));
|
||||
|
||||
add_meta_name = memnew(LineEdit);
|
||||
add_meta_name->set_custom_minimum_size(Size2(200 * EDSCALE, 1));
|
||||
hbc->add_child(add_meta_name);
|
||||
hbc->add_child(memnew(Label(TTR("Type:"))));
|
||||
|
||||
add_meta_type = memnew(OptionButton);
|
||||
|
||||
hbc->add_child(add_meta_type);
|
||||
|
||||
Control *spacing = memnew(Control);
|
||||
vbc->add_child(spacing);
|
||||
spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE));
|
||||
|
||||
set_ok_button_text(TTR("Add"));
|
||||
register_text_enter(add_meta_name);
|
||||
|
||||
validation_panel = memnew(EditorValidationPanel);
|
||||
vbc->add_child(validation_panel);
|
||||
validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name is valid."));
|
||||
validation_panel->set_update_callback(callable_mp(this, &AddMetadataDialog::_check_meta_name));
|
||||
validation_panel->set_accept_button(get_ok_button());
|
||||
|
||||
add_meta_name->connect(SceneStringName(text_changed), callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
|
||||
}
|
||||
|
||||
void AddMetadataDialog::_complete_init(const StringName &p_title) {
|
||||
add_meta_name->grab_focus();
|
||||
add_meta_name->set_text("");
|
||||
validation_panel->update();
|
||||
|
||||
set_title(vformat(TTR("Add Metadata Property for \"%s\""), p_title));
|
||||
|
||||
// Skip if we already completed the initialization.
|
||||
if (add_meta_type->get_item_count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Theme icons can be retrieved only the Window has been initialized.
|
||||
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
|
||||
if (i == Variant::NIL || i == Variant::RID || i == Variant::CALLABLE || i == Variant::SIGNAL) {
|
||||
continue; //not editable by inspector.
|
||||
}
|
||||
String type = i == Variant::OBJECT ? String("Resource") : Variant::get_type_name(Variant::Type(i));
|
||||
|
||||
add_meta_type->add_icon_item(get_editor_theme_icon(type), type, i);
|
||||
}
|
||||
}
|
||||
|
||||
void AddMetadataDialog::open(const StringName p_title, List<StringName> &p_existing_metas) {
|
||||
this->_existing_metas = p_existing_metas;
|
||||
_complete_init(p_title);
|
||||
popup_centered();
|
||||
}
|
||||
|
||||
StringName AddMetadataDialog::get_meta_name() {
|
||||
return add_meta_name->get_text();
|
||||
}
|
||||
|
||||
Variant AddMetadataDialog::get_meta_defval() {
|
||||
Variant defval;
|
||||
Callable::CallError ce;
|
||||
Variant::construct(Variant::Type(add_meta_type->get_selected_id()), defval, nullptr, 0, ce);
|
||||
return defval;
|
||||
}
|
||||
|
||||
void AddMetadataDialog::_check_meta_name() {
|
||||
const String meta_name = add_meta_name->get_text();
|
||||
|
||||
if (meta_name.is_empty()) {
|
||||
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name can't be empty."), EditorValidationPanel::MSG_ERROR);
|
||||
} else if (!meta_name.is_valid_ascii_identifier()) {
|
||||
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name must be a valid identifier."), EditorValidationPanel::MSG_ERROR);
|
||||
} else if (_existing_metas.find(meta_name)) {
|
||||
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, vformat(TTR("Metadata with name \"%s\" already exists."), meta_name), EditorValidationPanel::MSG_ERROR);
|
||||
} else if (meta_name[0] == '_') {
|
||||
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Names starting with _ are reserved for editor-only metadata."), EditorValidationPanel::MSG_ERROR);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/**************************************************************************/
|
||||
/* add_metadata_dialog.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ADD_METADATA_DIALOG_H
|
||||
#define ADD_METADATA_DIALOG_H
|
||||
|
||||
#include "core/object/callable_method_pointer.h"
|
||||
#include "editor/editor_help.h"
|
||||
#include "editor/editor_undo_redo_manager.h"
|
||||
#include "editor/gui/editor_validation_panel.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/item_list.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/option_button.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class AddMetadataDialog : public ConfirmationDialog {
|
||||
GDCLASS(AddMetadataDialog, ConfirmationDialog);
|
||||
|
||||
public:
|
||||
AddMetadataDialog();
|
||||
void open(const StringName p_title, List<StringName> &p_existing_metas);
|
||||
|
||||
StringName get_meta_name();
|
||||
Variant get_meta_defval();
|
||||
|
||||
private:
|
||||
List<StringName> _existing_metas;
|
||||
|
||||
void _check_meta_name();
|
||||
void _complete_init(const StringName &p_label);
|
||||
|
||||
LineEdit *add_meta_name = nullptr;
|
||||
OptionButton *add_meta_type = nullptr;
|
||||
EditorValidationPanel *validation_panel = nullptr;
|
||||
};
|
||||
#endif // ADD_METADATA_DIALOG_H
|
|
@ -32,6 +32,7 @@
|
|||
#include "editor_inspector.compat.inc"
|
||||
|
||||
#include "core/os/keyboard.h"
|
||||
#include "editor/add_metadata_dialog.h"
|
||||
#include "editor/doc_tools.h"
|
||||
#include "editor/editor_feature_profile.h"
|
||||
#include "editor/editor_main_screen.h"
|
||||
|
@ -4245,92 +4246,33 @@ Variant EditorInspector::get_property_clipboard() const {
|
|||
return property_clipboard;
|
||||
}
|
||||
|
||||
void EditorInspector::_add_meta_confirm() {
|
||||
String name = add_meta_name->get_text();
|
||||
|
||||
object->editor_set_section_unfold("metadata", true); // Ensure metadata is unfolded when adding a new metadata.
|
||||
|
||||
Variant defval;
|
||||
Callable::CallError ce;
|
||||
Variant::construct(Variant::Type(add_meta_type->get_selected_id()), defval, nullptr, 0, ce);
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(vformat(TTR("Add metadata %s"), name));
|
||||
undo_redo->add_do_method(object, "set_meta", name, defval);
|
||||
undo_redo->add_undo_method(object, "remove_meta", name);
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void EditorInspector::_check_meta_name() {
|
||||
const String meta_name = add_meta_name->get_text();
|
||||
|
||||
if (meta_name.is_empty()) {
|
||||
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name can't be empty."), EditorValidationPanel::MSG_ERROR);
|
||||
} else if (!meta_name.is_valid_ascii_identifier()) {
|
||||
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name must be a valid identifier."), EditorValidationPanel::MSG_ERROR);
|
||||
} else if (object->has_meta(meta_name)) {
|
||||
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, vformat(TTR("Metadata with name \"%s\" already exists."), meta_name), EditorValidationPanel::MSG_ERROR);
|
||||
} else if (meta_name[0] == '_') {
|
||||
validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Names starting with _ are reserved for editor-only metadata."), EditorValidationPanel::MSG_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorInspector::_show_add_meta_dialog() {
|
||||
if (!add_meta_dialog) {
|
||||
add_meta_dialog = memnew(ConfirmationDialog);
|
||||
|
||||
VBoxContainer *vbc = memnew(VBoxContainer);
|
||||
add_meta_dialog->add_child(vbc);
|
||||
|
||||
HBoxContainer *hbc = memnew(HBoxContainer);
|
||||
vbc->add_child(hbc);
|
||||
hbc->add_child(memnew(Label(TTR("Name:"))));
|
||||
|
||||
add_meta_name = memnew(LineEdit);
|
||||
add_meta_name->set_custom_minimum_size(Size2(200 * EDSCALE, 1));
|
||||
hbc->add_child(add_meta_name);
|
||||
hbc->add_child(memnew(Label(TTR("Type:"))));
|
||||
|
||||
add_meta_type = memnew(OptionButton);
|
||||
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
|
||||
if (i == Variant::NIL || i == Variant::RID || i == Variant::CALLABLE || i == Variant::SIGNAL) {
|
||||
continue; //not editable by inspector.
|
||||
}
|
||||
String type = i == Variant::OBJECT ? String("Resource") : Variant::get_type_name(Variant::Type(i));
|
||||
|
||||
add_meta_type->add_icon_item(get_editor_theme_icon(type), type, i);
|
||||
}
|
||||
hbc->add_child(add_meta_type);
|
||||
|
||||
Control *spacing = memnew(Control);
|
||||
vbc->add_child(spacing);
|
||||
spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE));
|
||||
|
||||
add_meta_dialog->set_ok_button_text(TTR("Add"));
|
||||
add_child(add_meta_dialog);
|
||||
add_meta_dialog->register_text_enter(add_meta_name);
|
||||
add_meta_dialog = memnew(AddMetadataDialog());
|
||||
add_meta_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorInspector::_add_meta_confirm));
|
||||
|
||||
validation_panel = memnew(EditorValidationPanel);
|
||||
vbc->add_child(validation_panel);
|
||||
validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name is valid."));
|
||||
validation_panel->set_update_callback(callable_mp(this, &EditorInspector::_check_meta_name));
|
||||
validation_panel->set_accept_button(add_meta_dialog->get_ok_button());
|
||||
|
||||
add_meta_name->connect(SceneStringName(text_changed), callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
|
||||
add_child(add_meta_dialog);
|
||||
}
|
||||
|
||||
StringName dialog_title;
|
||||
Node *node = Object::cast_to<Node>(object);
|
||||
if (node) {
|
||||
add_meta_dialog->set_title(vformat(TTR("Add Metadata Property for \"%s\""), node->get_name()));
|
||||
} else {
|
||||
// This should normally be reached when the object is derived from Resource.
|
||||
add_meta_dialog->set_title(vformat(TTR("Add Metadata Property for \"%s\""), object->get_class()));
|
||||
}
|
||||
// If object is derived from Node use node name, if derived from Resource use classname.
|
||||
dialog_title = node ? node->get_name() : StringName(object->get_class());
|
||||
|
||||
add_meta_dialog->popup_centered();
|
||||
add_meta_name->grab_focus();
|
||||
add_meta_name->set_text("");
|
||||
validation_panel->update();
|
||||
List<StringName> existing_meta_keys;
|
||||
object->get_meta_list(&existing_meta_keys);
|
||||
add_meta_dialog->open(dialog_title, existing_meta_keys);
|
||||
}
|
||||
|
||||
void EditorInspector::_add_meta_confirm() {
|
||||
// Ensure metadata is unfolded when adding a new metadata.
|
||||
object->editor_set_section_unfold("metadata", true);
|
||||
|
||||
String name = add_meta_dialog->get_meta_name();
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(vformat(TTR("Add metadata %s"), name));
|
||||
undo_redo->add_do_method(object, "set_meta", name, add_meta_dialog->get_meta_defval());
|
||||
undo_redo->add_undo_method(object, "remove_meta", name);
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void EditorInspector::_bind_methods() {
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#ifndef EDITOR_INSPECTOR_H
|
||||
#define EDITOR_INSPECTOR_H
|
||||
|
||||
#include "editor/add_metadata_dialog.h"
|
||||
#include "editor_property_name_processor.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/scroll_container.h"
|
||||
|
@ -575,14 +576,13 @@ class EditorInspector : public ScrollContainer {
|
|||
|
||||
bool _is_property_disabled_by_feature_profile(const StringName &p_property);
|
||||
|
||||
ConfirmationDialog *add_meta_dialog = nullptr;
|
||||
AddMetadataDialog *add_meta_dialog = nullptr;
|
||||
LineEdit *add_meta_name = nullptr;
|
||||
OptionButton *add_meta_type = nullptr;
|
||||
EditorValidationPanel *validation_panel = nullptr;
|
||||
|
||||
void _add_meta_confirm();
|
||||
void _show_add_meta_dialog();
|
||||
void _check_meta_name();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
#include "scene/resources/skeleton_profile.h"
|
||||
#include "scene/resources/surface_tool.h"
|
||||
|
||||
void BoneTransformEditor::create_editors() {
|
||||
void BonePropertiesEditor::create_editors() {
|
||||
section = memnew(EditorInspectorSection);
|
||||
section->setup("trf_properties", label, this, Color(0.0f, 0.0f, 0.0f), true);
|
||||
section->unfold();
|
||||
|
@ -61,7 +61,7 @@ void BoneTransformEditor::create_editors() {
|
|||
enabled_checkbox = memnew(EditorPropertyCheck());
|
||||
enabled_checkbox->set_label("Pose Enabled");
|
||||
enabled_checkbox->set_selectable(false);
|
||||
enabled_checkbox->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
|
||||
enabled_checkbox->connect("property_changed", callable_mp(this, &BonePropertiesEditor::_value_changed));
|
||||
section->get_vbox()->add_child(enabled_checkbox);
|
||||
|
||||
// Position property.
|
||||
|
@ -69,8 +69,8 @@ void BoneTransformEditor::create_editors() {
|
|||
position_property->setup(-10000, 10000, 0.001, true);
|
||||
position_property->set_label("Position");
|
||||
position_property->set_selectable(false);
|
||||
position_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
|
||||
position_property->connect("property_keyed", callable_mp(this, &BoneTransformEditor::_property_keyed));
|
||||
position_property->connect("property_changed", callable_mp(this, &BonePropertiesEditor::_value_changed));
|
||||
position_property->connect("property_keyed", callable_mp(this, &BonePropertiesEditor::_property_keyed));
|
||||
section->get_vbox()->add_child(position_property);
|
||||
|
||||
// Rotation property.
|
||||
|
@ -78,8 +78,8 @@ void BoneTransformEditor::create_editors() {
|
|||
rotation_property->setup(-10000, 10000, 0.001, true);
|
||||
rotation_property->set_label("Rotation");
|
||||
rotation_property->set_selectable(false);
|
||||
rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
|
||||
rotation_property->connect("property_keyed", callable_mp(this, &BoneTransformEditor::_property_keyed));
|
||||
rotation_property->connect("property_changed", callable_mp(this, &BonePropertiesEditor::_value_changed));
|
||||
rotation_property->connect("property_keyed", callable_mp(this, &BonePropertiesEditor::_property_keyed));
|
||||
section->get_vbox()->add_child(rotation_property);
|
||||
|
||||
// Scale property.
|
||||
|
@ -87,8 +87,8 @@ void BoneTransformEditor::create_editors() {
|
|||
scale_property->setup(-10000, 10000, 0.001, true, true);
|
||||
scale_property->set_label("Scale");
|
||||
scale_property->set_selectable(false);
|
||||
scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed));
|
||||
scale_property->connect("property_keyed", callable_mp(this, &BoneTransformEditor::_property_keyed));
|
||||
scale_property->connect("property_changed", callable_mp(this, &BonePropertiesEditor::_value_changed));
|
||||
scale_property->connect("property_keyed", callable_mp(this, &BonePropertiesEditor::_property_keyed));
|
||||
section->get_vbox()->add_child(scale_property);
|
||||
|
||||
// Transform/Matrix section.
|
||||
|
@ -102,23 +102,37 @@ void BoneTransformEditor::create_editors() {
|
|||
rest_matrix->set_label("Transform");
|
||||
rest_matrix->set_selectable(false);
|
||||
rest_section->get_vbox()->add_child(rest_matrix);
|
||||
|
||||
// Bone Metadata property
|
||||
meta_section = memnew(EditorInspectorSection);
|
||||
meta_section->setup("bone_meta", TTR("Bone Metadata"), this, Color(.0f, .0f, .0f), true);
|
||||
section->get_vbox()->add_child(meta_section);
|
||||
|
||||
add_metadata_button = EditorInspector::create_inspector_action_button(TTR("Add Bone Metadata"));
|
||||
add_metadata_button->connect(SceneStringName(pressed), callable_mp(this, &BonePropertiesEditor::_show_add_meta_dialog));
|
||||
section->get_vbox()->add_child(add_metadata_button);
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->connect("version_changed", callable_mp(this, &BonePropertiesEditor::_update_properties));
|
||||
undo_redo->connect("history_changed", callable_mp(this, &BonePropertiesEditor::_update_properties));
|
||||
}
|
||||
|
||||
void BoneTransformEditor::_notification(int p_what) {
|
||||
void BonePropertiesEditor::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
const Color section_color = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor));
|
||||
section->set_bg_color(section_color);
|
||||
rest_section->set_bg_color(section_color);
|
||||
add_metadata_button->set_icon(get_editor_theme_icon(SNAME("Add")));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void BoneTransformEditor::_value_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {
|
||||
if (updating) {
|
||||
void BonePropertiesEditor::_value_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {
|
||||
if (updating || !skeleton) {
|
||||
return;
|
||||
}
|
||||
if (skeleton) {
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS);
|
||||
undo_redo->add_undo_property(skeleton, p_property, skeleton->get(p_property));
|
||||
|
@ -131,21 +145,93 @@ void BoneTransformEditor::_value_changed(const String &p_property, const Variant
|
|||
}
|
||||
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
}
|
||||
|
||||
BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) :
|
||||
void BonePropertiesEditor::_meta_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {
|
||||
if (!skeleton || p_property.get_slicec('/', 2) != "bone_meta") {
|
||||
return;
|
||||
}
|
||||
|
||||
int bone = p_property.get_slicec('/', 1).to_int();
|
||||
if (bone >= skeleton->get_bone_count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = p_property.get_slicec('/', 3);
|
||||
if (!skeleton->has_bone_meta(1, key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(vformat(TTR("Modify metadata '%s' for bone '%s'"), key, skeleton->get_bone_name(bone)));
|
||||
undo_redo->add_do_property(skeleton, p_property, p_value);
|
||||
undo_redo->add_do_method(meta_editors[p_property], "update_property");
|
||||
undo_redo->add_undo_property(skeleton, p_property, skeleton->get_bone_meta(bone, key));
|
||||
undo_redo->add_undo_method(meta_editors[p_property], "update_property");
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void BonePropertiesEditor::_meta_deleted(const String &p_property) {
|
||||
if (!skeleton || p_property.get_slicec('/', 2) != "bone_meta") {
|
||||
return;
|
||||
}
|
||||
|
||||
int bone = p_property.get_slicec('/', 1).to_int();
|
||||
if (bone >= skeleton->get_bone_count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = p_property.get_slicec('/', 3);
|
||||
if (!skeleton->has_bone_meta(1, key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(vformat(TTR("Remove metadata '%s' from bone '%s'"), key, skeleton->get_bone_name(bone)));
|
||||
undo_redo->add_do_property(skeleton, p_property, Variant());
|
||||
undo_redo->add_undo_property(skeleton, p_property, skeleton->get_bone_meta(bone, key));
|
||||
undo_redo->commit_action();
|
||||
|
||||
emit_signal(SNAME("property_deleted"), p_property);
|
||||
}
|
||||
|
||||
void BonePropertiesEditor::_show_add_meta_dialog() {
|
||||
if (!add_meta_dialog) {
|
||||
add_meta_dialog = memnew(AddMetadataDialog());
|
||||
add_meta_dialog->connect(SceneStringName(confirmed), callable_mp(this, &BonePropertiesEditor::_add_meta_confirm));
|
||||
add_child(add_meta_dialog);
|
||||
}
|
||||
|
||||
int bone = Skeleton3DEditor::get_singleton()->get_selected_bone();
|
||||
StringName dialog_title = skeleton->get_bone_name(bone);
|
||||
|
||||
List<StringName> existing_meta_keys;
|
||||
skeleton->get_bone_meta_list(bone, &existing_meta_keys);
|
||||
add_meta_dialog->open(dialog_title, existing_meta_keys);
|
||||
}
|
||||
|
||||
void BonePropertiesEditor::_add_meta_confirm() {
|
||||
int bone = Skeleton3DEditor::get_singleton()->get_selected_bone();
|
||||
String name = add_meta_dialog->get_meta_name();
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(vformat(TTR("Add metadata '%s' to bone '%s'"), name, skeleton->get_bone_name(bone)));
|
||||
undo_redo->add_do_method(skeleton, "set_bone_meta", bone, name, add_meta_dialog->get_meta_defval());
|
||||
undo_redo->add_undo_method(skeleton, "set_bone_meta", bone, name, Variant());
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
BonePropertiesEditor::BonePropertiesEditor(Skeleton3D *p_skeleton) :
|
||||
skeleton(p_skeleton) {
|
||||
create_editors();
|
||||
}
|
||||
|
||||
void BoneTransformEditor::set_keyable(const bool p_keyable) {
|
||||
void BonePropertiesEditor::set_keyable(const bool p_keyable) {
|
||||
position_property->set_keying(p_keyable);
|
||||
rotation_property->set_keying(p_keyable);
|
||||
scale_property->set_keying(p_keyable);
|
||||
}
|
||||
|
||||
void BoneTransformEditor::set_target(const String &p_prop) {
|
||||
void BonePropertiesEditor::set_target(const String &p_prop) {
|
||||
enabled_checkbox->set_object_and_property(skeleton, p_prop + "enabled");
|
||||
enabled_checkbox->update_property();
|
||||
|
||||
|
@ -162,7 +248,7 @@ void BoneTransformEditor::set_target(const String &p_prop) {
|
|||
rest_matrix->update_property();
|
||||
}
|
||||
|
||||
void BoneTransformEditor::_property_keyed(const String &p_path, bool p_advance) {
|
||||
void BonePropertiesEditor::_property_keyed(const String &p_path, bool p_advance) {
|
||||
AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();
|
||||
if (!te || !te->has_keying()) {
|
||||
return;
|
||||
|
@ -183,16 +269,17 @@ void BoneTransformEditor::_property_keyed(const String &p_path, bool p_advance)
|
|||
}
|
||||
}
|
||||
|
||||
void BoneTransformEditor::_update_properties() {
|
||||
void BonePropertiesEditor::_update_properties() {
|
||||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
int selected = Skeleton3DEditor::get_singleton()->get_selected_bone();
|
||||
List<PropertyInfo> props;
|
||||
HashSet<StringName> meta_seen;
|
||||
skeleton->get_property_list(&props);
|
||||
for (const PropertyInfo &E : props) {
|
||||
PackedStringArray split = E.name.split("/");
|
||||
if (split.size() == 3 && split[0] == "bones") {
|
||||
if (split.size() >= 3 && split[0] == "bones") {
|
||||
if (split[1].to_int() == selected) {
|
||||
if (split[2] == "enabled") {
|
||||
enabled_checkbox->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY);
|
||||
|
@ -224,9 +311,35 @@ void BoneTransformEditor::_update_properties() {
|
|||
rest_matrix->update_editor_property_status();
|
||||
rest_matrix->queue_redraw();
|
||||
}
|
||||
if (split[2] == "bone_meta") {
|
||||
meta_seen.insert(E.name);
|
||||
if (!meta_editors.find(E.name)) {
|
||||
EditorProperty *editor = EditorInspectorDefaultPlugin::get_editor_for_property(skeleton, E.type, E.name, PROPERTY_HINT_NONE, "", E.usage);
|
||||
editor->set_label(split[3]);
|
||||
editor->set_object_and_property(skeleton, E.name);
|
||||
editor->set_deletable(true);
|
||||
editor->set_selectable(false);
|
||||
editor->connect("property_changed", callable_mp(this, &BonePropertiesEditor::_meta_changed));
|
||||
editor->connect("property_deleted", callable_mp(this, &BonePropertiesEditor::_meta_deleted));
|
||||
|
||||
meta_section->get_vbox()->add_child(editor);
|
||||
editor->update_property();
|
||||
editor->update_editor_property_status();
|
||||
editor->queue_redraw();
|
||||
|
||||
meta_editors[E.name] = editor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// UI for any bone metadata prop not seen during the iteration has to be deleted
|
||||
for (KeyValue<StringName, EditorProperty *> iter : meta_editors) {
|
||||
if (!meta_seen.has(iter.key)) {
|
||||
callable_mp((Node *)meta_section->get_vbox(), &Node::remove_child).call_deferred(iter.value);
|
||||
meta_editors.remove(meta_editors.find(iter.key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Skeleton3DEditor *Skeleton3DEditor::singleton = nullptr;
|
||||
|
@ -992,7 +1105,7 @@ void Skeleton3DEditor::create_editors() {
|
|||
SET_DRAG_FORWARDING_GCD(joint_tree, Skeleton3DEditor);
|
||||
s_con->add_child(joint_tree);
|
||||
|
||||
pose_editor = memnew(BoneTransformEditor(skeleton));
|
||||
pose_editor = memnew(BonePropertiesEditor(skeleton));
|
||||
pose_editor->set_label(TTR("Bone Transform"));
|
||||
pose_editor->set_visible(false);
|
||||
add_child(pose_editor);
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#ifndef SKELETON_3D_EDITOR_PLUGIN_H
|
||||
#define SKELETON_3D_EDITOR_PLUGIN_H
|
||||
|
||||
#include "editor/add_metadata_dialog.h"
|
||||
#include "editor/editor_properties.h"
|
||||
#include "editor/gui/editor_file_dialog.h"
|
||||
#include "editor/plugins/editor_plugin.h"
|
||||
|
@ -50,8 +51,8 @@ class Tree;
|
|||
class TreeItem;
|
||||
class VSeparator;
|
||||
|
||||
class BoneTransformEditor : public VBoxContainer {
|
||||
GDCLASS(BoneTransformEditor, VBoxContainer);
|
||||
class BonePropertiesEditor : public VBoxContainer {
|
||||
GDCLASS(BonePropertiesEditor, VBoxContainer);
|
||||
|
||||
EditorInspectorSection *section = nullptr;
|
||||
|
||||
|
@ -63,6 +64,10 @@ class BoneTransformEditor : public VBoxContainer {
|
|||
EditorInspectorSection *rest_section = nullptr;
|
||||
EditorPropertyTransform3D *rest_matrix = nullptr;
|
||||
|
||||
EditorInspectorSection *meta_section = nullptr;
|
||||
AddMetadataDialog *add_meta_dialog = nullptr;
|
||||
Button *add_metadata_button = nullptr;
|
||||
|
||||
Rect2 background_rects[5];
|
||||
|
||||
Skeleton3D *skeleton = nullptr;
|
||||
|
@ -79,11 +84,18 @@ class BoneTransformEditor : public VBoxContainer {
|
|||
|
||||
void _property_keyed(const String &p_path, bool p_advance);
|
||||
|
||||
void _meta_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing);
|
||||
void _meta_deleted(const String &p_property);
|
||||
void _show_add_meta_dialog();
|
||||
void _add_meta_confirm();
|
||||
|
||||
HashMap<StringName, EditorProperty *> meta_editors;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
BoneTransformEditor(Skeleton3D *p_skeleton);
|
||||
BonePropertiesEditor(Skeleton3D *p_skeleton);
|
||||
|
||||
// Which transform target to modify.
|
||||
void set_target(const String &p_prop);
|
||||
|
@ -123,8 +135,8 @@ class Skeleton3DEditor : public VBoxContainer {
|
|||
};
|
||||
|
||||
Tree *joint_tree = nullptr;
|
||||
BoneTransformEditor *rest_editor = nullptr;
|
||||
BoneTransformEditor *pose_editor = nullptr;
|
||||
BonePropertiesEditor *rest_editor = nullptr;
|
||||
BonePropertiesEditor *pose_editor = nullptr;
|
||||
|
||||
HBoxContainer *topmenu_bar = nullptr;
|
||||
MenuButton *skeleton_options = nullptr;
|
||||
|
|
|
@ -5534,6 +5534,10 @@ void GLTFDocument::_convert_skeleton_to_gltf(Skeleton3D *p_skeleton3d, Ref<GLTFS
|
|||
joint_node->set_name(_gen_unique_name(p_state, skeleton->get_bone_name(bone_i)));
|
||||
joint_node->transform = skeleton->get_bone_pose(bone_i);
|
||||
joint_node->joint = true;
|
||||
|
||||
if (p_skeleton3d->has_bone_meta(bone_i, "extras")) {
|
||||
joint_node->set_meta("extras", p_skeleton3d->get_bone_meta(bone_i, "extras"));
|
||||
}
|
||||
GLTFNodeIndex current_node_i = p_state->nodes.size();
|
||||
p_state->scene_nodes.insert(current_node_i, skeleton);
|
||||
p_state->nodes.push_back(joint_node);
|
||||
|
|
|
@ -602,6 +602,11 @@ Error SkinTool::_create_skeletons(
|
|||
skeleton->set_bone_pose_rotation(bone_index, node->transform.basis.get_rotation_quaternion());
|
||||
skeleton->set_bone_pose_scale(bone_index, node->transform.basis.get_scale());
|
||||
|
||||
// Store bone-level GLTF extras in skeleton per bone meta.
|
||||
if (node->has_meta("extras")) {
|
||||
skeleton->set_bone_meta(bone_index, "extras", node->get_meta("extras"));
|
||||
}
|
||||
|
||||
if (node->parent >= 0 && nodes[node->parent]->skeleton == skel_i) {
|
||||
const int bone_parent = skeleton->find_bone(nodes[node->parent]->get_name());
|
||||
ERR_FAIL_COND_V(bone_parent < 0, FAILED);
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
#include "modules/gltf/gltf_document.h"
|
||||
#include "modules/gltf/gltf_state.h"
|
||||
#include "scene/3d/mesh_instance_3d.h"
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
#include "scene/main/window.h"
|
||||
#include "scene/resources/3d/primitive_meshes.h"
|
||||
#include "scene/resources/material.h"
|
||||
|
@ -158,6 +159,62 @@ TEST_CASE("[SceneTree][Node] GLTF test mesh and material meta export and import"
|
|||
memdelete(original);
|
||||
memdelete(loaded);
|
||||
}
|
||||
|
||||
TEST_CASE("[SceneTree][Node] GLTF test skeleton and bone export and import") {
|
||||
// Setup scene.
|
||||
Skeleton3D *skeleton = memnew(Skeleton3D);
|
||||
skeleton->set_name("skeleton");
|
||||
Dictionary skeleton_extras;
|
||||
skeleton_extras["node_type"] = "skeleton";
|
||||
skeleton->set_meta("extras", skeleton_extras);
|
||||
|
||||
skeleton->add_bone("parent");
|
||||
skeleton->set_bone_rest(0, Transform3D());
|
||||
Dictionary parent_bone_extras;
|
||||
parent_bone_extras["bone"] = "i_am_parent_bone";
|
||||
skeleton->set_bone_meta(0, "extras", parent_bone_extras);
|
||||
|
||||
skeleton->add_bone("child");
|
||||
skeleton->set_bone_rest(1, Transform3D());
|
||||
skeleton->set_bone_parent(1, 0);
|
||||
Dictionary child_bone_extras;
|
||||
child_bone_extras["bone"] = "i_am_child_bone";
|
||||
skeleton->set_bone_meta(1, "extras", child_bone_extras);
|
||||
|
||||
// We have to have a mesh to link with skeleton or it will not get imported.
|
||||
Ref<PlaneMesh> meshdata = memnew(PlaneMesh);
|
||||
meshdata->set_name("planemesh");
|
||||
|
||||
MeshInstance3D *mesh = memnew(MeshInstance3D);
|
||||
mesh->set_mesh(meshdata);
|
||||
mesh->set_name("mesh_instance_3d");
|
||||
|
||||
Node3D *scene = memnew(Node3D);
|
||||
SceneTree::get_singleton()->get_root()->add_child(scene);
|
||||
scene->add_child(skeleton);
|
||||
scene->add_child(mesh);
|
||||
scene->set_name("node3d");
|
||||
|
||||
// Now that both skeleton and mesh are part of scene, link them.
|
||||
mesh->set_skeleton_path(mesh->get_path_to(skeleton));
|
||||
|
||||
// Convert to GLFT and back.
|
||||
String tempfile = OS::get_singleton()->get_cache_path().path_join("gltf_bone_extras");
|
||||
Node *loaded = _gltf_export_then_import(scene, tempfile);
|
||||
|
||||
// Compare the results.
|
||||
CHECK(loaded->get_name() == "node3d");
|
||||
Skeleton3D *result = Object::cast_to<Skeleton3D>(loaded->find_child("Skeleton3D", false, true));
|
||||
CHECK(result->get_bone_name(0) == "parent");
|
||||
CHECK(Dictionary(result->get_bone_meta(0, "extras"))["bone"] == "i_am_parent_bone");
|
||||
CHECK(result->get_bone_name(1) == "child");
|
||||
CHECK(Dictionary(result->get_bone_meta(1, "extras"))["bone"] == "i_am_child_bone");
|
||||
|
||||
memdelete(skeleton);
|
||||
memdelete(mesh);
|
||||
memdelete(scene);
|
||||
memdelete(loaded);
|
||||
}
|
||||
} // namespace TestGltfExtras
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
|
|
|
@ -103,6 +103,8 @@ bool Skeleton3D::_set(const StringName &p_path, const Variant &p_value) {
|
|||
set_bone_pose_rotation(which, p_value);
|
||||
} else if (what == "scale") {
|
||||
set_bone_pose_scale(which, p_value);
|
||||
} else if (what == "bone_meta") {
|
||||
set_bone_meta(which, path.get_slicec('/', 3), p_value);
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
} else if (what == "pose" || what == "bound_children") {
|
||||
// Kept for compatibility from 3.x to 4.x.
|
||||
|
@ -170,6 +172,8 @@ bool Skeleton3D::_get(const StringName &p_path, Variant &r_ret) const {
|
|||
r_ret = get_bone_pose_rotation(which);
|
||||
} else if (what == "scale") {
|
||||
r_ret = get_bone_pose_scale(which);
|
||||
} else if (what == "bone_meta") {
|
||||
r_ret = get_bone_meta(which, path.get_slicec('/', 3));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -187,6 +191,11 @@ void Skeleton3D::_get_property_list(List<PropertyInfo> *p_list) const {
|
|||
p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("position"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::QUATERNION, prep + PNAME("rotation"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, prep + PNAME("scale"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
|
||||
for (const KeyValue<StringName, Variant> &K : bones[i].metadata) {
|
||||
PropertyInfo pi = PropertyInfo(bones[i].metadata[K.key].get_type(), prep + PNAME("bone_meta/") + K.key, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR);
|
||||
p_list->push_back(pi);
|
||||
}
|
||||
}
|
||||
|
||||
for (PropertyInfo &E : *p_list) {
|
||||
|
@ -531,6 +540,57 @@ void Skeleton3D::set_bone_name(int p_bone, const String &p_name) {
|
|||
version++;
|
||||
}
|
||||
|
||||
Variant Skeleton3D::get_bone_meta(int p_bone, const StringName &p_key) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, Variant());
|
||||
|
||||
if (!bones[p_bone].metadata.has(p_key)) {
|
||||
return Variant();
|
||||
}
|
||||
return bones[p_bone].metadata[p_key];
|
||||
}
|
||||
|
||||
TypedArray<StringName> Skeleton3D::_get_bone_meta_list_bind(int p_bone) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, TypedArray<StringName>());
|
||||
|
||||
TypedArray<StringName> _metaret;
|
||||
for (const KeyValue<StringName, Variant> &K : bones[p_bone].metadata) {
|
||||
_metaret.push_back(K.key);
|
||||
}
|
||||
return _metaret;
|
||||
}
|
||||
|
||||
void Skeleton3D::get_bone_meta_list(int p_bone, List<StringName> *p_list) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
for (const KeyValue<StringName, Variant> &K : bones[p_bone].metadata) {
|
||||
p_list->push_back(K.key);
|
||||
}
|
||||
}
|
||||
|
||||
bool Skeleton3D::has_bone_meta(int p_bone, const StringName &p_key) const {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX_V(p_bone, bone_size, false);
|
||||
|
||||
return bones[p_bone].metadata.has(p_key);
|
||||
}
|
||||
|
||||
void Skeleton3D::set_bone_meta(int p_bone, const StringName &p_key, const Variant &p_value) {
|
||||
const int bone_size = bones.size();
|
||||
ERR_FAIL_INDEX(p_bone, bone_size);
|
||||
|
||||
if (p_value.get_type() == Variant::NIL) {
|
||||
if (bones.write[p_bone].metadata.has(p_key)) {
|
||||
bones.write[p_bone].metadata.erase(p_key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bones.write[p_bone].metadata.insert(p_key, p_value, false);
|
||||
}
|
||||
|
||||
bool Skeleton3D::is_bone_parent_of(int p_bone, int p_parent_bone_id) const {
|
||||
int parent_of_bone = get_bone_parent(p_bone);
|
||||
|
||||
|
@ -1014,6 +1074,11 @@ void Skeleton3D::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("get_bone_name", "bone_idx"), &Skeleton3D::get_bone_name);
|
||||
ClassDB::bind_method(D_METHOD("set_bone_name", "bone_idx", "name"), &Skeleton3D::set_bone_name);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_bone_meta", "bone_idx", "key"), &Skeleton3D::get_bone_meta);
|
||||
ClassDB::bind_method(D_METHOD("get_bone_meta_list", "bone_idx"), &Skeleton3D::_get_bone_meta_list_bind);
|
||||
ClassDB::bind_method(D_METHOD("has_bone_meta", "bone_idx", "key"), &Skeleton3D::has_bone_meta);
|
||||
ClassDB::bind_method(D_METHOD("set_bone_meta", "bone_idx", "key", "value"), &Skeleton3D::set_bone_meta);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_concatenated_bone_names"), &Skeleton3D::get_concatenated_bone_names);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_bone_parent", "bone_idx"), &Skeleton3D::get_bone_parent);
|
||||
|
|
|
@ -116,6 +116,8 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
HashMap<StringName, Variant> metadata;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
Transform3D pose_global_no_override;
|
||||
real_t global_pose_override_amount = 0.0;
|
||||
|
@ -193,6 +195,7 @@ protected:
|
|||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
void _notification(int p_what);
|
||||
TypedArray<StringName> _get_bone_meta_list_bind(int p_bone) const;
|
||||
static void _bind_methods();
|
||||
|
||||
virtual void add_child_notify(Node *p_child) override;
|
||||
|
@ -238,6 +241,12 @@ public:
|
|||
void set_motion_scale(float p_motion_scale);
|
||||
float get_motion_scale() const;
|
||||
|
||||
// bone metadata
|
||||
Variant get_bone_meta(int p_bone, const StringName &p_key) const;
|
||||
void get_bone_meta_list(int p_bone, List<StringName> *p_list) const;
|
||||
bool has_bone_meta(int p_bone, const StringName &p_key) const;
|
||||
void set_bone_meta(int p_bone, const StringName &p_key, const Variant &p_value);
|
||||
|
||||
// Posing API
|
||||
Transform3D get_bone_pose(int p_bone) const;
|
||||
Vector3 get_bone_pose_position(int p_bone) const;
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/**************************************************************************/
|
||||
/* test_skeleton_3d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef TEST_SKELETON_3D_H
|
||||
#define TEST_SKELETON_3D_H
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
|
||||
namespace TestSkeleton3D {
|
||||
|
||||
TEST_CASE("[Skeleton3D] Test per-bone meta") {
|
||||
Skeleton3D *skeleton = memnew(Skeleton3D);
|
||||
skeleton->add_bone("root");
|
||||
skeleton->set_bone_rest(0, Transform3D());
|
||||
|
||||
// Adding meta to bone.
|
||||
skeleton->set_bone_meta(0, "key1", "value1");
|
||||
skeleton->set_bone_meta(0, "key2", 12345);
|
||||
CHECK_MESSAGE(skeleton->get_bone_meta(0, "key1") == "value1", "Bone meta missing.");
|
||||
CHECK_MESSAGE(skeleton->get_bone_meta(0, "key2") == Variant(12345), "Bone meta missing.");
|
||||
|
||||
// Rename bone and check if meta persists.
|
||||
skeleton->set_bone_name(0, "renamed_root");
|
||||
CHECK_MESSAGE(skeleton->get_bone_meta(0, "key1") == "value1", "Bone meta missing.");
|
||||
CHECK_MESSAGE(skeleton->get_bone_meta(0, "key2") == Variant(12345), "Bone meta missing.");
|
||||
|
||||
// Retrieve list of keys.
|
||||
List<StringName> keys;
|
||||
skeleton->get_bone_meta_list(0, &keys);
|
||||
CHECK_MESSAGE(keys.size() == 2, "Wrong number of bone meta keys.");
|
||||
CHECK_MESSAGE(keys.find("key1"), "key1 not found in bone meta list");
|
||||
CHECK_MESSAGE(keys.find("key2"), "key2 not found in bone meta list");
|
||||
|
||||
// Removing meta.
|
||||
skeleton->set_bone_meta(0, "key1", Variant());
|
||||
skeleton->set_bone_meta(0, "key2", Variant());
|
||||
CHECK_MESSAGE(!skeleton->has_bone_meta(0, "key1"), "Bone meta key1 should be deleted.");
|
||||
CHECK_MESSAGE(!skeleton->has_bone_meta(0, "key2"), "Bone meta key2 should be deleted.");
|
||||
List<StringName> should_be_empty_keys;
|
||||
skeleton->get_bone_meta_list(0, &should_be_empty_keys);
|
||||
CHECK_MESSAGE(should_be_empty_keys.size() == 0, "Wrong number of bone meta keys.");
|
||||
|
||||
// Deleting non-existing key should succeed.
|
||||
skeleton->set_bone_meta(0, "non-existing-key", Variant());
|
||||
memdelete(skeleton);
|
||||
}
|
||||
} // namespace TestSkeleton3D
|
||||
|
||||
#endif // TEST_SKELETON_3D_H
|
|
@ -160,6 +160,7 @@
|
|||
#include "tests/scene/test_path_3d.h"
|
||||
#include "tests/scene/test_path_follow_3d.h"
|
||||
#include "tests/scene/test_primitives.h"
|
||||
#include "tests/scene/test_skeleton_3d.h"
|
||||
#endif // _3D_DISABLED
|
||||
|
||||
#include "modules/modules_tests.gen.h"
|
||||
|
|
Loading…
Reference in New Issue