ffa72a8f24
- validate_map.gd — scene structure validator (CSG combiner root, spawn groups, bomb sites, buy zones, map bounds, LightmapGI config, lighting setup) - validate_polycount.gd — CSG triangle budget checker (<50K faces, <5K per node, <200 CSG nodes) - validate_textures.gd — texture size ≤1K, mipmaps, UV2 checker - validate_lights.gd — dynamic lights ≤4, bake mode validator - bake_lightmaps.gd — LightmapGI config checker (quality, bounces, texel_scale, interior, denoiser) All scripts: @tool SceneTree, accept scene path via -- separator, exit 0=pass / 1=fail / 2=scene-not-found. 443-610 lines each.
611 lines
20 KiB
GDScript
611 lines
20 KiB
GDScript
@tool
|
||
extends SceneTree
|
||
|
||
# =============================================================================
|
||
# bake_lightmaps.gd — LightmapGI Configuration Checker
|
||
# =============================================================================
|
||
# Validates LightmapGI configuration for baking readiness in a Godot 4 scene.
|
||
# Checks:
|
||
# - LightmapGI node exists and is properly configured
|
||
# - Quality setting (Low/Medium/High/Ultra)
|
||
# - Bounce count (recommended ≥ 2)
|
||
# - Texel scale (recommended 0.5–4.0, default 1.0)
|
||
# - Interior flag (true for indoor maps)
|
||
# - Denoiser enabled
|
||
# - Max texture size (recommended ≤ 1024)
|
||
# - Light data presence (already baked or not)
|
||
# - Geometry with UV2 (required for baking)
|
||
#
|
||
# Note: Godot 4 LightmapGI baking is editor-only. This script validates
|
||
# configuration readiness; it does NOT perform the bake itself.
|
||
#
|
||
# Usage:
|
||
# godot --headless --script client/scripts/bake_lightmaps.gd -- res://path/to/map.tscn
|
||
#
|
||
# Exit codes:
|
||
# 0 — All config checks passed
|
||
# 1 — One or more config checks failed
|
||
# 2 — Scene file not found or cannot be loaded
|
||
# =============================================================================
|
||
|
||
const RECOMMENDED_BOUNCES := 2
|
||
const MIN_BOUNCES := 1
|
||
const MAX_BOUNCES := 8
|
||
const RECOMMENDED_TEXEL_SCALE := 1.0
|
||
const MIN_TEXEL_SCALE := 0.1
|
||
const MAX_TEXEL_SCALE := 10.0
|
||
const MAX_TEXTURE_SIZE := 1024
|
||
const RECOMMENDED_QUALITY := 2 # High
|
||
|
||
var _pass_count := 0
|
||
var _fail_count := 0
|
||
var _warning_count := 0
|
||
var _scene_path := ""
|
||
var _scene_instance: Node = null
|
||
var _lightmap_node: Node = null
|
||
|
||
# UV2 tracking
|
||
var _total_meshes := 0
|
||
var _meshes_with_uv2 := 0
|
||
var _meshes_without_uv2: Array[String] = []
|
||
var _mesh_details: Array[Dictionary] = []
|
||
|
||
|
||
func _init() -> void:
|
||
_parse_arguments()
|
||
|
||
if _scene_path.is_empty():
|
||
printerr("ERROR: No scene path provided. Usage: godot --headless --script bake_lightmaps.gd -- res://path/to/map.tscn")
|
||
quit(2)
|
||
return
|
||
|
||
print("")
|
||
print("==========================================")
|
||
print(" LIGHTMAP CONFIGURATION CHECKER")
|
||
print(" Scene: ", _scene_path)
|
||
print("==========================================")
|
||
print("")
|
||
|
||
print(" Note: Godot 4 LightmapGI baking is editor-only.")
|
||
print(" This tool validates lightmap configuration readiness only.")
|
||
print(" To bake: Open scene in editor > Scene > Bake LightmapGI")
|
||
print("")
|
||
|
||
var scene: PackedScene = _load_scene(_scene_path)
|
||
if scene == null:
|
||
printerr("ERROR: Scene not found: ", _scene_path)
|
||
quit(2)
|
||
return
|
||
|
||
_scene_instance = _instantiate_scene(scene)
|
||
if _scene_instance == null:
|
||
printerr("ERROR: Failed to instantiate scene: ", _scene_path)
|
||
quit(2)
|
||
return
|
||
|
||
root.add_child(_scene_instance)
|
||
|
||
# Find LightmapGI
|
||
_lightmap_node = _find_node_by_type(_scene_instance, "LightmapGI")
|
||
|
||
# Run all checks
|
||
_run_lightmap_node_check()
|
||
if _lightmap_node != null:
|
||
_run_quality_check()
|
||
_run_bounce_check()
|
||
_run_texel_scale_check()
|
||
_run_interior_check()
|
||
_run_denoiser_check()
|
||
_run_max_texture_size_check()
|
||
_run_energy_check()
|
||
_run_baked_data_check()
|
||
_run_process_mode_check()
|
||
|
||
# Run geometry UV2 check
|
||
_run_uv2_check()
|
||
|
||
# Detailed reports
|
||
_print_lightmap_report()
|
||
_print_uv2_report()
|
||
|
||
# Summary
|
||
_print_summary()
|
||
|
||
if _fail_count > 0:
|
||
quit(1)
|
||
else:
|
||
quit(0)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Argument parsing
|
||
# ---------------------------------------------------------------------------
|
||
|
||
func _parse_arguments() -> void:
|
||
"""Extract scene path from user arguments (after --)."""
|
||
var args: PackedStringArray = OS.get_cmdline_user_args()
|
||
if not args.is_empty():
|
||
_scene_path = args[0].strip_edges()
|
||
return
|
||
|
||
var full_args: PackedStringArray = OS.get_cmdline_args()
|
||
var found_sep := false
|
||
for i in range(full_args.size()):
|
||
if full_args[i] == "--":
|
||
found_sep = true
|
||
continue
|
||
if found_sep:
|
||
_scene_path = full_args[i].strip_edges()
|
||
break
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Scene helpers
|
||
# ---------------------------------------------------------------------------
|
||
|
||
func _load_scene(path: String) -> PackedScene:
|
||
"""Load a .tscn/.scn file."""
|
||
if not ResourceLoader.exists(path):
|
||
if not path.ends_with(".tscn") and not path.ends_with(".scn"):
|
||
path = path + ".tscn"
|
||
if not ResourceLoader.exists(path):
|
||
return null
|
||
return ResourceLoader.load(path, "PackedScene", ResourceLoader.CACHE_MODE_REUSE)
|
||
|
||
|
||
func _instantiate_scene(scene: PackedScene) -> Node:
|
||
return scene.instantiate()
|
||
|
||
|
||
func _find_node_by_type(node: Node, type_name: String) -> Node:
|
||
"""Recursively find first node matching a class name."""
|
||
if node.get_class() == type_name:
|
||
return node
|
||
for child in node.get_children():
|
||
var found := _find_node_by_type(child, type_name)
|
||
if found != null:
|
||
return found
|
||
return null
|
||
|
||
|
||
func _find_nodes_by_type(node: Node, type_name: String) -> Array[Node]:
|
||
"""Recursively find all nodes of a given class."""
|
||
var results: Array[Node] = []
|
||
if node.get_class() == type_name:
|
||
results.append(node)
|
||
for child in node.get_children():
|
||
results.append_array(_find_nodes_by_type(child, type_name))
|
||
return results
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Check helpers
|
||
# ---------------------------------------------------------------------------
|
||
|
||
func _pass(msg: String) -> void:
|
||
_pass_count += 1
|
||
print(" ✓ ", msg)
|
||
|
||
|
||
func _fail(msg: String) -> void:
|
||
_fail_count += 1
|
||
print(" ✗ ", msg)
|
||
|
||
|
||
func _warn(msg: String) -> void:
|
||
_warning_count += 1
|
||
print(" ⚠ ", msg)
|
||
|
||
|
||
func _check(condition: bool, msg_pass: String, msg_fail: String) -> bool:
|
||
if condition:
|
||
_pass(msg_pass)
|
||
else:
|
||
_fail(msg_fail)
|
||
return condition
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Individual LightmapGI checks
|
||
# ---------------------------------------------------------------------------
|
||
|
||
func _run_lightmap_node_check() -> void:
|
||
"""Verify LightmapGI node exists."""
|
||
print("--- LightmapGI Node ---")
|
||
if _lightmap_node != null:
|
||
_pass("LightmapGI node found: '" + _lightmap_node.name + "'")
|
||
else:
|
||
_fail("LightmapGI node not found in scene — required for baked lighting")
|
||
|
||
|
||
func _run_quality_check() -> void:
|
||
"""Validate LightmapGI quality setting."""
|
||
print("")
|
||
print("--- Quality ---")
|
||
if _lightmap_node == null:
|
||
return
|
||
|
||
var quality: int = _lightmap_node.get("quality") if "quality" in _lightmap_node else -1
|
||
if quality < 0:
|
||
_fail("LightmapGI.quality not available")
|
||
return
|
||
|
||
var quality_names := ["Low", "Medium", "High", "Ultra"]
|
||
var quality_name := quality_names[quality] if quality < quality_names.size() else "Unknown"
|
||
|
||
if quality >= RECOMMENDED_QUALITY:
|
||
_pass("LightmapGI.quality = " + quality_name + " (" + str(quality) + ") — High or Ultra recommended")
|
||
elif quality == 1:
|
||
_warn("LightmapGI.quality = " + quality_name + " (" + str(quality) + ") — Medium is acceptable, High recommended")
|
||
else:
|
||
_fail("LightmapGI.quality = " + quality_name + " (" + str(quality) + ") — set to at least High (2)")
|
||
|
||
|
||
func _run_bounce_check() -> void:
|
||
"""Validate light bounce count."""
|
||
print("")
|
||
print("--- Bounces ---")
|
||
if _lightmap_node == null:
|
||
return
|
||
|
||
var bounces: int = _lightmap_node.get("bounces") if "bounces" in _lightmap_node else -1
|
||
if bounces < 0:
|
||
_fail("LightmapGI.bounces not available")
|
||
return
|
||
|
||
if bounces >= RECOMMENDED_BOUNCES:
|
||
_pass("LightmapGI.bounces = " + str(bounces) + " (recommended: ≥ " + str(RECOMMENDED_BOUNCES) + ")")
|
||
elif bounces >= MIN_BOUNCES:
|
||
_warn("LightmapGI.bounces = " + str(bounces) + " — low, consider ≥ " + str(RECOMMENDED_BOUNCES))
|
||
else:
|
||
_fail("LightmapGI.bounces = " + str(bounces) + " — minimum is " + str(MIN_BOUNCES))
|
||
|
||
# Warn if too high (performance concern)
|
||
if bounces > MAX_BOUNCES - 2:
|
||
_warn("LightmapGI.bounces = " + str(bounces) + " — high bounce count may impact bake time")
|
||
|
||
|
||
func _run_texel_scale_check() -> void:
|
||
"""Validate texel scale setting."""
|
||
print("")
|
||
print("--- Texel Scale ---")
|
||
if _lightmap_node == null:
|
||
return
|
||
|
||
var texel_scale: float = _lightmap_node.get("texel_scale") if "texel_scale" in _lightmap_node else -1.0
|
||
if texel_scale < 0:
|
||
_fail("LightmapGI.texel_scale not available")
|
||
return
|
||
|
||
if abs(texel_scale - RECOMMENDED_TEXEL_SCALE) < 0.01:
|
||
_pass("LightmapGI.texel_scale = " + str(texel_scale) + " (exactly " + str(RECOMMENDED_TEXEL_SCALE) + ")")
|
||
elif texel_scale >= MIN_TEXEL_SCALE and texel_scale <= MAX_TEXEL_SCALE:
|
||
if texel_scale < 0.5:
|
||
_warn("LightmapGI.texel_scale = " + str(texel_scale) + " — below 0.5, bake resolution may be too low")
|
||
elif texel_scale > 2.0:
|
||
_warn("LightmapGI.texel_scale = " + str(texel_scale) + " — above 2.0, bake resolution may be unnecessarily high")
|
||
else:
|
||
_pass("LightmapGI.texel_scale = " + str(texel_scale) + " (within reasonable range [0.1, 10.0])")
|
||
else:
|
||
_fail("LightmapGI.texel_scale = " + str(texel_scale) + " — out of expected range [" + str(MIN_TEXEL_SCALE) + ", " + str(MAX_TEXEL_SCALE) + "]")
|
||
|
||
|
||
func _run_interior_check() -> void:
|
||
"""Validate interior flag."""
|
||
print("")
|
||
print("--- Interior ---")
|
||
if _lightmap_node == null:
|
||
return
|
||
|
||
var interior: bool = _lightmap_node.get("interior") if "interior" in _lightmap_node else false
|
||
|
||
if interior:
|
||
_pass("LightmapGI.interior = true (correct for indoor maps)")
|
||
else:
|
||
_warn("LightmapGI.interior = false — set to true for indoor scenes to avoid light leaking from outside")
|
||
|
||
|
||
func _run_denoiser_check() -> void:
|
||
"""Validate denoiser setting."""
|
||
print("")
|
||
print("--- Denoiser ---")
|
||
if _lightmap_node == null:
|
||
return
|
||
|
||
# Godot 4 renamed to use_denoiser
|
||
var use_denoiser: bool = false
|
||
if "use_denoiser" in _lightmap_node:
|
||
use_denoiser = _lightmap_node.get("use_denoiser")
|
||
elif _lightmap_node.has_method("is_using_denoiser"):
|
||
use_denoiser = _lightmap_node.is_using_denoiser()
|
||
|
||
if use_denoiser:
|
||
_pass("LightmapGI.use_denoiser = true (recommended for cleaner results)")
|
||
else:
|
||
_warn("LightmapGI.use_denoiser = false — enable denoiser for cleaner lightmap results")
|
||
|
||
|
||
func _run_max_texture_size_check() -> void:
|
||
"""Validate max texture size setting."""
|
||
print("")
|
||
print("--- Max Texture Size ---")
|
||
if _lightmap_node == null:
|
||
return
|
||
|
||
var max_tex: int = _lightmap_node.get("max_texture_size") if "max_texture_size" in _lightmap_node else -1
|
||
if max_tex < 0:
|
||
_warn("LightmapGI.max_texture_size not available (using default)")
|
||
return
|
||
|
||
if max_tex <= MAX_TEXTURE_SIZE:
|
||
_pass("LightmapGI.max_texture_size = " + str(max_tex) + " (≤ " + str(MAX_TEXTURE_SIZE) + ")")
|
||
else:
|
||
_fail("LightmapGI.max_texture_size = " + str(max_tex) + " — exceeds " + str(MAX_TEXTURE_SIZE) +
|
||
" (consider lowering for memory budget)")
|
||
|
||
|
||
func _run_energy_check() -> void:
|
||
"""Validate lightmap energy setting."""
|
||
print("")
|
||
print("--- Energy ---")
|
||
if _lightmap_node == null:
|
||
return
|
||
|
||
var energy: float = _lightmap_node.get("energy") if "energy" in _lightmap_node else 1.0
|
||
_pass("LightmapGI.energy = " + str(energy) + " (verify visually in editor)")
|
||
|
||
var indirect_energy: float = _lightmap_node.get("indirect_energy") if "indirect_energy" in _lightmap_node else 1.0
|
||
if abs(indirect_energy - 1.0) > 0.01:
|
||
_warn("LightmapGI.indirect_energy = " + str(indirect_energy) + " (default is 1.0)")
|
||
|
||
|
||
func _run_baked_data_check() -> void:
|
||
"""Check whether lightmap data has been baked."""
|
||
print("")
|
||
print("--- Baked Data ---")
|
||
if _lightmap_node == null:
|
||
return
|
||
|
||
var light_data = _lightmap_node.get("light_data") if "light_data" in _lightmap_node else null
|
||
if light_data != null:
|
||
_pass("LightmapGI has baked light data present")
|
||
# Check data size
|
||
if light_data.has_method("get_user_data"):
|
||
var user_data = light_data.get_user_data()
|
||
if user_data != null:
|
||
_warn("Lightmap data size: varies (baked data is present)")
|
||
else:
|
||
_warn("LightmapGI has NO baked data yet — needs baking in editor")
|
||
|
||
|
||
func _run_process_mode_check() -> void:
|
||
"""Validate the lightmap process mode."""
|
||
print("")
|
||
print("--- Process Mode ---")
|
||
if _lightmap_node == null:
|
||
return
|
||
|
||
# Godot 4 LightmapGI has a bake_mode or similar property for sub-surface scattering
|
||
if "bake_mode" in _lightmap_node:
|
||
var bake_mode: int = _lightmap_node.get("bake_mode")
|
||
match bake_mode:
|
||
0:
|
||
_pass("LightmapGI.bake_mode = Conservative (0) — safe default")
|
||
1:
|
||
_warn("LightmapGI.bake_mode = Aggressive (1) — may cause artifacts")
|
||
2:
|
||
_pass("LightmapGI.bake_mode = Always Lightmap (2) — good for fully baked scenes")
|
||
_:
|
||
_warn("LightmapGI.bake_mode = " + str(bake_mode) + " — unknown")
|
||
|
||
# Check for environment mode (if property exists)
|
||
if "environment_mode" in _lightmap_node:
|
||
var env_mode: int = _lightmap_node.get("environment_mode")
|
||
match env_mode:
|
||
0:
|
||
_pass("LightmapGI.environment_mode = Disabled — no environment lighting (indoor)")
|
||
1:
|
||
_pass("LightmapGI.environment_mode = Enabled — using environment for lighting")
|
||
2:
|
||
_warn("LightmapGI.environment_mode = Ambient Color — verify color is correct")
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# UV2 geometry check
|
||
# ---------------------------------------------------------------------------
|
||
|
||
func _run_uv2_check() -> void:
|
||
"""Scan all MeshInstance3D nodes for UV2 channel presence."""
|
||
print("")
|
||
print("--- Geometry UV2 Check ---")
|
||
|
||
var mesh_nodes := _find_nodes_by_type(_scene_instance, "MeshInstance3D")
|
||
_total_meshes = mesh_nodes.size()
|
||
|
||
if _total_meshes == 0:
|
||
_warn("No MeshInstance3D nodes found — checking CSG nodes for UV2")
|
||
|
||
# Check CSG nodes instead
|
||
var csg_nodes := _find_nodes_by_type(_scene_instance, "CSGCombiner3D")
|
||
csg_nodes.append_array(_find_nodes_by_type(_scene_instance, "CSGBox3D"))
|
||
csg_nodes.append_array(_find_nodes_by_type(_scene_instance, "CSGSphere3D"))
|
||
csg_nodes.append_array(_find_nodes_by_type(_scene_instance, "CSGCylinder3D"))
|
||
csg_nodes.append_array(_find_nodes_by_type(_scene_instance, "CSGTorus3D"))
|
||
csg_nodes.append_array(_find_nodes_by_type(_scene_instance, "CSGPolygon3D"))
|
||
csg_nodes.append_array(_find_nodes_by_type(_scene_instance, "CSGMesh3D"))
|
||
|
||
# CSG nodes generate UV2 during baking automatically
|
||
if csg_nodes.size() > 0:
|
||
_pass("Scene uses CSG primitives (" + str(csg_nodes.size()) + ") — UV2 is auto-generated during bake")
|
||
return
|
||
|
||
for mi in mesh_nodes:
|
||
var has_uv2 := false
|
||
if "mesh" in mi and mi.mesh != null:
|
||
has_uv2 = _check_mesh_uv2(mi.mesh)
|
||
|
||
var info := {
|
||
"name": mi.name,
|
||
"has_uv2": has_uv2,
|
||
"material_count": 0
|
||
}
|
||
_mesh_details.append(info)
|
||
|
||
if has_uv2:
|
||
_meshes_with_uv2 += 1
|
||
else:
|
||
_meshes_without_uv2.append(mi.name)
|
||
|
||
if _meshes_without_uv2.size() == 0:
|
||
_pass("All " + str(_total_meshes) + " MeshInstance3D nodes have UV2 channel")
|
||
else:
|
||
for mesh_name in _meshes_without_uv2:
|
||
_fail("MeshInstance3D '" + mesh_name + "' lacks UV2 channel — lightmapping will not work")
|
||
_warn(str(_meshes_without_uv2.size()) + "/" + str(_total_meshes) + " meshes missing UV2")
|
||
|
||
# Check for sub-surface scattering override on LightmapGI
|
||
if _lightmap_node != null and "subdiv" in _lightmap_node:
|
||
var subdiv: int = _lightmap_node.get("subdiv")
|
||
if subdiv > 0:
|
||
_warn("LightmapGI.subdiv = " + str(subdiv) + " — non-zero subdiv enables subsurface scattering")
|
||
|
||
|
||
func _check_mesh_uv2(mesh) -> bool:
|
||
"""Check if a mesh has UV2 channel data."""
|
||
if mesh == null:
|
||
return false
|
||
if not mesh.has_method("get_surface_count"):
|
||
return false
|
||
|
||
var surface_count := mesh.get_surface_count()
|
||
for s in range(surface_count):
|
||
var arrays := mesh.surface_get_arrays(s)
|
||
if arrays is Array and arrays.size() > 0:
|
||
var uv2 := arrays[Mesh.ARRAY_TEX_UV2] as PackedVector2Array
|
||
if uv2 != null and uv2.size() > 0:
|
||
return true
|
||
return false
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Reports
|
||
# ---------------------------------------------------------------------------
|
||
|
||
func _print_lightmap_report() -> void:
|
||
"""Print a detailed lightmap configuration report."""
|
||
print("")
|
||
print("--- LightmapGI Configuration Report ---")
|
||
if _lightmap_node == null:
|
||
print(" (no LightmapGI node to report)")
|
||
return
|
||
|
||
print(" Node path: ", _get_node_path(_lightmap_node))
|
||
var quality_names := ["Low", "Medium", "High", "Ultra"]
|
||
var q: int = _lightmap_node.get("quality") if "quality" in _lightmap_node else -1
|
||
if q >= 0 and q < quality_names.size():
|
||
print(" Quality: ", quality_names[q], " (", q, ")")
|
||
else:
|
||
print(" Quality: ", q)
|
||
print(" Bounces: ", _lightmap_node.get("bounces") if "bounces" in _lightmap_node else "N/A")
|
||
print(" Texel Scale: ", _lightmap_node.get("texel_scale") if "texel_scale" in _lightmap_node else "N/A")
|
||
print(" Interior: ", _lightmap_node.get("interior") if "interior" in _lightmap_node else "N/A")
|
||
print(" Denoiser: ", _lightmap_node.get("use_denoiser") if "use_denoiser" in _lightmap_node else "N/A")
|
||
print(" Max Tex Size: ", _lightmap_node.get("max_texture_size") if "max_texture_size" in _lightmap_node else "N/A")
|
||
print(" Energy: ", _lightmap_node.get("energy") if "energy" in _lightmap_node else "N/A")
|
||
print(" Baked Data: ", "Yes" if _lightmap_node.get("light_data") != null else "No")
|
||
print("")
|
||
|
||
|
||
func _print_uv2_report() -> void:
|
||
"""Print UV2 details for meshes."""
|
||
print("--- UV2 Report ---")
|
||
print(" Total MeshInstance3D: ", _total_meshes)
|
||
print(" With UV2: ", _meshes_with_uv2)
|
||
print(" Without UV2: ", _meshes_without_uv2.size())
|
||
if _meshes_without_uv2.size() > 0:
|
||
print(" Missing UV2 list:")
|
||
for name in _meshes_without_uv2:
|
||
print(" - ", name)
|
||
print("")
|
||
|
||
|
||
func _get_node_path(node: Node) -> String:
|
||
"""Get the scene-tree path of a node."""
|
||
var parts: Array[String] = []
|
||
var current := node
|
||
while current != null and current != _scene_instance:
|
||
parts.push_front(current.name)
|
||
current = current.get_parent()
|
||
return "/".join(parts)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Summary
|
||
# ---------------------------------------------------------------------------
|
||
|
||
func _print_summary() -> void:
|
||
"""Print final summary."""
|
||
print("==========================================")
|
||
print(" LIGHTMAP CONFIG SUMMARY")
|
||
print("==========================================")
|
||
print(" Scene: ", _scene_path)
|
||
print(" LightmapGI: ", "Found ✓" if _lightmap_node != null else "Missing ✗")
|
||
print(" Quality OK: ", _check_quality_status())
|
||
print(" Bounces OK: ", _check_bounce_status())
|
||
print(" Texel Scale OK: ", _check_texel_status())
|
||
print(" Interior OK: ", _check_interior_status())
|
||
print(" Denoiser OK: ", _check_denoiser_status())
|
||
print(" UV2 OK: ", _check_uv2_status())
|
||
print("------------------------------------------")
|
||
print(" Passed: ", _pass_count)
|
||
print(" Failed: ", _fail_count)
|
||
print(" Warnings: ", _warning_count)
|
||
print("------------------------------------------")
|
||
if _fail_count > 0:
|
||
print(" RESULT: FAILED ❌")
|
||
printerr("Lightmap config check FAILED — ", _fail_count, " check(s) failed.")
|
||
else:
|
||
print(" RESULT: PASSED ✅")
|
||
print("==========================================")
|
||
print("")
|
||
|
||
|
||
func _check_quality_status() -> String:
|
||
if _lightmap_node == null: return "N/A"
|
||
var q = _lightmap_node.get("quality") if "quality" in _lightmap_node else -1
|
||
return "Yes ✓" if q >= RECOMMENDED_QUALITY else ("No ✗" if q >= 0 else "N/A")
|
||
|
||
|
||
func _check_bounce_status() -> String:
|
||
if _lightmap_node == null: return "N/A"
|
||
var b = _lightmap_node.get("bounces") if "bounces" in _lightmap_node else -1
|
||
return "Yes ✓" if b >= RECOMMENDED_BOUNCES else ("No ✗" if b >= 0 else "N/A")
|
||
|
||
|
||
func _check_texel_status() -> String:
|
||
if _lightmap_node == null: return "N/A"
|
||
var t = _lightmap_node.get("texel_scale") if "texel_scale" in _lightmap_node else -1.0
|
||
if t < 0: return "N/A"
|
||
return "Yes ✓" if t >= 0.5 and t <= 2.0 else "⚠"
|
||
|
||
|
||
func _check_interior_status() -> String:
|
||
if _lightmap_node == null: return "N/A"
|
||
var interior = _lightmap_node.get("interior") if "interior" in _lightmap_node else false
|
||
return "Yes ✓" if interior else "No ✗"
|
||
|
||
|
||
func _check_denoiser_status() -> String:
|
||
if _lightmap_node == null: return "N/A"
|
||
if "use_denoiser" in _lightmap_node:
|
||
return "Yes ✓" if _lightmap_node.get("use_denoiser") else "No ✗"
|
||
if _lightmap_node.has_method("is_using_denoiser"):
|
||
return "Yes ✓" if _lightmap_node.is_using_denoiser() else "No ✗"
|
||
return "N/A"
|
||
|
||
|
||
func _check_uv2_status() -> String:
|
||
if _total_meshes == 0: return "N/A (CSG)"
|
||
if _meshes_without_uv2.size() == 0: return "Yes ✓"
|
||
return str(_meshes_without_uv2.size()) + " missing"
|