Răsfoiți Sursa

Implemented SkeletonEditorGizmo

Co-authored-by: Lyuma <[email protected]>
Silc Renew 4 ani în urmă
părinte
comite
f2e9867e9f
36 a modificat fișierele cu 1330 adăugiri și 414 ștergeri
  1. 12 7
      doc/classes/EditorPlugin.xml
  2. 9 0
      doc/classes/Node3D.xml
  3. 26 0
      doc/classes/Skeleton3D.xml
  4. 36 11
      editor/animation_track_editor.cpp
  5. 1 0
      editor/animation_track_editor.h
  6. 10 6
      editor/editor_node.cpp
  7. 1 1
      editor/editor_node.h
  8. 4 4
      editor/editor_plugin.cpp
  9. 8 2
      editor/editor_plugin.h
  10. 1 0
      editor/editor_themes.cpp
  11. 1 0
      editor/icons/EditorBoneHandle.svg
  12. 1 0
      editor/icons/ToolBoneSelect.svg
  13. 3 3
      editor/inspector_dock.cpp
  14. 2 2
      editor/plugins/animation_player_editor_plugin.cpp
  15. 3 1
      editor/plugins/animation_player_editor_plugin.h
  16. 12 12
      editor/plugins/canvas_item_editor_plugin.cpp
  17. 14 14
      editor/plugins/collision_polygon_3d_editor_plugin.cpp
  18. 2 2
      editor/plugins/collision_polygon_3d_editor_plugin.h
  19. 0 163
      editor/plugins/node_3d_editor_gizmos.cpp
  20. 0 12
      editor/plugins/node_3d_editor_gizmos.h
  21. 66 21
      editor/plugins/node_3d_editor_plugin.cpp
  22. 2 1
      editor/plugins/node_3d_editor_plugin.h
  23. 11 11
      editor/plugins/path_3d_editor_plugin.cpp
  24. 1 1
      editor/plugins/path_3d_editor_plugin.h
  25. 870 67
      editor/plugins/skeleton_3d_editor_plugin.cpp
  26. 114 21
      editor/plugins/skeleton_3d_editor_plugin.h
  27. 6 6
      editor/scene_tree_dock.cpp
  28. 2 2
      editor/scene_tree_editor.cpp
  29. 29 19
      modules/gridmap/grid_map_editor_plugin.cpp
  30. 2 2
      modules/gridmap/grid_map_editor_plugin.h
  31. 13 0
      scene/3d/node_3d.cpp
  32. 4 0
      scene/3d/node_3d.h
  33. 52 23
      scene/3d/skeleton_3d.cpp
  34. 6 0
      scene/3d/skeleton_3d.h
  35. 3 0
      scene/scene_string_names.cpp
  36. 3 0
      scene/scene_string_names.h

+ 12 - 7
doc/classes/EditorPlugin.xml

@@ -96,7 +96,7 @@
 			</description>
 		</method>
 		<method name="_forward_3d_gui_input" qualifiers="virtual">
-			<return type="bool" />
+			<return type="int" />
 			<argument index="0" name="viewport_camera" type="Camera3D" />
 			<argument index="1" name="event" type="InputEvent" />
 			<description>
@@ -105,13 +105,13 @@
 				[gdscript]
 				# Prevents the InputEvent to reach other Editor classes.
 				func _forward_3d_gui_input(camera, event):
-				    return true
+				    return EditorPlugin.AFTER_GUI_INPUT_STOP
 				[/gdscript]
 				[csharp]
 				// Prevents the InputEvent to reach other Editor classes.
 				public override bool _Forward3dGuiInput(Camera3D camera, InputEvent @event)
 				{
-				    return true;
+				    return EditorPlugin.AFTER_GUI_INPUT_STOP;
 				}
 				[/csharp]
 				[/codeblocks]
@@ -185,12 +185,12 @@
 				Called when there is a root node in the current edited scene, [method _handles] is implemented and an [InputEvent] happens in the 2D viewport. Intercepts the [InputEvent], if [code]return true[/code] [EditorPlugin] consumes the [code]event[/code], otherwise forwards [code]event[/code] to other Editor classes. Example:
 				[codeblocks]
 				[gdscript]
-				# Prevents the InputEvent to reach other Editor classes
+				# Prevents the InputEvent to reach other Editor classes.
 				func _forward_canvas_gui_input(event):
 				    return true
 				[/gdscript]
 				[csharp]
-				// Prevents the InputEvent to reach other Editor classes
+				// Prevents the InputEvent to reach other Editor classes.
 				public override bool ForwardCanvasGuiInput(InputEvent @event)
 				{
 				    return true;
@@ -202,13 +202,18 @@
 				[gdscript]
 				# Consumes InputEventMouseMotion and forwards other InputEvent types.
 				func _forward_canvas_gui_input(event):
-				    return event is InputEventMouseMotion
+				    if (event is InputEventMouseMotion):
+				        return true
+				    return false
 				[/gdscript]
 				[csharp]
 				// Consumes InputEventMouseMotion and forwards other InputEvent types.
 				public override bool ForwardCanvasGuiInput(InputEvent @event)
 				{
-				    return @event is InputEventMouseMotion;
+				    if (@event is InputEventMouseMotion) {
+				        return true;
+				    }
+				    return false
 				}
 				[/csharp]
 				[/codeblocks]

+ 9 - 0
doc/classes/Node3D.xml

@@ -212,6 +212,15 @@
 				Sets whether the node notifies about its global and local transformation changes. [Node3D] will not propagate this by default, unless it is in the editor context and it has a valid gizmo.
 			</description>
 		</method>
+		<method name="set_subgizmo_selection">
+			<return type="void" />
+			<argument index="0" name="gizmo" type="Node3DGizmo" />
+			<argument index="1" name="id" type="int" />
+			<argument index="2" name="transform" type="Transform3D" />
+			<description>
+				Set subgizmo selection for this node in the editor.
+			</description>
+		</method>
 		<method name="show">
 			<return type="void" />
 			<description>

+ 26 - 0
doc/classes/Skeleton3D.xml

@@ -189,6 +189,13 @@
 				This is helper function to make using [method Transform3D.looking_at] easier with bone poses.
 			</description>
 		</method>
+		<method name="is_bone_enabled" qualifiers="const">
+			<return type="bool" />
+			<argument index="0" name="bone_idx" type="int" />
+			<description>
+				Returns whether the bone pose for the bone at [code]bone_idx[/code] is enabled.
+			</description>
+		</method>
 		<method name="is_bone_rest_disabled" qualifiers="const">
 			<return type="bool" />
 			<argument index="0" name="bone_idx" type="int" />
@@ -282,6 +289,14 @@
 				Disables the rest pose for the bone at [code]bone_idx[/code] if [code]true[/code], enables the bone rest if [code]false[/code].
 			</description>
 		</method>
+		<method name="set_bone_enabled">
+			<return type="void" />
+			<argument index="0" name="bone_idx" type="int" />
+			<argument index="1" name="enabled" type="bool" default="true" />
+			<description>
+				Disables the pose for the bone at [code]bone_idx[/code] if [code]false[/code], enables the bone pose if [code]true[/code].
+			</description>
+		</method>
 		<method name="set_bone_global_pose_override">
 			<return type="void" />
 			<argument index="0" name="bone_idx" type="int" />
@@ -365,8 +380,15 @@
 	<members>
 		<member name="animate_physical_bones" type="bool" setter="set_animate_physical_bones" getter="get_animate_physical_bones" default="true">
 		</member>
+		<member name="show_rest_only" type="bool" setter="set_show_rest_only" getter="is_show_rest_only" default="false">
+		</member>
 	</members>
 	<signals>
+		<signal name="bone_enabled_changed">
+			<argument index="0" name="bone_idx" type="int" />
+			<description>
+			</description>
+		</signal>
 		<signal name="bone_pose_changed">
 			<argument index="0" name="bone_idx" type="int" />
 			<description>
@@ -377,6 +399,10 @@
 			<description>
 			</description>
 		</signal>
+		<signal name="show_rest_only_changed">
+			<description>
+			</description>
+		</signal>
 	</signals>
 	<constants>
 		<constant name="NOTIFICATION_UPDATE_SKELETON" value="50">

+ 36 - 11
editor/animation_track_editor.cpp

@@ -3359,7 +3359,7 @@ void AnimationTrackEditor::_query_insert(const InsertData &p_id) {
 	insert_data.push_back(p_id);
 
 	bool reset_allowed = true;
-	AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player();
+	AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
 	if (player->has_animation("RESET") && player->get_animation("RESET") == animation) {
 		// Avoid messing with the reset animation itself
 		reset_allowed = false;
@@ -3528,6 +3528,31 @@ void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_
 	_query_insert(id);
 }
 
+bool AnimationTrackEditor::has_transform_track(Node3D *p_node, const String &p_sub) {
+	if (!keying) {
+		return false;
+	}
+	if (!animation.is_valid()) {
+		return false;
+	}
+	if (!root) {
+		return false;
+	}
+
+	//let's build a node path
+	String path = root->get_path_to(p_node);
+	if (p_sub != "") {
+		path += ":" + p_sub;
+	}
+	int track_id = animation->find_track(path);
+	if (track_id >= 0) {
+		if (animation->track_get_type(track_id) == Animation::TYPE_TRANSFORM3D) {
+			return true;
+		}
+	}
+	return false;
+}
+
 void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant &p_value) {
 	String path = p_path;
 
@@ -3571,7 +3596,7 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p
 	String path = root->get_path_to(node);
 
 	if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") {
-		if (node == AnimationPlayerEditor::singleton->get_player()) {
+		if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
 			EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
 			return;
 		}
@@ -3672,7 +3697,7 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari
 	String path = root->get_path_to(node);
 
 	if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") {
-		if (node == AnimationPlayerEditor::singleton->get_player()) {
+		if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
 			EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
 			return;
 		}
@@ -3752,7 +3777,7 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari
 }
 
 Ref<Animation> AnimationTrackEditor::_create_and_get_reset_animation() {
-	AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player();
+	AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
 	if (player->has_animation("RESET")) {
 		return player->get_animation("RESET");
 	} else {
@@ -3760,9 +3785,9 @@ Ref<Animation> AnimationTrackEditor::_create_and_get_reset_animation() {
 		reset_anim.instantiate();
 		reset_anim->set_length(ANIM_MIN_LENGTH);
 		undo_redo->add_do_method(player, "add_animation", "RESET", reset_anim);
-		undo_redo->add_do_method(AnimationPlayerEditor::singleton, "_animation_player_changed", player);
+		undo_redo->add_do_method(AnimationPlayerEditor::get_singleton(), "_animation_player_changed", player);
 		undo_redo->add_undo_method(player, "remove_animation", "RESET");
-		undo_redo->add_undo_method(AnimationPlayerEditor::singleton, "_animation_player_changed", player);
+		undo_redo->add_undo_method(AnimationPlayerEditor::get_singleton(), "_animation_player_changed", player);
 		return reset_anim;
 	}
 }
@@ -4446,7 +4471,7 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) {
 				return;
 			}
 
-			if (node == AnimationPlayerEditor::singleton->get_player()) {
+			if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
 				EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
 				return;
 			}
@@ -5173,7 +5198,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) {
 }
 
 void AnimationTrackEditor::_edit_menu_about_to_popup() {
-	AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player();
+	AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
 	edit->get_popup()->set_item_disabled(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), !player->can_apply_reset());
 }
 
@@ -5517,7 +5542,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
 			goto_prev_step(false);
 		} break;
 		case EDIT_APPLY_RESET: {
-			AnimationPlayerEditor::singleton->get_player()->apply_reset(true);
+			AnimationPlayerEditor::get_singleton()->get_player()->apply_reset(true);
 
 		} break;
 		case EDIT_OPTIMIZE_ANIMATION: {
@@ -5537,9 +5562,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
 		case EDIT_CLEAN_UP_ANIMATION_CONFIRM: {
 			if (cleanup_all->is_pressed()) {
 				List<StringName> names;
-				AnimationPlayerEditor::singleton->get_player()->get_animation_list(&names);
+				AnimationPlayerEditor::get_singleton()->get_player()->get_animation_list(&names);
 				for (const StringName &E : names) {
-					_cleanup_animation(AnimationPlayerEditor::singleton->get_player()->get_animation(E));
+					_cleanup_animation(AnimationPlayerEditor::get_singleton()->get_player()->get_animation(E));
 				}
 			} else {
 				_cleanup_animation(animation);

+ 1 - 0
editor/animation_track_editor.h

@@ -531,6 +531,7 @@ public:
 	void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false);
 	void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance);
 	void insert_transform_key(Node3D *p_node, const String &p_sub, const Transform3D &p_xform);
+	bool has_transform_track(Node3D *p_node, const String &p_sub);
 
 	void show_select_node_warning(bool p_show);
 

+ 10 - 6
editor/editor_node.cpp

@@ -6930,7 +6930,7 @@ EditorNode::EditorNode() {
 	add_child(editor_interface);
 
 	//more visually meaningful to have this later
-	raise_bottom_panel_item(AnimationPlayerEditor::singleton);
+	raise_bottom_panel_item(AnimationPlayerEditor::get_singleton());
 
 	add_editor_plugin(VersionControlEditorPlugin::get_singleton());
 	add_editor_plugin(memnew(ShaderEditorPlugin(this)));
@@ -7204,20 +7204,24 @@ bool EditorPluginList::forward_gui_input(const Ref<InputEvent> &p_event) {
 	return discard;
 }
 
-bool EditorPluginList::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled) {
-	bool discard = false;
+EditorPlugin::AfterGUIInput EditorPluginList::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled) {
+	EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS;
 
 	for (int i = 0; i < plugins_list.size(); i++) {
 		if ((!serve_when_force_input_enabled) && plugins_list[i]->is_input_event_forwarding_always_enabled()) {
 			continue;
 		}
 
-		if (plugins_list[i]->forward_spatial_gui_input(p_camera, p_event)) {
-			discard = true;
+		EditorPlugin::AfterGUIInput current_after = plugins_list[i]->forward_spatial_gui_input(p_camera, p_event);
+		if (current_after == EditorPlugin::AFTER_GUI_INPUT_STOP) {
+			after = EditorPlugin::AFTER_GUI_INPUT_STOP;
+		}
+		if (after != EditorPlugin::AFTER_GUI_INPUT_STOP && current_after == EditorPlugin::AFTER_GUI_INPUT_DESELECT) {
+			after = EditorPlugin::AFTER_GUI_INPUT_DESELECT;
 		}
 	}
 
-	return discard;
+	return after;
 }
 
 void EditorPluginList::forward_canvas_draw_over_viewport(Control *p_overlay) {

+ 1 - 1
editor/editor_node.h

@@ -942,7 +942,7 @@ public:
 	bool forward_gui_input(const Ref<InputEvent> &p_event);
 	void forward_canvas_draw_over_viewport(Control *p_overlay);
 	void forward_canvas_force_draw_over_viewport(Control *p_overlay);
-	bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled);
+	EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled);
 	void forward_spatial_draw_over_viewport(Control *p_overlay);
 	void forward_spatial_force_draw_over_viewport(Control *p_overlay);
 	void add_plugin(EditorPlugin *p_plugin);

+ 4 - 4
editor/editor_plugin.cpp

@@ -593,14 +593,14 @@ int EditorPlugin::update_overlays() const {
 	}
 }
 
-bool EditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
-	bool success;
+EditorPlugin::AfterGUIInput EditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
+	int success;
 
 	if (GDVIRTUAL_CALL(_forward_3d_gui_input, p_camera, p_event, success)) {
-		return success;
+		return static_cast<EditorPlugin::AfterGUIInput>(success);
 	}
 
