@itwin/imodel-transformer 0.0.1-dev.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 (43) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +33 -0
  3. package/lib/cjs/ECReferenceTypesCache.d.ts +37 -0
  4. package/lib/cjs/ECReferenceTypesCache.d.ts.map +1 -0
  5. package/lib/cjs/ECReferenceTypesCache.js +180 -0
  6. package/lib/cjs/ECReferenceTypesCache.js.map +1 -0
  7. package/lib/cjs/EntityMap.d.ts +26 -0
  8. package/lib/cjs/EntityMap.d.ts.map +1 -0
  9. package/lib/cjs/EntityMap.js +55 -0
  10. package/lib/cjs/EntityMap.js.map +1 -0
  11. package/lib/cjs/EntityUnifier.d.ts +16 -0
  12. package/lib/cjs/EntityUnifier.d.ts.map +1 -0
  13. package/lib/cjs/EntityUnifier.js +72 -0
  14. package/lib/cjs/EntityUnifier.js.map +1 -0
  15. package/lib/cjs/IModelCloneContext.d.ts +32 -0
  16. package/lib/cjs/IModelCloneContext.d.ts.map +1 -0
  17. package/lib/cjs/IModelCloneContext.js +195 -0
  18. package/lib/cjs/IModelCloneContext.js.map +1 -0
  19. package/lib/cjs/IModelExporter.d.ts +353 -0
  20. package/lib/cjs/IModelExporter.d.ts.map +1 -0
  21. package/lib/cjs/IModelExporter.js +804 -0
  22. package/lib/cjs/IModelExporter.js.map +1 -0
  23. package/lib/cjs/IModelImporter.d.ts +230 -0
  24. package/lib/cjs/IModelImporter.d.ts.map +1 -0
  25. package/lib/cjs/IModelImporter.js +591 -0
  26. package/lib/cjs/IModelImporter.js.map +1 -0
  27. package/lib/cjs/IModelTransformer.d.ts +499 -0
  28. package/lib/cjs/IModelTransformer.d.ts.map +1 -0
  29. package/lib/cjs/IModelTransformer.js +1357 -0
  30. package/lib/cjs/IModelTransformer.js.map +1 -0
  31. package/lib/cjs/PendingReferenceMap.d.ts +35 -0
  32. package/lib/cjs/PendingReferenceMap.d.ts.map +1 -0
  33. package/lib/cjs/PendingReferenceMap.js +81 -0
  34. package/lib/cjs/PendingReferenceMap.js.map +1 -0
  35. package/lib/cjs/TransformerLoggerCategory.d.ts +23 -0
  36. package/lib/cjs/TransformerLoggerCategory.d.ts.map +1 -0
  37. package/lib/cjs/TransformerLoggerCategory.js +31 -0
  38. package/lib/cjs/TransformerLoggerCategory.js.map +1 -0
  39. package/lib/cjs/transformer.d.ts +25 -0
  40. package/lib/cjs/transformer.d.ts.map +1 -0
  41. package/lib/cjs/transformer.js +77 -0
  42. package/lib/cjs/transformer.js.map +1 -0
  43. package/package.json +120 -0
