gdharness 1.3.3 → 1.3.5
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 +532 -456
- package/build/godot/addons/gdharness_editor/animation_reads.gd +3 -2
- package/build/godot/addons/gdharness_editor/declared_values.gd +181 -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 +192 -231
- 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/class_tools.gd +20 -12
- 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/declared_values.gd +293 -0
- package/build/godot/addons/gdharness_runtime/declared_values.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +15 -0
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +4 -0
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +29 -1
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +26 -39
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +18 -0
- 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 +62 -5
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +48 -15
- package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -3
- package/build/godot/operations/animation_reads.gd +3 -2
- package/build/godot/operations/classdb_queries.gd +112 -46
- package/build/godot/operations/declared_values.gd +181 -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 +157 -36
- 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 +192 -231
- 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/build/keeper.js +83 -69
- package/package.json +1 -1
|
@@ -5,11 +5,13 @@ extends Node
|
|
|
5
5
|
|
|
6
6
|
const GdhFileReads = preload("../file_reads.gd")
|
|
7
7
|
const GdhRead = preload("../reading.gd")
|
|
8
|
+
const GdhGroups = preload("scene_groups.gd")
|
|
8
9
|
const GdhKin = preload("scene_kin.gd")
|
|
9
10
|
const GdhPaths = preload("scene_paths.gd")
|
|
10
11
|
const GdhPlacing = preload("scene_placing.gd")
|
|
11
12
|
const GdhPropertyValues = preload("../property_values.gd")
|
|
12
13
|
const GdhResourceTools = preload("resource_tools.gd")
|
|
14
|
+
const GdhSame = preload("../same_values.gd")
|
|
13
15
|
const GdhSceneFile = preload("../scene_file.gd")
|
|
14
16
|
const GdhSerialisation = preload("../serialisation.gd")
|
|
15
17
|
|
|
@@ -22,56 +24,12 @@ func set_editor_plugin(plugin: EditorPlugin) -> void:
|
|
|
22
24
|
_editor_plugin = plugin
|
|
23
25
|
|
|
24
26
|
|
|
25
|
-
func _ensure_res_path(path: String) -> String:
|
|
26
|
-
if not path.begins_with("res://"):
|
|
27
|
-
return "res://" + path
|
|
28
|
-
return path
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
func _to_scene_res_path(project_path: String, scene_path: String) -> String:
|
|
32
|
-
var p: String = scene_path.strip_edges()
|
|
33
|
-
if p.begins_with("res://"):
|
|
34
|
-
return p
|
|
35
|
-
|
|
36
|
-
if project_path.strip_edges() != "":
|
|
37
|
-
var normalized_project: String = project_path.replace("\\", "/")
|
|
38
|
-
var normalized_scene: String = p.replace("\\", "/")
|
|
39
|
-
if normalized_scene.begins_with(normalized_project):
|
|
40
|
-
var rel: String = normalized_scene.substr(normalized_project.length())
|
|
41
|
-
if rel.begins_with("/"):
|
|
42
|
-
rel = rel.substr(1)
|
|
43
|
-
return _ensure_res_path(rel)
|
|
44
|
-
|
|
45
|
-
return _ensure_res_path(p)
|
|
46
|
-
|
|
47
|
-
|
|
48
27
|
## [param scene_path] opened for a tool, as [scene, refusal]: the refusal is {} when it opened.
|
|
49
28
|
func _open(scene_path: String, for_writing: bool) -> Array:
|
|
50
29
|
var scene: GdhSceneFile = GdhSceneFile.new()
|
|
51
30
|
return [scene, scene.open(scene_path, for_writing)]
|
|
52
31
|
|
|
53
32
|
|
|
54
|
-
func _find_node(root: Node, path: String) -> Node:
|
|
55
|
-
if path == "." or path.is_empty():
|
|
56
|
-
return root
|
|
57
|
-
return root.get_node_or_null(path)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
## Why the property or signal [param missing] could not be found on [param node], adding the one
|
|
61
|
-
## cause the name alone does not show: a script that does not compile leaves the editor holding a
|
|
62
|
-
## placeholder with no properties or signals at all, and the refusal named the property instead.
|
|
63
|
-
## Such a script has no engine class beneath it. Compiling a copy of the source to find out failed on
|
|
64
|
-
## every script with a class_name, since the copy hides the global class the original declares.
|
|
65
|
-
func _missing_on(node: Node, missing: String) -> String:
|
|
66
|
-
var script: Script = node.get_script()
|
|
67
|
-
if script == null or not str(script.get_instance_base_type()).is_empty():
|
|
68
|
-
return missing
|
|
69
|
-
return (
|
|
70
|
-
"%s. Its script, %s, does not compile, so none of what it declares is known here."
|
|
71
|
-
% [missing.trim_suffix("."), script.resource_path]
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
|
|
75
33
|
## Whether each of [param names] reads the same off [param saved] as off [param node], answering
|
|
76
34
|
## the first that does not, or "".
|
|
77
35
|
func _kept(scene: GdhSceneFile, saved: Node, node: Node, names: Array) -> String:
|
|
@@ -116,7 +74,7 @@ func _properties_of(node: Node, include_defaults: bool, set_here: Dictionary) ->
|
|
|
116
74
|
if scripted.has(property)
|
|
117
75
|
else ClassDB.class_get_property_default_value(node.get_class(), property)
|
|
118
76
|
)
|
|
119
|
-
if
|
|
77
|
+
if GdhSame.same(value, default):
|
|
120
78
|
continue
|
|
121
79
|
props[property] = _readable(node, value)
|
|
122
80
|
return props
|
|
@@ -145,27 +103,20 @@ func _parse_properties_arg(raw_properties: Variant) -> Dictionary:
|
|
|
145
103
|
return {}
|
|
146
104
|
|
|
147
105
|
|
|
148
|
-
func _collect_nodes_recursive(node: Node, path: String, out_nodes: Array) -> void:
|
|
149
|
-
out_nodes.append({"path": path, "node": node})
|
|
150
|
-
for child: Node in node.get_children():
|
|
151
|
-
var child_path: String = str(child.name) if path == "." else path + "/" + str(child.name)
|
|
152
|
-
_collect_nodes_recursive(child, child_path, out_nodes)
|
|
153
|
-
|
|
154
|
-
|
|
155
106
|
func _inherited(args: Dictionary) -> Dictionary:
|
|
156
107
|
var project_path: String = str(args.get("projectPath", ""))
|
|
157
108
|
var script_path: String = str(args.get("script", ""))
|
|
158
109
|
return GdhKin.inherited(
|
|
159
110
|
args,
|
|
160
111
|
_new_scene_path(args),
|
|
161
|
-
|
|
162
|
-
"" if script_path.is_empty() else
|
|
112
|
+
GdhSceneFile.res_path(project_path, str(args.get("inherits", ""))),
|
|
113
|
+
"" if script_path.is_empty() else GdhSceneFile.res_path(project_path, script_path)
|
|
163
114
|
)
|
|
164
115
|
|
|
165
116
|
|
|
166
117
|
## The res:// path a create in [param args] writes, given .tscn when it has no extension, or "".
|
|
167
118
|
func _new_scene_path(args: Dictionary) -> String:
|
|
168
|
-
var scene_path: String =
|
|
119
|
+
var scene_path: String = GdhSceneFile.res_path(
|
|
169
120
|
str(args.get("projectPath", "")), str(args.get("scenePath", ""))
|
|
170
121
|
)
|
|
171
122
|
if scene_path == "res://":
|
|
@@ -191,7 +142,7 @@ func _create_of_a_class(args: Dictionary) -> Dictionary:
|
|
|
191
142
|
var asked_type: String = root_node_type
|
|
192
143
|
var made: Dictionary = GdhSceneFile.made_as(
|
|
193
144
|
root_node_type,
|
|
194
|
-
"" if script_path.is_empty() else
|
|
145
|
+
"" if script_path.is_empty() else GdhSceneFile.res_path(project_path, script_path),
|
|
195
146
|
"Node",
|
|
196
147
|
"rootNodeType"
|
|
197
148
|
)
|
|
@@ -219,7 +170,7 @@ func _create_of_a_class(args: Dictionary) -> Dictionary:
|
|
|
219
170
|
scene.asked(root, ["script"])
|
|
220
171
|
|
|
221
172
|
if not script_path.is_empty():
|
|
222
|
-
var full_script_path: String =
|
|
173
|
+
var full_script_path: String = GdhSceneFile.res_path(project_path, script_path)
|
|
223
174
|
var script: Script = load(full_script_path) as Script
|
|
224
175
|
if script == null:
|
|
225
176
|
root.free()
|
|
@@ -260,7 +211,7 @@ func _create_of_a_class(args: Dictionary) -> Dictionary:
|
|
|
260
211
|
|
|
261
212
|
func list_scene_nodes(args: Dictionary) -> Dictionary:
|
|
262
213
|
var project_path: String = str(args.get("projectPath", ""))
|
|
263
|
-
var scene_path: String =
|
|
214
|
+
var scene_path: String = GdhSceneFile.res_path(project_path, str(args.get("scenePath", "")))
|
|
264
215
|
var depth: int = GdhRead.as_int(args.get("depth", -1), -1)
|
|
265
216
|
var include_properties: bool = GdhRead.as_bool(args.get("includeProperties", false))
|
|
266
217
|
|
|
@@ -274,12 +225,13 @@ func list_scene_nodes(args: Dictionary) -> Dictionary:
|
|
|
274
225
|
|
|
275
226
|
func add_node(args: Dictionary) -> Dictionary:
|
|
276
227
|
var project_path: String = str(args.get("projectPath", ""))
|
|
277
|
-
var scene_path: String =
|
|
228
|
+
var scene_path: String = GdhSceneFile.res_path(project_path, str(args.get("scenePath", "")))
|
|
278
229
|
var node_type: String = str(args.get("nodeType", ""))
|
|
279
230
|
var instance_path: String = str(args.get("instancePath", ""))
|
|
280
231
|
var node_name: String = str(args.get("nodeName", ""))
|
|
281
232
|
var parent_node_path: String = str(args.get("parentNodePath", "."))
|
|
282
233
|
var properties: Dictionary = _parse_properties_arg(args.get("properties", {}))
|
|
234
|
+
var adding: Array = GdhGroups.asked(args, "addGroups")
|
|
283
235
|
|
|
284
236
|
if node_name.is_empty():
|
|
285
237
|
return {"ok": false, "error": "Missing nodeName"}
|
|
@@ -305,7 +257,7 @@ func add_node(args: Dictionary) -> Dictionary:
|
|
|
305
257
|
return refused
|
|
306
258
|
|
|
307
259
|
var root: Node = scene.root
|
|
308
|
-
var parent: Node =
|
|
260
|
+
var parent: Node = GdhSceneFile.node_at(root, parent_node_path)
|
|
309
261
|
if not parent:
|
|
310
262
|
return scene.refuse(scene.not_found("Parent node not found: " + parent_node_path, parent_node_path))
|
|
311
263
|
var foreign: String = scene.foreign(parent)
|
|
@@ -332,9 +284,11 @@ func add_node(args: Dictionary) -> Dictionary:
|
|
|
332
284
|
if not unsettled.is_empty():
|
|
333
285
|
return scene.refuse("Nothing was written: " + unsettled)
|
|
334
286
|
scene.asked(new_node, properties.keys())
|
|
335
|
-
var
|
|
336
|
-
if
|
|
337
|
-
|
|
287
|
+
var unwritten: String = _properties.write_all(new_node, properties)
|
|
288
|
+
if unwritten.is_empty():
|
|
289
|
+
unwritten = GdhGroups.change(scene, new_node, adding, [])
|
|
290
|
+
if not unwritten.is_empty():
|
|
291
|
+
return scene.refuse(unwritten)
|
|
338
292
|
|
|
339
293
|
var new_path: String = scene.path_of(new_node)
|
|
340
294
|
# Read before the write, which frees the scene it was added to.
|
|
@@ -346,11 +300,16 @@ func add_node(args: Dictionary) -> Dictionary:
|
|
|
346
300
|
"index": new_node.get_index(),
|
|
347
301
|
}
|
|
348
302
|
_properties.note_write(answer, new_node)
|
|
303
|
+
if not adding.is_empty():
|
|
304
|
+
answer.merge(GdhGroups.read(scene, new_node))
|
|
349
305
|
var written: Dictionary = scene.write(
|
|
350
306
|
func(saved: Node) -> String:
|
|
351
|
-
|
|
307
|
+
var unkept: String = _kept(
|
|
352
308
|
scene, saved, new_node, properties.keys() + (["script"] if class_script != null else [])
|
|
353
309
|
)
|
|
310
|
+
if unkept.is_empty() and saved.get_node_or_null(new_path) != null:
|
|
311
|
+
unkept = GdhGroups.unkept(saved.get_node(new_path), adding, [])
|
|
312
|
+
return unkept
|
|
354
313
|
)
|
|
355
314
|
if not written.is_empty():
|
|
356
315
|
return written
|
|
@@ -362,7 +321,7 @@ func add_node(args: Dictionary) -> Dictionary:
|
|
|
362
321
|
|
|
363
322
|
func delete_node(args: Dictionary) -> Dictionary:
|
|
364
323
|
var project_path: String = str(args.get("projectPath", ""))
|
|
365
|
-
var scene_path: String =
|
|
324
|
+
var scene_path: String = GdhSceneFile.res_path(project_path, str(args.get("scenePath", "")))
|
|
366
325
|
var node_path: String = str(args.get("nodePath", ""))
|
|
367
326
|
|
|
368
327
|
if node_path.is_empty() or node_path == ".":
|
|
@@ -381,14 +340,18 @@ func delete_node(args: Dictionary) -> Dictionary:
|
|
|
381
340
|
if not refused.is_empty():
|
|
382
341
|
return refused
|
|
383
342
|
|
|
384
|
-
var node: Node =
|
|
343
|
+
var node: Node = GdhSceneFile.node_at(scene.root, node_path)
|
|
385
344
|
if not node:
|
|
386
345
|
return scene.refuse(scene.not_found("Node not found: " + node_path, node_path))
|
|
387
|
-
var fixed: String = scene.fixed(node)
|
|
346
|
+
var fixed: String = scene.fixed(node, true)
|
|
388
347
|
if not fixed.is_empty():
|
|
389
348
|
return scene.refuse(fixed)
|
|
390
349
|
|
|
391
350
|
var at: String = scene.path_of(node)
|
|
351
|
+
var went: Array[String] = []
|
|
352
|
+
for under: Node in node.find_children("*", "", true, false):
|
|
353
|
+
if under.owner == scene.root:
|
|
354
|
+
went.append(scene.path_of(under))
|
|
392
355
|
node.get_parent().remove_child(node)
|
|
393
356
|
node.free()
|
|
394
357
|
var carried: GdhPaths = GdhPaths.new()
|
|
@@ -404,13 +367,15 @@ func delete_node(args: Dictionary) -> Dictionary:
|
|
|
404
367
|
return written
|
|
405
368
|
|
|
406
369
|
var answer: Dictionary = {"ok": true, "deletedNodePath": at}
|
|
370
|
+
if not went.is_empty():
|
|
371
|
+
answer["deletedWithIt"] = went
|
|
407
372
|
answer.merge(GdhKin.follow(scene_path, at, "", carried))
|
|
408
373
|
return answer
|
|
409
374
|
|
|
410
375
|
|
|
411
376
|
func duplicate_node(args: Dictionary) -> Dictionary:
|
|
412
377
|
var project_path: String = str(args.get("projectPath", ""))
|
|
413
|
-
var scene_path: String =
|
|
378
|
+
var scene_path: String = GdhSceneFile.res_path(project_path, str(args.get("scenePath", "")))
|
|
414
379
|
var node_path: String = str(args.get("nodePath", ""))
|
|
415
380
|
var new_name: String = str(args.get("newName", ""))
|
|
416
381
|
var parent_path: String = str(args.get("parentNodePath", ""))
|
|
@@ -425,7 +390,7 @@ func duplicate_node(args: Dictionary) -> Dictionary:
|
|
|
425
390
|
return refused
|
|
426
391
|
|
|
427
392
|
var root: Node = scene.root
|
|
428
|
-
var source: Node =
|
|
393
|
+
var source: Node = GdhSceneFile.node_at(root, node_path)
|
|
429
394
|
if not source:
|
|
430
395
|
return scene.refuse(scene.not_found("Node not found: " + node_path, node_path))
|
|
431
396
|
if source == root:
|
|
@@ -436,7 +401,7 @@ func duplicate_node(args: Dictionary) -> Dictionary:
|
|
|
436
401
|
|
|
437
402
|
var target_parent: Node = source.get_parent()
|
|
438
403
|
if not parent_path.is_empty():
|
|
439
|
-
target_parent =
|
|
404
|
+
target_parent = GdhSceneFile.node_at(root, parent_path)
|
|
440
405
|
if not target_parent:
|
|
441
406
|
return scene.refuse(scene.not_found("Parent not found: " + parent_path, parent_path))
|
|
442
407
|
foreign = scene.foreign(target_parent)
|
|
@@ -445,6 +410,13 @@ func duplicate_node(args: Dictionary) -> Dictionary:
|
|
|
445
410
|
var unusable: String = scene.unusable_name(target_parent, new_name)
|
|
446
411
|
if not unusable.is_empty():
|
|
447
412
|
return scene.refuse(unusable)
|
|
413
|
+
var place: Variant = args.get("index")
|
|
414
|
+
var unplaceable: String = GdhPlacing.unplaceable(target_parent, null, place)
|
|
415
|
+
if not unplaceable.is_empty():
|
|
416
|
+
return scene.refuse(unplaceable)
|
|
417
|
+
# The editor's Duplicate puts the copy right after the original.
|
|
418
|
+
if place == null and target_parent == source.get_parent():
|
|
419
|
+
place = source.get_index() + 1
|
|
448
420
|
|
|
449
421
|
var copies: Dictionary = {}
|
|
450
422
|
var duplicated_node: Node = GdhPlacing.copied(source, root, copies)
|
|
@@ -453,6 +425,7 @@ func duplicate_node(args: Dictionary) -> Dictionary:
|
|
|
453
425
|
|
|
454
426
|
duplicated_node.name = new_name
|
|
455
427
|
target_parent.add_child(duplicated_node)
|
|
428
|
+
GdhPlacing.place(duplicated_node, place)
|
|
456
429
|
GdhPlacing.own(duplicated_node, root)
|
|
457
430
|
GdhPlacing.reconnect(copies, source)
|
|
458
431
|
scene.copied(copies)
|
|
@@ -461,8 +434,11 @@ func duplicate_node(args: Dictionary) -> Dictionary:
|
|
|
461
434
|
var placed: Array = [scene.path_of(target_parent), duplicated_node.get_index()]
|
|
462
435
|
var written: Dictionary = scene.write(
|
|
463
436
|
func(saved: Node) -> String:
|
|
464
|
-
|
|
437
|
+
var copy: Node = saved.get_node_or_null(new_path)
|
|
438
|
+
if copy == null:
|
|
465
439
|
return "the copy would not be in the scene when it loads."
|
|
440
|
+
if copy.get_index() != placed[1]:
|
|
441
|
+
return "%s would load at index %d, not %d." % [new_path, copy.get_index(), placed[1]]
|
|
466
442
|
return ""
|
|
467
443
|
)
|
|
468
444
|
if not written.is_empty():
|
|
@@ -480,7 +456,7 @@ func duplicate_node(args: Dictionary) -> Dictionary:
|
|
|
480
456
|
|
|
481
457
|
func reparent_node(args: Dictionary) -> Dictionary:
|
|
482
458
|
var project_path: String = str(args.get("projectPath", ""))
|
|
483
|
-
var scene_path: String =
|
|
459
|
+
var scene_path: String = GdhSceneFile.res_path(project_path, str(args.get("scenePath", "")))
|
|
484
460
|
var node_path: String = str(args.get("nodePath", ""))
|
|
485
461
|
var new_parent_path: String = str(args.get("newParentPath", ""))
|
|
486
462
|
|
|
@@ -496,8 +472,8 @@ func reparent_node(args: Dictionary) -> Dictionary:
|
|
|
496
472
|
return refused
|
|
497
473
|
|
|
498
474
|
var root: Node = scene.root
|
|
499
|
-
var node: Node =
|
|
500
|
-
var new_parent: Node =
|
|
475
|
+
var node: Node = GdhSceneFile.node_at(root, node_path)
|
|
476
|
+
var new_parent: Node = GdhSceneFile.node_at(root, new_parent_path)
|
|
501
477
|
if not node:
|
|
502
478
|
return scene.refuse(scene.not_found("Node not found: " + node_path, node_path))
|
|
503
479
|
if not new_parent:
|
|
@@ -523,13 +499,24 @@ func reparent_node(args: Dictionary) -> Dictionary:
|
|
|
523
499
|
return scene.refuse(unplaceable)
|
|
524
500
|
|
|
525
501
|
var old_path: String = scene.path_of(node)
|
|
502
|
+
var old_parent: Node = node.get_parent()
|
|
503
|
+
var drawn_at: Dictionary = {}
|
|
504
|
+
for property: String in GdhPlacing.placing(node):
|
|
505
|
+
drawn_at[property] = _properties.read_at(node, property)
|
|
526
506
|
# Unowned while it moves: a node taken out of the tree keeps its owner, and adding it under the new
|
|
527
507
|
# parent warned that the owner was not above it. Node.reparent warns the same way.
|
|
528
508
|
node.owner = null
|
|
529
|
-
|
|
509
|
+
old_parent.remove_child(node)
|
|
530
510
|
new_parent.add_child(node)
|
|
531
511
|
GdhPlacing.place(node, place)
|
|
532
512
|
GdhPlacing.own(node, root)
|
|
513
|
+
if GdhRead.as_bool(args.get("keepGlobalTransform", true)):
|
|
514
|
+
GdhPlacing.keep_in_place(node, old_parent)
|
|
515
|
+
var also_changed: Dictionary = {}
|
|
516
|
+
for property: String in drawn_at:
|
|
517
|
+
var now: Variant = _properties.read_at(node, property)
|
|
518
|
+
if not GdhSame.same(now, drawn_at[property]):
|
|
519
|
+
also_changed[property] = {"was": _readable(node, drawn_at[property]), "now": _readable(node, now)}
|
|
533
520
|
|
|
534
521
|
var new_path: String = scene.path_of(node)
|
|
535
522
|
var index: int = node.get_index()
|
|
@@ -544,7 +531,7 @@ func reparent_node(args: Dictionary) -> Dictionary:
|
|
|
544
531
|
return "%s would still be in the scene as well when it loads." % old_path
|
|
545
532
|
if copy.get_index() != index:
|
|
546
533
|
return "%s would load at index %d, not %d." % [new_path, copy.get_index(), index]
|
|
547
|
-
return
|
|
534
|
+
return _kept(scene, saved, node, also_changed.keys())
|
|
548
535
|
)
|
|
549
536
|
if not written.is_empty():
|
|
550
537
|
carried.put_back(scene_path)
|
|
@@ -557,6 +544,8 @@ func reparent_node(args: Dictionary) -> Dictionary:
|
|
|
557
544
|
"newNodePath": new_path,
|
|
558
545
|
"index": index,
|
|
559
546
|
}
|
|
547
|
+
if not also_changed.is_empty():
|
|
548
|
+
answer["alsoChanged"] = also_changed
|
|
560
549
|
if new_path != old_path:
|
|
561
550
|
answer.merge(GdhKin.follow(scene_path, old_path, new_path, carried))
|
|
562
551
|
return answer
|
|
@@ -564,12 +553,14 @@ func reparent_node(args: Dictionary) -> Dictionary:
|
|
|
564
553
|
|
|
565
554
|
func set_node_properties(args: Dictionary) -> Dictionary:
|
|
566
555
|
var project_path: String = str(args.get("projectPath", ""))
|
|
567
|
-
var scene_path: String =
|
|
556
|
+
var scene_path: String = GdhSceneFile.res_path(project_path, str(args.get("scenePath", "")))
|
|
568
557
|
var node_path: String = str(args.get("nodePath", "."))
|
|
569
558
|
var properties: Dictionary = _parse_properties_arg(args.get("properties", {}))
|
|
570
559
|
var editable: Variant = args.get("editableChildren")
|
|
571
|
-
|
|
572
|
-
|
|
560
|
+
var adding: Array = GdhGroups.asked(args, "addGroups")
|
|
561
|
+
var removing: Array = GdhGroups.asked(args, "removeGroups")
|
|
562
|
+
if properties.is_empty() and editable == null and adding.is_empty() and removing.is_empty():
|
|
563
|
+
return {"ok": false, "error": "set takes properties, editableChildren, addGroups or removeGroups."}
|
|
573
564
|
|
|
574
565
|
var opened: Array = _open(scene_path, true)
|
|
575
566
|
var scene: GdhSceneFile = opened[0]
|
|
@@ -577,7 +568,7 @@ func set_node_properties(args: Dictionary) -> Dictionary:
|
|
|
577
568
|
if not refused.is_empty():
|
|
578
569
|
return refused
|
|
579
570
|
|
|
580
|
-
var node: Node =
|
|
571
|
+
var node: Node = GdhSceneFile.node_at(scene.root, node_path)
|
|
581
572
|
if not node:
|
|
582
573
|
return scene.refuse(scene.not_found("Node not found: " + node_path, node_path))
|
|
583
574
|
var foreign: String = scene.foreign(node)
|
|
@@ -603,7 +594,10 @@ func set_node_properties(args: Dictionary) -> Dictionary:
|
|
|
603
594
|
scene.asked(node, properties.keys())
|
|
604
595
|
var refused_property: String = _properties.write_all(node, properties)
|
|
605
596
|
if not refused_property.is_empty():
|
|
606
|
-
return scene.refuse(
|
|
597
|
+
return scene.refuse(GdhSceneFile.missing_on(node, refused_property))
|
|
598
|
+
var ungrouped: String = GdhGroups.change(scene, node, adding, removing)
|
|
599
|
+
if not ungrouped.is_empty():
|
|
600
|
+
return scene.refuse(ungrouped)
|
|
607
601
|
|
|
608
602
|
var held: Dictionary = {}
|
|
609
603
|
for key: Variant in properties:
|
|
@@ -611,11 +605,15 @@ func set_node_properties(args: Dictionary) -> Dictionary:
|
|
|
611
605
|
var new_path: String = scene.path_of(node)
|
|
612
606
|
var answer: Dictionary = {"ok": true, "nodePath": new_path, "properties": held}
|
|
613
607
|
_properties.note_write(answer, node)
|
|
608
|
+
if not adding.is_empty() or not removing.is_empty():
|
|
609
|
+
answer["groups"] = GdhGroups.read(scene, node).get("groups", [])
|
|
614
610
|
var carried: GdhPaths = GdhPaths.new()
|
|
615
611
|
var _rewritten: int = carried.carry(scene, {old_path: new_path} if new_path != old_path else {})
|
|
616
612
|
var written: Dictionary = scene.write(
|
|
617
613
|
func(saved: Node) -> String:
|
|
618
614
|
var unkept: String = _kept(scene, saved, node, properties.keys())
|
|
615
|
+
if unkept.is_empty():
|
|
616
|
+
unkept = GdhGroups.unkept(saved.get_node(new_path), adding, removing)
|
|
619
617
|
if unkept.is_empty() and editable != null:
|
|
620
618
|
if saved.is_editable_instance(saved.get_node(new_path)) != GdhRead.as_bool(editable):
|
|
621
619
|
return (
|
|
@@ -639,7 +637,7 @@ func set_node_properties(args: Dictionary) -> Dictionary:
|
|
|
639
637
|
|
|
640
638
|
func get_node_properties(args: Dictionary) -> Dictionary:
|
|
641
639
|
var project_path: String = str(args.get("projectPath", ""))
|
|
642
|
-
var scene_path: String =
|
|
640
|
+
var scene_path: String = GdhSceneFile.res_path(project_path, str(args.get("scenePath", "")))
|
|
643
641
|
var node_path: String = str(args.get("nodePath", "."))
|
|
644
642
|
var include_defaults: bool = GdhRead.as_bool(args.get("includeDefaults", false))
|
|
645
643
|
|
|
@@ -649,7 +647,7 @@ func get_node_properties(args: Dictionary) -> Dictionary:
|
|
|
649
647
|
if not refused.is_empty():
|
|
650
648
|
return refused
|
|
651
649
|
|
|
652
|
-
var node: Node =
|
|
650
|
+
var node: Node = GdhSceneFile.node_at(scene.root, node_path)
|
|
653
651
|
if not node:
|
|
654
652
|
return scene.refuse(scene.not_found("Node not found: " + node_path, node_path))
|
|
655
653
|
|
|
@@ -657,9 +655,18 @@ func get_node_properties(args: Dictionary) -> Dictionary:
|
|
|
657
655
|
# declares, the class's for the rest. Compared with a bare instance of the class, every exported
|
|
658
656
|
# variable read as changed; and a value this scene sets back to the default was hidden, so the
|
|
659
657
|
# names this scene's file sets are always answered.
|
|
660
|
-
var
|
|
658
|
+
var set_here: Dictionary = scene.set_here(node)
|
|
659
|
+
var props: Dictionary = _properties_of(node, include_defaults, set_here)
|
|
661
660
|
var cells: Dictionary = GdhResourceTools.cells_of(node)
|
|
662
|
-
var
|
|
661
|
+
var at: String = scene.path_of(node)
|
|
662
|
+
var here: Array = set_here.keys()
|
|
663
|
+
here.sort()
|
|
664
|
+
var answer: Dictionary = {"ok": true, "nodePath": at, "properties": props, "setHere": here}
|
|
665
|
+
answer.merge(GdhSceneFile.class_of(node))
|
|
666
|
+
answer.merge(GdhGroups.read(scene, node))
|
|
667
|
+
var from: String = scene.values_from(node, at)
|
|
668
|
+
if not from.is_empty():
|
|
669
|
+
answer["valuesFrom"] = from
|
|
663
670
|
if node != scene.root and node.owner != null and not node.scene_file_path.is_empty():
|
|
664
671
|
answer["instanceOf"] = node.scene_file_path
|
|
665
672
|
answer["editableChildren"] = scene.root.is_editable_instance(node)
|
|
@@ -679,11 +686,11 @@ func get_node_properties(args: Dictionary) -> Dictionary:
|
|
|
679
686
|
|
|
680
687
|
func save_scene(args: Dictionary) -> Dictionary:
|
|
681
688
|
var project_path: String = str(args.get("projectPath", ""))
|
|
682
|
-
var scene_path: String =
|
|
689
|
+
var scene_path: String = GdhSceneFile.res_path(project_path, str(args.get("scenePath", "")))
|
|
683
690
|
var new_path_raw: String = str(args.get("newPath", ""))
|
|
684
691
|
var target_path: String = scene_path
|
|
685
692
|
if not new_path_raw.is_empty():
|
|
686
|
-
target_path =
|
|
693
|
+
target_path = GdhSceneFile.res_path(project_path, new_path_raw)
|
|
687
694
|
|
|
688
695
|
if target_path != scene_path:
|
|
689
696
|
var unsaved: String = GdhSceneFile.unsaved_in_editor(target_path)
|
|
@@ -751,249 +758,3 @@ func _save_open_scene(scene_path: String) -> Dictionary:
|
|
|
751
758
|
"from": "editor",
|
|
752
759
|
"unsavedInEditor": GdhSceneFile.unsaved(scene_path)
|
|
753
760
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
func connect_signal(args: Dictionary) -> Dictionary:
|
|
757
|
-
var project_path: String = str(args.get("projectPath", ""))
|
|
758
|
-
var scene_path: String = _to_scene_res_path(project_path, str(args.get("scenePath", "")))
|
|
759
|
-
var source_node_path: String = str(args.get("sourceNodePath", ""))
|
|
760
|
-
var signal_name: String = str(args.get("signalName", ""))
|
|
761
|
-
var target_node_path: String = str(args.get("targetNodePath", ""))
|
|
762
|
-
var method_name: String = str(args.get("methodName", ""))
|
|
763
|
-
# A connection without CONNECT_PERSIST is a runtime one, and PackedScene.pack drops those on
|
|
764
|
-
# the way out: without this the scene saves unchanged and this answers success over nothing.
|
|
765
|
-
var flags: int = GdhRead.as_int(args.get("flags", 0)) | Object.CONNECT_PERSIST
|
|
766
|
-
|
|
767
|
-
if (
|
|
768
|
-
source_node_path.is_empty()
|
|
769
|
-
or signal_name.is_empty()
|
|
770
|
-
or target_node_path.is_empty()
|
|
771
|
-
or method_name.is_empty()
|
|
772
|
-
):
|
|
773
|
-
return {"ok": false, "error": "Missing required signal connection arguments"}
|
|
774
|
-
|
|
775
|
-
var opened: Array = _open(scene_path, true)
|
|
776
|
-
var scene: GdhSceneFile = opened[0]
|
|
777
|
-
var refused: Dictionary = opened[1]
|
|
778
|
-
if not refused.is_empty():
|
|
779
|
-
return refused
|
|
780
|
-
|
|
781
|
-
var ends: Array = _connection_ends(scene, source_node_path, signal_name, target_node_path)
|
|
782
|
-
if ends[0] == null:
|
|
783
|
-
return scene.refuse(str(ends[1]))
|
|
784
|
-
var source: Node = ends[0]
|
|
785
|
-
var target: Node = ends[1]
|
|
786
|
-
|
|
787
|
-
# Reconnected rather than left alone when the flags differ, so the connection saved is the one
|
|
788
|
-
# asked for. Left alone, the answer gave the flags asked for and the file kept the old ones.
|
|
789
|
-
var callable: Callable = Callable(target, method_name)
|
|
790
|
-
var method_found: bool = target.has_method(method_name)
|
|
791
|
-
var flags_before: int = -1
|
|
792
|
-
for connection: Dictionary in source.get_signal_connection_list(signal_name):
|
|
793
|
-
if connection.get("callable") == callable:
|
|
794
|
-
flags_before = GdhRead.as_int(connection.get("flags", 0))
|
|
795
|
-
if source.is_connected(signal_name, callable):
|
|
796
|
-
source.disconnect(signal_name, callable)
|
|
797
|
-
var connect_result: Error = source.connect(signal_name, callable, flags)
|
|
798
|
-
if connect_result != OK:
|
|
799
|
-
return scene.refuse("Failed to connect signal: " + error_string(connect_result))
|
|
800
|
-
|
|
801
|
-
var from: String = scene.path_of(source)
|
|
802
|
-
var to: String = scene.path_of(target)
|
|
803
|
-
var saved_flags: Array = [-1]
|
|
804
|
-
var written: Dictionary = scene.write(
|
|
805
|
-
func(saved: Node) -> String:
|
|
806
|
-
saved_flags[0] = _connection_flags(saved, from, signal_name, to, method_name)
|
|
807
|
-
if saved_flags[0] < 0:
|
|
808
|
-
return "the connection would not be in the scene when it loads."
|
|
809
|
-
return ""
|
|
810
|
-
)
|
|
811
|
-
if not written.is_empty():
|
|
812
|
-
return written
|
|
813
|
-
|
|
814
|
-
var answer: Dictionary = {
|
|
815
|
-
"ok": true,
|
|
816
|
-
"sourceNodePath": from,
|
|
817
|
-
"signalName": signal_name,
|
|
818
|
-
"targetNodePath": to,
|
|
819
|
-
"methodName": method_name,
|
|
820
|
-
"flags": saved_flags[0],
|
|
821
|
-
"flagNames": GdhSceneFile.flag_names(GdhRead.as_int(saved_flags[0])),
|
|
822
|
-
# Connected either way, as the editor's own dialog allows, since the method may be written
|
|
823
|
-
# next; a caller told nothing learnt of a misspelt one only when the signal fired.
|
|
824
|
-
"methodFound": method_found
|
|
825
|
-
}
|
|
826
|
-
# A second connect of the same pair read exactly like the first, so a caller could not tell a
|
|
827
|
-
# connection made from one that was already there, and a list showing one where two were made
|
|
828
|
-
# looked like a lost write.
|
|
829
|
-
if flags_before >= 0:
|
|
830
|
-
answer["alreadyConnected"] = true
|
|
831
|
-
if flags_before != GdhRead.as_int(saved_flags[0]):
|
|
832
|
-
answer["flagsBefore"] = GdhSceneFile.flag_names(flags_before)
|
|
833
|
-
return answer
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
## The two ends of a connection in [param scene], as [source, target], or [null, why not].
|
|
837
|
-
func _connection_ends(
|
|
838
|
-
scene: GdhSceneFile, source_node_path: String, signal_name: String, target_node_path: String
|
|
839
|
-
) -> Array:
|
|
840
|
-
var source: Node = _find_node(scene.root, source_node_path)
|
|
841
|
-
var target: Node = _find_node(scene.root, target_node_path)
|
|
842
|
-
if not source:
|
|
843
|
-
return [null, scene.not_found("Source node not found: " + source_node_path, source_node_path)]
|
|
844
|
-
if not target:
|
|
845
|
-
return [null, scene.not_found("Target node not found: " + target_node_path, target_node_path)]
|
|
846
|
-
# pack drops a connection with either end inside an instanced scene, and the answer said it was
|
|
847
|
-
# made or removed.
|
|
848
|
-
for end: Node in [source, target]:
|
|
849
|
-
var foreign: String = scene.foreign(end)
|
|
850
|
-
if not foreign.is_empty():
|
|
851
|
-
return [null, foreign]
|
|
852
|
-
if not source.has_signal(signal_name):
|
|
853
|
-
var said: String = "%s has no signal %s" % [source_node_path, signal_name]
|
|
854
|
-
return [null, _missing_on(source, GdhRead.offered(said, signal_name, GdhRead.signals_of(source)))]
|
|
855
|
-
return [source, target]
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
## The flags of the persistent connection from [param from]'s [param signal_name] to
|
|
859
|
-
## [param method] on [param to] in the tree under [param root], or -1 when there is none.
|
|
860
|
-
func _connection_flags(root: Node, from: String, signal_name: String, to: String, method: String) -> int:
|
|
861
|
-
var source: Node = root.get_node_or_null(from)
|
|
862
|
-
var target: Node = root.get_node_or_null(to)
|
|
863
|
-
if source == null or target == null or not source.has_signal(signal_name):
|
|
864
|
-
return -1
|
|
865
|
-
for conn: Dictionary in source.get_signal_connection_list(signal_name):
|
|
866
|
-
var flags: int = GdhRead.as_int(conn.get("flags", 0))
|
|
867
|
-
var callable: Callable = conn.get("callable", Callable())
|
|
868
|
-
if (
|
|
869
|
-
flags & Object.CONNECT_PERSIST
|
|
870
|
-
and callable.get_object() == target
|
|
871
|
-
and str(callable.get_method()) == method
|
|
872
|
-
):
|
|
873
|
-
return flags
|
|
874
|
-
return -1
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
func disconnect_signal(args: Dictionary) -> Dictionary:
|
|
878
|
-
var project_path: String = str(args.get("projectPath", ""))
|
|
879
|
-
var scene_path: String = _to_scene_res_path(project_path, str(args.get("scenePath", "")))
|
|
880
|
-
var source_node_path: String = str(args.get("sourceNodePath", ""))
|
|
881
|
-
var signal_name: String = str(args.get("signalName", ""))
|
|
882
|
-
var target_node_path: String = str(args.get("targetNodePath", ""))
|
|
883
|
-
var method_name: String = str(args.get("methodName", ""))
|
|
884
|
-
|
|
885
|
-
if (
|
|
886
|
-
source_node_path.is_empty()
|
|
887
|
-
or signal_name.is_empty()
|
|
888
|
-
or target_node_path.is_empty()
|
|
889
|
-
or method_name.is_empty()
|
|
890
|
-
):
|
|
891
|
-
return {"ok": false, "error": "Missing required signal disconnection arguments"}
|
|
892
|
-
|
|
893
|
-
var opened: Array = _open(scene_path, true)
|
|
894
|
-
var scene: GdhSceneFile = opened[0]
|
|
895
|
-
var refused: Dictionary = opened[1]
|
|
896
|
-
if not refused.is_empty():
|
|
897
|
-
return refused
|
|
898
|
-
|
|
899
|
-
var ends: Array = _connection_ends(scene, source_node_path, signal_name, target_node_path)
|
|
900
|
-
if ends[0] == null:
|
|
901
|
-
return scene.refuse(str(ends[1]))
|
|
902
|
-
var source: Node = ends[0]
|
|
903
|
-
var target: Node = ends[1]
|
|
904
|
-
|
|
905
|
-
var from: String = scene.path_of(source)
|
|
906
|
-
var to: String = scene.path_of(target)
|
|
907
|
-
# A connection that was never there saved the scene unchanged and answered as removed, so a
|
|
908
|
-
# typo in either name read as success.
|
|
909
|
-
if _connection_flags(scene.root, from, signal_name, to, method_name) < 0:
|
|
910
|
-
return scene.refuse(
|
|
911
|
-
"%s has no connection from %s.%s to %s.%s." % [scene_path, from, signal_name, to, method_name]
|
|
912
|
-
)
|
|
913
|
-
source.disconnect(signal_name, Callable(target, method_name))
|
|
914
|
-
|
|
915
|
-
var written: Dictionary = scene.write(
|
|
916
|
-
func(saved: Node) -> String:
|
|
917
|
-
if _connection_flags(saved, from, signal_name, to, method_name) >= 0:
|
|
918
|
-
return "the connection would still be in the scene when it loads."
|
|
919
|
-
return ""
|
|
920
|
-
)
|
|
921
|
-
if not written.is_empty():
|
|
922
|
-
return written
|
|
923
|
-
|
|
924
|
-
return {
|
|
925
|
-
"ok": true,
|
|
926
|
-
"removed": true,
|
|
927
|
-
"sourceNodePath": from,
|
|
928
|
-
"signalName": signal_name,
|
|
929
|
-
"targetNodePath": to,
|
|
930
|
-
"methodName": method_name
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
func list_connections(args: Dictionary) -> Dictionary:
|
|
935
|
-
var project_path: String = str(args.get("projectPath", ""))
|
|
936
|
-
var scene_path: String = _to_scene_res_path(project_path, str(args.get("scenePath", "")))
|
|
937
|
-
var filter_path: String = str(args.get("nodePath", ""))
|
|
938
|
-
|
|
939
|
-
var opened: Array = _open(scene_path, false)
|
|
940
|
-
var scene: GdhSceneFile = opened[0]
|
|
941
|
-
var refused: Dictionary = opened[1]
|
|
942
|
-
if not refused.is_empty():
|
|
943
|
-
return refused
|
|
944
|
-
|
|
945
|
-
var root: Node = scene.root
|
|
946
|
-
var filtered: Node = null
|
|
947
|
-
if not filter_path.is_empty():
|
|
948
|
-
filtered = _find_node(root, filter_path)
|
|
949
|
-
if filtered == null:
|
|
950
|
-
return scene.refuse(scene.not_found("Node not found: " + filter_path, filter_path))
|
|
951
|
-
var nodes: Array = []
|
|
952
|
-
_collect_nodes_recursive(root, ".", nodes)
|
|
953
|
-
|
|
954
|
-
# Only persistent connections, which are the ones a scene file holds: a Container connects
|
|
955
|
-
# signals of each child to itself as the child is added, and those were answered as if the file
|
|
956
|
-
# held them. Matched on either end, since the filter is for connections involving the node.
|
|
957
|
-
var connections: Array = []
|
|
958
|
-
for entry: Dictionary in nodes:
|
|
959
|
-
var path: String = str(entry["path"])
|
|
960
|
-
var node: Node = entry["node"]
|
|
961
|
-
for signal_info: Dictionary in node.get_signal_list():
|
|
962
|
-
var signal_name: String = str(signal_info.get("name", ""))
|
|
963
|
-
if signal_name.is_empty():
|
|
964
|
-
continue
|
|
965
|
-
for conn: Dictionary in node.get_signal_connection_list(signal_name):
|
|
966
|
-
var flags: int = GdhRead.as_int(conn.get("flags", 0))
|
|
967
|
-
if not flags & Object.CONNECT_PERSIST:
|
|
968
|
-
continue
|
|
969
|
-
var callable: Callable = conn.get("callable", Callable())
|
|
970
|
-
var target_obj: Object = callable.get_object()
|
|
971
|
-
if filtered != null and node != filtered and target_obj != filtered:
|
|
972
|
-
continue
|
|
973
|
-
var target_path: String = ""
|
|
974
|
-
var editable: bool = scene.foreign(node).is_empty()
|
|
975
|
-
if target_obj is Node:
|
|
976
|
-
target_path = str(root.get_path_to(target_obj as Node))
|
|
977
|
-
editable = editable and scene.foreign(target_obj as Node).is_empty()
|
|
978
|
-
(
|
|
979
|
-
connections
|
|
980
|
-
. append(
|
|
981
|
-
{
|
|
982
|
-
"sourceNodePath": path,
|
|
983
|
-
"signalName": signal_name,
|
|
984
|
-
"targetNodePath": target_path,
|
|
985
|
-
"methodName": str(callable.get_method()),
|
|
986
|
-
"flags": flags,
|
|
987
|
-
"flagNames": GdhSceneFile.flag_names(flags),
|
|
988
|
-
# False for a connection an instanced scene makes inside itself, which only that
|
|
989
|
-
# scene can change.
|
|
990
|
-
"editable": editable
|
|
991
|
-
}
|
|
992
|
-
)
|
|
993
|
-
)
|
|
994
|
-
|
|
995
|
-
scene.close()
|
|
996
|
-
var listed: Dictionary = {"ok": true, "connections": connections}
|
|
997
|
-
if Engine.is_editor_hint():
|
|
998
|
-
listed["unsavedInEditor"] = GdhSceneFile.unsaved(scene_path)
|
|
999
|
-
return listed
|