-	return false;
+	return EditorPlugin::AFTER_GUI_INPUT_PASS;
 }
 
 void EditorPlugin::forward_spatial_draw_over_viewport(Control *p_overlay) {

+ 8 - 2
editor/editor_plugin.h

@@ -151,7 +151,7 @@ protected:
 	GDVIRTUAL1R(bool, _forward_canvas_gui_input, Ref<InputEvent>)
 	GDVIRTUAL1(_forward_canvas_draw_over_viewport, Control *)
 	GDVIRTUAL1(_forward_canvas_force_draw_over_viewport, Control *)
-	GDVIRTUAL2R(bool, _forward_3d_gui_input, Camera3D *, Ref<InputEvent>)
+	GDVIRTUAL2R(int, _forward_3d_gui_input, Camera3D *, Ref<InputEvent>)
 	GDVIRTUAL1(_forward_3d_draw_over_viewport, Control *)
 	GDVIRTUAL1(_forward_3d_force_draw_over_viewport, Control *)
 	GDVIRTUAL0RC(String, _get_plugin_name)
@@ -200,6 +200,12 @@ public:
 		DOCK_SLOT_MAX
 	};
 
+	enum AfterGUIInput {
+		AFTER_GUI_INPUT_PASS,
+		AFTER_GUI_INPUT_STOP,
+		AFTER_GUI_INPUT_DESELECT
+	};
+
 	//TODO: send a resource for editing to the editor node?
 
 	void add_control_to_container(CustomControlContainer p_location, Control *p_control);
@@ -228,7 +234,7 @@ public:
 	virtual void forward_canvas_draw_over_viewport(Control *p_overlay);
 	virtual void forward_canvas_force_draw_over_viewport(Control *p_overlay);
 
-	virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event);
+	virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event);
 	virtual void forward_spatial_draw_over_viewport(Control *p_overlay);
 	virtual void forward_spatial_force_draw_over_viewport(Control *p_overlay);
 

+ 1 - 0
editor/editor_themes.cpp

@@ -213,6 +213,7 @@ void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme =
 		exceptions.insert("EditorPivot");
 		exceptions.insert("EditorHandle");
 		exceptions.insert("Editor3DHandle");
+		exceptions.insert("EditorBoneHandle");
 		exceptions.insert("Godot");
 		exceptions.insert("Sky");
 		exceptions.insert("EditorControlAnchor");

+ 1 - 0
editor/icons/EditorBoneHandle.svg

@@ -0,0 +1 @@
+<svg height="8" viewBox="0 0 8 8" width="8" xmlns="http://www.w3.org/2000/svg"><circle cx="4" cy="4" fill="#fff" r="4"/><circle cx="4" cy="4" fill="#000" r="2.5"/></svg>

+ 1 - 0
editor/icons/ToolBoneSelect.svg

@@ -0,0 +1 @@
+<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-opacity=".9961"><path d="m16 11.46-6.142-2.527-1.572-.647.647 1.572 2.527 6.142.913-2.72 1.815 1.817.91-.909-1.818-1.815z"/><path d="m7.784 11.008-.886-2.152c-.23-.56-.102-1.203.327-1.631.287-.287.67-.439 1.061-.439.192 0 .386.037.57.113l2.151.885.17-.17c.977.645 2.271.516 3.1-.311.964-.963.964-2.524 0-3.488-.377-.377-.867-.622-1.396-.697-.074-.529-.318-1.019-.695-1.397-.455-.453-1.067-.711-1.707-.721-.667-.01-1.309.25-1.782.72-.828.829-.96 2.126-.314 3.105l-3.558 3.561c-.978-.646-2.274-.515-3.103.312-.963.962-.963 2.524 0 3.487.378.377.868.621 1.396.695.075.529.319 1.02.696 1.396.963.964 2.525.964 3.488 0 .828-.828.96-2.125.314-3.104z"/></g></svg>

+ 3 - 3
editor/inspector_dock.cpp

@@ -383,7 +383,7 @@ void InspectorDock::_menu_expandall() {
 }
 
 void InspectorDock::_property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance) {
-	AnimationPlayerEditor::singleton->get_track_editor()->insert_value_key(p_keyed, p_value, p_advance);
+	AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_value_key(p_keyed, p_value, p_advance);
 }
 
 void InspectorDock::_transform_keyed(Object *sp, const String &p_sub, const Transform3D &p_key) {
@@ -391,7 +391,7 @@ void InspectorDock::_transform_keyed(Object *sp, const String &p_sub, const Tran
 	if (!s) {
 		return;
 	}
-	AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(s, p_sub, p_key);
+	AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_transform_key(s, p_sub, p_key);
 }
 
 void InspectorDock::_warning_pressed() {
@@ -545,7 +545,7 @@ void InspectorDock::go_back() {
 void InspectorDock::update_keying() {
 	bool valid = false;
 
-	if (AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) {
+	if (AnimationPlayerEditor::get_singleton()->get_track_editor()->has_keying()) {
 		EditorHistory *editor_history = EditorNode::get_singleton()->get_editor_history();
 		if (editor_history->get_path_size() >= 1) {
 			Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0));

+ 2 - 2
editor/plugins/animation_player_editor_plugin.cpp

@@ -298,7 +298,7 @@ void AnimationPlayerEditor::_animation_selected(int p_which) {
 
 	autoplay->set_pressed(current == player->get_autoplay());
 
-	AnimationPlayerEditor::singleton->get_track_editor()->update_keying();
+	AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying();
 	EditorNode::get_singleton()->update_keying();
 	_animation_key_editor_seek(timeline_position, false);
 }
@@ -826,7 +826,7 @@ void AnimationPlayerEditor::_update_player() {
 	pin->set_disabled(player == nullptr);
 
 	if (!player) {
-		AnimationPlayerEditor::singleton->get_track_editor()->update_keying();
+		AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying();
 		EditorNode::get_singleton()->update_keying();
 		return;
 	}

+ 3 - 1
editor/plugins/animation_player_editor_plugin.h

@@ -128,6 +128,7 @@ class AnimationPlayerEditor : public VBoxContainer {
 	bool updating_blends;
 
 	AnimationTrackEditor *track_editor;
+	static AnimationPlayerEditor *singleton;
 
 	// Onion skinning.
 	struct {
@@ -226,7 +227,8 @@ protected:
 
 public:
 	AnimationPlayer *get_player() const;
-	static AnimationPlayerEditor *singleton;
+
+	static AnimationPlayerEditor *get_singleton() { return singleton; }
 
 	bool is_pinned() const { return pin->is_pressed(); }
 	void unpin() { pin->set_pressed(false); }

+ 12 - 12
editor/plugins/canvas_item_editor_plugin.cpp

@@ -513,7 +513,7 @@ Object *CanvasItemEditor::_get_editor_data(Object *p_what) {
 }
 
 void CanvasItemEditor::_keying_changed() {
-	if (AnimationPlayerEditor::singleton->get_track_editor()->is_visible_in_tree()) {
+	if (AnimationPlayerEditor::get_singleton()->get_track_editor()->is_visible_in_tree()) {
 		animation_hb->show();
 	} else {
 		animation_hb->hide();
@@ -3816,7 +3816,7 @@ void CanvasItemEditor::_notification(int p_what) {
 			select_sb->set_default_margin(Side(i), 4);
 		}
 
-		AnimationPlayerEditor::singleton->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed));
+		AnimationPlayerEditor::get_singleton()->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed));
 		_keying_changed();
 
 	} else if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
@@ -4277,13 +4277,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
 			Node2D *n2d = Object::cast_to<Node2D>(canvas_item);
 
 			if (key_pos && p_location) {
-				AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing);
+				AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing);
 			}
 			if (key_rot && p_rotation) {
-				AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing);
+				AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing);
 			}
 			if (key_scale && p_scale) {
-				AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing);
+				AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing);
 			}
 
 			if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) {
@@ -4309,13 +4309,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
 				if (has_chain && ik_chain.size()) {
 					for (Node2D *&F : ik_chain) {
 						if (key_pos) {
-							AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "position", F->get_position(), p_on_existing);
+							AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "position", F->get_position(), p_on_existing);
 						}
 						if (key_rot) {
-							AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing);
+							AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing);
 						}
 						if (key_scale) {
-							AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing);
+							AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing);
 						}
 					}
 				}
@@ -4325,13 +4325,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
 			Control *ctrl = Object::cast_to<Control>(canvas_item);
 
 			if (key_pos) {
-				AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), p_on_existing);
+				AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), p_on_existing);
 			}
 			if (key_rot) {
-				AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation(), p_on_existing);
+				AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation(), p_on_existing);
 			}
 			if (key_scale) {
-				AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing);
+				AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing);
 			}
 		}
 	}
@@ -4771,7 +4771,7 @@ void CanvasItemEditor::_popup_callback(int p_op) {
 					}
 					/*
                                    if (key_scale)
-				   AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size());
+				   AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size());
                                    */
 				}
 			}

+ 14 - 14
editor/plugins/collision_polygon_3d_editor_plugin.cpp

@@ -103,9 +103,9 @@ void CollisionPolygon3DEditor::_wip_close() {
 	undo_redo->commit_action();
 }
 
-bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
+EditorPlugin::AfterGUIInput CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
 	if (!node) {
-		return false;
+		return EditorPlugin::AFTER_GUI_INPUT_PASS;
 	}
 
 	Transform3D gt = node->get_global_transform();
@@ -124,7 +124,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
 		Vector3 spoint;
 
 		if (!p.intersects_ray(ray_from, ray_dir, &spoint)) {
-			return false;
+			return EditorPlugin::AFTER_GUI_INPUT_PASS;
 		}
 
 		spoint = gi.xform(spoint);
@@ -151,19 +151,19 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
 						snap_ignore = false;
 						_polygon_draw();
 						edited_point = 1;
-						return true;
+						return EditorPlugin::AFTER_GUI_INPUT_STOP;
 					} else {
 						if (wip.size() > 1 && p_camera->unproject_position(gt.xform(Vector3(wip[0].x, wip[0].y, depth))).distance_to(gpoint) < grab_threshold) {
 							//wip closed
 							_wip_close();
 
-							return true;
+							return EditorPlugin::AFTER_GUI_INPUT_STOP;
 						} else {
 							wip.push_back(cpoint);
 							edited_point = wip.size();
 							snap_ignore = false;
 							_polygon_draw();
-							return true;
+							return EditorPlugin::AFTER_GUI_INPUT_STOP;
 						}
 					}
 				} else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed() && wip_active) {
@@ -184,7 +184,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
 								undo_redo->add_do_method(this, "_polygon_draw");
 								undo_redo->add_undo_method(this, "_polygon_draw");
 								undo_redo->commit_action();
-								return true;
+								return EditorPlugin::AFTER_GUI_INPUT_STOP;
 							}
 
 							//search edges
@@ -219,7 +219,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
 								_polygon_draw();
 								snap_ignore = true;
 
-								return true;
+								return EditorPlugin::AFTER_GUI_INPUT_STOP;
 							}
 						} else {
 							//look for points to move
@@ -244,7 +244,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
 								edited_point_pos = poly[closest_idx];
 								_polygon_draw();
 								snap_ignore = false;
-								return true;
+								return EditorPlugin::AFTER_GUI_INPUT_STOP;
 							}
 						}
 					} else {
@@ -253,7 +253,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
 						if (edited_point != -1) {
 							//apply
 
-							ERR_FAIL_INDEX_V(edited_point, poly.size(), false);
+							ERR_FAIL_INDEX_V(edited_point, poly.size(), EditorPlugin::AFTER_GUI_INPUT_PASS);
 							poly.write[edited_point] = edited_point_pos;
 							undo_redo->create_action(TTR("Edit Poly"));
 							undo_redo->add_do_method(node, "set_polygon", poly);
@@ -263,7 +263,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
 							undo_redo->commit_action();
 
 							edited_point = -1;
-							return true;
+							return EditorPlugin::AFTER_GUI_INPUT_STOP;
 						}
 					}
 				}
@@ -290,7 +290,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
 						undo_redo->add_do_method(this, "_polygon_draw");
 						undo_redo->add_undo_method(this, "_polygon_draw");
 						undo_redo->commit_action();
-						return true;
+						return EditorPlugin::AFTER_GUI_INPUT_STOP;
 					}
 				}
 
@@ -310,7 +310,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
 			Vector3 spoint;
 
 			if (!p.intersects_ray(ray_from, ray_dir, &spoint)) {
-				return false;
+				return EditorPlugin::AFTER_GUI_INPUT_PASS;
 			}
 
 			spoint = gi.xform(spoint);
@@ -332,7 +332,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
 		}
 	}
 
-	return false;
+	return EditorPlugin::AFTER_GUI_INPUT_PASS;
 }
 
 float CollisionPolygon3DEditor::_get_depth() {

+ 2 - 2
editor/plugins/collision_polygon_3d_editor_plugin.h

@@ -88,7 +88,7 @@ protected:
 	static void _bind_methods();
 
 public:
-	virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event);
+	virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event);
 	void edit(Node *p_collision_polygon);
 	CollisionPolygon3DEditor(EditorNode *p_editor);
 	~CollisionPolygon3DEditor();
@@ -101,7 +101,7 @@ class Polygon3DEditorPlugin : public EditorPlugin {
 	EditorNode *editor;
 
 public:
-	virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); }
+	virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); }
 
 	virtual String get_name() const override { return "Polygon3DEditor"; }
 	bool has_main_screen() const override { return false; }

+ 0 - 163
editor/plugins/node_3d_editor_gizmos.cpp

@@ -2070,169 +2070,6 @@ void Position3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
 	p_gizmo->add_collision_segments(cursor_points);
 }
 
