gdharness 1.3.3 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +105 -74
- package/build/godot/addons/gdharness_editor/animation_reads.gd +3 -2
- package/build/godot/addons/gdharness_editor/declared_values.gd +157 -4
- package/build/godot/addons/gdharness_editor/file_reads.gd +15 -3
- package/build/godot/addons/gdharness_editor/headless_check.gd +44 -23
- package/build/godot/addons/gdharness_editor/property_paths.gd +225 -0
- package/build/godot/addons/gdharness_editor/property_paths.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/property_values.gd +195 -221
- package/build/godot/addons/gdharness_editor/reading.gd +15 -0
- package/build/godot/addons/gdharness_editor/same_values.gd +177 -0
- package/build/godot/addons/gdharness_editor/same_values.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +97 -14
- package/build/godot/addons/gdharness_editor/serialisation.gd +75 -3
- package/build/godot/addons/gdharness_editor/tool_executor.gd +9 -3
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +38 -35
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +3 -2
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +91 -0
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +68 -2
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +94 -333
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +363 -0
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +15 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +10 -26
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +3 -2
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +2 -1
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +30 -0
- package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -3
- package/build/godot/operations/animation_reads.gd +3 -2
- package/build/godot/operations/declared_values.gd +157 -4
- package/build/godot/operations/dependencies.gd +14 -39
- package/build/godot/operations/editor_tools.gd +4 -3
- package/build/godot/operations/file_reads.gd +15 -3
- package/build/godot/operations/gdscript_analysis.gd +229 -75
- package/build/godot/operations/gdscript_authoring.gd +142 -34
- package/build/godot/operations/import_pipeline.gd +17 -6
- package/build/godot/operations/logger.gd +7 -0
- package/build/godot/operations/property_paths.gd +225 -0
- package/build/godot/operations/property_values.gd +195 -221
- package/build/godot/operations/reading.gd +15 -0
- package/build/godot/operations/same_values.gd +177 -0
- package/build/godot/operations/serialisation.gd +75 -3
- package/package.json +1 -1
|
@@ -29,18 +29,6 @@ func set_editor_plugin(plugin: EditorPlugin) -> void:
|
|
|
29
29
|
# =============================================================================
|
|
30
30
|
# Shared helpers
|
|
31
31
|
# =============================================================================
|
|
32
|
-
func _ensure_res_path(path: String) -> String:
|
|
33
|
-
if not path.begins_with("res://"):
|
|
34
|
-
return "res://" + path
|
|
35
|
-
return path
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
func _find_node(root: Node, path: String) -> Node:
|
|
39
|
-
if path == "." or path.is_empty():
|
|
40
|
-
return root
|
|
41
|
-
return root.get_node_or_null(path)
|
|
42
|
-
|
|
43
|
-
|
|
44
32
|
## [param scene_path] opened for writing, with the node at [param node_path] in it, which must be a
|
|
45
33
|
## [param kind] the scene owns. Answers [scene, node, refusal]; the refusal is {} when both were
|
|
46
34
|
## found.
|
|
@@ -49,7 +37,7 @@ func _open_at(scene_path: String, node_path: String, kind: String) -> Array:
|
|
|
49
37
|
var refused: Dictionary = scene.open(scene_path, true)
|
|
50
38
|
if not refused.is_empty():
|
|
51
39
|
return [scene, null, refused]
|
|
52
|
-
var node: Node =
|
|
40
|
+
var node: Node = GdhSceneFile.node_at(scene.root, node_path)
|
|
53
41
|
if node == null or not node.is_class(kind):
|
|
54
42
|
return [scene, null, _not_of_kind(scene, node, node_path, kind)]
|
|
55
43
|
var foreign: String = scene.foreign(node)
|
|
@@ -65,7 +53,7 @@ func _read_at(scene_path: String, node_path: String, kind: String) -> Array:
|
|
|
65
53
|
var refused: Dictionary = scene.open(scene_path, false)
|
|
66
54
|
if not refused.is_empty():
|
|
67
55
|
return [scene, null, refused]
|
|
68
|
-
var node: Node =
|
|
56
|
+
var node: Node = GdhSceneFile.node_at(scene.root, node_path)
|
|
69
57
|
if node == null or not node.is_class(kind):
|
|
70
58
|
return [scene, null, _not_of_kind(scene, node, node_path, kind)]
|
|
71
59
|
return [scene, node, {}]
|
|
@@ -233,7 +221,7 @@ func _animated(player: AnimationPlayer, track_path: String) -> Node:
|
|
|
233
221
|
## The animations of an AnimationPlayer, or one of them whole. A track comes back in the shape
|
|
234
222
|
## add_track takes, so one read here can be written to another animation as it stands.
|
|
235
223
|
func read_animations(args: Dictionary) -> Dictionary:
|
|
236
|
-
var scene_path: String =
|
|
224
|
+
var scene_path: String = GdhSceneFile.res_path("", str(args.get("scenePath", "")))
|
|
237
225
|
var player_node_path: String = str(args.get("playerNodePath", "."))
|
|
238
226
|
var animation_name: String = str(args.get("animationName", ""))
|
|
239
227
|
if scene_path.strip_edges() == "res://":
|
|
@@ -258,7 +246,7 @@ func _read_in(scene: GdhSceneFile, answer: Dictionary) -> Dictionary:
|
|
|
258
246
|
|
|
259
247
|
## The states and transitions of a state machine in an AnimationTree.
|
|
260
248
|
func read_animation_states(args: Dictionary) -> Dictionary:
|
|
261
|
-
var scene_path: String =
|
|
249
|
+
var scene_path: String = GdhSceneFile.res_path("", str(args.get("scenePath", "")))
|
|
262
250
|
var anim_tree_path: String = str(args.get("animTreePath", ""))
|
|
263
251
|
var state_machine_path: String = str(args.get("stateMachinePath", ""))
|
|
264
252
|
if scene_path.strip_edges() == "res://":
|
|
@@ -278,7 +266,7 @@ func read_animation_states(args: Dictionary) -> Dictionary:
|
|
|
278
266
|
# create_animation
|
|
279
267
|
# =============================================================================
|
|
280
268
|
func create_animation(args: Dictionary) -> Dictionary:
|
|
281
|
-
var scene_path: String =
|
|
269
|
+
var scene_path: String = GdhSceneFile.res_path("", str(args.get("scenePath", "")))
|
|
282
270
|
var player_node_path: String = str(args.get("playerNodePath", "."))
|
|
283
271
|
var animation_name: String = str(args.get("animationName", ""))
|
|
284
272
|
var loop_mode_name: String = str(args.get("loopMode", "none"))
|
|
@@ -335,7 +323,7 @@ func create_animation(args: Dictionary) -> Dictionary:
|
|
|
335
323
|
# add_animation_track
|
|
336
324
|
# =============================================================================
|
|
337
325
|
func add_animation_track(args: Dictionary) -> Dictionary:
|
|
338
|
-
var scene_path: String =
|
|
326
|
+
var scene_path: String = GdhSceneFile.res_path("", str(args.get("scenePath", "")))
|
|
339
327
|
var player_node_path: String = str(args.get("playerNodePath", "."))
|
|
340
328
|
var animation_name: String = str(args.get("animationName", ""))
|
|
341
329
|
var given_track: Variant = args.get("track", {})
|
|
@@ -553,7 +541,7 @@ func _key_values(target: Node, prop_name: String, keyframes: Array) -> Array:
|
|
|
553
541
|
var base: String = prop_name.get_slice(":", 0)
|
|
554
542
|
var declared: Dictionary = _properties.declaration(target, base)
|
|
555
543
|
if declared.is_empty():
|
|
556
|
-
return [false,
|
|
544
|
+
return [false, GdhPropertyValues.no_property(target, base)]
|
|
557
545
|
var values: Array = []
|
|
558
546
|
for index: int in keyframes.size():
|
|
559
547
|
var keyframe: Dictionary = keyframes[index]
|
|
@@ -612,7 +600,7 @@ func _animation_at(scene_path: String, player_node_path: String, animation_name:
|
|
|
612
600
|
## Changes the length, loop mode or step of an animation, naming any key a shorter length leaves
|
|
613
601
|
## past the end.
|
|
614
602
|
func set_animation(args: Dictionary) -> Dictionary:
|
|
615
|
-
var scene_path: String =
|
|
603
|
+
var scene_path: String = GdhSceneFile.res_path("", str(args.get("scenePath", "")))
|
|
616
604
|
var player_node_path: String = str(args.get("playerNodePath", "."))
|
|
617
605
|
var animation_name: String = str(args.get("animationName", ""))
|
|
618
606
|
var loop_mode_name: String = str(args.get("loopMode", ""))
|
|
@@ -673,7 +661,7 @@ func set_animation(args: Dictionary) -> Dictionary:
|
|
|
673
661
|
## the scene's trees that plays it, since a state naming an animation the player lacks plays nothing
|
|
674
662
|
## and says so only in the editor's log.
|
|
675
663
|
func rename_animation(args: Dictionary) -> Dictionary:
|
|
676
|
-
var scene_path: String =
|
|
664
|
+
var scene_path: String = GdhSceneFile.res_path("", str(args.get("scenePath", "")))
|
|
677
665
|
var player_node_path: String = str(args.get("playerNodePath", "."))
|
|
678
666
|
var animation_name: String = str(args.get("animationName", ""))
|
|
679
667
|
var new_name: String = str(args.get("newName", "")).strip_edges()
|
|
@@ -752,7 +740,7 @@ func rename_animation(args: Dictionary) -> Dictionary:
|
|
|
752
740
|
## Removes an animation, clearing the player's autoplay when it named it and naming the states of
|
|
753
741
|
## the scene's trees left playing nothing.
|
|
754
742
|
func remove_animation(args: Dictionary) -> Dictionary:
|
|
755
|
-
var scene_path: String =
|
|
743
|
+
var scene_path: String = GdhSceneFile.res_path("", str(args.get("scenePath", "")))
|
|
756
744
|
var player_node_path: String = str(args.get("playerNodePath", "."))
|
|
757
745
|
var animation_name: String = str(args.get("animationName", ""))
|
|
758
746
|
if scene_path.strip_edges() == "res://":
|
|
@@ -844,7 +832,7 @@ static func _collect_playing(
|
|
|
844
832
|
# add_animation_state
|
|
845
833
|
# =============================================================================
|
|
846
834
|
func add_animation_state(args: Dictionary) -> Dictionary:
|
|
847
|
-
var scene_path: String =
|
|
835
|
+
var scene_path: String = GdhSceneFile.res_path("", str(args.get("scenePath", "")))
|
|
848
836
|
var anim_tree_path: String = str(args.get("animTreePath", ""))
|
|
849
837
|
var state_name: String = str(args.get("stateName", ""))
|
|
850
838
|
var animation_name: String = str(args.get("animationName", ""))
|
|
@@ -872,15 +860,7 @@ func add_animation_state(args: Dictionary) -> Dictionary:
|
|
|
872
860
|
if sm.has_node(StringName(state_name)):
|
|
873
861
|
return scene.refuse("The state machine already has a state named %s." % state_name)
|
|
874
862
|
var player: AnimationPlayer = anim_tree.get_node_or_null(anim_tree.anim_player) as AnimationPlayer
|
|
875
|
-
|
|
876
|
-
# By its path from the scene root, as playerNodePath takes it; anim_player is relative to the
|
|
877
|
-
# tree ("../Anim"), which no argument takes.
|
|
878
|
-
return scene.refuse(
|
|
879
|
-
(
|
|
880
|
-
"The AnimationPlayer at %s has no animation %s for the state to play."
|
|
881
|
-
% [str(scene.root.get_path_to(player)), animation_name]
|
|
882
|
-
)
|
|
883
|
-
)
|
|
863
|
+
var found: bool = player != null and player.has_animation(StringName(animation_name))
|
|
884
864
|
|
|
885
865
|
var anim_node: AnimationNodeAnimation = AnimationNodeAnimation.new()
|
|
886
866
|
anim_node.animation = StringName(animation_name)
|
|
@@ -900,19 +880,42 @@ func add_animation_state(args: Dictionary) -> Dictionary:
|
|
|
900
880
|
"ok": true,
|
|
901
881
|
"stateName": state_name,
|
|
902
882
|
"animationName": animation_name,
|
|
903
|
-
# False when the tree has no player to look the animation up in
|
|
904
|
-
|
|
883
|
+
# False when the tree has no player to look the animation up in, or its player has none of
|
|
884
|
+
# that name.
|
|
885
|
+
"animationFound": found,
|
|
905
886
|
"position": GdhSerialisation.new().serialize_value(position),
|
|
906
887
|
}
|
|
888
|
+
# Written all the same, as the editor writes one: a state machine is often laid out before its
|
|
889
|
+
# animations are made, and refusing it stopped a tree being built in that order.
|
|
890
|
+
if player != null and not found:
|
|
891
|
+
answer["animationMissing"] = _missing_animation(scene, player, animation_name)
|
|
907
892
|
var files: Dictionary = machine[1]
|
|
908
893
|
return _finish(scene, Callable(), files, answer)
|
|
909
894
|
|
|
910
895
|
|
|
896
|
+
## Why the state playing [param animation_name] plays nothing yet. The player is named by its path
|
|
897
|
+
## from the scene root, as playerNodePath takes it; anim_player is relative to the tree ("../Anim"),
|
|
898
|
+
## which no argument takes.
|
|
899
|
+
static func _missing_animation(
|
|
900
|
+
scene: GdhSceneFile, player: AnimationPlayer, animation_name: String
|
|
901
|
+
) -> String:
|
|
902
|
+
var said: String = (
|
|
903
|
+
"The AnimationPlayer at %s has no animation %s yet, so the state plays nothing until it has one."
|
|
904
|
+
% [str(scene.root.get_path_to(player)), animation_name]
|
|
905
|
+
)
|
|
906
|
+
var close: PackedStringArray = GdhRead.nearest(animation_name, player.get_animation_list())
|
|
907
|
+
if close.is_empty():
|
|
908
|
+
return said
|
|
909
|
+
return (
|
|
910
|
+
said + " The nearest %s %s." % ["it has is" if close.size() == 1 else "it has are", ", ".join(close)]
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
|
|
911
914
|
# =============================================================================
|
|
912
915
|
# connect_animation_states
|
|
913
916
|
# =============================================================================
|
|
914
917
|
func connect_animation_states(args: Dictionary) -> Dictionary:
|
|
915
|
-
var scene_path: String =
|
|
918
|
+
var scene_path: String = GdhSceneFile.res_path("", str(args.get("scenePath", "")))
|
|
916
919
|
var anim_tree_path: String = str(args.get("animTreePath", ""))
|
|
917
920
|
var from_state: String = str(args.get("fromState", ""))
|
|
918
921
|
var to_state: String = str(args.get("toState", ""))
|
|
@@ -7,6 +7,7 @@ const GdhFileReads = preload("../file_reads.gd")
|
|
|
7
7
|
const GdhHeadlessCheck = preload("../headless_check.gd")
|
|
8
8
|
const GdhPropertyValues = preload("../property_values.gd")
|
|
9
9
|
const GdhRead = preload("../reading.gd")
|
|
10
|
+
const GdhSame = preload("../same_values.gd")
|
|
10
11
|
const GdhSceneFile = preload("../scene_file.gd")
|
|
11
12
|
|
|
12
13
|
# The minimal shader of a type: its declaration and the one function the engine calls for it.
|
|
@@ -136,7 +137,7 @@ func _save_checked(resource: Resource, path: String, names: Array, previous: Var
|
|
|
136
137
|
for key: Variant in names:
|
|
137
138
|
var property: String = str(key)
|
|
138
139
|
var read: Variant = _properties.read_at(loaded, property)
|
|
139
|
-
if not
|
|
140
|
+
if not GdhSame.same(read, _properties.read_at(resource, property)):
|
|
140
141
|
lost = (
|
|
141
142
|
(
|
|
142
143
|
"%s.%s would load from the file as %s: it is not a property the file keeps, so "
|
|
@@ -182,7 +183,7 @@ func _unkept_headless(resource: Resource, path: String, names: Array, previous:
|
|
|
182
183
|
var property: String = str(key)
|
|
183
184
|
asked[property] = true
|
|
184
185
|
asked[property.get_slice(":", 0)] = true
|
|
185
|
-
|
|
186
|
+
GdhHeadlessCheck.spare(reached, GdhHeadlessCheck.reached(resource, property))
|
|
186
187
|
return GdhHeadlessCheck.settle_saved(
|
|
187
188
|
path,
|
|
188
189
|
before,
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends RefCounted
|
|
3
|
+
|
|
4
|
+
## The groups a node of a scene is in. A scene's own file puts a node in a group and can take it
|
|
5
|
+
## out of one it put it in; a group the scene that makes the node puts it in, the one it is
|
|
6
|
+
## instanced from or one this scene inherits, can be changed only there, as the editor's group dock
|
|
7
|
+
## shows it locked.
|
|
8
|
+
|
|
9
|
+
const GdhSceneFile = preload("../scene_file.gd")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## The groups of [param node], sorted, as {groups, groupsHere}: groupsHere, those this scene's file
|
|
13
|
+
## puts it in, is given only where another scene puts it in some of them. Empty for a node in none.
|
|
14
|
+
static func read(scene: GdhSceneFile, node: Node) -> Dictionary:
|
|
15
|
+
var all: Array = _sorted(node.get_groups())
|
|
16
|
+
if all.is_empty():
|
|
17
|
+
return {}
|
|
18
|
+
var answer: Dictionary = {"groups": all}
|
|
19
|
+
var here: Array = _sorted(scene.groups_at(scene.path_of(node)))
|
|
20
|
+
if here.size() != all.size():
|
|
21
|
+
answer["groupsHere"] = here
|
|
22
|
+
return answer
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## The group names under [param key] of [param args], which the server has checked are strings, or
|
|
26
|
+
## none when not given.
|
|
27
|
+
static func asked(args: Dictionary, key: String) -> Array:
|
|
28
|
+
var given: Variant = args.get(key)
|
|
29
|
+
return given if given is Array else []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## Puts [param node] in each group of [param adding] and takes it out of each of [param removing],
|
|
33
|
+
## or answers why not with nothing changed.
|
|
34
|
+
static func change(scene: GdhSceneFile, node: Node, adding: Array, removing: Array) -> String:
|
|
35
|
+
for group: Variant in adding + removing:
|
|
36
|
+
if str(group).strip_edges().is_empty():
|
|
37
|
+
return (
|
|
38
|
+
"%s names no group: a group's name has something in it besides spaces."
|
|
39
|
+
% JSON.stringify(group)
|
|
40
|
+
)
|
|
41
|
+
for group: Variant in adding:
|
|
42
|
+
if removing.has(group):
|
|
43
|
+
return "%s is in both addGroups and removeGroups." % str(group)
|
|
44
|
+
var at: String = scene.path_of(node)
|
|
45
|
+
var here: PackedStringArray = scene.groups_at(at)
|
|
46
|
+
for group: Variant in removing:
|
|
47
|
+
var name: String = str(group)
|
|
48
|
+
if not node.is_in_group(name):
|
|
49
|
+
var held: Array = _sorted(node.get_groups())
|
|
50
|
+
return (
|
|
51
|
+
"%s is in no group %s; %s."
|
|
52
|
+
% [node.name, name, "it is in " + ", ".join(held) if not held.is_empty() else "it is in none"]
|
|
53
|
+
)
|
|
54
|
+
if not here.has(name):
|
|
55
|
+
var from: String = scene.values_from(node, at)
|
|
56
|
+
if from.is_empty():
|
|
57
|
+
return (
|
|
58
|
+
"%s is in %s, which no scene file puts it in: something puts it there as it is made, such as its script."
|
|
59
|
+
% [node.name, name]
|
|
60
|
+
)
|
|
61
|
+
return (
|
|
62
|
+
(
|
|
63
|
+
"%s is in %s because %s puts it there, and no scene using that one can take it out: "
|
|
64
|
+
+ "remove the group in %s, which takes it out in every scene using that one."
|
|
65
|
+
)
|
|
66
|
+
% [node.name, name, from, from]
|
|
67
|
+
)
|
|
68
|
+
for group: Variant in adding:
|
|
69
|
+
node.add_to_group(str(group), true)
|
|
70
|
+
for group: Variant in removing:
|
|
71
|
+
node.remove_from_group(str(group))
|
|
72
|
+
return ""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
## Why [param saved], the node as the written file loads it, is not in the groups asked, or "".
|
|
76
|
+
static func unkept(saved: Node, adding: Array, removing: Array) -> String:
|
|
77
|
+
for group: Variant in adding:
|
|
78
|
+
if not saved.is_in_group(str(group)):
|
|
79
|
+
return "%s would load in no group %s." % [saved.name, str(group)]
|
|
80
|
+
for group: Variant in removing:
|
|
81
|
+
if saved.is_in_group(str(group)):
|
|
82
|
+
return "%s would still load in the group %s." % [saved.name, str(group)]
|
|
83
|
+
return ""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
static func _sorted(groups: Variant) -> Array:
|
|
87
|
+
var names: Array = []
|
|
88
|
+
for group: Variant in groups:
|
|
89
|
+
names.append(str(group))
|
|
90
|
+
names.sort()
|
|
91
|
+
return names
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://76w2hadtl7nab
|
|
@@ -6,7 +6,7 @@ extends RefCounted
|
|
|
6
6
|
|
|
7
7
|
const GdhFileWalk = preload("../file_walk.gd")
|
|
8
8
|
const GdhRead = preload("../reading.gd")
|
|
9
|
-
const
|
|
9
|
+
const GdhSame = preload("../same_values.gd")
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
## The scene to instance for an add, or null for a node of a class, beside why neither can be made
|
|
@@ -85,7 +85,7 @@ static func copied(source: Node, root: Node, copies: Dictionary) -> Node:
|
|
|
85
85
|
continue
|
|
86
86
|
var property: String = str(declared.get("name", ""))
|
|
87
87
|
var value: Variant = source.get(property)
|
|
88
|
-
if not
|
|
88
|
+
if not GdhSame.same(copy.get(property), value):
|
|
89
89
|
copy.set(property, value)
|
|
90
90
|
for group: StringName in source.get_groups():
|
|
91
91
|
if not copy.is_in_group(group):
|
|
@@ -171,6 +171,72 @@ static func place(node: Node, asked: Variant) -> void:
|
|
|
171
171
|
node.get_parent().move_child(node, GdhRead.as_int(asked))
|
|
172
172
|
|
|
173
173
|
|
|
174
|
+
## The properties that hold where [param node] is drawn, which a reparent keeping it in place
|
|
175
|
+
## changes: a Control keeps only its position, as Node.reparent keeps it.
|
|
176
|
+
static func placing(node: Node) -> Array[String]:
|
|
177
|
+
if node is Node3D:
|
|
178
|
+
return ["position", "rotation", "scale"]
|
|
179
|
+
if node is Control:
|
|
180
|
+
return ["position"]
|
|
181
|
+
if node is Node2D:
|
|
182
|
+
return ["position", "rotation", "scale", "skew"]
|
|
183
|
+
return []
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
## Gives [param node], just moved from under [param was], the transform that keeps it where it was
|
|
187
|
+
## drawn, as the editor's reparent and Node.reparent do. Worked out from the local transforms
|
|
188
|
+
## rather than read off the global ones, which the engine answers only for a node in the tree, and
|
|
189
|
+
## the scene a tool edits is loaded from its file and never is. Nothing changes for a top-level
|
|
190
|
+
## node, or where the old and new parents place their children alike.
|
|
191
|
+
static func keep_in_place(node: Node, was: Node) -> void:
|
|
192
|
+
if node is Node3D:
|
|
193
|
+
var spatial: Node3D = node
|
|
194
|
+
var before: Transform3D = _spatial_space(was)
|
|
195
|
+
var after: Transform3D = _spatial_space(node.get_parent())
|
|
196
|
+
if not spatial.top_level and not before.is_equal_approx(after):
|
|
197
|
+
spatial.transform = after.affine_inverse() * before * spatial.transform
|
|
198
|
+
elif node is CanvasItem:
|
|
199
|
+
var drawn: CanvasItem = node
|
|
200
|
+
var before_2d: Transform2D = _canvas_space(was)
|
|
201
|
+
var after_2d: Transform2D = _canvas_space(node.get_parent())
|
|
202
|
+
if drawn.top_level or before_2d.is_equal_approx(after_2d):
|
|
203
|
+
return
|
|
204
|
+
var held: Transform2D = after_2d.affine_inverse() * before_2d * drawn.get_transform()
|
|
205
|
+
if node is Control:
|
|
206
|
+
var control: Control = node
|
|
207
|
+
control.position = held.origin
|
|
208
|
+
else:
|
|
209
|
+
var flat: Node2D = node
|
|
210
|
+
flat.transform = held
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
## Where the children of [param parent] are drawn in 3D: the transforms of it and the Node3Ds
|
|
214
|
+
## above it, up to the first that is top-level or whose parent is no Node3D.
|
|
215
|
+
static func _spatial_space(parent: Node) -> Transform3D:
|
|
216
|
+
var space: Transform3D = Transform3D.IDENTITY
|
|
217
|
+
var at: Node = parent
|
|
218
|
+
while at is Node3D:
|
|
219
|
+
var spatial: Node3D = at
|
|
220
|
+
space = spatial.transform * space
|
|
221
|
+
if spatial.top_level:
|
|
222
|
+
break
|
|
223
|
+
at = at.get_parent()
|
|
224
|
+
return space
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
## Where the children of [param parent] are drawn in 2D, as [method _spatial_space] works it out in 3D.
|
|
228
|
+
static func _canvas_space(parent: Node) -> Transform2D:
|
|
229
|
+
var space: Transform2D = Transform2D.IDENTITY
|
|
230
|
+
var at: Node = parent
|
|
231
|
+
while at is CanvasItem:
|
|
232
|
+
var drawn: CanvasItem = at
|
|
233
|
+
space = drawn.get_transform() * space
|
|
234
|
+
if drawn.top_level:
|
|
235
|
+
break
|
|
236
|
+
at = at.get_parent()
|
|
237
|
+
return space
|
|
238
|
+
|
|
239
|
+
|
|
174
240
|
## Why [param instanced] cannot be instanced into [param scene_path], or "" when it can: a scene
|
|
175
241
|
## inside itself, directly or through the scenes it instances, which Godot's own dialog refuses and
|
|
176
242
|
## which never finishes loading.
|