@shapediver/viewer.session-engine.session-engine 1.15.6 → 2.0.1

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 (97) hide show
  1. package/dist/implementation/OutputLoader.d.ts +1 -1
  2. package/dist/implementation/OutputLoader.d.ts.map +1 -1
  3. package/dist/implementation/OutputLoader.js +2 -5
  4. package/dist/implementation/OutputLoader.js.map +1 -1
  5. package/dist/implementation/SessionData.d.ts +4 -3
  6. package/dist/implementation/SessionData.d.ts.map +1 -1
  7. package/dist/implementation/SessionData.js.map +1 -1
  8. package/dist/implementation/{Session.d.ts → SessionEngine.d.ts} +79 -17
  9. package/dist/implementation/SessionEngine.d.ts.map +1 -0
  10. package/dist/implementation/SessionEngine.js +1132 -0
  11. package/dist/implementation/SessionEngine.js.map +1 -0
  12. package/dist/implementation/SessionOutputData.d.ts +4 -3
  13. package/dist/implementation/SessionOutputData.d.ts.map +1 -1
  14. package/dist/implementation/SessionOutputData.js.map +1 -1
  15. package/dist/implementation/SessionTreeNode.d.ts +4 -4
  16. package/dist/implementation/SessionTreeNode.d.ts.map +1 -1
  17. package/dist/implementation/SessionTreeNode.js +1 -4
  18. package/dist/implementation/SessionTreeNode.js.map +1 -1
  19. package/dist/implementation/dto/Export.d.ts +37 -0
  20. package/dist/implementation/dto/Export.d.ts.map +1 -0
  21. package/dist/implementation/dto/Export.js +204 -0
  22. package/dist/implementation/dto/Export.js.map +1 -0
  23. package/dist/implementation/dto/FileParameter.d.ts +10 -0
  24. package/dist/implementation/dto/FileParameter.d.ts.map +1 -0
  25. package/dist/implementation/dto/FileParameter.js +99 -0
  26. package/dist/implementation/dto/FileParameter.js.map +1 -0
  27. package/dist/implementation/dto/Output.d.ts +42 -0
  28. package/dist/implementation/dto/Output.d.ts.map +1 -0
  29. package/dist/implementation/dto/Output.js +200 -0
  30. package/dist/implementation/dto/Output.js.map +1 -0
  31. package/dist/implementation/dto/Parameter.d.ts +39 -0
  32. package/dist/implementation/dto/Parameter.d.ts.map +1 -0
  33. package/dist/implementation/dto/Parameter.js +341 -0
  34. package/dist/implementation/dto/Parameter.js.map +1 -0
  35. package/dist/index.d.ts +15 -5
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +14 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/interfaces/ISessionData.d.ts +7 -0
  40. package/dist/interfaces/ISessionData.d.ts.map +1 -0
  41. package/dist/interfaces/ISessionData.js +3 -0
  42. package/dist/interfaces/ISessionData.js.map +1 -0
  43. package/dist/interfaces/ISessionEngine.d.ts +123 -0
  44. package/dist/interfaces/ISessionEngine.d.ts.map +1 -0
  45. package/dist/interfaces/ISessionEngine.js +59 -0
  46. package/dist/interfaces/ISessionEngine.js.map +1 -0
  47. package/dist/interfaces/ISessionOutputData.d.ts +7 -0
  48. package/dist/interfaces/ISessionOutputData.d.ts.map +1 -0
  49. package/dist/interfaces/ISessionOutputData.js +3 -0
  50. package/dist/interfaces/ISessionOutputData.js.map +1 -0
  51. package/dist/interfaces/ISessionTreeNode.d.ts +5 -0
  52. package/dist/interfaces/ISessionTreeNode.d.ts.map +1 -0
  53. package/dist/interfaces/ISessionTreeNode.js +3 -0
  54. package/dist/interfaces/ISessionTreeNode.js.map +1 -0
  55. package/dist/interfaces/dto/IExport.d.ts +10 -0
  56. package/dist/interfaces/dto/IExport.d.ts.map +1 -0
  57. package/dist/interfaces/{ISession.js → dto/IExport.js} +1 -1
  58. package/dist/interfaces/dto/IExport.js.map +1 -0
  59. package/dist/interfaces/dto/IFileParameter.d.ts +5 -0
  60. package/dist/interfaces/dto/IFileParameter.d.ts.map +1 -0
  61. package/dist/interfaces/dto/IFileParameter.js +3 -0
  62. package/dist/interfaces/dto/IFileParameter.js.map +1 -0
  63. package/dist/interfaces/dto/IOutput.d.ts +25 -0
  64. package/dist/interfaces/dto/IOutput.d.ts.map +1 -0
  65. package/dist/interfaces/dto/IOutput.js +3 -0
  66. package/dist/interfaces/dto/IOutput.js.map +1 -0
  67. package/dist/interfaces/dto/IParameter.d.ts +10 -0
  68. package/dist/interfaces/dto/IParameter.d.ts.map +1 -0
  69. package/dist/interfaces/dto/IParameter.js +3 -0
  70. package/dist/interfaces/dto/IParameter.js.map +1 -0
  71. package/package.json +18 -11
  72. package/src/implementation/OutputDelayException.ts +22 -0
  73. package/src/implementation/OutputLoader.ts +281 -0
  74. package/src/implementation/SessionData.ts +43 -0
  75. package/src/implementation/SessionEngine.ts +1267 -0
  76. package/src/implementation/SessionOutputData.ts +43 -0
  77. package/src/implementation/SessionTreeNode.ts +41 -0
  78. package/src/implementation/dto/Export.ts +223 -0
  79. package/src/implementation/dto/FileParameter.ts +71 -0
  80. package/src/implementation/dto/Output.ts +221 -0
  81. package/src/implementation/dto/Parameter.ts +340 -0
  82. package/src/index.ts +26 -0
  83. package/src/interfaces/ISessionData.ts +16 -0
  84. package/src/interfaces/ISessionEngine.ts +123 -0
  85. package/src/interfaces/ISessionOutputData.ts +16 -0
  86. package/src/interfaces/ISessionTreeNode.ts +9 -0
  87. package/src/interfaces/dto/IExport.ts +17 -0
  88. package/src/interfaces/dto/IFileParameter.ts +9 -0
  89. package/src/interfaces/dto/IOutput.ts +45 -0
  90. package/src/interfaces/dto/IParameter.ts +19 -0
  91. package/tsconfig.json +17 -0
  92. package/dist/implementation/Session.d.ts.map +0 -1
  93. package/dist/implementation/Session.js +0 -536
  94. package/dist/implementation/Session.js.map +0 -1
  95. package/dist/interfaces/ISession.d.ts +0 -32
  96. package/dist/interfaces/ISession.d.ts.map +0 -1
  97. package/dist/interfaces/ISession.js.map +0 -1
