UNPKG

@itwin/core-backend

Version:
866 lines • 69.6 kB
/*--------------------------------------------------------------------------------------------- * 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"