@shopware-ag/dive 1.18.5 → 1.19.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +8 -0
  2. package/build/dive.cjs +220 -215
  3. package/build/dive.cjs.map +1 -1
  4. package/build/dive.js +233 -21102
  5. package/build/dive.js.map +1 -1
  6. package/build/dive.mjs +26111 -0
  7. package/build/dive.mjs.map +1 -0
  8. package/build/src/ar/AR.d.ts +37 -14
  9. package/build/src/ar/arquicklook/ARQuickLook.d.ts +5 -6
  10. package/build/src/ar/sceneviewer/SceneViewer.d.ts +42 -6
  11. package/build/src/com/actions/index.d.ts +2 -0
  12. package/build/src/com/actions/renderer/startrender.d.ts +5 -0
  13. package/build/src/com/actions/scene/launchar.d.ts +5 -2
  14. package/build/src/converter/Converter.d.ts +21 -0
  15. package/build/src/dive.d.ts +3 -2
  16. package/build/src/exporter/Exporter.d.ts +11 -0
  17. package/build/src/helper/applyMixins/applyMixins.d.ts +22 -6
  18. package/build/src/info/Info.d.ts +37 -13
  19. package/build/src/interface/Movable.d.ts +5 -5
  20. package/build/src/interface/Selectable.d.ts +4 -4
  21. package/build/src/loader/Loader.d.ts +11 -0
  22. package/build/src/model/Model.d.ts +2 -2
  23. package/build/src/node/Node.d.ts +3 -3
  24. package/build/src/scene/root/Root.d.ts +1 -1
  25. package/build/src/types/ExporterOptions.d.ts +15 -0
  26. package/build/src/types/FileTypes.d.ts +27 -0
  27. package/build/src/types/index.d.ts +4 -0
  28. package/build/src/types/info/index.d.ts +66 -0
  29. package/package.json +16 -1
  30. package/src/ar/AR.ts +72 -69
  31. package/src/ar/__test__/AR.test.ts +194 -105
  32. package/src/ar/arquicklook/ARQuickLook.ts +32 -72
  33. package/src/ar/arquicklook/__test__/ARQuickLook.test.ts +89 -38
  34. package/src/ar/sceneviewer/SceneViewer.ts +96 -51
  35. package/src/ar/sceneviewer/__test__/SceneViewer.test.ts +144 -47
  36. package/src/ar/webxr/WebXR.ts +5 -4
  37. package/src/com/Communication.ts +10 -7
  38. package/src/com/__test__/Communication.test.ts +16 -3
  39. package/src/com/actions/index.ts +2 -0
  40. package/src/com/actions/renderer/startrender.ts +5 -0
  41. package/src/com/actions/scene/launchar.ts +2 -2
  42. package/src/converter/Converter.ts +117 -0
  43. package/src/dive.ts +10 -6
  44. package/src/exporter/Exporter.ts +75 -0
  45. package/src/helper/applyMixins/applyMixins.ts +59 -7
  46. package/src/info/Info.ts +99 -75
  47. package/src/info/__test__/Info.test.ts +162 -154
  48. package/src/interface/Movable.ts +5 -5
  49. package/src/interface/Selectable.ts +4 -4
  50. package/src/loader/Loader.ts +48 -0
  51. package/src/model/Model.ts +10 -5
  52. package/src/model/__test__/Model.test.ts +4 -11
  53. package/src/node/Node.ts +7 -5
  54. package/src/scene/root/Root.ts +4 -4
  55. package/src/scene/root/__test__/Root.test.ts +4 -4
  56. package/src/toolbox/Toolbox.ts +1 -3
  57. package/src/types/ExporterOptions.ts +14 -0
  58. package/src/types/FileTypes.ts +37 -0
  59. package/src/types/index.ts +26 -0
  60. package/src/types/info/index.ts +76 -0
  61. package/build/src/exporters/usdz/USDZExporter.d.ts +0 -15
  62. package/build/src/loadingmanager/LoadingManager.d.ts +0 -14
  63. package/src/exporters/usdz/USDZExporter.ts +0 -21
  64. package/src/exporters/usdz/__test__/USDZExporter.test.ts +0 -57
  65. package/src/loadingmanager/LoadingManager.ts +0 -50
  66. package/src/loadingmanager/__test__/LoadingManager.test.ts +0 -27
