@loaders.gl/tile-converter 4.2.0-alpha.1 → 4.2.0-alpha.2

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 (38) hide show
  1. package/dist/3d-tiles-converter/3d-tiles-converter.d.ts +11 -0
  2. package/dist/3d-tiles-converter/3d-tiles-converter.d.ts.map +1 -1
  3. package/dist/3d-tiles-converter/3d-tiles-converter.js +77 -27
  4. package/dist/3d-tiles-converter/3d-tiles-converter.js.map +1 -1
  5. package/dist/3d-tiles-converter/helpers/load-i3s.d.ts +22 -1
  6. package/dist/3d-tiles-converter/helpers/load-i3s.d.ts.map +1 -1
  7. package/dist/3d-tiles-converter/helpers/load-i3s.js +49 -4
  8. package/dist/3d-tiles-converter/helpers/load-i3s.js.map +1 -1
  9. package/dist/converter-cli.js +2 -1
  10. package/dist/converter-cli.js.map +1 -1
  11. package/dist/converter.min.cjs +137 -130
  12. package/dist/deps-installer/deps-installer.js +1 -1
  13. package/dist/i3s-converter/helpers/load-3d-tiles.d.ts.map +1 -1
  14. package/dist/i3s-converter/helpers/load-3d-tiles.js +22 -2
  15. package/dist/i3s-converter/helpers/load-3d-tiles.js.map +1 -1
  16. package/dist/i3s-server/bin/i3s-server.min.cjs +86 -86
  17. package/dist/index.cjs +530 -66
  18. package/dist/lib/json-schemas/conversion-dump-json-schema.d.ts +463 -0
  19. package/dist/lib/json-schemas/conversion-dump-json-schema.d.ts.map +1 -0
  20. package/dist/lib/json-schemas/conversion-dump-json-schema.js +463 -0
  21. package/dist/lib/json-schemas/conversion-dump-json-schema.js.map +1 -0
  22. package/dist/lib/utils/conversion-dump.d.ts +12 -5
  23. package/dist/lib/utils/conversion-dump.d.ts.map +1 -1
  24. package/dist/lib/utils/conversion-dump.js +44 -24
  25. package/dist/lib/utils/conversion-dump.js.map +1 -1
  26. package/dist/lib/utils/file-utils.d.ts +6 -0
  27. package/dist/lib/utils/file-utils.d.ts.map +1 -1
  28. package/dist/lib/utils/file-utils.js +7 -0
  29. package/dist/lib/utils/file-utils.js.map +1 -1
  30. package/dist/pgm-loader.js +1 -1
  31. package/package.json +15 -14
  32. package/src/3d-tiles-converter/3d-tiles-converter.ts +104 -31
  33. package/src/3d-tiles-converter/helpers/load-i3s.ts +86 -7
  34. package/src/converter-cli.ts +2 -1
  35. package/src/i3s-converter/helpers/load-3d-tiles.ts +52 -2
  36. package/src/lib/json-schemas/conversion-dump-json-schema.ts +285 -0
  37. package/src/lib/utils/conversion-dump.ts +79 -27
  38. package/src/lib/utils/file-utils.ts +13 -0
@@ -1,36 +1,45 @@
1
- import {load} from '@loaders.gl/core';
1
+ import {LoaderWithParser, load} from '@loaders.gl/core';
2
2
  import {
3
3
  I3STileContent,
4
4
  I3STileHeader,
5
5
  I3STilesetHeader,
6
6
  I3SLoader,
7
- I3SLoaderOptions
7
+ I3SLoaderOptions,
8
+ parseSLPKArchive
8
9
  } from '@loaders.gl/i3s';
10
+ import {FileHandleFile} from '@loaders.gl/loader-utils';
11
+ import {ZipFileSystem} from '@loaders.gl/zip';
12
+
13
+ export type SLPKUrlParts = {slpkFileName: string; internalFileName: string};
9
14
 
