extends SceneTree ## Dev helper: render every generated prop side by side on a plain backdrop and ## save user://screenshots/10-prop-gallery.png. The image feeds ## `tools/screen_designs.py compare` so a prop regression is visible. ## ## Usage: godot --path . --script res://src/test/capture_prop_gallery.gd const OUT_DIR := "user://screenshots" const MODEL_DIR := "res://assets/models/generated/" const LIBRARY_DIR := "res://assets/models/blender/props/" const PROPS := ["bollard", "bench", "trash_bin", "planter_box", "newspaper_box", "fire_hydrant"] const SPACING := 1.9 var frames := 0 func _initialize() -> void: root.size = Vector2i(1280, 720) _build_stage() func _build_stage() -> void: var stage := Node3D.new() stage.name = "PropGallery" root.add_child(stage) var environment := Environment.new() environment.background_mode = Environment.BG_COLOR environment.background_color = Color(0.52, 0.54, 0.58) environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR environment.ambient_light_color = Color(0.78, 0.80, 0.84) environment.ambient_light_energy = 0.8 var world_env := WorldEnvironment.new() world_env.environment = environment stage.add_child(world_env) # Floor and a plain backdrop wall: a studio line-up with no street context. var floor_mesh := PlaneMesh.new() floor_mesh.size = Vector2(14, 8) var floor := MeshInstance3D.new() floor.mesh = floor_mesh floor.material_override = _flat(Color(0.50, 0.51, 0.54), 0.9) stage.add_child(floor) var backdrop_mesh := BoxMesh.new() backdrop_mesh.size = Vector3(14, 6, 0.1) var backdrop := MeshInstance3D.new() backdrop.mesh = backdrop_mesh backdrop.material_override = _flat(Color(0.55, 0.57, 0.61), 0.95) backdrop.position = Vector3(0, 3.0, -3.4) stage.add_child(backdrop) # Raised back row so both rows read in a single shot. var platform_mesh := BoxMesh.new() platform_mesh.size = Vector3(6.8, 0.8, 1.9) var platform := MeshInstance3D.new() platform.mesh = platform_mesh platform.material_override = _flat(Color(0.38, 0.39, 0.42), 0.85) platform.position = Vector3(0, 0.4, -2.1) stage.add_child(platform) var light := DirectionalLight3D.new() light.rotation_degrees = Vector3(-48, -35, 0) light.light_energy = 0.85 light.shadow_enabled = true stage.add_child(light) var camera := Camera3D.new() camera.position = Vector3(0, 2.1, 6.2) camera.fov = 50.0 camera.look_at_from_position(camera.position, Vector3(0, 0.75, -1.0)) camera.current = true stage.add_child(camera) # 3 x 2 grid: three props on the front row, three on the back row. var columns := [-1.95, 0.0, 1.95] for index in PROPS.size(): var prop_name: String = PROPS[index] var path := LIBRARY_DIR + prop_name + ".glb" if not ResourceLoader.exists(path): path = MODEL_DIR + prop_name + ".glb" var packed := ResourceLoader.load(path) as PackedScene if packed == null: continue var model := packed.instantiate() as Node3D var col: int = index % 3 var row: int = index / 3 model.position = Vector3(columns[col], 0.8 if row == 1 else 0.0, -2.1 * row) model.rotation_degrees.y = 14.0 stage.add_child(model) func _flat(color: Color, roughness: float) -> StandardMaterial3D: var material := StandardMaterial3D.new() material.albedo_color = color material.roughness = roughness return material func _process(_delta: float) -> bool: frames += 1 if frames < 20: return false var dir := DirAccess.open("user://") if dir == null or not dir.dir_exists("screenshots"): dir.make_dir_recursive("screenshots") var image := root.get_texture().get_image() var err := image.save_png(OUT_DIR.path_join("10-prop-gallery.png")) print("10-prop-gallery.png -> err=%d (%dx%d)" % [err, image.get_width(), image.get_height()]) quit(0) return true