UNPKG

@itwin/presentation-backend

Version:

Backend of iTwin.js Presentation library

231 lines • 11.5 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /* eslint-disable @typescript-eslint/no-deprecated */ /** @packageDocumentation * @module Core */ Object.defineProperty(exports, "__esModule", { value: true }); exports.SelectionScopesHelper = void 0; const core_bentley_1 = require("@itwin/core-bentley"); const core_common_1 = require("@itwin/core-common"); const presentation_common_1 = require("@itwin/presentation-common"); const Utils_js_1 = require("./Utils.js"); /** * Contains helper methods for computing selection scopes. Will get removed * once rules-driven scopes are implemented. * * @internal */ class SelectionScopesHelper { /* c8 ignore next */ constructor() { } static getSelectionScopes() { const createSelectionScope = (scopeId, label, description) => ({ id: scopeId, label, description, }); return [ createSelectionScope("element", "Element", "Select the picked element"), createSelectionScope("assembly", "Assembly", "Select parent of the picked element"), createSelectionScope("top-assembly", "Top Assembly", "Select the topmost parent of the picked element"), // WIP: temporarily comment-out "category" and "model" scopes since we can't hilite contents of them fast enough // createSelectionScope("category", "Category", "Select all elements in the picked element's category"), // createSelectionScope("model", "Model", "Select all elements in the picked element's model"), ]; } static getElementKey(iModel, elementId, ancestorLevel) { let currId = elementId; let parentId = iModel.elements.tryGetElementProps(currId)?.parent?.id; while (parentId && ancestorLevel !== 0) { currId = parentId; parentId = iModel.elements.tryGetElementProps(currId)?.parent?.id; --ancestorLevel; } return (0, Utils_js_1.getElementKey)(iModel, currId); } static async computeElementSelection(iModel, elementIds, ancestorLevel) { const parentKeys = new presentation_common_1.KeySet(); await forEachNonTransientId(elementIds, async (id) => { const key = this.getElementKey(iModel, id, ancestorLevel); key && parentKeys.add(key); }); return parentKeys; } static async computeCategorySelection(iModel, ids) { const categoryKeys = new presentation_common_1.KeySet(); await forEachNonTransientId(ids, async (id) => { const el = iModel.elements.tryGetElement(id); const category = el?.category ? iModel.elements.tryGetElementProps(el.category) : undefined; if (category) { categoryKeys.add({ className: category.classFullName, id: category.id }); } }); return categoryKeys; } static async computeModelSelection(iModel, ids) { const modelKeys = new presentation_common_1.KeySet(); await forEachNonTransientId(ids, async (id) => { const el = iModel.elements.tryGetElementProps(id); const model = el ? iModel.models.tryGetModelProps(el.model) : undefined; if (model) { modelKeys.add({ className: model.classFullName, id: model.id }); } }); return modelKeys; } static async getRelatedFunctionalElementKey(imodel, graphicalElementId) { const query = ` SELECT funcSchemaDef.Name || '.' || funcClassDef.Name funcElClassName, fe.ECInstanceId funcElId FROM bis.Element e LEFT JOIN func.PhysicalElementFulfillsFunction rel1 ON rel1.SourceECInstanceId = e.ECInstanceId LEFT JOIN func.DrawingGraphicRepresentsFunctionalElement rel2 ON rel2.SourceECInstanceId = e.ECInstanceId LEFT JOIN func.FunctionalElement fe ON fe.ECInstanceId IN (rel1.TargetECInstanceId, rel2.TargetECInstanceId) INNER JOIN meta.ECClassDef funcClassDef ON funcClassDef.ECInstanceId = fe.ECClassId INNER JOIN meta.ECSchemaDef funcSchemaDef ON funcSchemaDef.ECInstanceId = funcClassDef.Schema.Id WHERE e.ECInstanceId = ? `; const bindings = new core_common_1.QueryBinder(); bindings.bindId(1, graphicalElementId); for await (const row of imodel.createQueryReader(query, bindings)) { if (row.funcElClassName && row.funcElId) { return { className: row.funcElClassName.replace(".", ":"), id: row.funcElId }; } } return undefined; } static async findFirstRelatedFunctionalElementKey(imodel, graphicalElementId) { let currId = graphicalElementId; while (currId) { const relatedFunctionalKey = await this.getRelatedFunctionalElementKey(imodel, currId); if (relatedFunctionalKey) { return relatedFunctionalKey; } currId = imodel.elements.tryGetElementProps(currId)?.parent?.id; } return undefined; } static async elementClassDerivesFrom(imodel, elementId, baseClassFullName) { const query = ` SELECT 1 FROM bis.Element e INNER JOIN meta.ClassHasAllBaseClasses baseClassRels ON baseClassRels.SourceECInstanceId = e.ECClassId INNER JOIN meta.ECClassDef baseClass ON baseClass.ECInstanceId = baseClassRels.TargetECInstanceId INNER JOIN meta.ECSchemaDef baseSchema ON baseSchema.ECInstanceId = baseClass.Schema.Id WHERE e.ECInstanceId = ? AND (baseSchema.Name || ':' || baseClass.Name) = ? `; const bindings = new core_common_1.QueryBinder(); bindings.bindId(1, elementId); bindings.bindString(2, baseClassFullName); for await (const _ of imodel.createQueryReader(query, bindings)) { return true; } return false; } static async computeFunctionalElementSelection(iModel, ids) { const keys = new presentation_common_1.KeySet(); await forEachNonTransientId(ids, async (id) => { const is3d = await this.elementClassDerivesFrom(iModel, id, "BisCore.GeometricElement3d"); if (!is3d) { // if the input is not a 3d element, we try to find the first related functional element const firstFunctionalKey = await this.findFirstRelatedFunctionalElementKey(iModel, id); if (firstFunctionalKey) { keys.add(firstFunctionalKey); return; } } let keyToAdd; if (is3d) { // if we're computing scope for a 3d element, try to switch to its related functional element keyToAdd = await this.getRelatedFunctionalElementKey(iModel, id); } if (!keyToAdd) { keyToAdd = (0, Utils_js_1.getElementKey)(iModel, id); } keyToAdd && keys.add(keyToAdd); }); return keys; } static async computeFunctionalAssemblySelection(iModel, ids) { const keys = new presentation_common_1.KeySet(); await forEachNonTransientId(ids, async (id) => { let idToGetAssemblyFor = id; const is3d = await this.elementClassDerivesFrom(iModel, id, "BisCore.GeometricElement3d"); if (!is3d) { // if the input is not a 3d element, we try to find the first related functional element const firstFunctionalKey = await this.findFirstRelatedFunctionalElementKey(iModel, id); if (firstFunctionalKey) { idToGetAssemblyFor = firstFunctionalKey.id; } } // find the assembly of either the given element or the functional element const assemblyKey = this.getElementKey(iModel, idToGetAssemblyFor, 1); let keyToAdd = assemblyKey; if (is3d && keyToAdd) { // if we're computing scope for a 3d element, try to switch to its related functional element const relatedFunctionalKey = await this.getRelatedFunctionalElementKey(iModel, keyToAdd.id); if (relatedFunctionalKey) { keyToAdd = relatedFunctionalKey; } } keyToAdd && keys.add(keyToAdd); }); return keys; } static async computeFunctionalTopAssemblySelection(iModel, ids) { const keys = new presentation_common_1.KeySet(); await forEachNonTransientId(ids, async (id) => { let idToGetAssemblyFor = id; const is3d = await this.elementClassDerivesFrom(iModel, id, "BisCore.GeometricElement3d"); if (!is3d) { // if the input is not a 3d element, we try to find the first related functional element const firstFunctionalKey = await this.findFirstRelatedFunctionalElementKey(iModel, id); if (firstFunctionalKey) { idToGetAssemblyFor = firstFunctionalKey.id; } } // find the top assembly of either the given element or the functional element const topAssemblyKey = this.getElementKey(iModel, idToGetAssemblyFor, Number.MAX_SAFE_INTEGER); let keyToAdd = topAssemblyKey; if (is3d && keyToAdd) { // if we're computing scope for a 3d element, try to switch to its related functional element const relatedFunctionalKey = await this.getRelatedFunctionalElementKey(iModel, keyToAdd.id); if (relatedFunctionalKey) { keyToAdd = relatedFunctionalKey; } } keyToAdd && keys.add(keyToAdd); }); return keys; } static async computeSelection({ imodel, scope, elementIds }) { switch (scope.id) { case "element": return this.computeElementSelection(imodel, elementIds, scope.ancestorLevel ?? 0); case "assembly": return this.computeElementSelection(imodel, elementIds, 1); case "top-assembly": return this.computeElementSelection(imodel, elementIds, Number.MAX_SAFE_INTEGER); case "category": return this.computeCategorySelection(imodel, elementIds); case "model": return this.computeModelSelection(imodel, elementIds); case "functional": case "functional-element": return this.computeFunctionalElementSelection(imodel, elementIds); case "functional-assembly": return this.computeFunctionalAssemblySelection(imodel, elementIds); case "functional-top-assembly": return this.computeFunctionalTopAssemblySelection(imodel, elementIds); } throw new presentation_common_1.PresentationError(presentation_common_1.PresentationStatus.InvalidArgument, "scopeId"); } } exports.SelectionScopesHelper = SelectionScopesHelper; async function forEachNonTransientId(ids, callback) { await Promise.all(ids.filter((id) => !core_bentley_1.Id64.isTransient(id)).map(callback)); } //# sourceMappingURL=SelectionScopesHelper.js.map