Skip to content

Commit

Permalink
Merge branch 'main' into dan/extract-api
Browse files Browse the repository at this point in the history
  • Loading branch information
DanRod1999 authored Oct 14, 2024
2 parents 2aadbd0 + 37d2ecc commit 2dd19ff
Show file tree
Hide file tree
Showing 8 changed files with 468 additions and 218 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "add more cases to support preserveElementIdsForFiltering options",
"packageName": "@itwin/imodel-transformer",
"email": "Daniel.Erbynn@bentley.com",
"dependentChangeType": "patch"
}
2 changes: 1 addition & 1 deletion packages/transformer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@itwin/imodel-transformer",
"version": "1.0.0-dev.19",
"version": "1.0.0-dev.22",
"description": "API for exporting an iModel's parts and also importing them into another iModel",
"main": "lib/cjs/transformer.js",
"typings": "lib/cjs/transformer",
Expand Down
77 changes: 56 additions & 21 deletions packages/transformer/src/IModelImporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@ export class IModelImporter {
*/
private _duplicateCodeValueMap: Map<Id64String, string>;

/**
* A set of elementIds that the transformer adds to while exporting elements to indicate that the element already exists in the target.
* Defaults to an empty set.
* @note
*
* This is used as an optimization when `[[IModelTransformOptions.preserveElementIdsForFiltering]]` is set to `true`
* In normal cases where this option set to `false`,
* the importer determines whether to insert or update based off of whether the ID is defined on the `elementProps` passed to `importElement`.
* However, with `preserveElementIdsForFiltering` set to `true`, IDs are always set, so we can't determine insert/update like the normal case.
* The transformer already knows if an element exists or not by the time `importElement` is called and pushes to this set with `markElementToUpdateForPreserveId`.
* @note This set should stay small, as right after the transformer pushes to it, the importer will remove from the set.
*/
private _elementsToUpdateDuringPreserveIds = new Set<Id64String>([]);

/** The set of elements that should not be updated by this IModelImporter.
* Defaults to an empty set.
* @note Adding an element to this set is typically necessary when remapping a source element to one that already exists in the target and already has the desired properties.
Expand Down Expand Up @@ -155,6 +169,16 @@ export class IModelImporter {
return false;
}

/**
* Marks an element so that it can be updated during import when [[IModelTransformOptions.preserveElementIdsForFiltering]] is set to true.
*/
public markElementToUpdateDuringPreserveIds(elementId: Id64String) {
if (this._rootElementIds.has(elementId)) {
return;
}
this._elementsToUpdateDuringPreserveIds.add(elementId);
}

/** Import the specified ModelProps (either as an insert or an update) into the target iModel. */
public importModel(modelProps: ModelProps): void {
if (undefined === modelProps.id || !Id64.isValidId64(modelProps.id))
Expand Down Expand Up @@ -227,6 +251,29 @@ export class IModelImporter {
return `${modelProps.classFullName} [${modelProps.id!}]`;
}

/**
* Tries to update an element with the specified element properties.
* If a duplicate code error occurs, it assigns a new unique code value and retries the update
*/
private tryUpdateElement(elemProps: ElementProps) {
try {
this.onUpdateElement(elemProps);
} catch (err) {
if ((err as IModelError).errorNumber === IModelStatus.DuplicateCode) {
assert(
elemProps.code.value !== undefined,
"NULL code values are always considered unique and cannot clash"
);
this._duplicateCodeValueMap.set(elemProps.id!, elemProps.code.value);
// Using NULL code values as an alternative is not valid because definition elements cannot have NULL code values.
elemProps.code.value = Guid.createValue();
this.onUpdateElement(elemProps);
} else {
throw err;
}
}
}

/** Import the specified ElementProps (either as an insert or an update) into the target iModel. */
public importElement(elementProps: ElementProps): Id64String {
if (
Expand All @@ -239,47 +286,35 @@ export class IModelImporter {
);
return elementProps.id;
}

if (this.options.preserveElementIdsForFiltering) {
if (elementProps.id === undefined) {
throw new IModelError(
IModelStatus.BadElement,
"elementProps.id must be defined during a preserveIds operation"
);
}

// Categories are the only element that onInserted will immediately insert a new element (their default subcategory)
// since default subcategories always exist and always will be inserted after their categories, we treat them as an update
// to prevent duplicate inserts.
// Always present elements (0xe, 0x1, 0x10) also will be updated to prevent duplicate inserts.
// Otherwise we always insert during a preserveElementIdsForFiltering operation
if (
(isSubCategory(elementProps) && isDefaultSubCategory(elementProps)) ||
this._rootElementIds.has(elementProps.id)
) {
this.onUpdateElement(elementProps);
} else {
this.onInsertElement(elementProps);
if (this._elementsToUpdateDuringPreserveIds.has(elementProps.id)) {
this.tryUpdateElement(elementProps);
this._elementsToUpdateDuringPreserveIds.delete(elementProps.id);
} else {
this.onInsertElement(elementProps);
}
}
} else {
if (undefined !== elementProps.id) {
try {
this.onUpdateElement(elementProps);
} catch (err) {
if ((err as IModelError).errorNumber === IModelStatus.DuplicateCode) {
assert(
elementProps.code.value !== undefined,
"NULL code values are always considered unique and cannot clash"
);
this._duplicateCodeValueMap.set(
elementProps.id,
elementProps.code.value
);
// Using NULL code values as an alternative is not valid because definition elements cannot have NULL code values.
elementProps.code.value = Guid.createValue();
this.onUpdateElement(elementProps);
} else {
throw err;
}
}
this.tryUpdateElement(elementProps);
} else {
this.onInsertElement(elementProps); // targetElementProps.id assigned by insertElement
}
Expand Down
32 changes: 28 additions & 4 deletions packages/transformer/src/IModelTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1790,10 +1790,7 @@ export class IModelTransformer extends IModelExportHandler {
public override onExportElement(sourceElement: Element): void {
let targetElementId: Id64String;
let targetElementProps: ElementProps;
if (this._options.preserveElementIdsForFiltering) {
targetElementId = sourceElement.id;
targetElementProps = this.onTransformElement(sourceElement);
} else if (this._options.wasSourceIModelCopiedToTarget) {
if (this._options.wasSourceIModelCopiedToTarget) {
targetElementId = sourceElement.id;
targetElementProps =
this.targetDb.elements.getElementProps(targetElementId);
Expand Down Expand Up @@ -1854,6 +1851,33 @@ export class IModelTransformer extends IModelExportHandler {
? targetElementId
: undefined;

if (this._options.preserveElementIdsForFiltering) {
const isValid = Id64.isValid(targetElementId);
if (isValid && targetElementId !== sourceElement.id) {
// Element found with different id
throw new Error(
`Element id(${sourceElement.id}) cannot be preserved. Found a different mapping(${targetElementId}) from source element`
);
} else if (isValid && targetElementId === sourceElement.id) {
// targetElementId is valid (indicating update)
this.importer.markElementToUpdateDuringPreserveIds(sourceElement.id);
} else if (!isValid) {
const sourceInTargetElemProps =
this.targetDb.elements.tryGetElementProps(sourceElement.id);

// if we don't find mapping for source element in target(invalid) but another element with source id exists in target
if (sourceInTargetElemProps) {
// Element id is already taken by another element
throw new Error(
`Element id(${sourceElement.id}) cannot be preserved. An unrelated element in the target already uses id: ${sourceElement.id}`
);
} else {
// Element id in target is available to be remapped
targetElementProps.id = sourceElement.id;
}
}
}

if (!this._options.wasSourceIModelCopiedToTarget) {
this.importer.importElement(targetElementProps); // don't need to import if iModel was copied
}
Expand Down
121 changes: 0 additions & 121 deletions packages/transformer/src/test/TestUtils/RevisionUtility.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/transformer/src/test/TestUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
export * from "./AdvancedEqual";
export * from "./IModelTestUtils";
export * from "./KnownTestLocations";
export * from "./RevisionUtility";
export * from "./TestChangeSetUtility";
export * from "./imageData";
export * from "./GeometryTestUtil";
Loading

0 comments on commit 2dd19ff

Please sign in to comment.