From 6c4d75c16884bb29ef870344de675189b82683dd Mon Sep 17 00:00:00 2001 From: Arthur031221 Date: Wed, 7 Oct 2026 12:57:37 +0800 Subject: [PATCH] fix: omit horizontal anchoring for Quick Look wall exports Adopt the wall-export options from the follow-up to #4030 and test the USDZ metadata, model transforms and viewer rotation. Co-authored-by: Vincent Fretin <112249+vincentfretin@users.noreply.github.com> --- packages/model-viewer/src/features/ar.ts | 5 ++ .../model-viewer/src/test/features/ar-spec.ts | 59 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/packages/model-viewer/src/features/ar.ts b/packages/model-viewer/src/features/ar.ts index 7072ccc948..29ba9a4822 100644 --- a/packages/model-viewer/src/features/ar.ts +++ b/packages/model-viewer/src/features/ar.ts @@ -482,6 +482,10 @@ configuration or device capabilities'); updateSourceProgress(0.2); + const usdzOptions = this.arPlacement === 'wall' ? + {includeAnchoringProperties: false} : + {}; + const exporter = new USDZExporter(); const exportGroup = new Object3D(); @@ -496,6 +500,7 @@ configuration or device capabilities'); let arraybuffer: ArrayBuffer; try { arraybuffer = await exporter.parseAsync(exportGroup, { + ...usdzOptions, maxTextureSize: isNaN(this.arUsdzMaxTextureSize as any) ? Infinity : Math.max(parseInt(this.arUsdzMaxTextureSize), 16), diff --git a/packages/model-viewer/src/test/features/ar-spec.ts b/packages/model-viewer/src/test/features/ar-spec.ts index 9e97f38033..80ca0278e2 100644 --- a/packages/model-viewer/src/test/features/ar-spec.ts +++ b/packages/model-viewer/src/test/features/ar-spec.ts @@ -16,6 +16,7 @@ import '../renderer-gate.js'; import {expect} from 'chai'; +import {strFromU8, unzipSync} from 'three/examples/jsm/libs/fflate.module.js'; import {IS_ANDROID, IS_IOS} from '../../constants.js'; import {$openIOSARQuickLook, $openSceneViewer} from '../../features/ar.js'; @@ -250,6 +251,64 @@ suite('AR', () => { }); }); + suite('prepareUSDZ placement', () => { + const readUsda = async (url: string) => { + try { + const response = await fetch(url); + const files = unzipSync(new Uint8Array(await response.arrayBuffer())); + return strFromU8(files['model.usda']); + } finally { + URL.revokeObjectURL(url); + } + }; + + const transforms = (usda: string) => usda.split('\n').filter( + line => line.includes('matrix4d xformOp:transform')); + + for (const src + of [assetPath('models/cube.gltf'), + assetPath('models/offcenter-cube.gltf'), + '/packages/modelviewer.dev/assets/boom_2_.glb']) { + test(`preserves transforms and viewer state for ${src}`, async () => { + element.src = src; + element.orientation = '15deg 25deg 35deg'; + await waitForEvent(element, 'load'); + await element.updateComplete; + + const scene = element[$scene]; + const model = scene.models[0]; + const position = model.position.clone(); + const quaternion = model.quaternion.clone(); + const scale = model.scale.clone(); + const targetPosition = scene.target.position.clone(); + + const floor = await readUsda(await (element as any).prepareUSDZ()); + expect(floor).to.include('preliminary:anchoring:type = "plane"'); + expect(floor).to.include( + 'preliminary:planeAnchoring:alignment = "horizontal"'); + expect(transforms(floor)).not.to.be.empty; + + element.arPlacement = 'wall'; + element.resetTurntableRotation(0.7); + const wall = await readUsda(await (element as any).prepareUSDZ()); + expect(wall).not.to.include('preliminary:anchoring:type'); + expect(wall).not.to.include('preliminary:planeAnchoring:alignment'); + expect(transforms(wall)).to.deep.equal(transforms(floor)); + + element.resetTurntableRotation(1.3); + const rotatedWall = + await readUsda(await (element as any).prepareUSDZ()); + expect(transforms(rotatedWall)).to.deep.equal(transforms(wall)); + expect(element.turntableRotation).to.equal(1.3); + expect(model.parent).to.equal(scene.target); + expect(model.position.equals(position)).to.be.true; + expect(model.quaternion.equals(quaternion)).to.be.true; + expect(model.scale.equals(scale)).to.be.true; + expect(scene.target.position.equals(targetPosition)).to.be.true; + }); + } + }); + suite('shows the AR button', () => { setup(async () => { element.ar = true;