UNPKG

@itwin/core-frontend

Version:
870 lines 41.7 kB
"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