|
|
|
@ -58,6 +58,7 @@
|
|
|
|
|
#include "scene/gui/tab_bar.h"
|
|
|
|
|
#include "scene/gui/tab_container.h"
|
|
|
|
|
#include "scene/main/window.h"
|
|
|
|
|
#include "scene/property_utils.h"
|
|
|
|
|
#include "scene/resources/packed_scene.h"
|
|
|
|
|
#include "servers/display_server.h"
|
|
|
|
|
#include "servers/navigation_server_3d.h"
|
|
|
|
@ -997,6 +998,7 @@ void EditorNode::_resources_reimported(const Vector<String> &p_resources) {
|
|
|
|
|
|
|
|
|
|
for (const String &E : scenes) {
|
|
|
|
|
reload_scene(E);
|
|
|
|
|
reload_instances_with_path_in_edited_scenes(E);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scene_tabs->set_current_tab(current_tab);
|
|
|
|
@ -3899,7 +3901,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b
|
|
|
|
|
Ref<SceneState> state = sdata->get_state();
|
|
|
|
|
state->set_path(lpath);
|
|
|
|
|
new_scene->set_scene_inherited_state(state);
|
|
|
|
|
new_scene->set_scene_file_path(String());
|
|
|
|
|
new_scene->set_scene_file_path(lpath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new_scene->set_scene_instance_state(Ref<SceneState>());
|
|
|
|
@ -3933,6 +3935,134 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b
|
|
|
|
|
return OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HashMap<StringName, Variant> EditorNode::get_modified_properties_for_node(Node *p_node) {
|
|
|
|
|
HashMap<StringName, Variant> modified_property_map;
|
|
|
|
|
|
|
|
|
|
List<PropertyInfo> pinfo;
|
|
|
|
|
p_node->get_property_list(&pinfo);
|
|
|
|
|
for (const PropertyInfo &E : pinfo) {
|
|
|
|
|
if (E.usage & PROPERTY_USAGE_STORAGE) {
|
|
|
|
|
bool is_valid_revert = false;
|
|
|
|
|
Variant revert_value = EditorPropertyRevert::get_property_revert_value(p_node, E.name, &is_valid_revert);
|
|
|
|
|
Variant current_value = p_node->get(E.name);
|
|
|
|
|
if (is_valid_revert) {
|
|
|
|
|
if (PropertyUtils::is_property_value_different(current_value, revert_value)) {
|
|
|
|
|
modified_property_map[E.name] = current_value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return modified_property_map;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorNode::update_diff_data_for_node(
|
|
|
|
|
Node *p_edited_scene,
|
|
|
|
|
Node *p_root,
|
|
|
|
|
Node *p_node,
|
|
|
|
|
HashMap<NodePath, ModificationNodeEntry> &p_modification_table,
|
|
|
|
|
List<AdditiveNodeEntry> &p_addition_list) {
|
|
|
|
|
bool node_part_of_subscene = p_node != p_edited_scene &&
|
|
|
|
|
p_edited_scene->get_scene_inherited_state().is_valid() &&
|
|
|
|
|
p_edited_scene->get_scene_inherited_state()->find_node_by_path(p_edited_scene->get_path_to(p_node)) >= 0;
|
|
|
|
|
|
|
|
|
|
// Loop through the owners until either we reach the root node or nullptr
|
|
|
|
|
Node *valid_node_owner = p_node->get_owner();
|
|
|
|
|
while (valid_node_owner) {
|
|
|
|
|
if (valid_node_owner == p_root) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
valid_node_owner = valid_node_owner->get_owner();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((valid_node_owner == p_root && (p_root != p_edited_scene || !p_edited_scene->get_scene_file_path().is_empty())) || node_part_of_subscene || p_node == p_root) {
|
|
|
|
|
HashMap<StringName, Variant> modified_properties = get_modified_properties_for_node(p_node);
|
|
|
|
|
|
|
|
|
|
// Find all valid connections to other nodes.
|
|
|
|
|
List<Connection> connections_to;
|
|
|
|
|
p_node->get_all_signal_connections(&connections_to);
|
|
|
|
|
|
|
|
|
|
List<ConnectionWithNodePath> valid_connections_to;
|
|
|
|
|
for (const Connection &c : connections_to) {
|
|
|
|
|
Node *connection_target_node = Object::cast_to<Node>(c.callable.get_object());
|
|
|
|
|
if (connection_target_node) {
|
|
|
|
|
// TODO: add support for reinstating custom callables
|
|
|
|
|
if (!c.callable.is_custom()) {
|
|
|
|
|
ConnectionWithNodePath connection_to;
|
|
|
|
|
connection_to.connection = c;
|
|
|
|
|
connection_to.node_path = p_node->get_path_to(connection_target_node);
|
|
|
|
|
valid_connections_to.push_back(connection_to);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find all valid connections from other nodes.
|
|
|
|
|
List<Connection> connections_from;
|
|
|
|
|
p_node->get_signals_connected_to_this(&connections_from);
|
|
|
|
|
|
|
|
|
|
List<Connection> valid_connections_from;
|
|
|
|
|
for (const Connection &c : connections_from) {
|
|
|
|
|
Node *source_node = Object::cast_to<Node>(c.signal.get_object());
|
|
|
|
|
|
|
|
|
|
Node *valid_source_owner = nullptr;
|
|
|
|
|
if (source_node) {
|
|
|
|
|
valid_source_owner = source_node->get_owner();
|
|
|
|
|
while (valid_source_owner) {
|
|
|
|
|
if (valid_source_owner == p_root) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
valid_source_owner = valid_source_owner->get_owner();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!source_node || valid_source_owner == nullptr) {
|
|
|
|
|
// TODO: add support for reinstating custom callables
|
|
|
|
|
if (!c.callable.is_custom()) {
|
|
|
|
|
valid_connections_from.push_back(c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find all node groups.
|
|
|
|
|
List<Node::GroupInfo> groups;
|
|
|
|
|
p_node->get_groups(&groups);
|
|
|
|
|
|
|
|
|
|
if (!modified_properties.is_empty() || !valid_connections_to.is_empty() || !valid_connections_from.is_empty() || !groups.is_empty()) {
|
|
|
|
|
ModificationNodeEntry modification_node_entry;
|
|
|
|
|
modification_node_entry.property_table = modified_properties;
|
|
|
|
|
modification_node_entry.connections_to = valid_connections_to;
|
|
|
|
|
modification_node_entry.connections_from = valid_connections_from;
|
|
|
|
|
modification_node_entry.groups = groups;
|
|
|
|
|
|
|
|
|
|
p_modification_table[p_root->get_path_to(p_node)] = modification_node_entry;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
AdditiveNodeEntry new_additive_node_entry;
|
|
|
|
|
new_additive_node_entry.node = p_node;
|
|
|
|
|
new_additive_node_entry.parent = p_root->get_path_to(p_node->get_parent());
|
|
|
|
|
new_additive_node_entry.owner = p_node->get_owner();
|
|
|
|
|
new_additive_node_entry.index = p_node->get_index();
|
|
|
|
|
|
|
|
|
|
Node2D *node_2d = Object::cast_to<Node2D>(p_node);
|
|
|
|
|
if (node_2d) {
|
|
|
|
|
new_additive_node_entry.transform_2d = node_2d->get_relative_transform_to_parent(node_2d->get_parent());
|
|
|
|
|
}
|
|
|
|
|
Node3D *node_3d = Object::cast_to<Node3D>(p_node);
|
|
|
|
|
if (node_3d) {
|
|
|
|
|
new_additive_node_entry.transform_3d = node_3d->get_relative_transform(node_3d->get_parent());
|
|
|
|
|
}
|
|
|
|
|
p_addition_list.push_back(new_additive_node_entry);
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < p_node->get_child_count(); i++) {
|
|
|
|
|
Node *child = p_node->get_child(i);
|
|
|
|
|
update_diff_data_for_node(p_edited_scene, p_root, child, p_modification_table, p_addition_list);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
void EditorNode::open_request(const String &p_path) {
|
|
|
|
|
if (!opening_prev) {
|
|
|
|
|
List<String>::Element *prev_scene_item = previous_scenes.find(p_path);
|
|
|
|
@ -5789,6 +5919,352 @@ void EditorNode::reload_scene(const String &p_path) {
|
|
|
|
|
scene_tabs->set_current_tab(current_tab);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list) {
|
|
|
|
|
String scene_file_path = p_node->get_scene_file_path();
|
|
|
|
|
|
|
|
|
|
// This is going to get messy...
|
|
|
|
|
if (p_node->get_scene_file_path() == p_instance_path) {
|
|
|
|
|
p_instance_list.push_back(p_node);
|
|
|
|
|
} else {
|
|
|
|
|
Node *current_node = p_node;
|
|
|
|
|
|
|
|
|
|
Ref<SceneState> inherited_state = current_node->get_scene_inherited_state();
|
|
|
|
|
while (inherited_state.is_valid()) {
|
|
|
|
|
String inherited_path = inherited_state->get_path();
|
|
|
|
|
if (inherited_path == p_instance_path) {
|
|
|
|
|
p_instance_list.push_back(p_node);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inherited_state = inherited_state->get_base_scene_state();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < p_node->get_child_count(); i++) {
|
|
|
|
|
Node *child = p_node->get_child(i);
|
|
|
|
|
find_all_instances_inheriting_path_in_node(p_root, child, p_instance_path, p_instance_list);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_instance_path) {
|
|
|
|
|
int original_edited_scene_idx = editor_data.get_edited_scene();
|
|
|
|
|
HashMap<int, List<Node *>> edited_scene_map;
|
|
|
|
|
|
|
|
|
|
// Walk through each opened scene to get a global list of all instances which match
|
|
|
|
|
// the current reimported scenes.
|
|
|
|
|
for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
|
|
|
|
|
if (editor_data.get_scene_path(i) != p_instance_path) {
|
|
|
|
|
Node *edited_scene_root = editor_data.get_edited_scene_root(i);
|
|
|
|
|
|
|
|
|
|
if (edited_scene_root) {
|
|
|
|
|
List<Node *> valid_nodes;
|
|
|
|
|
find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, p_instance_path, valid_nodes);
|
|
|
|
|
if (valid_nodes.size() > 0) {
|
|
|
|
|
edited_scene_map[i] = valid_nodes;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (edited_scene_map.size() > 0) {
|
|
|
|
|
// Reload the new instance.
|
|
|
|
|
Error err;
|
|
|
|
|
Ref<PackedScene> instance_scene_packed_scene = ResourceLoader::load(p_instance_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE, &err);
|
|
|
|
|
instance_scene_packed_scene->set_path(p_instance_path, true);
|
|
|
|
|
|
|
|
|
|
ERR_FAIL_COND(err != OK);
|
|
|
|
|
ERR_FAIL_COND(instance_scene_packed_scene.is_null());
|
|
|
|
|
|
|
|
|
|
HashMap<String, Ref<PackedScene>> local_scene_cache;
|
|
|
|
|
local_scene_cache[p_instance_path] = instance_scene_packed_scene;
|
|
|
|
|
|
|
|
|
|
for (const KeyValue<int, List<Node *>> &edited_scene_map_elem : edited_scene_map) {
|
|
|
|
|
// Set the current scene.
|
|
|
|
|
int current_scene_idx = edited_scene_map_elem.key;
|
|
|
|
|
editor_data.set_edited_scene(current_scene_idx);
|
|
|
|
|
Node *current_edited_scene = editor_data.get_edited_scene_root(current_scene_idx);
|
|
|
|
|
|
|
|
|
|
// Clear the history for this tab (should we allow history to be retained?).
|
|
|
|
|
EditorUndoRedoManager::get_singleton()->clear_history();
|
|
|
|
|
|
|
|
|
|
// Update the version
|
|
|
|
|
editor_data.is_scene_changed(current_scene_idx);
|
|
|
|
|
|
|
|
|
|
for (Node *original_node : edited_scene_map_elem.value) {
|
|
|
|
|
// Walk the tree for the current node and extract relevant diff data, storing it in the modification table.
|
|
|
|
|
// For additional nodes which are part of the current scene, they get added to the addition table.
|
|
|
|
|
HashMap<NodePath, ModificationNodeEntry> modification_table;
|
|
|
|
|
List<AdditiveNodeEntry> addition_list;
|
|
|
|
|
update_diff_data_for_node(current_edited_scene, original_node, original_node, modification_table, addition_list);
|
|
|
|
|
|
|
|
|
|
// Disconnect all relevant connections, all connections from and persistent connections to.
|
|
|
|
|
for (const KeyValue<NodePath, ModificationNodeEntry> &modification_table_entry : modification_table) {
|
|
|
|
|
for (Connection conn : modification_table_entry.value.connections_from) {
|
|
|
|
|
conn.signal.get_object()->disconnect(conn.signal.get_name(), conn.callable);
|
|
|
|
|
}
|
|
|
|
|
for (ConnectionWithNodePath cwnp : modification_table_entry.value.connections_to) {
|
|
|
|
|
Connection conn = cwnp.connection;
|
|
|
|
|
if (conn.flags & CONNECT_PERSIST) {
|
|
|
|
|
conn.signal.get_object()->disconnect(conn.signal.get_name(), conn.callable);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store all the paths for any selected nodes which are ancestors of the node we're replacing.
|
|
|
|
|
List<NodePath> selected_node_paths;
|
|
|
|
|
for (Node *selected_node : editor_selection->get_selected_node_list()) {
|
|
|
|
|
if (selected_node == original_node || original_node->is_ancestor_of(selected_node)) {
|
|
|
|
|
selected_node_paths.push_back(original_node->get_path_to(selected_node));
|
|
|
|
|
editor_selection->remove_node(selected_node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove all nodes which were added as additional elements (they will be restored later).
|
|
|
|
|
for (AdditiveNodeEntry additive_node_entry : addition_list) {
|
|
|
|
|
Node *addition_node = additive_node_entry.node;
|
|
|
|
|
addition_node->get_parent()->remove_child(addition_node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clear ownership of the nodes (kind of hack to workaround an issue with
|
|
|
|
|
// replace_by when called on nodes in other tabs).
|
|
|
|
|
List<Node *> nodes_owned_by_original_node;
|
|
|
|
|
original_node->get_owned_by(original_node, &nodes_owned_by_original_node);
|
|
|
|
|
for (Node *owned_node : nodes_owned_by_original_node) {
|
|
|
|
|
owned_node->set_owner(nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete all the remaining node children.
|
|
|
|
|
while (original_node->get_child_count()) {
|
|
|
|
|
Node *child = original_node->get_child(0);
|
|
|
|
|
|
|
|
|
|
original_node->remove_child(child);
|
|
|
|
|
child->queue_free();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reset the editable instance state.
|
|
|
|
|
bool is_editable = true;
|
|
|
|
|
Node *owner = original_node->get_owner();
|
|
|
|
|
if (owner) {
|
|
|
|
|
is_editable = owner->is_editable_instance(original_node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load a replacement scene for the node.
|
|
|
|
|
Ref<PackedScene> current_packed_scene;
|
|
|
|
|
if (original_node->get_scene_file_path() == p_instance_path) {
|
|
|
|
|
// If the node file name directly matches the scene we're replacing,
|
|
|
|
|
// just load it since we already cached it.
|
|
|
|
|
current_packed_scene = instance_scene_packed_scene;
|
|
|
|
|
} else {
|
|
|
|
|
// Otherwise, check the inheritance chain, reloading and caching any scenes
|
|
|
|
|
// we require along the way.
|
|
|
|
|
List<String> required_load_paths;
|
|
|
|
|
String scene_path = original_node->get_scene_file_path();
|
|
|
|
|
// Do we need to check if the paths are empty?
|
|
|
|
|
if (!scene_path.is_empty()) {
|
|
|
|
|
required_load_paths.push_front(scene_path);
|
|
|
|
|
}
|
|
|
|
|
Ref<SceneState> inherited_state = original_node->get_scene_inherited_state();
|
|
|
|
|
while (inherited_state.is_valid()) {
|
|
|
|
|
String inherited_path = inherited_state->get_path();
|
|
|
|
|
// Do we need to check if the paths are empty?
|
|
|
|
|
if (!inherited_path.is_empty()) {
|
|
|
|
|
required_load_paths.push_front(inherited_path);
|
|
|
|
|
}
|
|
|
|
|
inherited_state = inherited_state->get_base_scene_state();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure the inheritance chain is loaded in the correct order so that cache can
|
|
|
|
|
// be properly updated.
|
|
|
|
|
for (String path : required_load_paths) {
|
|
|
|
|
if (!local_scene_cache.find(path)) {
|
|
|
|
|
current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_IGNORE, &err);
|
|
|
|
|
current_packed_scene->set_path(path, true);
|
|
|
|
|
local_scene_cache[path] = current_packed_scene;
|
|
|
|
|
} else {
|
|
|
|
|
current_packed_scene = local_scene_cache[path];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ERR_FAIL_COND(current_packed_scene.is_null());
|
|
|
|
|
|
|
|
|
|
// Instantiate the node.
|
|
|
|
|
Node *instantiated_node = nullptr;
|
|
|
|
|
if (current_packed_scene.is_valid()) {
|
|
|
|
|
instantiated_node = current_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ERR_FAIL_COND(!instantiated_node);
|
|
|
|
|
|
|
|
|
|
bool original_node_is_displayed_folded = original_node->is_displayed_folded();
|
|
|
|
|
bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder();
|
|
|
|
|
|
|
|
|
|
// Update the name to match
|
|
|
|
|
instantiated_node->set_name(original_node->get_name());
|
|
|
|
|
|
|
|
|
|
// Is this replacing the edited root node?
|
|
|
|
|
String original_node_file_path = original_node->get_scene_file_path();
|
|
|
|
|
|
|
|
|
|
if (current_edited_scene == original_node) {
|
|
|
|
|
instantiated_node->set_scene_instance_state(original_node->get_scene_instance_state());
|
|
|
|
|
// Fix unsaved inherited scene
|
|
|
|
|
if (original_node_file_path.is_empty()) {
|
|
|
|
|
Ref<SceneState> state = current_packed_scene->get_state();
|
|
|
|
|
state->set_path(current_packed_scene->get_path());
|
|
|
|
|
instantiated_node->set_scene_inherited_state(state);
|
|
|
|
|
}
|
|
|
|
|
editor_data.set_edited_scene_root(instantiated_node);
|
|
|
|
|
current_edited_scene = instantiated_node;
|
|
|
|
|
|
|
|
|
|
if (original_node->is_inside_tree()) {
|
|
|
|
|
SceneTreeDock::get_singleton()->set_edited_scene(current_edited_scene);
|
|
|
|
|
original_node->get_tree()->set_edited_scene_root(instantiated_node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Replace the original node with the instantiated version.
|
|
|
|
|
original_node->replace_by(instantiated_node, false);
|
|
|
|
|
|
|
|
|
|
// Mark the old node for deletion.
|
|
|
|
|
original_node->queue_free();
|
|
|
|
|
|
|
|
|
|
// Restore the folded and placeholder state from the original node.
|
|
|
|
|
instantiated_node->set_display_folded(original_node_is_displayed_folded);
|
|
|
|
|
instantiated_node->set_scene_instance_load_placeholder(original_node_scene_instance_load_placeholder);
|
|
|
|
|
|
|
|
|
|
if (owner) {
|
|
|
|
|
Ref<SceneState> ss_inst = owner->get_scene_instance_state();
|
|
|
|
|
if (ss_inst.is_valid()) {
|
|
|
|
|
ss_inst->update_instance_resource(p_instance_path, current_packed_scene);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
owner->set_editable_instance(instantiated_node, is_editable);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Attempt to re-add all the additional nodes.
|
|
|
|
|
for (AdditiveNodeEntry additive_node_entry : addition_list) {
|
|
|
|
|
Node *parent_node = instantiated_node->get_node_or_null(additive_node_entry.parent);
|
|
|
|
|
|
|
|
|
|
if (!parent_node) {
|
|
|
|
|
parent_node = current_edited_scene;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parent_node->add_child(additive_node_entry.node);
|
|
|
|
|
parent_node->move_child(additive_node_entry.node, additive_node_entry.index);
|
|
|
|
|
// If the additive node's owner was the node which got replaced, update it.
|
|
|
|
|
if (additive_node_entry.owner == original_node) {
|
|
|
|
|
additive_node_entry.owner = instantiated_node;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
additive_node_entry.node->set_owner(additive_node_entry.owner);
|
|
|
|
|
|
|
|
|
|
// If the parent node was lost, attempt to restore the original global transform.
|
|
|
|
|
{
|
|
|
|
|
Node2D *node_2d = Object::cast_to<Node2D>(additive_node_entry.node);
|
|
|
|
|
if (node_2d) {
|
|
|
|
|
node_2d->set_transform(additive_node_entry.transform_2d);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Node3D *node_3d = Object::cast_to<Node3D>(additive_node_entry.node);
|
|
|
|
|
if (node_3d) {
|
|
|
|
|
node_3d->set_transform(additive_node_entry.transform_3d);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore the selection.
|
|
|
|
|
if (selected_node_paths.size()) {
|
|
|
|
|
for (NodePath selected_node_path : selected_node_paths) {
|
|
|
|
|
Node *selected_node = instantiated_node->get_node_or_null(selected_node_path);
|
|
|
|
|
if (selected_node) {
|
|
|
|
|
editor_selection->add_node(selected_node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
editor_selection->update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Attempt to restore the modified properties and signals for the instantitated node and all its owned children.
|
|
|
|
|
for (KeyValue<NodePath, ModificationNodeEntry> &E : modification_table) {
|
|
|
|
|
NodePath new_current_path = E.key;
|
|
|
|
|
Node *modifiable_node = instantiated_node->get_node_or_null(new_current_path);
|
|
|
|
|
|
|
|
|
|
if (modifiable_node) {
|
|
|
|
|
// Get properties for this node.
|
|
|
|
|
List<PropertyInfo> pinfo;
|
|
|
|
|
modifiable_node->get_property_list(&pinfo);
|
|
|
|
|
|
|
|
|
|
// Get names of all valid property names (TODO: make this more efficent).
|
|
|
|
|
List<String> property_names;
|
|
|
|
|
for (const PropertyInfo &E2 : pinfo) {
|
|
|
|
|
if (E2.usage & PROPERTY_USAGE_STORAGE) {
|
|
|
|
|
property_names.push_back(E2.name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore the modified properties for this node.
|
|
|
|
|
for (const KeyValue<StringName, Variant> &E2 : E.value.property_table) {
|
|
|
|
|
if (property_names.find(E2.key)) {
|
|
|
|
|
modifiable_node->set(E2.key, E2.value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Restore the connections to other nodes.
|
|
|
|
|
for (const ConnectionWithNodePath &E2 : E.value.connections_to) {
|
|
|
|
|
Connection conn = E2.connection;
|
|
|
|
|
|
|
|
|
|
// Get the node the callable is targetting.
|
|
|
|
|
Node *target_node = cast_to<Node>(conn.callable.get_object());
|
|
|
|
|
|
|
|
|
|
// If the callable object no longer exists or is marked for deletion,
|
|
|
|
|
// attempt to reaccquire the closest match by using the node path
|
|
|
|
|
// we saved earlier.
|
|
|
|
|
if (!target_node || !target_node->is_queued_for_deletion()) {
|
|
|
|
|
target_node = modifiable_node->get_node_or_null(E2.node_path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (target_node) {
|
|
|
|
|
// Reconstruct the callable.
|
|
|
|
|
Callable new_callable = Callable(target_node, conn.callable.get_method());
|
|
|
|
|
|
|
|
|
|
if (!modifiable_node->is_connected(conn.signal.get_name(), new_callable)) {
|
|
|
|
|
ERR_FAIL_COND(modifiable_node->connect(conn.signal.get_name(), new_callable, conn.flags) != OK);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore the connections from other nodes.
|
|
|
|
|
for (const Connection &E2 : E.value.connections_from) {
|
|
|
|
|
Connection conn = E2;
|
|
|
|
|
|
|
|
|
|
bool valid = modifiable_node->has_method(conn.callable.get_method()) || Ref<Script>(modifiable_node->get_script()).is_null() || Ref<Script>(modifiable_node->get_script())->has_method(conn.callable.get_method());
|
|
|
|
|
ERR_CONTINUE_MSG(!valid, vformat("Attempt to connect signal '%s.%s' to nonexistent method '%s.%s'.", conn.signal.get_object()->get_class(), conn.signal.get_name(), conn.callable.get_object()->get_class(), conn.callable.get_method()));
|
|
|
|
|
|
|
|
|
|
// Get the object which the signal is connected from.
|
|
|
|
|
Object *source_object = conn.signal.get_object();
|
|
|
|
|
|
|
|
|
|
if (source_object) {
|
|
|
|
|
ERR_FAIL_COND(source_object->connect(conn.signal.get_name(), Callable(modifiable_node, conn.callable.get_method()), conn.flags) != OK);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Re-add the groups.
|
|
|
|
|
for (const Node::GroupInfo &E2 : E.value.groups) {
|
|
|
|
|
modifiable_node->add_to_group(E2.name, E2.persistent);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Cleanup the history of the changes.
|
|
|
|
|
editor_history.cleanup_history();
|
|
|
|
|
|
|
|
|
|
current_edited_scene->propagate_notification(NOTIFICATION_NODE_RECACHE_REQUESTED);
|
|
|
|
|
}
|
|
|
|
|
edited_scene_map.clear();
|
|
|
|
|
}
|
|
|
|
|
editor_data.set_edited_scene(original_edited_scene_idx);
|
|
|
|
|
|
|
|
|
|
_edit_current();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int EditorNode::plugin_init_callback_count = 0;
|
|
|
|
|
|
|
|
|
|
void EditorNode::add_plugin_init_callback(EditorPluginInitializeCallback p_callback) {
|
|
|
|
|