-/////
-
-Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() {
-	Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4));
-	create_material("skeleton_material", gizmo_color);
-}
-
-bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
-	return Object::cast_to<Skeleton3D>(p_spatial) != nullptr;
-}
-
-String Skeleton3DGizmoPlugin::get_gizmo_name() const {
-	return "Skeleton3D";
-}
-
-int Skeleton3DGizmoPlugin::get_priority() const {
-	return -1;
-}
-
-void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
-	Skeleton3D *skel = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
-
-	p_gizmo->clear();
-
-	Ref<Material> material = get_material("skeleton_material", p_gizmo);
-
-	Ref<SurfaceTool> surface_tool(memnew(SurfaceTool));
-
-	surface_tool->begin(Mesh::PRIMITIVE_LINES);
-	surface_tool->set_material(material);
-	LocalVector<Transform3D> grests;
-	grests.resize(skel->get_bone_count());
-
-	LocalVector<int> bones;
-	LocalVector<float> weights;
-	bones.resize(4);
-	weights.resize(4);
-
-	for (int i = 0; i < 4; i++) {
-		bones[i] = 0;
-		weights[i] = 0;
-	}
-
-	weights[0] = 1;
-
-	AABB aabb;
-
-	Color bonecolor = Color(1.0, 0.4, 0.4, 0.3);
-	Color rootcolor = Color(0.4, 1.0, 0.4, 0.1);
-
-	LocalVector<int> bones_to_process;
-	bones_to_process = skel->get_parentless_bones();
-
-	while (bones_to_process.size() > 0) {
-		int current_bone_idx = bones_to_process[0];
-		bones_to_process.erase(current_bone_idx);
-
-		LocalVector<int> child_bones_vector;
-		child_bones_vector = skel->get_bone_children(current_bone_idx);
-		int child_bones_size = child_bones_vector.size();
-
-		if (skel->get_bone_parent(current_bone_idx) < 0) {
-			grests[current_bone_idx] = skel->get_bone_rest(current_bone_idx);
-		}
-
-		for (int i = 0; i < child_bones_size; i++) {
-			int child_bone_idx = child_bones_vector[i];
-
-			grests[child_bone_idx] = grests[current_bone_idx] * skel->get_bone_rest(child_bone_idx);
-			Vector3 v0 = grests[current_bone_idx].origin;
-			Vector3 v1 = grests[child_bone_idx].origin;
-			Vector3 d = (v1 - v0).normalized();
-			real_t dist = v0.distance_to(v1);
-
-			// Find closest axis.
-			int closest = -1;
-			real_t closest_d = 0.0;
-			for (int j = 0; j < 3; j++) {
-				real_t dp = Math::abs(grests[current_bone_idx].basis[j].normalized().dot(d));
-				if (j == 0 || dp > closest_d) {
-					closest = j;
-				}
-			}
-
-			// Find closest other.
-			Vector3 first;
-			Vector3 points[4];
-			int point_idx = 0;
-			for (int j = 0; j < 3; j++) {
-				bones[0] = current_bone_idx;
-				surface_tool->set_bones(bones);
-				surface_tool->set_weights(weights);
-				surface_tool->set_color(rootcolor);
-				surface_tool->add_vertex(v0 - grests[current_bone_idx].basis[j].normalized() * dist * 0.05);
-				surface_tool->set_bones(bones);
-				surface_tool->set_weights(weights);
-				surface_tool->set_color(rootcolor);
-				surface_tool->add_vertex(v0 + grests[current_bone_idx].basis[j].normalized() * dist * 0.05);
-
-				if (j == closest) {
-					continue;
-				}
-
-				Vector3 axis;
-				if (first == Vector3()) {
-					axis = d.cross(d.cross(grests[current_bone_idx].basis[j])).normalized();
-					first = axis;
-				} else {
-					axis = d.cross(first).normalized();
-				}
-
-				for (int k = 0; k < 2; k++) {
-					if (k == 1) {
-						axis = -axis;
-					}
-					Vector3 point = v0 + d * dist * 0.2;
-					point += axis * dist * 0.1;
-
-					bones[0] = current_bone_idx;
-					surface_tool->set_bones(bones);
-					surface_tool->set_weights(weights);
-					surface_tool->set_color(bonecolor);
-					surface_tool->add_vertex(v0);
-					surface_tool->set_bones(bones);
-					surface_tool->set_weights(weights);
-					surface_tool->set_color(bonecolor);
-					surface_tool->add_vertex(point);
-
-					bones[0] = current_bone_idx;
-					surface_tool->set_bones(bones);
-					surface_tool->set_weights(weights);
-					surface_tool->set_color(bonecolor);
-					surface_tool->add_vertex(point);
-					bones[0] = child_bone_idx;
-					surface_tool->set_bones(bones);
-					surface_tool->set_weights(weights);
-					surface_tool->set_color(bonecolor);
-					surface_tool->add_vertex(v1);
-					points[point_idx++] = point;
-				}
-			}
-			SWAP(points[1], points[2]);
-			for (int j = 0; j < 4; j++) {
-				bones[0] = current_bone_idx;
-				surface_tool->set_bones(bones);
-				surface_tool->set_weights(weights);
-				surface_tool->set_color(bonecolor);
-				surface_tool->add_vertex(points[j]);
-				surface_tool->set_bones(bones);
-				surface_tool->set_weights(weights);
-				surface_tool->set_color(bonecolor);
-				surface_tool->add_vertex(points[(j + 1) % 4]);
-			}
-
-			// Add the bone's children to the list of bones to be processed.
-			bones_to_process.push_back(child_bones_vector[i]);
-		}
-	}
-
-	Ref<ArrayMesh> m = surface_tool->commit();
-	p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skel->register_skin(Ref<Skin>()));
-}
-
 ////
 
 PhysicalBone3DGizmoPlugin::PhysicalBone3DGizmoPlugin() {

+ 0 - 12
editor/plugins/node_3d_editor_gizmos.h

@@ -332,18 +332,6 @@ public:
 	Position3DGizmoPlugin();
 };
 
-class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin {
-	GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin);
-
-public:
-	bool has_gizmo(Node3D *p_spatial) override;
-	String get_gizmo_name() const override;
-	int get_priority() const override;
-	void redraw(EditorNode3DGizmo *p_gizmo) override;
-
-	Skeleton3DGizmoPlugin();
-};
-
 class PhysicalBone3DGizmoPlugin : public EditorNode3DGizmoPlugin {
 	GDCLASS(PhysicalBone3DGizmoPlugin, EditorNode3DGizmoPlugin);
 

+ 66 - 21
editor/plugins/node_3d_editor_plugin.cpp

@@ -1291,24 +1291,31 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
 		return; //do NONE
 	}
 
+	EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS;
 	{
 		EditorNode *en = editor;
 		EditorPluginList *force_input_forwarding_list = en->get_editor_plugins_force_input_forwarding();
 		if (!force_input_forwarding_list->is_empty()) {
-			bool discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true);
-			if (discard) {
+			EditorPlugin::AfterGUIInput discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true);
+			if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) {
 				return;
 			}
+			if (discard == EditorPlugin::AFTER_GUI_INPUT_DESELECT) {
+				after = EditorPlugin::AFTER_GUI_INPUT_DESELECT;
+			}
 		}
 	}
 	{
 		EditorNode *en = editor;
 		EditorPluginList *over_plugin_list = en->get_editor_plugins_over();
 		if (!over_plugin_list->is_empty()) {
-			bool discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false);
-			if (discard) {
+			EditorPlugin::AfterGUIInput discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false);
+			if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) {
 				return;
 			}
+			if (discard == EditorPlugin::AFTER_GUI_INPUT_DESELECT) {
+				after = EditorPlugin::AFTER_GUI_INPUT_DESELECT;
+			}
 		}
 	}
 
@@ -1573,17 +1580,19 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
 						break;
 					}
 
-					clicked = _select_ray(b->get_position());
+					if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) {
+						clicked = _select_ray(b->get_position());
 
-					//clicking is always deferred to either move or release
+						//clicking is always deferred to either move or release
 
-					clicked_wants_append = b->is_shift_pressed();
+						clicked_wants_append = b->is_shift_pressed();
 
-					if (clicked.is_null()) {
-						//default to regionselect
-						cursor.region_select = true;
-						cursor.region_begin = b->get_position();
-						cursor.region_end = b->get_position();
+						if (clicked.is_null()) {
+							//default to regionselect
+							cursor.region_select = true;
+							cursor.region_begin = b->get_position();
+							cursor.region_end = b->get_position();
+						}
 					}
 
 					surface->update();
@@ -1594,14 +1603,16 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
 						break;
 					}
 
-					if (clicked.is_valid()) {
-						_select_clicked(false);
-					}
+					if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) {
+						if (clicked.is_valid()) {
+							_select_clicked(false);
+						}
 
-					if (cursor.region_select) {
-						_select_region();
-						cursor.region_select = false;
-						surface->update();
+						if (cursor.region_select) {
+							_select_region();
+							cursor.region_select = false;
+							surface->update();
+						}
 					}
 
 					if (_edit.mode != TRANSFORM_NONE) {
@@ -2237,7 +2248,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
 				return;
 			}
 
-			if (!AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) {
+			if (!AnimationPlayerEditor::get_singleton()->get_track_editor()->has_keying()) {
 				set_message(TTR("Keying is disabled (no key inserted)."));
 				return;
 			}
@@ -6736,6 +6747,33 @@ void Node3DEditor::_request_gizmo(Object *p_obj) {
 	}
 }
 
+void Node3DEditor::_set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform) {
+	if (p_id == -1) {
+		_clear_subgizmo_selection(p_obj);
+		return;
+	}
+
+	Node3D *sp = nullptr;
+	if (p_obj) {
+		sp = Object::cast_to<Node3D>(p_obj);
+	} else {
+		sp = selected;
+	}
+
+	if (!sp) {
+		return;
+	}
+
+	Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
+	if (se) {
+		se->subgizmos.clear();
+		se->subgizmos.insert(p_id, p_transform);
+		se->gizmo = p_gizmo;
+		sp->update_gizmos();
+		update_transform_gizmo();
+	}
+}
+
 void Node3DEditor::_clear_subgizmo_selection(Object *p_obj) {
 	Node3D *sp = nullptr;
 	if (p_obj) {
@@ -6861,7 +6899,6 @@ void Node3DEditor::_register_all_gizmos() {
 	add_gizmo_plugin(Ref<OccluderInstance3DGizmoPlugin>(memnew(OccluderInstance3DGizmoPlugin)));
 	add_gizmo_plugin(Ref<SoftDynamicBody3DGizmoPlugin>(memnew(SoftDynamicBody3DGizmoPlugin)));
 	add_gizmo_plugin(Ref<Sprite3DGizmoPlugin>(memnew(Sprite3DGizmoPlugin)));
-	add_gizmo_plugin(Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin)));
 	add_gizmo_plugin(Ref<Position3DGizmoPlugin>(memnew(Position3DGizmoPlugin)));
 	add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin)));
 	add_gizmo_plugin(Ref<SpringArm3DGizmoPlugin>(memnew(SpringArm3DGizmoPlugin)));
@@ -6886,6 +6923,7 @@ void Node3DEditor::_register_all_gizmos() {
 void Node3DEditor::_bind_methods() {
 	ClassDB::bind_method("_get_editor_data", &Node3DEditor::_get_editor_data);
 	ClassDB::bind_method("_request_gizmo", &Node3DEditor::_request_gizmo);
+	ClassDB::bind_method("_set_subgizmo_selection", &Node3DEditor::_set_subgizmo_selection);
 	ClassDB::bind_method("_clear_subgizmo_selection", &Node3DEditor::_clear_subgizmo_selection);
 	ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons);
 
@@ -7712,6 +7750,13 @@ Vector3 Node3DEditor::snap_point(Vector3 p_target, Vector3 p_start) const {
 	return p_target;
 }
 
+bool Node3DEditor::is_gizmo_visible() const {
+	if (selected) {
+		return gizmo.visible && selected->is_transform_gizmo_visible();
+	}
+	return gizmo.visible;
+}
+
 double Node3DEditor::get_translate_snap() const {
 	double snap_value;
 	if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {

+ 2 - 1
editor/plugins/node_3d_editor_plugin.h

@@ -663,6 +663,7 @@ private:
 	Node3D *selected;
 
 	void _request_gizmo(Object *p_obj);
+	void _set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform = Transform3D());
 	void _clear_subgizmo_selection(Object *p_obj = nullptr);
 
 	static Node3DEditor *singleton;
@@ -756,7 +757,7 @@ public:
 	float get_fov() const { return settings_fov->get_value(); }
 
 	Transform3D get_gizmo_transform() const { return gizmo.transform; }
-	bool is_gizmo_visible() const { return gizmo.visible; }
+	bool is_gizmo_visible() const;
 
 	ToolMode get_tool_mode() const { return tool_mode; }
 	bool are_local_coords_enabled() const { return tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->is_pressed(); }

+ 11 - 11
editor/plugins/path_3d_editor_plugin.cpp

@@ -294,13 +294,13 @@ Path3DGizmo::Path3DGizmo(Path3D *p_path) {
 	orig_out_length = 0;
 }
 
-bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
+EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
 	if (!path) {
-		return false;
+		return EditorPlugin::AFTER_GUI_INPUT_PASS;
 	}
 	Ref<Curve3D> c = path->get_curve();
 	if (c.is_null()) {
-		return false;
+		return EditorPlugin::AFTER_GUI_INPUT_PASS;
 	}
 	Transform3D gt = path->get_global_transform();
 	Transform3D it = gt.affine_inverse();
@@ -329,14 +329,14 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref
 				const Vector3 *r = v3a.ptr();
 
 				if (p_camera->unproject_position(gt.xform(c->get_point_position(0))).distance_to(mbpos) < click_dist) {
-					return false; //nope, existing
+					return EditorPlugin::AFTER_GUI_INPUT_PASS; //nope, existing
 				}
 
 				for (int i = 0; i < c->get_point_count() - 1; i++) {
 					//find the offset and point index of the place to break up
 					int j = idx;
 					if (p_camera->unproject_position(gt.xform(c->get_point_position(i + 1))).distance_to(mbpos) < click_dist) {
-						return false; //nope, existing
+						return EditorPlugin::AFTER_GUI_INPUT_PASS; //nope, existing
 					}
 
 					while (j < rc && c->get_point_position(i + 1) != r[j]) {
@@ -386,7 +386,7 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref
 				ur->add_do_method(c.ptr(), "add_point", closest_seg_point, Vector3(), Vector3(), closest_seg + 1);
 				ur->add_undo_method(c.ptr(), "remove_point", closest_seg + 1);
 				ur->commit_action();
-				return true;
+				return EditorPlugin::AFTER_GUI_INPUT_STOP;
 
 			} else {
 				Vector3 org;
@@ -405,7 +405,7 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref
 					ur->add_do_method(c.ptr(), "add_point", it.xform(inters), Vector3(), Vector3(), -1);
 					ur->add_undo_method(c.ptr(), "remove_point", c->get_point_count());
 					ur->commit_action();
-					return true;
+					return EditorPlugin::AFTER_GUI_INPUT_STOP;
 				}
 
 				//add new at pos
@@ -425,27 +425,27 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref
 					ur->add_do_method(c.ptr(), "remove_point", i);
 					ur->add_undo_method(c.ptr(), "add_point", c->get_point_position(i), c->get_point_in(i), c->get_point_out(i), i);
 					ur->commit_action();
-					return true;
+					return EditorPlugin::AFTER_GUI_INPUT_STOP;
 				} else if (dist_to_p_out < click_dist) {
 					UndoRedo *ur = editor->get_undo_redo();
 					ur->create_action(TTR("Remove Out-Control Point"));
 					ur->add_do_method(c.ptr(), "set_point_out", i, Vector3());
 					ur->add_undo_method(c.ptr(), "set_point_out", i, c->get_point_out(i));
 					ur->commit_action();
-					return true;
+					return EditorPlugin::AFTER_GUI_INPUT_STOP;
 				} else if (dist_to_p_in < click_dist) {
 					UndoRedo *ur = editor->get_undo_redo();
 					ur->create_action(TTR("Remove In-Control Point"));
 					ur->add_do_method(c.ptr(), "set_point_in", i, Vector3());
 					ur->add_undo_method(c.ptr(), "set_point_in", i, c->get_point_in(i));
 					ur->commit_action();
-					return true;
+					return EditorPlugin::AFTER_GUI_INPUT_STOP;
 				}
 			}
 		}
 	}
 
