@itwin/core-frontend
Version:
iTwin.js frontend components
870 lines • 41.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DisplayStyle3dState = exports.DisplayStyle2dState = exports.DisplayStyleState = exports.TerrainDisplayOverrides = void 0;
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Views
*/
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 ApproximateTerrainHeights_1 = require("./ApproximateTerrainHeights");
const BackgroundMapGeometry_1 = require("./BackgroundMapGeometry");
const ContextRealityModelState_1 = require("./ContextRealityModelState");
const EntityState_1 = require("./EntityState");
const IModelApp_1 = require("./IModelApp");
const PlanarClipMaskState_1 = require("./PlanarClipMaskState");
const internal_1 = require("./tile/internal");
const Symbols_1 = require("./common/internal/Symbols");
const ScriptUtils_1 = require("./internal/ScriptUtils");
/** @internal */
class TerrainDisplayOverrides {
wantSkirts;
wantNormals;
produceGeometry;
}
exports.TerrainDisplayOverrides = TerrainDisplayOverrides;
/** A DisplayStyle defines the parameters for 'styling' the contents of a [[ViewState]].
* @public
* @extensions
*/
class DisplayStyleState extends EntityState_1.ElementState {
static get className() { return "DisplayStyle"; }
_ellipsoidMapGeometry;
_attachedRealityModelPlanarClipMasks = new Map();
/** @internal */
_queryRenderTimelinePropsPromise;
_assigningScript = false;
/** Event raised just before the [[scheduleScriptReference]] property is changed.
* @internal as of 5.0, use [[onScheduleScriptChanged]].
*/
[Symbols_1._onScheduleScriptReferenceChanged] = new core_bentley_1.BeEvent();
_scriptReference;
/** Event raised when schedule script edits are made, providing changed element IDs and the editing scope.
* @beta
*/
onScheduleEditingChanged = new core_bentley_1.BeEvent();
/** Event raised when schedule script edits are committed (finalized).
* @beta
*/
onScheduleEditingCommitted = new core_bentley_1.BeEvent();
/** Event raised just before the [[scheduleScript]] property is changed. */
onScheduleScriptChanged = new core_bentley_1.BeEvent();
/** Event raised just after [[setOSMBuildingDisplay]] changes the enabled state of the OSM buildings. */
onOSMBuildingDisplayChanged = new core_bentley_1.BeEvent();
/** Construct a new DisplayStyleState from its JSON representation.
* @param props JSON representation of the display style.
* @param iModel IModelConnection containing the display style.
* @param source If the constructor is being invoked from [[EntityState.clone]], the display style that is being cloned.
*/
constructor(props, iModel, source) {
super(props, iModel);
const styles = this.jsonProperties.styles;
if (source)
this._scriptReference = source._scriptReference;
if (styles) {
// ###TODO Use DisplayStyleSettings.planarClipMasks
if (styles.planarClipOvr)
for (const planarClipOvr of styles.planarClipOvr)
if (core_bentley_1.Id64.isValid(planarClipOvr.modelId))
this._attachedRealityModelPlanarClipMasks.set(planarClipOvr.modelId, PlanarClipMaskState_1.PlanarClipMaskState.fromJSON(planarClipOvr));
}
}
/** Ensures all of the data required by the display style is loaded. This method is invoked for you by [[ViewState.load]], but if
* you obtain a display style by some other means you should `await` this method before using the display style.
*/
async load() {
// If we were cloned, we may already have a valid schedule state, and our display style Id may be invalid / different.
// Preserve it if still usable.
if (this._scriptReference) {
if (this.settings.renderTimeline === this._scriptReference.sourceId) {
// The script came from the same RenderTimeline element. Keep it.
return;
}
if (undefined === this.settings.renderTimeline) {
// The script came from a display style's JSON properties. Keep it if (1) this style is not persistent or (2) this style has the same Id
if (this.id === this._scriptReference.sourceId || !core_bentley_1.Id64.isValidId64(this.id))
return;
}
}
// The schedule script stored in JSON properties takes precedence over the RenderTimeline if both are defined.
if (this.settings.scheduleScriptProps)
this.loadScriptReferenceFromScript(this.settings.scheduleScriptProps);
else
await this.loadScriptReferenceFromTimeline(this.settings.renderTimeline);
}
loadScriptReferenceFromScript(scriptProps) {
let newState;
try {
const script = core_common_1.RenderSchedule.Script.fromJSON(scriptProps);
if (script)
newState = new core_common_1.RenderSchedule.ScriptReference(this.id, script);
}
catch {
// schedule state is undefined.
}
if (newState !== this._scriptReference) {
this[Symbols_1._onScheduleScriptReferenceChanged].raiseEvent(newState);
this.onScheduleScriptChanged.raiseEvent(newState?.script);
this._scriptReference = newState;
}
}
async loadScriptReferenceFromTimeline(timelineId) {
let newState;
if (timelineId && core_bentley_1.Id64.isValidId64(timelineId)) {
try {
// If a subsequent call to loadScriptReferenceFromTimeline is made while we're awaiting this one, we'll abort this one.
const promise = this._queryRenderTimelinePropsPromise = this.queryRenderTimelineProps(timelineId);
const timeline = await promise;
if (promise !== this._queryRenderTimelinePropsPromise)
return;
if (timeline) {
const scriptProps = JSON.parse(timeline.script);
const script = core_common_1.RenderSchedule.Script.fromJSON(scriptProps);
if (script)
newState = new core_common_1.RenderSchedule.ScriptReference(timelineId, script);
}
}
catch {
// schedule state is undefined.
}
}
this._queryRenderTimelinePropsPromise = undefined;
if (newState !== this._scriptReference) {
this[Symbols_1._onScheduleScriptReferenceChanged].raiseEvent(newState);
this.onScheduleScriptChanged.raiseEvent(newState?.script);
this._scriptReference = newState;
}
}
/** @internal */
async queryRenderTimelineProps(timelineId) {
try {
const omitScriptElementIds = !IModelApp_1.IModelApp.tileAdmin.enableFrontendScheduleScripts;
return await this.iModel.elements.loadProps(timelineId, { renderTimeline: { omitScriptElementIds } });
}
catch {
return undefined;
}
}
/** @internal */
get displayTerrain() {
return this.viewFlags.backgroundMap && this.settings.backgroundMap.applyTerrain;
}
/** @internal */
get globeMode() { return this.settings.backgroundMap.globeMode; }
/** Settings controlling how the base map is displayed within a view.
* The base map can be provided by any map imagery source or set to be a single color.
*/
get backgroundMapBase() {
return this.settings.mapImagery.backgroundBase;
}
set backgroundMapBase(base) {
this.settings.mapImagery.backgroundBase = base;
this._synchBackgroundMapImagery();
}
/** The settings controlling how a background map is displayed within a view.
* @see [[ViewFlags.backgroundMap]] for toggling display of the map on or off.
*/
get backgroundMapSettings() { return this.settings.backgroundMap; }
set backgroundMapSettings(settings) {
this.settings.backgroundMap = settings;
}
/** Modify a subset of the background map display settings.
* @param name props JSON representation of the properties to change. Any properties not present will retain their current values in `this.backgroundMapSettings`.
* @see [[ViewFlags.backgroundMap]] for toggling display of the map.
* @see [[changeBackgroundMapProvider]] to change the type of map imagery displayed.
*
* Example that changes only the elevation, leaving the provider and type unchanged:
* ``` ts
* style.changeBackgroundMapProps({ groundBias: 16.2 });
* ```
* @public
*/
changeBackgroundMapProps(props) {
const newSettings = this.backgroundMapSettings.clone(props);
this.backgroundMapSettings = newSettings;
}
/** Change aspects of the [BackgroundMapProvider]($common) from which background map imagery is obtained.
* Any properties not explicitly specified by `props` will retain their current values.
* @public
*/
changeBackgroundMapProvider(props) {
const base = this.settings.mapImagery.backgroundBase;
if (base instanceof core_common_1.ColorDef) {
this.settings.mapImagery.backgroundBase = core_common_1.BaseMapLayerSettings.fromProvider(core_common_1.BackgroundMapProvider.fromJSON(props));
}
else {
const provider = base.provider ? base.provider.clone(props) : core_common_1.BackgroundMapProvider.fromJSON(props);
this.settings.mapImagery.backgroundBase = base.cloneWithProvider(provider);
}
this._synchBackgroundMapImagery();
}
/** Call a function for each reality model attached to this display style.
* @see [DisplayStyleSettings.contextRealityModels]($common).
*/
forEachRealityModel(func) {
for (const model of this.settings.contextRealityModels.models) {
(0, core_bentley_1.assert)(model instanceof ContextRealityModelState_1.ContextRealityModelState);
func(model);
}
}
*getRealityModels() {
for (const model of this.settings.contextRealityModels.models) {
(0, core_bentley_1.assert)(model instanceof ContextRealityModelState_1.ContextRealityModelState);
yield model;
}
}
/** Iterate over the reality models attached to this display style. */
get realityModels() {
return this.getRealityModels();
}
/** @internal */
*getTileTreeRefs() {
for (const model of this.realityModels) {
if (!model.invisible) {
yield model.treeRef;
}
}
}
/** Performs logical comparison against another display style. Two display styles are logically equivalent if they have the same name, Id, and settings.
* @param other The display style to which to compare.
* @returns true if the specified display style is logically equivalent to this display style - i.e., both styles have the same values for all of their settings.
*/
equalState(other) {
if (this.name !== other.name || this.id !== other.id)
return false;
else
return JSON.stringify(this.settings) === JSON.stringify(other.settings);
}
/** The name of this DisplayStyle */
get name() { return this.code.value; }
/** Change the Id of the [RenderTimeline]($backend) element that hosts the [RenderSchedule.Script]($common) to be applied by this display style for
* animating the contents of the view, and update [[scheduleScript]] using the script associated with the [RenderTimeline]($backend) element.
* @see [DisplayStyleSettings.renderTimeline]($common).
*/
async changeRenderTimeline(timelineId) {
// Potentially trigger async loading of new schedule state.
this.settings.renderTimeline = timelineId;
// Await async loading if necessary.
// Note the `await` in loadScriptReferenceFromTimeline will resolve before this one [per the spec](https://262.ecma-international.org/6.0/#sec-triggerpromisereactions).
if (this._queryRenderTimelinePropsPromise)
await this._queryRenderTimelinePropsPromise;
}
/**
* Begins or updates a schedule script editing session for the current display style.
* During an editing session, changes to the schedule script are applied incrementally
* using temporary dynamic tiles, allowing for interactive preview of visual changes like color,
* transforms, visibility, and cutting planes — without requiring a full tile tree reload.
*
* Calling this method multiple times will update the current editing session with new script changes.
* When all edits are complete, you must invoke [[commitScheduleEditing]] to finalize the session and
* trigger a full tile tree refresh with the committed script.
*
* @note You cannot use schedule script editing while a @see [[GraphicalEditingScope]] is active.
*
* Example:
* ```ts
* [[include:ScheduleScript_editingMode]]
* ```
*
* @beta
*/
setScheduleEditing(newScript) {
const prevScript = this.scheduleScript;
const changes = [];
const globalDelta = (0, ScriptUtils_1.getScriptDelta)(prevScript, newScript);
for (const timeline of newScript.modelTimelines) {
const ids = new Set();
for (const et of timeline.elementTimelines) {
for (const id of et.elementIds) {
if (globalDelta.has(id))
ids.add(id);
}
}
if (ids.size > 0)
changes.push({ timeline, elements: ids });
}
this.scheduleScript = newScript;
this.onScheduleEditingChanged.raiseEvent(changes);
for (const modelTimeline of this.scheduleScript.modelTimelines) {
modelTimeline.isEditingCommitted = false;
}
}
/**
* Finalizes a script editing session previously started with [[setScheduleEditing]].
* This applies all pending script changes and triggers a full tile tree reload to reflect them in the viewport.
* After this call, the schedule script is considered committed and editing mode ends.
*
* @see [[setScheduleEditing]] to begin a schedule script editing session.
* @beta
*/
commitScheduleEditing() {
this.onScheduleEditingCommitted.raiseEvent();
if (!this.scheduleScript)
return;
for (const modelTimeline of this.scheduleScript.modelTimelines) {
modelTimeline.isEditingCommitted = true;
}
}
/** The [RenderSchedule.Script]($common) that animates the contents of the view, if any.
* @see [[changeRenderTimeline]] to change the script.
*/
get scheduleScript() {
return this._scriptReference?.script;
}
set scheduleScript(script) {
if (script === this.scheduleScript)
return;
try {
const scriptRef = script ? new core_common_1.RenderSchedule.ScriptReference(script) : undefined;
this[Symbols_1._onScheduleScriptReferenceChanged].raiseEvent(scriptRef);
this.onScheduleScriptChanged.raiseEvent(script);
this._scriptReference = scriptRef;
this._assigningScript = true;
this.settings.scheduleScriptProps = script?.toJSON();
if (!script)
this.loadScriptReferenceFromTimeline(this.settings.renderTimeline); // eslint-disable-line @typescript-eslint/no-floating-promises
}
finally {
this._assigningScript = false;
}
}
/** The [RenderSchedule.Script]($common) that animates the contents of the view, if any, along with the Id of the element that hosts the script.
* @note The host element may be a [RenderTimeline]($backend) or a [DisplayStyle]($backend).
* @internal
*/
get [Symbols_1._scheduleScriptReference]() {
return this._scriptReference;
}
/** Attach a [ContextRealityModel]($common) to this display style.
* @see [DisplayStyleSettings.contextRealityModels]($common).
* @see [ContextRealityModels.add]($common)
*/
attachRealityModel(props) {
const model = this.settings.contextRealityModels.add(props);
(0, core_bentley_1.assert)(model instanceof ContextRealityModelState_1.ContextRealityModelState);
return model;
}
/** Detach the first [ContextRealityModel]($common) that matches the specified name and url.
* @see [DisplayStyleSettings.contextRealityModels]($common)
* @see [ContextRealityModels.delete]($common)
*/
detachRealityModelByNameAndUrl(name, url) {
const model = this.settings.contextRealityModels.models.find((x) => x.matchesNameAndUrl(name, url));
return undefined !== model && this.settings.contextRealityModels.delete(model);
}
/** Get the [[ContextRealityModelState]] that displays the OpenStreetMap worldwide building layer, if enabled.
* @see [[setOSMBuildingDisplay]]
*/
getOSMBuildingRealityModel() {
if (!this.iModel.isGeoLocated || this.globeMode !== core_common_1.GlobeMode.Ellipsoid) // The OSM tile tree is ellipsoidal.
return undefined;
const url = (0, internal_1.getCesiumOSMBuildingsUrl)();
if (undefined === url)
return undefined;
return this.contextRealityModelStates.find((x) => x.url === url);
}
/** Set the display of the OpenStreetMap worldwide building layer in this display style by attaching or detaching the reality model displaying the buildings.
* The OSM buildings are displayed from a reality model aggregated and served from Cesium ion.<(https://cesium.com/content/cesium-osm-buildings/>
* The options [[OsmBuildingDisplayOptions]] control the display and appearance overrides.
*/
setOSMBuildingDisplay(options) {
if (!this.iModel.isGeoLocated || this.globeMode !== core_common_1.GlobeMode.Ellipsoid) // The OSM tile tree is ellipsoidal.
return false;
const url = (0, internal_1.getCesiumOSMBuildingsUrl)();
if (undefined === url)
return false;
let model = this.settings.contextRealityModels.models.find((x) => x.url === url);
if (options.onOff === false) {
const turnedOff = undefined !== model && this.settings.contextRealityModels.delete(model);
if (turnedOff)
this.onOSMBuildingDisplayChanged.raiseEvent(false);
return turnedOff;
}
if (!model) {
const name = IModelApp_1.IModelApp.localization.getLocalizedString("iModelJs:RealityModelNames.OSMBuildings");
model = this.attachRealityModel({ tilesetUrl: url, name });
this.onOSMBuildingDisplayChanged.raiseEvent(true);
}
if (options.appearanceOverrides)
model.appearanceOverrides = options.appearanceOverrides;
return true;
}
/** Return if a context reality model is attached.
* @see [[ContextRealityModelProps]].
*/
hasAttachedRealityModel(name, url) {
return undefined !== this.settings.contextRealityModels.models.find((x) => x.matchesNameAndUrl(name, url));
}
/** @internal */
getMapLayers(isOverlay) { return isOverlay ? this.settings.mapImagery.overlayLayers : this.settings.mapImagery.backgroundLayers; }
/** Attach a map layer to display style.
* @param Settings representing the map layer.
* @param mapLayerIndex the [[MapLayerIndex]] where the map layer should be attached.
* @public
*/
attachMapLayer(options) {
const layerSettings = options.settings.clone({});
if (undefined === layerSettings)
return;
const isOverlay = options.mapLayerIndex.isOverlay;
const insertIndex = options.mapLayerIndex.index;
const layers = this.getMapLayers(isOverlay);
if (insertIndex < 0 || insertIndex > (layers.length - 1)) {
this.getMapLayers(isOverlay).push(layerSettings);
}
else {
layers.splice(insertIndex, 0, layerSettings);
}
this._synchBackgroundMapImagery();
}
/**
* @param mapLayerIndex the [[MapLayerIndex]] where the map layer should be attached.
* @internal
*/
attachMapLayerProps(options) {
const settings = core_common_1.MapLayerSettings.fromJSON(options.props);
if (undefined === settings)
return;
this.attachMapLayer({ settings, mapLayerIndex: options.mapLayerIndex });
}
/** @internal */
hasAttachedMapLayer(name, source, isOverlay) {
return -1 !== this.findMapLayerIndexByNameAndSource(name, source, isOverlay);
}
/** @internal */
detachMapLayerByNameAndSource(name, source, isOverlay) {
const index = this.findMapLayerIndexByNameAndSource(name, source, isOverlay);
if (-1 !== index)
this.detachMapLayerByIndex({ index, isOverlay });
}
/** Detach map layer at index (-1 to remove all layers)
* @param mapLayerIndex the [[MapLayerIndex]] of the map layer to detach.
* @public
*/
detachMapLayerByIndex(mapLayerIndex) {
const layers = this.getMapLayers(mapLayerIndex.isOverlay);
const index = mapLayerIndex.index;
if (index < 0)
layers.length = 0;
else
layers.splice(index, 1);
this._synchBackgroundMapImagery();
}
/**
* Lookup a maplayer index by name and source.
* @param name Name of of the layer.
* @param source Unique string identifying the layer.
* @param isOverlay true if layer is overlay, otherwise layer is background. Defaults to false.
* @public
*
*/
findMapLayerIndexByNameAndSource(name, source, isOverlay) {
return this.getMapLayers(isOverlay).findIndex((layer) => layer.matchesNameAndSource(name, source));
}
/** Return the map layer settings for a map layer at the provided index.
* @param mapLayerIndex the [[MapLayerIndex]] of the map layer.
* @public
*/
mapLayerAtIndex(mapLayerIndex) {
const layers = this.getMapLayers(mapLayerIndex.isOverlay);
const index = mapLayerIndex.index;
return (index < 0 || index >= layers.length) ? undefined : layers[index];
}
/** Return map base transparency as a number between 0 and 1.
* @public
*/
get baseMapTransparency() {
return this.settings.mapImagery.baseTransparency;
}
/** Change the map base transparency as a number between 0 and 1.
* @public
*/
changeBaseMapTransparency(transparency) {
if (this.settings.mapImagery.backgroundBase instanceof core_common_1.ColorDef) {
this.settings.mapImagery.backgroundBase = this.settings.mapImagery.backgroundBase.withTransparency(transparency * 255);
}
else {
this.settings.mapImagery.backgroundBase = this.settings.mapImagery.backgroundBase.clone({ transparency });
}
this._synchBackgroundMapImagery();
}
/** Modify a subset of a map layer settings.
* @param props props JSON representation of the properties to change. Any properties not present will retain their current values.
* @param mapLayerIndex the [[MapLayerIndex]] where the map layer should be inserted.
*
* Example that changes only the visibility of the first overlay map layer.
* ``` ts
* style.changeMapLayerProps({ visible: false }, 0, false);
* ```
* @public
*/
changeMapLayerProps(props, mapLayerIndex) {
const index = mapLayerIndex.index;
const layers = this.getMapLayers(mapLayerIndex.isOverlay);
if (index < 0 || index >= layers.length)
return;
layers[index] = layers[index].clone(props);
this._synchBackgroundMapImagery();
}
/** Change the credentials for a map layer.
* @param mapLayerIndex the [[MapLayerIndex]] of the map layer to change the credentials of.
* @public
*/
changeMapLayerCredentials(mapLayerIndex, userName, password) {
const layers = this.getMapLayers(mapLayerIndex.isOverlay);
const index = mapLayerIndex.index;
if (index < 0 || index >= layers.length)
return;
const layer = layers[index];
if (layer instanceof core_common_1.ImageMapLayerSettings) {
layer.setCredentials(userName, password);
this._synchBackgroundMapImagery();
}
}
/** Modify a subset of a sub-layer settings.
* @param props props JSON representation of the properties to change. Any properties not present will retain their current values.
* @param subLayerId Id of the sub-layer that should be modified.
* @param mapLayerIndex the [[MapLayerIndex]] of the map layer that contains the sub-layer to be modified.
*
* @public
*/
changeMapSubLayerProps(props, subLayerId, mapLayerIndex) {
const mapLayerSettings = this.mapLayerAtIndex(mapLayerIndex);
if (undefined === mapLayerSettings)
return;
if (!(mapLayerSettings instanceof core_common_1.ImageMapLayerSettings)) {
(0, core_bentley_1.assert)(false);
return;
}
const subLayers = new Array();
for (const subLayer of mapLayerSettings.subLayers) {
subLayers.push((subLayerId === -1 || subLayer.id === subLayerId) ? subLayer.clone(props).toJSON() : subLayer.toJSON());
}
this.changeMapLayerProps({ subLayers }, mapLayerIndex);
}
/* @internal */
_synchBackgroundMapImagery() {
this.settings.synchMapImagery();
}
/** Move map layer to top.
* @param mapLayerIndex the [[MapLayerIndex]] of the map layer to move.
* @public
*
*/
moveMapLayerToTop(mapLayerIndex) {
const layers = this.getMapLayers(mapLayerIndex.isOverlay);
const index = mapLayerIndex.index;
if (index >= 0 && index < layers.length - 1) {
const layer = layers.splice(index, 1);
layers.push(layer[0]);
this._synchBackgroundMapImagery();
}
}
/** Move map layer to bottom.
* @param mapLayerIndex the [[MapLayerIndex]] of the map layer to move.
* @public
*/
moveMapLayerToBottom(mapLayerIndex) {
const layers = this.getMapLayers(mapLayerIndex.isOverlay);
const index = mapLayerIndex.index;
if (index > 0 && index < layers.length) {
const layer = layers.splice(index, 1);
layers.unshift(layer[0]);
this._synchBackgroundMapImagery();
}
}
/** Reorder map layers
* @param fromIndex index of map layer to move
* @param toIndex insert index. If equal to length of map array the map layer is moved to end of array.
* @param isOverlay true if map-layer is part of the overlay map, otherwise it is part of the background map.
* @public
*/
moveMapLayerToIndex(fromIndex, toIndex, isOverlay) {
const layers = this.getMapLayers(isOverlay);
if (fromIndex === toIndex)
return;
if (fromIndex < 0 || fromIndex >= layers.length || toIndex > layers.length)
return;
const layer = layers.splice(fromIndex, 1);
layers.splice(toIndex, 0, layer[0]); // note: if toIndex === settings.mapImagery.backgroundLayers.length item is appended
this._synchBackgroundMapImagery();
}
/** Flags controlling various aspects of the display style.
* @see [DisplayStyleSettings.viewFlags]($common)
*/
get viewFlags() { return this.settings.viewFlags; }
set viewFlags(flags) { this.settings.viewFlags = flags; }
/** The background color for this DisplayStyle */
get backgroundColor() { return this.settings.backgroundColor; }
set backgroundColor(val) { this.settings.backgroundColor = val; }
/** The color used to draw geometry in monochrome mode.
* @see [ViewFlags.monochrome]($common) for enabling monochrome mode.
*/
get monochromeColor() { return this.settings.monochromeColor; }
set monochromeColor(val) { this.settings.monochromeColor = val; }
_backgroundMapGeometry;
/** @internal */
anyMapLayersVisible(overlay) {
const layers = this.getMapLayers(overlay);
for (const mapLayer of layers)
if (mapLayer.visible)
return true;
return false;
}
/** @internal */
getIsBackgroundMapVisible() {
return undefined !== this.iModel.ecefLocation && (this.viewFlags.backgroundMap || this.anyMapLayersVisible(false));
}
/** @internal */
get backgroundMapElevationBias() {
if (this.backgroundMapSettings.applyTerrain) {
const terrainSettings = this.backgroundMapSettings.terrainSettings;
switch (terrainSettings.heightOriginMode) {
case core_common_1.TerrainHeightOriginMode.Ground:
return (undefined === this.iModel.projectCenterAltitude) ? undefined : terrainSettings.heightOrigin + terrainSettings.exaggeration * this.iModel.projectCenterAltitude;
case core_common_1.TerrainHeightOriginMode.Geodetic:
return terrainSettings.heightOrigin;
case core_common_1.TerrainHeightOriginMode.Geoid:
return (undefined === this.iModel.geodeticToSeaLevel) ? undefined : terrainSettings.heightOrigin + this.iModel.geodeticToSeaLevel;
}
}
else {
return this.backgroundMapSettings.groundBias;
}
}
/** @internal */
getBackgroundMapGeometry() {
if (undefined === this.iModel.ecefLocation)
return undefined;
const bimElevationBias = this.backgroundMapElevationBias;
if (undefined === bimElevationBias)
return undefined;
const globeMode = this.globeMode;
if (undefined === this._backgroundMapGeometry || this._backgroundMapGeometry.globeMode !== globeMode || this._backgroundMapGeometry.bimElevationBias !== bimElevationBias) {
const geometry = new BackgroundMapGeometry_1.BackgroundMapGeometry(bimElevationBias, globeMode, this.iModel);
this._backgroundMapGeometry = { bimElevationBias, geometry, globeMode };
}
return this._backgroundMapGeometry.geometry;
}
/** [[ContextRealityModelState]]s attached to this display style.
* @see [DisplayStyleSettings.contextRealityModels]($common).
*/
get contextRealityModelStates() {
return this.settings.contextRealityModels.models;
}
/** @internal */
getGlobalGeometryAndHeightRange() {
let geometry = this.getIsBackgroundMapVisible() ? this.getBackgroundMapGeometry() : undefined;
const terrainRange = ApproximateTerrainHeights_1.ApproximateTerrainHeights.instance.globalHeightRange;
let heightRange = this.displayTerrain ? terrainRange : core_geometry_1.Range1d.createXX(-1, 1);
if (this.globeMode === core_common_1.GlobeMode.Ellipsoid && this.contextRealityModelStates.find((model) => model.isGlobal)) {
if (!geometry) {
if (!this._ellipsoidMapGeometry)
this._ellipsoidMapGeometry = new BackgroundMapGeometry_1.BackgroundMapGeometry(0, core_common_1.GlobeMode.Ellipsoid, this.iModel);
geometry = this._ellipsoidMapGeometry;
}
heightRange = terrainRange;
}
return geometry ? { geometry, heightRange } : undefined;
}
/** Returns true if this is a 3d display style. */
is3d() { return this instanceof DisplayStyle3dState; }
/** Customize the way geometry belonging to a [[SubCategory]] is drawn by this display style.
* @param id The ID of the SubCategory whose appearance is to be overridden.
* @param ovr The overrides to apply to the [[SubCategoryAppearance]].
* @see [[dropSubCategoryOverride]]
*/
overrideSubCategory(id, ovr) { this.settings.overrideSubCategory(id, ovr); }
/** Remove any [[SubCategoryOverride]] applied to a [[SubCategoryAppearance]] by this style.
* @param id The ID of the [[SubCategory]].
* @see [[overrideSubCategory]]
*/
dropSubCategoryOverride(id) { this.settings.dropSubCategoryOverride(id); }
/** Returns true if an [[SubCategoryOverride]]s are defined by this style. */
get hasSubCategoryOverride() { return this.settings.hasSubCategoryOverride; }
/** Obtain the overrides applied to a [[SubCategoryAppearance]] by this style.
* @param id The ID of the [[SubCategory]].
* @returns The corresponding SubCategoryOverride, or undefined if the SubCategory's appearance is not overridden.
* @see [[overrideSubCategory]]
*/
getSubCategoryOverride(id) { return this.settings.getSubCategoryOverride(id); }
/** For each subcategory belonging to any of the specified categories, make it visible by turning off the "invisible" flag in its subcategory appearance.
* This requires that the categories and subcategories have been previously loaded by, e.g., a call to IModelConnection.querySubCategories.
* @returns true if the visibility of any subcategory was modified.
* @see Viewport.changeCategoryDisplay
* @see ViewCreator3dOptions.allSubCategoriesVisible
* @internal
*/
enableAllLoadedSubCategories(categoryIds) {
let anyChanged = false;
for (const categoryId of core_bentley_1.Id64.iterable(categoryIds)) {
const subCategoryIds = this.iModel.subcategories.getSubCategories(categoryId);
if (undefined !== subCategoryIds)
for (const subCategoryId of subCategoryIds)
if (this.setSubCategoryVisible(subCategoryId, true))
anyChanged = true;
}
return anyChanged;
}
/** Change the "invisible" flag for the given subcategory's appearance.
* This requires that the subcategory appearance has been previously loaded by, e.g., a call to IModelConnection.Categories.getSubCategoryInfo.
* @returns true if the visibility of any subcategory was modified.
* @see [[enableAllLoadedSubCategories]]
* @internal
*/
setSubCategoryVisible(subCategoryId, visible) {
const app = this.iModel.subcategories.getSubCategoryAppearance(subCategoryId);
if (undefined === app)
return false; // category not enabled or subcategory not found
const curOvr = this.getSubCategoryOverride(subCategoryId);
const isAlreadyVisible = undefined !== curOvr && undefined !== curOvr.invisible ? !curOvr.invisible : !app.invisible;
if (isAlreadyVisible === visible)
return false;
// Preserve existing overrides - just flip the visibility flag.
const json = undefined !== curOvr ? curOvr.toJSON() : {};
json.invisible = !visible;
this.overrideSubCategory(subCategoryId, core_common_1.SubCategoryOverride.fromJSON(json));
return true;
}
/** Returns true if solar shadow display is enabled by this display style. */
get wantShadows() {
return this.is3d() && this.viewFlags.shadows && false !== IModelApp_1.IModelApp.renderSystem.options.displaySolarShadows;
}
/** @internal */
registerSettingsEventListeners() {
this.settings.onScheduleScriptPropsChanged.addListener((scriptProps) => {
if (this._assigningScript)
return;
try {
this._assigningScript = true;
if (scriptProps)
this.loadScriptReferenceFromScript(scriptProps);
else
this.loadScriptReferenceFromTimeline(this.settings.renderTimeline); // eslint-disable-line @typescript-eslint/no-floating-promises
}
finally {
this._assigningScript = false;
}
});
this.settings.onRenderTimelineChanged.addListener((newTimeline) => {
// Cancel any in-progress loading of script from timeline.
this._queryRenderTimelinePropsPromise = undefined;
if (!this.settings.scheduleScriptProps)
this.loadScriptReferenceFromTimeline(newTimeline); // eslint-disable-line @typescript-eslint/no-floating-promises
});
this.settings.onPlanarClipMaskChanged.addListener((id, newSettings) => {
if (newSettings)
this._attachedRealityModelPlanarClipMasks.set(id, PlanarClipMaskState_1.PlanarClipMaskState.create(newSettings));
else
this._attachedRealityModelPlanarClipMasks.delete(id);
});
}
/** @internal */
createRealityModel(props) {
return new ContextRealityModelState_1.ContextRealityModelState(props, this.iModel, this);
}
/** @internal */
getPlanarClipMaskState(modelId) {
const model = this.iModel.models.getLoaded(modelId)?.asSpatialModel;
return (model && model.isRealityModel) ? this._attachedRealityModelPlanarClipMasks.get(modelId) : undefined;
}
}
exports.DisplayStyleState = DisplayStyleState;
/** A display style that can be applied to 2d views.
* @public
* @extensions
*/
class DisplayStyle2dState extends DisplayStyleState {
static get className() { return "DisplayStyle2d"; }
_settings;
get settings() { return this._settings; }
/** @internal */
overrideTerrainDisplay() { return undefined; }
constructor(props, iModel) {
super(props, iModel);
this._settings = new core_common_1.DisplayStyleSettings(this.jsonProperties, {
createContextRealityModel: (modelProps) => this.createRealityModel(modelProps),
deferContextRealityModels: true,
});
this._settings.contextRealityModels.populate();
this.registerSettingsEventListeners();
}
}
exports.DisplayStyle2dState = DisplayStyle2dState;
/** A [[DisplayStyleState]] that can be applied to spatial views.
* @public
* @extensions
*/
class DisplayStyle3dState extends DisplayStyleState {
static get className() { return "DisplayStyle3d"; }
_settings;
get settings() { return this._settings; }
constructor(props, iModel, source) {
super(props, iModel, source);
this._settings = new core_common_1.DisplayStyle3dSettings(this.jsonProperties, {
createContextRealityModel: (modelProps) => this.createRealityModel(modelProps),
deferContextRealityModels: true,
});
this._settings.contextRealityModels.populate();
this.registerSettingsEventListeners();
}
get environment() {
return this.settings.environment;
}
set environment(env) {
this.settings.environment = env;
}
get lights() { return this.settings.lights; }
set lights(lights) { this.settings.lights = lights; }
/** The direction of the solar light. */
get sunDirection() {
return this.settings.lights.solar.direction;
}
/** Set the solar light direction based on time value
* @param time The time in unix time milliseconds.
* @see [DisplayStyle3dSettings.sunTime]($common) to obtain the current sun time.
* @see [DisplayStyle3dSettings.setSunTime]($common).
*/
setSunTime(time) {
this.settings.setSunTime(time, this.iModel);
}
/** Settings controlling shadow display. */
get solarShadows() {
return this.settings.solarShadows;
}
set solarShadows(settings) {
this.settings.solarShadows = settings;
}
/** @internal */
registerSettingsEventListeners() {
super.registerSettingsEventListeners();
this.settings.onOverridesApplied.addListener((overrides) => {
if (overrides.thematic && this.settings.thematic.displayMode === core_common_1.ThematicDisplayMode.Height && undefined === overrides.thematic.range) {
// Use the project extents as reasonable default height range.
// NB: assumes using Z axis...
const extents = this.iModel.projectExtents;
const props = { ...overrides.thematic };
props.range = { low: extents.zLow, high: extents.zHigh };
this.settings.thematic = core_common_1.ThematicDisplay.fromJSON(props);
}
});
}
/** @internal */
overrideTerrainDisplay() {
if (undefined !== this.settings.thematic) {
const ovr = new TerrainDisplayOverrides();
if (this.viewFlags.thematicDisplay && core_common_1.ThematicGradientMode.IsoLines === this.settings.thematic.gradientSettings.mode)
ovr.wantSkirts = false;
if (this.viewFlags.thematicDisplay && (core_common_1.ThematicDisplayMode.Slope === this.settings.thematic.displayMode || core_common_1.ThematicDisplayMode.HillShade === this.settings.thematic.displayMode))
ovr.wantNormals = true;
return ovr;
}
return undefined;
}
}
exports.DisplayStyle3dState = DisplayStyle3dState;
//# sourceMappingURL=DisplayStyleState.js.map