@itwin/imodel-transformer 2.0.0-dev.33 → 2.0.0-dev.35
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.
- package/lib/cjs/ElementAspectCleanup.d.ts +16 -0
- package/lib/cjs/ElementAspectCleanup.d.ts.map +1 -0
- package/lib/cjs/ElementAspectCleanup.js +83 -0
- package/lib/cjs/ElementAspectCleanup.js.map +1 -0
- package/lib/cjs/ElementAspectExportCoordinator.d.ts +44 -0
- package/lib/cjs/ElementAspectExportCoordinator.d.ts.map +1 -0
- package/lib/cjs/ElementAspectExportCoordinator.js +148 -0
- package/lib/cjs/ElementAspectExportCoordinator.js.map +1 -0
- package/lib/cjs/ElementAspectExportProcessor.d.ts +55 -0
- package/lib/cjs/ElementAspectExportProcessor.d.ts.map +1 -0
- package/lib/cjs/ElementAspectExportProcessor.js +186 -0
- package/lib/cjs/ElementAspectExportProcessor.js.map +1 -0
- package/lib/cjs/IModelCloneContext.d.ts.map +1 -1
- package/lib/cjs/IModelCloneContext.js +2 -1
- package/lib/cjs/IModelCloneContext.js.map +1 -1
- package/lib/cjs/IModelExporter.d.ts +48 -10
- package/lib/cjs/IModelExporter.d.ts.map +1 -1
- package/lib/cjs/IModelExporter.js +368 -41
- package/lib/cjs/IModelExporter.js.map +1 -1
- package/lib/cjs/IModelImporter.d.ts +6 -0
- package/lib/cjs/IModelImporter.d.ts.map +1 -1
- package/lib/cjs/IModelImporter.js +9 -0
- package/lib/cjs/IModelImporter.js.map +1 -1
- package/lib/cjs/IModelTransformer.d.ts +2 -0
- package/lib/cjs/IModelTransformer.d.ts.map +1 -1
- package/lib/cjs/IModelTransformer.js +40 -19
- package/lib/cjs/IModelTransformer.js.map +1 -1
- package/lib/cjs/IModelTransformerError.d.ts +2 -0
- package/lib/cjs/IModelTransformerError.d.ts.map +1 -1
- package/lib/cjs/IModelTransformerError.js +2 -0
- package/lib/cjs/IModelTransformerError.js.map +1 -1
- package/lib/cjs/imodel-transformer.d.ts +9 -1
- package/lib/cjs/imodel-transformer.d.ts.map +1 -1
- package/lib/cjs/imodel-transformer.js +9 -1
- package/lib/cjs/imodel-transformer.js.map +1 -1
- package/package.json +1 -1
- package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts +0 -16
- package/lib/cjs/DetachedExportElementAspectsStrategy.d.ts.map +0 -1
- package/lib/cjs/DetachedExportElementAspectsStrategy.js +0 -108
- package/lib/cjs/DetachedExportElementAspectsStrategy.js.map +0 -1
- package/lib/cjs/ExportElementAspectsStrategy.d.ts +0 -35
- package/lib/cjs/ExportElementAspectsStrategy.d.ts.map +0 -1
- package/lib/cjs/ExportElementAspectsStrategy.js +0 -52
- package/lib/cjs/ExportElementAspectsStrategy.js.map +0 -1
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts +0 -12
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.d.ts.map +0 -1
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.js +0 -48
- package/lib/cjs/ExportElementAspectsWithElementsStrategy.js.map +0 -1
|
@@ -14,9 +14,10 @@ const core_common_1 = require("@itwin/core-common");
|
|
|
14
14
|
const ecschema_metadata_1 = require("@itwin/ecschema-metadata");
|
|
15
15
|
const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
|
|
16
16
|
const node_assert_1 = require("node:assert");
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const ElementAspectExportProcessor_1 = require("./ElementAspectExportProcessor");
|
|
18
|
+
const ElementAspectExportCoordinator_1 = require("./ElementAspectExportCoordinator");
|
|
19
19
|
const IModelTransformerError_1 = require("./IModelTransformerError");
|
|
20
|
+
const ChangesetScanner_1 = require("./ChangesetScanner");
|
|
20
21
|
const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelExporter;
|
|
21
22
|
/** Handles the events generated by IModelExporter.
|
|
22
23
|
* @note Change information is available when `IModelExportHandler` methods are invoked via [IModelExporter.exportChanges]($transformer), but not available when invoked via [IModelExporter.exportAll]($transformer).
|
|
@@ -128,10 +129,14 @@ class IModelExportHandler {
|
|
|
128
129
|
exports.IModelExportHandler = IModelExportHandler;
|
|
129
130
|
/** Base class for exporting data from an iModel.
|
|
130
131
|
* @note Most uses cases will not require a custom subclass of `IModelExporter`. Instead, it is more typical to subclass/customize [IModelExportHandler]($transformer).
|
|
132
|
+
* ElementAspects are exported separately from element callbacks in bounded,
|
|
133
|
+
* owner-scoped groups. Aspect callbacks are not guaranteed to be adjacent to
|
|
134
|
+
* callbacks for their owners.
|
|
131
135
|
* @see [iModel Transformation and Data Exchange]($docs/learning/transformer/index.md), [[registerHandler]], [IModelTransformer]($transformer), [IModelImporter]($transformer)
|
|
132
136
|
* @beta
|
|
133
137
|
*/
|
|
134
138
|
class IModelExporter {
|
|
139
|
+
static _elementAspectOwnerBatchSize = 1000;
|
|
135
140
|
/** The read-only source iModel. */
|
|
136
141
|
sourceDb;
|
|
137
142
|
/** A flag that indicates whether element GeometryStreams are loaded or not.
|
|
@@ -197,20 +202,37 @@ class IModelExporter {
|
|
|
197
202
|
_excludedElementClasses = new Set();
|
|
198
203
|
/** The set of classes of Relationships that will be excluded (polymorphically) from transformation to the target iModel. */
|
|
199
204
|
_excludedRelationshipClasses = new Set();
|
|
200
|
-
/**
|
|
201
|
-
|
|
205
|
+
/** Exports ElementAspects for accepted owner groups. */
|
|
206
|
+
_elementAspectExportProcessor;
|
|
207
|
+
/** Coordinates accepted-owner scopes and bounded group processing. */
|
|
208
|
+
_elementAspectExportCoordinator;
|
|
209
|
+
/** Coordinates accepted ElementAspect owners and bounded group processing.
|
|
210
|
+
* @internal
|
|
211
|
+
*/
|
|
212
|
+
get elementAspectExportCoordinator() {
|
|
213
|
+
return this._elementAspectExportCoordinator;
|
|
214
|
+
}
|
|
215
|
+
/** Whether change traversal skips root-owned entities. */
|
|
216
|
+
_skipPropagateChangesToRootElements = false;
|
|
217
|
+
_rootElementIds = new Set([
|
|
218
|
+
core_common_1.IModel.rootSubjectId,
|
|
219
|
+
core_common_1.IModel.repositoryModelId,
|
|
220
|
+
core_common_1.IModel.dictionaryId,
|
|
221
|
+
"0xe", // The reality-data-sources modelId, skipped with root propagation.
|
|
222
|
+
]);
|
|
202
223
|
/** Construct a new IModelExporter
|
|
203
224
|
* @param sourceDb The source IModelDb
|
|
204
225
|
* @see registerHandler
|
|
205
226
|
*/
|
|
206
|
-
constructor(sourceDb
|
|
227
|
+
constructor(sourceDb) {
|
|
207
228
|
this.sourceDb = sourceDb;
|
|
208
|
-
this.
|
|
229
|
+
this._elementAspectExportProcessor = new ElementAspectExportProcessor_1.ElementAspectExportProcessor(this.sourceDb, {
|
|
209
230
|
onExportElementMultiAspects: async (aspects) => this.handler.onExportElementMultiAspects(aspects),
|
|
210
231
|
onExportElementUniqueAspect: async (aspect, isUpdate) => this.handler.onExportElementUniqueAspect(aspect, isUpdate),
|
|
211
232
|
shouldExportElementAspect: async (aspect) => this.handler.shouldExportElementAspect(aspect),
|
|
212
233
|
trackProgress: async () => this.trackProgress(),
|
|
213
234
|
});
|
|
235
|
+
this._elementAspectExportCoordinator = new ElementAspectExportCoordinator_1.ElementAspectExportCoordinator(IModelExporter._elementAspectOwnerBatchSize, () => this._elementAspectExportProcessor.excludedElementAspectClassFullNames, async (elementIds) => this._elementAspectExportProcessor.exportAllElementAspects(elementIds), () => this._elementAspectExportProcessor.resetCaches());
|
|
214
236
|
}
|
|
215
237
|
/**
|
|
216
238
|
* Initialize prerequisites of exporting. This is implicitly done by any `export*` calls that need initialization
|
|
@@ -228,7 +250,7 @@ class IModelExporter {
|
|
|
228
250
|
});
|
|
229
251
|
if (this._sourceDbChanges === undefined)
|
|
230
252
|
return;
|
|
231
|
-
this.
|
|
253
|
+
this._elementAspectExportProcessor.setAspectChanges(this._sourceDbChanges.aspect);
|
|
232
254
|
}
|
|
233
255
|
/** Register the handler that will be called by IModelExporter. */
|
|
234
256
|
registerHandler(handler) {
|
|
@@ -252,7 +274,7 @@ class IModelExporter {
|
|
|
252
274
|
}
|
|
253
275
|
/** Add a rule to exclude all ElementAspects of a specified class. */
|
|
254
276
|
excludeElementAspectClass(classFullName) {
|
|
255
|
-
this.
|
|
277
|
+
this._elementAspectExportProcessor.excludeElementAspectClass(classFullName);
|
|
256
278
|
}
|
|
257
279
|
/** Add a rule to exclude all Relationships of a specified class. */
|
|
258
280
|
excludeRelationshipClass(classFullName) {
|
|
@@ -265,12 +287,12 @@ class IModelExporter {
|
|
|
265
287
|
await this.initialize({});
|
|
266
288
|
await this.exportCodeSpecs();
|
|
267
289
|
await this.exportFonts();
|
|
268
|
-
await this.exportModel(core_common_1.IModel.repositoryModelId);
|
|
290
|
+
await this._elementAspectExportCoordinator.run(async () => this.exportModel(core_common_1.IModel.repositoryModelId));
|
|
269
291
|
await this.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
270
292
|
}
|
|
271
293
|
/** Export changes from the source iModel.
|
|
272
294
|
* Inserts, updates, and deletes are determined by inspecting the changeset(s).
|
|
273
|
-
* @throws [[ITwinError]]
|
|
295
|
+
* @throws [[ITwinError]] with scope `@itwin/imodel-transformer` and key `no-changesets` if the source iModel has no changesets and no custom changes. Call [[exportAll]] to export all content.
|
|
274
296
|
* @note To form a range of versions to process, set `startChangesetId` for the start (inclusive) of the desired
|
|
275
297
|
* range and open the source iModel as of the end (inclusive) of the desired range.
|
|
276
298
|
* @note the changedInstanceIds are just for this call to exportChanges, so you must continue to pass it in
|
|
@@ -307,22 +329,33 @@ class IModelExporter {
|
|
|
307
329
|
message: "Cannot export changes because the source iModel has no changesets or custom changes. Call exportAll() to export all content.",
|
|
308
330
|
});
|
|
309
331
|
}
|
|
332
|
+
this._skipPropagateChangesToRootElements =
|
|
333
|
+
initOpts.skipPropagateChangesToRootElements ?? false;
|
|
310
334
|
// _sourceDbChanges are initialized in this.initialize
|
|
311
335
|
(0, node_assert_1.strict)(this._sourceDbChanges !== undefined, "sourceDbChanges must be initialized.");
|
|
312
336
|
await this.exportCodeSpecs();
|
|
313
337
|
await this.exportFonts();
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
338
|
+
await this._elementAspectExportCoordinator.run(async () => {
|
|
339
|
+
if (initOpts.skipPropagateChangesToRootElements) {
|
|
340
|
+
// The root Subject is in the RepositoryModel. Traverse its children
|
|
341
|
+
// separately, then export other top-level repository elements while
|
|
342
|
+
// excluding the root so no element is visited twice.
|
|
343
|
+
await this.exportChildElements(core_common_1.IModel.rootSubjectId);
|
|
344
|
+
await this.exportModelContents(core_common_1.IModel.repositoryModelId, core_backend_1.Element.classFullName, true);
|
|
345
|
+
await this.exportSubModels(core_common_1.IModel.repositoryModelId);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
await this.exportModel(core_common_1.IModel.repositoryModelId);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
const aspectOnlyOwnerElementIds = new Set(this._sourceDbChanges.aspectOwnerElementIds);
|
|
352
|
+
for (const elementId of this._sourceDbChanges.element.insertIds) {
|
|
353
|
+
aspectOnlyOwnerElementIds.delete(elementId);
|
|
321
354
|
}
|
|
322
|
-
|
|
323
|
-
|
|
355
|
+
for (const elementId of this._sourceDbChanges.element.updateIds) {
|
|
356
|
+
aspectOnlyOwnerElementIds.delete(elementId);
|
|
324
357
|
}
|
|
325
|
-
await this.
|
|
358
|
+
await this.exportAspectsForOwners(await this.filterOwnerElementIdsForAspectExport(aspectOnlyOwnerElementIds));
|
|
326
359
|
await this.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
327
360
|
// handle deletes
|
|
328
361
|
if (this.visitElements) {
|
|
@@ -358,8 +391,10 @@ class IModelExporter {
|
|
|
358
391
|
// You can counteract the obvious impact of losing this expensive data by always calling
|
|
359
392
|
// exportChanges with the [[ExportChangesOptions.changedInstanceIds]] option set to
|
|
360
393
|
// whatever you want
|
|
361
|
-
if (this._resetChangeDataOnExport)
|
|
394
|
+
if (this._resetChangeDataOnExport) {
|
|
362
395
|
this._sourceDbChanges = undefined;
|
|
396
|
+
this._elementAspectExportProcessor.setAspectChanges(undefined);
|
|
397
|
+
}
|
|
363
398
|
}
|
|
364
399
|
_resetChangeDataOnExport = true;
|
|
365
400
|
/** Export schemas from the source iModel.
|
|
@@ -518,6 +553,9 @@ class IModelExporter {
|
|
|
518
553
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
519
554
|
*/
|
|
520
555
|
async exportModel(modeledElementId) {
|
|
556
|
+
return this.runScopedElementExport(async () => this.exportModelImpl(modeledElementId));
|
|
557
|
+
}
|
|
558
|
+
async exportModelImpl(modeledElementId) {
|
|
521
559
|
const model = this.sourceDb.models.getModel(modeledElementId);
|
|
522
560
|
if (model.isTemplate && !this.wantTemplateModels) {
|
|
523
561
|
return;
|
|
@@ -562,6 +600,9 @@ class IModelExporter {
|
|
|
562
600
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
563
601
|
*/
|
|
564
602
|
async exportModelContents(modelId, elementClassFullName = core_backend_1.Element.classFullName, skipRootSubject) {
|
|
603
|
+
return this.runScopedElementExport(async () => this.exportModelContentsImpl(modelId, elementClassFullName, skipRootSubject));
|
|
604
|
+
}
|
|
605
|
+
async exportModelContentsImpl(modelId, elementClassFullName, skipRootSubject) {
|
|
565
606
|
if (skipRootSubject) {
|
|
566
607
|
// NOTE: IModelTransformer.processAll should skip the root Subject since it is specific to the individual iModel and is not part of the changes that need to be synchronized
|
|
567
608
|
// NOTE: IModelExporter.exportAll should not skip the root Subject since the goal is to export everything
|
|
@@ -601,6 +642,9 @@ class IModelExporter {
|
|
|
601
642
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
602
643
|
*/
|
|
603
644
|
async exportSubModels(parentModelId) {
|
|
645
|
+
return this.runScopedElementExport(async () => this.exportSubModelsImpl(parentModelId));
|
|
646
|
+
}
|
|
647
|
+
async exportSubModelsImpl(parentModelId) {
|
|
604
648
|
core_bentley_1.Logger.logTrace(loggerCategory, `exportSubModels(${parentModelId})`);
|
|
605
649
|
const definitionModelIds = [];
|
|
606
650
|
const otherModelIds = [];
|
|
@@ -635,11 +679,10 @@ class IModelExporter {
|
|
|
635
679
|
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${element.id} by Id`);
|
|
636
680
|
return false;
|
|
637
681
|
}
|
|
638
|
-
if (element instanceof core_backend_1.GeometricElement
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
}
|
|
682
|
+
if (element instanceof core_backend_1.GeometricElement &&
|
|
683
|
+
this._excludedElementCategoryIds.has(element.category)) {
|
|
684
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${element.id} by Category`);
|
|
685
|
+
return false;
|
|
643
686
|
}
|
|
644
687
|
if (!this.wantTemplateModels &&
|
|
645
688
|
element instanceof core_backend_1.RecipeDefinitionElement) {
|
|
@@ -653,12 +696,19 @@ class IModelExporter {
|
|
|
653
696
|
}
|
|
654
697
|
}
|
|
655
698
|
// element has passed standard exclusion rules, now give handler a chance to accept/reject
|
|
656
|
-
|
|
699
|
+
if (!(await this.handler.shouldExportElement(element))) {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
return true;
|
|
657
703
|
}
|
|
658
|
-
/** Export the specified element
|
|
704
|
+
/** Export the specified element and its child elements (if applicable).
|
|
659
705
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
706
|
+
* @note ElementAspects are exported separately in owner-scoped groups, so their callbacks are not guaranteed to follow this element callback.
|
|
660
707
|
*/
|
|
661
708
|
async exportElement(elementId) {
|
|
709
|
+
return this.runScopedElementExport(async () => this.exportElementImpl(elementId));
|
|
710
|
+
}
|
|
711
|
+
async exportElementImpl(elementId) {
|
|
662
712
|
if (!this.visitElements) {
|
|
663
713
|
core_bentley_1.Logger.logTrace(loggerCategory, `visitElements=false, skipping exportElement(${elementId})`);
|
|
664
714
|
return;
|
|
@@ -690,7 +740,7 @@ class IModelExporter {
|
|
|
690
740
|
await this.handler.preExportElement(element);
|
|
691
741
|
await this.handler.onExportElement(element, isUpdate);
|
|
692
742
|
await this.trackProgress();
|
|
693
|
-
await this.
|
|
743
|
+
await this._elementAspectExportCoordinator.addAcceptedOwner(elementId);
|
|
694
744
|
return this.exportChildElements(elementId);
|
|
695
745
|
}
|
|
696
746
|
else {
|
|
@@ -701,6 +751,9 @@ class IModelExporter {
|
|
|
701
751
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
702
752
|
*/
|
|
703
753
|
async exportChildElements(elementId) {
|
|
754
|
+
return this.runScopedElementExport(async () => this.exportChildElementsImpl(elementId));
|
|
755
|
+
}
|
|
756
|
+
async exportChildElementsImpl(elementId) {
|
|
704
757
|
if (!this.visitElements) {
|
|
705
758
|
core_bentley_1.Logger.logTrace(loggerCategory, `visitElements=false, skipping exportChildElements(${elementId})`);
|
|
706
759
|
return;
|
|
@@ -713,10 +766,212 @@ class IModelExporter {
|
|
|
713
766
|
}
|
|
714
767
|
}
|
|
715
768
|
}
|
|
716
|
-
/** Exports all aspects
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
769
|
+
/** Exports all aspects owned by the supplied elements. */
|
|
770
|
+
async exportAspectsForOwners(ownerElementIds) {
|
|
771
|
+
return this._elementAspectExportCoordinator.exportOwners(ownerElementIds);
|
|
772
|
+
}
|
|
773
|
+
async filterOwnerElementIdsForAspectExport(ownerElementIds) {
|
|
774
|
+
if (ownerElementIds.size === 0)
|
|
775
|
+
return new Set();
|
|
776
|
+
const elementFacts = await this.queryAspectElementHierarchyFacts(ownerElementIds);
|
|
777
|
+
const modelFacts = await this.queryAspectModelHierarchyFacts(new Set([...elementFacts.values()].map((elementFact) => elementFact.modelId)));
|
|
778
|
+
await this.queryAspectElementFacts(new Set(modelFacts.keys()), elementFacts);
|
|
779
|
+
const elementIds = new Set();
|
|
780
|
+
for (const elementId of ownerElementIds) {
|
|
781
|
+
if (this._skipPropagateChangesToRootElements &&
|
|
782
|
+
this._rootElementIds.has(elementId)) {
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
if (await this.shouldExportElementHierarchyForAspect(elementId, elementFacts, modelFacts)) {
|
|
786
|
+
elementIds.add(elementId);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return elementIds;
|
|
790
|
+
}
|
|
791
|
+
async shouldExportElementHierarchyForAspect(elementId, elementFacts, modelFacts) {
|
|
792
|
+
let elementFact = elementFacts.get(elementId);
|
|
793
|
+
while (elementFact !== undefined) {
|
|
794
|
+
if (!(await this.shouldExportElementForAspect(elementFact.id, elementFact)))
|
|
795
|
+
return false;
|
|
796
|
+
if (!(await this.shouldExportModelForAspect(elementFact.modelId, elementFacts, modelFacts))) {
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
const parentId = elementFact.parentId;
|
|
800
|
+
if (parentId === undefined ||
|
|
801
|
+
(this._skipPropagateChangesToRootElements &&
|
|
802
|
+
parentId === core_common_1.IModel.rootSubjectId)) {
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
805
|
+
elementFact = elementFacts.get(parentId);
|
|
806
|
+
}
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
async shouldExportModelForAspect(modelId, elementFacts, modelFacts) {
|
|
810
|
+
const modelFact = modelFacts.get(modelId);
|
|
811
|
+
if (modelFact === undefined) {
|
|
812
|
+
this.sourceDb.models.getModel(modelId);
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
if (modelFact.isTemplate && !this.wantTemplateModels)
|
|
816
|
+
return false;
|
|
817
|
+
if (modelFact.id !== core_common_1.IModel.repositoryModelId &&
|
|
818
|
+
modelFact.id !== core_common_1.IModel.dictionaryId &&
|
|
819
|
+
modelFact.id !== "0xe" &&
|
|
820
|
+
!(await this.shouldExportElementForAspect(modelFact.id, elementFacts.get(modelFact.id)))) {
|
|
821
|
+
return false;
|
|
822
|
+
}
|
|
823
|
+
if (modelFact.parentModelId === undefined ||
|
|
824
|
+
modelFact.parentModelId === modelFact.id) {
|
|
825
|
+
return true;
|
|
826
|
+
}
|
|
827
|
+
return this.shouldExportModelForAspect(modelFact.parentModelId, elementFacts, modelFacts);
|
|
828
|
+
}
|
|
829
|
+
async runScopedElementExport(exportElements) {
|
|
830
|
+
await this._elementAspectExportCoordinator.run(exportElements);
|
|
831
|
+
}
|
|
832
|
+
/** Apply the element export filter when deciding whether to process an aspect owner. @internal */
|
|
833
|
+
async shouldExportElementForAspect(elementId, elementFact) {
|
|
834
|
+
if (!this.visitElements)
|
|
835
|
+
return false;
|
|
836
|
+
if (this.shouldExportElement ===
|
|
837
|
+
IModelExporter.prototype.shouldExportElement &&
|
|
838
|
+
elementFact !== undefined &&
|
|
839
|
+
!this.shouldExportElementFact(elementFact)) {
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
const element = this.sourceDb.elements.getElement({
|
|
843
|
+
id: elementId,
|
|
844
|
+
wantGeometry: false,
|
|
845
|
+
wantBRepData: false,
|
|
846
|
+
});
|
|
847
|
+
return this.shouldExportElement(element);
|
|
848
|
+
}
|
|
849
|
+
shouldExportElementFact(elementFact) {
|
|
850
|
+
if (this._excludedElementIds.has(elementFact.id)) {
|
|
851
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${elementFact.id} by Id`);
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
const elementClass = this.sourceDb.getJsClass(elementFact.classFullName);
|
|
855
|
+
if ((elementFact.classFullName === core_backend_1.GeometricElement.classFullName ||
|
|
856
|
+
elementClass.prototype instanceof core_backend_1.GeometricElement) &&
|
|
857
|
+
elementFact.categoryId !== undefined &&
|
|
858
|
+
this._excludedElementCategoryIds.has(elementFact.categoryId)) {
|
|
859
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${elementFact.id} by Category`);
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
if (!this.wantTemplateModels &&
|
|
863
|
+
(elementFact.classFullName === core_backend_1.RecipeDefinitionElement.classFullName ||
|
|
864
|
+
elementClass.prototype instanceof core_backend_1.RecipeDefinitionElement)) {
|
|
865
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded RecipeDefinitionElement ${elementFact.id} because wantTemplate=false`);
|
|
866
|
+
return false;
|
|
867
|
+
}
|
|
868
|
+
for (const excludedElementClass of this._excludedElementClasses) {
|
|
869
|
+
if (elementFact.classFullName === excludedElementClass.classFullName ||
|
|
870
|
+
elementClass.prototype instanceof excludedElementClass) {
|
|
871
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${elementFact.id} by class: ${excludedElementClass.classFullName}`);
|
|
872
|
+
return false;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
async queryAspectElementHierarchyFacts(elementIds) {
|
|
878
|
+
const elementFacts = new Map();
|
|
879
|
+
const queriedElementIds = new Set();
|
|
880
|
+
let pendingElementIds = new Set(elementIds);
|
|
881
|
+
while (pendingElementIds.size > 0) {
|
|
882
|
+
const currentElementIds = pendingElementIds;
|
|
883
|
+
pendingElementIds = new Set();
|
|
884
|
+
await this.queryAspectElementFacts(currentElementIds, elementFacts);
|
|
885
|
+
for (const elementId of currentElementIds)
|
|
886
|
+
queriedElementIds.add(elementId);
|
|
887
|
+
for (const elementId of currentElementIds) {
|
|
888
|
+
const parentId = elementFacts.get(elementId)?.parentId;
|
|
889
|
+
if (parentId !== undefined &&
|
|
890
|
+
!queriedElementIds.has(parentId) &&
|
|
891
|
+
!elementFacts.has(parentId)) {
|
|
892
|
+
pendingElementIds.add(parentId);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return elementFacts;
|
|
897
|
+
}
|
|
898
|
+
async queryAspectElementFacts(elementIds, elementFacts) {
|
|
899
|
+
const unqueriedElementIds = new Set([...elementIds].filter((elementId) => !elementFacts.has(elementId)));
|
|
900
|
+
if (unqueriedElementIds.size === 0)
|
|
901
|
+
return;
|
|
902
|
+
for (const elementIdBatch of this.getAspectOwnerIdBatches(unqueriedElementIds)) {
|
|
903
|
+
const queryParams = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIdBatch);
|
|
904
|
+
for await (const row of this.sourceDb.createQueryReader(`SELECT e.ECInstanceId id, e.Parent.Id parentId, e.Model.Id modelId,
|
|
905
|
+
ec_className(e.ECClassId, 's') schemaName,
|
|
906
|
+
ec_className(e.ECClassId, 'c') className
|
|
907
|
+
FROM bis.Element e
|
|
908
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = e.ECInstanceId
|
|
909
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`, queryParams, { usePrimaryConn: true })) {
|
|
910
|
+
elementFacts.set(row.id, {
|
|
911
|
+
id: row.id,
|
|
912
|
+
parentId: row.parentId ?? undefined,
|
|
913
|
+
modelId: row.modelId,
|
|
914
|
+
classFullName: `${row.schemaName}:${row.className}`,
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
if (this._excludedElementCategoryIds.size > 0) {
|
|
918
|
+
const categoryQueryParams = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIdBatch);
|
|
919
|
+
for await (const row of this.sourceDb.createQueryReader(`SELECT g.ECInstanceId id, g.Category.Id categoryId
|
|
920
|
+
FROM bis.GeometricElement g
|
|
921
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = g.ECInstanceId
|
|
922
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`, categoryQueryParams, { usePrimaryConn: true })) {
|
|
923
|
+
const elementFact = elementFacts.get(row.id);
|
|
924
|
+
if (elementFact !== undefined) {
|
|
925
|
+
elementFact.categoryId = row.categoryId;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
async queryAspectModelHierarchyFacts(modelIds) {
|
|
932
|
+
const modelFacts = new Map();
|
|
933
|
+
const queriedModelIds = new Set();
|
|
934
|
+
let pendingModelIds = new Set(modelIds);
|
|
935
|
+
while (pendingModelIds.size > 0) {
|
|
936
|
+
const currentModelIds = pendingModelIds;
|
|
937
|
+
pendingModelIds = new Set();
|
|
938
|
+
for (const modelIdBatch of this.getAspectOwnerIdBatches(currentModelIds)) {
|
|
939
|
+
const queryParams = new core_common_1.QueryBinder().bindIdSet("modelIds", modelIdBatch);
|
|
940
|
+
for await (const row of this.sourceDb.createQueryReader(`SELECT model.ECInstanceId id, model.ParentModel.Id parentModelId, model.IsTemplate isTemplate
|
|
941
|
+
FROM bis.Model model
|
|
942
|
+
INNER JOIN IdSet(:modelIds) ids ON ids.id = model.ECInstanceId
|
|
943
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`, queryParams, { usePrimaryConn: true })) {
|
|
944
|
+
modelFacts.set(row.id, {
|
|
945
|
+
id: row.id,
|
|
946
|
+
parentModelId: row.parentModelId ?? undefined,
|
|
947
|
+
isTemplate: row.isTemplate === true || row.isTemplate === 1,
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
for (const modelId of currentModelIds)
|
|
952
|
+
queriedModelIds.add(modelId);
|
|
953
|
+
for (const modelId of currentModelIds) {
|
|
954
|
+
const parentModelId = modelFacts.get(modelId)?.parentModelId;
|
|
955
|
+
if (parentModelId !== undefined &&
|
|
956
|
+
!queriedModelIds.has(parentModelId) &&
|
|
957
|
+
!modelFacts.has(parentModelId)) {
|
|
958
|
+
pendingModelIds.add(parentModelId);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return modelFacts;
|
|
963
|
+
}
|
|
964
|
+
*getAspectOwnerIdBatches(ids) {
|
|
965
|
+
let batch = new Set();
|
|
966
|
+
for (const id of ids) {
|
|
967
|
+
batch.add(id);
|
|
968
|
+
if (batch.size === IModelExporter._elementAspectOwnerBatchSize) {
|
|
969
|
+
yield batch;
|
|
970
|
+
batch = new Set();
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (batch.size > 0)
|
|
974
|
+
yield batch;
|
|
720
975
|
}
|
|
721
976
|
/** Exports all relationships that subclass from the specified base class.
|
|
722
977
|
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
@@ -829,6 +1084,13 @@ class ChangedInstanceIds {
|
|
|
829
1084
|
_aspectSubclassIds;
|
|
830
1085
|
_relationshipSubclassIds;
|
|
831
1086
|
_relationshipSubclassIdsToSkip;
|
|
1087
|
+
_aspectOwnerElementIds = new Set();
|
|
1088
|
+
/** Element IDs that own the aspects represented by `aspect` changes.
|
|
1089
|
+
* @internal
|
|
1090
|
+
*/
|
|
1091
|
+
get aspectOwnerElementIds() {
|
|
1092
|
+
return this._aspectOwnerElementIds;
|
|
1093
|
+
}
|
|
832
1094
|
_db;
|
|
833
1095
|
constructor(db) {
|
|
834
1096
|
this._db = db;
|
|
@@ -923,13 +1185,31 @@ class ChangedInstanceIds {
|
|
|
923
1185
|
this.handleChange(this.relationship, changeType, change.ECInstanceId);
|
|
924
1186
|
else if (this.isCodeSpec(ecClassId))
|
|
925
1187
|
this.handleChange(this.codeSpec, changeType, change.ECInstanceId);
|
|
926
|
-
else if (this.isAspect(ecClassId))
|
|
1188
|
+
else if (this.isAspect(ecClassId)) {
|
|
1189
|
+
const ownerElementId = change.Element?.Id ??
|
|
1190
|
+
this.tryGetAspectOwnerElementId(change.ECInstanceId);
|
|
1191
|
+
if (ownerElementId !== undefined) {
|
|
1192
|
+
this._aspectOwnerElementIds.add(ownerElementId);
|
|
1193
|
+
}
|
|
927
1194
|
this.handleChange(this.aspect, changeType, change.ECInstanceId);
|
|
1195
|
+
}
|
|
928
1196
|
else if (this.isModel(ecClassId))
|
|
929
1197
|
this.handleChange(this.model, changeType, change.ECInstanceId);
|
|
930
1198
|
else if (this.isElement(ecClassId))
|
|
931
1199
|
this.handleChange(this.element, changeType, change.ECInstanceId);
|
|
932
1200
|
}
|
|
1201
|
+
tryGetAspectOwnerElementId(aspectId) {
|
|
1202
|
+
try {
|
|
1203
|
+
return this._db.elements.getAspect(aspectId).element.id;
|
|
1204
|
+
}
|
|
1205
|
+
catch (error) {
|
|
1206
|
+
if (error instanceof core_common_1.IModelError &&
|
|
1207
|
+
error.errorNumber === core_bentley_1.IModelStatus.NotFound) {
|
|
1208
|
+
return undefined;
|
|
1209
|
+
}
|
|
1210
|
+
throw error;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
933
1213
|
/**
|
|
934
1214
|
* This method should only be called inside [[IModelTransformer.addCustomChanges]].
|
|
935
1215
|
* It adds the provided change to the element changes maintained by this instance of ChangedInstanceIds.
|
|
@@ -991,14 +1271,47 @@ class ChangedInstanceIds {
|
|
|
991
1271
|
* If the same ECInstanceId is seen multiple times, the changedInstanceIds will be modified accordingly, i.e. if an id 'x' was updated but now we see 'x' was deleted, we will remove 'x'
|
|
992
1272
|
* from the set of updatedIds and add it to the set of deletedIds for the appropriate class type.
|
|
993
1273
|
* @note It is the responsibility of the caller to ensure that the provided id is, in fact an aspect.
|
|
1274
|
+
* @param elementIds Owning element IDs. Required when `changeType` is
|
|
1275
|
+
* `Deleted` or the source aspect row is unavailable.
|
|
994
1276
|
* @note In most cases, this method does not need to be called. Its only for consumers to mimic changes as if they were found in a changeset, which should only be useful in certain cases such as the changing of filter criteria for a preexisting master branch relationship.
|
|
995
1277
|
* @beta
|
|
996
1278
|
*/
|
|
997
|
-
addCustomAspectChange(changeType, ids) {
|
|
1279
|
+
addCustomAspectChange(changeType, ids, elementIds) {
|
|
1280
|
+
if (elementIds !== undefined) {
|
|
1281
|
+
for (const elementId of core_bentley_1.Id64.iterable(elementIds)) {
|
|
1282
|
+
this._aspectOwnerElementIds.add(elementId);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
998
1285
|
for (const id of core_bentley_1.Id64.iterable(ids)) {
|
|
1286
|
+
if (elementIds === undefined && changeType === "Deleted") {
|
|
1287
|
+
core_bentley_1.ITwinError.throwError({
|
|
1288
|
+
iTwinErrorId: {
|
|
1289
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1290
|
+
key: IModelTransformerError_1.IModelTransformerError.AspectOwnerRequired,
|
|
1291
|
+
},
|
|
1292
|
+
message: "Custom deleted ElementAspect changes require the owning element ID.",
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
if (elementIds === undefined && changeType !== "Deleted") {
|
|
1296
|
+
const ownerElementId = this.tryGetAspectOwnerElementId(id);
|
|
1297
|
+
if (ownerElementId === undefined) {
|
|
1298
|
+
core_bentley_1.ITwinError.throwError({
|
|
1299
|
+
iTwinErrorId: {
|
|
1300
|
+
scope: IModelTransformerError_1.IModelTransformerErrorScope,
|
|
1301
|
+
key: IModelTransformerError_1.IModelTransformerError.AspectOwnerRequired,
|
|
1302
|
+
},
|
|
1303
|
+
message: "Custom ElementAspect changes require the owning element ID when the source aspect is unavailable.",
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
this._aspectOwnerElementIds.add(ownerElementId);
|
|
1307
|
+
}
|
|
999
1308
|
this.handleChange(this.aspect, changeType, id);
|
|
1000
1309
|
}
|
|
1001
1310
|
}
|
|
1311
|
+
recordCustomAspectChange(changeType, aspectId, ownerElementId) {
|
|
1312
|
+
this._aspectOwnerElementIds.add(ownerElementId);
|
|
1313
|
+
this.handleChange(this.aspect, changeType, aspectId);
|
|
1314
|
+
}
|
|
1002
1315
|
/**
|
|
1003
1316
|
* There is an optimization in [IModelExporter.exportModelContents] which doesn't try to export elements within a model unless the model itself is marked as `Updated` or 'Inserted' in sourceDbChanges. This method is used in [[addCustomElementChange]] and [[addCustomModelChange]] to add the parent model hierarchy to the 'updatedIds' so that the custom element changes are exported.
|
|
1004
1317
|
* Transformer will insert 'Updated' model to target if it does not exist there already. To handle such case, modeled elements of parent models are also marked as updated. This is done, because model can not be inserted without it's modeled element.
|
|
@@ -1007,13 +1320,16 @@ class ChangedInstanceIds {
|
|
|
1007
1320
|
const params = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
1008
1321
|
const ecQuery = `
|
|
1009
1322
|
WITH RECURSIVE hierarchy (parentId) AS (
|
|
1010
|
-
SELECT Model.Id
|
|
1323
|
+
SELECT element.Model.Id
|
|
1324
|
+
FROM bis.Element element
|
|
1325
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = element.ECInstanceId
|
|
1011
1326
|
UNION
|
|
1012
1327
|
SELECT ParentModel.id
|
|
1013
1328
|
FROM bis.Model e
|
|
1014
1329
|
INNER JOIN hierarchy h ON h.parentId = e.ECInstanceId
|
|
1015
1330
|
)
|
|
1016
1331
|
SELECT parentId FROM hierarchy where parentId is not null
|
|
1332
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES
|
|
1017
1333
|
`;
|
|
1018
1334
|
const parentModelIds = new Set();
|
|
1019
1335
|
for await (const row of this._db.createQueryReader(ecQuery, params, {
|
|
@@ -1032,9 +1348,16 @@ class ChangedInstanceIds {
|
|
|
1032
1348
|
return parentModelIds;
|
|
1033
1349
|
}
|
|
1034
1350
|
async markElementRelationshipsAsInserted(relationshipClassName, elementIds) {
|
|
1035
|
-
const ecQuery = `SELECT ECInstanceId
|
|
1036
|
-
|
|
1037
|
-
|
|
1351
|
+
const ecQuery = `SELECT relationship.ECInstanceId
|
|
1352
|
+
FROM ${relationshipClassName} relationship
|
|
1353
|
+
INNER JOIN IdSet(:elementIds) ids
|
|
1354
|
+
ON ids.id = relationship.TargetECInstanceId
|
|
1355
|
+
UNION
|
|
1356
|
+
SELECT relationship.ECInstanceId
|
|
1357
|
+
FROM ${relationshipClassName} relationship
|
|
1358
|
+
INNER JOIN IdSet(:elementIds) ids
|
|
1359
|
+
ON ids.id = relationship.SourceECInstanceId
|
|
1360
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`;
|
|
1038
1361
|
const queryBinder = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
1039
1362
|
const queryReader = this._db.createQueryReader(ecQuery, queryBinder, {
|
|
1040
1363
|
usePrimaryConn: true,
|
|
@@ -1048,13 +1371,17 @@ class ChangedInstanceIds {
|
|
|
1048
1371
|
core_backend_1.ElementUniqueAspect.classFullName,
|
|
1049
1372
|
core_backend_1.ElementMultiAspect.classFullName,
|
|
1050
1373
|
]) {
|
|
1051
|
-
const ecQuery = `
|
|
1374
|
+
const ecQuery = `SELECT aspect.ECInstanceId, aspect.Element.Id
|
|
1375
|
+
FROM ${aspectClassName} aspect
|
|
1376
|
+
INNER JOIN IdSet(:elementIds) ids ON ids.id = aspect.Element.Id
|
|
1377
|
+
OPTIONS ENABLE_EXPERIMENTAL_FEATURES`;
|
|
1052
1378
|
const queryBinder = new core_common_1.QueryBinder().bindIdSet("elementIds", elementIds);
|
|
1053
1379
|
const queryReader = this._db.createQueryReader(ecQuery, queryBinder, {
|
|
1054
1380
|
usePrimaryConn: true,
|
|
1055
1381
|
});
|
|
1056
1382
|
for await (const row of queryReader) {
|
|
1057
|
-
|
|
1383
|
+
const [aspectId, elementId] = row.toArray();
|
|
1384
|
+
this.recordCustomAspectChange("Inserted", aspectId, elementId);
|
|
1058
1385
|
}
|
|
1059
1386
|
}
|
|
1060
1387
|
}
|