-	return false;
+	return EditorPlugin::AFTER_GUI_INPUT_PASS;
 }
 
 void Path3DEditorPlugin::edit(Object *p_object) {

+ 1 - 1
editor/plugins/path_3d_editor_plugin.h

@@ -100,7 +100,7 @@ public:
 	Path3D *get_edited_path() { return path; }
 
 	static Path3DEditorPlugin *singleton;
-	virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override;
+	virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override;
 
 	virtual String get_name() const override { return "Path3D"; }
 	bool has_main_screen() const override { return false; }

+ 870 - 67
editor/plugins/skeleton_3d_editor_plugin.cpp

@@ -42,6 +42,7 @@
 #include "scene/3d/physics_body_3d.h"
 #include "scene/resources/capsule_shape_3d.h"
 #include "scene/resources/sphere_shape_3d.h"
+#include "scene/resources/surface_tool.h"
 
 void BoneTransformEditor::create_editors() {
 	const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor"));
@@ -50,6 +51,11 @@ void BoneTransformEditor::create_editors() {
 	section->setup("trf_properties", label, this, section_color, true);
 	add_child(section);
 
+	enabled_checkbox = memnew(CheckBox(TTR("Pose Enabled")));
+	enabled_checkbox->set_flat(true);
+	enabled_checkbox->set_visible(toggle_enabled);
+	section->get_vbox()->add_child(enabled_checkbox);
+
 	key_button = memnew(Button);
 	key_button->set_text(TTR("Key Transform"));
 	key_button->set_visible(keyable);
@@ -57,49 +63,40 @@ void BoneTransformEditor::create_editors() {
 	key_button->set_flat(true);
 	section->get_vbox()->add_child(key_button);
 
-	enabled_checkbox = memnew(CheckBox(TTR("Pose Enabled")));
-	enabled_checkbox->set_flat(true);
-	enabled_checkbox->set_visible(toggle_enabled);
-	section->get_vbox()->add_child(enabled_checkbox);
-
-	// Translation property
+	// Translation property.
 	translation_property = memnew(EditorPropertyVector3());
 	translation_property->setup(-10000, 10000, 0.001f, true);
 	translation_property->set_label("Translation");
 	translation_property->set_use_folding(true);
-	translation_property->set_read_only(false);
 	translation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3));
 	section->get_vbox()->add_child(translation_property);
 
-	// Rotation property
+	// Rotation property.
 	rotation_property = memnew(EditorPropertyVector3());
 	rotation_property->setup(-10000, 10000, 0.001f, true);
 	rotation_property->set_label("Rotation Degrees");
 	rotation_property->set_use_folding(true);
-	rotation_property->set_read_only(false);
 	rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3));
 	section->get_vbox()->add_child(rotation_property);
 
-	// Scale property
+	// Scale property.
 	scale_property = memnew(EditorPropertyVector3());
 	scale_property->setup(-10000, 10000, 0.001f, true);
 	scale_property->set_label("Scale");
 	scale_property->set_use_folding(true);
-	scale_property->set_read_only(false);
 	scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3));
 	section->get_vbox()->add_child(scale_property);
 
-	// Transform/Matrix section
+	// Transform/Matrix section.
 	transform_section = memnew(EditorInspectorSection);
 	transform_section->setup("trf_properties_transform", "Matrix", this, section_color, true);
 	section->get_vbox()->add_child(transform_section);
 
-	// Transform/Matrix property
+	// Transform/Matrix property.
 	transform_property = memnew(EditorPropertyTransform3D());
 	transform_property->setup(-10000, 10000, 0.001f, true);
 	transform_property->set_label("Transform");
 	transform_property->set_use_folding(true);
-	transform_property->set_read_only(false);
 	transform_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_transform));
 	transform_section->get_vbox()->add_child(transform_property);
 }
@@ -109,7 +106,7 @@ void BoneTransformEditor::_notification(int p_what) {
 		case NOTIFICATION_ENTER_TREE: {
 			create_editors();
 			key_button->connect("pressed", callable_mp(this, &BoneTransformEditor::_key_button_pressed));
-			enabled_checkbox->connect("toggled", callable_mp(this, &BoneTransformEditor::_checkbox_toggled));
+			enabled_checkbox->connect("pressed", callable_mp(this, &BoneTransformEditor::_checkbox_pressed));
 			[[fallthrough]];
 		}
 		case NOTIFICATION_SORT_CHILDREN: {
@@ -229,7 +226,7 @@ void BoneTransformEditor::_update_properties() {
 		return;
 	}
 
-	if (skeleton == nullptr) {
+	if (!skeleton) {
 		return;
 	}
 
@@ -244,7 +241,7 @@ void BoneTransformEditor::_update_custom_pose_properties() {
 		return;
 	}
 
-	if (skeleton == nullptr) {
+	if (!skeleton) {
 		return;
 	}
 
@@ -281,11 +278,32 @@ void BoneTransformEditor::set_target(const String &p_prop) {
 
 void BoneTransformEditor::set_keyable(const bool p_keyable) {
 	keyable = p_keyable;
+}
+
+void BoneTransformEditor::_update_key_button(const bool p_keyable) {
+	bool is_keyable = keyable && p_keyable;
 	if (key_button) {
-		key_button->set_visible(p_keyable);
+		key_button->set_visible(is_keyable);
 	}
 }
 
+void BoneTransformEditor::set_properties_read_only(const bool p_readonly) {
+	enabled_checkbox->set_disabled(p_readonly);
+	enabled_checkbox->update();
+}
+
+void BoneTransformEditor::set_transform_read_only(const bool p_readonly) {
+	translation_property->set_read_only(p_readonly);
+	rotation_property->set_read_only(p_readonly);
+	scale_property->set_read_only(p_readonly);
+	transform_property->set_read_only(p_readonly);
+	translation_property->update();
+	rotation_property->update();
+	scale_property->update();
+	transform_property->update();
+	_update_key_button(!p_readonly);
+}
+
 void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) {
 	toggle_enabled = p_enabled;
 	if (enabled_checkbox) {
@@ -294,7 +312,7 @@ void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) {
 }
 
 void BoneTransformEditor::_key_button_pressed() {
-	if (skeleton == nullptr) {
+	if (!skeleton) {
 		return;
 	}
 
@@ -305,30 +323,150 @@ void BoneTransformEditor::_key_button_pressed() {
 		return;
 	}
 
-	// Need to normalize the basis before you key it
 	Transform3D tform = compute_transform_from_vector3s();
-	tform.orthonormalize();
-	AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform);
+	AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_transform_key(skeleton, name, tform);
 }
 
-void BoneTransformEditor::_checkbox_toggled(const bool p_toggled) {
+void BoneTransformEditor::_checkbox_pressed() {
+	if (!skeleton) {
+		return;
+	}
+
+	const BoneId bone_id = property.get_slicec('/', 1).to_int();
 	if (enabled_checkbox) {
-		const String path = "bones/" + property.get_slicec('/', 1) + "/enabled";
-		skeleton->set(path, p_toggled);
+		undo_redo->create_action(TTR("Set Pose Enabled"));
+		bool enabled = skeleton->is_bone_enabled(bone_id);
+		undo_redo->add_do_method(skeleton, "set_bone_enabled", bone_id, !enabled);
+		undo_redo->add_undo_method(skeleton, "set_bone_enabled", bone_id, enabled);
+		undo_redo->commit_action();
 	}
 }
 
-void Skeleton3DEditor::_on_click_option(int p_option) {
+Skeleton3DEditor *Skeleton3DEditor::singleton = nullptr;
+
+void Skeleton3DEditor::set_keyable(const bool p_keyable) {
+	keyable = p_keyable;
+	skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_INSERT_KEYS, !p_keyable);
+	skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_INSERT_KEYS_EXISTED, !p_keyable);
+};
+
+void Skeleton3DEditor::set_rest_options_enabled(const bool p_rest_options_enabled) {
+	rest_options->get_popup()->set_item_disabled(REST_OPTION_POSE_TO_REST, !p_rest_options_enabled);
+};
+
+void Skeleton3DEditor::_update_show_rest_only() {
+	_update_pose_enabled(-1);
+}
+
+void Skeleton3DEditor::_update_pose_enabled(int p_bone) {
 	if (!skeleton) {
 		return;
 	}
+	if (pose_editor) {
+		pose_editor->set_properties_read_only(skeleton->is_show_rest_only());
 
-	switch (p_option) {
-		case MENU_OPTION_CREATE_PHYSICAL_SKELETON: {
+		if (selected_bone > 0) {
+			pose_editor->set_transform_read_only(skeleton->is_show_rest_only() || !(skeleton->is_bone_enabled(selected_bone)));
+		}
+	}
+	_update_gizmo_visible();
+}
+
+void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) {
+	if (!skeleton) {
+		return;
+	}
+
+	switch (p_skeleton_option) {
+		case SKELETON_OPTION_CREATE_PHYSICAL_SKELETON: {
 			create_physical_skeleton();
 			break;
 		}
+		case SKELETON_OPTION_INIT_POSE: {
+			init_pose();
+			break;
+		}
+		case SKELETON_OPTION_INSERT_KEYS: {
+			insert_keys(true);
+			break;
+		}
+		case SKELETON_OPTION_INSERT_KEYS_EXISTED: {
+			insert_keys(false);
+			break;
+		}
+	}
+}
+
+void Skeleton3DEditor::_on_click_rest_option(int p_rest_option) {
+	if (!skeleton) {
+		return;
+	}
+
+	switch (p_rest_option) {
+		case REST_OPTION_POSE_TO_REST: {
+			pose_to_rest();
+			break;
+		}
+	}
+}
+
+void Skeleton3DEditor::init_pose() {
+	const int bone_len = skeleton->get_bone_count();
+	if (!bone_len) {
+		return;
+	}
+	UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
+	ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS);
+	for (int i = 0; i < bone_len; i++) {
+		ur->add_do_method(skeleton, "set_bone_pose", i, Transform3D());
+		ur->add_undo_method(skeleton, "set_bone_pose", i, skeleton->get_bone_pose(i));
+	}
+	ur->commit_action();
+}
+
+void Skeleton3DEditor::insert_keys(bool p_all_bones) {
+	if (!skeleton) {
+		return;
+	}
+
+	int bone_len = skeleton->get_bone_count();
+	Node *root = EditorNode::get_singleton()->get_tree()->get_root();
+	String path = root->get_path_to(skeleton);
+
+	AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();
+	for (int i = 0; i < bone_len; i++) {
+		const String name = skeleton->get_bone_name(i);
+
+		if (name.is_empty()) {
+			continue;
+		}
+
+		if (!p_all_bones && !te->has_transform_track(skeleton, name)) {
+			continue;
+		}
+
+		Transform3D tform = skeleton->get_bone_pose(i);
+		te->insert_transform_key(skeleton, name, tform);
+	}
+}
+
+void Skeleton3DEditor::pose_to_rest() {
+	if (!skeleton) {
+		return;
 	}
+
+	// Todo: Do method with multiple bone selection.
+	UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
+	ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS);
+
+	ur->add_do_method(skeleton, "set_bone_pose", selected_bone, Transform3D());
+	ur->add_undo_method(skeleton, "set_bone_pose", selected_bone, skeleton->get_bone_pose(selected_bone));
+	ur->add_do_method(skeleton, "set_bone_custom_pose", selected_bone, Transform3D());
+	ur->add_undo_method(skeleton, "set_bone_custom_pose", selected_bone, skeleton->get_bone_custom_pose(selected_bone));
+	ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone) * skeleton->get_bone_custom_pose(selected_bone) * skeleton->get_bone_pose(selected_bone));
+	ur->add_undo_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone));
+
+	ur->commit_action();
 }
 
 void Skeleton3DEditor::create_physical_skeleton() {
@@ -356,7 +494,7 @@ void Skeleton3DEditor::create_physical_skeleton() {
 
 			bones_infos.write[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id);
 
-			/// create physical bone on parent
+			// Create physical bone on parent.
 			if (!bones_infos[parent].physical_bone) {
 				bones_infos.write[parent].physical_bone = create_physical_bone(parent, bone_id, bones_infos);
 
@@ -370,7 +508,7 @@ void Skeleton3DEditor::create_physical_skeleton() {
 				bones_infos[parent].physical_bone->set_owner(owner);
 				bones_infos[parent].physical_bone->get_child(0)->set_owner(owner); // set shape owner
 
-				/// Create joint between parent of parent
+				// Create joint between parent of parent.
 				if (-1 != parent_parent) {
 					bones_infos[parent].physical_bone->set_joint_type(PhysicalBone3D::JOINT_TYPE_PIN);
 				}
@@ -483,7 +621,7 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se
 	ERR_FAIL_NULL(skeleton);
 	UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
 	ur->create_action(TTR("Set Bone Parentage"));
-	// If the target is a child of ourselves, we move only *us* and not our children
+	// If the target is a child of ourselves, we move only *us* and not our children.
 	if (skeleton->is_bone_parent_of(p_target_boneidx, p_selected_boneidx)) {
 		const BoneId parent_idx = skeleton->get_bone_parent(p_selected_boneidx);
 		const int bone_count = skeleton->get_bone_count();
@@ -505,24 +643,30 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se
 
 void Skeleton3DEditor::_joint_tree_selection_changed() {
 	TreeItem *selected = joint_tree->get_selected();
-	const String path = selected->get_metadata(0);
+	if (selected) {
+		const String path = selected->get_metadata(0);
 
-	if (path.begins_with("bones/")) {
-		const int b_idx = path.get_slicec('/', 1).to_int();
-		const String bone_path = "bones/" + itos(b_idx) + "/";
+		if (path.begins_with("bones/")) {
+			const int b_idx = path.get_slicec('/', 1).to_int();
+			const String bone_path = "bones/" + itos(b_idx) + "/";
 
-		pose_editor->set_target(bone_path + "pose");
-		rest_editor->set_target(bone_path + "rest");
-		custom_pose_editor->set_target(bone_path + "custom_pose");
+			pose_editor->set_target(bone_path + "pose");
+			rest_editor->set_target(bone_path + "rest");
+			custom_pose_editor->set_target(bone_path + "custom_pose");
 
-		_update_properties();
+			pose_editor->set_visible(true);
+			rest_editor->set_visible(true);
+			custom_pose_editor->set_visible(true);
 
-		pose_editor->set_visible(true);
-		rest_editor->set_visible(true);
-		custom_pose_editor->set_visible(true);
+			selected_bone = b_idx;
+		}
 	}
+	set_rest_options_enabled(selected);
+	_update_properties();
+	_update_pose_enabled();
 }
 
+// May be not used with single select mode.
 void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos) {
 }
 
@@ -536,12 +680,13 @@ void Skeleton3DEditor::_update_properties() {
 	if (custom_pose_editor) {
 		custom_pose_editor->_update_custom_pose_properties();
 	}
+	_update_gizmo_transform();
 }
 
 void Skeleton3DEditor::update_joint_tree() {
 	joint_tree->clear();
 
-	if (skeleton == nullptr) {
+	if (!skeleton) {
 		return;
 	}
 
@@ -569,7 +714,7 @@ void Skeleton3DEditor::update_joint_tree() {
 		joint_item->set_selectable(0, true);
 		joint_item->set_metadata(0, "bones/" + itos(current_bone_idx));
 
-		// Add the bone's children to the list of bones to be processed
+		// Add the bone's children to the list of bones to be processed.
 		Vector<int> current_bone_child_bones = 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++) {
@@ -587,16 +732,56 @@ void Skeleton3DEditor::create_editors() {
 
 	set_focus_mode(FOCUS_ALL);
 
-	// Create Top Menu Bar
-	options = memnew(MenuButton);
-	Node3DEditor::get_singleton()->add_control_to_menu_panel(options);
+	Node3DEditor *ne = Node3DEditor::get_singleton();
+	AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();
 
-	options->set_text(TTR("Skeleton3D"));
-	options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons")));
+	// Create Top Menu Bar.
+	separator = memnew(VSeparator);
+	ne->add_control_to_menu_panel(separator);
 
-	options->get_popup()->add_item(TTR("Create physical skeleton"), MENU_OPTION_CREATE_PHYSICAL_SKELETON);
+	// Create Skeleton Option in Top Menu Bar.
+	skeleton_options = memnew(MenuButton);
+	ne->add_control_to_menu_panel(skeleton_options);
 
-	options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_option));
+	skeleton_options->set_text(TTR("Skeleton3D"));
+	skeleton_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons")));
+
+	skeleton_options->get_popup()->add_item(TTR("Init pose"), SKELETON_OPTION_INIT_POSE);
+	skeleton_options->get_popup()->add_item(TTR("Insert key of all bone poses"), SKELETON_OPTION_INSERT_KEYS);
+	skeleton_options->get_popup()->add_item(TTR("Insert key of bone poses already exist track"), SKELETON_OPTION_INSERT_KEYS_EXISTED);
+	skeleton_options->get_popup()->add_item(TTR("Create physical skeleton"), SKELETON_OPTION_CREATE_PHYSICAL_SKELETON);
+
+	skeleton_options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_skeleton_option));
+
+	// Create Rest Option in Top Menu Bar.
+	rest_options = memnew(MenuButton);
+	ne->add_control_to_menu_panel(rest_options);
+
+	rest_options->set_text(TTR("Edit Rest"));
+	rest_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons")));
+
+	rest_options->get_popup()->add_item(TTR("Apply current pose to rest"), REST_OPTION_POSE_TO_REST);
+	rest_options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_rest_option));
+	set_rest_options_enabled(false);
+
+	Vector<Variant> button_binds;
+	button_binds.resize(1);
+
+	edit_mode_button = memnew(Button);
+	ne->add_control_to_menu_panel(edit_mode_button);
+	edit_mode_button->set_tooltip(TTR("Edit Mode\nShow buttons on joints."));
+	edit_mode_button->set_toggle_mode(true);
+	edit_mode_button->set_flat(true);
+	edit_mode_button->connect("toggled", callable_mp(this, &Skeleton3DEditor::edit_mode_toggled));
+
+	edit_mode = false;
+
+	set_keyable(te->has_keying());
+
+	if (skeleton) {
+		skeleton->add_child(handles_mesh_instance);
+		handles_mesh_instance->set_skeleton_path(NodePath(""));
+	}
 
 	const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor"));
 
@@ -612,7 +797,7 @@ void Skeleton3DEditor::create_editors() {
 
 	joint_tree = memnew(Tree);
 	joint_tree->set_columns(1);
-	joint_tree->set_focus_mode(Control::FocusMode::FOCUS_NONE);
+	joint_tree->set_focus_mode(Control::FOCUS_NONE);
 	joint_tree->set_select_mode(Tree::SELECT_SINGLE);
 	joint_tree->set_hide_root(true);
 	joint_tree->set_v_size_flags(SIZE_EXPAND_FILL);
@@ -623,8 +808,8 @@ void Skeleton3DEditor::create_editors() {
 
 	pose_editor = memnew(BoneTransformEditor(skeleton));
 	pose_editor->set_label(TTR("Bone Pose"));
-	pose_editor->set_keyable(AnimationPlayerEditor::singleton->get_track_editor()->has_keying());
 	pose_editor->set_toggle_enabled(true);
+	pose_editor->set_keyable(te->has_keying());
 	pose_editor->set_visible(false);
 	add_child(pose_editor);
 
@@ -632,27 +817,34 @@ void Skeleton3DEditor::create_editors() {
 	rest_editor->set_label(TTR("Bone Rest"));
 	rest_editor->set_visible(false);
 	add_child(rest_editor);
+	rest_editor->set_transform_read_only(true);
 
 	custom_pose_editor = memnew(BoneTransformEditor(skeleton));
 	custom_pose_editor->set_label(TTR("Bone Custom Pose"));
 	custom_pose_editor->set_visible(false);
 	add_child(custom_pose_editor);
+	custom_pose_editor->set_transform_read_only(true);
 }
 
 void Skeleton3DEditor::_notification(int p_what) {
 	switch (p_what) {
+		case NOTIFICATION_READY: {
+			edit_mode_button->set_icon(get_theme_icon("ToolBoneSelect", "EditorIcons"));
+			get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Vector<Variant>(), Object::CONNECT_ONESHOT);
+			break;
+		}
 		case NOTIFICATION_ENTER_TREE: {
 			create_editors();
 			update_joint_tree();
 			update_editors();
-
-			get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Vector<Variant>(), Object::CONNECT_ONESHOT);
 			joint_tree->connect("item_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed));
 			joint_tree->connect("item_rmb_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select));
 #ifdef TOOLS_ENABLED
+			skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo));
 			skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties));
-#endif // TOOLS_ENABLED
-
+			skeleton->connect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_update_pose_enabled));
+			skeleton->connect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_show_rest_only));
+#endif
 			break;
 		}
 	}
