Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/classes/EditorSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,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 after 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
111 changes: 104 additions & 7 deletions editor/docks/scene_tree_dock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ void SceneTreeDock::shortcut_input(const Ref<InputEvent> &p_event) {
_tool_selected(TOOL_SHOW_IN_FILE_SYSTEM);
} else if (ED_IS_SHORTCUT("scene_tree/toggle_unique_name", p_event)) {
_tool_selected(TOOL_TOGGLE_SCENE_UNIQUE_NAME);
} else if (ED_IS_SHORTCUT("scene_tree/toggle_expose_node", p_event)) {
_tool_selected(TOOL_TOGGLE_SCENE_EXPOSE_NODE);
} else if (ED_IS_SHORTCUT("scene_tree/toggle_editable_children", p_event)) {
_tool_selected(TOOL_SCENE_EDITABLE_CHILDREN);
} else if (ED_IS_SHORTCUT("scene_tree/delete", p_event)) {
Expand Down Expand Up @@ -331,6 +333,14 @@ void SceneTreeDock::_perform_instantiate_scenes(const Vector<String> &p_files, N
instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_files[i]));

instances.push_back(instantiated_scene);
Vector<NodePath> exposed_nodes = sdata->get_state()->get_exposed_nodes();
instantiated_scene->set_meta(META_CONTAINS_EXPOSED_NODES, exposed_nodes.size() > 0);
for (const NodePath &e_path : exposed_nodes) {
Node *ei = instantiated_scene->get_node_or_null(e_path);
if (ei) {
ei->set_meta(META_EXPOSED_IN_INSTANCE, true);
}
}
}

if (error) {
Expand Down Expand Up @@ -456,6 +466,15 @@ void SceneTreeDock::_replace_with_branch_scene(const String &p_file, Node *base)
return;
}

Vector<NodePath> exposed_nodes = sdata->get_state()->get_exposed_nodes();
instantiated_scene->set_meta(META_CONTAINS_EXPOSED_NODES, exposed_nodes.size() > 0);
for (const NodePath &e_path : exposed_nodes) {
Node *ei = instantiated_scene->get_node_or_null(e_path);
if (ei) {
ei->set_meta(META_EXPOSED_IN_INSTANCE, true);
}
}

instantiated_scene->set_unique_name_in_owner(base->is_unique_name_in_owner());

