gdharness 1.3.41 → 1.3.43
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 +131 -47
- package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
- package/build/godot/addons/gdharness_editor/animation_reads.gd +84 -35
- package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -58
- package/build/godot/addons/gdharness_editor/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_editor/file_reads.gd +114 -39
- package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
- package/build/godot/addons/gdharness_editor/headless_check.gd +94 -41
- package/build/godot/addons/gdharness_editor/plugin.gd +2 -3
- package/build/godot/addons/gdharness_editor/property_paths.gd +22 -12
- package/build/godot/addons/gdharness_editor/property_values.gd +143 -78
- package/build/godot/addons/gdharness_editor/reading.gd +37 -12
- package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
- package/build/godot/addons/gdharness_editor/scene_file.gd +136 -79
- package/build/godot/addons/gdharness_editor/scene_uids.gd +12 -6
- package/build/godot/addons/gdharness_editor/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_editor/setting_reads.gd +7 -6
- package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -6
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +41 -28
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +84 -50
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +34 -30
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +24 -22
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +49 -29
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +26 -21
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +40 -40
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +181 -107
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +75 -43
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +35 -20
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +28 -13
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +66 -0
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +171 -62
- package/build/godot/addons/gdharness_editor/tools/script_paths.gd +20 -8
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +11 -8
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +47 -34
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +77 -50
- package/build/godot/addons/gdharness_editor/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_runtime/call_errors.gd +1 -2
- package/build/godot/addons/gdharness_runtime/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_runtime/error_report.gd +1 -2
- package/build/godot/addons/gdharness_runtime/reading.gd +37 -12
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +153 -148
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +64 -42
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +184 -122
- package/build/godot/addons/gdharness_runtime/runtime_frames.gd +2 -2
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +24 -20
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +134 -92
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +29 -13
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +0 -1
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +38 -20
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +17 -13
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +84 -46
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +117 -79
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +14 -17
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +23 -16
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +117 -66
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd +211 -0
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +44 -27
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +108 -73
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +111 -84
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +10 -7
- package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_tests/bench.gd +4 -5
- package/build/godot/addons/gdharness_tests/event.gd +10 -5
- package/build/godot/addons/gdharness_tests/share.gd +38 -18
- package/build/godot/addons/gdharness_tests/suites.gd +3 -3
- package/build/godot/addons/gdharness_tests/wait.gd +3 -2
- package/build/godot/loader/loader.gd +9 -9
- package/build/godot/operations/animation_reads.gd +84 -35
- package/build/godot/operations/audio_buses.gd +48 -19
- package/build/godot/operations/class_cache.gd +16 -11
- package/build/godot/operations/classdb_queries.gd +89 -44
- package/build/godot/operations/declared_values.gd +68 -32
- package/build/godot/operations/dependencies.gd +139 -93
- package/build/godot/operations/editor_tools.gd +9 -10
- package/build/godot/operations/export_presets.gd +100 -54
- package/build/godot/operations/file_reads.gd +114 -39
- package/build/godot/operations/file_walk.gd +12 -5
- package/build/godot/operations/gdscript_analysis.gd +105 -66
- package/build/godot/operations/gdscript_authoring.gd +138 -39
- package/build/godot/operations/godot_operations.gd +17 -11
- package/build/godot/operations/import_pipeline.gd +110 -59
- package/build/godot/operations/input_actions.gd +63 -39
- package/build/godot/operations/patterns.gd +3 -1
- package/build/godot/operations/plugins.gd +36 -16
- package/build/godot/operations/project_config.gd +144 -27
- package/build/godot/operations/project_diagnostics.gd +24 -11
- package/build/godot/operations/project_file.gd +16 -16
- package/build/godot/operations/project_validation.gd +71 -44
- package/build/godot/operations/property_paths.gd +22 -12
- package/build/godot/operations/property_values.gd +143 -78
- package/build/godot/operations/reading.gd +37 -12
- package/build/godot/operations/rename_checks.gd +2 -2
- package/build/godot/operations/resource_files.gd +11 -9
- package/build/godot/operations/same_values.gd +4 -4
- package/build/godot/operations/scene_connections.gd +27 -13
- package/build/godot/operations/script_check.gd +52 -49
- package/build/godot/operations/serialisation.gd +75 -51
- package/build/godot/operations/setting_reads.gd +7 -6
- package/build/godot/operations/value_shapes.gd +5 -6
- package/build/godot/tests/listed.gd +321 -268
- package/package.json +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
@tool
|
|
2
2
|
extends RefCounted
|
|
3
|
-
|
|
4
3
|
## 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
4
|
## out of one it put it in; a group the scene that makes the node puts it in, the one it is
|
|
6
5
|
## instanced from or one this scene inherits, can be changed only there, as the editor's group dock
|
|
@@ -15,20 +14,20 @@ static func read(scene: GdhSceneFile, node: Node) -> Dictionary:
|
|
|
15
14
|
var all: Array = _sorted(node.get_groups())
|
|
16
15
|
if all.is_empty():
|
|
17
16
|
return {}
|
|
18
|
-
var answer: Dictionary = {"groups": all}
|
|
17
|
+
var answer: Dictionary = { "groups": all }
|
|
19
18
|
var here: Array = _sorted(scene.groups_at(scene.path_of(node)))
|
|
20
19
|
if here.size() != all.size():
|
|
21
20
|
answer["groupsHere"] = here
|
|
22
21
|
return answer
|
|
23
22
|
|
|
24
23
|
|
|
25
|
-
## The groups of [param node], just added to this scene and put in [param adding] there, in the
|
|
26
|
-
## [method read] answers: this scene's file does not hold the node until it is saved.
|
|
24
|
+
## The groups of [param node], just added to this scene and put in [param adding] there, in the
|
|
25
|
+
## shape [method read] answers: this scene's file does not hold the node until it is saved.
|
|
27
26
|
static func read_added(node: Node, adding: Array) -> Dictionary:
|
|
28
27
|
var all: Array = _sorted(node.get_groups())
|
|
29
28
|
if all.is_empty():
|
|
30
29
|
return {}
|
|
31
|
-
var answer: Dictionary = {"groups": all}
|
|
30
|
+
var answer: Dictionary = { "groups": all }
|
|
32
31
|
var here: Array = []
|
|
33
32
|
for group: String in _sorted(adding):
|
|
34
33
|
if not here.has(group):
|
|
@@ -65,19 +64,27 @@ static func change(scene: GdhSceneFile, node: Node, adding: Array, removing: Arr
|
|
|
65
64
|
var held: Array = _sorted(node.get_groups())
|
|
66
65
|
return (
|
|
67
66
|
"%s is in no group %s; %s."
|
|
68
|
-
% [
|
|
67
|
+
% [
|
|
68
|
+
node.name,
|
|
69
|
+
name,
|
|
70
|
+
"it is in " + ", ".join(held) if not held.is_empty() else "it is in none",
|
|
71
|
+
]
|
|
69
72
|
)
|
|
70
73
|
if not here.has(name):
|
|
71
74
|
var from: String = scene.values_from(node, at)
|
|
72
75
|
if from.is_empty():
|
|
73
76
|
return (
|
|
74
|
-
|
|
77
|
+
(
|
|
78
|
+
"%s is in %s, which no scene file puts it in: something puts it there as "
|
|
79
|
+
+ "it is made, such as its script."
|
|
80
|
+
)
|
|
75
81
|
% [node.name, name]
|
|
76
82
|
)
|
|
77
83
|
return (
|
|
78
84
|
(
|
|
79
|
-
"%s is in %s because %s puts it there, and no scene using that one can take it
|
|
80
|
-
+ "remove the group in %s, which takes it out in every scene using that
|
|
85
|
+
"%s is in %s because %s puts it there, and no scene using that one can take it "
|
|
86
|
+
+ "out: remove the group in %s, which takes it out in every scene using that "
|
|
87
|
+
+ "one."
|
|
81
88
|
)
|
|
82
89
|
% [node.name, name, from, from]
|
|
83
90
|
)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
@tool
|
|
2
2
|
extends RefCounted
|
|
3
|
-
|
|
4
|
-
##
|
|
5
|
-
## or by instancing it with its children editable.
|
|
3
|
+
## The two ways a scene keeps its own values for nodes another scene makes: by inheriting that
|
|
4
|
+
## scene, or by instancing it with its children editable.
|
|
6
5
|
|
|
7
6
|
const GdhFileReads = preload("../file_reads.gd")
|
|
8
7
|
const GdhPaths = preload("scene_paths.gd")
|
|
@@ -18,11 +17,11 @@ static func taken(scene_path: String) -> Dictionary:
|
|
|
18
17
|
"error":
|
|
19
18
|
(
|
|
20
19
|
(
|
|
21
|
-
"%s already exists, and scene_create op=create would replace everything in it.
|
|
22
|
-
+ "with scene_node, or create the scene under another path."
|
|
20
|
+
"%s already exists, and scene_create op=create would replace everything in it. "
|
|
21
|
+
+ "Edit it with scene_node, or create the scene under another path."
|
|
23
22
|
)
|
|
24
23
|
% scene_path
|
|
25
|
-
)
|
|
24
|
+
),
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
|
|
@@ -32,12 +31,15 @@ static func inherited(
|
|
|
32
31
|
args: Dictionary, scene_path: String, base_path: String, script_path: String
|
|
33
32
|
) -> Dictionary:
|
|
34
33
|
if scene_path.is_empty():
|
|
35
|
-
return {"ok": false, "error": "Missing scenePath"}
|
|
34
|
+
return { "ok": false, "error": "Missing scenePath" }
|
|
36
35
|
if args.has("rootNodeType"):
|
|
37
36
|
return {
|
|
38
37
|
"ok": false,
|
|
39
38
|
"error":
|
|
40
|
-
|
|
39
|
+
(
|
|
40
|
+
"inherits takes the root's class from %s, so rootNodeType is not given with it."
|
|
41
|
+
% base_path
|
|
42
|
+
),
|
|
41
43
|
}
|
|
42
44
|
if FileAccess.file_exists(scene_path):
|
|
43
45
|
return taken(scene_path)
|
|
@@ -45,10 +47,10 @@ static func inherited(
|
|
|
45
47
|
if not script_path.is_empty():
|
|
46
48
|
script = load(script_path) as Script
|
|
47
49
|
if script == null:
|
|
48
|
-
return {"ok": false, "error": "No script loads from " + script_path}
|
|
50
|
+
return { "ok": false, "error": "No script loads from " + script_path }
|
|
49
51
|
var uncompiled: String = GdhSceneFile.uncompiled(script, script_path)
|
|
50
52
|
if not uncompiled.is_empty():
|
|
51
|
-
return {"ok": false, "error": uncompiled}
|
|
53
|
+
return { "ok": false, "error": uncompiled }
|
|
52
54
|
return inherit(scene_path, base_path, str(args.get("rootNodeName", "")), script)
|
|
53
55
|
|
|
54
56
|
|
|
@@ -57,17 +59,22 @@ static func inherited(
|
|
|
57
59
|
## given. Answers what scene_create answers, or a refusal.
|
|
58
60
|
##
|
|
59
61
|
## Written as the text the editor's New Inherited Scene saves rather than packed from an instance of
|
|
60
|
-
## the base: a headless engine making that instance runs every script in the base, and the pack
|
|
61
|
-
## keep what they set as this scene's own values.
|
|
62
|
-
static func inherit(
|
|
62
|
+
## the base: a headless engine making that instance runs every script in the base, and the pack
|
|
63
|
+
## would keep what they set as this scene's own values.
|
|
64
|
+
static func inherit(
|
|
65
|
+
scene_path: String, base_path: String, root_name: String, script: Script
|
|
66
|
+
) -> Dictionary:
|
|
63
67
|
var base: Dictionary = GdhFileReads.scene_node(base_path, ".")
|
|
64
68
|
if not base.get("ok", false):
|
|
65
|
-
return {
|
|
69
|
+
return {
|
|
70
|
+
"ok": false,
|
|
71
|
+
"error": "inherits: " + str(base.get("error", "%s holds no nodes" % base_path)),
|
|
72
|
+
}
|
|
66
73
|
var root_type: String = str(base["type"])
|
|
67
74
|
var name: String = str(base["name"]) if root_name.is_empty() else root_name
|
|
68
75
|
var unfit: String = GdhSceneFile.unfit_name(name)
|
|
69
76
|
if not unfit.is_empty():
|
|
70
|
-
return {"ok": false, "error": unfit}
|
|
77
|
+
return { "ok": false, "error": unfit }
|
|
71
78
|
if script != null:
|
|
72
79
|
var extended: String = str(script.get_instance_base_type())
|
|
73
80
|
if not ClassDB.is_parent_class(root_type, extended):
|
|
@@ -77,11 +84,11 @@ static func inherit(scene_path: String, base_path: String, root_name: String, sc
|
|
|
77
84
|
(
|
|
78
85
|
"%s extends %s, so it cannot be on the %s root %s inherits."
|
|
79
86
|
% [script.resource_path, extended, root_type, base_path]
|
|
80
|
-
)
|
|
87
|
+
),
|
|
81
88
|
}
|
|
82
89
|
var folder: String = GdhSceneFile.make_folder_for(scene_path)
|
|
83
90
|
if not folder.is_empty():
|
|
84
|
-
return {"ok": false, "error": folder}
|
|
91
|
+
return { "ok": false, "error": folder }
|
|
85
92
|
|
|
86
93
|
var lines: PackedStringArray = [
|
|
87
94
|
'[gd_scene format=3 uid="%s"]' % ResourceUID.id_to_text(ResourceUID.create_id()),
|
|
@@ -105,18 +112,24 @@ static func inherit(scene_path: String, base_path: String, root_name: String, sc
|
|
|
105
112
|
if file == null or not file.store_string("\n".join(lines) + "\n"):
|
|
106
113
|
return {
|
|
107
114
|
"ok": false,
|
|
108
|
-
"error":
|
|
115
|
+
"error":
|
|
116
|
+
"Could not write %s: %s" % [scene_path, error_string(FileAccess.get_open_error())],
|
|
109
117
|
}
|
|
110
118
|
file.close()
|
|
111
119
|
|
|
112
|
-
var loaded: PackedScene = ResourceLoader.load(
|
|
120
|
+
var loaded: PackedScene = ResourceLoader.load(
|
|
121
|
+
scene_path, "PackedScene", ResourceLoader.CACHE_MODE_IGNORE
|
|
122
|
+
)
|
|
113
123
|
var instanced: PackedScene = loaded.get_state().get_node_instance(0) if loaded != null else null
|
|
114
124
|
if instanced == null or instanced.resource_path != base_path:
|
|
115
125
|
var _removed: Error = DirAccess.remove_absolute(scene_path)
|
|
116
126
|
return {
|
|
117
127
|
"ok": false,
|
|
118
128
|
"error":
|
|
119
|
-
|
|
129
|
+
(
|
|
130
|
+
"%s did not load back as a scene inheriting %s, so it was removed."
|
|
131
|
+
% [scene_path, base_path]
|
|
132
|
+
),
|
|
120
133
|
}
|
|
121
134
|
GdhSceneFile.refresh(scene_path)
|
|
122
135
|
var answer: Dictionary = {
|
|
@@ -124,17 +137,23 @@ static func inherit(scene_path: String, base_path: String, root_name: String, sc
|
|
|
124
137
|
"scenePath": scene_path,
|
|
125
138
|
"inherits": base_path,
|
|
126
139
|
"rootNodeType": root_type,
|
|
127
|
-
"rootNodeName": name
|
|
140
|
+
"rootNodeName": name,
|
|
128
141
|
}
|
|
129
|
-
|
|
130
|
-
|
|
142
|
+
var properties: Dictionary = base["properties"]
|
|
143
|
+
var carried: Variant = script if script != null else properties.get("script")
|
|
144
|
+
if carried is Script:
|
|
145
|
+
var carried_script: Script = carried
|
|
146
|
+
answer["script"] = carried_script.resource_path
|
|
147
|
+
if not str(carried_script.get_global_name()).is_empty():
|
|
148
|
+
answer["scriptClass"] = str(carried_script.get_global_name())
|
|
131
149
|
return answer
|
|
132
150
|
|
|
133
151
|
|
|
134
152
|
## Makes the children of [param node], an instance in [param scene], editable or not, answering why
|
|
135
153
|
## it cannot or "". With them editable the scene keeps its own values for them, as the editor's
|
|
136
154
|
## Editable Children does; made not editable again, the values it kept for them are dropped. The
|
|
137
|
-
## caller has had [method GdhSceneFile.foreign] refuse a node inside an instance that is not
|
|
155
|
+
## caller has had [method GdhSceneFile.foreign] refuse a node inside an instance that is not
|
|
156
|
+
## editable.
|
|
138
157
|
static func set_editable(scene: GdhSceneFile, node: Node, editable: bool) -> String:
|
|
139
158
|
if node.owner == null or node.scene_file_path.is_empty():
|
|
140
159
|
return (
|
|
@@ -156,7 +175,9 @@ static func set_editable(scene: GdhSceneFile, node: Node, editable: bool) -> Str
|
|
|
156
175
|
## for a node deleted, they are only named. Answers followed, the scenes rewritten, and
|
|
157
176
|
## notFollowed, each one left as it was with why, each only when it names any, with what
|
|
158
177
|
## [param carried] gives.
|
|
159
|
-
static func follow(
|
|
178
|
+
static func follow(
|
|
179
|
+
changed: String, old_path: String, new_path: String, carried: GdhPaths
|
|
180
|
+
) -> Dictionary:
|
|
160
181
|
var texts: Dictionary = {}
|
|
161
182
|
_read_scenes("res://", texts)
|
|
162
183
|
var kin: Array[String] = [changed]
|
|
@@ -182,7 +203,7 @@ static func follow(changed: String, old_path: String, new_path: String, carried:
|
|
|
182
203
|
moves[old_path] = new_path
|
|
183
204
|
var read: Dictionary = GdhFileReads.scene_tree(scene_path, -1, false, false)
|
|
184
205
|
if not read.get("ok", false):
|
|
185
|
-
not_followed.append({"scenePath": scene_path, "reason": str(read.get("error", ""))})
|
|
206
|
+
not_followed.append({ "scenePath": scene_path, "reason": str(read.get("error", "")) })
|
|
186
207
|
continue
|
|
187
208
|
var tree: Dictionary = read["tree"]
|
|
188
209
|
_instances_of(tree, changed, old_path, new_path, moves)
|
|
@@ -193,28 +214,29 @@ static func follow(changed: String, old_path: String, new_path: String, carried:
|
|
|
193
214
|
rewritten = _rewritten(text, moves, old_path.get_base_dir() != new_path.get_base_dir())
|
|
194
215
|
if rewritten != text:
|
|
195
216
|
if not unsaved.is_empty():
|
|
196
|
-
not_followed.append({"scenePath": scene_path, "reason": unsaved})
|
|
217
|
+
not_followed.append({ "scenePath": scene_path, "reason": unsaved })
|
|
197
218
|
continue
|
|
198
219
|
var file: FileAccess = FileAccess.open(scene_path, FileAccess.WRITE)
|
|
199
220
|
if file == null or not file.store_string(rewritten):
|
|
200
221
|
not_followed.append(
|
|
201
|
-
{"scenePath": scene_path, "reason": error_string(FileAccess.get_open_error())}
|
|
222
|
+
{ "scenePath": scene_path, "reason": error_string(FileAccess.get_open_error()) }
|
|
202
223
|
)
|
|
203
224
|
continue
|
|
204
225
|
file.close()
|
|
205
226
|
GdhSceneFile.refresh(scene_path)
|
|
206
227
|
followed.append(scene_path)
|
|
207
228
|
var holds_paths: bool = text.contains("NodePath(")
|
|
208
|
-
# A script is only read, for the lines naming the node, so it does not need the file
|
|
229
|
+
# A script is only read, for the lines naming the node, so it does not need the file
|
|
230
|
+
# written.
|
|
209
231
|
if moves.is_empty() or not (holds_paths or text.contains('Script"')):
|
|
210
232
|
continue
|
|
211
233
|
if holds_paths and not unsaved.is_empty() and not new_path.is_empty():
|
|
212
|
-
not_followed.append({"scenePath": scene_path, "reason": unsaved})
|
|
234
|
+
not_followed.append({ "scenePath": scene_path, "reason": unsaved })
|
|
213
235
|
continue
|
|
214
236
|
var before: int = carried.retargeted.size()
|
|
215
237
|
var unwritten: String = carried.carry_into(scene_path, moves)
|
|
216
238
|
if not unwritten.is_empty() and not followed.has(scene_path):
|
|
217
|
-
not_followed.append({"scenePath": scene_path, "reason": unwritten})
|
|
239
|
+
not_followed.append({ "scenePath": scene_path, "reason": unwritten })
|
|
218
240
|
elif carried.retargeted.size() > before and not followed.has(scene_path):
|
|
219
241
|
followed.append(scene_path)
|
|
220
242
|
var answer: Dictionary = carried.answer()
|
|
@@ -233,7 +255,9 @@ static func _read_scenes(directory: String, texts: Dictionary) -> void:
|
|
|
233
255
|
_read_scenes(directory.path_join(name), texts)
|
|
234
256
|
for name: String in DirAccess.get_files_at(directory):
|
|
235
257
|
if name.ends_with(".tscn"):
|
|
236
|
-
texts[directory.path_join(name)] = FileAccess.get_file_as_string(
|
|
258
|
+
texts[directory.path_join(name)] = FileAccess.get_file_as_string(
|
|
259
|
+
directory.path_join(name)
|
|
260
|
+
)
|
|
237
261
|
|
|
238
262
|
|
|
239
263
|
## Whether the scene at [param scene_path] inherits [param changed], through any number of scenes.
|
|
@@ -248,19 +272,22 @@ static func _inherits(scene_path: String, changed: String, texts: Dictionary) ->
|
|
|
248
272
|
return false
|
|
249
273
|
|
|
250
274
|
|
|
251
|
-
## The scene the root of [param text] inherits, or "": a root entry naming an instance and no
|
|
275
|
+
## The scene the root of [param text] inherits, or "": a root entry naming an instance and no
|
|
276
|
+
## parent.
|
|
252
277
|
static func _base_of(text: String) -> String:
|
|
253
278
|
for line: String in text.split("\n"):
|
|
254
279
|
if not line.begins_with("[node "):
|
|
255
280
|
continue
|
|
256
|
-
var instanced: RegExMatch =
|
|
257
|
-
line
|
|
281
|
+
var instanced: RegExMatch = (
|
|
282
|
+
RegEx.create_from_string('instance=ExtResource\\("([^"]+)"\\)').search(line)
|
|
258
283
|
)
|
|
259
284
|
if instanced == null or line.contains(" parent="):
|
|
260
285
|
return ""
|
|
261
286
|
var named: RegExMatch = (
|
|
262
287
|
RegEx
|
|
263
|
-
. create_from_string(
|
|
288
|
+
. create_from_string(
|
|
289
|
+
'\\[ext_resource [^\\n]*path="([^"]+)" id="%s"\\]' % instanced.get_string(1)
|
|
290
|
+
)
|
|
264
291
|
. search(text)
|
|
265
292
|
)
|
|
266
293
|
return named.get_string(1) if named != null else ""
|
|
@@ -284,10 +311,11 @@ static func _instances_of(
|
|
|
284
311
|
|
|
285
312
|
## [param text] with every path [param moves] names, and every path below one, moved.
|
|
286
313
|
##
|
|
287
|
-
## Beside a path the saver writes the unique_ids of the nodes along it, which the loader follows
|
|
288
|
-
## the path has vanished. A rename leaves those ids as they were; with [param reparented] they
|
|
289
|
-
## through the old parent, which still exists, so a recovery would find the wrong node. They are
|
|
290
|
-
## out of each path moved, as a hand-written scene leaves them, and the next save writes them
|
|
314
|
+
## Beside a path the saver writes the unique_ids of the nodes along it, which the loader follows
|
|
315
|
+
## when the path has vanished. A rename leaves those ids as they were; with [param reparented] they
|
|
316
|
+
## run through the old parent, which still exists, so a recovery would find the wrong node. They are
|
|
317
|
+
## left out of each path moved, as a hand-written scene leaves them, and the next save writes them
|
|
318
|
+
## anew.
|
|
291
319
|
static func _rewritten(text: String, moves: Dictionary, reparented: bool) -> String:
|
|
292
320
|
if moves.is_empty():
|
|
293
321
|
return text
|
|
@@ -309,7 +337,11 @@ static func _rewritten(text: String, moves: Dictionary, reparented: bool) -> Str
|
|
|
309
337
|
var placed: String = _attribute_set(
|
|
310
338
|
line, "parent", "." if moved_parent.is_empty() else moved_parent
|
|
311
339
|
)
|
|
312
|
-
line =
|
|
340
|
+
line = (
|
|
341
|
+
_without_ids(placed, "parent_id_path")
|
|
342
|
+
if placed != line and reparented
|
|
343
|
+
else placed
|
|
344
|
+
)
|
|
313
345
|
else:
|
|
314
346
|
line = _attribute_moved(line, "parent", "parent_id_path", moves, reparented)
|
|
315
347
|
lines[at] = line
|
|
@@ -333,8 +365,8 @@ static func _attribute_set(line: String, attribute: String, value: String) -> St
|
|
|
333
365
|
return line.substr(0, found.get_start(1)) + value + line.substr(found.get_end(1))
|
|
334
366
|
|
|
335
367
|
|
|
336
|
-
## [param line] with the path under [param attribute] moved by [param moves], and, when that moved
|
|
337
|
-
## and the move was a reparent, without the ids under [param ids] that went along the old path.
|
|
368
|
+
## [param line] with the path under [param attribute] moved by [param moves], and, when that moved
|
|
369
|
+
## it and the move was a reparent, without the ids under [param ids] that went along the old path.
|
|
338
370
|
static func _attribute_moved(
|
|
339
371
|
line: String, attribute: String, ids: String, moves: Dictionary, reparented: bool
|
|
340
372
|
) -> String:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
@tool
|
|
2
2
|
extends RefCounted
|
|
3
|
-
|
|
4
3
|
## The node paths scenes hold, carried after a node moves as the editor's own rename carries them:
|
|
5
4
|
## the NodePath values on their nodes, inside arrays, dictionaries and the resources built into the
|
|
6
5
|
## scene too, and the tracks of the animations their players play. After a delete they are named
|
|
@@ -9,7 +8,11 @@ extends RefCounted
|
|
|
9
8
|
## A path is worked out from where its holder was and where it pointed before the move, so it is
|
|
10
9
|
## followed in a scene loaded after the move as well, where the old path no longer resolves.
|
|
11
10
|
|
|
12
|
-
enum {
|
|
11
|
+
enum {
|
|
12
|
+
UNCHANGED,
|
|
13
|
+
REWRITTEN,
|
|
14
|
+
DELETED,
|
|
15
|
+
}
|
|
13
16
|
|
|
14
17
|
const GdhHeadlessCheck = preload("../headless_check.gd")
|
|
15
18
|
const GdhRead = preload("../reading.gd")
|
|
@@ -87,7 +90,7 @@ func carry(scene: GdhSceneFile, moves: Dictionary) -> int:
|
|
|
87
90
|
var name: String = str(property["name"])
|
|
88
91
|
if not _may_hold_paths(property):
|
|
89
92
|
continue
|
|
90
|
-
var entry: Dictionary = {"scenePath": scene.path, "nodePath": now, "property": name}
|
|
93
|
+
var entry: Dictionary = { "scenePath": scene.path, "nodePath": now, "property": name }
|
|
91
94
|
var elsewhere: String = "" if set_here.has(name) else from
|
|
92
95
|
var carried: Array = _carried(node.get(name), was, now, entry, elsewhere)
|
|
93
96
|
if carried[0]:
|
|
@@ -241,10 +244,12 @@ static func _may_hold_paths(property: Dictionary) -> bool:
|
|
|
241
244
|
## it carried, as [whether any was, the value]. A resource built into the scene is changed in place.
|
|
242
245
|
## [param entry] says where the value is held, for the record of each path. A path in a resource may
|
|
243
246
|
## be relative to something other than its holder, as the editor allows; it is followed only when it
|
|
244
|
-
## names a node from where its holder was, as one relative to the holder does. A value
|
|
245
|
-
## elsewhere], a file other than the scene's, gives is named only when it names nothing since
|
|
246
|
-
## move, which its own file's pass could not see.
|
|
247
|
-
func _carried(
|
|
247
|
+
## names a node from where its holder was, as one relative to the holder does. A value
|
|
248
|
+
## [param elsewhere], a file other than the scene's, gives is named only when it names nothing since
|
|
249
|
+
## the move, which its own file's pass could not see.
|
|
250
|
+
func _carried(
|
|
251
|
+
value: Variant, was: String, now: String, entry: Dictionary, elsewhere: String
|
|
252
|
+
) -> Array:
|
|
248
253
|
match typeof(value):
|
|
249
254
|
TYPE_NODE_PATH:
|
|
250
255
|
var path: NodePath = value
|
|
@@ -256,7 +261,10 @@ func _carried(value: Variant, was: String, now: String, entry: Dictionary, elsew
|
|
|
256
261
|
not_retargeted.append(
|
|
257
262
|
_left(
|
|
258
263
|
record,
|
|
259
|
-
|
|
264
|
+
(
|
|
265
|
+
"The value is set in %s, and an edit here would not be saved there."
|
|
266
|
+
% elsewhere
|
|
267
|
+
)
|
|
260
268
|
)
|
|
261
269
|
)
|
|
262
270
|
elif after[0] == DELETED:
|
|
@@ -350,7 +358,9 @@ func _after(path: NodePath, was: String, now: String) -> Array:
|
|
|
350
358
|
## Whether [param path], held by the node at [param now], names no node.
|
|
351
359
|
func _names_nothing(path: NodePath, now: String) -> bool:
|
|
352
360
|
var holder: Node = _scene.root.get_node_or_null(now)
|
|
353
|
-
return
|
|
361
|
+
return (
|
|
362
|
+
holder != null and holder.get_node_or_null(NodePath(path.get_concatenated_names())) == null
|
|
363
|
+
)
|
|
354
364
|
|
|
355
365
|
|
|
356
366
|
## The path to [param target] through the unique node that was at [param start], written through its
|
|
@@ -424,8 +434,9 @@ func _tracks(mixer: AnimationMixer, now: String) -> void:
|
|
|
424
434
|
var animation: Animation = library.get_animation(animation_name)
|
|
425
435
|
var home: Dictionary = _home_of(animation, library)
|
|
426
436
|
var why: String = home["why"]
|
|
427
|
-
# Carried once, from the first scene playing it. One kept in a file of its own that this
|
|
428
|
-
# rewrote is held in _homes, so a later scene loading that file is given this same
|
|
437
|
+
# Carried once, from the first scene playing it. One kept in a file of its own that this
|
|
438
|
+
# call rewrote is held in _homes, so a later scene loading that file is given this same
|
|
439
|
+
# copy.
|
|
429
440
|
if _seen.has(animation):
|
|
430
441
|
continue
|
|
431
442
|
_seen[animation] = true
|
|
@@ -466,13 +477,14 @@ func _tracks(mixer: AnimationMixer, now: String) -> void:
|
|
|
466
477
|
|
|
467
478
|
|
|
468
479
|
## Where an edit to [param animation], played from [param library], is saved, as {file, home, why,
|
|
469
|
-
## theirs}: file "" with the scene, or the file of its own it or its library is kept in, with home
|
|
470
|
-
## resource saved there; why, when nothing written here would keep the edit; theirs, when that
|
|
471
|
-
## because it is kept in another scene or an imported file, whose tracks are written in its
|
|
480
|
+
## theirs}: file "" with the scene, or the file of its own it or its library is kept in, with home
|
|
481
|
+
## the resource saved there; why, when nothing written here would keep the edit; theirs, when that
|
|
482
|
+
## is because it is kept in another scene or an imported file, whose tracks are written in its
|
|
483
|
+
## terms.
|
|
472
484
|
func _home_of(animation: Animation, library: AnimationLibrary) -> Dictionary:
|
|
473
485
|
var file: String = animation.resource_path.get_slice("::", 0)
|
|
474
486
|
if file.is_empty() or file == _scene.path:
|
|
475
|
-
return {"file": "", "why": ""}
|
|
487
|
+
return { "file": "", "why": "" }
|
|
476
488
|
var home: Resource = animation
|
|
477
489
|
if animation.resource_path.contains("::"):
|
|
478
490
|
home = library if library.resource_path == file else null
|
|
@@ -483,18 +495,21 @@ func _home_of(animation: Animation, library: AnimationLibrary) -> Dictionary:
|
|
|
483
495
|
}
|
|
484
496
|
if not Engine.is_editor_hint():
|
|
485
497
|
var script: Script = home.get_script()
|
|
486
|
-
if (
|
|
498
|
+
if (
|
|
499
|
+
(script != null and not script.is_tool())
|
|
500
|
+
or GdhHeadlessCheck.holds_scripted_embedded(home, {})
|
|
501
|
+
):
|
|
487
502
|
return {
|
|
488
503
|
"why":
|
|
489
504
|
(
|
|
490
505
|
(
|
|
491
506
|
(
|
|
492
|
-
"%s holds a resource with a script that is not a tool, which a
|
|
493
|
-
+ "it loads the file. "
|
|
507
|
+
"%s holds a resource with a script that is not a tool, which a "
|
|
508
|
+
+ "headless engine runs as it loads the file. "
|
|
494
509
|
)
|
|
495
510
|
% file
|
|
496
511
|
)
|
|
497
512
|
+ GdhHeadlessCheck.REMEDY
|
|
498
|
-
)
|
|
513
|
+
),
|
|
499
514
|
}
|
|
500
|
-
return {"file": file, "home": home, "why": ""}
|
|
515
|
+
return { "file": file, "home": home, "why": "" }
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
@tool
|
|
2
2
|
extends RefCounted
|
|
3
|
-
|
|
4
3
|
## What a new node in a scene is made or copied from, what the scene owns of it, and where among its
|
|
5
4
|
## siblings a node may go.
|
|
6
5
|
|
|
@@ -15,7 +14,8 @@ static func what_to_add(node_type: String, instance_path: String, scene_path: St
|
|
|
15
14
|
if node_type.is_empty() == instance_path.is_empty():
|
|
16
15
|
return [
|
|
17
16
|
null,
|
|
18
|
-
"add takes nodeType for a node of a class, or instancePath for a scene, and one of
|
|
17
|
+
"add takes nodeType for a node of a class, or instancePath for a scene, and one of "
|
|
18
|
+
+ "them only.",
|
|
19
19
|
]
|
|
20
20
|
if not instance_path.is_empty():
|
|
21
21
|
# Asked before the cycle walk, which reads the file's dependencies and so loaded a file that
|
|
@@ -35,9 +35,12 @@ static func what_to_add(node_type: String, instance_path: String, scene_path: St
|
|
|
35
35
|
return [
|
|
36
36
|
null,
|
|
37
37
|
(
|
|
38
|
-
|
|
38
|
+
(
|
|
39
|
+
"nodeType must be a node class, and %s is a scene: pass it as instancePath to "
|
|
40
|
+
+ "instance it."
|
|
41
|
+
)
|
|
39
42
|
% node_type
|
|
40
|
-
)
|
|
43
|
+
),
|
|
41
44
|
]
|
|
42
45
|
if not ClassDB.class_exists(node_type) or not ClassDB.is_parent_class(node_type, "Node"):
|
|
43
46
|
return [null, not_a_node_class("nodeType", node_type)]
|
|
@@ -73,10 +76,12 @@ static func made(packed: PackedScene, node_type: String) -> Node:
|
|
|
73
76
|
static func copied(source: Node, root: Node, copies: Dictionary) -> Node:
|
|
74
77
|
var copy: Node = null
|
|
75
78
|
if not source.scene_file_path.is_empty():
|
|
76
|
-
# An instanced scene made again from its file, so the copy is an instance too.
|
|
77
|
-
# makes a plain node of the instance's class, and the pack saved it with
|
|
78
|
-
# instance's root frozen into this scene.
|
|
79
|
-
var packed: PackedScene =
|
|
79
|
+
# An instanced scene made again from its file, so the copy is an instance too.
|
|
80
|
+
# Node.duplicate makes a plain node of the instance's class, and the pack saved it with
|
|
81
|
+
# every property of the instance's root frozen into this scene.
|
|
82
|
+
var packed: PackedScene = (
|
|
83
|
+
ResourceLoader.load(source.scene_file_path, "PackedScene") as PackedScene
|
|
84
|
+
)
|
|
80
85
|
if packed == null:
|
|
81
86
|
return null
|
|
82
87
|
copy = packed.instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
|
|
@@ -123,7 +128,11 @@ static func reconnect(copies: Dictionary, top: Node) -> void:
|
|
|
123
128
|
if not flags & Object.CONNECT_PERSIST or target == null:
|
|
124
129
|
continue
|
|
125
130
|
var to: Object = copies.get(target, target)
|
|
126
|
-
if
|
|
131
|
+
if (
|
|
132
|
+
to == target
|
|
133
|
+
and target is Node
|
|
134
|
+
and (target == top or top.is_ancestor_of(target as Node))
|
|
135
|
+
):
|
|
127
136
|
continue
|
|
128
137
|
var aimed: Callable = Callable(to, callable.get_method())
|
|
129
138
|
if (
|
|
@@ -131,7 +140,10 @@ static func reconnect(copies: Dictionary, top: Node) -> void:
|
|
|
131
140
|
and copy.connect(signal_name, aimed, flags) != OK
|
|
132
141
|
):
|
|
133
142
|
push_error(
|
|
134
|
-
|
|
143
|
+
(
|
|
144
|
+
"gdharness: could not copy the connection %s of %s"
|
|
145
|
+
% [signal_name, source.name]
|
|
146
|
+
)
|
|
135
147
|
)
|
|
136
148
|
|
|
137
149
|
|
|
@@ -150,7 +162,9 @@ static func own(node: Node, root: Node) -> void:
|
|
|
150
162
|
static func unplaceable(parent: Node, node: Node, asked: Variant) -> String:
|
|
151
163
|
if asked == null:
|
|
152
164
|
return ""
|
|
153
|
-
var others: int =
|
|
165
|
+
var others: int = (
|
|
166
|
+
parent.get_child_count() - (1 if node != null and node.get_parent() == parent else 0)
|
|
167
|
+
)
|
|
154
168
|
var at: int = GdhRead.as_int(asked, -1)
|
|
155
169
|
if at < 0 or at > others:
|
|
156
170
|
return (
|
|
@@ -159,7 +173,7 @@ static func unplaceable(parent: Node, node: Node, asked: Variant) -> String:
|
|
|
159
173
|
str(at) if asked is int or asked is float else JSON.stringify(asked),
|
|
160
174
|
others,
|
|
161
175
|
parent.name,
|
|
162
|
-
others
|
|
176
|
+
others,
|
|
163
177
|
]
|
|
164
178
|
)
|
|
165
179
|
return ""
|
|
@@ -234,7 +248,8 @@ static func _spatial_space(parent: Node) -> Transform3D:
|
|
|
234
248
|
return space
|
|
235
249
|
|
|
236
250
|
|
|
237
|
-
## Where the children of [param parent] are drawn in 2D, as [method _spatial_space] works it out in
|
|
251
|
+
## Where the children of [param parent] are drawn in 2D, as [method _spatial_space] works it out in
|
|
252
|
+
## 3D.
|
|
238
253
|
static func _canvas_space(parent: Node) -> Transform2D:
|
|
239
254
|
var space: Transform2D = Transform2D.IDENTITY
|
|
240
255
|
var at: Node = parent
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends RefCounted
|
|
3
|
+
## Where else in a scene a resource built into it is held, since such a resource is one object
|
|
4
|
+
## wherever the scene uses it.
|
|
5
|
+
|
|
6
|
+
const GdhPropertyPaths = preload("../property_paths.gd")
|
|
7
|
+
const GdhRead = preload("../reading.gd")
|
|
8
|
+
const GdhSceneFile = preload("../scene_file.gd")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Every other place in [param scene] holding a resource that [param key] was written through on
|
|
12
|
+
## [param node], as {node, property}. A copy made by duplicate shares its original's resources, so
|
|
13
|
+
## the write changed each of them too.
|
|
14
|
+
static func holders(scene: GdhSceneFile, node: Node, key: String) -> Array[Dictionary]:
|
|
15
|
+
var found_at: Array[Dictionary] = []
|
|
16
|
+
var through: Array = GdhPropertyPaths.holder_of(node, key, GdhPropertyPaths.home_of(node))[2]
|
|
17
|
+
if through.is_empty():
|
|
18
|
+
return found_at
|
|
19
|
+
var wanted: Dictionary = {}
|
|
20
|
+
for reached: Resource in through:
|
|
21
|
+
wanted[reached.get_instance_id()] = true
|
|
22
|
+
var nodes: Array[Node] = [scene.root]
|
|
23
|
+
nodes.append_array(scene.root.find_children("*", "", true, false))
|
|
24
|
+
for at: Node in nodes:
|
|
25
|
+
for declared: Dictionary in at.get_property_list():
|
|
26
|
+
var property: String = str(declared.get("name", ""))
|
|
27
|
+
if (
|
|
28
|
+
property.is_empty()
|
|
29
|
+
or not (GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE)
|
|
30
|
+
):
|
|
31
|
+
continue
|
|
32
|
+
for found: String in _paths_to(at.get(property), property, wanted, {}):
|
|
33
|
+
if at != node or not key.begins_with(found + "/"):
|
|
34
|
+
found_at.append({ "node": scene.path_of(at), "property": found })
|
|
35
|
+
return found_at
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## The paths below [param path] at which [param value] holds a resource of [param wanted], by
|
|
39
|
+
## instance id, stepping into lists and into resources built into a scene.
|
|
40
|
+
static func _paths_to(
|
|
41
|
+
value: Variant, path: String, wanted: Dictionary, seen: Dictionary
|
|
42
|
+
) -> Array[String]:
|
|
43
|
+
var paths: Array[String] = []
|
|
44
|
+
if value is Array:
|
|
45
|
+
var items: Array = value
|
|
46
|
+
for index: int in items.size():
|
|
47
|
+
paths.append_array(_paths_to(items[index], "%s/%d" % [path, index], wanted, seen))
|
|
48
|
+
return paths
|
|
49
|
+
if not value is Resource:
|
|
50
|
+
return paths
|
|
51
|
+
var held: Resource = value
|
|
52
|
+
var id: int = held.get_instance_id()
|
|
53
|
+
if wanted.has(id):
|
|
54
|
+
paths.append(path)
|
|
55
|
+
var built_in: bool = held.resource_path.is_empty() or held.resource_path.contains("::")
|
|
56
|
+
if seen.has(id) or not built_in:
|
|
57
|
+
return paths
|
|
58
|
+
seen[id] = true
|
|
59
|
+
for declared: Dictionary in held.get_property_list():
|
|
60
|
+
var property: String = str(declared.get("name", ""))
|
|
61
|
+
if (
|
|
62
|
+
not property.is_empty()
|
|
63
|
+
and GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE
|
|
64
|
+
):
|
|
65
|
+
paths.append_array(_paths_to(held.get(property), path + "/" + property, wanted, seen))
|
|
66
|
+
return paths
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://blndo1urj5l2w
|