@@ -661,7 +853,8 @@ void Skeleton3DEditor::_notification(int p_what) {
 void Skeleton3DEditor::_node_removed(Node *p_node) {
 	if (skeleton && p_node == skeleton) {
 		skeleton = nullptr;
-		options->hide();
+		skeleton_options->hide();
+		rest_options->hide();
 	}
 
 	_update_properties();
@@ -671,24 +864,237 @@ void Skeleton3DEditor::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("_node_removed"), &Skeleton3DEditor::_node_removed);
 	ClassDB::bind_method(D_METHOD("_joint_tree_selection_changed"), &Skeleton3DEditor::_joint_tree_selection_changed);
 	ClassDB::bind_method(D_METHOD("_joint_tree_rmb_select"), &Skeleton3DEditor::_joint_tree_rmb_select);
+	ClassDB::bind_method(D_METHOD("_update_show_rest_only"), &Skeleton3DEditor::_update_show_rest_only);
+	ClassDB::bind_method(D_METHOD("_update_pose_enabled"), &Skeleton3DEditor::_update_pose_enabled);
 	ClassDB::bind_method(D_METHOD("_update_properties"), &Skeleton3DEditor::_update_properties);
-	ClassDB::bind_method(D_METHOD("_on_click_option"), &Skeleton3DEditor::_on_click_option);
+	ClassDB::bind_method(D_METHOD("_on_click_skeleton_option"), &Skeleton3DEditor::_on_click_skeleton_option);
+	ClassDB::bind_method(D_METHOD("_on_click_rest_option"), &Skeleton3DEditor::_on_click_rest_option);
+
+	ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw);
+	ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw);
+	ClassDB::bind_method(D_METHOD("drop_data_fw"), &Skeleton3DEditor::drop_data_fw);
 
-	ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw);
-	ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw);
-	ClassDB::bind_method(D_METHOD("_drop_data_fw"), &Skeleton3DEditor::drop_data_fw);
 	ClassDB::bind_method(D_METHOD("move_skeleton_bone"), &Skeleton3DEditor::move_skeleton_bone);
+
+	ClassDB::bind_method(D_METHOD("_draw_gizmo"), &Skeleton3DEditor::_draw_gizmo);
+}
+
+void Skeleton3DEditor::edit_mode_toggled(const bool pressed) {
+	edit_mode = pressed;
+	_update_gizmo_visible();
 }
 
 Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *p_skeleton) :
 		editor(p_editor),
 		editor_plugin(e_plugin),
 		skeleton(p_skeleton) {
+	singleton = this;
+
+	// Handle.
+	handle_material = Ref<ShaderMaterial>(memnew(ShaderMaterial));
+	handle_shader = Ref<Shader>(memnew(Shader));
+	handle_shader->set_code(R"(
+// Skeleton 3D gizmo handle shader.
+
+shader_type spatial;
+render_mode unshaded, shadows_disabled, depth_draw_always;
+uniform sampler2D texture_albedo : hint_albedo;
+uniform float point_size : hint_range(0,128) = 32;
+void vertex() {
+	if (!OUTPUT_IS_SRGB) {
+		COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) );
+	}
+	VERTEX = VERTEX;
+	POSITION=PROJECTION_MATRIX*INV_CAMERA_MATRIX*WORLD_MATRIX*vec4(VERTEX.xyz,1.0);
+	POSITION.z = mix(POSITION.z, 0, 0.999);
+	POINT_SIZE = point_size;
+}
+void fragment() {
+	vec4 albedo_tex = texture(texture_albedo,POINT_COORD);
+	vec3 col = albedo_tex.rgb + COLOR.rgb;
+	col = vec3(min(col.r,1.0),min(col.g,1.0),min(col.b,1.0));
+	ALBEDO = col;
+	if (albedo_tex.a < 0.5) { discard; }
+	ALPHA = albedo_tex.a;
+}
+)");
+	handle_material->set_shader(handle_shader);
+	Ref<Texture2D> handle = editor->get_gui_base()->get_theme_icon("EditorBoneHandle", "EditorIcons");
+	handle_material->set_shader_param("point_size", handle->get_width());
+	handle_material->set_shader_param("texture_albedo", handle);
+
+	handles_mesh_instance = memnew(MeshInstance3D);
+	handles_mesh_instance->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF);
+	handles_mesh.instantiate();
+	handles_mesh_instance->set_mesh(handles_mesh);
+}
+
+void Skeleton3DEditor::update_bone_original() {
+	if (!skeleton) {
+		return;
+	}
+	if (skeleton->get_bone_count() == 0 || selected_bone == -1) {
+		return;
+	}
+	bone_original = skeleton->get_bone_pose(selected_bone);
+}
+
+void Skeleton3DEditor::_hide_handles() {
+	handles_mesh_instance->hide();
+}
+
+void Skeleton3DEditor::_draw_gizmo() {
+	if (!skeleton) {
+		return;
+	}
+
+	// If you call get_bone_global_pose() while drawing the surface, such as toggle rest mode,
+	// the skeleton update will be done first and
+	// the drawing surface will be interrupted once and an error will occur.
+	skeleton->force_update_all_dirty_bones();
+
+	// Handles.
+	if (edit_mode) {
+		_draw_handles();
+	} else {
+		_hide_handles();
+	}
+}
+
+void Skeleton3DEditor::_draw_handles() {
+	handles_mesh_instance->show();
+
+	const int bone_len = skeleton->get_bone_count();
+	handles_mesh->clear_surfaces();
+	handles_mesh->surface_begin(Mesh::PRIMITIVE_POINTS);
+
+	for (int i = 0; i < bone_len; i++) {
+		Color c;
+		if (i == selected_bone) {
+			c = Color(1, 1, 0);
+		} else {
+			c = Color(0.1, 0.25, 0.8);
+		}
+		Vector3 point = skeleton->get_bone_global_pose(i).origin;
+		handles_mesh->surface_set_color(c);
+		handles_mesh->surface_add_vertex(point);
+	}
+	handles_mesh->surface_end();
+	handles_mesh->surface_set_material(0, handle_material);
+}
+
+TreeItem *Skeleton3DEditor::_find(TreeItem *p_node, const NodePath &p_path) {
+	if (!p_node) {
+		return nullptr;
+	}
+
+	NodePath np = p_node->get_metadata(0);
+	if (np == p_path) {
+		return p_node;
+	}
+
+	TreeItem *children = p_node->get_first_child();
+	while (children) {
+		TreeItem *n = _find(children, p_path);
+		if (n) {
+			return n;
+		}
+		children = children->get_next();
+	}
+
+	return nullptr;
+}
+
+void Skeleton3DEditor::_subgizmo_selection_change() {
+	if (!skeleton) {
+		return;
+	}
+
+	// Once validated by subgizmos_intersect_ray, but required if through inspector's bones tree.
+	if (!edit_mode) {
+		skeleton->clear_subgizmo_selection();
+		return;
+	}
+
+	int selected = -1;
+	Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
+	if (se) {
+		selected = se->get_selected_bone();
+	}
+
+	if (selected >= 0) {
+		Vector<Ref<Node3DGizmo>> gizmos = skeleton->get_gizmos();
+		for (int i = 0; i < gizmos.size(); i++) {
+			Ref<EditorNode3DGizmo> gizmo = gizmos[i];
+			if (!gizmo.is_valid()) {
+				continue;
+			}
+			Ref<Skeleton3DGizmoPlugin> plugin = gizmo->get_plugin();
+			if (!plugin.is_valid()) {
+				continue;
+			}
+			skeleton->set_subgizmo_selection(gizmo, selected, skeleton->get_bone_global_pose(selected));
+			break;
+		}
+	} else {
+		skeleton->clear_subgizmo_selection();
+	}
+}
+
+void Skeleton3DEditor::select_bone(int p_idx) {
+	if (p_idx >= 0) {
+		TreeItem *ti = _find(joint_tree->get_root(), "bones/" + itos(p_idx));
+		if (ti) {
+			// Make visible when it's collapsed.
+			TreeItem *node = ti->get_parent();
+			while (node && node != joint_tree->get_root()) {
+				node->set_collapsed(false);
+				node = node->get_parent();
+			}
+			ti->select(0);
+			joint_tree->scroll_to_item(ti);
+		}
+	} else {
+		selected_bone = -1;
+		joint_tree->deselect_all();
+		_joint_tree_selection_changed();
+	}
 }
 
 Skeleton3DEditor::~Skeleton3DEditor() {
-	if (options) {
-		Node3DEditor::get_singleton()->remove_control_from_menu_panel(options);
+	if (skeleton) {
+#ifdef TOOLS_ENABLED
+		skeleton->disconnect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_show_rest_only));
+		skeleton->disconnect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_update_pose_enabled));
+		skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo));
+		skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties));
+		skeleton->set_transform_gizmo_visible(true);
+#endif
+		handles_mesh_instance->get_parent()->remove_child(handles_mesh_instance);
+	}
+
+	handles_mesh_instance->queue_delete();
+
+	Node3DEditor *ne = Node3DEditor::get_singleton();
+
+	if (separator) {
+		ne->remove_control_from_menu_panel(separator);
+		memdelete(separator);
+	}
+
+	if (skeleton_options) {
+		ne->remove_control_from_menu_panel(skeleton_options);
+		memdelete(skeleton_options);
+	}
+
+	if (rest_options) {
+		ne->remove_control_from_menu_panel(rest_options);
+		memdelete(rest_options);
+	}
+
+	if (edit_mode_button) {
+		ne->remove_control_from_menu_panel(edit_mode_button);
+		memdelete(edit_mode_button);
 	}
 }
 
@@ -700,16 +1106,413 @@ void EditorInspectorPluginSkeleton::parse_begin(Object *p_object) {
 	Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_object);
 	ERR_FAIL_COND(!skeleton);
 
