@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.
- package/LICENSE.md +9 -0
- package/README.md +33 -0
- package/lib/cjs/ECReferenceTypesCache.d.ts +37 -0
- package/lib/cjs/ECReferenceTypesCache.d.ts.map +1 -0
- package/lib/cjs/ECReferenceTypesCache.js +180 -0
- package/lib/cjs/ECReferenceTypesCache.js.map +1 -0
- package/lib/cjs/EntityMap.d.ts +26 -0
- package/lib/cjs/EntityMap.d.ts.map +1 -0
- package/lib/cjs/EntityMap.js +55 -0
- package/lib/cjs/EntityMap.js.map +1 -0
- package/lib/cjs/EntityUnifier.d.ts +16 -0
- package/lib/cjs/EntityUnifier.d.ts.map +1 -0
- package/lib/cjs/EntityUnifier.js +72 -0
- package/lib/cjs/EntityUnifier.js.map +1 -0
- package/lib/cjs/IModelCloneContext.d.ts +32 -0
- package/lib/cjs/IModelCloneContext.d.ts.map +1 -0
- package/lib/cjs/IModelCloneContext.js +195 -0
- package/lib/cjs/IModelCloneContext.js.map +1 -0
- package/lib/cjs/IModelExporter.d.ts +353 -0
- package/lib/cjs/IModelExporter.d.ts.map +1 -0
- package/lib/cjs/IModelExporter.js +804 -0
- package/lib/cjs/IModelExporter.js.map +1 -0
- package/lib/cjs/IModelImporter.d.ts +230 -0
- package/lib/cjs/IModelImporter.d.ts.map +1 -0
- package/lib/cjs/IModelImporter.js +591 -0
- package/lib/cjs/IModelImporter.js.map +1 -0
- package/lib/cjs/IModelTransformer.d.ts +499 -0
- package/lib/cjs/IModelTransformer.d.ts.map +1 -0
- package/lib/cjs/IModelTransformer.js +1357 -0
- package/lib/cjs/IModelTransformer.js.map +1 -0
- package/lib/cjs/PendingReferenceMap.d.ts +35 -0
- package/lib/cjs/PendingReferenceMap.d.ts.map +1 -0
- package/lib/cjs/PendingReferenceMap.js +81 -0
- package/lib/cjs/PendingReferenceMap.js.map +1 -0
- package/lib/cjs/TransformerLoggerCategory.d.ts +23 -0
- package/lib/cjs/TransformerLoggerCategory.d.ts.map +1 -0
- package/lib/cjs/TransformerLoggerCategory.js +31 -0
- package/lib/cjs/TransformerLoggerCategory.js.map +1 -0
- package/lib/cjs/transformer.d.ts +25 -0
- package/lib/cjs/transformer.d.ts.map +1 -0
- package/lib/cjs/transformer.js +77 -0
- package/lib/cjs/transformer.js.map +1 -0
- package/package.json +120 -0
|
@@ -0,0 +1,804 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
|
|
4
|
+
* See LICENSE.md in the project root for license terms and full copyright notice.
|
|
5
|
+
*--------------------------------------------------------------------------------------------*/
|
|
6
|
+
/** @packageDocumentation
|
|
7
|
+
* @module iModels
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.ChangedInstanceIds = exports.ChangedInstanceOps = exports.IModelExporter = exports.IModelExportHandler = void 0;
|
|
11
|
+
const core_bentley_1 = require("@itwin/core-bentley");
|
|
12
|
+
const ecschema_metadata_1 = require("@itwin/ecschema-metadata");
|
|
13
|
+
const core_common_1 = require("@itwin/core-common");
|
|
14
|
+
const TransformerLoggerCategory_1 = require("./TransformerLoggerCategory");
|
|
15
|
+
const core_backend_1 = require("@itwin/core-backend");
|
|
16
|
+
const loggerCategory = TransformerLoggerCategory_1.TransformerLoggerCategory.IModelExporter;
|
|
17
|
+
/** Handles the events generated by IModelExporter.
|
|
18
|
+
* @note Change information is available when `IModelExportHandler` methods are invoked via [IModelExporter.exportChanges]($transformer), but not available when invoked via [IModelExporter.exportAll]($transformer).
|
|
19
|
+
* @note The handler is intended to be owned by (registered with) and called from the IModelExporter exclusively
|
|
20
|
+
* @see [iModel Transformation and Data Exchange]($docs/learning/transformer/index.md), [IModelExporter]($transformer)
|
|
21
|
+
* @beta
|
|
22
|
+
*/
|
|
23
|
+
class IModelExportHandler {
|
|
24
|
+
/** If `true` is returned, then the CodeSpec will be exported.
|
|
25
|
+
* @note This method can optionally be overridden to exclude an individual CodeSpec from the export. The base implementation always returns `true`.
|
|
26
|
+
*/
|
|
27
|
+
shouldExportCodeSpec(_codeSpec) { return true; }
|
|
28
|
+
/** Called when a CodeSpec should be exported.
|
|
29
|
+
* @param codeSpec The CodeSpec to export
|
|
30
|
+
* @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
|
|
31
|
+
* @note This should be overridden to actually do the export.
|
|
32
|
+
*/
|
|
33
|
+
onExportCodeSpec(_codeSpec, _isUpdate) { }
|
|
34
|
+
/** Called when a font should be exported.
|
|
35
|
+
* @param font The font to export
|
|
36
|
+
* @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
|
|
37
|
+
* @note This should be overridden to actually do the export.
|
|
38
|
+
*/
|
|
39
|
+
onExportFont(_font, _isUpdate) { }
|
|
40
|
+
/** Called when a model should be exported.
|
|
41
|
+
* @param model The model to export
|
|
42
|
+
* @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
|
|
43
|
+
* @note This should be overridden to actually do the export.
|
|
44
|
+
*/
|
|
45
|
+
onExportModel(_model, _isUpdate) { }
|
|
46
|
+
/** Called when a model should be deleted. */
|
|
47
|
+
onDeleteModel(_modelId) { }
|
|
48
|
+
/** If `true` is returned, then the element will be exported.
|
|
49
|
+
* @note This method can optionally be overridden to exclude an individual Element (and its children and ElementAspects) from the export. The base implementation always returns `true`.
|
|
50
|
+
*/
|
|
51
|
+
shouldExportElement(_element) { return true; }
|
|
52
|
+
/** Called when an element should be exported.
|
|
53
|
+
* @param element The element to export
|
|
54
|
+
* @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
|
|
55
|
+
* @note This should be overridden to actually do the export.
|
|
56
|
+
*/
|
|
57
|
+
onExportElement(_element, _isUpdate) { }
|
|
58
|
+
/**
|
|
59
|
+
* Do any asynchronous actions before exporting an element
|
|
60
|
+
* @note Do not implement this handler manually, it is internal, it will be removed.
|
|
61
|
+
* This will become a part of onExportElement once that becomes async
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
async preExportElement(_element) { }
|
|
65
|
+
/** Called when an element should be deleted. */
|
|
66
|
+
onDeleteElement(_elementId) { }
|
|
67
|
+
/** If `true` is returned, then the ElementAspect will be exported.
|
|
68
|
+
* @note This method can optionally be overridden to exclude an individual ElementAspect from the export. The base implementation always returns `true`.
|
|
69
|
+
*/
|
|
70
|
+
shouldExportElementAspect(_aspect) { return true; }
|
|
71
|
+
/** Called when an ElementUniqueAspect should be exported.
|
|
72
|
+
* @param aspect The ElementUniqueAspect to export
|
|
73
|
+
* @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
|
|
74
|
+
* @note This should be overridden to actually do the export.
|
|
75
|
+
*/
|
|
76
|
+
onExportElementUniqueAspect(_aspect, _isUpdate) { }
|
|
77
|
+
/** Called when ElementMultiAspects should be exported.
|
|
78
|
+
* @note This should be overridden to actually do the export.
|
|
79
|
+
*/
|
|
80
|
+
onExportElementMultiAspects(_aspects) { }
|
|
81
|
+
/** If `true` is returned, then the relationship will be exported.
|
|
82
|
+
* @note This method can optionally be overridden to exclude an individual CodeSpec from the export. The base implementation always returns `true`.
|
|
83
|
+
*/
|
|
84
|
+
shouldExportRelationship(_relationship) { return true; }
|
|
85
|
+
/** Called when a Relationship should be exported.
|
|
86
|
+
* @param relationship The Relationship to export
|
|
87
|
+
* @param isUpdate If defined, then `true` indicates an UPDATE operation while `false` indicates an INSERT operation. If not defined, then INSERT vs. UPDATE is not known.
|
|
88
|
+
* @note This should be overridden to actually do the export.
|
|
89
|
+
*/
|
|
90
|
+
onExportRelationship(_relationship, _isUpdate) { }
|
|
91
|
+
/** Called when a relationship should be deleted. */
|
|
92
|
+
onDeleteRelationship(_relInstanceId) { }
|
|
93
|
+
/** If `true` is returned, then the schema will be exported.
|
|
94
|
+
* @note This method can optionally be overridden to exclude an individual schema from the export. The base implementation always returns `true`.
|
|
95
|
+
*/
|
|
96
|
+
shouldExportSchema(_schemaKey) { return true; }
|
|
97
|
+
/** Called when a schema should be exported.
|
|
98
|
+
* @param schema The schema to export
|
|
99
|
+
* @note This should be overridden to actually do the export.
|
|
100
|
+
* @note return an [[ExportSchemaResult]] with a `schemaPath` property to notify overrides that call `super`
|
|
101
|
+
* where a schema was written for import.
|
|
102
|
+
*/
|
|
103
|
+
async onExportSchema(_schema) { }
|
|
104
|
+
/** This method is called when IModelExporter has made incremental progress based on the [[IModelExporter.progressInterval]] setting.
|
|
105
|
+
* This method is `async` to make it easier to integrate with asynchronous status and health reporting services.
|
|
106
|
+
* @note A subclass may override this method to report custom progress. The base implementation does nothing.
|
|
107
|
+
*/
|
|
108
|
+
async onProgress() { }
|
|
109
|
+
}
|
|
110
|
+
exports.IModelExportHandler = IModelExportHandler;
|
|
111
|
+
/** Base class for exporting data from an iModel.
|
|
112
|
+
* @note Most uses cases will not require a custom subclass of `IModelExporter`. Instead, it is more typical to subclass/customize [IModelExportHandler]($transformer).
|
|
113
|
+
* @see [iModel Transformation and Data Exchange]($docs/learning/transformer/index.md), [[registerHandler]], [IModelTransformer]($transformer), [IModelImporter]($transformer)
|
|
114
|
+
* @beta
|
|
115
|
+
*/
|
|
116
|
+
class IModelExporter {
|
|
117
|
+
/** Construct a new IModelExporter
|
|
118
|
+
* @param sourceDb The source IModelDb
|
|
119
|
+
* @see registerHandler
|
|
120
|
+
*/
|
|
121
|
+
constructor(sourceDb) {
|
|
122
|
+
/** A flag that indicates whether element GeometryStreams are loaded or not.
|
|
123
|
+
* @note As an optimization, exporters that don't need geometry can set this flag to `false`. The default is `true`.
|
|
124
|
+
* @note The transformer by default sets this to `false` as an optimization.
|
|
125
|
+
* @note This implies the `wantBRepData` option when loading elements.
|
|
126
|
+
* @see [ElementLoadProps.wantGeometry]($common)
|
|
127
|
+
*/
|
|
128
|
+
this.wantGeometry = true;
|
|
129
|
+
/** A flag that indicates whether template models should be exported or not. The default is `true`.
|
|
130
|
+
* @note If only exporting *instances* then template models can be skipped since they are just definitions that are cloned to create new instances.
|
|
131
|
+
* @see [Model.isTemplate]($backend)
|
|
132
|
+
*/
|
|
133
|
+
this.wantTemplateModels = true;
|
|
134
|
+
/** A flag that indicates whether *system* schemas should be exported or not. The default is `true` (previously false).
|
|
135
|
+
* This can be set to false for the legacy default behavior, but it may cause errors during schema processing in some cases.
|
|
136
|
+
* @see [[exportSchemas]]
|
|
137
|
+
*/
|
|
138
|
+
this.wantSystemSchemas = true;
|
|
139
|
+
/** A flag that determines whether this IModelExporter should visit Elements or not. The default is `true`.
|
|
140
|
+
* @note This flag is available as an optimization when the exporter doesn't need to visit elements, so can skip loading them.
|
|
141
|
+
*/
|
|
142
|
+
this.visitElements = true;
|
|
143
|
+
/** A flag that determines whether this IModelExporter should visit Relationships or not. The default is `true`.
|
|
144
|
+
* @note This flag is available as an optimization when the exporter doesn't need to visit relationships, so can skip loading them.
|
|
145
|
+
*/
|
|
146
|
+
this.visitRelationships = true;
|
|
147
|
+
/** The number of entities exported before incremental progress should be reported via the [[onProgress]] callback. */
|
|
148
|
+
this.progressInterval = 1000;
|
|
149
|
+
/** Tracks the current total number of entities exported. */
|
|
150
|
+
this._progressCounter = 0;
|
|
151
|
+
/** The set of CodeSpecs to exclude from the export. */
|
|
152
|
+
this._excludedCodeSpecNames = new Set();
|
|
153
|
+
/** The set of specific Elements to exclude from the export. */
|
|
154
|
+
this._excludedElementIds = new Set();
|
|
155
|
+
/** The set of Categories where Elements in that Category will be excluded from transformation to the target iModel. */
|
|
156
|
+
this._excludedElementCategoryIds = new Set();
|
|
157
|
+
/** The set of classes of Elements that will be excluded (polymorphically) from transformation to the target iModel. */
|
|
158
|
+
this._excludedElementClasses = new Set();
|
|
159
|
+
/** The set of classes of ElementAspects that will be excluded (polymorphically) from transformation to the target iModel. */
|
|
160
|
+
this._excludedElementAspectClasses = new Set();
|
|
161
|
+
/** The set of classFullNames for ElementAspects that will be excluded from transformation to the target iModel. */
|
|
162
|
+
this._excludedElementAspectClassFullNames = new Set();
|
|
163
|
+
/** The set of classes of Relationships that will be excluded (polymorphically) from transformation to the target iModel. */
|
|
164
|
+
this._excludedRelationshipClasses = new Set();
|
|
165
|
+
this._yieldManager = new core_bentley_1.YieldManager();
|
|
166
|
+
this.sourceDb = sourceDb;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Retrieve the cached entity change information.
|
|
170
|
+
* @note This will only be initialized after [IModelExporter.exportChanges] is invoked.
|
|
171
|
+
*/
|
|
172
|
+
get sourceDbChanges() {
|
|
173
|
+
return this._sourceDbChanges;
|
|
174
|
+
}
|
|
175
|
+
/** The handler called by this IModelExporter. */
|
|
176
|
+
get handler() {
|
|
177
|
+
if (undefined === this._handler) {
|
|
178
|
+
throw new Error("IModelExportHandler not registered");
|
|
179
|
+
}
|
|
180
|
+
return this._handler;
|
|
181
|
+
}
|
|
182
|
+
/** Register the handler that will be called by IModelExporter. */
|
|
183
|
+
registerHandler(handler) {
|
|
184
|
+
this._handler = handler;
|
|
185
|
+
}
|
|
186
|
+
/** Add a rule to exclude a CodeSpec */
|
|
187
|
+
excludeCodeSpec(codeSpecName) {
|
|
188
|
+
this._excludedCodeSpecNames.add(codeSpecName);
|
|
189
|
+
}
|
|
190
|
+
/** Add a rule to exclude a specific Element. */
|
|
191
|
+
excludeElement(elementId) {
|
|
192
|
+
this._excludedElementIds.add(elementId);
|
|
193
|
+
}
|
|
194
|
+
/** Add a rule to exclude all Elements in a specified Category. */
|
|
195
|
+
excludeElementsInCategory(categoryId) {
|
|
196
|
+
this._excludedElementCategoryIds.add(categoryId);
|
|
197
|
+
}
|
|
198
|
+
/** Add a rule to exclude all Elements of a specified class. */
|
|
199
|
+
excludeElementClass(classFullName) {
|
|
200
|
+
this._excludedElementClasses.add(this.sourceDb.getJsClass(classFullName));
|
|
201
|
+
}
|
|
202
|
+
/** Add a rule to exclude all ElementAspects of a specified class. */
|
|
203
|
+
excludeElementAspectClass(classFullName) {
|
|
204
|
+
this._excludedElementAspectClassFullNames.add(classFullName); // allows non-polymorphic exclusion before query
|
|
205
|
+
this._excludedElementAspectClasses.add(this.sourceDb.getJsClass(classFullName)); // allows polymorphic exclusion after query/load
|
|
206
|
+
}
|
|
207
|
+
/** Add a rule to exclude all Relationships of a specified class. */
|
|
208
|
+
excludeRelationshipClass(classFullName) {
|
|
209
|
+
this._excludedRelationshipClasses.add(this.sourceDb.getJsClass(classFullName));
|
|
210
|
+
}
|
|
211
|
+
/** Export all entity instance types from the source iModel.
|
|
212
|
+
* @note [[exportSchemas]] must be called separately.
|
|
213
|
+
*/
|
|
214
|
+
async exportAll() {
|
|
215
|
+
await this.exportCodeSpecs();
|
|
216
|
+
await this.exportFonts();
|
|
217
|
+
await this.exportModel(core_common_1.IModel.repositoryModelId);
|
|
218
|
+
await this.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
219
|
+
}
|
|
220
|
+
/** Export changes from the source iModel.
|
|
221
|
+
* @param user The user
|
|
222
|
+
* @param startChangesetId Include changes from this changeset up through and including the current changeset.
|
|
223
|
+
* If this parameter is not provided, then just the current changeset will be exported.
|
|
224
|
+
* @note To form a range of versions to export, set `startChangesetId` for the start (inclusive) of the desired range and open the source iModel as of the end (inclusive) of the desired range.
|
|
225
|
+
*/
|
|
226
|
+
async exportChanges(user, startChangesetId) {
|
|
227
|
+
if (!this.sourceDb.isBriefcaseDb()) {
|
|
228
|
+
throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "Must be a briefcase to export changes");
|
|
229
|
+
}
|
|
230
|
+
if ("" === this.sourceDb.changeset.id) {
|
|
231
|
+
await this.exportAll(); // no changesets, so revert to exportAll
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (undefined === startChangesetId) {
|
|
235
|
+
startChangesetId = this.sourceDb.changeset.id;
|
|
236
|
+
}
|
|
237
|
+
this._sourceDbChanges = await ChangedInstanceIds.initialize(user, this.sourceDb, startChangesetId);
|
|
238
|
+
await this.exportCodeSpecs();
|
|
239
|
+
await this.exportFonts();
|
|
240
|
+
await this.exportModelContents(core_common_1.IModel.repositoryModelId);
|
|
241
|
+
await this.exportSubModels(core_common_1.IModel.repositoryModelId);
|
|
242
|
+
await this.exportRelationships(core_backend_1.ElementRefersToElements.classFullName);
|
|
243
|
+
// handle deletes
|
|
244
|
+
if (this.visitElements) {
|
|
245
|
+
// must delete models first since they have a constraint on the submodeling element which may also be deleted
|
|
246
|
+
for (const modelId of this._sourceDbChanges.model.deleteIds) {
|
|
247
|
+
this.handler.onDeleteModel(modelId);
|
|
248
|
+
}
|
|
249
|
+
for (const elementId of this._sourceDbChanges.element.deleteIds) {
|
|
250
|
+
// We don't know how the handler wants to handle deletions, and we don't have enough information
|
|
251
|
+
// to know if deleted entities were related, so when processing changes, ignore errors from deletion.
|
|
252
|
+
// Technically, to keep the ignored error scope small, we ignore only the error of looking up a missing element,
|
|
253
|
+
// that approach works at least for the IModelTransformer.
|
|
254
|
+
// In the future, the handler may be responsible for doing the work of finding out which elements were cascade deleted,
|
|
255
|
+
// and returning them for the exporter to use to avoid double-deleting with error ignoring
|
|
256
|
+
try {
|
|
257
|
+
this.handler.onDeleteElement(elementId);
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
const isMissingErr = err instanceof core_common_1.IModelError && err.errorNumber === core_bentley_1.IModelStatus.NotFound;
|
|
261
|
+
if (!isMissingErr)
|
|
262
|
+
throw err;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (this.visitRelationships) {
|
|
267
|
+
for (const relInstanceId of this._sourceDbChanges.relationship.deleteIds) {
|
|
268
|
+
this.handler.onDeleteRelationship(relInstanceId);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/** Export schemas from the source iModel.
|
|
273
|
+
* @note This must be called separately from [[exportAll]] or [[exportChanges]].
|
|
274
|
+
*/
|
|
275
|
+
async exportSchemas() {
|
|
276
|
+
/* eslint-disable @typescript-eslint/indent */
|
|
277
|
+
const sql = `
|
|
278
|
+
SELECT s.Name, s.VersionMajor, s.VersionWrite, s.VersionMinor
|
|
279
|
+
FROM ECDbMeta.ECSchemaDef s
|
|
280
|
+
${this.wantSystemSchemas ? "" : `
|
|
281
|
+
WHERE ECInstanceId >= (SELECT ECInstanceId FROM ECDbMeta.ECSchemaDef WHERE Name='BisCore')
|
|
282
|
+
`}
|
|
283
|
+
ORDER BY ECInstanceId
|
|
284
|
+
`;
|
|
285
|
+
/* eslint-enable @typescript-eslint/indent */
|
|
286
|
+
const schemaNamesToExport = [];
|
|
287
|
+
this.sourceDb.withPreparedStatement(sql, (statement) => {
|
|
288
|
+
while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
|
|
289
|
+
const schemaName = statement.getValue(0).getString();
|
|
290
|
+
const versionMajor = statement.getValue(1).getInteger();
|
|
291
|
+
const versionWrite = statement.getValue(2).getInteger();
|
|
292
|
+
const versionMinor = statement.getValue(3).getInteger();
|
|
293
|
+
const schemaKey = new ecschema_metadata_1.SchemaKey(schemaName, new ecschema_metadata_1.ECVersion(versionMajor, versionWrite, versionMinor));
|
|
294
|
+
if (this.handler.shouldExportSchema(schemaKey)) {
|
|
295
|
+
schemaNamesToExport.push(schemaName);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
if (schemaNamesToExport.length === 0)
|
|
300
|
+
return;
|
|
301
|
+
const schemaLoader = new ecschema_metadata_1.SchemaLoader((name) => this.sourceDb.getSchemaProps(name));
|
|
302
|
+
await Promise.all(schemaNamesToExport.map(async (schemaName) => {
|
|
303
|
+
const schema = schemaLoader.getSchema(schemaName);
|
|
304
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportSchema(${schemaName})`);
|
|
305
|
+
return this.handler.onExportSchema(schema);
|
|
306
|
+
}));
|
|
307
|
+
}
|
|
308
|
+
/** For logging, indicate the change type if known. */
|
|
309
|
+
getChangeOpSuffix(isUpdate) {
|
|
310
|
+
return isUpdate ? " UPDATE" : undefined === isUpdate ? "" : " INSERT";
|
|
311
|
+
}
|
|
312
|
+
/** Export all CodeSpecs from the source iModel.
|
|
313
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
314
|
+
*/
|
|
315
|
+
async exportCodeSpecs() {
|
|
316
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportCodeSpecs()`);
|
|
317
|
+
const sql = `SELECT Name FROM BisCore:CodeSpec ORDER BY ECInstanceId`;
|
|
318
|
+
await this.sourceDb.withPreparedStatement(sql, async (statement) => {
|
|
319
|
+
while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
|
|
320
|
+
const codeSpecName = statement.getValue(0).getString();
|
|
321
|
+
await this.exportCodeSpecByName(codeSpecName);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/** Export a single CodeSpec from the source iModel.
|
|
326
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
327
|
+
*/
|
|
328
|
+
async exportCodeSpecByName(codeSpecName) {
|
|
329
|
+
const codeSpec = this.sourceDb.codeSpecs.getByName(codeSpecName);
|
|
330
|
+
let isUpdate;
|
|
331
|
+
if (undefined !== this._sourceDbChanges) { // is changeset information available?
|
|
332
|
+
if (this._sourceDbChanges.codeSpec.insertIds.has(codeSpec.id)) {
|
|
333
|
+
isUpdate = false;
|
|
334
|
+
}
|
|
335
|
+
else if (this._sourceDbChanges.codeSpec.updateIds.has(codeSpec.id)) {
|
|
336
|
+
isUpdate = true;
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
return; // not in changeset, don't export
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// passed changeset test, now apply standard exclusion rules
|
|
343
|
+
if (this._excludedCodeSpecNames.has(codeSpec.name)) {
|
|
344
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluding CodeSpec: ${codeSpec.name}`);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
// CodeSpec has passed standard exclusion rules, now give handler a chance to accept/reject export
|
|
348
|
+
if (this.handler.shouldExportCodeSpec(codeSpec)) {
|
|
349
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportCodeSpec(${codeSpecName})${this.getChangeOpSuffix(isUpdate)}`);
|
|
350
|
+
this.handler.onExportCodeSpec(codeSpec, isUpdate);
|
|
351
|
+
return this.trackProgress();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/** Export a single CodeSpec from the source iModel.
|
|
355
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
356
|
+
*/
|
|
357
|
+
async exportCodeSpecById(codeSpecId) {
|
|
358
|
+
const codeSpec = this.sourceDb.codeSpecs.getById(codeSpecId);
|
|
359
|
+
return this.exportCodeSpecByName(codeSpec.name);
|
|
360
|
+
}
|
|
361
|
+
/** Export all fonts from the source iModel.
|
|
362
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
363
|
+
*/
|
|
364
|
+
async exportFonts() {
|
|
365
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportFonts()`);
|
|
366
|
+
for (const font of this.sourceDb.fontMap.fonts.values()) {
|
|
367
|
+
await this.exportFontByNumber(font.id);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/** Export a single font from the source iModel.
|
|
371
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
372
|
+
*/
|
|
373
|
+
async exportFontByName(fontName) {
|
|
374
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportFontByName(${fontName})`);
|
|
375
|
+
const font = this.sourceDb.fontMap.getFont(fontName);
|
|
376
|
+
if (undefined !== font) {
|
|
377
|
+
await this.exportFontByNumber(font.id);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/** Export a single font from the source iModel.
|
|
381
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
382
|
+
*/
|
|
383
|
+
async exportFontByNumber(fontNumber) {
|
|
384
|
+
let isUpdate;
|
|
385
|
+
if (undefined !== this._sourceDbChanges) { // is changeset information available?
|
|
386
|
+
const fontId = core_bentley_1.Id64.fromUint32Pair(fontNumber, 0); // changeset information uses Id64String, not number
|
|
387
|
+
if (this._sourceDbChanges.font.insertIds.has(fontId)) {
|
|
388
|
+
isUpdate = false;
|
|
389
|
+
}
|
|
390
|
+
else if (this._sourceDbChanges.font.updateIds.has(fontId)) {
|
|
391
|
+
isUpdate = true;
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
return; // not in changeset, don't export
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportFontById(${fontNumber})`);
|
|
398
|
+
const font = this.sourceDb.fontMap.getFont(fontNumber);
|
|
399
|
+
if (undefined !== font) {
|
|
400
|
+
this.handler.onExportFont(font, isUpdate);
|
|
401
|
+
return this.trackProgress();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/** Export the model container, contents, and sub-models from the source iModel.
|
|
405
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
406
|
+
*/
|
|
407
|
+
async exportModel(modeledElementId) {
|
|
408
|
+
const model = this.sourceDb.models.getModel(modeledElementId);
|
|
409
|
+
if (model.isTemplate && !this.wantTemplateModels) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const modeledElement = this.sourceDb.elements.getElement({ id: modeledElementId, wantGeometry: this.wantGeometry, wantBRepData: this.wantGeometry });
|
|
413
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportModel(${modeledElementId})`);
|
|
414
|
+
if (this.shouldExportElement(modeledElement)) {
|
|
415
|
+
await this.exportModelContainer(model);
|
|
416
|
+
if (this.visitElements) {
|
|
417
|
+
await this.exportModelContents(modeledElementId);
|
|
418
|
+
}
|
|
419
|
+
await this.exportSubModels(modeledElementId);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/** Export the model (the container only) from the source iModel. */
|
|
423
|
+
async exportModelContainer(model) {
|
|
424
|
+
let isUpdate;
|
|
425
|
+
if (undefined !== this._sourceDbChanges) { // is changeset information available?
|
|
426
|
+
if (this._sourceDbChanges.model.insertIds.has(model.id)) {
|
|
427
|
+
isUpdate = false;
|
|
428
|
+
}
|
|
429
|
+
else if (this._sourceDbChanges.model.updateIds.has(model.id)) {
|
|
430
|
+
isUpdate = true;
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
return; // not in changeset, don't export
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
this.handler.onExportModel(model, isUpdate);
|
|
437
|
+
return this.trackProgress();
|
|
438
|
+
}
|
|
439
|
+
/** Export the model contents.
|
|
440
|
+
* @param modelId The only required parameter
|
|
441
|
+
* @param elementClassFullName Can be optionally specified if the goal is to export a subset of the model contents
|
|
442
|
+
* @param skipRootSubject Decides whether or not to export the root Subject. It is normally left undefined except for internal implementation purposes.
|
|
443
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
444
|
+
*/
|
|
445
|
+
async exportModelContents(modelId, elementClassFullName = core_backend_1.Element.classFullName, skipRootSubject) {
|
|
446
|
+
if (skipRootSubject) {
|
|
447
|
+
// 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
|
|
448
|
+
// NOTE: IModelExporter.exportAll should not skip the root Subject since the goal is to export everything
|
|
449
|
+
(0, core_bentley_1.assert)(modelId === core_common_1.IModel.repositoryModelId); // flag is only relevant when processing the RepositoryModel
|
|
450
|
+
}
|
|
451
|
+
if (!this.visitElements) {
|
|
452
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `visitElements=false, skipping exportModelContents(${modelId})`);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (undefined !== this._sourceDbChanges) { // is changeset information available?
|
|
456
|
+
if (!this._sourceDbChanges.model.insertIds.has(modelId) && !this._sourceDbChanges.model.updateIds.has(modelId)) {
|
|
457
|
+
return; // this optimization assumes that the Model changes (LastMod) any time an Element in the Model changes
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportModelContents(${modelId})`);
|
|
461
|
+
let sql;
|
|
462
|
+
if (skipRootSubject) {
|
|
463
|
+
sql = `SELECT ECInstanceId FROM ${elementClassFullName} WHERE Parent.Id IS NULL AND Model.Id=:modelId AND ECInstanceId!=:rootSubjectId ORDER BY ECInstanceId`;
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
sql = `SELECT ECInstanceId FROM ${elementClassFullName} WHERE Parent.Id IS NULL AND Model.Id=:modelId ORDER BY ECInstanceId`;
|
|
467
|
+
}
|
|
468
|
+
await this.sourceDb.withPreparedStatement(sql, async (statement) => {
|
|
469
|
+
statement.bindId("modelId", modelId);
|
|
470
|
+
if (skipRootSubject) {
|
|
471
|
+
statement.bindId("rootSubjectId", core_common_1.IModel.rootSubjectId);
|
|
472
|
+
}
|
|
473
|
+
while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
|
|
474
|
+
await this.exportElement(statement.getValue(0).getId());
|
|
475
|
+
await this._yieldManager.allowYield();
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
/** Export the sub-models directly below the specified model.
|
|
480
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
481
|
+
*/
|
|
482
|
+
async exportSubModels(parentModelId) {
|
|
483
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportSubModels(${parentModelId})`);
|
|
484
|
+
const definitionModelIds = [];
|
|
485
|
+
const otherModelIds = [];
|
|
486
|
+
const sql = `SELECT ECInstanceId FROM ${core_backend_1.Model.classFullName} WHERE ParentModel.Id=:parentModelId ORDER BY ECInstanceId`;
|
|
487
|
+
this.sourceDb.withPreparedStatement(sql, (statement) => {
|
|
488
|
+
statement.bindId("parentModelId", parentModelId);
|
|
489
|
+
while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
|
|
490
|
+
const modelId = statement.getValue(0).getId();
|
|
491
|
+
const model = this.sourceDb.models.getModel(modelId);
|
|
492
|
+
if (model instanceof core_backend_1.DefinitionModel) {
|
|
493
|
+
definitionModelIds.push(modelId);
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
otherModelIds.push(modelId);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
// export DefinitionModels before other types of Models
|
|
501
|
+
for (const definitionModelId of definitionModelIds) {
|
|
502
|
+
await this.exportModel(definitionModelId);
|
|
503
|
+
}
|
|
504
|
+
for (const otherModelId of otherModelIds) {
|
|
505
|
+
await this.exportModel(otherModelId);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/** Returns true if the specified element should be exported.
|
|
509
|
+
* This considers the standard IModelExporter exclusion rules plus calls [IModelExportHandler.shouldExportElement]($transformer) for any custom exclusion rules.
|
|
510
|
+
* @note This method is called from within [[exportChanges]] and [[exportAll]], so usually does not need to be called directly.
|
|
511
|
+
*/
|
|
512
|
+
shouldExportElement(element) {
|
|
513
|
+
if (this._excludedElementIds.has(element.id)) {
|
|
514
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${element.id} by Id`);
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
if (element instanceof core_backend_1.GeometricElement) {
|
|
518
|
+
if (this._excludedElementCategoryIds.has(element.category)) {
|
|
519
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${element.id} by Category`);
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (!this.wantTemplateModels && (element instanceof core_backend_1.RecipeDefinitionElement)) {
|
|
524
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded RecipeDefinitionElement ${element.id} because wantTemplate=false`);
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
for (const excludedElementClass of this._excludedElementClasses) {
|
|
528
|
+
if (element instanceof excludedElementClass) {
|
|
529
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded element ${element.id} by class: ${excludedElementClass.classFullName}`);
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// element has passed standard exclusion rules, now give handler a chance to accept/reject
|
|
534
|
+
return this.handler.shouldExportElement(element);
|
|
535
|
+
}
|
|
536
|
+
/** Export the specified element, its child elements (if applicable), and any owned ElementAspects.
|
|
537
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
538
|
+
*/
|
|
539
|
+
async exportElement(elementId) {
|
|
540
|
+
if (!this.visitElements) {
|
|
541
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `visitElements=false, skipping exportElement(${elementId})`);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
let isUpdate;
|
|
545
|
+
if (undefined !== this._sourceDbChanges) { // is changeset information available?
|
|
546
|
+
if (this._sourceDbChanges.element.insertIds.has(elementId)) {
|
|
547
|
+
isUpdate = false;
|
|
548
|
+
}
|
|
549
|
+
else if (this._sourceDbChanges.element.updateIds.has(elementId)) {
|
|
550
|
+
isUpdate = true;
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
// NOTE: This optimization assumes that the Element will change (LastMod) if an owned ElementAspect changes
|
|
554
|
+
// NOTE: However, child elements may have changed without the parent changing
|
|
555
|
+
return this.exportChildElements(elementId);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const element = this.sourceDb.elements.getElement({ id: elementId, wantGeometry: this.wantGeometry, wantBRepData: this.wantGeometry });
|
|
559
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportElement(${element.id}, "${element.getDisplayLabel()}")${this.getChangeOpSuffix(isUpdate)}`);
|
|
560
|
+
// the order and `await`ing of calls beyond here is depended upon by the IModelTransformer for a current bug workaround
|
|
561
|
+
if (this.shouldExportElement(element)) {
|
|
562
|
+
await this.handler.preExportElement(element);
|
|
563
|
+
this.handler.onExportElement(element, isUpdate);
|
|
564
|
+
await this.trackProgress();
|
|
565
|
+
await this.exportElementAspects(elementId);
|
|
566
|
+
return this.exportChildElements(elementId);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/** Export the child elements of the specified element from the source iModel.
|
|
570
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
571
|
+
*/
|
|
572
|
+
async exportChildElements(elementId) {
|
|
573
|
+
if (!this.visitElements) {
|
|
574
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `visitElements=false, skipping exportChildElements(${elementId})`);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const childElementIds = this.sourceDb.elements.queryChildren(elementId);
|
|
578
|
+
if (childElementIds.length > 0) {
|
|
579
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportChildElements(${elementId})`);
|
|
580
|
+
for (const childElementId of childElementIds) {
|
|
581
|
+
await this.exportElement(childElementId);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
/** Returns `true` if the specified ElementAspect should be exported or `false` if if should be excluded. */
|
|
586
|
+
shouldExportElementAspect(aspect) {
|
|
587
|
+
for (const excludedElementAspectClass of this._excludedElementAspectClasses) {
|
|
588
|
+
if (aspect instanceof excludedElementAspectClass) {
|
|
589
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded ElementAspect by class: ${aspect.classFullName}`);
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
// ElementAspect has passed standard exclusion rules, now give handler a chance to accept/reject
|
|
594
|
+
return this.handler.shouldExportElementAspect(aspect);
|
|
595
|
+
}
|
|
596
|
+
/** Export ElementAspects from the specified element from the source iModel. */
|
|
597
|
+
async exportElementAspects(elementId) {
|
|
598
|
+
const _uniqueAspects = await Promise.all(this.sourceDb.elements
|
|
599
|
+
._queryAspects(elementId, core_backend_1.ElementUniqueAspect.classFullName, this._excludedElementAspectClassFullNames)
|
|
600
|
+
.filter((a) => this.shouldExportElementAspect(a))
|
|
601
|
+
.map(async (uniqueAspect) => {
|
|
602
|
+
var _a, _b, _c, _d;
|
|
603
|
+
const isInsertChange = (_b = (_a = this._sourceDbChanges) === null || _a === void 0 ? void 0 : _a.aspect.insertIds.has(uniqueAspect.id)) !== null && _b !== void 0 ? _b : false;
|
|
604
|
+
const isUpdateChange = (_d = (_c = this._sourceDbChanges) === null || _c === void 0 ? void 0 : _c.aspect.updateIds.has(uniqueAspect.id)) !== null && _d !== void 0 ? _d : false;
|
|
605
|
+
const doExport = this._sourceDbChanges === undefined || isInsertChange || isUpdateChange;
|
|
606
|
+
if (doExport) {
|
|
607
|
+
const isKnownUpdate = this._sourceDbChanges ? isUpdateChange : undefined;
|
|
608
|
+
this.handler.onExportElementUniqueAspect(uniqueAspect, isKnownUpdate);
|
|
609
|
+
await this.trackProgress();
|
|
610
|
+
}
|
|
611
|
+
}));
|
|
612
|
+
const multiAspects = this.sourceDb.elements
|
|
613
|
+
._queryAspects(elementId, core_backend_1.ElementMultiAspect.classFullName, this._excludedElementAspectClassFullNames)
|
|
614
|
+
.filter((a) => this.shouldExportElementAspect(a));
|
|
615
|
+
if (multiAspects.length > 0) {
|
|
616
|
+
this.handler.onExportElementMultiAspects(multiAspects);
|
|
617
|
+
return this.trackProgress();
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/** Exports all relationships that subclass from the specified base class.
|
|
621
|
+
* @note This method is called from [[exportChanges]] and [[exportAll]], so it only needs to be called directly when exporting a subset of an iModel.
|
|
622
|
+
*/
|
|
623
|
+
async exportRelationships(baseRelClassFullName) {
|
|
624
|
+
if (!this.visitRelationships) {
|
|
625
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `visitRelationships=false, skipping exportRelationships()`);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportRelationships(${baseRelClassFullName})`);
|
|
629
|
+
const sql = `SELECT ECInstanceId FROM ${baseRelClassFullName}`;
|
|
630
|
+
await this.sourceDb.withPreparedStatement(sql, async (statement) => {
|
|
631
|
+
while (core_bentley_1.DbResult.BE_SQLITE_ROW === statement.step()) {
|
|
632
|
+
const relInstanceId = statement.getValue(0).getId();
|
|
633
|
+
const relProps = this.sourceDb.relationships.getInstanceProps(baseRelClassFullName, relInstanceId);
|
|
634
|
+
await this.exportRelationship(relProps.classFullName, relInstanceId); // must call exportRelationship using the actual classFullName, not baseRelClassFullName
|
|
635
|
+
await this._yieldManager.allowYield();
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
/** Export a relationship from the source iModel. */
|
|
640
|
+
async exportRelationship(relClassFullName, relInstanceId) {
|
|
641
|
+
if (!this.visitRelationships) {
|
|
642
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `visitRelationships=false, skipping exportRelationship(${relClassFullName}, ${relInstanceId})`);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
let isUpdate;
|
|
646
|
+
if (undefined !== this._sourceDbChanges) { // is changeset information available?
|
|
647
|
+
if (this._sourceDbChanges.relationship.insertIds.has(relInstanceId)) {
|
|
648
|
+
isUpdate = false;
|
|
649
|
+
}
|
|
650
|
+
else if (this._sourceDbChanges.relationship.updateIds.has(relInstanceId)) {
|
|
651
|
+
isUpdate = true;
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
return; // not in changeset, don't export
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
// passed changeset test, now apply standard exclusion rules
|
|
658
|
+
core_bentley_1.Logger.logTrace(loggerCategory, `exportRelationship(${relClassFullName}, ${relInstanceId})`);
|
|
659
|
+
const relationship = this.sourceDb.relationships.getInstance(relClassFullName, relInstanceId);
|
|
660
|
+
for (const excludedRelationshipClass of this._excludedRelationshipClasses) {
|
|
661
|
+
if (relationship instanceof excludedRelationshipClass) {
|
|
662
|
+
core_bentley_1.Logger.logInfo(loggerCategory, `Excluded relationship by class: ${excludedRelationshipClass.classFullName}`);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
// relationship has passed standard exclusion rules, now give handler a chance to accept/reject export
|
|
667
|
+
if (this.handler.shouldExportRelationship(relationship)) {
|
|
668
|
+
this.handler.onExportRelationship(relationship, isUpdate);
|
|
669
|
+
await this.trackProgress();
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
/** Tracks incremental progress */
|
|
673
|
+
async trackProgress() {
|
|
674
|
+
this._progressCounter++;
|
|
675
|
+
if (0 === (this._progressCounter % this.progressInterval)) {
|
|
676
|
+
return this.handler.onProgress();
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* You may override this to store arbitrary json state in a exporter state dump, useful for some resumptions
|
|
681
|
+
* @see [[IModelTransformer.saveStateToFile]]
|
|
682
|
+
*/
|
|
683
|
+
getAdditionalStateJson() {
|
|
684
|
+
return {};
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* You may override this to load arbitrary json state in a transformer state dump, useful for some resumptions
|
|
688
|
+
* @see [[IModelTransformer.loadStateFromFile]]
|
|
689
|
+
*/
|
|
690
|
+
loadAdditionalStateJson(_additionalState) { }
|
|
691
|
+
/**
|
|
692
|
+
* Reload our state from a JSON object
|
|
693
|
+
* Intended for [[IModelTransformer.resumeTransformation]]
|
|
694
|
+
* @internal
|
|
695
|
+
* You can load custom json from the exporter save state for custom exporters by overriding [[IModelExporter.loadAdditionalStateJson]]
|
|
696
|
+
*/
|
|
697
|
+
loadStateFromJson(state) {
|
|
698
|
+
if (state.exporterClass !== this.constructor.name)
|
|
699
|
+
throw Error("resuming from a differently named exporter class, it is not necessarily valid to resume with a different exporter class");
|
|
700
|
+
this.wantGeometry = state.wantGeometry;
|
|
701
|
+
this.wantTemplateModels = state.wantTemplateModels;
|
|
702
|
+
this.wantSystemSchemas = state.wantSystemSchemas;
|
|
703
|
+
this.visitElements = state.visitElements;
|
|
704
|
+
this.visitRelationships = state.visitRelationships;
|
|
705
|
+
this._excludedCodeSpecNames = new Set(state.excludedCodeSpecNames);
|
|
706
|
+
this._excludedElementIds = core_bentley_1.CompressedId64Set.decompressSet(state.excludedElementIds),
|
|
707
|
+
this._excludedElementCategoryIds = core_bentley_1.CompressedId64Set.decompressSet(state.excludedElementCategoryIds),
|
|
708
|
+
this._excludedElementClasses = new Set(state.excludedElementClassNames.map((c) => this.sourceDb.getJsClass(c)));
|
|
709
|
+
this._excludedElementAspectClassFullNames = new Set(state.excludedElementAspectClassFullNames);
|
|
710
|
+
this._excludedElementAspectClasses = new Set(state.excludedElementAspectClassFullNames.map((c) => this.sourceDb.getJsClass(c)));
|
|
711
|
+
this._excludedRelationshipClasses = new Set(state.excludedRelationshipClassNames.map((c) => this.sourceDb.getJsClass(c)));
|
|
712
|
+
this.loadAdditionalStateJson(state.additionalState);
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Serialize state to a JSON object
|
|
716
|
+
* Intended for [[IModelTransformer.resumeTransformation]]
|
|
717
|
+
* @internal
|
|
718
|
+
* You can add custom json to the exporter save state for custom exporters by overriding [[IModelExporter.getAdditionalStateJson]]
|
|
719
|
+
*/
|
|
720
|
+
saveStateToJson() {
|
|
721
|
+
return {
|
|
722
|
+
exporterClass: this.constructor.name,
|
|
723
|
+
wantGeometry: this.wantGeometry,
|
|
724
|
+
wantTemplateModels: this.wantTemplateModels,
|
|
725
|
+
wantSystemSchemas: this.wantSystemSchemas,
|
|
726
|
+
visitElements: this.visitElements,
|
|
727
|
+
visitRelationships: this.visitRelationships,
|
|
728
|
+
excludedCodeSpecNames: [...this._excludedCodeSpecNames],
|
|
729
|
+
excludedElementIds: core_bentley_1.CompressedId64Set.compressSet(this._excludedElementIds),
|
|
730
|
+
excludedElementCategoryIds: core_bentley_1.CompressedId64Set.compressSet(this._excludedElementCategoryIds),
|
|
731
|
+
excludedElementClassNames: Array.from(this._excludedElementClasses, (cls) => cls.classFullName),
|
|
732
|
+
excludedElementAspectClassFullNames: [...this._excludedElementAspectClassFullNames],
|
|
733
|
+
excludedRelationshipClassNames: Array.from(this._excludedRelationshipClasses, (cls) => cls.classFullName),
|
|
734
|
+
additionalState: this.getAdditionalStateJson(),
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
exports.IModelExporter = IModelExporter;
|
|
739
|
+
/** Class for holding change information.
|
|
740
|
+
* @beta
|
|
741
|
+
*/
|
|
742
|
+
class ChangedInstanceOps {
|
|
743
|
+
constructor() {
|
|
744
|
+
this.insertIds = new Set();
|
|
745
|
+
this.updateIds = new Set();
|
|
746
|
+
this.deleteIds = new Set();
|
|
747
|
+
}
|
|
748
|
+
/** Initializes the object from IModelJsNative.ChangedInstanceOpsProps. */
|
|
749
|
+
addFromJson(val) {
|
|
750
|
+
if (undefined !== val) {
|
|
751
|
+
if ((undefined !== val.insert) && (Array.isArray(val.insert)))
|
|
752
|
+
val.insert.forEach((id) => this.insertIds.add(id));
|
|
753
|
+
if ((undefined !== val.update) && (Array.isArray(val.update)))
|
|
754
|
+
val.update.forEach((id) => this.updateIds.add(id));
|
|
755
|
+
if ((undefined !== val.delete) && (Array.isArray(val.delete)))
|
|
756
|
+
val.delete.forEach((id) => this.deleteIds.add(id));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
exports.ChangedInstanceOps = ChangedInstanceOps;
|
|
761
|
+
/**
|
|
762
|
+
* Class for discovering modified elements between 2 versions of an iModel.
|
|
763
|
+
* @beta
|
|
764
|
+
*/
|
|
765
|
+
class ChangedInstanceIds {
|
|
766
|
+
constructor() {
|
|
767
|
+
this.codeSpec = new ChangedInstanceOps();
|
|
768
|
+
this.model = new ChangedInstanceOps();
|
|
769
|
+
this.element = new ChangedInstanceOps();
|
|
770
|
+
this.aspect = new ChangedInstanceOps();
|
|
771
|
+
this.relationship = new ChangedInstanceOps();
|
|
772
|
+
this.font = new ChangedInstanceOps();
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Initializes a new ChangedInstanceIds object with information taken from a range of changesets.
|
|
776
|
+
* @param accessToken Access token.
|
|
777
|
+
* @param iModel IModel briefcase whose changesets will be queried.
|
|
778
|
+
* @param firstChangesetId Changeset id.
|
|
779
|
+
* @note Modified element information will be taken from a range of changesets. First changeset in a range will be the 'firstChangesetId', the last will be whichever changeset the 'iModel' briefcase is currently opened on.
|
|
780
|
+
*/
|
|
781
|
+
static async initialize(accessToken, iModel, firstChangesetId) {
|
|
782
|
+
const iModelId = iModel.iModelId;
|
|
783
|
+
const first = (await core_backend_1.IModelHost.hubAccess.queryChangeset({ iModelId, changeset: { id: firstChangesetId }, accessToken })).index;
|
|
784
|
+
const end = (await core_backend_1.IModelHost.hubAccess.queryChangeset({ iModelId, changeset: { id: iModel.changeset.id }, accessToken })).index;
|
|
785
|
+
const changesets = await core_backend_1.IModelHost.hubAccess.downloadChangesets({ accessToken, iModelId, range: { first, end }, targetDir: core_backend_1.BriefcaseManager.getChangeSetsPath(iModelId) });
|
|
786
|
+
const changedInstanceIds = new ChangedInstanceIds();
|
|
787
|
+
const changesetFiles = changesets.map((c) => c.pathname);
|
|
788
|
+
const statusOrResult = iModel.nativeDb.extractChangedInstanceIdsFromChangeSets(changesetFiles);
|
|
789
|
+
if (statusOrResult.error) {
|
|
790
|
+
throw new core_common_1.IModelError(statusOrResult.error.status, "Error processing changeset");
|
|
791
|
+
}
|
|
792
|
+
const result = statusOrResult.result;
|
|
793
|
+
(0, core_bentley_1.assert)(result !== undefined);
|
|
794
|
+
changedInstanceIds.codeSpec.addFromJson(result.codeSpec);
|
|
795
|
+
changedInstanceIds.model.addFromJson(result.model);
|
|
796
|
+
changedInstanceIds.element.addFromJson(result.element);
|
|
797
|
+
changedInstanceIds.aspect.addFromJson(result.aspect);
|
|
798
|
+
changedInstanceIds.relationship.addFromJson(result.relationship);
|
|
799
|
+
changedInstanceIds.font.addFromJson(result.font);
|
|
800
|
+
return changedInstanceIds;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
exports.ChangedInstanceIds = ChangedInstanceIds;
|
|
804
|
+
//# sourceMappingURL=IModelExporter.js.map
|