10
15
  /**
11
16
  * Load I3S node content
12
17
  * @param sourceTileset - source layer JSON
13
18
  * @param sourceTile - source I3S node metadata
14
19
  * @param tilesetLoadOptions - load options for Tiles3DLoader
20
+ * @param slpkFilesystem - loaded instance of ZipFileSystem for local convertion from SLPK file
15
21
  * @returns - 3DTiles tile content or null
16
22
  */
17
23
  export const loadI3SContent = async (
18
24
  sourceTileset: I3STilesetHeader | null,
19
25
  sourceTile: I3STileHeader,
20
- tilesetLoadOptions: I3SLoaderOptions
26
+ tilesetLoadOptions: I3SLoaderOptions,
27
+ slpkFilesystem: ZipFileSystem | null
21
28
  ): Promise<I3STileContent | null> => {
22
29
  if (!sourceTileset || !sourceTile.contentUrl) {
23
30
  return null;
24
31
  }
25
32
 
26
- const loadOptions = {
33
+ const loadOptions: I3SLoaderOptions = {
27
34
  ...tilesetLoadOptions,
28
35
  i3s: {
29
36
  ...tilesetLoadOptions.i3s,
37
+ // @ts-expect-error
30
38
  isTileset: false,
39
+ // @ts-expect-error
31
40
  isTileHeader: false,
32
41
  _tileOptions: {
33
- attributeUrls: sourceTile.attributeUrls,
42
+ attributeUrls: sourceTile.attributeUrls || [],
34
43
  textureUrl: sourceTile.textureUrl,
35
44
  textureFormat: sourceTile.textureFormat,
36
45
  textureLoaderOptions: sourceTile.textureLoaderOptions,
@@ -40,13 +49,83 @@ export const loadI3SContent = async (
40
49
  },
41
50
  _tilesetOptions: {
42
51
  store: sourceTileset.store,
52
+ // @ts-expect-error
43
53
  attributeStorageInfo: sourceTileset.attributeStorageInfo,
54
+ // @ts-expect-error
44
55
  fields: sourceTileset.fields
45
56
  }
46
57
  }
47
58
  };
48
- const tileContent = await load(sourceTile.contentUrl, I3SLoader, loadOptions);
59
+ const tileContent = await loadFromArchive(
60
+ sourceTile.contentUrl,
61
+ I3SLoader,
62
+ loadOptions,
63
+ slpkFilesystem
64
+ );
49
65
 
50
- // @ts-expect-error
51
66
  return tileContent;
52
67
  };
68
+
69
+ /**
70
+ * Load local SLPK file to ZipFileSystem instance
71
+ * @param url - path to SLPK file
72
+ * @returns instance of ZipFileSystem or null if url is not an SLPK file
73
+ */
74
+ export async function openSLPK(url: string): Promise<ZipFileSystem | null> {
75
+ const slpkUrlParts = url.split('.slpk');
76
+ const {slpkFileName} = getSlpkUrlParts(url) || {};
77
+ if (slpkFileName) {
78
+ const slpkFileName = `${slpkUrlParts[0]}.slpk`;
79
+ const fileProvider = new FileHandleFile(slpkFileName);
80
+ const archive = await parseSLPKArchive(fileProvider, undefined, slpkFileName);
81
+ const fileSystem = new ZipFileSystem(archive);
82
+ return fileSystem;
83
+ }
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * Load a resource with load options and .3tz format support
89
+ * @param url - resource URL
90
+ * @param loader - loader to parse data (Tiles3DLoader / CesiumIonLoader)
91
+ * @param loadOptions - i3s loader options
92
+ * @returns i3s resource
93
+ */
94
+ export async function loadFromArchive(
95
+ url: string,
96
+ loader: LoaderWithParser,
97
+ loadOptions: I3SLoaderOptions,
98
+ fileSystem: ZipFileSystem | null
99
+ ) {
100
+ const slpkUrlParts = getSlpkUrlParts(url);
101
+ if (fileSystem !== null && slpkUrlParts !== null) {
102
+ const {internalFileName} = slpkUrlParts;
103
+ const content = await load(internalFileName, loader, {
104
+ ...loadOptions,
105
+ fetch: fileSystem.fetch.bind(fileSystem)
106
+ });
107
+ return content;
108
+ }
109
+ return await load(url, loader, loadOptions);
110
+ }
111
+
112
+ /**
113
+ * Extract slpk file path and internal from the url
114
+ * For example, for `./path/to/file.slpk/nodes/0` it returns
115
+ * {"slpkFileName": "./path/to/file.slpk", "internalFileName": "/nodes/0" }
116
+ * @param url full internal file path
117
+ * @returns object with internal slpk file parts
118
+ */
119
+ function getSlpkUrlParts(url: string): SLPKUrlParts | null {
120
+ const slpkUrlParts = url.split('.slpk');
121
+ let result: SLPKUrlParts | null;
122
+ // Not '.slpk'. The file will be loaded with global fetch function
123
+ if (slpkUrlParts.length === 1) {
124
+ result = null;
125
+ } else if (slpkUrlParts.length === 2) {
126
+ result = {slpkFileName: `${slpkUrlParts[0]}.slpk`, internalFileName: slpkUrlParts[1].slice(1)};
127
+ } else {
128
+ throw new Error('Unexpected URL format');
129
+ }
130
+ return result;
131
+ }
@@ -210,7 +210,8 @@ async function convert(options: ValidatedTileConversionOptions) {
210
210
  outputPath: options.output,
211
211
  tilesetName: options.name,
212
212
  maxDepth: options.maxDepth,
213
- egmFilePath: options.egm
213
+ egmFilePath: options.egm,
214
+ inquirer
214
215
  });
215
216
  break;
216
217
  case TILESET_TYPE._3DTILES:
@@ -4,8 +4,17 @@ import type {
4
4
  Tiles3DTileJSONPostprocessed,
5
5
  Tiles3DTilesetJSONPostprocessed
6
6
  } from '@loaders.gl/3d-tiles';
7
- import {Tiles3DArchiveFileSystem} from '@loaders.gl/3d-tiles';
7
+ import {Tiles3DArchive} from '@loaders.gl/3d-tiles';
8
8
  import {LoaderWithParser, load} from '@loaders.gl/core';
9
+ import {FileHandleFile, FileProvider} from '@loaders.gl/loader-utils';
10
+ import {
11
+ CD_HEADER_SIGNATURE,
12
+ ZipFileSystem,
13
+ parseHashTable,
14
+ parseZipCDFileHeader,
15
+ parseZipLocalFileHeader,
16
+ searchFromTheEnd
17
+ } from '@loaders.gl/zip';
9
18
 
10
19
  /**
11
20
  * Load nested 3DTiles tileset. If the sourceTile is not nested tileset - do nothing
@@ -104,7 +113,10 @@ export async function loadFromArchive(
104
113
  }
105
114
  if (filename) {
106
115
  const tz3Path = `${tz3UrlParts[0]}.3tz`;
107
- const fileSystem = new Tiles3DArchiveFileSystem(tz3Path);
116
+ const fileProvider = new FileHandleFile(tz3Path);
117
+ const hashTable = await loadHashTable(fileProvider);
118
+ const archive = new Tiles3DArchive(fileProvider, hashTable, tz3Path);
119
+ const fileSystem = new ZipFileSystem(archive);
108
120
  const content = await load(filename, loader, {
109
121
  ...loadOptions,
110
122
  fetch: fileSystem.fetch.bind(fileSystem)
@@ -123,3 +135,41 @@ export async function loadFromArchive(
123
135
  export function isNestedTileset(tile: Tiles3DTileJSONPostprocessed) {
124
136
  return tile?.type === 'json' || tile?.type === '3tz';
125
137
  }
138
+
139
+ /**
140
+ * Load hash file from 3TZ
141
+ * @param fileProvider - binary reader of 3TZ
142
+ * @returns hash table of the 3TZ file content or undefined if the hash file is not presented inside
143
+ */
144
+ async function loadHashTable(
145
+ fileProvider: FileProvider
146
+ ): Promise<undefined | Record<string, bigint>> {
147
+ let hashTable: undefined | Record<string, bigint>;
148
+
149
+ const hashCDOffset = await searchFromTheEnd(fileProvider, CD_HEADER_SIGNATURE);
150
+
151
+ const cdFileHeader = await parseZipCDFileHeader(hashCDOffset, fileProvider);
152
+
153
+ // '@3dtilesIndex1@' is index file that must be the last in the archive. It allows
154
+ // to improve load and read performance when the archive contains a very large number
155
+ // of files.
156
+ if (cdFileHeader?.fileName === '@3dtilesIndex1@') {
157
+ const localFileHeader = await parseZipLocalFileHeader(
158
+ cdFileHeader.localHeaderOffset,
159
+ fileProvider
160
+ );
161
+ if (!localFileHeader) {
162
+ throw new Error('corrupted 3tz');
163
+ }
164
+
165
+ const fileDataOffset = localFileHeader.fileDataOffset;
166
+ const hashFile = await fileProvider.slice(
167
+ fileDataOffset,
168
+ fileDataOffset + localFileHeader.compressedSize
169
+ );
170
+
171
+ hashTable = parseHashTable(hashFile);
172
+ }
173
+
174
+ return hashTable;
175
+ }
@@ -0,0 +1,285 @@
1
+ export const dumpJsonSchema = {
2
+ type: 'object',
3
+ properties: {
4
+ options: {
5
+ type: 'object',
6
+ properties: {
7
+ inputUrl: {type: 'string'},
8
+ outputPath: {type: 'string'},
9
+ tilesetName: {type: 'string'},
10
+ maxDepth: {type: 'number'},
11
+ slpk: {type: 'boolean'},
12
+ egmFilePath: {type: 'string'},
13
+ token: {type: 'string'},
14
+ draco: {type: 'boolean'},
15
+ mergeMaterials: {type: 'boolean'},
16
+ generateTextures: {type: 'boolean'},
17
+ generateBoundingVolumes: {type: 'boolean'},
18
+ metadataClass: {type: 'string'},
19
+ analyze: {type: 'boolean'}
20
+ },
21
+ required: ['inputUrl', 'outputPath', 'tilesetName']
22
+ },
23
+ tilesConverted: {
24
+ type: 'object',
25
+ patternProperties: {
26
+ '.*': {
27
+ type: 'object',
28
+ properties: {
29
+ nodes: {
30
+ type: 'array',
31
+ items: {
32
+ type: 'object',
33
+ properties: {
34
+ nodeId: {type: ['number', 'string']},
35
+ done: {type: 'boolean'},
36
+ progress: {type: 'object', patternProperties: {'.*': {type: 'boolean'}}},
37
+ dumpMetadata: {
38
+ type: 'object',
39
+ properties: {
40
+ boundingVolumes: {
41
+ type: ['object', 'null'],
42
+ properties: {
43
+ mbs: {
44
+ type: 'array',
45
+ minItems: 4,
46
+ maxItems: 4,
47
+ items: {type: 'number'}
48
+ },
49
+ obb: {
50
+ type: 'object',
51
+ properties: {
52
+ center: {
53
+ type: 'array',
54
+ minItems: 3,
55
+ maxItems: 3,
56
+ items: {type: 'number'}
57
+ },
58
+ halfSize: {
59
+ type: 'array',
60
+ minItems: 3,
61
+ maxItems: 3,
62
+ items: {type: 'number'}
63
+ },
64
+ quaternion: {
65
+ type: 'array',
66
+ minItems: 4,
67
+ maxItems: 4,
68
+ items: {type: 'number'}
69
+ }
70
+ },
71
+ required: ['center', 'halfSize', 'quaternion']
72
+ }
73
+ },
74
+ required: ['mbs', 'obb']
75
+ },
76
+ attributesCount: {type: 'number'},
77
+ featureCount: {type: 'number'},
78
+ geometry: {type: 'boolean'},
79
+ hasUvRegions: {type: 'boolean'},
80
+ materialId: {type: 'number'},
81
+ texelCountHint: {type: 'number'},
82
+ vertexCount: {type: 'number'}
83
+ },
84
+ required: [
85
+ 'boundingVolumes',
86
+ 'featureCount',
87
+ 'geometry',
88
+ 'hasUvRegions',
89
+ 'materialId',
90
+ 'vertexCount'
91
+ ]
92
+ }
93
+ },
94
+ required: ['nodeId', 'done']
95
+ }
96
+ }
97
+ },
98
+ required: ['nodes']
99
+ }
100
+ }
101
+ },
102
+ textureSetDefinitions: {
103
+ type: 'array',
104
+ items: {
105
+ type: 'object',
106
+ properties: {
107
+ formats: {
108
+ type: 'array',
109
+ items: {
110
+ type: 'object',
111
+ properties: {
112
+ name: {type: 'string'},
113
+ format: {enum: ['jpg', 'png', 'ktx-etc2', 'dds', 'ktx2']}
114
+ },
115
+ required: ['name', 'format']
116
+ }
117
+ },
118
+ atlas: {type: 'boolean'}
119
+ },
120
+ required: ['formats']
121
+ }
122
+ },
123
+ attributeMetadataInfo: {
124
+ type: 'object',
125
+ properties: {
126
+ attributeStorageInfo: {
127
+ type: 'array',
128
+ items: {
129
+ type: 'object',
130
+ properties: {
131
+ key: {type: 'string'},
132
+ name: {type: 'string'},
133
+ header: {
134
+ type: 'array',
135
+ items: {
136
+ type: 'object',
137
+ properties: {property: {type: 'string'}, valueType: {type: 'string'}},
138
+ required: ['property', 'valueType']
139
+ }
140
+ },
141
+ ordering: {type: 'array', items: {type: 'string'}},
142
+ attributeValues: {$ref: '#/$defs/AttributeValue'},
143
+ attributeByteCounts: {$ref: '#/$defs/AttributeValue'},
144
+ objectIds: {$ref: '#/$defs/AttributeValue'}
145
+ },
146
+ required: ['key', 'name', 'header']
147
+ }
148
+ },
149
+ fields: {
150
+ type: 'array',
151
+ items: {
152
+ type: 'object',
153
+ properties: {
154
+ name: {type: 'string'},
155
+ type: {$ref: '#/$defs/ESRIField'},
156
+ alias: {type: 'string'},
157
+ domain: {$ref: '#/$defs/Domain'}
158
+ },
159
+ required: ['name', 'type']
160
+ }
161
+ },
162
+ popupInfo: {
163
+ type: 'object',
164
+ properties: {
165
+ title: {type: 'string'},
166
+ description: {type: 'string'},
167
+ expressionInfos: {type: 'array', items: {}},
168
+ fieldInfos: {type: 'array', items: {$ref: '#/$defs/FieldInfo'}},
169
+ mediaInfos: {type: 'array', items: {}},
170
+ popupElements: {
171
+ type: 'array',
172
+ items: {
173
+ type: 'object',
174
+ properties: {
175
+ text: {type: 'string'},
176
+ type: {type: 'string'},
177
+ fieldInfos: {type: 'array', items: {$ref: '#/$defs/FieldInfo'}}
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+ },
184
+ required: ['attributeStorageInfo', 'fields']
185
+ },
186
+ materialDefinitions: {
187
+ type: 'array',
188
+ items: {
189
+ type: 'object',
190
+ properties: {
191
+ pbrMetallicRoughness: {
192
+ type: 'object',
193
+ properties: {
194
+ baseColorFactor: {
195
+ type: 'array',
196
+ minItems: 4,
197
+ maxItems: 4,
198
+ items: {type: 'number'}
199
+ },
200
+ baseColorTexture: {$ref: '#/$defs/I3SMaterialTexture'},
201
+ metallicFactor: {type: 'number'},
202
+ roughnessFactor: {type: 'number'},
203
+ metallicRoughnessTexture: {$ref: '#/$defs/I3SMaterialTexture'}
204
+ },
205
+ required: ['metallicFactor', 'roughnessFactor']
206
+ },
207
+ normalTexture: {$ref: '#/$defs/I3SMaterialTexture'},
208
+ occlusionTexture: {$ref: '#/$defs/I3SMaterialTexture'},
209
+ emissiveTexture: {$ref: '#/$defs/I3SMaterialTexture'},
210
+ emissiveFactor: {type: 'array', minItems: 3, maxItems: 3, items: {type: 'number'}},
211
+ alphaMode: {enum: ['opaque', 'mask', 'blend']},
212
+ alphaCutoff: {type: 'number'},
213
+ doubleSided: {type: 'boolean'},
214
+ cullFace: {enum: ['none', 'front', 'back']}
215
+ },
216
+ required: ['pbrMetallicRoughness', 'alphaMode']
217
+ }
218
+ }
219
+ },
220
+ required: ['options', 'tilesConverted'],
221
+ $defs: {
222
+ AttributeValue: {
223
+ type: 'object',
224
+ properties: {
225
+ valueType: {type: 'string'},
226
+ encoding: {type: 'string'},
227
+ valuesPerElement: {type: 'number'}
228
+ },
229
+ required: ['valueType']
230
+ },
231
+ ESRIField: {
232
+ enum: [
233
+ 'esriFieldTypeDate',
234
+ 'esriFieldTypeSingle',
235
+ 'esriFieldTypeDouble',
236
+ 'esriFieldTypeGUID',
237
+ 'esriFieldTypeGlobalID',
238
+ 'esriFieldTypeInteger',
239
+ 'esriFieldTypeOID',
240
+ 'esriFieldTypeSmallInteger',
241
+ 'esriFieldTypeString'
242
+ ]
243
+ },
244
+ Domain: {
245
+ type: 'object',
246
+ properties: {
247
+ type: {type: 'string'},
248
+ name: {type: 'string'},
249
+ description: {type: 'string'},
250
+ fieldType: {type: 'string'},
251
+ range: {type: 'array', items: {type: 'number'}},
252
+ codedValues: {
253
+ type: 'array',
254
+ items: {
255
+ type: 'object',
256
+ properties: {name: {type: 'string'}, code: {type: ['string', 'number']}},
257
+ required: ['name', 'code']
258
+ }
259
+ },
260
+ mergePolicy: {type: 'string'},
261
+ splitPolicy: {type: 'string'}
262
+ },
263
+ required: ['type', 'name']
264
+ },
265
+ FieldInfo: {
266
+ type: 'object',
267
+ properties: {
268
+ fieldName: {type: 'string'},
269
+ visible: {type: 'boolean'},
270
+ isEditable: {type: 'boolean'},
271
+ label: {type: 'string'}
272
+ },
273
+ required: ['fieldName', 'visible', 'isEditable', 'label']
274
+ },
275
+ I3SMaterialTexture: {
276
+ type: 'object',
277
+ properties: {
278
+ textureSetDefinitionId: {type: 'number'},
279
+ texCoord: {type: 'number'},
280
+ factor: {type: 'number'}
281
+ },
282
+ required: ['textureSetDefinitionId']
283
+ }
284
+ }
285
+ };
@@ -1,9 +1,12 @@
1
1
  import {isDeepStrictEqual} from 'util';
2
2
  import {DUMP_FILE_SUFFIX} from '../../constants';
3
- import {isFileExists, openJson, removeFile, writeFile} from './file-utils';
3
+ import {isFileExists, openJson, removeFile, renameFile, writeFile} from './file-utils';
4
4
  import {join} from 'path';
5
5
  import {BoundingVolumes, I3SMaterialDefinition, TextureSetDefinitionFormats} from '@loaders.gl/i3s';
6
6
  import {AttributeMetadataInfoObject} from '../../i3s-converter/helpers/attribute-metadata-info';
7
+ import process from 'process';
8
+ import Ajv from 'ajv';
9
+ import {dumpJsonSchema} from '../json-schemas/conversion-dump-json-schema';
7
10
 
8
11
  export type ConversionDumpOptions = {
9
12
  inputUrl: string;
@@ -22,7 +25,7 @@ export type ConversionDumpOptions = {
22
25
  };
23
26
 
24
27
  type NodeDoneStatus = {
25
- nodeId: number;
28
+ nodeId: number | string;
26
29
  done: boolean;
27
30
  progress?: Record<string, boolean>;
28
31
  dumpMetadata?: DumpMetadata;
@@ -60,7 +63,7 @@ export class ConversionDump {
60
63
  /** Attributes Metadata */
61
64
  attributeMetadataInfo?: AttributeMetadataInfoObject;
62
65
  /** Array of materials definitions */
63
- materialDefinitions: I3SMaterialDefinition[] = [];
66
+ materialDefinitions?: I3SMaterialDefinition[];
64
67
 
65
68
  constructor() {
66
69
  this.tilesConverted = {};
@@ -108,23 +111,34 @@ export class ConversionDump {
108
111
  `${this.options.tilesetName}${DUMP_FILE_SUFFIX}`
109
112
  );
110
113
  if (await isFileExists(dumpFilename)) {
111
- const {
112
- options,
113
- tilesConverted,
114
- textureSetDefinitions,
115
- attributeMetadataInfo,
116
- materialDefinitions
117
- } = await openJson(
118
- join(this.options.outputPath, this.options.tilesetName),
119
- `${this.options.tilesetName}${DUMP_FILE_SUFFIX}`
120
- );
121
- if (isDeepStrictEqual(options, JSON.parse(JSON.stringify(this.options)))) {
122
- this.tilesConverted = tilesConverted;
123
- this.textureSetDefinitions = textureSetDefinitions;
124
- this.attributeMetadataInfo = attributeMetadataInfo;
125
- this.materialDefinitions = materialDefinitions;
126
- this.restored = true;
127
- return;
114
+ try {
115
+ const dump = await openJson(
116
+ join(this.options.outputPath, this.options.tilesetName),
117
+ `${this.options.tilesetName}${DUMP_FILE_SUFFIX}`
118
+ );
119
+
120
+ const {
121
+ options,
122
+ tilesConverted,
123
+ textureSetDefinitions,
124
+ attributeMetadataInfo,
125
+ materialDefinitions
126
+ } = dump;
127
+
128
+ const ajv = new Ajv();
129
+ const dumpJsonValidate = ajv.compile(dumpJsonSchema);
130
+ const isDumpValid = dumpJsonValidate(dump);
131
+
132
+ if (isDumpValid && isDeepStrictEqual(options, JSON.parse(JSON.stringify(this.options)))) {
133
+ this.tilesConverted = tilesConverted;
134
+ this.textureSetDefinitions = textureSetDefinitions;
135
+ this.attributeMetadataInfo = attributeMetadataInfo;
136
+ this.materialDefinitions = materialDefinitions;
137
+ this.restored = true;
138
+ return;
139
+ }
140
+ } catch (error) {
141
+ console.log("Can't open dump file", error);
128
142
  }
129
143
  }
130
144
  await this.deleteDumpFile();
@@ -142,8 +156,8 @@ export class ConversionDump {
142
156
  if (this.attributeMetadataInfo) {
143
157
  delete this.attributeMetadataInfo;
144
158
  }
145
- if (this.materialDefinitions.length > 0) {
146
- this.materialDefinitions = [];
159
+ if (this.materialDefinitions) {
160
+ delete this.materialDefinitions;
147
161
  }
148
162
  }
149
163
 
@@ -153,6 +167,7 @@ export class ConversionDump {
153
167
  private async updateDumpFile(): Promise<void> {
154
168
  if (this.options?.outputPath && this.options.tilesetName) {
155
169
  try {
170
+ const time = process.hrtime();
156
171
  await writeFile(
157
172
  join(this.options.outputPath, this.options.tilesetName),
158
173
  JSON.stringify({
@@ -162,7 +177,19 @@ export class ConversionDump {
162
177
  attributeMetadataInfo: this.attributeMetadataInfo,
163
178
  materialDefinitions: this.materialDefinitions
164
179
  }),
165
- `${this.options.tilesetName}${DUMP_FILE_SUFFIX}`
180
+ `${this.options.tilesetName}${DUMP_FILE_SUFFIX}.${time[0]}.${time[1]}`
181
+ );
182
+ await renameFile(
183
+ join(
184
+ this.options.outputPath,
185
+ this.options.tilesetName,
186
+ `${this.options.tilesetName}${DUMP_FILE_SUFFIX}.${time[0]}.${time[1]}`
187
+ ),
188
+ join(
189
+ this.options.outputPath,
190
+ this.options.tilesetName,
191
+ `${this.options.tilesetName}${DUMP_FILE_SUFFIX}`
192
+ )
166
193
  );
167
194
  } catch (error) {
168
195
  console.log("Can't update dump file", error);
@@ -218,9 +245,9 @@ export class ConversionDump {
218
245
  * @param fileName - source filename
219
246
  * @param nodeId - nodeId of the node
220
247
  */
221
- async addNode(filename: string, nodeId: number, dumpMetadata: DumpMetadata) {
248
+ async addNode(filename: string, nodeId: number | string, dumpMetadata?: DumpMetadata) {
222
249
  const {nodes} = this.getRecord(filename) || {nodes: []};
223
- nodes.push({nodeId, done: false, progress: {}, dumpMetadata});
250
+ nodes.push({nodeId, done: false, dumpMetadata});
224
251
  if (nodes.length === 1) {
225
252
  this.setRecord(filename, {nodes});
226
253
  }
@@ -250,7 +277,12 @@ export class ConversionDump {
250
277
  * @param resourceType - resource type to update status
251
278
  * @param value - value
252
279
  */
253
- updateDoneStatus(filename: string, nodeId: number, resourceType: string, value: boolean) {
280
+ updateDoneStatus(
281
+ filename: string,
282
+ nodeId: number | string,
283
+ resourceType: string,
284
+ value: boolean
285
+ ) {
254
286
  const nodeDump = this.tilesConverted[filename]?.nodes.find(
255
287
  (element) => element.nodeId === nodeId
256
288
  );
@@ -271,7 +303,7 @@ export class ConversionDump {
271
303
  * @param writeResults - array of writing resource files results
272
304
  */
273
305
  async updateConvertedTilesDump(
274
- changedRecords: {outputId?: number; sourceId?: string; resourceType?: string}[],
306
+ changedRecords: {outputId?: number | string; sourceId?: string; resourceType?: string}[],
275
307
  writeResults: PromiseSettledResult<string | null>[]
276
308
  ) {
277
309
  for (let i = 0; i < changedRecords.length; i++) {
@@ -299,6 +331,26 @@ export class ConversionDump {
299
331
  await this.updateDumpFile();
300
332
  }
301
333
 
334
+ /**
335
+ * Update 3d-tiles-converter dump file
336
+ * @param filename - source filename
337
+ * @param nodeId - nodeId
338
+ * @param done - conversion status
339
+ */
340
+ async updateConvertedNodesDumpFile(
341
+ filename: string,
342
+ nodeId: number | string,
343
+ done: boolean
344
+ ): Promise<void> {
345
+ const nodeDump = this.tilesConverted[filename]?.nodes.find(
346
+ (element) => element.nodeId === nodeId
347
+ );
348
+ if (nodeDump) {
349
+ nodeDump.done = done;
350
+ await this.updateDumpFile();
351
+ }
352
+ }
353
+
302
354
  /**
303
355
  * Check is source file conversion complete
304
356
  * @param filename - source filename