# Headless GLB fixup: rotate a model to face +Z and scale to a target height, # baking the transform into vertices via the root node transform on export. # Env: ROT_IN (glb path), ROT_OUT (glb path), ROT_YAW_DEG, ROT_HEIGHT_M extends SceneTree func _init() -> void: var in_path := OS.get_environment("ROT_IN") var out_path := OS.get_environment("ROT_OUT") var yaw_deg := float(OS.get_environment("ROT_YAW_DEG")) var target_h := float(OS.get_environment("ROT_HEIGHT_M")) var doc := GLTFDocument.new() var state := GLTFState.new() if doc.append_from_file(in_path, state) != OK: push_error("load failed: " + in_path) quit(1) return var scene := doc.generate_scene(state) # measure current height from merged mesh AABB var merged := AABB() var first := true var stack: Array = [scene] while not stack.is_empty(): var n: Node = stack.pop_back() if n is MeshInstance3D: var box: AABB = (n as MeshInstance3D).get_aabb() if first: merged = box first = false else: merged = merged.merge(box) for c in n.get_children(): stack.push_back(c) var h := merged.size.y var s := target_h / h if h > 0.001 else 1.0 print("height=", h, " scale=", s) var wrapper := Node3D.new() wrapper.name = "Root" wrapper.rotate_y(deg_to_rad(yaw_deg)) wrapper.scale = Vector3(s, s, s) # move model so feet sit at y=0 after scaling scene.position.y = -merged.position.y var parent := scene.get_parent() if parent != null: parent.remove_child(scene) wrapper.add_child(scene) _own_recursive(scene, wrapper) var out_doc := GLTFDocument.new() var out_state := GLTFState.new() if out_doc.append_from_scene(wrapper, out_state) != OK: push_error("export append failed") quit(1) return if out_doc.write_to_filesystem(out_state, out_path) != OK: push_error("write failed: " + out_path) quit(1) return print("WROTE ", out_path) quit(0) func _own_recursive(n: Node, owner: Node) -> void: n.owner = owner for c in n.get_children(): _own_recursive(c, owner)