@itwin/core-backend
Version:
iTwin.js backend components
866 lines • 69.6 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 { Buffer } from "node:buffer";
import * as chai from "chai";
import { assert, expect } from "chai";
import * as chaiAsPromised from "chai-as-promised";
import * as path from "path";
import { BeEvent, DbResult, Guid, Id64, IModelStatus, omit } from "@itwin/core-bentley";
import { Base64EncodedString, Code, CodeScopeSpec, ColorDef, Environment, FontType, GeometryParams, GeometryStreamBuilder, ImageSourceFormat, IModel, IModelError, IModelReadRpcInterface, IModelVersion, PlanProjectionSettings, RelatedElement, RpcConfiguration, RpcManager, RpcPendingResponse, SkyBoxImageType, SubCategoryAppearance, SubCategoryOverride, SyncMode, } from "@itwin/core-common";
import { Box, Cone, LineString3d, Point2d, Point3d, Range2d, Range3d, StandardViewIndex, Vector3d, YawPitchRollAngles } from "@itwin/core-geometry";
import { CheckpointManager, V2CheckpointManager } from "../CheckpointManager";
import { ClassRegistry } from "../ClassRegistry";
import { _nativeDb, AuxCoordSystem2d, BriefcaseDb, BriefcaseLocalValue, BriefcaseManager, CategorySelector, ChannelControl, DisplayStyle2d, DisplayStyle3d, DrawingCategory, DrawingViewDefinition, Element, ElementOwnsChildElements, ElementOwnsMultiAspects, ElementOwnsUniqueAspect, ExternalSource, ExternalSourceIsInRepository, FunctionalModel, FunctionalSchema, GroupModel, IModelDb, IModelHost, IModelJsFs, InformationPartitionElement, Model, ModelSelector, OrthographicViewDefinition, PhysicalModel, PhysicalObject, PhysicalPartition, RenderMaterialElement, SnapshotDb, SpatialCategory, SubCategory, SubjectOwnsPartitionElements, Texture, ViewDefinition, } from "../core-backend";
import { DefinitionPartition, Drawing, DrawingGraphic, GeometryPart, LinkElement, PhysicalElement, RepositoryLink, Subject } from "../Element";
import { DefinitionModel, DocumentListModel, DrawingModel, InformationRecordModel, SpatialLocationModel } from "../Model";
import { DrawingGraphicRepresentsElement, ElementDrivesElement } from "../Relationship";
import { RpcBriefcaseUtility } from "../rpc-impl/RpcBriefcaseUtility";
import { Schema, Schemas } from "../Schema";
import { HubMock } from "../internal/HubMock";
import { KnownTestLocations } from "./KnownTestLocations";
import { _hubAccess } from "../internal/Symbols";
chai.use(chaiAsPromised);
/* eslint-disable @typescript-eslint/explicit-member-accessibility */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
RpcConfiguration.developmentMode = true;
// Initialize the RPC interface classes used by tests
RpcManager.initializeInterface(IModelReadRpcInterface);
export class TestBim extends Schema {
static get schemaName() { return "TestBim"; }
}
export class TestElementDrivesElement extends ElementDrivesElement {
static get className() { return "TestElementDrivesElement"; }
static rootChanged = new BeEvent();
static deletedDependency = new BeEvent();
static onRootChanged(props, imodel) { this.rootChanged.raiseEvent(props, imodel); }
static onDeletedDependency(props, imodel) { this.deletedDependency.raiseEvent(props, imodel); }
}
export class TestPhysicalObject extends PhysicalElement {
static get className() { return "TestPhysicalObject"; }
static beforeOutputsHandled = new BeEvent();
static allInputsHandled = new BeEvent();
static onBeforeOutputsHandled(id, imodel) { this.beforeOutputsHandled.raiseEvent(id, imodel); }
static onAllInputsHandled(id, imodel) { this.allInputsHandled.raiseEvent(id, imodel); }
}
/** the types of users available for tests */
export var TestUserType;
(function (TestUserType) {
TestUserType[TestUserType["Regular"] = 0] = "Regular";
TestUserType[TestUserType["Manager"] = 1] = "Manager";
TestUserType[TestUserType["Super"] = 2] = "Super";
TestUserType[TestUserType["SuperManager"] = 3] = "SuperManager";
})(TestUserType || (TestUserType = {}));
/** A wrapper around the BackendHubAccess API through IModelHost[_hubAccess].
*
* All methods in this class should be usable with any BackendHubAccess implementation (i.e. HubMock and IModelHubBackend).
*/
export class HubWrappers {
static get hubMock() { return HubMock; }
static async getAccessToken(user) {
return TestUserType[user];
}
/** Create an iModel with the name provided if it does not already exist. If it does exist, the iModelId is returned. */
static async createIModel(accessToken, iTwinId, iModelName) {
assert.isTrue(this.hubMock.isValid, "Must use HubMock for tests that modify iModels");
let iModelId = await IModelHost[_hubAccess].queryIModelByName({ accessToken, iTwinId, iModelName });
if (!iModelId)
iModelId = await IModelHost[_hubAccess].createNewIModel({ accessToken, iTwinId, iModelName, description: `Description for iModel` });
return iModelId;
}
/** Deletes and re-creates an iModel with the provided name in the iTwin.
* @returns the iModelId of the newly created iModel.
*/
static async recreateIModel(...[arg]) {
assert.isTrue(this.hubMock.isValid, "Must use HubMock for tests that modify iModels");
const deleteIModel = await IModelHost[_hubAccess].queryIModelByName(arg);
if (undefined !== deleteIModel)
await IModelHost[_hubAccess].deleteIModel({ accessToken: arg.accessToken, iTwinId: arg.iTwinId, iModelId: deleteIModel });
// Create a new iModel
return IModelHost[_hubAccess].createNewIModel({ description: `Description for ${arg.iModelName}`, ...arg });
}
/** Delete an IModel from the hub */
static async deleteIModel(accessToken, iTwinId, iModelName) {
const iModelId = await IModelHost[_hubAccess].queryIModelByName({ accessToken, iTwinId, iModelName });
if (undefined === iModelId)
return;
await IModelHost[_hubAccess].deleteIModel({ accessToken, iTwinId, iModelId });
}
/** Push an iModel to the Hub */
static async pushIModel(accessToken, iTwinId, pathname, iModelName, overwrite) {
// Delete any existing iModels with the same name as the required iModel
const locIModelName = iModelName || path.basename(pathname, ".bim");
const iModelId = await IModelHost[_hubAccess].queryIModelByName({ accessToken, iTwinId, iModelName: locIModelName });
if (iModelId) {
if (!overwrite)
return iModelId;
await IModelHost[_hubAccess].deleteIModel({ accessToken, iTwinId, iModelId });
}
// Upload a new iModel
return IModelHost[_hubAccess].createNewIModel({ accessToken, iTwinId, iModelName: locIModelName, version0: pathname });
}
/** Helper to open a briefcase db directly with the BriefcaseManager API */
static async downloadAndOpenBriefcase(args) {
const props = await BriefcaseManager.downloadBriefcase(args);
if (args.noLock) {
const briefcase = await BriefcaseDb.open({ fileName: props.fileName });
briefcase[_nativeDb].saveLocalValue(BriefcaseLocalValue.NoLocking, "true");
briefcase.saveChanges();
briefcase.close();
}
return BriefcaseDb.open({ fileName: props.fileName });
}
/** Opens the specific iModel as a Briefcase through the same workflow the IModelReadRpc.getConnectionProps method will use. Replicates the way a frontend would open the iModel. */
static async openBriefcaseUsingRpc(args) {
if (undefined === args.asOf)
args.asOf = IModelVersion.latest().toJSON();
const openArgs = {
tokenProps: {
iTwinId: args.iTwinId,
iModelId: args.iModelId,
changeset: (await IModelHost[_hubAccess].getChangesetFromVersion({ accessToken: args.accessToken, version: IModelVersion.fromJSON(args.asOf), iModelId: args.iModelId })),
},
activity: { accessToken: args.accessToken, activityId: "", applicationId: "", applicationVersion: "", sessionId: "" },
syncMode: args.briefcaseId === 0 ? SyncMode.PullOnly : SyncMode.PullAndPush,
forceDownload: args.deleteFirst,
};
assert.isTrue(this.hubMock.isValid || openArgs.syncMode === SyncMode.PullOnly, "use HubMock to acquire briefcases");
while (true) {
try {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return (await RpcBriefcaseUtility.open(openArgs));
}
catch (error) {
if (!(error instanceof RpcPendingResponse))
throw error;
}
}
}
/** Downloads a checkpoint and opens it as a SnapShotDb */
static async downloadAndOpenCheckpoint(args) {
if (undefined === args.asOf)
args.asOf = IModelVersion.latest().toJSON();
const checkpoint = {
iTwinId: args.iTwinId,
iModelId: args.iModelId,
accessToken: args.accessToken,
changeset: (await IModelHost[_hubAccess].getChangesetFromVersion({ accessToken: args.accessToken, version: IModelVersion.fromJSON(args.asOf), iModelId: args.iModelId })),
};
const folder = path.join(V2CheckpointManager.getFolder(), checkpoint.iModelId);
const filename = path.join(folder, `${checkpoint.changeset.id === "" ? "first" : checkpoint.changeset.id}.bim`);
const request = { checkpoint, localFile: filename };
let db = SnapshotDb.tryFindByKey(CheckpointManager.getKey(request.checkpoint));
if (undefined !== db)
return db;
db = IModelTestUtils.tryOpenLocalFile(request);
if (db)
return db;
await V2CheckpointManager.downloadCheckpoint(request);
await CheckpointManager.updateToRequestedVersion(request);
return IModelTestUtils.openCheckpoint(request.localFile, request.checkpoint);
}
/** Opens the specific Checkpoint iModel, `SyncMode.FixedVersion`, through the same workflow the IModelReadRpc.getConnectionProps method will use. Replicates the way a frontend would open the iModel. */
static async openCheckpointUsingRpc(args) {
if (undefined === args.asOf)
args.asOf = IModelVersion.latest().toJSON();
const changeset = await IModelHost[_hubAccess].getChangesetFromVersion({ accessToken: args.accessToken, version: IModelVersion.fromJSON(args.asOf), iModelId: args.iModelId });
const openArgs = {
tokenProps: {
iTwinId: args.iTwinId,
iModelId: args.iModelId,
changeset,
},
activity: { accessToken: args.accessToken, activityId: "", applicationId: "", applicationVersion: "", sessionId: "" },
syncMode: SyncMode.FixedVersion,
forceDownload: args.deleteFirst,
};
while (true) {
try {
return (await RpcBriefcaseUtility.open(openArgs));
}
catch (error) {
if (!(error instanceof RpcPendingResponse))
throw error;
}
}
}
/**
* Purges all acquired briefcases for the specified iModel (and user), if the specified threshold of acquired briefcases is exceeded
*/
static async purgeAcquiredBriefcasesById(accessToken, iModelId, onReachThreshold = () => { }, acquireThreshold = 16) {
const briefcases = await IModelHost[_hubAccess].getMyBriefcaseIds({ accessToken, iModelId });
if (briefcases.length > acquireThreshold) {
if (undefined !== onReachThreshold)
onReachThreshold();
const promises = [];
briefcases.forEach((briefcaseId) => {
promises.push(IModelHost[_hubAccess].releaseBriefcase({ accessToken, iModelId, briefcaseId }));
});
await Promise.all(promises);
}
}
static async closeAndDeleteBriefcaseDb(accessToken, briefcaseDb) {
const fileName = briefcaseDb.pathName;
const iModelId = briefcaseDb.iModelId;
briefcaseDb.close();
await BriefcaseManager.deleteBriefcaseFiles(fileName, accessToken);
// try to clean up empty briefcase directories, and empty iModel directories.
if (0 === BriefcaseManager.getCachedBriefcases(iModelId).length) {
IModelJsFs.removeSync(BriefcaseManager.getBriefcaseBasePath(iModelId));
const imodelPath = BriefcaseManager.getIModelPath(iModelId);
if (0 === IModelJsFs.readdirSync(imodelPath).length) {
IModelJsFs.removeSync(imodelPath);
}
}
}
}
export class IModelTestUtils {
static get knownTestLocations() { return KnownTestLocations; }
/** Generate a name for an iModel that's unique using the baseName provided and appending a new GUID. */
static generateUniqueName(baseName) {
return `${baseName} - ${Guid.createValue()}`;
}
static openCheckpoint(fileName, checkpoint) {
const snapshot = SnapshotDb.openFile(fileName, { key: CheckpointManager.getKey(checkpoint) });
snapshot._iTwinId = checkpoint.iTwinId;
return snapshot;
}
/** try to open an existing local file to satisfy a download request */
static tryOpenLocalFile(request) {
const checkpoint = request.checkpoint;
if (CheckpointManager.verifyCheckpoint(checkpoint, request.localFile))
return this.openCheckpoint(request.localFile, checkpoint);
return undefined;
}
/** Prepare for an output file by:
* - Resolving the output file name under the known test output directory
* - Making directories as necessary
* - Removing a previous copy of the output file
* @param subDirName Sub-directory under known test output directory. Should match the name of the test file minus the .test.ts file extension.
* @param fileName Name of output fille
*/
static prepareOutputFile(subDirName, fileName) {
if (!IModelJsFs.existsSync(this.knownTestLocations.outputDir))
IModelJsFs.mkdirSync(this.knownTestLocations.outputDir);
const outputDir = path.join(this.knownTestLocations.outputDir, subDirName);
if (!IModelJsFs.existsSync(outputDir))
IModelJsFs.mkdirSync(outputDir);
const outputFile = path.join(outputDir, fileName);
if (IModelJsFs.existsSync(outputFile))
IModelJsFs.unlinkSync(outputFile);
return outputFile;
}
/** Resolve an asset file path from the asset name by looking in the known assets directory */
static resolveAssetFile(assetName) {
const assetFile = path.join(this.knownTestLocations.assetsDir, assetName);
assert.isTrue(IModelJsFs.existsSync(assetFile));
return assetFile;
}
static resolveFontFile(fontName) {
const subDirs = ["Karla", "DejaVu", "Sitka"];
const fontSubDirectory = subDirs.find((x) => fontName.startsWith(x));
fontName = fontSubDirectory ? path.join(fontSubDirectory, fontName) : fontName;
const assetName = path.join("Fonts", fontName);
return this.resolveAssetFile(assetName);
}
/** Orchestrates the steps necessary to create a new snapshot iModel from a seed file. */
static createSnapshotFromSeed(testFileName, seedFileName) {
const seedDb = SnapshotDb.openFile(seedFileName);
const testDb = SnapshotDb.createFrom(seedDb, testFileName);
seedDb.close();
testDb.channels.addAllowedChannel(ChannelControl.sharedChannelName);
return testDb;
}
static getUniqueModelCode(testDb, newModelCodeBase) {
let newModelCode = newModelCodeBase;
let iter = 0;
while (true) {
const modelCode = InformationPartitionElement.createCode(testDb, IModel.rootSubjectId, newModelCode);
if (testDb.elements.queryElementIdByCode(modelCode) === undefined)
return modelCode;
newModelCode = newModelCodeBase + iter;
++iter;
}
}
static generateChangeSetId() {
let result = "";
for (let i = 0; i < 20; ++i) {
result += Math.floor(Math.random() * 256).toString(16).padStart(2, "0");
}
return { id: result };
}
/** Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel. */
static createAndInsertPhysicalPartition(testDb, newModelCode, parentId) {
const model = parentId ? testDb.elements.getElement(parentId).model : IModel.repositoryModelId;
const parent = new SubjectOwnsPartitionElements(parentId || IModel.rootSubjectId);
const modeledElementProps = {
classFullName: PhysicalPartition.classFullName,
parent,
model,
code: newModelCode,
};
const modeledElement = testDb.elements.createElement(modeledElementProps);
return testDb.elements.insertElement(modeledElement.toJSON());
}
/** Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel. */
static async createAndInsertPhysicalPartitionAsync(testDb, newModelCode, parentId) {
const model = parentId ? testDb.elements.getElement(parentId).model : IModel.repositoryModelId;
const parent = new SubjectOwnsPartitionElements(parentId || IModel.rootSubjectId);
const modeledElementProps = {
classFullName: PhysicalPartition.classFullName,
parent,
model,
code: newModelCode,
};
const modeledElement = testDb.elements.createElement(modeledElementProps);
await testDb.locks.acquireLocks({ shared: model });
return testDb.elements.insertElement(modeledElement.toJSON());
}
/** Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel. */
static createAndInsertPhysicalModel(testDb, modeledElementRef, privateModel = false) {
const newModel = testDb.models.createModel({ modeledElement: modeledElementRef, classFullName: PhysicalModel.classFullName, isPrivate: privateModel });
const newModelId = newModel.id = testDb.models.insertModel(newModel.toJSON());
assert.isTrue(Id64.isValidId64(newModelId));
assert.isTrue(Id64.isValidId64(newModel.id));
assert.deepEqual(newModelId, newModel.id);
return newModelId;
}
/** Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel. */
static async createAndInsertPhysicalModelAsync(testDb, modeledElementRef, privateModel = false) {
const newModel = testDb.models.createModel({ modeledElement: modeledElementRef, classFullName: PhysicalModel.classFullName, isPrivate: privateModel });
const newModelId = newModel.insert();
assert.isTrue(Id64.isValidId64(newModelId));
assert.isTrue(Id64.isValidId64(newModel.id));
assert.deepEqual(newModelId, newModel.id);
return newModelId;
}
/**
* Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel.
* @return [modeledElementId, modelId]
*/
static createAndInsertPhysicalPartitionAndModel(testImodel, newModelCode, privateModel = false, parent) {
const eid = IModelTestUtils.createAndInsertPhysicalPartition(testImodel, newModelCode, parent);
const modeledElementRef = new RelatedElement({ id: eid });
const mid = IModelTestUtils.createAndInsertPhysicalModel(testImodel, modeledElementRef, privateModel);
return [eid, mid];
}
/**
* Create and insert a PhysicalPartition element (in the repositoryModel) and an associated PhysicalModel.
* @return [modeledElementId, modelId]
*/
static async createAndInsertPhysicalPartitionAndModelAsync(testImodel, newModelCode, privateModel = false, parentId) {
const eid = await IModelTestUtils.createAndInsertPhysicalPartitionAsync(testImodel, newModelCode, parentId);
const modeledElementRef = new RelatedElement({ id: eid });
const mid = await IModelTestUtils.createAndInsertPhysicalModelAsync(testImodel, modeledElementRef, privateModel);
return [eid, mid];
}
/** Create and insert a Drawing Partition element (in the repositoryModel). */
static createAndInsertDrawingPartition(testDb, newModelCode, parentId) {
const model = parentId ? testDb.elements.getElement(parentId).model : IModel.repositoryModelId;
const parent = new SubjectOwnsPartitionElements(parentId || IModel.rootSubjectId);
const modeledElementProps = {
classFullName: Drawing.classFullName,
parent,
model,
code: newModelCode,
};
const modeledElement = testDb.elements.createElement(modeledElementProps);
return testDb.elements.insertElement(modeledElement.toJSON());
}
/** Create and insert a DrawingModel associated with Drawing Partition. */
static createAndInsertDrawingModel(testDb, modeledElementRef, privateModel = false) {
const newModel = testDb.models.createModel({ modeledElement: modeledElementRef, classFullName: DrawingModel.classFullName, isPrivate: privateModel });
const newModelId = newModel.insert();
assert.isTrue(Id64.isValidId64(newModelId));
assert.isTrue(Id64.isValidId64(newModel.id));
assert.deepEqual(newModelId, newModel.id);
return newModelId;
}
/**
* Create and insert a Drawing Partition element (in the repositoryModel) and an associated DrawingModel.
* @return [modeledElementId, modelId]
*/
static createAndInsertDrawingPartitionAndModel(testImodel, newModelCode, privateModel = false, parent) {
const eid = IModelTestUtils.createAndInsertDrawingPartition(testImodel, newModelCode, parent);
const modeledElementRef = new RelatedElement({ id: eid });
const mid = IModelTestUtils.createAndInsertDrawingModel(testImodel, modeledElementRef, privateModel);
return [eid, mid];
}
static getUniqueSpatialCategoryCode(scopeModel, newCodeBaseValue) {
let newCodeValue = newCodeBaseValue;
let iter = 0;
while (true) {
if (SpatialCategory.queryCategoryIdByName(scopeModel.iModel, scopeModel.id, newCodeValue) === undefined)
return SpatialCategory.createCode(scopeModel.iModel, scopeModel.id, newCodeValue);
newCodeValue = newCodeBaseValue + iter;
++iter;
}
}
// Create a PhysicalObject. (Does not insert it.)
static createPhysicalObject(testImodel, modelId, categoryId, elemCode) {
const elementProps = {
classFullName: "Generic:PhysicalObject",
model: modelId,
category: categoryId,
code: elemCode ? elemCode : Code.createEmpty(),
};
return testImodel.elements.createElement(elementProps);
}
static registerTestBimSchema() {
if (undefined === Schemas.getRegisteredSchema(TestBim.schemaName)) {
Schemas.registerSchema(TestBim);
ClassRegistry.register(TestPhysicalObject, TestBim);
ClassRegistry.register(TestElementDrivesElement, TestBim);
}
}
static executeQuery(db, ecsql, bindings) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return db.withPreparedStatement(ecsql, (stmt) => {
if (bindings)
stmt.bindValues(bindings);
const rows = [];
while (DbResult.BE_SQLITE_ROW === stmt.step()) {
rows.push(stmt.getRow());
if (rows.length > IModelDb.maxLimit)
throw new IModelError(IModelStatus.BadRequest, "Max LIMIT exceeded in SELECT statement");
}
return rows;
});
}
static createJobSubjectElement(iModel, name) {
const subj = Subject.create(iModel, iModel.elements.getRootSubject().id, name);
subj.setJsonProperty("Subject", { Job: name }); // eslint-disable-line @typescript-eslint/naming-convention
return subj;
}
/** Flushes the Txns in the TxnTable - this allows importing of schemas */
static flushTxns(iModelDb) {
iModelDb[_nativeDb].deleteAllTxns();
return true;
}
static querySubjectId(iModelDb, subjectCodeValue) {
const subjectId = iModelDb.elements.queryElementIdByCode(Subject.createCode(iModelDb, IModel.rootSubjectId, subjectCodeValue));
assert.isTrue(Id64.isValidId64(subjectId));
return subjectId;
}
static queryDefinitionPartitionId(iModelDb, parentSubjectId, suffix) {
const partitionCode = DefinitionPartition.createCode(iModelDb, parentSubjectId, `Definition${suffix}`);
const partitionId = iModelDb.elements.queryElementIdByCode(partitionCode);
assert.isTrue(Id64.isValidId64(partitionId));
return partitionId;
}
static querySpatialCategoryId(iModelDb, modelId, suffix) {
const categoryCode = SpatialCategory.createCode(iModelDb, modelId, `SpatialCategory${suffix}`);
const categoryId = iModelDb.elements.queryElementIdByCode(categoryCode);
assert.isTrue(Id64.isValidId64(categoryId));
return categoryId;
}
static queryPhysicalPartitionId(iModelDb, parentSubjectId, suffix) {
const partitionCode = PhysicalPartition.createCode(iModelDb, parentSubjectId, `Physical${suffix}`);
const partitionId = iModelDb.elements.queryElementIdByCode(partitionCode);
assert.isTrue(Id64.isValidId64(partitionId));
return partitionId;
}
static queryPhysicalElementId(iModelDb, modelId, categoryId, suffix) {
const elementId = IModelTestUtils.queryByUserLabel(iModelDb, `PhysicalObject${suffix}`);
assert.isTrue(Id64.isValidId64(elementId));
const element = iModelDb.elements.getElement(elementId);
assert.equal(element.model, modelId);
assert.equal(element.category, categoryId);
return elementId;
}
static insertSpatialCategory(iModelDb, modelId, categoryName, color) {
const appearance = {
color: color.toJSON(),
transp: 0,
invisible: false,
};
return SpatialCategory.insert(iModelDb, modelId, categoryName, appearance);
}
static createBoxes(subCategoryIds) {
const length = 1.0;
const entryOrigin = Point3d.createZero();
const geometryStreamBuilder = new GeometryStreamBuilder();
geometryStreamBuilder.appendGeometry(Box.createDgnBox(entryOrigin, Vector3d.unitX(), Vector3d.unitY(), new Point3d(0, 0, length), length, length, length, length, true));
for (const subCategoryId of subCategoryIds) {
entryOrigin.addInPlace({ x: 1, y: 1, z: 1 });
geometryStreamBuilder.appendSubCategoryChange(subCategoryId);
geometryStreamBuilder.appendGeometry(Box.createDgnBox(entryOrigin, Vector3d.unitX(), Vector3d.unitY(), new Point3d(0, 0, length), length, length, length, length, true));
}
return geometryStreamBuilder.geometryStream;
}
static createBox(size, categoryId, subCategoryId, renderMaterialId, geometryPartId) {
const geometryStreamBuilder = new GeometryStreamBuilder();
if ((undefined !== categoryId) && (undefined !== subCategoryId)) {
geometryStreamBuilder.appendSubCategoryChange(subCategoryId);
if (undefined !== renderMaterialId) {
const geometryParams = new GeometryParams(categoryId, subCategoryId);
geometryParams.materialId = renderMaterialId;
geometryStreamBuilder.appendGeometryParamsChange(geometryParams);
}
}
geometryStreamBuilder.appendGeometry(Box.createDgnBox(Point3d.createZero(), Vector3d.unitX(), Vector3d.unitY(), new Point3d(0, 0, size.z), size.x, size.y, size.x, size.y, true));
if (undefined !== geometryPartId) {
geometryStreamBuilder.appendGeometryPart3d(geometryPartId);
}
return geometryStreamBuilder.geometryStream;
}
static createCylinder(radius) {
const pointA = Point3d.create(0, 0, 0);
const pointB = Point3d.create(0, 0, 2 * radius);
const cylinder = Cone.createBaseAndTarget(pointA, pointB, Vector3d.unitX(), Vector3d.unitY(), radius, radius, true);
const geometryStreamBuilder = new GeometryStreamBuilder();
geometryStreamBuilder.appendGeometry(cylinder);
return geometryStreamBuilder.geometryStream;
}
static createRectangle(size) {
const geometryStreamBuilder = new GeometryStreamBuilder();
geometryStreamBuilder.appendGeometry(LineString3d.createPoints([
new Point3d(0, 0),
new Point3d(size.x, 0),
new Point3d(size.x, size.y),
new Point3d(0, size.y),
new Point3d(0, 0),
]));
return geometryStreamBuilder.geometryStream;
}
static insertTextureElement(iModelDb, modelId, textureName) {
// This is an encoded png containing a 3x3 square with white in top left pixel, blue in middle pixel, and green in bottom right pixel. The rest of the square is red.
const pngData = [137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 3, 0, 0, 0, 3, 8, 2, 0, 0, 0, 217, 74, 34, 232, 0, 0, 0, 1, 115, 82, 71, 66, 0, 174, 206, 28, 233, 0, 0, 0, 4, 103, 65, 77, 65, 0, 0, 177, 143, 11, 252, 97, 5, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 14, 195, 0, 0, 14, 195, 1, 199, 111, 168, 100, 0, 0, 0, 24, 73, 68, 65, 84, 24, 87, 99, 248, 15, 4, 12, 12, 64, 4, 198, 64, 46, 132, 5, 162, 254, 51, 0, 0, 195, 90, 10, 246, 127, 175, 154, 145, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130];
const textureData = Buffer.from(pngData).toString("base64");
return Texture.insertTexture(iModelDb, modelId, textureName, ImageSourceFormat.Png, textureData, `Description for ${textureName}`);
}
static queryByUserLabel(iModelDb, userLabel) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return iModelDb.withPreparedStatement(`SELECT ECInstanceId FROM ${Element.classFullName} WHERE UserLabel=:userLabel`, (statement) => {
statement.bindString("userLabel", userLabel);
return DbResult.BE_SQLITE_ROW === statement.step() ? statement.getValue(0).getId() : Id64.invalid;
});
}
static queryByCodeValue(iModelDb, codeValue) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return iModelDb.withPreparedStatement(`SELECT ECInstanceId FROM ${Element.classFullName} WHERE CodeValue=:codeValue`, (statement) => {
statement.bindString("codeValue", codeValue);
return DbResult.BE_SQLITE_ROW === statement.step() ? statement.getValue(0).getId() : Id64.invalid;
});
}
static insertRepositoryLink(iModelDb, codeValue, url, format) {
const repositoryLinkProps = {
classFullName: RepositoryLink.classFullName,
model: IModel.repositoryModelId,
code: LinkElement.createCode(iModelDb, IModel.repositoryModelId, codeValue),
url,
format,
};
return iModelDb.elements.insertElement(repositoryLinkProps);
}
static insertExternalSource(iModelDb, repositoryId, userLabel) {
const externalSourceProps = {
classFullName: ExternalSource.classFullName,
model: IModel.repositoryModelId,
code: Code.createEmpty(),
userLabel,
repository: new ExternalSourceIsInRepository(repositoryId),
connectorName: "Connector",
connectorVersion: "0.0.1",
};
return iModelDb.elements.insertElement(externalSourceProps);
}
static dumpIModelInfo(iModelDb) {
const outputFileName = `${iModelDb.pathName}.info.txt`;
if (IModelJsFs.existsSync(outputFileName)) {
IModelJsFs.removeSync(outputFileName);
}
IModelJsFs.appendFileSync(outputFileName, `${iModelDb.pathName}\n`);
IModelJsFs.appendFileSync(outputFileName, "\n=== CodeSpecs ===\n");
// eslint-disable-next-line @typescript-eslint/no-deprecated
iModelDb.withPreparedStatement(`SELECT ECInstanceId,Name FROM BisCore:CodeSpec ORDER BY ECInstanceId`, (statement) => {
while (DbResult.BE_SQLITE_ROW === statement.step()) {
const codeSpecId = statement.getValue(0).getId();
const codeSpecName = statement.getValue(1).getString();
IModelJsFs.appendFileSync(outputFileName, `${codeSpecId}, ${codeSpecName}\n`);
}
});
IModelJsFs.appendFileSync(outputFileName, "\n=== Schemas ===\n");
// eslint-disable-next-line @typescript-eslint/no-deprecated
iModelDb.withPreparedStatement(`SELECT Name FROM ECDbMeta.ECSchemaDef ORDER BY ECInstanceId`, (statement) => {
while (DbResult.BE_SQLITE_ROW === statement.step()) {
const schemaName = statement.getValue(0).getString();
IModelJsFs.appendFileSync(outputFileName, `${schemaName}\n`);
}
});
IModelJsFs.appendFileSync(outputFileName, "\n=== Models ===\n");
// eslint-disable-next-line @typescript-eslint/no-deprecated
iModelDb.withPreparedStatement(`SELECT ECInstanceId FROM ${Model.classFullName} ORDER BY ECInstanceId`, (statement) => {
while (DbResult.BE_SQLITE_ROW === statement.step()) {
const modelId = statement.getValue(0).getId();
const model = iModelDb.models.getModel(modelId);
IModelJsFs.appendFileSync(outputFileName, `${modelId}, ${model.name}, ${model.parentModel}, ${model.classFullName}\n`);
}
});
IModelJsFs.appendFileSync(outputFileName, "\n=== ViewDefinitions ===\n");
// eslint-disable-next-line @typescript-eslint/no-deprecated
iModelDb.withPreparedStatement(`SELECT ECInstanceId FROM ${ViewDefinition.classFullName} ORDER BY ECInstanceId`, (statement) => {
while (DbResult.BE_SQLITE_ROW === statement.step()) {
const viewDefinitionId = statement.getValue(0).getId();
const viewDefinition = iModelDb.elements.getElement(viewDefinitionId);
IModelJsFs.appendFileSync(outputFileName, `${viewDefinitionId}, ${viewDefinition.code.value}, ${viewDefinition.classFullName}\n`);
}
});
IModelJsFs.appendFileSync(outputFileName, "\n=== Elements ===\n");
// eslint-disable-next-line @typescript-eslint/no-deprecated
iModelDb.withPreparedStatement(`SELECT COUNT(*) FROM ${Element.classFullName}`, (statement) => {
if (DbResult.BE_SQLITE_ROW === statement.step()) {
const count = statement.getValue(0).getInteger();
IModelJsFs.appendFileSync(outputFileName, `Count of ${Element.classFullName}=${count}\n`);
}
});
// eslint-disable-next-line @typescript-eslint/no-deprecated
iModelDb.withPreparedStatement(`SELECT COUNT(*) FROM ${PhysicalObject.classFullName}`, (statement) => {
if (DbResult.BE_SQLITE_ROW === statement.step()) {
const count = statement.getValue(0).getInteger();
IModelJsFs.appendFileSync(outputFileName, `Count of ${PhysicalObject.classFullName}=${count}\n`);
}
});
// eslint-disable-next-line @typescript-eslint/no-deprecated
iModelDb.withPreparedStatement(`SELECT COUNT(*) FROM ${GeometryPart.classFullName}`, (statement) => {
if (DbResult.BE_SQLITE_ROW === statement.step()) {
const count = statement.getValue(0).getInteger();
IModelJsFs.appendFileSync(outputFileName, `Count of ${GeometryPart.classFullName}=${count}\n`);
}
});
}
}
export class ExtensiveTestScenario {
static uniqueAspectGuid = Guid.createValue();
static federationGuid3 = Guid.createValue();
static async prepareDb(sourceDb) {
// Import desired schemas
const sourceSchemaFileName = path.join(KnownTestLocations.assetsDir, "ExtensiveTestScenario.ecschema.xml");
await sourceDb.importSchemas([FunctionalSchema.schemaFilePath, sourceSchemaFileName]);
FunctionalSchema.registerSchema();
}
static async populateDb(sourceDb) {
// make sure Arial is in the font table
const arialFontId = await sourceDb.fonts.acquireId({ name: "Arial", type: FontType.TrueType });
expect(arialFontId).not.to.be.undefined;
expect(arialFontId).greaterThan(0);
// Initialize project extents
const projectExtents = new Range3d(-1000, -1000, -1000, 1000, 1000, 1000);
sourceDb.updateProjectExtents(projectExtents);
// Insert CodeSpecs
const codeSpecId1 = sourceDb.codeSpecs.insert("SourceCodeSpec", CodeScopeSpec.Type.Model);
const codeSpecId2 = sourceDb.codeSpecs.insert("ExtraCodeSpec", CodeScopeSpec.Type.ParentElement);
const codeSpecId3 = sourceDb.codeSpecs.insert("InformationRecords", CodeScopeSpec.Type.Model);
assert.isTrue(Id64.isValidId64(codeSpecId1));
assert.isTrue(Id64.isValidId64(codeSpecId2));
assert.isTrue(Id64.isValidId64(codeSpecId3));
// Insert RepositoryModel structure
const subjectId = Subject.insert(sourceDb, IModel.rootSubjectId, "Subject", "Subject Description");
assert.isTrue(Id64.isValidId64(subjectId));
const sourceOnlySubjectId = Subject.insert(sourceDb, IModel.rootSubjectId, "Only in Source");
assert.isTrue(Id64.isValidId64(sourceOnlySubjectId));
const definitionModelId = DefinitionModel.insert(sourceDb, subjectId, "Definition");
assert.isTrue(Id64.isValidId64(definitionModelId));
const informationModelId = InformationRecordModel.insert(sourceDb, subjectId, "Information");
assert.isTrue(Id64.isValidId64(informationModelId));
const groupModelId = GroupModel.insert(sourceDb, subjectId, "Group");
assert.isTrue(Id64.isValidId64(groupModelId));
const physicalModelId = PhysicalModel.insert(sourceDb, subjectId, "Physical");
assert.isTrue(Id64.isValidId64(physicalModelId));
const spatialLocationModelId = SpatialLocationModel.insert(sourceDb, subjectId, "SpatialLocation", true);
assert.isTrue(Id64.isValidId64(spatialLocationModelId));
const functionalModelId = FunctionalModel.insert(sourceDb, subjectId, "Functional");
assert.isTrue(Id64.isValidId64(functionalModelId));
const documentListModelId = DocumentListModel.insert(sourceDb, subjectId, "Document");
assert.isTrue(Id64.isValidId64(documentListModelId));
const drawingId = Drawing.insert(sourceDb, documentListModelId, "Drawing");
assert.isTrue(Id64.isValidId64(drawingId));
// Insert DefinitionElements
const modelSelectorId = ModelSelector.insert(sourceDb, definitionModelId, "SpatialModels", [physicalModelId, spatialLocationModelId]);
assert.isTrue(Id64.isValidId64(modelSelectorId));
const spatialCategoryId = IModelTestUtils.insertSpatialCategory(sourceDb, definitionModelId, "SpatialCategory", ColorDef.green);
assert.isTrue(Id64.isValidId64(spatialCategoryId));
const sourcePhysicalCategoryId = IModelTestUtils.insertSpatialCategory(sourceDb, definitionModelId, "SourcePhysicalCategory", ColorDef.blue);
assert.isTrue(Id64.isValidId64(sourcePhysicalCategoryId));
const subCategoryId = SubCategory.insert(sourceDb, spatialCategoryId, "SubCategory", { color: ColorDef.blue.toJSON() });
assert.isTrue(Id64.isValidId64(subCategoryId));
const filteredSubCategoryId = SubCategory.insert(sourceDb, spatialCategoryId, "FilteredSubCategory", { color: ColorDef.green.toJSON() });
assert.isTrue(Id64.isValidId64(filteredSubCategoryId));
const drawingCategoryId = DrawingCategory.insert(sourceDb, definitionModelId, "DrawingCategory", new SubCategoryAppearance());
assert.isTrue(Id64.isValidId64(drawingCategoryId));
const spatialCategorySelectorId = CategorySelector.insert(sourceDb, definitionModelId, "SpatialCategories", [spatialCategoryId, sourcePhysicalCategoryId]);
assert.isTrue(Id64.isValidId64(spatialCategorySelectorId));
const drawingCategorySelectorId = CategorySelector.insert(sourceDb, definitionModelId, "DrawingCategories", [drawingCategoryId]);
assert.isTrue(Id64.isValidId64(drawingCategorySelectorId));
const auxCoordSystemProps = {
classFullName: AuxCoordSystem2d.classFullName,
model: definitionModelId,
code: AuxCoordSystem2d.createCode(sourceDb, definitionModelId, "AuxCoordSystem2d"),
};
const auxCoordSystemId = sourceDb.elements.insertElement(auxCoordSystemProps);
assert.isTrue(Id64.isValidId64(auxCoordSystemId));
const textureId = IModelTestUtils.insertTextureElement(sourceDb, definitionModelId, "Texture");
assert.isTrue(Id64.isValidId64(textureId));
const renderMaterialId = RenderMaterialElement.insert(sourceDb, definitionModelId, "RenderMaterial", { paletteName: "PaletteName" });
assert.isTrue(Id64.isValidId64(renderMaterialId));
const geometryPartProps = {
classFullName: GeometryPart.classFullName,
model: definitionModelId,
code: GeometryPart.createCode(sourceDb, definitionModelId, "GeometryPart"),
geom: IModelTestUtils.createBox(Point3d.create(3, 3, 3)),
};
const geometryPartId = sourceDb.elements.insertElement(geometryPartProps);
assert.isTrue(Id64.isValidId64(geometryPartId));
// Insert InformationRecords
const informationRecordProps1 = {
classFullName: "ExtensiveTestScenario:SourceInformationRecord",
model: informationModelId,
code: { spec: codeSpecId3, scope: informationModelId, value: "InformationRecord1" },
commonString: "Common1",
sourceString: "One",
};
const informationRecordId1 = sourceDb.elements.insertElement(informationRecordProps1);
assert.isTrue(Id64.isValidId64(informationRecordId1));
const informationRecordProps2 = {
classFullName: "ExtensiveTestScenario:SourceInformationRecord",
model: informationModelId,
code: { spec: codeSpecId3, scope: informationModelId, value: "InformationRecord2" },
commonString: "Common2",
sourceString: "Two",
};
const informationRecordId2 = sourceDb.elements.insertElement(informationRecordProps2);
assert.isTrue(Id64.isValidId64(informationRecordId2));
const informationRecordProps3 = {
classFullName: "ExtensiveTestScenario:SourceInformationRecord",
model: informationModelId,
code: { spec: codeSpecId3, scope: informationModelId, value: "InformationRecord3" },
commonString: "Common3",
sourceString: "Three",
};
const informationRecordId3 = sourceDb.elements.insertElement(informationRecordProps3);
assert.isTrue(Id64.isValidId64(informationRecordId3));
// Insert PhysicalObject1
const physicalObjectProps1 = {
classFullName: PhysicalObject.classFullName,
model: physicalModelId,
category: spatialCategoryId,
code: Code.createEmpty(),
userLabel: "PhysicalObject1",
geom: IModelTestUtils.createBox(Point3d.create(1, 1, 1), spatialCategoryId, subCategoryId, renderMaterialId, geometryPartId),
placement: {
origin: Point3d.create(1, 1, 1),
angles: YawPitchRollAngles.createDegrees(0, 0, 0),
},
};
const physicalObjectId1 = sourceDb.elements.insertElement(physicalObjectProps1);
assert.isTrue(Id64.isValidId64(physicalObjectId1));
// Insert PhysicalObject1 children
const childObjectProps1A = physicalObjectProps1;
childObjectProps1A.userLabel = "ChildObject1A";
childObjectProps1A.parent = new ElementOwnsChildElements(physicalObjectId1);
childObjectProps1A.placement.origin = Point3d.create(0, 1, 1);
const childObjectId1A = sourceDb.elements.insertElement(childObjectProps1A);
assert.isTrue(Id64.isValidId64(childObjectId1A));
const childObjectProps1B = childObjectProps1A;
childObjectProps1B.userLabel = "ChildObject1B";
childObjectProps1B.placement.origin = Point3d.create(1, 0, 1);
const childObjectId1B = sourceDb.elements.insertElement(childObjectProps1B);
assert.isTrue(Id64.isValidId64(childObjectId1B));
// Insert PhysicalObject2
const physicalObjectProps2 = {
classFullName: PhysicalObject.classFullName,
model: physicalModelId,
category: sourcePhysicalCategoryId,
code: Code.createEmpty(),
userLabel: "PhysicalObject2",
geom: IModelTestUtils.createBox(Point3d.create(2, 2, 2)),
placement: {
origin: Point3d.create(2, 2, 2),
angles: YawPitchRollAngles.createDegrees(0, 0, 0),
},
};
const physicalObjectId2 = sourceDb.elements.insertElement(physicalObjectProps2);
assert.isTrue(Id64.isValidId64(physicalObjectId2));
// Insert PhysicalObject3
const physicalObjectProps3 = {
classFullName: PhysicalObject.classFullName,
model: physicalModelId,
category: sourcePhysicalCategoryId,
code: Code.createEmpty(),
federationGuid: ExtensiveTestScenario.federationGuid3,
userLabel: "PhysicalObject3",
};
const physicalObjectId3 = sourceDb.elements.insertElement(physicalObjectProps3);
assert.isTrue(Id64.isValidId64(physicalObjectId3));
// Insert PhysicalObject4
const physicalObjectProps4 = {
classFullName: PhysicalObject.classFullName,
model: physicalModelId,
category: spatialCategoryId,
code: Code.createEmpty(),
userLabel: "PhysicalObject4",
geom: IModelTestUtils.createBoxes([subCategoryId, filteredSubCategoryId]),
placement: {
origin: Point3d.create(4, 4, 4),
angles: YawPitchRollAngles.createDegrees(0, 0, 0),
},
};
const physicalObjectId4 = sourceDb.elements.insertElement(physicalObjectProps4);
assert.isTrue(Id64.isValidId64(physicalObjectId4));
// Insert PhysicalElement1
const sourcePhysicalElementProps = {
classFullName: "ExtensiveTestScenario:SourcePhysicalElement",
model: physicalModelId,
category: sourcePhysicalCategoryId,
code: Code.createEmpty(),
userLabel: "PhysicalElement1",
geom: IModelTestUtils.createBox(Point3d.create(2, 2, 2)),
placement: {
origin: Point3d.create(4, 4, 4),
angles: YawPitchRollAngles.createDegrees(0, 0, 0),
},
sourceString: "S1",
sourceDouble: 1.1,
sourceNavigation: { id: sourcePhysicalCategoryId, relClassName: "ExtensiveTestScenario:SourcePhysicalElementUsesSourceDefinition" },
commonNavigation: { id: sourcePhysicalCategoryId },
commonString: "Common",
commonDouble: 7.3,
sourceBinary: new Uint8Array([1, 3, 5, 7]),
commonBinary: Base64EncodedString.fromUint8Array(new Uint8Array([2, 4, 6, 8])),
extraString: "Extra",
};
const sourcePhysicalElementId = sourceDb.elements.insertElement(sourcePhysicalElementProps);
assert.isTrue(Id64.isValidId64(sourcePhysicalElementId));
assert.doesNotThrow(() => sourceDb.elements.getElement(sourcePhysicalElementId));
// Insert ElementAspects
const aspectProps = {
classFullName: "ExtensiveTestScenario:SourceUniqueAspect",
element: new ElementOwnsUniqueAspect(physicalObjectId1),
commonDouble: 1.1,
commonString: "Unique",
commonLong: physicalObjectId1,
commonBinary: Base64EncodedString.fromUint8Array(new Uint8Array([2, 4, 6, 8])),
sourceDouble: 11.1,
sourceString: "UniqueAspect",
sourceLong: physicalObjectId1,
sourceGuid: ExtensiveTestScenario.uniqueAspectGuid,
extraString: "Extra",
};
sourceDb.elements.insertAspect(aspectProps);
const sourceUniqueAspect = sourceDb.elements.getAspects(physicalObjectId1, "ExtensiveTestScenario:SourceUniqueAspect")[0];
expect(sourceUniqueAspect).to.deep.subsetEqual(omit(aspectProps, ["commonBinary"]), { normalizeClassNameProps: true });
sourceDb.elements.insertAspect({
classFullName: "ExtensiveTestScenario:SourceMultiAspect",
element: new ElementOwnsMultiAspects(physicalObjectId1),
commonDouble: 2.2,
commonString: "Multi",
commonLong: physicalObjectId1,
sourceDouble: 22.2,
sourceString: "MultiAspect",
sourceLong: physicalObjectId1,
sourceGuid: Guid.createValue(),
extraString: "Extra",
});
sourceDb.elements.insertAspect({
classFullName: "ExtensiveTestScenario:SourceMultiAspect"