gdharness 1.2.41 → 1.2.42
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 +84 -26
- package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -4
- package/build/godot/addons/gdharness_editor/file_reads.gd +134 -10
- package/build/godot/addons/gdharness_editor/headless_check.gd +34 -4
- package/build/godot/addons/gdharness_editor/reading.gd +25 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +209 -15
- package/build/godot/addons/gdharness_editor/serialisation.gd +5 -1
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +346 -0
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +438 -0
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +90 -48
- package/build/godot/addons/gdharness_runtime/reading.gd +25 -0
- package/build/godot/addons/gdharness_runtime/serialisation.gd +5 -1
- package/build/godot/operations/animation_reads.gd +7 -4
- package/build/godot/operations/editor_tools.gd +19 -1
- package/build/godot/operations/file_reads.gd +134 -10
- package/build/godot/operations/reading.gd +25 -0
- package/build/godot/operations/serialisation.gd +5 -1
- package/build/index.js +84 -26
- package/package.json +1 -1
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
@tool
|
|
2
|
+
extends RefCounted
|
|
3
|
+
|
|
4
|
+
## The node paths scenes hold, carried after a node moves as the editor's own rename carries them:
|
|
5
|
+
## the NodePath values on their nodes, inside arrays, dictionaries and the resources built into the
|
|
6
|
+
## scene too, and the tracks of the animations their players play. After a delete they are named
|
|
7
|
+
## rather than changed, since what they named is gone and dropping a track would lose its keys.
|
|
8
|
+
##
|
|
9
|
+
## A path is worked out from where its holder was and where it pointed before the move, so it is
|
|
10
|
+
## followed in a scene loaded after the move as well, where the old path no longer resolves.
|
|
11
|
+
|
|
12
|
+
enum { UNCHANGED, REWRITTEN, DELETED }
|
|
13
|
+
|
|
14
|
+
const HeadlessCheck = preload("../headless_check.gd")
|
|
15
|
+
const Read = preload("../reading.gd")
|
|
16
|
+
const SceneFile = preload("../scene_file.gd")
|
|
17
|
+
## PROPERTY_HINT_NO_NODEPATH, which marks a property the editor's rename skips. It is not bound for
|
|
18
|
+
## scripts, and follows PROPERTY_HINT_ONESHOT in the engine's list.
|
|
19
|
+
const HINT_NO_NODEPATH: int = PROPERTY_HINT_ONESHOT + 1
|
|
20
|
+
|
|
21
|
+
## Each path rewritten, as {scenePath, nodePath, property or animation and track, from, to}.
|
|
22
|
+
var retargeted: Array[Dictionary] = []
|
|
23
|
+
## Each path that needed rewriting and was left, with why.
|
|
24
|
+
var not_retargeted: Array[Dictionary] = []
|
|
25
|
+
## Each path naming a deleted node, or a node below one, which now names nothing.
|
|
26
|
+
var still_naming: Array[Dictionary] = []
|
|
27
|
+
## The files beside a scene holding a rewritten track, each to the resource saved there.
|
|
28
|
+
var _homes: Dictionary = {}
|
|
29
|
+
## Each track rewritten in an animation one of [member _homes] keeps, as [animation, track, the path
|
|
30
|
+
## it had, its record, the file]. The editor shares such a resource with every scene using it, so a
|
|
31
|
+
## rewrite that is not saved is put back.
|
|
32
|
+
var _outside: Array[Array] = []
|
|
33
|
+
var _scene: SceneFile = null
|
|
34
|
+
var _moves: Dictionary = {}
|
|
35
|
+
var _back: Dictionary = {}
|
|
36
|
+
var _seen: Dictionary = {}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Where [param given] went under [param moves], each path a node was at to where it is now or ""
|
|
40
|
+
## when it was deleted: to where a path it names went, or below it.
|
|
41
|
+
static func moved(given: String, moves: Dictionary) -> String:
|
|
42
|
+
for old: String in moves:
|
|
43
|
+
var now: String = moves[old]
|
|
44
|
+
if given == old:
|
|
45
|
+
return now
|
|
46
|
+
if given.begins_with(old + "/"):
|
|
47
|
+
return "" if now.is_empty() else now + given.substr(old.length())
|
|
48
|
+
return given
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## Carries [param moves], from the root of [param scene], into the paths it holds. Called after the
|
|
52
|
+
## move and before the write. Answers how many paths it rewrote.
|
|
53
|
+
func carry(scene: SceneFile, moves: Dictionary) -> int:
|
|
54
|
+
if moves.is_empty():
|
|
55
|
+
return 0
|
|
56
|
+
_scene = scene
|
|
57
|
+
_moves = moves
|
|
58
|
+
_back = {}
|
|
59
|
+
for old: String in moves:
|
|
60
|
+
if not str(moves[old]).is_empty():
|
|
61
|
+
_back[moves[old]] = old
|
|
62
|
+
var before: int = retargeted.size()
|
|
63
|
+
var nodes: Array[Node] = [scene.root]
|
|
64
|
+
while not nodes.is_empty():
|
|
65
|
+
var node: Node = nodes.pop_back()
|
|
66
|
+
var children: Array[Node] = node.get_children()
|
|
67
|
+
children.reverse()
|
|
68
|
+
nodes.append_array(children)
|
|
69
|
+
if not scene.foreign(node).is_empty():
|
|
70
|
+
continue
|
|
71
|
+
var now: String = scene.path_of(node)
|
|
72
|
+
var was: String = moved(now, _back)
|
|
73
|
+
# A value another file gives is in that file's terms, which its own pass carried already.
|
|
74
|
+
var from: String = scene.values_from(node, was)
|
|
75
|
+
var set_here: Dictionary = scene.set_at(was) if not from.is_empty() else {}
|
|
76
|
+
for property: Dictionary in node.get_property_list():
|
|
77
|
+
var name: String = str(property["name"])
|
|
78
|
+
if not _may_hold_paths(property):
|
|
79
|
+
continue
|
|
80
|
+
var entry: Dictionary = {"scenePath": scene.path, "nodePath": now, "property": name}
|
|
81
|
+
var elsewhere: String = "" if set_here.has(name) else from
|
|
82
|
+
var carried: Array = _carried(node.get(name), was, now, entry, elsewhere)
|
|
83
|
+
if carried[0]:
|
|
84
|
+
scene.asked(node, [name])
|
|
85
|
+
node.set(name, carried[1])
|
|
86
|
+
var mixer: AnimationMixer = node as AnimationMixer
|
|
87
|
+
if mixer != null:
|
|
88
|
+
_tracks(mixer, now)
|
|
89
|
+
return retargeted.size() - before
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
## Opens [param scene_path], carries [param moves] into it, and writes it when that rewrote a path.
|
|
93
|
+
## Answers "" or why it was left as it was.
|
|
94
|
+
func carry_into(scene_path: String, moves: Dictionary) -> String:
|
|
95
|
+
var scene: SceneFile = SceneFile.new()
|
|
96
|
+
var refused: Dictionary = scene.open(scene_path, true)
|
|
97
|
+
if not refused.is_empty():
|
|
98
|
+
return str(refused.get("error", ""))
|
|
99
|
+
if carry(scene, moves) == 0:
|
|
100
|
+
scene.close()
|
|
101
|
+
return ""
|
|
102
|
+
var written: Dictionary = scene.write()
|
|
103
|
+
if written.is_empty():
|
|
104
|
+
return ""
|
|
105
|
+
var why: String = str(written.get("error", ""))
|
|
106
|
+
put_back(scene_path, why)
|
|
107
|
+
return why
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
## Undoes what was carried into [param scene_path] where it outlives the scene's own nodes, in the
|
|
111
|
+
## animations kept beside it, for a scene that was not written. With [param why], the paths
|
|
112
|
+
## rewritten there move to notRetargeted with it.
|
|
113
|
+
func put_back(scene_path: String, why: String = "") -> void:
|
|
114
|
+
var _undone: Array[Dictionary] = _put_back(
|
|
115
|
+
func(record: Dictionary) -> bool: return record["scenePath"] == scene_path
|
|
116
|
+
)
|
|
117
|
+
for record: Dictionary in retargeted.duplicate():
|
|
118
|
+
if record["scenePath"] == scene_path:
|
|
119
|
+
retargeted.erase(record)
|
|
120
|
+
if not why.is_empty():
|
|
121
|
+
not_retargeted.append(_left(record, why))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
## The fields an answer gives for what was carried, each only when it names any, once the files
|
|
125
|
+
## beside the scenes holding rewritten tracks are saved. The tracks of a file that does not save are
|
|
126
|
+
## put back and move to notRetargeted.
|
|
127
|
+
func answer() -> Dictionary:
|
|
128
|
+
for file: String in _homes:
|
|
129
|
+
var resource: Resource = _homes[file]
|
|
130
|
+
var saving: Error = SceneFile.save_keeping_uid(resource, file)
|
|
131
|
+
if saving == OK:
|
|
132
|
+
if Engine.is_editor_hint():
|
|
133
|
+
EditorInterface.get_resource_filesystem().update_file(file)
|
|
134
|
+
continue
|
|
135
|
+
var why: String = "Failed to save %s: %s" % [file, error_string(saving)]
|
|
136
|
+
for record: Dictionary in _put_back(func(_record: Dictionary) -> bool: return false, file):
|
|
137
|
+
retargeted.erase(record)
|
|
138
|
+
not_retargeted.append(_left(record, why))
|
|
139
|
+
_homes.clear()
|
|
140
|
+
_outside.clear()
|
|
141
|
+
var fields: Dictionary = {}
|
|
142
|
+
if not retargeted.is_empty():
|
|
143
|
+
fields["retargeted"] = retargeted
|
|
144
|
+
if not not_retargeted.is_empty():
|
|
145
|
+
fields["notRetargeted"] = not_retargeted
|
|
146
|
+
if not still_naming.is_empty():
|
|
147
|
+
fields["stillNaming"] = still_naming
|
|
148
|
+
return fields
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
## Puts back each track rewritten outside the scene whose record [param matching] takes, or that is
|
|
152
|
+
## kept in [param file], and answers their records. A file left with none is not saved.
|
|
153
|
+
func _put_back(matching: Callable, file: String = "") -> Array[Dictionary]:
|
|
154
|
+
var records: Array[Dictionary] = []
|
|
155
|
+
var kept: Array[Array] = []
|
|
156
|
+
for edit: Array in _outside:
|
|
157
|
+
var record: Dictionary = edit[3]
|
|
158
|
+
if edit[4] != file and not matching.call(record):
|
|
159
|
+
kept.append(edit)
|
|
160
|
+
continue
|
|
161
|
+
var animation: Animation = edit[0]
|
|
162
|
+
var track: int = edit[1]
|
|
163
|
+
var had: NodePath = edit[2]
|
|
164
|
+
animation.track_set_path(track, had)
|
|
165
|
+
records.append(record)
|
|
166
|
+
_outside = kept
|
|
167
|
+
for home: String in _homes.keys():
|
|
168
|
+
if not _outside.any(func(edit: Array) -> bool: return edit[4] == home):
|
|
169
|
+
var _dropped: bool = _homes.erase(home)
|
|
170
|
+
return records
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
## [param record] of a path that was not rewritten after all, with why.
|
|
174
|
+
static func _left(record: Dictionary, why: String) -> Dictionary:
|
|
175
|
+
var left: Dictionary = record.duplicate()
|
|
176
|
+
var _had_one: bool = left.erase("to")
|
|
177
|
+
left["reason"] = why
|
|
178
|
+
return left
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
## Whether [param property] can hold a node path the editor's rename would follow: one saved or
|
|
182
|
+
## shown, of a type that holds paths, and not marked as holding none.
|
|
183
|
+
static func _may_hold_paths(property: Dictionary) -> bool:
|
|
184
|
+
if not Read.as_int(property.get("usage")) & (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR):
|
|
185
|
+
return false
|
|
186
|
+
if Read.as_int(property.get("hint")) == HINT_NO_NODEPATH:
|
|
187
|
+
return false
|
|
188
|
+
return [TYPE_OBJECT, TYPE_NODE_PATH, TYPE_ARRAY, TYPE_DICTIONARY].has(Read.as_int(property.get("type")))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
## [param value], held by the node that was at [param was] and is at [param now], with each path in
|
|
192
|
+
## it carried, as [whether any was, the value]. A resource built into the scene is changed in place.
|
|
193
|
+
## [param entry] says where the value is held, for the record of each path. A path in a resource may
|
|
194
|
+
## be relative to something other than its holder, as the editor allows; it is followed only when it
|
|
195
|
+
## names a node from where its holder was, as one relative to the holder does. A value [param
|
|
196
|
+
## elsewhere], a file other than the scene's, gives is named only when it names nothing since the
|
|
197
|
+
## move, which its own file's pass could not see.
|
|
198
|
+
func _carried(value: Variant, was: String, now: String, entry: Dictionary, elsewhere: String) -> Array:
|
|
199
|
+
match typeof(value):
|
|
200
|
+
TYPE_NODE_PATH:
|
|
201
|
+
var path: NodePath = value
|
|
202
|
+
var after: Array = _after(path, was, now)
|
|
203
|
+
var record: Dictionary = entry.duplicate()
|
|
204
|
+
record["from"] = str(path)
|
|
205
|
+
if not elsewhere.is_empty():
|
|
206
|
+
if after[0] == REWRITTEN and _names_nothing(path, now):
|
|
207
|
+
not_retargeted.append(
|
|
208
|
+
_left(
|
|
209
|
+
record,
|
|
210
|
+
"The value is set in %s, and an edit here would not be saved there." % elsewhere
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
elif after[0] == DELETED:
|
|
214
|
+
still_naming.append(record)
|
|
215
|
+
elif after[0] == REWRITTEN:
|
|
216
|
+
record["to"] = after[1]
|
|
217
|
+
retargeted.append(record)
|
|
218
|
+
return [true, NodePath(str(after[1]))]
|
|
219
|
+
TYPE_ARRAY:
|
|
220
|
+
var items: Array = value
|
|
221
|
+
var changed: bool = false
|
|
222
|
+
for index: int in items.size():
|
|
223
|
+
var carried: Array = _carried(items[index], was, now, entry, elsewhere)
|
|
224
|
+
if carried[0]:
|
|
225
|
+
if not changed:
|
|
226
|
+
items = items.duplicate()
|
|
227
|
+
changed = true
|
|
228
|
+
items[index] = carried[1]
|
|
229
|
+
return [changed, items]
|
|
230
|
+
TYPE_DICTIONARY:
|
|
231
|
+
var pairs: Dictionary = value
|
|
232
|
+
var changed: bool = false
|
|
233
|
+
for key: Variant in pairs.keys():
|
|
234
|
+
var carried: Array = _carried(pairs[key], was, now, entry, elsewhere)
|
|
235
|
+
if carried[0]:
|
|
236
|
+
if not changed:
|
|
237
|
+
pairs = pairs.duplicate()
|
|
238
|
+
changed = true
|
|
239
|
+
pairs[key] = carried[1]
|
|
240
|
+
return [changed, pairs]
|
|
241
|
+
TYPE_OBJECT:
|
|
242
|
+
var held: Object = value
|
|
243
|
+
# The editor leaves animations to the tracks below, and materials for what it costs.
|
|
244
|
+
if not held is Resource or held is Animation or held is Material:
|
|
245
|
+
return [false, value]
|
|
246
|
+
var resource: Resource = held
|
|
247
|
+
var file: String = resource.resource_path.get_slice("::", 0)
|
|
248
|
+
if not elsewhere.is_empty() or (not file.is_empty() and file != _scene.path):
|
|
249
|
+
return [false, value]
|
|
250
|
+
var reached: bool = false
|
|
251
|
+
for property: Dictionary in resource.get_property_list():
|
|
252
|
+
var name: String = str(property["name"])
|
|
253
|
+
if not _may_hold_paths(property):
|
|
254
|
+
continue
|
|
255
|
+
var carried: Array = _carried(resource.get(name), was, now, entry, "")
|
|
256
|
+
if carried[0]:
|
|
257
|
+
resource.set(name, carried[1])
|
|
258
|
+
reached = true
|
|
259
|
+
if reached:
|
|
260
|
+
_scene.reached_into(resource)
|
|
261
|
+
return [false, value]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
## What [param path], held by the node that was at [param was] and is at [param now], becomes, as
|
|
265
|
+
## [UNCHANGED, ""], [REWRITTEN, the new path] or [DELETED, ""].
|
|
266
|
+
func _after(path: NodePath, was: String, now: String) -> Array:
|
|
267
|
+
if path.is_empty() or path.is_absolute() or path.get_name_count() == 0:
|
|
268
|
+
return [UNCHANGED, ""]
|
|
269
|
+
var names: PackedStringArray = []
|
|
270
|
+
for index: int in path.get_name_count():
|
|
271
|
+
var _named: bool = names.append(str(path.get_name(index)))
|
|
272
|
+
var start: String = was
|
|
273
|
+
var unique: bool = names[0].begins_with("%")
|
|
274
|
+
if unique:
|
|
275
|
+
start = _unique_was(names[0].substr(1), now)
|
|
276
|
+
if start.is_empty():
|
|
277
|
+
return [UNCHANGED, ""]
|
|
278
|
+
names = names.slice(1)
|
|
279
|
+
var target_was: String = _walked(start, names)
|
|
280
|
+
if target_was.is_empty():
|
|
281
|
+
return [UNCHANGED, ""]
|
|
282
|
+
var target_now: String = moved(target_was, _moves)
|
|
283
|
+
if target_now == target_was and now == was:
|
|
284
|
+
return [UNCHANGED, ""]
|
|
285
|
+
if target_now.is_empty():
|
|
286
|
+
return [DELETED, ""]
|
|
287
|
+
var holder: Node = _scene.root.get_node_or_null(now)
|
|
288
|
+
var target: Node = _scene.root.get_node_or_null(target_now)
|
|
289
|
+
if holder == null or target == null:
|
|
290
|
+
return [UNCHANGED, ""]
|
|
291
|
+
if holder.get_node_or_null(NodePath(path.get_concatenated_names())) == target:
|
|
292
|
+
return [UNCHANGED, ""]
|
|
293
|
+
var to: String = str(holder.get_path_to(target, unique))
|
|
294
|
+
if path.get_subname_count() > 0:
|
|
295
|
+
to += ":" + path.get_concatenated_subnames()
|
|
296
|
+
return [REWRITTEN, to]
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
## Whether [param path], held by the node at [param now], names no node.
|
|
300
|
+
func _names_nothing(path: NodePath, now: String) -> bool:
|
|
301
|
+
var holder: Node = _scene.root.get_node_or_null(now)
|
|
302
|
+
return holder != null and holder.get_node_or_null(NodePath(path.get_concatenated_names())) == null
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
## Where the node a path held by the node at [param now] names as %[param name] was: one marked
|
|
306
|
+
## unique in the same owner that had that name before the move, or a deleted one that had it.
|
|
307
|
+
func _unique_was(name: String, now: String) -> String:
|
|
308
|
+
var holder: Node = _scene.root.get_node_or_null(now)
|
|
309
|
+
if holder == null:
|
|
310
|
+
return ""
|
|
311
|
+
var scope: Node = holder
|
|
312
|
+
if holder.owner != null:
|
|
313
|
+
scope = holder.owner
|
|
314
|
+
var nodes: Array[Node] = [_scene.root]
|
|
315
|
+
while not nodes.is_empty():
|
|
316
|
+
var node: Node = nodes.pop_back()
|
|
317
|
+
nodes.append_array(node.get_children())
|
|
318
|
+
if node.unique_name_in_owner and node.owner == scope:
|
|
319
|
+
var was: String = moved(_scene.path_of(node), _back)
|
|
320
|
+
if was.get_file() == name:
|
|
321
|
+
return was
|
|
322
|
+
for old: String in _moves:
|
|
323
|
+
if str(_moves[old]).is_empty() and old.get_file() == name:
|
|
324
|
+
return old
|
|
325
|
+
return ""
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
## The path from the root reached by walking [param names] from [param start], or "" when they
|
|
329
|
+
## climb out of the scene.
|
|
330
|
+
static func _walked(start: String, names: PackedStringArray) -> String:
|
|
331
|
+
var parts: PackedStringArray = []
|
|
332
|
+
if start != ".":
|
|
333
|
+
parts = start.split("/")
|
|
334
|
+
for name: String in names:
|
|
335
|
+
if name == "..":
|
|
336
|
+
if parts.is_empty():
|
|
337
|
+
return ""
|
|
338
|
+
parts.remove_at(parts.size() - 1)
|
|
339
|
+
elif name != ".":
|
|
340
|
+
var _added: bool = parts.append(name)
|
|
341
|
+
return "." if parts.is_empty() else "/".join(parts)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
## Carries the moves into the tracks of the animations [param mixer], at [param now], plays, which
|
|
345
|
+
## name nodes from its root node. That is read once the mixer's own root_node is carried, so it is
|
|
346
|
+
## the node the tracks were written against. An AnimationTree playing an AnimationPlayer's
|
|
347
|
+
## animations leaves them to the player. A track in a file nothing here writes is named only when it
|
|
348
|
+
## names nothing since the move, as a value another file gives is.
|
|
349
|
+
func _tracks(mixer: AnimationMixer, now: String) -> void:
|
|
350
|
+
if mixer is AnimationTree:
|
|
351
|
+
var tree: AnimationTree = mixer
|
|
352
|
+
if tree.get_node_or_null(tree.anim_player) is AnimationPlayer:
|
|
353
|
+
return
|
|
354
|
+
var root: Node = mixer.get_node_or_null(mixer.root_node)
|
|
355
|
+
if root == null:
|
|
356
|
+
return
|
|
357
|
+
var root_now: String = _scene.path_of(root)
|
|
358
|
+
var root_was: String = moved(root_now, _back)
|
|
359
|
+
for library_name: StringName in mixer.get_animation_library_list():
|
|
360
|
+
var library: AnimationLibrary = mixer.get_animation_library(library_name)
|
|
361
|
+
for animation_name: StringName in library.get_animation_list():
|
|
362
|
+
var animation: Animation = library.get_animation(animation_name)
|
|
363
|
+
var home: Dictionary = _home_of(animation, library)
|
|
364
|
+
var why: String = home["why"]
|
|
365
|
+
# Carried once, from the first scene playing it. One kept in a file of its own that this call
|
|
366
|
+
# rewrote is held in _homes, so a later scene loading that file is given this same copy.
|
|
367
|
+
if _seen.has(animation):
|
|
368
|
+
continue
|
|
369
|
+
_seen[animation] = true
|
|
370
|
+
var named: String = (
|
|
371
|
+
str(animation_name)
|
|
372
|
+
if str(library_name).is_empty()
|
|
373
|
+
else "%s/%s" % [library_name, animation_name]
|
|
374
|
+
)
|
|
375
|
+
for track: int in animation.get_track_count():
|
|
376
|
+
if animation.track_is_imported(track):
|
|
377
|
+
continue
|
|
378
|
+
var path: NodePath = animation.track_get_path(track)
|
|
379
|
+
var after: Array = _after(path, root_was, root_now)
|
|
380
|
+
var record: Dictionary = {
|
|
381
|
+
"scenePath": _scene.path,
|
|
382
|
+
"nodePath": now,
|
|
383
|
+
"animation": named,
|
|
384
|
+
"track": track,
|
|
385
|
+
"from": str(path),
|
|
386
|
+
}
|
|
387
|
+
if home.get("theirs", false):
|
|
388
|
+
if after[0] == REWRITTEN and _names_nothing(path, root_now):
|
|
389
|
+
not_retargeted.append(_left(record, why))
|
|
390
|
+
elif after[0] == DELETED:
|
|
391
|
+
still_naming.append(record)
|
|
392
|
+
elif after[0] == REWRITTEN and not why.is_empty():
|
|
393
|
+
not_retargeted.append(_left(record, why))
|
|
394
|
+
elif after[0] == REWRITTEN:
|
|
395
|
+
var file: String = home["file"]
|
|
396
|
+
if file.is_empty():
|
|
397
|
+
_scene.reached_into(animation)
|
|
398
|
+
else:
|
|
399
|
+
_homes[file] = home["home"]
|
|
400
|
+
_outside.append([animation, track, path, record, file])
|
|
401
|
+
animation.track_set_path(track, NodePath(str(after[1])))
|
|
402
|
+
record["to"] = after[1]
|
|
403
|
+
retargeted.append(record)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
## Where an edit to [param animation], played from [param library], is saved, as {file, home, why,
|
|
407
|
+
## theirs}: file "" with the scene, or the file of its own it or its library is kept in, with home the
|
|
408
|
+
## resource saved there; why, when nothing written here would keep the edit; theirs, when that is
|
|
409
|
+
## because it is kept in another scene or an imported file, whose tracks are written in its terms.
|
|
410
|
+
func _home_of(animation: Animation, library: AnimationLibrary) -> Dictionary:
|
|
411
|
+
var file: String = animation.resource_path.get_slice("::", 0)
|
|
412
|
+
if file.is_empty() or file == _scene.path:
|
|
413
|
+
return {"file": "", "why": ""}
|
|
414
|
+
var home: Resource = animation
|
|
415
|
+
if animation.resource_path.contains("::"):
|
|
416
|
+
home = library if library.resource_path == file else null
|
|
417
|
+
if home == null or not FileAccess.file_exists(file) or FileAccess.file_exists(file + ".import"):
|
|
418
|
+
return {
|
|
419
|
+
"why": "The animation is kept in %s, and an edit here would not be saved there." % file,
|
|
420
|
+
"theirs": true,
|
|
421
|
+
}
|
|
422
|
+
if not Engine.is_editor_hint():
|
|
423
|
+
var script: Script = home.get_script()
|
|
424
|
+
if (script != null and not script.is_tool()) or HeadlessCheck.holds_scripted_embedded(home, {}):
|
|
425
|
+
return {
|
|
426
|
+
"why":
|
|
427
|
+
(
|
|
428
|
+
(
|
|
429
|
+
(
|
|
430
|
+
"%s holds a resource with a script that is not a tool, which a headless engine runs as "
|
|
431
|
+
+ "it loads the file. "
|
|
432
|
+
)
|
|
433
|
+
% file
|
|
434
|
+
)
|
|
435
|
+
+ HeadlessCheck.REMEDY
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
return {"file": file, "home": home, "why": ""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
uid://cgc8ac024d58g
|