| name | docs-screenshots |
| description | Re-capture or resize VRCQuestTools documentation screenshots (Website/static/img/*.png), produced by the Tools/VRCQuestTools/Debug/Screenshots menu, after a Unity Inspector/EditorWindow field, default value, or layout changes, or when adding a brand-new capture target. Use whenever a component gains/loses/renames a field, a default value changes, an Inspector grows or shrinks, an existing screenshot now shows clipped content, a scrollbar, or excess blank space, or a new component/window needs a doc screenshot. |
Documentation Screenshot Maintenance
Maintains the screenshots embedded in Website/docs/ (en) and
Website/i18n/ja/docusaurus-plugin-content-docs/current/ (ja), captured by the
debug tool at Assets/VRCQuestTools-DebugUtil/Editor/Screenshots/ and exposed
under the Unity menu Tools/VRCQuestTools/Debug/Screenshots/....
Why this tool exists
- Reproducible: every window/inspector is captured at a fixed size and
position, so screenshots don't vary run to run.
- English UI: capture always forces
DisplayLanguage.English for the
duration, regardless of the developer's own language setting, and restores
it afterward.
- Realistic sample data: when nothing is selected, the tool creates a
temporary GameObject and seeds its fields with plausible, procedurally
generated data (materials, textures, a skinned mesh with a blend shape, a
PhysBone/PhysBoneCollider/ContactReceiver) instead of leaving fields empty.
It never touches a GameObject the developer has hand-selected in the
Hierarchy.
- OS-independent capture: pixels come from Unity's internal GrabPixels
rendering (
WindowPixelCapture.cs, via uLoopMCP's
InternalEditorUtilityBridge), not real OS screen pixels, so capture is
unaffected by Remote Desktop session state or windows overlapping on
screen.
When to use this skill
- A component gained, lost, or renamed a serialized field.
- A field's default value changed (the sample screenshot may now show a
stale or misleading value).
- An Inspector or EditorWindow's layout got taller/shorter/wider (a new
warning box, an extra foldout section, more/fewer rows in a list, etc.).
- An existing screenshot now shows a scrollbar, clipped text/controls, or a
noticeably large blank area at the bottom.
- A brand-new component or window needs its own documentation screenshot for
the first time.
Key files
| File | Role |
|---|
ScreenshotMenu.cs | One [MenuItem] per target, each delegating to a private Capture...(Action onDone) method that holds the target's file name, size, and populate/configure lambda in one place. Capture All enqueues those same methods directly (never duplicates their parameters), so there is exactly one place to edit per target. |
ScreenshotSettings.cs | Fixed Vector2 size per capture target, plus the shared CaptureOrigin and output directory. |
SampleContentFactory.cs | Procedural, in-memory-only sample data helpers (CreateSampleMaterial, CreateSampleTexture, CreateSampleSkinnedMeshRenderer, CreateAdditionalMaterialConvertSettings). Reuse these before writing a new one. |
AvatarFixtures.cs | Instantiates the SimpleCubeAvatar test fixture prefab as a temporary avatar (InstantiateSimpleCubeAvatar), plus a variant with sample PhysBone/PhysBoneCollider/ContactReceiver children (InstantiateSimpleCubeAvatarWithDynamics). |
ComponentScreenshotCapture.cs / WindowScreenshotCapture.cs | The actual capture plumbing (open, wait a couple of frames, grab pixels, save PNG, clean up). Rarely need touching. |
CaptureEnvironmentScope.cs / AvatarConverterSettingsFoldoutScope.cs | IDisposable save-force-restore scopes: the former forces English + suppresses the update banner globally, the latter forces one Inspector's persisted foldout state open. Follow this pattern if a new target has similar persisted UI state to manage. |
Procedure
-
Identify the affected target(s). Find the target's file name, its
populate/configure lambda in ScreenshotMenu.cs, and its size constant
in ScreenshotSettings.cs.
-
Update sample data if a field needs it. Add or adjust the
populate/configure lambda so the new/changed field shows plausible,
non-empty data. Two hard rules:
- Only mutate inside the temp-GameObject fallback path — never overwrite
a GameObject the developer selected themselves
(
ComponentScreenshotCapture.Capture's populate callback already only
fires on that path; keep it that way).
- Prefer reusing a
SampleContentFactory helper. If you need a new kind
of sample object, follow the existing convention: build it
procedurally (no new binary assets in the repo), mark temporary
GameObjects HideFlags.HideInHierarchy | HideFlags.DontSave, and have
the caller destroy everything it created in the capture's onDone
callback.
-
Handle collapsed/foldout fields. If the field lives behind a
collapsed foldout, it won't show up in the screenshot just because you
populated it — check which of these three patterns the target's Editor
class uses, and handle it before capture:
SerializedProperty.isExpanded (default array/list drawn via
EditorGUILayout.PropertyField): force it open with
new SerializedObject(component).FindProperty("fieldName").isExpanded = true;
then ApplyModifiedProperties() (see CaptureMaterialSwap in
ScreenshotMenu.cs for a working example).
- A local bool field on the Editor class itself (not a nested
ScriptableSingleton): check its default value first — if it already
defaults to true, nothing to do.
- A
ScriptableSingleton-backed persisted state (survives across the
whole Editor session — this is more common than it looks: e.g. both
AvatarConverterSettingsEditorState and MaterialConversionSettingsEditor's
private nested EditorState are ScriptableSingletons, even though the
latter's foldOutAdditionalMaterialSettings happens to default true).
This is the developer's own real, currently-open-or-closed Inspector
state — never just flip it and leave it. Add a dedicated
save/force/restore scope modeled on
, and dispose it in the
capture's so the developer's own UI state is restored
afterward. (If the field you need already defaults and you're
not forcing anything else on that Editor, you can skip adding a scope —
but if the developer ever collapses it during their own work, the next
capture will silently show it collapsed until someone notices.)
Known gotchas
- A capture comes back blank. Since capture uses Unity's internal
GrabPixels rendering (not real OS screen pixels), this is no longer an
environment/focus/RDP issue — treat it as a real bug and check the Unity
console for an exception during that capture.
PlatformComponentRemoverEditor auto-rebuilds its list every draw.
Its OnInspectorGUIInternal calls TargetComponent.UpdateComponentSettings()
unconditionally on every repaint, which resyncs componentSettings: it
drops any entry whose component reference is no longer present on the
GameObject, and appends a fresh removeOnPC = false, removeOnAndroid = false entry for any sibling component not yet tracked — but an entry
whose component is still present is kept as-is, flags and all, so
edits to an already-tracked entry are not lost across redraws. The
practical implication for this tool: pre-seed componentSettings with
your desired flags before the first draw, right after adding the
sibling component (see MenuCapturePlatformComponentRemover). If you
only add the sibling and let the first draw run before setting flags,
UpdateComponentSettings() will already have created a fresh
default-false entry for it, which is just extra work to find and edit
afterward.
Adding a brand-new capture target
- Add a
Vector2 size constant in ScreenshotSettings.cs.
- Add a
[MenuItem] + a private Capture... method in ScreenshotMenu.cs,
following the pattern of an existing target of the same kind (component
vs. window).
- Add it to the
Capture All queue in ScreenshotMenu.cs.
- If the doc page doesn't have a screenshot placeholder yet, add one to
both the English (
Website/docs/...) and Japanese
(Website/i18n/ja/docusaurus-plugin-content-docs/current/...) pages
before embedding the image.