fix(editor): decompress compressed textures on GLB export (#435)

three r184's GLTFExporter throws 'setTextureUtils() must be called' when a
scene contains KTX2/basis-compressed textures. Wire up WebGPUTextureUtils
(matching the app's WebGPURenderer) so compressed textures are decompressed
during GLB export.

decompress() is called without the live renderer on purpose: it resizes
whatever renderer it is given and never restores it, which would corrupt the
visible canvas. Omitting it lets three create and dispose its own throwaway
renderer for the blit.

Reworked from #435 (anton-pascal); switches WebGL->WebGPU utils and avoids the
live-renderer resize gotcha surfaced in review.

Co-authored-by: Open Pascal <open@pascal.app>
This commit is contained in:
Anton
2026-06-24 15:56:30 -04:00
committed by GitHub
co-authored by Open Pascal
parent 5fe0965297
commit 088f6c50a4
@@ -7,6 +7,7 @@ import type { Mesh, Object3D } from 'three'
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js' import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js'
import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter.js' import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter.js'
import { STLExporter } from 'three/examples/jsm/exporters/STLExporter.js' import { STLExporter } from 'three/examples/jsm/exporters/STLExporter.js'
import * as WebGPUTextureUtils from 'three/examples/jsm/utils/WebGPUTextureUtils.js'
export function ExportManager() { export function ExportManager() {
const scene = useThree((state) => state.scene) const scene = useThree((state) => state.scene)
@@ -43,6 +44,18 @@ export function ExportManager() {
// Default: GLB export (existing behavior) // Default: GLB export (existing behavior)
const exporter = new GLTFExporter() const exporter = new GLTFExporter()
// Compressed (KTX2/basis) textures must be decompressed during export or
// three r184's GLTFExporter throws "setTextureUtils() must be called".
// The app renders with WebGPURenderer, so use the WebGPU texture utils.
// We intentionally do NOT pass the live renderer: decompress() resizes
// whatever renderer it's given (and never restores it), which would
// corrupt the visible canvas. Omitting it lets three spin up and dispose
// its own throwaway renderer for the blit instead.
exporter.setTextureUtils({
decompress: (texture, maxTextureSize) =>
WebGPUTextureUtils.decompress(texture, maxTextureSize),
})
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
exporter.parse( exporter.parse(
exportScene, exportScene,