UNPKG

@itwin/core-frontend

Version:
417 lines • 17.9 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. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Tiles */ Object.defineProperty(exports, "__esModule", { value: true }); exports.IModelTileTree = void 0; exports.iModelTileTreeParamsFromJSON = iModelTileTreeParamsFromJSON; const core_bentley_1 = require("@itwin/core-bentley"); const core_geometry_1 = require("@itwin/core-geometry"); const core_common_1 = require("@itwin/core-common"); const IModelApp_1 = require("../../IModelApp"); const GraphicalEditingScope_1 = require("../../GraphicalEditingScope"); const GraphicBranch_1 = require("../../render/GraphicBranch"); const internal_1 = require("../../tile/internal"); // Overrides nothing. const viewFlagOverrides = {}; function iModelTileTreeParamsFromJSON(props, iModel, modelId, options) { const location = core_geometry_1.Transform.fromJSON(props.location); const { formatVersion, id, rootTile, contentIdQualifier, maxInitialTilesToSkip, geometryGuid } = props; const tileScreenSize = props.tileScreenSize ?? 512; let contentRange; if (undefined !== props.contentRange) contentRange = core_geometry_1.Range3d.fromJSON(props.contentRange); let transformNodeRanges; if (props.transformNodeRanges) { transformNodeRanges = new Map(); for (const entry of props.transformNodeRanges) transformNodeRanges.set(entry.id, core_geometry_1.Range3d.fromJSON(entry)); } const priority = core_common_1.BatchType.Primary === options.batchType ? internal_1.TileLoadPriority.Primary : internal_1.TileLoadPriority.Classifier; return { formatVersion, id, rootTile, iModel, location, modelId, contentRange, geometryGuid, contentIdQualifier, maxInitialTilesToSkip, priority, options, tileScreenSize, transformNodeRanges, }; } function findElementChangesForModel(changes, modelId) { for (const change of changes) if (change.id === modelId) return change.elements; return undefined; } /** No graphical editing scope is currently active. */ class StaticState { type = "static"; [Symbol.dispose]; constructor(root) { this[Symbol.dispose] = GraphicalEditingScope_1.GraphicalEditingScope.onEnter.addOnce((scope) => { root.transition(new InteractiveState(scope, root)); }); } } /** A graphical editing scope is currently active, but no elements in the tile tree's model have been modified. */ class InteractiveState { type = "interactive"; [Symbol.dispose]; constructor(scope, root) { const removeEndingListener = scope.onExiting.addOnce((_) => { root.transition(new StaticState(root)); }); const removeGeomListener = scope.onGeometryChanges.addListener((changes, _scope) => { (0, core_bentley_1.assert)(scope === _scope); const elemChanges = findElementChangesForModel(changes, root.tree.modelId); if (elemChanges) root.transition(new DynamicState(root, elemChanges, scope)); }); this[Symbol.dispose] = () => { removeEndingListener(); removeGeomListener(); }; } } /** Elements in the tile tree's model have been modified during the current editing scope. */ class DynamicState { type = "dynamic"; rootTile; _dispose; [Symbol.dispose]() { this._dispose(); this.rootTile[Symbol.dispose](); } constructor(root, elemChanges, scope) { this.rootTile = internal_1.DynamicIModelTile.create(root, elemChanges); const removeEndingListener = scope.onExiting.addOnce((_) => { root.transition(new StaticState(root)); }); const removeGeomListener = scope.onGeometryChanges.addListener((changes, _scope) => { (0, core_bentley_1.assert)(scope === _scope); const elems = findElementChangesForModel(changes, root.tree.modelId); if (elems) this.rootTile.handleGeometryChanges(elems); }); this._dispose = () => { removeEndingListener(); removeGeomListener(); }; } } class ScheduleScriptDynamicState { type = "dynamic"; rootTile; _dispose; [Symbol.dispose]() { this._dispose(); this.rootTile[Symbol.dispose](); } constructor(root, elemChanges) { this.rootTile = internal_1.DynamicIModelTile.create(root, elemChanges); this._dispose = () => { }; } } /** The tile tree has been disposed. */ class DisposedState { type = "disposed"; [Symbol.dispose]() { } } const disposedState = new DisposedState(); /** Represents the root [[Tile]] of an [[IModelTileTree]]. The root tile has one or two direct child tiles which represent different branches of the tree: * - The static branch, containing tiles that represent the state of the model's geometry as of the beginning of the current [[GraphicalEditingScope]]. * - The dynamic branch, containing tiles representing the geometry of elements that have been modified during the current [[GraphicalEditingScope]]. * If no editing scope is currently active, the dynamic branch does not exist, and the static branch represents the current state of all elements in the model. */ class RootTile extends internal_1.Tile { staticBranch; _tileState; _staticTreeContentRange; get tileState() { return this._tileState; } constructor(params, tree) { const rootParams = { ...params, range: params.range.clone(), contentRange: params.contentRange?.clone(), isLeaf: false, contentId: "", }; super(rootParams, tree); this.staticBranch = new internal_1.IModelTile(params, tree); this._staticTreeContentRange = tree.contentRange?.clone(); if (!this._contentRange) this._contentRange = this.staticBranch.contentRange.clone(); // Determine initial state. const scope = tree.iModel.isBriefcaseConnection() ? tree.iModel.editingScope : undefined; if (undefined === scope) { this._tileState = new StaticState(this); } else { const changes = scope.getGeometryChangesForModel(tree.modelId); this._tileState = changes ? new DynamicState(this, changes, scope) : new InteractiveState(scope, this); } // Load the children immediately. this.setIsReady(); this.loadChildren(); } [Symbol.dispose]() { this.transition(disposedState); super[Symbol.dispose](); } _loadChildren(resolve, _reject) { const children = [this.staticBranch]; if (this._tileState.type === "dynamic") children.push(this._tileState.rootTile); resolve(children); } get channel() { throw new Error("Root iModel tile has no content"); } async requestContent(_isCanceled) { (0, core_bentley_1.assert)(false, "Root iModel tile has no content"); return undefined; } async readContent(_data, _system, _isCanceled) { throw new Error("Root iModel tile has no content"); } draw(args, tiles, numStaticTiles) { (0, core_bentley_1.assert)(numStaticTiles >= 0 && numStaticTiles <= tiles.length); // Draw the static tiles. for (let i = 0; i < numStaticTiles; i++) tiles[i].drawGraphics(args); if ("dynamic" !== this._tileState.type || numStaticTiles === tiles.length) { if ("dynamic" === this._tileState.type) args.addAppearanceProvider(this._tileState.rootTile.appearanceProvider); args.drawGraphics(); return; } // We need to hide any modified elements in the static tiles. Pull their graphics into a separate branch. if (!args.graphics.isEmpty) { const staticBranch = new GraphicBranch_1.GraphicBranch(); for (const staticGraphic of args.graphics.entries) staticBranch.add(staticGraphic); let appearanceProvider = this._tileState.rootTile.appearanceProvider; if (args.appearanceProvider) appearanceProvider = core_common_1.FeatureAppearanceProvider.chain(args.appearanceProvider, appearanceProvider); args.graphics.clear(); args.graphics.add(args.context.createGraphicBranch(staticBranch, core_geometry_1.Transform.createIdentity(), { appearanceProvider })); } // Draw the dynamic tiles. for (let i = numStaticTiles; i < tiles.length; i++) tiles[i].drawGraphics(args); args.drawGraphics(); } prune(olderThan) { this.staticBranch.pruneChildren(olderThan); if ("dynamic" === this._tileState.type) this._tileState.rootTile.pruneChildren(olderThan); } transition(newState) { (0, core_bentley_1.assert)(newState.type !== this._tileState.type); const resetRange = "dynamic" === this._tileState.type; (0, core_bentley_1.assert)(undefined !== this.children); if ("dynamic" === this._tileState.type) { (0, core_bentley_1.assert)(2 === this.children.length); this.children.pop(); } else if ("dynamic" === newState.type) { (0, core_bentley_1.assert)(1 === this.children.length); this.children.push(newState.rootTile); } this._tileState[Symbol.dispose](); this._tileState = newState; if (resetRange) this.resetRange(); } resetRange() { this.staticBranch.range.clone(this.range); this.staticBranch.contentRange.clone(this._contentRange); if (this._staticTreeContentRange && this.tree.contentRange) this._staticTreeContentRange.clone(this.tree.contentRange); } get tileScreenSize() { return this.staticBranch.iModelTree.tileScreenSize; } updateDynamicRange(tile) { this.resetRange(); if (this._staticTreeContentRange && this.tree.contentRange && !tile.contentRange.isNull) this.tree.contentRange.extendRange(tile.contentRange); if (!tile.range.isNull) this.range.extendRange(tile.range); (0, core_bentley_1.assert)(undefined !== this._contentRange); if (!tile.contentRange.isNull) this._contentRange.extendRange(tile.contentRange); } } /** A TileTree whose contents are derived from geometry stored in a Model in an IModelDb. * @internal exported strictly for display-test-app until we remove CommonJS support. */ class IModelTileTree extends internal_1.TileTree { decoder; _rootTile; _options; _transformNodeRanges; contentIdQualifier; geometryGuid; maxTilesToSkip; maxInitialTilesToSkip; contentIdProvider; stringifiedSectionClip; tileScreenSize; iModelTileTreeId; /** Strictly for debugging/testing - forces tile selection to halt at the specified depth. */ debugMaxDepth; /** A little hacky...we must not override selectTiles(), but draw() needs to distinguish between static and dynamic tiles. * So _selectTiles() puts the static tiles first in the Tile[] array, and records the number of static tiles selected, to be * used by draw(). */ _numStaticTilesSelected = 0; layerImageryTrees = []; _layerHandler; get layerHandler() { return this._layerHandler; } constructor(params, treeId) { super(params); this.iModelTileTreeId = treeId; this.contentIdQualifier = params.contentIdQualifier; this.geometryGuid = params.geometryGuid; this.tileScreenSize = params.tileScreenSize; this._layerHandler = new internal_1.LayerTileTreeHandler(this); if (core_common_1.BatchType.Primary === treeId.type) this.stringifiedSectionClip = treeId.sectionCut; this.maxInitialTilesToSkip = params.maxInitialTilesToSkip ?? 0; this.maxTilesToSkip = IModelApp_1.IModelApp.tileAdmin.maximumLevelsToSkip; this._options = params.options; this._transformNodeRanges = params.transformNodeRanges; this.contentIdProvider = core_common_1.ContentIdProvider.create(params.options.allowInstancing, IModelApp_1.IModelApp.tileAdmin, params.formatVersion); params.rootTile.contentId = this.contentIdProvider.rootContentId; this._rootTile = new RootTile((0, internal_1.iModelTileParamsFromJSON)(params.rootTile, undefined), this); this.decoder = (0, internal_1.acquireImdlDecoder)({ type: this.batchType, omitEdges: false === this.edgeOptions, timeline: this.timeline, iModel: this.iModel, batchModelId: this.modelId, is3d: this.is3d, containsTransformNodes: this.containsTransformNodes, noWorker: !IModelApp_1.IModelApp.tileAdmin.decodeImdlInWorker, }); } [Symbol.dispose]() { this.decoder.release(); super[Symbol.dispose](); } get maxDepth() { return 32; } get rootTile() { return this._rootTile; } /** Exposed chiefly for tests. */ get staticBranch() { return this._rootTile.staticBranch; } get is3d() { return this._options.is3d; } get isContentUnbounded() { return false; } get viewFlagOverrides() { return viewFlagOverrides; } get batchType() { return this._options.batchType; } get edgeOptions() { return this._options.edges; } get timeline() { return this._options.timeline; } get loadPriority() { // If the model has been modified, we want to prioritize keeping its graphics up to date. return this.tileState === "dynamic" ? internal_1.TileLoadPriority.Dynamic : super.loadPriority; } _selectTiles(args) { args.markUsed(this._rootTile); const tiles = []; this._rootTile.staticBranch.selectTiles(tiles, args, 0); this._numStaticTilesSelected = tiles.length; if (this._rootTile.tileState.type === "dynamic") this._rootTile.tileState.rootTile.selectTiles(tiles, args); return tiles; } draw(args) { const tiles = this.selectTiles(args); this._rootTile.draw(args, tiles, this._numStaticTilesSelected); if (args.shouldCollectClassifierGraphics) this._layerHandler.collectClassifierGraphics(args, tiles); } prune() { const olderThan = core_bentley_1.BeTimePoint.now().minus(this.expirationTime); this._rootTile.prune(olderThan); } /** Exposed strictly for tests. */ get tileState() { return this._rootTile.tileState.type; } /** Exposed strictly for tests. */ get hiddenElements() { const state = this._rootTile.tileState; return "dynamic" === state.type ? state.rootTile.hiddenElements : []; } /** Strictly for tests. */ get dynamicElements() { const state = this._rootTile.tileState; return "dynamic" === state.type ? state.rootTile.dynamicElements : []; } getTransformNodeRange(nodeId) { return this._transformNodeRanges?.get(nodeId); } get containsTransformNodes() { return undefined !== this._transformNodeRanges; } async onScheduleEditingChanged(changes) { const displayStyle = IModelApp_1.IModelApp.viewManager.selectedView?.displayStyle; const newScript = displayStyle?.scheduleScript; const scriptRef = newScript ? new core_common_1.RenderSchedule.ScriptReference(displayStyle.id, newScript) : undefined; const scriptInfo = IModelApp_1.IModelApp.tileAdmin.getScriptInfoForTreeId(this.modelId, scriptRef); if (scriptInfo?.timeline && this.timeline !== scriptInfo.timeline) { this.decoder = (0, internal_1.acquireImdlDecoder)({ type: this.batchType, omitEdges: this.edgeOptions === false, timeline: scriptInfo.timeline, iModel: this.iModel, batchModelId: this.modelId, is3d: this.is3d, containsTransformNodes: this.containsTransformNodes, noWorker: !IModelApp_1.IModelApp.tileAdmin.decodeImdlInWorker, }); this._options.timeline = scriptInfo.timeline; } const relevantChange = changes.find((c) => c.timeline.modelId === this.modelId); if (!relevantChange || relevantChange.elements.size === 0) return; const elementIds = Array.from(relevantChange.elements); const placements = await this.iModel.elements.getPlacements(elementIds, { type: this.is3d ? "3d" : "2d", }); if (placements.length === 0) return; const changedElements = placements.map((x) => { return { id: x.elementId, type: core_bentley_1.DbOpcode.Update, range: x.calculateRange(), }; }); if (changedElements.length === 0) return; if (this._rootTile.tileState.type === "dynamic") { this._rootTile.transition(new StaticState(this._rootTile)); } this._rootTile.transition(new ScheduleScriptDynamicState(this._rootTile, changedElements)); } onScheduleEditingCommitted() { if (this._rootTile.tileState.type !== "static") this._rootTile.transition(new StaticState(this._rootTile)); } } exports.IModelTileTree = IModelTileTree; //# sourceMappingURL=IModelTileTree.js.map