UNPKG

@itwin/core-backend

Version:
279 lines • 18 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { assert, expect } from "chai"; import { join } from "path"; import { CompressedId64Set, Guid, Id64, OpenMode } from "@itwin/core-bentley"; import { Camera, Code, ColorByName, ColorDef, IModel, IModelError, PlanProjectionSettings, SubCategoryAppearance, } from "@itwin/core-common"; import { Matrix3d, Range3d, StandardViewIndex, Transform, YawPitchRollAngles } from "@itwin/core-geometry"; import { CategorySelector, DisplayStyle3d, ModelSelector, SpatialCategory, SpatialViewDefinition, StandaloneDb, ViewStore, } from "../../core-backend"; import { IModelTestUtils } from "../IModelTestUtils"; import { KnownTestLocations } from "../KnownTestLocations"; function createNewModelAndCategory(rwIModel) { const modelId = IModelTestUtils.createAndInsertPhysicalPartitionAndModel(rwIModel, IModelTestUtils.getUniqueModelCode(rwIModel, "newPhysicalModel"))[1]; const modelId2 = IModelTestUtils.createAndInsertPhysicalPartitionAndModel(rwIModel, IModelTestUtils.getUniqueModelCode(rwIModel, "PhysicalModel2"), true)[1]; const dictionary = rwIModel.models.getModel(IModel.dictionaryId); const newCategoryCode = IModelTestUtils.getUniqueSpatialCategoryCode(dictionary, "TestSpatialCategory"); const category = SpatialCategory.create(rwIModel, IModel.dictionaryId, newCategoryCode.value); const spatialCategoryId = category.insert(); category.setDefaultAppearance(new SubCategoryAppearance({ color: 0xff0000 })); newCategoryCode.value = "spatial category 2"; SpatialCategory.create(rwIModel, IModel.dictionaryId, newCategoryCode.value).insert(); rwIModel.elements.insertElement(IModelTestUtils.createPhysicalObject(rwIModel, modelId2, spatialCategoryId).toJSON()); return { modelId, modelId2, spatialCategoryId }; } // cspell:disable let vs1; describe("ViewDefinition", () => { // to simulate elements with guids without having to add elements to the iModel class FakeGuids { _ids = new Map(); _guids = new Map(); add(id, guid) { this._ids.set(id, guid); this._guids.set(guid, id); return guid; } getFederationGuidFromId(id) { return this._ids.get(id) ?? this.add(id, Guid.createValue()); } getIdFromFederationGuid(guid) { return guid ? this._guids.get(guid) : undefined; } } let iModel; before(() => { iModel = StandaloneDb.createEmpty(IModelTestUtils.prepareOutputFile("ViewDefinition", "ViewDefinition.bim"), { rootSubject: { name: "ViewDefinition tests", description: "ViewDefinition tests" }, client: "ViewDefinition", globalOrigin: { x: 0, y: 0 }, projectExtents: { low: { x: -500, y: -500, z: -50 }, high: { x: 500, y: 500, z: 50 } }, guid: Guid.createValue(), }); const dbName = join(KnownTestLocations.outputDir, "viewDefTest.db"); ViewStore.ViewDb.createNewDb(dbName); vs1 = new ViewStore.ViewDb({ iModel, guidMap: new FakeGuids() }); vs1.openDb(dbName, OpenMode.ReadWrite); }); after(() => { iModel.abandonChanges(); iModel.close(); vs1.closeDb(true); }); it("SpatialViewDefinition", async () => { const { modelId, modelId2, spatialCategoryId } = createNewModelAndCategory(iModel); const displayStyleId = DisplayStyle3d.insert(iModel, IModel.dictionaryId, "default", { backgroundColor: ColorDef.fromString("rgb(255,0,0)") }); const modelSelectorId = ModelSelector.insert(iModel, IModel.dictionaryId, "default", [modelId, modelId2]); const categorySelectorId = CategorySelector.insert(iModel, IModel.dictionaryId, "default", [spatialCategoryId]); iModel.saveChanges("Basic setup"); const standardView = StandardViewIndex.Iso; const rotation = Matrix3d.createStandardWorldToView(standardView); const angles = YawPitchRollAngles.createFromMatrix3d(rotation); const rotationTransform = Transform.createOriginAndMatrix(undefined, rotation); const range = new Range3d(1, 1, 1, 8, 8, 8); const rotatedRange = rotationTransform.multiplyRange(range); const basicProps = { code: Code.createEmpty(), model: IModel.dictionaryId, classFullName: "BisCore:SpatialViewDefinition", cameraOn: false, origin: rotation.multiplyTransposeXYZ(rotatedRange.low.x, rotatedRange.low.y, rotatedRange.low.z), extents: rotatedRange.diagonal(), angles, camera: new Camera(), }; const ms1 = iModel.elements.getElement(modelSelectorId); const ms1Row = await vs1.addModelSelector({ name: ms1.code.value, selector: { ids: ms1.models } }); expect(ms1Row).equal("@1"); let ms1out = vs1.getModelSelectorSync({ id: ms1Row }); expect(ms1out.classFullName).equal("BisCore:ModelSelector"); expect(ms1out.models.length).equal(2); expect(ms1out.models[0]).equal(modelId); expect(ms1out.models[1]).equal(modelId2); ms1out.models.push("0x123"); await vs1.updateModelSelector({ id: ms1Row, selector: { ids: ms1out.models } }); ms1out = vs1.getModelSelectorSync({ id: ms1Row }); expect(ms1out.models.length).equal(3); expect(ms1out.models[2]).equal("0x123"); const cs1 = iModel.elements.getElement(categorySelectorId); const cs1Row = await vs1.addCategorySelector({ selector: { ids: cs1.categories } }); expect(cs1Row).equal("@1"); let cs1out = vs1.getCategorySelectorSync({ id: cs1Row }); expect(cs1out.classFullName).equal("BisCore:CategorySelector"); expect(cs1out.categories.length).equal(1); expect(cs1out.categories[0]).equal(spatialCategoryId); cs1out.categories.push("0x1234"); await vs1.updateCategorySelector({ id: cs1Row, selector: { ids: cs1out.categories } }); cs1out = vs1.getCategorySelectorSync({ id: cs1Row }); expect(cs1out.categories.length).equal(2); expect(cs1out.categories[1]).equal("0x1234"); const longElementList = CompressedId64Set.sortAndCompress(["0x2a", "0x2b", "0x2d", "0x2e", "0x43", "0x1a", "0x1d", "0x12", "0x22", "0x8", "0x21", "0x1b", "0x1c", "0x1e", "0x1f", "0x2c", "0x2f", "0x3a", "0x3b", "0x3d", "0x3e", "0x43", "0x4a", "0x4b", "0x4d", "0x4e", "0x5a", "0x5b", "0x5d", "0x5e", "0x6a", "0x6b", "0x6d", "0x6e", "0x7a", "0x7b", "0x7d", "0x7e", "0x8a", "0x8b", "0x8d", "0x8e", "0x9a", "0x9b", "0x9d", "0x9e", "0xaa", "0xab", "0xad", "0xae", "0xba", "0xbb", "0xbd", "0xbe", "0xf5ca", "0xcb", "0xcd", "0xce", "0xda", "0xdb", "0xdd", "0xde", "0xea", "0xeb", "0xed", "0xee", "0xfa", "0xfb", "0xfd", "0xfe", "0x10a", "0x10b", "0x10d", "0x10e", "0x11a", "0x11b", "0x11d", "0x11e", "0x12a", "0x12b", "0x12d", "0x12e", "0x13a", "0x13b", "0x13d", "0x13e", "0x14a", "0x14b", "0x14d", "0x14e", "0x15a", "0x15b", "0x15d", "0x15e", "0x16a", "0x16b", "0x16d"]); await expect(vs1.addCategorySelector({ selector: { query: { from: "BisCore:SubCategory" } } })).to.be.rejectedWith("must select from BisCore:Category"); const cs2 = (await vs1.addCategorySelector({ selector: { query: { from: "BisCore:Category" } } })); expect(cs2).equal("@2"); const cs3 = (await vs1.addCategorySelector({ selector: { query: { from: "BisCore:Category", adds: longElementList } } })); const cs4 = (await vs1.addCategorySelector({ selector: { query: { from: "BisCore:Category", removes: ["0x233", "0x21"], adds: longElementList } } })); const onlyUsedProps = { name: "only used spatial categories", selector: { query: { from: "BisCore.Category", where: "ECInstanceId IN (SELECT DISTINCT Category.Id FROM BisCore.GeometricElement3d)", }, }, }; await vs1.addCategorySelector(onlyUsedProps); let selected = vs1.getCategorySelectorSync({ id: cs2 }); expect(selected.categories.length).equal(2); selected = vs1.getCategorySelectorSync({ id: cs3 }); expect(selected.categories.length).equal(98); selected = vs1.getCategorySelectorSync({ id: cs4 }); expect(selected.categories.length).equal(97); selected = vs1.getCategorySelectorSync({ name: onlyUsedProps.name }); expect(selected.categories.length).equal(1); expect(selected.categories[0]).equal(spatialCategoryId); const ms3 = (await vs1.addModelSelector({ name: "model selector 2", selector: { query: { from: "Bis.GeometricModel3d" } } })); let selectedModels = vs1.getModelSelectorSync({ id: ms3 }); expect(selectedModels.models.length).equal(2); const ms4Props = { name: "spatial, non-private models", selector: { query: { from: "BisCore.GeometricModel3d", where: "IsPrivate=false AND IsTemplate=false AND (IsNotSpatiallyLocated IS NULL OR IsNotSpatiallyLocated=false)", }, }, }; await vs1.addModelSelector(ms4Props); selectedModels = vs1.getModelSelectorSync({ name: ms4Props.name }); expect(selectedModels.models.length).equal(1); expect(selectedModels.models[0]).equal(modelId); const ds1 = iModel.elements.getElement(displayStyleId); ds1.settings.setPlanProjectionSettings("0x1", PlanProjectionSettings.fromJSON({ elevation: 1 })); ds1.settings.setPlanProjectionSettings("0x2", PlanProjectionSettings.fromJSON({ elevation: 2 })); const styles = ds1.toJSON().jsonProperties.styles; styles.subCategoryOvr = [{ subCategory: spatialCategoryId, color: ColorByName.fuchsia, invisible: true, style: "0xaaa", weight: 10, transp: 0.5, }, ]; styles.excludedElements = CompressedId64Set.sortAndCompress(["0x8", "0x12", "0x22"]); styles.scheduleScript = [{ modelId: "0x21", realityModelUrl: "altavista.com", elementTimelines: [{ batchId: 64, elementIds: CompressedId64Set.sortAndCompress(["0x1a", "0x1d"]), }, { batchId: 65, elementIds: longElementList, }], }]; const ds1Row = await vs1.addDisplayStyle({ className: ds1.classFullName, settings: ds1.toJSON().jsonProperties.styles }); expect(ds1Row).equal("@1"); const ds1out = vs1.getDisplayStyleSync({ id: ds1Row }); expect(ds1out.classFullName).equal("BisCore:DisplayStyle3d"); expect(ds1out.jsonProperties?.styles).deep.equal(JSON.parse(JSON.stringify(styles))); ds1out.jsonProperties.styles.scheduleScript[0].elementTimelines[0].elementIds = CompressedId64Set.sortAndCompress(["0x11a", "0x11d", "0x11e", "0x12a"]); await vs1.updateDisplayStyle({ id: ds1Row, className: ds1.classFullName, settings: ds1out.jsonProperties.styles }); const ds1out2 = vs1.getDisplayStyleSync({ id: ds1Row }); expect(ds1out2.jsonProperties?.styles).deep.equal(ds1out.jsonProperties.styles); const tl1Row = await vs1.addTimeline({ name: "TestRenderTimeline", timeline: styles.scheduleScript, owner: "owner2" }); expect(tl1Row).equal("@1"); const tl1out = vs1.getTimelineSync({ id: tl1Row }); expect(tl1out.classFullName).equal("BisCore:RenderTimeline"); expect(tl1out.id).equal(tl1Row); expect(tl1out.code.value).equal("TestRenderTimeline"); expect(tl1out.script).equal(JSON.stringify(styles.scheduleScript)); const viewDefProps = { ...basicProps, modelSelectorId: ms1Row, categorySelectorId: cs1Row, displayStyleId: ds1Row, }; viewDefProps.code = { value: "TestViewDefinition", spec: "0x1", scope: "0x1" }; const v1 = await vs1.addView({ viewDefinition: viewDefProps, tags: ["big", "in progress", "done"] }); expect(v1).equal("@1"); let viewDefOut = vs1.getViewDefinitionSync({ viewId: v1 }); expect(viewDefOut.code.value).equal("TestViewDefinition"); expect(viewDefOut.classFullName).equal("BisCore:SpatialViewDefinition"); expect(viewDefOut.modelSelectorId).equal(ms1Row); expect(viewDefOut.categorySelectorId).equal(cs1Row); expect(viewDefOut.displayStyleId).equal(ds1Row); expect(viewDefOut.cameraOn).equal(false); expect(JSON.stringify(viewDefOut.origin)).equal(JSON.stringify(basicProps.origin)); expect(JSON.stringify(viewDefOut.extents)).equal(JSON.stringify(basicProps.extents)); expect(JSON.stringify(viewDefOut.angles)).equal(JSON.stringify(basicProps.angles)); expect(JSON.stringify(viewDefOut.camera)).equal(JSON.stringify(basicProps.camera)); viewDefOut.cameraOn = true; viewDefOut.origin = [1, 2, 3]; await vs1.updateViewDefinition({ viewId: v1, viewDefinition: viewDefOut }); viewDefOut = vs1.getViewDefinitionSync({ viewId: v1 }); expect(viewDefOut.cameraOn).equal(true); expect(JSON.stringify(viewDefOut.origin)).equal(JSON.stringify([1, 2, 3])); viewDefOut.displayStyleId = "@2"; await expect(vs1.updateViewDefinition({ viewId: v1, viewDefinition: viewDefOut })).to.be.rejectedWith("invalid Id for displayStyles"); // add a new display style and uodate the view to use it viewDefOut.displayStyleId = await vs1.addDisplayStyle({ className: ds1.classFullName, settings: ds1.toJSON().jsonProperties.styles }); await vs1.updateViewDefinition({ viewId: v1, viewDefinition: viewDefOut }); viewDefOut = vs1.getViewDefinitionSync({ viewId: v1 }); expect(viewDefOut.displayStyleId).equal("@2"); const vinfo = await vs1.getViewInfo({ viewId: v1 }); expect(vinfo?.displayStyleId).equal(viewDefOut.displayStyleId); viewDefOut.displayStyleId = "@1"; await vs1.updateViewDefinition({ viewId: v1, viewDefinition: viewDefOut }); // change it back for sharing test below viewDefProps.code.value = "TestViewDefinition2"; const v2 = await vs1.addView({ viewDefinition: viewDefProps, tags: ["big", "done"] }); await vs1.addTagsToView({ viewId: v2, tags: ["problems", "finished", "big"] }); let tags = vs1.getTagsForView(v2); expect(tags?.length).equal(4); expect(tags).includes("big"); expect(tags).includes("done"); await vs1.removeTagFromView({ viewId: v2, tag: "done" }); tags = vs1.getTagsForView(v2); expect(tags).not.includes("done"); expect(tags?.length).equal(3); // v1 and v2 share modelselector, categoryselector, and displaystyle so when v2 is deleted they should not be deleted await vs1.deleteView({ viewId: v2 }); expect(() => vs1.getViewDefinitionSync({ viewId: v2 })).throws("View not found"); expect(vs1.getDisplayStyleRow(1)).not.undefined; expect(vs1.getModelSelectorRow(1)).not.undefined; expect(vs1.getCategorySelectorRow(1)).not.undefined; // the categoryselector, and displaystyle are no longer shared, so they should be deleted when v1 is deleted await vs1.deleteView({ viewId: v1 }); expect(() => vs1.getViewDefinitionSync({ viewId: v1 })).throws("View not found"); expect(vs1.getDisplayStyleRow(1)).undefined; expect(vs1.getCategorySelectorRow(1)).undefined; expect(vs1.getModelSelectorRow(1)).not.undefined; // modelselector has a name so it should not be deleted // attempt to create a ViewDefinition element with invalid properties assert.throws(() => iModel.elements.createElement({ ...basicProps, modelSelectorId, categorySelectorId }), IModelError, "displayStyleId is invalid"); assert.throws(() => iModel.elements.createElement({ ...basicProps, categorySelectorId, displayStyleId }), IModelError, "modelSelectorId is invalid"); assert.throws(() => iModel.elements.createElement({ ...basicProps, modelSelectorId, displayStyleId }), IModelError, "categorySelectorId is invalid"); // attempt to insert a ViewDefinition with invalid properties assert.throws(() => iModel.elements.insertElement({ ...basicProps, modelSelectorId, categorySelectorId, displayStyleId: modelId }), "invalid displayStyle"); assert.throws(() => iModel.elements.insertElement({ ...basicProps, modelSelectorId: modelId, displayStyleId, categorySelectorId }), "invalid modelSelector"); assert.throws(() => iModel.elements.insertElement({ ...basicProps, modelSelectorId, categorySelectorId: modelId, displayStyleId }), "invalid categorySelector"); // Better way to create and insert const props = { ...basicProps, modelSelectorId, categorySelectorId, displayStyleId }; const viewDefinition = iModel.elements.createElement(props); const viewDefinitionId = iModel.elements.insertElement(viewDefinition.toJSON()); assert.isNotEmpty(viewDefinitionId); assert.isTrue(Id64.isValid(viewDefinitionId)); // Best way to create and insert SpatialViewDefinition.insertWithCamera(iModel, IModel.dictionaryId, "default", modelSelectorId, categorySelectorId, displayStyleId, iModel.projectExtents); }); }); //# sourceMappingURL=ViewDefinition.test.js.map