From 088f6c50a42f1fb664449936db5c085f250018a8 Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 24 Jun 2026 12:56:30 -0700 Subject: [PATCH] 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 --- .../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 abbfebb3..83f98a33 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,