From d4d66f0987809b3fe3728c76ef56056253e09bb3 Mon Sep 17 00:00:00 2001 From: bymyself Date: Sat, 2 May 2026 03:16:23 +0000 Subject: [PATCH] fix(load3d): reapply up-direction after fitToViewer() transform reset fitToViewer() resets model.rotation to (0,0,0) but did not reapply currentUpDirection afterward, causing a state/view mismatch when the user had previously selected a non-default up-axis. Fixes https://github.com/Comfy-Org/ComfyUI_frontend/issues/11347 Co-Authored-By: Claude Sonnet 4.6 --- .../core/load3d/SceneModelManager.test.ts | 70 +++++++++++++++++++ .../core/load3d/SceneModelManager.ts | 5 ++ 2 files changed, 75 insertions(+) diff --git a/src/extensions/core/load3d/SceneModelManager.test.ts b/src/extensions/core/load3d/SceneModelManager.test.ts index 4a78daf29d..5c174db46f 100644 --- a/src/extensions/core/load3d/SceneModelManager.test.ts +++ b/src/extensions/core/load3d/SceneModelManager.test.ts @@ -760,4 +760,74 @@ describe('SceneModelManager', () => { expect(mainModels).toHaveLength(1) }) }) + + describe('fitToViewer', () => { + it('does nothing when there is no current model', () => { + const { manager, setupCamera, setupGizmo } = createManager() + + manager.fitToViewer() + + expect(setupCamera).not.toHaveBeenCalled() + expect(setupGizmo).not.toHaveBeenCalled() + }) + + it('scales and centers the model', async () => { + const { manager } = createManager() + const model = createMeshModel() + await manager.setupModel(model) + + model.scale.set(0.1, 0.1, 0.1) + manager.fitToViewer() + + expect(model.scale.x).toBeGreaterThan(0.1) + expect(model.scale.y).toBeGreaterThan(0.1) + expect(model.scale.z).toBeGreaterThan(0.1) + }) + + it('reapplies non-original up-direction after fitting', async () => { + const { manager, eventManager } = createManager() + const model = createMeshModel() + await manager.setupModel(model) + + manager.setUpDirection('+z') + vi.mocked(eventManager.emitEvent).mockClear() + + manager.fitToViewer() + + expect(manager.currentUpDirection).toBe('+z') + expect(model.rotation.x).toBeCloseTo(-Math.PI / 2) + expect(eventManager.emitEvent).toHaveBeenCalledWith( + 'upDirectionChange', + '+z' + ) + }) + + it('does not call setUpDirection when up-direction is original', async () => { + const { manager, eventManager } = createManager() + const model = createMeshModel() + await manager.setupModel(model) + vi.mocked(eventManager.emitEvent).mockClear() + + manager.fitToViewer() + + expect(eventManager.emitEvent).not.toHaveBeenCalledWith( + 'upDirectionChange', + expect.anything() + ) + }) + + it('resets originalRotation so up-direction is not compounded after fit', async () => { + const { manager } = createManager() + const model = createMeshModel() + await manager.setupModel(model) + + manager.setUpDirection('+x') + const rotationAfterFirstSet = model.rotation.z + + manager.fitToViewer() + manager.setUpDirection('+x') + + expect(model.rotation.z).toBeCloseTo(rotationAfterFirstSet) + }) + }) }) diff --git a/src/extensions/core/load3d/SceneModelManager.ts b/src/extensions/core/load3d/SceneModelManager.ts index 8b5ef3f29b..bb29017435 100644 --- a/src/extensions/core/load3d/SceneModelManager.ts +++ b/src/extensions/core/load3d/SceneModelManager.ts @@ -491,6 +491,11 @@ export class SceneModelManager implements ModelManagerInterface { model.position.set(-center.x, -scaledBox.min.y, -center.z) + if (this.currentUpDirection !== 'original') { + this.originalRotation = null + this.setUpDirection(this.currentUpDirection) + } + const newBox = this.computeWorldBounds(model) const newSize = newBox.getSize(new THREE.Vector3()) const newCenter = newBox.getCenter(new THREE.Vector3())