@itwin/core-frontend
Version:
iTwin.js frontend components
417 lines • 17.9 kB
JavaScript
"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