@@ -0,0 +1,1267 @@
1
+ import { container } from 'tsyringe'
2
+ import { HttpClient, HttpResponse, PerformanceEvaluator, UuidGenerator, SystemInfo, Logger, LOGGING_TOPIC, ShapeDiverViewerSessionError, ShapeDiverViewerError, Converter, SettingsEngine, EVENTTYPE, EventEngine, StateEngine, ShapeDiverViewerSettingsError } from '@shapediver/viewer.shared.services'
3
+
4
+ import { OutputDelayException } from './OutputDelayException'
5
+ import { OutputLoader } from './OutputLoader'
6
+ import { SessionTreeNode } from './SessionTreeNode'
7
+ import { ISessionEngine, PARAMETER_TYPE } from '../interfaces/ISessionEngine'
8
+ import { SessionData } from './SessionData'
9
+ import { create, ShapeDiverError as ShapeDiverBackendError, ShapeDiverResponseErrorType, ShapeDiverRequestGltfUploadQueryConversion, ShapeDiverResponseDto, ShapeDiverResponseError, ShapeDiverResponseExport, ShapeDiverResponseExportDefinitionType, ShapeDiverResponseOutput, ShapeDiverResponseParameter, ShapeDiverSdk, ShapeDiverSdkConfigType, ShapeDiverResponseModelComputationStatus } from '@shapediver/sdk.geometry-api-sdk-v2'
10
+ import { AxiosRequestConfig } from 'axios'
11
+ import { ISessionTreeNode } from '../interfaces/ISessionTreeNode'
12
+ import { ITree, ITreeNode, Tree, TreeNode } from '@shapediver/viewer.shared.node-tree'
13
+ import { ITaskEvent, TASK_TYPE } from '@shapediver/viewer.shared.types'
14
+ import { FileParameter } from './dto/FileParameter'
15
+ import { IFileParameter } from '../interfaces/dto/IFileParameter'
16
+ import { IExport } from '../interfaces/dto/IExport'
17
+ import { IParameter } from '../interfaces/dto/IParameter'
18
+ import { IOutput } from '../interfaces/dto/IOutput'
19
+ import { Parameter } from './dto/Parameter'
20
+ import { vec3 } from 'gl-matrix'
21
+ import { Export } from './dto/Export'
22
+ import { Output } from './dto/Output'
23
+ import { convert, ISettingsV3_1, validate } from '@shapediver/viewer.settings'
24
+
25
+ export class SessionEngine implements ISessionEngine {
26
+ // #region Properties (40)
27
+
28
+ private readonly _converter: Converter = <Converter>container.resolve(Converter);
29
+ private readonly _eventEngine = <EventEngine>container.resolve(EventEngine);
30
+ private readonly _exports: { [key: string]: IExport; } = {};
31
+ private readonly _httpClient: HttpClient = <HttpClient>container.resolve(HttpClient);
32
+ private readonly _id: string;
33
+ private readonly _logger: Logger = <Logger>container.resolve(Logger);
34
+ private readonly _modelViewUrl: string;
35
+ private readonly _outputLoader: OutputLoader;
36
+ private readonly _outputs: { [key: string]: IOutput; } = {};
37
+ private readonly _outputsFreeze: { [key: string]: boolean; } = {};
38
+ private readonly _parameterValues: { [key: string]: string; } = {};
39
+ private readonly _parameters: { [key: string]: IParameter<any>; } = {};
40
+ private readonly _performanceEvaluator = <PerformanceEvaluator>container.resolve(PerformanceEvaluator);
41
+ private readonly _sceneTree: ITree = <ITree>container.resolve(Tree);
42
+ private readonly _sessionEngineId = (<UuidGenerator>container.resolve(UuidGenerator)).create();
43
+ private readonly _settingsEngine: SettingsEngine = new SettingsEngine();
44
+ private readonly _stateEngine: StateEngine = <StateEngine>container.resolve(StateEngine);
45
+ private readonly _ticket: string;
46
+ private readonly _uuidGenerator = <UuidGenerator>container.resolve(UuidGenerator);
47
+
48
+ private _automaticSceneUpdate: boolean = true;
49
+ private _bearerToken?: string;
50
+ private _closeOnFailure: () => Promise<void> = async () => { };
51
+
52
+ private _closed: boolean = false;
53
+ private _customizeOnParameterChange: boolean = false;
54
+ private _dataCache: {
55
+ [key: string]: Promise<HttpResponse<any>>
56
+ } = {};
57
+ private _excludeViewports: string[] = [];
58
+ private _headers = {
59
+ "X-ShapeDiver-Origin": (<SystemInfo>container.resolve(SystemInfo)).origin,
60
+ "X-ShapeDiver-SessionEngineId": this._sessionEngineId,
61
+ "X-ShapeDiver-BuildVersion": '',
62
+ "X-ShapeDiver-BuildDate": ''
63
+ };
64
+ private _initialized: boolean = false;
65
+ private _modelId?: string;
66
+ private _node: ITreeNode;
67
+ private _refreshBearerToken?: () => Promise<string>;
68
+ private _responseDto?: ShapeDiverResponseDto;
69
+ private _retryCounter = 0;
70
+ private _sdk: ShapeDiverSdk;
71
+ private _sessionId?: string;
72
+ private _updateCallback: ((newNode: ITreeNode, oldNode: ITreeNode) => void) | null = null;
73
+ private _viewerSettings?: object;
74
+
75
+ #customizationProcess!: string;
76
+ #parameterHistory: {
77
+ [key: string]: {
78
+ value: any,
79
+ valueString: string
80
+ }
81
+ }[] = [];
82
+ #parameterHistoryCall = false;
83
+ #parameterHistoryForward: {
84
+ [key: string]: {
85
+ value: any,
86
+ valueString: string
87
+ }
88
+ }[] = [];
89
+
90
+ // #endregion Properties (40)
91
+
92
+ // #region Constructors (1)
93
+
94
+ /**
95
+ * Can be use to initialize a session with the ticket and modelViewUrl and returns a scene graph node with the result.
96
+ * Can be use to customize the session with updated parameters to get the updated scene graph node.
97
+ */
98
+ constructor(properties: { id: string, ticket: string, modelViewUrl: string, buildVersion: string, buildDate: string, bearerToken?: string, excludeViewports?: string[] }) {
99
+ this._id = properties.id;
100
+ this._node = new TreeNode(properties.id);
101
+ this._ticket = properties.ticket;
102
+ this._modelViewUrl = properties.modelViewUrl;
103
+ this._excludeViewports = properties.excludeViewports || [];
104
+ this._bearerToken = properties.bearerToken;
105
+ this._headers['X-ShapeDiver-BuildDate'] = properties.buildDate;
106
+ this._headers['X-ShapeDiver-BuildVersion'] = properties.buildVersion;
107
+ this._outputLoader = new OutputLoader();
108
+
109
+ this._sdk = create(this._modelViewUrl, this._bearerToken);
110
+ this._sdk.setConfigurationValue(ShapeDiverSdkConfigType.REQUEST_HEADERS, this._headers);
111
+ }
112
+
113
+ // #endregion Constructors (1)
114
+
115
+ // #region Public Accessors (23)
116
+
117
+ public get automaticSceneUpdate(): boolean {
118
+ return this._automaticSceneUpdate;
119
+ }
120
+
121
+ public set automaticSceneUpdate(value: boolean) {
122
+ this._automaticSceneUpdate = value;
123
+ value ? this._sceneTree.addNode(this._node) : this._sceneTree.removeNode(this._node);
124
+ }
125
+
126
+ public get bearerToken(): string | undefined {
127
+ return this._bearerToken;
128
+ }
129
+
130
+ public set bearerToken(value: string | undefined) {
131
+ this._bearerToken = value;
132
+ this._sdk.setConfigurationValue(ShapeDiverSdkConfigType.JWT_TOKEN, value);
133
+ }
134
+
135
+ public get canUploadGLTF(): boolean {
136
+ try {
137
+ this.checkAvailability('gltf-upload');
138
+ return true;
139
+ } catch (e) {
140
+ return false;
141
+ }
142
+ }
143
+
144
+ public get customizeOnParameterChange(): boolean {
145
+ return this._customizeOnParameterChange;
146
+ }
147
+
148
+ public set customizeOnParameterChange(value: boolean) {
149
+ this._customizeOnParameterChange = value;
150
+ }
151
+
152
+ public get excludeViewports(): string[] {
153
+ return this._excludeViewports;
154
+ }
155
+
156
+ public set excludeViewports(value: string[]) {
157
+ this._excludeViewports = value;
158
+ this._node.excludeViewports = value;
159
+ }
160
+
161
+ public get exports(): { [key: string]: IExport; } {
162
+ return this._exports;
163
+ }
164
+
165
+ public get id(): string {
166
+ return this._id;
167
+ }
168
+
169
+ public get initialized(): boolean {
170
+ return this._initialized;
171
+ }
172
+
173
+ public get modelViewUrl(): string {
174
+ return this._modelViewUrl;
175
+ }
176
+
177
+ public get node(): ITreeNode {
178
+ return this._node;
179
+ }
180
+
181
+ public get outputs(): { [key: string]: IOutput; } {
182
+ return this._outputs;
183
+ }
184
+
185
+ public get outputsFreeze(): { [key: string]: boolean; } {
186
+ return this._outputsFreeze;
187
+ }
188
+
189
+ public get parameterValues(): { [key: string]: string; } {
190
+ return this._parameterValues;
191
+ }
192
+
193
+ public get parameters(): { [key: string]: IParameter<any>; } {
194
+ return this._parameters;
195
+ }
196
+
197
+ public get refreshBearerToken(): (() => Promise<string>) | undefined {
198
+ return this._refreshBearerToken;
199
+ }
200
+
201
+ public set refreshBearerToken(value: (() => Promise<string>) | undefined) {
202
+ this._refreshBearerToken = value;
203
+ }
204
+
205
+ public get settingsEngine(): SettingsEngine {
206
+ return this._settingsEngine;
207
+ }
208
+
209
+ public get ticket(): string {
210
+ return this._ticket;
211
+ }
212
+
213
+ public get updateCallback(): ((newNode: ITreeNode, oldNode: ITreeNode) => void) | null {
214
+ return this._updateCallback;
215
+ }
216
+
217
+ public set updateCallback(value: ((newNode: ITreeNode, oldNode: ITreeNode) => void) | null) {
218
+ this._updateCallback = value;
219
+ }
220
+
221
+ public get viewerSettings(): object | undefined {
222
+ return this._viewerSettings;
223
+ }
224
+
225
+ // #endregion Public Accessors (23)
226
+
227
+ // #region Public Methods (22)
228
+
229
+ public applySettings(response: ShapeDiverResponseDto, sections?: {
230
+ session?: {
231
+ parameter?: { displayname?: boolean | undefined; order?: boolean | undefined; hidden?: boolean | undefined; value?: boolean | undefined } | undefined;
232
+ export?: { displayname?: boolean | undefined; order?: boolean | undefined; hidden?: boolean | undefined } | undefined
233
+ } | undefined;
234
+ viewport?: { ar?: boolean | undefined; scene?: boolean | undefined; camera?: boolean | undefined; light?: boolean | undefined; environment?: boolean | undefined; general?: boolean | undefined } | undefined
235
+ }) {
236
+ try {
237
+ sections = sections || {};
238
+ if (sections.session === undefined) {
239
+ sections.session = {
240
+ parameter: { displayname: false, order: false, hidden: false },
241
+ export: { displayname: false, order: false, hidden: false }
242
+ };
243
+ }
244
+ if (sections.session.parameter === undefined)
245
+ sections.session.parameter = { displayname: false, order: false, hidden: false, value: false };
246
+ if (sections.session.export === undefined)
247
+ sections.session.export = { displayname: false, order: false, hidden: false };
248
+ if (sections.viewport === undefined)
249
+ sections.viewport = { ar: false, scene: false, camera: false, light: false, environment: false, general: false };
250
+
251
+ let config: object;
252
+ if ((<ShapeDiverResponseDto>response).viewer !== undefined) {
253
+ config = (<ShapeDiverResponseDto>response).viewer!.config;
254
+ } else {
255
+ const error = new ShapeDiverViewerSettingsError('Api.applySettings: No config object available.');
256
+ throw this._logger.handleError(LOGGING_TOPIC.SETTINGS, 'Api.applySettings', error);
257
+ }
258
+
259
+ try {
260
+ validate(config)
261
+ } catch (e) {
262
+ const error = new ShapeDiverViewerSettingsError('Api.applySettings: Was not able to validate config object.');
263
+ throw this._logger.handleError(LOGGING_TOPIC.SETTINGS, 'Api.applySettings', error);
264
+ }
265
+
266
+ const settings = <ISettingsV3_1>convert(config, '3.1');
267
+
268
+ const exportMappingUid: { [key: string]: string | undefined } = {};
269
+ if (sections.session.export.displayname || sections.session.export.order || sections.session.export.hidden)
270
+ if (response.exports)
271
+ for (let exportId in response.exports)
272
+ if (response.exports[exportId].uid !== undefined)
273
+ exportMappingUid[response.exports[exportId].uid!] = exportId;
274
+
275
+ const currentSettings = this._settingsEngine.settings;
276
+
277
+ // apply parameter settings
278
+ if (sections.session.parameter.displayname || sections.session.parameter.order || sections.session.parameter.hidden || sections.session.parameter.value) {
279
+ for (let p in this.parameters) {
280
+ if (settings.session[p]) {
281
+ if (sections.session.parameter.displayname) this.parameters[p].displayname = settings.session[p].displayname;
282
+ if (sections.session.parameter.order) this.parameters[p].order = settings.session[p].order;
283
+ if (sections.session.parameter.hidden) this.parameters[p].hidden = settings.session[p].hidden || false;
284
+ }
285
+
286
+ if (response.parameters && response.parameters[p]) {
287
+ if (sections.session.parameter.value) this.parameters[p].value = response.parameters[p].defval !== undefined ? response.parameters[p].defval : this.parameters[p].value;
288
+ }
289
+ }
290
+ }
291
+
292
+ // apply export settings
293
+ if (sections.session.export.displayname || sections.session.export.order || sections.session.export.hidden) {
294
+ for (let p in this.exports) {
295
+ let idForSettings = '';
296
+ if (settings.session[p]) {
297
+ idForSettings = p;
298
+ } else {
299
+ const uid = this.exports[p].uid;
300
+ if (!uid) continue;
301
+ if (!exportMappingUid[uid]) continue;
302
+ idForSettings = exportMappingUid[uid]!;
303
+ }
304
+ if (settings.session[idForSettings]) {
305
+ if (sections.session.export.displayname) this.exports[p].displayname = settings.session[idForSettings].displayname;
306
+ if (sections.session.export.order) this.exports[p].order = settings.session[idForSettings].order;
307
+ if (sections.session.export.hidden) this.exports[p].hidden = settings.session[idForSettings].hidden || false;
308
+ }
309
+ }
310
+ }
311
+
312
+ // apply ar settings
313
+ if (sections.viewport.ar) {
314
+ currentSettings.ar = settings.ar;
315
+ currentSettings.general.transformation = settings.general.transformation;
316
+ }
317
+
318
+ // apply camera settings
319
+ if (sections.viewport.camera)
320
+ currentSettings.camera = settings.camera;
321
+
322
+ // apply light settings
323
+ if (sections.viewport.light)
324
+ currentSettings.light = settings.light;
325
+
326
+ // apply scene settings
327
+ if (sections.viewport.scene) {
328
+ currentSettings.environmentGeometry.gridColor = settings.environmentGeometry.gridColor;
329
+ currentSettings.environmentGeometry.gridVisibility = settings.environmentGeometry.gridVisibility;
330
+ currentSettings.environmentGeometry.groundPlaneColor = settings.environmentGeometry.groundPlaneColor;
331
+ currentSettings.environmentGeometry.groundPlaneVisibility = settings.environmentGeometry.groundPlaneVisibility;
332
+
333
+ currentSettings.rendering.shadows = settings.rendering.shadows;
334
+ currentSettings.rendering.ambientOcclusion = settings.rendering.ambientOcclusion;
335
+
336
+ currentSettings.rendering.textureEncoding = settings.rendering.textureEncoding;
337
+ currentSettings.rendering.outputEncoding = settings.rendering.outputEncoding;
338
+ currentSettings.rendering.physicallyCorrectLights = settings.rendering.physicallyCorrectLights;
339
+ currentSettings.rendering.toneMapping = settings.rendering.toneMapping;
340
+ currentSettings.rendering.toneMappingExposure = settings.rendering.toneMappingExposure;
341
+ }
342
+
343
+ if (sections.viewport.general) {
344
+ currentSettings.general.commitParameters = settings.general.commitParameters;
345
+ currentSettings.general.pointSize = settings.general.pointSize;
346
+ }
347
+
348
+ // apply environment settings
349
+ if (sections.viewport.environment) {
350
+ currentSettings.environment.clearAlpha = settings.environment.clearAlpha;
351
+ currentSettings.environment.clearColor = settings.environment.clearColor;
352
+ currentSettings.environment.map = settings.environment.map;
353
+ currentSettings.environment.mapAsBackground = settings.environment.mapAsBackground;
354
+ }
355
+
356
+ } catch (e) {
357
+ if (e instanceof ShapeDiverViewerError || e instanceof ShapeDiverBackendError) throw e;
358
+ throw this._logger.handleError(LOGGING_TOPIC.GENERAL, 'Api.applySettings', e);
359
+ }
360
+ }
361
+
362
+ public canGoBack(): boolean {
363
+ // the first entry is always the one from the init call
364
+ // all additional entries can be undone
365
+ return this.#parameterHistory.length > 1;
366
+ }
367
+
368
+ public canGoForward(): boolean {
369
+ return this.#parameterHistoryForward.length > 0;
370
+ }
371
+
372
+ public async close(retry = false): Promise<void> {
373
+ this.checkAvailability('close');
374
+
375
+ try {
376
+ this._httpClient.removeDataLoading(this._sessionId!)
377
+ await this._sdk.session.close(this._sessionId!);
378
+ if (this._automaticSceneUpdate) this._sceneTree.removeNode(this._node);
379
+
380
+ this._closed = true;
381
+ } catch (e) {
382
+ await this.handleError(LOGGING_TOPIC.SESSION, 'Session.close', e, retry);
383
+ return await this.close(true);
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Customizes the session with updated parameters to get the updated scene graph node.
389
+ *
390
+ * @param parameters the parameter set to update the session
391
+ * @returns promise with a scene graph node
392
+ */
393
+ public async customize(): Promise<ITreeNode> {
394
+ const eventId = this._uuidGenerator.create();
395
+ const customizationId = this._uuidGenerator.create();
396
+ try {
397
+ const eventStart: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 0, data: { sessionId: this.id }, status: 'Customizing session' };
398
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_START, eventStart);
399
+
400
+ const oldNode = this.node.cloneInstance();
401
+ this.#customizationProcess = customizationId;
402
+
403
+ this._logger.debugLow(LOGGING_TOPIC.SESSION, `Session(${this.id}).customize: Customizing session.`);
404
+
405
+ for (let r in this._stateEngine.renderingEngines)
406
+ this._stateEngine.renderingEngines[r].busy.push(customizationId);
407
+
408
+ const eventFileUpload: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 0.1, data: { sessionId: this.id }, status: 'Uploading file parameters' };
409
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_PROCESS, eventFileUpload);
410
+
411
+ const fileParameterIds: { [key: string]: string } = {}
412
+ // load file parameter first
413
+ for (const parameterId in this.parameters) {
414
+ if (this.parameters[parameterId] instanceof FileParameter) {
415
+ fileParameterIds[parameterId] = await (<IFileParameter>this.parameters[parameterId]).upload();
416
+
417
+ // OPTION TO SKIP - PART 1a
418
+ if (this.#customizationProcess !== customizationId) {
419
+ for (let r in this._stateEngine.renderingEngines)
420
+ if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
421
+ this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
422
+
423
+ this._logger.debug(LOGGING_TOPIC.SESSION, `Session(${this.id}).customize: Session customization was exceeded by other customization request.`);
424
+
425
+ const eventCancel1a: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Session customization was exceeded by other customization request' };
426
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel1a);
427
+ return new SessionTreeNode();
428
+ }
429
+ }
430
+ }
431
+
432
+ // OPTION TO SKIP - PART 1b
433
+ if (this.#customizationProcess !== customizationId) {
434
+ for (let r in this._stateEngine.renderingEngines)
435
+ if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
436
+ this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
437
+
438
+ const eventCancel1b: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Session customization was exceeded by other customization request' };
439
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel1b);
440
+ this._logger.debug(LOGGING_TOPIC.SESSION, `Session(${this.id}).customize: Session customization was exceeded by other customization request.`);
441
+ return new SessionTreeNode();
442
+ }
443
+
444
+ // assign the uploaded parameters
445
+ for (const parameterId in fileParameterIds)
446
+ this.parameters[parameterId].value = fileParameterIds[parameterId];
447
+
448
+ const parameterSet: {
449
+ [key: string]: {
450
+ value: any,
451
+ valueString: string
452
+ }
453
+ } = {};
454
+
455
+ // create a set of the current validated parameter values
456
+ for (const parameterId in this.parameters) {
457
+ parameterSet[parameterId] = {
458
+ value: this.parameters[parameterId].value,
459
+ valueString: this.parameters[parameterId].stringify()
460
+ }
461
+ }
462
+
463
+ // update the session engine parameter values if everything succeeded
464
+ for (const parameterId in this.parameters)
465
+ this.parameterValues[parameterId] = parameterSet[parameterId].valueString;
466
+ this._logger.info(LOGGING_TOPIC.SESSION, `Session(${this.id}).customize: Customizing session with parameters ${JSON.stringify(this.parameterValues)}.`);
467
+
468
+ const eventRequest: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 0.25, data: { sessionId: this.id }, status: 'Sending customization request' };
469
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_PROCESS, eventRequest);
470
+
471
+ const newNode = await this.customizeInternal(() => this.#customizationProcess !== customizationId);
472
+
473
+ const eventSceneUpdate: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 0.75, data: { sessionId: this.id }, status: 'Updating scene' };
474
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_PROCESS, eventSceneUpdate);
475
+
476
+ // OPTION TO SKIP - PART 2
477
+ if (this.#customizationProcess !== customizationId) {
478
+ for (let r in this._stateEngine.renderingEngines)
479
+ if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
480
+ this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
481
+
482
+ const eventCancel2: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Session customization was exceeded by other customization request' };
483
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel2);
484
+ this._logger.debug(LOGGING_TOPIC.SESSION, `Session(${this.id}).customize: Session customization was exceeded by other customization request.`);
485
+ return newNode;
486
+ }
487
+
488
+ // if this is not a call by the goBack or goForward functions, add the parameter values to the history and delete the forward history
489
+ if (!this.#parameterHistoryCall) {
490
+ this.#parameterHistory.push(parameterSet);
491
+ this.#parameterHistoryForward = [];
492
+ }
493
+
494
+ if (this.automaticSceneUpdate) this._sceneTree.removeNode(this.node);
495
+ this._node = newNode;
496
+ if (this.automaticSceneUpdate) this._sceneTree.addNode(this.node);
497
+
498
+ this._logger.debug(LOGGING_TOPIC.SESSION, `Session(${this.id}).customize: Customization request finished, updating geometry.`);
499
+
500
+ // set the session values to the current ones in all parameters
501
+ for (const parameterId in this.parameters)
502
+ (<any>this.parameters[parameterId].sessionValue) = parameterSet[parameterId].value;
503
+
504
+ if (this._updateCallback) this._updateCallback(newNode, oldNode);
505
+
506
+ // set the output content to what has been updated
507
+ for (const outputId in this.outputs)
508
+ this.outputs[outputId].updateOutput(
509
+ newNode.children.find(c => c.name === outputId)!,
510
+ oldNode.children.find(c => c.name === outputId)!
511
+ );
512
+
513
+ // set the export definitions
514
+ for (const exportId in this.exports)
515
+ this.exports[exportId].updateExport();
516
+
517
+ this._warningCreator();
518
+
519
+ this.node.excludeViewports = this._excludeViewports;
520
+
521
+ for (let r in this._stateEngine.renderingEngines)
522
+ if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
523
+ this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
524
+
525
+ this._logger.debug(LOGGING_TOPIC.SESSION, `Session(${this.id}).customize: Session customized.`);
526
+
527
+ this._eventEngine.emitEvent(EVENTTYPE.SESSION.SESSION_CUSTOMIZED, { sessionId: this.id });
528
+
529
+ const eventEnd: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Session customized' };
530
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_END, eventEnd);
531
+
532
+ return this.node;
533
+ } catch (e) {
534
+ const eventCancel: ITaskEvent = { type: TASK_TYPE.SESSION_CUSTOMIZATION, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Session customization failed' };
535
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel);
536
+
537
+ for (let r in this._stateEngine.renderingEngines)
538
+ if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
539
+ this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
540
+
541
+ if (e instanceof ShapeDiverViewerError || e instanceof ShapeDiverBackendError) throw e;
542
+ throw this._logger.handleError(LOGGING_TOPIC.SESSION, `Session(${this.id}).customize`, e);
543
+ }
544
+ }
545
+
546
+ public customizeParallel(parameterValues: { [key: string]: string }): Promise<ITreeNode> {
547
+ // https://shapediver.atlassian.net/browse/SS-5408
548
+ throw new Error('Method not implemented.')
549
+ }
550
+
551
+ public async goBack(): Promise<ITreeNode> {
552
+ if (!this.canGoBack()) {
553
+ this._logger.debug(LOGGING_TOPIC.SESSION, `Session(${this.id}).goBack: Cannot go further back.`);
554
+ return new TreeNode();
555
+ }
556
+ // get the current parameter set and store it in the forward history later on
557
+ const currentParameterSet = this.#parameterHistory.pop()!;
558
+
559
+ // adjust the parameters according to the last parameter set
560
+ const lastParameterSet = this.#parameterHistory[this.#parameterHistory.length - 1];
561
+ for (const parameterId in lastParameterSet)
562
+ this.parameters[parameterId].value = lastParameterSet[parameterId].value;
563
+
564
+ // call the customization function with the parameterHistoryCall value set to true
565
+ this.#parameterHistoryCall = true;
566
+ const node = await this.customize();
567
+ this.#parameterHistoryCall = false;
568
+
569
+ // add the current (not anymore current) parameter set to the forward history
570
+ this.#parameterHistoryForward.push(currentParameterSet);
571
+ return node;
572
+ }
573
+
574
+ public async goForward(): Promise<ITreeNode> {
575
+ if (!this.canGoForward()) {
576
+ this._logger.debug(LOGGING_TOPIC.SESSION, `Session(${this.id}).goForward: Cannot go further forward.`);
577
+ return new TreeNode();
578
+ }
579
+ // get the last undone parameter set and apply the values to the parameters
580
+ const lastParameterSet = this.#parameterHistoryForward.pop()!;
581
+ for (const parameterId in lastParameterSet)
582
+ this.parameters[parameterId].value = lastParameterSet[parameterId].value;
583
+
584
+ // call the customization function with the parameterHistoryCall value set to true
585
+ this.#parameterHistoryCall = true;
586
+ const node = await this.customize();
587
+ this.#parameterHistoryCall = false;
588
+
589
+ // add the current parameter set to the history
590
+ this.#parameterHistory.push(lastParameterSet);
591
+ return node;
592
+ }
593
+
594
+ /**
595
+ * Initializes the session with the ticket and modelViewUrl.
596
+ *
597
+ * @returns promise with a scene graph node
598
+ */
599
+ public async init(parameterValues?: {
600
+ [key: string]: string;
601
+ }, retry = false): Promise<void> {
602
+ if (this._initialized === true) {
603
+ const error = new ShapeDiverViewerSessionError('Session.init: Session already initialized.');
604
+ throw this._logger.handleError(LOGGING_TOPIC.SESSION, 'Session.init', error);
605
+ }
606
+
607
+ try {
608
+ this._performanceEvaluator.startSection('sessionResponse');
609
+ this._responseDto = await this._sdk.session.init(this._ticket, parameterValues);
610
+ this._performanceEvaluator.endSection('sessionResponse');
611
+
612
+ this._viewerSettings = this._responseDto.viewer?.config;
613
+ this._settingsEngine.loadSettings(this._viewerSettings);
614
+ this._sessionId = this._responseDto.sessionId;
615
+ this._modelId = this._responseDto.model?.id;
616
+
617
+ this._httpClient.addDataLoading(this._sessionId!, {
618
+ getOutput: this._sdk.asset.getOutput.bind(this._sdk.asset),
619
+ getTexture: this._sdk.asset.getTexture.bind(this._sdk.asset),
620
+ getExport: this._sdk.asset.getExport.bind(this._sdk.asset),
621
+ downloadTexture: this._sdk.asset.downloadImage.bind(this._sdk.asset),
622
+ })
623
+
624
+ if (!this._sessionId)
625
+ throw new ShapeDiverViewerSessionError(`Session.init: Initialization of session failed. ResponseDto did not have a sessionId.`)
626
+ if (!this._modelId)
627
+ throw new ShapeDiverViewerSessionError(`Session.init: Initialization of session failed. ResponseDto did not have a model.id.`)
628
+
629
+ this.updateResponseDto(this._responseDto, parameterValues);
630
+ this._initialized = true;
631
+ } catch (e) {
632
+ await this.handleError(LOGGING_TOPIC.SESSION, 'Session.init', e, retry);
633
+ return await this.init(parameterValues, true);
634
+ }
635
+ }
636
+
637
+ /**
638
+ * Load the outputs and return the scene graph node of the result.
639
+ * In case the outputs have a delay property, another customization request with the parameter set is sent.
640
+ *
641
+ * @param parameters the parameter set to update the session
642
+ * @param outputs the outputs to load
643
+ * @returns promise with a scene graph node
644
+ */
645
+ public async loadOutputs(cancelRequest: () => boolean = () => false, retry = false): Promise<ISessionTreeNode> {
646
+ this.checkAvailability();
647
+
648
+ const o = Object.assign({}, this._outputs);
649
+ const of = Object.assign({}, this._outputsFreeze);
650
+ try {
651
+ const node = await this._outputLoader.loadOutputs(this._responseDto!, o, of);
652
+ node.data.push(new SessionData(this._responseDto!));
653
+
654
+ if (cancelRequest()) return node;
655
+
656
+ if (this._automaticSceneUpdate) this._sceneTree.removeNode(this._node);
657
+ this._node = node;
658
+ if (this._automaticSceneUpdate) this._sceneTree.addNode(this._node);
659
+
660
+ this.node.excludeViewports = this._excludeViewports;
661
+
662
+ return node;
663
+ }
664
+ catch (e) {
665
+ if (e instanceof OutputDelayException) {
666
+ await this.timeout(e.delay);
667
+ } else {
668
+ await this.handleError(LOGGING_TOPIC.SESSION, 'Session.loadOutputs', e, retry);
669
+ if (cancelRequest()) return new SessionTreeNode();
670
+ return await this.loadOutputs(cancelRequest, true);
671
+ }
672
+
673
+ if (cancelRequest()) return new SessionTreeNode();
674
+ let outputMapping: { [key: string]: string } = {};
675
+ for (let output in o)
676
+ outputMapping[output] = o[output].version;
677
+
678
+ try {
679
+ const responseDto = await this._sdk.output.getCache(this._sessionId!, outputMapping);
680
+ if (cancelRequest()) return new SessionTreeNode();
681
+ this.updateResponseDto(responseDto);
682
+ return await this.loadOutputs(cancelRequest);
683
+ } catch (e) {
684
+ await this.handleError(LOGGING_TOPIC.SESSION, 'Session.loadOutputs', e, retry);
685
+ if (cancelRequest()) return new SessionTreeNode();
686
+ return await this.loadOutputs(cancelRequest, true);
687
+ }
688
+ }
689
+ }
690
+
691
+ public async requestExport(exportId: string, parameters: { [key: string]: string }, maxWaitTime: number, retry = false): Promise<ShapeDiverResponseExport> {
692
+ this.checkAvailability('export');
693
+ try {
694
+ const responseDto = await this._sdk.utils.submitAndWaitForExport(this._sdk, this._sessionId!, { exports: { id: exportId }, parameters }, maxWaitTime)
695
+ this.updateResponseDto(responseDto);
696
+ return this.exports[exportId];
697
+ } catch (e) {
698
+ await this.handleError(LOGGING_TOPIC.SESSION, 'Session.requestExport', e, retry);
699
+ return await this.requestExport(exportId, parameters, maxWaitTime, true);
700
+ }
701
+ }
702
+
703
+ public async saveDefaultParameterValues(): Promise<boolean> {
704
+ try {
705
+ this._logger.debugLow(LOGGING_TOPIC.SESSION, `Session(${this.id}).saveDefaultParameters: Saving default parameters.`);
706
+ const response = await this.saveDefaultParameters();
707
+ if (response) {
708
+ this._logger.debug(LOGGING_TOPIC.SESSION, `Session(${this.id}).saveDefaultParameters: Saved default parameters.`);
709
+ } else {
710
+ const error = new ShapeDiverViewerSessionError(`Session(${this.id}).saveDefaultParameters: Could not save default parameters.`);
711
+ throw this._logger.handleError(LOGGING_TOPIC.SESSION, `Session(${this.id}).saveDefaultParameters`, error);
712
+ }
713
+ return response;
714
+ } catch (e) {
715
+ if (e instanceof ShapeDiverViewerError || e instanceof ShapeDiverBackendError) throw e;
716
+ throw this._logger.handleError(LOGGING_TOPIC.SESSION, `Session(${this.id}).saveDefaultParameters`, e);
717
+ }
718
+ }
719
+
720
+ public async saveDefaultParameters(retry = false): Promise<boolean> {
721
+ this.checkAvailability('defaultparam', true);
722
+ try {
723
+ await this._sdk.model.setDefaultParams(this._modelId!, this._parameterValues)
724
+ return true;
725
+ } catch (e) {
726
+ await this.handleError(LOGGING_TOPIC.SESSION, 'Session.saveDefaultParameters', e, retry);
727
+ return await this.saveDefaultParameters(true);
728
+ }
729
+ }
730
+
731
+ /**
732
+ * Save the export properties for displayname, order, tooltip and hidden
733
+ *
734
+ * @param exports
735
+ * @returns
736
+ */
737
+ public async saveExportProperties(exports: {
738
+ [key: string]: {
739
+ displayname: string,
740
+ hidden: boolean,
741
+ order: number,
742
+ tooltip: string
743
+ }
744
+ }, retry = false): Promise<boolean> {
745
+ this.checkAvailability('export-definition', true);
746
+ try {
747
+ await this._sdk.export.updateDefinitions(this._modelId!, exports);
748
+ return true;
749
+ } catch (e) {
750
+ await this.handleError(LOGGING_TOPIC.SESSION, 'Session.saveExportProperties', e, retry);
751
+ return await this.saveExportProperties(exports, true);
752
+ }
753
+ }
754
+
755
+ /**
756
+ * Save the output properties for displayname, order, tooltip and hidden
757
+ *
758
+ * @param outputs
759
+ * @returns
760
+ */
761
+ public async saveOutputProperties(outputs: {
762
+ [key: string]: {
763
+ displayname: string,
764
+ hidden: boolean,
765
+ order: number,
766
+ tooltip: string
767
+ }
768
+ }, retry = false): Promise<boolean> {
769
+ this.checkAvailability('output-definition', true);
770
+ try {
771
+ await this._sdk.output.updateDefinitions(this._modelId!, outputs);
772
+ return true;
773
+ } catch (e) {
774
+ await this.handleError(LOGGING_TOPIC.SESSION, 'Session.saveOutputProperties', e, retry);
775
+ return await this.saveOutputProperties(outputs, true);
776
+ }
777
+ }
778
+
779
+ /**
780
+ * Save the parameter properties for displayname, order, tooltip and hidden
781
+ *
782
+ * @param parameters
783
+ * @returns
784
+ */
785
+ public async saveParameterProperties(parameters: {
786
+ [key: string]: {
787
+ displayname: string,
788
+ hidden: boolean,
789
+ order: number,
790
+ tooltip: string
791
+ }
792
+ }, retry = false): Promise<boolean> {
793
+ this.checkAvailability('parameter-definition', true);
794
+ try {
795
+ await this._sdk.model.updateParameterDefinitions(this._modelId!, parameters);
796
+ return true;
797
+ } catch (e) {
798
+ await this.handleError(LOGGING_TOPIC.SESSION, 'Session.saveParameterProperties', e, retry);
799
+ return await this.saveParameterProperties(parameters, true);
800
+ }
801
+ }
802
+
803
+ public async saveSettings(json: any, retry = false): Promise<boolean> {
804
+ this.checkAvailability('configure', true);
805
+ try {
806
+ await this._sdk.model.updateConfig(this._modelId!, json);
807
+ return true;
808
+ } catch (e) {
809
+ await this.handleError(LOGGING_TOPIC.SESSION, 'Session.saveSettings', e, retry);
810
+ return await this.saveSettings(json, true);
811
+ }
812
+ }
813
+
814
+ public async saveUiProperties(saveInSettings: boolean = true): Promise<boolean> {
815
+ try {
816
+ this._logger.debugLow(LOGGING_TOPIC.SESSION, `Session(${this.id}).saveSessionProperties: Saving session properties.`);
817
+
818
+ // settings saving
819
+ this._saveSessionSettings();
820
+
821
+ let properties: {
822
+ [key: string]: {
823
+ displayname: string,
824
+ hidden: boolean,
825
+ order: number,
826
+ tooltip: string
827
+ }
828
+ } = {};
829
+ for (let p in this.parameters) {
830
+ properties[p] = {
831
+ displayname: this.parameters[p].displayname !== undefined ? this.parameters[p].displayname! : '',
832
+ hidden: this.parameters[p].hidden !== undefined ? this.parameters[p].hidden : false,
833
+ order: this.parameters[p].order !== undefined ? this.parameters[p].order! : 0,
834
+ tooltip: this.parameters[p].tooltip !== undefined ? this.parameters[p].tooltip! : '',
835
+ };
836
+ }
837
+ const responseP = Object.values(properties).length !== 0 ? await this.saveParameterProperties(properties) : true;
838
+
839
+ properties = {};
840
+ for (let e in this.exports) {
841
+ properties[e] = {
842
+ displayname: this.exports[e].displayname !== undefined ? this.exports[e].displayname! : '',
843
+ hidden: this.exports[e].hidden !== undefined ? this.exports[e].hidden : false,
844
+ order: this.exports[e].order !== undefined ? this.exports[e].order! : 0,
845
+ tooltip: this.exports[e].tooltip !== undefined ? this.exports[e].tooltip! : '',
846
+ };
847
+ }
848
+ const responseE = Object.values(properties).length !== 0 ? await this.saveExportProperties(properties) : true;
849
+
850
+ properties = {};
851
+ for (let o in this.outputs) {
852
+ properties[o] = {
853
+ displayname: this.outputs[o].displayname !== undefined ? this.outputs[o].displayname! : '',
854
+ hidden: this.outputs[o].hidden !== undefined ? this.outputs[o].hidden : false,
855
+ order: this.outputs[o].order !== undefined ? this.outputs[o].order! : 0,
856
+ tooltip: this.outputs[o].tooltip !== undefined ? this.outputs[o].tooltip! : '',
857
+ };
858
+ }
859
+ const responseO = Object.values(properties).length !== 0 ? await this.saveOutputProperties(properties) : true;
860
+
861
+ // save partial settings
862
+ const response = saveInSettings ? await this.saveSettings(this._settingsEngine.convertToTargetVersion()) : true;
863
+
864
+ if (response && responseP && responseO && responseE) {
865
+ this._logger.debug(LOGGING_TOPIC.SESSION, `Session(${this.id}).saveSessionProperties: Saved session properties.`);
866
+ } else {
867
+ this._logger.warn(LOGGING_TOPIC.SESSION, `Session(${this.id}).saveSessionProperties: Could not save session properties.`);
868
+ }
869
+ return response && responseP && responseO && responseE;
870
+ } catch (e) {
871
+ if (e instanceof ShapeDiverViewerError || e instanceof ShapeDiverBackendError) throw e;
872
+ throw this._logger.handleError(LOGGING_TOPIC.SESSION, `Session(${this.id}).saveSessionProperties`, e);
873
+ }
874
+ }
875
+
876
+ public async updateOutputs(): Promise<ITreeNode> {
877
+ const eventId = this._uuidGenerator.create();
878
+ const customizationId = this._uuidGenerator.create();
879
+ const eventStart: ITaskEvent = { type: TASK_TYPE.SESSION_OUTPUTS_UPDATE, id: eventId, progress: 0, data: { sessionId: this.id }, status: 'Updating outputs' };
880
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_START, eventStart);
881
+
882
+ const oldNode = this.node.cloneInstance();
883
+ this.#customizationProcess = customizationId;
884
+
885
+ this._logger.debugLow(LOGGING_TOPIC.SESSION, `Session(${this.id}).updateOutputs: Updating Outputs.`);
886
+
887
+ for (let r in this._stateEngine.renderingEngines)
888
+ this._stateEngine.renderingEngines[r].busy.push(customizationId);
889
+
890
+ const eventRequest: ITaskEvent = { type: TASK_TYPE.SESSION_OUTPUTS_UPDATE, id: eventId, progress: 0.25, data: { sessionId: this.id }, status: 'Loading outputs' };
891
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_PROCESS, eventRequest);
892
+
893
+ const newNode = await this.loadOutputs(() => this.#customizationProcess !== customizationId);
894
+
895
+ const eventSceneUpdate: ITaskEvent = { type: TASK_TYPE.SESSION_OUTPUTS_UPDATE, id: eventId, progress: 0.75, data: { sessionId: this.id }, status: 'Updating scene' };
896
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_PROCESS, eventSceneUpdate);
897
+
898
+ // OPTION TO SKIP - PART 1
899
+ if (this.#customizationProcess !== customizationId) {
900
+ for (let r in this._stateEngine.renderingEngines)
901
+ if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
902
+ this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
903
+
904
+ const eventCancel1: ITaskEvent = { type: TASK_TYPE.SESSION_OUTPUTS_UPDATE, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Output updating was exceeded by other customization request' };
905
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_CANCEL, eventCancel1);
906
+ this._logger.debug(LOGGING_TOPIC.SESSION, `Session(${this.id}).updateOutputs: Output updating was exceeded by other request.`);
907
+ return newNode;
908
+ }
909
+
910
+ if (this.automaticSceneUpdate) this._sceneTree.removeNode(this.node);
911
+ this._node = newNode;
912
+ if (this.automaticSceneUpdate) this._sceneTree.addNode(this.node);
913
+
914
+ this._logger.debug(LOGGING_TOPIC.SESSION, `Session(${this.id}).updateOutputs: Updating outputs finished, updating geometry.`);
915
+
916
+ if (this._updateCallback) this._updateCallback(newNode, oldNode);
917
+
918
+ // set the output content to what has been updated
919
+ for (const outputId in this.outputs) {
920
+ this.outputs[outputId].updateOutput(
921
+ newNode.children.find(c => c.name === outputId)!,
922
+ oldNode.children.find(c => c.name === outputId)!
923
+ );
924
+ }
925
+
926
+ // set the export definitions
927
+ for (const exportId in this.exports)
928
+ this.exports[exportId].updateExport();
929
+
930
+ this._warningCreator();
931
+ this.node.excludeViewports = this.excludeViewports;
932
+
933
+ for (let r in this._stateEngine.renderingEngines)
934
+ if (this._stateEngine.renderingEngines[r].busy.includes(customizationId))
935
+ this._stateEngine.renderingEngines[r].busy.splice(this._stateEngine.renderingEngines[r].busy.indexOf(customizationId), 1);
936
+
937
+ this._logger.debug(LOGGING_TOPIC.SESSION, `Session(${this.id}).updateOutputs: Updated outputs.`);
938
+
939
+ const eventEnd: ITaskEvent = { type: TASK_TYPE.SESSION_OUTPUTS_UPDATE, id: eventId, progress: 1, data: { sessionId: this.id }, status: 'Outputs updated' };
940
+ this._eventEngine.emitEvent(EVENTTYPE.TASK.TASK_END, eventEnd);
941
+
942
+ return this.node;
943
+ }
944
+
945
+ public async uploadFile(parameterId: string, data: File, type: string, retry = false): Promise<string> {
946
+ this.checkAvailability('file-upload');
947
+ try {
948
+ const responseDto = await this._sdk.file.requestUpload(this._sessionId!, {
949
+ [parameterId]: { size: data.size, format: type }
950
+ })
951
+
952
+ if (responseDto && responseDto.asset && responseDto.asset.file && responseDto.asset.file[parameterId]) {
953
+ const fileAsset = responseDto.asset.file[parameterId];
954
+ await this._sdk.utils.upload(fileAsset.href, await data.arrayBuffer(), type);
955
+ return fileAsset.id;
956
+ } else {
957
+ const error = new ShapeDiverViewerSessionError(`Session.uploadFile: Upload reply has not the required format.`);
958
+ throw this._logger.handleError(LOGGING_TOPIC.SESSION, 'Session.uploadFile', error);
959
+ }
960
+ } catch (e) {
961
+ await this.handleError(LOGGING_TOPIC.SESSION, 'Session.uploadFile', e, retry);
962
+ return await this.uploadFile(parameterId, data, type, true);
963
+ }
964
+ }
965
+
966
+ public async uploadGLTF(blob: Blob, conversion: ShapeDiverRequestGltfUploadQueryConversion = ShapeDiverRequestGltfUploadQueryConversion.NONE, retry = false): Promise<string> {
967
+ this.checkAvailability('gltf-upload');
968
+ try {
969
+ const responseDto = await this._sdk.gltf.upload(this._sessionId!, await blob.arrayBuffer(), 'model/gltf-binary', conversion);
970
+ if (!responseDto || !responseDto.gltf || !responseDto.gltf.href) {
971
+ const error = new ShapeDiverViewerSessionError(`Session.uploadGLTF: Upload reply has not the required format.`);
972
+ throw this._logger.handleError(LOGGING_TOPIC.SESSION, 'Session.uploadGLTF', error);
973
+ }
974
+ return responseDto.gltf.href;
975
+ } catch (e) {
976
+ await this.handleError(LOGGING_TOPIC.SESSION, 'Session.uploadGLTF', e, retry);
977
+ return await this.uploadGLTF(blob, conversion, true);
978
+ }
979
+ }
980
+
981
+ // #endregion Public Methods (22)
982
+
983
+ // #region Private Methods (8)
984
+
985
+ private _saveSessionSettings() {
986
+ const parameters = this.parameters;
987
+ const exports = this.exports;
988
+
989
+ const sessionProperties: {
990
+ [key: string]: {
991
+ order: number;
992
+ displayname: string;
993
+ hidden: boolean;
994
+ }
995
+ } = {};
996
+ for (let p in parameters) {
997
+ sessionProperties[p] = {
998
+ order: parameters[p].order || 0,
999
+ displayname: parameters[p].displayname || '',
1000
+ hidden: parameters[p].hidden
1001
+ }
1002
+ }
1003
+ for (let e in exports) {
1004
+ sessionProperties[e] = {
1005
+ order: exports[e].order || 0,
1006
+ displayname: exports[e].displayname || '',
1007
+ hidden: exports[e].hidden
1008
+ }
1009
+ }
1010
+ this._settingsEngine.session = sessionProperties;
1011
+
1012
+ let orderedOutputs: IOutput[] = [];
1013
+ for (let o in this.outputs) orderedOutputs.push(this.outputs[o]);
1014
+ orderedOutputs.sort((a, b) => ((a.order || Infinity) - (b.order || Infinity)));
1015
+ let zerosOutputs = orderedOutputs.filter(x => x.order === 0);
1016
+ orderedOutputs = orderedOutputs.filter((el) => { return !zerosOutputs.includes(el); });
1017
+ orderedOutputs = zerosOutputs.concat(orderedOutputs);
1018
+
1019
+ const controlOrderOutputs = orderedOutputs.map((value) => { return value.id; });
1020
+ for (let i = 0; i < controlOrderOutputs.length; i++) {
1021
+ if (this.outputs[controlOrderOutputs[i]])
1022
+ if (this.outputs[controlOrderOutputs[i]]!.order !== i)
1023
+ this.outputs[controlOrderOutputs[i]]!.order = i;
1024
+ }
1025
+ }
1026
+
1027
+ private _warningCreator() {
1028
+ // set the output content to what has been updated
1029
+ for (const outputId in this.outputs) {
1030
+ let warning: string = '';
1031
+ if (this.outputs[outputId].msg)
1032
+ warning += `\n\t- ${this.outputs[outputId].msg}`;
1033
+ if (this.outputs[outputId].status_collect && this.outputs[outputId].status_collect !== ShapeDiverResponseModelComputationStatus.SUCCESS)
1034
+ warning += `\n\t- status_collect is ${this.outputs[outputId].status_collect}`;
1035
+ if (this.outputs[outputId].status_computation && this.outputs[outputId].status_computation !== ShapeDiverResponseModelComputationStatus.SUCCESS)
1036
+ warning += `\n\t- status_computation is ${this.outputs[outputId].status_computation}`;
1037
+ if (warning)
1038
+ this._logger.warn(LOGGING_TOPIC.SESSION, `\nOutput(${outputId}):${warning}`);
1039
+ }
1040
+
1041
+ // set the export definitions
1042
+ for (const exportId in this.exports) {
1043
+ let warning: string = '';
1044
+ if (this.exports[exportId].msg)
1045
+ warning += `\n\t- ${this.exports[exportId].msg}`;
1046
+ if (this.exports[exportId].status_collect && this.exports[exportId].status_collect !== ShapeDiverResponseModelComputationStatus.SUCCESS)
1047
+ warning += `\n\t- status_collect is ${this.exports[exportId].status_collect}`;
1048
+ if (this.exports[exportId].status_computation && this.exports[exportId].status_computation !== ShapeDiverResponseModelComputationStatus.SUCCESS)
1049
+ warning += `\n\t- status_computation is ${this.exports[exportId].status_computation}`;
1050
+ if (warning)
1051
+ this._logger.warn(LOGGING_TOPIC.SESSION, `\nExport(${exportId}):${warning}`);
1052
+ }
1053
+ }
1054
+
1055
+ private checkAvailability(action?: string, checkForModelId = false) {
1056
+ if (!this._responseDto) {
1057
+ const error = new ShapeDiverViewerSessionError(`Session.checkAvailability: responseDto not available.`);
1058
+ throw this._logger.handleError(LOGGING_TOPIC.SESSION, 'Session.checkAvailability', error);
1059
+ }
1060
+
1061
+ if (!this._sessionId) {
1062
+ const error = new ShapeDiverViewerSessionError(`Session.checkAvailability: sessionId not available.`);
1063
+ throw this._logger.handleError(LOGGING_TOPIC.SESSION, 'Session.checkAvailability', error);
1064
+ }
1065
+
1066
+ if (checkForModelId && !this._modelId) {
1067
+ const error = new ShapeDiverViewerSessionError(`Session.checkAvailability: modelId not available.`);
1068
+ throw this._logger.handleError(LOGGING_TOPIC.SESSION, 'Session.checkAvailability', error);
1069
+ }
1070
+
1071
+ if (action && !this._responseDto.actions) {
1072
+ const error = new ShapeDiverViewerSessionError(`Session.checkAvailability: actions not available.`);
1073
+ throw this._logger.handleError(LOGGING_TOPIC.SESSION, 'Session.checkAvailability', error);
1074
+ }
1075
+
1076
+ const responseDtoAction = this._responseDto.actions?.find(a => a.name === action);
1077
+ if (action && !responseDtoAction) {
1078
+ const error = new ShapeDiverViewerSessionError(`Session.checkAvailability: action ${action} not available.`);
1079
+ throw this._logger.handleError(LOGGING_TOPIC.SESSION, 'Session.checkAvailability', error);
1080
+ }
1081
+ }
1082
+
1083
+ private async customizeInternal(cancelRequest: () => boolean): Promise<ISessionTreeNode> {
1084
+ return this.customizeSession(this._parameterValues, cancelRequest);
1085
+ }
1086
+
1087
+ private async customizeSession(parameters: { [key: string]: string }, cancelRequest: () => boolean, retry = false): Promise<ISessionTreeNode> {
1088
+ this.checkAvailability('customize');
1089
+ try {
1090
+ this._performanceEvaluator.startSection('sessionResponse');
1091
+ const responseDto = await this._sdk.utils.submitAndWaitForCustomization(this._sdk, this._sessionId!, parameters);
1092
+ this._performanceEvaluator.endSection('sessionResponse');
1093
+ if (cancelRequest()) return new SessionTreeNode();
1094
+ this.updateResponseDto(responseDto);
1095
+ return this.loadOutputs(cancelRequest);
1096
+ } catch (e) {
1097
+ await this.handleError(LOGGING_TOPIC.SESSION, 'Session.customizeSession', e, retry);
1098
+ if (cancelRequest()) return new SessionTreeNode();
1099
+ return await this.customizeSession(parameters, cancelRequest, true);
1100
+ }
1101
+ }
1102
+
1103
+ private async handleError(topic: LOGGING_TOPIC, scope: string, e: ShapeDiverBackendError | ShapeDiverViewerError | Error | unknown, retry = false) {
1104
+ if (e instanceof ShapeDiverResponseError) {
1105
+ if (e.error === ShapeDiverResponseErrorType.SESSION_GONE_ERROR) {
1106
+ // case 1: the session is no longer available
1107
+ // we try to re-initialize the session 3 times, if that does not work, we close it
1108
+
1109
+ this._logger.warn(topic, `The session has been closed, trying to re-initialize.`);
1110
+
1111
+ if (this._retryCounter < 3) {
1112
+ // we retry this 3 times, the `retry` option in the init function is set to true and passed on
1113
+ this._retryCounter = retry ? this._retryCounter + 1 : 1;
1114
+ try {
1115
+ this._initialized = false;
1116
+ await this.init(this.parameterValues, true);
1117
+ } catch (e) {
1118
+ if (e instanceof ShapeDiverViewerError || e instanceof ShapeDiverBackendError) throw e;
1119
+ throw this._logger.handleError(topic, scope, e);
1120
+ }
1121
+ } else {
1122
+ // the retries were exceeded, we close the session
1123
+ this._logger.warn(LOGGING_TOPIC.SESSION, 'Tried to retry the connect multiple times, bearer token still not valid. Closing Session.');
1124
+ try { await this._closeOnFailure(); } catch (e) { }
1125
+ throw this._logger.handleError(topic, scope, e);
1126
+ }
1127
+ } else if (e.error === ShapeDiverResponseErrorType.JWT_VALIDATION_ERROR) {
1128
+ // if any of the above errors occur, we try to get a new bearer token
1129
+ // if we get a new one, we retry 3 times (by requiring new bearer tokens every time)
1130
+ if (this._retryCounter < 3) {
1131
+ if (this._refreshBearerToken) {
1132
+ this.bearerToken = await this._refreshBearerToken();
1133
+ this._retryCounter = retry ? this._retryCounter + 1 : 1;
1134
+ this._logger.warn(LOGGING_TOPIC.SESSION, 'Re-trying with new bearer token.');
1135
+ } else {
1136
+ // no bearer tokens are supplied, we close the session
1137
+ this._logger.warn(LOGGING_TOPIC.SESSION, 'No retry possible, no new bearer token was supplied. Closing Session.');
1138
+ try { await this._closeOnFailure(); } catch (e) { }
1139
+ throw this._logger.handleError(topic, scope, e);
1140
+ }
1141
+ } else {
1142
+ // the retries were exceeded, we close the session
1143
+ this._logger.warn(LOGGING_TOPIC.SESSION, 'Tried to retry the connect multiple times, bearer token still not valid. Closing Session.');
1144
+ try { await this._closeOnFailure(); } catch (e) { }
1145
+ throw this._logger.handleError(topic, scope, e);
1146
+ }
1147
+ } else {
1148
+ throw this._logger.handleError(topic, scope, e);
1149
+ }
1150
+ } else {
1151
+ throw this._logger.handleError(topic, scope, e);
1152
+ }
1153
+ }
1154
+
1155
+ /**
1156
+ * Returns a promise that resolves after the amount of milliseconds provided.
1157
+ *
1158
+ * @param ms the milliseconds
1159
+ * @returns promise that resolve after specified milliseconds
1160
+ */
1161
+ private async timeout(ms: number): Promise<any> {
1162
+ return new Promise(resolve => setTimeout(resolve, ms));
1163
+ }
1164
+
1165
+ private updateResponseDto(responseDto: ShapeDiverResponseDto, initialParameters?: {
1166
+ [key: string]: string;
1167
+ }) {
1168
+ if (!this._responseDto) {
1169
+ this._responseDto = responseDto;
1170
+ return;
1171
+ }
1172
+
1173
+ // convert parameters
1174
+ if (responseDto.parameters) {
1175
+ for (let parameterId in responseDto.parameters) {
1176
+ this._responseDto.parameters = this._responseDto.parameters || {};
1177
+ this._responseDto.parameters[parameterId] = this._responseDto.parameters[parameterId] || responseDto.parameters[parameterId];
1178
+ }
1179
+ }
1180
+
1181
+ // convert outputs
1182
+ if (responseDto.outputs) {
1183
+ for (let outputId in responseDto.outputs) {
1184
+ this._responseDto.outputs = this._responseDto.outputs || {};
1185
+ if ('version' in responseDto.outputs[outputId] || !(this._responseDto.outputs[outputId] && 'version' in this._responseDto.outputs[outputId]))
1186
+ this._responseDto.outputs[outputId] = responseDto.outputs[outputId];
1187
+ }
1188
+ }
1189
+
1190
+ // convert exports
1191
+ if (responseDto.exports) {
1192
+ for (let exportId in responseDto.exports) {
1193
+ this._responseDto.exports = this._responseDto.exports || {};
1194
+ if ('version' in responseDto.exports[exportId] || !(this._responseDto.exports[exportId] && 'version' in this._responseDto.exports[exportId]))
1195
+ this._responseDto.exports[exportId] = responseDto.exports[exportId];
1196
+ }
1197
+ }
1198
+
1199
+ const parameterSet: {
1200
+ [key: string]: {
1201
+ value: any,
1202
+ valueString: string
1203
+ }
1204
+ } = {};
1205
+
1206
+ for (let parameterId in this._responseDto.parameters) {
1207
+ if (this.parameters[parameterId]) continue;
1208
+ this._responseDto.parameters[parameterId].id = parameterId;
1209
+
1210
+ switch (true) {
1211
+ case this._responseDto.parameters[parameterId].type === PARAMETER_TYPE.BOOL:
1212
+ this.parameters[parameterId] = new Parameter<boolean>(this._responseDto.parameters[parameterId], this);
1213
+ break;
1214
+ case this._responseDto.parameters[parameterId].type === PARAMETER_TYPE.COLOR:
1215
+ this.parameters[parameterId] = new Parameter<number | vec3>(this._responseDto.parameters[parameterId], this);
1216
+ break;
1217
+ case this._responseDto.parameters[parameterId].type === PARAMETER_TYPE.FILE:
1218
+ this.parameters[parameterId] = new FileParameter(this._responseDto.parameters[parameterId], this);
1219
+ break;
1220
+ case this._responseDto.parameters[parameterId].type === PARAMETER_TYPE.EVEN || this._responseDto.parameters[parameterId].type === PARAMETER_TYPE.FLOAT || this._responseDto.parameters[parameterId].type === PARAMETER_TYPE.INT || this._responseDto.parameters[parameterId].type === PARAMETER_TYPE.ODD:
1221
+ this.parameters[parameterId] = new Parameter<number>(this._responseDto.parameters[parameterId], this);
1222
+ break;
1223
+ default:
1224
+ this.parameters[parameterId] = new Parameter<string>(this._responseDto.parameters[parameterId], this);
1225
+ break;
1226
+ }
1227
+
1228
+ if(initialParameters && initialParameters[parameterId] !== undefined)
1229
+ this.parameters[parameterId].value = initialParameters[parameterId]
1230
+
1231
+ parameterSet[parameterId] = {
1232
+ value: this.parameters[parameterId].value,
1233
+ valueString: this.parameters[parameterId].stringify()
1234
+ }
1235
+
1236
+ if (!this.initialized)
1237
+ this.parameterValues[parameterId] = parameterSet[parameterId].valueString;
1238
+ }
1239
+
1240
+ // store the initialization as the first parameter set in the history
1241
+ if (!this.initialized)
1242
+ this.#parameterHistory.push(parameterSet);
1243
+
1244
+ for (let exportId in this._responseDto.exports) {
1245
+ if (this._responseDto.exports[exportId].type === ShapeDiverResponseExportDefinitionType.EMAIL || this._responseDto.exports[exportId].type === ShapeDiverResponseExportDefinitionType.DOWNLOAD) {
1246
+ if(!this.exports[exportId]) {
1247
+ this._responseDto.exports[exportId].id = exportId;
1248
+ this.exports[exportId] = new Export(this._responseDto.exports[exportId], this);
1249
+ } else {
1250
+ this.exports[exportId].updateExportDefinition(this._responseDto.exports[exportId])
1251
+ }
1252
+ }
1253
+ }
1254
+
1255
+ for (let outputId in this._responseDto.outputs) {
1256
+ if(!this.outputs[outputId]) {
1257
+ this._responseDto.outputs[outputId].id = outputId;
1258
+ if (this.outputsFreeze[outputId] === undefined) this.outputsFreeze[outputId] = false;
1259
+ this.outputs[outputId] = new Output(<ShapeDiverResponseOutput>this._responseDto.outputs[outputId], this);
1260
+ } else {
1261
+ this.outputs[outputId].updateOutputDefinition(<ShapeDiverResponseOutput>this._responseDto.outputs[outputId])
1262
+ }
1263
+ }
1264
+ }
1265
+
1266
+ // #endregion Private Methods (8)
1267
+ }