Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore unversioniert; letztere sind unter ../backups gesichert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
130 lines
5.6 KiB
GDScript
130 lines
5.6 KiB
GDScript
extends Node
|
||
## Real Godot renders. Use model_gallery.tscn -- --capture after GLB import.
|
||
|
||
const STAGE: Script = preload("res://tools/model_gallery_stage.gd")
|
||
const OUT_DIR: String = "res://docs/screenshots/phase4"
|
||
const GROUPS: Array[Dictionary] = [
|
||
{"key": "glutpakt", "title": "GLUTPAKT", "subtitle": "Basalt · Orangeglut · Messing"},
|
||
{"key": "nietenkonvent", "title": "NIETENKONVENT", "subtitle": "Petrol · Ocker · Stahl · Keramik"},
|
||
{"key": "nebelchor", "title": "NEBELCHOR", "subtitle": "Indigo · Violett · Mint · Nebel"},
|
||
{"key": "wurzelbund", "title": "WURZELBUND", "subtitle": "Borke · Moos · Blatt · Blüte"},
|
||
{"key": "neutral", "title": "SÖLDNER & ARBEITER", "subtitle": "Fünf Send-Einheiten und der Funkenleser"},
|
||
{"key": "rift", "title": "RISSKREATUREN", "subtitle": "Schalen · Knochen · Rissglut"},
|
||
{"key": "environment", "title": "SPIELFELD & SAUMKERNE", "subtitle": "Modulares Lane-Segment und zwei Teamkerne"}
|
||
]
|
||
|
||
var _assets: Array[Dictionary] = []
|
||
var _sheet: Control
|
||
var _views: Array[Dictionary] = []
|
||
|
||
|
||
func _ready() -> void:
|
||
var manifest: String = "res://assets/phase4_manifest.json"
|
||
if not FileAccess.file_exists(manifest):
|
||
_fail("Phase 4 manifest is missing; export all models first.")
|
||
return
|
||
var data: Variant = JSON.parse_string(FileAccess.get_file_as_string(manifest))
|
||
if not data is Dictionary:
|
||
_fail("Invalid Phase 4 manifest.")
|
||
return
|
||
for value: Variant in data.get("assets", []):
|
||
if value is Dictionary:
|
||
_assets.append(value)
|
||
if _assets.size() != 59:
|
||
_fail("Expected 59 exported models, found %d." % _assets.size())
|
||
return
|
||
for asset: Dictionary in _assets:
|
||
if not ResourceLoader.exists(str(asset.get("resource_path", ""))):
|
||
_fail("Model has not been imported: " + str(asset.get("id", "?")))
|
||
return
|
||
DirAccess.make_dir_recursive_absolute(OUT_DIR)
|
||
get_window().size = Vector2i(1600, 960)
|
||
get_tree().create_timer(90.0).timeout.connect(func() -> void: _fail("Capture timed out."))
|
||
for group: Dictionary in GROUPS:
|
||
if not _build_sheet(group):
|
||
return
|
||
await get_tree().process_frame
|
||
await get_tree().process_frame
|
||
await RenderingServer.frame_post_draw
|
||
await get_tree().create_timer(0.25).timeout
|
||
var image: Image = get_viewport().get_texture().get_image()
|
||
var output: String = OUT_DIR + "/models-" + str(group["key"]) + ".png"
|
||
var error: Error = image.save_png(output)
|
||
if error != OK:
|
||
_fail("Could not save " + output)
|
||
return
|
||
print("CAPTURED ", output, " (", image.get_width(), "×", image.get_height(), ")")
|
||
_sheet.free()
|
||
_views.clear()
|
||
print("PASS: captured all 59 models in seven real Godot contact sheets.")
|
||
get_tree().quit(0)
|
||
|
||
|
||
func _build_sheet(group: Dictionary) -> bool:
|
||
var selected: Array[Dictionary] = []
|
||
var key: String = str(group["key"])
|
||
for asset: Dictionary in _assets:
|
||
var matches: bool = str(asset.get("faction", "")) == key
|
||
if key == "environment":
|
||
matches = str(asset.get("role", "")) in ["core", "lane"]
|
||
if matches:
|
||
selected.append(asset)
|
||
_sheet = Control.new()
|
||
_sheet.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
add_child(_sheet)
|
||
var background: ColorRect = ColorRect.new()
|
||
background.color = Color("10141b")
|
||
background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
_sheet.add_child(background)
|
||
var margin: MarginContainer = MarginContainer.new()
|
||
margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||
for edge: String in ["left", "right"]:
|
||
margin.add_theme_constant_override("margin_" + edge, 28)
|
||
for edge: String in ["top", "bottom"]:
|
||
margin.add_theme_constant_override("margin_" + edge, 22)
|
||
_sheet.add_child(margin)
|
||
var layout: VBoxContainer = VBoxContainer.new()
|
||
layout.add_theme_constant_override("separation", 12)
|
||
margin.add_child(layout)
|
||
layout.add_child(_label("SAUMWACHT / " + str(group["title"]), 30, Color("ddc898")))
|
||
layout.add_child(_label("Phase 4 · " + str(group["subtitle"]) + " · %d Modelle" % selected.size(), 17, Color("abb5c5")))
|
||
var grid: GridContainer = GridContainer.new()
|
||
grid.columns = 3 if selected.size() <= 6 else 5
|
||
grid.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
grid.add_theme_constant_override("h_separation", 12)
|
||
grid.add_theme_constant_override("v_separation", 16)
|
||
layout.add_child(grid)
|
||
for asset: Dictionary in selected:
|
||
var card: VBoxContainer = VBoxContainer.new()
|
||
card.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
card.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||
card.add_theme_constant_override("separation", 5)
|
||
grid.add_child(card)
|
||
var view: Dictionary = STAGE.create(card, Vector2(270, 230))
|
||
_views.append(view)
|
||
if not STAGE.show_asset(view, asset):
|
||
_fail("Capture failed to load " + str(asset["id"]))
|
||
return false
|
||
STAGE.add_id_tag(view, str(asset["id"]))
|
||
var title: Label = _label(str(asset["name"]), 19, Color("eee6d8"))
|
||
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
card.add_child(title)
|
||
var detail: Label = _label("%s · %d Dreiecke" % [str(asset["id"]).to_upper(), int(asset.get("triangles", 0))], 14, Color("a1acbd"))
|
||
detail.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||
card.add_child(detail)
|
||
layout.add_child(_label("Originale parametrisierte Modelle · Einheitliche Studiobeleuchtung · Vorschau proportional auf gemeinsame Bühne eingepasst", 14, Color("8491a3")))
|
||
return true
|
||
|
||
|
||
func _label(text: String, font_size: int, color: Color) -> Label:
|
||
var result: Label = Label.new()
|
||
result.text = text
|
||
result.add_theme_font_size_override("font_size", font_size)
|
||
result.add_theme_color_override("font_color", color)
|
||
return result
|
||
|
||
|
||
func _fail(message: String) -> void:
|
||
printerr("FAIL: ", message)
|
||
get_tree().quit(1)
|