-	Skeleton3DEditor *skel_editor = memnew(Skeleton3DEditor(this, editor, skeleton));
+	skel_editor = memnew(Skeleton3DEditor(this, editor, skeleton));
 	add_custom_control(skel_editor);
 }
 
 Skeleton3DEditorPlugin::Skeleton3DEditorPlugin(EditorNode *p_node) {
 	editor = p_node;
 
-	Ref<EditorInspectorPluginSkeleton> skeleton_plugin;
-	skeleton_plugin.instantiate();
+	skeleton_plugin = memnew(EditorInspectorPluginSkeleton);
 	skeleton_plugin->editor = editor;
 
 	EditorInspector::add_inspector_plugin(skeleton_plugin);
+
+	Ref<Skeleton3DGizmoPlugin> gizmo_plugin = Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin));
+	Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
+}
+
+EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
+	Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
+	Node3DEditor *ne = Node3DEditor::get_singleton();
+	if (se->is_edit_mode()) {
+		const Ref<InputEventMouseButton> mb = p_event;
+		if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+			if (ne->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) {
+				if (!ne->is_gizmo_visible()) {
+					return EditorPlugin::AFTER_GUI_INPUT_STOP;
+				}
+			}
+			if (mb->is_pressed()) {
+				se->update_bone_original();
+			}
+		}
+		return EditorPlugin::AFTER_GUI_INPUT_DESELECT;
+	}
+	return EditorPlugin::AFTER_GUI_INPUT_PASS;
+}
+
+bool Skeleton3DEditorPlugin::handles(Object *p_object) const {
+	return p_object->is_class("Skeleton3D");
+}
+
+void Skeleton3DEditor::_update_gizmo_transform() {
+	Node3DEditor::get_singleton()->update_transform_gizmo();
+};
+
+void Skeleton3DEditor::_update_gizmo_visible() {
+	_subgizmo_selection_change();
+	if (edit_mode) {
+		if (selected_bone == -1) {
+#ifdef TOOLS_ENABLED
+			skeleton->set_transform_gizmo_visible(false);
+#endif
+		} else {
+#ifdef TOOLS_ENABLED
+			if (skeleton->is_bone_enabled(selected_bone) && !skeleton->is_show_rest_only()) {
+				skeleton->set_transform_gizmo_visible(true);
+			} else {
+				skeleton->set_transform_gizmo_visible(false);
+			}
+#endif
+		}
+	} else {
+#ifdef TOOLS_ENABLED
+		skeleton->set_transform_gizmo_visible(true);
+#endif
+	}
+	_draw_gizmo();
+}
+
+int Skeleton3DEditor::get_selected_bone() const {
+	return selected_bone;
+}
+
+Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() {
+	unselected_mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
+	unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+	unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+	unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+	unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
+
+	selected_mat = Ref<ShaderMaterial>(memnew(ShaderMaterial));
+	selected_sh = Ref<Shader>(memnew(Shader));
+	selected_sh->set_code(R"(
+// Skeleton 3D gizmo bones shader.
+
+shader_type spatial;
+render_mode unshaded, shadows_disabled;
+void vertex() {
+	if (!OUTPUT_IS_SRGB) {
+		COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) );
+	}
+	VERTEX = VERTEX;
+	POSITION=PROJECTION_MATRIX*INV_CAMERA_MATRIX*WORLD_MATRIX*vec4(VERTEX.xyz,1.0);
+	POSITION.z = mix(POSITION.z, 0, 0.998);
+}
+void fragment() {
+	ALBEDO = COLOR.rgb;
+	ALPHA = COLOR.a;
+}
+)");
+	selected_mat->set_shader(selected_sh);
+
+	// Regist properties in editor settings.
+	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4));
+	EDITOR_DEF("editors/3d_gizmos/gizmo_colors/selected_bone", Color(0.8, 0.3, 0.0));
+	EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_axis_length", (float)0.1);
+	EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_shape", 1);
+	EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d_gizmos/gizmo_settings/bone_shape", PROPERTY_HINT_ENUM, "Wire,Octahedron"));
+}
+
+bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+	return Object::cast_to<Skeleton3D>(p_spatial) != nullptr;
+}
+
+String Skeleton3DGizmoPlugin::get_gizmo_name() const {
+	return "Skeleton3D";
+}
+
+int Skeleton3DGizmoPlugin::get_priority() const {
+	return -1;
+}
+
+int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const {
+	Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
+	ERR_FAIL_COND_V(!skeleton, -1);
+
+	Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
+
+	if (!se->is_edit_mode()) {
+		return -1;
+	}
+
+	if (Node3DEditor::get_singleton()->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) {
+		return -1;
+	}
+
+	// Select bone.
+	real_t grab_threshold = 4 * EDSCALE;
+	Vector3 ray_from = p_camera->get_global_transform().origin;
+	Transform3D gt = skeleton->get_global_transform();
+	int closest_idx = -1;
+	real_t closest_dist = 1e10;
+	const int bone_len = skeleton->get_bone_count();
+	for (int i = 0; i < bone_len; i++) {
+		Vector3 joint_pos_3d = gt.xform(skeleton->get_bone_global_pose(i).origin);
+		Vector2 joint_pos_2d = p_camera->unproject_position(joint_pos_3d);
+		real_t dist_3d = ray_from.distance_to(joint_pos_3d);
+		real_t dist_2d = p_point.distance_to(joint_pos_2d);
+		if (dist_2d < grab_threshold && dist_3d < closest_dist) {
+			closest_dist = dist_3d;
+			closest_idx = i;
+		}
+	}
+
+	if (closest_idx >= 0) {
+		WARN_PRINT("ray:");
+		WARN_PRINT(itos(closest_idx));
+		se->select_bone(closest_idx);
+		return closest_idx;
+	}
+
+	se->select_bone(-1);
+	return -1;
+}
+
+Transform3D Skeleton3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+	Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
+	ERR_FAIL_COND_V(!skeleton, Transform3D());
+
+	return skeleton->get_bone_global_pose(p_id);
+}
+
+void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) {
+	Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
+	ERR_FAIL_COND(!skeleton);
+
+	// Prepare for global to local.
+	Transform3D original_to_local = Transform3D();
+	int parent_idx = skeleton->get_bone_parent(p_id);
+	if (parent_idx >= 0) {
+		original_to_local = original_to_local * skeleton->get_bone_global_pose(parent_idx);
+	}
+	original_to_local = original_to_local * skeleton->get_bone_rest(p_id) * skeleton->get_bone_custom_pose(p_id);
+	Basis to_local = original_to_local.get_basis().inverse();
+
+	// Prepare transform.
+	Transform3D t = Transform3D();
+
+	// Basis.
+	t.basis = to_local * p_transform.get_basis();
+
+	// Origin.
+	Vector3 orig = Vector3();
+	orig = skeleton->get_bone_pose(p_id).origin;
+	Vector3 sub = p_transform.origin - skeleton->get_bone_global_pose(p_id).origin;
+	t.origin = orig + to_local.xform(sub);
+
+	// Apply transform.
+	skeleton->set_bone_pose(p_id, t);
+}
+
+void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) {
+	Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
+	ERR_FAIL_COND(!skeleton);
+
+	Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
+
+	UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
+	for (int i = 0; i < p_ids.size(); i++) {
+		ur->create_action(TTR("Set Bone Transform"));
+		ur->add_do_method(skeleton, "set_bone_pose", p_ids[i], skeleton->get_bone_pose(p_ids[i]));
+		ur->add_undo_method(skeleton, "set_bone_pose", p_ids[i], se->get_bone_original());
+	}
+	ur->commit_action();
+}
+
+void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+	Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
+	p_gizmo->clear();
+
+	int selected = -1;
+	Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
+	if (se) {
+		selected = se->get_selected_bone();
+	}
+
+	Color bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/skeleton");
+	Color selected_bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/selected_bone");
+	real_t bone_axis_length = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_settings/bone_axis_length");
+	int bone_shape = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_settings/bone_shape");
+
+	LocalVector<Color> axis_colors;
+	axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_x_color"), SNAME("Editor")));
+	axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_y_color"), SNAME("Editor")));
+	axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_z_color"), SNAME("Editor")));
+
+	Ref<SurfaceTool> surface_tool(memnew(SurfaceTool));
+	surface_tool->begin(Mesh::PRIMITIVE_LINES);
+
+	if (p_gizmo->is_selected()) {
+		surface_tool->set_material(selected_mat);
+	} else {
+		unselected_mat->set_albedo(bone_color);
+		surface_tool->set_material(unselected_mat);
+	}
+
+	Vector<Transform3D> grests;
+	grests.resize(skeleton->get_bone_count());
+
+	LocalVector<int> bones;
+	LocalVector<float> weights;
+	bones.resize(4);
+	weights.resize(4);
+	for (int i = 0; i < 4; i++) {
+		bones[i] = 0;
+		weights[i] = 0;
+	}
+	weights[0] = 1;
+
+	int current_bone_index = 0;
+	Vector<int> bones_to_process = skeleton->get_parentless_bones();
+
+	while (bones_to_process.size() > current_bone_index) {
+		int current_bone_idx = bones_to_process[current_bone_index];
+		current_bone_index++;
+
+		Color current_bone_color = (current_bone_idx == selected) ? selected_bone_color : bone_color;
+
+		Vector<int> child_bones_vector;
+		child_bones_vector = skeleton->get_bone_children(current_bone_idx);
+		int child_bones_size = child_bones_vector.size();
+
+		// You have children but no parent, then you must be a root/parentless bone.
+		if (skeleton->get_bone_parent(current_bone_idx) < 0) {
+			grests.write[current_bone_idx] = skeleton->get_bone_rest(current_bone_idx);
+		}
+
+		for (int i = 0; i < child_bones_size; i++) {
+			// Something wrong.
+			if (child_bones_vector[i] < 0) {
+				continue;
+			}
+
+			int child_bone_idx = child_bones_vector[i];
+
+			grests.write[child_bone_idx] = grests[current_bone_idx] * skeleton->get_bone_rest(child_bone_idx);
+
+			Vector3 v0 = grests[current_bone_idx].origin;
+			Vector3 v1 = grests[child_bone_idx].origin;
+			Vector3 d = (v1 - v0).normalized();
+			real_t dist = v0.distance_to(v1);
+
+			// Find closest axis.
+			int closest = -1;
+			real_t closest_d = 0.0;
+			for (int j = 0; j < 3; j++) {
+				real_t dp = Math::abs(grests[current_bone_idx].basis[j].normalized().dot(d));
+				if (j == 0 || dp > closest_d) {
+					closest = j;
+				}
+			}
+
+			// Draw bone.
+			switch (bone_shape) {
+				case 0: { // Wire shape.
+					surface_tool->set_color(current_bone_color);
+					bones[0] = current_bone_idx;
+					surface_tool->set_bones(bones);
+					surface_tool->set_weights(weights);
+					surface_tool->add_vertex(v0);
+					bones[0] = child_bone_idx;
+					surface_tool->set_bones(bones);
+					surface_tool->set_weights(weights);
+					surface_tool->add_vertex(v1);
+				} break;
+
+				case 1: { // Octahedron shape.
+					Vector3 first;
+					Vector3 points[6];
+					int point_idx = 0;
+					for (int j = 0; j < 3; j++) {
+						Vector3 axis;
+						if (first == Vector3()) {
+							axis = d.cross(d.cross(grests[current_bone_idx].basis[j])).normalized();
+							first = axis;
+						} else {
+							axis = d.cross(first).normalized();
+						}
+
+						surface_tool->set_color(current_bone_color);
+						for (int k = 0; k < 2; k++) {
+							if (k == 1) {
+								axis = -axis;
+							}
+							Vector3 point = v0 + d * dist * 0.2;
+							point += axis * dist * 0.1;
+
+							bones[0] = current_bone_idx;
+							surface_tool->set_bones(bones);
+							surface_tool->set_weights(weights);
+							surface_tool->add_vertex(v0);
+							surface_tool->set_bones(bones);
+							surface_tool->set_weights(weights);
+							surface_tool->add_vertex(point);
+
+							surface_tool->set_bones(bones);
+							surface_tool->set_weights(weights);
+							surface_tool->add_vertex(point);
+							bones[0] = child_bone_idx;
+							surface_tool->set_bones(bones);
+							surface_tool->set_weights(weights);
+							surface_tool->add_vertex(v1);
+							points[point_idx++] = point;
+						}
+					}
+					surface_tool->set_color(current_bone_color);
+					SWAP(points[1], points[2]);
+					bones[0] = current_bone_idx;
+					for (int j = 0; j < 6; j++) {
+						surface_tool->set_bones(bones);
+						surface_tool->set_weights(weights);
+						surface_tool->add_vertex(points[j]);
+						surface_tool->set_bones(bones);
+						surface_tool->set_weights(weights);
+						surface_tool->add_vertex(points[(j + 1) % 6]);
+					}
+				} break;
+			}
+
+			// Axis as root of the bone.
+			for (int j = 0; j < 3; j++) {
+				bones[0] = current_bone_idx;
+				surface_tool->set_color(axis_colors[j]);
+				surface_tool->set_bones(bones);
+				surface_tool->set_weights(weights);
+				surface_tool->add_vertex(v0);
+				surface_tool->set_bones(bones);
+				surface_tool->set_weights(weights);
+				surface_tool->add_vertex(v0 + (grests[current_bone_idx].basis.inverse())[j].normalized() * dist * bone_axis_length);
+
+				if (j == closest) {
+					continue;
+				}
+			}
+
+			// Axis at the end of the bone children.
+			if (i == child_bones_size - 1) {
+				for (int j = 0; j < 3; j++) {
+					bones[0] = child_bone_idx;
+					surface_tool->set_color(axis_colors[j]);
+					surface_tool->set_bones(bones);
+					surface_tool->set_weights(weights);
+					surface_tool->add_vertex(v1);
+					surface_tool->set_bones(bones);
+					surface_tool->set_weights(weights);
+					surface_tool->add_vertex(v1 + (grests[child_bone_idx].basis.inverse())[j].normalized() * dist * bone_axis_length);
+
+					if (j == closest) {
+						continue;
+					}
+				}
+			}
+
+			// Add the bone's children to the list of bones to be processed.
+			bones_to_process.push_back(child_bones_vector[i]);
+		}
+	}
+
+	Ref<ArrayMesh> m = surface_tool->commit();
+	p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(Ref<Skin>()));
 }

+ 114 - 21
editor/plugins/skeleton_3d_editor_plugin.h

@@ -33,7 +33,12 @@
 
 #include "editor/editor_node.h"
 #include "editor/editor_plugin.h"
+#include "editor/editor_properties.h"
+#include "node_3d_editor_plugin.h"
+#include "scene/3d/camera_3d.h"
+#include "scene/3d/mesh_instance_3d.h"
 #include "scene/3d/skeleton_3d.h"
+#include "scene/resources/immediate_mesh.h"
 
 class EditorInspectorPluginSkeleton;
 class Joint;
@@ -41,8 +46,6 @@ class PhysicalBone3D;
 class Skeleton3DEditorPlugin;
 class Button;
 class CheckBox;
