#if UNITY_6000_7_OR_NEWER using System.Collections.Generic; using System.Linq; using Unity.Pipeline.Commands; using UnityEngine; using UnityEngine.UIElements; using Object = UnityEngine.Object; namespace Unity.Pipeline.Runtime.Commands { /// /// Captures a UI Toolkit from a live runtime panel to a PNG and /// returns it base64-encoded (and writes it to disk). Runtime-only so it is available in a /// development Player as well as the editor's Play mode. /// /// A runtime panel is hosted by either a or a . /// The panel is identified by name (the asset name or the host /// GameObject name) rather than an asset path, because a Player has no AssetDatabase. /// public static class CaptureRuntimeElementCommand { [CliCommand("capture_runtime_element", "Capture a UI Toolkit VisualElement (by selector) from a live runtime panel (UIDocument or PanelRenderer) to a PNG; returns path + base64.", MainThreadRequired = true, RuntimeOnly = true)] public static CaptureElementResponse CaptureRuntimeElement( [CliArg("panel", "Name of the target panel: matches the PanelSettings asset name or the host GameObject name (UIDocument or PanelRenderer). Optional when exactly one panel exists.")] string panel = "", [CliArg("selector", "Element selector: '#name', '.class', a type name (e.g. Button), descendant (space) / child ('>') chains, optional pseudo-states (:checked,:hover,:focus,:active,:enabled,:disabled,:not(...)).", Required = true)] string selector = "", [CliArg("output", "Output PNG path (absolute, or relative to Application.persistentDataPath). Defaults to a timestamped file under Application.persistentDataPath.")] string output = "") { if (string.IsNullOrWhiteSpace(selector)) return CaptureElementResponse.Fail("A 'selector' is required."); var hosts = GatherHosts(); if (hosts.Count == 0) return CaptureElementResponse.Fail("No live runtime UI panels found (UIDocument or PanelRenderer). Show the UI first."); List matched; string resolvedName; if (string.IsNullOrWhiteSpace(panel)) { if (hosts.Count > 1) { var names = string.Join(", ", hosts.Select(h => h.DisplayName).Distinct()); return CaptureElementResponse.Fail( $"Multiple runtime panels exist; specify --panel as one of: {names}."); } matched = hosts; resolvedName = hosts[0].DisplayName; } else { matched = hosts.Where(h => h.PanelSettingsName == panel || h.GameObjectName == panel).ToList(); if (matched.Count == 0) { var names = string.Join(", ", hosts.Select(h => h.DisplayName).Distinct()); return CaptureElementResponse.Fail( $"No live runtime panel named '{panel}'. Available: {names}."); } resolvedName = panel; } // Lower sorting order is drawn first (further back); query in that order so the topmost // match is found last only if earlier panels miss. Roots that can't be resolved are skipped. var roots = matched .OrderBy(h => h.SortingOrder) .Select(h => h.GetRoot()) .Where(r => r != null) .ToList(); if (roots.Count == 0) return CaptureElementResponse.Fail( $"Panel '{resolvedName}' has no initialized root VisualElement yet. Show the UI first."); var element = VisualElementCaptureSupport.ResolveElement(roots, selector, out _); if (element == null) return CaptureElementResponse.Fail( $"No element matched selector '{selector}' in panel '{resolvedName}'."); var dir = Application.persistentDataPath; return VisualElementCaptureSupport.CaptureAndRespond( element, selector, $"panel:{resolvedName}", output, relativeBaseDir: dir, defaultDir: dir, prefix: "element"); } /// A live runtime UI host (UIDocument or PanelRenderer) and how to reach its root. class Host { public string PanelSettingsName; public string GameObjectName; public float SortingOrder; public System.Func GetRoot; public string DisplayName => PanelSettingsName ?? GameObjectName; } static List GatherHosts() { var hosts = new List(); foreach (var doc in PipelineUtils.FindObjectsByType()) { var d = doc; hosts.Add(new Host { PanelSettingsName = d.panelSettings != null ? d.panelSettings.name : null, GameObjectName = d.gameObject.name, SortingOrder = d.sortingOrder, GetRoot = () => d.rootVisualElement }); } // PanelRenderer is a 6000.5+ UI Toolkit API; on older editors only UIDocument panels exist. foreach (var renderer in PipelineUtils.FindObjectsByType()) { var r = renderer; hosts.Add(new Host { PanelSettingsName = r.panelSettings != null ? r.panelSettings.name : null, GameObjectName = r.gameObject.name, SortingOrder = r.sortingOrder, GetRoot = () => GetPanelRendererRoot(r) }); } return hosts; } // PanelRenderer.rootVisualElement is internal; the public route to its root is a UI-reload // callback, which fires synchronously when the panel is already initialized. Register, capture // the root, and immediately unregister. PanelRenderer is a 6000.5+ API. static VisualElement GetPanelRendererRoot(PanelRenderer renderer) { VisualElement captured = null; PanelRenderer.VersionedUIReloadCallback callback = (pr, root, version) => captured = root; renderer.RegisterUIReloadCallback(callback); renderer.UnregisterUIReloadCallback(callback); return captured; } } } #endif