From 3aafaeb447d9e51738aa7583e915e58dfdd58e3f Mon Sep 17 00:00:00 2001 From: Arthur031221 Date: Fri, 2 Oct 2026 11:54:17 +0800 Subject: [PATCH] fix: restore viewer state after Quick Look export failure --- packages/model-viewer/src/features/ar.ts | 39 ++++++++++-------- .../model-viewer/src/test/features/ar-spec.ts | 40 +++++++++++++++++++ 2 files changed, 63 insertions(+), 16 deletions(-) diff --git a/packages/model-viewer/src/features/ar.ts b/packages/model-viewer/src/features/ar.ts index ef0f4d7cf4..7072ccc948 100644 --- a/packages/model-viewer/src/features/ar.ts +++ b/packages/model-viewer/src/features/ar.ts @@ -405,7 +405,13 @@ configuration or device capabilities'); this[$arButtonContainer].classList.remove('enabled'); - const objectURL = generateUsdz ? await this.prepareUSDZ() : this.iosSrc!; + let objectURL: string; + try { + objectURL = generateUsdz ? await this.prepareUSDZ() : this.iosSrc!; + } catch (error) { + this[$arButtonContainer].classList.add('enabled'); + throw error; + } const modelUrl = new URL(objectURL, self.location.toString()); if (generateUsdz) { @@ -487,15 +493,22 @@ configuration or device capabilities'); } exportGroup.updateWorldMatrix(false, true); - const arraybuffer = await exporter.parseAsync(exportGroup, { - maxTextureSize: isNaN(this.arUsdzMaxTextureSize as any) ? - Infinity : - Math.max(parseInt(this.arUsdzMaxTextureSize), 16), - }); - - for (const m of models) { - exportGroup.remove(m); - target.add(m); + let arraybuffer: ArrayBuffer; + try { + arraybuffer = await exporter.parseAsync(exportGroup, { + maxTextureSize: isNaN(this.arUsdzMaxTextureSize as any) ? + Infinity : + Math.max(parseInt(this.arUsdzMaxTextureSize), 16), + }); + } finally { + for (const m of models) { + exportGroup.remove(m); + target.add(m); + } + if (shadow != null) { + shadow.visible = visible; + } + updateSourceProgress(1); } const blob = new Blob([arraybuffer], { @@ -504,12 +517,6 @@ configuration or device capabilities'); const url = URL.createObjectURL(blob); - updateSourceProgress(1); - - if (shadow != null) { - shadow.visible = visible; - } - return url; } } diff --git a/packages/model-viewer/src/test/features/ar-spec.ts b/packages/model-viewer/src/test/features/ar-spec.ts index a044e3b5c8..9e97f38033 100644 --- a/packages/model-viewer/src/test/features/ar-spec.ts +++ b/packages/model-viewer/src/test/features/ar-spec.ts @@ -19,6 +19,7 @@ import {expect} from 'chai'; import {IS_ANDROID, IS_IOS} from '../../constants.js'; import {$openIOSARQuickLook, $openSceneViewer} from '../../features/ar.js'; +import {$progressTracker, $scene} from '../../model-viewer-base.js'; import {ModelViewerElement} from '../../model-viewer.js'; import {waitForEvent} from '../../utilities.js'; import {assetPath, rafPasses, spy} from '../helpers.js'; @@ -143,6 +144,45 @@ suite('AR', () => { }); suite('openQuickLook', () => { + test( + 'restores the viewer after video texture USDZ conversion fails', + async () => { + element.shadowIntensity = 1; + element.src = assetPath('models/cube.gltf'); + await waitForEvent(element, 'load'); + const texture = + element.createVideoTexture(assetPath('models/lottie-logo.mp4')); + element.model!.materials[0] + .pbrMetallicRoughness.baseColorTexture.setTexture(texture); + + const scene = (element as any)[$scene]; + const parent = scene.models[0].parent; + const shadowVisible = scene.shadow?.visible; + expect(scene.shadow).to.not.be.null; + expect(shadowVisible).to.be.true; + const button = element.shadowRoot!.querySelector('.ar-button')!; + button.classList.add('enabled'); + const progress = (element as any)[$progressTracker]; + expect(progress.ongoingActivityCount).to.equal(0); + + let thrown: unknown; + try { + await (element as any)[$openIOSARQuickLook](); + } catch (error) { + thrown = error; + } + + expect(thrown).to.be.instanceOf(Error); + expect((thrown as Error).message) + .to.include('No valid image data found'); + expect([ + scene.models[0].parent === parent, + scene.shadow?.visible === shadowVisible, + progress.ongoingActivityCount === 0, + button.classList.contains('enabled'), + ]).to.deep.equal([true, true, true, true]); + }); + test('sets hash for fixed scale', () => { element.src = 'https://example.com/model.gltf'; element.iosSrc = 'https://example.com/model.usdz';