-class EditorPropertyTransform3D;
-class EditorPropertyVector3;
 
 class BoneTransformEditor : public VBoxContainer {
 	GDCLASS(BoneTransformEditor, VBoxContainer);
@@ -81,6 +84,8 @@ class BoneTransformEditor : public VBoxContainer {
 	void _value_changed_transform(const String p_property_name, const Transform3D p_transform, const StringName p_edited_property_name, const bool p_boolean);
 	// Changes the transform to the given transform and updates the UI accordingly.
 	void _change_transform(Transform3D p_new_transform);
+	// Update it is truely keyable then.
+	void _update_key_button(const bool p_keyable);
 	// Creates a Transform using the EditorPropertyVector3 properties.
 	Transform3D compute_transform_from_vector3s() const;
 
@@ -92,7 +97,7 @@ protected:
 public:
 	BoneTransformEditor(Skeleton3D *p_skeleton);
 
-	// Which transform target to modify
+	// Which transform target to modify.
 	void set_target(const String &p_prop);
 	void set_label(const String &p_label) { label = p_label; }
 
@@ -100,20 +105,21 @@ public:
 	void _update_custom_pose_properties();
 	void _update_transform_properties(Transform3D p_transform);
 
-	// Can/cannot modify the spinner values for the Transform
-	void set_read_only(const bool p_read_only);
-
-	// Transform can be keyed, whether or not to show the button
+	// Transform can be keyed, whether or not to show the button.
 	void set_keyable(const bool p_keyable);
 
-	// Bone can be toggled enabled or disabled, whether or not to show the checkbox
+	// When rest mode, pose and custom_pose editor are diasbled.
+	void set_properties_read_only(const bool p_readonly);
+	void set_transform_read_only(const bool p_readonly);
+
+	// Bone can be toggled enabled or disabled, whether or not to show the checkbox.
 	void set_toggle_enabled(const bool p_enabled);
 
-	// Key Transform Button pressed
+	// Key Transform Button pressed.
 	void _key_button_pressed();
 
-	// Bone Enabled Checkbox toggled
-	void _checkbox_toggled(const bool p_toggled);
+	// Bone Enabled Checkbox toggled.
+	void _checkbox_pressed();
 };
 
 class Skeleton3DEditor : public VBoxContainer {
@@ -121,13 +127,20 @@ class Skeleton3DEditor : public VBoxContainer {
 
 	friend class Skeleton3DEditorPlugin;
 
-	enum Menu {
-		MENU_OPTION_CREATE_PHYSICAL_SKELETON
+	enum SkeletonOption {
+		SKELETON_OPTION_INIT_POSE,
+		SKELETON_OPTION_INSERT_KEYS,
+		SKELETON_OPTION_INSERT_KEYS_EXISTED,
+		SKELETON_OPTION_CREATE_PHYSICAL_SKELETON
+	};
+
+	enum RestOption {
+		REST_OPTION_POSE_TO_REST
 	};
 
 	struct BoneInfo {
 		PhysicalBone3D *physical_bone = nullptr;
-		Transform3D relative_rest; // Relative to skeleton node
+		Transform3D relative_rest; // Relative to skeleton node.
 	};
 
 	EditorNode *editor;
@@ -140,13 +153,24 @@ class Skeleton3DEditor : public VBoxContainer {
 	BoneTransformEditor *pose_editor = nullptr;
 	BoneTransformEditor *custom_pose_editor = nullptr;
 
-	MenuButton *options = nullptr;
+	VSeparator *separator;
+	MenuButton *skeleton_options = nullptr;
+	MenuButton *rest_options = nullptr;
+	Button *edit_mode_button;
+
+	bool edit_mode = false;
+
 	EditorFileDialog *file_dialog = nullptr;
 
-	UndoRedo *undo_redo = nullptr;
+	bool keyable;
+
+	static Skeleton3DEditor *singleton;
 
-	void _on_click_option(int p_option);
+	void _on_click_skeleton_option(int p_skeleton_option);
+	void _on_click_rest_option(int p_rest_option);
 	void _file_selected(const String &p_file);
+	TreeItem *_find(TreeItem *p_node, const NodePath &p_path);
+	void edit_mode_toggled(const bool pressed);
 
 	EditorFileDialog *file_export_lib = nullptr;
 
@@ -155,6 +179,10 @@ class Skeleton3DEditor : public VBoxContainer {
 
 	void create_editors();
 
+	void init_pose();
+	void insert_keys(bool p_all_bones);
+	void pose_to_rest();
+
 	void create_physical_skeleton();
 	PhysicalBone3D *create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos);
 
@@ -162,20 +190,56 @@ class Skeleton3DEditor : public VBoxContainer {
 	bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
 	void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
 
+	void set_keyable(const bool p_keyable);
+	void set_rest_options_enabled(const bool p_rest_options_enabled);
+
+	// Handle.
+	MeshInstance3D *handles_mesh_instance;
+	Ref<ImmediateMesh> handles_mesh;
+	Ref<ShaderMaterial> handle_material;
+	Ref<Shader> handle_shader;
+
+	Transform3D bone_original;
+
+	void _update_pose_enabled(int p_bone = -1);
+	void _update_show_rest_only();
+
+	void _update_gizmo_transform();
+	void _update_gizmo_visible();
+
+	void _hide_handles();
+
+	void _draw_gizmo();
+	void _draw_handles();
+
+	void _joint_tree_selection_changed();
+	void _joint_tree_rmb_select(const Vector2 &p_pos);
+	void _update_properties();
+
+	void _subgizmo_selection_change();
+
+	int selected_bone = -1;
+
 protected:
 	void _notification(int p_what);
 	void _node_removed(Node *p_node);
 	static void _bind_methods();
 
 public:
+	static Skeleton3DEditor *get_singleton() { return singleton; }
+
+	void select_bone(int p_idx);
+
+	int get_selected_bone() const;
+
 	void move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx);
 
 	Skeleton3D *get_skeleton() const { return skeleton; };
 
-	void _joint_tree_selection_changed();
-	void _joint_tree_rmb_select(const Vector2 &p_pos);
+	bool is_edit_mode() const { return edit_mode; }
 
-	void _update_properties();
+	void update_bone_original();
+	Transform3D get_bone_original() { return bone_original; };
 
 	Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *skeleton);
 	~Skeleton3DEditor();
@@ -186,6 +250,7 @@ class EditorInspectorPluginSkeleton : public EditorInspectorPlugin {
 
 	friend class Skeleton3DEditorPlugin;
 
+	Skeleton3DEditor *skel_editor;
 	EditorNode *editor;
 
 public:
@@ -196,12 +261,40 @@ public:
 class Skeleton3DEditorPlugin : public EditorPlugin {
 	GDCLASS(Skeleton3DEditorPlugin, EditorPlugin);
 
+	EditorInspectorPluginSkeleton *skeleton_plugin;
 	EditorNode *editor;
 
 public:
-	Skeleton3DEditorPlugin(EditorNode *p_node);
+	virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override;
+
+	bool has_main_screen() const override { return false; }
+	virtual bool handles(Object *p_object) const override;
 
 	virtual String get_name() const override { return "Skeleton3D"; }
+
+	Skeleton3DEditorPlugin(EditorNode *p_node);
+};
+
+class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+	GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+	Ref<StandardMaterial3D> unselected_mat;
+	Ref<ShaderMaterial> selected_mat;
+	Ref<Shader> selected_sh;
+
+public:
+	bool has_gizmo(Node3D *p_spatial) override;
+	String get_gizmo_name() const override;
+	int get_priority() const override;
+
+	int subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const override;
+	Transform3D get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+	void set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) override;
+	void commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) override;
+
+	void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+	Skeleton3DGizmoPlugin();
 };
 
 #endif // SKELETON_3D_EDITOR_PLUGIN_H

+ 6 - 6
editor/scene_tree_dock.cpp

@@ -1833,8 +1833,8 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
 
 		editor_data->get_undo_redo().add_do_method(this, "_set_owners", edited_scene, owners);
 
-		if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == node) {
-			editor_data->get_undo_redo().add_do_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", node);
+		if (AnimationPlayerEditor::get_singleton()->get_track_editor()->get_root() == node) {
+			editor_data->get_undo_redo().add_do_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", node);
 		}
 
 		editor_data->get_undo_redo().add_undo_method(new_parent, "remove_child", node);
@@ -1859,8 +1859,8 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
 		editor_data->get_undo_redo().add_undo_method(node->get_parent(), "add_child", node);
 		editor_data->get_undo_redo().add_undo_method(node->get_parent(), "move_child", node, child_pos);
 		editor_data->get_undo_redo().add_undo_method(this, "_set_owners", edited_scene, owners);
-		if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == node) {
-			editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", node);
+		if (AnimationPlayerEditor::get_singleton()->get_track_editor()->get_root() == node) {
+			editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", node);
 		}
 
 		if (p_keep_global_xform) {
@@ -2071,8 +2071,8 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
 			editor_data->get_undo_redo().add_do_method(n->get_parent(), "remove_child", n);
 			editor_data->get_undo_redo().add_undo_method(n->get_parent(), "add_child", n);
 			editor_data->get_undo_redo().add_undo_method(n->get_parent(), "move_child", n, n->get_index());
-			if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == n) {
-				editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", n);
+			if (AnimationPlayerEditor::get_singleton()->get_track_editor()->get_root() == n) {
+				editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", n);
 			}
 			editor_data->get_undo_redo().add_undo_method(this, "_set_owners", edited_scene, owners);
 			editor_data->get_undo_redo().add_undo_reference(n);

+ 2 - 2
editor/scene_tree_editor.cpp

@@ -102,7 +102,7 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i
 		undo_redo->commit_action();
 	} else if (p_id == BUTTON_PIN) {
 		if (n->is_class("AnimationPlayer")) {
-			AnimationPlayerEditor::singleton->unpin();
+			AnimationPlayerEditor::get_singleton()->unpin();
 			_update_tree();
 		}
 
@@ -386,7 +386,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll
 
 			_update_visibility_color(p_node, item);
 		} else if (p_node->is_class("AnimationPlayer")) {
-			bool is_pinned = AnimationPlayerEditor::singleton->get_player() == p_node && AnimationPlayerEditor::singleton->is_pinned();
+			bool is_pinned = AnimationPlayerEditor::get_singleton()->get_player() == p_node && AnimationPlayerEditor::get_singleton()->is_pinned();
 
 			if (is_pinned) {
 				item->add_button(0, get_theme_icon(SNAME("Pin"), SNAME("EditorIcons")), BUTTON_PIN, false, TTR("AnimationPlayer is pinned.\nClick to unpin."));

+ 29 - 19
modules/gridmap/grid_map_editor_plugin.cpp

@@ -603,9 +603,9 @@ void GridMapEditor::_do_paste() {
 	_clear_clipboard_data();
 }
 
-bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
+EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
 	if (!node) {
-		return false;
+		return EditorPlugin::AFTER_GUI_INPUT_PASS;
 	}
 
 	Ref<InputEventMouseButton> mb = p_event;
@@ -616,12 +616,12 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
 				floor->set_value(floor->get_value() + mb->get_factor());
 			}
 
-			return true; // Eaten.
+			return EditorPlugin::AFTER_GUI_INPUT_STOP; // Eaten.
 		} else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && (mb->is_command_pressed() || mb->is_shift_pressed())) {
 			if (mb->is_pressed()) {
 				floor->set_value(floor->get_value() - mb->get_factor());
 			}
-			return true;
+			return EditorPlugin::AFTER_GUI_INPUT_STOP;
 		}
 
 		if (mb->is_pressed()) {
@@ -648,19 +648,22 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
 					_clear_clipboard_data();
 					input_action = INPUT_NONE;
 					_update_paste_indicator();
-					return true;
+					return EditorPlugin::AFTER_GUI_INPUT_STOP;
 				} else if (selection.active) {
 					_set_selection(false);
-					return true;
+					return EditorPlugin::AFTER_GUI_INPUT_STOP;
 				} else {
 					input_action = INPUT_ERASE;
 					set_items.clear();
 				}
 			} else {
-				return false;
+				return EditorPlugin::AFTER_GUI_INPUT_PASS;
 			}
 
-			return do_input_action(p_camera, Point2(mb->get_position().x, mb->get_position().y), true);
+			if (do_input_action(p_camera, Point2(mb->get_position().x, mb->get_position().y), true)) {
+				return EditorPlugin::AFTER_GUI_INPUT_STOP;
+			}
+			return EditorPlugin::AFTER_GUI_INPUT_PASS;
 		} else {
 			if ((mb->get_button_index() == MOUSE_BUTTON_RIGHT && input_action == INPUT_ERASE) || (mb->get_button_index() == MOUSE_BUTTON_LEFT && input_action == INPUT_PAINT)) {
 				if (set_items.size()) {
@@ -677,7 +680,11 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
 				}
 				set_items.clear();
 				input_action = INPUT_NONE;
-				return set_items.size() > 0;
+
+				if (set_items.size() > 0) {
+					return EditorPlugin::AFTER_GUI_INPUT_STOP;
+				}
+				return EditorPlugin::AFTER_GUI_INPUT_PASS;
 			}
 
 			if (mb->get_button_index() == MOUSE_BUTTON_LEFT && input_action == INPUT_SELECT) {
@@ -690,11 +697,11 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
 			if (mb->get_button_index() == MOUSE_BUTTON_LEFT && input_action != INPUT_NONE) {
 				set_items.clear();
 				input_action = INPUT_NONE;
-				return true;
+				return EditorPlugin::AFTER_GUI_INPUT_STOP;
 			}
 			if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && (input_action == INPUT_ERASE || input_action == INPUT_PASTE)) {
 				input_action = INPUT_NONE;
-				return true;
+				return EditorPlugin::AFTER_GUI_INPUT_STOP;
 			}
 		}
 	}
@@ -702,7 +709,10 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
 	Ref<InputEventMouseMotion> mm = p_event;
 
 	if (mm.is_valid()) {
-		return do_input_action(p_camera, mm->get_position(), false);
+		if (do_input_action(p_camera, mm->get_position(), false)) {
+			return EditorPlugin::AFTER_GUI_INPUT_STOP;
+		}
+		return EditorPlugin::AFTER_GUI_INPUT_PASS;
 	}
 
 	Ref<InputEventKey> k = p_event;
@@ -714,16 +724,16 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
 					_clear_clipboard_data();
 					input_action = INPUT_NONE;
 					_update_paste_indicator();
-					return true;
+					return EditorPlugin::AFTER_GUI_INPUT_STOP;
 				} else if (selection.active) {
 					_set_selection(false);
-					return true;
+					return EditorPlugin::AFTER_GUI_INPUT_STOP;
 				} else {
 					selected_palette = -1;
 					mesh_library_palette->deselect_all();
 					update_palette();
 					_update_cursor_instance();
-					return true;
+					return EditorPlugin::AFTER_GUI_INPUT_STOP;
 				}
 			}
 
@@ -731,12 +741,12 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
 				if (k->get_keycode() == options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_PREV_LEVEL))) {
 					selection.click[edit_axis]--;
 					_validate_selection();
-					return true;
+					return EditorPlugin::AFTER_GUI_INPUT_STOP;
 				}
 				if (k->get_keycode() == options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_NEXT_LEVEL))) {
 					selection.click[edit_axis]++;
 					_validate_selection();
-					return true;
+					return EditorPlugin::AFTER_GUI_INPUT_STOP;
 				}
 			}
 		}
@@ -755,12 +765,12 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
 			if (step) {
 				floor->set_value(floor->get_value() + step);
 			}
-			return true;
+			return EditorPlugin::AFTER_GUI_INPUT_STOP;
 		}
 	}
 	accumulated_floor_delta = 0.0;
 
-	return false;
+	return EditorPlugin::AFTER_GUI_INPUT_PASS;
 }
 
 struct _CGMEItemSort {

+ 2 - 2
modules/gridmap/grid_map_editor_plugin.h

@@ -232,7 +232,7 @@ protected:
 	static void _bind_methods();
 
 public:
-	bool forward_spatial_input_event(Camera3D *p_camera, const Ref<InputEvent> &p_event);
+	EditorPlugin::AfterGUIInput forward_spatial_input_event(Camera3D *p_camera, const Ref<InputEvent> &p_event);
 
 	void edit(GridMap *p_gridmap);
 	GridMapEditor() {}
@@ -250,7 +250,7 @@ protected:
 	void _notification(int p_what);
 
 public:
-	virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return grid_map_editor->forward_spatial_input_event(p_camera, p_event); }
+	virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return grid_map_editor->forward_spatial_input_event(p_camera, p_event); }
 	virtual String get_name() const override { return "GridMap"; }
 	bool has_main_screen() const override { return false; }
 	virtual void edit(Object *p_object) override;

+ 13 - 0
scene/3d/node_3d.cpp

@@ -376,6 +376,18 @@ void Node3D::update_gizmos() {
 #endif
 }
 
