Skip to content

Commit

Permalink
Add the ability to expose nodes for direct access in instantiated scenes
Browse files Browse the repository at this point in the history
  • Loading branch information
yahkr committed Nov 2, 2024
1 parent 08f9cba commit dcd1298
Show file tree
Hide file tree
Showing 16 changed files with 380 additions and 17 deletions.
3 changes: 3 additions & 0 deletions doc/classes/EditorSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@
<member name="docks/scene_tree/ask_before_deleting_related_animation_tracks" type="bool" setter="" getter="">
If [code]true[/code], when a node is deleted with animation tracks referencing it, a confirmation dialog appears before the tracks are deleted. The dialog will appear even when using the "Delete (No Confirm)" shortcut.
</member>
<member name="docks/scene_tree/ask_before_revoking_node_exposure" type="bool" setter="" getter="">
If [code]true[/code], displays a confirmation dialog after left-clicking the "exposed" icon next to a node name in the Scene tree dock. When clicked, this icon revokes the node's scene exposure, which can impact the behavior of scripts that rely on this exposed node due to identifiers not being found anymore.
</member>
<member name="docks/scene_tree/ask_before_revoking_unique_name" type="bool" setter="" getter="">
If [code]true[/code], displays a confirmation dialog before left-clicking the "percent" icon next to a node name in the Scene tree dock. When clicked, this icon revokes the node's scene-unique name, which can impact the behavior of scripts that rely on this scene-unique name due to identifiers not being found anymore.
</member>
Expand Down
22 changes: 22 additions & 0 deletions doc/classes/Node.xml
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,12 @@
Returns [code]true[/code] if [param node] has editable children enabled relative to this node. This method is intended to be used in editor plugins and tools. See also [method set_editable_instance].
</description>
</method>
<method name="is_exposed_in_instance" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if [Node] has been exposed in scene in which it belongs, whether it was exposed in its original scene or re-exposed in this instance. This method is intended to be used in editor plugins and tools.
</description>
</method>
<method name="is_greater_than" qualifiers="const">
<return type="bool" />
<param index="0" name="node" type="Node" />
Expand All @@ -614,6 +620,12 @@
Returns [code]true[/code] if this node is currently inside a [SceneTree]. See also [method get_tree].
</description>
</method>
<method name="is_marked_for_exposure" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if [Node] has been marked to be exposed by the currently edited scene. This method is intended to be used in editor plugins and tools. See also [method set_marked_for_exposure].
</description>
</method>
<method name="is_multiplayer_authority" qualifiers="const">
<return type="bool" />
<description>
Expand Down Expand Up @@ -885,6 +897,13 @@
Set to [code]true[/code] to allow all nodes owned by [param node] to be available, and editable, in the Scene dock, even if their [member owner] is not the scene root. This method is intended to be used in editor plugins and tools, but it also works in release builds. See also [method is_editable_instance].
</description>
</method>
<method name="set_marked_for_exposure">
<return type="void" />
<param index="0" name="enable" type="bool" />
<description>
Mark this node to be exposed when saving this scene. This allows the node to be accessed in the editor when the scene is instantiated in another.
</description>
</method>
<method name="set_multiplayer_authority">
<return type="void" />
<param index="0" name="id" type="int" />
Expand Down Expand Up @@ -997,6 +1016,9 @@
<member name="editor_description" type="String" setter="set_editor_description" getter="get_editor_description" default="&quot;&quot;">
An optional description to the node. It will be displayed as a tooltip when hovering over the node in the editor's Scene dock.
</member>
<member name="exposed_in_owner" type="bool" setter="set_exposed_in_owner" getter="is_exposed_in_owner" default="false">
This node's state as an exposed node in its [member owner]. This is set in the scene the node originates from. This method is intended to be used in editor plugins and tools.
</member>
<member name="multiplayer" type="MultiplayerAPI" setter="" getter="get_multiplayer">
The [MultiplayerAPI] instance associated with this node. See [method SceneTree.get_multiplayer].
[b]Note:[/b] Renaming the node, or moving it in the tree, will not move the [MultiplayerAPI] to the new path, you will have to update this manually.
Expand Down
12 changes: 9 additions & 3 deletions editor/editor_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -729,9 +729,15 @@ bool EditorData::check_and_update_scene(int p_idx) {
for (const Node *E : edited_scene.write[p_idx].selection) {
NodePath p = edited_scene[p_idx].root->get_path_to(E);
Node *new_node = new_scene->get_node(p);
if (new_node) {
new_selection.push_back(new_node);
}
// Node can't be found, skip it.
if (!new_node)
continue;

// Node is no longer exposed, skip it.
if (E->is_exposed_in_owner() && !new_node->is_exposed_in_owner())
continue;

new_selection.push_back(new_node);
}

new_scene->set_scene_file_path(edited_scene[p_idx].root->get_scene_file_path());
Expand Down
1 change: 1 addition & 0 deletions editor/editor_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
_initial_set("interface/editors/show_scene_tree_root_selection", true);
_initial_set("interface/editors/derive_script_globals_by_name", true);
_initial_set("docks/scene_tree/ask_before_revoking_unique_name", true);
_initial_set("docks/scene_tree/ask_before_revoking_node_exposure", true);

// Inspector
EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_RANGE, "interface/inspector/max_array_dictionary_items_per_page", 20, "10,100,1")
Expand Down
71 changes: 69 additions & 2 deletions editor/gui/scene_tree_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,18 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i
} else {
_revoke_unique_name();
}
} else if (p_id == BUTTON_EXPOSED) {
bool ask_before_revoking_node_exposure = EDITOR_GET("docks/scene_tree/ask_before_revoking_node_exposure");
revoke_node = n;
if (ask_before_revoking_node_exposure) {
String msg = vformat(TTR("Unexpose node \"%s\"?"), n->get_name());
ask_before_revoke_node_exposure_checkbox->set_pressed(false);
revoke_node_exposure_dialog_label->set_text(msg);
revoke_node_exposure->reset_size();
revoke_node_exposure->popup_centered();
} else {
_toggle_node_exposure();
}
}
}

