Both capture helpers now accept --out=res://docs/screenshots, and tools/check_screenshots.sh renders the staged set (using xvfb-run when headless) before running screen_designs.py compare. Record the judge result: 7 match, 4 review, 0 diff. Headless 226/226 and the smoke both pass in a clean worktree.
113 lines
3.9 KiB
GDScript
113 lines
3.9 KiB
GDScript
extends SceneTree
|
|
## Dev helper: render every generated prop side by side on a plain backdrop and
|
|
## save user://screenshots/10-prop-gallery.png by default. 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
|
|
## godot --path . --script res://src/test/capture_prop_gallery.gd -- --out=res://docs/screenshots
|
|
|
|
var 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:
|
|
for arg in OS.get_cmdline_user_args():
|
|
if arg.begins_with("--out="):
|
|
out_dir = arg.trim_prefix("--out=")
|
|
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
|