extends SceneTree # Generalised Lightmap Baking Config Checker # ========================================== # Validates that a map scene's LightmapGI is configured correctly for baking. # Generalised replacement for scripts/bake_lighting.gd — works on ANY map scene. # # Note: Godot 4.7 LightmapGI baking is editor-only. This script validates # configuration only; actual baking must be done from the Godot editor. # # Usage: # godot --headless --script bake_lightmaps.gd -- res://maps/my_map.tscn # # Exit codes: # 0 — All checks passed (or warnings only) # 1 — One or more checks failed (fatal) # 2 — Scene file could not be loaded var _scene_path: String = "" var _checks: Array = [] # [label, passed, is_warning] func _init() -> void: _parse_args() if _scene_path.is_empty(): printerr("ERROR: No scene path provided.") print("Usage: godot --headless --script bake_lightmaps.gd -- res://maps/my_map.tscn") quit(2) return print("") print("=== Lightmap Baking Config Checker: %s ===" % _scene_path) print("") var scene = ResourceLoader.load(_scene_path) if not scene: printerr("ERROR: Could not load scene: %s" % _scene_path) quit(2) return var instance = scene.instantiate() if not instance: printerr("ERROR: Could not instantiate scene!") quit(2) return root.add_child(instance) print("Scene loaded and instantiated successfully.") print("") print("Note: Godot 4.x LightmapGI baking is editor-only.") print("This script validates configuration only.") print("To bake: Open scene in editor > Select LightmapGI > 'Bake Lightmap'") print("") var lightmap: LightmapGI = _find_node_of_type(instance, LightmapGI) if not lightmap: printerr("ERROR: No LightmapGI node found in scene!") quit(1) return _print_config(lightmap) _run_checks(lightmap, instance) _print_results() func _parse_args() -> void: var args := OS.get_cmdline_args() var found_sep := false for a in args: if a == "--": found_sep = true continue if found_sep and not a.begins_with("-"): _scene_path = a return for a in args: if a.contains("res://") or a.ends_with(".tscn"): _scene_path = a return func _check(label: String, passed: bool, is_warning: bool = false) -> void: _checks.append([label, passed, is_warning]) func _print_config(lightmap: LightmapGI) -> void: print(" LightmapGI configuration:") print(" Quality: ", _quality_name(lightmap.quality), " (", lightmap.quality, ")") print(" Bounces: ", lightmap.bounces) print(" Texel Scale: ", lightmap.texel_scale) print(" Max Tex Size: ", lightmap.max_texture_size) print(" Interior: ", lightmap.interior) print(" Denoiser: ", lightmap.is_using_denoiser()) print(" Energy: ", lightmap.lightmap_energy) print(" Bias: ", lightmap.lightmap_probe_bias) if lightmap.light_data != null: print(" Baked data: PRESENT ✓") else: print(" Baked data: NOT YET BAKED") print("") func _run_checks(lightmap: LightmapGI, instance: Node) -> void: # Configuration checks _check("Quality ≥ Medium (1)", lightmap.quality >= 1) _check("Bounces ≥ 2", lightmap.bounces >= 2) _check("Texel Scale ≤ 2.0", lightmap.texel_scale <= 2.0) _check("Interior = true", lightmap.interior == true) # Denoiser recommended _check("Use denoiser enabled", lightmap.is_using_denoiser(), true) # Max texture size ≤ 4096 (sanity) _check("Max texture size ≤ 4096", lightmap.max_texture_size <= 4096) # Energy shouldn't be zero _check("Lightmap energy > 0", lightmap.lightmap_energy > 0) # Check for lightmap-enabled meshes var meshes: Array = _find_nodes_of_type(instance, MeshInstance3D) var static_meshes := 0 var dynamic_meshes := 0 for mi in meshes: if mi.gi_mode == GeometryInstance3D.GI_MODE_STATIC: static_meshes += 1 elif mi.gi_mode == GeometryInstance3D.GI_MODE_DYNAMIC: dynamic_meshes += 1 _check("Static geometry meshes found for lightmapping", static_meshes > 0) # Baked data status (informational) if lightmap.light_data != null: print(" ✓ Lightmap has baked data.") # Check bake timestamp if available var ld = lightmap.light_data if ld is LightmapGIData and ld.has_method("get_user_data"): print(" Bake data size: %d bytes" % ld.get_user_data().size()) else: print(" ⚠ Lightmap NOT YET BAKED. Open in Godot editor and bake.") # Summary print("") print(" Geometry summary:") print(" MeshInstance3D nodes: %d" % meshes.size()) print(" GI_MODE_STATIC: %d" % static_meshes) print(" GI_MODE_DYNAMIC: %d" % dynamic_meshes) func _print_results() -> void: print("") print(" ─── Validation Results ───") var passed := 0 var failed := 0 var warnings := 0 for c in _checks: if c[1]: passed += 1 print(" ✓ %s" % c[0]) else: if c[2]: warnings += 1 print(" ⚠ %s" % c[0]) else: failed += 1 print(" ✗ %s" % c[0]) print("") print(" Passed: %d Warnings: %d Failed: %d" % [passed, warnings, failed]) print("") if failed > 0: printerr("Validation FAILED — %d check(s) failed." % failed) quit(1) else: print("=== Config check complete ===") quit(0) # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- func _quality_name(q: int) -> String: match q: 0: return "Low" 1: return "Medium" 2: return "High" 3: return "Ultra" return "Unknown" func _find_node_of_type(parent: Node, type: Script) -> Node: for child in parent.get_children(): if is_instance_of(child, type): return child var found = _find_node_of_type(child, type) if found: return found return null func _find_nodes_of_type(parent: Node, type: Script) -> Array: var results: Array = [] for child in parent.get_children(): if is_instance_of(child, type): results.append(child) results.append_array(_find_nodes_of_type(child, type)) return results