Expand All @@ -199,7 +211,6 @@ void SceneTreeEditor::_update_ask_before_revoking_unique_name() {

void SceneTreeEditor::_revoke_unique_name() {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();

undo_redo->create_action(TTR("Disable Scene Unique Name"));
undo_redo->add_do_method(revoke_node, "set_unique_name_in_owner", false);
undo_redo->add_undo_method(revoke_node, "set_unique_name_in_owner", true);
Expand All @@ -208,6 +219,33 @@ void SceneTreeEditor::_revoke_unique_name() {
undo_redo->commit_action();
}

void SceneTreeEditor::_update_ask_before_revoking_node_exposure() {
if (ask_before_revoke_node_exposure_checkbox->is_pressed()) {
EditorSettings::get_singleton()->set("docks/scene_tree/ask_before_revoking_node_exposure", false);
ask_before_revoke_node_exposure_checkbox->set_pressed(false);
}
_toggle_node_exposure();
}

void SceneTreeEditor::_toggle_node_exposure() {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
bool enabled = revoke_node->is_marked_for_exposure();
if (enabled) {
undo_redo->create_action(TTR("Expose Node In Scene"));
} else {
undo_redo->create_action(TTR("Unexpose Node In Scene"));
}
if (revoke_node->get_owner() == get_tree()->get_edited_scene_root()) {
undo_redo->add_do_method(revoke_node, "set_exposed_in_owner", !enabled);
undo_redo->add_undo_method(revoke_node, "set_exposed_in_owner", enabled);
}
undo_redo->add_do_method(revoke_node, "set_marked_for_exposure", !enabled);
undo_redo->add_undo_method(revoke_node, "set_marked_for_exposure", enabled);
undo_redo->add_do_method(this, "_update_tree");
undo_redo->add_undo_method(this, "_update_tree");
undo_redo->commit_action();
}

void SceneTreeEditor::_toggle_visible(Node *p_node) {
if (p_node->has_method("is_visible") && p_node->has_method("set_visible")) {
bool v = bool(p_node->call("is_visible"));
Expand All @@ -228,9 +266,16 @@ void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) {
bool part_of_subscene = false;

if (!display_foreign && p_node->get_owner() != get_scene_node() && p_node != get_scene_node()) {
if ((show_enabled_subscene || can_open_instance) && p_node->get_owner() && (get_scene_node()->is_editable_instance(p_node->get_owner()))) {
if ((show_enabled_subscene || can_open_instance) && p_node->get_owner() && (get_scene_node()->is_editable_instance(p_node->get_owner()) || (p_node->is_exposed_in_instance() && p_node->is_exposed_in_owner()))) {
part_of_subscene = true;
//allow
} else if (p_node->has_exposed_nodes()) {
for (int i = 0; i < p_node->get_child_count(); i++) {
if (!get_scene_node()->is_editable_instance(p_node->get_child(i)->get_owner()) || p_node->get_child(i)->is_exposed_in_instance()) {
_add_nodes(p_node->get_child(i), p_parent);
}
}
return;
} else {
return;
}
Expand Down Expand Up @@ -343,6 +388,16 @@ void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) {
item->add_button(0, get_editor_theme_icon(warning_icon), BUTTON_WARNING, false, TTR("Node configuration warning:") + all_warnings);
}

if (p_node->is_exposed_in_owner() && p_node->get_owner() == EditorNode::get_singleton()->get_edited_scene()) {
item->add_button(0, get_editor_theme_icon(SNAME("SceneExposedNode")), BUTTON_EXPOSED, false, vformat(TTR("This node will be exposed in the editor when this scene is instantiated.\nClick to disable this.")));
} else {
if (p_node->is_marked_for_exposure()) {
item->add_button(0, get_editor_theme_icon(SNAME("SceneExposedNode")), BUTTON_EXPOSED, false, vformat(TTR("This node has been exposed in the underlying scene.\nThis node will be exposed in the editor when this scene is instantiated.\nClick to disable this.")));
} else if (p_node->is_exposed_in_instance()) {
item->add_button(0, get_editor_theme_icon(SNAME("SceneExposedNodeInstanced")), BUTTON_EXPOSED, p_node->get_owner() != EditorNode::get_singleton()->get_edited_scene(), TTR("This node has been exposed in the underlying scene."));
}
}

if (p_node->is_unique_name_in_owner()) {
item->add_button(0, get_editor_theme_icon(SNAME("SceneUniqueName")), BUTTON_UNIQUE, p_node->get_owner() != EditorNode::get_singleton()->get_edited_scene(), vformat(TTR("This node can be accessed from within anywhere in the scene by preceding it with the '%s' prefix in a node path.\nClick to disable this."), UNIQUE_NODE_PREFIX));
}
Expand Down Expand Up @@ -1662,6 +1717,18 @@ SceneTreeEditor::SceneTreeEditor(bool p_label, bool p_can_rename, bool p_can_ope
ask_before_revoke_checkbox->set_tooltip_text(TTR("This dialog can also be enabled/disabled in the Editor Settings: Docks > Scene Tree > Ask Before Revoking Unique Name."));
vb->add_child(ask_before_revoke_checkbox);

revoke_node_exposure = memnew(ConfirmationDialog);
revoke_node_exposure->set_ok_button_text(TTR("Revoke"));
add_child(revoke_node_exposure);
revoke_node_exposure->connect(SceneStringName(confirmed), callable_mp(this, &SceneTreeEditor::_update_ask_before_revoking_node_exposure));
VBoxContainer *nevb = memnew(VBoxContainer);
revoke_node_exposure->add_child(nevb);
revoke_node_exposure_dialog_label = memnew(Label);
nevb->add_child(revoke_node_exposure_dialog_label);
ask_before_revoke_node_exposure_checkbox = memnew(CheckBox(TTR("Don't Ask Again")));
ask_before_revoke_node_exposure_checkbox->set_tooltip_text(TTR("This dialog can also be enabled/disabled in the Editor Settings: Docks > Scene Tree > Ask Before Revoking Node Exposure."));
nevb->add_child(ask_before_revoke_node_exposure_checkbox);

script_types = memnew(List<StringName>);
ClassDB::get_inheriters_from_class("Script", script_types);
}
Expand Down
7 changes: 7 additions & 0 deletions editor/gui/scene_tree_editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class SceneTreeEditor : public Control {
BUTTON_GROUPS = 7,
BUTTON_PIN = 8,
BUTTON_UNIQUE = 9,
BUTTON_EXPOSED = 10,
};

Tree *tree = nullptr;
Expand All @@ -74,6 +75,10 @@ class SceneTreeEditor : public Control {
CheckBox *ask_before_revoke_checkbox = nullptr;
Node *revoke_node = nullptr;

ConfirmationDialog *revoke_node_exposure = nullptr;
Label *revoke_node_exposure_dialog_label = nullptr;
CheckBox *ask_before_revoke_node_exposure_checkbox = nullptr;

bool auto_expand_selected = true;
bool connect_to_script_mode = false;
bool connecting_signal = false;
Expand Down Expand Up @@ -152,6 +157,8 @@ class SceneTreeEditor : public Control {

void _update_ask_before_revoking_unique_name();
void _revoke_unique_name();
void _update_ask_before_revoking_node_exposure();
void _toggle_node_exposure();

public:
// Public for use with callable_mp.
Expand Down
1 change: 1 addition & 0 deletions editor/icons/SceneExposedNode.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions editor/icons/SceneExposedNodeInstanced.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions editor/import/3d/resource_importer_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1410,6 +1410,10 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<
}
}

if (!isroot && (node_settings.has("import/exposed"))) {
p_node->set_exposed_in_owner(bool(node_settings["import/exposed"]));
}

if (Object::cast_to<ImporterMeshInstance3D>(p_node)) {
ObjectID node_id = p_node->get_instance_id();
for (int i = 0; i < post_importer_plugins.size(); i++) {
Expand Down Expand Up @@ -1989,9 +1993,11 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
switch (p_category) {
case INTERNAL_IMPORT_CATEGORY_NODE: {
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/exposed", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
} break;
case INTERNAL_IMPORT_CATEGORY_MESH_3D_NODE: {
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/exposed", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "generate/physics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "generate/navmesh", PROPERTY_HINT_ENUM, "Disabled,Mesh + NavMesh,NavMesh Only"), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "physics/body_type", PROPERTY_HINT_ENUM, "Static,Dynamic,Area"), 0));
Expand Down Expand Up @@ -2070,6 +2076,7 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
} break;
case INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE: {
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/exposed", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "optimizer/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_velocity_error", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.01));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "optimizer/max_angular_error", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.01));
Expand All @@ -2082,6 +2089,7 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
} break;
case INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE: {
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/exposed", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "rest_pose/load_pose", PROPERTY_HINT_ENUM, "Default Pose,Use AnimationPlayer,Load External Animation", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0));
r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "rest_pose/external_animation_library", PROPERTY_HINT_RESOURCE_TYPE, "Animation,AnimationLibrary", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Variant()));
r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "rest_pose/selected_animation", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), ""));
Expand Down Expand Up @@ -2632,6 +2640,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
} break;
}

mesh_node->set_exposed_in_owner(src_mesh_node->is_exposed_in_owner());
mesh_node->set_layer_mask(src_mesh_node->get_layer_mask());
mesh_node->set_cast_shadows_setting(src_mesh_node->get_cast_shadows_setting());
mesh_node->set_visibility_range_begin(src_mesh_node->get_visibility_range_begin());
Expand Down
Loading

0 comments on commit dcd1298

Please sign in to comment.