@itwin/core-backend
Version:
iTwin.js backend components
383 lines • 25 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { Id64 } from "@itwin/core-bentley";
import { Code, IModel, SubCategoryAppearance } from "@itwin/core-common";
import { Point2d } from "@itwin/core-geometry";
import { assert } from "chai";
import * as path from "path";
import * as sinon from "sinon";
import { DefinitionContainer, DefinitionModel, DocumentListModel, Drawing, DrawingCategory, DrawingGraphic, ElementGroupsMembers, ElementOwnsChildElements, ExternalSource, ExternalSourceGroup, PhysicalPartition, SnapshotDb, SpatialCategory, SubCategory, Subject } from "../../core-backend";
import { deleteElementSubTrees, deleteElementTree, ElementTreeBottomUp, ElementTreeWalkerScope } from "../../ElementTreeWalker";
import { IModelTestUtils } from "../IModelTestUtils";
import { KnownTestLocations } from "../KnownTestLocations";
// Test class that collects the results of a bottom-up tree walk
class ElementTreeCollector extends ElementTreeBottomUp {
subModels = [];
definitionModels = [];
elements = [];
definitions = [];
constructor(iModel) { super(iModel); }
visitModel(model, _scope) {
if (model instanceof DefinitionModel)
this.definitionModels.push(model.id);
else
this.subModels.push(model.id);
}
visitElement(elementId, scope) {
if (scope.inDefinitionModel)
this.definitions.push(elementId); // may be some other kind of InformationContentElement - that's OK.
else
this.elements.push(elementId);
}
collect(topElement) {
this.processElementTree(topElement, ElementTreeWalkerScope.createTopScope(this._iModel, topElement));
}
}
class SelectedElementCollector extends ElementTreeCollector {
_elementsToReport;
constructor(iModel, _elementsToReport) {
super(iModel);
this._elementsToReport = _elementsToReport;
}
shouldExploreModel(_model) { return true; }
shouldVisitModel(_model) { return false; }
shouldVisitElement(elementId) { return this._elementsToReport.includes(elementId); }
}
function doesElementExist(iModel, elementId) {
return iModel.elements.tryGetElementProps(elementId) !== undefined;
}
function doesModelExist(iModel, mid) {
return iModel.models.tryGetModelProps(mid) !== undefined;
}
function doesGroupRelationshipExist(iModel, source, target) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return iModel.withPreparedStatement(`select count(*) from ${ElementGroupsMembers.classFullName} where sourceecinstanceid=? and targetecinstanceid=?`, (stmt) => {
stmt.bindId(1, source);
stmt.bindId(2, target);
stmt.step();
return stmt.getValue(0).getInteger() !== 0;
});
}
describe("ElementTreeWalker", () => {
let iModel;
let originalEnv;
let repositoryLinkId;
let jobSubjectId;
let childSubject;
let definitionModelId;
let definitionContainerId;
let drawingDefinitionModelId;
let spatialCategoryId;
let nestedSpatialCategoryId;
let drawingCategoryId;
let drawingSubCategory1Id;
let drawingSubCategory2Id;
let xsGroup;
let xsElement;
let documentListModelId;
let drawingModelId;
let drawingGraphicId1;
let physicalModelId;
let physicalObjectId1;
let physicalObjectId2;
let physicalObjectId3;
before(async () => {
originalEnv = { ...process.env };
IModelTestUtils.registerTestBimSchema();
});
after(() => {
process.env = originalEnv;
});
beforeEach(async () => {
// Uncomment the following two lines to debug test failures
// Logger.initializeToConsole();
// Logger.setLevel("core-backend.IModelDb.ElementTreeWalker", LogLevel.Trace);
const iModelFileName = IModelTestUtils.prepareOutputFile("ElementTreeWalker", "Test.bim");
iModel = SnapshotDb.createEmpty(iModelFileName, { rootSubject: { name: "ElementTreeWalker Test" } });
const schemaPathname = path.join(KnownTestLocations.assetsDir, "TestBim.ecschema.xml");
await iModel.importSchemas([schemaPathname]); // will throw an exception if import fails
/*
[RepositoryModel]
RepositoryLink
Job Subject
+- DefinitionParitition -- [DefinitionModel]
| DrawingCategory
| default SubCategory + 2 non-default SubCategories
| ExternalSourceGroup
| ExternalSource child1
+- DefinitionParitition -- [DefinitionModel]
| SpatialCategory
| DefinitionContainer
| SpatialCategory
|
+- DocumentList -- [DocumentListModel]
| Drawing -- [DrawingModel]
| DrawingGraphic
+- Child Subject
|
+- PhysicalPartition -- [PhysicalModel]
PhysicalObject, PhysicalObject, PhysicalObject (grouped)
*/
repositoryLinkId = IModelTestUtils.insertRepositoryLink(iModel, "test link", "foo", "bar");
jobSubjectId = IModelTestUtils.createJobSubjectElement(iModel, "Job").insert();
childSubject = Subject.insert(iModel, jobSubjectId, "Child Subject");
definitionModelId = DefinitionModel.insert(iModel, jobSubjectId, "Definition");
spatialCategoryId = SpatialCategory.insert(iModel, definitionModelId, "SpatialCategory", new SubCategoryAppearance());
drawingDefinitionModelId = DefinitionModel.insert(iModel, jobSubjectId, "DrawingDefinition");
drawingCategoryId = DrawingCategory.insert(iModel, drawingDefinitionModelId, "DrawingCategory", new SubCategoryAppearance());
drawingSubCategory1Id = SubCategory.insert(iModel, drawingCategoryId, "SubCategory1", new SubCategoryAppearance());
drawingSubCategory2Id = SubCategory.insert(iModel, drawingCategoryId, "SubCategory2", new SubCategoryAppearance());
definitionContainerId = DefinitionContainer.insert(iModel, definitionModelId, Code.createEmpty());
nestedSpatialCategoryId = SpatialCategory.insert(iModel, definitionContainerId, "nested", {});
xsGroup = iModel.elements.insertElement({ classFullName: ExternalSourceGroup.classFullName, model: drawingDefinitionModelId, code: Code.createEmpty() });
xsElement = iModel.elements.insertElement({ classFullName: ExternalSource.classFullName, model: drawingDefinitionModelId, parent: new ElementOwnsChildElements(xsGroup), code: Code.createEmpty() });
documentListModelId = DocumentListModel.insert(iModel, jobSubjectId, "Document");
assert.isTrue(Id64.isValidId64(documentListModelId));
drawingModelId = Drawing.insert(iModel, documentListModelId, "Drawing");
const drawingGraphicProps1 = {
classFullName: DrawingGraphic.classFullName,
model: drawingModelId,
category: drawingCategoryId,
code: Code.createEmpty(),
userLabel: "DrawingGraphic1",
geom: IModelTestUtils.createRectangle(Point2d.create(1, 1)),
placement: { origin: Point2d.create(2, 2), angle: 0 },
};
drawingGraphicId1 = iModel.elements.insertElement(drawingGraphicProps1);
[, physicalModelId] = IModelTestUtils.createAndInsertPhysicalPartitionAndModel(iModel, PhysicalPartition.createCode(iModel, childSubject, "Physical"), false, childSubject);
const elementProps = {
classFullName: "TestBim:TestPhysicalObject",
model: physicalModelId,
category: spatialCategoryId,
code: Code.createEmpty(),
};
const elementProps2 = {
classFullName: "TestBim:TestPhysicalObject",
model: physicalModelId,
category: nestedSpatialCategoryId,
code: Code.createEmpty(),
};
physicalObjectId1 = iModel.elements.insertElement(iModel.elements.createElement(elementProps).toJSON());
physicalObjectId2 = iModel.elements.insertElement(iModel.elements.createElement(elementProps2).toJSON());
physicalObjectId3 = iModel.elements.insertElement(iModel.elements.createElement(elementProps).toJSON());
ElementGroupsMembers.create(iModel, physicalObjectId1, physicalObjectId2).insert();
ElementGroupsMembers.create(iModel, physicalObjectId1, physicalObjectId3).insert();
assert.isTrue(doesElementExist(iModel, repositoryLinkId));
assert.equal(iModel.elements.getElement(jobSubjectId).parent?.id, IModel.rootSubjectId);
assert.equal(iModel.elements.getElement(definitionModelId).parent?.id, jobSubjectId);
assert.equal(iModel.elements.getElement(definitionContainerId).model, definitionModelId);
assert.equal(iModel.elements.getElement(spatialCategoryId).model, definitionModelId);
assert.equal(iModel.elements.getElement(nestedSpatialCategoryId).model, definitionContainerId);
assert.equal(iModel.elements.getElement(drawingDefinitionModelId).parent?.id, jobSubjectId);
assert.equal(iModel.elements.getElement(drawingCategoryId).model, drawingDefinitionModelId);
assert.equal(iModel.elements.getElement(drawingSubCategory1Id).parent?.id, drawingCategoryId);
assert.equal(iModel.elements.getElement(drawingSubCategory2Id).parent?.id, drawingCategoryId);
assert.equal(iModel.elements.getElement(xsGroup).model, drawingDefinitionModelId);
assert.equal(iModel.elements.getElement(xsElement).parent?.id, xsGroup);
assert.equal(iModel.elements.getElement(documentListModelId).parent?.id, jobSubjectId);
assert.equal(iModel.elements.getElement(drawingModelId).model, documentListModelId);
assert.equal(iModel.elements.getElement(drawingGraphicId1).model, drawingModelId);
assert.equal(iModel.elements.getElement(physicalModelId).parent?.id, childSubject);
assert.equal(iModel.elements.getElement(physicalObjectId1).model, physicalModelId);
assert.equal(iModel.elements.getElement(physicalObjectId2).model, physicalModelId);
assert.equal(iModel.elements.getElement(physicalObjectId3).model, physicalModelId);
assert.isTrue(doesGroupRelationshipExist(iModel, physicalObjectId1, physicalObjectId2));
assert.isTrue(doesGroupRelationshipExist(iModel, physicalObjectId1, physicalObjectId3));
assert.isTrue(doesModelExist(iModel, definitionModelId));
assert.isTrue(doesModelExist(iModel, definitionContainerId));
assert.isTrue(doesModelExist(iModel, drawingDefinitionModelId));
assert.isTrue(doesModelExist(iModel, drawingModelId));
assert.isTrue(doesModelExist(iModel, physicalModelId));
});
afterEach(() => {
sinon.restore();
iModel.close();
});
it("DFS search and deleteElementTree", () => {
// First, check that DFS search visits elements and models in expected bottom-up order
{
const collector1 = new ElementTreeCollector(iModel);
collector1.collect(jobSubjectId);
assert.isTrue(collector1.subModels.includes(physicalModelId));
assert.isTrue(collector1.subModels.includes(drawingModelId));
assert.isTrue(collector1.subModels.includes(documentListModelId));
assert.isTrue(collector1.subModels.indexOf(drawingModelId) < collector1.subModels.indexOf(documentListModelId), "in bottom-up search, a child model should be visited before its parent model");
assert.isFalse(collector1.subModels.includes(definitionModelId));
assert.isTrue(collector1.definitionModels.includes(definitionModelId));
assert.isFalse(collector1.subModels.includes(definitionContainerId));
assert.isTrue(collector1.definitionModels.includes(definitionContainerId));
assert.isFalse(collector1.subModels.includes(drawingDefinitionModelId));
assert.isTrue(collector1.definitionModels.includes(drawingDefinitionModelId));
assert.isTrue(collector1.definitions.includes(drawingCategoryId));
assert.isTrue(collector1.definitions.includes(spatialCategoryId));
assert.isTrue(collector1.definitions.includes(nestedSpatialCategoryId));
assert.isFalse(collector1.elements.includes(drawingCategoryId));
assert.isFalse(collector1.elements.includes(spatialCategoryId));
assert.isFalse(collector1.elements.includes(nestedSpatialCategoryId));
assert.isTrue(collector1.elements.indexOf(physicalObjectId1) < collector1.elements.indexOf(physicalModelId), "in bottom-up search, an element in a model should be visited before its model's element");
assert.isTrue(collector1.elements.indexOf(drawingGraphicId1) < collector1.elements.indexOf(drawingModelId), "in bottom-up search, an element in a model should be visited before its model's element");
assert.isTrue(collector1.elements.indexOf(drawingModelId) < collector1.elements.indexOf(documentListModelId), "in bottom-up search, an element in a model should be visited before its model's element");
assert.isTrue(collector1.elements.indexOf(documentListModelId) < collector1.elements.indexOf(jobSubjectId), "in bottom-up search, a child element should be visited before its parent element");
assert.isTrue(collector1.elements.indexOf(definitionModelId) < collector1.elements.indexOf(jobSubjectId), "in bottom-up search, a child element should be visited before its parent element");
assert.isTrue(collector1.elements.indexOf(definitionContainerId) < collector1.elements.indexOf(definitionModelId), "in bottom-up search, a child element should be visited before its parent element");
assert.isTrue(collector1.elements.indexOf(drawingDefinitionModelId) < collector1.elements.indexOf(jobSubjectId), "in bottom-up search, a child element should be visited before its parent element");
assert.isTrue(collector1.elements.indexOf(childSubject) < collector1.elements.indexOf(jobSubjectId), "in bottom-up search, a child element should be visited before its parent element");
assert.isTrue(collector1.elements.indexOf(physicalModelId) < collector1.elements.indexOf(childSubject), "in bottom-up search, a child element should be visited before its parent element");
}
// Exercise the search filters
{
const collector2 = new SelectedElementCollector(iModel, [drawingGraphicId1, spatialCategoryId, nestedSpatialCategoryId, physicalObjectId3]);
collector2.collect(jobSubjectId);
assert.isTrue(collector2.definitions.length === 2);
assert.isTrue(collector2.definitions.includes(spatialCategoryId));
assert.isTrue(collector2.definitions.includes(nestedSpatialCategoryId));
assert.isTrue(collector2.elements.length === 2);
assert.isTrue(collector2.elements.includes(drawingGraphicId1));
assert.isTrue(collector2.elements.includes(physicalObjectId3));
}
// Test the deleteElementTree function
deleteElementTree(iModel, jobSubjectId);
assert.isTrue(doesModelExist(iModel, IModel.repositoryModelId));
assert.isTrue(doesModelExist(iModel, IModel.dictionaryId));
assert.isTrue(doesElementExist(iModel, repositoryLinkId), "RepositoryLink should not have been deleted, since it is not under Job Subject");
assert.isFalse(doesElementExist(iModel, definitionModelId));
assert.isFalse(doesElementExist(iModel, definitionContainerId));
assert.isFalse(doesElementExist(iModel, drawingDefinitionModelId));
assert.isFalse(doesElementExist(iModel, spatialCategoryId));
assert.isFalse(doesElementExist(iModel, nestedSpatialCategoryId));
assert.isFalse(doesElementExist(iModel, drawingCategoryId));
assert.isFalse(doesElementExist(iModel, xsGroup));
assert.isFalse(doesElementExist(iModel, xsElement));
assert.isFalse(doesElementExist(iModel, documentListModelId));
assert.isFalse(doesElementExist(iModel, drawingModelId));
assert.isFalse(doesElementExist(iModel, drawingGraphicId1));
assert.isFalse(doesElementExist(iModel, physicalModelId));
assert.isFalse(doesElementExist(iModel, physicalObjectId1));
assert.isFalse(doesElementExist(iModel, physicalObjectId2));
assert.isFalse(doesElementExist(iModel, physicalObjectId3));
assert.isFalse(doesGroupRelationshipExist(iModel, physicalObjectId1, physicalObjectId2));
assert.isFalse(doesGroupRelationshipExist(iModel, physicalObjectId1, physicalObjectId3));
assert.isFalse(doesElementExist(iModel, jobSubjectId));
assert.isFalse(doesModelExist(iModel, definitionModelId));
assert.isFalse(doesModelExist(iModel, drawingDefinitionModelId));
assert.isFalse(doesModelExist(iModel, drawingModelId));
assert.isFalse(doesModelExist(iModel, physicalModelId));
});
it("deleteElementSubTrees", () => {
/*
[RepositoryModel]
RepositoryLink
Job Subject
+- DefinitionParitition -- [DefinitionModel]
| DrawingCategory <-- PRUNE
| default SubCategory + 2 non-default SubCategories
| ExternalSourceGroup
| ExternalSource child1
+- DefinitionParitition -- [DefinitionModel]
| SpatialCategory
| DefinitionContainer
| SpatialCategory
|
+- DocumentList -- [DocumentListModel]
| Drawing -- [DrawingModel] <-- PRUNE
| DrawingGraphic "
+- Child Subject
|
+- PhysicalPartition -- [PhysicalModel]
PhysicalObject, PhysicalObject, PhysicalObject (grouped)
^-- PRUNE
*/
const toPrune = new Set();
toPrune.add(drawingModelId);
toPrune.add(drawingCategoryId);
toPrune.add(physicalObjectId3);
deleteElementSubTrees(iModel, jobSubjectId, (elementId) => toPrune.has(elementId));
assert.isFalse(doesElementExist(iModel, drawingCategoryId));
assert.isFalse(doesElementExist(iModel, drawingSubCategory1Id));
assert.isFalse(doesElementExist(iModel, drawingSubCategory2Id));
assert.isFalse(doesElementExist(iModel, drawingModelId));
assert.isFalse(doesModelExist(iModel, drawingModelId));
assert.isFalse(doesElementExist(iModel, drawingGraphicId1)); // contents of drawing model should be gone
assert.isFalse(doesElementExist(iModel, physicalObjectId3));
assert.isFalse(doesGroupRelationshipExist(iModel, physicalObjectId1, physicalObjectId3));
assert.isTrue(doesElementExist(iModel, repositoryLinkId));
assert.isTrue(doesElementExist(iModel, definitionModelId));
assert.isTrue(doesElementExist(iModel, definitionContainerId));
assert.isTrue(doesElementExist(iModel, drawingDefinitionModelId));
assert.equal(iModel.elements.getElement(xsGroup).model, drawingDefinitionModelId);
assert.equal(iModel.elements.getElement(xsElement).parent?.id, xsGroup);
assert.isTrue(doesElementExist(iModel, spatialCategoryId));
assert.isTrue(doesElementExist(iModel, nestedSpatialCategoryId));
assert.isTrue(doesElementExist(iModel, documentListModelId));
assert.isTrue(doesElementExist(iModel, physicalModelId));
assert.isTrue(doesElementExist(iModel, physicalObjectId1));
assert.isTrue(doesElementExist(iModel, physicalObjectId2));
assert.isTrue(doesGroupRelationshipExist(iModel, physicalObjectId1, physicalObjectId2));
assert.isTrue(doesElementExist(iModel, jobSubjectId));
assert.isTrue(doesModelExist(iModel, definitionModelId));
assert.isTrue(doesModelExist(iModel, definitionContainerId));
assert.isTrue(doesModelExist(iModel, drawingDefinitionModelId));
assert.isTrue(doesModelExist(iModel, physicalModelId));
assert.isTrue(doesModelExist(iModel, IModel.repositoryModelId));
assert.isTrue(doesModelExist(iModel, IModel.dictionaryId));
});
it("deleteDefinitionPartition", () => {
/*
[RepositoryModel]
RepositoryLink
Job Subject
+- DefinitionParitition -- [DefinitionModel] <-- PRUNE
| DrawingCategory
| default SubCategory + 2 non-default SubCategories
| ExternalSourceGroup
| ExternalSource child1
+- DefinitionParitition -- [DefinitionModel]
| SpatialCategory
| DefinitionContainer
| SpatialCategory |
+- DocumentList -- [DocumentListModel]
| Drawing -- [DrawingModel]
| DrawingGraphic
+- Child Subject
|
+- PhysicalPartition -- [PhysicalModel]
PhysicalObject, PhysicalObject, PhysicalObject (grouped)
*/
const toPrune = new Set();
toPrune.add(drawingDefinitionModelId);
toPrune.add(documentListModelId); // (also get rid of the elements that use the definitions)
deleteElementSubTrees(iModel, jobSubjectId, (elementId) => toPrune.has(elementId));
assert.isFalse(doesElementExist(iModel, drawingDefinitionModelId));
assert.isFalse(doesModelExist(iModel, drawingDefinitionModelId));
assert.isFalse(doesElementExist(iModel, drawingCategoryId));
assert.isFalse(doesElementExist(iModel, drawingSubCategory1Id));
assert.isFalse(doesElementExist(iModel, drawingSubCategory2Id));
assert.isFalse(doesElementExist(iModel, xsGroup));
assert.isFalse(doesElementExist(iModel, xsElement));
assert.isFalse(doesElementExist(iModel, documentListModelId));
assert.isFalse(doesModelExist(iModel, documentListModelId));
assert.isFalse(doesElementExist(iModel, drawingModelId));
assert.isFalse(doesModelExist(iModel, drawingModelId));
assert.isFalse(doesElementExist(iModel, drawingGraphicId1));
assert.isTrue(doesElementExist(iModel, repositoryLinkId));
assert.isTrue(doesElementExist(iModel, definitionModelId));
assert.isTrue(doesElementExist(iModel, definitionContainerId));
assert.isTrue(doesElementExist(iModel, spatialCategoryId));
assert.isTrue(doesElementExist(iModel, nestedSpatialCategoryId));
assert.isTrue(doesElementExist(iModel, physicalModelId));
assert.isTrue(doesElementExist(iModel, physicalObjectId1));
assert.isTrue(doesElementExist(iModel, physicalObjectId2));
assert.isTrue(doesElementExist(iModel, physicalObjectId3));
assert.isTrue(doesGroupRelationshipExist(iModel, physicalObjectId1, physicalObjectId2));
assert.isTrue(doesGroupRelationshipExist(iModel, physicalObjectId1, physicalObjectId3));
assert.isTrue(doesElementExist(iModel, jobSubjectId));
assert.isTrue(doesModelExist(iModel, definitionModelId));
assert.isTrue(doesModelExist(iModel, definitionContainerId));
assert.isTrue(doesModelExist(iModel, physicalModelId));
assert.isTrue(doesModelExist(iModel, IModel.repositoryModelId));
assert.isTrue(doesModelExist(iModel, IModel.dictionaryId));
});
});
//# sourceMappingURL=ElementTreeWalker.test.js.map