package/src/ar/AR.ts CHANGED
@@ -1,49 +1,40 @@
1
- import { DIVEInfo, WebXRUnsupportedReason } from '../info/Info';
2
- import { DIVEARQuickLook } from './arquicklook/ARQuickLook';
3
- import { DIVEWebXR } from './webxr/WebXR';
4
- import { type DIVEScene } from '../scene/Scene';
5
- import { type DIVERenderer } from '../renderer/Renderer';
6
- import DIVEOrbitControls from '../controls/OrbitControls';
7
- import { DIVESceneViewer } from './sceneviewer/SceneViewer';
8
-
9
- export type DIVEAROptions = {
1
+ import { SystemInfo } from '../info/Info';
2
+ import { ESystem } from '../types/info';
3
+ import { ARQuickLook } from './arquicklook/ARQuickLook';
4
+ import { SceneViewer } from './sceneviewer/SceneViewer';
5
+
6
+ /**
7
+ * Options for configuring the AR system behavior
8
+ */
9
+ export type ARSystemOptions = {
10
+ /** The placement orientation for AR content - either horizontal or vertical */
10
11
  arPlacement: 'horizontal' | 'vertical';
12
+ /** The scaling behavior for AR content - either automatic or fixed */
11
13
  arScale: 'auto' | 'fixed';
12
- /**
13
- * experimental, currently deactivated
14
- */
15
- useWebXR: false;
16
14
  };
17
15
 
18
- export class DIVEAR {
19
- private _renderer: DIVERenderer;
20
- private _scene: DIVEScene;
21
- private _controller: DIVEOrbitControls;
22
-
23
- constructor(
24
- renderer: DIVERenderer,
25
- scene: DIVEScene,
26
- controller: DIVEOrbitControls,
27
- ) {
28
- this._renderer = renderer;
29
- this._scene = scene;
30
- this._controller = controller;
31
- }
32
-
33
- public async Launch(options?: DIVEAROptions): Promise<void> {
34
- const system = DIVEInfo.GetSystem();
16
+ /**
17
+ * Main class for handling AR functionality across different platforms
18
+ * Provides methods to launch AR experiences using platform-specific implementations
19
+ */
20
+ export class ARSystem {
21
+ /**
22
+ * Launches an AR experience using the appropriate platform-specific implementation
23
+ *
24
+ * @param uri - The URI of the 3D model to display in AR
25
+ * @param options - Optional configuration for the AR experience
26
+ * @returns Promise that resolves when AR is launched successfully
27
+ * @throws Error if AR is not supported on the current platform
28
+ */
29
+ public async launch(uri: string, options?: ARSystemOptions): Promise<void> {
30
+ const system = SystemInfo.GetSystem();
35
31
 
36
- if (system === 'iOS') {
37
- return this.tryARQuickLook();
32
+ if (system === ESystem.IOS) {
33
+ return this.tryARQuickLook(uri, options);
38
34
  }
39
35
 
40
- if (system === 'Android') {
41
- if (options?.useWebXR) {
42
- console.warn('DIVE: WebXR is experimental on Android.');
43
- return this.tryWebXR();
44
- }
45
-
46
- return this.trySceneViewer();
36
+ if (system === ESystem.ANDROID) {
37
+ return this.trySceneViewer(uri, options);
47
38
  }
48
39
 
49
40
  console.log(
@@ -51,48 +42,60 @@ export class DIVEAR {
51
42
  system +
52
43
  ')',
53
44
  );
45
+ return Promise.reject(
46
+ new Error('AR not supported on non-mobile systems'),
47
+ );
54
48
  }
55
49
 
56
- private async tryARQuickLook(options?: DIVEAROptions): Promise<void> {
57
- const support = DIVEInfo.GetSupportsARQuickLook();
50
+ /**
51
+ * Attempts to launch AR using ARQuickLook (iOS-specific implementation)
52
+ *
53
+ * @param uri - The URI of the 3D model to display in AR
54
+ * @param options - Optional configuration for the AR experience
55
+ * @returns Promise that resolves when ARQuickLook is launched successfully
56
+ * @throws Error if ARQuickLook is not supported on the device
57
+ */
58
+ private async tryARQuickLook(
59
+ uri: string,
60
+ options?: ARSystemOptions,
61
+ ): Promise<void> {
62
+ const support = SystemInfo.GetSupportsARQuickLook();
58
63
  if (!support) {
59
64
  console.log('ARQuickLook not supported');
60
- return Promise.reject();
65
+ return Promise.reject(new Error('ARQuickLook not supported'));
61
66
  }
62
67
 
63
68
  console.log('DIVE: Launching AR with ARQuickLook ...');
64
69
 
65
- // Launch ARQuickLook
66
- await DIVEARQuickLook.Launch(this._scene, options);
67
- return Promise.resolve();
68
- }
69
-
70
- private async tryWebXR(): Promise<void> {
71
- const support = await DIVEInfo.GetSupportsWebXR();
72
- if (!support) {
73
- console.log(
74
- 'WebXR not supported. Reason: ' +
75
- WebXRUnsupportedReason[
76
- DIVEInfo.GetWebXRUnsupportedReason()!
77
- ],
78
- );
79
- return Promise.reject();
70
+ try {
71
+ return new ARQuickLook().launch(uri, options);
72
+ } catch (error) {
73
+ console.error('Error launching ARQuickLook:', error);
74
+ return Promise.reject(error);
80
75
  }
81
-
82
- console.log('DIVE: Launching AR with WebXR ...');
83
- // Launch WebXR
84
- await DIVEWebXR.Launch(this._renderer, this._scene, this._controller);
85
- return Promise.resolve();
86
76
  }
87
77
 
88
- private async trySceneViewer(options?: DIVEAROptions): Promise<void> {
89
- // actually we don't have to try here, because SceneViewer is supported on all devices by now.
90
- // if there are no AR services (ARCore) installed on the device, SceneViewer will only show the model in 3D.
91
- // we also have no options to detect if SceneViewer is supported.
92
-
78
+ /**
79
+ * Launches AR using SceneViewer (Android-specific implementation)
80
+ * Note: SceneViewer is supported on all Android devices. If ARCore is not installed,
81
+ * the model will be displayed in 3D view mode instead of AR mode.
82
+ *
83
+ * @param uri - The URI of the 3D model to display in AR
84
+ * @param options - Optional configuration for the AR experience
85
+ * @returns Promise that resolves when SceneViewer is launched successfully
86
+ * @throws Error if there's an issue launching SceneViewer
87
+ */
88
+ private async trySceneViewer(
89
+ uri: string,
90
+ options?: ARSystemOptions,
91
+ ): Promise<void> {
93
92
  console.log('DIVE: Launching AR with SceneViewer ...');
94
93
 
95
- DIVESceneViewer.Launch(this._scene, options);
96
- return Promise.resolve();
94
+ try {
95
+ return new SceneViewer().launch(uri, options);
96
+ } catch (error) {
97
+ console.error('Error launching SceneViewer:', error);
98
+ return Promise.reject(error);
99
+ }
97
100
  }
98
101
  }
@@ -1,187 +1,276 @@
1
- import { DIVEAR, type DIVEAROptions } from '../AR';
2
- import { DIVEInfo } from '../../info/Info';
3
- import { DIVERenderer } from '../../renderer/Renderer';
4
- import { DIVEScene } from '../../scene/Scene';
5
- import DIVEOrbitControls from '../../controls/OrbitControls';
6
- import { DIVEARQuickLook } from '../arquicklook/ARQuickLook';
7
- import { DIVESceneViewer } from '../sceneviewer/SceneViewer';
8
- import { DIVEWebXR } from '../webxr/WebXR';
9
-
10
- jest.mock('../arquicklook/ARQuickLook', () => ({
11
- DIVEARQuickLook: {
12
- Launch: jest.fn(),
1
+ import { ARSystem, type ARSystemOptions } from '../AR';
2
+ import { SystemInfo } from '../../info/Info';
3
+ import { ARQuickLook } from '../arquicklook/ARQuickLook';
4
+ import { SceneViewer } from '../sceneviewer/SceneViewer';
5
+
6
+ // Mock Info
7
+ jest.mock('../../info/Info', () => ({
8
+ SystemInfo: {
9
+ GetSystem: jest.fn(),
10
+ GetSupportsARQuickLook: jest.fn(),
13
11
  },
14
12
  }));
15
13
 
16
- jest.mock('../webxr/WebXR', () => ({
17
- DIVEWebXR: {
18
- Launch: jest.fn(),
19
- },
14
+ // Mock ARQuickLook
15
+ const mockLaunchARQuickLook = jest.fn().mockResolvedValue(undefined);
16
+ jest.mock('../arquicklook/ARQuickLook', () => ({
17
+ ARQuickLook: jest.fn().mockImplementation(() => ({
18
+ launch: mockLaunchARQuickLook,
19
+ })),
20
20
  }));
21
21
 
22
+ // Mock SceneViewer
23
+ const mockSceneViewerLaunch = jest.fn().mockResolvedValue(undefined);
22
24
  jest.mock('../sceneviewer/SceneViewer', () => ({
23
- DIVESceneViewer: {
24
- Launch: jest.fn(),
25
- },
25
+ SceneViewer: jest.fn().mockImplementation(() => ({
26
+ launch: mockSceneViewerLaunch,
27
+ })),
26
28
  }));
27
29
 
28
- describe('DIVEAR', () => {
29
- let renderer: DIVERenderer;
30
- let scene: DIVEScene;
31
- let controller: DIVEOrbitControls;
32
- let diveAR: DIVEAR;
30
+ describe('ARSystem', () => {
31
+ let diveAR: ARSystem;
32
+ const mockUri = 'https://example.com/model.glb';
33
33
 
34
34
  beforeEach(() => {
35
- renderer = {} as DIVERenderer;
36
- scene = {} as DIVEScene;
37
- controller = {} as DIVEOrbitControls;
38
- diveAR = new DIVEAR(renderer, scene, controller);
35
+ diveAR = new ARSystem();
36
+ jest.clearAllMocks();
39
37
  });
40
38
 
41
- describe('Launch', () => {
39
+ describe('launch', () => {
42
40
  describe('AR Quick Look', () => {
43
41
  it('should launch ARQuickLook on iOS', async () => {
44
- jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('iOS');
45
- jest.spyOn(DIVEInfo, 'GetSupportsARQuickLook').mockReturnValue(
46
- true,
47
- );
48
- const arQuickLookLaunchSpy = jest.spyOn(
49
- DIVEARQuickLook,
50
- 'Launch',
51
- );
42
+ (SystemInfo.GetSystem as jest.Mock).mockReturnValue('iOS');
43
+ (
44
+ SystemInfo.GetSupportsARQuickLook as jest.Mock
45
+ ).mockReturnValue(true);
52
46
 
53
47
  const consoleLogSpy = jest
54
48
  .spyOn(console, 'log')
55
49
  .mockImplementation();
56
50
 
57
- await diveAR.Launch();
51
+ await diveAR.launch(mockUri);
58
52
 
59
- expect(arQuickLookLaunchSpy).toHaveBeenCalledWith(
60
- scene,
53
+ expect(mockLaunchARQuickLook).toHaveBeenCalledWith(
54
+ mockUri,
61
55
  undefined,
62
56
  );
63
- arQuickLookLaunchSpy.mockRestore();
57
+ expect(consoleLogSpy).toHaveBeenCalledWith(
58
+ 'DIVE: Launching AR with ARQuickLook ...',
59
+ );
60
+ consoleLogSpy.mockRestore();
61
+ });
62
+
63
+ it('should launch ARQuickLook on iOS with options', async () => {
64
+ (SystemInfo.GetSystem as jest.Mock).mockReturnValue('iOS');
65
+ (
66
+ SystemInfo.GetSupportsARQuickLook as jest.Mock
67
+ ).mockReturnValue(true);
64
68
 
65
- expect(consoleLogSpy).toHaveBeenCalled();
69
+ const options: ARSystemOptions = {
70
+ arPlacement: 'vertical',
71
+ arScale: 'fixed',
72
+ };
73
+
74
+ const consoleLogSpy = jest
75
+ .spyOn(console, 'log')
76
+ .mockImplementation();
77
+
78
+ await diveAR.launch(mockUri, options);
79
+
80
+ expect(mockLaunchARQuickLook).toHaveBeenCalledWith(
81
+ mockUri,
82
+ options,
83
+ );
84
+ expect(consoleLogSpy).toHaveBeenCalledWith(
85
+ 'DIVE: Launching AR with ARQuickLook ...',
86
+ );
66
87
  consoleLogSpy.mockRestore();
67
88
  });
68
89
 
69
90
  it('should not launch ARQuickLook on iOS if not supported', async () => {
70
- jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('iOS');
71
- jest.spyOn(DIVEInfo, 'GetSupportsARQuickLook').mockReturnValue(
72
- false,
91
+ (SystemInfo.GetSystem as jest.Mock).mockReturnValue('iOS');
92
+ (
93
+ SystemInfo.GetSupportsARQuickLook as jest.Mock
94
+ ).mockReturnValue(false);
95
+
96
+ const consoleLogSpy = jest
97
+ .spyOn(console, 'log')
98
+ .mockImplementation();
99
+
100
+ try {
101
+ await diveAR.launch(mockUri);
102
+ fail('Expected launch to reject');
103
+ } catch (error: unknown) {
104
+ if (error instanceof Error) {
105
+ expect(error.message).toBe('ARQuickLook not supported');
106
+ } else {
107
+ fail('Expected error to be an Error instance');
108
+ }
109
+ }
110
+ expect(consoleLogSpy).toHaveBeenCalledWith(
111
+ 'ARQuickLook not supported',
73
112
  );
74
- const arQuickLookLaunchSpy = jest.spyOn(
75
- DIVEARQuickLook,
76
- 'Launch',
113
+ consoleLogSpy.mockRestore();
114
+ });
115
+
116
+ it('should handle ARQuickLook launch errors', async () => {
117
+ (SystemInfo.GetSystem as jest.Mock).mockReturnValue('iOS');
118
+ (
119
+ SystemInfo.GetSupportsARQuickLook as jest.Mock
120
+ ).mockReturnValue(true);
121
+
122
+ const mockError = new Error('Launch failed');
123
+ const mockInstance = {
124
+ launch: jest.fn().mockImplementation(() => {
125
+ throw mockError;
126
+ }),
127
+ };
128
+ (ARQuickLook as jest.Mock).mockImplementation(
129
+ () => mockInstance,
77
130
  );
78
131
 
79
132
  const consoleLogSpy = jest
80
133
  .spyOn(console, 'log')
81
134
  .mockImplementation();
135
+ const consoleErrorSpy = jest
136
+ .spyOn(console, 'error')
137
+ .mockImplementation();
82
138
 
83
- await diveAR.Launch().catch(() => {});
84
-
85
- expect(arQuickLookLaunchSpy).not.toHaveBeenCalled();
86
- arQuickLookLaunchSpy.mockRestore();
139
+ try {
140
+ await diveAR.launch(mockUri);
141
+ fail('Expected launch to reject');
142
+ } catch (error: unknown) {
143
+ if (error instanceof Error) {
144
+ expect(error.message).toBe('Launch failed');
145
+ } else {
146
+ fail('Expected error to be an Error instance');
147
+ }
148
+ }
149
+
150
+ expect(consoleLogSpy).toHaveBeenCalledWith(
151
+ 'DIVE: Launching AR with ARQuickLook ...',
152
+ );
153
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
154
+ 'Error launching ARQuickLook:',
155
+ mockError,
156
+ );
87
157
 
88
- expect(consoleLogSpy).toHaveBeenCalled();
89
158
  consoleLogSpy.mockRestore();
159
+ consoleErrorSpy.mockRestore();
90
160
  });
91
161
  });
92
162
 
93
163
  describe('Scene Viewer', () => {
94
164
  it('should launch SceneViewer on Android', async () => {
95
- jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Android');
165
+ (SystemInfo.GetSystem as jest.Mock).mockReturnValue('Android');
96
166
 
97
167
  const consoleLogSpy = jest
98
168
  .spyOn(console, 'log')
99
169
  .mockImplementation();
100
170
 
101
- await diveAR.Launch();
171
+ await diveAR.launch(mockUri);
102
172
 
103
- expect(DIVESceneViewer.Launch).toHaveBeenCalledWith(
104
- scene,
173
+ expect(mockSceneViewerLaunch).toHaveBeenCalledWith(
174
+ mockUri,
105
175
  undefined,
106
176
  );
107
-
108
- expect(consoleLogSpy).toHaveBeenCalled();
177
+ expect(consoleLogSpy).toHaveBeenCalledWith(
178
+ 'DIVE: Launching AR with SceneViewer ...',
179
+ );
109
180
  consoleLogSpy.mockRestore();
110
181
  });
111
- });
112
182
 
113
- describe('WebXR', () => {
114
- it('should launch WebXR on Android with useWebXR option', async () => {
115
- jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Android');
116
- jest.spyOn(DIVEInfo, 'GetSupportsWebXR').mockResolvedValue(
117
- true,
118
- );
183
+ it('should launch SceneViewer on Android with options', async () => {
184
+ (SystemInfo.GetSystem as jest.Mock).mockReturnValue('Android');
185
+
186
+ const options: ARSystemOptions = {
187
+ arPlacement: 'vertical',
188
+ arScale: 'fixed',
189
+ };
119
190
 
120
191
  const consoleLogSpy = jest
121
192
  .spyOn(console, 'log')
122
193
  .mockImplementation();
123
- const consoleWarnSpy = jest
124
- .spyOn(console, 'warn')
125
- .mockImplementation();
126
194
 
127
- await diveAR.Launch({
128
- useWebXR: true,
129
- } as unknown as DIVEAROptions);
195
+ await diveAR.launch(mockUri, options);
130
196
 
131
- expect(DIVEWebXR.Launch).toHaveBeenCalledWith(
132
- renderer,
133
- scene,
134
- controller,
197
+ expect(mockSceneViewerLaunch).toHaveBeenCalledWith(
198
+ mockUri,
199
+ options,
200
+ );
201
+ expect(consoleLogSpy).toHaveBeenCalledWith(
202
+ 'DIVE: Launching AR with SceneViewer ...',
135
203
  );
136
- expect(consoleWarnSpy).toHaveBeenCalled();
137
- consoleWarnSpy.mockRestore();
138
-
139
- expect(consoleLogSpy).toHaveBeenCalled();
140
204
  consoleLogSpy.mockRestore();
141
205
  });
142
206
 
143
- it('should not launch WebXR on Android with useWebXR option', async () => {
144
- jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Android');
145
- jest.spyOn(DIVEInfo, 'GetSupportsWebXR').mockResolvedValue(
146
- false,
207
+ it('should handle SceneViewer launch errors', async () => {
208
+ (SystemInfo.GetSystem as jest.Mock).mockReturnValue('Android');
209
+
210
+ const mockError = new Error('Launch failed');
211
+ const mockInstance = {
212
+ launch: jest.fn().mockImplementation(() => {
213
+ throw mockError;
214
+ }),
215
+ };
216
+ (SceneViewer as jest.Mock).mockImplementation(
217
+ () => mockInstance,
147
218
  );
148
219
 
149
220
  const consoleLogSpy = jest
150
221
  .spyOn(console, 'log')
151
222
  .mockImplementation();
152
- const consoleWarnSpy = jest
153
- .spyOn(console, 'warn')
223
+ const consoleErrorSpy = jest
224
+ .spyOn(console, 'error')
154
225
  .mockImplementation();
155
226
 
156
- await diveAR
157
- .Launch({
158
- useWebXR: true,
159
- } as unknown as DIVEAROptions)
160
- .catch(() => {});
161
-
162
- expect(DIVEWebXR.Launch).toHaveBeenCalledWith(
163
- renderer,
164
- scene,
165
- controller,
227
+ try {
228
+ await diveAR.launch(mockUri);
229
+ fail('Expected launch to reject');
230
+ } catch (error: unknown) {
231
+ if (error instanceof Error) {
232
+ expect(error.message).toBe('Launch failed');
233
+ } else {
234
+ fail('Expected error to be an Error instance');
235
+ }
236
+ }
237
+
238
+ expect(consoleLogSpy).toHaveBeenCalledWith(
239
+ 'DIVE: Launching AR with SceneViewer ...',
240
+ );
241
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
242
+ 'Error launching SceneViewer:',
243
+ mockError,
166
244
  );
167
- expect(consoleWarnSpy).toHaveBeenCalled();
168
- consoleWarnSpy.mockRestore();
169
245
 
170
- expect(consoleLogSpy).toHaveBeenCalled();
171
246
  consoleLogSpy.mockRestore();
247
+ consoleErrorSpy.mockRestore();
172
248
  });
173
249
  });
174
250
 
175
- it('should log AR not supported on non-mobile systems', async () => {
251
+ it('should reject on non-mobile systems', async () => {
252
+ (SystemInfo.GetSystem as jest.Mock).mockReturnValue('Windows');
253
+
176
254
  const consoleLogSpy = jest
177
255
  .spyOn(console, 'log')
178
256
  .mockImplementation();
179
257
 
180
- jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Windows');
181
-
182
- await diveAR.Launch();
183
-
184
- expect(consoleLogSpy).toHaveBeenCalled();
258
+ try {
259
+ await diveAR.launch(mockUri);
260
+ fail('Expected launch to reject');
261
+ } catch (error: unknown) {
262
+ if (error instanceof Error) {
263
+ expect(error.message).toBe(
264
+ 'AR not supported on non-mobile systems',
265
+ );
266
+ } else {
267
+ fail('Expected error to be an Error instance');
268
+ }
269
+ }
270
+ expect(consoleLogSpy).toHaveBeenCalledWith(
271
+ 'DIVE: AR not supported. Not a mobile system. (System is Windows)',
272
+ );
273
+ consoleLogSpy.mockRestore();
185
274
  });
186
275
  });
187
276
  });
@@ -1,20 +1,39 @@
1
- import { type DIVEScene } from '../../scene/Scene';
2
- import { type DIVEAROptions } from '../AR';
3
-
4
- export class DIVEARQuickLook {
5
- public static Launch(
6
- scene: DIVEScene,
7
- options?: DIVEAROptions,
8
- ): Promise<void> {
9
- const url = this.findARQuickLookSrc(scene);
1
+ import { type ARSystemOptions } from '../AR';
2
+ import { Converter } from '../../converter/Converter';
3
+ import { type USDZExporterOptions } from '../../types';
4
+
5
+ export class ARQuickLook {
6
+ public async launch(uri: string, options?: ARSystemOptions): Promise<void> {
7
+ const usdzUrl = await this.convertToUSDZ(uri, options);
8
+ return this.launchARQuickLook(usdzUrl, options);
9
+ }
10
10
 
11
- // launch ARQuickLook
12
- return this.launchARQuickLook(url, options);
11
+ private async convertToUSDZ(
12
+ uri: string,
13
+ options?: ARSystemOptions,
14
+ ): Promise<string> {
15
+ // Convert the file to USDZ format
16
+ const usdzBuffer = await Converter.convert(uri).to('usdz', {
17
+ quickLookCompatible: true,
18
+ ar: {
19
+ anchoring: { type: 'plane' },
20
+ planeAnchoring: {
21
+ alignment:
22
+ options?.arPlacement === 'vertical'
23
+ ? 'vertical'
24
+ : 'horizontal',
25
+ },
26
+ },
27
+ } as USDZExporterOptions);
28
+
29
+ // Create a blob from the USDZ buffer
30
+ const blob = new Blob([usdzBuffer], { type: 'model/vnd.usdz+zip' });
31
+ return URL.createObjectURL(blob);
13
32
  }
14
33
 
15
- private static launchARQuickLook(
34
+ private launchARQuickLook(
16
35
  uri: string,
17
- options?: DIVEAROptions,
36
+ options?: ARSystemOptions,
18
37
  ): Promise<void> {
19
38
  return new Promise((resolve) => {
20
39
  if (options?.arScale === 'fixed') {
@@ -31,63 +50,4 @@ export class DIVEARQuickLook {
31
50
  a.click();
32
51
  });
33
52
  }
34
-
35
- private static findARQuickLookSrc(scene: DIVEScene): string {
36
- let uri: string | null = null;
37
-
38
- scene.traverse((object) => {
39
- if (uri) return;
40
- if (object.userData.uri) {
41
- uri = object.userData.uri;
42
- }
43
- });
44
-
45
- if (!uri) {
46
- throw new Error('No model found in scene');
47
- }
48
-
49
- return uri;
50
- }
51
-
52
- // private static extractModels(scene: DIVEScene): Object3D[] {
53
- // // extract models
54
- // return scene.Root.children;
55
- // }
56
-
57
- // private static launchARFromNode(
58
- // node: Object3D,
59
- // options?: DIVEAROptions,
60
- // ): Promise<void> {
61
- // // bundle USDZ
62
- // return this._usdzExporter
63
- // .parse(node, {
64
- // quickLookCompatible: true,
65
- // ar: {
66
- // anchoring: { type: 'plane' },
67
- // planeAnchoring: {
68
- // alignment:
69
- // options?.arPlacement === 'vertical'
70
- // ? 'vertical'
71
- // : 'horizontal',
72
- // },
73
- // },
74
- // })
75
- // .then((usdz: Uint8Array) => {
76
- // // create blob
77
- // const blob = new Blob([usdz], { type: 'model/vnd.usdz+zip' });
78
- // let url = URL.createObjectURL(blob);
79
-
80
- // if (options?.arScale === 'fixed') {
81
- // url = url.concat('#allowsContentScaling=0');
82
- // }
83
-
84
- // // launch ARQuickLook
85
- // const a = document.createElement('a');
86
- // a.innerHTML = '<picture></picture>'; // This is actually needed so the viewer opens instantly
87
- // a.rel = 'ar';
88
- // a.href = url;
89
- // a.download = 'scene.usdz';
90
- // a.click();
91
- // });
92
- // }
93
53
  }