/
IModelElementCloneContext.ts
186 lines (165 loc) · 8.54 KB
/
IModelElementCloneContext.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module iModels
*/
import { Id64, Id64String } from "@itwin/core-bentley";
import { Code, CodeScopeSpec, CodeSpec, ElementProps, IModel, PropertyMetaData, RelatedElement } from "@itwin/core-common";
import { IModelJsNative } from "@bentley/imodeljs-native";
import { SubCategory } from "./Category";
import { Element } from "./Element";
import { IModelDb } from "./IModelDb";
import { IModelHost } from "./IModelHost";
import { SQLiteDb } from "./SQLiteDb";
/** The context for transforming a *source* Element to a *target* Element and remapping internal identifiers to the target iModel.
* @beta
*/
export class IModelElementCloneContext {
/** The source IModelDb. */
public readonly sourceDb: IModelDb;
/** The target IModelDb. */
public readonly targetDb: IModelDb;
/** The native import context */
private _nativeContext: IModelJsNative.ImportContext;
/** Construct a new IModelElementCloneContext. It must be initialized with `initialize`, consider using [[IModelElementCloneContext.create]] instead
* @param sourceDb The source IModelDb.
* @param targetDb If provided the target IModelDb. If not provided, the source and target are the same IModelDb.
*/
public constructor(sourceDb: IModelDb, targetDb?: IModelDb) {
this.sourceDb = sourceDb;
this.targetDb = (undefined !== targetDb) ? targetDb : sourceDb;
this._nativeContext = new IModelHost.platform.ImportContext(this.sourceDb.nativeDb, this.targetDb.nativeDb);
}
/** perform necessary initialization to use a clone context, namely caching the reference types in the source's schemas */
public async initialize() {
}
/** construct and initialize an IModelElementCloneContext at once, for where you construct in an async context */
public static async create(...args: ConstructorParameters<typeof IModelElementCloneContext>): Promise<IModelElementCloneContext> {
const instance = new this(...args);
await instance.initialize();
return instance;
}
/** Returns `true` if this context is for transforming between 2 iModels and `false` if it for transforming within the same iModel. */
public get isBetweenIModels(): boolean { return this.sourceDb !== this.targetDb; }
/** Dispose any native resources associated with this IModelElementCloneContext. */
public dispose(): void { this._nativeContext.dispose(); }
/** Debugging aid that dumps the Id remapping details and other information to the specified output file.
* @internal
*/
public dump(outputFileName: string): void { this._nativeContext.dump(outputFileName); }
/** Add a rule that remaps the specified source [CodeSpec]($common) to the specified target [CodeSpec]($common).
* @param sourceCodeSpecName The name of the CodeSpec from the source iModel.
* @param targetCodeSpecName The name of the CodeSpec from the target iModel.
* @throws [[IModelError]] if either CodeSpec could not be found.
*/
public remapCodeSpec(sourceCodeSpecName: string, targetCodeSpecName: string): void {
const sourceCodeSpec: CodeSpec = this.sourceDb.codeSpecs.getByName(sourceCodeSpecName);
const targetCodeSpec: CodeSpec = this.targetDb.codeSpecs.getByName(targetCodeSpecName);
this._nativeContext.addCodeSpecId(sourceCodeSpec.id, targetCodeSpec.id);
}
/** Add a rule that remaps the specified source class to the specified target class. */
public remapElementClass(sourceClassFullName: string, targetClassFullName: string): void {
this._nativeContext.addClass(sourceClassFullName, targetClassFullName);
}
/** Add a rule that remaps the specified source Element to the specified target Element. */
public remapElement(sourceId: Id64String, targetId: Id64String): void {
this._nativeContext.addElementId(sourceId, targetId);
}
/** Remove a rule that remaps the specified source Element. */
public removeElement(sourceId: Id64String): void {
this._nativeContext.removeElementId(sourceId);
}
/** Look up a target CodeSpecId from the source CodeSpecId.
* @returns the target CodeSpecId or [Id64.invalid]($bentley) if a mapping not found.
*/
public findTargetCodeSpecId(sourceId: Id64String): Id64String {
if (Id64.invalid === sourceId) {
return Id64.invalid;
}
return this._nativeContext.findCodeSpecId(sourceId);
}
/** Look up a target ElementId from the source ElementId.
* @returns the target ElementId or [Id64.invalid]($bentley) if a mapping not found.
*/
public findTargetElementId(sourceElementId: Id64String): Id64String {
if (Id64.invalid === sourceElementId) {
return Id64.invalid;
}
return this._nativeContext.findElementId(sourceElementId);
}
/** Filter out geometry entries in the specified SubCategory from GeometryStreams in the target iModel.
* @note It is not possible to filter out a *default* SubCategory. A request to do so will be ignored.
* @see [SubCategory.isDefaultSubCategory]($backend)
*/
public filterSubCategory(sourceSubCategoryId: Id64String): void {
const sourceSubCategory = this.sourceDb.elements.tryGetElement<SubCategory>(sourceSubCategoryId, SubCategory);
if (sourceSubCategory && !sourceSubCategory.isDefaultSubCategory) {
this._nativeContext.filterSubCategoryId(sourceSubCategoryId);
}
}
/** Returns `true` if there are any SubCategories being filtered. */
public get hasSubCategoryFilter(): boolean {
return this._nativeContext.hasSubCategoryFilter();
}
/** Returns `true` if this SubCategory is being filtered. */
public isSubCategoryFiltered(subCategoryId: Id64String): boolean {
return this._nativeContext.isSubCategoryFiltered(subCategoryId);
}
/** Import the specified font from the source iModel into the target iModel.
* @internal
*/
public importFont(sourceFontNumber: number): void {
this.targetDb.clearFontMap(); // so it will be reloaded with new font info
this._nativeContext.importFont(sourceFontNumber);
}
/** Import a single CodeSpec from the source iModel into the target iModel.
* @internal
*/
public importCodeSpec(sourceCodeSpecId: Id64String): void {
this._nativeContext.importCodeSpec(sourceCodeSpecId);
}
/** Clone the specified source Element into ElementProps for the target iModel.
* @internal
*/
public cloneElement(sourceElement: Element, cloneOptions?: IModelJsNative.CloneElementOptions): ElementProps {
const targetElementProps: ElementProps = this._nativeContext.cloneElement(sourceElement.id, cloneOptions);
// Ensure that all NavigationProperties in targetElementProps have a defined value so "clearing" changes will be part of the JSON used for update
sourceElement.forEachProperty((propertyName: string, meta: PropertyMetaData) => {
if ((meta.isNavigation) && (undefined === (sourceElement as any)[propertyName])) {
(targetElementProps as any)[propertyName] = RelatedElement.none;
}
}, false); // exclude custom because C++ has already handled them
if (this.isBetweenIModels) {
// The native C++ cloneElement strips off federationGuid, want to put it back if transformation is between iModels
targetElementProps.federationGuid = sourceElement.federationGuid;
if (CodeScopeSpec.Type.Repository === this.targetDb.codeSpecs.getById(targetElementProps.code.spec).scopeType) {
targetElementProps.code.scope = IModel.rootSubjectId;
}
}
// unlike other references, code cannot be null. If it is null, use an empty code instead
if (targetElementProps.code.scope === Id64.invalid || targetElementProps.code.spec === Id64.invalid) {
targetElementProps.code = Code.createEmpty();
}
const jsClass = this.sourceDb.getJsClass<typeof Element>(sourceElement.classFullName);
// eslint-disable-next-line @typescript-eslint/dot-notation
jsClass["onCloned"](this, sourceElement.toJSON(), targetElementProps);
return targetElementProps;
}
/**
* serialize state to a sqlite database at a given path
* assumes the database has not already had any context state serialized to it
* @internal
*/
public saveStateToDb(db: SQLiteDb): void {
this._nativeContext.saveStateToDb(db.nativeDb);
}
/**
* load state from a sqlite database at a given path
* @internal
*/
public loadStateFromDb(db: SQLiteDb): void {
this._nativeContext.loadStateFromDb(db.nativeDb);
}
}