gdharness 1.3.9 → 1.3.11
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 +4740 -3212
- package/build/godot/addons/gdharness_editor/animation_reads.gd +115 -4
- package/build/godot/addons/gdharness_editor/declared_values.gd +272 -0
- package/build/godot/addons/gdharness_editor/file_reads.gd +102 -18
- package/build/godot/addons/gdharness_editor/headless_check.gd +49 -2
- package/build/godot/addons/gdharness_editor/property_paths.gd +89 -16
- package/build/godot/addons/gdharness_editor/property_values.gd +180 -121
- package/build/godot/addons/gdharness_editor/reading.gd +51 -1
- package/build/godot/addons/gdharness_editor/scene_file.gd +77 -140
- package/build/godot/addons/gdharness_editor/scene_uids.gd +107 -0
- package/build/godot/addons/gdharness_editor/scene_uids.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/serialisation.gd +11 -22
- package/build/godot/addons/gdharness_editor/setting_reads.gd +121 -0
- package/build/godot/addons/gdharness_editor/setting_reads.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tool_executor.gd +28 -5
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +39 -20
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +49 -362
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +464 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +37 -0
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +221 -0
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +147 -492
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -0
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +4 -3
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +2 -1
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +79 -5
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +7 -30
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +183 -37
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +485 -0
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/value_shapes.gd +128 -1
- package/build/godot/addons/gdharness_runtime/declared_values.gd +272 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +51 -1
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +35 -3
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +185 -30
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +86 -79
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +17 -0
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +13 -4
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +309 -22
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +163 -20
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +49 -3
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +5 -2
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +146 -5
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +279 -11
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +27 -0
- package/build/godot/addons/gdharness_runtime/serialisation.gd +11 -22
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +128 -1
- package/build/godot/operations/animation_reads.gd +115 -4
- package/build/godot/operations/audio_buses.gd +93 -7
- package/build/godot/operations/classdb_queries.gd +119 -7
- package/build/godot/operations/declared_values.gd +272 -0
- package/build/godot/operations/dependencies.gd +154 -41
- package/build/godot/operations/editor_tools.gd +22 -14
- package/build/godot/operations/export_presets.gd +602 -4
- package/build/godot/operations/file_reads.gd +102 -18
- package/build/godot/operations/gdscript_authoring.gd +11 -1
- package/build/godot/operations/godot_operations.gd +16 -2
- package/build/godot/operations/import_pipeline.gd +20 -221
- package/build/godot/operations/input_actions.gd +108 -47
- package/build/godot/operations/project_config.gd +83 -69
- package/build/godot/operations/project_diagnostics.gd +4 -0
- package/build/godot/operations/project_validation.gd +266 -0
- package/build/godot/operations/property_paths.gd +89 -16
- package/build/godot/operations/property_values.gd +180 -121
- package/build/godot/operations/reading.gd +51 -1
- package/build/godot/operations/scene_connections.gd +222 -0
- package/build/godot/operations/serialisation.gd +11 -22
- package/build/godot/operations/setting_reads.gd +121 -0
- package/build/godot/operations/value_shapes.gd +128 -1
- package/build/godot/tests/listed.gd +73 -13
- package/build/keeper.js +1371 -706
- package/package.json +1 -1
|
@@ -16,15 +16,19 @@ const GdhSerialisation = preload("serialisation.gd")
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
## The nodes of [param scene_path] as {name, type, path, children}, with instanceOf and
|
|
19
|
-
## editableChildren on a node that instances another scene and
|
|
20
|
-
## [param include_properties] is set, the file's value or the default where it sets none
|
|
19
|
+
## editableChildren on a node that instances another scene and its stored properties when
|
|
20
|
+
## [param include_properties] is set, the file's value or the default where it sets none, and on a
|
|
21
|
+
## node another file makes setHere, the properties this file sets on it. Defaults the file leaves
|
|
22
|
+
## alone are counted as defaultsLeftOut rather than listed unless [param include_defaults] is set.
|
|
21
23
|
## [param depth] -1 reads every level.
|
|
22
|
-
static func scene_tree(
|
|
24
|
+
static func scene_tree(
|
|
25
|
+
scene_path: String, depth: int, include_properties: bool, include_defaults: bool
|
|
26
|
+
) -> Dictionary:
|
|
23
27
|
var laid: Dictionary = _laid(scene_path)
|
|
24
28
|
if not laid.get("ok", false):
|
|
25
29
|
return laid
|
|
26
30
|
var nodes: Dictionary = laid["nodes"]
|
|
27
|
-
return {"ok": true, "tree": _entry(nodes, ".", include_properties, depth, 0)}
|
|
31
|
+
return {"ok": true, "tree": _entry(nodes, ".", include_properties, include_defaults, depth, 0)}
|
|
28
32
|
|
|
29
33
|
|
|
30
34
|
## The node at [param node_path] in [param scene_path] as {ok, name, type, properties}, laid as
|
|
@@ -176,6 +180,7 @@ static func _lay(
|
|
|
176
180
|
continue
|
|
177
181
|
else:
|
|
178
182
|
nodes[path] = _made(str(state.get_node_type(index)))
|
|
183
|
+
nodes[path]["madeHere"] = loaded != null
|
|
179
184
|
if ids[index] >= 0:
|
|
180
185
|
nodes[path]["uniqueId"] = ids[index]
|
|
181
186
|
# Laid at the root with no scene handed in is a scene the one being read inherits, and the
|
|
@@ -202,10 +207,15 @@ static func _lay(
|
|
|
202
207
|
if not grouped.has(str(group)):
|
|
203
208
|
grouped.append(str(group))
|
|
204
209
|
var held: Dictionary = nodes[path]["properties"]
|
|
210
|
+
# What the file being read sets, kept apart for a node another file makes, whose values read
|
|
211
|
+
# the same whether this scene changed them or that one gave them.
|
|
212
|
+
var laid: Dictionary = nodes[path]
|
|
213
|
+
var set_here: Array = laid.get_or_add("setHere", [])
|
|
205
214
|
for property: int in state.get_node_property_count(index):
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
)
|
|
215
|
+
var named: String = str(state.get_node_property_name(index, property))
|
|
216
|
+
held[named] = state.get_node_property_value(index, property)
|
|
217
|
+
if loaded != null and not set_here.has(named):
|
|
218
|
+
set_here.append(named)
|
|
209
219
|
# Only the scene being read and the scenes it inherits share its root, the owner whose editable
|
|
210
220
|
# instances a write here sets; a file instanced below makes its own owner's editable.
|
|
211
221
|
if prefix == ".":
|
|
@@ -321,7 +331,12 @@ static func _joined(prefix: String, given: String) -> String:
|
|
|
321
331
|
|
|
322
332
|
|
|
323
333
|
static func _entry(
|
|
324
|
-
nodes: Dictionary,
|
|
334
|
+
nodes: Dictionary,
|
|
335
|
+
path: String,
|
|
336
|
+
include_properties: bool,
|
|
337
|
+
include_defaults: bool,
|
|
338
|
+
depth: int,
|
|
339
|
+
current_depth: int
|
|
325
340
|
) -> Dictionary:
|
|
326
341
|
var node: Dictionary = nodes[path]
|
|
327
342
|
var children: Array[Dictionary] = []
|
|
@@ -344,7 +359,21 @@ static func _entry(
|
|
|
344
359
|
data["groups"] = sorted
|
|
345
360
|
if include_properties:
|
|
346
361
|
var given: Dictionary = node["properties"]
|
|
347
|
-
|
|
362
|
+
var stored: Dictionary = _stored(str(node["type"]), given, include_defaults)
|
|
363
|
+
data["properties"] = stored["properties"]
|
|
364
|
+
if stored["left_out"] > 0:
|
|
365
|
+
data["defaultsLeftOut"] = stored["left_out"]
|
|
366
|
+
var cells: int = _cell_count(str(node["type"]), given)
|
|
367
|
+
if cells >= 0:
|
|
368
|
+
data["cellCount"] = cells
|
|
369
|
+
data["cellsListedBy"] = (
|
|
370
|
+
"scene_node get with nodePath %s, which lists them as set_tilemap_cells takes them" % path
|
|
371
|
+
)
|
|
372
|
+
if not node.get("madeHere", false):
|
|
373
|
+
var kept: Array = node.get("setHere", [])
|
|
374
|
+
var set_here: Array = kept.duplicate()
|
|
375
|
+
set_here.sort()
|
|
376
|
+
data["setHere"] = set_here
|
|
348
377
|
var below: Array[String] = node["children"]
|
|
349
378
|
# Counted rather than left as an empty list, which reads as a node with no children.
|
|
350
379
|
if depth >= 0 and current_depth >= depth:
|
|
@@ -352,19 +381,56 @@ static func _entry(
|
|
|
352
381
|
data["childrenNotListed"] = below.size()
|
|
353
382
|
return data
|
|
354
383
|
for child: String in below:
|
|
355
|
-
children.append(_entry(nodes, child, include_properties, depth, current_depth + 1))
|
|
384
|
+
children.append(_entry(nodes, child, include_properties, include_defaults, depth, current_depth + 1))
|
|
356
385
|
return data
|
|
357
386
|
|
|
358
387
|
|
|
388
|
+
## How many cells a TileMapLayer or TileMap of [param type] holds under the values [param given], or
|
|
389
|
+
## -1 when it is another node or the file sets none of its cells. The file keeps them as packed bytes,
|
|
390
|
+
## which a read of the tree answered with alone, so a caller decoded the base64 to learn what was
|
|
391
|
+
## painted. Only cells the file sets are counted: an instance's come from its own scene.
|
|
392
|
+
static func _cell_count(type: String, given: Dictionary) -> int:
|
|
393
|
+
if not ClassDB.class_exists(type) or not ClassDB.can_instantiate(type):
|
|
394
|
+
return -1
|
|
395
|
+
var single: bool = ClassDB.is_parent_class(type, "TileMapLayer")
|
|
396
|
+
if not single and not ClassDB.is_parent_class(type, "TileMap"):
|
|
397
|
+
return -1
|
|
398
|
+
var painted: bool = false
|
|
399
|
+
for property: Variant in given:
|
|
400
|
+
var named: String = str(property)
|
|
401
|
+
if named == "tile_map_data" or (named.begins_with("layer_") and named.ends_with("/tile_data")):
|
|
402
|
+
painted = true
|
|
403
|
+
if not painted:
|
|
404
|
+
return -1
|
|
405
|
+
var bare: Node = ClassDB.instantiate(type)
|
|
406
|
+
for property: Variant in given:
|
|
407
|
+
if property != "script":
|
|
408
|
+
bare.set(str(property), given[property])
|
|
409
|
+
var count: int = 0
|
|
410
|
+
if single:
|
|
411
|
+
count = (bare as TileMapLayer).get_used_cells().size()
|
|
412
|
+
else:
|
|
413
|
+
var layered: TileMap = bare as TileMap
|
|
414
|
+
for layer: int in layered.get_layers_count():
|
|
415
|
+
count += layered.get_used_cells(layer).size()
|
|
416
|
+
bare.free()
|
|
417
|
+
return count
|
|
418
|
+
|
|
419
|
+
|
|
359
420
|
## Every property a node of [param type] stores, with the value [param given] sets or the default,
|
|
360
|
-
## read as a node holding those values would answer them.
|
|
421
|
+
## read as a node holding those values would answer them, as {properties, left_out}.
|
|
361
422
|
##
|
|
362
423
|
## The engine's half is read off a bare node of the class with the file's values set on it, which
|
|
363
424
|
## runs no script: what a node lists depends on its values (a thread group's settings appear only
|
|
364
425
|
## once it has one) and some setters set others (a Control's anchors preset sets how it grows), so
|
|
365
426
|
## the class's defaults and the file's entries alone answered neither. The script's half is its
|
|
366
427
|
## declared defaults under the file's values.
|
|
367
|
-
|
|
428
|
+
##
|
|
429
|
+
## Without [param include_defaults], properties keeps only what the file sets and what differs from
|
|
430
|
+
## a node of the class holding none of its values, and left_out counts the rest. Every Control
|
|
431
|
+
## carries a hundred properties, and ten of them came to 57 KB of defaults around the few values a
|
|
432
|
+
## menu scene set.
|
|
433
|
+
static func _stored(type: String, given: Dictionary, include_defaults: bool) -> Dictionary:
|
|
368
434
|
var attached: Variant = given.get("script")
|
|
369
435
|
var script: Script = attached if attached is Script else null
|
|
370
436
|
var scripted: Dictionary = {}
|
|
@@ -374,8 +440,10 @@ static func _stored(type: String, given: Dictionary) -> Dictionary:
|
|
|
374
440
|
if not property.is_empty() and GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
|
|
375
441
|
scripted[property] = script.get_property_default_value(property)
|
|
376
442
|
var values: Dictionary = {}
|
|
443
|
+
var defaults: Dictionary = {}
|
|
377
444
|
if ClassDB.class_exists(type) and ClassDB.can_instantiate(type):
|
|
378
445
|
var bare: Object = ClassDB.instantiate(type)
|
|
446
|
+
var plain: Object = ClassDB.instantiate(type)
|
|
379
447
|
for property: Variant in given:
|
|
380
448
|
if property != "script" and not scripted.has(property):
|
|
381
449
|
bare.set(str(property), given[property])
|
|
@@ -383,18 +451,30 @@ static func _stored(type: String, given: Dictionary) -> Dictionary:
|
|
|
383
451
|
var property: String = str(declared.get("name", ""))
|
|
384
452
|
if not property.is_empty() and GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
|
|
385
453
|
values[property] = bare.get(property)
|
|
386
|
-
|
|
387
|
-
|
|
454
|
+
defaults[property] = plain.get(property)
|
|
455
|
+
for made: Object in [bare, plain]:
|
|
456
|
+
if made is Node:
|
|
457
|
+
made.free()
|
|
388
458
|
for property: String in scripted:
|
|
389
459
|
values[property] = scripted[property]
|
|
460
|
+
defaults[property] = scripted[property]
|
|
390
461
|
# Given last, which keeps what a bare node cannot hold: the script itself, and a path to a node
|
|
391
462
|
# for a property that holds the node once it is in a tree.
|
|
392
463
|
values.merge(given, true)
|
|
393
464
|
var serialiser: GdhSerialisation = GdhSerialisation.new()
|
|
394
465
|
var answered: Dictionary = {}
|
|
466
|
+
var left_out: int = 0
|
|
395
467
|
for property: Variant in values:
|
|
468
|
+
var as_default: bool = (
|
|
469
|
+
not given.has(property)
|
|
470
|
+
and defaults.has(property)
|
|
471
|
+
and GdhSame.same(values[property], defaults[property])
|
|
472
|
+
)
|
|
473
|
+
if as_default and not include_defaults:
|
|
474
|
+
left_out += 1
|
|
475
|
+
continue
|
|
396
476
|
answered[property] = serialiser.serialize_value(values[property])
|
|
397
|
-
return answered
|
|
477
|
+
return {"properties": answered, "left_out": left_out}
|
|
398
478
|
|
|
399
479
|
|
|
400
480
|
## What a resource file holds, in the form modify takes, so a value read here can be written back.
|
|
@@ -407,12 +487,16 @@ static func resource(res_path: String, include_defaults: bool) -> Dictionary:
|
|
|
407
487
|
var file: String = res_path.get_slice("::", 0)
|
|
408
488
|
if not FileAccess.file_exists(file):
|
|
409
489
|
return {"ok": false, "error": "No resource at " + file}
|
|
410
|
-
var
|
|
411
|
-
if
|
|
490
|
+
var whole: Resource = ResourceLoader.load(file, "", ResourceLoader.CACHE_MODE_IGNORE)
|
|
491
|
+
if whole == null:
|
|
412
492
|
return {"ok": false, "error": "%s did not load as a resource." % file}
|
|
493
|
+
# The file's resource is held until the read is done, since what it keeps can depend on it: a
|
|
494
|
+
# TileSet that goes unlinks its sources, and an atlas source with no TileSet lists none of its
|
|
495
|
+
# tiles' physics, navigation or custom data.
|
|
496
|
+
var loaded: Resource = whole
|
|
413
497
|
if file != res_path:
|
|
414
498
|
var id: String = res_path.trim_prefix(file + "::")
|
|
415
|
-
var inner: Resource = _kept_in(
|
|
499
|
+
var inner: Resource = _kept_in(whole, file, id, {})
|
|
416
500
|
if inner == null:
|
|
417
501
|
return {"ok": false, "error": "%s keeps no resource with the id %s." % [file, id]}
|
|
418
502
|
loaded = inner
|
|
@@ -505,7 +505,17 @@ func _add_constant(lines: Array[String], mod: Dictionary, placed: Array[Dictiona
|
|
|
505
505
|
or line.begins_with("class ")
|
|
506
506
|
):
|
|
507
507
|
break
|
|
508
|
-
|
|
508
|
+
after = _past_class_doc(lines, after)
|
|
509
|
+
# Set apart from the header or the signals above and from what follows, as the style guide lays
|
|
510
|
+
# out its groups; one below the constants already there joins them.
|
|
511
|
+
var block: Array[String] = [const_line]
|
|
512
|
+
var above: String = lines[after - 1].strip_edges() if after > 0 else ""
|
|
513
|
+
var below: String = lines[after].strip_edges() if after < lines.size() else ""
|
|
514
|
+
if not above.is_empty() and not GdhPatterns.without_annotations(above).begins_with("const "):
|
|
515
|
+
block.push_front("")
|
|
516
|
+
if not below.is_empty() and not GdhPatterns.without_annotations(below).begins_with("const "):
|
|
517
|
+
block.push_back("")
|
|
518
|
+
return after + block.find(const_line) if _insert(lines, after, block, placed) else -1
|
|
509
519
|
|
|
510
520
|
|
|
511
521
|
# Inserts the declaration and answers with its zero-based index, or -1 for one that could not be placed.
|
|
@@ -24,6 +24,7 @@ const GdhFileReads = preload("file_reads.gd")
|
|
|
24
24
|
const GdhGdscriptAnalysis = preload("gdscript_analysis.gd")
|
|
25
25
|
const GdhGdscriptAuthoring = preload("gdscript_authoring.gd")
|
|
26
26
|
const GdhImportPipeline = preload("import_pipeline.gd")
|
|
27
|
+
const GdhProjectValidation = preload("project_validation.gd")
|
|
27
28
|
const GdhInputActions = preload("input_actions.gd")
|
|
28
29
|
const GdhLog = preload("logger.gd")
|
|
29
30
|
const GdhPlugins = preload("plugins.gd")
|
|
@@ -51,6 +52,8 @@ const BEGUN: String = "[gdharness] begun"
|
|
|
51
52
|
const AFTER_THE_AUTOLOADS: Array[String] = [
|
|
52
53
|
"check_scripts",
|
|
53
54
|
"create_script",
|
|
55
|
+
# An exporter made in _init, before an editor's main loop has started, crashed the engine.
|
|
56
|
+
"export_options",
|
|
54
57
|
"modify_script",
|
|
55
58
|
"read_animation_states",
|
|
56
59
|
"read_animations",
|
|
@@ -188,7 +191,8 @@ func _run(operation: String, params: Dictionary) -> Dictionary:
|
|
|
188
191
|
GdhFileReads.scene_tree(
|
|
189
192
|
str(params.get("scene_path", "")),
|
|
190
193
|
GdhRead.as_int(params.get("depth", -1), -1),
|
|
191
|
-
GdhRead.as_bool(params.get("include_properties", false))
|
|
194
|
+
GdhRead.as_bool(params.get("include_properties", false)),
|
|
195
|
+
GdhRead.as_bool(params.get("include_defaults", false))
|
|
192
196
|
)
|
|
193
197
|
)
|
|
194
198
|
"read_resource":
|
|
@@ -229,8 +233,14 @@ func _run(operation: String, params: Dictionary) -> Dictionary:
|
|
|
229
233
|
payload = GdhExportPresets.new(_log).list_export_presets(params)
|
|
230
234
|
"add_export_preset":
|
|
231
235
|
payload = GdhExportPresets.new(_log).add_export_preset(params)
|
|
236
|
+
"set_export_preset":
|
|
237
|
+
payload = GdhExportPresets.new(_log).set_export_preset(params)
|
|
238
|
+
"remove_export_preset":
|
|
239
|
+
payload = GdhExportPresets.new(_log).remove_export_preset(params)
|
|
240
|
+
"export_options":
|
|
241
|
+
payload = GdhExportPresets.new(_log).export_options(params)
|
|
232
242
|
"validate_project":
|
|
233
|
-
payload =
|
|
243
|
+
payload = GdhProjectValidation.new(_log).validate_project(params)
|
|
234
244
|
|
|
235
245
|
# Dependencies and project inspection
|
|
236
246
|
"get_dependencies":
|
|
@@ -295,6 +305,10 @@ func _run(operation: String, params: Dictionary) -> Dictionary:
|
|
|
295
305
|
payload = GdhAudioBuses.new(_log).set_audio_bus_effect(params)
|
|
296
306
|
"set_audio_bus_volume":
|
|
297
307
|
payload = GdhAudioBuses.new(_log).set_audio_bus_volume(params)
|
|
308
|
+
"rename_audio_bus":
|
|
309
|
+
payload = GdhAudioBuses.new(_log).rename_audio_bus(params)
|
|
310
|
+
"set_audio_bus_send":
|
|
311
|
+
payload = GdhAudioBuses.new(_log).set_audio_bus_send(params)
|
|
298
312
|
|
|
299
313
|
# ClassDB
|
|
300
314
|
"query_classes":
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const GdhDependencies = preload("dependencies.gd")
|
|
4
|
-
const GdhExportPresets = preload("export_presets.gd")
|
|
5
|
-
const GdhScriptCheck = preload("script_check.gd")
|
|
6
3
|
const GdhRead = preload("reading.gd")
|
|
7
4
|
const GdhSerialisation = preload("serialisation.gd")
|
|
8
5
|
const GdhFileWalk = preload("file_walk.gd")
|
|
@@ -40,7 +37,14 @@ const IMPORTABLE_EXTENSIONS: Array[String] = [
|
|
|
40
37
|
"fbx",
|
|
41
38
|
"obj",
|
|
42
39
|
"dae",
|
|
40
|
+
"csv",
|
|
41
|
+
"glsl",
|
|
43
42
|
]
|
|
43
|
+
## What the engine imports through Blender, which it finds only through an editor setting. With the
|
|
44
|
+
## setting unset, a headless import stops at the first of these and imports nothing after it.
|
|
45
|
+
const BLENDER_EXTENSIONS: Array[String] = ["blend"]
|
|
46
|
+
const BLENDER_SETTING: String = "filesystem/import/blender/blender_path"
|
|
47
|
+
const BLENDER_ENABLED: String = "filesystem/import/blender/enabled"
|
|
44
48
|
## The format version each importer writes as `importer_version` where it is above 0, which the
|
|
45
49
|
## editor reimports anything older than. Read off sidecars 4.7.2 writes: scene and animation_library
|
|
46
50
|
## write 1, and the texture importers write none. A case imports one fresh and compares, so an engine
|
|
@@ -348,224 +352,6 @@ static func _all_ints(held: Variant, keys: bool) -> bool:
|
|
|
348
352
|
return true
|
|
349
353
|
|
|
350
354
|
|
|
351
|
-
# Validate project for export
|
|
352
|
-
func validate_project(params: Dictionary) -> Dictionary:
|
|
353
|
-
var preset_name: String = str(params.get("preset", ""))
|
|
354
|
-
var include_suggestions: bool = GdhRead.as_bool(params.get("include_suggestions", true), true)
|
|
355
|
-
|
|
356
|
-
_log.info("Validating project" + (" for preset: " + preset_name if not preset_name.is_empty() else ""))
|
|
357
|
-
|
|
358
|
-
var issues: Array[Dictionary] = []
|
|
359
|
-
var warnings: Array[Dictionary] = []
|
|
360
|
-
var checks_performed: Array[String] = []
|
|
361
|
-
|
|
362
|
-
checks_performed.append("project_file")
|
|
363
|
-
if not FileAccess.file_exists("res://project.godot"):
|
|
364
|
-
issues.append(
|
|
365
|
-
_finding(
|
|
366
|
-
"error",
|
|
367
|
-
"project_file",
|
|
368
|
-
"project.godot not found",
|
|
369
|
-
"Ensure you are running this from a valid Godot project directory",
|
|
370
|
-
include_suggestions
|
|
371
|
-
)
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
checks_performed.append("main_scene")
|
|
375
|
-
var main_scene: String = str(ProjectSettings.get_setting("application/run/main_scene", ""))
|
|
376
|
-
if main_scene.is_empty():
|
|
377
|
-
issues.append(
|
|
378
|
-
_finding(
|
|
379
|
-
"error",
|
|
380
|
-
"main_scene",
|
|
381
|
-
"No main scene set",
|
|
382
|
-
"Set a main scene in Project Settings > Application > Run > Main Scene",
|
|
383
|
-
include_suggestions
|
|
384
|
-
)
|
|
385
|
-
)
|
|
386
|
-
elif not GdhFileWalk.missing_reason(main_scene).is_empty():
|
|
387
|
-
issues.append(
|
|
388
|
-
_finding(
|
|
389
|
-
"error",
|
|
390
|
-
"main_scene",
|
|
391
|
-
"Main scene " + GdhFileWalk.missing_reason(main_scene),
|
|
392
|
-
"Update the main scene setting or create the missing scene file",
|
|
393
|
-
include_suggestions
|
|
394
|
-
)
|
|
395
|
-
)
|
|
396
|
-
|
|
397
|
-
# A file never imported is on disk, so the reference checks pass it, and still nothing loads it:
|
|
398
|
-
# a scene using one fails to load in a run or an export.
|
|
399
|
-
checks_performed.append("imports")
|
|
400
|
-
var never: Array[String] = []
|
|
401
|
-
for path: String in _files.find_files_with_extensions("res://", IMPORTABLE_EXTENSIONS):
|
|
402
|
-
if not FileAccess.file_exists(path + ".import"):
|
|
403
|
-
never.append(path)
|
|
404
|
-
if not never.is_empty():
|
|
405
|
-
never.sort()
|
|
406
|
-
warnings.append(
|
|
407
|
-
_finding(
|
|
408
|
-
"warning",
|
|
409
|
-
"imports",
|
|
410
|
-
never_imported_line(never),
|
|
411
|
-
"Run project_import reimport, or open the project in the editor, whose scan imports them",
|
|
412
|
-
include_suggestions
|
|
413
|
-
)
|
|
414
|
-
)
|
|
415
|
-
|
|
416
|
-
checks_performed.append("export_presets")
|
|
417
|
-
if not FileAccess.file_exists("res://export_presets.cfg"):
|
|
418
|
-
warnings.append(
|
|
419
|
-
_finding(
|
|
420
|
-
"warning",
|
|
421
|
-
"export_presets",
|
|
422
|
-
"No export presets configured",
|
|
423
|
-
"Add a preset with project_export add_preset, or in the editor: Project > Export",
|
|
424
|
-
include_suggestions
|
|
425
|
-
)
|
|
426
|
-
)
|
|
427
|
-
|
|
428
|
-
# The preset asked about is looked for rather than only logged: validating "for" a preset that
|
|
429
|
-
# does not exist answered valid, as though it had been checked.
|
|
430
|
-
if not preset_name.is_empty():
|
|
431
|
-
checks_performed.append("export_preset")
|
|
432
|
-
var named: Array[String] = GdhExportPresets.names()
|
|
433
|
-
if preset_name not in named:
|
|
434
|
-
issues.append(
|
|
435
|
-
_finding(
|
|
436
|
-
"error",
|
|
437
|
-
"export_preset",
|
|
438
|
-
(
|
|
439
|
-
"No export preset is named "
|
|
440
|
-
+ preset_name
|
|
441
|
-
+ (
|
|
442
|
-
": the project has none"
|
|
443
|
-
if named.is_empty()
|
|
444
|
-
else ": the presets are " + ", ".join(named)
|
|
445
|
-
)
|
|
446
|
-
),
|
|
447
|
-
"Pass one of the preset names, or add the preset with project_export add_preset",
|
|
448
|
-
include_suggestions
|
|
449
|
-
)
|
|
450
|
-
)
|
|
451
|
-
|
|
452
|
-
checks_performed.append("icon")
|
|
453
|
-
var icon_path: String = str(ProjectSettings.get_setting("application/config/icon", ""))
|
|
454
|
-
if icon_path.is_empty():
|
|
455
|
-
warnings.append(
|
|
456
|
-
_finding(
|
|
457
|
-
"warning",
|
|
458
|
-
"icon",
|
|
459
|
-
"No application icon set",
|
|
460
|
-
"Set an icon in Project Settings > Application > Config > Icon",
|
|
461
|
-
include_suggestions
|
|
462
|
-
)
|
|
463
|
-
)
|
|
464
|
-
elif not FileAccess.file_exists(icon_path):
|
|
465
|
-
warnings.append(
|
|
466
|
-
_finding(
|
|
467
|
-
"warning",
|
|
468
|
-
"icon",
|
|
469
|
-
"Icon file does not exist: " + icon_path,
|
|
470
|
-
"Update the icon path or add the missing icon file",
|
|
471
|
-
include_suggestions
|
|
472
|
-
)
|
|
473
|
-
)
|
|
474
|
-
|
|
475
|
-
checks_performed.append("project_name")
|
|
476
|
-
var project_name: String = str(ProjectSettings.get_setting("application/config/name", ""))
|
|
477
|
-
if project_name.is_empty():
|
|
478
|
-
warnings.append(
|
|
479
|
-
_finding(
|
|
480
|
-
"warning",
|
|
481
|
-
"project_name",
|
|
482
|
-
"No project name set",
|
|
483
|
-
"Set a project name in Project Settings > Application > Config > Name",
|
|
484
|
-
include_suggestions
|
|
485
|
-
)
|
|
486
|
-
)
|
|
487
|
-
|
|
488
|
-
checks_performed.append("scripts")
|
|
489
|
-
var script_files: Array[String] = _files.find_files_with_extensions("res://", ["gd"])
|
|
490
|
-
var scripts_checked: int = 0
|
|
491
|
-
var script_issues: Array[Dictionary] = []
|
|
492
|
-
|
|
493
|
-
# A hundred scripts is enough to say whether the project is tidy without a large one
|
|
494
|
-
# turning validation into a full read of its source tree.
|
|
495
|
-
for script_path: String in script_files.slice(0, 100):
|
|
496
|
-
scripts_checked += 1
|
|
497
|
-
var file: FileAccess = FileAccess.open(script_path, FileAccess.READ)
|
|
498
|
-
if file:
|
|
499
|
-
var content: String = file.get_as_text()
|
|
500
|
-
file.close()
|
|
501
|
-
|
|
502
|
-
if "# TODO" in content or "# FIXME" in content:
|
|
503
|
-
script_issues.append({"path": script_path, "issue": "Contains TODO/FIXME comments"})
|
|
504
|
-
if "pass # TODO" in content:
|
|
505
|
-
script_issues.append({"path": script_path, "issue": "Contains unimplemented functions"})
|
|
506
|
-
|
|
507
|
-
# Compiled by the server and handed in, as for the health check: a script the engine refuses
|
|
508
|
-
# stops the export's game at the first scene using it, which the comment check above never saw.
|
|
509
|
-
var compiled: Dictionary = params.get("compiled", {})
|
|
510
|
-
if compiled.has("checked"):
|
|
511
|
-
checks_performed.append("compiles")
|
|
512
|
-
var failures: Array = compiled.get("failures", [])
|
|
513
|
-
for failure: Dictionary in failures:
|
|
514
|
-
issues.append(
|
|
515
|
-
_finding(
|
|
516
|
-
"error",
|
|
517
|
-
"compiles",
|
|
518
|
-
GdhScriptCheck.failure_line(failure),
|
|
519
|
-
"script_diagnostics gives every error in it with its line",
|
|
520
|
-
include_suggestions
|
|
521
|
-
)
|
|
522
|
-
)
|
|
523
|
-
|
|
524
|
-
checks_performed.append("references")
|
|
525
|
-
for entry: Dictionary in GdhDependencies.new(_log).missing_references():
|
|
526
|
-
issues.append(
|
|
527
|
-
_finding(
|
|
528
|
-
"error",
|
|
529
|
-
"references",
|
|
530
|
-
GdhDependencies.missing_line(entry),
|
|
531
|
-
"Restore the file, or point the reference at the file that replaced it",
|
|
532
|
-
include_suggestions
|
|
533
|
-
)
|
|
534
|
-
)
|
|
535
|
-
|
|
536
|
-
if script_issues.size() > 0:
|
|
537
|
-
var warning: Dictionary = _finding(
|
|
538
|
-
"warning",
|
|
539
|
-
"scripts",
|
|
540
|
-
str(script_issues.size()) + " script issues found",
|
|
541
|
-
"Review and resolve TODO/FIXME items before release",
|
|
542
|
-
include_suggestions
|
|
543
|
-
)
|
|
544
|
-
warning["details"] = script_issues.slice(0, 5)
|
|
545
|
-
warnings.append(warning)
|
|
546
|
-
|
|
547
|
-
return {
|
|
548
|
-
"valid": issues.is_empty(),
|
|
549
|
-
"issues": issues,
|
|
550
|
-
"warnings": warnings,
|
|
551
|
-
"checks_performed": checks_performed,
|
|
552
|
-
"scripts_checked": scripts_checked,
|
|
553
|
-
# Said beside the count, so a project of four hundred scripts is not read as checked whole.
|
|
554
|
-
"scripts_total": script_files.size(),
|
|
555
|
-
"issue_count": issues.size(),
|
|
556
|
-
"warning_count": warnings.size()
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
func _finding(
|
|
561
|
-
type: String, check: String, message: String, suggestion: String, include_suggestion: bool
|
|
562
|
-
) -> Dictionary:
|
|
563
|
-
var finding: Dictionary = {"type": type, "check": check, "message": message}
|
|
564
|
-
if include_suggestion:
|
|
565
|
-
finding["suggestion"] = suggestion
|
|
566
|
-
return finding
|
|
567
|
-
|
|
568
|
-
|
|
569
355
|
func _tally(summary: Dictionary, status: Dictionary) -> void:
|
|
570
356
|
summary["total"] += 1
|
|
571
357
|
var state: String = status["status"]
|
|
@@ -662,6 +448,19 @@ func _never_imported(resource_path: String) -> String:
|
|
|
662
448
|
var extension: String = resource_path.get_extension().to_lower()
|
|
663
449
|
if extension in IMPORTABLE_EXTENSIONS:
|
|
664
450
|
return ""
|
|
451
|
+
if extension in BLENDER_EXTENSIONS:
|
|
452
|
+
if not ProjectSettings.get_setting(BLENDER_ENABLED, true):
|
|
453
|
+
return "the project sets " + BLENDER_ENABLED + " to false, so the engine leaves them unimported"
|
|
454
|
+
return (
|
|
455
|
+
"the engine imports ."
|
|
456
|
+
+ extension
|
|
457
|
+
+ " files through Blender, which it finds through the editor setting "
|
|
458
|
+
+ BLENDER_SETTING
|
|
459
|
+
+ ". An editor with it set imports them on its scan; an import pass with it unset stops at"
|
|
460
|
+
+ " the first one and imports nothing, and setting "
|
|
461
|
+
+ BLENDER_ENABLED
|
|
462
|
+
+ " to false has the engine leave them unimported"
|
|
463
|
+
)
|
|
665
464
|
# Loadable without a sidecar is what a scene, a script or a saved resource is: read as it is.
|
|
666
465
|
if ResourceLoader.exists(resource_path):
|
|
667
466
|
return "the engine loads ." + extension + " files as they are, without importing them"
|