gdharness 1.3.2 → 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.
Files changed (46) hide show
  1. package/build/cli.js +163 -85
  2. package/build/godot/addons/gdharness_editor/animation_reads.gd +14 -11
  3. package/build/godot/addons/gdharness_editor/declared_values.gd +157 -4
  4. package/build/godot/addons/gdharness_editor/file_reads.gd +15 -3
  5. package/build/godot/addons/gdharness_editor/file_walk.gd +134 -0
  6. package/build/godot/addons/gdharness_editor/file_walk.gd.uid +1 -0
  7. package/build/godot/addons/gdharness_editor/headless_check.gd +44 -23
  8. package/build/godot/addons/gdharness_editor/property_paths.gd +225 -0
  9. package/build/godot/addons/gdharness_editor/property_paths.gd.uid +1 -0
  10. package/build/godot/addons/gdharness_editor/property_values.gd +249 -227
  11. package/build/godot/addons/gdharness_editor/reading.gd +15 -0
  12. package/build/godot/addons/gdharness_editor/same_values.gd +177 -0
  13. package/build/godot/addons/gdharness_editor/same_values.gd.uid +1 -0
  14. package/build/godot/addons/gdharness_editor/scene_file.gd +100 -15
  15. package/build/godot/addons/gdharness_editor/serialisation.gd +75 -3
  16. package/build/godot/addons/gdharness_editor/tool_executor.gd +12 -3
  17. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +402 -55
  18. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +3 -2
  19. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +91 -0
  20. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd.uid +1 -0
  21. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +88 -5
  22. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +102 -337
  23. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +363 -0
  24. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd.uid +1 -0
  25. package/build/godot/addons/gdharness_runtime/reading.gd +15 -0
  26. package/build/godot/addons/gdharness_runtime/runtime_input.gd +10 -26
  27. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +3 -2
  28. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +2 -1
  29. package/build/godot/addons/gdharness_runtime/runtime_values.gd +30 -0
  30. package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -3
  31. package/build/godot/operations/animation_reads.gd +14 -11
  32. package/build/godot/operations/declared_values.gd +157 -4
  33. package/build/godot/operations/dependencies.gd +14 -39
  34. package/build/godot/operations/editor_tools.gd +7 -3
  35. package/build/godot/operations/file_reads.gd +15 -3
  36. package/build/godot/operations/file_walk.gd +1 -0
  37. package/build/godot/operations/gdscript_analysis.gd +229 -75
  38. package/build/godot/operations/gdscript_authoring.gd +142 -34
  39. package/build/godot/operations/import_pipeline.gd +17 -6
  40. package/build/godot/operations/logger.gd +7 -0
  41. package/build/godot/operations/property_paths.gd +225 -0
  42. package/build/godot/operations/property_values.gd +249 -227
  43. package/build/godot/operations/reading.gd +15 -0
  44. package/build/godot/operations/same_values.gd +177 -0
  45. package/build/godot/operations/serialisation.gd +75 -3
  46. package/package.json +1 -1
@@ -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
@@ -4,8 +4,9 @@ extends RefCounted
4
4
  ## What a new node in a scene is made or copied from, what the scene owns of it, and where among its
5
5
  ## siblings a node may go.
6
6
 
7
+ const GdhFileWalk = preload("../file_walk.gd")
7
8
  const GdhRead = preload("../reading.gd")
8
- const GdhPropertyValues = preload("../property_values.gd")
9
+ const GdhSame = preload("../same_values.gd")
9
10
 
10
11
 
11
12
  ## The scene to instance for an add, or null for a node of a class, beside why neither can be made
@@ -17,11 +18,15 @@ static func what_to_add(node_type: String, instance_path: String, scene_path: St
17
18
  "add takes nodeType for a node of a class, or instancePath for a scene, and one of them only."
18
19
  ]
19
20
  if not instance_path.is_empty():
21
+ # Asked before the cycle walk, which reads the file's dependencies and so loaded a file that
22
+ # was not there, laying the engine's error and a backtrace beneath the refusal.
23
+ if not ResourceLoader.exists(instance_path, "PackedScene"):
24
+ var said: String = "No scene at %s to instance" % instance_path
25
+ var offered: String = GdhFileWalk.new().with_nearest_files(said, instance_path)
26
+ return [null, offered if offered != said else said + "."]
20
27
  var cycle: String = _instancing_cycle(scene_path, instance_path)
21
28
  if not cycle.is_empty():
22
29
  return [null, cycle]
23
- if not ResourceLoader.exists(instance_path, "PackedScene"):
24
- return [null, "No scene at %s to instance." % instance_path]
25
30
  var packed: PackedScene = ResourceLoader.load(instance_path, "PackedScene") as PackedScene
26
31
  if packed == null or not packed.can_instantiate():
27
32
  return [null, "%s does not load as a scene that can be instanced." % instance_path]
@@ -35,12 +40,24 @@ static func what_to_add(node_type: String, instance_path: String, scene_path: St
35
40
  )
36
41
  ]
37
42
  if not ClassDB.class_exists(node_type) or not ClassDB.is_parent_class(node_type, "Node"):
38
- return [null, "nodeType must be a node class, and %s is not one." % node_type]
43
+ return [null, not_a_node_class("nodeType", node_type)]
39
44
  if not ClassDB.can_instantiate(node_type):
40
45
  return [null, "%s cannot be instantiated." % node_type]
41
46
  return [null, ""]
42
47
 
43
48
 
49
+ ## The refusal for [param given], sent as [param argument], which names no node class, with the
50
+ ## node classes nearest it, so "Node2DD" is offered Node2D.
51
+ static func not_a_node_class(argument: String, given: String) -> String:
52
+ var said: String = "%s must be a node class, and %s is not one" % [argument, given]
53
+ var classes: PackedStringArray = ClassDB.get_inheriters_from_class("Node")
54
+ var _added: bool = classes.append("Node")
55
+ var close: PackedStringArray = GdhRead.nearest(given, classes)
56
+ if close.is_empty():
57
+ return said + "."
58
+ return "%s; the nearest %s %s." % [said, "is" if close.size() == 1 else "are", ", ".join(close)]
59
+
60
+
44
61
  ## A new node of [param node_type], or an instance of [param packed] when there is one. The instance
45
62
  ## is made in the editor's edit state, so the pack writes it as an instance of its file, and owning
46
63
  ## its root leaves its own nodes to the instanced scene.
@@ -68,7 +85,7 @@ static func copied(source: Node, root: Node, copies: Dictionary) -> Node:
68
85
  continue
69
86
  var property: String = str(declared.get("name", ""))
70
87
  var value: Variant = source.get(property)
71
- if not GdhPropertyValues.same(copy.get(property), value):
88
+ if not GdhSame.same(copy.get(property), value):
72
89
  copy.set(property, value)
73
90
  for group: StringName in source.get_groups():
74
91
  if not copy.is_in_group(group):
@@ -154,6 +171,72 @@ static func place(node: Node, asked: Variant) -> void:
154
171
  node.get_parent().move_child(node, GdhRead.as_int(asked))
155
172
 
156
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
+
157
240
  ## Why [param instanced] cannot be instanced into [param scene_path], or "" when it can: a scene
158
241
  ## inside itself, directly or through the scenes it instances, which Godot's own dialog refuses and
159
242
  ## which never finishes loading.