extends Node3D ## cargo_pants preview capture (T-1089 wave 2) — parameterized copy of ## preview_shorts_modern.gd for a LOWER-BODY 3-region garment: per-body region ## mask (reference fallback), no logo, camera framed on the full leg span ## (waistband to ankle hem) so the thigh cargo pockets and straight fit read. ## Front + 3/4 Walk renders. Temporary tool scene — safe to delete after ## captures. ## ## Config via env vars (all optional): ## GARMENT_PREVIEW_ITEM default "cargo_pants" ## GARMENT_PREVIEW_BODY default "average_m" ## GARMENT_PREVIEW_OUT default "/.cache/garment-preview/cargo_pants" const CLOTHING_DIR := "res://assets/characters/clothing/" const SHADER_PATH := "res://assets/characters/shaders/toon_garment.gdshader" const VIEWPORT := Vector2i(768, 1024) const BODY_KEY_TO_ENUM := { "average_m": CharacterVisualDescriptor.BodyType.AVERAGE_M, "average_f": CharacterVisualDescriptor.BodyType.AVERAGE_F, "muscular_m": CharacterVisualDescriptor.BodyType.MUSCULAR_M, "muscular_f": CharacterVisualDescriptor.BodyType.MUSCULAR_F, "thin_m": CharacterVisualDescriptor.BodyType.THIN_M, "thin_f": CharacterVisualDescriptor.BodyType.THIN_F, "heavy_m": CharacterVisualDescriptor.BodyType.HEAVY_M, "heavy_f": CharacterVisualDescriptor.BodyType.HEAVY_F, "teen_m": CharacterVisualDescriptor.BodyType.TEEN_M, "teen_f": CharacterVisualDescriptor.BodyType.TEEN_F, "child": CharacterVisualDescriptor.BodyType.CHILD, } # Utility palette: charcoal waistband, olive legs, grey-olive pockets+flaps # (tint_2 demonstrates the B region recolors independently). tint_3 unused. const TINT_WAISTBAND := Color(0.13, 0.14, 0.16) # R region const TINT_LEGS := Color(0.45, 0.47, 0.38) # G region const TINT_POCKETS := Color(0.36, 0.37, 0.33) # B region var _out_dir := "" var _cam: Camera3D = null func _ready() -> void: var item := _env("GARMENT_PREVIEW_ITEM", "cargo_pants") var body := _env("GARMENT_PREVIEW_BODY", "average_m") _out_dir = _env("GARMENT_PREVIEW_OUT", "/var/mnt/data/projects/settled-reach/.cache/garment-preview/cargo_pants") get_window().size = VIEWPORT _setup_stage() _run.call_deferred(item, body) func _env(key: String, fallback: String) -> String: var v := OS.get_environment(key) return v if not v.is_empty() else fallback func _setup_stage() -> void: var light := DirectionalLight3D.new() light.rotation_degrees = Vector3(-38, 28, 0) light.light_energy = 1.3 add_child(light) var env := Environment.new() env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR env.ambient_light_color = Color(0.82, 0.82, 0.86) env.ambient_light_energy = 0.9 env.background_mode = Environment.BG_COLOR env.background_color = Color(0.16, 0.17, 0.20) var we := WorldEnvironment.new() we.environment = env add_child(we) _cam = Camera3D.new() _cam.fov = 45.0 add_child(_cam) # Frame the full leg span (waistband ~1.03 down to the ankle hem) so the # thigh cargo pockets and the straight-fit silhouette both read. _cam.position = Vector3(0.0, 0.72, 2.05) _cam.look_at_from_position(_cam.position, Vector3(0.0, 0.60, 0.0), Vector3.UP) func _run(item: String, body: String) -> void: DirAccess.make_dir_recursive_absolute(_out_dir) if not BODY_KEY_TO_ENUM.has(body): push_error("preview: unknown body %s" % body) get_tree().quit(1) return var cv := CharacterVisual.new() add_child(cv) var desc := CharacterVisualDescriptor.new() desc.body_type = BODY_KEY_TO_ENUM[body] desc.head_id = "head_001" desc.hair_id = "buzzed" desc.eyebrow_id = "regular" desc.skin_tone = 3 cv.load_descriptor(desc) var skel := cv.get_skeleton() if skel == null: push_error("preview: no skeleton") get_tree().quit(1) return var meshes := _attach_garment(skel, item, body) print("preview: attached %d garment meshes" % meshes.size()) var shader := load(SHADER_PATH) as Shader # Per-body region mask (offset-shell route); reference fallback mirrors # the runtime compositor. var mask := _try_load(CLOTHING_DIR + "%s/%s_mask.png" % [item, body]) if mask == null: mask = _try_load(CLOTHING_DIR + "%s/reference_mask.png" % item) for mi in meshes: _apply_garment_shader(mi, shader, mask) # Freeze on a mid-stride Walk pose so pockets + hem read under motion. var ap := cv.get_animation_player() if ap != null: var walk := _resolve_clip(ap, "Walk") if not walk.is_empty(): cv.play_animation(walk) ap.pause() ap.seek(ap.current_animation_length * 0.25, true) await get_tree().process_frame # Front + 3/4 (task deliverable: two preview renders). for yaw in [0, 45]: cv.rotation_degrees.y = float(yaw) await RenderingServer.frame_post_draw await RenderingServer.frame_post_draw var fname := "%s__%s__yaw%03d.png" % [item, body, yaw] get_viewport().get_texture().get_image().save_png(_out_dir.path_join(fname)) print("preview: wrote ", fname) print("preview: DONE") get_tree().quit(0) func _attach_garment(skel: Skeleton3D, item: String, body: String) -> Array: var out: Array = [] var glb := CLOTHING_DIR + "%s/%s.glb" % [item, body] if not ResourceLoader.exists(glb): push_error("preview: garment GLB missing %s" % glb) return out var inst := (load(glb) as PackedScene).instantiate() var stack: Array[Node] = [inst] while not stack.is_empty(): var n: Node = stack.pop_back() for c in n.get_children(): stack.append(c) if n is MeshInstance3D and (n as MeshInstance3D).skin != null: var mi := n as MeshInstance3D mi.get_parent().remove_child(mi) mi.owner = null skel.add_child(mi) out.append(mi) inst.queue_free() return out func _apply_garment_shader(mi: MeshInstance3D, shader: Shader, mask: Texture2D) -> void: if mi.mesh == null or shader == null: return var albedo := _albedo_of(mi) var mat := ShaderMaterial.new() mat.shader = shader if albedo: mat.set_shader_parameter("albedo_tex", albedo) if mask: mat.set_shader_parameter("region_mask", mask) mat.set_shader_parameter("tint_0", TINT_WAISTBAND) mat.set_shader_parameter("tint_1", TINT_LEGS) mat.set_shader_parameter("tint_2", TINT_POCKETS) mat.set_shader_parameter("tint_3", Color.WHITE) mat.set_shader_parameter("shadow_strength", 0.2) mi.material_override = mat func _albedo_of(mi: MeshInstance3D) -> Texture2D: for surf in range(mi.mesh.get_surface_count()): var m := mi.mesh.surface_get_material(surf) if m is BaseMaterial3D: return (m as BaseMaterial3D).albedo_texture return null func _try_load(path: String) -> Texture2D: return load(path) as Texture2D if ResourceLoader.exists(path) else null func _resolve_clip(ap: AnimationPlayer, requested: String) -> String: for lib_name in ap.get_animation_library_list(): var lib := ap.get_animation_library(lib_name) for a in lib.get_animation_list(): if str(a).to_lower().contains(requested.to_lower()): return str(a) return ""