@@ -0,0 +1,591 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hasEntityChanged = exports.IModelImporter = void 0;
4
+ /*---------------------------------------------------------------------------------------------
5
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
6
+ * See LICENSE.md in the project root for license terms and full copyright notice.
7
+ *--------------------------------------------------------------------------------------------*/
8
+ /** @packageDocumentation
9
+ * @module iModels
10
+ */
11
+ const core_bentley_1 = require("@itwin/core-bentley");
12
+ const core_common_1 = require("@itwin/core-common");
13
+ const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
14
+ const core_backend_1 = require("@itwin/core-backend");
15
+ const assert = require("assert");
16
+ const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelImporter;
17
+ /** Base class for importing data into an iModel.
18
+ * @see [iModel Transformation and Data Exchange]($docs/learning/transformer/index.md)
19
+ * @see [IModelExporter]($transformer)
20
+ * @see [IModelTransformer]($transformer)
21
+ * @beta
22
+ */
23
+ class IModelImporter {
24
+ /** Construct a new IModelImporter
25
+ * @param targetDb The target IModelDb
26
+ * @param options The options that specify how the import should be done.
27
+ */
28
+ constructor(targetDb, options) {
29
+ var _a, _b, _c;
30
+ /** The set of elements that should not be updated by this IModelImporter.
31
+ * Defaults to the elements that are always present (even in an "empty" iModel) and therefore do not need to be updated
32
+ * @note Adding an element to this set is typically necessary when remapping a source element to one that already exists in the target and already has the desired properties.
33
+ */
34
+ this.doNotUpdateElementIds = new Set([
35
+ core_common_1.IModel.rootSubjectId,
36
+ core_common_1.IModel.dictionaryId,
37
+ IModelImporter._realityDataSourceLinkPartitionStaticId,
38
+ ]);
39
+ /** The number of entity changes before incremental progress should be reported via the [[onProgress]] callback. */
40
+ this.progressInterval = 1000;
41
+ /** Tracks the current total number of entity changes. */
42
+ this._progressCounter = 0;
43
+ /** */
44
+ this._modelPropertiesToIgnore = new Set([
45
+ "geometryGuid", // cannot compare GeometricModel.GeometryGuid values across iModels
46
+ ]);
47
+ this.targetDb = targetDb;
48
+ this.options = {
49
+ autoExtendProjectExtents: (_a = options === null || options === void 0 ? void 0 : options.autoExtendProjectExtents) !== null && _a !== void 0 ? _a : true,
50
+ preserveElementIdsForFiltering: (_b = options === null || options === void 0 ? void 0 : options.preserveElementIdsForFiltering) !== null && _b !== void 0 ? _b : false,
51
+ simplifyElementGeometry: (_c = options === null || options === void 0 ? void 0 : options.simplifyElementGeometry) !== null && _c !== void 0 ? _c : false,
52
+ };
53
+ }
54
+ /** If `true` (the default), compute the projectExtents of the target iModel after elements are imported.
55
+ * The computed projectExtents will either include or exclude *outliers* depending on the `excludeOutliers` flag that defaults to `false`.
56
+ * @see [[IModelImportOptions.autoExtendProjectExtents]]
57
+ * @see [IModelImporter Options]($docs/learning/transformer/index.md#IModelImporter)
58
+ * @deprecated in 3.x. Use [[IModelImporter.options.autoExtendProjectExtents]] instead
59
+ */
60
+ get autoExtendProjectExtents() {
61
+ return this.options.autoExtendProjectExtents;
62
+ }
63
+ set autoExtendProjectExtents(val) {
64
+ this.options.autoExtendProjectExtents = val;
65
+ }
66
+ /**
67
+ * See [IModelTransformOptions.preserveElementIdsForFiltering]($transformer)
68
+ * @deprecated in 3.x. Use [[IModelImporter.options.preserveElementIdsForFiltering]] instead
69
+ */
70
+ get preserveElementIdsForFiltering() {
71
+ return this.options.preserveElementIdsForFiltering;
72
+ }
73
+ set preserveElementIdsForFiltering(val) {
74
+ this.options.preserveElementIdsForFiltering = val;
75
+ }
76
+ /**
77
+ * See [[IModelImportOptions.simplifyElementGeometry]]
78
+ * @deprecated in 3.x. Use [[IModelImporter.options.simplifyElementGeometry]] instead
79
+ */
80
+ get simplifyElementGeometry() {
81
+ return this.options.simplifyElementGeometry;
82
+ }
83
+ set simplifyElementGeometry(val) {
84
+ this.options.simplifyElementGeometry = val;
85
+ }
86
+ /** Import the specified ModelProps (either as an insert or an update) into the target iModel. */
87
+ importModel(modelProps) {
88
+ if ((undefined === modelProps.id) || !core_bentley_1.Id64.isValidId64(modelProps.id))
89
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidId, "Model Id not provided, should be the same as the ModeledElementId");
90
+ if (this.doNotUpdateElementIds.has(modelProps.id)) {
91
+ core_bentley_1.Logger.logInfo(loggerCategory, `Do not update target model ${modelProps.id}`);
92
+ return;
93
+ }
94
+ try {
95
+ const model = this.targetDb.models.getModel(modelProps.id); // throws IModelError.NotFound if model does not exist
96
+ if (hasEntityChanged(model, modelProps, this._modelPropertiesToIgnore)) {
97
+ this.onUpdateModel(modelProps);
98
+ }
99
+ }
100
+ catch (error) {
101
+ // catch NotFound error and insertModel
102
+ if (error instanceof core_common_1.IModelError && error.errorNumber === core_bentley_1.IModelStatus.NotFound) {
103
+ this.onInsertModel(modelProps);
104
+ return;
105
+ }
106
+ throw error;
107
+ }
108
+ }
109
+ /** Create a new Model from the specified ModelProps and insert it into the target iModel.
110
+ * @note A subclass may override this method to customize insert behavior but should call `super.onInsertModel`.
111
+ */
112
+ onInsertModel(modelProps) {
113
+ try {
114
+ const modelId = this.targetDb.models.insertModel(modelProps);
115
+ core_bentley_1.Logger.logInfo(loggerCategory, `Inserted ${this.formatModelForLogger(modelProps)}`);
116
+ this.trackProgress();
117
+ return modelId;
118
+ }
119
+ catch (error) {
120
+ if (!this.targetDb.containsClass(modelProps.classFullName)) {
121
+ // replace standard insert error with something more helpful
122
+ const errorMessage = `Model class "${modelProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`;
123
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidName, errorMessage);
124
+ }
125
+ throw error; // throw original error
126
+ }
127
+ }
128
+ /** Update an existing Model in the target iModel from the specified ModelProps.
129
+ * @note A subclass may override this method to customize update behavior but should call `super.onUpdateModel`.
130
+ */
131
+ onUpdateModel(modelProps) {
132
+ this.targetDb.models.updateModel(modelProps);
133
+ core_bentley_1.Logger.logInfo(loggerCategory, `Updated ${this.formatModelForLogger(modelProps)}`);
134
+ this.trackProgress();
135
+ }
136
+ /** Format a Model for the Logger. */
137
+ formatModelForLogger(modelProps) {
138
+ return `${modelProps.classFullName} [${modelProps.id}]`;
139
+ }
140
+ /** Import the specified ElementProps (either as an insert or an update) into the target iModel. */
141
+ importElement(elementProps) {
142
+ if (undefined !== elementProps.id && this.doNotUpdateElementIds.has(elementProps.id)) {
143
+ core_bentley_1.Logger.logInfo(loggerCategory, `Do not update target element ${elementProps.id}`);
144
+ return elementProps.id;
145
+ }
146
+ if (this.options.preserveElementIdsForFiltering) {
147
+ if (elementProps.id === undefined) {
148
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, `elementProps.id must be defined during a preserveIds operation`);
149
+ }
150
+ // Categories are the only element that onInserted will immediately insert a new element (their default subcategory)
151
+ // since default subcategories always exist and always will be inserted after their categories, we treat them as an update
152
+ // to prevent duplicate inserts.
153
+ // Otherwise we always insert during a preserveElementIdsForFiltering operation
154
+ if (isSubCategory(elementProps) && isDefaultSubCategory(elementProps)) {
155
+ this.onUpdateElement(elementProps);
156
+ }
157
+ else {
158
+ this.onInsertElement(elementProps);
159
+ }
160
+ }
161
+ else {
162
+ if (undefined !== elementProps.id) {
163
+ this.onUpdateElement(elementProps);
164
+ }
165
+ else {
166
+ this.onInsertElement(elementProps); // targetElementProps.id assigned by insertElement
167
+ }
168
+ }
169
+ return elementProps.id;
170
+ }
171
+ /** Create a new Element from the specified ElementProps and insert it into the target iModel.
172
+ * @returns The Id of the newly inserted Element.
173
+ * @note A subclass may override this method to customize insert behavior but should call `super.onInsertElement`.
174
+ */
175
+ onInsertElement(elementProps) {
176
+ try {
177
+ const elementId = this.targetDb.nativeDb.insertElement(elementProps, { forceUseId: this.options.preserveElementIdsForFiltering });
178
+ // set the id like [IModelDb.insertElement]($backend), does, the raw nativeDb method does not
179
+ elementProps.id = elementId;
180
+ core_bentley_1.Logger.logInfo(loggerCategory, `Inserted ${this.formatElementForLogger(elementProps)}`);
181
+ this.trackProgress();
182
+ if (this.options.simplifyElementGeometry) {
183
+ this.targetDb.nativeDb.simplifyElementGeometry({ id: elementId, convertBReps: true });
184
+ core_bentley_1.Logger.logInfo(loggerCategory, `Simplified element geometry for ${this.formatElementForLogger(elementProps)}`);
185
+ }
186
+ return elementId;
187
+ }
188
+ catch (error) {
189
+ if (!this.targetDb.containsClass(elementProps.classFullName)) {
190
+ // replace standard insert error with something more helpful
191
+ const errorMessage = `Element class "${elementProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`;
192
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidName, errorMessage);
193
+ }
194
+ throw error; // throw original error
195
+ }
196
+ }
197
+ /** Update an existing Element in the target iModel from the specified ElementProps.
198
+ * @note A subclass may override this method to customize update behavior but should call `super.onUpdateElement`.
199
+ */
200
+ onUpdateElement(elementProps) {
201
+ if (!elementProps.id) {
202
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidId, "ElementId not provided");
203
+ }
204
+ this.targetDb.elements.updateElement(elementProps);
205
+ core_bentley_1.Logger.logInfo(loggerCategory, `Updated ${this.formatElementForLogger(elementProps)}`);
206
+ this.trackProgress();
207
+ if (this.options.simplifyElementGeometry) {
208
+ this.targetDb.nativeDb.simplifyElementGeometry({ id: elementProps.id, convertBReps: true });
209
+ core_bentley_1.Logger.logInfo(loggerCategory, `Simplified element geometry for ${this.formatElementForLogger(elementProps)}`);
210
+ }
211
+ }
212
+ /** Delete the specified Element (and all its children) from the target iModel.
213
+ * Will delete special elements like definition elements and subjects.
214
+ * @note A subclass may override this method to customize delete behavior but should call `super.onDeleteElement`.
215
+ */
216
+ onDeleteElement(elementId) {
217
+ (0, core_backend_1.deleteElementTree)(this.targetDb, elementId);
218
+ core_bentley_1.Logger.logInfo(loggerCategory, `Deleted element ${elementId} and its descendants`);
219
+ this.trackProgress();
220
+ }
221
+ /** Delete the specified Element from the target iModel. */
222
+ deleteElement(elementId) {
223
+ if (this.doNotUpdateElementIds.has(elementId)) {
224
+ core_bentley_1.Logger.logInfo(loggerCategory, `Do not delete target element ${elementId}`);
225
+ return;
226
+ }
227
+ this.onDeleteElement(elementId);
228
+ }
229
+ /** Delete the specified Model from the target iModel.
230
+ * @note A subclass may override this method to customize delete behavior but should call `super.onDeleteModel`.
231
+ */
232
+ onDeleteModel(modelId) {
233
+ this.targetDb.models.deleteModel(modelId);
234
+ core_bentley_1.Logger.logInfo(loggerCategory, `Deleted model ${modelId}`);
235
+ this.trackProgress();
236
+ }
237
+ /** Delete the specified Model from the target iModel. */
238
+ deleteModel(modelId) {
239
+ this.onDeleteModel(modelId);
240
+ }
241
+ /** Format an Element for the Logger. */
242
+ formatElementForLogger(elementProps) {
243
+ const namePiece = elementProps.code.value ? `${elementProps.code.value} ` : elementProps.userLabel ? `${elementProps.userLabel} ` : "";
244
+ return `${elementProps.classFullName} ${namePiece}[${elementProps.id}]`;
245
+ }
246
+ /** Import an ElementUniqueAspect into the target iModel. */
247
+ importElementUniqueAspect(aspectProps) {
248
+ const aspects = this.targetDb.elements.getAspects(aspectProps.element.id, aspectProps.classFullName);
249
+ if (aspects.length === 0) {
250
+ return this.onInsertElementAspect(aspectProps);
251
+ }
252
+ else if (hasEntityChanged(aspects[0], aspectProps)) {
253
+ aspectProps.id = aspects[0].id;
254
+ this.onUpdateElementAspect(aspectProps);
255
+ }
256
+ return aspects[0].id;
257
+ }
258
+ /** Import the collection of ElementMultiAspects into the target iModel.
259
+ * @param aspectPropsArray The ElementMultiAspects to import
260
+ * @param filterFunc Optional filter func that is used to exclude target ElementMultiAspects that were added during iModel transformation from the update detection logic.
261
+ * @note For insert vs. update reasons, it is important to process all ElementMultiAspects owned by an Element at once since we don't have aspect-specific provenance.
262
+ * @returns the array of ids of the resulting ElementMultiAspects, in the same order of the aspectPropsArray parameter
263
+ */
264
+ importElementMultiAspects(aspectPropsArray,
265
+ /** caller must use this to enforce any aspects added by IModelTransformer are not considered for update */
266
+ filterFunc = () => true) {
267
+ const result = new Array(aspectPropsArray.length).fill(undefined);
268
+ if (aspectPropsArray.length === 0) {
269
+ return result;
270
+ }
271
+ const elementId = aspectPropsArray[0].element.id;
272
+ // Determine the set of ElementMultiAspect classes to consider
273
+ const aspectClassFullNames = new Set();
274
+ aspectPropsArray.forEach((aspectsProps) => {
275
+ aspectClassFullNames.add(aspectsProps.classFullName);
276
+ });
277
+ // Handle ElementMultiAspects in groups by class
278
+ aspectClassFullNames.forEach((aspectClassFullName) => {
279
+ const proposedAspects = aspectPropsArray
280
+ .map((props, index) => ({ props, index }))
281
+ .filter(({ props }) => aspectClassFullName === props.classFullName);
282
+ const currentAspects = this.targetDb.elements
283
+ .getAspects(elementId, aspectClassFullName)
284
+ .map((props, index) => ({ props, index }))
285
+ .filter(({ props }) => filterFunc(props));
286
+ if (proposedAspects.length >= currentAspects.length) {
287
+ proposedAspects.forEach(({ props, index: resultIndex }, index) => {
288
+ let id;
289
+ if (index < currentAspects.length) {
290
+ id = currentAspects[index].props.id;
291
+ props.id = id;
292
+ if (hasEntityChanged(currentAspects[index].props, props)) {
293
+ this.onUpdateElementAspect(props);
294
+ }
295
+ id = props.id;
296
+ }
297
+ else {
298
+ id = this.onInsertElementAspect(props);
299
+ }
300
+ result[resultIndex] = id;
301
+ });
302
+ }
303
+ else {
304
+ currentAspects.forEach(({ props, index: resultIndex }, index) => {
305
+ let id;
306
+ if (index < proposedAspects.length) {
307
+ id = props.id;
308
+ proposedAspects[index].props.id = id;
309
+ if (hasEntityChanged(props, proposedAspects[index].props)) {
310
+ this.onUpdateElementAspect(proposedAspects[index].props);
311
+ }
312
+ result[resultIndex] = id;
313
+ }
314
+ else {
315
+ this.onDeleteElementAspect(props);
316
+ }
317
+ });
318
+ }
319
+ });
320
+ assert(result.every((r) => typeof r !== undefined));
321
+ return result;
322
+ }
323
+ /** Insert the ElementAspect into the target iModel.
324
+ * @note A subclass may override this method to customize insert behavior but should call `super.onInsertElementAspect`.
325
+ */
326
+ onInsertElementAspect(aspectProps) {
327
+ try {
328
+ const id = this.targetDb.elements.insertAspect(aspectProps);
329
+ core_bentley_1.Logger.logInfo(loggerCategory, `Inserted ${this.formatElementAspectForLogger(aspectProps)}`);
330
+ this.trackProgress();
331
+ return id;
332
+ }
333
+ catch (error) {
334
+ if (!this.targetDb.containsClass(aspectProps.classFullName)) {
335
+ // replace standard insert error with something more helpful
336
+ const errorMessage = `ElementAspect class "${aspectProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`;
337
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidName, errorMessage);
338
+ }
339
+ throw error; // throw original error
340
+ }
341
+ }
342
+ /** Update the ElementAspect within the target iModel.
343
+ * @note A subclass may override this method to customize update behavior but should call `super.onUpdateElementAspect`.
344
+ */
345
+ onUpdateElementAspect(aspectProps) {
346
+ this.targetDb.elements.updateAspect(aspectProps);
347
+ core_bentley_1.Logger.logInfo(loggerCategory, `Updated ${this.formatElementAspectForLogger(aspectProps)}`);
348
+ this.trackProgress();
349
+ }
350
+ /** Delete the specified ElementAspect from the target iModel.
351
+ * @note A subclass may override this method to customize delete behavior but should call `super.onDeleteElementAspect`.
352
+ */
353
+ onDeleteElementAspect(targetElementAspect) {
354
+ this.targetDb.elements.deleteAspect(targetElementAspect.id);
355
+ core_bentley_1.Logger.logInfo(loggerCategory, `Deleted ${this.formatElementAspectForLogger(targetElementAspect)}`);
356
+ this.trackProgress();
357
+ }
358
+ /** Format an ElementAspect for the Logger. */
359
+ formatElementAspectForLogger(elementAspectProps) {
360
+ return `${elementAspectProps.classFullName} elementId=[${elementAspectProps.element.id}]`;
361
+ }
362
+ /** Import the specified RelationshipProps (either as an insert or an update) into the target iModel.
363
+ * @returns The instance Id of the inserted or updated Relationship.
364
+ */
365
+ importRelationship(relationshipProps) {
366
+ if ((undefined === relationshipProps.sourceId) || !core_bentley_1.Id64.isValidId64(relationshipProps.sourceId)) {
367
+ core_bentley_1.Logger.logInfo(loggerCategory, `Ignoring ${relationshipProps.classFullName} instance because of invalid RelationshipProps.sourceId`);
368
+ return core_bentley_1.Id64.invalid;
369
+ }
370
+ if ((undefined === relationshipProps.targetId) || !core_bentley_1.Id64.isValidId64(relationshipProps.targetId)) {
371
+ core_bentley_1.Logger.logInfo(loggerCategory, `Ignoring ${relationshipProps.classFullName} instance because of invalid RelationshipProps.targetId`);
372
+ return core_bentley_1.Id64.invalid;
373
+ }
374
+ // check for an existing relationship
375
+ const relSourceAndTarget = { sourceId: relationshipProps.sourceId, targetId: relationshipProps.targetId };
376
+ const relationship = this.targetDb.relationships.tryGetInstance(relationshipProps.classFullName, relSourceAndTarget);
377
+ if (undefined !== relationship) { // if relationship found, update it
378
+ relationshipProps.id = relationship.id;
379
+ if (hasEntityChanged(relationship, relationshipProps)) {
380
+ this.onUpdateRelationship(relationshipProps);
381
+ }
382
+ return relationshipProps.id;
383
+ }
384
+ else {
385
+ return this.onInsertRelationship(relationshipProps);
386
+ }
387
+ }
388
+ /** Create a new Relationship from the specified RelationshipProps and insert it into the target iModel.
389
+ * @returns The instance Id of the newly inserted relationship.
390
+ * @note A subclass may override this method to customize insert behavior but should call `super.onInsertRelationship`.
391
+ */
392
+ onInsertRelationship(relationshipProps) {
393
+ try {
394
+ const targetRelInstanceId = this.targetDb.relationships.insertInstance(relationshipProps);
395
+ core_bentley_1.Logger.logInfo(loggerCategory, `Inserted ${this.formatRelationshipForLogger(relationshipProps)}`);
396
+ this.trackProgress();
397
+ return targetRelInstanceId;
398
+ }
399
+ catch (error) {
400
+ if (!this.targetDb.containsClass(relationshipProps.classFullName)) {
401
+ // replace standard insert error with something more helpful
402
+ const errorMessage = `Relationship class "${relationshipProps.classFullName}" not found in the target iModel. Was the latest version of the schema imported?`;
403
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidName, errorMessage);
404
+ }
405
+ throw error; // throw original error
406
+ }
407
+ }
408
+ /** Update an existing Relationship in the target iModel from the specified RelationshipProps.
409
+ * @note A subclass may override this method to customize update behavior but should call `super.onUpdateRelationship`.
410
+ */
411
+ onUpdateRelationship(relationshipProps) {
412
+ if (!relationshipProps.id) {
413
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.InvalidId, "Relationship instance Id not provided");
414
+ }
415
+ this.targetDb.relationships.updateInstance(relationshipProps);
416
+ core_bentley_1.Logger.logInfo(loggerCategory, `Updated ${this.formatRelationshipForLogger(relationshipProps)}`);
417
+ this.trackProgress();
418
+ }
419
+ /** Delete the specified Relationship from the target iModel. */
420
+ onDeleteRelationship(relationshipProps) {
421
+ this.targetDb.relationships.deleteInstance(relationshipProps);
422
+ core_bentley_1.Logger.logInfo(loggerCategory, `Deleted relationship ${this.formatRelationshipForLogger(relationshipProps)}`);
423
+ this.trackProgress();
424
+ }
425
+ /** Delete the specified Relationship from the target iModel. */
426
+ deleteRelationship(relationshipProps) {
427
+ this.onDeleteRelationship(relationshipProps);
428
+ }
429
+ /** Format a Relationship for the Logger. */
430
+ formatRelationshipForLogger(relProps) {
431
+ return `${relProps.classFullName} sourceId=[${relProps.sourceId}] targetId=[${relProps.targetId}]`;
432
+ }
433
+ /** Tracks incremental progress */
434
+ trackProgress() {
435
+ this._progressCounter++;
436
+ if (0 === (this._progressCounter % this.progressInterval)) {
437
+ this.onProgress();
438
+ }
439
+ }
440
+ /** This method is called when IModelImporter has made incremental progress based on the [[progressInterval]] setting.
441
+ * @note A subclass may override this method to report custom progress but should call `super.onProgress`.
442
+ */
443
+ onProgress() { }
444
+ /** Optionally compute the projectExtents for the target iModel depending on the options for this IModelImporter.
445
+ * @note This method is automatically called from [IModelTransformer.processChanges]($transformer) and [IModelTransformer.processAll]($transformer).
446
+ * @see [IModelDb.computeProjectExtents]($backend), [[autoExtendProjectExtents]]
447
+ */
448
+ computeProjectExtents() {
449
+ const computedProjectExtents = this.targetDb.computeProjectExtents({ reportExtentsWithOutliers: true, reportOutliers: true });
450
+ core_bentley_1.Logger.logInfo(loggerCategory, `Current projectExtents=${JSON.stringify(this.targetDb.projectExtents)}`);
451
+ core_bentley_1.Logger.logInfo(loggerCategory, `Computed projectExtents without outliers=${JSON.stringify(computedProjectExtents.extents)}`);
452
+ core_bentley_1.Logger.logInfo(loggerCategory, `Computed projectExtents with outliers=${JSON.stringify(computedProjectExtents.extentsWithOutliers)}`);
453
+ if (this.options.autoExtendProjectExtents) {
454
+ const excludeOutliers = typeof this.options.autoExtendProjectExtents === "object" ? this.options.autoExtendProjectExtents.excludeOutliers : false;
455
+ const newProjectExtents = excludeOutliers ? computedProjectExtents.extents : computedProjectExtents.extentsWithOutliers;
456
+ if (!newProjectExtents.isAlmostEqual(this.targetDb.projectExtents)) {
457
+ this.targetDb.updateProjectExtents(newProjectExtents);
458
+ core_bentley_1.Logger.logInfo(loggerCategory, `Updated projectExtents=${JSON.stringify(this.targetDb.projectExtents)}`);
459
+ }
460
+ if (!excludeOutliers && computedProjectExtents.outliers && computedProjectExtents.outliers.length > 0) {
461
+ core_bentley_1.Logger.logInfo(loggerCategory, `${computedProjectExtents.outliers.length} outliers detected within projectExtents`);
462
+ }
463
+ }
464
+ else {
465
+ if (!this.targetDb.projectExtents.containsRange(computedProjectExtents.extents)) {
466
+ core_bentley_1.Logger.logWarning(loggerCategory, "Current project extents may be too small");
467
+ }
468
+ if (computedProjectExtents.outliers && computedProjectExtents.outliers.length > 0) {
469
+ core_bentley_1.Logger.logInfo(loggerCategory, `${computedProjectExtents.outliers.length} outliers detected within projectExtents`);
470
+ }
471
+ }
472
+ }
473
+ /** Examine the geometry streams of every [GeometricElement3d]($backend) in the target iModel and apply the specified optimizations.
474
+ * @note This method is automatically called from [[IModelTransformer.processChanges]] and [[IModelTransformer.processAll]] if
475
+ * [[IModelTransformOptions.optimizeGeometry]] is defined.
476
+ */
477
+ optimizeGeometry(options) {
478
+ if (options.inlineUniqueGeometryParts) {
479
+ const result = this.targetDb.nativeDb.inlineGeometryPartReferences();
480
+ core_bentley_1.Logger.logInfo(loggerCategory, `Inlined ${result.numRefsInlined} references to ${result.numCandidateParts} geometry parts and deleted ${result.numPartsDeleted} parts.`);
481
+ }
482
+ }
483
+ /**
484
+ * You may override this to store arbitrary json state in a exporter state dump, useful for some resumptions
485
+ * @see [[IModelTransformer.saveStateToFile]]
486
+ */
487
+ getAdditionalStateJson() {
488
+ return {};
489
+ }
490
+ /**
491
+ * You may override this to load arbitrary json state in a transformer state dump, useful for some resumptions
492
+ * @see [[IModelTransformer.loadStateFromFile]]
493
+ */
494
+ loadAdditionalStateJson(_additionalState) { }
495
+ /**
496
+ * Reload our state from a JSON object
497
+ * Intended for [[IModelTransformer.resumeTransformation]]
498
+ * @internal
499
+ * You can load custom json from the importer save state for custom importers by overriding [[IModelImporter.loadAdditionalStateJson]]
500
+ */
501
+ loadStateFromJson(state) {
502
+ if (state.importerClass !== this.constructor.name)
503
+ throw Error("resuming from a differently named importer class, it is not necessarily valid to resume with a different importer class");
504
+ // ignore readonly since this runs right after construction in [[IModelTransformer.resumeTransformation]]
505
+ this.options = state.options;
506
+ if (this.targetDb.iModelId !== state.targetDbId)
507
+ throw Error("can only load importer state when the same target is reused");
508
+ // TODO: fix upstream, looks like a bad case for the linter rule when casting away readonly for this generic
509
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
510
+ this.doNotUpdateElementIds = core_bentley_1.CompressedId64Set.decompressSet(state.doNotUpdateElementIds);
511
+ this.loadAdditionalStateJson(state.additionalState);
512
+ }
513
+ /**
514
+ * Serialize state to a JSON object
515
+ * Intended for [[IModelTransformer.resumeTransformation]]
516
+ * @internal
517
+ * You can add custom json to the importer save state for custom importers by overriding [[IModelImporter.getAdditionalStateJson]]
518
+ */
519
+ saveStateToJson() {
520
+ return {
521
+ importerClass: this.constructor.name,
522
+ options: this.options,
523
+ targetDbId: this.targetDb.iModelId || this.targetDb.nativeDb.getFilePath(),
524
+ doNotUpdateElementIds: core_bentley_1.CompressedId64Set.compressSet(this.doNotUpdateElementIds),
525
+ additionalState: this.getAdditionalStateJson(),
526
+ };
527
+ }
528
+ }
529
+ exports.IModelImporter = IModelImporter;
530
+ IModelImporter._realityDataSourceLinkPartitionStaticId = "0xe";
531
+ /** Returns true if a change within an Entity is detected.
532
+ * @param entity The current persistent Entity.
533
+ * @param entityProps The new EntityProps to compare against
534
+ * @note This method should only be called if changeset information is not available.
535
+ * @internal
536
+ */
537
+ function hasEntityChanged(entity, entityProps, namesToIgnore) {
538
+ let changed = false;
539
+ entity.forEachProperty((propertyName, propertyMeta) => {
540
+ if (!changed) {
541
+ if (namesToIgnore && namesToIgnore.has(propertyName)) {
542
+ // skip
543
+ }
544
+ else if (core_common_1.PrimitiveTypeCode.Binary === propertyMeta.primitiveType) {
545
+ changed = hasBinaryValueChanged(entity.asAny[propertyName], entityProps[propertyName]);
546
+ }
547
+ else if (propertyMeta.isNavigation) {
548
+ changed = hasNavigationValueChanged(entity.asAny[propertyName], entityProps[propertyName]);
549
+ }
550
+ else {
551
+ changed = hasValueChanged(entity.asAny[propertyName], entityProps[propertyName]);
552
+ }
553
+ }
554
+ });
555
+ return changed;
556
+ }
557
+ exports.hasEntityChanged = hasEntityChanged;
558
+ /** Returns true if the specified binary values are different. */
559
+ function hasBinaryValueChanged(binaryProperty1, binaryProperty2) {
560
+ const jsonString1 = JSON.stringify(binaryProperty1, core_common_1.Base64EncodedString.replacer);
561
+ const jsonString2 = JSON.stringify(binaryProperty2, core_common_1.Base64EncodedString.replacer);
562
+ return jsonString1 !== jsonString2;
563
+ }
564
+ /** Returns true if the specified navigation property values are different. */
565
+ function hasNavigationValueChanged(navigationProperty1, navigationProperty2) {
566
+ const relatedElement1 = core_common_1.RelatedElement.fromJSON(navigationProperty1);
567
+ const relatedElement2 = core_common_1.RelatedElement.fromJSON(navigationProperty2);
568
+ const jsonString1 = JSON.stringify(relatedElement1);
569
+ const jsonString2 = JSON.stringify(relatedElement2);
570
+ return jsonString1 !== jsonString2;
571
+ }
572
+ /** Returns true if the specified navigation property values are different. */
573
+ function hasValueChanged(property1, property2) {
574
+ return JSON.stringify(property1) !== JSON.stringify(property2);
575
+ }
576
+ /** check if element props are a subcategory */
577
+ function isSubCategory(props) {
578
+ return props.classFullName === core_backend_1.SubCategory.classFullName;
579
+ }
580
+ /** check if element props are a subcategory without loading the element */
581
+ function isDefaultSubCategory(props) {
582
+ var _a;
583
+ if (props.id === undefined)
584
+ return false;
585
+ if (!core_bentley_1.Id64.isId64(props.id))
586
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, `subcategory had invalid id`);
587
+ if (((_a = props.parent) === null || _a === void 0 ? void 0 : _a.id) === undefined)
588
+ throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadElement, `subcategory with id ${props.id} had no parent`);
589
+ return props.id === core_backend_1.IModelDb.getDefaultSubCategoryId(props.parent.id);
590
+ }
591
+ //# sourceMappingURL=IModelImporter.js.map