Skip to content

Fix thumbnail export: stale camera angle, redirector crash, opaque transparent background - #5

Open
GatheredSatyr53 wants to merge 1 commit into
NanceDevDiaries:mainfrom
GatheredSatyr53:fix/thumbnail-export-rotation-crash-transparency
Open

Fix thumbnail export: stale camera angle, redirector crash, opaque transparent background#5
GatheredSatyr53 wants to merge 1 commit into
NanceDevDiaries:mainfrom
GatheredSatyr53:fix/thumbnail-export-rotation-crash-transparency

Conversation

@GatheredSatyr53

Copy link
Copy Markdown

Three independent defects in the "Export to Texture" action, all in FThumbnailToTextureToolModule::ExecuteSaveThumbnailAsTexture. They surfaced in one session while exporting static mesh thumbnails, but none of them are specific to static meshes.

1. Exported texture always used the default camera angle

With the default settings (neither UseTransparentBackground nor UseCustomBackgroundMaterial) the export never rendered anything. It read the thumbnail cached inside the asset's .uasset through ThumbnailTools::LoadThumbnailsFromPackage.

That cached copy is only regenerated on save when it is missing, empty or dirty (UnrealEdSrv.cpp, the ObjectsMissingThumbnails branch). Rotating a thumbnail in the Content Browser goes through SAssetThumbnailEditModeTools::OnMouseMove, which mutates the asset's USceneThumbnailInfo and, on mouse up, calls MarkPackageDirty() on that info object - the cached image itself is never marked dirty. So the cache keeps whatever angle was baked in at first save, normally the default one.

The Content Browser hides this, because static meshes are drawn there live via UStaticMeshThumbnailRenderer and the cache is ignored. The export took the cache, hence the mismatch.

Now the thumbnail is rendered on demand with ThumbnailTools::RenderThumbnail(..., AlwaysFlush, ...) - the same path the Content Browser uses - so the orbit the user set is honoured. Background appearance is unchanged, since this still goes through the engine's own renderer.

2. Editor crashed when the target name was occupied by a redirector

Deleting or moving a previously exported texture can leave an UObjectRedirector at that path. NewObject<UTexture2D>() with an explicit name then hit a fatal error in StaticAllocateObject and took the whole editor down:

Fatal error: UObjectGlobals.cpp [Line: 3673]
Cannot replace existing object of a different class.
  New Object:      /Script/Engine.Texture2D             /Game/ProceduralTextures/T_Foo
  Existing Object: /Script/CoreUObject.ObjectRedirector /Game/ProceduralTextures/T_Foo

The path is now checked before allocating:

  • a redirector is moved aside into the transient package - it is only a forwarding stub, and saving the package drops it;
  • an unrelated asset of some other class is no longer overwritten: an error is logged and that asset is skipped;
  • an existing UTexture2D still gets replaced, as before.

3. Transparent background came out opaque white

Transparency is chroma keyed against the background material's Color parameter, but the render target was cleared to a hardcoded FLinearColor::White. BackgroundMaterialNoShadow keys on magenta (1, 0, 1, 1), so any pixel the background geometry did not cover kept the white clear colour, never matched the key, and stayed fully opaque.

The key colour is now resolved once and used for both the clear colour and the alpha cutoff, so the two cannot drift apart. The return value of GetVectorParameterValue is honoured as well: FLinearColor's default constructor leaves the channels uninitialised (FLinearColor() = default;), so ignoring it risked keying against stack garbage if the parameter were ever renamed.

Also in passing

  • Dropped ObjectFullName / ObjectFullNames, dead once the cache read is gone.
  • continue rather than return when an asset name is unusable, so one bad entry no longer abandons the rest of the selection.
  • Collapsed a row-by-row copy that used the same offset for source and destination - it only ever copied each row onto itself - into the plain memcpy it always was.

Testing

Built with RunUAT BuildPlugin against UE 5.7: BUILD SUCCESSFUL, no new warnings. The only warning in the log is pre-existing (CustomThumbnailHelpers.cpp:524, UStaticMesh::ThumbnailInfo deprecated in 5.7 in favour of GetThumbnailInfo()); left alone to keep this PR scoped.

Note this branch is cut from main and does not include the missing-include fix in #4; the two are independent. A non-unity build such as BuildPlugin needs #4 as well, which was applied locally for the verification above.

Co-authored with Claude Opus 5.

🤖 Generated with Claude Code

Three independent defects in the "Export to Texture" action, all in
FThumbnailToTextureToolModule::ExecuteSaveThumbnailAsTexture.

1. Exported texture always used the default camera angle.

With the default settings (neither UseTransparentBackground nor
UseCustomBackgroundMaterial) the export never rendered anything - it read
the thumbnail cached inside the asset's .uasset via
ThumbnailTools::LoadThumbnailsFromPackage. That cached copy is only
regenerated on save when it is missing, empty or dirty, and rotating a
thumbnail in the Content Browser only mutates the asset's
USceneThumbnailInfo; it calls MarkPackageDirty() on the info object, never
marking the cached image itself dirty. The cache therefore keeps whatever
angle was baked in at first save - normally the default one - while the
Content Browser shows a live render and looks correct. Render the
thumbnail on demand through ThumbnailTools::RenderThumbnail instead, which
is the same path the Content Browser uses, so the user's orbit is honoured.

2. Editor crashed when the target name was occupied by a redirector.

Deleting or moving a previously exported texture can leave an
UObjectRedirector at that path. NewObject<UTexture2D>() with an explicit
name then hit the fatal "Cannot replace existing object of a different
class" in StaticAllocateObject and took the editor down. Check the path
first: move a redirector aside into the transient package (it is only a
forwarding stub, and saving the package drops it), and refuse to overwrite
an unrelated asset of some other class - log an error and skip instead.

3. Transparent background came out opaque white.

Transparency is chroma keyed against the background material's "Color"
parameter, but the render target was cleared to a hardcoded
FLinearColor::White. Any pixel the background geometry did not cover kept
that white, never matched the key colour, and stayed opaque. Resolve the
key colour once and use it for both the clear colour and the cutoff, so
the two cannot drift apart. The return value of GetVectorParameterValue is
now honoured as well - FLinearColor's default constructor leaves the
channels uninitialised, so ignoring it risked keying against stack
garbage.

Also in passing:
- Drop ObjectFullName/ObjectFullNames, dead once the cache read is gone.
- Use continue rather than return when an asset name is unusable, so one
  bad entry no longer abandons the rest of the selection.
- Collapse a row-by-row copy that used the same offset for source and
  destination, so it only ever copied each row onto itself, into the plain
  memcpy it always was.

Verified with RunUAT BuildPlugin against UE 5.7: BUILD SUCCESSFUL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant