@itwin/presentation-backend
Version:
Backend of iTwin.js Presentation library
231 lines • 11.5 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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
;