Node2D *copy_2d = Object::cast_to<Node2D>(instantiated_scene);
Expand Down Expand Up @@ -1501,6 +1520,57 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
undo_redo->commit_action();
}
} break;
case TOOL_TOGGLE_SCENE_EXPOSE_NODE: {
const List<Node *>::Element *first_selected = editor_selection->get_top_selected_node_list().front();
if (first_selected == nullptr) {
return;
}
if (first_selected->get() == EditorNode::get_singleton()->get_edited_scene()) {
editor_selection->remove_node(first_selected->get());
first_selected = editor_selection->get_top_selected_node_list().front();
if (first_selected == nullptr) {
return;
}
}

const List<Node *> full_selection = editor_selection->get_full_selected_node_list();
bool enabling = !first_selected->get()->has_meta(META_MARKED_FOR_EXPOSURE);

EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
if (enabling) {
undo_redo->create_action(TTR("Expose Node(s) In Scene"));
} else {
undo_redo->create_action(TTR("Unexpose Node(s) In Scene"));
}

for (Node *node : full_selection) {
// Only operate on nodes whose state will actually change
bool is_exposed = node->has_meta(META_MARKED_FOR_EXPOSURE);

if (enabling && !is_exposed) {
// Only expose nodes that are not already exposed
if (get_tree()->get_edited_scene_root() == node->get_owner()) {
undo_redo->add_do_method(node, "set_meta", META_EXPOSED_IN_OWNER, true);
undo_redo->add_undo_method(node, "remove_meta", META_EXPOSED_IN_OWNER);
}
undo_redo->add_do_method(node, "set_meta", META_MARKED_FOR_EXPOSURE, true);
undo_redo->add_undo_method(node, "remove_meta", META_MARKED_FOR_EXPOSURE);
undo_redo->add_do_method(scene_tree, "update_tree");
undo_redo->add_undo_method(scene_tree, "update_tree");
} else if (!enabling && is_exposed) {
// Only unexpose nodes that are currently exposed
undo_redo->add_do_method(node, "remove_meta", META_MARKED_FOR_EXPOSURE);
undo_redo->add_undo_method(node, "set_meta", META_MARKED_FOR_EXPOSURE, true);
if (get_tree()->get_edited_scene_root() == node->get_owner()) {
undo_redo->add_do_method(node, "remove_meta", META_EXPOSED_IN_OWNER);
undo_redo->add_undo_method(node, "set_meta", META_EXPOSED_IN_OWNER, true);
}
undo_redo->add_do_method(scene_tree, "update_tree");
undo_redo->add_undo_method(scene_tree, "update_tree");
}
}
undo_redo->commit_action();
} break;
case TOOL_CREATE_2D_SCENE:
case TOOL_CREATE_3D_SCENE:
case TOOL_CREATE_USER_INTERFACE:
Expand Down Expand Up @@ -2740,6 +2810,11 @@ void SceneTreeDock::_toggle_editable_children(Node *p_node) {

for (Node *owned_node : owned) {
if (owned_node != p_node && owned_node != edited_scene && owned_node->get_owner() == edited_scene && owned_node->get_parent()->get_owner() != edited_scene) {
// Preserve children of exposed nodes
if (owned_node->get_parent()->has_meta(META_EXPOSED_IN_OWNER)) {
continue;
}

owned_nodes_array.push_back(owned_node);
paths_array.push_back(p_node->get_path_to(owned_node->get_parent()));
name_array.push_back(owned_node->get_name());
Expand Down Expand Up @@ -3516,7 +3591,7 @@ static bool _is_node_visible(Node *p_node) {
if (!p_node->get_owner()) {
return false;
}
if (p_node->get_owner() != EditorNode::get_singleton()->get_edited_scene() && !EditorNode::get_singleton()->get_edited_scene()->is_editable_instance(p_node->get_owner())) {
if (p_node->get_owner() != EditorNode::get_singleton()->get_edited_scene() && !EditorNode::get_singleton()->get_edited_scene()->is_editable_instance(p_node->get_owner()) && !p_node->has_meta(META_MARKED_FOR_EXPOSURE)) {
return false;
}

Expand All @@ -3538,6 +3613,10 @@ static bool _has_visible_children(Node *p_node) {
return true;
}

if (p_node->has_exposed_nodes()) {
return true;
}

return false;
}

Expand All @@ -3550,9 +3629,13 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) {
to_node = nullptr;
ERR_FAIL_MSG("Cannot perform drop above the root node!");
}

to_pos = to_node->get_index(false);
to_node = to_node->get_parent();
if (to_node->has_meta(META_EXPOSED_IN_INSTANCE)) {
to_node = to_node->get_owner();
to_pos = -1;
} else {
to_pos = to_node->get_index(false);
to_node = to_node->get_parent();
}

} else if (p_type == 1) {
//drop at below selected node
Expand All @@ -3565,7 +3648,11 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) {
Node *lower_sibling = nullptr;

if (_has_visible_children(to_node)) {
to_pos = 0;
if (to_node->has_exposed_nodes()) {
to_pos = -1;
} else {
to_pos = 0;
}
} else {
for (int i = to_node->get_index(false) + 1; i < to_node->get_parent()->get_child_count(false); i++) {
Node *c = to_node->get_parent()->get_child(i, false);
Expand All @@ -3578,7 +3665,12 @@ void SceneTreeDock::_normalize_drop(Node *&to_node, int &to_pos, int p_type) {
to_pos = lower_sibling->get_index(false);
}

to_node = to_node->get_parent();
if (to_node->has_meta(META_EXPOSED_IN_INSTANCE)) {
to_pos = to_node->get_index(false) + 1;
to_node = to_node->get_owner();
} else {
to_node = to_node->get_parent();
}
}
}
}
Expand Down Expand Up @@ -3949,16 +4041,20 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
break;
}
}

Node *node = full_selection.front()->get();
if (all_owned) {
// Group "toggle_unique_name" with "copy_node_path", if it is available.
if (menu->get_item_index(TOOL_COPY_NODE_PATH) == -1) {
menu->add_separator();
}
Node *node = full_selection.front()->get();
menu->add_icon_check_item(get_editor_theme_icon(SNAME("SceneUniqueName")), TTRC("Access as Unique Name"), TOOL_TOGGLE_SCENE_UNIQUE_NAME);
menu->set_item_shortcut(menu->get_item_index(TOOL_TOGGLE_SCENE_UNIQUE_NAME), ED_GET_SHORTCUT("scene_tree/toggle_unique_name"));
menu->set_item_checked(menu->get_item_index(TOOL_TOGGLE_SCENE_UNIQUE_NAME), node->is_unique_name_in_owner());
}
menu->add_icon_check_item(get_editor_theme_icon(SNAME("SceneExposedNode")), TTRC("Expose in Instances"), TOOL_TOGGLE_SCENE_EXPOSE_NODE);
menu->set_item_shortcut(menu->get_item_index(TOOL_TOGGLE_SCENE_EXPOSE_NODE), ED_GET_SHORTCUT("scene_tree/toggle_expose_node"));
menu->set_item_checked(menu->get_item_index(TOOL_TOGGLE_SCENE_EXPOSE_NODE), node->has_meta(META_MARKED_FOR_EXPOSURE));
}

