From 986995fb8223808a85d326550cb20fc7522a11ad Mon Sep 17 00:00:00 2001 From: Open Pascal Date: Wed, 24 Jun 2026 19:51:18 +0000 Subject: [PATCH] fix(editor): decompress compressed textures on GLB export 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: anton-pascal --- .../editor/src/components/editor/export-manager.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/editor/src/components/editor/export-manager.tsx b/packages/editor/src/components/editor/export-manager.tsx index abbfebb3d..83f98a33d 100644 --- a/packages/editor/src/components/editor/export-manager.tsx +++ b/packages/editor/src/components/editor/export-manager.tsx @@ -7,6 +7,7 @@ import type { Mesh, Object3D } from 'three' import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js' import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter.js' import { STLExporter } from 'three/examples/jsm/exporters/STLExporter.js' +import * as WebGPUTextureUtils from 'three/examples/jsm/utils/WebGPUTextureUtils.js' export function ExportManager() { const scene = useThree((state) => state.scene) @@ -43,6 +44,18 @@ export function ExportManager() { // Default: GLB export (existing behavior) 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((resolve, reject) => { exporter.parse( exportScene,