+void Node3D::set_subgizmo_selection(Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform) {
+#ifdef TOOLS_ENABLED
+	if (!is_inside_world()) {
+		return;
+	}
+
+	if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) {
+		get_tree()->call_group_flags(0, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_set_subgizmo_selection, this, p_gizmo, p_id, p_transform);
+	}
+#endif
+}
+
 void Node3D::clear_subgizmo_selection() {
 #ifdef TOOLS_ENABLED
 	if (!is_inside_world()) {
@@ -792,6 +804,7 @@ void Node3D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("add_gizmo", "gizmo"), &Node3D::add_gizmo);
 	ClassDB::bind_method(D_METHOD("get_gizmos"), &Node3D::get_gizmos_bind);
 	ClassDB::bind_method(D_METHOD("clear_gizmos"), &Node3D::clear_gizmos);
+	ClassDB::bind_method(D_METHOD("set_subgizmo_selection", "gizmo", "id", "transform"), &Node3D::set_subgizmo_selection);
 	ClassDB::bind_method(D_METHOD("clear_subgizmo_selection"), &Node3D::clear_subgizmo_selection);
 
 	ClassDB::bind_method(D_METHOD("set_visible", "visible"), &Node3D::set_visible);

+ 4 - 0
scene/3d/node_3d.h

@@ -92,6 +92,7 @@ class Node3D : public Node {
 		Vector<Ref<Node3DGizmo>> gizmos;
 		bool gizmos_disabled = false;
 		bool gizmos_dirty = false;
+		bool transform_gizmo_visible = true;
 #endif
 
 	} data;
@@ -145,6 +146,8 @@ public:
 #ifdef TOOLS_ENABLED
 	virtual Transform3D get_global_gizmo_transform() const;
 	virtual Transform3D get_local_gizmo_transform() const;
+	virtual void set_transform_gizmo_visible(bool p_enabled) { data.transform_gizmo_visible = p_enabled; };
+	virtual bool is_transform_gizmo_visible() const { return data.transform_gizmo_visible; };
 #endif
 
 	void set_as_top_level(bool p_enabled);
@@ -155,6 +158,7 @@ public:
 
 	void set_disable_gizmos(bool p_enabled);
 	void update_gizmos();
+	void set_subgizmo_selection(Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform = Transform3D());
 	void clear_subgizmo_selection();
 	Vector<Ref<Node3DGizmo>> get_gizmos() const;
 	Array get_gizmos_bind() const;

+ 52 - 23
scene/3d/skeleton_3d.cpp

@@ -32,6 +32,7 @@
 
 #include "core/object/message_queue.h"
 #include "core/variant/type_info.h"
+#include "editor/plugins/skeleton_3d_editor_plugin.h"
 #include "scene/3d/physics_body_3d.h"
 #include "scene/resources/skeleton_modification_3d.h"
 #include "scene/resources/surface_tool.h"
@@ -178,7 +179,7 @@ void Skeleton3D::_update_process_order() {
 
 	for (int i = 0; i < len; i++) {
 		if (bonesptr[i].parent >= len) {
-			//validate this just in case
+			// Validate this just in case.
 			ERR_PRINT("Bone " + itos(i) + " has invalid parent: " + itos(bonesptr[i].parent));
 			bonesptr[i].parent = -1;
 		}
@@ -186,9 +187,9 @@ void Skeleton3D::_update_process_order() {
 		if (bonesptr[i].parent != -1) {
 			int parent_bone_idx = bonesptr[i].parent;
 
-			// Check to see if this node is already added to the parent:
+			// Check to see if this node is already added to the parent.
 			if (bonesptr[parent_bone_idx].child_bones.find(i) < 0) {
-				// Add the child node
+				// Add the child node.
 				bonesptr[parent_bone_idx].child_bones.push_back(i);
 			} else {
 				ERR_PRINT("Skeleton3D parenthood graph is cyclic");
@@ -210,10 +211,10 @@ void Skeleton3D::_notification(int p_what) {
 			int len = bones.size();
 			dirty = false;
 
-			// Update bone transforms
+			// Update bone transforms.
 			force_update_all_bone_transforms();
 
-			//update skins
+			// Update skins.
 			for (Set<SkinReference *>::Element *E = skin_bindings.front(); E; E = E->next()) {
 				const Skin *skin = E->get()->skin.operator->();
 				RID skeleton = E->get()->skeleton;
@@ -231,7 +232,7 @@ void Skeleton3D::_notification(int p_what) {
 						StringName bind_name = skin->get_bind_name(i);
 
 						if (bind_name != StringName()) {
-							//bind name used, use this
+							// Bind name used, use this.
 							bool found = false;
 							for (int j = 0; j < len; j++) {
 								if (bonesptr[j].name == bind_name) {
@@ -453,7 +454,8 @@ int Skeleton3D::get_bone_axis_forward_enum(int p_bone) {
 	return bones[p_bone].rest_bone_forward_axis;
 }
 
-// skeleton creation api
+// Skeleton creation api
+
 void Skeleton3D::add_bone(const String &p_name) {
 	ERR_FAIL_COND(p_name == "" || p_name.find(":") != -1 || p_name.find("/") != -1);
 
@@ -626,6 +628,7 @@ void Skeleton3D::set_bone_enabled(int p_bone, bool p_enabled) {
 	ERR_FAIL_INDEX(p_bone, bone_size);
 
 	bones.write[p_bone].enabled = p_enabled;
+	emit_signal(SceneStringNames::get_singleton()->bone_enabled_changed, p_bone);
 	_make_dirty();
 }
 
@@ -635,6 +638,16 @@ bool Skeleton3D::is_bone_enabled(int p_bone) const {
 	return bones[p_bone].enabled;
 }
 
+void Skeleton3D::set_show_rest_only(bool p_enabled) {
+	show_rest_only = p_enabled;
+	emit_signal(SceneStringNames::get_singleton()->show_rest_only_changed);
+	_make_dirty();
+}
+
+bool Skeleton3D::is_show_rest_only() const {
+	return show_rest_only;
+}
+
 void Skeleton3D::clear_bones() {
 	bones.clear();
 	process_order_dirty = true;
@@ -642,7 +655,7 @@ void Skeleton3D::clear_bones() {
 	_make_dirty();
 }
 
-// posing api
+// Posing api
 
 void Skeleton3D::set_bone_pose(int p_bone, const Transform3D &p_pose) {
 	const int bone_size = bones.size();
@@ -697,7 +710,7 @@ void Skeleton3D::localize_rests() {
 			set_bone_rest(current_bone_idx, bones[bones[current_bone_idx].parent].rest.affine_inverse() * bones[current_bone_idx].rest);
 		}
 
-		// Add the bone's children to the list of bones to be processed
+		// Add the bone's children to the list of bones to be processed.
 		int child_bone_size = bones[current_bone_idx].child_bones.size();
 		for (int i = 0; i < child_bone_size; i++) {
 			bones_to_process.push_back(bones[current_bone_idx].child_bones[i]);
@@ -831,7 +844,7 @@ void Skeleton3D::physical_bones_start_simulation_on(const TypedArray<StringName>
 
 	Vector<int> sim_bones;
 	if (p_bones.size() <= 0) {
-		sim_bones.push_back(0); // if no bones is specified, activate ragdoll on full body
+		sim_bones.push_back(0); // If no bones is specified, activate ragdoll on full body.
 	} else {
 		sim_bones.resize(p_bones.size());
 		int c = 0;
@@ -884,19 +897,19 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) {
 	Ref<Skin> skin = p_skin;
 
 	if (skin.is_null()) {
-		//need to create one from existing code, this is for compatibility only
-		//when skeletons did not support skins. It is also used by gizmo
-		//to display the skeleton.
+		// Need to create one from existing code, this is for compatibility only
+		// when skeletons did not support skins. It is also used by gizmo
+		// to display the skeleton.
 
 		skin.instantiate();
 		skin->set_bind_count(bones.size());
-		_update_process_order(); //just in case
+		_update_process_order(); // Just in case.
 
-		// pose changed, rebuild cache of inverses
+		// Pose changed, rebuild cache of inverses.
 		const Bone *bonesptr = bones.ptr();
 		int len = bones.size();
 
-		// calculate global rests and invert them
+		// Calculate global rests and invert them.
 		LocalVector<int> bones_to_process;
 		bones_to_process = get_parentless_bones();
 		while (bones_to_process.size() > 0) {
@@ -919,7 +932,7 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) {
 		}
 
 		for (int i = 0; i < len; i++) {
-			//the inverse is what is actually required
+			// The inverse is what is actually required.
 			skin->set_bind_bone(i, i);
 			skin->set_bind_pose(i, skin->get_bind_pose(i).affine_inverse());
 		}
@@ -940,11 +953,17 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) {
 
 	skin->connect("changed", Callable(skin_ref.operator->(), "_skin_changed"));
 
-	_make_dirty(); //skin needs to be updated, so update skeleton
+	_make_dirty(); // Skin needs to be updated, so update skeleton.
 
 	return skin_ref;
 }
 
+void Skeleton3D::force_update_all_dirty_bones() {
+	if (dirty) {
+		const_cast<Skeleton3D *>(this)->notification(NOTIFICATION_UPDATE_SKELETON);
+	}
+}
+
 void Skeleton3D::force_update_all_bone_transforms() {
 	_update_process_order();
 
@@ -966,9 +985,10 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
 		bones_to_process.erase(current_bone_idx);
 
 		Bone &b = bonesptr[current_bone_idx];
+		bool bone_enabled = b.enabled && !show_rest_only;
 
 		if (b.disable_rest) {
-			if (b.enabled) {
+			if (bone_enabled) {
 				Transform3D pose = b.pose;
 				if (b.custom_pose_enable) {
 					pose = b.custom_pose * pose;
@@ -991,7 +1011,7 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
 			}
 
 		} else {
-			if (b.enabled) {
+			if (bone_enabled) {
 				Transform3D pose = b.pose;
 				if (b.custom_pose_enable) {
 					pose = b.custom_pose * pose;
@@ -1035,7 +1055,7 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
 			b.global_pose_override_amount = 0.0;
 		}
 
-		// Add the bone's children to the list of bones to be processed
+		// Add the bone's children to the list of bones to be processed.
 		int child_bone_size = b.child_bones.size();
 		for (int i = 0; i < child_bone_size; i++) {
 			bones_to_process.push_back(b.child_bones[i]);
@@ -1045,7 +1065,7 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
 	}
 }
 
-// helper functions
+// Helper functions
 
 Transform3D Skeleton3D::global_pose_to_world_transform(Transform3D p_global_pose) {
 	return get_global_transform() * p_global_pose;
@@ -1175,6 +1195,9 @@ void Skeleton3D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_bone_pose", "bone_idx"), &Skeleton3D::get_bone_pose);
 	ClassDB::bind_method(D_METHOD("set_bone_pose", "bone_idx", "pose"), &Skeleton3D::set_bone_pose);
 
+	ClassDB::bind_method(D_METHOD("is_bone_enabled", "bone_idx"), &Skeleton3D::is_bone_enabled);
+	ClassDB::bind_method(D_METHOD("set_bone_enabled", "bone_idx", "enabled"), &Skeleton3D::set_bone_enabled, DEFVAL(true));
+
 	ClassDB::bind_method(D_METHOD("clear_bones_global_pose_override"), &Skeleton3D::clear_bones_global_pose_override);
 	ClassDB::bind_method(D_METHOD("set_bone_global_pose_override", "bone_idx", "pose", "amount", "persistent"), &Skeleton3D::set_bone_global_pose_override, DEFVAL(false));
 	ClassDB::bind_method(D_METHOD("get_bone_global_pose_override", "bone_idx"), &Skeleton3D::get_bone_global_pose_override);
@@ -1198,6 +1221,9 @@ void Skeleton3D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("local_pose_to_global_pose", "bone_idx", "local_pose"), &Skeleton3D::local_pose_to_global_pose);
 	ClassDB::bind_method(D_METHOD("global_pose_z_forward_to_bone_forward", "bone_idx", "basis"), &Skeleton3D::global_pose_z_forward_to_bone_forward);
 
+	ClassDB::bind_method(D_METHOD("set_show_rest_only"), &Skeleton3D::set_show_rest_only);
+	ClassDB::bind_method(D_METHOD("is_show_rest_only"), &Skeleton3D::is_show_rest_only);
+
 	ClassDB::bind_method(D_METHOD("set_animate_physical_bones"), &Skeleton3D::set_animate_physical_bones);
 	ClassDB::bind_method(D_METHOD("get_animate_physical_bones"), &Skeleton3D::get_animate_physical_bones);
 
@@ -1212,6 +1238,7 @@ void Skeleton3D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("execute_modifications", "delta", "execution_mode"), &Skeleton3D::execute_modifications);
 
 #ifndef _3D_DISABLED
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_rest_only"), "set_show_rest_only", "is_show_rest_only");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "animate_physical_bones"), "set_animate_physical_bones", "get_animate_physical_bones");
 #endif // _3D_DISABLED
 
@@ -1220,6 +1247,8 @@ void Skeleton3D::_bind_methods() {
 #endif // TOOLS_ENABLED
 
 	ADD_SIGNAL(MethodInfo("bone_pose_changed", PropertyInfo(Variant::INT, "bone_idx")));
+	ADD_SIGNAL(MethodInfo("bone_enabled_changed", PropertyInfo(Variant::INT, "bone_idx")));
+	ADD_SIGNAL(MethodInfo("show_rest_only_changed"));
 
 	BIND_CONSTANT(NOTIFICATION_UPDATE_SKELETON);
 }
@@ -1228,7 +1257,7 @@ Skeleton3D::Skeleton3D() {
 }
 
 Skeleton3D::~Skeleton3D() {
-	//some skins may remain bound
+	// Some skins may remain bound.
 	for (Set<SkinReference *>::Element *E = skin_bindings.front(); E; E = E->next()) {
 		E->get()->skeleton_node = nullptr;
 	}

+ 6 - 0
scene/3d/skeleton_3d.h

@@ -137,6 +137,8 @@ private:
 	void _make_dirty();
 	bool dirty = false;
 
+	bool show_rest_only = false;
+
 	uint64_t version = 1;
 
 	void _update_process_order();
@@ -197,6 +199,9 @@ public:
 
 	void set_bone_enabled(int p_bone, bool p_enabled);
 	bool is_bone_enabled(int p_bone) const;
+
+	void set_show_rest_only(bool p_enabled);
+	bool is_show_rest_only() const;
 	void clear_bones();
 
 	// posing api
@@ -219,6 +224,7 @@ public:
 
 	Ref<SkinReference> register_skin(const Ref<Skin> &p_skin);
 
+	void force_update_all_dirty_bones();
 	void force_update_all_bone_transforms();
 	void force_update_bone_children_transforms(int bone_idx);
 

+ 3 - 0
scene/scene_string_names.cpp

@@ -64,6 +64,8 @@ SceneStringNames::SceneStringNames() {
 
 	pose_updated = StaticCString::create("pose_updated");
 	bone_pose_changed = StaticCString::create("bone_pose_changed");
+	bone_enabled_changed = StaticCString::create("bone_enabled_changed");
+	show_rest_only_changed = StaticCString::create("show_rest_only_changed");
 
 	mouse_entered = StaticCString::create("mouse_entered");
 	mouse_exited = StaticCString::create("mouse_exited");
@@ -134,6 +136,7 @@ SceneStringNames::SceneStringNames() {
 
 	_spatial_editor_group = StaticCString::create("_spatial_editor_group");
 	_request_gizmo = StaticCString::create("_request_gizmo");
+	_set_subgizmo_selection = StaticCString::create("_set_subgizmo_selection");
 	_clear_subgizmo_selection = StaticCString::create("_clear_subgizmo_selection");
 
 	offset = StaticCString::create("offset");

+ 3 - 0
scene/scene_string_names.h

@@ -99,6 +99,8 @@ public:
 
 	StringName pose_updated;
 	StringName bone_pose_changed;
+	StringName bone_enabled_changed;
+	StringName show_rest_only_changed;
 
 	StringName body_shape_entered;
 	StringName body_entered;
@@ -154,6 +156,7 @@ public:
 
 	StringName _spatial_editor_group;
 	StringName _request_gizmo;
+	StringName _set_subgizmo_selection;
 	StringName _clear_subgizmo_selection;
 
 	StringName offset;