if (selection.size() == 1) {
Expand Down Expand Up @@ -4737,6 +4833,7 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec
ED_SHORTCUT("scene_tree/copy_node_path", TTRC("Copy Node Path"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C);
ED_SHORTCUT("scene_tree/show_in_file_system", TTRC("Show in FileSystem"));
ED_SHORTCUT("scene_tree/toggle_unique_name", TTRC("Toggle Access as Unique Name"));
ED_SHORTCUT("scene_tree/toggle_expose_node", TTRC("Toggle Node Exposure"));
ED_SHORTCUT("scene_tree/toggle_editable_children", TTRC("Toggle Editable Children"));
ED_SHORTCUT("scene_tree/delete_no_confirm", TTRC("Delete (No Confirm)"), KeyModifierMask::SHIFT | Key::KEY_DELETE);
ED_SHORTCUT("scene_tree/delete", TTRC("Delete"), Key::KEY_DELETE);
Expand Down
2 changes: 2 additions & 0 deletions editor/docks/scene_tree_dock.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class SceneTreeDock : public EditorDock {
TOOL_SCENE_CLEAR_INHERITANCE_CONFIRM,
TOOL_SCENE_OPEN_INHERITED,
TOOL_TOGGLE_SCENE_UNIQUE_NAME,
TOOL_TOGGLE_SCENE_EXPOSE_NODE,
TOOL_CREATE_2D_SCENE,
TOOL_CREATE_3D_SCENE,
TOOL_CREATE_USER_INTERFACE,
Expand Down Expand Up @@ -155,6 +156,7 @@ class SceneTreeDock : public EditorDock {
Label *delete_dialog_label = nullptr;
CheckBox *delete_tracks_checkbox = nullptr;
ConfirmationDialog *editable_instance_remove_dialog = nullptr;
ConfirmationDialog *revoke_node_exposure_dialog = nullptr;
ConfirmationDialog *placeholder_editable_instance_remove_dialog = nullptr;

ReparentDialog *reparent_dialog = nullptr;
Expand Down
12 changes: 10 additions & 2 deletions editor/editor_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -723,9 +723,17 @@ 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->has_meta(META_EXPOSED_IN_OWNER) && !new_node->has_meta(META_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/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.
18 changes: 17 additions & 1 deletion editor/import/3d/resource_importer_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1482,6 +1482,13 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<
}
}

if (!isroot && node_settings.has("import/exposed")) {
if (bool(node_settings["import/exposed"])) {
p_node->set_meta(META_EXPOSED_IN_OWNER, true);
p_node->get_owner()->set_meta(META_CONTAINS_EXPOSED_NODES, true);
}
}

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 @@ -2120,10 +2127,12 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "node/node_type", PROPERTY_HINT_TYPE_STRING, "Node"), ""));
r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "node/script", PROPERTY_HINT_RESOURCE_TYPE, "Script"), Variant()));
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::OBJECT, "node/script", PROPERTY_HINT_RESOURCE_TYPE, "Script"), Variant()));
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, "StaticBody3D,RigidBody3D,Area3D"), 0));
Expand Down Expand Up @@ -2205,6 +2214,7 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
case INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE: {
r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "node/script", PROPERTY_HINT_RESOURCE_TYPE, "Script"), Variant()));
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 @@ -2218,6 +2228,7 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
case INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE: {
r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "node/script", PROPERTY_HINT_RESOURCE_TYPE, "Script"), Variant()));
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 @@ -2772,7 +2783,12 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
mesh_node->set_gi_mode(GeometryInstance3D::GI_MODE_STATIC);
} break;
}

if (src_mesh_node->has_meta(META_EXPOSED_IN_OWNER)) {
mesh_node->set_meta(META_EXPOSED_IN_OWNER, true);
}
if (mesh_node->get_owner() != nullptr) {
mesh_node->get_owner()->set_meta(META_CONTAINS_EXPOSED_NODES, src_mesh_node->has_meta(META_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_visible(src_mesh_node->is_visible());
Expand Down
9 changes: 9 additions & 0 deletions editor/scene/3d/node_3d_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5201,6 +5201,15 @@ bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path
instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path));
}

Vector<NodePath> exposed_nodes = scene->get_state()->get_exposed_nodes();
instantiated_scene->set_meta(META_CONTAINS_EXPOSED_NODES, exposed_nodes.size() > 0);
for (const NodePath &e_path : exposed_nodes) {
Node *ei = instantiated_scene->get_node_or_null(e_path);
if (ei) {
ei->set_meta(META_EXPOSED_IN_INSTANCE, true);
}
}

EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true);
undo_redo->add_do_method(instantiated_scene, "set_owner", EditorNode::get_singleton()->get_edited_scene());
Expand Down
Loading